So I’ve been working on a small piece of software to interrogate an RFID reader over its serial connection, implemented using a SiLabs USB Virtual Com Port. Naturally, I’ve been doing this on my Mac. Because this particular piece of kit doesn’t really have a protocol as such but rather implements something much more like a serial terminal, I chose to use Expect to interrogate it, which works reasonably well. I don’t want something production-grade because this is an evaluation kit, which will interact rather differently from the final version.
Anyway, I was having a large problem trying to get the bothersome thing to open. Here is what you would typically write in Expect (really Tcl, which is a fairly cool scripting language).
set port "/dev/tty.serial-0001"
spawn -open [open $port w+]
set baud 115200
stty ispeed $baud ospeed $baud \
cs8 -parenb -parodd -cstopb \
-raw -echo < $port
For those not very familiar with Expect, Expect is a very useful program that you can use for scripting terminal programs by sending patterns and expecting particular outputs. That’s Expect in a very small nutshell. Expect is scripted in the Tcl scripting language.
spawn is the Expect command to start a process (or in this case open a particular file descriptor [returned from the open function]) that it will send data to and expect data from. stty is a command you use to set terminal parameters. Note that “raw” turns on raw (not “cooked”) access, while “-raw” turns it off. here I’m setting the speed to 115200 with 8 data bits, no parity and 1 stop bit. I’m also using “cooked” mode (meaning it will translate line endings and do various other things) and turning off echo.
My problem was two-fold, first thing was the call to open was blocking (“hanging”, if you prefer); second was that the call to stty would also hang. Expect says that stty is not portable and that different platforms will need different invocations, I just had to figure out what that was.
Using dtruss see how screen opened the port (screen /dev/tty.serial-0001 115200) indicated that I needed to open the port non-blocking. The manual page for stty(1) also adds an option -f, which tells it to open the port non-blocking. So, the invocation that works is as follows, wrapped into a function:
proc open_device {port} {
#################################################################
# NOTE: The settings of stty are not portable between systems.
# In particular, Mac OS X should be run with '-f $port'
# instead of '< $port' because it opens it non-blocking.
#################################################################
global spawn_id; # important
puts stderr "Opening serial port"
spawn -noecho -open [open $port {RDWR NOCTTY NONBLOCK}]
puts stderr "Setting TTY parameters"
stty -f $port 115200 -raw -echo \
speed 115200 clocal cread cs8 -parenb -parodd -cstopb \
-crtscts -dsrflow -dtrflow
puts stderr "Terminal configured"
}
Note that the w+ in the open call has been replaced with a list of options, including the option not to use this as a controlling TTY (which is typically for serial comms) and the option not to block while opening (I guess the port blocks because it hasn’t been configured yet). The call to stty has also been changed to reflect the native command (In Expect, the stty command ends up using the system stty command).
Also note that I’ve turned off all flow control, as this particular device doesn’t use it; as a result of this I’ve had to use slow-sends to avoid overrunning the device (I think, it could just be that this is my first real Expect/Tcl script)