Merge pull request #351 from cefn/master
win32: Working CMD.exe terminal using Windows 10 ANSI support
diff --git a/.gitignore b/.gitignore
index b33f61a..fb44ebb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,3 +7,5 @@
*.egg-info
/MANIFEST
+
+.idea
\ No newline at end of file
diff --git a/.travis.yml b/.travis.yml
index 6792d62..ff48704 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -5,8 +5,6 @@
python:
- 2.7
- - 3.2
- - 3.3
- 3.4
- 3.5
- 3.6
diff --git a/documentation/pyserial_api.rst b/documentation/pyserial_api.rst
index 2045244..efd82c1 100644
--- a/documentation/pyserial_api.rst
+++ b/documentation/pyserial_api.rst
@@ -36,7 +36,7 @@
: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 @@
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).
@@ -157,6 +157,22 @@
Returns an instance of :class:`bytes` when available (Python 2.6
and newer) and :class:`str` otherwise.
+ .. method:: read_until(expected=LF, size=None)
+
+ :param expected: The byte string to search for.
+ :param size: Number of bytes to read.
+ :return: Bytes read from the port.
+ :rtype: bytes
+
+ Read until an expected sequence is found ('\\n' by default), the size
+ is exceeded or until timeout occurs. If a timeout is set it may
+ return less characters as requested. With no timeout it will block
+ until the requested number of bytes is read.
+
+ .. versionchanged:: 2.5
+ Returns an instance of :class:`bytes` when available (Python 2.6
+ and newer) and :class:`str` otherwise.
+
.. method:: write(data)
:param data: Data to send.
@@ -168,7 +184,7 @@
Write the bytes *data* to the port. This should be of type ``bytes``
(or compatible such as ``bytearray`` or ``memoryview``). Unicode
- strings must be encoded (e.g. ``'hello'.encode('utf-8'``).
+ strings must be encoded (e.g. ``'hello'.encode('utf-8')``.
.. versionchanged:: 2.5
Accepts instances of :class:`bytes` and :class:`bytearray` when
@@ -221,7 +237,7 @@
.. 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..42a53fa 100644
--- a/documentation/url_handlers.rst
+++ b/documentation/url_handlers.rst
@@ -16,6 +16,7 @@
- ``hwgrep://<regexp>[&skip_busy][&n=N]``
- ``spy://port[?option[=value][&option[=value]]]``
- ``alt://port?class=<classname>``
+- ``cp2110://<bus>:<dev>:<if>``
.. versionchanged:: 3.0 Options are specified with ``?`` and ``&`` instead of ``/``
@@ -48,7 +49,7 @@
- ``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
@@ -235,6 +236,21 @@
.. versionadded:: 3.0
+``cp2110://``
+=============
+
+This backend implements support for HID-to-UART devices manufactured by Silicon
+Labs and marketed as CP2110 and CP2114. The implementation is (mostly)
+OS-independent and in userland. It relies on `cython-hidapi`_.
+
+.. _cython-hidapi: https://github.com/trezor/cython-hidapi
+
+Examples::
+
+ cp2110://0001:004a:00
+ cp2110://0002:0077:00
+
+.. versionadded:: 3.5
Examples
========
@@ -247,5 +263,5 @@
- ``hwgrep://0451:f432`` (USB VID:PID)
- ``spy://COM54?file=log.txt``
- ``alt:///dev/ttyUSB0?class=PosixPollSerial``
-
+- ``cp2110://0001:004a:00``
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 @@
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 @@
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 @@
# 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/__init__.py b/serial/__init__.py
index dcd7c12..afd63a6 100644
--- a/serial/__init__.py
+++ b/serial/__init__.py
@@ -15,7 +15,7 @@
from serial.serialutil import *
#~ SerialBase, SerialException, to_bytes, iterbytes
-__version__ = '3.4'
+__version__ = '3.4.1'
VERSION = __version__
diff --git a/serial/rfc2217.py b/serial/rfc2217.py
index d962c1e..2ae188e 100644
--- a/serial/rfc2217.py
+++ b/serial/rfc2217.py
@@ -76,7 +76,7 @@
import serial
from serial.serialutil import SerialBase, SerialException, to_bytes, \
- iterbytes, portNotOpenError, Timeout
+ iterbytes, PortNotOpenError, Timeout
# port string is expected to be something like this:
# rfc2217://host:port
@@ -483,7 +483,7 @@
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:
@@ -598,7 +598,7 @@
def in_waiting(self):
"""Return the number of bytes currently in the input buffer."""
if not self.is_open:
- raise portNotOpenError
+ raise PortNotOpenError()
return self._read_buffer.qsize()
def read(self, size=1):
@@ -608,12 +608,12 @@
until the requested number of bytes is read.
"""
if not self.is_open:
- raise portNotOpenError
+ raise PortNotOpenError()
data = bytearray()
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:
@@ -632,7 +632,7 @@
closed.
"""
if not self.is_open:
- raise portNotOpenError
+ raise PortNotOpenError()
with self._write_lock:
try:
self._socket.sendall(to_bytes(data).replace(IAC, IAC_DOUBLED))
@@ -643,7 +643,7 @@
def reset_input_buffer(self):
"""Clear input buffer, discarding all that is in the buffer."""
if not self.is_open:
- raise portNotOpenError
+ raise PortNotOpenError()
self.rfc2217_send_purge(PURGE_RECEIVE_BUFFER)
# empty read buffer
while self._read_buffer.qsize():
@@ -655,7 +655,7 @@
discarding all that is in the buffer.
"""
if not self.is_open:
- raise portNotOpenError
+ raise PortNotOpenError()
self.rfc2217_send_purge(PURGE_TRANSMIT_BUFFER)
def _update_break_state(self):
@@ -664,7 +664,7 @@
possible.
"""
if not self.is_open:
- raise portNotOpenError
+ raise PortNotOpenError()
if self.logger:
self.logger.info('set BREAK to {}'.format('active' if self._break_state else 'inactive'))
if self._break_state:
@@ -675,7 +675,7 @@
def _update_rts_state(self):
"""Set terminal status line: Request To Send."""
if not self.is_open:
- raise portNotOpenError
+ raise PortNotOpenError()
if self.logger:
self.logger.info('set RTS to {}'.format('active' if self._rts_state else 'inactive'))
if self._rts_state:
@@ -686,7 +686,7 @@
def _update_dtr_state(self):
"""Set terminal status line: Data Terminal Ready."""
if not self.is_open:
- raise portNotOpenError
+ raise PortNotOpenError()
if self.logger:
self.logger.info('set DTR to {}'.format('active' if self._dtr_state else 'inactive'))
if self._dtr_state:
@@ -698,28 +698,28 @@
def cts(self):
"""Read terminal status line: Clear To Send."""
if not self.is_open:
- raise portNotOpenError
+ raise PortNotOpenError()
return bool(self.get_modem_state() & MODEMSTATE_MASK_CTS)
@property
def dsr(self):
"""Read terminal status line: Data Set Ready."""
if not self.is_open:
- raise portNotOpenError
+ raise PortNotOpenError()
return bool(self.get_modem_state() & MODEMSTATE_MASK_DSR)
@property
def ri(self):
"""Read terminal status line: Ring Indicator."""
if not self.is_open:
- raise portNotOpenError
+ raise PortNotOpenError()
return bool(self.get_modem_state() & MODEMSTATE_MASK_RI)
@property
def cd(self):
"""Read terminal status line: Carrier Detect."""
if not self.is_open:
- raise portNotOpenError
+ raise PortNotOpenError()
return bool(self.get_modem_state() & MODEMSTATE_MASK_CD)
# - - - platform specific - - -
@@ -790,7 +790,6 @@
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/serialcli.py b/serial/serialcli.py
index ddd0cdf..4614736 100644
--- a/serial/serialcli.py
+++ b/serial/serialcli.py
@@ -148,7 +148,7 @@
def in_waiting(self):
"""Return the number of characters currently in the input buffer."""
if not self.is_open:
- raise portNotOpenError
+ raise PortNotOpenError()
return self._port_handle.BytesToRead
def read(self, size=1):
@@ -158,7 +158,7 @@
until the requested number of bytes is read.
"""
if not self.is_open:
- raise portNotOpenError
+ raise PortNotOpenError()
# must use single byte reads as this is the only way to read
# without applying encodings
data = bytearray()
@@ -174,7 +174,7 @@
def write(self, data):
"""Output the given string over the serial port."""
if not self.is_open:
- raise portNotOpenError
+ raise PortNotOpenError()
#~ if not isinstance(data, (bytes, bytearray)):
#~ raise TypeError('expected %s or bytearray, got %s' % (bytes, type(data)))
try:
@@ -182,13 +182,13 @@
# as this is the only one not applying encodings
self._port_handle.Write(as_byte_array(data), 0, len(data))
except System.TimeoutException:
- raise writeTimeoutError
+ raise SerialTimeoutException('Write timeout')
return len(data)
def reset_input_buffer(self):
"""Clear input buffer, discarding all that is in the buffer."""
if not self.is_open:
- raise portNotOpenError
+ raise PortNotOpenError()
self._port_handle.DiscardInBuffer()
def reset_output_buffer(self):
@@ -197,7 +197,7 @@
discarding all that is in the buffer.
"""
if not self.is_open:
- raise portNotOpenError
+ raise PortNotOpenError()
self._port_handle.DiscardOutBuffer()
def _update_break_state(self):
@@ -205,40 +205,40 @@
Set break: Controls TXD. When active, to transmitting is possible.
"""
if not self.is_open:
- raise portNotOpenError
+ raise PortNotOpenError()
self._port_handle.BreakState = bool(self._break_state)
def _update_rts_state(self):
"""Set terminal status line: Request To Send"""
if not self.is_open:
- raise portNotOpenError
+ raise PortNotOpenError()
self._port_handle.RtsEnable = bool(self._rts_state)
def _update_dtr_state(self):
"""Set terminal status line: Data Terminal Ready"""
if not self.is_open:
- raise portNotOpenError
+ raise PortNotOpenError()
self._port_handle.DtrEnable = bool(self._dtr_state)
@property
def cts(self):
"""Read terminal status line: Clear To Send"""
if not self.is_open:
- raise portNotOpenError
+ raise PortNotOpenError()
return self._port_handle.CtsHolding
@property
def dsr(self):
"""Read terminal status line: Data Set Ready"""
if not self.is_open:
- raise portNotOpenError
+ raise PortNotOpenError()
return self._port_handle.DsrHolding
@property
def ri(self):
"""Read terminal status line: Ring Indicator"""
if not self.is_open:
- raise portNotOpenError
+ raise PortNotOpenError()
#~ return self._port_handle.XXX
return False # XXX an error would be better
@@ -246,7 +246,7 @@
def cd(self):
"""Read terminal status line: Carrier Detect"""
if not self.is_open:
- raise portNotOpenError
+ raise PortNotOpenError()
return self._port_handle.CDHolding
# - - platform specific - - - -
diff --git a/serial/serialjava.py b/serial/serialjava.py
index 9c920c5..0789a78 100644
--- a/serial/serialjava.py
+++ b/serial/serialjava.py
@@ -152,7 +152,7 @@
def in_waiting(self):
"""Return the number of characters currently in the input buffer."""
if not self.sPort:
- raise portNotOpenError
+ raise PortNotOpenError()
return self._instream.available()
def read(self, size=1):
@@ -162,7 +162,7 @@
until the requested number of bytes is read.
"""
if not self.sPort:
- raise portNotOpenError
+ raise PortNotOpenError()
read = bytearray()
if size > 0:
while len(read) < size:
@@ -177,7 +177,7 @@
def write(self, data):
"""Output the given string over the serial port."""
if not self.sPort:
- raise portNotOpenError
+ raise PortNotOpenError()
if not isinstance(data, (bytes, bytearray)):
raise TypeError('expected %s or bytearray, got %s' % (bytes, type(data)))
self._outstream.write(data)
@@ -186,7 +186,7 @@
def reset_input_buffer(self):
"""Clear input buffer, discarding all that is in the buffer."""
if not self.sPort:
- raise portNotOpenError
+ raise PortNotOpenError()
self._instream.skip(self._instream.available())
def reset_output_buffer(self):
@@ -195,57 +195,57 @@
discarding all that is in the buffer.
"""
if not self.sPort:
- raise portNotOpenError
+ raise PortNotOpenError()
self._outstream.flush()
def send_break(self, duration=0.25):
"""Send break condition. Timed, returns to idle state after given duration."""
if not self.sPort:
- raise portNotOpenError
+ raise PortNotOpenError()
self.sPort.sendBreak(duration*1000.0)
def _update_break_state(self):
"""Set break: Controls TXD. When active, to transmitting is possible."""
if self.fd is None:
- raise portNotOpenError
+ raise PortNotOpenError()
raise SerialException("The _update_break_state function is not implemented in java.")
def _update_rts_state(self):
"""Set terminal status line: Request To Send"""
if not self.sPort:
- raise portNotOpenError
+ raise PortNotOpenError()
self.sPort.setRTS(self._rts_state)
def _update_dtr_state(self):
"""Set terminal status line: Data Terminal Ready"""
if not self.sPort:
- raise portNotOpenError
+ raise PortNotOpenError()
self.sPort.setDTR(self._dtr_state)
@property
def cts(self):
"""Read terminal status line: Clear To Send"""
if not self.sPort:
- raise portNotOpenError
+ raise PortNotOpenError()
self.sPort.isCTS()
@property
def dsr(self):
"""Read terminal status line: Data Set Ready"""
if not self.sPort:
- raise portNotOpenError
+ raise PortNotOpenError()
self.sPort.isDSR()
@property
def ri(self):
"""Read terminal status line: Ring Indicator"""
if not self.sPort:
- raise portNotOpenError
+ raise PortNotOpenError()
self.sPort.isRI()
@property
def cd(self):
"""Read terminal status line: Carrier Detect"""
if not self.sPort:
- raise portNotOpenError
+ raise PortNotOpenError()
self.sPort.isCD()
diff --git a/serial/serialposix.py b/serial/serialposix.py
index 507e2fe..2f125c3 100644
--- a/serial/serialposix.py
+++ b/serial/serialposix.py
@@ -39,7 +39,7 @@
import serial
from serial.serialutil import SerialBase, SerialException, to_bytes, \
- portNotOpenError, writeTimeoutError, Timeout
+ PortNotOpenError, SerialTimeoutException, Timeout
class PlatformSpecificBase(object):
@@ -54,6 +54,15 @@
def set_low_latency_mode(self, low_latency_settings):
raise NotImplementedError('Low latency not supported on this platform')
+ def _update_break_state(self):
+ """\
+ Set break: Controls TXD. When active, no transmitting is possible.
+ """
+ if self._break_state:
+ fcntl.ioctl(self.fd, TIOCSBRK)
+ else:
+ fcntl.ioctl(self.fd, TIOCCBRK)
+
# some systems support an extra flag to enable the two in POSIX unsupported
# paritiy settings for MARK and SPACE
@@ -205,6 +214,9 @@
class PlatformSpecific(PlatformSpecificBase):
osx_version = os.uname()[2].split('.')
+ TIOCSBRK = 0x2000747B # _IO('t', 123)
+ TIOCCBRK = 0x2000747A # _IO('t', 122)
+
# Tiger or above can support arbitrary serial speeds
if int(osx_version[0]) >= 8:
def _set_special_baudrate(self, baudrate):
@@ -212,6 +224,15 @@
buf = array.array('i', [baudrate])
fcntl.ioctl(self.fd, IOSSIOSPEED, buf, 1)
+ def _update_break_state(self):
+ """\
+ Set break: Controls TXD. When active, no transmitting is possible.
+ """
+ if self._break_state:
+ fcntl.ioctl(self.fd, PlatformSpecific.TIOCSBRK)
+ else:
+ fcntl.ioctl(self.fd, PlatformSpecific.TIOCCBRK)
+
elif plat[:3] == 'bsd' or \
plat[:7] == 'freebsd' or \
plat[:6] == 'netbsd' or \
@@ -227,6 +248,19 @@
# a literal value.
BAUDRATE_CONSTANTS = ReturnBaudrate()
+ TIOCSBRK = 0x2000747B # _IO('t', 123)
+ TIOCCBRK = 0x2000747A # _IO('t', 122)
+
+
+ def _update_break_state(self):
+ """\
+ Set break: Controls TXD. When active, no transmitting is possible.
+ """
+ if self._break_state:
+ fcntl.ioctl(self.fd, PlatformSpecific.TIOCSBRK)
+ else:
+ fcntl.ioctl(self.fd, PlatformSpecific.TIOCCBRK)
+
else:
class PlatformSpecific(PlatformSpecificBase):
pass
@@ -371,8 +405,15 @@
ispeed = ospeed = self.BAUDRATE_CONSTANTS[self._baudrate]
except KeyError:
#~ raise ValueError('Invalid baud rate: %r' % self._baudrate)
- # may need custom baud rate, it isn't in our list.
- ispeed = ospeed = getattr(termios, 'B38400')
+
+ # See if BOTHER is defined for this platform; if it is, use
+ # this for a speed not defined in the baudrate constants list.
+ try:
+ ispeed = ospeed = BOTHER
+ except NameError:
+ # may need custom baud rate, it isn't in our list.
+ ispeed = ospeed = getattr(termios, 'B38400')
+
try:
custom_baud = int(self._baudrate) # store for later
except ValueError:
@@ -498,7 +539,7 @@
until the requested number of bytes is read.
"""
if not self.is_open:
- raise portNotOpenError
+ raise PortNotOpenError()
read = bytearray()
timeout = Timeout(self._timeout)
while len(read) < size:
@@ -514,16 +555,6 @@
if not ready:
break # timeout
buf = os.read(self.fd, size - len(read))
- # read should always return some data as select reported it was
- # ready to read when we get to this point.
- if not buf:
- # Disconnected devices, at least on Linux, show the
- # behavior that they are always ready to read immediately
- # but reading returns nothing.
- raise SerialException(
- 'device reports readiness to read but returned no data '
- '(device disconnected or multiple access on port?)')
- read.extend(buf)
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
@@ -536,6 +567,18 @@
# see also http://www.python.org/dev/peps/pep-3151/#select
if e[0] not in (errno.EAGAIN, errno.EALREADY, errno.EWOULDBLOCK, errno.EINPROGRESS, errno.EINTR):
raise SerialException('read failed: {}'.format(e))
+ else:
+ # read should always return some data as select reported it was
+ # ready to read when we get to this point.
+ if not buf:
+ # Disconnected devices, at least on Linux, show the
+ # behavior that they are always ready to read immediately
+ # but reading returns nothing.
+ raise SerialException(
+ 'device reports readiness to read but returned no data '
+ '(device disconnected or multiple access on port?)')
+ read.extend(buf)
+
if timeout.expired():
break
return bytes(read)
@@ -551,7 +594,7 @@
def write(self, data):
"""Output the given byte string over the serial port."""
if not self.is_open:
- raise portNotOpenError
+ raise PortNotOpenError()
d = to_bytes(data)
tx_len = length = len(d)
timeout = Timeout(self._write_timeout)
@@ -566,13 +609,13 @@
# when timeout is set, use select to wait for being ready
# with the time left as timeout
if timeout.expired():
- raise writeTimeoutError
+ raise SerialTimeoutException('Write timeout')
abort, ready, _ = select.select([self.pipe_abort_write_r], [self.fd], [], timeout.time_left())
if abort:
os.read(self.pipe_abort_write_r, 1000)
break
if not ready:
- raise writeTimeoutError
+ raise SerialTimeoutException('Write timeout')
else:
assert timeout.time_left() is None
# wait for write operation
@@ -599,7 +642,7 @@
if e[0] not in (errno.EAGAIN, errno.EALREADY, errno.EWOULDBLOCK, errno.EINPROGRESS, errno.EINTR):
raise SerialException('write failed: {}'.format(e))
if not timeout.is_non_blocking and timeout.expired():
- raise writeTimeoutError
+ raise SerialTimeoutException('Write timeout')
return length - len(d)
def flush(self):
@@ -608,13 +651,13 @@
is written.
"""
if not self.is_open:
- raise portNotOpenError
+ raise PortNotOpenError()
termios.tcdrain(self.fd)
def reset_input_buffer(self):
"""Clear input buffer, discarding all that is in the buffer."""
if not self.is_open:
- raise portNotOpenError
+ raise PortNotOpenError()
termios.tcflush(self.fd, termios.TCIFLUSH)
def reset_output_buffer(self):
@@ -623,7 +666,7 @@
that is in the buffer.
"""
if not self.is_open:
- raise portNotOpenError
+ raise PortNotOpenError()
termios.tcflush(self.fd, termios.TCOFLUSH)
def send_break(self, duration=0.25):
@@ -632,18 +675,9 @@
duration.
"""
if not self.is_open:
- raise portNotOpenError
+ raise PortNotOpenError()
termios.tcsendbreak(self.fd, int(duration / 0.25))
- def _update_break_state(self):
- """\
- Set break: Controls TXD. When active, no transmitting is possible.
- """
- if self._break_state:
- fcntl.ioctl(self.fd, TIOCSBRK)
- else:
- fcntl.ioctl(self.fd, TIOCCBRK)
-
def _update_rts_state(self):
"""Set terminal status line: Request To Send"""
if self._rts_state:
@@ -662,7 +696,7 @@
def cts(self):
"""Read terminal status line: Clear To Send"""
if not self.is_open:
- raise portNotOpenError
+ raise PortNotOpenError()
s = fcntl.ioctl(self.fd, TIOCMGET, TIOCM_zero_str)
return struct.unpack('I', s)[0] & TIOCM_CTS != 0
@@ -670,7 +704,7 @@
def dsr(self):
"""Read terminal status line: Data Set Ready"""
if not self.is_open:
- raise portNotOpenError
+ raise PortNotOpenError()
s = fcntl.ioctl(self.fd, TIOCMGET, TIOCM_zero_str)
return struct.unpack('I', s)[0] & TIOCM_DSR != 0
@@ -678,7 +712,7 @@
def ri(self):
"""Read terminal status line: Ring Indicator"""
if not self.is_open:
- raise portNotOpenError
+ raise PortNotOpenError()
s = fcntl.ioctl(self.fd, TIOCMGET, TIOCM_zero_str)
return struct.unpack('I', s)[0] & TIOCM_RI != 0
@@ -686,7 +720,7 @@
def cd(self):
"""Read terminal status line: Carrier Detect"""
if not self.is_open:
- raise portNotOpenError
+ raise PortNotOpenError()
s = fcntl.ioctl(self.fd, TIOCMGET, TIOCM_zero_str)
return struct.unpack('I', s)[0] & TIOCM_CD != 0
@@ -705,7 +739,7 @@
WARNING: this function is not portable to different platforms!
"""
if not self.is_open:
- raise portNotOpenError
+ raise PortNotOpenError()
return self.fd
def set_input_flow_control(self, enable=True):
@@ -715,7 +749,7 @@
WARNING: this function is not portable to different platforms!
"""
if not self.is_open:
- raise portNotOpenError
+ raise PortNotOpenError()
if enable:
termios.tcflow(self.fd, termios.TCION)
else:
@@ -728,7 +762,7 @@
WARNING: this function is not portable to different platforms!
"""
if not self.is_open:
- raise portNotOpenError
+ raise PortNotOpenError()
if enable:
termios.tcflow(self.fd, termios.TCOON)
else:
@@ -754,7 +788,7 @@
until the requested number of bytes is read.
"""
if not self.is_open:
- raise portNotOpenError
+ raise PortNotOpenError()
read = bytearray()
timeout = Timeout(self._timeout)
poll = select.poll()
@@ -831,7 +865,7 @@
until the requested number of bytes is read.
"""
if not self.is_open:
- raise portNotOpenError
+ raise PortNotOpenError()
read = bytearray()
while len(read) < size:
buf = os.read(self.fd, size - len(read))
diff --git a/serial/serialutil.py b/serial/serialutil.py
index e847a6a..8c5ccfe 100644
--- a/serial/serialutil.py
+++ b/serial/serialutil.py
@@ -97,8 +97,10 @@
"""Write timeouts give an exception"""
-writeTimeoutError = SerialTimeoutException('Write timeout')
-portNotOpenError = SerialException('Attempting to use a port that is not open')
+class PortNotOpenError(SerialException):
+ """Port is not open"""
+ def __init__(self):
+ super(PortNotOpenError, self).__init__('Attempting to use a port that is not open')
class Timeout(object):
@@ -559,7 +561,7 @@
# context manager
def __enter__(self):
- if not self.is_open:
+ if self._port is not None and not self.is_open:
self.open()
return self
@@ -574,7 +576,7 @@
duration.
"""
if not self.is_open:
- raise portNotOpenError
+ raise PortNotOpenError()
self.break_condition = True
time.sleep(duration)
self.break_condition = False
@@ -649,19 +651,19 @@
"""
return self.read(self.in_waiting)
- def read_until(self, terminator=LF, size=None):
+ def read_until(self, expected=LF, size=None):
"""\
- Read until a termination sequence is found ('\n' by default), the size
+ Read until an expected sequence is found ('\n' by default), the size
is exceeded or until timeout occurs.
"""
- lenterm = len(terminator)
+ lenterm = len(expected)
line = bytearray()
timeout = Timeout(self._timeout)
while True:
c = self.read(1)
if c:
line += c
- if line[-lenterm:] == terminator:
+ if line[-lenterm:] == expected:
break
if size is not None and len(line) >= size:
break
diff --git a/serial/serialwin32.py b/serial/serialwin32.py
index 74bfd05..54d3e12 100644
--- a/serial/serialwin32.py
+++ b/serial/serialwin32.py
@@ -17,7 +17,7 @@
from serial import win32
import serial
-from serial.serialutil import SerialBase, SerialException, to_bytes, portNotOpenError, writeTimeoutError
+from serial.serialutil import SerialBase, SerialException, to_bytes, PortNotOpenError, SerialTimeoutException
class Serial(SerialBase):
@@ -266,7 +266,7 @@
until the requested number of bytes is read.
"""
if not self.is_open:
- raise portNotOpenError
+ raise PortNotOpenError()
if size > 0:
win32.ResetEvent(self._overlapped_read.hEvent)
flags = win32.DWORD()
@@ -303,7 +303,7 @@
def write(self, data):
"""Output the given byte string over the serial port."""
if not self.is_open:
- raise portNotOpenError
+ raise PortNotOpenError()
#~ if not isinstance(data, (bytes, bytearray)):
#~ raise TypeError('expected %s or bytearray, got %s' % (bytes, type(data)))
# convert data (needed in case of memoryview instance: Py 3.1 io lib), ctypes doesn't like memoryview
@@ -322,7 +322,7 @@
if win32.GetLastError() == win32.ERROR_OPERATION_ABORTED:
return n.value # canceled IO is no error
if n.value != len(data):
- raise writeTimeoutError
+ raise SerialTimeoutException('Write timeout')
return n.value
else:
errorcode = win32.ERROR_SUCCESS if success else win32.GetLastError()
@@ -351,7 +351,7 @@
def reset_input_buffer(self):
"""Clear input buffer, discarding all that is in the buffer."""
if not self.is_open:
- raise portNotOpenError
+ raise PortNotOpenError()
win32.PurgeComm(self._port_handle, win32.PURGE_RXCLEAR | win32.PURGE_RXABORT)
def reset_output_buffer(self):
@@ -360,13 +360,13 @@
that is in the buffer.
"""
if not self.is_open:
- raise portNotOpenError
+ raise PortNotOpenError()
win32.PurgeComm(self._port_handle, win32.PURGE_TXCLEAR | win32.PURGE_TXABORT)
def _update_break_state(self):
"""Set break: Controls TXD. When active, to transmitting is possible."""
if not self.is_open:
- raise portNotOpenError
+ raise PortNotOpenError()
if self._break_state:
win32.SetCommBreak(self._port_handle)
else:
@@ -388,7 +388,7 @@
def _GetCommModemStatus(self):
if not self.is_open:
- raise portNotOpenError
+ raise PortNotOpenError()
stat = win32.DWORD()
win32.GetCommModemStatus(self._port_handle, ctypes.byref(stat))
return stat.value
@@ -418,7 +418,7 @@
def set_buffer_size(self, rx_size=4096, tx_size=None):
"""\
Recommend a buffer size to the driver (device driver can ignore this
- value). Must be called before the port is opened.
+ value). Must be called after the port is opened.
"""
if tx_size is None:
tx_size = rx_size
@@ -432,7 +432,7 @@
WARNING: this function is not portable to different platforms!
"""
if not self.is_open:
- raise portNotOpenError
+ raise PortNotOpenError()
if enable:
win32.EscapeCommFunction(self._port_handle, win32.SETXON)
else:
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 @@
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 @@
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_common.py b/serial/tools/list_ports_common.py
index 8a1b625..617f3dc 100644
--- a/serial/tools/list_ports_common.py
+++ b/serial/tools/list_ports_common.py
@@ -77,6 +77,9 @@
def __eq__(self, other):
return isinstance(other, ListPortInfo) and self.device == other.device
+ def __hash__(self):
+ return hash(self.device)
+
def __lt__(self, other):
if not isinstance(other, ListPortInfo):
raise TypeError('unorderable types: {}() and {}()'.format(
diff --git a/serial/tools/list_ports_osx.py b/serial/tools/list_ports_osx.py
index f46a820..34a7f5a 100644
--- a/serial/tools/list_ports_osx.py
+++ b/serial/tools/list_ports_osx.py
@@ -35,25 +35,40 @@
kCFAllocatorDefault = ctypes.c_void_p.in_dll(cf, "kCFAllocatorDefault")
kCFStringEncodingMacRoman = 0
+kCFStringEncodingUTF8 = 0x08000100
+
+# defined in `IOKit/usb/USBSpec.h`
+kUSBVendorString = 'USB Vendor Name'
+kUSBSerialNumberString = 'USB Serial Number'
+
+# `io_name_t` defined as `typedef char io_name_t[128];`
+# in `device/device_types.h`
+io_name_size = 128
+
+# defined in `mach/kern_return.h`
+KERN_SUCCESS = 0
+# kern_return_t defined as `typedef int kern_return_t;` in `mach/i386/kern_return.h`
+kern_return_t = ctypes.c_int
iokit.IOServiceMatching.restype = ctypes.c_void_p
iokit.IOServiceGetMatchingServices.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p]
-iokit.IOServiceGetMatchingServices.restype = ctypes.c_void_p
+iokit.IOServiceGetMatchingServices.restype = kern_return_t
iokit.IORegistryEntryGetParentEntry.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p]
+iokit.IOServiceGetMatchingServices.restype = kern_return_t
iokit.IORegistryEntryCreateCFProperty.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_uint32]
iokit.IORegistryEntryCreateCFProperty.restype = ctypes.c_void_p
iokit.IORegistryEntryGetPath.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p]
-iokit.IORegistryEntryGetPath.restype = ctypes.c_void_p
+iokit.IORegistryEntryGetPath.restype = kern_return_t
iokit.IORegistryEntryGetName.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
-iokit.IORegistryEntryGetName.restype = ctypes.c_void_p
+iokit.IORegistryEntryGetName.restype = kern_return_t
iokit.IOObjectGetClass.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
-iokit.IOObjectGetClass.restype = ctypes.c_void_p
+iokit.IOObjectGetClass.restype = kern_return_t
iokit.IOObjectRelease.argtypes = [ctypes.c_void_p]
@@ -64,6 +79,9 @@
cf.CFStringGetCStringPtr.argtypes = [ctypes.c_void_p, ctypes.c_uint32]
cf.CFStringGetCStringPtr.restype = ctypes.c_char_p
+cf.CFStringGetCString.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_long, ctypes.c_uint32]
+cf.CFStringGetCString.restype = ctypes.c_bool
+
cf.CFNumberGetValue.argtypes = [ctypes.c_void_p, ctypes.c_uint32, ctypes.c_void_p]
cf.CFNumberGetValue.restype = ctypes.c_void_p
@@ -88,8 +106,8 @@
"""
key = cf.CFStringCreateWithCString(
kCFAllocatorDefault,
- property.encode("mac_roman"),
- kCFStringEncodingMacRoman)
+ property.encode("utf-8"),
+ kCFStringEncodingUTF8)
CFContainer = iokit.IORegistryEntryCreateCFProperty(
device_type,
@@ -101,7 +119,12 @@
if CFContainer:
output = cf.CFStringGetCStringPtr(CFContainer, 0)
if output is not None:
- output = output.decode('mac_roman')
+ output = output.decode('utf-8')
+ else:
+ buffer = ctypes.create_string_buffer(io_name_size);
+ success = cf.CFStringGetCString(CFContainer, ctypes.byref(buffer), io_name_size, kCFStringEncodingUTF8)
+ if success:
+ output = buffer.value.decode('utf-8')
cf.CFRelease(CFContainer)
return output
@@ -118,8 +141,8 @@
"""
key = cf.CFStringCreateWithCString(
kCFAllocatorDefault,
- property.encode("mac_roman"),
- kCFStringEncodingMacRoman)
+ property.encode("utf-8"),
+ kCFStringEncodingUTF8)
CFContainer = iokit.IORegistryEntryCreateCFProperty(
device_type,
@@ -137,12 +160,19 @@
return number.value
return None
-
def IORegistryEntryGetName(device):
- pathname = ctypes.create_string_buffer(100) # TODO: Is this ok?
- iokit.IOObjectGetClass(device, ctypes.byref(pathname))
- return pathname.value
+ devicename = ctypes.create_string_buffer(io_name_size);
+ res = iokit.IORegistryEntryGetName(device, ctypes.byref(devicename))
+ if res != KERN_SUCCESS:
+ return None
+ # this works in python2 but may not be valid. Also I don't know if
+ # this encoding is guaranteed. It may be dependent on system locale.
+ return devicename.value.decode('utf-8')
+def IOObjectGetClass(device):
+ classname = ctypes.create_string_buffer(io_name_size)
+ iokit.IOObjectGetClass(device, ctypes.byref(classname))
+ return classname.value
def GetParentDeviceByType(device, parent_type):
""" Find the first parent of a device that implements the parent_type
@@ -150,15 +180,15 @@
@return Pointer to the parent type, or None if it was not found.
"""
# First, try to walk up the IOService tree to find a parent of this device that is a IOUSBDevice.
- parent_type = parent_type.encode('mac_roman')
- while IORegistryEntryGetName(device) != parent_type:
+ parent_type = parent_type.encode('utf-8')
+ while IOObjectGetClass(device) != parent_type:
parent = ctypes.c_void_p()
response = iokit.IORegistryEntryGetParentEntry(
device,
- "IOService".encode("mac_roman"),
+ "IOService".encode("utf-8"),
ctypes.byref(parent))
# If we weren't able to find a parent for the device, we're done.
- if response != 0:
+ if response != KERN_SUCCESS:
return None
device = parent
return device
@@ -172,7 +202,7 @@
iokit.IOServiceGetMatchingServices(
kIOMasterPortDefault,
- iokit.IOServiceMatching(service_type.encode('mac_roman')),
+ iokit.IOServiceMatching(service_type.encode('utf-8')),
ctypes.byref(serial_port_iterator))
services = []
@@ -246,9 +276,12 @@
# fetch some useful informations from properties
info.vid = get_int_property(usb_device, "idVendor", kCFNumberSInt16Type)
info.pid = get_int_property(usb_device, "idProduct", kCFNumberSInt16Type)
- info.serial_number = get_string_property(usb_device, "USB Serial Number")
- info.product = get_string_property(usb_device, "USB Product Name") or 'n/a'
- info.manufacturer = get_string_property(usb_device, "USB Vendor Name")
+ info.serial_number = get_string_property(usb_device, kUSBSerialNumberString)
+ # We know this is a usb device, so the
+ # IORegistryEntryName should always be aliased to the
+ # usb product name string descriptor.
+ info.product = IORegistryEntryGetName(usb_device) or 'n/a'
+ info.manufacturer = get_string_property(usb_device, kUSBVendorString)
locationID = get_int_property(usb_device, "locationID", kCFNumberSInt32Type)
info.location = location_to_string(locationID)
info.interface = search_for_locationID_in_interfaces(serial_interfaces, locationID)
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.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 @@
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 @@
# 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_cp2110.py b/serial/urlhandler/protocol_cp2110.py
new file mode 100644
index 0000000..44ad4eb
--- /dev/null
+++ b/serial/urlhandler/protocol_cp2110.py
@@ -0,0 +1,258 @@
+#! python
+#
+# Backend for Silicon Labs CP2110/4 HID-to-UART devices.
+#
+# This file is part of pySerial. https://github.com/pyserial/pyserial
+# (C) 2001-2015 Chris Liechti <cliechti@gmx.net>
+# (C) 2019 Google LLC
+#
+# SPDX-License-Identifier: BSD-3-Clause
+
+# This backend implements support for HID-to-UART devices manufactured
+# by Silicon Labs and marketed as CP2110 and CP2114. The
+# implementation is (mostly) OS-independent and in userland. It relies
+# on cython-hidapi (https://github.com/trezor/cython-hidapi).
+
+# The HID-to-UART protocol implemented by CP2110/4 is described in the
+# AN434 document from Silicon Labs:
+# https://www.silabs.com/documents/public/application-notes/AN434-CP2110-4-Interface-Specification.pdf
+
+# TODO items:
+
+# - rtscts support is configured for hardware flow control, but the
+# signaling is missing (AN434 suggests this is done through GPIO).
+# - Cancelling reads and writes is not supported.
+# - Baudrate validation is not implemented, as it depends on model and configuration.
+
+import struct
+import threading
+
+try:
+ import urlparse
+except ImportError:
+ import urllib.parse as urlparse
+
+try:
+ import Queue
+except ImportError:
+ import queue as Queue
+
+import hid # hidapi
+
+import serial
+from serial.serialutil import SerialBase, SerialException, PortNotOpenError, to_bytes, Timeout
+
+
+# Report IDs and related constant
+_REPORT_GETSET_UART_ENABLE = 0x41
+_DISABLE_UART = 0x00
+_ENABLE_UART = 0x01
+
+_REPORT_SET_PURGE_FIFOS = 0x43
+_PURGE_TX_FIFO = 0x01
+_PURGE_RX_FIFO = 0x02
+
+_REPORT_GETSET_UART_CONFIG = 0x50
+
+_REPORT_SET_TRANSMIT_LINE_BREAK = 0x51
+_REPORT_SET_STOP_LINE_BREAK = 0x52
+
+
+class Serial(SerialBase):
+ # This is not quite correct. AN343 specifies that the minimum
+ # baudrate is different between CP2110 and CP2114, and it's halved
+ # when using non-8-bit symbols.
+ BAUDRATES = (300, 375, 600, 1200, 1800, 2400, 4800, 9600, 19200,
+ 38400, 57600, 115200, 230400, 460800, 500000, 576000,
+ 921600, 1000000)
+
+ def __init__(self, *args, **kwargs):
+ self._hid_handle = None
+ self._read_buffer = None
+ self._thread = None
+ super(Serial, self).__init__(*args, **kwargs)
+
+ def open(self):
+ if self._port is None:
+ raise SerialException("Port must be configured before it can be used.")
+ if self.is_open:
+ raise SerialException("Port is already open.")
+
+ self._read_buffer = Queue.Queue()
+
+ self._hid_handle = hid.device()
+ try:
+ portpath = self.from_url(self.portstr)
+ self._hid_handle.open_path(portpath)
+ except OSError as msg:
+ raise SerialException(msg.errno, "could not open port {}: {}".format(self._port, msg))
+
+ try:
+ self._reconfigure_port()
+ except:
+ try:
+ self._hid_handle.close()
+ except:
+ pass
+ self._hid_handle = None
+ raise
+ else:
+ self.is_open = True
+ self._thread = threading.Thread(target=self._hid_read_loop)
+ self._thread.setDaemon(True)
+ self._thread.setName('pySerial CP2110 reader thread for {}'.format(self._port))
+ self._thread.start()
+
+ def from_url(self, url):
+ parts = urlparse.urlsplit(url)
+ if parts.scheme != "cp2110":
+ raise SerialException(
+ 'expected a string in the forms '
+ '"cp2110:///dev/hidraw9" or "cp2110://0001:0023:00": '
+ 'not starting with cp2110:// {{!r}}'.format(parts.scheme))
+ if parts.netloc: # cp2100://BUS:DEVICE:ENDPOINT, for libusb
+ return parts.netloc.encode('utf-8')
+ return parts.path.encode('utf-8')
+
+ def close(self):
+ self.is_open = False
+ if self._thread:
+ self._thread.join(1) # read timeout is 0.1
+ self._thread = None
+ self._hid_handle.close()
+ self._hid_handle = None
+
+ def _reconfigure_port(self):
+ parity_value = None
+ if self._parity == serial.PARITY_NONE:
+ parity_value = 0x00
+ elif self._parity == serial.PARITY_ODD:
+ parity_value = 0x01
+ elif self._parity == serial.PARITY_EVEN:
+ parity_value = 0x02
+ elif self._parity == serial.PARITY_MARK:
+ parity_value = 0x03
+ elif self._parity == serial.PARITY_SPACE:
+ parity_value = 0x04
+ else:
+ raise ValueError('Invalid parity: {!r}'.format(self._parity))
+
+ if self.rtscts:
+ flow_control_value = 0x01
+ else:
+ flow_control_value = 0x00
+
+ data_bits_value = None
+ if self._bytesize == 5:
+ data_bits_value = 0x00
+ elif self._bytesize == 6:
+ data_bits_value = 0x01
+ elif self._bytesize == 7:
+ data_bits_value = 0x02
+ elif self._bytesize == 8:
+ data_bits_value = 0x03
+ else:
+ raise ValueError('Invalid char len: {!r}'.format(self._bytesize))
+
+ stop_bits_value = None
+ if self._stopbits == serial.STOPBITS_ONE:
+ stop_bits_value = 0x00
+ elif self._stopbits == serial.STOPBITS_ONE_POINT_FIVE:
+ stop_bits_value = 0x01
+ elif self._stopbits == serial.STOPBITS_TWO:
+ stop_bits_value = 0x01
+ else:
+ raise ValueError('Invalid stop bit specification: {!r}'.format(self._stopbits))
+
+ configuration_report = struct.pack(
+ '>BLBBBB',
+ _REPORT_GETSET_UART_CONFIG,
+ self._baudrate,
+ parity_value,
+ flow_control_value,
+ data_bits_value,
+ stop_bits_value)
+
+ self._hid_handle.send_feature_report(configuration_report)
+
+ self._hid_handle.send_feature_report(
+ bytes((_REPORT_GETSET_UART_ENABLE, _ENABLE_UART)))
+ self._update_break_state()
+
+ @property
+ def in_waiting(self):
+ return self._read_buffer.qsize()
+
+ def reset_input_buffer(self):
+ if not self.is_open:
+ raise PortNotOpenError()
+ self._hid_handle.send_feature_report(
+ bytes((_REPORT_SET_PURGE_FIFOS, _PURGE_RX_FIFO)))
+ # empty read buffer
+ while self._read_buffer.qsize():
+ self._read_buffer.get(False)
+
+ def reset_output_buffer(self):
+ if not self.is_open:
+ raise PortNotOpenError()
+ self._hid_handle.send_feature_report(
+ bytes((_REPORT_SET_PURGE_FIFOS, _PURGE_TX_FIFO)))
+
+ def _update_break_state(self):
+ if not self._hid_handle:
+ raise PortNotOpenError()
+
+ if self._break_state:
+ self._hid_handle.send_feature_report(
+ bytes((_REPORT_SET_TRANSMIT_LINE_BREAK, 0)))
+ else:
+ # Note that while AN434 states "There are no data bytes in
+ # the payload other than the Report ID", either hidapi or
+ # Linux does not seem to send the report otherwise.
+ self._hid_handle.send_feature_report(
+ bytes((_REPORT_SET_STOP_LINE_BREAK, 0)))
+
+ def read(self, size=1):
+ if not self.is_open:
+ raise PortNotOpenError()
+
+ data = bytearray()
+ try:
+ timeout = Timeout(self._timeout)
+ while len(data) < size:
+ if self._thread is None:
+ raise SerialException('connection failed (reader thread died)')
+ buf = self._read_buffer.get(True, timeout.time_left())
+ if buf is None:
+ return bytes(data)
+ data += buf
+ if timeout.expired():
+ break
+ except Queue.Empty: # -> timeout
+ pass
+ return bytes(data)
+
+ def write(self, data):
+ if not self.is_open:
+ raise PortNotOpenError()
+ data = to_bytes(data)
+ tx_len = len(data)
+ while tx_len > 0:
+ to_be_sent = min(tx_len, 0x3F)
+ report = to_bytes([to_be_sent]) + data[:to_be_sent]
+ self._hid_handle.write(report)
+
+ data = data[to_be_sent:]
+ tx_len = len(data)
+
+ def _hid_read_loop(self):
+ try:
+ while self.is_open:
+ data = self._hid_handle.read(64, timeout_ms=100)
+ if not data:
+ continue
+ data_len = data.pop(0)
+ assert data_len == len(data)
+ self._read_buffer.put(bytearray(data))
+ finally:
+ self._thread = None
diff --git a/serial/urlhandler/protocol_loop.py b/serial/urlhandler/protocol_loop.py
index 985e7a7..526583f 100644
--- a/serial/urlhandler/protocol_loop.py
+++ b/serial/urlhandler/protocol_loop.py
@@ -27,7 +27,7 @@
except ImportError:
import Queue as queue
-from serial.serialutil import SerialBase, SerialException, to_bytes, iterbytes, writeTimeoutError, portNotOpenError
+from serial.serialutil import SerialBase, SerialException, to_bytes, iterbytes, SerialTimeoutException, PortNotOpenError
# map log level names to constants. used in from_url()
LOGGER_LEVELS = {
@@ -127,7 +127,7 @@
def in_waiting(self):
"""Return the number of bytes currently in the input buffer."""
if not self.is_open:
- raise portNotOpenError
+ raise PortNotOpenError()
if self.logger:
# attention the logged value can differ from return value in
# threaded environments...
@@ -141,7 +141,7 @@
until the requested number of bytes is read.
"""
if not self.is_open:
- raise portNotOpenError
+ raise PortNotOpenError()
if self._timeout is not None and self._timeout != 0:
timeout = time.time() + self._timeout
else:
@@ -181,7 +181,7 @@
"""
self._cancel_write = False
if not self.is_open:
- raise portNotOpenError
+ raise PortNotOpenError()
data = to_bytes(data)
# calculate aprox time that would be used to send the data
time_used_to_send = 10.0 * len(data) / self._baudrate
@@ -195,7 +195,7 @@
time_left -= 0.5
if self._cancel_write:
return 0 # XXX
- raise writeTimeoutError
+ raise SerialTimeoutException('Write timeout')
for byte in iterbytes(data):
self.queue.put(byte, timeout=self._write_timeout)
return len(data)
@@ -203,7 +203,7 @@
def reset_input_buffer(self):
"""Clear input buffer, discarding all that is in the buffer."""
if not self.is_open:
- raise portNotOpenError
+ raise PortNotOpenError()
if self.logger:
self.logger.info('reset_input_buffer()')
try:
@@ -218,7 +218,7 @@
discarding all that is in the buffer.
"""
if not self.is_open:
- raise portNotOpenError
+ raise PortNotOpenError()
if self.logger:
self.logger.info('reset_output_buffer()')
try:
@@ -249,7 +249,7 @@
def cts(self):
"""Read terminal status line: Clear To Send"""
if not self.is_open:
- raise portNotOpenError
+ raise PortNotOpenError()
if self.logger:
self.logger.info('CTS -> state of RTS ({!r})'.format(self._rts_state))
return self._rts_state
@@ -265,7 +265,7 @@
def ri(self):
"""Read terminal status line: Ring Indicator"""
if not self.is_open:
- raise portNotOpenError
+ raise PortNotOpenError()
if self.logger:
self.logger.info('returning dummy for RI')
return False
@@ -274,7 +274,7 @@
def cd(self):
"""Read terminal status line: Carrier Detect"""
if not self.is_open:
- raise portNotOpenError
+ raise PortNotOpenError()
if self.logger:
self.logger.info('returning dummy for CD')
return True
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..2888467 100644
--- a/serial/urlhandler/protocol_socket.py
+++ b/serial/urlhandler/protocol_socket.py
@@ -29,7 +29,7 @@
import urllib.parse as urlparse
from serial.serialutil import SerialBase, SerialException, to_bytes, \
- portNotOpenError, writeTimeoutError, Timeout
+ PortNotOpenError, SerialTimeoutException, Timeout
# map log level names to constants. used in from_url()
LOGGER_LEVELS = {
@@ -136,7 +136,7 @@
def in_waiting(self):
"""Return the number of bytes currently in the input buffer."""
if not self.is_open:
- raise portNotOpenError
+ raise PortNotOpenError()
# Poll the socket to see if it is ready for reading.
# If ready, at least one byte will be to read.
lr, lw, lx = select.select([self._socket], [], [], 0)
@@ -152,7 +152,7 @@
until the requested number of bytes is read.
"""
if not self.is_open:
- raise portNotOpenError
+ raise PortNotOpenError()
read = bytearray()
timeout = Timeout(self._timeout)
while len(read) < size:
@@ -193,7 +193,7 @@
closed.
"""
if not self.is_open:
- raise portNotOpenError
+ raise PortNotOpenError()
d = to_bytes(data)
tx_len = length = len(d)
@@ -209,10 +209,10 @@
# when timeout is set, use select to wait for being ready
# with the time left as timeout
if timeout.expired():
- raise writeTimeoutError
+ raise SerialTimeoutException('Write timeout')
_, ready, _ = select.select([], [self._socket], [], timeout.time_left())
if not ready:
- raise writeTimeoutError
+ raise SerialTimeoutException('Write timeout')
else:
assert timeout.time_left() is None
# wait for write operation
@@ -236,20 +236,21 @@
if e[0] not in (errno.EAGAIN, errno.EALREADY, errno.EWOULDBLOCK, errno.EINPROGRESS, errno.EINTR):
raise SerialException('write failed: {}'.format(e))
if not timeout.is_non_blocking and timeout.expired():
- raise writeTimeoutError
+ raise SerialTimeoutException('Write timeout')
return length - len(d)
def reset_input_buffer(self):
"""Clear input buffer, discarding all that is in the buffer."""
if not self.is_open:
- raise portNotOpenError
+ raise PortNotOpenError()
# just use recv to remove input, while there is some
ready = True
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
@@ -269,7 +270,7 @@
discarding all that is in the buffer.
"""
if not self.is_open:
- raise portNotOpenError
+ raise PortNotOpenError()
if self.logger:
self.logger.info('ignored reset_output_buffer')
@@ -279,7 +280,7 @@
duration.
"""
if not self.is_open:
- raise portNotOpenError
+ raise PortNotOpenError()
if self.logger:
self.logger.info('ignored send_break({!r})'.format(duration))
@@ -303,7 +304,7 @@
def cts(self):
"""Read terminal status line: Clear To Send"""
if not self.is_open:
- raise portNotOpenError
+ raise PortNotOpenError()
if self.logger:
self.logger.info('returning dummy for cts')
return True
@@ -312,7 +313,7 @@
def dsr(self):
"""Read terminal status line: Data Set Ready"""
if not self.is_open:
- raise portNotOpenError
+ raise PortNotOpenError()
if self.logger:
self.logger.info('returning dummy for dsr')
return True
@@ -321,7 +322,7 @@
def ri(self):
"""Read terminal status line: Ring Indicator"""
if not self.is_open:
- raise portNotOpenError
+ raise PortNotOpenError()
if self.logger:
self.logger.info('returning dummy for ri')
return False
@@ -330,7 +331,7 @@
def cd(self):
"""Read terminal status line: Carrier Detect"""
if not self.is_open:
- raise portNotOpenError
+ raise PortNotOpenError()
if self.logger:
self.logger.info('returning dummy for cd)')
return True
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.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 @@
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
diff --git a/setup.py b/setup.py
index 6e8b586..ea53643 100644
--- a/setup.py
+++ b/setup.py
@@ -97,4 +97,7 @@
],
platforms='any',
scripts=['serial/tools/miniterm.py'],
+ extras_require = {
+ 'cp2110': ['hidapi'],
+ },
)
diff --git a/test/handlers/protocol_test.py b/test/handlers/protocol_test.py
index f2e572f..c0cffa6 100644
--- a/test/handlers/protocol_test.py
+++ b/test/handlers/protocol_test.py
@@ -80,7 +80,7 @@
def inWaiting(self):
"""Return the number of characters currently in the input buffer."""
- if not self._isOpen: raise portNotOpenError
+ if not self._isOpen: raise PortNotOpenError()
if self.logger:
# set this one to debug as the function could be called often...
self.logger.debug('WARNING: inWaiting returns dummy value')
@@ -90,7 +90,7 @@
"""Read size bytes from the serial port. If a timeout is set it may
return less characters as requested. With no timeout it will block
until the requested number of bytes is read."""
- if not self._isOpen: raise portNotOpenError
+ if not self._isOpen: raise PortNotOpenError()
data = '123' # dummy data
return bytes(data)
@@ -98,73 +98,73 @@
"""Output the given string over the serial port. Can block if the
connection is blocked. May raise SerialException if the connection is
closed."""
- if not self._isOpen: raise portNotOpenError
+ if not self._isOpen: raise PortNotOpenError()
# nothing done
return len(data)
def flushInput(self):
"""Clear input buffer, discarding all that is in the buffer."""
- if not self._isOpen: raise portNotOpenError
+ if not self._isOpen: raise PortNotOpenError()
if self.logger:
self.logger.info('ignored flushInput')
def flushOutput(self):
"""Clear output buffer, aborting the current output and
discarding all that is in the buffer."""
- if not self._isOpen: raise portNotOpenError
+ if not self._isOpen: raise PortNotOpenError()
if self.logger:
self.logger.info('ignored flushOutput')
def sendBreak(self, duration=0.25):
"""Send break condition. Timed, returns to idle state after given
duration."""
- if not self._isOpen: raise portNotOpenError
+ if not self._isOpen: raise PortNotOpenError()
if self.logger:
self.logger.info('ignored sendBreak({!r})'.format(duration))
def setBreak(self, level=True):
"""Set break: Controls TXD. When active, to transmitting is
possible."""
- if not self._isOpen: raise portNotOpenError
+ if not self._isOpen: raise PortNotOpenError()
if self.logger:
self.logger.info('ignored setBreak({!r})'.format(level))
def setRTS(self, level=True):
"""Set terminal status line: Request To Send"""
- if not self._isOpen: raise portNotOpenError
+ if not self._isOpen: raise PortNotOpenError()
if self.logger:
self.logger.info('ignored setRTS({!r})'.format(level))
def setDTR(self, level=True):
"""Set terminal status line: Data Terminal Ready"""
- if not self._isOpen: raise portNotOpenError
+ if not self._isOpen: raise PortNotOpenError()
if self.logger:
self.logger.info('ignored setDTR({!r})'.format(level))
def getCTS(self):
"""Read terminal status line: Clear To Send"""
- if not self._isOpen: raise portNotOpenError
+ if not self._isOpen: raise PortNotOpenError()
if self.logger:
self.logger.info('returning dummy for getCTS()')
return True
def getDSR(self):
"""Read terminal status line: Data Set Ready"""
- if not self._isOpen: raise portNotOpenError
+ if not self._isOpen: raise PortNotOpenError()
if self.logger:
self.logger.info('returning dummy for getDSR()')
return True
def getRI(self):
"""Read terminal status line: Ring Indicator"""
- if not self._isOpen: raise portNotOpenError
+ if not self._isOpen: raise PortNotOpenError()
if self.logger:
self.logger.info('returning dummy for getRI()')
return False
def getCD(self):
"""Read terminal status line: Carrier Detect"""
- if not self._isOpen: raise portNotOpenError
+ if not self._isOpen: raise PortNotOpenError()
if self.logger:
self.logger.info('returning dummy for getCD()')
return True