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,
159 }
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,
212 }
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,
219 }
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()