Gathering a list of all available serial ports in a system is a trickier task than you might expect. While it is not difficult, it is rather obscure. On Windows, for example, you are probably used to having serial ports enumerated from
COM9, and on Linux from
/dev/ttyS0 and up. However, modern versions of these operating systems are a lot more flexible than this.
Windows traditionally assigns serial ports identifiers
COM9. These names are reserved in the file namespace. If you attempt to create a file with this name anywhere in the filesystem, you’ll receive an error. These reserved names allow you to get a handle directly to them by opening them with
CreateFile(). This is a legacy from previous versions of Windows. This mechanism also has a rather severe limitation of restricting you to only nine serial ports.
In Windows NT-based operating systems, this restriction no longer exists. You can specify a serial port greater than COM9 via the Win32 Device Namespace. In fact, you can open COM1 – COM9 this way too. The only difference is that you must prefix the port name with
\\.\. However, you may not realize that there is no longer anything special about the prefix
COM. Serial ports are often named this way by convention (including by popular USB-to-Serial drivers from FTDI and Prolific), but need not be. Check out the com0com virtual serial port adapter. This driver allows you to name a serial port whatever you want!
Note: If you do decide to try out com0com, be sure to follow the instructions to enable test mode on 64-bit Windows, or you will not be able to install the driver. Unfortunately, Microsoft’s model of code-signing requirements is not compatible with open-source.
So, given the flexibility of serial port naming on Windows, it should be evident that a dumb loop that checks
COM9 is not guaranteed to find all serial ports on a given system. Even if you have only a few serial ports, they may not be in this range. The FTDI driver, for example, will persist port names by device serial number. This means that if you install a second 8-port USB-to-Serial adapter, it might start numbering them from
COM9 and up!
You may have also seen that some poorly implemented programs cannot open serial ports above COM9. This is because they do not use the device namespace.
If you want to properly enumerate the serial ports on any Windows system, including USB-to-serial adapters and com0com virtual ports, the best mechanism to use is registry based. The
HKLM\HARDWARE\DEVICEMAP\SERIALCOMM key can be enumerated to list all available serial-port like devices in the system.
Each REG_SZ entry in this key has a name of a physical device mapped to a value for its device namespace name. A list of all values from this registry key will be a complete list of serial ports attached to the system.
Linux serial ports are typically mapped to
X is a small integer. However, there will typically be device nodes created for many more devices than actually exist in the system. Unlike on Windows, USB-to-Serial ports do not typically appear in devfs like internal serial ports. Instead, convention is for them to be created as
/dev/ttyUSBX devices, where
X is a small integer.
I’ve never seen a driver deviation from these two conventions, but just as on Windows, they need not follow them, and the user can potentially name devices differently. So, while globbing these file patterns is a quick-and-dirty solution, there needs to be a better way.
Until recently, I had no idea what that better way was. I suspected it might involve sysfs in some way, but I never found a way to differentiate ttys from serial ports. I eventually came across the golden solution for Linux in a Stack Overflow response with not nearly enough upvotes (only one at the time I found it!). It turns out a new feature quietly made its way into udev back in 2008 or so:
/dev/serial. This directory provides an elegant, centralized way to enumerate all serial ports in a Linux system, similar to the
SERIALCOMM registry key on Windows. There are a few points worth noting:
/dev/serial does not exist in kernels built sometime before 2008
/dev/serial will not exist unless at least one serial port is physically present in a system
To enumerate the ports, you can iterate over your choice of
/dev/serial/by-path. Each file is symlinked to the actual device node of the serial port.
A Python Package
Since these mechanisms are less than obvious, and not included together in any software that I am aware of, I created a small Python package serialenum that will list available serial ports and is cross-platform. It’s only a few lines of code, but it implements the methods described in this article for Windows and Linux. Usage is simple:
>>> import serialenum >>> serialenum.enumerate() ['/dev/ttyS0', '/dev/ttyUSB0', '/dev/ttyUSB1']
I’d like to extend it to support more platforms like OS X and FreeBSD, but I need input from others more familiar with these operating systems.