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