|Date:||30 March 2018|
The practical take-away from this post is that if you’re ever trying to debug serial communications with a device that — against all tradition — only transmits when the Data Terminal Ready line is clear (devices should normally do the opposite: transmit only when Data Terminal Ready is set), then never run stty on the serial port to double-check your settings.
Because stty turns Data Terminal Ready back on. Without even asking you!
So the device will never communicate with you, and you may very nearly conclude that your device is broken before you happen to remove the stty call and see the device finally work. So that’s the take-away. But the full story is a bit longer.
After a recent marathon two-day coding session left my wrists aching slightly, I decided to look for an alternative keyboard for tasks like browsing documentation and catching up on email. While I will always want the bandwidth of a full two-handed keyboard when composing text, I decided to try out my old Twiddler keyboard for browsing, since Gmail shortcuts should be easy enough to type with one hand.
The Twiddler keyboard uses chords to allow a single hand to produce all the keystrokes possible on a typical keyboard. You press a combination of buttons, instead of just one, to type a single letter or symbol. Normal keyboards issue a letter or symbol the moment you put a key down. But because a chording keyboard doesn’t know the complete chord until all your fingers are down, it waits until the moment you finish and lift a finger back up to type the character corresponding to that chord.
I disliked the Twiddler’s native keyboard mapping, so I came up with my own mapping that I named “TabSpace” and that’s still available from my old projects page:
It may surprise you to learn that even though I’m the author of a custom Twiddler keyboard map renowned for its efficiency, I never wound up using the Twiddler much. I bought it in grad school when I was briefly interested in wearable computing — back when that meant running Emacs in a glasses-mounted display powered by a computer in an over-the-shoulder satchel. I wound up interested in other forms of mobile computing after my workplace issued me a Palm V. I realized that I preferred a mobile experience that could be stashed away in a pocket to a device that would be constantly intruding in my line of sight.
So pulling the old Twiddler back out for browsing email seemed like a great chance to finally use the device. The first surprise was the reminder that its cables are quite a bit different from those of a modern keyboard:
It took a few tries to remember why the Twiddler needs two connectors. The Twiddler design constraints seem to have been:
I wasn’t able to connect the Twiddler to my laptop. While I found a small PS/2-to-USB converter in a drawer, that left the Twiddler without any way to communicate. To both give it power and also receive data, I had to move over to my Linux desktop — apparently the last computer in the entire house with a serial port. This means I’ll stand at its monitor to check my email instead of sitting in the easy chair. While I’m not as comfortable or productive when I try writing code at a standing desk, it should be fine for reading email.
After several increasingly obscure Google searches — it always amazes me how little of the late 1990s seems to survive in searchable form — I found two extant open source drivers for the original Twiddler.
The old general purpose mouse (gpm) daemon has not only survived the long years but features a sporadically active GitHub repository and includes the Twiddler among the serial mice it supports. However, it appears that gpm can only broadcast mouse events (motion and mouse button clicks) to the X Windows system — not keystrokes, which it can only offer to the bare Linux console. This of course makes perfect sense for a daemon that’s really designed as a mouse driver, not a keyboard driver.
A quick experiment suggested that gpm doesn’t know to select the correct baud rate for the Twiddler (though, confusingly enough, gpm does mention the correct baud rate in a source code comment), so I wasn’t able to get gpm working with the Twiddler even for typing at the Linux console.
But there is an alternative to gpm. Happily, it appears that a single lone copy still exists of the open-source Linux drivers I remember using originally, though they required a bit more searching: the old MIT Wearable Computing site’s Keyboards page offers a copy of Jeff Levine’s twid-linux.tar.gz driver.
Even after all of these years, I was able to coax it into compiling on modern Linux — but the result was only that my mouse began to jitter around on my screen without the Twiddler appearing to be in control. A quick strace revealed that the driver was at least reading data successfully from the serial port, but it was evidently not making sense to the driver.
Was my old device simply broken, and it was in vain that I had kept it carefully in its box all these years? Or was the driver not communicating correctly?
It was clearly time to step in with some simple Python code to bypass the intricacies of the old drivers and see if reliable communication could be established.
The worst kind of debugging is where you start in a broken state and have no idea whether one tweak or a dozen tweaks stand between you and a solution — and you have no way of knowing whether any particular tweak you make is moving you closer to the goal or farther away.
But I did have one glimmering source of hope as I stepped my Python code through many permutations of baud rate, stop bits, and other serial port and TTY settings: I was heartened by the fact that I could still see data flowing with strace whenever I powered back up the old legacy drivers. In fact I kept doing that, every half hour or so, just to convince myself the device wasn’t broken and silent. My old Twiddler — I had to keep reminding myself — could, somehow, still be induced to send bits.
The problem was that when I tried establishing the same communications settings in my own script as had been used in the original drivers — even being careful to drop DTR, exactly like the original driver does (the “Data Terminal Ready” serial line, which would normally be set if the computer were ready to receive) — I still saw no data. What was going on?
The answer is that my code, by this point in its development and debugging, looked roughly like:
# Set up terminal settings. f = open('/dev/ttyS0', 'r+b', buffering=0) ... # Print the settings to the screen to double-check. os.system('stty -a < /dev/ttyS0') # Try reading from the port. ...
It turns out? I was betrayed by stty -a! I thought it would merely read the state of the serial port without changing it, but instead it was undoing my careful work of setting DTR to a non-standard value and was turning it back on instead. It was one of those stunning Heisenberg moments when a tool you had thought was a clear lens for observation turns out to itself have been affecting the state of your experiment!
To get the driver working:
Only once all of these settings were in place did the serial line light up and the Twiddler started sending coherent data, in the format promised by the comments of the various drivers — five-byte packets each giving the state of each button and the x- and y-orientation of the device for driving a mouse position.
For the sake of digital preservation, I’ve checked in to GitHub not only the Python code of my own driver but the original C-language driver by Jeff Levine:
I’m happy to have written a new driver that no longer needs root permission to operation, that works with modern kernels, and involves no compilation step. In case anyone else with one of these old devices should stumble by the repository, I hope it works for you as well!