blob: 66628c0e70c7bbd718145a890837c16c7d4ac28c [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
76from serial.serialutil import SerialBase, SerialException, to_bytes, iterbytes, portNotOpenError
Chris Liechtib4cda3a2015-08-08 17:12:08 +020077
cliechti8099bed2009-08-01 23:59:18 +000078# port string is expected to be something like this:
79# rfc2217://host:port
80# host may be an IP or including domain, whatever.
81# port is 0...65535
82
Chris Liechti3ad62fb2015-08-29 21:53:32 +020083# map log level names to constants. used in from_url()
cliechti5cc3eb12009-08-11 23:04:30 +000084LOGGER_LEVELS = {
Chris Liechticb6ce1b2016-02-02 01:53:56 +010085 'debug': logging.DEBUG,
86 'info': logging.INFO,
87 'warning': logging.WARNING,
88 'error': logging.ERROR,
89}
cliechti5cc3eb12009-08-11 23:04:30 +000090
91
cliechti8099bed2009-08-01 23:59:18 +000092# telnet protocol characters
Chris Liechti033f17c2015-08-30 21:28:04 +020093SE = b'\xf0' # Subnegotiation End
Chris Liechtib4cda3a2015-08-08 17:12:08 +020094NOP = b'\xf1' # No Operation
Chris Liechti033f17c2015-08-30 21:28:04 +020095DM = b'\xf2' # Data Mark
Chris Liechtib4cda3a2015-08-08 17:12:08 +020096BRK = b'\xf3' # Break
Chris Liechti033f17c2015-08-30 21:28:04 +020097IP = b'\xf4' # Interrupt process
98AO = b'\xf5' # Abort output
Chris Liechtib4cda3a2015-08-08 17:12:08 +020099AYT = b'\xf6' # Are You There
Chris Liechti033f17c2015-08-30 21:28:04 +0200100EC = b'\xf7' # Erase Character
101EL = b'\xf8' # Erase Line
102GA = b'\xf9' # Go Ahead
103SB = b'\xfa' # Subnegotiation Begin
Chris Liechtib4cda3a2015-08-08 17:12:08 +0200104WILL = b'\xfb'
105WONT = b'\xfc'
Chris Liechti033f17c2015-08-30 21:28:04 +0200106DO = b'\xfd'
Chris Liechtib4cda3a2015-08-08 17:12:08 +0200107DONT = b'\xfe'
Chris Liechti033f17c2015-08-30 21:28:04 +0200108IAC = b'\xff' # Interpret As Command
Chris Liechtib4cda3a2015-08-08 17:12:08 +0200109IAC_DOUBLED = b'\xff\xff'
cliechti8099bed2009-08-01 23:59:18 +0000110
111# selected telnet options
Chris Liechtib4cda3a2015-08-08 17:12:08 +0200112BINARY = b'\x00' # 8-bit data path
113ECHO = b'\x01' # echo
114SGA = b'\x03' # suppress go ahead
cliechti8099bed2009-08-01 23:59:18 +0000115
116# RFC2217
Chris Liechtib4cda3a2015-08-08 17:12:08 +0200117COM_PORT_OPTION = b'\x2c'
cliechti8099bed2009-08-01 23:59:18 +0000118
119# Client to Access Server
Chris Liechtib4cda3a2015-08-08 17:12:08 +0200120SET_BAUDRATE = b'\x01'
121SET_DATASIZE = b'\x02'
122SET_PARITY = b'\x03'
123SET_STOPSIZE = b'\x04'
124SET_CONTROL = b'\x05'
125NOTIFY_LINESTATE = b'\x06'
126NOTIFY_MODEMSTATE = b'\x07'
127FLOWCONTROL_SUSPEND = b'\x08'
128FLOWCONTROL_RESUME = b'\x09'
129SET_LINESTATE_MASK = b'\x0a'
130SET_MODEMSTATE_MASK = b'\x0b'
131PURGE_DATA = b'\x0c'
cliechti8099bed2009-08-01 23:59:18 +0000132
Chris Liechtib4cda3a2015-08-08 17:12:08 +0200133SERVER_SET_BAUDRATE = b'\x65'
134SERVER_SET_DATASIZE = b'\x66'
135SERVER_SET_PARITY = b'\x67'
136SERVER_SET_STOPSIZE = b'\x68'
137SERVER_SET_CONTROL = b'\x69'
138SERVER_NOTIFY_LINESTATE = b'\x6a'
139SERVER_NOTIFY_MODEMSTATE = b'\x6b'
140SERVER_FLOWCONTROL_SUSPEND = b'\x6c'
141SERVER_FLOWCONTROL_RESUME = b'\x6d'
142SERVER_SET_LINESTATE_MASK = b'\x6e'
143SERVER_SET_MODEMSTATE_MASK = b'\x6f'
144SERVER_PURGE_DATA = b'\x70'
cliechti8099bed2009-08-01 23:59:18 +0000145
146RFC2217_ANSWER_MAP = {
147 SET_BAUDRATE: SERVER_SET_BAUDRATE,
148 SET_DATASIZE: SERVER_SET_DATASIZE,
149 SET_PARITY: SERVER_SET_PARITY,
150 SET_STOPSIZE: SERVER_SET_STOPSIZE,
151 SET_CONTROL: SERVER_SET_CONTROL,
152 NOTIFY_LINESTATE: SERVER_NOTIFY_LINESTATE,
153 NOTIFY_MODEMSTATE: SERVER_NOTIFY_MODEMSTATE,
154 FLOWCONTROL_SUSPEND: SERVER_FLOWCONTROL_SUSPEND,
155 FLOWCONTROL_RESUME: SERVER_FLOWCONTROL_RESUME,
156 SET_LINESTATE_MASK: SERVER_SET_LINESTATE_MASK,
157 SET_MODEMSTATE_MASK: SERVER_SET_MODEMSTATE_MASK,
158 PURGE_DATA: SERVER_PURGE_DATA,
159}
160
Chris Liechtib4cda3a2015-08-08 17:12:08 +0200161SET_CONTROL_REQ_FLOW_SETTING = b'\x00' # Request Com Port Flow Control Setting (outbound/both)
162SET_CONTROL_USE_NO_FLOW_CONTROL = b'\x01' # Use No Flow Control (outbound/both)
163SET_CONTROL_USE_SW_FLOW_CONTROL = b'\x02' # Use XON/XOFF Flow Control (outbound/both)
164SET_CONTROL_USE_HW_FLOW_CONTROL = b'\x03' # Use HARDWARE Flow Control (outbound/both)
165SET_CONTROL_REQ_BREAK_STATE = b'\x04' # Request BREAK State
166SET_CONTROL_BREAK_ON = b'\x05' # Set BREAK State ON
167SET_CONTROL_BREAK_OFF = b'\x06' # Set BREAK State OFF
168SET_CONTROL_REQ_DTR = b'\x07' # Request DTR Signal State
169SET_CONTROL_DTR_ON = b'\x08' # Set DTR Signal State ON
170SET_CONTROL_DTR_OFF = b'\x09' # Set DTR Signal State OFF
171SET_CONTROL_REQ_RTS = b'\x0a' # Request RTS Signal State
172SET_CONTROL_RTS_ON = b'\x0b' # Set RTS Signal State ON
173SET_CONTROL_RTS_OFF = b'\x0c' # Set RTS Signal State OFF
174SET_CONTROL_REQ_FLOW_SETTING_IN = b'\x0d' # Request Com Port Flow Control Setting (inbound)
175SET_CONTROL_USE_NO_FLOW_CONTROL_IN = b'\x0e' # Use No Flow Control (inbound)
176SET_CONTROL_USE_SW_FLOW_CONTOL_IN = b'\x0f' # Use XON/XOFF Flow Control (inbound)
177SET_CONTROL_USE_HW_FLOW_CONTOL_IN = b'\x10' # Use HARDWARE Flow Control (inbound)
178SET_CONTROL_USE_DCD_FLOW_CONTROL = b'\x11' # Use DCD Flow Control (outbound/both)
179SET_CONTROL_USE_DTR_FLOW_CONTROL = b'\x12' # Use DTR Flow Control (inbound)
180SET_CONTROL_USE_DSR_FLOW_CONTROL = b'\x13' # Use DSR Flow Control (outbound/both)
cliechti8099bed2009-08-01 23:59:18 +0000181
Chris Liechtib4cda3a2015-08-08 17:12:08 +0200182LINESTATE_MASK_TIMEOUT = 128 # Time-out Error
183LINESTATE_MASK_SHIFTREG_EMPTY = 64 # Transfer Shift Register Empty
184LINESTATE_MASK_TRANSREG_EMPTY = 32 # Transfer Holding Register Empty
185LINESTATE_MASK_BREAK_DETECT = 16 # Break-detect Error
186LINESTATE_MASK_FRAMING_ERROR = 8 # Framing Error
187LINESTATE_MASK_PARTIY_ERROR = 4 # Parity Error
188LINESTATE_MASK_OVERRUN_ERROR = 2 # Overrun Error
189LINESTATE_MASK_DATA_READY = 1 # Data Ready
cliechti8099bed2009-08-01 23:59:18 +0000190
Chris Liechtib4cda3a2015-08-08 17:12:08 +0200191MODEMSTATE_MASK_CD = 128 # Receive Line Signal Detect (also known as Carrier Detect)
192MODEMSTATE_MASK_RI = 64 # Ring Indicator
193MODEMSTATE_MASK_DSR = 32 # Data-Set-Ready Signal State
194MODEMSTATE_MASK_CTS = 16 # Clear-To-Send Signal State
195MODEMSTATE_MASK_CD_CHANGE = 8 # Delta Receive Line Signal Detect
196MODEMSTATE_MASK_RI_CHANGE = 4 # Trailing-edge Ring Detector
197MODEMSTATE_MASK_DSR_CHANGE = 2 # Delta Data-Set-Ready
198MODEMSTATE_MASK_CTS_CHANGE = 1 # Delta Clear-To-Send
cliechti8099bed2009-08-01 23:59:18 +0000199
Chris Liechtib4cda3a2015-08-08 17:12:08 +0200200PURGE_RECEIVE_BUFFER = b'\x01' # Purge access server receive data buffer
201PURGE_TRANSMIT_BUFFER = b'\x02' # Purge access server transmit data buffer
Chris Liechti70ca49c2016-02-07 23:54:03 +0100202PURGE_BOTH_BUFFERS = b'\x03' # Purge both the access server receive data
203 # buffer and the access server transmit data buffer
cliechti8099bed2009-08-01 23:59:18 +0000204
205
206RFC2217_PARITY_MAP = {
Chris Liechti033f17c2015-08-30 21:28:04 +0200207 serial.PARITY_NONE: 1,
208 serial.PARITY_ODD: 2,
209 serial.PARITY_EVEN: 3,
210 serial.PARITY_MARK: 4,
211 serial.PARITY_SPACE: 5,
cliechti8099bed2009-08-01 23:59:18 +0000212}
Chris Liechti033f17c2015-08-30 21:28:04 +0200213RFC2217_REVERSE_PARITY_MAP = dict((v, k) for k, v in RFC2217_PARITY_MAP.items())
cliechti8099bed2009-08-01 23:59:18 +0000214
215RFC2217_STOPBIT_MAP = {
Chris Liechti033f17c2015-08-30 21:28:04 +0200216 serial.STOPBITS_ONE: 1,
217 serial.STOPBITS_ONE_POINT_FIVE: 3,
218 serial.STOPBITS_TWO: 2,
cliechti8099bed2009-08-01 23:59:18 +0000219}
Chris Liechti033f17c2015-08-30 21:28:04 +0200220RFC2217_REVERSE_STOPBIT_MAP = dict((v, k) for k, v in RFC2217_STOPBIT_MAP.items())
cliechti8099bed2009-08-01 23:59:18 +0000221
cliechti130d1f02009-08-04 02:10:58 +0000222# Telnet filter states
223M_NORMAL = 0
224M_IAC_SEEN = 1
225M_NEGOTIATE = 2
cliechti8099bed2009-08-01 23:59:18 +0000226
cliechti130d1f02009-08-04 02:10:58 +0000227# TelnetOption and TelnetSubnegotiation states
cliechtiac205322009-08-02 20:40:21 +0000228REQUESTED = 'REQUESTED'
229ACTIVE = 'ACTIVE'
230INACTIVE = 'INACTIVE'
231REALLY_INACTIVE = 'REALLY_INACTIVE'
232
Chris Liechti033f17c2015-08-30 21:28:04 +0200233
cliechtiac205322009-08-02 20:40:21 +0000234class TelnetOption(object):
cliechti1ef7e3e2009-08-03 02:38:43 +0000235 """Manage a single telnet option, keeps track of DO/DONT WILL/WONT."""
236
Chris Liechti70ca49c2016-02-07 23:54:03 +0100237 def __init__(self, connection, name, option, send_yes, send_no, ack_yes,
238 ack_no, initial_state, activation_callback=None):
cliechtieada4fd2013-07-31 16:26:07 +0000239 """\
240 Initialize option.
cliechti1ef7e3e2009-08-03 02:38:43 +0000241 :param connection: connection used to transmit answers
242 :param name: a readable name for debug outputs
243 :param send_yes: what to send when option is to be enabled.
244 :param send_no: what to send when option is to be disabled.
245 :param ack_yes: what to expect when remote agrees on option.
246 :param ack_no: what to expect when remote disagrees on option.
247 :param initial_state: options initialized with REQUESTED are tried to
248 be enabled on startup. use INACTIVE for all others.
249 """
cliechti2b929b72009-08-02 23:49:02 +0000250 self.connection = connection
cliechtiac205322009-08-02 20:40:21 +0000251 self.name = name
252 self.option = option
253 self.send_yes = send_yes
254 self.send_no = send_no
255 self.ack_yes = ack_yes
256 self.ack_no = ack_no
257 self.state = initial_state
258 self.active = False
cliechti86b593e2009-08-05 16:28:12 +0000259 self.activation_callback = activation_callback
cliechtiac205322009-08-02 20:40:21 +0000260
261 def __repr__(self):
cliechti1ef7e3e2009-08-03 02:38:43 +0000262 """String for debug outputs"""
Chris Liechti1f6643d2016-02-16 21:06:52 +0100263 return "{o.name}:{o.active}({o.state})".format(o=self)
cliechtiac205322009-08-02 20:40:21 +0000264
cliechti2b929b72009-08-02 23:49:02 +0000265 def process_incoming(self, command):
cliechti7d448562014-08-03 21:57:45 +0000266 """\
267 A DO/DONT/WILL/WONT was received for this option, update state and
268 answer when needed.
269 """
cliechtiac205322009-08-02 20:40:21 +0000270 if command == self.ack_yes:
271 if self.state is REQUESTED:
272 self.state = ACTIVE
273 self.active = True
cliechti86b593e2009-08-05 16:28:12 +0000274 if self.activation_callback is not None:
275 self.activation_callback()
cliechtiac205322009-08-02 20:40:21 +0000276 elif self.state is ACTIVE:
277 pass
278 elif self.state is INACTIVE:
279 self.state = ACTIVE
Chris Liechti277220e2016-02-18 23:27:52 +0100280 self.connection.telnet_send_option(self.send_yes, self.option)
cliechtiac205322009-08-02 20:40:21 +0000281 self.active = True
cliechti86b593e2009-08-05 16:28:12 +0000282 if self.activation_callback is not None:
283 self.activation_callback()
cliechtiac205322009-08-02 20:40:21 +0000284 elif self.state is REALLY_INACTIVE:
Chris Liechti277220e2016-02-18 23:27:52 +0100285 self.connection.telnet_send_option(self.send_no, self.option)
cliechtiac205322009-08-02 20:40:21 +0000286 else:
Chris Liechti1f6643d2016-02-16 21:06:52 +0100287 raise ValueError('option in illegal state {!r}'.format(self))
cliechtiac205322009-08-02 20:40:21 +0000288 elif command == self.ack_no:
289 if self.state is REQUESTED:
290 self.state = INACTIVE
291 self.active = False
292 elif self.state is ACTIVE:
293 self.state = INACTIVE
Chris Liechti277220e2016-02-18 23:27:52 +0100294 self.connection.telnet_send_option(self.send_no, self.option)
cliechtiac205322009-08-02 20:40:21 +0000295 self.active = False
296 elif self.state is INACTIVE:
297 pass
298 elif self.state is REALLY_INACTIVE:
299 pass
300 else:
Chris Liechti1f6643d2016-02-16 21:06:52 +0100301 raise ValueError('option in illegal state {!r}'.format(self))
cliechtiac205322009-08-02 20:40:21 +0000302
303
cliechti2b929b72009-08-02 23:49:02 +0000304class TelnetSubnegotiation(object):
cliechtieada4fd2013-07-31 16:26:07 +0000305 """\
306 A object to handle subnegotiation of options. In this case actually
307 sub-sub options for RFC 2217. It is used to track com port options.
308 """
cliechti2b929b72009-08-02 23:49:02 +0000309
310 def __init__(self, connection, name, option, ack_option=None):
Chris Liechti033f17c2015-08-30 21:28:04 +0200311 if ack_option is None:
312 ack_option = option
cliechti2b929b72009-08-02 23:49:02 +0000313 self.connection = connection
314 self.name = name
315 self.option = option
316 self.value = None
317 self.ack_option = ack_option
318 self.state = INACTIVE
319
320 def __repr__(self):
cliechti044d8662009-08-11 21:40:31 +0000321 """String for debug outputs."""
Chris Liechti1f6643d2016-02-16 21:06:52 +0100322 return "{sn.name}:{sn.state}".format(sn=self)
cliechti2b929b72009-08-02 23:49:02 +0000323
324 def set(self, value):
cliechtieada4fd2013-07-31 16:26:07 +0000325 """\
cliechti7d448562014-08-03 21:57:45 +0000326 Request a change of the value. a request is sent to the server. if
cliechti2b929b72009-08-02 23:49:02 +0000327 the client needs to know if the change is performed he has to check the
cliechtieada4fd2013-07-31 16:26:07 +0000328 state of this object.
329 """
cliechti2b929b72009-08-02 23:49:02 +0000330 self.value = value
331 self.state = REQUESTED
Chris Liechti277220e2016-02-18 23:27:52 +0100332 self.connection.rfc2217_send_subnegotiation(self.option, self.value)
cliechti6a300772009-08-12 02:28:56 +0000333 if self.connection.logger:
Chris Liechti1f6643d2016-02-16 21:06:52 +0100334 self.connection.logger.debug("SB Requesting {} -> {!r}".format(self.name, self.value))
cliechti2b929b72009-08-02 23:49:02 +0000335
Chris Liechti277220e2016-02-18 23:27:52 +0100336 def is_ready(self):
cliechtieada4fd2013-07-31 16:26:07 +0000337 """\
cliechti7d448562014-08-03 21:57:45 +0000338 Check if answer from server has been received. when server rejects
cliechtieada4fd2013-07-31 16:26:07 +0000339 the change, raise a ValueError.
340 """
cliechti2b929b72009-08-02 23:49:02 +0000341 if self.state == REALLY_INACTIVE:
Chris Liechti1f6643d2016-02-16 21:06:52 +0100342 raise ValueError("remote rejected value for option {!r}".format(self.name))
cliechti2b929b72009-08-02 23:49:02 +0000343 return self.state == ACTIVE
cliechti1ef7e3e2009-08-03 02:38:43 +0000344 # add property to have a similar interface as TelnetOption
Chris Liechti277220e2016-02-18 23:27:52 +0100345 active = property(is_ready)
cliechti2b929b72009-08-02 23:49:02 +0000346
cliechti044d8662009-08-11 21:40:31 +0000347 def wait(self, timeout=3):
cliechtieada4fd2013-07-31 16:26:07 +0000348 """\
cliechti7d448562014-08-03 21:57:45 +0000349 Wait until the subnegotiation has been acknowledged or timeout. It
cliechti1ef7e3e2009-08-03 02:38:43 +0000350 can also throw a value error when the answer from the server does not
cliechtieada4fd2013-07-31 16:26:07 +0000351 match the value sent.
352 """
cliechti044d8662009-08-11 21:40:31 +0000353 timeout_time = time.time() + timeout
cliechti2b929b72009-08-02 23:49:02 +0000354 while time.time() < timeout_time:
355 time.sleep(0.05) # prevent 100% CPU load
Chris Liechti277220e2016-02-18 23:27:52 +0100356 if self.is_ready():
cliechti2b929b72009-08-02 23:49:02 +0000357 break
358 else:
Chris Liechti1f6643d2016-02-16 21:06:52 +0100359 raise SerialException("timeout while waiting for option {!r}".format(self.name))
cliechti2b929b72009-08-02 23:49:02 +0000360
Chris Liechti277220e2016-02-18 23:27:52 +0100361 def check_answer(self, suboption):
cliechtieada4fd2013-07-31 16:26:07 +0000362 """\
cliechti7d448562014-08-03 21:57:45 +0000363 Check an incoming subnegotiation block. The parameter already has
cliechtieada4fd2013-07-31 16:26:07 +0000364 cut off the header like sub option number and com port option value.
365 """
cliechti2b929b72009-08-02 23:49:02 +0000366 if self.value == suboption[:len(self.value)]:
367 self.state = ACTIVE
368 else:
Chris Liechti277220e2016-02-18 23:27:52 +0100369 # error propagation done in is_ready
cliechti2b929b72009-08-02 23:49:02 +0000370 self.state = REALLY_INACTIVE
cliechti6a300772009-08-12 02:28:56 +0000371 if self.connection.logger:
Chris Liechti1f6643d2016-02-16 21:06:52 +0100372 self.connection.logger.debug("SB Answer {} -> {!r} -> {}".format(self.name, suboption, self.state))
cliechti2b929b72009-08-02 23:49:02 +0000373
374
Chris Liechtief6b7b42015-08-06 22:19:26 +0200375class Serial(SerialBase):
cliechti044d8662009-08-11 21:40:31 +0000376 """Serial port implementation for RFC 2217 remote serial ports."""
cliechti8099bed2009-08-01 23:59:18 +0000377
378 BAUDRATES = (50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800,
379 9600, 19200, 38400, 57600, 115200)
380
Chris Liechtibb5341b2015-10-31 23:31:26 +0100381 def __init__(self, *args, **kwargs):
382 super(Serial, self).__init__(*args, **kwargs)
383 self._thread = None
384 self._socket = None
Chris Liechti920917b2016-02-17 18:26:16 +0100385 self._linestate = 0
386 self._modemstate = None
387 self._modemstate_expires = 0
388 self._remote_suspend_flow = False
389 self._write_lock = None
390 self.logger = None
391 self._ignore_set_control_answer = False
392 self._poll_modem_state = False
393 self._network_timeout = 3
394 self._telnet_options = None
395 self._rfc2217_port_settings = None
396 self._rfc2217_options = None
397 self._read_buffer = None
Chris Liechtibb5341b2015-10-31 23:31:26 +0100398
cliechti8099bed2009-08-01 23:59:18 +0000399 def open(self):
cliechtieada4fd2013-07-31 16:26:07 +0000400 """\
401 Open port with current settings. This may throw a SerialException
402 if the port cannot be opened.
403 """
cliechti6a300772009-08-12 02:28:56 +0000404 self.logger = None
cliechti81c54762009-08-03 23:53:27 +0000405 self._ignore_set_control_answer = False
cliechti7cb78e82009-08-05 15:47:57 +0000406 self._poll_modem_state = False
cliechtidfe2d272009-08-10 22:19:41 +0000407 self._network_timeout = 3
cliechti8099bed2009-08-01 23:59:18 +0000408 if self._port is None:
409 raise SerialException("Port must be configured before it can be used.")
Chris Liechti3ad62fb2015-08-29 21:53:32 +0200410 if self.is_open:
cliechti8f69e702011-03-19 00:22:32 +0000411 raise SerialException("Port is already open.")
cliechti8099bed2009-08-01 23:59:18 +0000412 try:
Chris Liechti3ad62fb2015-08-29 21:53:32 +0200413 self._socket = socket.create_connection(self.from_url(self.portstr))
cliechti6a300772009-08-12 02:28:56 +0000414 self._socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
Chris Liechti68340d72015-08-03 14:15:48 +0200415 except Exception as msg:
cliechti8099bed2009-08-01 23:59:18 +0000416 self._socket = None
Chris Liechti1f6643d2016-02-16 21:06:52 +0100417 raise SerialException("Could not open port {}: {}".format(self.portstr, msg))
cliechti8099bed2009-08-01 23:59:18 +0000418
Chris Liechti033f17c2015-08-30 21:28:04 +0200419 self._socket.settimeout(5) # XXX good value?
cliechti8099bed2009-08-01 23:59:18 +0000420
cliechti1ef7e3e2009-08-03 02:38:43 +0000421 # use a thread save queue as buffer. it also simplifies implementing
422 # the read timeout
cliechti8099bed2009-08-01 23:59:18 +0000423 self._read_buffer = Queue.Queue()
cliechti81c54762009-08-03 23:53:27 +0000424 # to ensure that user writes does not interfere with internal
425 # telnet/rfc2217 options establish a lock
426 self._write_lock = threading.Lock()
cliechtiac205322009-08-02 20:40:21 +0000427 # name the following separately so that, below, a check can be easily done
428 mandadory_options = [
cliechti2b929b72009-08-02 23:49:02 +0000429 TelnetOption(self, 'we-BINARY', BINARY, WILL, WONT, DO, DONT, INACTIVE),
cliechti2b929b72009-08-02 23:49:02 +0000430 TelnetOption(self, 'we-RFC2217', COM_PORT_OPTION, WILL, WONT, DO, DONT, REQUESTED),
cliechtiac205322009-08-02 20:40:21 +0000431 ]
432 # all supported telnet options
433 self._telnet_options = [
cliechtia29275e2009-08-03 00:08:04 +0000434 TelnetOption(self, 'ECHO', ECHO, DO, DONT, WILL, WONT, REQUESTED),
cliechti2b929b72009-08-02 23:49:02 +0000435 TelnetOption(self, 'we-SGA', SGA, WILL, WONT, DO, DONT, REQUESTED),
436 TelnetOption(self, 'they-SGA', SGA, DO, DONT, WILL, WONT, REQUESTED),
cliechti81c54762009-08-03 23:53:27 +0000437 TelnetOption(self, 'they-BINARY', BINARY, DO, DONT, WILL, WONT, INACTIVE),
438 TelnetOption(self, 'they-RFC2217', COM_PORT_OPTION, DO, DONT, WILL, WONT, REQUESTED),
cliechtiac205322009-08-02 20:40:21 +0000439 ] + mandadory_options
cliechti044d8662009-08-11 21:40:31 +0000440 # RFC 2217 specific states
cliechti2b929b72009-08-02 23:49:02 +0000441 # COM port settings
442 self._rfc2217_port_settings = {
443 'baudrate': TelnetSubnegotiation(self, 'baudrate', SET_BAUDRATE, SERVER_SET_BAUDRATE),
444 'datasize': TelnetSubnegotiation(self, 'datasize', SET_DATASIZE, SERVER_SET_DATASIZE),
445 'parity': TelnetSubnegotiation(self, 'parity', SET_PARITY, SERVER_SET_PARITY),
446 'stopsize': TelnetSubnegotiation(self, 'stopsize', SET_STOPSIZE, SERVER_SET_STOPSIZE),
Chris Liechticb6ce1b2016-02-02 01:53:56 +0100447 }
cliechticb20a4f2011-04-25 02:25:54 +0000448 # There are more subnegotiation objects, combine all in one dictionary
cliechti2b929b72009-08-02 23:49:02 +0000449 # for easy access
450 self._rfc2217_options = {
451 'purge': TelnetSubnegotiation(self, 'purge', PURGE_DATA, SERVER_PURGE_DATA),
cliechti81c54762009-08-03 23:53:27 +0000452 'control': TelnetSubnegotiation(self, 'control', SET_CONTROL, SERVER_SET_CONTROL),
Chris Liechticb6ce1b2016-02-02 01:53:56 +0100453 }
cliechti2b929b72009-08-02 23:49:02 +0000454 self._rfc2217_options.update(self._rfc2217_port_settings)
455 # cache for line and modem states that the server sends to us
cliechti8099bed2009-08-01 23:59:18 +0000456 self._linestate = 0
cliechti7cb78e82009-08-05 15:47:57 +0000457 self._modemstate = None
458 self._modemstate_expires = 0
cliechti044d8662009-08-11 21:40:31 +0000459 # RFC 2217 flow control between server and client
cliechti672d0292009-08-03 02:01:57 +0000460 self._remote_suspend_flow = False
cliechti8099bed2009-08-01 23:59:18 +0000461
Chris Liechti39d52172015-10-25 22:53:51 +0100462 self.is_open = True
Chris Liechti277220e2016-02-18 23:27:52 +0100463 self._thread = threading.Thread(target=self._telnet_read_loop)
cliechti8099bed2009-08-01 23:59:18 +0000464 self._thread.setDaemon(True)
Chris Liechti1f6643d2016-02-16 21:06:52 +0100465 self._thread.setName('pySerial RFC 2217 reader thread for {}'.format(self._port))
cliechti8099bed2009-08-01 23:59:18 +0000466 self._thread.start()
467
Chris Liechti39d52172015-10-25 22:53:51 +0100468 try: # must clean-up if open fails
469 # negotiate Telnet/RFC 2217 -> send initial requests
470 for option in self._telnet_options:
471 if option.state is REQUESTED:
Chris Liechti277220e2016-02-18 23:27:52 +0100472 self.telnet_send_option(option.send_yes, option.option)
Chris Liechti39d52172015-10-25 22:53:51 +0100473 # now wait until important options are negotiated
474 timeout_time = time.time() + self._network_timeout
475 while time.time() < timeout_time:
476 time.sleep(0.05) # prevent 100% CPU load
477 if sum(o.active for o in mandadory_options) == sum(o.state != INACTIVE for o in mandadory_options):
478 break
479 else:
Chris Liechti920917b2016-02-17 18:26:16 +0100480 raise SerialException(
481 "Remote does not seem to support RFC2217 or BINARY mode {!r}".format(mandadory_options))
Chris Liechti39d52172015-10-25 22:53:51 +0100482 if self.logger:
Chris Liechti1f6643d2016-02-16 21:06:52 +0100483 self.logger.info("Negotiated options: {}".format(self._telnet_options))
cliechti8099bed2009-08-01 23:59:18 +0000484
Chris Liechti39d52172015-10-25 22:53:51 +0100485 # fine, go on, set RFC 2271 specific things
486 self._reconfigure_port()
487 # all things set up get, now a clean start
488 if not self._dsrdtr:
489 self._update_dtr_state()
490 if not self._rtscts:
491 self._update_rts_state()
492 self.reset_input_buffer()
493 self.reset_output_buffer()
494 except:
495 self.close()
496 raise
cliechti8099bed2009-08-01 23:59:18 +0000497
Chris Liechti3ad62fb2015-08-29 21:53:32 +0200498 def _reconfigure_port(self):
cliechti8099bed2009-08-01 23:59:18 +0000499 """Set communication parameters on opened port."""
500 if self._socket is None:
501 raise SerialException("Can only operate on open ports")
502
cliechti8099bed2009-08-01 23:59:18 +0000503 # if self._timeout != 0 and self._interCharTimeout is not None:
cliechti8099bed2009-08-01 23:59:18 +0000504 # XXX
505
Chris Liechti3ad62fb2015-08-29 21:53:32 +0200506 if self._write_timeout is not None:
507 raise NotImplementedError('write_timeout is currently not supported')
cliechti2b929b72009-08-02 23:49:02 +0000508 # XXX
cliechti8099bed2009-08-01 23:59:18 +0000509
cliechti2b929b72009-08-02 23:49:02 +0000510 # Setup the connection
cliechti1ef7e3e2009-08-03 02:38:43 +0000511 # to get good performance, all parameter changes are sent first...
Chris Liechtiba45c522016-02-06 23:53:23 +0100512 if not 0 < self._baudrate < 2 ** 32:
Chris Liechti1f6643d2016-02-16 21:06:52 +0100513 raise ValueError("invalid baudrate: {!r}".format(self._baudrate))
Chris Liechti01587b12015-08-05 02:39:32 +0200514 self._rfc2217_port_settings['baudrate'].set(struct.pack(b'!I', self._baudrate))
515 self._rfc2217_port_settings['datasize'].set(struct.pack(b'!B', self._bytesize))
516 self._rfc2217_port_settings['parity'].set(struct.pack(b'!B', RFC2217_PARITY_MAP[self._parity]))
517 self._rfc2217_port_settings['stopsize'].set(struct.pack(b'!B', RFC2217_STOPBIT_MAP[self._stopbits]))
cliechti8099bed2009-08-01 23:59:18 +0000518
cliechti2b929b72009-08-02 23:49:02 +0000519 # and now wait until parameters are active
520 items = self._rfc2217_port_settings.values()
cliechti6a300772009-08-12 02:28:56 +0000521 if self.logger:
Chris Liechti1f6643d2016-02-16 21:06:52 +0100522 self.logger.debug("Negotiating settings: {}".format(items))
cliechtidfe2d272009-08-10 22:19:41 +0000523 timeout_time = time.time() + self._network_timeout
cliechti2b929b72009-08-02 23:49:02 +0000524 while time.time() < timeout_time:
525 time.sleep(0.05) # prevent 100% CPU load
526 if sum(o.active for o in items) == len(items):
527 break
528 else:
Chris Liechti1f6643d2016-02-16 21:06:52 +0100529 raise SerialException("Remote does not accept parameter change (RFC2217): {!r}".format(items))
cliechti6a300772009-08-12 02:28:56 +0000530 if self.logger:
Chris Liechti1f6643d2016-02-16 21:06:52 +0100531 self.logger.info("Negotiated settings: {}".format(items))
cliechti8099bed2009-08-01 23:59:18 +0000532
533 if self._rtscts and self._xonxoff:
cliechti1ef7e3e2009-08-03 02:38:43 +0000534 raise ValueError('xonxoff and rtscts together are not supported')
cliechti8099bed2009-08-01 23:59:18 +0000535 elif self._rtscts:
Chris Liechti277220e2016-02-18 23:27:52 +0100536 self.rfc2217_set_control(SET_CONTROL_USE_HW_FLOW_CONTROL)
cliechti8099bed2009-08-01 23:59:18 +0000537 elif self._xonxoff:
Chris Liechti277220e2016-02-18 23:27:52 +0100538 self.rfc2217_set_control(SET_CONTROL_USE_SW_FLOW_CONTROL)
cliechti8099bed2009-08-01 23:59:18 +0000539 else:
Chris Liechti277220e2016-02-18 23:27:52 +0100540 self.rfc2217_set_control(SET_CONTROL_USE_NO_FLOW_CONTROL)
cliechti8099bed2009-08-01 23:59:18 +0000541
542 def close(self):
543 """Close port"""
Chris Liechti39d52172015-10-25 22:53:51 +0100544 self.is_open = False
545 if self._socket:
546 try:
547 self._socket.shutdown(socket.SHUT_RDWR)
548 self._socket.close()
549 except:
550 # ignore errors.
551 pass
552 if self._thread:
553 self._thread.join(7) # XXX more than socket timeout
554 self._thread = None
cliechti8099bed2009-08-01 23:59:18 +0000555 # in case of quick reconnects, give the server some time
556 time.sleep(0.3)
Chris Liechti39d52172015-10-25 22:53:51 +0100557 self._socket = None
cliechti8099bed2009-08-01 23:59:18 +0000558
Chris Liechti3ad62fb2015-08-29 21:53:32 +0200559 def from_url(self, url):
Chris Liechti920917b2016-02-17 18:26:16 +0100560 """\
561 extract host and port from an URL string, other settings are extracted
562 an stored in instance
563 """
Chris Liechtia4222112015-08-07 01:03:12 +0200564 parts = urlparse.urlsplit(url)
Chris Liechtid14b1ab2015-08-21 00:28:53 +0200565 if parts.scheme != "rfc2217":
Chris Liechti1f6643d2016-02-16 21:06:52 +0100566 raise SerialException(
567 'expected a string in the form '
568 '"rfc2217://<host>:<port>[?option[&option...]]": '
569 'not starting with rfc2217:// ({!r})'.format(parts.scheme))
cliechti8099bed2009-08-01 23:59:18 +0000570 try:
Chris Liechtia4222112015-08-07 01:03:12 +0200571 # process options now, directly altering self
Chris Liechtid14b1ab2015-08-21 00:28:53 +0200572 for option, values in urlparse.parse_qs(parts.query, True).items():
573 if option == 'logging':
Chris Liechtia4222112015-08-07 01:03:12 +0200574 logging.basicConfig() # XXX is that good to call it here?
575 self.logger = logging.getLogger('pySerial.rfc2217')
Chris Liechtid14b1ab2015-08-21 00:28:53 +0200576 self.logger.setLevel(LOGGER_LEVELS[values[0]])
Chris Liechtia4222112015-08-07 01:03:12 +0200577 self.logger.debug('enabled logging')
578 elif option == 'ign_set_control':
579 self._ignore_set_control_answer = True
580 elif option == 'poll_modem':
581 self._poll_modem_state = True
582 elif option == 'timeout':
Chris Liechtid14b1ab2015-08-21 00:28:53 +0200583 self._network_timeout = float(values[0])
Chris Liechtia4222112015-08-07 01:03:12 +0200584 else:
Chris Liechti1f6643d2016-02-16 21:06:52 +0100585 raise ValueError('unknown option: {!r}'.format(option))
Chris Liechti4daa9d52016-03-22 00:32:01 +0100586 if not 0 <= parts.port < 65536:
Chris Liechti033f17c2015-08-30 21:28:04 +0200587 raise ValueError("port not in range 0...65535")
Chris Liechti68340d72015-08-03 14:15:48 +0200588 except ValueError as e:
Chris Liechti1f6643d2016-02-16 21:06:52 +0100589 raise SerialException(
590 'expected a string in the form '
591 '"rfc2217://<host>:<port>[?option[&option...]]": {}'.format(e))
Chris Liechti4daa9d52016-03-22 00:32:01 +0100592 return (parts.hostname, parts.port)
cliechti8099bed2009-08-01 23:59:18 +0000593
594 # - - - - - - - - - - - - - - - - - - - - - - - -
595
Chris Liechtief1fe252015-08-27 23:25:21 +0200596 @property
597 def in_waiting(self):
Chris Liechti3ad62fb2015-08-29 21:53:32 +0200598 """Return the number of bytes currently in the input buffer."""
Chris Liechti033f17c2015-08-30 21:28:04 +0200599 if not self.is_open:
600 raise portNotOpenError
cliechti8099bed2009-08-01 23:59:18 +0000601 return self._read_buffer.qsize()
602
603 def read(self, size=1):
cliechtieada4fd2013-07-31 16:26:07 +0000604 """\
605 Read size bytes from the serial port. If a timeout is set it may
cliechti8099bed2009-08-01 23:59:18 +0000606 return less characters as requested. With no timeout it will block
cliechtieada4fd2013-07-31 16:26:07 +0000607 until the requested number of bytes is read.
608 """
Chris Liechti033f17c2015-08-30 21:28:04 +0200609 if not self.is_open:
610 raise portNotOpenError
cliechti8099bed2009-08-01 23:59:18 +0000611 data = bytearray()
612 try:
613 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 Liechti01587b12015-08-05 02:39:32 +0200616 data += self._read_buffer.get(True, self._timeout)
Chris Liechti033f17c2015-08-30 21:28:04 +0200617 except Queue.Empty: # -> timeout
cliechti8099bed2009-08-01 23:59:18 +0000618 pass
619 return bytes(data)
620
621 def write(self, data):
cliechtieada4fd2013-07-31 16:26:07 +0000622 """\
Chris Liechti3ad62fb2015-08-29 21:53:32 +0200623 Output the given byte string over the serial port. Can block if the
cliechti8099bed2009-08-01 23:59:18 +0000624 connection is blocked. May raise SerialException if the connection is
cliechtieada4fd2013-07-31 16:26:07 +0000625 closed.
626 """
Chris Liechti033f17c2015-08-30 21:28:04 +0200627 if not self.is_open:
628 raise portNotOpenError
Chris Liechti01587b12015-08-05 02:39:32 +0200629 with self._write_lock:
cliechti81c54762009-08-03 23:53:27 +0000630 try:
cliechti38077122013-10-16 02:57:27 +0000631 self._socket.sendall(to_bytes(data).replace(IAC, IAC_DOUBLED))
Chris Liechti68340d72015-08-03 14:15:48 +0200632 except socket.error as e:
Chris Liechti1f6643d2016-02-16 21:06:52 +0100633 raise SerialException("connection failed (socket error): {}".format(e))
cliechti8099bed2009-08-01 23:59:18 +0000634 return len(data)
635
Chris Liechtief1fe252015-08-27 23:25:21 +0200636 def reset_input_buffer(self):
cliechti8099bed2009-08-01 23:59:18 +0000637 """Clear input buffer, discarding all that is in the buffer."""
Chris Liechti033f17c2015-08-30 21:28:04 +0200638 if not self.is_open:
639 raise portNotOpenError
Chris Liechti277220e2016-02-18 23:27:52 +0100640 self.rfc2217_send_purge(PURGE_RECEIVE_BUFFER)
cliechti8099bed2009-08-01 23:59:18 +0000641 # empty read buffer
642 while self._read_buffer.qsize():
643 self._read_buffer.get(False)
644
Chris Liechtief1fe252015-08-27 23:25:21 +0200645 def reset_output_buffer(self):
cliechtieada4fd2013-07-31 16:26:07 +0000646 """\
647 Clear output buffer, aborting the current output and
648 discarding all that is in the buffer.
649 """
Chris Liechti033f17c2015-08-30 21:28:04 +0200650 if not self.is_open:
651 raise portNotOpenError
Chris Liechti277220e2016-02-18 23:27:52 +0100652 self.rfc2217_send_purge(PURGE_TRANSMIT_BUFFER)
cliechti8099bed2009-08-01 23:59:18 +0000653
Chris Liechtief1fe252015-08-27 23:25:21 +0200654 def _update_break_state(self):
cliechtieada4fd2013-07-31 16:26:07 +0000655 """\
656 Set break: Controls TXD. When active, to transmitting is
657 possible.
658 """
Chris Liechti033f17c2015-08-30 21:28:04 +0200659 if not self.is_open:
660 raise portNotOpenError
cliechti6a300772009-08-12 02:28:56 +0000661 if self.logger:
Chris Liechti1f6643d2016-02-16 21:06:52 +0100662 self.logger.info('set BREAK to {}'.format('active' if self._break_state else 'inactive'))
Chris Liechtief1fe252015-08-27 23:25:21 +0200663 if self._break_state:
Chris Liechti277220e2016-02-18 23:27:52 +0100664 self.rfc2217_set_control(SET_CONTROL_BREAK_ON)
cliechti8099bed2009-08-01 23:59:18 +0000665 else:
Chris Liechti277220e2016-02-18 23:27:52 +0100666 self.rfc2217_set_control(SET_CONTROL_BREAK_OFF)
cliechti8099bed2009-08-01 23:59:18 +0000667
Chris Liechtief1fe252015-08-27 23:25:21 +0200668 def _update_rts_state(self):
cliechti044d8662009-08-11 21:40:31 +0000669 """Set terminal status line: Request To Send."""
Chris Liechti033f17c2015-08-30 21:28:04 +0200670 if not self.is_open:
671 raise portNotOpenError
cliechti6a300772009-08-12 02:28:56 +0000672 if self.logger:
Chris Liechti1f6643d2016-02-16 21:06:52 +0100673 self.logger.info('set RTS to {}'.format('active' if self._rts_state else 'inactive'))
Chris Liechtief1fe252015-08-27 23:25:21 +0200674 if self._rts_state:
Chris Liechti277220e2016-02-18 23:27:52 +0100675 self.rfc2217_set_control(SET_CONTROL_RTS_ON)
cliechti8099bed2009-08-01 23:59:18 +0000676 else:
Chris Liechti277220e2016-02-18 23:27:52 +0100677 self.rfc2217_set_control(SET_CONTROL_RTS_OFF)
cliechti8099bed2009-08-01 23:59:18 +0000678
Chris Liechti920917b2016-02-17 18:26:16 +0100679 def _update_dtr_state(self):
cliechti044d8662009-08-11 21:40:31 +0000680 """Set terminal status line: Data Terminal Ready."""
Chris Liechti033f17c2015-08-30 21:28:04 +0200681 if not self.is_open:
682 raise portNotOpenError
cliechti6a300772009-08-12 02:28:56 +0000683 if self.logger:
Chris Liechti1f6643d2016-02-16 21:06:52 +0100684 self.logger.info('set DTR to {}'.format('active' if self._dtr_state else 'inactive'))
Chris Liechtief1fe252015-08-27 23:25:21 +0200685 if self._dtr_state:
Chris Liechti277220e2016-02-18 23:27:52 +0100686 self.rfc2217_set_control(SET_CONTROL_DTR_ON)
cliechti8099bed2009-08-01 23:59:18 +0000687 else:
Chris Liechti277220e2016-02-18 23:27:52 +0100688 self.rfc2217_set_control(SET_CONTROL_DTR_OFF)
cliechti8099bed2009-08-01 23:59:18 +0000689
Chris Liechtief1fe252015-08-27 23:25:21 +0200690 @property
691 def cts(self):
cliechti044d8662009-08-11 21:40:31 +0000692 """Read terminal status line: Clear To Send."""
Chris Liechti033f17c2015-08-30 21:28:04 +0200693 if not self.is_open:
694 raise portNotOpenError
Chris Liechti277220e2016-02-18 23:27:52 +0100695 return bool(self.get_modem_state() & MODEMSTATE_MASK_CTS)
cliechti8099bed2009-08-01 23:59:18 +0000696
Chris Liechtief1fe252015-08-27 23:25:21 +0200697 @property
698 def dsr(self):
cliechti044d8662009-08-11 21:40:31 +0000699 """Read terminal status line: Data Set Ready."""
Chris Liechti033f17c2015-08-30 21:28:04 +0200700 if not self.is_open:
701 raise portNotOpenError
Chris Liechti277220e2016-02-18 23:27:52 +0100702 return bool(self.get_modem_state() & MODEMSTATE_MASK_DSR)
cliechti8099bed2009-08-01 23:59:18 +0000703
Chris Liechtief1fe252015-08-27 23:25:21 +0200704 @property
705 def ri(self):
cliechti044d8662009-08-11 21:40:31 +0000706 """Read terminal status line: Ring Indicator."""
Chris Liechti033f17c2015-08-30 21:28:04 +0200707 if not self.is_open:
708 raise portNotOpenError
Chris Liechti277220e2016-02-18 23:27:52 +0100709 return bool(self.get_modem_state() & MODEMSTATE_MASK_RI)
cliechti8099bed2009-08-01 23:59:18 +0000710
Chris Liechtief1fe252015-08-27 23:25:21 +0200711 @property
712 def cd(self):
cliechti044d8662009-08-11 21:40:31 +0000713 """Read terminal status line: Carrier Detect."""
Chris Liechti033f17c2015-08-30 21:28:04 +0200714 if not self.is_open:
715 raise portNotOpenError
Chris Liechti277220e2016-02-18 23:27:52 +0100716 return bool(self.get_modem_state() & MODEMSTATE_MASK_CD)
cliechti8099bed2009-08-01 23:59:18 +0000717
718 # - - - platform specific - - -
719 # None so far
720
721 # - - - RFC2217 specific - - -
722
Chris Liechti277220e2016-02-18 23:27:52 +0100723 def _telnet_read_loop(self):
cliechti7d448562014-08-03 21:57:45 +0000724 """Read loop for the socket."""
cliechti8099bed2009-08-01 23:59:18 +0000725 mode = M_NORMAL
726 suboption = None
cliechti81c54762009-08-03 23:53:27 +0000727 try:
Chris Liechti39d52172015-10-25 22:53:51 +0100728 while self.is_open:
cliechti81c54762009-08-03 23:53:27 +0000729 try:
730 data = self._socket.recv(1024)
731 except socket.timeout:
732 # just need to get out of recv form time to time to check if
733 # still alive
734 continue
Chris Liechti68340d72015-08-03 14:15:48 +0200735 except socket.error as e:
cliechti81c54762009-08-03 23:53:27 +0000736 # connection fails -> terminate loop
cliechticb20a4f2011-04-25 02:25:54 +0000737 if self.logger:
Chris Liechti1f6643d2016-02-16 21:06:52 +0100738 self.logger.debug("socket error in reader thread: {}".format(e))
cliechti81c54762009-08-03 23:53:27 +0000739 break
Chris Liechti033f17c2015-08-30 21:28:04 +0200740 if not data:
741 break # lost connection
Chris Liechtif99cd5c2015-08-13 22:54:16 +0200742 for byte in iterbytes(data):
cliechti81c54762009-08-03 23:53:27 +0000743 if mode == M_NORMAL:
744 # interpret as command or as data
745 if byte == IAC:
746 mode = M_IAC_SEEN
cliechti8099bed2009-08-01 23:59:18 +0000747 else:
cliechti81c54762009-08-03 23:53:27 +0000748 # store data in read buffer or sub option buffer
749 # depending on state
750 if suboption is not None:
Chris Liechti01587b12015-08-05 02:39:32 +0200751 suboption += byte
cliechti81c54762009-08-03 23:53:27 +0000752 else:
753 self._read_buffer.put(byte)
754 elif mode == M_IAC_SEEN:
755 if byte == IAC:
756 # interpret as command doubled -> insert character
757 # itself
cliechtif325c032009-12-25 16:09:49 +0000758 if suboption is not None:
Chris Liechti01587b12015-08-05 02:39:32 +0200759 suboption += IAC
cliechtif325c032009-12-25 16:09:49 +0000760 else:
761 self._read_buffer.put(IAC)
cliechti81c54762009-08-03 23:53:27 +0000762 mode = M_NORMAL
763 elif byte == SB:
764 # sub option start
765 suboption = bytearray()
766 mode = M_NORMAL
767 elif byte == SE:
768 # sub option end -> process it now
Chris Liechti277220e2016-02-18 23:27:52 +0100769 self._telnet_process_subnegotiation(bytes(suboption))
cliechti81c54762009-08-03 23:53:27 +0000770 suboption = None
771 mode = M_NORMAL
772 elif byte in (DO, DONT, WILL, WONT):
773 # negotiation
774 telnet_command = byte
775 mode = M_NEGOTIATE
776 else:
777 # other telnet commands
Chris Liechti277220e2016-02-18 23:27:52 +0100778 self._telnet_process_command(byte)
cliechti81c54762009-08-03 23:53:27 +0000779 mode = M_NORMAL
Chris Liechti033f17c2015-08-30 21:28:04 +0200780 elif mode == M_NEGOTIATE: # DO, DONT, WILL, WONT was received, option now following
Chris Liechti277220e2016-02-18 23:27:52 +0100781 self._telnet_negotiate_option(telnet_command, byte)
cliechti8099bed2009-08-01 23:59:18 +0000782 mode = M_NORMAL
cliechti81c54762009-08-03 23:53:27 +0000783 finally:
784 self._thread = None
cliechti6a300772009-08-12 02:28:56 +0000785 if self.logger:
786 self.logger.debug("read thread terminated")
cliechti8099bed2009-08-01 23:59:18 +0000787
788 # - incoming telnet commands and options
789
Chris Liechti277220e2016-02-18 23:27:52 +0100790 def _telnet_process_command(self, command):
cliechti044d8662009-08-11 21:40:31 +0000791 """Process commands other than DO, DONT, WILL, WONT."""
cliechti1ef7e3e2009-08-03 02:38:43 +0000792 # Currently none. RFC2217 only uses negotiation and subnegotiation.
cliechti6a300772009-08-12 02:28:56 +0000793 if self.logger:
Chris Liechti1f6643d2016-02-16 21:06:52 +0100794 self.logger.warning("ignoring Telnet command: {!r}".format(command))
cliechti8099bed2009-08-01 23:59:18 +0000795
Chris Liechti277220e2016-02-18 23:27:52 +0100796 def _telnet_negotiate_option(self, command, option):
cliechti044d8662009-08-11 21:40:31 +0000797 """Process incoming DO, DONT, WILL, WONT."""
cliechti2b929b72009-08-02 23:49:02 +0000798 # check our registered telnet options and forward command to them
799 # they know themselves if they have to answer or not
cliechtiac205322009-08-02 20:40:21 +0000800 known = False
801 for item in self._telnet_options:
cliechti2b929b72009-08-02 23:49:02 +0000802 # can have more than one match! as some options are duplicated for
803 # 'us' and 'them'
cliechtiac205322009-08-02 20:40:21 +0000804 if item.option == option:
cliechti2b929b72009-08-02 23:49:02 +0000805 item.process_incoming(command)
cliechtiac205322009-08-02 20:40:21 +0000806 known = True
807 if not known:
808 # handle unknown options
809 # only answer to positive requests and deny them
810 if command == WILL or command == DO:
Chris Liechti277220e2016-02-18 23:27:52 +0100811 self.telnet_send_option((DONT if command == WILL else WONT), option)
cliechti6a300772009-08-12 02:28:56 +0000812 if self.logger:
Chris Liechti1f6643d2016-02-16 21:06:52 +0100813 self.logger.warning("rejected Telnet option: {!r}".format(option))
cliechtiac205322009-08-02 20:40:21 +0000814
Chris Liechti277220e2016-02-18 23:27:52 +0100815 def _telnet_process_subnegotiation(self, suboption):
cliechti044d8662009-08-11 21:40:31 +0000816 """Process subnegotiation, the data between IAC SB and IAC SE."""
cliechti8099bed2009-08-01 23:59:18 +0000817 if suboption[0:1] == COM_PORT_OPTION:
818 if suboption[1:2] == SERVER_NOTIFY_LINESTATE and len(suboption) >= 3:
Chris Liechti033f17c2015-08-30 21:28:04 +0200819 self._linestate = ord(suboption[2:3]) # ensure it is a number
cliechti6a300772009-08-12 02:28:56 +0000820 if self.logger:
Chris Liechti1f6643d2016-02-16 21:06:52 +0100821 self.logger.info("NOTIFY_LINESTATE: {}".format(self._linestate))
cliechti8099bed2009-08-01 23:59:18 +0000822 elif suboption[1:2] == SERVER_NOTIFY_MODEMSTATE and len(suboption) >= 3:
Chris Liechti033f17c2015-08-30 21:28:04 +0200823 self._modemstate = ord(suboption[2:3]) # ensure it is a number
cliechti6a300772009-08-12 02:28:56 +0000824 if self.logger:
Chris Liechti1f6643d2016-02-16 21:06:52 +0100825 self.logger.info("NOTIFY_MODEMSTATE: {}".format(self._modemstate))
cliechti7cb78e82009-08-05 15:47:57 +0000826 # update time when we think that a poll would make sense
827 self._modemstate_expires = time.time() + 0.3
cliechti672d0292009-08-03 02:01:57 +0000828 elif suboption[1:2] == FLOWCONTROL_SUSPEND:
829 self._remote_suspend_flow = True
830 elif suboption[1:2] == FLOWCONTROL_RESUME:
831 self._remote_suspend_flow = False
cliechti8099bed2009-08-01 23:59:18 +0000832 else:
cliechti2b929b72009-08-02 23:49:02 +0000833 for item in self._rfc2217_options.values():
834 if item.ack_option == suboption[1:2]:
cliechti81c54762009-08-03 23:53:27 +0000835 #~ print "processing COM_PORT_OPTION: %r" % list(suboption[1:])
Chris Liechti277220e2016-02-18 23:27:52 +0100836 item.check_answer(bytes(suboption[2:]))
cliechti2b929b72009-08-02 23:49:02 +0000837 break
838 else:
cliechti6a300772009-08-12 02:28:56 +0000839 if self.logger:
Chris Liechti1f6643d2016-02-16 21:06:52 +0100840 self.logger.warning("ignoring COM_PORT_OPTION: {!r}".format(suboption))
cliechti8099bed2009-08-01 23:59:18 +0000841 else:
cliechti6a300772009-08-12 02:28:56 +0000842 if self.logger:
Chris Liechti1f6643d2016-02-16 21:06:52 +0100843 self.logger.warning("ignoring subnegotiation: {!r}".format(suboption))
cliechti8099bed2009-08-01 23:59:18 +0000844
845 # - outgoing telnet commands and options
846
cliechti81c54762009-08-03 23:53:27 +0000847 def _internal_raw_write(self, data):
cliechti044d8662009-08-11 21:40:31 +0000848 """internal socket write with no data escaping. used to send telnet stuff."""
Chris Liechti01587b12015-08-05 02:39:32 +0200849 with self._write_lock:
cliechti81c54762009-08-03 23:53:27 +0000850 self._socket.sendall(data)
cliechti81c54762009-08-03 23:53:27 +0000851
Chris Liechti277220e2016-02-18 23:27:52 +0100852 def telnet_send_option(self, action, option):
cliechti044d8662009-08-11 21:40:31 +0000853 """Send DO, DONT, WILL, WONT."""
cliechti81c54762009-08-03 23:53:27 +0000854 self._internal_raw_write(to_bytes([IAC, action, option]))
cliechti8099bed2009-08-01 23:59:18 +0000855
Chris Liechti277220e2016-02-18 23:27:52 +0100856 def rfc2217_send_subnegotiation(self, option, value=b''):
cliechti044d8662009-08-11 21:40:31 +0000857 """Subnegotiation of RFC2217 parameters."""
cliechtif325c032009-12-25 16:09:49 +0000858 value = value.replace(IAC, IAC_DOUBLED)
cliechti81c54762009-08-03 23:53:27 +0000859 self._internal_raw_write(to_bytes([IAC, SB, COM_PORT_OPTION, option] + list(value) + [IAC, SE]))
cliechti2b929b72009-08-02 23:49:02 +0000860
Chris Liechti277220e2016-02-18 23:27:52 +0100861 def rfc2217_send_purge(self, value):
862 """\
863 Send purge request to the remote.
864 (PURGE_RECEIVE_BUFFER / PURGE_TRANSMIT_BUFFER / PURGE_BOTH_BUFFERS)
865 """
cliechti2b929b72009-08-02 23:49:02 +0000866 item = self._rfc2217_options['purge']
Chris Liechti033f17c2015-08-30 21:28:04 +0200867 item.set(value) # transmit desired purge type
868 item.wait(self._network_timeout) # wait for acknowledge from the server
cliechti2b929b72009-08-02 23:49:02 +0000869
Chris Liechti277220e2016-02-18 23:27:52 +0100870 def rfc2217_set_control(self, value):
871 """transmit change of control line to remote"""
cliechti81c54762009-08-03 23:53:27 +0000872 item = self._rfc2217_options['control']
Chris Liechti033f17c2015-08-30 21:28:04 +0200873 item.set(value) # transmit desired control type
cliechti81c54762009-08-03 23:53:27 +0000874 if self._ignore_set_control_answer:
875 # answers are ignored when option is set. compatibility mode for
cliechticb20a4f2011-04-25 02:25:54 +0000876 # servers that answer, but not the expected one... (or no answer
cliechti81c54762009-08-03 23:53:27 +0000877 # at all) i.e. sredird
878 time.sleep(0.1) # this helps getting the unit tests passed
879 else:
cliechtidfe2d272009-08-10 22:19:41 +0000880 item.wait(self._network_timeout) # wait for acknowledge from the server
cliechti8099bed2009-08-01 23:59:18 +0000881
Chris Liechti277220e2016-02-18 23:27:52 +0100882 def rfc2217_flow_server_ready(self):
cliechtieada4fd2013-07-31 16:26:07 +0000883 """\
884 check if server is ready to receive data. block for some time when
885 not.
886 """
cliechti672d0292009-08-03 02:01:57 +0000887 #~ if self._remote_suspend_flow:
Chris Liechtiba45c522016-02-06 23:53:23 +0100888 #~ wait---
cliechti672d0292009-08-03 02:01:57 +0000889
Chris Liechti277220e2016-02-18 23:27:52 +0100890 def get_modem_state(self):
cliechtieada4fd2013-07-31 16:26:07 +0000891 """\
cliechti7d448562014-08-03 21:57:45 +0000892 get last modem state (cached value. If value is "old", request a new
893 one. This cache helps that we don't issue to many requests when e.g. all
894 status lines, one after the other is queried by the user (getCTS, getDSR
cliechtieada4fd2013-07-31 16:26:07 +0000895 etc.)
896 """
cliechti7cb78e82009-08-05 15:47:57 +0000897 # active modem state polling enabled? is the value fresh enough?
898 if self._poll_modem_state and self._modemstate_expires < time.time():
cliechti6a300772009-08-12 02:28:56 +0000899 if self.logger:
900 self.logger.debug('polling modem state')
cliechti7cb78e82009-08-05 15:47:57 +0000901 # when it is older, request an update
Chris Liechti277220e2016-02-18 23:27:52 +0100902 self.rfc2217_send_subnegotiation(NOTIFY_MODEMSTATE)
cliechtidfe2d272009-08-10 22:19:41 +0000903 timeout_time = time.time() + self._network_timeout
cliechti7cb78e82009-08-05 15:47:57 +0000904 while time.time() < timeout_time:
905 time.sleep(0.05) # prevent 100% CPU load
906 # when expiration time is updated, it means that there is a new
907 # value
908 if self._modemstate_expires > time.time():
909 break
Chris Liechti01587b12015-08-05 02:39:32 +0200910 else:
911 if self.logger:
912 self.logger.warning('poll for modem state failed')
cliechti7cb78e82009-08-05 15:47:57 +0000913 # even when there is a timeout, do not generate an error just
914 # return the last known value. this way we can support buggy
915 # servers that do not respond to polls, but send automatic
916 # updates.
917 if self._modemstate is not None:
cliechti6a300772009-08-12 02:28:56 +0000918 if self.logger:
919 self.logger.debug('using cached modem state')
cliechti7cb78e82009-08-05 15:47:57 +0000920 return self._modemstate
921 else:
922 # never received a notification from the server
cliechti8fb119c2009-08-05 23:39:45 +0000923 raise SerialException("remote sends no NOTIFY_MODEMSTATE")
cliechti8099bed2009-08-01 23:59:18 +0000924
cliechti5cc3eb12009-08-11 23:04:30 +0000925
cliechti595ed5b2009-08-10 01:43:32 +0000926#############################################################################
cliechti5cc3eb12009-08-11 23:04:30 +0000927# The following is code that helps implementing an RFC 2217 server.
cliechti8099bed2009-08-01 23:59:18 +0000928
cliechti8ccc2ff2009-08-05 12:44:46 +0000929class PortManager(object):
cliechtieada4fd2013-07-31 16:26:07 +0000930 """\
931 This class manages the state of Telnet and RFC 2217. It needs a serial
cliechticb20a4f2011-04-25 02:25:54 +0000932 instance and a connection to work with. Connection is expected to implement
cliechtieada4fd2013-07-31 16:26:07 +0000933 a (thread safe) write function, that writes the string to the network.
934 """
cliechti130d1f02009-08-04 02:10:58 +0000935
cliechti6a300772009-08-12 02:28:56 +0000936 def __init__(self, serial_port, connection, logger=None):
cliechti130d1f02009-08-04 02:10:58 +0000937 self.serial = serial_port
938 self.connection = connection
cliechti6a300772009-08-12 02:28:56 +0000939 self.logger = logger
cliechti86b593e2009-08-05 16:28:12 +0000940 self._client_is_rfc2217 = False
cliechti130d1f02009-08-04 02:10:58 +0000941
942 # filter state machine
943 self.mode = M_NORMAL
944 self.suboption = None
945 self.telnet_command = None
946
947 # states for modem/line control events
948 self.modemstate_mask = 255
949 self.last_modemstate = None
950 self.linstate_mask = 0
951
952 # all supported telnet options
953 self._telnet_options = [
954 TelnetOption(self, 'ECHO', ECHO, WILL, WONT, DO, DONT, REQUESTED),
955 TelnetOption(self, 'we-SGA', SGA, WILL, WONT, DO, DONT, REQUESTED),
956 TelnetOption(self, 'they-SGA', SGA, DO, DONT, WILL, WONT, INACTIVE),
957 TelnetOption(self, 'we-BINARY', BINARY, WILL, WONT, DO, DONT, INACTIVE),
958 TelnetOption(self, 'they-BINARY', BINARY, DO, DONT, WILL, WONT, REQUESTED),
cliechti86b593e2009-08-05 16:28:12 +0000959 TelnetOption(self, 'we-RFC2217', COM_PORT_OPTION, WILL, WONT, DO, DONT, REQUESTED, self._client_ok),
960 TelnetOption(self, 'they-RFC2217', COM_PORT_OPTION, DO, DONT, WILL, WONT, INACTIVE, self._client_ok),
Chris Liechticb6ce1b2016-02-02 01:53:56 +0100961 ]
cliechti130d1f02009-08-04 02:10:58 +0000962
963 # negotiate Telnet/RFC2217 -> send initial requests
cliechti6a300772009-08-12 02:28:56 +0000964 if self.logger:
965 self.logger.debug("requesting initial Telnet/RFC 2217 options")
cliechti130d1f02009-08-04 02:10:58 +0000966 for option in self._telnet_options:
967 if option.state is REQUESTED:
Chris Liechti277220e2016-02-18 23:27:52 +0100968 self.telnet_send_option(option.send_yes, option.option)
cliechti130d1f02009-08-04 02:10:58 +0000969 # issue 1st modem state notification
cliechti86b593e2009-08-05 16:28:12 +0000970
971 def _client_ok(self):
cliechtieada4fd2013-07-31 16:26:07 +0000972 """\
cliechti7d448562014-08-03 21:57:45 +0000973 callback of telnet option. It gets called when option is activated.
974 This one here is used to detect when the client agrees on RFC 2217. A
cliechti86b593e2009-08-05 16:28:12 +0000975 flag is set so that other functions like check_modem_lines know if the
cliechti7d448562014-08-03 21:57:45 +0000976 client is OK.
cliechtieada4fd2013-07-31 16:26:07 +0000977 """
cliechti86b593e2009-08-05 16:28:12 +0000978 # The callback is used for we and they so if one party agrees, we're
979 # already happy. it seems not all servers do the negotiation correctly
980 # and i guess there are incorrect clients too.. so be happy if client
981 # answers one or the other positively.
982 self._client_is_rfc2217 = True
cliechti6a300772009-08-12 02:28:56 +0000983 if self.logger:
984 self.logger.info("client accepts RFC 2217")
cliechti8fb119c2009-08-05 23:39:45 +0000985 # this is to ensure that the client gets a notification, even if there
986 # was no change
987 self.check_modem_lines(force_notification=True)
cliechti130d1f02009-08-04 02:10:58 +0000988
989 # - outgoing telnet commands and options
990
Chris Liechti277220e2016-02-18 23:27:52 +0100991 def telnet_send_option(self, action, option):
cliechti044d8662009-08-11 21:40:31 +0000992 """Send DO, DONT, WILL, WONT."""
cliechti130d1f02009-08-04 02:10:58 +0000993 self.connection.write(to_bytes([IAC, action, option]))
994
Chris Liechti277220e2016-02-18 23:27:52 +0100995 def rfc2217_send_subnegotiation(self, option, value=b''):
cliechti044d8662009-08-11 21:40:31 +0000996 """Subnegotiation of RFC 2217 parameters."""
cliechtif325c032009-12-25 16:09:49 +0000997 value = value.replace(IAC, IAC_DOUBLED)
cliechti130d1f02009-08-04 02:10:58 +0000998 self.connection.write(to_bytes([IAC, SB, COM_PORT_OPTION, option] + list(value) + [IAC, SE]))
999
1000 # - check modem lines, needs to be called periodically from user to
1001 # establish polling
1002
cliechti7cb78e82009-08-05 15:47:57 +00001003 def check_modem_lines(self, force_notification=False):
Chris Liechti277220e2016-02-18 23:27:52 +01001004 """\
1005 read control lines from serial port and compare the last value sent to remote.
1006 send updates on changes.
1007 """
cliechti130d1f02009-08-04 02:10:58 +00001008 modemstate = (
Chris Liechtiba45c522016-02-06 23:53:23 +01001009 (self.serial.getCTS() and MODEMSTATE_MASK_CTS) |
1010 (self.serial.getDSR() and MODEMSTATE_MASK_DSR) |
1011 (self.serial.getRI() and MODEMSTATE_MASK_RI) |
1012 (self.serial.getCD() and MODEMSTATE_MASK_CD))
cliechti7cb78e82009-08-05 15:47:57 +00001013 # check what has changed
Chris Liechti033f17c2015-08-30 21:28:04 +02001014 deltas = modemstate ^ (self.last_modemstate or 0) # when last is None -> 0
cliechti7cb78e82009-08-05 15:47:57 +00001015 if deltas & MODEMSTATE_MASK_CTS:
1016 modemstate |= MODEMSTATE_MASK_CTS_CHANGE
1017 if deltas & MODEMSTATE_MASK_DSR:
1018 modemstate |= MODEMSTATE_MASK_DSR_CHANGE
1019 if deltas & MODEMSTATE_MASK_RI:
1020 modemstate |= MODEMSTATE_MASK_RI_CHANGE
1021 if deltas & MODEMSTATE_MASK_CD:
1022 modemstate |= MODEMSTATE_MASK_CD_CHANGE
1023 # if new state is different and the mask allows this change, send
cliechti86b593e2009-08-05 16:28:12 +00001024 # notification. suppress notifications when client is not rfc2217
cliechti7cb78e82009-08-05 15:47:57 +00001025 if modemstate != self.last_modemstate or force_notification:
cliechti8fb119c2009-08-05 23:39:45 +00001026 if (self._client_is_rfc2217 and (modemstate & self.modemstate_mask)) or force_notification:
Chris Liechti277220e2016-02-18 23:27:52 +01001027 self.rfc2217_send_subnegotiation(
Chris Liechti920917b2016-02-17 18:26:16 +01001028 SERVER_NOTIFY_MODEMSTATE,
1029 to_bytes([modemstate & self.modemstate_mask]))
cliechti6a300772009-08-12 02:28:56 +00001030 if self.logger:
Chris Liechti1f6643d2016-02-16 21:06:52 +01001031 self.logger.info("NOTIFY_MODEMSTATE: {}".format(modemstate))
cliechti7cb78e82009-08-05 15:47:57 +00001032 # save last state, but forget about deltas.
1033 # otherwise it would also notify about changing deltas which is
1034 # probably not very useful
1035 self.last_modemstate = modemstate & 0xf0
cliechti130d1f02009-08-04 02:10:58 +00001036
cliechti32c10332009-08-05 13:23:43 +00001037 # - outgoing data escaping
1038
1039 def escape(self, data):
cliechtieada4fd2013-07-31 16:26:07 +00001040 """\
cliechti7d448562014-08-03 21:57:45 +00001041 This generator function is for the user. All outgoing data has to be
cliechticb20a4f2011-04-25 02:25:54 +00001042 properly escaped, so that no IAC character in the data stream messes up
1043 the Telnet state machine in the server.
cliechti32c10332009-08-05 13:23:43 +00001044
1045 socket.sendall(escape(data))
1046 """
Chris Liechtib15dc052015-10-19 23:17:16 +02001047 for byte in iterbytes(data):
cliechti32c10332009-08-05 13:23:43 +00001048 if byte == IAC:
1049 yield IAC
1050 yield IAC
1051 else:
1052 yield byte
1053
cliechti130d1f02009-08-04 02:10:58 +00001054 # - incoming data filter
1055
1056 def filter(self, data):
cliechtieada4fd2013-07-31 16:26:07 +00001057 """\
cliechti7d448562014-08-03 21:57:45 +00001058 Handle a bunch of incoming bytes. This is a generator. It will yield
cliechti044d8662009-08-11 21:40:31 +00001059 all characters not of interest for Telnet/RFC 2217.
cliechti130d1f02009-08-04 02:10:58 +00001060
1061 The idea is that the reader thread pushes data from the socket through
1062 this filter:
1063
1064 for byte in filter(socket.recv(1024)):
1065 # do things like CR/LF conversion/whatever
1066 # and write data to the serial port
1067 serial.write(byte)
1068
1069 (socket error handling code left as exercise for the reader)
1070 """
Chris Liechtif99cd5c2015-08-13 22:54:16 +02001071 for byte in iterbytes(data):
cliechti130d1f02009-08-04 02:10:58 +00001072 if self.mode == M_NORMAL:
1073 # interpret as command or as data
1074 if byte == IAC:
1075 self.mode = M_IAC_SEEN
1076 else:
1077 # store data in sub option buffer or pass it to our
1078 # consumer depending on state
1079 if self.suboption is not None:
Chris Liechti01587b12015-08-05 02:39:32 +02001080 self.suboption += byte
cliechti130d1f02009-08-04 02:10:58 +00001081 else:
1082 yield byte
1083 elif self.mode == M_IAC_SEEN:
1084 if byte == IAC:
1085 # interpret as command doubled -> insert character
1086 # itself
cliechtif325c032009-12-25 16:09:49 +00001087 if self.suboption is not None:
Chris Liechti01587b12015-08-05 02:39:32 +02001088 self.suboption += byte
cliechtif325c032009-12-25 16:09:49 +00001089 else:
1090 yield byte
cliechti130d1f02009-08-04 02:10:58 +00001091 self.mode = M_NORMAL
1092 elif byte == SB:
1093 # sub option start
1094 self.suboption = bytearray()
1095 self.mode = M_NORMAL
1096 elif byte == SE:
1097 # sub option end -> process it now
Chris Liechti277220e2016-02-18 23:27:52 +01001098 self._telnet_process_subnegotiation(bytes(self.suboption))
cliechti130d1f02009-08-04 02:10:58 +00001099 self.suboption = None
1100 self.mode = M_NORMAL
1101 elif byte in (DO, DONT, WILL, WONT):
1102 # negotiation
1103 self.telnet_command = byte
1104 self.mode = M_NEGOTIATE
1105 else:
1106 # other telnet commands
Chris Liechti277220e2016-02-18 23:27:52 +01001107 self._telnet_process_command(byte)
cliechti130d1f02009-08-04 02:10:58 +00001108 self.mode = M_NORMAL
Chris Liechti033f17c2015-08-30 21:28:04 +02001109 elif self.mode == M_NEGOTIATE: # DO, DONT, WILL, WONT was received, option now following
Chris Liechti277220e2016-02-18 23:27:52 +01001110 self._telnet_negotiate_option(self.telnet_command, byte)
cliechti130d1f02009-08-04 02:10:58 +00001111 self.mode = M_NORMAL
1112
1113 # - incoming telnet commands and options
1114
Chris Liechti277220e2016-02-18 23:27:52 +01001115 def _telnet_process_command(self, command):
cliechti044d8662009-08-11 21:40:31 +00001116 """Process commands other than DO, DONT, WILL, WONT."""
cliechti130d1f02009-08-04 02:10:58 +00001117 # Currently none. RFC2217 only uses negotiation and subnegotiation.
cliechti6a300772009-08-12 02:28:56 +00001118 if self.logger:
Chris Liechti1f6643d2016-02-16 21:06:52 +01001119 self.logger.warning("ignoring Telnet command: {!r}".format(command))
cliechti130d1f02009-08-04 02:10:58 +00001120
Chris Liechti277220e2016-02-18 23:27:52 +01001121 def _telnet_negotiate_option(self, command, option):
cliechti044d8662009-08-11 21:40:31 +00001122 """Process incoming DO, DONT, WILL, WONT."""
cliechti130d1f02009-08-04 02:10:58 +00001123 # check our registered telnet options and forward command to them
1124 # they know themselves if they have to answer or not
1125 known = False
1126 for item in self._telnet_options:
1127 # can have more than one match! as some options are duplicated for
1128 # 'us' and 'them'
1129 if item.option == option:
1130 item.process_incoming(command)
1131 known = True
1132 if not known:
1133 # handle unknown options
1134 # only answer to positive requests and deny them
1135 if command == WILL or command == DO:
Chris Liechti277220e2016-02-18 23:27:52 +01001136 self.telnet_send_option((DONT if command == WILL else WONT), option)
cliechti6a300772009-08-12 02:28:56 +00001137 if self.logger:
Chris Liechti1f6643d2016-02-16 21:06:52 +01001138 self.logger.warning("rejected Telnet option: {!r}".format(option))
cliechti130d1f02009-08-04 02:10:58 +00001139
Chris Liechti277220e2016-02-18 23:27:52 +01001140 def _telnet_process_subnegotiation(self, suboption):
cliechti044d8662009-08-11 21:40:31 +00001141 """Process subnegotiation, the data between IAC SB and IAC SE."""
cliechti130d1f02009-08-04 02:10:58 +00001142 if suboption[0:1] == COM_PORT_OPTION:
cliechti6a300772009-08-12 02:28:56 +00001143 if self.logger:
Chris Liechti1f6643d2016-02-16 21:06:52 +01001144 self.logger.debug('received COM_PORT_OPTION: {!r}'.format(suboption))
cliechti130d1f02009-08-04 02:10:58 +00001145 if suboption[1:2] == SET_BAUDRATE:
1146 backup = self.serial.baudrate
1147 try:
Chris Liechti01587b12015-08-05 02:39:32 +02001148 (baudrate,) = struct.unpack(b"!I", suboption[2:6])
cliechtieada4fd2013-07-31 16:26:07 +00001149 if baudrate != 0:
1150 self.serial.baudrate = baudrate
Chris Liechtid2146002015-08-04 16:57:16 +02001151 except ValueError as e:
cliechti6a300772009-08-12 02:28:56 +00001152 if self.logger:
Chris Liechti1f6643d2016-02-16 21:06:52 +01001153 self.logger.error("failed to set baud rate: {}".format(e))
cliechti130d1f02009-08-04 02:10:58 +00001154 self.serial.baudrate = backup
cliechti5cc3eb12009-08-11 23:04:30 +00001155 else:
cliechti6a300772009-08-12 02:28:56 +00001156 if self.logger:
Chris Liechti1f6643d2016-02-16 21:06:52 +01001157 self.logger.info("{} baud rate: {}".format('set' if baudrate else 'get', self.serial.baudrate))
Chris Liechti277220e2016-02-18 23:27:52 +01001158 self.rfc2217_send_subnegotiation(SERVER_SET_BAUDRATE, struct.pack(b"!I", self.serial.baudrate))
cliechti130d1f02009-08-04 02:10:58 +00001159 elif suboption[1:2] == SET_DATASIZE:
1160 backup = self.serial.bytesize
1161 try:
Chris Liechti142ae562015-08-23 01:11:06 +02001162 (datasize,) = struct.unpack(b"!B", suboption[2:3])
cliechtieada4fd2013-07-31 16:26:07 +00001163 if datasize != 0:
1164 self.serial.bytesize = datasize
Chris Liechtid2146002015-08-04 16:57:16 +02001165 except ValueError as e:
cliechti6a300772009-08-12 02:28:56 +00001166 if self.logger:
Chris Liechti1f6643d2016-02-16 21:06:52 +01001167 self.logger.error("failed to set data size: {}".format(e))
cliechti130d1f02009-08-04 02:10:58 +00001168 self.serial.bytesize = backup
cliechti5cc3eb12009-08-11 23:04:30 +00001169 else:
cliechti6a300772009-08-12 02:28:56 +00001170 if self.logger:
Chris Liechti1f6643d2016-02-16 21:06:52 +01001171 self.logger.info("{} data size: {}".format('set' if datasize else 'get', self.serial.bytesize))
Chris Liechti277220e2016-02-18 23:27:52 +01001172 self.rfc2217_send_subnegotiation(SERVER_SET_DATASIZE, struct.pack(b"!B", self.serial.bytesize))
cliechti130d1f02009-08-04 02:10:58 +00001173 elif suboption[1:2] == SET_PARITY:
1174 backup = self.serial.parity
1175 try:
Chris Liechti01587b12015-08-05 02:39:32 +02001176 parity = struct.unpack(b"!B", suboption[2:3])[0]
cliechtieada4fd2013-07-31 16:26:07 +00001177 if parity != 0:
Chris Liechti920917b2016-02-17 18:26:16 +01001178 self.serial.parity = RFC2217_REVERSE_PARITY_MAP[parity]
Chris Liechtid2146002015-08-04 16:57:16 +02001179 except ValueError as e:
cliechti6a300772009-08-12 02:28:56 +00001180 if self.logger:
Chris Liechti1f6643d2016-02-16 21:06:52 +01001181 self.logger.error("failed to set parity: {}".format(e))
cliechti130d1f02009-08-04 02:10:58 +00001182 self.serial.parity = backup
cliechti5cc3eb12009-08-11 23:04:30 +00001183 else:
cliechti6a300772009-08-12 02:28:56 +00001184 if self.logger:
Chris Liechti1f6643d2016-02-16 21:06:52 +01001185 self.logger.info("{} parity: {}".format('set' if parity else 'get', self.serial.parity))
Chris Liechti277220e2016-02-18 23:27:52 +01001186 self.rfc2217_send_subnegotiation(
Chris Liechti920917b2016-02-17 18:26:16 +01001187 SERVER_SET_PARITY,
1188 struct.pack(b"!B", RFC2217_PARITY_MAP[self.serial.parity]))
cliechti130d1f02009-08-04 02:10:58 +00001189 elif suboption[1:2] == SET_STOPSIZE:
1190 backup = self.serial.stopbits
1191 try:
Chris Liechti01587b12015-08-05 02:39:32 +02001192 stopbits = struct.unpack(b"!B", suboption[2:3])[0]
cliechtieada4fd2013-07-31 16:26:07 +00001193 if stopbits != 0:
1194 self.serial.stopbits = RFC2217_REVERSE_STOPBIT_MAP[stopbits]
Chris Liechtid2146002015-08-04 16:57:16 +02001195 except ValueError as e:
cliechti6a300772009-08-12 02:28:56 +00001196 if self.logger:
Chris Liechti1f6643d2016-02-16 21:06:52 +01001197 self.logger.error("failed to set stop bits: {}".format(e))
cliechti130d1f02009-08-04 02:10:58 +00001198 self.serial.stopbits = backup
cliechti5cc3eb12009-08-11 23:04:30 +00001199 else:
cliechti6a300772009-08-12 02:28:56 +00001200 if self.logger:
Chris Liechti1f6643d2016-02-16 21:06:52 +01001201 self.logger.info("{} stop bits: {}".format('set' if stopbits else 'get', self.serial.stopbits))
Chris Liechti277220e2016-02-18 23:27:52 +01001202 self.rfc2217_send_subnegotiation(
Chris Liechti920917b2016-02-17 18:26:16 +01001203 SERVER_SET_STOPSIZE,
1204 struct.pack(b"!B", RFC2217_STOPBIT_MAP[self.serial.stopbits]))
cliechti130d1f02009-08-04 02:10:58 +00001205 elif suboption[1:2] == SET_CONTROL:
1206 if suboption[2:3] == SET_CONTROL_REQ_FLOW_SETTING:
1207 if self.serial.xonxoff:
Chris Liechti277220e2016-02-18 23:27:52 +01001208 self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_SW_FLOW_CONTROL)
cliechti130d1f02009-08-04 02:10:58 +00001209 elif self.serial.rtscts:
Chris Liechti277220e2016-02-18 23:27:52 +01001210 self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_HW_FLOW_CONTROL)
cliechti130d1f02009-08-04 02:10:58 +00001211 else:
Chris Liechti277220e2016-02-18 23:27:52 +01001212 self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_NO_FLOW_CONTROL)
cliechti130d1f02009-08-04 02:10:58 +00001213 elif suboption[2:3] == SET_CONTROL_USE_NO_FLOW_CONTROL:
1214 self.serial.xonxoff = False
1215 self.serial.rtscts = False
cliechti6a300772009-08-12 02:28:56 +00001216 if self.logger:
1217 self.logger.info("changed flow control to None")
Chris Liechti277220e2016-02-18 23:27:52 +01001218 self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_NO_FLOW_CONTROL)
cliechti130d1f02009-08-04 02:10:58 +00001219 elif suboption[2:3] == SET_CONTROL_USE_SW_FLOW_CONTROL:
1220 self.serial.xonxoff = True
cliechti6a300772009-08-12 02:28:56 +00001221 if self.logger:
1222 self.logger.info("changed flow control to XON/XOFF")
Chris Liechti277220e2016-02-18 23:27:52 +01001223 self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_SW_FLOW_CONTROL)
cliechti130d1f02009-08-04 02:10:58 +00001224 elif suboption[2:3] == SET_CONTROL_USE_HW_FLOW_CONTROL:
1225 self.serial.rtscts = True
cliechti6a300772009-08-12 02:28:56 +00001226 if self.logger:
1227 self.logger.info("changed flow control to RTS/CTS")
Chris Liechti277220e2016-02-18 23:27:52 +01001228 self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_HW_FLOW_CONTROL)
cliechti130d1f02009-08-04 02:10:58 +00001229 elif suboption[2:3] == SET_CONTROL_REQ_BREAK_STATE:
cliechti6a300772009-08-12 02:28:56 +00001230 if self.logger:
1231 self.logger.warning("requested break state - not implemented")
Chris Liechti033f17c2015-08-30 21:28:04 +02001232 pass # XXX needs cached value
cliechti130d1f02009-08-04 02:10:58 +00001233 elif suboption[2:3] == SET_CONTROL_BREAK_ON:
1234 self.serial.setBreak(True)
cliechti6a300772009-08-12 02:28:56 +00001235 if self.logger:
1236 self.logger.info("changed BREAK to active")
Chris Liechti277220e2016-02-18 23:27:52 +01001237 self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_BREAK_ON)
cliechti130d1f02009-08-04 02:10:58 +00001238 elif suboption[2:3] == SET_CONTROL_BREAK_OFF:
1239 self.serial.setBreak(False)
cliechti6a300772009-08-12 02:28:56 +00001240 if self.logger:
1241 self.logger.info("changed BREAK to inactive")
Chris Liechti277220e2016-02-18 23:27:52 +01001242 self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_BREAK_OFF)
cliechti130d1f02009-08-04 02:10:58 +00001243 elif suboption[2:3] == SET_CONTROL_REQ_DTR:
cliechti6a300772009-08-12 02:28:56 +00001244 if self.logger:
1245 self.logger.warning("requested DTR state - not implemented")
Chris Liechti033f17c2015-08-30 21:28:04 +02001246 pass # XXX needs cached value
cliechti130d1f02009-08-04 02:10:58 +00001247 elif suboption[2:3] == SET_CONTROL_DTR_ON:
1248 self.serial.setDTR(True)
cliechti6a300772009-08-12 02:28:56 +00001249 if self.logger:
1250 self.logger.info("changed DTR to active")
Chris Liechti277220e2016-02-18 23:27:52 +01001251 self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_DTR_ON)
cliechti130d1f02009-08-04 02:10:58 +00001252 elif suboption[2:3] == SET_CONTROL_DTR_OFF:
1253 self.serial.setDTR(False)
cliechti6a300772009-08-12 02:28:56 +00001254 if self.logger:
1255 self.logger.info("changed DTR to inactive")
Chris Liechti277220e2016-02-18 23:27:52 +01001256 self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_DTR_OFF)
cliechti130d1f02009-08-04 02:10:58 +00001257 elif suboption[2:3] == SET_CONTROL_REQ_RTS:
cliechti6a300772009-08-12 02:28:56 +00001258 if self.logger:
1259 self.logger.warning("requested RTS state - not implemented")
Chris Liechti033f17c2015-08-30 21:28:04 +02001260 pass # XXX needs cached value
Chris Liechti277220e2016-02-18 23:27:52 +01001261 #~ self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_RTS_ON)
cliechti130d1f02009-08-04 02:10:58 +00001262 elif suboption[2:3] == SET_CONTROL_RTS_ON:
1263 self.serial.setRTS(True)
cliechti6a300772009-08-12 02:28:56 +00001264 if self.logger:
1265 self.logger.info("changed RTS to active")
Chris Liechti277220e2016-02-18 23:27:52 +01001266 self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_RTS_ON)
cliechti130d1f02009-08-04 02:10:58 +00001267 elif suboption[2:3] == SET_CONTROL_RTS_OFF:
1268 self.serial.setRTS(False)
cliechti6a300772009-08-12 02:28:56 +00001269 if self.logger:
1270 self.logger.info("changed RTS to inactive")
Chris Liechti277220e2016-02-18 23:27:52 +01001271 self.rfc2217_send_subnegotiation(SERVER_SET_CONTROL, SET_CONTROL_RTS_OFF)
cliechti130d1f02009-08-04 02:10:58 +00001272 #~ elif suboption[2:3] == SET_CONTROL_REQ_FLOW_SETTING_IN:
1273 #~ elif suboption[2:3] == SET_CONTROL_USE_NO_FLOW_CONTROL_IN:
1274 #~ elif suboption[2:3] == SET_CONTROL_USE_SW_FLOW_CONTOL_IN:
1275 #~ elif suboption[2:3] == SET_CONTROL_USE_HW_FLOW_CONTOL_IN:
1276 #~ elif suboption[2:3] == SET_CONTROL_USE_DCD_FLOW_CONTROL:
1277 #~ elif suboption[2:3] == SET_CONTROL_USE_DTR_FLOW_CONTROL:
1278 #~ elif suboption[2:3] == SET_CONTROL_USE_DSR_FLOW_CONTROL:
1279 elif suboption[1:2] == NOTIFY_LINESTATE:
cliechti7cb78e82009-08-05 15:47:57 +00001280 # client polls for current state
Chris Liechti277220e2016-02-18 23:27:52 +01001281 self.rfc2217_send_subnegotiation(
Chris Liechti920917b2016-02-17 18:26:16 +01001282 SERVER_NOTIFY_LINESTATE,
1283 to_bytes([0])) # sorry, nothing like that implemented
cliechti130d1f02009-08-04 02:10:58 +00001284 elif suboption[1:2] == NOTIFY_MODEMSTATE:
cliechti6a300772009-08-12 02:28:56 +00001285 if self.logger:
1286 self.logger.info("request for modem state")
cliechti7cb78e82009-08-05 15:47:57 +00001287 # client polls for current state
1288 self.check_modem_lines(force_notification=True)
cliechti130d1f02009-08-04 02:10:58 +00001289 elif suboption[1:2] == FLOWCONTROL_SUSPEND:
cliechti6a300772009-08-12 02:28:56 +00001290 if self.logger:
1291 self.logger.info("suspend")
cliechti130d1f02009-08-04 02:10:58 +00001292 self._remote_suspend_flow = True
1293 elif suboption[1:2] == FLOWCONTROL_RESUME:
cliechti6a300772009-08-12 02:28:56 +00001294 if self.logger:
1295 self.logger.info("resume")
cliechti130d1f02009-08-04 02:10:58 +00001296 self._remote_suspend_flow = False
1297 elif suboption[1:2] == SET_LINESTATE_MASK:
Chris Liechti033f17c2015-08-30 21:28:04 +02001298 self.linstate_mask = ord(suboption[2:3]) # ensure it is a number
cliechti6a300772009-08-12 02:28:56 +00001299 if self.logger:
Chris Liechti1f6643d2016-02-16 21:06:52 +01001300 self.logger.info("line state mask: 0x{:02x}".format(self.linstate_mask))
cliechti130d1f02009-08-04 02:10:58 +00001301 elif suboption[1:2] == SET_MODEMSTATE_MASK:
Chris Liechti033f17c2015-08-30 21:28:04 +02001302 self.modemstate_mask = ord(suboption[2:3]) # ensure it is a number
cliechti6a300772009-08-12 02:28:56 +00001303 if self.logger:
Chris Liechti1f6643d2016-02-16 21:06:52 +01001304 self.logger.info("modem state mask: 0x{:02x}".format(self.modemstate_mask))
cliechti130d1f02009-08-04 02:10:58 +00001305 elif suboption[1:2] == PURGE_DATA:
1306 if suboption[2:3] == PURGE_RECEIVE_BUFFER:
Chris Liechti3ad62fb2015-08-29 21:53:32 +02001307 self.serial.reset_input_buffer()
cliechti6a300772009-08-12 02:28:56 +00001308 if self.logger:
1309 self.logger.info("purge in")
Chris Liechti277220e2016-02-18 23:27:52 +01001310 self.rfc2217_send_subnegotiation(SERVER_PURGE_DATA, PURGE_RECEIVE_BUFFER)
cliechti130d1f02009-08-04 02:10:58 +00001311 elif suboption[2:3] == PURGE_TRANSMIT_BUFFER:
Chris Liechti3ad62fb2015-08-29 21:53:32 +02001312 self.serial.reset_output_buffer()
cliechti6a300772009-08-12 02:28:56 +00001313 if self.logger:
1314 self.logger.info("purge out")
Chris Liechti277220e2016-02-18 23:27:52 +01001315 self.rfc2217_send_subnegotiation(SERVER_PURGE_DATA, PURGE_TRANSMIT_BUFFER)
cliechti130d1f02009-08-04 02:10:58 +00001316 elif suboption[2:3] == PURGE_BOTH_BUFFERS:
Chris Liechti3ad62fb2015-08-29 21:53:32 +02001317 self.serial.reset_input_buffer()
1318 self.serial.reset_output_buffer()
cliechti6a300772009-08-12 02:28:56 +00001319 if self.logger:
1320 self.logger.info("purge both")
Chris Liechti277220e2016-02-18 23:27:52 +01001321 self.rfc2217_send_subnegotiation(SERVER_PURGE_DATA, PURGE_BOTH_BUFFERS)
cliechti130d1f02009-08-04 02:10:58 +00001322 else:
cliechti6a300772009-08-12 02:28:56 +00001323 if self.logger:
Chris Liechti1f6643d2016-02-16 21:06:52 +01001324 self.logger.error("undefined PURGE_DATA: {!r}".format(list(suboption[2:])))
cliechti130d1f02009-08-04 02:10:58 +00001325 else:
cliechti6a300772009-08-12 02:28:56 +00001326 if self.logger:
Chris Liechti1f6643d2016-02-16 21:06:52 +01001327 self.logger.error("undefined COM_PORT_OPTION: {!r}".format(list(suboption[1:])))
cliechti130d1f02009-08-04 02:10:58 +00001328 else:
cliechti6a300772009-08-12 02:28:56 +00001329 if self.logger:
Chris Liechti1f6643d2016-02-16 21:06:52 +01001330 self.logger.warning("unknown subnegotiation: {!r}".format(suboption))
cliechti130d1f02009-08-04 02:10:58 +00001331
1332
1333# simple client test
cliechti8099bed2009-08-01 23:59:18 +00001334if __name__ == '__main__':
1335 import sys
1336 s = Serial('rfc2217://localhost:7000', 115200)
Chris Liechti1f6643d2016-02-16 21:06:52 +01001337 sys.stdout.write('{}\n'.format(s))
cliechti8099bed2009-08-01 23:59:18 +00001338
cliechti8099bed2009-08-01 23:59:18 +00001339 sys.stdout.write("write...\n")
Chris Liechtib4cda3a2015-08-08 17:12:08 +02001340 s.write(b"hello\n")
cliechti8099bed2009-08-01 23:59:18 +00001341 s.flush()
Chris Liechti1f6643d2016-02-16 21:06:52 +01001342 sys.stdout.write("read: {}\n".format(s.read(5)))
cliechti8099bed2009-08-01 23:59:18 +00001343 s.close()