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