Merge pull request #1 from pyserial/master

Catch up to the main fork
diff --git a/CHANGES.rst b/CHANGES.rst
index 56d42b7..82a3b39 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -710,3 +710,27 @@
 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 @@
 - 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 @@
 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 @@
       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 @@
 
 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 @@
 
 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 @@
 # 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 @@
 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 @@
     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 @@
 ============
 - 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 @@
 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 @@
 - 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 @@
             :const:`STOPBITS_TWO`
 
         :param float timeout:
-            Set a read timeout value.
+            Set a read timeout value in seconds.
 
         :param bool xonxoff:
             Enable software flow control.
@@ -48,7 +48,7 @@
             Enable hardware (DSR/DTR) flow control.
 
         :param float write_timeout:
-            Set a write timeout value.
+            Set a write timeout value in seconds.
 
         :param float inter_byte_timeout:
             Inter-character timeout, :const:`None` to disable (default).
@@ -122,7 +122,7 @@
 
             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 @@
             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 @@
 
     .. 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 @@
 
     .. 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 @@
         :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 @@
         :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 @@
 
         .. 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 @@
         >>> 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 @@
 
     .. method:: isOpen()
 
-    	.. deprecated:: 3.0 see :attr:`is_open`
+        .. deprecated:: 3.0 see :attr:`is_open`
 
     .. attribute:: writeTimeout
 
@@ -701,8 +734,10 @@
 
 
 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 @@
 
 
 
-
 :rfc:`2217` Network ports
 -------------------------
 
@@ -1060,7 +1094,7 @@
     :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 @@
 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 @@
 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 @@
               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 @@
 
 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 @@
     ---    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 @@
 
 ``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 @@
     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 @@
                     # 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 @@
             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 @@
             # 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 @@
             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 @@
     group = parser.add_argument_group('serial port')
 
     group.add_argument(
+        "--bytesize",
+        choices=[5, 6, 7, 8],
+        type=int,
+        help="set bytesize, one of {5 6 7 8}, default: 8",
+        default=8)
+
+    group.add_argument(
         "--parity",
         choices=['N', 'E', 'O', 'S', 'M'],
         type=lambda c: c.upper(),
@@ -73,6 +80,13 @@
         default='N')
 
     group.add_argument(
+        "--stopbits",
+        choices=[1, 1.5, 2],
+        type=float,
+        help="set stopbits, one of {1 1.5 2}, default: 1",
+        default=1)
+
+    group.add_argument(
         '--rtscts',
         action='store_true',
         help='enable RTS/CTS flow control (default off)',
@@ -117,7 +131,9 @@
     # connect to serial port
     ser = serial.serial_for_url(args.SERIALPORT, do_not_open=True)
     ser.baudrate = args.BAUDRATE
+    ser.bytesize = args.bytesize
     ser.parity = args.parity
+    ser.stopbits = args.stopbits
     ser.rtscts = args.rtscts
     ser.xonxoff = args.xonxoff
 
diff --git a/serial/__init__.py b/serial/__init__.py
index 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 @@
             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 @@
                     # 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 @@
         """\
         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 @@
         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 @@
                         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 @@
                         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 @@
                     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 @@
 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 @@
     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 @@
             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 @@
                 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 @@
                 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 @@
         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 @@
     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 @@
     # context manager
 
     def __enter__(self):
+        if not self.is_open:
+            self.open()
         return self
 
     def __exit__(self, *args, **kwargs):
@@ -645,19 +649,19 @@
         """
         return self.read(self.in_waiting)
 
-    def read_until(self, terminator=LF, size=None):
+    def read_until(self, expected=LF, size=None):
         """\
-        Read until a termination sequence is found ('\n' by default), the size
+        Read until an expected sequence is found ('\n' by default), the size
         is exceeded or until timeout occurs.
         """
-        lenterm = len(terminator)
+        lenterm = len(expected)
         line = bytearray()
         timeout = Timeout(self._timeout)
         while True:
             c = self.read(1)
             if c:
                 line += c
-                if line[-lenterm:] == terminator:
+                if line[-lenterm:] == expected:
                     break
                 if size is not None and len(line) >= size:
                     break
diff --git a/serial/serialwin32.py b/serial/serialwin32.py
index 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 @@
     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 @@
                 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 @@
     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 @@
 
 """
 
+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 @@
 based on their descriptions or hardware ID.
 """
 
+from __future__ import absolute_import
+
 import sys
 import os
 import re
@@ -34,14 +36,14 @@
 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 
 
-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 @@
         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 @@
     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 @@
 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 @@
         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 @@
         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 @@
         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 @@
 
     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 @@
             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 @@
             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 @@
             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 @@
     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 @@
 currently just identical to the port name.
 """
 
+from __future__ import absolute_import
+
 import glob
 import sys
 import os
@@ -34,48 +36,64 @@
     # 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.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 @@
 
     # 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 @@
             # 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 @@
                             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 @@
         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 @@
     """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 @@
     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 @@
         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 @@
             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 @@
             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 @@
             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 @@
         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 @@
     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",
-        default=False)
-
-    group = parser.add_argument_group("data handling")
+        '--non-exclusive',
+        dest='exclusive',
+        action='store_false',
+        help='disable locking for native ports',
+        default=True)
 
     group.add_argument(
-        "-e", "--echo",
-        action="store_true",
-        help="enable local echo (default off)",
+        '--ask',
+        action='store_true',
+        help='ask again for port when open fails',
+        default=False)
+
+    group = parser.add_argument_group('data handling')
+
+    group.add_argument(
+        '-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 @@
                     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 @@
     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 @@
                 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 @@
                 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 @@
                 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.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 @@
 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 @@
         '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()