]> Gitweb @ Texas Instruments - Open Source Git Repositories - git.TI.com/gitweb - processor-sdk/performance-audio-sr.git/blobdiff - procsdk_audio_x_xx_xx_xx/tools/pyalpha/pyalpha/serial/rfc2217.py
Add pyalpha tool, (legacy) bin files, and updates make_sw for alpha header file packa...
[processor-sdk/performance-audio-sr.git] / procsdk_audio_x_xx_xx_xx / tools / pyalpha / pyalpha / serial / rfc2217.py
diff --git a/procsdk_audio_x_xx_xx_xx/tools/pyalpha/pyalpha/serial/rfc2217.py b/procsdk_audio_x_xx_xx_xx/tools/pyalpha/pyalpha/serial/rfc2217.py
new file mode 100644 (file)
index 0000000..5e3cbe3
--- /dev/null
@@ -0,0 +1,1341 @@
+#! python
+#
+# This module implements a RFC2217 compatible client. RF2217 descibes a
+# protocol to access serial ports over TCP/IP and allows setting the baud rate,
+# modem control lines etc.
+#
+# This file is part of pySerial. https://github.com/pyserial/pyserial
+# (C) 2001-2015 Chris Liechti <cliechti@gmx.net>
+#
+# SPDX-License-Identifier:    BSD-3-Clause
+
+# TODO:
+# - setting control line -> answer is not checked (had problems with one of the
+#   severs). consider implementing a compatibility mode flag to make check
+#   conditional
+# - write timeout not implemented at all
+
+# ###########################################################################
+# observations and issues with servers
+# ===========================================================================
+# sredird V2.2.1
+# - http://www.ibiblio.org/pub/Linux/system/serial/   sredird-2.2.2.tar.gz
+# - does not acknowledge SET_CONTROL (RTS/DTR) correctly, always responding
+#   [105 1] instead of the actual value.
+# - SET_BAUDRATE answer contains 4 extra null bytes -> probably for larger
+#   numbers than 2**32?
+# - To get the signature [COM_PORT_OPTION 0] has to be sent.
+# - run a server: while true; do nc -l -p 7000 -c "sredird debug /dev/ttyUSB0 /var/lock/sredir"; done
+# ===========================================================================
+# telnetcpcd (untested)
+# - http://ftp.wayne.edu/kermit/sredird/telnetcpcd-1.09.tar.gz
+# - To get the signature [COM_PORT_OPTION] w/o data has to be sent.
+# ===========================================================================
+# ser2net
+# - does not negotiate BINARY or COM_PORT_OPTION for his side but at least
+#   acknowledges that the client activates these options
+# - The configuration may be that the server prints a banner. As this client
+#   implementation does a flushInput on connect, this banner is hidden from
+#   the user application.
+# - NOTIFY_MODEMSTATE: the poll interval of the server seems to be one
+#   second.
+# - To get the signature [COM_PORT_OPTION 0] has to be sent.
+# - run a server: run ser2net daemon, in /etc/ser2net.conf:
+#     2000:telnet:0:/dev/ttyS0:9600 remctl banner
+# ###########################################################################
+
+# How to identify ports? pySerial might want to support other protocols in the
+# future, so lets use an URL scheme.
+# for RFC2217 compliant servers we will use this:
+#    rfc2217://<host>:<port>[?option[&option...]]
+#
+# options:
+# - "logging" set log level print diagnostic messages (e.g. "logging=debug")
+# - "ign_set_control": do not look at the answers to SET_CONTROL
+# - "poll_modem": issue NOTIFY_MODEMSTATE requests when CTS/DTR/RI/CD is read.
+#   Without this option it expects that the server sends notifications
+#   automatically on change (which most servers do and is according to the
+#   RFC).
+# the order of the options is not relevant
+
+import logging
+import socket
+import struct
+import threading
+import time
+try:
+    import urlparse
+except ImportError:
+    import urllib.parse as urlparse
+try:
+    import Queue
+except ImportError:
+    import queue as Queue
+
+import serial
+from serial.serialutil import SerialBase, SerialException, to_bytes, iterbytes, portNotOpenError
+
+# port string is expected to be something like this:
+# rfc2217://host:port
+# host may be an IP or including domain, whatever.
+# port is 0...65535
+
+# map log level names to constants. used in from_url()
+LOGGER_LEVELS = {
+    'debug': logging.DEBUG,
+    'info': logging.INFO,
+    'warning': logging.WARNING,
+    'error': logging.ERROR,
+}
+
+
+# telnet protocol characters
+SE = b'\xf0'    # Subnegotiation End
+NOP = b'\xf1'   # No Operation
+DM = b'\xf2'    # Data Mark
+BRK = b'\xf3'   # Break
+IP = b'\xf4'    # Interrupt process
+AO = b'\xf5'    # Abort output
+AYT = b'\xf6'   # Are You There
+EC = b'\xf7'    # Erase Character
+EL = b'\xf8'    # Erase Line
+GA = b'\xf9'    # Go Ahead
+SB = b'\xfa'    # Subnegotiation Begin
+WILL = b'\xfb'
+WONT = b'\xfc'
+DO = b'\xfd'
+DONT = b'\xfe'
+IAC = b'\xff'   # Interpret As Command
+IAC_DOUBLED = b'\xff\xff'
+
+# selected telnet options
+BINARY = b'\x00'    # 8-bit data path
+ECHO = b'\x01'      # echo
+SGA = b'\x03'       # suppress go ahead
+
+# RFC2217
+COM_PORT_OPTION = b'\x2c'
+
+# Client to Access Server
+SET_BAUDRATE = b'\x01'
+SET_DATASIZE = b'\x02'
+SET_PARITY = b'\x03'
+SET_STOPSIZE = b'\x04'
+SET_CONTROL = b'\x05'
+NOTIFY_LINESTATE = b'\x06'
+NOTIFY_MODEMSTATE = b'\x07'
+FLOWCONTROL_SUSPEND = b'\x08'
+FLOWCONTROL_RESUME = b'\x09'
+SET_LINESTATE_MASK = b'\x0a'
+SET_MODEMSTATE_MASK = b'\x0b'
+PURGE_DATA = b'\x0c'
+
+SERVER_SET_BAUDRATE = b'\x65'
+SERVER_SET_DATASIZE = b'\x66'
+SERVER_SET_PARITY = b'\x67'
+SERVER_SET_STOPSIZE = b'\x68'
+SERVER_SET_CONTROL = b'\x69'
+SERVER_NOTIFY_LINESTATE = b'\x6a'
+SERVER_NOTIFY_MODEMSTATE = b'\x6b'
+SERVER_FLOWCONTROL_SUSPEND = b'\x6c'
+SERVER_FLOWCONTROL_RESUME = b'\x6d'
+SERVER_SET_LINESTATE_MASK = b'\x6e'
+SERVER_SET_MODEMSTATE_MASK = b'\x6f'
+SERVER_PURGE_DATA = b'\x70'
+
+RFC2217_ANSWER_MAP = {
+    SET_BAUDRATE: SERVER_SET_BAUDRATE,
+    SET_DATASIZE: SERVER_SET_DATASIZE,
+    SET_PARITY: SERVER_SET_PARITY,
+    SET_STOPSIZE: SERVER_SET_STOPSIZE,
+    SET_CONTROL: SERVER_SET_CONTROL,
+    NOTIFY_LINESTATE: SERVER_NOTIFY_LINESTATE,
+    NOTIFY_MODEMSTATE: SERVER_NOTIFY_MODEMSTATE,
+    FLOWCONTROL_SUSPEND: SERVER_FLOWCONTROL_SUSPEND,
+    FLOWCONTROL_RESUME: SERVER_FLOWCONTROL_RESUME,
+    SET_LINESTATE_MASK: SERVER_SET_LINESTATE_MASK,
+    SET_MODEMSTATE_MASK: SERVER_SET_MODEMSTATE_MASK,
+    PURGE_DATA: SERVER_PURGE_DATA,
+}
+
+SET_CONTROL_REQ_FLOW_SETTING = b'\x00'        # Request Com Port Flow Control Setting (outbound/both)
+SET_CONTROL_USE_NO_FLOW_CONTROL = b'\x01'     # Use No Flow Control (outbound/both)
+SET_CONTROL_USE_SW_FLOW_CONTROL = b'\x02'     # Use XON/XOFF Flow Control (outbound/both)
+SET_CONTROL_USE_HW_FLOW_CONTROL = b'\x03'     # Use HARDWARE Flow Control (outbound/both)
+SET_CONTROL_REQ_BREAK_STATE = b'\x04'         # Request BREAK State
+SET_CONTROL_BREAK_ON = b'\x05'                # Set BREAK State ON
+SET_CONTROL_BREAK_OFF = b'\x06'               # Set BREAK State OFF
+SET_CONTROL_REQ_DTR = b'\x07'                 # Request DTR Signal State
+SET_CONTROL_DTR_ON = b'\x08'                  # Set DTR Signal State ON
+SET_CONTROL_DTR_OFF = b'\x09'                 # Set DTR Signal State OFF
+SET_CONTROL_REQ_RTS = b'\x0a'                 # Request RTS Signal State
+SET_CONTROL_RTS_ON = b'\x0b'                  # Set RTS Signal State ON
+SET_CONTROL_RTS_OFF = b'\x0c'                 # Set RTS Signal State OFF
+SET_CONTROL_REQ_FLOW_SETTING_IN = b'\x0d'     # Request Com Port Flow Control Setting (inbound)
+SET_CONTROL_USE_NO_FLOW_CONTROL_IN = b'\x0e'  # Use No Flow Control (inbound)
+SET_CONTROL_USE_SW_FLOW_CONTOL_IN = b'\x0f'   # Use XON/XOFF Flow Control (inbound)
+SET_CONTROL_USE_HW_FLOW_CONTOL_IN = b'\x10'   # Use HARDWARE Flow Control (inbound)
+SET_CONTROL_USE_DCD_FLOW_CONTROL = b'\x11'    # Use DCD Flow Control (outbound/both)
+SET_CONTROL_USE_DTR_FLOW_CONTROL = b'\x12'    # Use DTR Flow Control (inbound)
+SET_CONTROL_USE_DSR_FLOW_CONTROL = b'\x13'    # Use DSR Flow Control (outbound/both)
+
+LINESTATE_MASK_TIMEOUT = 128        # Time-out Error
+LINESTATE_MASK_SHIFTREG_EMPTY = 64  # Transfer Shift Register Empty
+LINESTATE_MASK_TRANSREG_EMPTY = 32  # Transfer Holding Register Empty
+LINESTATE_MASK_BREAK_DETECT = 16    # Break-detect Error
+LINESTATE_MASK_FRAMING_ERROR = 8    # Framing Error
+LINESTATE_MASK_PARTIY_ERROR = 4     # Parity Error
+LINESTATE_MASK_OVERRUN_ERROR = 2    # Overrun Error
+LINESTATE_MASK_DATA_READY = 1       # Data Ready
+
+MODEMSTATE_MASK_CD = 128            # Receive Line Signal Detect (also known as Carrier Detect)
+MODEMSTATE_MASK_RI = 64             # Ring Indicator
+MODEMSTATE_MASK_DSR = 32            # Data-Set-Ready Signal State
+MODEMSTATE_MASK_CTS = 16            # Clear-To-Send Signal State
+MODEMSTATE_MASK_CD_CHANGE = 8       # Delta Receive Line Signal Detect
+MODEMSTATE_MASK_RI_CHANGE = 4       # Trailing-edge Ring Detector
+MODEMSTATE_MASK_DSR_CHANGE = 2      # Delta Data-Set-Ready
+MODEMSTATE_MASK_CTS_CHANGE = 1      # Delta Clear-To-Send
+
+PURGE_RECEIVE_BUFFER = b'\x01'      # Purge access server receive data buffer
+PURGE_TRANSMIT_BUFFER = b'\x02'     # Purge access server transmit data buffer
+PURGE_BOTH_BUFFERS = b'\x03'        # Purge both the access server receive data
+                                    # buffer and the access server transmit data buffer
+
+
+RFC2217_PARITY_MAP = {
+    serial.PARITY_NONE: 1,
+    serial.PARITY_ODD: 2,
+    serial.PARITY_EVEN: 3,
+    serial.PARITY_MARK: 4,
+    serial.PARITY_SPACE: 5,
+}
+RFC2217_REVERSE_PARITY_MAP = dict((v, k) for k, v in RFC2217_PARITY_MAP.items())
+
+RFC2217_STOPBIT_MAP = {
+    serial.STOPBITS_ONE: 1,
+    serial.STOPBITS_ONE_POINT_FIVE: 3,
+    serial.STOPBITS_TWO: 2,
+}
+RFC2217_REVERSE_STOPBIT_MAP = dict((v, k) for k, v in RFC2217_STOPBIT_MAP.items())
+
+# Telnet filter states
+M_NORMAL = 0
+M_IAC_SEEN = 1
+M_NEGOTIATE = 2
+
+# TelnetOption and TelnetSubnegotiation states
+REQUESTED = 'REQUESTED'
+ACTIVE = 'ACTIVE'
+INACTIVE = 'INACTIVE'
+REALLY_INACTIVE = 'REALLY_INACTIVE'
+
+
+class TelnetOption(object):
+    """Manage a single telnet option, keeps track of DO/DONT WILL/WONT."""
+
+    def __init__(self, connection, name, option, send_yes, send_no, ack_yes,
+                 ack_no, initial_state, activation_callback=None):
+        """\
+        Initialize option.
+        :param connection: connection used to transmit answers
+        :param name: a readable name for debug outputs
+        :param send_yes: what to send when option is to be enabled.
+        :param send_no: what to send when option is to be disabled.
+        :param ack_yes: what to expect when remote agrees on option.
+        :param ack_no: what to expect when remote disagrees on option.
+        :param initial_state: options initialized with REQUESTED are tried to
+            be enabled on startup. use INACTIVE for all others.
+        """
+        self.connection = connection
+        self.name = name
+        self.option = option
+        self.send_yes = send_yes
+        self.send_no = send_no
+        self.ack_yes = ack_yes
+        self.ack_no = ack_no
+        self.state = initial_state
+        self.active = False
+        self.activation_callback = activation_callback
+
+    def __repr__(self):
+        """String for debug outputs"""
+        return "{o.name}:{o.active}({o.state})".format(o=self)
+
+    def process_incoming(self, command):
+        """\
+        A DO/DONT/WILL/WONT was received for this option, update state and
+        answer when needed.
+        """
+        if command == self.ack_yes:
+            if self.state is REQUESTED:
+                self.state = ACTIVE
+                self.active = True
+                if self.activation_callback is not None:
+                    self.activation_callback()
+            elif self.state is ACTIVE:
+                pass
+            elif self.state is INACTIVE:
+                self.state = ACTIVE
+                self.connection.telnet_send_option(self.send_yes, self.option)
+                self.active = True
+                if self.activation_callback is not None:
+                    self.activation_callback()
+            elif self.state is REALLY_INACTIVE:
+                self.connection.telnet_send_option(self.send_no, self.option)
+            else:
+                raise ValueError('option in illegal state {!r}'.format(self))
+        elif command == self.ack_no:
+            if self.state is REQUESTED:
+                self.state = INACTIVE
+                self.active = False
+            elif self.state is ACTIVE:
+                self.state = INACTIVE
+                self.connection.telnet_send_option(self.send_no, self.option)
+                self.active = False
+            elif self.state is INACTIVE:
+                pass
+            elif self.state is REALLY_INACTIVE:
+                pass
+            else:
+                raise ValueError('option in illegal state {!r}'.format(self))
+
+
+class TelnetSubnegotiation(object):
+    """\
+    A object to handle subnegotiation of options. In this case actually
+    sub-sub options for RFC 2217. It is used to track com port options.
+    """
+
+    def __init__(self, connection, name, option, ack_option=None):
+        if ack_option is None:
+            ack_option = option
+        self.connection = connection
+        self.name = name
+        self.option = option
+        self.value = None
+        self.ack_option = ack_option
+        self.state = INACTIVE
+
+    def __repr__(self):
+        """String for debug outputs."""
+        return "{sn.name}:{sn.state}".format(sn=self)
+
+    def set(self, value):
+        """\
+        Request a change of the value. a request is sent to the server. if
+        the client needs to know if the change is performed he has to check the
+        state of this object.
+        """
+        self.value = value
+        self.state = REQUESTED
+        self.connection.rfc2217_send_subnegotiation(self.option, self.value)
+        if self.connection.logger:
+            self.connection.logger.debug("SB Requesting {} -> {!r}".format(self.name, self.value))
+
+    def is_ready(self):
+        """\
+        Check if answer from server has been received. when server rejects
+        the change, raise a ValueError.
+        """
+        if self.state == REALLY_INACTIVE:
+            raise ValueError("remote rejected value for option {!r}".format(self.name))
+        return self.state == ACTIVE
+    # add property to have a similar interface as TelnetOption
+    active = property(is_ready)
+
+    def wait(self, timeout=3):
+        """\
+        Wait until the subnegotiation has been acknowledged or timeout. It
+        can also throw a value error when the answer from the server does not
+        match the value sent.
+        """
+        timeout_time = time.time() + timeout
+        while time.time() < timeout_time:
+            time.sleep(0.05)    # prevent 100% CPU load
+            if self.is_ready():
+                break
+        else:
+            raise SerialException("timeout while waiting for option {!r}".format(self.name))
+
+    def check_answer(self, suboption):
+        """\
+        Check an incoming subnegotiation block. The parameter already has
+        cut off the header like sub option number and com port option value.
+        """
+        if self.value == suboption[:len(self.value)]:
+            self.state = ACTIVE
+        else:
+            # error propagation done in is_ready
+            self.state = REALLY_INACTIVE
+        if self.connection.logger:
+            self.connection.logger.debug("SB Answer {} -> {!r} -> {}".format(self.name, suboption, self.state))
+
+
+class Serial(SerialBase):
+    """Serial port implementation for RFC 2217 remote serial ports."""
+
+    BAUDRATES = (50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800,
+                 9600, 19200, 38400, 57600, 115200)
+
+    def __init__(self, *args, **kwargs):
+        super(Serial, self).__init__(*args, **kwargs)
+        self._thread = None
+        self._socket = None
+        self._linestate = 0
+        self._modemstate = None
+        self._modemstate_expires = 0
+        self._remote_suspend_flow = False
+        self._write_lock = None
+        self.logger = None
+        self._ignore_set_control_answer = False
+        self._poll_modem_state = False
+        self._network_timeout = 3
+        self._telnet_options = None
+        self._rfc2217_port_settings = None
+        self._rfc2217_options = None
+        self._read_buffer = None
+
+    def open(self):
+        """\
+        Open port with current settings. This may throw a SerialException
+        if the port cannot be opened.
+        """
+        self.logger = None
+        self._ignore_set_control_answer = False
+        self._poll_modem_state = False
+        self._network_timeout = 3
+        if self._port is None:
+            raise SerialException("Port must be configured before it can be used.")
+        if self.is_open:
+            raise SerialException("Port is already open.")
+        try:
+            self._socket = socket.create_connection(self.from_url(self.portstr), timeout=5)  # XXX good value?
+            self._socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
+        except Exception as msg:
+            self._socket = None
+            raise SerialException("Could not open port {}: {}".format(self.portstr, msg))
+
+        # use a thread save queue as buffer. it also simplifies implementing
+        # the read timeout
+        self._read_buffer = Queue.Queue()
+        # to ensure that user writes does not interfere with internal
+        # telnet/rfc2217 options establish a lock
+        self._write_lock = threading.Lock()
+        # name the following separately so that, below, a check can be easily done
+        mandadory_options = [
+            TelnetOption(self, 'we-BINARY', BINARY, WILL, WONT, DO, DONT, INACTIVE),
+            TelnetOption(self, 'we-RFC2217', COM_PORT_OPTION, WILL, WONT, DO, DONT, REQUESTED),
+        ]
+        # all supported telnet options
+        self._telnet_options = [
+            TelnetOption(self, 'ECHO', ECHO, DO, DONT, WILL, WONT, REQUESTED),
+            TelnetOption(self, 'we-SGA', SGA, WILL, WONT, DO, DONT, REQUESTED),
+            TelnetOption(self, 'they-SGA', SGA, DO, DONT, WILL, WONT, REQUESTED),
+            TelnetOption(self, 'they-BINARY', BINARY, DO, DONT, WILL, WONT, INACTIVE),
+            TelnetOption(self, 'they-RFC2217', COM_PORT_OPTION, DO, DONT, WILL, WONT, REQUESTED),
+        ] + mandadory_options
+        # RFC 2217 specific states
+        # COM port settings
+        self._rfc2217_port_settings = {
+            'baudrate': TelnetSubnegotiation(self, 'baudrate', SET_BAUDRATE, SERVER_SET_BAUDRATE),
+            'datasize': TelnetSubnegotiation(self, 'datasize', SET_DATASIZE, SERVER_SET_DATASIZE),
+            'parity':   TelnetSubnegotiation(self, 'parity',   SET_PARITY,   SERVER_SET_PARITY),
+            'stopsize': TelnetSubnegotiation(self, 'stopsize', SET_STOPSIZE, SERVER_SET_STOPSIZE),
+        }
+        # There are more subnegotiation objects, combine all in one dictionary
+        # for easy access
+        self._rfc2217_options = {
+            'purge':    TelnetSubnegotiation(self, 'purge',    PURGE_DATA,   SERVER_PURGE_DATA),
+            'control':  TelnetSubnegotiation(self, 'control',  SET_CONTROL,  SERVER_SET_CONTROL),
+        }
+        self._rfc2217_options.update(self._rfc2217_port_settings)
+        # cache for line and modem states that the server sends to us
+        self._linestate = 0
+        self._modemstate = None
+        self._modemstate_expires = 0
+        # RFC 2217 flow control between server and client
+        self._remote_suspend_flow = False
+
+        self.is_open = True
+        self._thread = threading.Thread(target=self._telnet_read_loop)
+        self._thread.setDaemon(True)
+        self._thread.setName('pySerial RFC 2217 reader thread for {}'.format(self._port))
+        self._thread.start()
+
+        try:    # must clean-up if open fails
+            # negotiate Telnet/RFC 2217 -> send initial requests
+            for option in self._telnet_options:
+                if option.state is REQUESTED:
+                    self.telnet_send_option(option.send_yes, option.option)
+            # now wait until important options are negotiated
+            timeout_time = time.time() + self._network_timeout
+            while time.time() < timeout_time:
+                time.sleep(0.05)    # prevent 100% CPU load
+                if sum(o.active for o in mandadory_options) == sum(o.state != INACTIVE for o in mandadory_options):
+                    break
+            else:
+                raise SerialException(
+                    "Remote does not seem to support RFC2217 or BINARY mode {!r}".format(mandadory_options))
+            if self.logger:
+                self.logger.info("Negotiated options: {}".format(self._telnet_options))
+
+            # fine, go on, set RFC 2271 specific things
+            self._reconfigure_port()
+            # all things set up get, now a clean start
+            if not self._dsrdtr:
+                self._update_dtr_state()
+            if not self._rtscts:
+                self._update_rts_state()
+            self.reset_input_buffer()
+            self.reset_output_buffer()
+        except:
+            self.close()
+            raise
+
+    def _reconfigure_port(self):
+        """Set communication parameters on opened port."""
+        if self._socket is None:
+            raise SerialException("Can only operate on open ports")
+
+        # if self._timeout != 0 and self._interCharTimeout is not None:
+            # XXX
+
+        if self._write_timeout is not None:
+            raise NotImplementedError('write_timeout is currently not supported')
+            # XXX
+
+        # Setup the connection
+        # to get good performance, all parameter changes are sent first...
+        if not 0 < self._baudrate < 2 ** 32:
+            raise ValueError("invalid baudrate: {!r}".format(self._baudrate))
+        self._rfc2217_port_settings['baudrate'].set(struct.pack(b'!I', self._baudrate))
+        self._rfc2217_port_settings['datasize'].set(struct.pack(b'!B', self._bytesize))
+        self._rfc2217_port_settings['parity'].set(struct.pack(b'!B', RFC2217_PARITY_MAP[self._parity]))
+        self._rfc2217_port_settings['stopsize'].set(struct.pack(b'!B', RFC2217_STOPBIT_MAP[self._stopbits]))
+
+        # and now wait until parameters are active
+        items = self._rfc2217_port_settings.values()
+        if self.logger:
+            self.logger.debug("Negotiating settings: {}".format(items))
+        timeout_time = time.time() + self._network_timeout
+        while time.time() < timeout_time:
+            time.sleep(0.05)    # prevent 100% CPU load
+            if sum(o.active for o in items) == len(items):
+                break
+        else:
+            raise SerialException("Remote does not accept parameter change (RFC2217): {!r}".format(items))
+        if self.logger:
+            self.logger.info("Negotiated settings: {}".format(items))
+
+        if self._rtscts and self._xonxoff:
+            raise ValueError('xonxoff and rtscts together are not supported')
+        elif self._rtscts:
+            self.rfc2217_set_control(SET_CONTROL_USE_HW_FLOW_CONTROL)
+        elif self._xonxoff:
+            self.rfc2217_set_control(SET_CONTROL_USE_SW_FLOW_CONTROL)
+        else:
+            self.rfc2217_set_control(SET_CONTROL_USE_NO_FLOW_CONTROL)
+
+    def close(self):
+        """Close port"""
+        self.is_open = False
+        if self._socket:
+            try:
+                self._socket.shutdown(socket.SHUT_RDWR)
+                self._socket.close()
+            except:
+                # ignore errors.
+                pass
+        if self._thread:
+            self._thread.join(7)  # XXX more than socket timeout
+            self._thread = None
+            # in case of quick reconnects, give the server some time
+            time.sleep(0.3)
+        self._socket = None
+
+    def from_url(self, url):
+        """\
+        extract host and port from an URL string, other settings are extracted
+        an stored in instance
+        """
+        parts = urlparse.urlsplit(url)
+        if parts.scheme != "rfc2217":
+            raise SerialException(
+                'expected a string in the form '
+                '"rfc2217://<host>:<port>[?option[&option...]]": '
+                'not starting with rfc2217:// ({!r})'.format(parts.scheme))
+        try:
+            # process options now, directly altering self
+            for option, values in urlparse.parse_qs(parts.query, True).items():
+                if option == 'logging':
+                    logging.basicConfig()   # XXX is that good to call it here?
+                    self.logger = logging.getLogger('pySerial.rfc2217')
+                    self.logger.setLevel(LOGGER_LEVELS[values[0]])
+                    self.logger.debug('enabled logging')
+                elif option == 'ign_set_control':
+                    self._ignore_set_control_answer = True
+                elif option == 'poll_modem':
+                    self._poll_modem_state = True
+                elif option == 'timeout':
+                    self._network_timeout = float(values[0])
+                else:
+                    raise ValueError('unknown option: {!r}'.format(option))
+            if not 0 <= parts.port < 65536:
+                raise ValueError("port not in range 0...65535")
+        except ValueError as e:
+            raise SerialException(
+                'expected a string in the form '
+                '"rfc2217://<host>:<port>[?option[&option...]]": {}'.format(e))
+        return (parts.hostname, parts.port)
+
+    #  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -
+
+    @property
+    def in_waiting(self):
+        """Return the number of bytes currently in the input buffer."""
+        if not self.is_open:
+            raise portNotOpenError
+        return self._read_buffer.qsize()
+
+    def read(self, size=1):
+        """\
+        Read size bytes from the serial port. If a timeout is set it may
+        return less characters as requested. With no timeout it will block
+        until the requested number of bytes is read.
+        """
+        if not self.is_open:
+            raise portNotOpenError
+        data = bytearray()
+        try:
+            while len(data) < size:
+                if self._thread is None:
+                    raise SerialException('connection failed (reader thread died)')
+                data += self._read_buffer.get(True, self._timeout)
+        except Queue.Empty:  # -> timeout
+            pass
+        return bytes(data)
+
+    def write(self, data):
+        """\
+        Output the given byte string over the serial port. Can block if the
+        connection is blocked. May raise SerialException if the connection is
+        closed.
+        """
+        if not self.is_open:
+            raise portNotOpenError
+        with self._write_lock:
+            try:
+                self._socket.sendall(to_bytes(data).replace(IAC, IAC_DOUBLED))
+            except socket.error as e:
+                raise SerialException("connection failed (socket error): {}".format(e))
+        return len(data)
+
+    def reset_input_buffer(self):
+        """Clear input buffer, discarding all that is in the buffer."""
+        if not self.is_open:
+            raise portNotOpenError
+        self.rfc2217_send_purge(PURGE_RECEIVE_BUFFER)
+        # empty read buffer
+        while self._read_buffer.qsize():
+            self._read_buffer.get(False)
+
+    def reset_output_buffer(self):
+        """\
+        Clear output buffer, aborting the current output and
+        discarding all that is in the buffer.
+        """
+        if not self.is_open:
+            raise portNotOpenError
+        self.rfc2217_send_purge(PURGE_TRANSMIT_BUFFER)
+
+    def _update_break_state(self):
+        """\
+        Set break: Controls TXD. When active, to transmitting is
+        possible.
+        """
+        if not self.is_open:
+            raise portNotOpenError
+        if self.logger:
+            self.logger.info('set BREAK to {}'.format('active' if self._break_state else 'inactive'))
+        if self._break_state:
+            self.rfc2217_set_control(SET_CONTROL_BREAK_ON)
+        else:
+            self.rfc2217_set_control(SET_CONTROL_BREAK_OFF)
+
+    def _update_rts_state(self):
+        """Set terminal status line: Request To Send."""
+        if not self.is_open:
+            raise portNotOpenError
+        if self.logger:
+            self.logger.info('set RTS to {}'.format('active' if self._rts_state else 'inactive'))
+        if self._rts_state:
+            self.rfc2217_set_control(SET_CONTROL_RTS_ON)
+        else:
+            self.rfc2217_set_control(SET_CONTROL_RTS_OFF)
+
+    def _update_dtr_state(self):
+        """Set terminal status line: Data Terminal Ready."""
+        if not self.is_open:
+            raise portNotOpenError
+        if self.logger:
+            self.logger.info('set DTR to {}'.format('active' if self._dtr_state else 'inactive'))
+        if self._dtr_state:
+            self.rfc2217_set_control(SET_CONTROL_DTR_ON)
+        else:
+            self.rfc2217_set_control(SET_CONTROL_DTR_OFF)
+
+    @property
+    def cts(self):
+        """Read terminal status line: Clear To Send."""
+        if not self.is_open:
+            raise portNotOpenError
+        return bool(self.get_modem_state() & MODEMSTATE_MASK_CTS)
+
+    @property
+    def dsr(self):
+        """Read terminal status line: Data Set Ready."""
+        if not self.is_open:
+            raise portNotOpenError
+        return bool(self.get_modem_state() & MODEMSTATE_MASK_DSR)
+
+    @property
+    def ri(self):
+        """Read terminal status line: Ring Indicator."""
+        if not self.is_open:
+            raise portNotOpenError
+        return bool(self.get_modem_state() & MODEMSTATE_MASK_RI)
+
+    @property
+    def cd(self):
+        """Read terminal status line: Carrier Detect."""
+        if not self.is_open:
+            raise portNotOpenError
+        return bool(self.get_modem_state() & MODEMSTATE_MASK_CD)
+
+    # - - - platform specific - - -
+    # None so far
+
+    # - - - RFC2217 specific - - -
+
+    def _telnet_read_loop(self):
+        """Read loop for the socket."""
+        mode = M_NORMAL
+        suboption = None
+        try:
+            while self.is_open:
+                try:
+                    data = self._socket.recv(1024)
+                except socket.timeout:
+                    # just need to get out of recv form time to time to check if
+                    # still alive
+                    continue
+                except socket.error as e:
+                    # connection fails -> terminate loop
+                    if self.logger:
+                        self.logger.debug("socket error in reader thread: {}".format(e))
+                    break
+                if not data:
+                    break  # lost connection
+                for byte in iterbytes(data):
+                    if mode == M_NORMAL:
+                        # interpret as command or as data
+                        if byte == IAC:
+                            mode = M_IAC_SEEN
+                        else:
+                            # store data in read buffer or sub option buffer
+                            # depending on state
+                            if suboption is not None:
+                                suboption += byte
+                            else:
+                                self._read_buffer.put(byte)
+                    elif mode == M_IAC_SEEN:
+                        if byte == IAC:
+                            # interpret as command doubled -> insert character
+                            # itself
+                            if suboption is not None:
+                                suboption += IAC
+                            else:
+                                self._read_buffer.put(IAC)
+                            mode = M_NORMAL
+                        elif byte == SB:
+                            # sub option start
+                            suboption = bytearray()
+                            mode = M_NORMAL
+                        elif byte == SE:
+                            # sub option end -> process it now
+                            self._telnet_process_subnegotiation(bytes(suboption))
+                            suboption = None
+                            mode = M_NORMAL
+                        elif byte in (DO, DONT, WILL, WONT):
+                            # negotiation
+                            telnet_command = byte
+                            mode = M_NEGOTIATE
+                        else:
+                            # other telnet commands
+                            self._telnet_process_command(byte)
+                            mode = M_NORMAL
+                    elif mode == M_NEGOTIATE:  # DO, DONT, WILL, WONT was received, option now following
+                        self._telnet_negotiate_option(telnet_command, byte)
+                        mode = M_NORMAL
+        finally:
+            self._thread = None
+            if self.logger:
+                self.logger.debug("read thread terminated")
+
+    # - incoming telnet commands and options
+
+    def _telnet_process_command(self, command):
+        """Process commands other than DO, DONT, WILL, WONT."""
+        # Currently none. RFC2217 only uses negotiation and subnegotiation.
+        if self.logger:
+            self.logger.warning("ignoring Telnet command: {!r}".format(command))
+
+    def _telnet_negotiate_option(self, command, option):
+        """Process incoming DO, DONT, WILL, WONT."""
+        # check our registered telnet options and forward command to them
+        # they know themselves if they have to answer or not
+        known = False
+        for item in self._telnet_options:
+            # can have more than one match! as some options are duplicated for
+            # 'us' and 'them'
+            if item.option == option:
+                item.process_incoming(command)
+                known = True
+        if not known:
+            # handle unknown options
+            # only answer to positive requests and deny them
+            if command == WILL or command == DO:
+                self.telnet_send_option((DONT if command == WILL else WONT), option)
+                if self.logger:
+                    self.logger.warning("rejected Telnet option: {!r}".format(option))
+
+    def _telnet_process_subnegotiation(self, suboption):
+        """Process subnegotiation, the data between IAC SB and IAC SE."""
+        if suboption[0:1] == COM_PORT_OPTION:
+            if suboption[1:2] == SERVER_NOTIFY_LINESTATE and len(suboption) >= 3:
+                self._linestate = ord(suboption[2:3])  # ensure it is a number
+                if self.logger:
+                    self.logger.info("NOTIFY_LINESTATE: {}".format(self._linestate))
+            elif suboption[1:2] == SERVER_NOTIFY_MODEMSTATE and len(suboption) >= 3:
+                self._modemstate = ord(suboption[2:3])  # ensure it is a number
+                if self.logger:
+                    self.logger.info("NOTIFY_MODEMSTATE: {}".format(self._modemstate))
+                # update time when we think that a poll would make sense
+                self._modemstate_expires = time.time() + 0.3
+            elif suboption[1:2] == FLOWCONTROL_SUSPEND:
+                self._remote_suspend_flow = True
+            elif suboption[1:2] == FLOWCONTROL_RESUME:
+                self._remote_suspend_flow = False
+            else:
+                for item in self._rfc2217_options.values():
+                    if item.ack_option == suboption[1:2]:
+                        #~ print "processing COM_PORT_OPTION: %r" % list(suboption[1:])
+                        item.check_answer(bytes(suboption[2:]))
+                        break
+                else:
+                    if self.logger:
+                        self.logger.warning("ignoring COM_PORT_OPTION: {!r}".format(suboption))
+        else:
+            if self.logger:
+                self.logger.warning("ignoring subnegotiation: {!r}".format(suboption))
+
+    # - outgoing telnet commands and options
+
+    def _internal_raw_write(self, data):
+        """internal socket write with no data escaping. used to send telnet stuff."""
+        with self._write_lock:
+            self._socket.sendall(data)
+
+    def telnet_send_option(self, action, option):
+        """Send DO, DONT, WILL, WONT."""
+        self._internal_raw_write(to_bytes([IAC, action, option]))
+
+    def rfc2217_send_subnegotiation(self, option, value=b''):
+        """Subnegotiation of RFC2217 parameters."""
+        value = value.replace(IAC, IAC_DOUBLED)
+        self._internal_raw_write(to_bytes([IAC, SB, COM_PORT_OPTION, option] + list(value) + [IAC, SE]))
+
+    def rfc2217_send_purge(self, value):
+        """\
+        Send purge request to the remote.
+        (PURGE_RECEIVE_BUFFER / PURGE_TRANSMIT_BUFFER / PURGE_BOTH_BUFFERS)
+        """
+        item = self._rfc2217_options['purge']
+        item.set(value)  # transmit desired purge type
+        item.wait(self._network_timeout)  # wait for acknowledge from the server
+
+    def rfc2217_set_control(self, value):
+        """transmit change of control line to remote"""
+        item = self._rfc2217_options['control']
+        item.set(value)  # transmit desired control type
+        if self._ignore_set_control_answer:
+            # answers are ignored when option is set. compatibility mode for
+            # servers that answer, but not the expected one... (or no answer
+            # at all) i.e. sredird
+            time.sleep(0.1)  # this helps getting the unit tests passed
+        else:
+            item.wait(self._network_timeout)  # wait for acknowledge from the server
+
+    def rfc2217_flow_server_ready(self):
+        """\
+        check if server is ready to receive data. block for some time when
+        not.
+        """
+        #~ if self._remote_suspend_flow:
+        #~     wait---
+
+    def get_modem_state(self):
+        """\
+        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
+        etc.)
+        """
+        # active modem state polling enabled? is the value fresh enough?
+        if self._poll_modem_state and self._modemstate_expires < time.time():
+            if self.logger:
+                self.logger.debug('polling modem state')
+            # when it is older, request an update
+            self.rfc2217_send_subnegotiation(NOTIFY_MODEMSTATE)
+            timeout_time = time.time() + self._network_timeout
+            while time.time() < timeout_time:
+                time.sleep(0.05)    # prevent 100% CPU load
+                # when expiration time is updated, it means that there is a new
+                # value
+                if self._modemstate_expires > time.time():
+                    break
+            else:
+                if self.logger:
+                    self.logger.warning('poll for modem state failed')
+            # even when there is a timeout, do not generate an error just
+            # return the last known value. this way we can support buggy
+            # servers that do not respond to polls, but send automatic
+            # updates.
+        if self._modemstate is not None:
+            if self.logger:
+                self.logger.debug('using cached modem state')
+            return self._modemstate
+        else:
+            # never received a notification from the server
+            raise SerialException("remote sends no NOTIFY_MODEMSTATE")
+
+
+#############################################################################
+# The following is code that helps implementing an RFC 2217 server.
+
+class PortManager(object):
+    """\
+    This class manages the state of Telnet and RFC 2217. It needs a serial
+    instance and a connection to work with. Connection is expected to implement
+    a (thread safe) write function, that writes the string to the network.
+    """
+
+    def __init__(self, serial_port, connection, logger=None):
+        self.serial = serial_port
+        self.connection = connection
+        self.logger = logger
+        self._client_is_rfc2217 = False
+
+        # filter state machine
+        self.mode = M_NORMAL
+        self.suboption = None
+        self.telnet_command = None
+
+        # states for modem/line control events
+        self.modemstate_mask = 255
+        self.last_modemstate = None
+        self.linstate_mask = 0
+
+        # all supported telnet options
+        self._telnet_options = [
+            TelnetOption(self, 'ECHO', ECHO, WILL, WONT, DO, DONT, REQUESTED),
+            TelnetOption(self, 'we-SGA', SGA, WILL, WONT, DO, DONT, REQUESTED),
+            TelnetOption(self, 'they-SGA', SGA, DO, DONT, WILL, WONT, INACTIVE),
+            TelnetOption(self, 'we-BINARY', BINARY, WILL, WONT, DO, DONT, INACTIVE),
+            TelnetOption(self, 'they-BINARY', BINARY, DO, DONT, WILL, WONT, REQUESTED),
+            TelnetOption(self, 'we-RFC2217', COM_PORT_OPTION, WILL, WONT, DO, DONT, REQUESTED, self._client_ok),
+            TelnetOption(self, 'they-RFC2217', COM_PORT_OPTION, DO, DONT, WILL, WONT, INACTIVE, self._client_ok),
+        ]
+
+        # negotiate Telnet/RFC2217 -> send initial requests
+        if self.logger:
+            self.logger.debug("requesting initial Telnet/RFC 2217 options")
+        for option in self._telnet_options:
+            if option.state is REQUESTED:
+                self.telnet_send_option(option.send_yes, option.option)
+        # issue 1st modem state notification
+
+    def _client_ok(self):
+        """\
+        callback of telnet option. It gets called when option is activated.
+        This one here is used to detect when the client agrees on RFC 2217. A
+        flag is set so that other functions like check_modem_lines know if the
+        client is OK.
+        """
+        # The callback is used for we and they so if one party agrees, we're
+        # already happy. it seems not all servers do the negotiation correctly
+        # and i guess there are incorrect clients too.. so be happy if client
+        # answers one or the other positively.
+        self._client_is_rfc2217 = True
+        if self.logger:
+            self.logger.info("client accepts RFC 2217")
+        # this is to ensure that the client gets a notification, even if there
+        # was no change
+        self.check_modem_lines(force_notification=True)
+
+    # - outgoing telnet commands and options
+
+    def telnet_send_option(self, action, option):
+        """Send DO, DONT, WILL, WONT."""
+        self.connection.write(to_bytes([IAC, action, option]))
+
+    def rfc2217_send_subnegotiation(self, option, value=b''):
+        """Subnegotiation of RFC 2217 parameters."""
+        value = value.replace(IAC, IAC_DOUBLED)
+        self.connection.write(to_bytes([IAC, SB, COM_PORT_OPTION, option] + list(value) + [IAC, SE]))
+
+    # - check modem lines, needs to be called periodically from user to
+    # establish polling
+
+    def check_modem_lines(self, force_notification=False):
+        """\
+        read control lines from serial port and compare the last value sent to remote.
+        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))
+        # check what has changed
+        deltas = modemstate ^ (self.last_modemstate or 0)  # when last is None -> 0
+        if deltas & MODEMSTATE_MASK_CTS:
+            modemstate |= MODEMSTATE_MASK_CTS_CHANGE
+        if deltas & MODEMSTATE_MASK_DSR:
+            modemstate |= MODEMSTATE_MASK_DSR_CHANGE
+        if deltas & MODEMSTATE_MASK_RI:
+            modemstate |= MODEMSTATE_MASK_RI_CHANGE
+        if deltas & MODEMSTATE_MASK_CD:
+            modemstate |= MODEMSTATE_MASK_CD_CHANGE
+        # if new state is different and the mask allows this change, send
+        # notification. suppress notifications when client is not rfc2217
+        if modemstate != self.last_modemstate or force_notification:
+            if (self._client_is_rfc2217 and (modemstate & self.modemstate_mask)) or force_notification:
+                self.rfc2217_send_subnegotiation(
+                    SERVER_NOTIFY_MODEMSTATE,
+                    to_bytes([modemstate & self.modemstate_mask]))
+                if self.logger:
+                    self.logger.info("NOTIFY_MODEMSTATE: {}".format(modemstate))
+            # save last state, but forget about deltas.
+            # otherwise it would also notify about changing deltas which is
+            # probably not very useful
+            self.last_modemstate = modemstate & 0xf0
+
+    # - outgoing data escaping
+
+    def escape(self, data):
+        """\
+        This generator function is for the user. All outgoing data has to be
+        properly escaped, so that no IAC character in the data stream messes up
+        the Telnet state machine in the server.
+
+        socket.sendall(escape(data))
+        """
+        for byte in iterbytes(data):
+            if byte == IAC:
+                yield IAC
+                yield IAC
+            else:
+                yield byte
+
+    # - incoming data filter
+
+    def filter(self, data):
+        """\
+        Handle a bunch of incoming bytes. This is a generator. It will yield
+        all characters not of interest for Telnet/RFC 2217.
+
+        The idea is that the reader thread pushes data from the socket through
+        this filter:
+
+        for byte in filter(socket.recv(1024)):
+            # do things like CR/LF conversion/whatever
+            # and write data to the serial port
+            serial.write(byte)
+
+        (socket error handling code left as exercise for the reader)
+        """
+        for byte in iterbytes(data):
+            if self.mode == M_NORMAL:
+                # interpret as command or as data
+                if byte == IAC:
+                    self.mode = M_IAC_SEEN
+                else:
+                    # store data in sub option buffer or pass it to our
+                    # consumer depending on state
+                    if self.suboption is not None:
+                        self.suboption += byte
+                    else:
+                        yield byte
+            elif self.mode == M_IAC_SEEN:
+                if byte == IAC:
+                    # interpret as command doubled -> insert character
+                    # itself
+                    if self.suboption is not None:
+                        self.suboption += byte
+                    else:
+                        yield byte
+                    self.mode = M_NORMAL
+                elif byte == SB:
+                    # sub option start
+                    self.suboption = bytearray()
+                    self.mode = M_NORMAL
+                elif byte == SE:
+                    # sub option end -> process it now
+                    self._telnet_process_subnegotiation(bytes(self.suboption))
+                    self.suboption = None
+                    self.mode = M_NORMAL
+                elif byte in (DO, DONT, WILL, WONT):
+                    # negotiation
+                    self.telnet_command = byte
+                    self.mode = M_NEGOTIATE
+                else:
+                    # other telnet commands
+                    self._telnet_process_command(byte)
+                    self.mode = M_NORMAL
+            elif self.mode == M_NEGOTIATE:  # DO, DONT, WILL, WONT was received, option now following
+                self._telnet_negotiate_option(self.telnet_command, byte)
+                self.mode = M_NORMAL
+
+    # - incoming telnet commands and options
+
+    def _telnet_process_command(self, command):
+        """Process commands other than DO, DONT, WILL, WONT."""
+        # Currently none. RFC2217 only uses negotiation and subnegotiation.
+        if self.logger:
+            self.logger.warning("ignoring Telnet command: {!r}".format(command))
+
+    def _telnet_negotiate_option(self, command, option):
+        """Process incoming DO, DONT, WILL, WONT."""
+        # check our registered telnet options and forward command to them
+        # they know themselves if they have to answer or not
+        known = False
+        for item in self._telnet_options:
+            # can have more than one match! as some options are duplicated for
+            # 'us' and 'them'
+            if item.option == option:
+                item.process_incoming(command)
+                known = True
+        if not known:
+            # handle unknown options
+            # only answer to positive requests and deny them
+            if command == WILL or command == DO:
+                self.telnet_send_option((DONT if command == WILL else WONT), option)
+                if self.logger:
+                    self.logger.warning("rejected Telnet option: {!r}".format(option))
+
+    def _telnet_process_subnegotiation(self, suboption):
+        """Process subnegotiation, the data between IAC SB and IAC SE."""
+        if suboption[0:1] == COM_PORT_OPTION:
+            if self.logger:
+                self.logger.debug('received COM_PORT_OPTION: {!r}'.format(suboption))
+            if suboption[1:2] == SET_BAUDRATE:
+                backup = self.serial.baudrate
+                try:
+                    (baudrate,) = struct.unpack(b"!I", suboption[2:6])
+                    if baudrate != 0:
+                        self.serial.baudrate = baudrate
+                except ValueError as e:
+                    if self.logger:
+                        self.logger.error("failed to set baud rate: {}".format(e))
+                    self.serial.baudrate = backup
+                else:
+                    if self.logger:
+                        self.logger.info("{} baud rate: {}".format('set' if baudrate else 'get', self.serial.baudrate))
+                self.rfc2217_send_subnegotiation(SERVER_SET_BAUDRATE, struct.pack(b"!I", self.serial.baudrate))
+            elif suboption[1:2] == SET_DATASIZE:
+                backup = self.serial.bytesize
+                try:
+                    (datasize,) = struct.unpack(b"!B", suboption[2:3])
+                    if datasize != 0:
+                        self.serial.bytesize = datasize
+                except ValueError as e:
+                    if self.logger:
+                        self.logger.error("failed to set data size: {}".format(e))
+                    self.serial.bytesize = backup
+                else:
+                    if self.logger:
+                        self.logger.info("{} data size: {}".format('set' if datasize else 'get', self.serial.bytesize))
+                self.rfc2217_send_subnegotiation(SERVER_SET_DATASIZE, struct.pack(b"!B", self.serial.bytesize))
+            elif suboption[1:2] == SET_PARITY:
+                backup = self.serial.parity
+                try:
+                    parity = struct.unpack(b"!B", suboption[2:3])[0]
+                    if parity != 0:
+                        self.serial.parity = RFC2217_REVERSE_PARITY_MAP[parity]
+                except ValueError as e:
+                    if self.logger:
+                        self.logger.error("failed to set parity: {}".format(e))
+                    self.serial.parity = backup
+                else:
+                    if self.logger:
+                        self.logger.info("{} parity: {}".format('set' if parity else 'get', self.serial.parity))
+                self.rfc2217_send_subnegotiation(
+                    SERVER_SET_PARITY,
+                    struct.pack(b"!B", RFC2217_PARITY_MAP[self.serial.parity]))
+            elif suboption[1:2] == SET_STOPSIZE:
+                backup = self.serial.stopbits
+                try:
+                    stopbits = struct.unpack(b"!B", suboption[2:3])[0]
+                    if stopbits != 0:
+                        self.serial.stopbits = RFC2217_REVERSE_STOPBIT_MAP[stopbits]
+                except ValueError as e:
+                    if self.logger:
+                        self.logger.error("failed to set stop bits: {}".format(e))
+                    self.serial.stopbits = backup
+                else:
+                    if self.logger:
+                        self.logger.info("{} stop bits: {}".format('set' if stopbits else 'get', self.serial.stopbits))
+                self.rfc2217_send_subnegotiation(
+                    SERVER_SET_STOPSIZE,
+                    struct.pack(b"!B", RFC2217_STOPBIT_MAP[self.serial.stopbits]))
+            elif suboption[1:2] == SET_CONTROL:
+                if suboption[2:3] == SET_CONTROL_REQ_FLOW_SETTING:
+                    if self.serial.xonxoff:
+                        self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_SW_FLOW_CONTROL)
+                    elif self.serial.rtscts:
+                        self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_HW_FLOW_CONTROL)
+                    else:
+                        self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_NO_FLOW_CONTROL)
+                elif suboption[2:3] == SET_CONTROL_USE_NO_FLOW_CONTROL:
+                    self.serial.xonxoff = False
+                    self.serial.rtscts = False
+                    if self.logger:
+                        self.logger.info("changed flow control to None")
+                    self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_NO_FLOW_CONTROL)
+                elif suboption[2:3] == SET_CONTROL_USE_SW_FLOW_CONTROL:
+                    self.serial.xonxoff = True
+                    if self.logger:
+                        self.logger.info("changed flow control to XON/XOFF")
+                    self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_SW_FLOW_CONTROL)
+                elif suboption[2:3] == SET_CONTROL_USE_HW_FLOW_CONTROL:
+                    self.serial.rtscts = True
+                    if self.logger:
+                        self.logger.info("changed flow control to RTS/CTS")
+                    self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_HW_FLOW_CONTROL)
+                elif suboption[2:3] == SET_CONTROL_REQ_BREAK_STATE:
+                    if self.logger:
+                        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)
+                    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)
+                    if self.logger:
+                        self.logger.info("changed BREAK to inactive")
+                    self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_BREAK_OFF)
+                elif suboption[2:3] == SET_CONTROL_REQ_DTR:
+                    if self.logger:
+                        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)
+                    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)
+                    if self.logger:
+                        self.logger.info("changed DTR to inactive")
+                    self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_DTR_OFF)
+                elif suboption[2:3] == SET_CONTROL_REQ_RTS:
+                    if self.logger:
+                        self.logger.warning("requested RTS state - not implemented")
+                    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)
+                    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)
+                    if self.logger:
+                        self.logger.info("changed RTS to inactive")
+                    self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_RTS_OFF)
+                #~ elif suboption[2:3] == SET_CONTROL_REQ_FLOW_SETTING_IN:
+                #~ elif suboption[2:3] == SET_CONTROL_USE_NO_FLOW_CONTROL_IN:
+                #~ elif suboption[2:3] == SET_CONTROL_USE_SW_FLOW_CONTOL_IN:
+                #~ elif suboption[2:3] == SET_CONTROL_USE_HW_FLOW_CONTOL_IN:
+                #~ elif suboption[2:3] == SET_CONTROL_USE_DCD_FLOW_CONTROL:
+                #~ elif suboption[2:3] == SET_CONTROL_USE_DTR_FLOW_CONTROL:
+                #~ elif suboption[2:3] == SET_CONTROL_USE_DSR_FLOW_CONTROL:
+            elif suboption[1:2] == NOTIFY_LINESTATE:
+                # client polls for current state
+                self.rfc2217_send_subnegotiation(
+                    SERVER_NOTIFY_LINESTATE,
+                    to_bytes([0]))   # sorry, nothing like that implemented
+            elif suboption[1:2] == NOTIFY_MODEMSTATE:
+                if self.logger:
+                    self.logger.info("request for modem state")
+                # client polls for current state
+                self.check_modem_lines(force_notification=True)
+            elif suboption[1:2] == FLOWCONTROL_SUSPEND:
+                if self.logger:
+                    self.logger.info("suspend")
+                self._remote_suspend_flow = True
+            elif suboption[1:2] == FLOWCONTROL_RESUME:
+                if self.logger:
+                    self.logger.info("resume")
+                self._remote_suspend_flow = False
+            elif suboption[1:2] == SET_LINESTATE_MASK:
+                self.linstate_mask = ord(suboption[2:3])  # ensure it is a number
+                if self.logger:
+                    self.logger.info("line state mask: 0x{:02x}".format(self.linstate_mask))
+            elif suboption[1:2] == SET_MODEMSTATE_MASK:
+                self.modemstate_mask = ord(suboption[2:3])  # ensure it is a number
+                if self.logger:
+                    self.logger.info("modem state mask: 0x{:02x}".format(self.modemstate_mask))
+            elif suboption[1:2] == PURGE_DATA:
+                if suboption[2:3] == PURGE_RECEIVE_BUFFER:
+                    self.serial.reset_input_buffer()
+                    if self.logger:
+                        self.logger.info("purge in")
+                    self.rfc2217_send_subnegotiation(SERVER_PURGE_DATA, PURGE_RECEIVE_BUFFER)
+                elif suboption[2:3] == PURGE_TRANSMIT_BUFFER:
+                    self.serial.reset_output_buffer()
+                    if self.logger:
+                        self.logger.info("purge out")
+                    self.rfc2217_send_subnegotiation(SERVER_PURGE_DATA, PURGE_TRANSMIT_BUFFER)
+                elif suboption[2:3] == PURGE_BOTH_BUFFERS:
+                    self.serial.reset_input_buffer()
+                    self.serial.reset_output_buffer()
+                    if self.logger:
+                        self.logger.info("purge both")
+                    self.rfc2217_send_subnegotiation(SERVER_PURGE_DATA, PURGE_BOTH_BUFFERS)
+                else:
+                    if self.logger:
+                        self.logger.error("undefined PURGE_DATA: {!r}".format(list(suboption[2:])))
+            else:
+                if self.logger:
+                    self.logger.error("undefined COM_PORT_OPTION: {!r}".format(list(suboption[1:])))
+        else:
+            if self.logger:
+                self.logger.warning("unknown subnegotiation: {!r}".format(suboption))
+
+
+# simple client test
+if __name__ == '__main__':
+    import sys
+    s = Serial('rfc2217://localhost:7000', 115200)
+    sys.stdout.write('{}\n'.format(s))
+
+    sys.stdout.write("write...\n")
+    s.write(b"hello\n")
+    s.flush()
+    sys.stdout.write("read: {}\n".format(s.read(5)))
+    s.close()