summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--documentation/pyserial_api.rst6
-rw-r--r--documentation/url_handlers.rst2
-rwxr-xr-xexamples/tcp_serial_redirect.py16
-rw-r--r--serial/rfc2217.py5
-rw-r--r--serial/threaded/__init__.py4
-rw-r--r--serial/tools/list_ports_windows.py127
-rw-r--r--serial/urlhandler/protocol_rfc2217.py2
-rw-r--r--serial/urlhandler/protocol_socket.py3
-rw-r--r--serial/win32.py10
9 files changed, 156 insertions, 19 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/documentation/url_handlers.rst b/documentation/url_handlers.rst
index b4f0da7..5cc1aaa 100644
--- a/documentation/url_handlers.rst
+++ b/documentation/url_handlers.rst
@@ -48,7 +48,7 @@ Supported options in the URL are:
- ``timeout=<value>``: Change network timeout (default 3 seconds). This is
useful when the server takes a little more time to send its answers. The
- timeout applies to the initial Telnet / :rfc:`2271` negotiation as well
+ timeout applies to the initial Telnet / :rfc:`2217` negotiation as well
as changing port settings or control line change commands.
- ``logging={debug|info|warning|error}``: Prints diagnostic messages (not
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/rfc2217.py b/serial/rfc2217.py
index d962c1e..d388038 100644
--- a/serial/rfc2217.py
+++ b/serial/rfc2217.py
@@ -483,7 +483,7 @@ class Serial(SerialBase):
if self.logger:
self.logger.info("Negotiated options: {}".format(self._telnet_options))
- # fine, go on, set RFC 2271 specific things
+ # fine, go on, set RFC 2217 specific things
self._reconfigure_port()
# all things set up get, now a clean start
if not self._dsrdtr:
@@ -613,7 +613,7 @@ class Serial(SerialBase):
try:
timeout = Timeout(self._timeout)
while len(data) < size:
- if self._thread is None:
+ if self._thread is None or not self._thread.is_alive():
raise SerialException('connection failed (reader thread died)')
buf = self._read_buffer.get(True, timeout.time_left())
if buf is None:
@@ -790,7 +790,6 @@ class Serial(SerialBase):
self._telnet_negotiate_option(telnet_command, byte)
mode = M_NORMAL
finally:
- self._thread = None
if self.logger:
self.logger.debug("read thread terminated")
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_rfc2217.py b/serial/urlhandler/protocol_rfc2217.py
index 8be310f..ebeec3a 100644
--- a/serial/urlhandler/protocol_rfc2217.py
+++ b/serial/urlhandler/protocol_rfc2217.py
@@ -1,6 +1,6 @@
#! python
#
-# This is a thin wrapper to load the rfc2271 implementation.
+# This is a thin wrapper to load the rfc2217 implementation.
#
# This file is part of pySerial. https://github.com/pyserial/pyserial
# (C) 2011 Chris Liechti <cliechti@gmx.net>
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