1#! python
2#
3# This module implements a RFC2217 compatible client. RF2217 descibes a
4# protocol to access serial ports over TCP/IP and allows setting the baud rate,
5# modem control lines etc.
6#
7# This file is part of pySerial. https://github.com/pyserial/pyserial
8# (C) 2001-2015 Chris Liechti <cliechti@gmx.net>
9#
10# SPDX-License-Identifier:    BSD-3-Clause
11
12# TODO:
13# - setting control line -> answer is not checked (had problems with one of the
14#   severs). consider implementing a compatibility mode flag to make check
15#   conditional
16# - write timeout not implemented at all
17
18# ###########################################################################
19# observations and issues with servers
20# ===========================================================================
21# sredird V2.2.1
22# - http://www.ibiblio.org/pub/Linux/system/serial/   sredird-2.2.2.tar.gz
23# - does not acknowledge SET_CONTROL (RTS/DTR) correctly, always responding
24#   [105 1] instead of the actual value.
25# - SET_BAUDRATE answer contains 4 extra null bytes -> probably for larger
26#   numbers than 2**32?
27# - To get the signature [COM_PORT_OPTION 0] has to be sent.
28# - run a server: while true; do nc -l -p 7000 -c "sredird debug /dev/ttyUSB0 /var/lock/sredir"; done
29# ===========================================================================
30# telnetcpcd (untested)
31# - http://ftp.wayne.edu/kermit/sredird/telnetcpcd-1.09.tar.gz
32# - To get the signature [COM_PORT_OPTION] w/o data has to be sent.
33# ===========================================================================
34# ser2net
35# - does not negotiate BINARY or COM_PORT_OPTION for his side but at least
36#   acknowledges that the client activates these options
37# - The configuration may be that the server prints a banner. As this client
38#   implementation does a flushInput on connect, this banner is hidden from
39#   the user application.
40# - NOTIFY_MODEMSTATE: the poll interval of the server seems to be one
41#   second.
42# - To get the signature [COM_PORT_OPTION 0] has to be sent.
43# - run a server: run ser2net daemon, in /etc/ser2net.conf:
44#     2000:telnet:0:/dev/ttyS0:9600 remctl banner
45# ###########################################################################
46
47# How to identify ports? pySerial might want to support other protocols in the
48# future, so lets use an URL scheme.
49# for RFC2217 compliant servers we will use this:
50#    rfc2217://<host>:<port>[?option[&option...]]
51#
52# options:
53# - "logging" set log level print diagnostic messages (e.g. "logging=debug")
54# - "ign_set_control": do not look at the answers to SET_CONTROL
55# - "poll_modem": issue NOTIFY_MODEMSTATE requests when CTS/DTR/RI/CD is read.
56#   Without this option it expects that the server sends notifications
57#   automatically on change (which most servers do and is according to the
58#   RFC).
59# the order of the options is not relevant
60
61from __future__ import absolute_import
62
63import logging
64import socket
65import struct
66import threading
67import time
68try:
69    import urlparse
70except ImportError:
71    import urllib.parse as urlparse
72try:
73    import Queue
74except ImportError:
75    import queue as Queue
76
77import serial
78from serial.serialutil import SerialBase, SerialException, to_bytes, \
79    iterbytes, PortNotOpenError, Timeout
80
81# port string is expected to be something like this:
82# rfc2217://host:port
83# host may be an IP or including domain, whatever.
84# port is 0...65535
85
86# map log level names to constants. used in from_url()
87LOGGER_LEVELS = {
88    'debug': logging.DEBUG,
89    'info': logging.INFO,
90    'warning': logging.WARNING,
91    'error': logging.ERROR,
92}
93
94
95# telnet protocol characters
96SE = b'\xf0'    # Subnegotiation End
97NOP = b'\xf1'   # No Operation
98DM = b'\xf2'    # Data Mark
99BRK = b'\xf3'   # Break
100IP = b'\xf4'    # Interrupt process
101AO = b'\xf5'    # Abort output
102AYT = b'\xf6'   # Are You There
103EC = b'\xf7'    # Erase Character
104EL = b'\xf8'    # Erase Line
105GA = b'\xf9'    # Go Ahead
106SB = b'\xfa'    # Subnegotiation Begin
107WILL = b'\xfb'
108WONT = b'\xfc'
109DO = b'\xfd'
110DONT = b'\xfe'
111IAC = b'\xff'   # Interpret As Command
112IAC_DOUBLED = b'\xff\xff'
113
114# selected telnet options
115BINARY = b'\x00'    # 8-bit data path
116ECHO = b'\x01'      # echo
117SGA = b'\x03'       # suppress go ahead
118
119# RFC2217
120COM_PORT_OPTION = b'\x2c'
121
122# Client to Access Server
123SET_BAUDRATE = b'\x01'
124SET_DATASIZE = b'\x02'
125SET_PARITY = b'\x03'
126SET_STOPSIZE = b'\x04'
127SET_CONTROL = b'\x05'
128NOTIFY_LINESTATE = b'\x06'
129NOTIFY_MODEMSTATE = b'\x07'
130FLOWCONTROL_SUSPEND = b'\x08'
131FLOWCONTROL_RESUME = b'\x09'
132SET_LINESTATE_MASK = b'\x0a'
133SET_MODEMSTATE_MASK = b'\x0b'
134PURGE_DATA = b'\x0c'
135
136SERVER_SET_BAUDRATE = b'\x65'
137SERVER_SET_DATASIZE = b'\x66'
138SERVER_SET_PARITY = b'\x67'
139SERVER_SET_STOPSIZE = b'\x68'
140SERVER_SET_CONTROL = b'\x69'
141SERVER_NOTIFY_LINESTATE = b'\x6a'
142SERVER_NOTIFY_MODEMSTATE = b'\x6b'
143SERVER_FLOWCONTROL_SUSPEND = b'\x6c'
144SERVER_FLOWCONTROL_RESUME = b'\x6d'
145SERVER_SET_LINESTATE_MASK = b'\x6e'
146SERVER_SET_MODEMSTATE_MASK = b'\x6f'
147SERVER_PURGE_DATA = b'\x70'
148
149RFC2217_ANSWER_MAP = {
150    SET_BAUDRATE: SERVER_SET_BAUDRATE,
151    SET_DATASIZE: SERVER_SET_DATASIZE,
152    SET_PARITY: SERVER_SET_PARITY,
153    SET_STOPSIZE: SERVER_SET_STOPSIZE,
154    SET_CONTROL: SERVER_SET_CONTROL,
155    NOTIFY_LINESTATE: SERVER_NOTIFY_LINESTATE,
156    NOTIFY_MODEMSTATE: SERVER_NOTIFY_MODEMSTATE,
157    FLOWCONTROL_SUSPEND: SERVER_FLOWCONTROL_SUSPEND,
158    FLOWCONTROL_RESUME: SERVER_FLOWCONTROL_RESUME,
159    SET_LINESTATE_MASK: SERVER_SET_LINESTATE_MASK,
160    SET_MODEMSTATE_MASK: SERVER_SET_MODEMSTATE_MASK,
161    PURGE_DATA: SERVER_PURGE_DATA,
162}
163
164SET_CONTROL_REQ_FLOW_SETTING = b'\x00'        # Request Com Port Flow Control Setting (outbound/both)
165SET_CONTROL_USE_NO_FLOW_CONTROL = b'\x01'     # Use No Flow Control (outbound/both)
166SET_CONTROL_USE_SW_FLOW_CONTROL = b'\x02'     # Use XON/XOFF Flow Control (outbound/both)
167SET_CONTROL_USE_HW_FLOW_CONTROL = b'\x03'     # Use HARDWARE Flow Control (outbound/both)
168SET_CONTROL_REQ_BREAK_STATE = b'\x04'         # Request BREAK State
169SET_CONTROL_BREAK_ON = b'\x05'                # Set BREAK State ON
170SET_CONTROL_BREAK_OFF = b'\x06'               # Set BREAK State OFF
171SET_CONTROL_REQ_DTR = b'\x07'                 # Request DTR Signal State
172SET_CONTROL_DTR_ON = b'\x08'                  # Set DTR Signal State ON
173SET_CONTROL_DTR_OFF = b'\x09'                 # Set DTR Signal State OFF
174SET_CONTROL_REQ_RTS = b'\x0a'                 # Request RTS Signal State
175SET_CONTROL_RTS_ON = b'\x0b'                  # Set RTS Signal State ON
176SET_CONTROL_RTS_OFF = b'\x0c'                 # Set RTS Signal State OFF
177SET_CONTROL_REQ_FLOW_SETTING_IN = b'\x0d'     # Request Com Port Flow Control Setting (inbound)
178SET_CONTROL_USE_NO_FLOW_CONTROL_IN = b'\x0e'  # Use No Flow Control (inbound)
179SET_CONTROL_USE_SW_FLOW_CONTOL_IN = b'\x0f'   # Use XON/XOFF Flow Control (inbound)
180SET_CONTROL_USE_HW_FLOW_CONTOL_IN = b'\x10'   # Use HARDWARE Flow Control (inbound)
181SET_CONTROL_USE_DCD_FLOW_CONTROL = b'\x11'    # Use DCD Flow Control (outbound/both)
182SET_CONTROL_USE_DTR_FLOW_CONTROL = b'\x12'    # Use DTR Flow Control (inbound)
183SET_CONTROL_USE_DSR_FLOW_CONTROL = b'\x13'    # Use DSR Flow Control (outbound/both)
184
185LINESTATE_MASK_TIMEOUT = 128        # Time-out Error
186LINESTATE_MASK_SHIFTREG_EMPTY = 64  # Transfer Shift Register Empty
187LINESTATE_MASK_TRANSREG_EMPTY = 32  # Transfer Holding Register Empty
188LINESTATE_MASK_BREAK_DETECT = 16    # Break-detect Error
189LINESTATE_MASK_FRAMING_ERROR = 8    # Framing Error
190LINESTATE_MASK_PARTIY_ERROR = 4     # Parity Error
191LINESTATE_MASK_OVERRUN_ERROR = 2    # Overrun Error
192LINESTATE_MASK_DATA_READY = 1       # Data Ready
193
194MODEMSTATE_MASK_CD = 128            # Receive Line Signal Detect (also known as Carrier Detect)
195MODEMSTATE_MASK_RI = 64             # Ring Indicator
196MODEMSTATE_MASK_DSR = 32            # Data-Set-Ready Signal State
197MODEMSTATE_MASK_CTS = 16            # Clear-To-Send Signal State
198MODEMSTATE_MASK_CD_CHANGE = 8       # Delta Receive Line Signal Detect
199MODEMSTATE_MASK_RI_CHANGE = 4       # Trailing-edge Ring Detector
200MODEMSTATE_MASK_DSR_CHANGE = 2      # Delta Data-Set-Ready
201MODEMSTATE_MASK_CTS_CHANGE = 1      # Delta Clear-To-Send
202
203PURGE_RECEIVE_BUFFER = b'\x01'      # Purge access server receive data buffer
204PURGE_TRANSMIT_BUFFER = b'\x02'     # Purge access server transmit data buffer
205PURGE_BOTH_BUFFERS = b'\x03'        # Purge both the access server receive data
206                                    # buffer and the access server transmit data buffer
207
208
209RFC2217_PARITY_MAP = {
210    serial.PARITY_NONE: 1,
211    serial.PARITY_ODD: 2,
212    serial.PARITY_EVEN: 3,
213    serial.PARITY_MARK: 4,
214    serial.PARITY_SPACE: 5,
215}
216RFC2217_REVERSE_PARITY_MAP = dict((v, k) for k, v in RFC2217_PARITY_MAP.items())
217
218RFC2217_STOPBIT_MAP = {
219    serial.STOPBITS_ONE: 1,
220    serial.STOPBITS_ONE_POINT_FIVE: 3,
221    serial.STOPBITS_TWO: 2,
222}
223RFC2217_REVERSE_STOPBIT_MAP = dict((v, k) for k, v in RFC2217_STOPBIT_MAP.items())
224
225# Telnet filter states
226M_NORMAL = 0
227M_IAC_SEEN = 1
228M_NEGOTIATE = 2
229
230# TelnetOption and TelnetSubnegotiation states
231REQUESTED = 'REQUESTED'
232ACTIVE = 'ACTIVE'
233INACTIVE = 'INACTIVE'
234REALLY_INACTIVE = 'REALLY_INACTIVE'
235
236
237class TelnetOption(object):
238    """Manage a single telnet option, keeps track of DO/DONT WILL/WONT."""
239
240    def __init__(self, connection, name, option, send_yes, send_no, ack_yes,
241                 ack_no, initial_state, activation_callback=None):
242        """\
243        Initialize option.
244        :param connection: connection used to transmit answers
245        :param name: a readable name for debug outputs
246        :param send_yes: what to send when option is to be enabled.
247        :param send_no: what to send when option is to be disabled.
248        :param ack_yes: what to expect when remote agrees on option.
249        :param ack_no: what to expect when remote disagrees on option.
250        :param initial_state: options initialized with REQUESTED are tried to
251            be enabled on startup. use INACTIVE for all others.
252        """
253        self.connection = connection
254        self.name = name
255        self.option = option
256        self.send_yes = send_yes
257        self.send_no = send_no
258        self.ack_yes = ack_yes
259        self.ack_no = ack_no
260        self.state = initial_state
261        self.active = False
262        self.activation_callback = activation_callback
263
264    def __repr__(self):
265        """String for debug outputs"""
266        return "{o.name}:{o.active}({o.state})".format(o=self)
267
268    def process_incoming(self, command):
269        """\
270        A DO/DONT/WILL/WONT was received for this option, update state and
271        answer when needed.
272        """
273        if command == self.ack_yes:
274            if self.state is REQUESTED:
275                self.state = ACTIVE
276                self.active = True
277                if self.activation_callback is not None:
278                    self.activation_callback()
279            elif self.state is ACTIVE:
280                pass
281            elif self.state is INACTIVE:
282                self.state = ACTIVE
283                self.connection.telnet_send_option(self.send_yes, self.option)
284                self.active = True
285                if self.activation_callback is not None:
286                    self.activation_callback()
287            elif self.state is REALLY_INACTIVE:
288                self.connection.telnet_send_option(self.send_no, self.option)
289            else:
290                raise ValueError('option in illegal state {!r}'.format(self))
291        elif command == self.ack_no:
292            if self.state is REQUESTED:
293                self.state = INACTIVE
294                self.active = False
295            elif self.state is ACTIVE:
296                self.state = INACTIVE
297                self.connection.telnet_send_option(self.send_no, self.option)
298                self.active = False
299            elif self.state is INACTIVE:
300                pass
301            elif self.state is REALLY_INACTIVE:
302                pass
303            else:
304                raise ValueError('option in illegal state {!r}'.format(self))
305
306
307class TelnetSubnegotiation(object):
308    """\
309    A object to handle subnegotiation of options. In this case actually
310    sub-sub options for RFC 2217. It is used to track com port options.
311    """
312
313    def __init__(self, connection, name, option, ack_option=None):
314        if ack_option is None:
315            ack_option = option
316        self.connection = connection
317        self.name = name
318        self.option = option
319        self.value = None
320        self.ack_option = ack_option
321        self.state = INACTIVE
322
323    def __repr__(self):
324        """String for debug outputs."""
325        return "{sn.name}:{sn.state}".format(sn=self)
326
327    def set(self, value):
328        """\
329        Request a change of the value. a request is sent to the server. if
330        the client needs to know if the change is performed he has to check the
331        state of this object.
332        """
333        self.value = value
334        self.state = REQUESTED
335        self.connection.rfc2217_send_subnegotiation(self.option, self.value)
336        if self.connection.logger:
337            self.connection.logger.debug("SB Requesting {} -> {!r}".format(self.name, self.value))
338
339    def is_ready(self):
340        """\
341        Check if answer from server has been received. when server rejects
342        the change, raise a ValueError.
343        """
344        if self.state == REALLY_INACTIVE:
345            raise ValueError("remote rejected value for option {!r}".format(self.name))
346        return self.state == ACTIVE
347    # add property to have a similar interface as TelnetOption
348    active = property(is_ready)
349
350    def wait(self, timeout=3):
351        """\
352        Wait until the subnegotiation has been acknowledged or timeout. It
353        can also throw a value error when the answer from the server does not
354        match the value sent.
355        """
356        timeout_timer = Timeout(timeout)
357        while not timeout_timer.expired():
358            time.sleep(0.05)    # prevent 100% CPU load
359            if self.is_ready():
360                break
361        else:
362            raise SerialException("timeout while waiting for option {!r}".format(self.name))
363
364    def check_answer(self, suboption):
365        """\
366        Check an incoming subnegotiation block. The parameter already has
367        cut off the header like sub option number and com port option value.
368        """
369        if self.value == suboption[:len(self.value)]:
370            self.state = ACTIVE
371        else:
372            # error propagation done in is_ready
373            self.state = REALLY_INACTIVE
374        if self.connection.logger:
375            self.connection.logger.debug("SB Answer {} -> {!r} -> {}".format(self.name, suboption, self.state))
376
377
378class Serial(SerialBase):
379    """Serial port implementation for RFC 2217 remote serial ports."""
380
381    BAUDRATES = (50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800,
382                 9600, 19200, 38400, 57600, 115200)
383
384    def __init__(self, *args, **kwargs):
385        self._thread = None
386        self._socket = None
387        self._linestate = 0
388        self._modemstate = None
389        self._modemstate_timeout = Timeout(-1)
390        self._remote_suspend_flow = False
391        self._write_lock = None
392        self.logger = None
393        self._ignore_set_control_answer = False
394        self._poll_modem_state = False
395        self._network_timeout = 3
396        self._telnet_options = None
397        self._rfc2217_port_settings = None
398        self._rfc2217_options = None
399        self._read_buffer = None
400        super(Serial, self).__init__(*args, **kwargs)  # must be last call in case of auto-open
401
402    def open(self):
403        """\
404        Open port with current settings. This may throw a SerialException
405        if the port cannot be opened.
406        """
407        self.logger = None
408        self._ignore_set_control_answer = False
409        self._poll_modem_state = False
410        self._network_timeout = 3
411        if self._port is None:
412            raise SerialException("Port must be configured before it can be used.")
413        if self.is_open:
414            raise SerialException("Port is already open.")
415        try:
416            self._socket = socket.create_connection(self.from_url(self.portstr), timeout=5)  # XXX good value?
417            self._socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
418        except Exception as msg:
419            self._socket = None
420            raise SerialException("Could not open port {}: {}".format(self.portstr, msg))
421
422        # use a thread save queue as buffer. it also simplifies implementing
423        # the read timeout
424        self._read_buffer = Queue.Queue()
425        # to ensure that user writes does not interfere with internal
426        # telnet/rfc2217 options establish a lock
427        self._write_lock = threading.Lock()
428        # name the following separately so that, below, a check can be easily done
429        mandadory_options = [
430            TelnetOption(self, 'we-BINARY', BINARY, WILL, WONT, DO, DONT, INACTIVE),
431            TelnetOption(self, 'we-RFC2217', COM_PORT_OPTION, WILL, WONT, DO, DONT, REQUESTED),
432        ]
433        # all supported telnet options
434        self._telnet_options = [
435            TelnetOption(self, 'ECHO', ECHO, DO, DONT, WILL, WONT, REQUESTED),
436            TelnetOption(self, 'we-SGA', SGA, WILL, WONT, DO, DONT, REQUESTED),
437            TelnetOption(self, 'they-SGA', SGA, DO, DONT, WILL, WONT, REQUESTED),
438            TelnetOption(self, 'they-BINARY', BINARY, DO, DONT, WILL, WONT, INACTIVE),
439            TelnetOption(self, 'they-RFC2217', COM_PORT_OPTION, DO, DONT, WILL, WONT, REQUESTED),
440        ] + mandadory_options
441        # RFC 2217 specific states
442        # COM port settings
443        self._rfc2217_port_settings = {
444            'baudrate': TelnetSubnegotiation(self, 'baudrate', SET_BAUDRATE, SERVER_SET_BAUDRATE),
445            'datasize': TelnetSubnegotiation(self, 'datasize', SET_DATASIZE, SERVER_SET_DATASIZE),
446            'parity':   TelnetSubnegotiation(self, 'parity',   SET_PARITY,   SERVER_SET_PARITY),
447            'stopsize': TelnetSubnegotiation(self, 'stopsize', SET_STOPSIZE, SERVER_SET_STOPSIZE),
448        }
449        # There are more subnegotiation objects, combine all in one dictionary
450        # for easy access
451        self._rfc2217_options = {
452            'purge':    TelnetSubnegotiation(self, 'purge',    PURGE_DATA,   SERVER_PURGE_DATA),
453            'control':  TelnetSubnegotiation(self, 'control',  SET_CONTROL,  SERVER_SET_CONTROL),
454        }
455        self._rfc2217_options.update(self._rfc2217_port_settings)
456        # cache for line and modem states that the server sends to us
457        self._linestate = 0
458        self._modemstate = None
459        self._modemstate_timeout = Timeout(-1)
460        # RFC 2217 flow control between server and client
461        self._remote_suspend_flow = False
462
463        self.is_open = True
464        self._thread = threading.Thread(target=self._telnet_read_loop)
465        self._thread.setDaemon(True)
466        self._thread.setName('pySerial RFC 2217 reader thread for {}'.format(self._port))
467        self._thread.start()
468
469        try:    # must clean-up if open fails
470            # negotiate Telnet/RFC 2217 -> send initial requests
471            for option in self._telnet_options:
472                if option.state is REQUESTED:
473                    self.telnet_send_option(option.send_yes, option.option)
474            # now wait until important options are negotiated
475            timeout = Timeout(self._network_timeout)
476            while not timeout.expired():
477                time.sleep(0.05)    # prevent 100% CPU load
478                if sum(o.active for o in mandadory_options) == sum(o.state != INACTIVE for o in mandadory_options):
479                    break
480            else:
481                raise SerialException(
482                    "Remote does not seem to support RFC2217 or BINARY mode {!r}".format(mandadory_options))
483            if self.logger:
484                self.logger.info("Negotiated options: {}".format(self._telnet_options))
485
486            # fine, go on, set RFC 2217 specific things
487            self._reconfigure_port()
488            # all things set up get, now a clean start
489            if not self._dsrdtr:
490                self._update_dtr_state()
491            if not self._rtscts:
492                self._update_rts_state()
493            self.reset_input_buffer()
494            self.reset_output_buffer()
495        except:
496            self.close()
497            raise
498
499    def _reconfigure_port(self):
500        """Set communication parameters on opened port."""
501        if self._socket is None:
502            raise SerialException("Can only operate on open ports")
503
504        # if self._timeout != 0 and self._interCharTimeout is not None:
505            # XXX
506
507        if self._write_timeout is not None:
508            raise NotImplementedError('write_timeout is currently not supported')
509            # XXX
510
511        # Setup the connection
512        # to get good performance, all parameter changes are sent first...
513        if not 0 < self._baudrate < 2 ** 32:
514            raise ValueError("invalid baudrate: {!r}".format(self._baudrate))
515        self._rfc2217_port_settings['baudrate'].set(struct.pack(b'!I', self._baudrate))
516        self._rfc2217_port_settings['datasize'].set(struct.pack(b'!B', self._bytesize))
517        self._rfc2217_port_settings['parity'].set(struct.pack(b'!B', RFC2217_PARITY_MAP[self._parity]))
518        self._rfc2217_port_settings['stopsize'].set(struct.pack(b'!B', RFC2217_STOPBIT_MAP[self._stopbits]))
519
520        # and now wait until parameters are active
521        items = self._rfc2217_port_settings.values()
522        if self.logger:
523            self.logger.debug("Negotiating settings: {}".format(items))
524        timeout = Timeout(self._network_timeout)
525        while not timeout.expired():
526            time.sleep(0.05)    # prevent 100% CPU load
527            if sum(o.active for o in items) == len(items):
528                break
529        else:
530            raise SerialException("Remote does not accept parameter change (RFC2217): {!r}".format(items))
531        if self.logger:
532            self.logger.info("Negotiated settings: {}".format(items))
533
534        if self._rtscts and self._xonxoff:
535            raise ValueError('xonxoff and rtscts together are not supported')
536        elif self._rtscts:
537            self.rfc2217_set_control(SET_CONTROL_USE_HW_FLOW_CONTROL)
538        elif self._xonxoff:
539            self.rfc2217_set_control(SET_CONTROL_USE_SW_FLOW_CONTROL)
540        else:
541            self.rfc2217_set_control(SET_CONTROL_USE_NO_FLOW_CONTROL)
542
543    def close(self):
544        """Close port"""
545        self.is_open = False
546        if self._socket:
547            try:
548                self._socket.shutdown(socket.SHUT_RDWR)
549                self._socket.close()
550            except:
551                # ignore errors.
552                pass
553        if self._thread:
554            self._thread.join(7)  # XXX more than socket timeout
555            self._thread = None
556            # in case of quick reconnects, give the server some time
557            time.sleep(0.3)
558        self._socket = None
559
560    def from_url(self, url):
561        """\
562        extract host and port from an URL string, other settings are extracted
563        an stored in instance
564        """
565        parts = urlparse.urlsplit(url)
566        if parts.scheme != "rfc2217":
567            raise SerialException(
568                'expected a string in the form '
569                '"rfc2217://<host>:<port>[?option[&option...]]": '
570                'not starting with rfc2217:// ({!r})'.format(parts.scheme))
571        try:
572            # process options now, directly altering self
573            for option, values in urlparse.parse_qs(parts.query, True).items():
574                if option == 'logging':
575                    logging.basicConfig()   # XXX is that good to call it here?
576                    self.logger = logging.getLogger('pySerial.rfc2217')
577                    self.logger.setLevel(LOGGER_LEVELS[values[0]])
578                    self.logger.debug('enabled logging')
579                elif option == 'ign_set_control':
580                    self._ignore_set_control_answer = True
581                elif option == 'poll_modem':
582                    self._poll_modem_state = True
583                elif option == 'timeout':
584                    self._network_timeout = float(values[0])
585                else:
586                    raise ValueError('unknown option: {!r}'.format(option))
587            if not 0 <= parts.port < 65536:
588                raise ValueError("port not in range 0...65535")
589        except ValueError as e:
590            raise SerialException(
591                'expected a string in the form '
592                '"rfc2217://<host>:<port>[?option[&option...]]": {}'.format(e))
593        return (parts.hostname, parts.port)
594
595    #  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -
596
597    @property
598    def in_waiting(self):
599        """Return the number of bytes currently in the input buffer."""
600        if not self.is_open:
601            raise PortNotOpenError()
602        return self._read_buffer.qsize()
603
604    def read(self, size=1):
605        """\
606        Read size bytes from the serial port. If a timeout is set it may
607        return less characters as requested. With no timeout it will block
608        until the requested number of bytes is read.
609        """
610        if not self.is_open:
611            raise PortNotOpenError()
612        data = bytearray()
613        try:
614            timeout = Timeout(self._timeout)
615            while len(data) < size:
616                if self._thread is None or not self._thread.is_alive():
617                    raise SerialException('connection failed (reader thread died)')
618                buf = self._read_buffer.get(True, timeout.time_left())
619                if buf is None:
620                    return bytes(data)
621                data += buf
622                if timeout.expired():
623                    break
624        except Queue.Empty:  # -> timeout
625            pass
626        return bytes(data)
627
628    def write(self, data):
629        """\
630        Output the given byte string over the serial port. Can block if the
631        connection is blocked. May raise SerialException if the connection is
632        closed.
633        """
634        if not self.is_open:
635            raise PortNotOpenError()
636        with self._write_lock:
637            try:
638                self._socket.sendall(to_bytes(data).replace(IAC, IAC_DOUBLED))
639            except socket.error as e:
640                raise SerialException("connection failed (socket error): {}".format(e))
641        return len(data)
642
643    def reset_input_buffer(self):
644        """Clear input buffer, discarding all that is in the buffer."""
645        if not self.is_open:
646            raise PortNotOpenError()
647        self.rfc2217_send_purge(PURGE_RECEIVE_BUFFER)
648        # empty read buffer
649        while self._read_buffer.qsize():
650            self._read_buffer.get(False)
651
652    def reset_output_buffer(self):
653        """\
654        Clear output buffer, aborting the current output and
655        discarding all that is in the buffer.
656        """
657        if not self.is_open:
658            raise PortNotOpenError()
659        self.rfc2217_send_purge(PURGE_TRANSMIT_BUFFER)
660
661    def _update_break_state(self):
662        """\
663        Set break: Controls TXD. When active, to transmitting is
664        possible.
665        """
666        if not self.is_open:
667            raise PortNotOpenError()
668        if self.logger:
669            self.logger.info('set BREAK to {}'.format('active' if self._break_state else 'inactive'))
670        if self._break_state:
671            self.rfc2217_set_control(SET_CONTROL_BREAK_ON)
672        else:
673            self.rfc2217_set_control(SET_CONTROL_BREAK_OFF)
674
675    def _update_rts_state(self):
676        """Set terminal status line: Request To Send."""
677        if not self.is_open:
678            raise PortNotOpenError()
679        if self.logger:
680            self.logger.info('set RTS to {}'.format('active' if self._rts_state else 'inactive'))
681        if self._rts_state:
682            self.rfc2217_set_control(SET_CONTROL_RTS_ON)
683        else:
684            self.rfc2217_set_control(SET_CONTROL_RTS_OFF)
685
686    def _update_dtr_state(self):
687        """Set terminal status line: Data Terminal Ready."""
688        if not self.is_open:
689            raise PortNotOpenError()
690        if self.logger:
691            self.logger.info('set DTR to {}'.format('active' if self._dtr_state else 'inactive'))
692        if self._dtr_state:
693            self.rfc2217_set_control(SET_CONTROL_DTR_ON)
694        else:
695            self.rfc2217_set_control(SET_CONTROL_DTR_OFF)
696
697    @property
698    def cts(self):
699        """Read terminal status line: Clear To Send."""
700        if not self.is_open:
701            raise PortNotOpenError()
702        return bool(self.get_modem_state() & MODEMSTATE_MASK_CTS)
703
704    @property
705    def dsr(self):
706        """Read terminal status line: Data Set Ready."""
707        if not self.is_open:
708            raise PortNotOpenError()
709        return bool(self.get_modem_state() & MODEMSTATE_MASK_DSR)
710
711    @property
712    def ri(self):
713        """Read terminal status line: Ring Indicator."""
714        if not self.is_open:
715            raise PortNotOpenError()
716        return bool(self.get_modem_state() & MODEMSTATE_MASK_RI)
717
718    @property
719    def cd(self):
720        """Read terminal status line: Carrier Detect."""
721        if not self.is_open:
722            raise PortNotOpenError()
723        return bool(self.get_modem_state() & MODEMSTATE_MASK_CD)
724
725    # - - - platform specific - - -
726    # None so far
727
728    # - - - RFC2217 specific - - -
729
730    def _telnet_read_loop(self):
731        """Read loop for the socket."""
732        mode = M_NORMAL
733        suboption = None
734        try:
735            while self.is_open:
736                try:
737                    data = self._socket.recv(1024)
738                except socket.timeout:
739                    # just need to get out of recv form time to time to check if
740                    # still alive
741                    continue
742                except socket.error as e:
743                    # connection fails -> terminate loop
744                    if self.logger:
745                        self.logger.debug("socket error in reader thread: {}".format(e))
746                    self._read_buffer.put(None)
747                    break
748                if not data:
749                    self._read_buffer.put(None)
750                    break  # lost connection
751                for byte in iterbytes(data):
752                    if mode == M_NORMAL:
753                        # interpret as command or as data
754                        if byte == IAC:
755                            mode = M_IAC_SEEN
756                        else:
757                            # store data in read buffer or sub option buffer
758                            # depending on state
759                            if suboption is not None:
760                                suboption += byte
761                            else:
762                                self._read_buffer.put(byte)
763                    elif mode == M_IAC_SEEN:
764                        if byte == IAC:
765                            # interpret as command doubled -> insert character
766                            # itself
767                            if suboption is not None:
768                                suboption += IAC
769                            else:
770                                self._read_buffer.put(IAC)
771                            mode = M_NORMAL
772                        elif byte == SB:
773                            # sub option start
774                            suboption = bytearray()
775                            mode = M_NORMAL
776                        elif byte == SE:
777                            # sub option end -> process it now
778                            self._telnet_process_subnegotiation(bytes(suboption))
779                            suboption = None
780                            mode = M_NORMAL
781                        elif byte in (DO, DONT, WILL, WONT):
782                            # negotiation
783                            telnet_command = byte
784                            mode = M_NEGOTIATE
785                        else:
786                            # other telnet commands
787                            self._telnet_process_command(byte)
788                            mode = M_NORMAL
789                    elif mode == M_NEGOTIATE:  # DO, DONT, WILL, WONT was received, option now following
790                        self._telnet_negotiate_option(telnet_command, byte)
791                        mode = M_NORMAL
792        finally:
793            if self.logger:
794                self.logger.debug("read thread terminated")
795
796    # - incoming telnet commands and options
797
798    def _telnet_process_command(self, command):
799        """Process commands other than DO, DONT, WILL, WONT."""
800        # Currently none. RFC2217 only uses negotiation and subnegotiation.
801        if self.logger:
802            self.logger.warning("ignoring Telnet command: {!r}".format(command))
803
804    def _telnet_negotiate_option(self, command, option):
805        """Process incoming DO, DONT, WILL, WONT."""
806        # check our registered telnet options and forward command to them
807        # they know themselves if they have to answer or not
808        known = False
809        for item in self._telnet_options:
810            # can have more than one match! as some options are duplicated for
811            # 'us' and 'them'
812            if item.option == option:
813                item.process_incoming(command)
814                known = True
815        if not known:
816            # handle unknown options
817            # only answer to positive requests and deny them
818            if command == WILL or command == DO:
819                self.telnet_send_option((DONT if command == WILL else WONT), option)
820                if self.logger:
821                    self.logger.warning("rejected Telnet option: {!r}".format(option))
822
823    def _telnet_process_subnegotiation(self, suboption):
824        """Process subnegotiation, the data between IAC SB and IAC SE."""
825        if suboption[0:1] == COM_PORT_OPTION:
826            if suboption[1:2] == SERVER_NOTIFY_LINESTATE and len(suboption) >= 3:
827                self._linestate = ord(suboption[2:3])  # ensure it is a number
828                if self.logger:
829                    self.logger.info("NOTIFY_LINESTATE: {}".format(self._linestate))
830            elif suboption[1:2] == SERVER_NOTIFY_MODEMSTATE and len(suboption) >= 3:
831                self._modemstate = ord(suboption[2:3])  # ensure it is a number
832                if self.logger:
833                    self.logger.info("NOTIFY_MODEMSTATE: {}".format(self._modemstate))
834                # update time when we think that a poll would make sense
835                self._modemstate_timeout.restart(0.3)
836            elif suboption[1:2] == FLOWCONTROL_SUSPEND:
837                self._remote_suspend_flow = True
838            elif suboption[1:2] == FLOWCONTROL_RESUME:
839                self._remote_suspend_flow = False
840            else:
841                for item in self._rfc2217_options.values():
842                    if item.ack_option == suboption[1:2]:
843                        #~ print "processing COM_PORT_OPTION: %r" % list(suboption[1:])
844                        item.check_answer(bytes(suboption[2:]))
845                        break
846                else:
847                    if self.logger:
848                        self.logger.warning("ignoring COM_PORT_OPTION: {!r}".format(suboption))
849        else:
850            if self.logger:
851                self.logger.warning("ignoring subnegotiation: {!r}".format(suboption))
852
853    # - outgoing telnet commands and options
854
855    def _internal_raw_write(self, data):
856        """internal socket write with no data escaping. used to send telnet stuff."""
857        with self._write_lock:
858            self._socket.sendall(data)
859
860    def telnet_send_option(self, action, option):
861        """Send DO, DONT, WILL, WONT."""
862        self._internal_raw_write(IAC + action + option)
863
864    def rfc2217_send_subnegotiation(self, option, value=b''):
865        """Subnegotiation of RFC2217 parameters."""
866        value = value.replace(IAC, IAC_DOUBLED)
867        self._internal_raw_write(IAC + SB + COM_PORT_OPTION + option + value + IAC + SE)
868
869    def rfc2217_send_purge(self, value):
870        """\
871        Send purge request to the remote.
872        (PURGE_RECEIVE_BUFFER / PURGE_TRANSMIT_BUFFER / PURGE_BOTH_BUFFERS)
873        """
874        item = self._rfc2217_options['purge']
875        item.set(value)  # transmit desired purge type
876        item.wait(self._network_timeout)  # wait for acknowledge from the server
877
878    def rfc2217_set_control(self, value):
879        """transmit change of control line to remote"""
880        item = self._rfc2217_options['control']
881        item.set(value)  # transmit desired control type
882        if self._ignore_set_control_answer:
883            # answers are ignored when option is set. compatibility mode for
884            # servers that answer, but not the expected one... (or no answer
885            # at all) i.e. sredird
886            time.sleep(0.1)  # this helps getting the unit tests passed
887        else:
888            item.wait(self._network_timeout)  # wait for acknowledge from the server
889
890    def rfc2217_flow_server_ready(self):
891        """\
892        check if server is ready to receive data. block for some time when
893        not.
894        """
895        #~ if self._remote_suspend_flow:
896        #~     wait---
897
898    def get_modem_state(self):
899        """\
900        get last modem state (cached value. If value is "old", request a new
901        one. This cache helps that we don't issue to many requests when e.g. all
902        status lines, one after the other is queried by the user (CTS, DSR
903        etc.)
904        """
905        # active modem state polling enabled? is the value fresh enough?
906        if self._poll_modem_state and self._modemstate_timeout.expired():
907            if self.logger:
908                self.logger.debug('polling modem state')
909            # when it is older, request an update
910            self.rfc2217_send_subnegotiation(NOTIFY_MODEMSTATE)
911            timeout = Timeout(self._network_timeout)
912            while not timeout.expired():
913                time.sleep(0.05)    # prevent 100% CPU load
914                # when expiration time is updated, it means that there is a new
915                # value
916                if not self._modemstate_timeout.expired():
917                    break
918            else:
919                if self.logger:
920                    self.logger.warning('poll for modem state failed')
921            # even when there is a timeout, do not generate an error just
922            # return the last known value. this way we can support buggy
923            # servers that do not respond to polls, but send automatic
924            # updates.
925        if self._modemstate is not None:
926            if self.logger:
927                self.logger.debug('using cached modem state')
928            return self._modemstate
929        else:
930            # never received a notification from the server
931            raise SerialException("remote sends no NOTIFY_MODEMSTATE")
932
933
934#############################################################################
935# The following is code that helps implementing an RFC 2217 server.
936
937class PortManager(object):
938    """\
939    This class manages the state of Telnet and RFC 2217. It needs a serial
940    instance and a connection to work with. Connection is expected to implement
941    a (thread safe) write function, that writes the string to the network.
942    """
943
944    def __init__(self, serial_port, connection, logger=None):
945        self.serial = serial_port
946        self.connection = connection
947        self.logger = logger
948        self._client_is_rfc2217 = False
949
950        # filter state machine
951        self.mode = M_NORMAL
952        self.suboption = None
953        self.telnet_command = None
954
955        # states for modem/line control events
956        self.modemstate_mask = 255
957        self.last_modemstate = None
958        self.linstate_mask = 0
959
960        # all supported telnet options
961        self._telnet_options = [
962            TelnetOption(self, 'ECHO', ECHO, WILL, WONT, DO, DONT, REQUESTED),
963            TelnetOption(self, 'we-SGA', SGA, WILL, WONT, DO, DONT, REQUESTED),
964            TelnetOption(self, 'they-SGA', SGA, DO, DONT, WILL, WONT, INACTIVE),
965            TelnetOption(self, 'we-BINARY', BINARY, WILL, WONT, DO, DONT, INACTIVE),
966            TelnetOption(self, 'they-BINARY', BINARY, DO, DONT, WILL, WONT, REQUESTED),
967            TelnetOption(self, 'we-RFC2217', COM_PORT_OPTION, WILL, WONT, DO, DONT, REQUESTED, self._client_ok),
968            TelnetOption(self, 'they-RFC2217', COM_PORT_OPTION, DO, DONT, WILL, WONT, INACTIVE, self._client_ok),
969        ]
970
971        # negotiate Telnet/RFC2217 -> send initial requests
972        if self.logger:
973            self.logger.debug("requesting initial Telnet/RFC 2217 options")
974        for option in self._telnet_options:
975            if option.state is REQUESTED:
976                self.telnet_send_option(option.send_yes, option.option)
977        # issue 1st modem state notification
978
979    def _client_ok(self):
980        """\
981        callback of telnet option. It gets called when option is activated.
982        This one here is used to detect when the client agrees on RFC 2217. A
983        flag is set so that other functions like check_modem_lines know if the
984        client is OK.
985        """
986        # The callback is used for we and they so if one party agrees, we're
987        # already happy. it seems not all servers do the negotiation correctly
988        # and i guess there are incorrect clients too.. so be happy if client
989        # answers one or the other positively.
990        self._client_is_rfc2217 = True
991        if self.logger:
992            self.logger.info("client accepts RFC 2217")
993        # this is to ensure that the client gets a notification, even if there
994        # was no change
995        self.check_modem_lines(force_notification=True)
996
997    # - outgoing telnet commands and options
998
999    def telnet_send_option(self, action, option):
1000        """Send DO, DONT, WILL, WONT."""
1001        self.connection.write(IAC + action + option)
1002
1003    def rfc2217_send_subnegotiation(self, option, value=b''):
1004        """Subnegotiation of RFC 2217 parameters."""
1005        value = value.replace(IAC, IAC_DOUBLED)
1006        self.connection.write(IAC + SB + COM_PORT_OPTION + option + value + IAC + SE)
1007
1008    # - check modem lines, needs to be called periodically from user to
1009    # establish polling
1010
1011    def check_modem_lines(self, force_notification=False):
1012        """\
1013        read control lines from serial port and compare the last value sent to remote.
1014        send updates on changes.
1015        """
1016        modemstate = (
1017            (self.serial.cts and MODEMSTATE_MASK_CTS) |
1018            (self.serial.dsr and MODEMSTATE_MASK_DSR) |
1019            (self.serial.ri and MODEMSTATE_MASK_RI) |
1020            (self.serial.cd and MODEMSTATE_MASK_CD))
1021        # check what has changed
1022        deltas = modemstate ^ (self.last_modemstate or 0)  # when last is None -> 0
1023        if deltas & MODEMSTATE_MASK_CTS:
1024            modemstate |= MODEMSTATE_MASK_CTS_CHANGE
1025        if deltas & MODEMSTATE_MASK_DSR:
1026            modemstate |= MODEMSTATE_MASK_DSR_CHANGE
1027        if deltas & MODEMSTATE_MASK_RI:
1028            modemstate |= MODEMSTATE_MASK_RI_CHANGE
1029        if deltas & MODEMSTATE_MASK_CD:
1030            modemstate |= MODEMSTATE_MASK_CD_CHANGE
1031        # if new state is different and the mask allows this change, send
1032        # notification. suppress notifications when client is not rfc2217
1033        if modemstate != self.last_modemstate or force_notification:
1034            if (self._client_is_rfc2217 and (modemstate & self.modemstate_mask)) or force_notification:
1035                self.rfc2217_send_subnegotiation(
1036                    SERVER_NOTIFY_MODEMSTATE,
1037                    to_bytes([modemstate & self.modemstate_mask]))
1038                if self.logger:
1039                    self.logger.info("NOTIFY_MODEMSTATE: {}".format(modemstate))
1040            # save last state, but forget about deltas.
1041            # otherwise it would also notify about changing deltas which is
1042            # probably not very useful
1043            self.last_modemstate = modemstate & 0xf0
1044
1045    # - outgoing data escaping
1046
1047    def escape(self, data):
1048        """\
1049        This generator function is for the user. All outgoing data has to be
1050        properly escaped, so that no IAC character in the data stream messes up
1051        the Telnet state machine in the server.
1052
1053        socket.sendall(escape(data))
1054        """
1055        for byte in iterbytes(data):
1056            if byte == IAC:
1057                yield IAC
1058                yield IAC
1059            else:
1060                yield byte
1061
1062    # - incoming data filter
1063
1064    def filter(self, data):
1065        """\
1066        Handle a bunch of incoming bytes. This is a generator. It will yield
1067        all characters not of interest for Telnet/RFC 2217.
1068
1069        The idea is that the reader thread pushes data from the socket through
1070        this filter:
1071
1072        for byte in filter(socket.recv(1024)):
1073            # do things like CR/LF conversion/whatever
1074            # and write data to the serial port
1075            serial.write(byte)
1076
1077        (socket error handling code left as exercise for the reader)
1078        """
1079        for byte in iterbytes(data):
1080            if self.mode == M_NORMAL:
1081                # interpret as command or as data
1082                if byte == IAC:
1083                    self.mode = M_IAC_SEEN
1084                else:
1085                    # store data in sub option buffer or pass it to our
1086                    # consumer depending on state
1087                    if self.suboption is not None:
1088                        self.suboption += byte
1089                    else:
1090                        yield byte
1091            elif self.mode == M_IAC_SEEN:
1092                if byte == IAC:
1093                    # interpret as command doubled -> insert character
1094                    # itself
1095                    if self.suboption is not None:
1096                        self.suboption += byte
1097                    else:
1098                        yield byte
1099                    self.mode = M_NORMAL
1100                elif byte == SB:
1101                    # sub option start
1102                    self.suboption = bytearray()
1103                    self.mode = M_NORMAL
1104                elif byte == SE:
1105                    # sub option end -> process it now
1106                    self._telnet_process_subnegotiation(bytes(self.suboption))
1107                    self.suboption = None
1108                    self.mode = M_NORMAL
1109                elif byte in (DO, DONT, WILL, WONT):
1110                    # negotiation
1111                    self.telnet_command = byte
1112                    self.mode = M_NEGOTIATE
1113                else:
1114                    # other telnet commands
1115                    self._telnet_process_command(byte)
1116                    self.mode = M_NORMAL
1117            elif self.mode == M_NEGOTIATE:  # DO, DONT, WILL, WONT was received, option now following
1118                self._telnet_negotiate_option(self.telnet_command, byte)
1119                self.mode = M_NORMAL
1120
1121    # - incoming telnet commands and options
1122
1123    def _telnet_process_command(self, command):
1124        """Process commands other than DO, DONT, WILL, WONT."""
1125        # Currently none. RFC2217 only uses negotiation and subnegotiation.
1126        if self.logger:
1127            self.logger.warning("ignoring Telnet command: {!r}".format(command))
1128
1129    def _telnet_negotiate_option(self, command, option):
1130        """Process incoming DO, DONT, WILL, WONT."""
1131        # check our registered telnet options and forward command to them
1132        # they know themselves if they have to answer or not
1133        known = False
1134        for item in self._telnet_options:
1135            # can have more than one match! as some options are duplicated for
1136            # 'us' and 'them'
1137            if item.option == option:
1138                item.process_incoming(command)
1139                known = True
1140        if not known:
1141            # handle unknown options
1142            # only answer to positive requests and deny them
1143            if command == WILL or command == DO:
1144                self.telnet_send_option((DONT if command == WILL else WONT), option)
1145                if self.logger:
1146                    self.logger.warning("rejected Telnet option: {!r}".format(option))
1147
1148    def _telnet_process_subnegotiation(self, suboption):
1149        """Process subnegotiation, the data between IAC SB and IAC SE."""
1150        if suboption[0:1] == COM_PORT_OPTION:
1151            if self.logger:
1152                self.logger.debug('received COM_PORT_OPTION: {!r}'.format(suboption))
1153            if suboption[1:2] == SET_BAUDRATE:
1154                backup = self.serial.baudrate
1155                try:
1156                    (baudrate,) = struct.unpack(b"!I", suboption[2:6])
1157                    if baudrate != 0:
1158                        self.serial.baudrate = baudrate
1159                except ValueError as e:
1160                    if self.logger:
1161                        self.logger.error("failed to set baud rate: {}".format(e))
1162                    self.serial.baudrate = backup
1163                else:
1164                    if self.logger:
1165                        self.logger.info("{} baud rate: {}".format('set' if baudrate else 'get', self.serial.baudrate))
1166                self.rfc2217_send_subnegotiation(SERVER_SET_BAUDRATE, struct.pack(b"!I", self.serial.baudrate))
1167            elif suboption[1:2] == SET_DATASIZE:
1168                backup = self.serial.bytesize
1169                try:
1170                    (datasize,) = struct.unpack(b"!B", suboption[2:3])
1171                    if datasize != 0:
1172                        self.serial.bytesize = datasize
1173                except ValueError as e:
1174                    if self.logger:
1175                        self.logger.error("failed to set data size: {}".format(e))
1176                    self.serial.bytesize = backup
1177                else:
1178                    if self.logger:
1179                        self.logger.info("{} data size: {}".format('set' if datasize else 'get', self.serial.bytesize))
1180                self.rfc2217_send_subnegotiation(SERVER_SET_DATASIZE, struct.pack(b"!B", self.serial.bytesize))
1181            elif suboption[1:2] == SET_PARITY:
1182                backup = self.serial.parity
1183                try:
1184                    parity = struct.unpack(b"!B", suboption[2:3])[0]
1185                    if parity != 0:
1186                        self.serial.parity = RFC2217_REVERSE_PARITY_MAP[parity]
1187                except ValueError as e:
1188                    if self.logger:
1189                        self.logger.error("failed to set parity: {}".format(e))
1190                    self.serial.parity = backup
1191                else:
1192                    if self.logger:
1193                        self.logger.info("{} parity: {}".format('set' if parity else 'get', self.serial.parity))
1194                self.rfc2217_send_subnegotiation(
1195                    SERVER_SET_PARITY,
1196                    struct.pack(b"!B", RFC2217_PARITY_MAP[self.serial.parity]))
1197            elif suboption[1:2] == SET_STOPSIZE:
1198                backup = self.serial.stopbits
1199                try:
1200                    stopbits = struct.unpack(b"!B", suboption[2:3])[0]
1201                    if stopbits != 0:
1202                        self.serial.stopbits = RFC2217_REVERSE_STOPBIT_MAP[stopbits]
1203                except ValueError as e:
1204                    if self.logger:
1205                        self.logger.error("failed to set stop bits: {}".format(e))
1206                    self.serial.stopbits = backup
1207                else:
1208                    if self.logger:
1209                        self.logger.info("{} stop bits: {}".format('set' if stopbits else 'get', self.serial.stopbits))
1210                self.rfc2217_send_subnegotiation(
1211                    SERVER_SET_STOPSIZE,
1212                    struct.pack(b"!B", RFC2217_STOPBIT_MAP[self.serial.stopbits]))
1213            elif suboption[1:2] == SET_CONTROL:
1214                if suboption[2:3] == SET_CONTROL_REQ_FLOW_SETTING:
1215                    if self.serial.xonxoff:
1216                        self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_SW_FLOW_CONTROL)
1217                    elif self.serial.rtscts:
1218                        self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_HW_FLOW_CONTROL)
1219                    else:
1220                        self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_NO_FLOW_CONTROL)
1221                elif suboption[2:3] == SET_CONTROL_USE_NO_FLOW_CONTROL:
1222                    self.serial.xonxoff = False
1223                    self.serial.rtscts = False
1224                    if self.logger:
1225                        self.logger.info("changed flow control to None")
1226                    self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_NO_FLOW_CONTROL)
1227                elif suboption[2:3] == SET_CONTROL_USE_SW_FLOW_CONTROL:
1228                    self.serial.xonxoff = True
1229                    if self.logger:
1230                        self.logger.info("changed flow control to XON/XOFF")
1231                    self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_SW_FLOW_CONTROL)
1232                elif suboption[2:3] == SET_CONTROL_USE_HW_FLOW_CONTROL:
1233                    self.serial.rtscts = True
1234                    if self.logger:
1235                        self.logger.info("changed flow control to RTS/CTS")
1236                    self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_HW_FLOW_CONTROL)
1237                elif suboption[2:3] == SET_CONTROL_REQ_BREAK_STATE:
1238                    if self.logger:
1239                        self.logger.warning("requested break state - not implemented")
1240                    pass  # XXX needs cached value
1241                elif suboption[2:3] == SET_CONTROL_BREAK_ON:
1242                    self.serial.break_condition = True
1243                    if self.logger:
1244                        self.logger.info("changed BREAK to active")
1245                    self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_BREAK_ON)
1246                elif suboption[2:3] == SET_CONTROL_BREAK_OFF:
1247                    self.serial.break_condition = False
1248                    if self.logger:
1249                        self.logger.info("changed BREAK to inactive")
1250                    self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_BREAK_OFF)
1251                elif suboption[2:3] == SET_CONTROL_REQ_DTR:
1252                    if self.logger:
1253                        self.logger.warning("requested DTR state - not implemented")
1254                    pass  # XXX needs cached value
1255                elif suboption[2:3] == SET_CONTROL_DTR_ON:
1256                    self.serial.dtr = True
1257                    if self.logger:
1258                        self.logger.info("changed DTR to active")
1259                    self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_DTR_ON)
1260                elif suboption[2:3] == SET_CONTROL_DTR_OFF:
1261                    self.serial.dtr = False
1262                    if self.logger:
1263                        self.logger.info("changed DTR to inactive")
1264                    self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_DTR_OFF)
1265                elif suboption[2:3] == SET_CONTROL_REQ_RTS:
1266                    if self.logger:
1267                        self.logger.warning("requested RTS state - not implemented")
1268                    pass  # XXX needs cached value
1269                    #~ self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_RTS_ON)
1270                elif suboption[2:3] == SET_CONTROL_RTS_ON:
1271                    self.serial.rts = True
1272                    if self.logger:
1273                        self.logger.info("changed RTS to active")
1274                    self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_RTS_ON)
1275                elif suboption[2:3] == SET_CONTROL_RTS_OFF:
1276                    self.serial.rts = False
1277                    if self.logger:
1278                        self.logger.info("changed RTS to inactive")
1279                    self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_RTS_OFF)
1280                #~ elif suboption[2:3] == SET_CONTROL_REQ_FLOW_SETTING_IN:
1281                #~ elif suboption[2:3] == SET_CONTROL_USE_NO_FLOW_CONTROL_IN:
1282                #~ elif suboption[2:3] == SET_CONTROL_USE_SW_FLOW_CONTOL_IN:
1283                #~ elif suboption[2:3] == SET_CONTROL_USE_HW_FLOW_CONTOL_IN:
1284                #~ elif suboption[2:3] == SET_CONTROL_USE_DCD_FLOW_CONTROL:
1285                #~ elif suboption[2:3] == SET_CONTROL_USE_DTR_FLOW_CONTROL:
1286                #~ elif suboption[2:3] == SET_CONTROL_USE_DSR_FLOW_CONTROL:
1287            elif suboption[1:2] == NOTIFY_LINESTATE:
1288                # client polls for current state
1289                self.rfc2217_send_subnegotiation(
1290                    SERVER_NOTIFY_LINESTATE,
1291                    to_bytes([0]))   # sorry, nothing like that implemented
1292            elif suboption[1:2] == NOTIFY_MODEMSTATE:
1293                if self.logger:
1294                    self.logger.info("request for modem state")
1295                # client polls for current state
1296                self.check_modem_lines(force_notification=True)
1297            elif suboption[1:2] == FLOWCONTROL_SUSPEND:
1298                if self.logger:
1299                    self.logger.info("suspend")
1300                self._remote_suspend_flow = True
1301            elif suboption[1:2] == FLOWCONTROL_RESUME:
1302                if self.logger:
1303                    self.logger.info("resume")
1304                self._remote_suspend_flow = False
1305            elif suboption[1:2] == SET_LINESTATE_MASK:
1306                self.linstate_mask = ord(suboption[2:3])  # ensure it is a number
1307                if self.logger:
1308                    self.logger.info("line state mask: 0x{:02x}".format(self.linstate_mask))
1309            elif suboption[1:2] == SET_MODEMSTATE_MASK:
1310                self.modemstate_mask = ord(suboption[2:3])  # ensure it is a number
1311                if self.logger:
1312                    self.logger.info("modem state mask: 0x{:02x}".format(self.modemstate_mask))
1313            elif suboption[1:2] == PURGE_DATA:
1314                if suboption[2:3] == PURGE_RECEIVE_BUFFER:
1315                    self.serial.reset_input_buffer()
1316                    if self.logger:
1317                        self.logger.info("purge in")
1318                    self.rfc2217_send_subnegotiation(SERVER_PURGE_DATA, PURGE_RECEIVE_BUFFER)
1319                elif suboption[2:3] == PURGE_TRANSMIT_BUFFER:
1320                    self.serial.reset_output_buffer()
1321                    if self.logger:
1322                        self.logger.info("purge out")
1323                    self.rfc2217_send_subnegotiation(SERVER_PURGE_DATA, PURGE_TRANSMIT_BUFFER)
1324                elif suboption[2:3] == PURGE_BOTH_BUFFERS:
1325                    self.serial.reset_input_buffer()
1326                    self.serial.reset_output_buffer()
1327                    if self.logger:
1328                        self.logger.info("purge both")
1329                    self.rfc2217_send_subnegotiation(SERVER_PURGE_DATA, PURGE_BOTH_BUFFERS)
1330                else:
1331                    if self.logger:
1332                        self.logger.error("undefined PURGE_DATA: {!r}".format(list(suboption[2:])))
1333            else:
1334                if self.logger:
1335                    self.logger.error("undefined COM_PORT_OPTION: {!r}".format(list(suboption[1:])))
1336        else:
1337            if self.logger:
1338                self.logger.warning("unknown subnegotiation: {!r}".format(suboption))
1339
1340
1341# simple client test
1342if __name__ == '__main__':
1343    import sys
1344    s = Serial('rfc2217://localhost:7000', 115200)
1345    sys.stdout.write('{}\n'.format(s))
1346
1347    sys.stdout.write("write...\n")
1348    s.write(b"hello\n")
1349    s.flush()
1350    sys.stdout.write("read: {}\n".format(s.read(5)))
1351    s.close()
1352