diff options
-rw-r--r-- | documentation/pyserial_api.rst | 6 | ||||
-rwxr-xr-x | examples/tcp_serial_redirect.py | 16 | ||||
-rw-r--r-- | serial/threaded/__init__.py | 4 | ||||
-rw-r--r-- | serial/tools/list_ports_windows.py | 127 | ||||
-rw-r--r-- | serial/urlhandler/protocol_socket.py | 3 | ||||
-rw-r--r-- | serial/win32.py | 10 |
6 files changed, 152 insertions, 14 deletions
diff --git a/documentation/pyserial_api.rst b/documentation/pyserial_api.rst index d9dcb32..76177dd 100644 --- a/documentation/pyserial_api.rst +++ b/documentation/pyserial_api.rst @@ -36,7 +36,7 @@ Native ports :const:`STOPBITS_TWO` :param float timeout: - Set a read timeout value. + Set a read timeout value in seconds. :param bool xonxoff: Enable software flow control. @@ -48,7 +48,7 @@ Native ports Enable hardware (DSR/DTR) flow control. :param float write_timeout: - Set a write timeout value. + Set a write timeout value in seconds. :param float inter_byte_timeout: Inter-character timeout, :const:`None` to disable (default). @@ -237,7 +237,7 @@ Native ports .. method:: send_break(duration=0.25) - :param float duration: Time to activate the BREAK condition. + :param float duration: Time in seconds, to activate the BREAK condition. Send break condition. Timed, returns to idle state after given duration. diff --git a/examples/tcp_serial_redirect.py b/examples/tcp_serial_redirect.py index 53dc0ad..ae7fe2d 100755 --- a/examples/tcp_serial_redirect.py +++ b/examples/tcp_serial_redirect.py @@ -66,6 +66,13 @@ it waits for the next connect. group = parser.add_argument_group('serial port') group.add_argument( + "--bytesize", + choices=[5, 6, 7, 8], + type=int, + help="set bytesize, one of {5 6 7 8}, default: 8", + default=8) + + group.add_argument( "--parity", choices=['N', 'E', 'O', 'S', 'M'], type=lambda c: c.upper(), @@ -73,6 +80,13 @@ it waits for the next connect. default='N') group.add_argument( + "--stopbits", + choices=[1, 1.5, 2], + type=float, + help="set stopbits, one of {1 1.5 2}, default: 1", + default=1) + + group.add_argument( '--rtscts', action='store_true', help='enable RTS/CTS flow control (default off)', @@ -117,7 +131,9 @@ it waits for the next connect. # connect to serial port ser = serial.serial_for_url(args.SERIALPORT, do_not_open=True) ser.baudrate = args.BAUDRATE + ser.bytesize = args.bytesize ser.parity = args.parity + ser.stopbits = args.stopbits ser.rtscts = args.rtscts ser.xonxoff = args.xonxoff diff --git a/serial/threaded/__init__.py b/serial/threaded/__init__.py index 9b8fa01..b8940b6 100644 --- a/serial/threaded/__init__.py +++ b/serial/threaded/__init__.py @@ -203,7 +203,7 @@ class ReaderThread(threading.Thread): break else: if data: - # make a separated try-except for called used code + # make a separated try-except for called user code try: self.protocol.data_received(data) except Exception as e: @@ -216,7 +216,7 @@ class ReaderThread(threading.Thread): def write(self, data): """Thread safe writing (uses lock)""" with self._lock: - self.serial.write(data) + return self.serial.write(data) def close(self): """Close the serial port and exit reader thread (uses lock)""" diff --git a/serial/tools/list_ports_windows.py b/serial/tools/list_ports_windows.py index 19b9499..2c530e8 100644 --- a/serial/tools/list_ports_windows.py +++ b/serial/tools/list_ports_windows.py @@ -118,11 +118,25 @@ 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 @@ -132,19 +146,110 @@ 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() + 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 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... - guids_size = DWORD() + PortsGUIDs = (GUID * 8)() # so far only seen one used, so hope 8 are enough... + ports_guids_size = DWORD() if not SetupDiClassGuidsFromName( "Ports", - GUIDs, - ctypes.sizeof(GUIDs), - ctypes.byref(guids_size)): + 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() + if not 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 in range(guids_size.value): + for index in range(len(GUIDs)): bInterfaceNumber = None g_hdi = SetupDiGetClassDevs( ctypes.byref(GUIDs[index]), @@ -213,15 +318,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( diff --git a/serial/urlhandler/protocol_socket.py b/serial/urlhandler/protocol_socket.py index 36cdf1f..11f6a05 100644 --- a/serial/urlhandler/protocol_socket.py +++ b/serial/urlhandler/protocol_socket.py @@ -249,7 +249,8 @@ class Serial(SerialBase): while ready: ready, _, _ = select.select([self._socket], [], [], 0) try: - self._socket.recv(4096) + if ready: + ready = self._socket.recv(4096) except OSError as e: # this is for Python 3.x where select.error is a subclass of # OSError ignore BlockingIOErrors and EINTR. other errors are shown diff --git a/serial/win32.py b/serial/win32.py index 08b6e67..157f470 100644 --- a/serial/win32.py +++ b/serial/win32.py @@ -181,6 +181,10 @@ WaitForSingleObject = _stdcall_libraries['kernel32'].WaitForSingleObject WaitForSingleObject.restype = DWORD WaitForSingleObject.argtypes = [HANDLE, DWORD] +WaitCommEvent = _stdcall_libraries['kernel32'].WaitCommEvent +WaitCommEvent.restype = BOOL +WaitCommEvent.argtypes = [HANDLE, LPDWORD, LPOVERLAPPED] + CancelIoEx = _stdcall_libraries['kernel32'].CancelIoEx CancelIoEx.restype = BOOL CancelIoEx.argtypes = [HANDLE, LPOVERLAPPED] @@ -247,6 +251,12 @@ EV_BREAK = 64 # Variable c_int PURGE_RXCLEAR = 8 # Variable c_int INFINITE = 0xFFFFFFFF +CE_RXOVER = 0x0001 +CE_OVERRUN = 0x0002 +CE_RXPARITY = 0x0004 +CE_FRAME = 0x0008 +CE_BREAK = 0x0010 + class N11_OVERLAPPED4DOLLAR_48E(Union): pass |