summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNJDFan <rob.gaddi@gmail.com>2019-06-20 16:04:55 -0700
committerGitHub <noreply@github.com>2019-06-20 16:04:55 -0700
commit3215eb3088a4ca55c5217c2e99da5584c3ee02c1 (patch)
treec64894a3b91f219add4a296db8376d9b3b9bdcef
parent5c021d4bdab2297602b4459f75bef2e00e5ec9ab (diff)
parentacab9d2c0efb63323faebfd5e3405d77cd4b5617 (diff)
downloadpyserial-git-3215eb3088a4ca55c5217c2e99da5584c3ee02c1.tar.gz
Merge pull request #1 from pyserial/master
Catch up to the main fork
-rw-r--r--CHANGES.rst24
-rw-r--r--README.rst10
-rw-r--r--documentation/appendix.rst6
-rw-r--r--documentation/conf.py4
-rw-r--r--documentation/examples.rst8
-rw-r--r--documentation/index.rst1
-rw-r--r--documentation/pyserial.rst19
-rw-r--r--documentation/pyserial_api.rst68
-rw-r--r--documentation/shortintro.rst2
-rw-r--r--documentation/tools.rst47
-rw-r--r--documentation/url_handlers.rst9
-rwxr-xr-xexamples/port_publisher.py12
-rwxr-xr-xexamples/tcp_serial_redirect.py16
-rw-r--r--serial/__init__.py4
-rw-r--r--serial/rfc2217.py31
-rw-r--r--serial/rs485.py2
-rw-r--r--serial/serialcli.py2
-rw-r--r--serial/serialjava.py2
-rw-r--r--serial/serialposix.py74
-rw-r--r--serial/serialutil.py12
-rw-r--r--serial/serialwin32.py4
-rw-r--r--serial/threaded/__init__.py6
-rw-r--r--serial/tools/hexlify_codec.py2
-rw-r--r--serial/tools/list_ports.py15
-rw-r--r--serial/tools/list_ports_common.py36
-rw-r--r--serial/tools/list_ports_linux.py38
-rw-r--r--serial/tools/list_ports_osx.py5
-rw-r--r--serial/tools/list_ports_posix.py34
-rw-r--r--serial/tools/list_ports_windows.py19
-rw-r--r--serial/tools/miniterm.py355
-rw-r--r--serial/urlhandler/protocol_alt.py2
-rw-r--r--serial/urlhandler/protocol_hwgrep.py2
-rw-r--r--serial/urlhandler/protocol_loop.py2
-rw-r--r--serial/urlhandler/protocol_rfc2217.py2
-rw-r--r--serial/urlhandler/protocol_socket.py44
-rw-r--r--serial/urlhandler/protocol_spy.py2
-rw-r--r--serial/win32.py12
-rw-r--r--setup.py3
-rwxr-xr-xtest/test_context.py49
39 files changed, 711 insertions, 274 deletions
diff --git a/CHANGES.rst b/CHANGES.rst
index 56d42b7..82a3b39 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -710,3 +710,27 @@ Bugfixes (posix):
Bugfixes (win32):
- [#194] spurious write fails with ERROR_SUCCESS
+
+
+Version 3.4 2017-07-22
+------------------------
+Improvements:
+
+- miniterm: suspend function (temporarily release port, :kbd:`Ctrl-T s`)
+- [#240] context manager automatically opens port on ``__enter__``
+- [#141] list_ports: add interface number to location string
+- [#225] protocol_socket: Retry if ``BlockingIOError`` occurs in
+ ``reset_input_buffer``.
+
+Bugfixes:
+
+- [#153] list_ports: option to include symlinked devices
+- [#237] list_ports: workaround for special characters in port names
+
+Bugfixes (posix):
+
+- allow calling cancel functions w/o error if port is closed
+- [#220] protocol_socket: sync error handling with posix version
+- [#227] posix: ignore more blocking errors and EINTR, timeout only
+ applies to blocking I/O
+- [#228] fix: port_publisher typo
diff --git a/README.rst b/README.rst
index ec8dced..ab3ce6f 100644
--- a/README.rst
+++ b/README.rst
@@ -12,7 +12,7 @@ appropriate backend.
- Project Homepage: https://github.com/pyserial/pyserial
- Download Page: https://pypi.python.org/pypi/pyserial
-BSD license, (C) 2001-2016 Chris Liechti <cliechti@gmx.net>
+BSD license, (C) 2001-2017 Chris Liechti <cliechti@gmx.net>
Documentation
@@ -36,6 +36,14 @@ Detailed information can be found in `documentation/pyserial.rst`_.
The usual setup.py for Python_ libraries is used for the source distribution.
Windows installers are also available (see download link above).
+or
+
+To install this package with conda run:
+
+``conda install -c conda-forge pyserial``
+
+conda builds are available for linux, mac and windows.
+
.. _`documentation/pyserial.rst`: https://github.com/pyserial/pyserial/blob/master/documentation/pyserial.rst#installation
.. _examples: https://github.com/pyserial/pyserial/blob/master/examples
.. _Python: http://python.org/
diff --git a/documentation/appendix.rst b/documentation/appendix.rst
index 80ade6d..57e8e2f 100644
--- a/documentation/appendix.rst
+++ b/documentation/appendix.rst
@@ -68,7 +68,7 @@ Application works when .py file is run, but fails when packaged (py2exe etc.)
used.
- :func:`serial.serial_for_url` does a dynamic lookup of protocol handlers
- at runtime. If this function is used, the desired handlers have to be
+ at runtime. If this function is used, the desired handlers have to be
included manually (e.g. 'serial.urlhandler.protocol_socket',
'serial.urlhandler.protocol_rfc2217', etc.). This can be done either with
the "includes" option in ``setup.py`` or by a dummy import in one of the
@@ -93,7 +93,7 @@ User supplied URL handlers
Support for Python 2.6 or earlier
Support for older Python releases than 2.7 will not return to pySerial 3.x.
- Python 2.7 is now many years old (released 2010). If you insist on using
+ Python 2.7 is now many years old (released 2010). If you insist on using
Python 2.6 or earlier, it is recommend to use pySerial `2.7`_
(or any 2.x version).
@@ -109,7 +109,7 @@ com0com - http://com0com.sourceforge.net/
License
=======
-Copyright (c) 2001-2016 Chris Liechti <cliechti@gmx.net>
+Copyright (c) 2001-2017 Chris Liechti <cliechti@gmx.net>
All Rights Reserved.
Redistribution and use in source and binary forms, with or without
diff --git a/documentation/conf.py b/documentation/conf.py
index 64605a6..df9d14e 100644
--- a/documentation/conf.py
+++ b/documentation/conf.py
@@ -45,9 +45,9 @@ copyright = u'2001-2017, Chris Liechti'
# built documents.
#
# The short X.Y version.
-version = '3.3'
+version = '3.4'
# The full version, including alpha/beta/rc tags.
-release = '3.3'
+release = '3.4'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
diff --git a/documentation/examples.rst b/documentation/examples.rst
index 787fd00..0430267 100644
--- a/documentation/examples.rst
+++ b/documentation/examples.rst
@@ -237,8 +237,10 @@ The project uses a number of unit test to verify the functionality. They all
need a loop back connector. The scripts itself contain more information. All
test scripts are contained in the directory ``test``.
-The unit tests are performed on port ``0`` unless a different device name or
-``rfc2217://`` URL is given on the command line (argv[1]).
+The unit tests are performed on port ``loop://`` unless a different device
+name or URL is given on the command line (``sys.argv[1]``). e.g. to run the
+test on an attached USB-serial converter ``hwgrep://USB`` could be used or
+the actual name such as ``/dev/ttyUSB0`` or ``COM1`` (depending on platform).
run_all_tests.py_
Collect all tests from all ``test*`` files and run them. By default, the
@@ -254,7 +256,7 @@ test_high_load.py_
Tests involving sending a lot of data.
test_readline.py_
- Tests involving readline.
+ Tests involving ``readline``.
test_iolib.py_
Tests involving the :mod:`io` library. Only available for Python 2.6 and
diff --git a/documentation/index.rst b/documentation/index.rst
index 10bf305..c3ca19d 100644
--- a/documentation/index.rst
+++ b/documentation/index.rst
@@ -1,4 +1,5 @@
.. pySerial documentation master file
+.. _welcome:
Welcome to pySerial's documentation
===================================
diff --git a/documentation/pyserial.rst b/documentation/pyserial.rst
index 602134d..8a1afa8 100644
--- a/documentation/pyserial.rst
+++ b/documentation/pyserial.rst
@@ -48,7 +48,7 @@ Requirements
============
- Python 2.7 or Python 3.4 and newer
-- If running on Windows: Something newer than WinXP
+- If running on Windows: Windows 7 or newer
- If running on Jython: "Java Communications" (JavaComm) or compatible
extension for Java
@@ -76,6 +76,21 @@ Using the `python`/`python3` executable of the desired version (2.7/3.x).
Developers also may be interested to get the source archive, because it
contains examples, tests and the this documentation.
+From Conda
+----------
+pySerial can be installed from Conda::
+
+ conda install pyserial
+
+ or
+
+ conda install -c conda-forge pyserial
+
+Currently the default conda channel will provide version 3.4 whereas the
+conda-forge channel provides the current 3.x version.
+
+Conda: https://www.continuum.io/downloads
+
From source (zip/tar.gz or checkout)
------------------------------------
Download the archive from http://pypi.python.org/pypi/pyserial or
@@ -93,7 +108,7 @@ There are also packaged versions for some Linux distributions:
- Debian/Ubuntu: "python-serial", "python3-serial"
- Fedora / RHEL / CentOS / EPEL: "pyserial"
- Arch Linux: "python-pyserial"
-- Gento: "dev-python/pyserial"
+- Gentoo: "dev-python/pyserial"
Note that some distributions may package an older version of pySerial.
These packages are created and maintained by developers working on
diff --git a/documentation/pyserial_api.rst b/documentation/pyserial_api.rst
index 65eeae5..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).
@@ -122,7 +122,7 @@ Native ports
Some OS and/or drivers may activate RTS and or DTR automatically,
as soon as the port is opened. There may be a glitch on RTS/DTR
- when :attr:`rts`` or :attr:`dtr` are set differently from their
+ when :attr:`rts` or :attr:`dtr` are set differently from their
default value (``True`` / active).
.. note::
@@ -157,6 +157,22 @@ Native ports
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.
@@ -205,7 +221,7 @@ Native ports
.. method:: reset_input_buffer()
- Flush input buffer, discarding all it's contents.
+ Flush input buffer, discarding all its contents.
.. versionchanged:: 3.0 renamed from ``flushInput()``
@@ -221,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.
@@ -243,7 +259,7 @@ Native ports
:type: bool
Set RTS line to specified logic level. It is possible to assign this
- value before opening the serial port, then the value is applied uppon
+ value before opening the serial port, then the value is applied upon
:meth:`open` (with restrictions, see :meth:`open`).
.. attribute:: dtr
@@ -253,7 +269,7 @@ Native ports
:type: bool
Set DTR line to specified logic level. It is possible to assign this
- value before opening the serial port, then the value is applied uppon
+ value before opening the serial port, then the value is applied upon
:meth:`open` (with restrictions, see :meth:`open`).
Read-only attributes:
@@ -465,6 +481,18 @@ Native ports
.. versionadded:: 2.5
+ .. method:: readline(size=-1)
+
+ Provided via :meth:`io.IOBase.readline`
+
+ .. method:: readlines(hint=-1)
+
+ Provided via :meth:`io.IOBase.readlines`
+
+ .. method:: writelines(lines)
+
+ Provided via :meth:`io.IOBase.writelines`
+
The port settings can be read and written as dictionary. The following
keys are supported: ``write_timeout``, ``inter_byte_timeout``,
``dsrdtr``, ``baudrate``, ``timeout``, ``parity``, ``bytesize``,
@@ -515,17 +543,22 @@ Native ports
>>> with serial.serial_for_url(port) as s:
... s.write(b'hello')
- Here no port argument is given, so it is not opened automatically:
+ The port is opened automatically:
- >>> with serial.Serial() as s:
- ... s.port = ...
- ... s.open()
+ >>> port = serial.Serial()
+ >>> port.port = '...'
+ >>> with port as s:
... s.write(b'hello')
+ Which also means that ``with`` statements can be used repeatedly,
+ each time opening and closing the port.
+
+ .. versionchanged:: 3.4 the port is automatically opened
+
.. method:: __exit__(exc_type, exc_val, exc_tb)
- Closes serial port.
+ Closes serial port (exceptions are not handled by ``__exit__``).
Platform specific methods.
@@ -618,7 +651,7 @@ Native ports
.. method:: isOpen()
- .. deprecated:: 3.0 see :attr:`is_open`
+ .. deprecated:: 3.0 see :attr:`is_open`
.. attribute:: writeTimeout
@@ -701,8 +734,10 @@ Native ports
Implementation detail: some attributes and functions are provided by the
-class :class:`SerialBase` and some by the platform specific class and
-others by the base class mentioned above.
+class :class:`serial.SerialBase` which inherits from :class:`io.RawIOBase`
+and some by the platform specific class and others by the base class
+mentioned above.
+
RS485 support
-------------
@@ -795,7 +830,6 @@ on regular serial ports (``serial.rs485`` needs to be imported).
-
:rfc:`2217` Network ports
-------------------------
@@ -1060,7 +1094,7 @@ Module functions and attributes
:returns: a generator that yields bytes
Some versions of Python (3.x) would return integers instead of bytes when
- looping over an instance of ``bytes``. This helper function ensures that
+ looping over an instance of ``bytes``. This helper function ensures that
bytes are returned.
.. versionadded:: 3.0
diff --git a/documentation/shortintro.rst b/documentation/shortintro.rst
index 02385d9..b9230e3 100644
--- a/documentation/shortintro.rst
+++ b/documentation/shortintro.rst
@@ -108,5 +108,5 @@ include entries that matched.
Accessing ports
---------------
pySerial includes a small console based terminal program called
-:ref:`miniterm`. It ca be started with ``python -m serial.tools.miniterm <port_name>``
+:ref:`miniterm`. It can be started with ``python -m serial.tools.miniterm <port_name>``
(use option ``-h`` to get a listing of all options).
diff --git a/documentation/tools.rst b/documentation/tools.rst
index 437a884..8ed7fce 100644
--- a/documentation/tools.rst
+++ b/documentation/tools.rst
@@ -12,8 +12,10 @@ This module can be executed to get a list of ports (``python -m
serial.tools.list_ports``). It also contains the following functions.
-.. function:: comports()
+.. function:: comports(include_links=False)
+ :param bool include_links: include symlinks under ``/dev`` when they point
+ to a serial port
:return: a list containing :class:`ListPortInfo` objects.
The function returns a list of :obj:`ListPortInfo` objects.
@@ -26,22 +28,36 @@ serial.tools.list_ports``). It also contains the following functions.
systems description and hardware ID will not be available
(``None``).
+ Under Linux, OSX and Windows, extended information will be available for
+ USB devices (e.g. the :attr:`ListPortInfo.hwid` string contains `VID:PID`,
+ `SER` (serial number), `LOCATION` (hierarchy), which makes them searchable
+ via :func:`grep`. The USB info is also available as attributes of
+ :attr:`ListPortInfo`.
+
+ If *include_links* is true, all devices under ``/dev`` are inspected and
+ tested if they are a link to a known serial port device. These entries
+ will include ``LINK`` in their ``hwid`` string. This implies that the same
+ device listed twice, once under its original name and once under linked
+ name.
+
:platform: Posix (/dev files)
:platform: Linux (/dev files, sysfs)
:platform: OSX (iokit)
:platform: Windows (setupapi, registry)
-.. function:: grep(regexp)
+.. function:: grep(regexp, include_links=False)
:param regexp: regular expression (see stdlib :mod:`re`)
+ :param bool include_links: include symlinks under ``/dev`` when they point
+ to a serial port
:return: an iterable that yields :class:`ListPortInfo` objects, see also
:func:`comports`.
- Search for ports using a regular expression. Port name, description and
- hardware ID are searched (case insensitive). The function returns an
- iterable that contains the same data that :func:`comports` generates, but
- includes only those entries that match the regexp.
+ Search for ports using a regular expression. Port ``name``,
+ ``description`` and ``hwid`` are searched (case insensitive). The function
+ returns an iterable that contains the same data that :func:`comports`
+ generates, but includes only those entries that match the regexp.
.. class:: ListPortInfo
@@ -109,18 +125,20 @@ serial.tools.list_ports``). It also contains the following functions.
Help for ``python -m serial.tools.list_ports``::
- usage: list_ports.py [-h] [-v] [-q] [-n N] [regexp]
+ usage: list_ports.py [-h] [-v] [-q] [-n N] [-s] [regexp]
Serial port enumeration
positional arguments:
- regexp only show ports that match this regex
+ regexp only show ports that match this regex
optional arguments:
- -h, --help show this help message and exit
- -v, --verbose show more messages
- -q, --quiet suppress all messages
- -n N only output the N-th entry
+ -h, --help show this help message and exit
+ -v, --verbose show more messages
+ -q, --quiet suppress all messages
+ -n N only output the N-th entry
+ -s, --include-links include entries that are symlinks to real devices
+
Examples:
@@ -256,6 +274,11 @@ Typing :kbd:`Ctrl+T Ctrl+H` when it is running shows the help text::
--- x X disable/enable software flow control
--- r R disable/enable hardware flow control
+:kbd:`Ctrl+T s` suspends the connection (port is opened) and reconnects when a
+key is pressed. This can be used to temporarily access the serial port with an
+other application, without exiting miniterm. If reconnecting fails it is
+also possible to exit (:kbd:`Ctrl+]`) or change the port (:kbd:`p`).
+
.. versionchanged:: 2.5
Added :kbd:`Ctrl+T` menu and added support for opening URLs.
.. versionchanged:: 2.6
diff --git a/documentation/url_handlers.rst b/documentation/url_handlers.rst
index adacc2e..b4f0da7 100644
--- a/documentation/url_handlers.rst
+++ b/documentation/url_handlers.rst
@@ -211,7 +211,8 @@ The spy output will be live in the second terminal window.
``alt://``
==========
-This handler allows to select alternate implementations of the native serial port.
+This handler allows to select alternate implementations of the native serial
+port.
Currently only the POSIX platform provides alternative implementations.
@@ -221,10 +222,10 @@ Currently only the POSIX platform provides alternative implementations.
disconnecting while it's in use (e.g. USB-serial unplugged).
``VTIMESerial``
- Implement timeout using ``VTIME``/``VMIN`` of tty device instead of using
- ``select``. This means that inter character timeout and overall timeout
+ Implement timeout using ``VTIME``/``VMIN`` of TTY device instead of using
+ ``select``. This means that inter character timeout and overall timeout
can not be used at the same time. Overall timeout is disabled when
- inter-character timeout is used. The error handling is degraded.
+ inter-character timeout is used. The error handling is degraded.
Examples::
diff --git a/examples/port_publisher.py b/examples/port_publisher.py
index ae07f77..eecc2a1 100755
--- a/examples/port_publisher.py
+++ b/examples/port_publisher.py
@@ -221,7 +221,7 @@ class Forwarder(ZeroconfService):
# escape outgoing data when needed (Telnet IAC (0xff) character)
if self.rfc2217:
data = serial.to_bytes(self.rfc2217.escape(data))
- self.buffer_ser2net += data
+ self.buffer_ser2net.extend(data)
else:
self.handle_serial_error()
except Exception as msg:
@@ -250,13 +250,15 @@ class Forwarder(ZeroconfService):
if data:
# Process RFC 2217 stuff when enabled
if self.rfc2217:
- data = serial.to_bytes(self.rfc2217.filter(data))
+ data = b''.join(self.rfc2217.filter(data))
# add data to buffer
- self.buffer_net2ser += data
+ self.buffer_net2ser.extend(data)
else:
# empty read indicates disconnection
self.handle_disconnect()
except socket.error:
+ if self.log is not None:
+ self.log.exception("{}: error reading...".format(self.device))
self.handle_socket_error()
def handle_socket_write(self):
@@ -267,6 +269,8 @@ class Forwarder(ZeroconfService):
# and remove the sent data from the buffer
self.buffer_ser2net = self.buffer_ser2net[count:]
except socket.error:
+ if self.log is not None:
+ self.log.exception("{}: error writing...".format(self.device))
self.handle_socket_error()
def handle_socket_error(self):
@@ -465,7 +469,7 @@ terminated, it waits for the next connect.
if pid > 0:
# exit from second parent, save eventual PID before
if args.pidfile is not None:
- open(args.pidfile, 'w').write("{}".formt(pid))
+ open(args.pidfile, 'w').write("{}".format(pid))
sys.exit(0)
except OSError as e:
log.critical("fork #2 failed: {} ({})\n".format(e.errno, e.strerror))
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/__init__.py b/serial/__init__.py
index 64c43c1..dcd7c12 100644
--- a/serial/__init__.py
+++ b/serial/__init__.py
@@ -7,13 +7,15 @@
#
# SPDX-License-Identifier: BSD-3-Clause
+from __future__ import absolute_import
+
import sys
import importlib
from serial.serialutil import *
#~ SerialBase, SerialException, to_bytes, iterbytes
-__version__ = '3.3'
+__version__ = '3.4'
VERSION = __version__
diff --git a/serial/rfc2217.py b/serial/rfc2217.py
index a8bb006..d962c1e 100644
--- a/serial/rfc2217.py
+++ b/serial/rfc2217.py
@@ -58,6 +58,8 @@
# RFC).
# the order of the options is not relevant
+from __future__ import absolute_import
+
import logging
import socket
import struct
@@ -613,7 +615,10 @@ class Serial(SerialBase):
while len(data) < size:
if self._thread is None:
raise SerialException('connection failed (reader thread died)')
- data += self._read_buffer.get(True, timeout.time_left())
+ 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
@@ -738,8 +743,10 @@ class Serial(SerialBase):
# connection fails -> terminate loop
if self.logger:
self.logger.debug("socket error in reader thread: {}".format(e))
+ self._read_buffer.put(None)
break
if not data:
+ self._read_buffer.put(None)
break # lost connection
for byte in iterbytes(data):
if mode == M_NORMAL:
@@ -893,7 +900,7 @@ class Serial(SerialBase):
"""\
get last modem state (cached value. If value is "old", request a new
one. This cache helps that we don't issue to many requests when e.g. all
- status lines, one after the other is queried by the user (getCTS, getDSR
+ status lines, one after the other is queried by the user (CTS, DSR
etc.)
"""
# active modem state polling enabled? is the value fresh enough?
@@ -1008,10 +1015,10 @@ class PortManager(object):
send updates on changes.
"""
modemstate = (
- (self.serial.getCTS() and MODEMSTATE_MASK_CTS) |
- (self.serial.getDSR() and MODEMSTATE_MASK_DSR) |
- (self.serial.getRI() and MODEMSTATE_MASK_RI) |
- (self.serial.getCD() and MODEMSTATE_MASK_CD))
+ (self.serial.cts and MODEMSTATE_MASK_CTS) |
+ (self.serial.dsr and MODEMSTATE_MASK_DSR) |
+ (self.serial.ri and MODEMSTATE_MASK_RI) |
+ (self.serial.cd and MODEMSTATE_MASK_CD))
# check what has changed
deltas = modemstate ^ (self.last_modemstate or 0) # when last is None -> 0
if deltas & MODEMSTATE_MASK_CTS:
@@ -1233,12 +1240,12 @@ class PortManager(object):
self.logger.warning("requested break state - not implemented")
pass # XXX needs cached value
elif suboption[2:3] == SET_CONTROL_BREAK_ON:
- self.serial.setBreak(True)
+ self.serial.break_condition = True
if self.logger:
self.logger.info("changed BREAK to active")
self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_BREAK_ON)
elif suboption[2:3] == SET_CONTROL_BREAK_OFF:
- self.serial.setBreak(False)
+ self.serial.break_condition = False
if self.logger:
self.logger.info("changed BREAK to inactive")
self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_BREAK_OFF)
@@ -1247,12 +1254,12 @@ class PortManager(object):
self.logger.warning("requested DTR state - not implemented")
pass # XXX needs cached value
elif suboption[2:3] == SET_CONTROL_DTR_ON:
- self.serial.setDTR(True)
+ self.serial.dtr = True
if self.logger:
self.logger.info("changed DTR to active")
self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_DTR_ON)
elif suboption[2:3] == SET_CONTROL_DTR_OFF:
- self.serial.setDTR(False)
+ self.serial.dtr = False
if self.logger:
self.logger.info("changed DTR to inactive")
self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_DTR_OFF)
@@ -1262,12 +1269,12 @@ class PortManager(object):
pass # XXX needs cached value
#~ self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_RTS_ON)
elif suboption[2:3] == SET_CONTROL_RTS_ON:
- self.serial.setRTS(True)
+ self.serial.rts = True
if self.logger:
self.logger.info("changed RTS to active")
self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_RTS_ON)
elif suboption[2:3] == SET_CONTROL_RTS_OFF:
- self.serial.setRTS(False)
+ self.serial.rts = False
if self.logger:
self.logger.info("changed RTS to inactive")
self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_RTS_OFF)
diff --git a/serial/rs485.py b/serial/rs485.py
index 2939350..d7aff6f 100644
--- a/serial/rs485.py
+++ b/serial/rs485.py
@@ -13,6 +13,8 @@ serial ports (where supported).
NOTE: Some implementations may only support a subset of the settings.
"""
+from __future__ import absolute_import
+
import time
import serial
diff --git a/serial/serialcli.py b/serial/serialcli.py
index 0727a52..ddd0cdf 100644
--- a/serial/serialcli.py
+++ b/serial/serialcli.py
@@ -7,6 +7,8 @@
#
# SPDX-License-Identifier: BSD-3-Clause
+from __future__ import absolute_import
+
import System
import System.IO.Ports
from serial.serialutil import *
diff --git a/serial/serialjava.py b/serial/serialjava.py
index 7bd5b3e..9c920c5 100644
--- a/serial/serialjava.py
+++ b/serial/serialjava.py
@@ -7,6 +7,8 @@
#
# SPDX-License-Identifier: BSD-3-Clause
+from __future__ import absolute_import
+
from serial.serialutil import *
diff --git a/serial/serialposix.py b/serial/serialposix.py
index bb2fa03..507e2fe 100644
--- a/serial/serialposix.py
+++ b/serial/serialposix.py
@@ -26,6 +26,8 @@
# - aix (AIX) /dev/tty%d
+from __future__ import absolute_import
+
# pylint: disable=abstract-method
import errno
import fcntl
@@ -49,6 +51,9 @@ class PlatformSpecificBase(object):
def _set_rs485_mode(self, rs485_settings):
raise NotImplementedError('RS485 not supported on this platform')
+ def set_low_latency_mode(self, low_latency_settings):
+ raise NotImplementedError('Low latency not supported on this platform')
+
# some systems support an extra flag to enable the two in POSIX unsupported
# paritiy settings for MARK and SPACE
@@ -113,6 +118,24 @@ if plat[:5] == 'linux': # Linux (confirmed) # noqa
4000000: 0o010017
}
+ def set_low_latency_mode(self, low_latency_settings):
+ buf = array.array('i', [0] * 32)
+
+ try:
+ # get serial_struct
+ fcntl.ioctl(self.fd, termios.TIOCGSERIAL, buf)
+
+ # set or unset ASYNC_LOW_LATENCY flag
+ if low_latency_settings:
+ buf[4] |= 0x2000
+ else:
+ buf[4] &= ~0x2000
+
+ # set serial_struct
+ fcntl.ioctl(self.fd, termios.TIOCSSERIAL, buf)
+ except IOError as e:
+ raise ValueError('Failed to update ASYNC_LOW_LATENCY flag to {}: {}'.format(low_latency_settings, e))
+
def _set_special_baudrate(self, baudrate):
# right size is 44 on x86_64, allow for some growth
buf = array.array('i', [0] * 64)
@@ -503,24 +526,27 @@ class Serial(SerialBase, PlatformSpecific):
read.extend(buf)
except OSError as e:
# this is for Python 3.x where select.error is a subclass of
- # OSError ignore EAGAIN errors. all other errors are shown
- if e.errno != errno.EAGAIN and e.errno != errno.EINTR:
+ # OSError ignore BlockingIOErrors and EINTR. other errors are shown
+ # https://www.python.org/dev/peps/pep-0475.
+ if e.errno not in (errno.EAGAIN, errno.EALREADY, errno.EWOULDBLOCK, errno.EINPROGRESS, errno.EINTR):
raise SerialException('read failed: {}'.format(e))
except select.error as e:
# this is for Python 2.x
- # ignore EAGAIN errors. all other errors are shown
+ # ignore BlockingIOErrors and EINTR. all errors are shown
# see also http://www.python.org/dev/peps/pep-3151/#select
- if e[0] != errno.EAGAIN:
+ if e[0] not in (errno.EAGAIN, errno.EALREADY, errno.EWOULDBLOCK, errno.EINPROGRESS, errno.EINTR):
raise SerialException('read failed: {}'.format(e))
if timeout.expired():
break
return bytes(read)
def cancel_read(self):
- os.write(self.pipe_abort_read_w, b"x")
+ if self.is_open:
+ os.write(self.pipe_abort_read_w, b"x")
def cancel_write(self):
- os.write(self.pipe_abort_write_w, b"x")
+ if self.is_open:
+ os.write(self.pipe_abort_write_w, b"x")
def write(self, data):
"""Output the given byte string over the serial port."""
@@ -560,12 +586,20 @@ class Serial(SerialBase, PlatformSpecific):
tx_len -= n
except SerialException:
raise
- except OSError as v:
- if v.errno != errno.EAGAIN:
- raise SerialException('write failed: {}'.format(v))
- # still calculate and check timeout
- if timeout.expired():
- raise writeTimeoutError
+ 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
+ # https://www.python.org/dev/peps/pep-0475.
+ if e.errno not in (errno.EAGAIN, errno.EALREADY, errno.EWOULDBLOCK, errno.EINPROGRESS, errno.EINTR):
+ raise SerialException('write failed: {}'.format(e))
+ except select.error as e:
+ # this is for Python 2.x
+ # ignore BlockingIOErrors and EINTR. all errors are shown
+ # 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('write failed: {}'.format(e))
+ if not timeout.is_non_blocking and timeout.expired():
+ raise writeTimeoutError
return length - len(d)
def flush(self):
@@ -722,21 +756,28 @@ class PosixPollSerial(Serial):
if not self.is_open:
raise portNotOpenError
read = bytearray()
+ timeout = Timeout(self._timeout)
poll = select.poll()
poll.register(self.fd, select.POLLIN | select.POLLERR | select.POLLHUP | select.POLLNVAL)
+ poll.register(self.pipe_abort_read_r, select.POLLIN | select.POLLERR | select.POLLHUP | select.POLLNVAL)
if size > 0:
while len(read) < size:
# print "\tread(): size",size, "have", len(read) #debug
# wait until device becomes ready to read (or something fails)
- for fd, event in poll.poll(self._timeout * 1000):
+ for fd, event in poll.poll(None if timeout.is_infinite else (timeout.time_left() * 1000)):
+ if fd == self.pipe_abort_read_r:
+ break
if event & (select.POLLERR | select.POLLHUP | select.POLLNVAL):
raise SerialException('device reports error (poll)')
# we don't care if it is select.POLLIN or timeout, that's
# handled below
+ if fd == self.pipe_abort_read_r:
+ os.read(self.pipe_abort_read_r, 1000)
+ break
buf = os.read(self.fd, size - len(read))
read.extend(buf)
- if ((self._timeout is not None and self._timeout >= 0) or
- (self._inter_byte_timeout is not None and self._inter_byte_timeout > 0)) and not buf:
+ if timeout.expired() \
+ or (self._inter_byte_timeout is not None and self._inter_byte_timeout > 0) and not buf:
break # early abort on timeout
return bytes(read)
@@ -748,6 +789,9 @@ class VTIMESerial(Serial):
the error handling is degraded.
Overall timeout is disabled when inter-character timeout is used.
+
+ Note that this implementation does NOT support cancel_read(), it will
+ just ignore that.
"""
def _reconfigure_port(self, force_update=True):
diff --git a/serial/serialutil.py b/serial/serialutil.py
index e4df90f..2cce816 100644
--- a/serial/serialutil.py
+++ b/serial/serialutil.py
@@ -7,6 +7,8 @@
#
# SPDX-License-Identifier: BSD-3-Clause
+from __future__ import absolute_import
+
import io
import time
@@ -557,6 +559,8 @@ class SerialBase(io.RawIOBase):
# context manager
def __enter__(self):
+ if not self.is_open:
+ self.open()
return self
def __exit__(self, *args, **kwargs):
@@ -645,19 +649,19 @@ class SerialBase(io.RawIOBase):
"""
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 7b88999..bd1944c 100644
--- a/serial/serialwin32.py
+++ b/serial/serialwin32.py
@@ -9,6 +9,8 @@
#
# Initial patch to use ctypes by Giovanni Bajo <rasky@develer.com>
+from __future__ import absolute_import
+
# pylint: disable=invalid-name,too-few-public-methods
import ctypes
import time
@@ -416,7 +418,7 @@ class Serial(SerialBase):
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
diff --git a/serial/threaded/__init__.py b/serial/threaded/__init__.py
index 74b6924..b8940b6 100644
--- a/serial/threaded/__init__.py
+++ b/serial/threaded/__init__.py
@@ -9,6 +9,8 @@
"""\
Support threading with serial ports.
"""
+from __future__ import absolute_import
+
import serial
import threading
@@ -201,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:
@@ -214,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/hexlify_codec.py b/serial/tools/hexlify_codec.py
index 1371da2..bd8f6b0 100644
--- a/serial/tools/hexlify_codec.py
+++ b/serial/tools/hexlify_codec.py
@@ -18,6 +18,8 @@ Therefore decoding is binary to text and thus converting binary data to hex dump
"""
+from __future__ import absolute_import
+
import codecs
import serial
diff --git a/serial/tools/list_ports.py b/serial/tools/list_ports.py
index 2271dd1..0d7e3d4 100644
--- a/serial/tools/list_ports.py
+++ b/serial/tools/list_ports.py
@@ -16,6 +16,8 @@ Additionally a grep function is supplied that can be used to search for ports
based on their descriptions or hardware ID.
"""
+from __future__ import absolute_import
+
import sys
import os
import re
@@ -34,14 +36,14 @@ else:
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-def grep(regexp):
+def grep(regexp, include_links=False):
"""\
Search for ports using a regular expression. Port name, description and
hardware ID are searched. The function returns an iterable that returns the
same tuples as comport() would do.
"""
r = re.compile(regexp, re.I)
- for info in comports():
+ for info in comports(include_links):
port, desc, hwid = info
if r.search(port) or r.search(desc) or r.search(hwid):
yield info
@@ -73,6 +75,11 @@ def main():
type=int,
help='only output the N-th entry')
+ parser.add_argument(
+ '-s', '--include-links',
+ action='store_true',
+ help='include entries that are symlinks to real devices')
+
args = parser.parse_args()
hits = 0
@@ -80,9 +87,9 @@ def main():
if args.regexp:
if not args.quiet:
sys.stderr.write("Filtered list with regexp: {!r}\n".format(args.regexp))
- iterator = sorted(grep(args.regexp))
+ iterator = sorted(grep(args.regexp, include_links=args.include_links))
else:
- iterator = sorted(comports())
+ iterator = sorted(comports(include_links=args.include_links))
# list them
for n, (port, desc, hwid) in enumerate(iterator, 1):
if args.n is None or args.n == n:
diff --git a/serial/tools/list_ports_common.py b/serial/tools/list_ports_common.py
index df12939..617f3dc 100644
--- a/serial/tools/list_ports_common.py
+++ b/serial/tools/list_ports_common.py
@@ -7,7 +7,13 @@
# (C) 2015 Chris Liechti <cliechti@gmx.net>
#
# SPDX-License-Identifier: BSD-3-Clause
+
+from __future__ import absolute_import
+
import re
+import glob
+import os
+import os.path
def numsplit(text):
@@ -29,9 +35,9 @@ def numsplit(text):
class ListPortInfo(object):
"""Info collection base class for serial ports"""
- def __init__(self, device=None):
+ def __init__(self, device, skip_link_detection=False):
self.device = device
- self.name = None
+ self.name = os.path.basename(device)
self.description = 'n/a'
self.hwid = 'n/a'
# USB specific data
@@ -42,6 +48,9 @@ class ListPortInfo(object):
self.manufacturer = None
self.product = None
self.interface = None
+ # special handling for links
+ if not skip_link_detection and device is not None and os.path.islink(device):
+ self.hwid = 'LINK={}'.format(os.path.realpath(device))
def usb_description(self):
"""return a short string to name the port based on USB info"""
@@ -66,9 +75,16 @@ class ListPortInfo(object):
self.hwid = self.usb_info()
def __eq__(self, other):
- return self.device == other.device
+ 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(
+ type(self).__name__,
+ type(other).__name__))
return numsplit(self.device) < numsplit(other.device)
def __str__(self):
@@ -85,6 +101,20 @@ class ListPortInfo(object):
else:
raise IndexError('{} > 2'.format(index))
+
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+def list_links(devices):
+ """\
+ search all /dev devices and look for symlinks to known ports already
+ listed in devices.
+ """
+ links = []
+ for device in glob.glob('/dev/*'):
+ if os.path.islink(device) and os.path.realpath(device) in devices:
+ links.append(device)
+ return links
+
+
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# test
if __name__ == '__main__':
diff --git a/serial/tools/list_ports_linux.py b/serial/tools/list_ports_linux.py
index 0dfa81f..9346ae9 100644
--- a/serial/tools/list_ports_linux.py
+++ b/serial/tools/list_ports_linux.py
@@ -8,6 +8,8 @@
#
# SPDX-License-Identifier: BSD-3-Clause
+from __future__ import absolute_import
+
import glob
import os
from serial.tools import list_ports_common
@@ -18,7 +20,12 @@ class SysFS(list_ports_common.ListPortInfo):
def __init__(self, device):
super(SysFS, self).__init__(device)
- self.name = os.path.basename(device)
+ # special handling for links
+ if device is not None and os.path.islink(device):
+ device = os.path.realpath(device)
+ is_link = True
+ else:
+ is_link = False
self.usb_device_path = None
if os.path.exists('/sys/class/tty/{}/device'.format(self.name)):
self.device_path = os.path.realpath('/sys/class/tty/{}/device'.format(self.name))
@@ -28,17 +35,28 @@ class SysFS(list_ports_common.ListPortInfo):
self.subsystem = None
# check device type
if self.subsystem == 'usb-serial':
- self.usb_device_path = os.path.dirname(os.path.dirname(self.device_path))
+ self.usb_interface_path = os.path.dirname(self.device_path)
elif self.subsystem == 'usb':
- self.usb_device_path = os.path.dirname(self.device_path)
+ self.usb_interface_path = self.device_path
else:
- self.usb_device_path = None
+ self.usb_interface_path = None
# fill-in info for USB devices
- if self.usb_device_path is not None:
+ if self.usb_interface_path is not None:
+ self.usb_device_path = os.path.dirname(self.usb_interface_path)
+
+ try:
+ num_if = int(self.read_line(self.usb_device_path, 'bNumInterfaces'))
+ except ValueError:
+ num_if = 1
+
self.vid = int(self.read_line(self.usb_device_path, 'idVendor'), 16)
self.pid = int(self.read_line(self.usb_device_path, 'idProduct'), 16)
self.serial_number = self.read_line(self.usb_device_path, 'serial')
- self.location = os.path.basename(self.usb_device_path)
+ if num_if > 1: # multi interface devices like FT4232
+ self.location = os.path.basename(self.usb_interface_path)
+ else:
+ self.location = os.path.basename(self.usb_device_path)
+
self.manufacturer = self.read_line(self.usb_device_path, 'manufacturer')
self.product = self.read_line(self.usb_device_path, 'product')
self.interface = self.read_line(self.device_path, 'interface')
@@ -53,6 +71,9 @@ class SysFS(list_ports_common.ListPortInfo):
self.description = self.name
self.hwid = os.path.basename(self.device_path)
+ if is_link:
+ self.hwid += ' LINK={}'.format(device)
+
def read_line(self, *args):
"""\
Helper function to read a single line from a file.
@@ -67,13 +88,16 @@ class SysFS(list_ports_common.ListPortInfo):
return None
-def comports():
+def comports(include_links=False):
devices = glob.glob('/dev/ttyS*') # built-in serial ports
devices.extend(glob.glob('/dev/ttyUSB*')) # usb-serial with own driver
+ devices.extend(glob.glob('/dev/ttyXRUSB*')) # xr-usb-serial port exar (DELL Edge 3001)
devices.extend(glob.glob('/dev/ttyACM*')) # usb-serial with CDC-ACM profile
devices.extend(glob.glob('/dev/ttyAMA*')) # ARM internal port (raspi)
devices.extend(glob.glob('/dev/rfcomm*')) # BT serial devices
devices.extend(glob.glob('/dev/ttyAP*')) # Advantech multi-port serial controllers
+ if include_links:
+ devices.extend(list_ports_common.list_links(devices))
return [info
for info in [SysFS(d) for d in devices]
if info.subsystem != "platform"] # hide non-present internal serial ports
diff --git a/serial/tools/list_ports_osx.py b/serial/tools/list_ports_osx.py
index 1d57b96..f46a820 100644
--- a/serial/tools/list_ports_osx.py
+++ b/serial/tools/list_ports_osx.py
@@ -21,6 +21,8 @@
# Also see the 'IORegistryExplorer' for an idea of what we are actually searching
+from __future__ import absolute_import
+
import ctypes
import ctypes.util
@@ -227,7 +229,8 @@ def search_for_locationID_in_interfaces(serial_interfaces, locationID):
return None
-def comports():
+def comports(include_links=False):
+ # XXX include_links is currently ignored. are links in /dev even supported here?
# Scan for all iokit serial ports
services = GetIOServicesByType('IOSerialBSDClient')
ports = []
diff --git a/serial/tools/list_ports_posix.py b/serial/tools/list_ports_posix.py
index 6ea4db9..79bc8ed 100644
--- a/serial/tools/list_ports_posix.py
+++ b/serial/tools/list_ports_posix.py
@@ -16,6 +16,8 @@ As currently no method is known to get the second two strings easily, they are
currently just identical to the port name.
"""
+from __future__ import absolute_import
+
import glob
import sys
import os
@@ -34,48 +36,64 @@ elif plat == 'cygwin': # cygwin/win32
# cygwin accepts /dev/com* in many contexts
# (such as 'open' call, explicit 'ls'), but 'glob.glob'
# and bare 'ls' do not; so use /dev/ttyS* instead
- def comports():
+ def comports(include_links=False):
devices = glob.glob('/dev/ttyS*')
+ if include_links:
+ devices.extend(list_ports_common.list_links(devices))
return [list_ports_common.ListPortInfo(d) for d in devices]
elif plat[:7] == 'openbsd': # OpenBSD
- def comports():
+ def comports(include_links=False):
devices = glob.glob('/dev/cua*')
+ if include_links:
+ devices.extend(list_ports_common.list_links(devices))
return [list_ports_common.ListPortInfo(d) for d in devices]
elif plat[:3] == 'bsd' or plat[:7] == 'freebsd':
- def comports():
+ def comports(include_links=False):
devices = glob.glob('/dev/cua*[!.init][!.lock]')
+ if include_links:
+ devices.extend(list_ports_common.list_links(devices))
return [list_ports_common.ListPortInfo(d) for d in devices]
elif plat[:6] == 'netbsd': # NetBSD
- def comports():
+ def comports(include_links=False):
"""scan for available ports. return a list of device names."""
devices = glob.glob('/dev/dty*')
+ if include_links:
+ devices.extend(list_ports_common.list_links(devices))
return [list_ports_common.ListPortInfo(d) for d in devices]
elif plat[:4] == 'irix': # IRIX
- def comports():
+ def comports(include_links=False):
"""scan for available ports. return a list of device names."""
devices = glob.glob('/dev/ttyf*')
+ if include_links:
+ devices.extend(list_ports_common.list_links(devices))
return [list_ports_common.ListPortInfo(d) for d in devices]
elif plat[:2] == 'hp': # HP-UX (not tested)
- def comports():
+ def comports(include_links=False):
"""scan for available ports. return a list of device names."""
devices = glob.glob('/dev/tty*p0')
+ if include_links:
+ devices.extend(list_ports_common.list_links(devices))
return [list_ports_common.ListPortInfo(d) for d in devices]
elif plat[:5] == 'sunos': # Solaris/SunOS
- def comports():
+ def comports(include_links=False):
"""scan for available ports. return a list of device names."""
devices = glob.glob('/dev/tty*c')
+ if include_links:
+ devices.extend(list_ports_common.list_links(devices))
return [list_ports_common.ListPortInfo(d) for d in devices]
elif plat[:3] == 'aix': # AIX
- def comports():
+ def comports(include_links=False):
"""scan for available ports. return a list of device names."""
devices = glob.glob('/dev/tty*')
+ if include_links:
+ devices.extend(list_ports_common.list_links(devices))
return [list_ports_common.ListPortInfo(d) for d in devices]
else:
diff --git a/serial/tools/list_ports_windows.py b/serial/tools/list_ports_windows.py
index 93fa128..19b9499 100644
--- a/serial/tools/list_ports_windows.py
+++ b/serial/tools/list_ports_windows.py
@@ -8,6 +8,8 @@
#
# SPDX-License-Identifier: BSD-3-Clause
+from __future__ import absolute_import
+
# pylint: disable=invalid-name,too-few-public-methods
import re
import ctypes
@@ -113,7 +115,7 @@ RegCloseKey.argtypes = [HKEY]
RegCloseKey.restype = LONG
RegQueryValueEx = advapi32.RegQueryValueExW
-RegQueryValueEx.argtypes = [HKEY, LPCTSTR , LPDWORD, LPDWORD, LPBYTE, LPDWORD]
+RegQueryValueEx.argtypes = [HKEY, LPCTSTR, LPDWORD, LPDWORD, LPBYTE, LPDWORD]
RegQueryValueEx.restype = LONG
@@ -143,6 +145,7 @@ def iterate_comports():
# repeat for all possible GUIDs
for index in range(guids_size.value):
+ bInterfaceNumber = None
g_hdi = SetupDiGetClassDevs(
ctypes.byref(GUIDs[index]),
None,
@@ -205,18 +208,20 @@ def iterate_comports():
# stringify
szHardwareID_str = szHardwareID.value
- info = list_ports_common.ListPortInfo(port_name_buffer.value)
+ info = list_ports_common.ListPortInfo(port_name_buffer.value, skip_link_detection=True)
# 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}))?(\\(\w+))?', szHardwareID_str, re.I)
+ m = re.search(r'VID_([0-9a-f]{4})(&PID_([0-9a-f]{4}))?(&MI_(\d{2}))?(\\(\w+))?', 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):
- info.serial_number = m.group(5)
+ bInterfaceNumber = int(m.group(5))
+ if m.group(7):
+ info.serial_number = m.group(7)
# calculate a location string
loc_path_str = ctypes.create_unicode_buffer(250)
if SetupDiGetDeviceRegistryProperty(
@@ -238,6 +243,10 @@ def iterate_comports():
else:
location.append('-')
location.append(g.group(2))
+ if bInterfaceNumber is not None:
+ location.append(':{}.{}'.format(
+ 'x', # XXX how to determine correct bConfigurationValue?
+ bInterfaceNumber))
if location:
info.location = ''.join(location)
info.hwid = info.usb_info()
@@ -287,7 +296,7 @@ def iterate_comports():
SetupDiDestroyDeviceInfoList(g_hdi)
-def comports():
+def comports(include_links=False):
"""Return a list of info objects about serial ports"""
return list(iterate_comports())
diff --git a/serial/tools/miniterm.py b/serial/tools/miniterm.py
index 14182f0..3b8d5d2 100644
--- a/serial/tools/miniterm.py
+++ b/serial/tools/miniterm.py
@@ -3,10 +3,12 @@
# Very simple serial terminal
#
# This file is part of pySerial. https://github.com/pyserial/pyserial
-# (C)2002-2015 Chris Liechti <cliechti@gmx.net>
+# (C)2002-2017 Chris Liechti <cliechti@gmx.net>
#
# SPDX-License-Identifier: BSD-3-Clause
+from __future__ import absolute_import
+
import codecs
import os
import sys
@@ -275,12 +277,12 @@ class DebugIO(Transform):
"""Print what is sent and received"""
def rx(self, text):
- sys.stderr.write(' [RX:{}] '.format(repr(text)))
+ sys.stderr.write(' [RX:{!r}] '.format(text))
sys.stderr.flush()
return text
def tx(self, text):
- sys.stderr.write(' [TX:{}] '.format(repr(text)))
+ sys.stderr.write(' [TX:{!r}] '.format(text))
sys.stderr.flush()
return text
@@ -315,7 +317,7 @@ def ask_for_port():
sys.stderr.write('\n--- Available ports:\n')
ports = []
for n, (port, desc, hwid) in enumerate(sorted(comports()), 1):
- sys.stderr.write('--- {:2}: {:20} {}\n'.format(n, port, desc))
+ sys.stderr.write('--- {:2}: {:20} {!r}\n'.format(n, port, desc))
ports.append(port)
while True:
port = raw_input('--- Enter port index or full name: ')
@@ -347,8 +349,8 @@ class Miniterm(object):
self.eol = eol
self.filters = filters
self.update_transformations()
- self.exit_character = 0x1d # GS/CTRL+]
- self.menu_character = 0x14 # Menu: CTRL+T
+ self.exit_character = unichr(0x1d) # GS/CTRL+]
+ self.menu_character = unichr(0x14) # Menu: CTRL+T
self.alive = None
self._reader_alive = None
self.receiver_thread = None
@@ -502,25 +504,7 @@ class Miniterm(object):
if self.echo:
self.console.write(c)
elif c == '\x15': # CTRL+U -> upload file
- sys.stderr.write('\n--- File to upload: ')
- sys.stderr.flush()
- with self.console:
- filename = sys.stdin.readline().rstrip('\r\n')
- if filename:
- try:
- with open(filename, 'rb') as f:
- sys.stderr.write('--- Sending file {} ---\n'.format(filename))
- while True:
- block = f.read(1024)
- if not block:
- break
- self.serial.write(block)
- # Wait for output buffer to drain.
- self.serial.flush()
- sys.stderr.write('.') # Progress indicator.
- sys.stderr.write('\n--- File {} sent ---\n'.format(filename))
- except IOError as e:
- sys.stderr.write('--- ERROR opening file {}: {} ---\n'.format(filename, e))
+ self.upload_file()
elif c in '\x08hH?': # CTRL+H, h, H, ? -> Show help
sys.stderr.write(self.get_help_text())
elif c == '\x12': # CTRL+R -> Toggle RTS
@@ -536,24 +520,9 @@ class Miniterm(object):
self.echo = not self.echo
sys.stderr.write('--- local echo {} ---\n'.format('active' if self.echo else 'inactive'))
elif c == '\x06': # CTRL+F -> edit filters
- sys.stderr.write('\n--- Available Filters:\n')
- sys.stderr.write('\n'.join(
- '--- {:<10} = {.__doc__}'.format(k, v)
- for k, v in sorted(TRANSFORMATIONS.items())))
- sys.stderr.write('\n--- Enter new filter name(s) [{}]: '.format(' '.join(self.filters)))
- with self.console:
- new_filters = sys.stdin.readline().lower().split()
- if new_filters:
- for f in new_filters:
- if f not in TRANSFORMATIONS:
- sys.stderr.write('--- unknown filter: {}\n'.format(repr(f)))
- break
- else:
- self.filters = new_filters
- self.update_transformations()
- sys.stderr.write('--- filters: {}\n'.format(' '.join(self.filters)))
+ self.change_filter()
elif c == '\x0c': # CTRL+L -> EOL mode
- modes = list(EOL_TRANSFORMATIONS) # keys
+ modes = list(EOL_TRANSFORMATIONS) # keys
eol = modes.index(self.eol) + 1
if eol >= len(modes):
eol = 0
@@ -561,63 +530,17 @@ class Miniterm(object):
sys.stderr.write('--- EOL: {} ---\n'.format(self.eol.upper()))
self.update_transformations()
elif c == '\x01': # CTRL+A -> set encoding
- sys.stderr.write('\n--- Enter new encoding name [{}]: '.format(self.input_encoding))
- with self.console:
- new_encoding = sys.stdin.readline().strip()
- if new_encoding:
- try:
- codecs.lookup(new_encoding)
- except LookupError:
- sys.stderr.write('--- invalid encoding name: {}\n'.format(new_encoding))
- else:
- self.set_rx_encoding(new_encoding)
- self.set_tx_encoding(new_encoding)
- sys.stderr.write('--- serial input encoding: {}\n'.format(self.input_encoding))
- sys.stderr.write('--- serial output encoding: {}\n'.format(self.output_encoding))
+ self.change_encoding()
elif c == '\x09': # CTRL+I -> info
self.dump_port_settings()
#~ elif c == '\x01': # CTRL+A -> cycle escape mode
#~ elif c == '\x0c': # CTRL+L -> cycle linefeed mode
elif c in 'pP': # P -> change port
- with self.console:
- try:
- port = ask_for_port()
- except KeyboardInterrupt:
- port = None
- if port and port != self.serial.port:
- # reader thread needs to be shut down
- self._stop_reader()
- # save settings
- settings = self.serial.getSettingsDict()
- try:
- new_serial = serial.serial_for_url(port, do_not_open=True)
- # restore settings and open
- new_serial.applySettingsDict(settings)
- new_serial.rts = self.serial.rts
- new_serial.dtr = self.serial.dtr
- new_serial.open()
- new_serial.break_condition = self.serial.break_condition
- except Exception as e:
- sys.stderr.write('--- ERROR opening new port: {} ---\n'.format(e))
- new_serial.close()
- else:
- self.serial.close()
- self.serial = new_serial
- sys.stderr.write('--- Port changed to: {} ---\n'.format(self.serial.port))
- # and restart the reader thread
- self._start_reader()
+ self.change_port()
+ elif c in 'sS': # S -> suspend / open port temporarily
+ self.suspend_port()
elif c in 'bB': # B -> change baudrate
- sys.stderr.write('\n--- Baudrate: ')
- sys.stderr.flush()
- with self.console:
- backup = self.serial.baudrate
- try:
- self.serial.baudrate = int(sys.stdin.readline().strip())
- except ValueError as e:
- sys.stderr.write('--- ERROR setting baudrate: {} ---\n'.format(e))
- self.serial.baudrate = backup
- else:
- self.dump_port_settings()
+ self.change_baudrate()
elif c == '8': # 8 -> change to 8 bits
self.serial.bytesize = serial.EIGHTBITS
self.dump_port_settings()
@@ -657,6 +580,138 @@ class Miniterm(object):
else:
sys.stderr.write('--- unknown menu character {} --\n'.format(key_description(c)))
+ def upload_file(self):
+ """Ask user for filenname and send its contents"""
+ sys.stderr.write('\n--- File to upload: ')
+ sys.stderr.flush()
+ with self.console:
+ filename = sys.stdin.readline().rstrip('\r\n')
+ if filename:
+ try:
+ with open(filename, 'rb') as f:
+ sys.stderr.write('--- Sending file {} ---\n'.format(filename))
+ while True:
+ block = f.read(1024)
+ if not block:
+ break
+ self.serial.write(block)
+ # Wait for output buffer to drain.
+ self.serial.flush()
+ sys.stderr.write('.') # Progress indicator.
+ sys.stderr.write('\n--- File {} sent ---\n'.format(filename))
+ except IOError as e:
+ sys.stderr.write('--- ERROR opening file {}: {} ---\n'.format(filename, e))
+
+ def change_filter(self):
+ """change the i/o transformations"""
+ sys.stderr.write('\n--- Available Filters:\n')
+ sys.stderr.write('\n'.join(
+ '--- {:<10} = {.__doc__}'.format(k, v)
+ for k, v in sorted(TRANSFORMATIONS.items())))
+ sys.stderr.write('\n--- Enter new filter name(s) [{}]: '.format(' '.join(self.filters)))
+ with self.console:
+ new_filters = sys.stdin.readline().lower().split()
+ if new_filters:
+ for f in new_filters:
+ if f not in TRANSFORMATIONS:
+ sys.stderr.write('--- unknown filter: {!r}\n'.format(f))
+ break
+ else:
+ self.filters = new_filters
+ self.update_transformations()
+ sys.stderr.write('--- filters: {}\n'.format(' '.join(self.filters)))
+
+ def change_encoding(self):
+ """change encoding on the serial port"""
+ sys.stderr.write('\n--- Enter new encoding name [{}]: '.format(self.input_encoding))
+ with self.console:
+ new_encoding = sys.stdin.readline().strip()
+ if new_encoding:
+ try:
+ codecs.lookup(new_encoding)
+ except LookupError:
+ sys.stderr.write('--- invalid encoding name: {}\n'.format(new_encoding))
+ else:
+ self.set_rx_encoding(new_encoding)
+ self.set_tx_encoding(new_encoding)
+ sys.stderr.write('--- serial input encoding: {}\n'.format(self.input_encoding))
+ sys.stderr.write('--- serial output encoding: {}\n'.format(self.output_encoding))
+
+ def change_baudrate(self):
+ """change the baudrate"""
+ sys.stderr.write('\n--- Baudrate: ')
+ sys.stderr.flush()
+ with self.console:
+ backup = self.serial.baudrate
+ try:
+ self.serial.baudrate = int(sys.stdin.readline().strip())
+ except ValueError as e:
+ sys.stderr.write('--- ERROR setting baudrate: {} ---\n'.format(e))
+ self.serial.baudrate = backup
+ else:
+ self.dump_port_settings()
+
+ def change_port(self):
+ """Have a conversation with the user to change the serial port"""
+ with self.console:
+ try:
+ port = ask_for_port()
+ except KeyboardInterrupt:
+ port = None
+ if port and port != self.serial.port:
+ # reader thread needs to be shut down
+ self._stop_reader()
+ # save settings
+ settings = self.serial.getSettingsDict()
+ try:
+ new_serial = serial.serial_for_url(port, do_not_open=True)
+ # restore settings and open
+ new_serial.applySettingsDict(settings)
+ new_serial.rts = self.serial.rts
+ new_serial.dtr = self.serial.dtr
+ new_serial.open()
+ new_serial.break_condition = self.serial.break_condition
+ except Exception as e:
+ sys.stderr.write('--- ERROR opening new port: {} ---\n'.format(e))
+ new_serial.close()
+ else:
+ self.serial.close()
+ self.serial = new_serial
+ sys.stderr.write('--- Port changed to: {} ---\n'.format(self.serial.port))
+ # and restart the reader thread
+ self._start_reader()
+
+ def suspend_port(self):
+ """\
+ open port temporarily, allow reconnect, exit and port change to get
+ out of the loop
+ """
+ # reader thread needs to be shut down
+ self._stop_reader()
+ self.serial.close()
+ sys.stderr.write('\n--- Port closed: {} ---\n'.format(self.serial.port))
+ do_change_port = False
+ while not self.serial.is_open:
+ sys.stderr.write('--- Quit: {exit} | p: port change | any other key to reconnect ---\n'.format(
+ exit=key_description(self.exit_character)))
+ k = self.console.getkey()
+ if k == self.exit_character:
+ self.stop() # exit app
+ break
+ elif k in 'pP':
+ do_change_port = True
+ break
+ try:
+ self.serial.open()
+ except Exception as e:
+ sys.stderr.write('--- ERROR opening port: {} ---\n'.format(e))
+ if do_change_port:
+ self.change_port()
+ else:
+ # and restart the reader thread
+ self._start_reader()
+ sys.stderr.write('--- Port opened: {} ---\n'.format(self.serial.port))
+
def get_help_text(self):
"""return the help text"""
# help text, starts with blank line!
@@ -707,123 +762,130 @@ def main(default_port=None, default_baudrate=9600, default_rts=None, default_dtr
import argparse
parser = argparse.ArgumentParser(
- description="Miniterm - A simple terminal program for the serial port.")
+ description='Miniterm - A simple terminal program for the serial port.')
parser.add_argument(
- "port",
+ 'port',
nargs='?',
- help="serial port name ('-' to show port list)",
+ help='serial port name ("-" to show port list)',
default=default_port)
parser.add_argument(
- "baudrate",
+ 'baudrate',
nargs='?',
type=int,
- help="set baud rate, default: %(default)s",
+ help='set baud rate, default: %(default)s',
default=default_baudrate)
- group = parser.add_argument_group("port settings")
+ group = parser.add_argument_group('port settings')
group.add_argument(
- "--parity",
+ '--parity',
choices=['N', 'E', 'O', 'S', 'M'],
type=lambda c: c.upper(),
- help="set parity, one of {N E O S M}, default: N",
+ help='set parity, one of {N E O S M}, default: N',
default='N')
group.add_argument(
- "--rtscts",
- action="store_true",
- help="enable RTS/CTS flow control (default off)",
+ '--rtscts',
+ action='store_true',
+ help='enable RTS/CTS flow control (default off)',
default=False)
group.add_argument(
- "--xonxoff",
- action="store_true",
- help="enable software flow control (default off)",
+ '--xonxoff',
+ action='store_true',
+ help='enable software flow control (default off)',
default=False)
group.add_argument(
- "--rts",
+ '--rts',
type=int,
- help="set initial RTS line state (possible values: 0, 1)",
+ help='set initial RTS line state (possible values: 0, 1)',
default=default_rts)
group.add_argument(
- "--dtr",
+ '--dtr',
type=int,
- help="set initial DTR line state (possible values: 0, 1)",
+ help='set initial DTR line state (possible values: 0, 1)',
default=default_dtr)
group.add_argument(
- "--ask",
- action="store_true",
- help="ask again for port when open fails",
+ '--non-exclusive',
+ dest='exclusive',
+ action='store_false',
+ help='disable locking for native ports',
+ default=True)
+
+ group.add_argument(
+ '--ask',
+ action='store_true',
+ help='ask again for port when open fails',
default=False)
- group = parser.add_argument_group("data handling")
+ group = parser.add_argument_group('data handling')
group.add_argument(
- "-e", "--echo",
- action="store_true",
- help="enable local echo (default off)",
+ '-e', '--echo',
+ action='store_true',
+ help='enable local echo (default off)',
default=False)
group.add_argument(
- "--encoding",
- dest="serial_port_encoding",
- metavar="CODEC",
- help="set the encoding for the serial port (e.g. hexlify, Latin1, UTF-8), default: %(default)s",
+ '--encoding',
+ dest='serial_port_encoding',
+ metavar='CODEC',
+ help='set the encoding for the serial port (e.g. hexlify, Latin1, UTF-8), default: %(default)s',
default='UTF-8')
group.add_argument(
- "-f", "--filter",
- action="append",
- metavar="NAME",
- help="add text transformation",
+ '-f', '--filter',
+ action='append',
+ metavar='NAME',
+ help='add text transformation',
default=[])
group.add_argument(
- "--eol",
+ '--eol',
choices=['CR', 'LF', 'CRLF'],
type=lambda c: c.upper(),
- help="end of line mode",
+ help='end of line mode',
default='CRLF')
group.add_argument(
- "--raw",
- action="store_true",
- help="Do no apply any encodings/transformations",
+ '--raw',
+ action='store_true',
+ help='Do no apply any encodings/transformations',
default=False)
- group = parser.add_argument_group("hotkeys")
+ group = parser.add_argument_group('hotkeys')
group.add_argument(
- "--exit-char",
+ '--exit-char',
type=int,
metavar='NUM',
- help="Unicode of special character that is used to exit the application, default: %(default)s",
+ help='Unicode of special character that is used to exit the application, default: %(default)s',
default=0x1d) # GS/CTRL+]
group.add_argument(
- "--menu-char",
+ '--menu-char',
type=int,
metavar='NUM',
- help="Unicode code of special character that is used to control miniterm (menu), default: %(default)s",
+ help='Unicode code of special character that is used to control miniterm (menu), default: %(default)s',
default=0x14) # Menu: CTRL+T
- group = parser.add_argument_group("diagnostics")
+ group = parser.add_argument_group('diagnostics')
group.add_argument(
- "-q", "--quiet",
- action="store_true",
- help="suppress non-error messages",
+ '-q', '--quiet',
+ action='store_true',
+ help='suppress non-error messages',
default=False)
group.add_argument(
- "--develop",
- action="store_true",
- help="show Python traceback on error",
+ '--develop',
+ action='store_true',
+ help='show Python traceback on error',
default=False)
args = parser.parse_args()
@@ -876,9 +938,12 @@ def main(default_port=None, default_baudrate=9600, default_rts=None, default_dtr
sys.stderr.write('--- forcing RTS {}\n'.format('active' if args.rts else 'inactive'))
serial_instance.rts = args.rts
+ if isinstance(serial_instance, serial.Serial):
+ serial_instance.exclusive = args.exclusive
+
serial_instance.open()
except serial.SerialException as e:
- sys.stderr.write('could not open port {}: {}\n'.format(repr(args.port), e))
+ sys.stderr.write('could not open port {!r}: {}\n'.format(args.port, e))
if args.develop:
raise
if not args.ask:
@@ -914,7 +979,7 @@ def main(default_port=None, default_baudrate=9600, default_rts=None, default_dtr
except KeyboardInterrupt:
pass
if not args.quiet:
- sys.stderr.write("\n--- exit ---\n")
+ sys.stderr.write('\n--- exit ---\n')
miniterm.join()
miniterm.close()
diff --git a/serial/urlhandler/protocol_alt.py b/serial/urlhandler/protocol_alt.py
index c14a87e..2e666ca 100644
--- a/serial/urlhandler/protocol_alt.py
+++ b/serial/urlhandler/protocol_alt.py
@@ -16,6 +16,8 @@
# use poll based implementation on Posix (Linux):
# python -m serial.tools.miniterm alt:///dev/ttyUSB0?class=PosixPollSerial
+from __future__ import absolute_import
+
try:
import urlparse
except ImportError:
diff --git a/serial/urlhandler/protocol_hwgrep.py b/serial/urlhandler/protocol_hwgrep.py
index 49bbebe..1a288c9 100644
--- a/serial/urlhandler/protocol_hwgrep.py
+++ b/serial/urlhandler/protocol_hwgrep.py
@@ -20,6 +20,8 @@
# n=<N> pick the N'th entry instead of the first one (numbering starts at 1)
# skip_busy tries to open port to check if it is busy, fails on posix as ports are not locked!
+from __future__ import absolute_import
+
import serial
import serial.tools.list_ports
diff --git a/serial/urlhandler/protocol_loop.py b/serial/urlhandler/protocol_loop.py
index 7bf6cf9..985e7a7 100644
--- a/serial/urlhandler/protocol_loop.py
+++ b/serial/urlhandler/protocol_loop.py
@@ -13,6 +13,8 @@
# URL format: loop://[option[/option...]]
# options:
# - "debug" print diagnostic messages
+from __future__ import absolute_import
+
import logging
import numbers
import time
diff --git a/serial/urlhandler/protocol_rfc2217.py b/serial/urlhandler/protocol_rfc2217.py
index 1898803..8be310f 100644
--- a/serial/urlhandler/protocol_rfc2217.py
+++ b/serial/urlhandler/protocol_rfc2217.py
@@ -7,4 +7,6 @@
#
# SPDX-License-Identifier: BSD-3-Clause
+from __future__ import absolute_import
+
from serial.rfc2217 import Serial # noqa
diff --git a/serial/urlhandler/protocol_socket.py b/serial/urlhandler/protocol_socket.py
index a35cf75..36cdf1f 100644
--- a/serial/urlhandler/protocol_socket.py
+++ b/serial/urlhandler/protocol_socket.py
@@ -16,6 +16,8 @@
# options:
# - "debug" print diagnostic messages
+from __future__ import absolute_import
+
import errno
import logging
import select
@@ -170,14 +172,15 @@ class Serial(SerialBase):
read.extend(buf)
except OSError as e:
# this is for Python 3.x where select.error is a subclass of
- # OSError ignore EAGAIN errors. all other errors are shown
- if e.errno != errno.EAGAIN:
+ # OSError ignore BlockingIOErrors and EINTR. other errors are shown
+ # https://www.python.org/dev/peps/pep-0475.
+ if e.errno not in (errno.EAGAIN, errno.EALREADY, errno.EWOULDBLOCK, errno.EINPROGRESS, errno.EINTR):
raise SerialException('read failed: {}'.format(e))
except (select.error, socket.error) as e:
# this is for Python 2.x
- # ignore EAGAIN errors. all other errors are shown
+ # ignore BlockingIOErrors and EINTR. all errors are shown
# see also http://www.python.org/dev/peps/pep-3151/#select
- if e[0] != errno.EAGAIN:
+ if e[0] not in (errno.EAGAIN, errno.EALREADY, errno.EWOULDBLOCK, errno.EINPROGRESS, errno.EINTR):
raise SerialException('read failed: {}'.format(e))
if timeout.expired():
break
@@ -220,12 +223,20 @@ class Serial(SerialBase):
tx_len -= n
except SerialException:
raise
- except OSError as v:
- if v.errno != errno.EAGAIN:
- raise SerialException('write failed: {}'.format(v))
- # still calculate and check timeout
- if timeout.expired():
- raise writeTimeoutError
+ 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
+ # https://www.python.org/dev/peps/pep-0475.
+ if e.errno not in (errno.EAGAIN, errno.EALREADY, errno.EWOULDBLOCK, errno.EINPROGRESS, errno.EINTR):
+ raise SerialException('write failed: {}'.format(e))
+ except select.error as e:
+ # this is for Python 2.x
+ # ignore BlockingIOErrors and EINTR. all errors are shown
+ # 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('write failed: {}'.format(e))
+ if not timeout.is_non_blocking and timeout.expired():
+ raise writeTimeoutError
return length - len(d)
def reset_input_buffer(self):
@@ -241,15 +252,16 @@ class Serial(SerialBase):
self._socket.recv(4096)
except OSError as e:
# this is for Python 3.x where select.error is a subclass of
- # OSError ignore EAGAIN errors. all other errors are shown
- if e.errno != errno.EAGAIN:
- raise SerialException('reset_input_buffer failed: {}'.format(e))
+ # OSError ignore BlockingIOErrors and EINTR. other errors are shown
+ # https://www.python.org/dev/peps/pep-0475.
+ if e.errno not in (errno.EAGAIN, errno.EALREADY, errno.EWOULDBLOCK, errno.EINPROGRESS, errno.EINTR):
+ raise SerialException('read failed: {}'.format(e))
except (select.error, socket.error) as e:
# this is for Python 2.x
- # ignore EAGAIN errors. all other errors are shown
+ # ignore BlockingIOErrors and EINTR. all errors are shown
# see also http://www.python.org/dev/peps/pep-3151/#select
- if e[0] != errno.EAGAIN:
- raise SerialException('reset_input_buffer failed: {}'.format(e))
+ if e[0] not in (errno.EAGAIN, errno.EALREADY, errno.EWOULDBLOCK, errno.EINPROGRESS, errno.EINTR):
+ raise SerialException('read failed: {}'.format(e))
def reset_output_buffer(self):
"""\
diff --git a/serial/urlhandler/protocol_spy.py b/serial/urlhandler/protocol_spy.py
index 3479010..92aaa2e 100644
--- a/serial/urlhandler/protocol_spy.py
+++ b/serial/urlhandler/protocol_spy.py
@@ -20,6 +20,8 @@
# redirect output to an other terminal window on Posix (Linux):
# python -m serial.tools.miniterm spy:///dev/ttyUSB0?dev=/dev/pts/14\&color
+from __future__ import absolute_import
+
import sys
import time
diff --git a/serial/win32.py b/serial/win32.py
index 905ce0f..157f470 100644
--- a/serial/win32.py
+++ b/serial/win32.py
@@ -9,6 +9,8 @@
# pylint: disable=invalid-name,too-few-public-methods,protected-access,too-many-instance-attributes
+from __future__ import absolute_import
+
from ctypes import c_ulong, c_void_p, c_int64, c_char, \
WinDLL, sizeof, Structure, Union, POINTER
from ctypes.wintypes import HANDLE
@@ -179,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]
@@ -245,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
diff --git a/setup.py b/setup.py
index f2b60a6..6e8b586 100644
--- a/setup.py
+++ b/setup.py
@@ -6,7 +6,7 @@
# For Python 3.x use the corresponding Python executable,
# e.g. "python3 setup.py ..."
#
-# (C) 2001-2016 Chris Liechti <cliechti@gmx.net>
+# (C) 2001-2017 Chris Liechti <cliechti@gmx.net>
#
# SPDX-License-Identifier: BSD-3-Clause
import io
@@ -89,6 +89,7 @@ Latest:
'Programming Language :: Python :: 3.3',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
+ 'Programming Language :: Python :: 3.6',
'Topic :: Communications',
'Topic :: Software Development :: Libraries',
'Topic :: Software Development :: Libraries :: Python Modules',
diff --git a/test/test_context.py b/test/test_context.py
new file mode 100755
index 0000000..456c85a
--- /dev/null
+++ b/test/test_context.py
@@ -0,0 +1,49 @@
+#! /usr/bin/env python
+#
+# This file is part of pySerial - Cross platform serial port support for Python
+# (C) 2017 Guillaume Galeazzi <guillaume.g@leazzi.ch>
+#
+# SPDX-License-Identifier: BSD-3-Clause
+"""\
+Some tests for the serial module.
+Part of pySerial (http://pyserial.sf.net) (C)2001-2011 cliechti@gmx.net
+
+Intended to be run on different platforms, to ensure portability of
+the code.
+
+Cover some of the aspects of context managment
+"""
+
+import unittest
+import serial
+
+# on which port should the tests be performed:
+PORT = 'loop://'
+
+
+class Test_Context(unittest.TestCase):
+ """Test context"""
+
+ def setUp(self):
+ # create a closed serial port
+ self.s = serial.serial_for_url(PORT)
+
+ def tearDown(self):
+ self.s.close()
+
+ def test_with_idempotent(self):
+ with self.s as stream:
+ stream.write(b'1234')
+
+ # do other stuff like calling an exe which use COM4
+
+ with self.s as stream:
+ stream.write(b'5678')
+
+
+if __name__ == '__main__':
+ import sys
+ sys.stdout.write(__doc__)
+ sys.argv[1:] = ['-v']
+ # When this module is executed from the command-line, it runs all its tests
+ unittest.main()