diff options
author | NJDFan <rob.gaddi@gmail.com> | 2019-06-20 16:04:55 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-06-20 16:04:55 -0700 |
commit | 3215eb3088a4ca55c5217c2e99da5584c3ee02c1 (patch) | |
tree | c64894a3b91f219add4a296db8376d9b3b9bdcef | |
parent | 5c021d4bdab2297602b4459f75bef2e00e5ec9ab (diff) | |
parent | acab9d2c0efb63323faebfd5e3405d77cd4b5617 (diff) | |
download | pyserial-git-3215eb3088a4ca55c5217c2e99da5584c3ee02c1.tar.gz |
Merge pull request #1 from pyserial/master
Catch up to the main fork
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 @@ -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 @@ -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()
|