blob: e8ddb7f2e1514ac65a1a74be8cb6c74a045e1605 [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
gesorthyb5766f22017-03-23 08:35:14 +0100896 status lines, one after the other is queried by the user (CTS, DSR
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 = (
gesorthyb5766f22017-03-23 08:35:14 +01001011 (self.serial.cts and MODEMSTATE_MASK_CTS) |
1012 (self.serial.dsr and MODEMSTATE_MASK_DSR) |
1013 (self.serial.ri and MODEMSTATE_MASK_RI) |
1014 (self.serial.cd 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:
gesorthyb5766f22017-03-23 08:35:14 +01001236 self.serial.break_condition = 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:
gesorthyb5766f22017-03-23 08:35:14 +01001241 self.serial.break_condition = 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:
gesorthyb5766f22017-03-23 08:35:14 +01001250 self.serial.dtr = 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:
gesorthyb5766f22017-03-23 08:35:14 +01001255 self.serial.dtr = 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:
gesorthyb5766f22017-03-23 08:35:14 +01001265 self.serial.rts = 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:
gesorthyb5766f22017-03-23 08:35:14 +01001270 self.serial.rts = 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()