Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mechanism for consistent port ordering for TinyUSB CDC devices on Windows #400

Open
Qlexio opened this issue Jan 27, 2025 · 0 comments
Open

Comments

@Qlexio
Copy link
Owner

Qlexio commented Jan 27, 2025

I've got an ESP32-S3 + TinyUSB project that creates two CDC serial devices. On macOS and Linux these always enumerate in a consistent order, and thus I'm able to sort them trivially (e.g. on macOS /dev/cu.usbmodemmyproj1, /dev/cu.usbmodemmyproj3).

However, Windows does not do this and will sometimes reorder the COM numbers, such that the lower COM number may be the second port instantiated from TinyUSB. This is a problem as there seems to be no way with pyserial's list_ports method to determine the correct ordering of the ports. I've tried to sort by usb_info which includes the location, but location is only present on one of the two devices and it's not necessarily the first TinyUSB device. These are the two ports:

'USB VID:PID=303A:4002 SER=MYPROJ'
'USB VID:PID=303A:4002 SER=MYPROJ LOCATION=1-2.4.4:x.2'

I have a workaround using pywin32 that queries for serial devices, and then sorts by the MI_# string that's present in the PNPDeviceID (which consistently orders the ports correctly). This guarantees I can always pick the correct port even if the COM ordering is reversed.

def find_windows_com_ports(vid, pid, serial_number=None):
    import win32com.client

    # Caution: GPT says `Win32_SerialPort` isn't reliable with USB devices.
    # But using `Win32_PnPEntity` requires parsing the COM port out of the
    # `Name`` which seems like it might break on Chinese language systems.
    # Seems to work for me.
    wmi = win32com.client.GetObject("winmgmts:")
    query = f"SELECT * FROM Win32_SerialPort"
    serial_ports = []

    for device in wmi.ExecQuery(query):
        logger.info(f"Found COM port: {device.DeviceID} {device.PNPDeviceID}")
        if device.PNPDeviceID and f"VID_{vid}&PID_{pid}" in device.PNPDeviceID:
            com_port = device.DeviceID
            # Extract MI_ number from DeviceID
            # Not exactly sure what this is but it seems to consistently enumerate in a
            # consistent order for the two USB devices, regardless of the COM port order
            mi_match = re.search(r"MI_([0-9A-F]+)", device.PNPDeviceID)
            mi = mi_match.group(1) if mi_match else "Unknown"

            # If we had a way to get the "Bus reported device description" here that'd work too
            # and be more consistent with other platforms. But we don't, and I guess it's hard?
            # See also: https://github.com/pyserial/pyserial/pull/338
            if serial_number is not None:
                ports = list_ports.grep(com_port)
                port = next(ports)
                if port.serial_number != serial_number:
                    logger.info(f"Skipping {com_port} ({serial_number} != {port.serial_number})")
                    continue

            serial_ports.append((com_port, mi))

    # Sort by the second field (MI number)
    serial_ports.sort(key=lambda x: x[1])

    return [port[0] for port in serial_ports]

I don't love this workaround since it requires an additional dependency on Windows (pywin32) and is a special code path to maintain and test only for Windows.

I'm opening this issue to discuss other ways of capturing the device order in a way that can be exposed in serial.tools.list_ports. Exposing the MI_ number that pywin32 has could solve the problem. Or if we could get LOCATION included on both ports that would probably also solve the issue. Or possibly there's a more elegant solution I'm not informed enough to reason about!


This issue has been cloned from: pyserial#767

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant