首页 > 解决方案 > 如何使用 python 在 windows 中获取 FTDI FT4232 芯片位置信息或序列号?

问题描述

我正在使用 FTDI FT4232 芯片来扩展 Windows 10 上的四个 COM 端口。

这些扩展的 COM 端口链接到四个不同的设备。因此,如果我想通过这些 COM 端口与其他设备通信,我需要知道要调用哪些端口。

我正在用 python 编码,这是我用 pyserial 得到的。

>python -m serial.tools.list_ports -v
COM3
    desc: USB Serial Port (COM3)
    hwid: USB VID:PID=0403:6011 SER=5
COM4
    desc: USB Serial Port (COM4)
    hwid: USB VID:PID=0403:6011 SER=5
COM5
    desc: USB Serial Port (COM5)
    hwid: USB VID:PID=0403:6011 SER=5
COM6
    desc: USB Serial Port (COM6)
    hwid: USB VID:PID=0403:6011 SER=5
4 ports found

但是在 Linux 中,pyserial 可以获得位置信息,可以用来区分这四个端口。

~$:python3 -m serial.tools.list_ports -v
/dev/ttyUSB0        
    desc: Quad RS232-HS
    hwid: USB VID:PID=0403:6011 LOCATION=1-2.1:1.0
/dev/ttyUSB1        
    desc: Quad RS232-HS
    hwid: USB VID:PID=0403:6011 LOCATION=1-2.1:1.1
/dev/ttyUSB2        
    desc: Quad RS232-HS
    hwid: USB VID:PID=0403:6011 LOCATION=1-2.1:1.2
/dev/ttyUSB3        
    desc: Quad RS232-HS
    hwid: USB VID:PID=0403:6011 LOCATION=1-2.1:1.3
4 ports found

有人对这个问题有任何想法吗?

临时解决方案:修改list_ports_windows.py如下:

    elif szHardwareID_str.startswith('FTDIBUS'):
        m = re.search(r'VID_([0-9a-f]{4})\+PID_([0-9a-f]{4})(\+(\w*))&(\w*)&(\w*)&(\w*)&(\w*)?', szHardwareID_str, re.I)
        if m:
            info.vid = int(m.group(1), 16)
            info.pid = int(m.group(2), 16)
            if m.group(8):
                info.serial_number =  int(m.group(8),16)
        # USB location is hidden by FDTI driver :(
        info.hwid = info.usb_info()

然后你可以用vid+pid+serial_number调用你想要的端口。

标签: pyserialftdi

解决方案


实际上,在serial.tools.list_ports.comports返回的ListPortInfo中有一个location字段。但是,list_ports (list_ports_windows.py) 的 windows 实现似乎无法正确恢复此信息。我使用日志记录模块通过将以下行添加到我的 pyserial(安装在 win10 64,python3.8 上)来对此进行调查。添加

logging.debug("szHardwareID_str: %s" % szHardwareID_str) #added this lin

# stringify
szHardwareID_str = szHardwareID.value

为我产生:

DEBUG:root:szHardwareID_str: ACPI\PNP0501\0
DEBUG:root:szHardwareID_str: FTDIBUS\VID_0403+PID_6010+6&192CD50C&0&13&2\0000
DEBUG:root:szHardwareID_str: FTDIBUS\VID_0403+PID_6010+6&192CD50C&0&13&1\0000

so on可以看到location其实是在这个字符串里面但是后面的字符串分析没能正确提取到这个信息。IE实现的搜索不匹配locationID:

m = re.search(r'VID_([0-9a-f]{4})(&PID_([0-9a-f]{4}))?(&MI_(\d{2}))?(\\(\w+))?', szHardwareID_str, re.I)

长话短说:据我所知,您要么修改 pyserial(通过创建补丁+拉取请求在本地或全局范围内),要么使用 pyserial.tools.list_ports 模块手动“提取”信息。例如:

import ctypes
import serial
import logging

import serial.tools.list_ports_windows as lp

logging.basicConfig(level=logging.DEBUG)

################copied from list_ports_windows.py
GUIDs = (lp.GUID * 8)()  # so far only seen one used, so hope 8 are enough...
guids_size = lp.DWORD()
if not lp.SetupDiClassGuidsFromName(
        "Ports",
        GUIDs,
        ctypes.sizeof(GUIDs),
        ctypes.byref(guids_size)):
    raise ctypes.WinError()

# repeat for all possible GUIDs
for index in range(guids_size.value):
    bInterfaceNumber = None
    g_hdi = lp.SetupDiGetClassDevs(
        ctypes.byref(GUIDs[index]),
        None,
        lp.NULL,
        lp.DIGCF_PRESENT)  # was DIGCF_PRESENT|DIGCF_DEVICEINTERFACE which misses CDC ports

    devinfo = lp.SP_DEVINFO_DATA()
    devinfo.cbSize = ctypes.sizeof(devinfo)
    index = 0
    while lp.SetupDiEnumDeviceInfo(g_hdi, index, ctypes.byref(devinfo)):
        index += 1

        # get the real com port name
        hkey = lp.SetupDiOpenDevRegKey(
            g_hdi,
            ctypes.byref(devinfo),
            lp.DICS_FLAG_GLOBAL,
            0,
            lp.DIREG_DEV,  # DIREG_DRV for SW info
            lp.KEY_READ)
        port_name_buffer = ctypes.create_unicode_buffer(250)
        port_name_length = lp.ULONG(ctypes.sizeof(port_name_buffer))
        lp.RegQueryValueEx(
            hkey,
            "PortName",
            None,
            None,
            ctypes.byref(port_name_buffer),
            ctypes.byref(port_name_length))
        lp.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
        if not lp.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
            if not lp.SetupDiGetDeviceRegistryProperty(
                    g_hdi,
                    ctypes.byref(devinfo),
                    lp.SPDRP_HARDWAREID,
                    None,
                    ctypes.byref(szHardwareID),
                    ctypes.sizeof(szHardwareID) - 1,
                    None):
                # Ignore ERROR_INSUFFICIENT_BUFFER
                if ctypes.GetLastError() != lp.ERROR_INSUFFICIENT_BUFFER:
                    raise ctypes.WinError()
        # stringify
        szHardwareID_str = szHardwareID.value

        print(szHardwareID_str)

然而,一般的解决方案(即补丁)将是恕我直言的最佳方式。也许你可以在官方 pyserial repos 上打开一个 ISSUE。


推荐阅读