Accessing ports ¶. PySerial includes a small console based terminal program called serial.tools.miniterm. It can be started with python -m serial.tools.miniterm (use option -h to get a listing of all options). Listports does not seem to find all the available com ports, because I have a USB serial device that does not show up using the listports generator, yet realterm finds it and puts it in the list of selectable ports without any problem. It ends up being at COM20, and if I open COM20 with. I'm looking for a robust way to list the available serial (COM) ports on a Windows machine. There's this post about using WMI, but I would like something less.NET specific - I want to get the list.
PermalinkJoin GitHub today
GitHub is home to over 40 million developers working together to host and review code, manage projects, and build software together.
Sign up4 contributors
#! python |
# |
# Enumerate serial ports on Windows including a human readable description |
# and hardware information. |
# |
# This file is part of pySerial. https://github.com/pyserial/pyserial |
# (C) 2001-2016 Chris Liechti <cliechti@gmx.net> |
# |
# SPDX-License-Identifier: BSD-3-Clause |
from__future__import absolute_import |
# pylint: disable=invalid-name,too-few-public-methods |
import re |
import ctypes |
from ctypes.wintypes importBOOL |
from ctypes.wintypes importHWND |
from ctypes.wintypes importDWORD |
from ctypes.wintypes importWORD |
from ctypes.wintypes importLONG |
from ctypes.wintypes importULONG |
from ctypes.wintypes importHKEY |
from ctypes.wintypes importBYTE |
import serial |
from serial.win32 importULONG_PTR |
from serial.tools import list_ports_common |
defValidHandle(value, func, arguments): |
if value 0: |
raise ctypes.WinError() |
return value |
NULL=0 |
HDEVINFO= ctypes.c_void_p |
LPCTSTR= ctypes.c_wchar_p |
PCTSTR= ctypes.c_wchar_p |
PTSTR= ctypes.c_wchar_p |
LPDWORD=PDWORD= ctypes.POINTER(DWORD) |
#~ LPBYTE = PBYTE = ctypes.POINTER(BYTE) |
LPBYTE=PBYTE= ctypes.c_void_p #XXX avoids error about types |
ACCESS_MASK=DWORD |
REGSAM=ACCESS_MASK |
classGUID(ctypes.Structure): |
_fields_ = [ |
('Data1', DWORD), |
('Data2', WORD), |
('Data3', WORD), |
('Data4', BYTE*8), |
] |
def__str__(self): |
return'{{{:08x}-{:04x}-{:04x}-{}-{}}}'.format( |
self.Data1, |
self.Data2, |
self.Data3, |
''.join(['{:02x}'.format(d) for d inself.Data4[:2]]), |
''.join(['{:02x}'.format(d) for d inself.Data4[2:]]), |
) |
classSP_DEVINFO_DATA(ctypes.Structure): |
_fields_ = [ |
('cbSize', DWORD), |
('ClassGuid', GUID), |
('DevInst', DWORD), |
('Reserved', ULONG_PTR), |
] |
def__str__(self): |
return'ClassGuid:{} DevInst:{}'.format(self.ClassGuid, self.DevInst) |
PSP_DEVINFO_DATA= ctypes.POINTER(SP_DEVINFO_DATA) |
PSP_DEVICE_INTERFACE_DETAIL_DATA= ctypes.c_void_p |
setupapi = ctypes.windll.LoadLibrary('setupapi') |
SetupDiDestroyDeviceInfoList = setupapi.SetupDiDestroyDeviceInfoList |
SetupDiDestroyDeviceInfoList.argtypes = [HDEVINFO] |
SetupDiDestroyDeviceInfoList.restype =BOOL |
SetupDiClassGuidsFromName = setupapi.SetupDiClassGuidsFromNameW |
SetupDiClassGuidsFromName.argtypes = [PCTSTR, ctypes.POINTER(GUID), DWORD, PDWORD] |
SetupDiClassGuidsFromName.restype =BOOL |
SetupDiEnumDeviceInfo = setupapi.SetupDiEnumDeviceInfo |
SetupDiEnumDeviceInfo.argtypes = [HDEVINFO, DWORD, PSP_DEVINFO_DATA] |
SetupDiEnumDeviceInfo.restype =BOOL |
SetupDiGetClassDevs = setupapi.SetupDiGetClassDevsW |
SetupDiGetClassDevs.argtypes = [ctypes.POINTER(GUID), PCTSTR, HWND, DWORD] |
SetupDiGetClassDevs.restype =HDEVINFO |
SetupDiGetClassDevs.errcheck = ValidHandle |
SetupDiGetDeviceRegistryProperty = setupapi.SetupDiGetDeviceRegistryPropertyW |
SetupDiGetDeviceRegistryProperty.argtypes = [HDEVINFO, PSP_DEVINFO_DATA, DWORD, PDWORD, PBYTE, DWORD, PDWORD] |
SetupDiGetDeviceRegistryProperty.restype =BOOL |
SetupDiGetDeviceInstanceId = setupapi.SetupDiGetDeviceInstanceIdW |
SetupDiGetDeviceInstanceId.argtypes = [HDEVINFO, PSP_DEVINFO_DATA, PTSTR, DWORD, PDWORD] |
SetupDiGetDeviceInstanceId.restype =BOOL |
SetupDiOpenDevRegKey = setupapi.SetupDiOpenDevRegKey |
SetupDiOpenDevRegKey.argtypes = [HDEVINFO, PSP_DEVINFO_DATA, DWORD, DWORD, DWORD, REGSAM] |
SetupDiOpenDevRegKey.restype =HKEY |
advapi32 = ctypes.windll.LoadLibrary('Advapi32') |
RegCloseKey = advapi32.RegCloseKey |
RegCloseKey.argtypes = [HKEY] |
RegCloseKey.restype =LONG |
RegQueryValueEx = advapi32.RegQueryValueExW |
RegQueryValueEx.argtypes = [HKEY, LPCTSTR, LPDWORD, LPDWORD, LPBYTE, LPDWORD] |
RegQueryValueEx.restype =LONG |
cfgmgr32 = ctypes.windll.LoadLibrary('Cfgmgr32') |
CM_Get_Parent= cfgmgr32.CM_Get_Parent |
CM_Get_Parent.argtypes = [PDWORD, DWORD, ULONG] |
CM_Get_Parent.restype =LONG |
CM_Get_Device_IDW= cfgmgr32.CM_Get_Device_IDW |
CM_Get_Device_IDW.argtypes = [DWORD, PTSTR, ULONG, ULONG] |
CM_Get_Device_IDW.restype =LONG |
CM_MapCrToWin32Err= cfgmgr32.CM_MapCrToWin32Err |
CM_MapCrToWin32Err.argtypes = [DWORD, DWORD] |
CM_MapCrToWin32Err.restype =DWORD |
DIGCF_PRESENT=2 |
DIGCF_DEVICEINTERFACE=16 |
INVALID_HANDLE_VALUE=0 |
ERROR_INSUFFICIENT_BUFFER=122 |
ERROR_NOT_FOUND=1168 |
SPDRP_HARDWAREID=1 |
SPDRP_FRIENDLYNAME=12 |
SPDRP_LOCATION_PATHS=35 |
SPDRP_MFG=11 |
DICS_FLAG_GLOBAL=1 |
DIREG_DEV=0x00000001 |
KEY_READ=0x20019 |
MAX_USB_DEVICE_TREE_TRAVERSAL_DEPTH=5 |
defget_parent_serial_number(child_devinst, child_vid, child_pid, depth=0): |
'' Get the serial number of the parent of a device. |
Args: |
child_devinst: The device instance handle to get the parent serial number of. |
child_vid: The vendor ID of the child device. |
child_pid: The product ID of the child device. |
depth: The current iteration depth of the USB device tree. |
'' |
# If the traversal depth is beyond the max, abandon attempting to find the serial number. |
if depth >MAX_USB_DEVICE_TREE_TRAVERSAL_DEPTH: |
return'' |
# Get the parent device instance. |
devinst = DWORD() |
ret = CM_Get_Parent(ctypes.byref(devinst), child_devinst, 0) |
if ret: |
win_error = CM_MapCrToWin32Err(DWORD(ret), DWORD(0)) |
# If there is no parent available, the child was the root device. We cannot traverse |
# further. |
if win_error ERROR_NOT_FOUND: |
return'' |
raise ctypes.WinError(win_error) |
# Get the ID of the parent device and parse it for vendor ID, product ID, and serial number. |
parentHardwareID = ctypes.create_unicode_buffer(250) |
ret = CM_Get_Device_IDW( |
devinst, |
parentHardwareID, |
ctypes.sizeof(parentHardwareID) -1, |
0) |
if ret: |
raise ctypes.WinError(CM_MapCrToWin32Err(DWORD(ret), DWORD(0))) |
parentHardwareID_str = parentHardwareID.value |
m = re.search(r'VID_([0-9a-f]{4})(&PID_([0-9a-f]{4}))?(&MI_(d{2}))?((.*))?', |
parentHardwareID_str, |
re.I) |
vid =int(m.group(1), 16) |
pid =None |
serial_number =None |
if m.group(3): |
pid =int(m.group(3), 16) |
if m.group(7): |
serial_number = m.group(7) |
# Check that the USB serial number only contains alpha-numeric characters. It may be a windows |
# device ID (ephemeral ID). |
if serial_number andnot re.match(r'^w+$', serial_number): |
serial_number =None |
ifnot vid ornot pid: |
# If pid and vid are not available at this device level, continue to the parent. |
return get_parent_serial_number(devinst, child_vid, child_pid, depth +1) |
if pid != child_pid or vid != child_vid: |
# If the VID or PID has changed, we are no longer looking at the same physical device. The |
# serial number is unknown. |
return'' |
# In this case, the vid and pid of the parent device are identical to the child. However, if |
# there still isn't a serial number available, continue to the next parent. |
ifnot serial_number: |
return get_parent_serial_number(devinst, child_vid, child_pid, depth +1) |
# Finally, the VID and PID are identical to the child and a serial number is present, so return |
# it. |
return serial_number |
defiterate_comports(): |
''Return a generator that yields descriptions for serial ports'' |
PortsGUIDs = (GUID*8)() # so far only seen one used, so hope 8 are enough... |
ports_guids_size = DWORD() |
ifnot SetupDiClassGuidsFromName( |
'Ports', |
PortsGUIDs, |
ctypes.sizeof(PortsGUIDs), |
ctypes.byref(ports_guids_size)): |
raise ctypes.WinError() |
ModemsGUIDs = (GUID*8)() # so far only seen one used, so hope 8 are enough... |
modems_guids_size = DWORD() |
ifnot SetupDiClassGuidsFromName( |
'Modem', |
ModemsGUIDs, |
ctypes.sizeof(ModemsGUIDs), |
ctypes.byref(modems_guids_size)): |
raise ctypes.WinError() |
GUIDs = PortsGUIDs[:ports_guids_size.value] + ModemsGUIDs[:modems_guids_size.value] |
# repeat for all possible GUIDs |
for index inrange(len(GUIDs)): |
bInterfaceNumber =None |
g_hdi = SetupDiGetClassDevs( |
ctypes.byref(GUIDs[index]), |
None, |
NULL, |
DIGCF_PRESENT) # was DIGCF_PRESENT|DIGCF_DEVICEINTERFACE which misses CDC ports |
devinfo = SP_DEVINFO_DATA() |
devinfo.cbSize = ctypes.sizeof(devinfo) |
index =0 |
while SetupDiEnumDeviceInfo(g_hdi, index, ctypes.byref(devinfo)): |
index +=1 |
# get the real com port name |
hkey = SetupDiOpenDevRegKey( |
g_hdi, |
ctypes.byref(devinfo), |
DICS_FLAG_GLOBAL, |
0, |
DIREG_DEV, # DIREG_DRV for SW info |
KEY_READ) |
port_name_buffer = ctypes.create_unicode_buffer(250) |
port_name_length = ULONG(ctypes.sizeof(port_name_buffer)) |
RegQueryValueEx( |
hkey, |
'PortName', |
None, |
None, |
ctypes.byref(port_name_buffer), |
ctypes.byref(port_name_length)) |
RegCloseKey(hkey) |
# unfortunately does this method also include parallel ports. |
# we could check for names starting with COM or just exclude LPT |
# and hope that other 'unknown' names are serial ports... |
if port_name_buffer.value.startswith('LPT'): |
continue |
# hardware ID |
szHardwareID = ctypes.create_unicode_buffer(250) |
# try to get ID that includes serial number |
ifnot SetupDiGetDeviceInstanceId( |
g_hdi, |
ctypes.byref(devinfo), |
#~ ctypes.byref(szHardwareID), |
szHardwareID, |
ctypes.sizeof(szHardwareID) -1, |
None): |
# fall back to more generic hardware ID if that would fail |
ifnot SetupDiGetDeviceRegistryProperty( |
g_hdi, |
ctypes.byref(devinfo), |
SPDRP_HARDWAREID, |
None, |
ctypes.byref(szHardwareID), |
ctypes.sizeof(szHardwareID) -1, |
None): |
# Ignore ERROR_INSUFFICIENT_BUFFER |
if ctypes.GetLastError() !=ERROR_INSUFFICIENT_BUFFER: |
raise ctypes.WinError() |
# stringify |
szHardwareID_str = szHardwareID.value |
info = list_ports_common.ListPortInfo(port_name_buffer.value, skip_link_detection=True) |
# in case of USB, make a more readable string, similar to that form |
# that we also generate on other platforms |
if szHardwareID_str.startswith('USB'): |
m = re.search(r'VID_([0-9a-f]{4})(&PID_([0-9a-f]{4}))?(&MI_(d{2}))?((.*))?', szHardwareID_str, re.I) |
if m: |
info.vid =int(m.group(1), 16) |
if m.group(3): |
info.pid =int(m.group(3), 16) |
if m.group(5): |
bInterfaceNumber =int(m.group(5)) |
# Check that the USB serial number only contains alpha-numeric characters. It |
# may be a windows device ID (ephemeral ID) for composite devices. |
if m.group(7) and re.match(r'^w+$', m.group(7)): |
info.serial_number = m.group(7) |
else: |
info.serial_number = get_parent_serial_number(devinfo.DevInst, info.vid, info.pid) |
# calculate a location string |
loc_path_str = ctypes.create_unicode_buffer(250) |
if SetupDiGetDeviceRegistryProperty( |
g_hdi, |
ctypes.byref(devinfo), |
SPDRP_LOCATION_PATHS, |
None, |
ctypes.byref(loc_path_str), |
ctypes.sizeof(loc_path_str) -1, |
None): |
m = re.finditer(r'USBROOT((w+))|#USB((w+))', loc_path_str.value) |
location = [] |
for g in m: |
if g.group(1): |
location.append('{:d}'.format(int(g.group(1)) +1)) |
else: |
iflen(location) >1: |
location.append('.') |
else: |
location.append('-') |
location.append(g.group(2)) |
if bInterfaceNumber isnotNone: |
location.append(':{}.{}'.format( |
'x', #XXX how to determine correct bConfigurationValue? |
bInterfaceNumber)) |
if location: |
info.location =''.join(location) |
info.hwid = info.usb_info() |
elif szHardwareID_str.startswith('FTDIBUS'): |
m = re.search(r'VID_([0-9a-f]{4})+PID_([0-9a-f]{4})(+(w+))?', szHardwareID_str, re.I) |
if m: |
info.vid =int(m.group(1), 16) |
info.pid =int(m.group(2), 16) |
if m.group(4): |
info.serial_number = m.group(4) |
# USB location is hidden by FDTI driver :( |
info.hwid = info.usb_info() |
else: |
info.hwid = szHardwareID_str |
# friendly name |
szFriendlyName = ctypes.create_unicode_buffer(250) |
if SetupDiGetDeviceRegistryProperty( |
g_hdi, |
ctypes.byref(devinfo), |
SPDRP_FRIENDLYNAME, |
#~ SPDRP_DEVICEDESC, |
None, |
ctypes.byref(szFriendlyName), |
ctypes.sizeof(szFriendlyName) -1, |
None): |
info.description = szFriendlyName.value |
#~ else: |
# Ignore ERROR_INSUFFICIENT_BUFFER |
#~ if ctypes.GetLastError() != ERROR_INSUFFICIENT_BUFFER: |
#~ raise IOError('failed to get details for %s (%s)' % (devinfo, szHardwareID.value)) |
# ignore errors and still include the port in the list, friendly name will be same as port name |
# manufacturer |
szManufacturer = ctypes.create_unicode_buffer(250) |
if SetupDiGetDeviceRegistryProperty( |
g_hdi, |
ctypes.byref(devinfo), |
SPDRP_MFG, |
#~ SPDRP_DEVICEDESC, |
None, |
ctypes.byref(szManufacturer), |
ctypes.sizeof(szManufacturer) -1, |
None): |
info.manufacturer = szManufacturer.value |
yield info |
SetupDiDestroyDeviceInfoList(g_hdi) |
defcomports(include_links=False): |
''Return a list of info objects about serial ports'' |
returnlist(iterate_comports()) |
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
# test |
if__name__'__main__': |
for port, desc, hwid insorted(comports()): |
print('{}: {} [{}]'.format(port, desc, hwid)) |
Copy lines Copy permalink
I need list or enumerate of existing serial ports, Till now I was using this method enumerate_serial_ports(), but its not working with windows 7. Do you know some alternative how can I find out available serial ports under windows 7?
I get IterationError
Pyserial List Serial Ports Free
MelounWhat Is Pyserial
Meloun3 Answers
You're raising an IterationError
, but that exception doesn't actually exist. Maybe you should try raising EnvironmentError
for that condition as well.
The pySerial docs include some sample code for finding serial ports. Check them out: http://pyserial.sourceforge.net/examples.html#finding-serial-ports
mrbmrbThere's now a list_ports module built in to pyserial.
The module can also be executed directly:
mgalgsmgalgsBelow you find my helper function to print the names and description of the available com ports, using the serial
module:
Example output: