There are several methods for getting a list of all serial (COM) ports on a Windows machine. Here I want to focus on one that seems to work very well on the computers I tried, and incidentally it’s also the simplest to implement.
Digging into the registry
The list of serial ports on the machine appears under the HARDWARE\\DEVICEMAP\\SERIALCOMM key in the Windows registry. You can use the regedit tool to reach this key and make sure the serial ports defined on your machine are there (including the virtual ports created with com0com or another emulator).
Reading the registry in Python is very simple using the _winreg module:
import _winreg as winreg import itertools def enumerate_serial_ports(): """ Uses the Win32 registry to return an iterator of serial (COM) ports existing on this computer. """ path = 'HARDWARE\\DEVICEMAP\\SERIALCOMM' try: key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, path) except WindowsError: raise IterationError for i in itertools.count(): try: val = winreg.EnumValue(key, i) yield str(val) except EnvironmentError: break
There are other methods for listing the serial ports, but for the time being I’ll stick to this one.
As I’ve mentioned in the last post, Windows requires some port names to appear in a "full" form prepended by several special characters. AFAIK these are all the ports except COM[1..9]. Here’s a simple function for generating a "full name" suitable for passing to PySerial:
import re def full_port_name(portname): """ Given a port-name (of the form COM7, COM12, CNCA0, etc.) returns a full name suitable for opening with the Serial class. """ m = re.match('^COM(\d+)$', portname) if m and int(m.group(1)) < 10: return portname return '\\\\.\\' + portname
While the "simple" name returned by enumerate_serial_ports is readable and suitable for display to the user, only the full name can be passed to serial.Serial for opening the port.
A simple GUI for listing the ports
Finally, I’ve coded a simple PyQt based GUI for listing the available ports and allowing the user to try and open them (even if the port appears as available on a computer, it may be in use by another program – so opening it will fail).
Here’s the code. It assumes that the enumerate_serial_ports and full_port_name functions were placed in a module called serialutils somewhere on the Python path.
Also, note how the simple port names from enumerate_serial_ports are shown in the list widget, and when trying to actually open the port, the program converts them into full names "under the hood". This is a common practice in serial-port programming.
""" Lists the serial ports available on the (Windows) computer. Eli Bendersky (firstname.lastname@example.org) License: this code is in the public domain """ import sys from PyQt4.QtCore import * from PyQt4.QtGui import * import serial from serial.serialutil import SerialException from serialutils import full_port_name, enumerate_serial_ports class ListPortsDialog(QDialog): def __init__(self, parent=None): super(ListPortsDialog, self).__init__(parent) self.setWindowTitle('List of serial ports') self.ports_list = QListWidget() self.tryopen_button = QPushButton('Try to open') self.connect(self.tryopen_button, SIGNAL('clicked()'), self.on_tryopen) layout = QVBoxLayout() layout.addWidget(self.ports_list) layout.addWidget(self.tryopen_button) self.setLayout(layout) self.fill_ports_list() def on_tryopen(self): cur_item = self.ports_list.currentItem() if cur_item is not None: fullname = full_port_name(str(cur_item.text())) try: ser = serial.Serial(fullname, 38400) ser.close() QMessageBox.information(self, 'Success', 'Opened %s successfully' % cur_item.text()) except SerialException, e: QMessageBox.critical(self, 'Failure', 'Failed to open %s:\n%s' % ( cur_item.text(), e)) def fill_ports_list(self): for portname in enumerate_serial_ports(): self.ports_list.addItem(portname) if __name__ == "__main__": app = QApplication(sys.argv) form = ListPortsDialog() form.show() app.exec_()
This runs with ActivePython 2.5.2, PyQt 4.4.3, and the latest PySerial.