summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRyan Summers <summers.ryan.m@gmail.com>2019-02-12 17:23:41 +0100
committerRyan Summers <summers.ryan.m@gmail.com>2019-02-12 17:23:41 +0100
commitcd2248d7c136bc33b37d025aa81c230d094835ba (patch)
treeae7aa04ef17b76798f38f4f7c6d70d4ad3dff82e
parenta4d8f27bf636ee598c4368d7e488f78a226bf778 (diff)
downloadpyserial-git-cd2248d7c136bc33b37d025aa81c230d094835ba.tar.gz
Added recursive search for device USB serial number to support composite devices
-rw-r--r--serial/tools/list_ports_windows.py92
1 files changed, 90 insertions, 2 deletions
diff --git a/serial/tools/list_ports_windows.py b/serial/tools/list_ports_windows.py
index 19b9499..4dd1b9a 100644
--- a/serial/tools/list_ports_windows.py
+++ b/serial/tools/list_ports_windows.py
@@ -118,6 +118,16 @@ 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
+
+
DIGCF_PRESENT = 2
DIGCF_DEVICEINTERFACE = 16
@@ -132,6 +142,78 @@ DIREG_DEV = 0x00000001
KEY_READ = 0x20019
+MAX_USB_DEVICE_TREE_TRAVERSAL_DEPTH = 5
+
+
+def get_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()
+ if CM_Get_Parent(
+ ctypes.byref(devinst),
+ child_devinst,
+ 0):
+ raise ctypes.WinError()
+
+ # Get the ID of the parent device and parse it for vendor ID, product ID, and serial number.
+ parentHardwareID = ctypes.create_unicode_buffer(250)
+
+ if CM_Get_Device_IDW(
+ devinst,
+ parentHardwareID,
+ ctypes.sizeof(parentHardwareID) - 1,
+ 0):
+ raise ctypes.WinError()
+
+ 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 and not re.match(r'^\w+$', serial_number):
+ serial_number = None
+
+ if not vid or not 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.
+ if not 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
+
+
def iterate_comports():
"""Return a generator that yields descriptions for serial ports"""
GUIDs = (GUID * 8)() # so far only seen one used, so hope 8 are enough...
@@ -213,15 +295,21 @@ def iterate_comports():
# 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}))?(\\(\w+))?', szHardwareID_str, re.I)
+ 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))
- if m.group(7):
+
+ # 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(