blob: a8bb0062e9e1f07488bb6ef9d69b75a981149b2c [file] [log] [blame]
cliechti8099bed2009-08-01 23:59:18 +00001#! python
2#
cliechti8099bed2009-08-01 23:59:18 +00003# 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#
Chris Liechti3e02f702015-12-16 23:06:04 +01007# This file is part of pySerial. https://github.com/pyserial/pyserial
Chris Liechti01587b12015-08-05 02:39:32 +02008# (C) 2001-2015 Chris Liechti <cliechti@gmx.net>
Chris Liechtifbdd8a02015-08-09 02:37:45 +02009#
10# SPDX-License-Identifier: BSD-3-Clause
cliechti8099bed2009-08-01 23:59:18 +000011
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
cliechti8099bed2009-08-01 23:59:18 +000017
Chris Liechti033f17c2015-08-30 21:28:04 +020018# ###########################################################################
cliechti81c54762009-08-03 23:53:27 +000019# observations and issues with servers
Chris Liechti033f17c2015-08-30 21:28:04 +020020# ===========================================================================
cliechti81c54762009-08-03 23:53:27 +000021# 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
Chris Liechti033f17c2015-08-30 21:28:04 +020029# ===========================================================================
cliechti81c54762009-08-03 23:53:27 +000030# 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.
Chris Liechti033f17c2015-08-30 21:28:04 +020033# ===========================================================================
cliechti81c54762009-08-03 23:53:27 +000034# 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
Chris Liechti033f17c2015-08-30 21:28:04 +020045# ###########################################################################
cliechti81c54762009-08-03 23:53:27 +000046
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:
Chris Liechti142ae562015-08-23 01:11:06 +020050# rfc2217://<host>:<port>[?option[&option...]]
cliechti81c54762009-08-03 23:53:27 +000051#
52# options:
Chris Liechti01587b12015-08-05 02:39:32 +020053# - "logging" set log level print diagnostic messages (e.g. "logging=debug")
cliechti81c54762009-08-03 23:53:27 +000054# - "ign_set_control": do not look at the answers to SET_CONTROL
cliechti7cb78e82009-08-05 15:47:57 +000055# - "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).
cliechti81c54762009-08-03 23:53:27 +000059# the order of the options is not relevant
60
cliechti5cc3eb12009-08-11 23:04:30 +000061import logging
Chris Liechtic4bca9e2015-08-07 14:40:41 +020062import socket
63import struct
64import threading
65import time
66try:
67 import urlparse
68except ImportError:
69 import urllib.parse as urlparse
Chris Liechtid2146002015-08-04 16:57:16 +020070try:
71 import Queue
72except ImportError:
73 import queue as Queue
74
Chris Liechti033f17c2015-08-30 21:28:04 +020075import serial
Chris Liechti8f6d3d02016-08-31 00:46:50 +020076from serial.serialutil import SerialBase, SerialException, to_bytes, \
77 iterbytes, portNotOpenError, Timeout
Chris Liechtib4cda3a2015-08-08 17:12:08 +020078
cliechti8099bed2009-08-01 23:59:18 +000079# port string is expected to be something like this:
80# rfc2217://host:port
81# host may be an IP or including domain, whatever.
82# port is 0...65535
83
Chris Liechti3ad62fb2015-08-29 21:53:32 +020084# map log level names to constants. used in from_url()
cliechti5cc3eb12009-08-11 23:04:30 +000085LOGGER_LEVELS = {
Chris Liechticb6ce1b2016-02-02 01:53:56 +010086 'debug': logging.DEBUG,
87 'info': logging.INFO,
88 'warning': logging.WARNING,
89 'error': logging.ERROR,
90}
cliechti5cc3eb12009-08-11 23:04:30 +000091
92
cliechti8099bed2009-08-01 23:59:18 +000093# telnet protocol characters
Chris Liechti033f17c2015-08-30 21:28:04 +020094SE = b'\xf0' # Subnegotiation End
Chris Liechtib4cda3a2015-08-08 17:12:08 +020095NOP = b'\xf1' # No Operation
Chris Liechti033f17c2015-08-30 21:28:04 +020096DM = b'\xf2' # Data Mark
Chris Liechtib4cda3a2015-08-08 17:12:08 +020097BRK = b'\xf3' # Break
Chris Liechti033f17c2015-08-30 21:28:04 +020098IP = b'\xf4' # Interrupt process
99AO = b'\xf5' # Abort output
Chris Liechtib4cda3a2015-08-08 17:12:08 +0200100AYT = b'\xf6' # Are You There
Chris Liechti033f17c2015-08-30 21:28:04 +0200101EC = b'\xf7' # Erase Character
102EL = b'\xf8' # Erase Line
103GA = b'\xf9' # Go Ahead
104SB = b'\xfa' # Subnegotiation Begin
Chris Liechtib4cda3a2015-08-08 17:12:08 +0200105WILL = b'\xfb'
106WONT = b'\xfc'
Chris Liechti033f17c2015-08-30 21:28:04 +0200107DO = b'\xfd'
Chris Liechtib4cda3a2015-08-08 17:12:08 +0200108DONT = b'\xfe'
Chris Liechti033f17c2015-08-30 21:28:04 +0200109IAC = b'\xff' # Interpret As Command
Chris Liechtib4cda3a2015-08-08 17:12:08 +0200110IAC_DOUBLED = b'\xff\xff'
cliechti8099bed2009-08-01 23:59:18 +0000111
112# selected telnet options
Chris Liechtib4cda3a2015-08-08 17:12:08 +0200113BINARY = b'\x00' # 8-bit data path
114ECHO = b'\x01' # echo
115SGA = b'\x03' # suppress go ahead
cliechti8099bed2009-08-01 23:59:18 +0000116
117# RFC2217
Chris Liechtib4cda3a2015-08-08 17:12:08 +0200118COM_PORT_OPTION = b'\x2c'
cliechti8099bed2009-08-01 23:59:18 +0000119
120# Client to Access Server
Chris Liechtib4cda3a2015-08-08 17:12:08 +0200121SET_BAUDRATE = b'\x01'
122SET_DATASIZE = b'\x02'
123SET_PARITY = b'\x03'
124SET_STOPSIZE = b'\x04'
125SET_CONTROL = b'\x05'
126NOTIFY_LINESTATE = b'\x06'
127NOTIFY_MODEMSTATE = b'\x07'
128FLOWCONTROL_SUSPEND = b'\x08'
129FLOWCONTROL_RESUME = b'\x09'
130SET_LINESTATE_MASK = b'\x0a'
131SET_MODEMSTATE_MASK = b'\x0b'
132PURGE_DATA = b'\x0c'
cliechti8099bed2009-08-01 23:59:18 +0000133
Chris Liechtib4cda3a2015-08-08 17:12:08 +0200134SERVER_SET_BAUDRATE = b'\x65'
135SERVER_SET_DATASIZE = b'\x66'
136SERVER_SET_PARITY = b'\x67'
137SERVER_SET_STOPSIZE = b'\x68'
138SERVER_SET_CONTROL = b'\x69'
139SERVER_NOTIFY_LINESTATE = b'\x6a'
140SERVER_NOTIFY_MODEMSTATE = b'\x6b'
141SERVER_FLOWCONTROL_SUSPEND = b'\x6c'
142SERVER_FLOWCONTROL_RESUME = b'\x6d'
143SERVER_SET_LINESTATE_MASK = b'\x6e'
144SERVER_SET_MODEMSTATE_MASK = b'\x6f'
145SERVER_PURGE_DATA = b'\x70'
cliechti8099bed2009-08-01 23:59:18 +0000146
147RFC2217_ANSWER_MAP = {
148 SET_BAUDRATE: SERVER_SET_BAUDRATE,
149 SET_DATASIZE: SERVER_SET_DATASIZE,
150 SET_PARITY: SERVER_SET_PARITY,
151 SET_STOPSIZE: SERVER_SET_STOPSIZE,
152 SET_CONTROL: SERVER_SET_CONTROL,
153 NOTIFY_LINESTATE: SERVER_NOTIFY_LINESTATE,
154 NOTIFY_MODEMSTATE: SERVER_NOTIFY_MODEMSTATE,
155 FLOWCONTROL_SUSPEND: SERVER_FLOWCONTROL_SUSPEND,
156 FLOWCONTROL_RESUME: SERVER_FLOWCONTROL_RESUME,
157 SET_LINESTATE_MASK: SERVER_SET_LINESTATE_MASK,
158 SET_MODEMSTATE_MASK: SERVER_SET_MODEMSTATE_MASK,
159 PURGE_DATA: SERVER_PURGE_DATA,
160}
161
Chris Liechtib4cda3a2015-08-08 17:12:08 +0200162SET_CONTROL_REQ_FLOW_SETTING = b'\x00' # Request Com Port Flow Control Setting (outbound/both)
163SET_CONTROL_USE_NO_FLOW_CONTROL = b'\x01' # Use No Flow Control (outbound/both)
164SET_CONTROL_USE_SW_FLOW_CONTROL = b'\x02' # Use XON/XOFF Flow Control (outbound/both)
165SET_CONTROL_USE_HW_FLOW_CONTROL = b'\x03' # Use HARDWARE Flow Control (outbound/both)
166SET_CONTROL_REQ_BREAK_STATE = b'\x04' # Request BREAK State
167SET_CONTROL_BREAK_ON = b'\x05' # Set BREAK State ON
168SET_CONTROL_BREAK_OFF = b'\x06' # Set BREAK State OFF
169SET_CONTROL_REQ_DTR = b'\x07' # Request DTR Signal State
170SET_CONTROL_DTR_ON = b'\x08' # Set DTR Signal State ON
171SET_CONTROL_DTR_OFF = b'\x09' # Set DTR Signal State OFF
172SET_CONTROL_REQ_RTS = b'\x0a' # Request RTS Signal State
173SET_CONTROL_RTS_ON = b'\x0b' # Set RTS Signal State ON
174SET_CONTROL_RTS_OFF = b'\x0c' # Set RTS Signal State OFF
175SET_CONTROL_REQ_FLOW_SETTING_IN = b'\x0d' # Request Com Port Flow Control Setting (inbound)
176SET_CONTROL_USE_NO_FLOW_CONTROL_IN = b'\x0e' # Use No Flow Control (inbound)
177SET_CONTROL_USE_SW_FLOW_CONTOL_IN = b'\x0f' # Use XON/XOFF Flow Control (inbound)
178SET_CONTROL_USE_HW_FLOW_CONTOL_IN = b'\x10' # Use HARDWARE Flow Control (inbound)
179SET_CONTROL_USE_DCD_FLOW_CONTROL = b'\x11' # Use DCD Flow Control (outbound/both)
180SET_CONTROL_USE_DTR_FLOW_CONTROL = b'\x12' # Use DTR Flow Control (inbound)
181SET_CONTROL_USE_DSR_FLOW_CONTROL = b'\x13' # Use DSR Flow Control (outbound/both)
cliechti8099bed2009-08-01 23:59:18 +0000182
Chris Liechtib4cda3a2015-08-08 17:12:08 +0200183LINESTATE_MASK_TIMEOUT = 128 # Time-out Error
184LINESTATE_MASK_SHIFTREG_EMPTY = 64 # Transfer Shift Register Empty
185LINESTATE_MASK_TRANSREG_EMPTY = 32 # Transfer Holding Register Empty
186LINESTATE_MASK_BREAK_DETECT = 16 # Break-detect Error
187LINESTATE_MASK_FRAMING_ERROR = 8 # Framing Error
188LINESTATE_MASK_PARTIY_ERROR = 4 # Parity Error
189LINESTATE_MASK_OVERRUN_ERROR = 2 # Overrun Error
190LINESTATE_MASK_DATA_READY = 1 # Data Ready
cliechti8099bed2009-08-01 23:59:18 +0000191
Chris Liechtib4cda3a2015-08-08 17:12:08 +0200192MODEMSTATE_MASK_CD = 128 # Receive Line Signal Detect (also known as Carrier Detect)
193MODEMSTATE_MASK_RI = 64 # Ring Indicator
194MODEMSTATE_MASK_DSR = 32 # Data-Set-Ready Signal State
195MODEMSTATE_MASK_CTS = 16 # Clear-To-Send Signal State
196MODEMSTATE_MASK_CD_CHANGE = 8 # Delta Receive Line Signal Detect
197MODEMSTATE_MASK_RI_CHANGE = 4 # Trailing-edge Ring Detector
198MODEMSTATE_MASK_DSR_CHANGE = 2 # Delta Data-Set-Ready
199MODEMSTATE_MASK_CTS_CHANGE = 1 # Delta Clear-To-Send
cliechti8099bed2009-08-01 23:59:18 +0000200
Chris Liechtib4cda3a2015-08-08 17:12:08 +0200201PURGE_RECEIVE_BUFFER = b'\x01' # Purge access server receive data buffer
202PURGE_TRANSMIT_BUFFER = b'\x02' # Purge access server transmit data buffer
Chris Liechti70ca49c2016-02-07 23:54:03 +0100203PURGE_BOTH_BUFFERS = b'\x03' # Purge both the access server receive data
204 # buffer and the access server transmit data buffer
cliechti8099bed2009-08-01 23:59:18 +0000205
206
207RFC2217_PARITY_MAP = {
Chris Liechti033f17c2015-08-30 21:28:04 +0200208 serial.PARITY_NONE: 1,
209 serial.PARITY_ODD: 2,
210 serial.PARITY_EVEN: 3,
211 serial.PARITY_MARK: 4,
212 serial.PARITY_SPACE: 5,
cliechti8099bed2009-08-01 23:59:18 +0000213}
Chris Liechti033f17c2015-08-30 21:28:04 +0200214RFC2217_REVERSE_PARITY_MAP = dict((v, k) for k, v in RFC2217_PARITY_MAP.items())
cliechti8099bed2009-08-01 23:59:18 +0000215
216RFC2217_STOPBIT_MAP = {
Chris Liechti033f17c2015-08-30 21:28:04 +0200217 serial.STOPBITS_ONE: 1,
218 serial.STOPBITS_ONE_POINT_FIVE: 3,
219 serial.STOPBITS_TWO: 2,
cliechti8099bed2009-08-01 23:59:18 +0000220}
Chris Liechti033f17c2015-08-30 21:28:04 +0200221RFC2217_REVERSE_STOPBIT_MAP = dict((v, k) for k, v in RFC2217_STOPBIT_MAP.items())
cliechti8099bed2009-08-01 23:59:18 +0000222
cliechti130d1f02009-08-04 02:10:58 +0000223# Telnet filter states
224M_NORMAL = 0
225M_IAC_SEEN = 1
226M_NEGOTIATE = 2
cliechti8099bed2009-08-01 23:59:18 +0000227
cliechti130d1f02009-08-04 02:10:58 +0000228# TelnetOption and TelnetSubnegotiation states
cliechtiac205322009-08-02 20:40:21 +0000229REQUESTED = 'REQUESTED'
230ACTIVE = 'ACTIVE'
231INACTIVE = 'INACTIVE'
232REALLY_INACTIVE = 'REALLY_INACTIVE'
233
Chris Liechti033f17c2015-08-30 21:28:04 +0200234
cliechtiac205322009-08-02 20:40:21 +0000235class TelnetOption(object):
cliechti1ef7e3e2009-08-03 02:38:43 +0000236 """Manage a single telnet option, keeps track of DO/DONT WILL/WONT."""
237
Chris Liechti70ca49c2016-02-07 23:54:03 +0100238 def __init__(self, connection, name, option, send_yes, send_no, ack_yes,
239 ack_no, initial_state, activation_callback=None):
cliechtieada4fd2013-07-31 16:26:07 +0000240 """\
241 Initialize option.
cliechti1ef7e3e2009-08-03 02:38:43 +0000242 :param connection: connection used to transmit answers
243 :param name: a readable name for debug outputs
244 :param send_yes: what to send when option is to be enabled.
245 :param send_no: what to send when option is to be disabled.
246 :param ack_yes: what to expect when remote agrees on option.
247 :param ack_no: what to expect when remote disagrees on option.
248 :param initial_state: options initialized with REQUESTED are tried to
249 be enabled on startup. use INACTIVE for all others.
250 """
cliechti2b929b72009-08-02 23:49:02 +0000251 self.connection = connection
cliechtiac205322009-08-02 20:40:21 +0000252 self.name = name
253 self.option = option
254 self.send_yes = send_yes
255 self.send_no = send_no
256 self.ack_yes = ack_yes
257 self.ack_no = ack_no
258 self.state = initial_state
259 self.active = False
cliechti86b593e2009-08-05 16:28:12 +0000260 self.activation_callback = activation_callback
cliechtiac205322009-08-02 20:40:21 +0000261
262 def __repr__(self):
cliechti1ef7e3e2009-08-03 02:38:43 +0000263 """String for debug outputs"""
Chris Liechti1f6643d2016-02-16 21:06:52 +0100264 return "{o.name}:{o.active}({o.state})".format(o=self)
cliechtiac205322009-08-02 20:40:21 +0000265
cliechti2b929b72009-08-02 23:49:02 +0000266 def process_incoming(self, command):
cliechti7d448562014-08-03 21:57:45 +0000267 """\
268 A DO/DONT/WILL/WONT was received for this option, update state and
269 answer when needed.
270 """
cliechtiac205322009-08-02 20:40:21 +0000271 if command == self.ack_yes:
272 if self.state is REQUESTED:
273 self.state = ACTIVE
274 self.active = True
cliechti86b593e2009-08-05 16:28:12 +0000275 if self.activation_callback is not None:
276 self.activation_callback()
cliechtiac205322009-08-02 20:40:21 +0000277 elif self.state is ACTIVE:
278 pass
279 elif self.state is INACTIVE:
280 self.state = ACTIVE
Chris Liechti277220e2016-02-18 23:27:52 +0100281 self.connection.telnet_send_option(self.send_yes, self.option)
cliechtiac205322009-08-02 20:40:21 +0000282 self.active = True
cliechti86b593e2009-08-05 16:28:12 +0000283 if self.activation_callback is not None:
284 self.activation_callback()
cliechtiac205322009-08-02 20:40:21 +0000285 elif self.state is REALLY_INACTIVE:
Chris Liechti277220e2016-02-18 23:27:52 +0100286 self.connection.telnet_send_option(self.send_no, self.option)
cliechtiac205322009-08-02 20:40:21 +0000287 else:
Chris Liechti1f6643d2016-02-16 21:06:52 +0100288 raise ValueError('option in illegal state {!r}'.format(self))
cliechtiac205322009-08-02 20:40:21 +0000289 elif command == self.ack_no:
290 if self.state is REQUESTED:
291 self.state = INACTIVE
292 self.active = False
293 elif self.state is ACTIVE:
294 self.state = INACTIVE
Chris Liechti277220e2016-02-18 23:27:52 +0100295 self.connection.telnet_send_option(self.send_no, self.option)
cliechtiac205322009-08-02 20:40:21 +0000296 self.active = False
297 elif self.state is INACTIVE:
298 pass
299 elif self.state is REALLY_INACTIVE:
300 pass
301 else:
Chris Liechti1f6643d2016-02-16 21:06:52 +0100302 raise ValueError('option in illegal state {!r}'.format(self))
cliechtiac205322009-08-02 20:40:21 +0000303
304
cliechti2b929b72009-08-02 23:49:02 +0000305class TelnetSubnegotiation(object):
cliechtieada4fd2013-07-31 16:26:07 +0000306 """\
307 A object to handle subnegotiation of options. In this case actually
308 sub-sub options for RFC 2217. It is used to track com port options.
309 """
cliechti2b929b72009-08-02 23:49:02 +0000310
311 def __init__(self, connection, name, option, ack_option=None):
Chris Liechti033f17c2015-08-30 21:28:04 +0200312 if ack_option is None:
313 ack_option = option
cliechti2b929b72009-08-02 23:49:02 +0000314 self.connection = connection
315 self.name = name
316 self.option = option
317 self.value = None
318 self.ack_option = ack_option
319 self.state = INACTIVE
320
321 def __repr__(self):
cliechti044d8662009-08-11 21:40:31 +0000322 """String for debug outputs."""
Chris Liechti1f6643d2016-02-16 21:06:52 +0100323 return "{sn.name}:{sn.state}".format(sn=self)
cliechti2b929b72009-08-02 23:49:02 +0000324
325 def set(self, value):
cliechtieada4fd2013-07-31 16:26:07 +0000326 """\
cliechti7d448562014-08-03 21:57:45 +0000327 Request a change of the value. a request is sent to the server. if
cliechti2b929b72009-08-02 23:49:02 +0000328 the client needs to know if the change is performed he has to check the
cliechtieada4fd2013-07-31 16:26:07 +0000329 state of this object.
330 """
cliechti2b929b72009-08-02 23:49:02 +0000331 self.value = value
332 self.state = REQUESTED
Chris Liechti277220e2016-02-18 23:27:52 +0100333 self.connection.rfc2217_send_subnegotiation(self.option, self.value)
cliechti6a300772009-08-12 02:28:56 +0000334 if self.connection.logger:
Chris Liechti1f6643d2016-02-16 21:06:52 +0100335 self.connection.logger.debug("SB Requesting {} -> {!r}".format(self.name, self.value))
cliechti2b929b72009-08-02 23:49:02 +0000336
Chris Liechti277220e2016-02-18 23:27:52 +0100337 def is_ready(self):
cliechtieada4fd2013-07-31 16:26:07 +0000338 """\
cliechti7d448562014-08-03 21:57:45 +0000339 Check if answer from server has been received. when server rejects
cliechtieada4fd2013-07-31 16:26:07 +0000340 the change, raise a ValueError.
341 """
cliechti2b929b72009-08-02 23:49:02 +0000342 if self.state == REALLY_INACTIVE:
Chris Liechti1f6643d2016-02-16 21:06:52 +0100343 raise ValueError("remote rejected value for option {!r}".format(self.name))
cliechti2b929b72009-08-02 23:49:02 +0000344 return self.state == ACTIVE
cliechti1ef7e3e2009-08-03 02:38:43 +0000345 # add property to have a similar interface as TelnetOption
Chris Liechti277220e2016-02-18 23:27:52 +0100346 active = property(is_ready)
cliechti2b929b72009-08-02 23:49:02 +0000347
cliechti044d8662009-08-11 21:40:31 +0000348 def wait(self, timeout=3):
cliechtieada4fd2013-07-31 16:26:07 +0000349 """\
cliechti7d448562014-08-03 21:57:45 +0000350 Wait until the subnegotiation has been acknowledged or timeout. It
cliechti1ef7e3e2009-08-03 02:38:43 +0000351 can also throw a value error when the answer from the server does not
cliechtieada4fd2013-07-31 16:26:07 +0000352 match the value sent.
353 """
Chris Liechti8f6d3d02016-08-31 00:46:50 +0200354 timeout_timer = Timeout(timeout)
355 while not timeout_timer.expired():
cliechti2b929b72009-08-02 23:49:02 +0000356 time.sleep(0.05) # prevent 100% CPU load
Chris Liechti277220e2016-02-18 23:27:52 +0100357 if self.is_ready():
cliechti2b929b72009-08-02 23:49:02 +0000358 break
359 else:
Chris Liechti1f6643d2016-02-16 21:06:52 +0100360 raise SerialException("timeout while waiting for option {!r}".format(self.name))
cliechti2b929b72009-08-02 23:49:02 +0000361
Chris Liechti277220e2016-02-18 23:27:52 +0100362 def check_answer(self, suboption):
cliechtieada4fd2013-07-31 16:26:07 +0000363 """\
cliechti7d448562014-08-03 21:57:45 +0000364 Check an incoming subnegotiation block. The parameter already has
cliechtieada4fd2013-07-31 16:26:07 +0000365 cut off the header like sub option number and com port option value.
366 """
cliechti2b929b72009-08-02 23:49:02 +0000367 if self.value == suboption[:len(self.value)]:
368 self.state = ACTIVE
369 else:
Chris Liechti277220e2016-02-18 23:27:52 +0100370 # error propagation done in is_ready
cliechti2b929b72009-08-02 23:49:02 +0000371 self.state = REALLY_INACTIVE
cliechti6a300772009-08-12 02:28:56 +0000372 if self.connection.logger:
Chris Liechti1f6643d2016-02-16 21:06:52 +0100373 self.connection.logger.debug("SB Answer {} -> {!r} -> {}".format(self.name, suboption, self.state))
cliechti2b929b72009-08-02 23:49:02 +0000374
375
Chris Liechtief6b7b42015-08-06 22:19:26 +0200376class Serial(SerialBase):
cliechti044d8662009-08-11 21:40:31 +0000377 """Serial port implementation for RFC 2217 remote serial ports."""
cliechti8099bed2009-08-01 23:59:18 +0000378
379 BAUDRATES = (50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800,
380 9600, 19200, 38400, 57600, 115200)
381
Chris Liechtibb5341b2015-10-31 23:31:26 +0100382 def __init__(self, *args, **kwargs):
Chris Liechtibb5341b2015-10-31 23:31:26 +0100383 self._thread = None
384 self._socket = None
Chris Liechti920917b2016-02-17 18:26:16 +0100385 self._linestate = 0
386 self._modemstate = None
Chris Liechtif0197252016-09-01 19:48:03 +0200387 self._modemstate_timeout = Timeout(-1)
Chris Liechti920917b2016-02-17 18:26:16 +0100388 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
Chris Liechti89032612016-12-14 19:05:49 +0100398 super(Serial, self).__init__(*args, **kwargs) # must be last call in case of auto-open
Chris Liechtibb5341b2015-10-31 23:31:26 +0100399
cliechti8099bed2009-08-01 23:59:18 +0000400 def open(self):
cliechtieada4fd2013-07-31 16:26:07 +0000401 """\
402 Open port with current settings. This may throw a SerialException
403 if the port cannot be opened.
404 """
cliechti6a300772009-08-12 02:28:56 +0000405 self.logger = None
cliechti81c54762009-08-03 23:53:27 +0000406 self._ignore_set_control_answer = False
cliechti7cb78e82009-08-05 15:47:57 +0000407 self._poll_modem_state = False
cliechtidfe2d272009-08-10 22:19:41 +0000408 self._network_timeout = 3
cliechti8099bed2009-08-01 23:59:18 +0000409 if self._port is None:
410 raise SerialException("Port must be configured before it can be used.")
Chris Liechti3ad62fb2015-08-29 21:53:32 +0200411 if self.is_open:
cliechti8f69e702011-03-19 00:22:32 +0000412 raise SerialException("Port is already open.")
cliechti8099bed2009-08-01 23:59:18 +0000413 try:
Chris Liechti43b3b102016-06-07 21:31:47 +0200414 self._socket = socket.create_connection(self.from_url(self.portstr), timeout=5) # XXX good value?
cliechti6a300772009-08-12 02:28:56 +0000415 self._socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
Chris Liechti68340d72015-08-03 14:15:48 +0200416 except Exception as msg:
cliechti8099bed2009-08-01 23:59:18 +0000417 self._socket = None
Chris Liechti1f6643d2016-02-16 21:06:52 +0100418 raise SerialException("Could not open port {}: {}".format(self.portstr, msg))
cliechti8099bed2009-08-01 23:59:18 +0000419
cliechti1ef7e3e2009-08-03 02:38:43 +0000420 # use a thread save queue as buffer. it also simplifies implementing
421 # the read timeout
cliechti8099bed2009-08-01 23:59:18 +0000422 self._read_buffer = Queue.Queue()
cliechti81c54762009-08-03 23:53:27 +0000423 # to ensure that user writes does not interfere with internal
424 # telnet/rfc2217 options establish a lock
425 self._write_lock = threading.Lock()
cliechtiac205322009-08-02 20:40:21 +0000426 # name the following separately so that, below, a check can be easily done
427 mandadory_options = [
cliechti2b929b72009-08-02 23:49:02 +0000428 TelnetOption(self, 'we-BINARY', BINARY, WILL, WONT, DO, DONT, INACTIVE),
cliechti2b929b72009-08-02 23:49:02 +0000429 TelnetOption(self, 'we-RFC2217', COM_PORT_OPTION, WILL, WONT, DO, DONT, REQUESTED),
cliechtiac205322009-08-02 20:40:21 +0000430 ]
431 # all supported telnet options
432 self._telnet_options = [
cliechtia29275e2009-08-03 00:08:04 +0000433 TelnetOption(self, 'ECHO', ECHO, DO, DONT, WILL, WONT, REQUESTED),
cliechti2b929b72009-08-02 23:49:02 +0000434 TelnetOption(self, 'we-SGA', SGA, WILL, WONT, DO, DONT, REQUESTED),
435 TelnetOption(self, 'they-SGA', SGA, DO, DONT, WILL, WONT, REQUESTED),
cliechti81c54762009-08-03 23:53:27 +0000436 TelnetOption(self, 'they-BINARY', BINARY, DO, DONT, WILL, WONT, INACTIVE),
437 TelnetOption(self, 'they-RFC2217', COM_PORT_OPTION, DO, DONT, WILL, WONT, REQUESTED),
cliechtiac205322009-08-02 20:40:21 +0000438 ] + mandadory_options
cliechti044d8662009-08-11 21:40:31 +0000439 # RFC 2217 specific states
cliechti2b929b72009-08-02 23:49:02 +0000440 # COM port settings
441 self._rfc2217_port_settings = {
442 'baudrate': TelnetSubnegotiation(self, 'baudrate', SET_BAUDRATE, SERVER_SET_BAUDRATE),
443 'datasize': TelnetSubnegotiation(self, 'datasize', SET_DATASIZE, SERVER_SET_DATASIZE),
444 'parity': TelnetSubnegotiation(self, 'parity', SET_PARITY, SERVER_SET_PARITY),
445 'stopsize': TelnetSubnegotiation(self, 'stopsize', SET_STOPSIZE, SERVER_SET_STOPSIZE),
Chris Liechticb6ce1b2016-02-02 01:53:56 +0100446 }
cliechticb20a4f2011-04-25 02:25:54 +0000447 # There are more subnegotiation objects, combine all in one dictionary
cliechti2b929b72009-08-02 23:49:02 +0000448 # for easy access
449 self._rfc2217_options = {
450 'purge': TelnetSubnegotiation(self, 'purge', PURGE_DATA, SERVER_PURGE_DATA),
cliechti81c54762009-08-03 23:53:27 +0000451 'control': TelnetSubnegotiation(self, 'control', SET_CONTROL, SERVER_SET_CONTROL),
Chris Liechticb6ce1b2016-02-02 01:53:56 +0100452 }
cliechti2b929b72009-08-02 23:49:02 +0000453 self._rfc2217_options.update(self._rfc2217_port_settings)
454 # cache for line and modem states that the server sends to us
cliechti8099bed2009-08-01 23:59:18 +0000455 self._linestate = 0
cliechti7cb78e82009-08-05 15:47:57 +0000456 self._modemstate = None
Chris Liechtif0197252016-09-01 19:48:03 +0200457 self._modemstate_timeout = Timeout(-1)
cliechti044d8662009-08-11 21:40:31 +0000458 # RFC 2217 flow control between server and client
cliechti672d0292009-08-03 02:01:57 +0000459 self._remote_suspend_flow = False
cliechti8099bed2009-08-01 23:59:18 +0000460
Chris Liechti39d52172015-10-25 22:53:51 +0100461 self.is_open = True
Chris Liechti277220e2016-02-18 23:27:52 +0100462 self._thread = threading.Thread(target=self._telnet_read_loop)
cliechti8099bed2009-08-01 23:59:18 +0000463 self._thread.setDaemon(True)
Chris Liechti1f6643d2016-02-16 21:06:52 +0100464 self._thread.setName('pySerial RFC 2217 reader thread for {}'.format(self._port))
cliechti8099bed2009-08-01 23:59:18 +0000465 self._thread.start()
466
Chris Liechti39d52172015-10-25 22:53:51 +0100467 try: # must clean-up if open fails
468 # negotiate Telnet/RFC 2217 -> send initial requests
469 for option in self._telnet_options:
470 if option.state is REQUESTED:
Chris Liechti277220e2016-02-18 23:27:52 +0100471 self.telnet_send_option(option.send_yes, option.option)
Chris Liechti39d52172015-10-25 22:53:51 +0100472 # now wait until important options are negotiated
Chris Liechti8f6d3d02016-08-31 00:46:50 +0200473 timeout = Timeout(self._network_timeout)
474 while not timeout.expired():
Chris Liechti39d52172015-10-25 22:53:51 +0100475 time.sleep(0.05) # prevent 100% CPU load
476 if sum(o.active for o in mandadory_options) == sum(o.state != INACTIVE for o in mandadory_options):
477 break
478 else:
Chris Liechti920917b2016-02-17 18:26:16 +0100479 raise SerialException(
480 "Remote does not seem to support RFC2217 or BINARY mode {!r}".format(mandadory_options))
Chris Liechti39d52172015-10-25 22:53:51 +0100481 if self.logger:
Chris Liechti1f6643d2016-02-16 21:06:52 +0100482 self.logger.info("Negotiated options: {}".format(self._telnet_options))
cliechti8099bed2009-08-01 23:59:18 +0000483
Chris Liechti39d52172015-10-25 22:53:51 +0100484 # fine, go on, set RFC 2271 specific things
485 self._reconfigure_port()
486 # all things set up get, now a clean start
487 if not self._dsrdtr:
488 self._update_dtr_state()
489 if not self._rtscts:
490 self._update_rts_state()
491 self.reset_input_buffer()
492 self.reset_output_buffer()
493 except:
494 self.close()
495 raise
cliechti8099bed2009-08-01 23:59:18 +0000496
Chris Liechti3ad62fb2015-08-29 21:53:32 +0200497 def _reconfigure_port(self):
cliechti8099bed2009-08-01 23:59:18 +0000498 """Set communication parameters on opened port."""
499 if self._socket is None:
500 raise SerialException("Can only operate on open ports")
501
cliechti8099bed2009-08-01 23:59:18 +0000502 # if self._timeout != 0 and self._interCharTimeout is not None:
cliechti8099bed2009-08-01 23:59:18 +0000503 # XXX
504
Chris Liechti3ad62fb2015-08-29 21:53:32 +0200505 if self._write_timeout is not None:
506 raise NotImplementedError('write_timeout is currently not supported')
cliechti2b929b72009-08-02 23:49:02 +0000507 # XXX
cliechti8099bed2009-08-01 23:59:18 +0000508
cliechti2b929b72009-08-02 23:49:02 +0000509 # Setup the connection
cliechti1ef7e3e2009-08-03 02:38:43 +0000510 # to get good performance, all parameter changes are sent first...
Chris Liechtiba45c522016-02-06 23:53:23 +0100511 if not 0 < self._baudrate < 2 ** 32:
Chris Liechti1f6643d2016-02-16 21:06:52 +0100512 raise ValueError("invalid baudrate: {!r}".format(self._baudrate))
Chris Liechti01587b12015-08-05 02:39:32 +0200513 self._rfc2217_port_settings['baudrate'].set(struct.pack(b'!I', self._baudrate))
514 self._rfc2217_port_settings['datasize'].set(struct.pack(b'!B', self._bytesize))
515 self._rfc2217_port_settings['parity'].set(struct.pack(b'!B', RFC2217_PARITY_MAP[self._parity]))
516 self._rfc2217_port_settings['stopsize'].set(struct.pack(b'!B', RFC2217_STOPBIT_MAP[self._stopbits]))
cliechti8099bed2009-08-01 23:59:18 +0000517
cliechti2b929b72009-08-02 23:49:02 +0000518 # and now wait until parameters are active
519 items = self._rfc2217_port_settings.values()
cliechti6a300772009-08-12 02:28:56 +0000520 if self.logger:
Chris Liechti1f6643d2016-02-16 21:06:52 +0100521 self.logger.debug("Negotiating settings: {}".format(items))
Chris Liechti8f6d3d02016-08-31 00:46:50 +0200522 timeout = Timeout(self._network_timeout)
523 while not timeout.expired():
cliechti2b929b72009-08-02 23:49:02 +0000524 time.sleep(0.05) # prevent 100% CPU load
525 if sum(o.active for o in items) == len(items):
526 break
527 else:
Chris Liechti1f6643d2016-02-16 21:06:52 +0100528 raise SerialException("Remote does not accept parameter change (RFC2217): {!r}".format(items))
cliechti6a300772009-08-12 02:28:56 +0000529 if self.logger:
Chris Liechti1f6643d2016-02-16 21:06:52 +0100530 self.logger.info("Negotiated settings: {}".format(items))
cliechti8099bed2009-08-01 23:59:18 +0000531
532 if self._rtscts and self._xonxoff:
cliechti1ef7e3e2009-08-03 02:38:43 +0000533 raise ValueError('xonxoff and rtscts together are not supported')
cliechti8099bed2009-08-01 23:59:18 +0000534 elif self._rtscts:
Chris Liechti277220e2016-02-18 23:27:52 +0100535 self.rfc2217_set_control(SET_CONTROL_USE_HW_FLOW_CONTROL)
cliechti8099bed2009-08-01 23:59:18 +0000536 elif self._xonxoff:
Chris Liechti277220e2016-02-18 23:27:52 +0100537 self.rfc2217_set_control(SET_CONTROL_USE_SW_FLOW_CONTROL)
cliechti8099bed2009-08-01 23:59:18 +0000538 else:
Chris Liechti277220e2016-02-18 23:27:52 +0100539 self.rfc2217_set_control(SET_CONTROL_USE_NO_FLOW_CONTROL)
cliechti8099bed2009-08-01 23:59:18 +0000540
541 def close(self):
542 """Close port"""
Chris Liechti39d52172015-10-25 22:53:51 +0100543 self.is_open = False
544 if self._socket:
545 try:
546 self._socket.shutdown(socket.SHUT_RDWR)
547 self._socket.close()
548 except:
549 # ignore errors.
550 pass
551 if self._thread:
552 self._thread.join(7) # XXX more than socket timeout
553 self._thread = None
cliechti8099bed2009-08-01 23:59:18 +0000554 # in case of quick reconnects, give the server some time
555 time.sleep(0.3)
Chris Liechti39d52172015-10-25 22:53:51 +0100556 self._socket = None
cliechti8099bed2009-08-01 23:59:18 +0000557
Chris Liechti3ad62fb2015-08-29 21:53:32 +0200558 def from_url(self, url):
Chris Liechti920917b2016-02-17 18:26:16 +0100559 """\
560 extract host and port from an URL string, other settings are extracted
561 an stored in instance
562 """
Chris Liechtia4222112015-08-07 01:03:12 +0200563 parts = urlparse.urlsplit(url)
Chris Liechtid14b1ab2015-08-21 00:28:53 +0200564 if parts.scheme != "rfc2217":
Chris Liechti1f6643d2016-02-16 21:06:52 +0100565 raise SerialException(
566 'expected a string in the form '
567 '"rfc2217://<host>:<port>[?option[&option...]]": '
568 'not starting with rfc2217:// ({!r})'.format(parts.scheme))
cliechti8099bed2009-08-01 23:59:18 +0000569 try:
Chris Liechtia4222112015-08-07 01:03:12 +0200570 # process options now, directly altering self
Chris Liechtid14b1ab2015-08-21 00:28:53 +0200571 for option, values in urlparse.parse_qs(parts.query, True).items():
572 if option == 'logging':
Chris Liechtia4222112015-08-07 01:03:12 +0200573 logging.basicConfig() # XXX is that good to call it here?
574 self.logger = logging.getLogger('pySerial.rfc2217')
Chris Liechtid14b1ab2015-08-21 00:28:53 +0200575 self.logger.setLevel(LOGGER_LEVELS[values[0]])
Chris Liechtia4222112015-08-07 01:03:12 +0200576 self.logger.debug('enabled logging')
577 elif option == 'ign_set_control':
578 self._ignore_set_control_answer = True
579 elif option == 'poll_modem':
580 self._poll_modem_state = True
581 elif option == 'timeout':
Chris Liechtid14b1ab2015-08-21 00:28:53 +0200582 self._network_timeout = float(values[0])
Chris Liechtia4222112015-08-07 01:03:12 +0200583 else:
Chris Liechti1f6643d2016-02-16 21:06:52 +0100584 raise ValueError('unknown option: {!r}'.format(option))
Chris Liechti4daa9d52016-03-22 00:32:01 +0100585 if not 0 <= parts.port < 65536:
Chris Liechti033f17c2015-08-30 21:28:04 +0200586 raise ValueError("port not in range 0...65535")
Chris Liechti68340d72015-08-03 14:15:48 +0200587 except ValueError as e:
Chris Liechti1f6643d2016-02-16 21:06:52 +0100588 raise SerialException(
589 'expected a string in the form '
590 '"rfc2217://<host>:<port>[?option[&option...]]": {}'.format(e))
Chris Liechti4daa9d52016-03-22 00:32:01 +0100591 return (parts.hostname, parts.port)
cliechti8099bed2009-08-01 23:59:18 +0000592
593 # - - - - - - - - - - - - - - - - - - - - - - - -
594
Chris Liechtief1fe252015-08-27 23:25:21 +0200595 @property
596 def in_waiting(self):
Chris Liechti3ad62fb2015-08-29 21:53:32 +0200597 """Return the number of bytes currently in the input buffer."""
Chris Liechti033f17c2015-08-30 21:28:04 +0200598 if not self.is_open:
599 raise portNotOpenError
cliechti8099bed2009-08-01 23:59:18 +0000600 return self._read_buffer.qsize()
601
602 def read(self, size=1):
cliechtieada4fd2013-07-31 16:26:07 +0000603 """\
604 Read size bytes from the serial port. If a timeout is set it may
cliechti8099bed2009-08-01 23:59:18 +0000605 return less characters as requested. With no timeout it will block
cliechtieada4fd2013-07-31 16:26:07 +0000606 until the requested number of bytes is read.
607 """
Chris Liechti033f17c2015-08-30 21:28:04 +0200608 if not self.is_open:
609 raise portNotOpenError
cliechti8099bed2009-08-01 23:59:18 +0000610 data = bytearray()
611 try:
Chris Liechti129aca62016-12-21 03:19:30 +0100612 timeout = Timeout(self._timeout)
cliechti8099bed2009-08-01 23:59:18 +0000613 while len(data) < size:
cliechti81c54762009-08-03 23:53:27 +0000614 if self._thread is None:
615 raise SerialException('connection failed (reader thread died)')
Chris Liechti129aca62016-12-21 03:19:30 +0100616 data += self._read_buffer.get(True, timeout.time_left())
617 if timeout.expired():
618 break
Chris Liechti033f17c2015-08-30 21:28:04 +0200619 except Queue.Empty: # -> timeout
cliechti8099bed2009-08-01 23:59:18 +0000620 pass
621 return bytes(data)
622
623 def write(self, data):
cliechtieada4fd2013-07-31 16:26:07 +0000624 """\
Chris Liechti3ad62fb2015-08-29 21:53:32 +0200625 Output the given byte string over the serial port. Can block if the
cliechti8099bed2009-08-01 23:59:18 +0000626 connection is blocked. May raise SerialException if the connection is
cliechtieada4fd2013-07-31 16:26:07 +0000627 closed.
628 """
Chris Liechti033f17c2015-08-30 21:28:04 +0200629 if not self.is_open:
630 raise portNotOpenError
Chris Liechti01587b12015-08-05 02:39:32 +0200631 with self._write_lock:
cliechti81c54762009-08-03 23:53:27 +0000632 try:
cliechti38077122013-10-16 02:57:27 +0000633 self._socket.sendall(to_bytes(data).replace(IAC, IAC_DOUBLED))
Chris Liechti68340d72015-08-03 14:15:48 +0200634 except socket.error as e:
Chris Liechti1f6643d2016-02-16 21:06:52 +0100635 raise SerialException("connection failed (socket error): {}".format(e))
cliechti8099bed2009-08-01 23:59:18 +0000636 return len(data)
637
Chris Liechtief1fe252015-08-27 23:25:21 +0200638 def reset_input_buffer(self):
cliechti8099bed2009-08-01 23:59:18 +0000639 """Clear input buffer, discarding all that is in the buffer."""
Chris Liechti033f17c2015-08-30 21:28:04 +0200640 if not self.is_open:
641 raise portNotOpenError
Chris Liechti277220e2016-02-18 23:27:52 +0100642 self.rfc2217_send_purge(PURGE_RECEIVE_BUFFER)
cliechti8099bed2009-08-01 23:59:18 +0000643 # empty read buffer
644 while self._read_buffer.qsize():
645 self._read_buffer.get(False)
646
Chris Liechtief1fe252015-08-27 23:25:21 +0200647 def reset_output_buffer(self):
cliechtieada4fd2013-07-31 16:26:07 +0000648 """\
649 Clear output buffer, aborting the current output and
650 discarding all that is in the buffer.
651 """
Chris Liechti033f17c2015-08-30 21:28:04 +0200652 if not self.is_open:
653 raise portNotOpenError
Chris Liechti277220e2016-02-18 23:27:52 +0100654 self.rfc2217_send_purge(PURGE_TRANSMIT_BUFFER)
cliechti8099bed2009-08-01 23:59:18 +0000655
Chris Liechtief1fe252015-08-27 23:25:21 +0200656 def _update_break_state(self):
cliechtieada4fd2013-07-31 16:26:07 +0000657 """\
658 Set break: Controls TXD. When active, to transmitting is
659 possible.
660 """
Chris Liechti033f17c2015-08-30 21:28:04 +0200661 if not self.is_open:
662 raise portNotOpenError
cliechti6a300772009-08-12 02:28:56 +0000663 if self.logger:
Chris Liechti1f6643d2016-02-16 21:06:52 +0100664 self.logger.info('set BREAK to {}'.format('active' if self._break_state else 'inactive'))
Chris Liechtief1fe252015-08-27 23:25:21 +0200665 if self._break_state:
Chris Liechti277220e2016-02-18 23:27:52 +0100666 self.rfc2217_set_control(SET_CONTROL_BREAK_ON)
cliechti8099bed2009-08-01 23:59:18 +0000667 else:
Chris Liechti277220e2016-02-18 23:27:52 +0100668 self.rfc2217_set_control(SET_CONTROL_BREAK_OFF)
cliechti8099bed2009-08-01 23:59:18 +0000669
Chris Liechtief1fe252015-08-27 23:25:21 +0200670 def _update_rts_state(self):
cliechti044d8662009-08-11 21:40:31 +0000671 """Set terminal status line: Request To Send."""
Chris Liechti033f17c2015-08-30 21:28:04 +0200672 if not self.is_open:
673 raise portNotOpenError
cliechti6a300772009-08-12 02:28:56 +0000674 if self.logger:
Chris Liechti1f6643d2016-02-16 21:06:52 +0100675 self.logger.info('set RTS to {}'.format('active' if self._rts_state else 'inactive'))
Chris Liechtief1fe252015-08-27 23:25:21 +0200676 if self._rts_state:
Chris Liechti277220e2016-02-18 23:27:52 +0100677 self.rfc2217_set_control(SET_CONTROL_RTS_ON)
cliechti8099bed2009-08-01 23:59:18 +0000678 else:
Chris Liechti277220e2016-02-18 23:27:52 +0100679 self.rfc2217_set_control(SET_CONTROL_RTS_OFF)
cliechti8099bed2009-08-01 23:59:18 +0000680
Chris Liechti920917b2016-02-17 18:26:16 +0100681 def _update_dtr_state(self):
cliechti044d8662009-08-11 21:40:31 +0000682 """Set terminal status line: Data Terminal Ready."""
Chris Liechti033f17c2015-08-30 21:28:04 +0200683 if not self.is_open:
684 raise portNotOpenError
cliechti6a300772009-08-12 02:28:56 +0000685 if self.logger:
Chris Liechti1f6643d2016-02-16 21:06:52 +0100686 self.logger.info('set DTR to {}'.format('active' if self._dtr_state else 'inactive'))
Chris Liechtief1fe252015-08-27 23:25:21 +0200687 if self._dtr_state:
Chris Liechti277220e2016-02-18 23:27:52 +0100688 self.rfc2217_set_control(SET_CONTROL_DTR_ON)
cliechti8099bed2009-08-01 23:59:18 +0000689 else:
Chris Liechti277220e2016-02-18 23:27:52 +0100690 self.rfc2217_set_control(SET_CONTROL_DTR_OFF)
cliechti8099bed2009-08-01 23:59:18 +0000691
Chris Liechtief1fe252015-08-27 23:25:21 +0200692 @property
693 def cts(self):
cliechti044d8662009-08-11 21:40:31 +0000694 """Read terminal status line: Clear To Send."""
Chris Liechti033f17c2015-08-30 21:28:04 +0200695 if not self.is_open:
696 raise portNotOpenError
Chris Liechti277220e2016-02-18 23:27:52 +0100697 return bool(self.get_modem_state() & MODEMSTATE_MASK_CTS)
cliechti8099bed2009-08-01 23:59:18 +0000698
Chris Liechtief1fe252015-08-27 23:25:21 +0200699 @property
700 def dsr(self):
cliechti044d8662009-08-11 21:40:31 +0000701 """Read terminal status line: Data Set Ready."""
Chris Liechti033f17c2015-08-30 21:28:04 +0200702 if not self.is_open:
703 raise portNotOpenError
Chris Liechti277220e2016-02-18 23:27:52 +0100704 return bool(self.get_modem_state() & MODEMSTATE_MASK_DSR)
cliechti8099bed2009-08-01 23:59:18 +0000705
Chris Liechtief1fe252015-08-27 23:25:21 +0200706 @property
707 def ri(self):
cliechti044d8662009-08-11 21:40:31 +0000708 """Read terminal status line: Ring Indicator."""
Chris Liechti033f17c2015-08-30 21:28:04 +0200709 if not self.is_open:
710 raise portNotOpenError
Chris Liechti277220e2016-02-18 23:27:52 +0100711 return bool(self.get_modem_state() & MODEMSTATE_MASK_RI)
cliechti8099bed2009-08-01 23:59:18 +0000712
Chris Liechtief1fe252015-08-27 23:25:21 +0200713 @property
714 def cd(self):
cliechti044d8662009-08-11 21:40:31 +0000715 """Read terminal status line: Carrier Detect."""
Chris Liechti033f17c2015-08-30 21:28:04 +0200716 if not self.is_open:
717 raise portNotOpenError
Chris Liechti277220e2016-02-18 23:27:52 +0100718 return bool(self.get_modem_state() & MODEMSTATE_MASK_CD)
cliechti8099bed2009-08-01 23:59:18 +0000719
720 # - - - platform specific - - -
721 # None so far
722
723 # - - - RFC2217 specific - - -
724
Chris Liechti277220e2016-02-18 23:27:52 +0100725 def _telnet_read_loop(self):
cliechti7d448562014-08-03 21:57:45 +0000726 """Read loop for the socket."""
cliechti8099bed2009-08-01 23:59:18 +0000727 mode = M_NORMAL
728 suboption = None
cliechti81c54762009-08-03 23:53:27 +0000729 try:
Chris Liechti39d52172015-10-25 22:53:51 +0100730 while self.is_open:
cliechti81c54762009-08-03 23:53:27 +0000731 try:
732 data = self._socket.recv(1024)
733 except socket.timeout:
734 # just need to get out of recv form time to time to check if
735 # still alive
736 continue
Chris Liechti68340d72015-08-03 14:15:48 +0200737 except socket.error as e:
cliechti81c54762009-08-03 23:53:27 +0000738 # connection fails -> terminate loop
cliechticb20a4f2011-04-25 02:25:54 +0000739 if self.logger:
Chris Liechti1f6643d2016-02-16 21:06:52 +0100740 self.logger.debug("socket error in reader thread: {}".format(e))
cliechti81c54762009-08-03 23:53:27 +0000741 break
Chris Liechti033f17c2015-08-30 21:28:04 +0200742 if not data:
743 break # lost connection
Chris Liechtif99cd5c2015-08-13 22:54:16 +0200744 for byte in iterbytes(data):
cliechti81c54762009-08-03 23:53:27 +0000745 if mode == M_NORMAL:
746 # interpret as command or as data
747 if byte == IAC:
748 mode = M_IAC_SEEN
cliechti8099bed2009-08-01 23:59:18 +0000749 else:
cliechti81c54762009-08-03 23:53:27 +0000750 # store data in read buffer or sub option buffer
751 # depending on state
752 if suboption is not None:
Chris Liechti01587b12015-08-05 02:39:32 +0200753 suboption += byte
cliechti81c54762009-08-03 23:53:27 +0000754 else:
755 self._read_buffer.put(byte)
756 elif mode == M_IAC_SEEN:
757 if byte == IAC:
758 # interpret as command doubled -> insert character
759 # itself
cliechtif325c032009-12-25 16:09:49 +0000760 if suboption is not None:
Chris Liechti01587b12015-08-05 02:39:32 +0200761 suboption += IAC
cliechtif325c032009-12-25 16:09:49 +0000762 else:
763 self._read_buffer.put(IAC)
cliechti81c54762009-08-03 23:53:27 +0000764 mode = M_NORMAL
765 elif byte == SB:
766 # sub option start
767 suboption = bytearray()
768 mode = M_NORMAL
769 elif byte == SE:
770 # sub option end -> process it now
Chris Liechti277220e2016-02-18 23:27:52 +0100771 self._telnet_process_subnegotiation(bytes(suboption))
cliechti81c54762009-08-03 23:53:27 +0000772 suboption = None
773 mode = M_NORMAL
774 elif byte in (DO, DONT, WILL, WONT):
775 # negotiation
776 telnet_command = byte
777 mode = M_NEGOTIATE
778 else:
779 # other telnet commands
Chris Liechti277220e2016-02-18 23:27:52 +0100780 self._telnet_process_command(byte)
cliechti81c54762009-08-03 23:53:27 +0000781 mode = M_NORMAL
Chris Liechti033f17c2015-08-30 21:28:04 +0200782 elif mode == M_NEGOTIATE: # DO, DONT, WILL, WONT was received, option now following
Chris Liechti277220e2016-02-18 23:27:52 +0100783 self._telnet_negotiate_option(telnet_command, byte)
cliechti8099bed2009-08-01 23:59:18 +0000784 mode = M_NORMAL
cliechti81c54762009-08-03 23:53:27 +0000785 finally:
786 self._thread = None
cliechti6a300772009-08-12 02:28:56 +0000787 if self.logger:
788 self.logger.debug("read thread terminated")
cliechti8099bed2009-08-01 23:59:18 +0000789
790 # - incoming telnet commands and options
791
Chris Liechti277220e2016-02-18 23:27:52 +0100792 def _telnet_process_command(self, command):
cliechti044d8662009-08-11 21:40:31 +0000793 """Process commands other than DO, DONT, WILL, WONT."""
cliechti1ef7e3e2009-08-03 02:38:43 +0000794 # Currently none. RFC2217 only uses negotiation and subnegotiation.
cliechti6a300772009-08-12 02:28:56 +0000795 if self.logger:
Chris Liechti1f6643d2016-02-16 21:06:52 +0100796 self.logger.warning("ignoring Telnet command: {!r}".format(command))
cliechti8099bed2009-08-01 23:59:18 +0000797
Chris Liechti277220e2016-02-18 23:27:52 +0100798 def _telnet_negotiate_option(self, command, option):
cliechti044d8662009-08-11 21:40:31 +0000799 """Process incoming DO, DONT, WILL, WONT."""
cliechti2b929b72009-08-02 23:49:02 +0000800 # check our registered telnet options and forward command to them
801 # they know themselves if they have to answer or not
cliechtiac205322009-08-02 20:40:21 +0000802 known = False
803 for item in self._telnet_options:
cliechti2b929b72009-08-02 23:49:02 +0000804 # can have more than one match! as some options are duplicated for
805 # 'us' and 'them'
cliechtiac205322009-08-02 20:40:21 +0000806 if item.option == option:
cliechti2b929b72009-08-02 23:49:02 +0000807 item.process_incoming(command)
cliechtiac205322009-08-02 20:40:21 +0000808 known = True
809 if not known:
810 # handle unknown options
811 # only answer to positive requests and deny them
812 if command == WILL or command == DO:
Chris Liechti277220e2016-02-18 23:27:52 +0100813 self.telnet_send_option((DONT if command == WILL else WONT), option)
cliechti6a300772009-08-12 02:28:56 +0000814 if self.logger:
Chris Liechti1f6643d2016-02-16 21:06:52 +0100815 self.logger.warning("rejected Telnet option: {!r}".format(option))
cliechtiac205322009-08-02 20:40:21 +0000816
Chris Liechti277220e2016-02-18 23:27:52 +0100817 def _telnet_process_subnegotiation(self, suboption):
cliechti044d8662009-08-11 21:40:31 +0000818 """Process subnegotiation, the data between IAC SB and IAC SE."""
cliechti8099bed2009-08-01 23:59:18 +0000819 if suboption[0:1] == COM_PORT_OPTION:
820 if suboption[1:2] == SERVER_NOTIFY_LINESTATE and len(suboption) >= 3:
Chris Liechti033f17c2015-08-30 21:28:04 +0200821 self._linestate = ord(suboption[2:3]) # ensure it is a number
cliechti6a300772009-08-12 02:28:56 +0000822 if self.logger:
Chris Liechti1f6643d2016-02-16 21:06:52 +0100823 self.logger.info("NOTIFY_LINESTATE: {}".format(self._linestate))
cliechti8099bed2009-08-01 23:59:18 +0000824 elif suboption[1:2] == SERVER_NOTIFY_MODEMSTATE and len(suboption) >= 3:
Chris Liechti033f17c2015-08-30 21:28:04 +0200825 self._modemstate = ord(suboption[2:3]) # ensure it is a number
cliechti6a300772009-08-12 02:28:56 +0000826 if self.logger:
Chris Liechti1f6643d2016-02-16 21:06:52 +0100827 self.logger.info("NOTIFY_MODEMSTATE: {}".format(self._modemstate))
cliechti7cb78e82009-08-05 15:47:57 +0000828 # update time when we think that a poll would make sense
Chris Liechtif0197252016-09-01 19:48:03 +0200829 self._modemstate_timeout.restart(0.3)
cliechti672d0292009-08-03 02:01:57 +0000830 elif suboption[1:2] == FLOWCONTROL_SUSPEND:
831 self._remote_suspend_flow = True
832 elif suboption[1:2] == FLOWCONTROL_RESUME:
833 self._remote_suspend_flow = False
cliechti8099bed2009-08-01 23:59:18 +0000834 else:
cliechti2b929b72009-08-02 23:49:02 +0000835 for item in self._rfc2217_options.values():
836 if item.ack_option == suboption[1:2]:
cliechti81c54762009-08-03 23:53:27 +0000837 #~ print "processing COM_PORT_OPTION: %r" % list(suboption[1:])
Chris Liechti277220e2016-02-18 23:27:52 +0100838 item.check_answer(bytes(suboption[2:]))
cliechti2b929b72009-08-02 23:49:02 +0000839 break
840 else:
cliechti6a300772009-08-12 02:28:56 +0000841 if self.logger:
Chris Liechti1f6643d2016-02-16 21:06:52 +0100842 self.logger.warning("ignoring COM_PORT_OPTION: {!r}".format(suboption))
cliechti8099bed2009-08-01 23:59:18 +0000843 else:
cliechti6a300772009-08-12 02:28:56 +0000844 if self.logger:
Chris Liechti1f6643d2016-02-16 21:06:52 +0100845 self.logger.warning("ignoring subnegotiation: {!r}".format(suboption))
cliechti8099bed2009-08-01 23:59:18 +0000846
847 # - outgoing telnet commands and options
848
cliechti81c54762009-08-03 23:53:27 +0000849 def _internal_raw_write(self, data):
cliechti044d8662009-08-11 21:40:31 +0000850 """internal socket write with no data escaping. used to send telnet stuff."""
Chris Liechti01587b12015-08-05 02:39:32 +0200851 with self._write_lock:
cliechti81c54762009-08-03 23:53:27 +0000852 self._socket.sendall(data)
cliechti81c54762009-08-03 23:53:27 +0000853
Chris Liechti277220e2016-02-18 23:27:52 +0100854 def telnet_send_option(self, action, option):
cliechti044d8662009-08-11 21:40:31 +0000855 """Send DO, DONT, WILL, WONT."""
Greg Bowser77ca2902016-11-22 13:04:16 -0500856 self._internal_raw_write(IAC + action + option)
cliechti8099bed2009-08-01 23:59:18 +0000857
Chris Liechti277220e2016-02-18 23:27:52 +0100858 def rfc2217_send_subnegotiation(self, option, value=b''):
cliechti044d8662009-08-11 21:40:31 +0000859 """Subnegotiation of RFC2217 parameters."""
cliechtif325c032009-12-25 16:09:49 +0000860 value = value.replace(IAC, IAC_DOUBLED)
Greg Bowser77ca2902016-11-22 13:04:16 -0500861 self._internal_raw_write(IAC + SB + COM_PORT_OPTION + option + value + IAC + SE)
cliechti2b929b72009-08-02 23:49:02 +0000862
Chris Liechti277220e2016-02-18 23:27:52 +0100863 def rfc2217_send_purge(self, value):
864 """\
865 Send purge request to the remote.
866 (PURGE_RECEIVE_BUFFER / PURGE_TRANSMIT_BUFFER / PURGE_BOTH_BUFFERS)
867 """
cliechti2b929b72009-08-02 23:49:02 +0000868 item = self._rfc2217_options['purge']
Chris Liechti033f17c2015-08-30 21:28:04 +0200869 item.set(value) # transmit desired purge type
870 item.wait(self._network_timeout) # wait for acknowledge from the server
cliechti2b929b72009-08-02 23:49:02 +0000871
Chris Liechti277220e2016-02-18 23:27:52 +0100872 def rfc2217_set_control(self, value):
873 """transmit change of control line to remote"""
cliechti81c54762009-08-03 23:53:27 +0000874 item = self._rfc2217_options['control']
Chris Liechti033f17c2015-08-30 21:28:04 +0200875 item.set(value) # transmit desired control type
cliechti81c54762009-08-03 23:53:27 +0000876 if self._ignore_set_control_answer:
877 # answers are ignored when option is set. compatibility mode for
cliechticb20a4f2011-04-25 02:25:54 +0000878 # servers that answer, but not the expected one... (or no answer
cliechti81c54762009-08-03 23:53:27 +0000879 # at all) i.e. sredird
880 time.sleep(0.1) # this helps getting the unit tests passed
881 else:
cliechtidfe2d272009-08-10 22:19:41 +0000882 item.wait(self._network_timeout) # wait for acknowledge from the server
cliechti8099bed2009-08-01 23:59:18 +0000883
Chris Liechti277220e2016-02-18 23:27:52 +0100884 def rfc2217_flow_server_ready(self):
cliechtieada4fd2013-07-31 16:26:07 +0000885 """\
886 check if server is ready to receive data. block for some time when
887 not.
888 """
cliechti672d0292009-08-03 02:01:57 +0000889 #~ if self._remote_suspend_flow:
Chris Liechtiba45c522016-02-06 23:53:23 +0100890 #~ wait---
cliechti672d0292009-08-03 02:01:57 +0000891
Chris Liechti277220e2016-02-18 23:27:52 +0100892 def get_modem_state(self):
cliechtieada4fd2013-07-31 16:26:07 +0000893 """\
cliechti7d448562014-08-03 21:57:45 +0000894 get last modem state (cached value. If value is "old", request a new
895 one. This cache helps that we don't issue to many requests when e.g. all
896 status lines, one after the other is queried by the user (getCTS, getDSR
cliechtieada4fd2013-07-31 16:26:07 +0000897 etc.)
898 """
cliechti7cb78e82009-08-05 15:47:57 +0000899 # active modem state polling enabled? is the value fresh enough?
Chris Liechtif0197252016-09-01 19:48:03 +0200900 if self._poll_modem_state and self._modemstate_timeout.expired():
cliechti6a300772009-08-12 02:28:56 +0000901 if self.logger:
902 self.logger.debug('polling modem state')
cliechti7cb78e82009-08-05 15:47:57 +0000903 # when it is older, request an update
Chris Liechti277220e2016-02-18 23:27:52 +0100904 self.rfc2217_send_subnegotiation(NOTIFY_MODEMSTATE)
Chris Liechti8f6d3d02016-08-31 00:46:50 +0200905 timeout = Timeout(self._network_timeout)
906 while not timeout.expired():
cliechti7cb78e82009-08-05 15:47:57 +0000907 time.sleep(0.05) # prevent 100% CPU load
908 # when expiration time is updated, it means that there is a new
909 # value
Chris Liechtif0197252016-09-01 19:48:03 +0200910 if not self._modemstate_timeout.expired():
cliechti7cb78e82009-08-05 15:47:57 +0000911 break
Chris Liechti01587b12015-08-05 02:39:32 +0200912 else:
913 if self.logger:
914 self.logger.warning('poll for modem state failed')
cliechti7cb78e82009-08-05 15:47:57 +0000915 # even when there is a timeout, do not generate an error just
916 # return the last known value. this way we can support buggy
917 # servers that do not respond to polls, but send automatic
918 # updates.
919 if self._modemstate is not None:
cliechti6a300772009-08-12 02:28:56 +0000920 if self.logger:
921 self.logger.debug('using cached modem state')
cliechti7cb78e82009-08-05 15:47:57 +0000922 return self._modemstate
923 else:
924 # never received a notification from the server
cliechti8fb119c2009-08-05 23:39:45 +0000925 raise SerialException("remote sends no NOTIFY_MODEMSTATE")
cliechti8099bed2009-08-01 23:59:18 +0000926
cliechti5cc3eb12009-08-11 23:04:30 +0000927
cliechti595ed5b2009-08-10 01:43:32 +0000928#############################################################################
cliechti5cc3eb12009-08-11 23:04:30 +0000929# The following is code that helps implementing an RFC 2217 server.
cliechti8099bed2009-08-01 23:59:18 +0000930
cliechti8ccc2ff2009-08-05 12:44:46 +0000931class PortManager(object):
cliechtieada4fd2013-07-31 16:26:07 +0000932 """\
933 This class manages the state of Telnet and RFC 2217. It needs a serial
cliechticb20a4f2011-04-25 02:25:54 +0000934 instance and a connection to work with. Connection is expected to implement
cliechtieada4fd2013-07-31 16:26:07 +0000935 a (thread safe) write function, that writes the string to the network.
936 """
cliechti130d1f02009-08-04 02:10:58 +0000937
cliechti6a300772009-08-12 02:28:56 +0000938 def __init__(self, serial_port, connection, logger=None):
cliechti130d1f02009-08-04 02:10:58 +0000939 self.serial = serial_port
940 self.connection = connection
cliechti6a300772009-08-12 02:28:56 +0000941 self.logger = logger
cliechti86b593e2009-08-05 16:28:12 +0000942 self._client_is_rfc2217 = False
cliechti130d1f02009-08-04 02:10:58 +0000943
944 # filter state machine
945 self.mode = M_NORMAL
946 self.suboption = None
947 self.telnet_command = None
948
949 # states for modem/line control events
950 self.modemstate_mask = 255
951 self.last_modemstate = None
952 self.linstate_mask = 0
953
954 # all supported telnet options
955 self._telnet_options = [
956 TelnetOption(self, 'ECHO', ECHO, WILL, WONT, DO, DONT, REQUESTED),
957 TelnetOption(self, 'we-SGA', SGA, WILL, WONT, DO, DONT, REQUESTED),
958 TelnetOption(self, 'they-SGA', SGA, DO, DONT, WILL, WONT, INACTIVE),
959 TelnetOption(self, 'we-BINARY', BINARY, WILL, WONT, DO, DONT, INACTIVE),
960 TelnetOption(self, 'they-BINARY', BINARY, DO, DONT, WILL, WONT, REQUESTED),
cliechti86b593e2009-08-05 16:28:12 +0000961 TelnetOption(self, 'we-RFC2217', COM_PORT_OPTION, WILL, WONT, DO, DONT, REQUESTED, self._client_ok),
962 TelnetOption(self, 'they-RFC2217', COM_PORT_OPTION, DO, DONT, WILL, WONT, INACTIVE, self._client_ok),
Chris Liechticb6ce1b2016-02-02 01:53:56 +0100963 ]
cliechti130d1f02009-08-04 02:10:58 +0000964
965 # negotiate Telnet/RFC2217 -> send initial requests
cliechti6a300772009-08-12 02:28:56 +0000966 if self.logger:
967 self.logger.debug("requesting initial Telnet/RFC 2217 options")
cliechti130d1f02009-08-04 02:10:58 +0000968 for option in self._telnet_options:
969 if option.state is REQUESTED:
Chris Liechti277220e2016-02-18 23:27:52 +0100970 self.telnet_send_option(option.send_yes, option.option)
cliechti130d1f02009-08-04 02:10:58 +0000971 # issue 1st modem state notification
cliechti86b593e2009-08-05 16:28:12 +0000972
973 def _client_ok(self):
cliechtieada4fd2013-07-31 16:26:07 +0000974 """\
cliechti7d448562014-08-03 21:57:45 +0000975 callback of telnet option. It gets called when option is activated.
976 This one here is used to detect when the client agrees on RFC 2217. A
cliechti86b593e2009-08-05 16:28:12 +0000977 flag is set so that other functions like check_modem_lines know if the
cliechti7d448562014-08-03 21:57:45 +0000978 client is OK.
cliechtieada4fd2013-07-31 16:26:07 +0000979 """
cliechti86b593e2009-08-05 16:28:12 +0000980 # The callback is used for we and they so if one party agrees, we're
981 # already happy. it seems not all servers do the negotiation correctly
982 # and i guess there are incorrect clients too.. so be happy if client
983 # answers one or the other positively.
984 self._client_is_rfc2217 = True
cliechti6a300772009-08-12 02:28:56 +0000985 if self.logger:
986 self.logger.info("client accepts RFC 2217")
cliechti8fb119c2009-08-05 23:39:45 +0000987 # this is to ensure that the client gets a notification, even if there
988 # was no change
989 self.check_modem_lines(force_notification=True)
cliechti130d1f02009-08-04 02:10:58 +0000990
991 # - outgoing telnet commands and options
992
Chris Liechti277220e2016-02-18 23:27:52 +0100993 def telnet_send_option(self, action, option):
cliechti044d8662009-08-11 21:40:31 +0000994 """Send DO, DONT, WILL, WONT."""
Greg Bowser77ca2902016-11-22 13:04:16 -0500995 self.connection.write(IAC + action + option)
cliechti130d1f02009-08-04 02:10:58 +0000996
Chris Liechti277220e2016-02-18 23:27:52 +0100997 def rfc2217_send_subnegotiation(self, option, value=b''):
cliechti044d8662009-08-11 21:40:31 +0000998 """Subnegotiation of RFC 2217 parameters."""
cliechtif325c032009-12-25 16:09:49 +0000999 value = value.replace(IAC, IAC_DOUBLED)
Greg Bowser77ca2902016-11-22 13:04:16 -05001000 self.connection.write(IAC + SB + COM_PORT_OPTION + option + value + IAC + SE)
cliechti130d1f02009-08-04 02:10:58 +00001001
1002 # - check modem lines, needs to be called periodically from user to
1003 # establish polling
1004
cliechti7cb78e82009-08-05 15:47:57 +00001005 def check_modem_lines(self, force_notification=False):
Chris Liechti277220e2016-02-18 23:27:52 +01001006 """\
1007 read control lines from serial port and compare the last value sent to remote.
1008 send updates on changes.
1009 """
cliechti130d1f02009-08-04 02:10:58 +00001010 modemstate = (
Chris Liechtiba45c522016-02-06 23:53:23 +01001011 (self.serial.getCTS() and MODEMSTATE_MASK_CTS) |
1012 (self.serial.getDSR() and MODEMSTATE_MASK_DSR) |
1013 (self.serial.getRI() and MODEMSTATE_MASK_RI) |
1014 (self.serial.getCD() and MODEMSTATE_MASK_CD))
cliechti7cb78e82009-08-05 15:47:57 +00001015 # check what has changed
Chris Liechti033f17c2015-08-30 21:28:04 +02001016 deltas = modemstate ^ (self.last_modemstate or 0) # when last is None -> 0
cliechti7cb78e82009-08-05 15:47:57 +00001017 if deltas & MODEMSTATE_MASK_CTS:
1018 modemstate |= MODEMSTATE_MASK_CTS_CHANGE
1019 if deltas & MODEMSTATE_MASK_DSR:
1020 modemstate |= MODEMSTATE_MASK_DSR_CHANGE
1021 if deltas & MODEMSTATE_MASK_RI:
1022 modemstate |= MODEMSTATE_MASK_RI_CHANGE
1023 if deltas & MODEMSTATE_MASK_CD:
1024 modemstate |= MODEMSTATE_MASK_CD_CHANGE
1025 # if new state is different and the mask allows this change, send
cliechti86b593e2009-08-05 16:28:12 +00001026 # notification. suppress notifications when client is not rfc2217
cliechti7cb78e82009-08-05 15:47:57 +00001027 if modemstate != self.last_modemstate or force_notification:
cliechti8fb119c2009-08-05 23:39:45 +00001028 if (self._client_is_rfc2217 and (modemstate & self.modemstate_mask)) or force_notification:
Chris Liechti277220e2016-02-18 23:27:52 +01001029 self.rfc2217_send_subnegotiation(
Chris Liechti920917b2016-02-17 18:26:16 +01001030 SERVER_NOTIFY_MODEMSTATE,
1031 to_bytes([modemstate & self.modemstate_mask]))
cliechti6a300772009-08-12 02:28:56 +00001032 if self.logger:
Chris Liechti1f6643d2016-02-16 21:06:52 +01001033 self.logger.info("NOTIFY_MODEMSTATE: {}".format(modemstate))
cliechti7cb78e82009-08-05 15:47:57 +00001034 # save last state, but forget about deltas.
1035 # otherwise it would also notify about changing deltas which is
1036 # probably not very useful
1037 self.last_modemstate = modemstate & 0xf0
cliechti130d1f02009-08-04 02:10:58 +00001038
cliechti32c10332009-08-05 13:23:43 +00001039 # - outgoing data escaping
1040
1041 def escape(self, data):
cliechtieada4fd2013-07-31 16:26:07 +00001042 """\
cliechti7d448562014-08-03 21:57:45 +00001043 This generator function is for the user. All outgoing data has to be
cliechticb20a4f2011-04-25 02:25:54 +00001044 properly escaped, so that no IAC character in the data stream messes up
1045 the Telnet state machine in the server.
cliechti32c10332009-08-05 13:23:43 +00001046
1047 socket.sendall(escape(data))
1048 """
Chris Liechtib15dc052015-10-19 23:17:16 +02001049 for byte in iterbytes(data):
cliechti32c10332009-08-05 13:23:43 +00001050 if byte == IAC:
1051 yield IAC
1052 yield IAC
1053 else:
1054 yield byte
1055
cliechti130d1f02009-08-04 02:10:58 +00001056 # - incoming data filter
1057
1058 def filter(self, data):
cliechtieada4fd2013-07-31 16:26:07 +00001059 """\
cliechti7d448562014-08-03 21:57:45 +00001060 Handle a bunch of incoming bytes. This is a generator. It will yield
cliechti044d8662009-08-11 21:40:31 +00001061 all characters not of interest for Telnet/RFC 2217.
cliechti130d1f02009-08-04 02:10:58 +00001062
1063 The idea is that the reader thread pushes data from the socket through
1064 this filter:
1065
1066 for byte in filter(socket.recv(1024)):
1067 # do things like CR/LF conversion/whatever
1068 # and write data to the serial port
1069 serial.write(byte)
1070
1071 (socket error handling code left as exercise for the reader)
1072 """
Chris Liechtif99cd5c2015-08-13 22:54:16 +02001073 for byte in iterbytes(data):
cliechti130d1f02009-08-04 02:10:58 +00001074 if self.mode == M_NORMAL:
1075 # interpret as command or as data
1076 if byte == IAC:
1077 self.mode = M_IAC_SEEN
1078 else:
1079 # store data in sub option buffer or pass it to our
1080 # consumer depending on state
1081 if self.suboption is not None:
Chris Liechti01587b12015-08-05 02:39:32 +02001082 self.suboption += byte
cliechti130d1f02009-08-04 02:10:58 +00001083 else:
1084 yield byte
1085 elif self.mode == M_IAC_SEEN:
1086 if byte == IAC:
1087 # interpret as command doubled -> insert character
1088 # itself
cliechtif325c032009-12-25 16:09:49 +00001089 if self.suboption is not None:
Chris Liechti01587b12015-08-05 02:39:32 +02001090 self.suboption += byte
cliechtif325c032009-12-25 16:09:49 +00001091 else:
1092 yield byte
cliechti130d1f02009-08-04 02:10:58 +00001093 self.mode = M_NORMAL
1094 elif byte == SB:
1095 # sub option start
1096 self.suboption = bytearray()
1097 self.mode = M_NORMAL
1098 elif byte == SE:
1099 # sub option end -> process it now
Chris Liechti277220e2016-02-18 23:27:52 +01001100 self._telnet_process_subnegotiation(bytes(self.suboption))
cliechti130d1f02009-08-04 02:10:58 +00001101 self.suboption = None
1102 self.mode = M_NORMAL
1103 elif byte in (DO, DONT, WILL, WONT):
1104 # negotiation
1105 self.telnet_command = byte
1106 self.mode = M_NEGOTIATE
1107 else:
1108 # other telnet commands
Chris Liechti277220e2016-02-18 23:27:52 +01001109 self._telnet_process_command(byte)
cliechti130d1f02009-08-04 02:10:58 +00001110 self.mode = M_NORMAL
Chris Liechti033f17c2015-08-30 21:28:04 +02001111 elif self.mode == M_NEGOTIATE: # DO, DONT, WILL, WONT was received, option now following
Chris Liechti277220e2016-02-18 23:27:52 +01001112 self._telnet_negotiate_option(self.telnet_command, byte)
cliechti130d1f02009-08-04 02:10:58 +00001113 self.mode = M_NORMAL
1114
1115 # - incoming telnet commands and options
1116
Chris Liechti277220e2016-02-18 23:27:52 +01001117 def _telnet_process_command(self, command):
cliechti044d8662009-08-11 21:40:31 +00001118 """Process commands other than DO, DONT, WILL, WONT."""
cliechti130d1f02009-08-04 02:10:58 +00001119 # Currently none. RFC2217 only uses negotiation and subnegotiation.
cliechti6a300772009-08-12 02:28:56 +00001120 if self.logger:
Chris Liechti1f6643d2016-02-16 21:06:52 +01001121 self.logger.warning("ignoring Telnet command: {!r}".format(command))
cliechti130d1f02009-08-04 02:10:58 +00001122
Chris Liechti277220e2016-02-18 23:27:52 +01001123 def _telnet_negotiate_option(self, command, option):
cliechti044d8662009-08-11 21:40:31 +00001124 """Process incoming DO, DONT, WILL, WONT."""
cliechti130d1f02009-08-04 02:10:58 +00001125 # check our registered telnet options and forward command to them
1126 # they know themselves if they have to answer or not
1127 known = False
1128 for item in self._telnet_options:
1129 # can have more than one match! as some options are duplicated for
1130 # 'us' and 'them'
1131 if item.option == option:
1132 item.process_incoming(command)
1133 known = True
1134 if not known:
1135 # handle unknown options
1136 # only answer to positive requests and deny them
1137 if command == WILL or command == DO:
Chris Liechti277220e2016-02-18 23:27:52 +01001138 self.telnet_send_option((DONT if command == WILL else WONT), option)
cliechti6a300772009-08-12 02:28:56 +00001139 if self.logger:
Chris Liechti1f6643d2016-02-16 21:06:52 +01001140 self.logger.warning("rejected Telnet option: {!r}".format(option))
cliechti130d1f02009-08-04 02:10:58 +00001141
Chris Liechti277220e2016-02-18 23:27:52 +01001142 def _telnet_process_subnegotiation(self, suboption):
cliechti044d8662009-08-11 21:40:31 +00001143 """Process subnegotiation, the data between IAC SB and IAC SE."""
cliechti130d1f02009-08-04 02:10:58 +00001144 if suboption[0:1] == COM_PORT_OPTION:
cliechti6a300772009-08-12 02:28:56 +00001145 if self.logger:
Chris Liechti1f6643d2016-02-16 21:06:52 +01001146 self.logger.debug('received COM_PORT_OPTION: {!r}'.format(suboption))
cliechti130d1f02009-08-04 02:10:58 +00001147 if suboption[1:2] == SET_BAUDRATE:
1148 backup = self.serial.baudrate
1149 try:
Chris Liechti01587b12015-08-05 02:39:32 +02001150 (baudrate,) = struct.unpack(b"!I", suboption[2:6])
cliechtieada4fd2013-07-31 16:26:07 +00001151 if baudrate != 0:
1152 self.serial.baudrate = baudrate
Chris Liechtid2146002015-08-04 16:57:16 +02001153 except ValueError as e:
cliechti6a300772009-08-12 02:28:56 +00001154 if self.logger:
Chris Liechti1f6643d2016-02-16 21:06:52 +01001155 self.logger.error("failed to set baud rate: {}".format(e))
cliechti130d1f02009-08-04 02:10:58 +00001156 self.serial.baudrate = backup
cliechti5cc3eb12009-08-11 23:04:30 +00001157 else:
cliechti6a300772009-08-12 02:28:56 +00001158 if self.logger:
Chris Liechti1f6643d2016-02-16 21:06:52 +01001159 self.logger.info("{} baud rate: {}".format('set' if baudrate else 'get', self.serial.baudrate))
Chris Liechti277220e2016-02-18 23:27:52 +01001160 self.rfc2217_send_subnegotiation(SERVER_SET_BAUDRATE, struct.pack(b"!I", self.serial.baudrate))
cliechti130d1f02009-08-04 02:10:58 +00001161 elif suboption[1:2] == SET_DATASIZE:
1162 backup = self.serial.bytesize
1163 try:
Chris Liechti142ae562015-08-23 01:11:06 +02001164 (datasize,) = struct.unpack(b"!B", suboption[2:3])
cliechtieada4fd2013-07-31 16:26:07 +00001165 if datasize != 0:
1166 self.serial.bytesize = datasize
Chris Liechtid2146002015-08-04 16:57:16 +02001167 except ValueError as e:
cliechti6a300772009-08-12 02:28:56 +00001168 if self.logger:
Chris Liechti1f6643d2016-02-16 21:06:52 +01001169 self.logger.error("failed to set data size: {}".format(e))
cliechti130d1f02009-08-04 02:10:58 +00001170 self.serial.bytesize = backup
cliechti5cc3eb12009-08-11 23:04:30 +00001171 else:
cliechti6a300772009-08-12 02:28:56 +00001172 if self.logger:
Chris Liechti1f6643d2016-02-16 21:06:52 +01001173 self.logger.info("{} data size: {}".format('set' if datasize else 'get', self.serial.bytesize))
Chris Liechti277220e2016-02-18 23:27:52 +01001174 self.rfc2217_send_subnegotiation(SERVER_SET_DATASIZE, struct.pack(b"!B", self.serial.bytesize))
cliechti130d1f02009-08-04 02:10:58 +00001175 elif suboption[1:2] == SET_PARITY:
1176 backup = self.serial.parity
1177 try:
Chris Liechti01587b12015-08-05 02:39:32 +02001178 parity = struct.unpack(b"!B", suboption[2:3])[0]
cliechtieada4fd2013-07-31 16:26:07 +00001179 if parity != 0:
Chris Liechti920917b2016-02-17 18:26:16 +01001180 self.serial.parity = RFC2217_REVERSE_PARITY_MAP[parity]
Chris Liechtid2146002015-08-04 16:57:16 +02001181 except ValueError as e:
cliechti6a300772009-08-12 02:28:56 +00001182 if self.logger:
Chris Liechti1f6643d2016-02-16 21:06:52 +01001183 self.logger.error("failed to set parity: {}".format(e))
cliechti130d1f02009-08-04 02:10:58 +00001184 self.serial.parity = backup
cliechti5cc3eb12009-08-11 23:04:30 +00001185 else:
cliechti6a300772009-08-12 02:28:56 +00001186 if self.logger:
Chris Liechti1f6643d2016-02-16 21:06:52 +01001187 self.logger.info("{} parity: {}".format('set' if parity else 'get', self.serial.parity))
Chris Liechti277220e2016-02-18 23:27:52 +01001188 self.rfc2217_send_subnegotiation(
Chris Liechti920917b2016-02-17 18:26:16 +01001189 SERVER_SET_PARITY,
1190 struct.pack(b"!B", RFC2217_PARITY_MAP[self.serial.parity]))
cliechti130d1f02009-08-04 02:10:58 +00001191 elif suboption[1:2] == SET_STOPSIZE:
1192 backup = self.serial.stopbits
1193 try:
Chris Liechti01587b12015-08-05 02:39:32 +02001194 stopbits = struct.unpack(b"!B", suboption[2:3])[0]
cliechtieada4fd2013-07-31 16:26:07 +00001195 if stopbits != 0:
1196 self.serial.stopbits = RFC2217_REVERSE_STOPBIT_MAP[stopbits]
Chris Liechtid2146002015-08-04 16:57:16 +02001197 except ValueError as e:
cliechti6a300772009-08-12 02:28:56 +00001198 if self.logger:
Chris Liechti1f6643d2016-02-16 21:06:52 +01001199 self.logger.error("failed to set stop bits: {}".format(e))
cliechti130d1f02009-08-04 02:10:58 +00001200 self.serial.stopbits = backup
cliechti5cc3eb12009-08-11 23:04:30 +00001201 else:
cliechti6a300772009-08-12 02:28:56 +00001202 if self.logger:
Chris Liechti1f6643d2016-02-16 21:06:52 +01001203 self.logger.info("{} stop bits: {}".format('set' if stopbits else 'get', self.serial.stopbits))
Chris Liechti277220e2016-02-18 23:27:52 +01001204 self.rfc2217_send_subnegotiation(
Chris Liechti920917b2016-02-17 18:26:16 +01001205 SERVER_SET_STOPSIZE,
1206 struct.pack(b"!B", RFC2217_STOPBIT_MAP[self.serial.stopbits]))
cliechti130d1f02009-08-04 02:10:58 +00001207 elif suboption[1:2] == SET_CONTROL:
1208 if suboption[2:3] == SET_CONTROL_REQ_FLOW_SETTING:
1209 if self.serial.xonxoff:
Chris Liechti277220e2016-02-18 23:27:52 +01001210 self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_SW_FLOW_CONTROL)
cliechti130d1f02009-08-04 02:10:58 +00001211 elif self.serial.rtscts:
Chris Liechti277220e2016-02-18 23:27:52 +01001212 self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_HW_FLOW_CONTROL)
cliechti130d1f02009-08-04 02:10:58 +00001213 else:
Chris Liechti277220e2016-02-18 23:27:52 +01001214 self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_NO_FLOW_CONTROL)
cliechti130d1f02009-08-04 02:10:58 +00001215 elif suboption[2:3] == SET_CONTROL_USE_NO_FLOW_CONTROL:
1216 self.serial.xonxoff = False
1217 self.serial.rtscts = False
cliechti6a300772009-08-12 02:28:56 +00001218 if self.logger:
1219 self.logger.info("changed flow control to None")
Chris Liechti277220e2016-02-18 23:27:52 +01001220 self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_NO_FLOW_CONTROL)
cliechti130d1f02009-08-04 02:10:58 +00001221 elif suboption[2:3] == SET_CONTROL_USE_SW_FLOW_CONTROL:
1222 self.serial.xonxoff = True
cliechti6a300772009-08-12 02:28:56 +00001223 if self.logger:
1224 self.logger.info("changed flow control to XON/XOFF")
Chris Liechti277220e2016-02-18 23:27:52 +01001225 self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_SW_FLOW_CONTROL)
cliechti130d1f02009-08-04 02:10:58 +00001226 elif suboption[2:3] == SET_CONTROL_USE_HW_FLOW_CONTROL:
1227 self.serial.rtscts = True
cliechti6a300772009-08-12 02:28:56 +00001228 if self.logger:
1229 self.logger.info("changed flow control to RTS/CTS")
Chris Liechti277220e2016-02-18 23:27:52 +01001230 self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_HW_FLOW_CONTROL)
cliechti130d1f02009-08-04 02:10:58 +00001231 elif suboption[2:3] == SET_CONTROL_REQ_BREAK_STATE:
cliechti6a300772009-08-12 02:28:56 +00001232 if self.logger:
1233 self.logger.warning("requested break state - not implemented")
Chris Liechti033f17c2015-08-30 21:28:04 +02001234 pass # XXX needs cached value
cliechti130d1f02009-08-04 02:10:58 +00001235 elif suboption[2:3] == SET_CONTROL_BREAK_ON:
1236 self.serial.setBreak(True)
cliechti6a300772009-08-12 02:28:56 +00001237 if self.logger:
1238 self.logger.info("changed BREAK to active")
Chris Liechti277220e2016-02-18 23:27:52 +01001239 self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_BREAK_ON)
cliechti130d1f02009-08-04 02:10:58 +00001240 elif suboption[2:3] == SET_CONTROL_BREAK_OFF:
1241 self.serial.setBreak(False)
cliechti6a300772009-08-12 02:28:56 +00001242 if self.logger:
1243 self.logger.info("changed BREAK to inactive")
Chris Liechti277220e2016-02-18 23:27:52 +01001244 self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_BREAK_OFF)
cliechti130d1f02009-08-04 02:10:58 +00001245 elif suboption[2:3] == SET_CONTROL_REQ_DTR:
cliechti6a300772009-08-12 02:28:56 +00001246 if self.logger:
1247 self.logger.warning("requested DTR state - not implemented")
Chris Liechti033f17c2015-08-30 21:28:04 +02001248 pass # XXX needs cached value
cliechti130d1f02009-08-04 02:10:58 +00001249 elif suboption[2:3] == SET_CONTROL_DTR_ON:
1250 self.serial.setDTR(True)
cliechti6a300772009-08-12 02:28:56 +00001251 if self.logger:
1252 self.logger.info("changed DTR to active")
Chris Liechti277220e2016-02-18 23:27:52 +01001253 self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_DTR_ON)
cliechti130d1f02009-08-04 02:10:58 +00001254 elif suboption[2:3] == SET_CONTROL_DTR_OFF:
1255 self.serial.setDTR(False)
cliechti6a300772009-08-12 02:28:56 +00001256 if self.logger:
1257 self.logger.info("changed DTR to inactive")
Chris Liechti277220e2016-02-18 23:27:52 +01001258 self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_DTR_OFF)
cliechti130d1f02009-08-04 02:10:58 +00001259 elif suboption[2:3] == SET_CONTROL_REQ_RTS:
cliechti6a300772009-08-12 02:28:56 +00001260 if self.logger:
1261 self.logger.warning("requested RTS state - not implemented")
Chris Liechti033f17c2015-08-30 21:28:04 +02001262 pass # XXX needs cached value
Chris Liechti277220e2016-02-18 23:27:52 +01001263 #~ self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_RTS_ON)
cliechti130d1f02009-08-04 02:10:58 +00001264 elif suboption[2:3] == SET_CONTROL_RTS_ON:
1265 self.serial.setRTS(True)
cliechti6a300772009-08-12 02:28:56 +00001266 if self.logger:
1267 self.logger.info("changed RTS to active")
Chris Liechti277220e2016-02-18 23:27:52 +01001268 self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_RTS_ON)
cliechti130d1f02009-08-04 02:10:58 +00001269 elif suboption[2:3] == SET_CONTROL_RTS_OFF:
1270 self.serial.setRTS(False)
cliechti6a300772009-08-12 02:28:56 +00001271 if self.logger:
1272 self.logger.info("changed RTS to inactive")
Chris Liechti277220e2016-02-18 23:27:52 +01001273 self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_RTS_OFF)
cliechti130d1f02009-08-04 02:10:58 +00001274 #~ elif suboption[2:3] == SET_CONTROL_REQ_FLOW_SETTING_IN:
1275 #~ elif suboption[2:3] == SET_CONTROL_USE_NO_FLOW_CONTROL_IN:
1276 #~ elif suboption[2:3] == SET_CONTROL_USE_SW_FLOW_CONTOL_IN:
1277 #~ elif suboption[2:3] == SET_CONTROL_USE_HW_FLOW_CONTOL_IN:
1278 #~ elif suboption[2:3] == SET_CONTROL_USE_DCD_FLOW_CONTROL:
1279 #~ elif suboption[2:3] == SET_CONTROL_USE_DTR_FLOW_CONTROL:
1280 #~ elif suboption[2:3] == SET_CONTROL_USE_DSR_FLOW_CONTROL:
1281 elif suboption[1:2] == NOTIFY_LINESTATE:
cliechti7cb78e82009-08-05 15:47:57 +00001282 # client polls for current state
Chris Liechti277220e2016-02-18 23:27:52 +01001283 self.rfc2217_send_subnegotiation(
Chris Liechti920917b2016-02-17 18:26:16 +01001284 SERVER_NOTIFY_LINESTATE,
1285 to_bytes([0])) # sorry, nothing like that implemented
cliechti130d1f02009-08-04 02:10:58 +00001286 elif suboption[1:2] == NOTIFY_MODEMSTATE:
cliechti6a300772009-08-12 02:28:56 +00001287 if self.logger:
1288 self.logger.info("request for modem state")
cliechti7cb78e82009-08-05 15:47:57 +00001289 # client polls for current state
1290 self.check_modem_lines(force_notification=True)
cliechti130d1f02009-08-04 02:10:58 +00001291 elif suboption[1:2] == FLOWCONTROL_SUSPEND:
cliechti6a300772009-08-12 02:28:56 +00001292 if self.logger:
1293 self.logger.info("suspend")
cliechti130d1f02009-08-04 02:10:58 +00001294 self._remote_suspend_flow = True
1295 elif suboption[1:2] == FLOWCONTROL_RESUME:
cliechti6a300772009-08-12 02:28:56 +00001296 if self.logger:
1297 self.logger.info("resume")
cliechti130d1f02009-08-04 02:10:58 +00001298 self._remote_suspend_flow = False
1299 elif suboption[1:2] == SET_LINESTATE_MASK:
Chris Liechti033f17c2015-08-30 21:28:04 +02001300 self.linstate_mask = ord(suboption[2:3]) # ensure it is a number
cliechti6a300772009-08-12 02:28:56 +00001301 if self.logger:
Chris Liechti1f6643d2016-02-16 21:06:52 +01001302 self.logger.info("line state mask: 0x{:02x}".format(self.linstate_mask))
cliechti130d1f02009-08-04 02:10:58 +00001303 elif suboption[1:2] == SET_MODEMSTATE_MASK:
Chris Liechti033f17c2015-08-30 21:28:04 +02001304 self.modemstate_mask = ord(suboption[2:3]) # ensure it is a number
cliechti6a300772009-08-12 02:28:56 +00001305 if self.logger:
Chris Liechti1f6643d2016-02-16 21:06:52 +01001306 self.logger.info("modem state mask: 0x{:02x}".format(self.modemstate_mask))
cliechti130d1f02009-08-04 02:10:58 +00001307 elif suboption[1:2] == PURGE_DATA:
1308 if suboption[2:3] == PURGE_RECEIVE_BUFFER:
Chris Liechti3ad62fb2015-08-29 21:53:32 +02001309 self.serial.reset_input_buffer()
cliechti6a300772009-08-12 02:28:56 +00001310 if self.logger:
1311 self.logger.info("purge in")
Chris Liechti277220e2016-02-18 23:27:52 +01001312 self.rfc2217_send_subnegotiation(SERVER_PURGE_DATA, PURGE_RECEIVE_BUFFER)
cliechti130d1f02009-08-04 02:10:58 +00001313 elif suboption[2:3] == PURGE_TRANSMIT_BUFFER:
Chris Liechti3ad62fb2015-08-29 21:53:32 +02001314 self.serial.reset_output_buffer()
cliechti6a300772009-08-12 02:28:56 +00001315 if self.logger:
1316 self.logger.info("purge out")
Chris Liechti277220e2016-02-18 23:27:52 +01001317 self.rfc2217_send_subnegotiation(SERVER_PURGE_DATA, PURGE_TRANSMIT_BUFFER)
cliechti130d1f02009-08-04 02:10:58 +00001318 elif suboption[2:3] == PURGE_BOTH_BUFFERS:
Chris Liechti3ad62fb2015-08-29 21:53:32 +02001319 self.serial.reset_input_buffer()
1320 self.serial.reset_output_buffer()
cliechti6a300772009-08-12 02:28:56 +00001321 if self.logger:
1322 self.logger.info("purge both")
Chris Liechti277220e2016-02-18 23:27:52 +01001323 self.rfc2217_send_subnegotiation(SERVER_PURGE_DATA, PURGE_BOTH_BUFFERS)
cliechti130d1f02009-08-04 02:10:58 +00001324 else:
cliechti6a300772009-08-12 02:28:56 +00001325 if self.logger:
Chris Liechti1f6643d2016-02-16 21:06:52 +01001326 self.logger.error("undefined PURGE_DATA: {!r}".format(list(suboption[2:])))
cliechti130d1f02009-08-04 02:10:58 +00001327 else:
cliechti6a300772009-08-12 02:28:56 +00001328 if self.logger:
Chris Liechti1f6643d2016-02-16 21:06:52 +01001329 self.logger.error("undefined COM_PORT_OPTION: {!r}".format(list(suboption[1:])))
cliechti130d1f02009-08-04 02:10:58 +00001330 else:
cliechti6a300772009-08-12 02:28:56 +00001331 if self.logger:
Chris Liechti1f6643d2016-02-16 21:06:52 +01001332 self.logger.warning("unknown subnegotiation: {!r}".format(suboption))
cliechti130d1f02009-08-04 02:10:58 +00001333
1334
1335# simple client test
cliechti8099bed2009-08-01 23:59:18 +00001336if __name__ == '__main__':
1337 import sys
1338 s = Serial('rfc2217://localhost:7000', 115200)
Chris Liechti1f6643d2016-02-16 21:06:52 +01001339 sys.stdout.write('{}\n'.format(s))
cliechti8099bed2009-08-01 23:59:18 +00001340
cliechti8099bed2009-08-01 23:59:18 +00001341 sys.stdout.write("write...\n")
Chris Liechtib4cda3a2015-08-08 17:12:08 +02001342 s.write(b"hello\n")
cliechti8099bed2009-08-01 23:59:18 +00001343 s.flush()
Chris Liechti1f6643d2016-02-16 21:06:52 +01001344 sys.stdout.write("read: {}\n".format(s.read(5)))
cliechti8099bed2009-08-01 23:59:18 +00001345 s.close()