pyserial - 如何使用 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调用你想要的端口。
解决方案
实际上,在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。
推荐阅读
- regex - REGEXP_SUBSTR Oracle 不同于 regext.com
- rest - 如何在 Xamarin.Forms 应用程序中组织服务中的 HTTP 响应
- c++ - Red Hat 7.2 GCC版本支持编译C++14代码?
- c++ - 如何在 C++ 中创建任意类型的指针常量?
- vue.js - 如何在 Vuex 中使用突变来被动地创建或删除对象属性?
- android - 安装失败,原因是:“关闭”错误 | 安卓工作室 4.0.2
- python-3.x - 无余除法
- java - Java 垃圾收集器跨虚拟机的不同行为
- sql - SQL 中的 Powershell 漏洞评估
- php - 在 PHP 中计算一年中的最后一周以跳到明年