Home | | Python | | Share This Page |
A Python Serial interface terminal
— P. Lutus — Message Page —
Copyright © 2013, P. Lutus
(double-click any word to see its definition)
NOTE: This project has been rewritten (10.25.2013), it now uses WxWidgets for its user interface, and offers Windows and Linux self-contained executables, meaning no system setup required. See below in the "Licensing, Source" section for these downloads.
Most people who use serial interfaces on small computers must rely on USB-to-serial adaptors, since the old-style serial connectors have vanished and few find a use for serial interfaces any more — they are slow, relatively unsophisticated, and a bit tricky to set up.
But there are a few niche applications — anything involving GPS will likely need a serial communications channel at some point. And most real terminals (not virtual ones) communicate with a serial interface.
I have always needed a serial terminal for one reason or another, some having to do with owning a boat, or being a radio amateur, or wanting to communicate with a shipboard satellite dish. Years ago I simply kept a real serial terminal around — glass screen, heavy, unreliable. But some years ago I decided to virtualize it, intending that any handy computer could stand in for a serial terminal.
Since then I've written any number of serial terminal programs, most not very good. This Python program is by far the best — it has more features and is more robust in the face of unexpected circumstances.
Like many of my recent programs, this one uses the WxWidgets toolkit, and I designed its GUI with WxGlade. In its most recent version the program supports ANSI cursor control, command history, line editing and display colors.
This terminal program offers the usual kinds of serial setup options like baud rate and port, and it is reasonably fast — I have successfully transferred text files at the highest practical speed (115,200 baud) with no data loss. But because of the possible legacy applications for a terminal program, it also offers the slowest possible baud rate (50 baud). At 50 baud you can almost tell what's being sent by listening to the line noise.
Because this is a terminal program, the keyboard's output is captured and dedicated to the I/O stream, and is therefore unavailable for typical program uses. In practical terms, this means you can't use Ctrl+C to copy text from the display. To acquire a copy of the session text, use the program's logging feature.
During testing I would set up one computer as a serial port server and another as a client, running SerialTerminal. Here is the Bash script for the server side:
#!/bin/sh port="ttyUSB0" [ -n "$1" ] && port=$1 trap "{ /sbin/fuser -k /dev/$port; exit 0; }" EXIT baud=115200 term=ansi while true do echo -n "`date`: Hosting $baud baud $term serial process on $port ... " /sbin/agetty -hL $baud $port $term /sbin/fuser -k /dev/$port echo -e "\n`date`: Closed serial session." doneUPDATE: Because of changes in how "agetty" is managed, resulting from increased concerns about security, some recent Linux distributions won't run the above script any more. To fix the problem, make this change:
Original: /sbin/agetty -hL $baud $port $term Change: /usr/bin/setsid /sbin/agetty -hL $baud $port $termOnly make this change if the script fails.With the above script running on one machine and a null-modem cable between two USB-to-serial converters, I was able to figure out what my program's weaknesses were and correct them. In particular I wanted to see if I could transfer text data without errors at 115,200 baud — this repeated test helped the design.
Program Notes
Threads
First and most important, a program like this really should be run on multiple threads. I wrote a serial terminal program before this one in Ruby and it used threads. It seem Ruby's thread handling is superior to Python's. The problem was that Ruby doesn't have a serial library, so I was reduced to calling platform-specific utilities to set things up, and there were times when the serial setup would fail mysteriously.
Much of my time designing this program was spent trying to get Python threads to work. Eventually I realized the problem was insoluble and is caused by Python's crappy thread handling, so I implemented a solution using a timer instead. I set up a recurring timer with an interval of 100 milliseconds, which calls a data processing function that reads the keyboard and serial port. This seems to work fine, even at the highest data rates. And, unlike the threaded version, there are no more core dumps every thirty seconds.
User Interface
This program originally used GTK for its user interface, but I eventually realized that installing/supporting this library on Windows was next to impossible, so I recently switched to wxWidgets, which has more cross-platform support. WxWidgets also tries harder to match the appearance of a given operating system's interface. So this is definitely a step up.
Locking problem
About the time of Fedora 14, the default location for serial port lock files changed, and this complicated the setup for preventing multiple accesses to serial ports. The old arrangement for a given port was:
- Check to see if there is an empty file named "/var/lock/LCK..(port name)", for example for /dev/ttyUSB0 it would be "/var/lock/LCK..ttyUSB0". The presence of this file meant the serial port was busy and inaccessibe.
- If there was no such file, one would create the file, open the port, then remove the file once the port was no longer neded.
This scheme worked because the /var/lock directory belonged to the lock group, and one only needed to be a lock group member to write to it. For some reason, the Linux kernel developers (or one or more Linux distribution developers) decided this was way too easy, and they have changed the permissions so the /var/lock directory is owned by root and only user writable, so ordinary users can no longer write to it. The apparent long-term goal is to move lock files to /var/lock/lockdev, but I haven't done this for the simple reason that I have a number of serial interface programs that contend for this resource, not all under my control, and they all expect the flags to be located in /var/lock.
So I offer this temporary fix, which keeps things running until the developers think of a more coordinated plan. As root:
# chgrp lock /dev/lock # chmod g+w /dev/lockAlso, users of serial terminals and related programs need to be members of the lock group. As root:
# usermod -a -G lock (user-name)Legacy Issue
In this version of SerialTerminal I have changed the configuration file location. It was originally a simple file (.SerialTerminal) located in the user's home direectory. The new version create a directory with the same name as the original configuration file. The directory contains the configuration file as well as the program's log file. If you find that the program won't save its configuration (meaning it requires you to re-enter customizations each time you run the program), delete the old configuration file and run SerialTerminal again.
Licensing, Source
SerialTerminal is released under the GNU General Public License.
Here is a Python source archive including a WxGlade design source file.
Here is a self-contained Windows executable ZIP arcive — no system setup required, no need to download the above files. Just unZIP the archive and locate the executable anywhere convenient.
Here is a self-contained Linux executable — no system setup required, no need to download the above files. Just unZIP the archive and locate the executable anywhere convenient.
Linux users may prefer to download the source archive (first item in this list) insread of running the bundled executable, to save space (the Linux self-contained executable is rather large). To do this, download the source archive, put all its contents in any convenient directory, and execute SerialTerminal.py.
Revision History
- Version 1.6 10.25.2013. Rewrote the application to use the WxWidgets graphic library, added more ANSI tag management including cursor control and display colors, created self-contained executables for Linux and Windows with pyinstaller.
- Version 1.5 10.03.2011. Changed the configuration load/save routine to include window size.
- Version 1.4 01.15.2011. Corrected some small bugs.
- Version 1.3 12.15.2010. Made some improvements, added a program icon in XPM format within the program listing.
- Version 1.2 12.09.2010. Changed from libglade to GTKBuilder library, made a number of improvements.
- Version 1.1 12.08.2010. Changed how the program manages satellite mode.
- Version 1.0 12.07.2010. Initial Public Release.
Home | | Python | | Share This Page |