blob: a9b285a979ce0967a32b5b7ed9922e0909296011 [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
cliechti1ef7e3e2009-08-03 02:38:43 +0000280 self.connection.telnetSendOption(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:
cliechti1ef7e3e2009-08-03 02:38:43 +0000285 self.connection.telnetSendOption(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
cliechti1ef7e3e2009-08-03 02:38:43 +0000294 self.connection.telnetSendOption(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
cliechti1ef7e3e2009-08-03 02:38:43 +0000332 self.connection.rfc2217SendSubnegotiation(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
336 def isReady(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
cliechti2b929b72009-08-02 23:49:02 +0000345 active = property(isReady)
346
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
356 if self.isReady():
357 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
361 def checkAnswer(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:
369 # error propagation done in isReady
370 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
385
cliechti8099bed2009-08-01 23:59:18 +0000386 def open(self):
cliechtieada4fd2013-07-31 16:26:07 +0000387 """\
388 Open port with current settings. This may throw a SerialException
389 if the port cannot be opened.
390 """
cliechti6a300772009-08-12 02:28:56 +0000391 self.logger = None
cliechti81c54762009-08-03 23:53:27 +0000392 self._ignore_set_control_answer = False
cliechti7cb78e82009-08-05 15:47:57 +0000393 self._poll_modem_state = False
cliechtidfe2d272009-08-10 22:19:41 +0000394 self._network_timeout = 3
cliechti8099bed2009-08-01 23:59:18 +0000395 if self._port is None:
396 raise SerialException("Port must be configured before it can be used.")
Chris Liechti3ad62fb2015-08-29 21:53:32 +0200397 if self.is_open:
cliechti8f69e702011-03-19 00:22:32 +0000398 raise SerialException("Port is already open.")
cliechti8099bed2009-08-01 23:59:18 +0000399 try:
Chris Liechti3ad62fb2015-08-29 21:53:32 +0200400 self._socket = socket.create_connection(self.from_url(self.portstr))
cliechti6a300772009-08-12 02:28:56 +0000401 self._socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
Chris Liechti68340d72015-08-03 14:15:48 +0200402 except Exception as msg:
cliechti8099bed2009-08-01 23:59:18 +0000403 self._socket = None
Chris Liechti1f6643d2016-02-16 21:06:52 +0100404 raise SerialException("Could not open port {}: {}".format(self.portstr, msg))
cliechti8099bed2009-08-01 23:59:18 +0000405
Chris Liechti033f17c2015-08-30 21:28:04 +0200406 self._socket.settimeout(5) # XXX good value?
cliechti8099bed2009-08-01 23:59:18 +0000407
cliechti1ef7e3e2009-08-03 02:38:43 +0000408 # use a thread save queue as buffer. it also simplifies implementing
409 # the read timeout
cliechti8099bed2009-08-01 23:59:18 +0000410 self._read_buffer = Queue.Queue()
cliechti81c54762009-08-03 23:53:27 +0000411 # to ensure that user writes does not interfere with internal
412 # telnet/rfc2217 options establish a lock
413 self._write_lock = threading.Lock()
cliechtiac205322009-08-02 20:40:21 +0000414 # name the following separately so that, below, a check can be easily done
415 mandadory_options = [
cliechti2b929b72009-08-02 23:49:02 +0000416 TelnetOption(self, 'we-BINARY', BINARY, WILL, WONT, DO, DONT, INACTIVE),
cliechti2b929b72009-08-02 23:49:02 +0000417 TelnetOption(self, 'we-RFC2217', COM_PORT_OPTION, WILL, WONT, DO, DONT, REQUESTED),
cliechtiac205322009-08-02 20:40:21 +0000418 ]
419 # all supported telnet options
420 self._telnet_options = [
cliechtia29275e2009-08-03 00:08:04 +0000421 TelnetOption(self, 'ECHO', ECHO, DO, DONT, WILL, WONT, REQUESTED),
cliechti2b929b72009-08-02 23:49:02 +0000422 TelnetOption(self, 'we-SGA', SGA, WILL, WONT, DO, DONT, REQUESTED),
423 TelnetOption(self, 'they-SGA', SGA, DO, DONT, WILL, WONT, REQUESTED),
cliechti81c54762009-08-03 23:53:27 +0000424 TelnetOption(self, 'they-BINARY', BINARY, DO, DONT, WILL, WONT, INACTIVE),
425 TelnetOption(self, 'they-RFC2217', COM_PORT_OPTION, DO, DONT, WILL, WONT, REQUESTED),
cliechtiac205322009-08-02 20:40:21 +0000426 ] + mandadory_options
cliechti044d8662009-08-11 21:40:31 +0000427 # RFC 2217 specific states
cliechti2b929b72009-08-02 23:49:02 +0000428 # COM port settings
429 self._rfc2217_port_settings = {
430 'baudrate': TelnetSubnegotiation(self, 'baudrate', SET_BAUDRATE, SERVER_SET_BAUDRATE),
431 'datasize': TelnetSubnegotiation(self, 'datasize', SET_DATASIZE, SERVER_SET_DATASIZE),
432 'parity': TelnetSubnegotiation(self, 'parity', SET_PARITY, SERVER_SET_PARITY),
433 'stopsize': TelnetSubnegotiation(self, 'stopsize', SET_STOPSIZE, SERVER_SET_STOPSIZE),
Chris Liechticb6ce1b2016-02-02 01:53:56 +0100434 }
cliechticb20a4f2011-04-25 02:25:54 +0000435 # There are more subnegotiation objects, combine all in one dictionary
cliechti2b929b72009-08-02 23:49:02 +0000436 # for easy access
437 self._rfc2217_options = {
438 'purge': TelnetSubnegotiation(self, 'purge', PURGE_DATA, SERVER_PURGE_DATA),
cliechti81c54762009-08-03 23:53:27 +0000439 'control': TelnetSubnegotiation(self, 'control', SET_CONTROL, SERVER_SET_CONTROL),
Chris Liechticb6ce1b2016-02-02 01:53:56 +0100440 }
cliechti2b929b72009-08-02 23:49:02 +0000441 self._rfc2217_options.update(self._rfc2217_port_settings)
442 # cache for line and modem states that the server sends to us
cliechti8099bed2009-08-01 23:59:18 +0000443 self._linestate = 0
cliechti7cb78e82009-08-05 15:47:57 +0000444 self._modemstate = None
445 self._modemstate_expires = 0
cliechti044d8662009-08-11 21:40:31 +0000446 # RFC 2217 flow control between server and client
cliechti672d0292009-08-03 02:01:57 +0000447 self._remote_suspend_flow = False
cliechti8099bed2009-08-01 23:59:18 +0000448
Chris Liechti39d52172015-10-25 22:53:51 +0100449 self.is_open = True
cliechti1ef7e3e2009-08-03 02:38:43 +0000450 self._thread = threading.Thread(target=self._telnetReadLoop)
cliechti8099bed2009-08-01 23:59:18 +0000451 self._thread.setDaemon(True)
Chris Liechti1f6643d2016-02-16 21:06:52 +0100452 self._thread.setName('pySerial RFC 2217 reader thread for {}'.format(self._port))
cliechti8099bed2009-08-01 23:59:18 +0000453 self._thread.start()
454
Chris Liechti39d52172015-10-25 22:53:51 +0100455 try: # must clean-up if open fails
456 # negotiate Telnet/RFC 2217 -> send initial requests
457 for option in self._telnet_options:
458 if option.state is REQUESTED:
459 self.telnetSendOption(option.send_yes, option.option)
460 # now wait until important options are negotiated
461 timeout_time = time.time() + self._network_timeout
462 while time.time() < timeout_time:
463 time.sleep(0.05) # prevent 100% CPU load
464 if sum(o.active for o in mandadory_options) == sum(o.state != INACTIVE for o in mandadory_options):
465 break
466 else:
Chris Liechti1f6643d2016-02-16 21:06:52 +0100467 raise SerialException("Remote does not seem to support RFC2217 or BINARY mode {!r}".format(mandadory_options))
Chris Liechti39d52172015-10-25 22:53:51 +0100468 if self.logger:
Chris Liechti1f6643d2016-02-16 21:06:52 +0100469 self.logger.info("Negotiated options: {}".format(self._telnet_options))
cliechti8099bed2009-08-01 23:59:18 +0000470
Chris Liechti39d52172015-10-25 22:53:51 +0100471 # fine, go on, set RFC 2271 specific things
472 self._reconfigure_port()
473 # all things set up get, now a clean start
474 if not self._dsrdtr:
475 self._update_dtr_state()
476 if not self._rtscts:
477 self._update_rts_state()
478 self.reset_input_buffer()
479 self.reset_output_buffer()
480 except:
481 self.close()
482 raise
cliechti8099bed2009-08-01 23:59:18 +0000483
Chris Liechti3ad62fb2015-08-29 21:53:32 +0200484 def _reconfigure_port(self):
cliechti8099bed2009-08-01 23:59:18 +0000485 """Set communication parameters on opened port."""
486 if self._socket is None:
487 raise SerialException("Can only operate on open ports")
488
cliechti8099bed2009-08-01 23:59:18 +0000489 # if self._timeout != 0 and self._interCharTimeout is not None:
cliechti8099bed2009-08-01 23:59:18 +0000490 # XXX
491
Chris Liechti3ad62fb2015-08-29 21:53:32 +0200492 if self._write_timeout is not None:
493 raise NotImplementedError('write_timeout is currently not supported')
cliechti2b929b72009-08-02 23:49:02 +0000494 # XXX
cliechti8099bed2009-08-01 23:59:18 +0000495
cliechti2b929b72009-08-02 23:49:02 +0000496 # Setup the connection
cliechti1ef7e3e2009-08-03 02:38:43 +0000497 # to get good performance, all parameter changes are sent first...
Chris Liechtiba45c522016-02-06 23:53:23 +0100498 if not 0 < self._baudrate < 2 ** 32:
Chris Liechti1f6643d2016-02-16 21:06:52 +0100499 raise ValueError("invalid baudrate: {!r}".format(self._baudrate))
Chris Liechti01587b12015-08-05 02:39:32 +0200500 self._rfc2217_port_settings['baudrate'].set(struct.pack(b'!I', self._baudrate))
501 self._rfc2217_port_settings['datasize'].set(struct.pack(b'!B', self._bytesize))
502 self._rfc2217_port_settings['parity'].set(struct.pack(b'!B', RFC2217_PARITY_MAP[self._parity]))
503 self._rfc2217_port_settings['stopsize'].set(struct.pack(b'!B', RFC2217_STOPBIT_MAP[self._stopbits]))
cliechti8099bed2009-08-01 23:59:18 +0000504
cliechti2b929b72009-08-02 23:49:02 +0000505 # and now wait until parameters are active
506 items = self._rfc2217_port_settings.values()
cliechti6a300772009-08-12 02:28:56 +0000507 if self.logger:
Chris Liechti1f6643d2016-02-16 21:06:52 +0100508 self.logger.debug("Negotiating settings: {}".format(items))
cliechtidfe2d272009-08-10 22:19:41 +0000509 timeout_time = time.time() + self._network_timeout
cliechti2b929b72009-08-02 23:49:02 +0000510 while time.time() < timeout_time:
511 time.sleep(0.05) # prevent 100% CPU load
512 if sum(o.active for o in items) == len(items):
513 break
514 else:
Chris Liechti1f6643d2016-02-16 21:06:52 +0100515 raise SerialException("Remote does not accept parameter change (RFC2217): {!r}".format(items))
cliechti6a300772009-08-12 02:28:56 +0000516 if self.logger:
Chris Liechti1f6643d2016-02-16 21:06:52 +0100517 self.logger.info("Negotiated settings: {}".format(items))
cliechti8099bed2009-08-01 23:59:18 +0000518
519 if self._rtscts and self._xonxoff:
cliechti1ef7e3e2009-08-03 02:38:43 +0000520 raise ValueError('xonxoff and rtscts together are not supported')
cliechti8099bed2009-08-01 23:59:18 +0000521 elif self._rtscts:
cliechti1ef7e3e2009-08-03 02:38:43 +0000522 self.rfc2217SetControl(SET_CONTROL_USE_HW_FLOW_CONTROL)
cliechti8099bed2009-08-01 23:59:18 +0000523 elif self._xonxoff:
cliechti1ef7e3e2009-08-03 02:38:43 +0000524 self.rfc2217SetControl(SET_CONTROL_USE_SW_FLOW_CONTROL)
cliechti8099bed2009-08-01 23:59:18 +0000525 else:
cliechti1ef7e3e2009-08-03 02:38:43 +0000526 self.rfc2217SetControl(SET_CONTROL_USE_NO_FLOW_CONTROL)
cliechti8099bed2009-08-01 23:59:18 +0000527
528 def close(self):
529 """Close port"""
Chris Liechti39d52172015-10-25 22:53:51 +0100530 self.is_open = False
531 if self._socket:
532 try:
533 self._socket.shutdown(socket.SHUT_RDWR)
534 self._socket.close()
535 except:
536 # ignore errors.
537 pass
538 if self._thread:
539 self._thread.join(7) # XXX more than socket timeout
540 self._thread = None
cliechti8099bed2009-08-01 23:59:18 +0000541 # in case of quick reconnects, give the server some time
542 time.sleep(0.3)
Chris Liechti39d52172015-10-25 22:53:51 +0100543 self._socket = None
cliechti8099bed2009-08-01 23:59:18 +0000544
Chris Liechti3ad62fb2015-08-29 21:53:32 +0200545 def from_url(self, url):
cliechti1ef7e3e2009-08-03 02:38:43 +0000546 """extract host and port from an URL string"""
Chris Liechtia4222112015-08-07 01:03:12 +0200547 parts = urlparse.urlsplit(url)
Chris Liechtid14b1ab2015-08-21 00:28:53 +0200548 if parts.scheme != "rfc2217":
Chris Liechti1f6643d2016-02-16 21:06:52 +0100549 raise SerialException(
550 'expected a string in the form '
551 '"rfc2217://<host>:<port>[?option[&option...]]": '
552 'not starting with rfc2217:// ({!r})'.format(parts.scheme))
cliechti8099bed2009-08-01 23:59:18 +0000553 try:
Chris Liechtia4222112015-08-07 01:03:12 +0200554 # process options now, directly altering self
Chris Liechtid14b1ab2015-08-21 00:28:53 +0200555 for option, values in urlparse.parse_qs(parts.query, True).items():
556 if option == 'logging':
Chris Liechtia4222112015-08-07 01:03:12 +0200557 logging.basicConfig() # XXX is that good to call it here?
558 self.logger = logging.getLogger('pySerial.rfc2217')
Chris Liechtid14b1ab2015-08-21 00:28:53 +0200559 self.logger.setLevel(LOGGER_LEVELS[values[0]])
Chris Liechtia4222112015-08-07 01:03:12 +0200560 self.logger.debug('enabled logging')
561 elif option == 'ign_set_control':
562 self._ignore_set_control_answer = True
563 elif option == 'poll_modem':
564 self._poll_modem_state = True
565 elif option == 'timeout':
Chris Liechtid14b1ab2015-08-21 00:28:53 +0200566 self._network_timeout = float(values[0])
Chris Liechtia4222112015-08-07 01:03:12 +0200567 else:
Chris Liechti1f6643d2016-02-16 21:06:52 +0100568 raise ValueError('unknown option: {!r}'.format(option))
cliechti81c54762009-08-03 23:53:27 +0000569 # get host and port
Chris Liechtia4222112015-08-07 01:03:12 +0200570 host, port = parts.hostname, parts.port
Chris Liechti033f17c2015-08-30 21:28:04 +0200571 if not 0 <= port < 65536:
572 raise ValueError("port not in range 0...65535")
Chris Liechti68340d72015-08-03 14:15:48 +0200573 except ValueError as e:
Chris Liechti1f6643d2016-02-16 21:06:52 +0100574 raise SerialException(
575 'expected a string in the form '
576 '"rfc2217://<host>:<port>[?option[&option...]]": {}'.format(e))
cliechti8099bed2009-08-01 23:59:18 +0000577 return (host, port)
578
579 # - - - - - - - - - - - - - - - - - - - - - - - -
580
Chris Liechtief1fe252015-08-27 23:25:21 +0200581 @property
582 def in_waiting(self):
Chris Liechti3ad62fb2015-08-29 21:53:32 +0200583 """Return the number of bytes currently in the input buffer."""
Chris Liechti033f17c2015-08-30 21:28:04 +0200584 if not self.is_open:
585 raise portNotOpenError
cliechti8099bed2009-08-01 23:59:18 +0000586 return self._read_buffer.qsize()
587
588 def read(self, size=1):
cliechtieada4fd2013-07-31 16:26:07 +0000589 """\
590 Read size bytes from the serial port. If a timeout is set it may
cliechti8099bed2009-08-01 23:59:18 +0000591 return less characters as requested. With no timeout it will block
cliechtieada4fd2013-07-31 16:26:07 +0000592 until the requested number of bytes is read.
593 """
Chris Liechti033f17c2015-08-30 21:28:04 +0200594 if not self.is_open:
595 raise portNotOpenError
cliechti8099bed2009-08-01 23:59:18 +0000596 data = bytearray()
597 try:
598 while len(data) < size:
cliechti81c54762009-08-03 23:53:27 +0000599 if self._thread is None:
600 raise SerialException('connection failed (reader thread died)')
Chris Liechti01587b12015-08-05 02:39:32 +0200601 data += self._read_buffer.get(True, self._timeout)
Chris Liechti033f17c2015-08-30 21:28:04 +0200602 except Queue.Empty: # -> timeout
cliechti8099bed2009-08-01 23:59:18 +0000603 pass
604 return bytes(data)
605
606 def write(self, data):
cliechtieada4fd2013-07-31 16:26:07 +0000607 """\
Chris Liechti3ad62fb2015-08-29 21:53:32 +0200608 Output the given byte string over the serial port. Can block if the
cliechti8099bed2009-08-01 23:59:18 +0000609 connection is blocked. May raise SerialException if the connection is
cliechtieada4fd2013-07-31 16:26:07 +0000610 closed.
611 """
Chris Liechti033f17c2015-08-30 21:28:04 +0200612 if not self.is_open:
613 raise portNotOpenError
Chris Liechti01587b12015-08-05 02:39:32 +0200614 with self._write_lock:
cliechti81c54762009-08-03 23:53:27 +0000615 try:
cliechti38077122013-10-16 02:57:27 +0000616 self._socket.sendall(to_bytes(data).replace(IAC, IAC_DOUBLED))
Chris Liechti68340d72015-08-03 14:15:48 +0200617 except socket.error as e:
Chris Liechti1f6643d2016-02-16 21:06:52 +0100618 raise SerialException("connection failed (socket error): {}".format(e))
cliechti8099bed2009-08-01 23:59:18 +0000619 return len(data)
620
Chris Liechtief1fe252015-08-27 23:25:21 +0200621 def reset_input_buffer(self):
cliechti8099bed2009-08-01 23:59:18 +0000622 """Clear input buffer, discarding all that is in the buffer."""
Chris Liechti033f17c2015-08-30 21:28:04 +0200623 if not self.is_open:
624 raise portNotOpenError
cliechti1ef7e3e2009-08-03 02:38:43 +0000625 self.rfc2217SendPurge(PURGE_RECEIVE_BUFFER)
cliechti8099bed2009-08-01 23:59:18 +0000626 # empty read buffer
627 while self._read_buffer.qsize():
628 self._read_buffer.get(False)
629
Chris Liechtief1fe252015-08-27 23:25:21 +0200630 def reset_output_buffer(self):
cliechtieada4fd2013-07-31 16:26:07 +0000631 """\
632 Clear output buffer, aborting the current output and
633 discarding all that is in the buffer.
634 """
Chris Liechti033f17c2015-08-30 21:28:04 +0200635 if not self.is_open:
636 raise portNotOpenError
cliechti1ef7e3e2009-08-03 02:38:43 +0000637 self.rfc2217SendPurge(PURGE_TRANSMIT_BUFFER)
cliechti8099bed2009-08-01 23:59:18 +0000638
Chris Liechtief1fe252015-08-27 23:25:21 +0200639 def _update_break_state(self):
cliechtieada4fd2013-07-31 16:26:07 +0000640 """\
641 Set break: Controls TXD. When active, to transmitting is
642 possible.
643 """
Chris Liechti033f17c2015-08-30 21:28:04 +0200644 if not self.is_open:
645 raise portNotOpenError
cliechti6a300772009-08-12 02:28:56 +0000646 if self.logger:
Chris Liechti1f6643d2016-02-16 21:06:52 +0100647 self.logger.info('set BREAK to {}'.format('active' if self._break_state else 'inactive'))
Chris Liechtief1fe252015-08-27 23:25:21 +0200648 if self._break_state:
cliechti1ef7e3e2009-08-03 02:38:43 +0000649 self.rfc2217SetControl(SET_CONTROL_BREAK_ON)
cliechti8099bed2009-08-01 23:59:18 +0000650 else:
cliechti1ef7e3e2009-08-03 02:38:43 +0000651 self.rfc2217SetControl(SET_CONTROL_BREAK_OFF)
cliechti8099bed2009-08-01 23:59:18 +0000652
Chris Liechtief1fe252015-08-27 23:25:21 +0200653 def _update_rts_state(self):
cliechti044d8662009-08-11 21:40:31 +0000654 """Set terminal status line: Request To Send."""
Chris Liechti033f17c2015-08-30 21:28:04 +0200655 if not self.is_open:
656 raise portNotOpenError
cliechti6a300772009-08-12 02:28:56 +0000657 if self.logger:
Chris Liechti1f6643d2016-02-16 21:06:52 +0100658 self.logger.info('set RTS to {}'.format('active' if self._rts_state else 'inactive'))
Chris Liechtief1fe252015-08-27 23:25:21 +0200659 if self._rts_state:
cliechti1ef7e3e2009-08-03 02:38:43 +0000660 self.rfc2217SetControl(SET_CONTROL_RTS_ON)
cliechti8099bed2009-08-01 23:59:18 +0000661 else:
cliechti1ef7e3e2009-08-03 02:38:43 +0000662 self.rfc2217SetControl(SET_CONTROL_RTS_OFF)
cliechti8099bed2009-08-01 23:59:18 +0000663
Chris Liechtief1fe252015-08-27 23:25:21 +0200664 def _update_dtr_state(self, level=True):
cliechti044d8662009-08-11 21:40:31 +0000665 """Set terminal status line: Data Terminal Ready."""
Chris Liechti033f17c2015-08-30 21:28:04 +0200666 if not self.is_open:
667 raise portNotOpenError
cliechti6a300772009-08-12 02:28:56 +0000668 if self.logger:
Chris Liechti1f6643d2016-02-16 21:06:52 +0100669 self.logger.info('set DTR to {}'.format('active' if self._dtr_state else 'inactive'))
Chris Liechtief1fe252015-08-27 23:25:21 +0200670 if self._dtr_state:
cliechti1ef7e3e2009-08-03 02:38:43 +0000671 self.rfc2217SetControl(SET_CONTROL_DTR_ON)
cliechti8099bed2009-08-01 23:59:18 +0000672 else:
cliechti1ef7e3e2009-08-03 02:38:43 +0000673 self.rfc2217SetControl(SET_CONTROL_DTR_OFF)
cliechti8099bed2009-08-01 23:59:18 +0000674
Chris Liechtief1fe252015-08-27 23:25:21 +0200675 @property
676 def cts(self):
cliechti044d8662009-08-11 21:40:31 +0000677 """Read terminal status line: Clear To Send."""
Chris Liechti033f17c2015-08-30 21:28:04 +0200678 if not self.is_open:
679 raise portNotOpenError
cliechti7cb78e82009-08-05 15:47:57 +0000680 return bool(self.getModemState() & MODEMSTATE_MASK_CTS)
cliechti8099bed2009-08-01 23:59:18 +0000681
Chris Liechtief1fe252015-08-27 23:25:21 +0200682 @property
683 def dsr(self):
cliechti044d8662009-08-11 21:40:31 +0000684 """Read terminal status line: Data Set Ready."""
Chris Liechti033f17c2015-08-30 21:28:04 +0200685 if not self.is_open:
686 raise portNotOpenError
cliechti7cb78e82009-08-05 15:47:57 +0000687 return bool(self.getModemState() & MODEMSTATE_MASK_DSR)
cliechti8099bed2009-08-01 23:59:18 +0000688
Chris Liechtief1fe252015-08-27 23:25:21 +0200689 @property
690 def ri(self):
cliechti044d8662009-08-11 21:40:31 +0000691 """Read terminal status line: Ring Indicator."""
Chris Liechti033f17c2015-08-30 21:28:04 +0200692 if not self.is_open:
693 raise portNotOpenError
cliechti7cb78e82009-08-05 15:47:57 +0000694 return bool(self.getModemState() & MODEMSTATE_MASK_RI)
cliechti8099bed2009-08-01 23:59:18 +0000695
Chris Liechtief1fe252015-08-27 23:25:21 +0200696 @property
697 def cd(self):
cliechti044d8662009-08-11 21:40:31 +0000698 """Read terminal status line: Carrier Detect."""
Chris Liechti033f17c2015-08-30 21:28:04 +0200699 if not self.is_open:
700 raise portNotOpenError
cliechti7cb78e82009-08-05 15:47:57 +0000701 return bool(self.getModemState() & MODEMSTATE_MASK_CD)
cliechti8099bed2009-08-01 23:59:18 +0000702
703 # - - - platform specific - - -
704 # None so far
705
706 # - - - RFC2217 specific - - -
707
cliechti1ef7e3e2009-08-03 02:38:43 +0000708 def _telnetReadLoop(self):
cliechti7d448562014-08-03 21:57:45 +0000709 """Read loop for the socket."""
cliechti8099bed2009-08-01 23:59:18 +0000710 mode = M_NORMAL
711 suboption = None
cliechti81c54762009-08-03 23:53:27 +0000712 try:
Chris Liechti39d52172015-10-25 22:53:51 +0100713 while self.is_open:
cliechti81c54762009-08-03 23:53:27 +0000714 try:
715 data = self._socket.recv(1024)
716 except socket.timeout:
717 # just need to get out of recv form time to time to check if
718 # still alive
719 continue
Chris Liechti68340d72015-08-03 14:15:48 +0200720 except socket.error as e:
cliechti81c54762009-08-03 23:53:27 +0000721 # connection fails -> terminate loop
cliechticb20a4f2011-04-25 02:25:54 +0000722 if self.logger:
Chris Liechti1f6643d2016-02-16 21:06:52 +0100723 self.logger.debug("socket error in reader thread: {}".format(e))
cliechti81c54762009-08-03 23:53:27 +0000724 break
Chris Liechti033f17c2015-08-30 21:28:04 +0200725 if not data:
726 break # lost connection
Chris Liechtif99cd5c2015-08-13 22:54:16 +0200727 for byte in iterbytes(data):
cliechti81c54762009-08-03 23:53:27 +0000728 if mode == M_NORMAL:
729 # interpret as command or as data
730 if byte == IAC:
731 mode = M_IAC_SEEN
cliechti8099bed2009-08-01 23:59:18 +0000732 else:
cliechti81c54762009-08-03 23:53:27 +0000733 # store data in read buffer or sub option buffer
734 # depending on state
735 if suboption is not None:
Chris Liechti01587b12015-08-05 02:39:32 +0200736 suboption += byte
cliechti81c54762009-08-03 23:53:27 +0000737 else:
738 self._read_buffer.put(byte)
739 elif mode == M_IAC_SEEN:
740 if byte == IAC:
741 # interpret as command doubled -> insert character
742 # itself
cliechtif325c032009-12-25 16:09:49 +0000743 if suboption is not None:
Chris Liechti01587b12015-08-05 02:39:32 +0200744 suboption += IAC
cliechtif325c032009-12-25 16:09:49 +0000745 else:
746 self._read_buffer.put(IAC)
cliechti81c54762009-08-03 23:53:27 +0000747 mode = M_NORMAL
748 elif byte == SB:
749 # sub option start
750 suboption = bytearray()
751 mode = M_NORMAL
752 elif byte == SE:
753 # sub option end -> process it now
754 self._telnetProcessSubnegotiation(bytes(suboption))
755 suboption = None
756 mode = M_NORMAL
757 elif byte in (DO, DONT, WILL, WONT):
758 # negotiation
759 telnet_command = byte
760 mode = M_NEGOTIATE
761 else:
762 # other telnet commands
763 self._telnetProcessCommand(byte)
764 mode = M_NORMAL
Chris Liechti033f17c2015-08-30 21:28:04 +0200765 elif mode == M_NEGOTIATE: # DO, DONT, WILL, WONT was received, option now following
cliechti81c54762009-08-03 23:53:27 +0000766 self._telnetNegotiateOption(telnet_command, byte)
cliechti8099bed2009-08-01 23:59:18 +0000767 mode = M_NORMAL
cliechti81c54762009-08-03 23:53:27 +0000768 finally:
769 self._thread = None
cliechti6a300772009-08-12 02:28:56 +0000770 if self.logger:
771 self.logger.debug("read thread terminated")
cliechti8099bed2009-08-01 23:59:18 +0000772
773 # - incoming telnet commands and options
774
cliechti1ef7e3e2009-08-03 02:38:43 +0000775 def _telnetProcessCommand(self, command):
cliechti044d8662009-08-11 21:40:31 +0000776 """Process commands other than DO, DONT, WILL, WONT."""
cliechti1ef7e3e2009-08-03 02:38:43 +0000777 # Currently none. RFC2217 only uses negotiation and subnegotiation.
cliechti6a300772009-08-12 02:28:56 +0000778 if self.logger:
Chris Liechti1f6643d2016-02-16 21:06:52 +0100779 self.logger.warning("ignoring Telnet command: {!r}".format(command))
cliechti8099bed2009-08-01 23:59:18 +0000780
cliechti1ef7e3e2009-08-03 02:38:43 +0000781 def _telnetNegotiateOption(self, command, option):
cliechti044d8662009-08-11 21:40:31 +0000782 """Process incoming DO, DONT, WILL, WONT."""
cliechti2b929b72009-08-02 23:49:02 +0000783 # check our registered telnet options and forward command to them
784 # they know themselves if they have to answer or not
cliechtiac205322009-08-02 20:40:21 +0000785 known = False
786 for item in self._telnet_options:
cliechti2b929b72009-08-02 23:49:02 +0000787 # can have more than one match! as some options are duplicated for
788 # 'us' and 'them'
cliechtiac205322009-08-02 20:40:21 +0000789 if item.option == option:
cliechti2b929b72009-08-02 23:49:02 +0000790 item.process_incoming(command)
cliechtiac205322009-08-02 20:40:21 +0000791 known = True
792 if not known:
793 # handle unknown options
794 # only answer to positive requests and deny them
795 if command == WILL or command == DO:
Chris Liechti142ae562015-08-23 01:11:06 +0200796 self.telnetSendOption((DONT if command == WILL else WONT), option)
cliechti6a300772009-08-12 02:28:56 +0000797 if self.logger:
Chris Liechti1f6643d2016-02-16 21:06:52 +0100798 self.logger.warning("rejected Telnet option: {!r}".format(option))
cliechtiac205322009-08-02 20:40:21 +0000799
cliechti1ef7e3e2009-08-03 02:38:43 +0000800 def _telnetProcessSubnegotiation(self, suboption):
cliechti044d8662009-08-11 21:40:31 +0000801 """Process subnegotiation, the data between IAC SB and IAC SE."""
cliechti8099bed2009-08-01 23:59:18 +0000802 if suboption[0:1] == COM_PORT_OPTION:
803 if suboption[1:2] == SERVER_NOTIFY_LINESTATE and len(suboption) >= 3:
Chris Liechti033f17c2015-08-30 21:28:04 +0200804 self._linestate = ord(suboption[2:3]) # ensure it is a number
cliechti6a300772009-08-12 02:28:56 +0000805 if self.logger:
Chris Liechti1f6643d2016-02-16 21:06:52 +0100806 self.logger.info("NOTIFY_LINESTATE: {}".format(self._linestate))
cliechti8099bed2009-08-01 23:59:18 +0000807 elif suboption[1:2] == SERVER_NOTIFY_MODEMSTATE and len(suboption) >= 3:
Chris Liechti033f17c2015-08-30 21:28:04 +0200808 self._modemstate = ord(suboption[2:3]) # ensure it is a number
cliechti6a300772009-08-12 02:28:56 +0000809 if self.logger:
Chris Liechti1f6643d2016-02-16 21:06:52 +0100810 self.logger.info("NOTIFY_MODEMSTATE: {}".format(self._modemstate))
cliechti7cb78e82009-08-05 15:47:57 +0000811 # update time when we think that a poll would make sense
812 self._modemstate_expires = time.time() + 0.3
cliechti672d0292009-08-03 02:01:57 +0000813 elif suboption[1:2] == FLOWCONTROL_SUSPEND:
814 self._remote_suspend_flow = True
815 elif suboption[1:2] == FLOWCONTROL_RESUME:
816 self._remote_suspend_flow = False
cliechti8099bed2009-08-01 23:59:18 +0000817 else:
cliechti2b929b72009-08-02 23:49:02 +0000818 for item in self._rfc2217_options.values():
819 if item.ack_option == suboption[1:2]:
cliechti81c54762009-08-03 23:53:27 +0000820 #~ print "processing COM_PORT_OPTION: %r" % list(suboption[1:])
cliechti2b929b72009-08-02 23:49:02 +0000821 item.checkAnswer(bytes(suboption[2:]))
822 break
823 else:
cliechti6a300772009-08-12 02:28:56 +0000824 if self.logger:
Chris Liechti1f6643d2016-02-16 21:06:52 +0100825 self.logger.warning("ignoring COM_PORT_OPTION: {!r}".format(suboption))
cliechti8099bed2009-08-01 23:59:18 +0000826 else:
cliechti6a300772009-08-12 02:28:56 +0000827 if self.logger:
Chris Liechti1f6643d2016-02-16 21:06:52 +0100828 self.logger.warning("ignoring subnegotiation: {!r}".format(suboption))
cliechti8099bed2009-08-01 23:59:18 +0000829
830 # - outgoing telnet commands and options
831
cliechti81c54762009-08-03 23:53:27 +0000832 def _internal_raw_write(self, data):
cliechti044d8662009-08-11 21:40:31 +0000833 """internal socket write with no data escaping. used to send telnet stuff."""
Chris Liechti01587b12015-08-05 02:39:32 +0200834 with self._write_lock:
cliechti81c54762009-08-03 23:53:27 +0000835 self._socket.sendall(data)
cliechti81c54762009-08-03 23:53:27 +0000836
cliechti1ef7e3e2009-08-03 02:38:43 +0000837 def telnetSendOption(self, action, option):
cliechti044d8662009-08-11 21:40:31 +0000838 """Send DO, DONT, WILL, WONT."""
cliechti81c54762009-08-03 23:53:27 +0000839 self._internal_raw_write(to_bytes([IAC, action, option]))
cliechti8099bed2009-08-01 23:59:18 +0000840
Chris Liechtib4cda3a2015-08-08 17:12:08 +0200841 def rfc2217SendSubnegotiation(self, option, value=b''):
cliechti044d8662009-08-11 21:40:31 +0000842 """Subnegotiation of RFC2217 parameters."""
cliechtif325c032009-12-25 16:09:49 +0000843 value = value.replace(IAC, IAC_DOUBLED)
cliechti81c54762009-08-03 23:53:27 +0000844 self._internal_raw_write(to_bytes([IAC, SB, COM_PORT_OPTION, option] + list(value) + [IAC, SE]))
cliechti2b929b72009-08-02 23:49:02 +0000845
cliechti1ef7e3e2009-08-03 02:38:43 +0000846 def rfc2217SendPurge(self, value):
cliechti2b929b72009-08-02 23:49:02 +0000847 item = self._rfc2217_options['purge']
Chris Liechti033f17c2015-08-30 21:28:04 +0200848 item.set(value) # transmit desired purge type
849 item.wait(self._network_timeout) # wait for acknowledge from the server
cliechti2b929b72009-08-02 23:49:02 +0000850
cliechti1ef7e3e2009-08-03 02:38:43 +0000851 def rfc2217SetControl(self, value):
cliechti81c54762009-08-03 23:53:27 +0000852 item = self._rfc2217_options['control']
Chris Liechti033f17c2015-08-30 21:28:04 +0200853 item.set(value) # transmit desired control type
cliechti81c54762009-08-03 23:53:27 +0000854 if self._ignore_set_control_answer:
855 # answers are ignored when option is set. compatibility mode for
cliechticb20a4f2011-04-25 02:25:54 +0000856 # servers that answer, but not the expected one... (or no answer
cliechti81c54762009-08-03 23:53:27 +0000857 # at all) i.e. sredird
858 time.sleep(0.1) # this helps getting the unit tests passed
859 else:
cliechtidfe2d272009-08-10 22:19:41 +0000860 item.wait(self._network_timeout) # wait for acknowledge from the server
cliechti8099bed2009-08-01 23:59:18 +0000861
cliechti1ef7e3e2009-08-03 02:38:43 +0000862 def rfc2217FlowServerReady(self):
cliechtieada4fd2013-07-31 16:26:07 +0000863 """\
864 check if server is ready to receive data. block for some time when
865 not.
866 """
cliechti672d0292009-08-03 02:01:57 +0000867 #~ if self._remote_suspend_flow:
Chris Liechtiba45c522016-02-06 23:53:23 +0100868 #~ wait---
cliechti672d0292009-08-03 02:01:57 +0000869
cliechti7cb78e82009-08-05 15:47:57 +0000870 def getModemState(self):
cliechtieada4fd2013-07-31 16:26:07 +0000871 """\
cliechti7d448562014-08-03 21:57:45 +0000872 get last modem state (cached value. If value is "old", request a new
873 one. This cache helps that we don't issue to many requests when e.g. all
874 status lines, one after the other is queried by the user (getCTS, getDSR
cliechtieada4fd2013-07-31 16:26:07 +0000875 etc.)
876 """
cliechti7cb78e82009-08-05 15:47:57 +0000877 # active modem state polling enabled? is the value fresh enough?
878 if self._poll_modem_state and self._modemstate_expires < time.time():
cliechti6a300772009-08-12 02:28:56 +0000879 if self.logger:
880 self.logger.debug('polling modem state')
cliechti7cb78e82009-08-05 15:47:57 +0000881 # when it is older, request an update
882 self.rfc2217SendSubnegotiation(NOTIFY_MODEMSTATE)
cliechtidfe2d272009-08-10 22:19:41 +0000883 timeout_time = time.time() + self._network_timeout
cliechti7cb78e82009-08-05 15:47:57 +0000884 while time.time() < timeout_time:
885 time.sleep(0.05) # prevent 100% CPU load
886 # when expiration time is updated, it means that there is a new
887 # value
888 if self._modemstate_expires > time.time():
889 break
Chris Liechti01587b12015-08-05 02:39:32 +0200890 else:
891 if self.logger:
892 self.logger.warning('poll for modem state failed')
cliechti7cb78e82009-08-05 15:47:57 +0000893 # even when there is a timeout, do not generate an error just
894 # return the last known value. this way we can support buggy
895 # servers that do not respond to polls, but send automatic
896 # updates.
897 if self._modemstate is not None:
cliechti6a300772009-08-12 02:28:56 +0000898 if self.logger:
899 self.logger.debug('using cached modem state')
cliechti7cb78e82009-08-05 15:47:57 +0000900 return self._modemstate
901 else:
902 # never received a notification from the server
cliechti8fb119c2009-08-05 23:39:45 +0000903 raise SerialException("remote sends no NOTIFY_MODEMSTATE")
cliechti8099bed2009-08-01 23:59:18 +0000904
cliechti5cc3eb12009-08-11 23:04:30 +0000905
cliechti595ed5b2009-08-10 01:43:32 +0000906#############################################################################
cliechti5cc3eb12009-08-11 23:04:30 +0000907# The following is code that helps implementing an RFC 2217 server.
cliechti8099bed2009-08-01 23:59:18 +0000908
cliechti8ccc2ff2009-08-05 12:44:46 +0000909class PortManager(object):
cliechtieada4fd2013-07-31 16:26:07 +0000910 """\
911 This class manages the state of Telnet and RFC 2217. It needs a serial
cliechticb20a4f2011-04-25 02:25:54 +0000912 instance and a connection to work with. Connection is expected to implement
cliechtieada4fd2013-07-31 16:26:07 +0000913 a (thread safe) write function, that writes the string to the network.
914 """
cliechti130d1f02009-08-04 02:10:58 +0000915
cliechti6a300772009-08-12 02:28:56 +0000916 def __init__(self, serial_port, connection, logger=None):
cliechti130d1f02009-08-04 02:10:58 +0000917 self.serial = serial_port
918 self.connection = connection
cliechti6a300772009-08-12 02:28:56 +0000919 self.logger = logger
cliechti86b593e2009-08-05 16:28:12 +0000920 self._client_is_rfc2217 = False
cliechti130d1f02009-08-04 02:10:58 +0000921
922 # filter state machine
923 self.mode = M_NORMAL
924 self.suboption = None
925 self.telnet_command = None
926
927 # states for modem/line control events
928 self.modemstate_mask = 255
929 self.last_modemstate = None
930 self.linstate_mask = 0
931
932 # all supported telnet options
933 self._telnet_options = [
934 TelnetOption(self, 'ECHO', ECHO, WILL, WONT, DO, DONT, REQUESTED),
935 TelnetOption(self, 'we-SGA', SGA, WILL, WONT, DO, DONT, REQUESTED),
936 TelnetOption(self, 'they-SGA', SGA, DO, DONT, WILL, WONT, INACTIVE),
937 TelnetOption(self, 'we-BINARY', BINARY, WILL, WONT, DO, DONT, INACTIVE),
938 TelnetOption(self, 'they-BINARY', BINARY, DO, DONT, WILL, WONT, REQUESTED),
cliechti86b593e2009-08-05 16:28:12 +0000939 TelnetOption(self, 'we-RFC2217', COM_PORT_OPTION, WILL, WONT, DO, DONT, REQUESTED, self._client_ok),
940 TelnetOption(self, 'they-RFC2217', COM_PORT_OPTION, DO, DONT, WILL, WONT, INACTIVE, self._client_ok),
Chris Liechticb6ce1b2016-02-02 01:53:56 +0100941 ]
cliechti130d1f02009-08-04 02:10:58 +0000942
943 # negotiate Telnet/RFC2217 -> send initial requests
cliechti6a300772009-08-12 02:28:56 +0000944 if self.logger:
945 self.logger.debug("requesting initial Telnet/RFC 2217 options")
cliechti130d1f02009-08-04 02:10:58 +0000946 for option in self._telnet_options:
947 if option.state is REQUESTED:
948 self.telnetSendOption(option.send_yes, option.option)
949 # issue 1st modem state notification
cliechti86b593e2009-08-05 16:28:12 +0000950
951 def _client_ok(self):
cliechtieada4fd2013-07-31 16:26:07 +0000952 """\
cliechti7d448562014-08-03 21:57:45 +0000953 callback of telnet option. It gets called when option is activated.
954 This one here is used to detect when the client agrees on RFC 2217. A
cliechti86b593e2009-08-05 16:28:12 +0000955 flag is set so that other functions like check_modem_lines know if the
cliechti7d448562014-08-03 21:57:45 +0000956 client is OK.
cliechtieada4fd2013-07-31 16:26:07 +0000957 """
cliechti86b593e2009-08-05 16:28:12 +0000958 # The callback is used for we and they so if one party agrees, we're
959 # already happy. it seems not all servers do the negotiation correctly
960 # and i guess there are incorrect clients too.. so be happy if client
961 # answers one or the other positively.
962 self._client_is_rfc2217 = True
cliechti6a300772009-08-12 02:28:56 +0000963 if self.logger:
964 self.logger.info("client accepts RFC 2217")
cliechti8fb119c2009-08-05 23:39:45 +0000965 # this is to ensure that the client gets a notification, even if there
966 # was no change
967 self.check_modem_lines(force_notification=True)
cliechti130d1f02009-08-04 02:10:58 +0000968
969 # - outgoing telnet commands and options
970
971 def telnetSendOption(self, action, option):
cliechti044d8662009-08-11 21:40:31 +0000972 """Send DO, DONT, WILL, WONT."""
cliechti130d1f02009-08-04 02:10:58 +0000973 self.connection.write(to_bytes([IAC, action, option]))
974
Chris Liechtib4cda3a2015-08-08 17:12:08 +0200975 def rfc2217SendSubnegotiation(self, option, value=b''):
cliechti044d8662009-08-11 21:40:31 +0000976 """Subnegotiation of RFC 2217 parameters."""
cliechtif325c032009-12-25 16:09:49 +0000977 value = value.replace(IAC, IAC_DOUBLED)
cliechti130d1f02009-08-04 02:10:58 +0000978 self.connection.write(to_bytes([IAC, SB, COM_PORT_OPTION, option] + list(value) + [IAC, SE]))
979
980 # - check modem lines, needs to be called periodically from user to
981 # establish polling
982
cliechti7cb78e82009-08-05 15:47:57 +0000983 def check_modem_lines(self, force_notification=False):
cliechti130d1f02009-08-04 02:10:58 +0000984 modemstate = (
Chris Liechtiba45c522016-02-06 23:53:23 +0100985 (self.serial.getCTS() and MODEMSTATE_MASK_CTS) |
986 (self.serial.getDSR() and MODEMSTATE_MASK_DSR) |
987 (self.serial.getRI() and MODEMSTATE_MASK_RI) |
988 (self.serial.getCD() and MODEMSTATE_MASK_CD))
cliechti7cb78e82009-08-05 15:47:57 +0000989 # check what has changed
Chris Liechti033f17c2015-08-30 21:28:04 +0200990 deltas = modemstate ^ (self.last_modemstate or 0) # when last is None -> 0
cliechti7cb78e82009-08-05 15:47:57 +0000991 if deltas & MODEMSTATE_MASK_CTS:
992 modemstate |= MODEMSTATE_MASK_CTS_CHANGE
993 if deltas & MODEMSTATE_MASK_DSR:
994 modemstate |= MODEMSTATE_MASK_DSR_CHANGE
995 if deltas & MODEMSTATE_MASK_RI:
996 modemstate |= MODEMSTATE_MASK_RI_CHANGE
997 if deltas & MODEMSTATE_MASK_CD:
998 modemstate |= MODEMSTATE_MASK_CD_CHANGE
999 # if new state is different and the mask allows this change, send
cliechti86b593e2009-08-05 16:28:12 +00001000 # notification. suppress notifications when client is not rfc2217
cliechti7cb78e82009-08-05 15:47:57 +00001001 if modemstate != self.last_modemstate or force_notification:
cliechti8fb119c2009-08-05 23:39:45 +00001002 if (self._client_is_rfc2217 and (modemstate & self.modemstate_mask)) or force_notification:
cliechti7cb78e82009-08-05 15:47:57 +00001003 self.rfc2217SendSubnegotiation(
Chris Liechticb6ce1b2016-02-02 01:53:56 +01001004 SERVER_NOTIFY_MODEMSTATE,
1005 to_bytes([modemstate & self.modemstate_mask]))
cliechti6a300772009-08-12 02:28:56 +00001006 if self.logger:
Chris Liechti1f6643d2016-02-16 21:06:52 +01001007 self.logger.info("NOTIFY_MODEMSTATE: {}".format(modemstate))
cliechti7cb78e82009-08-05 15:47:57 +00001008 # save last state, but forget about deltas.
1009 # otherwise it would also notify about changing deltas which is
1010 # probably not very useful
1011 self.last_modemstate = modemstate & 0xf0
cliechti130d1f02009-08-04 02:10:58 +00001012
cliechti32c10332009-08-05 13:23:43 +00001013 # - outgoing data escaping
1014
1015 def escape(self, data):
cliechtieada4fd2013-07-31 16:26:07 +00001016 """\
cliechti7d448562014-08-03 21:57:45 +00001017 This generator function is for the user. All outgoing data has to be
cliechticb20a4f2011-04-25 02:25:54 +00001018 properly escaped, so that no IAC character in the data stream messes up
1019 the Telnet state machine in the server.
cliechti32c10332009-08-05 13:23:43 +00001020
1021 socket.sendall(escape(data))
1022 """
Chris Liechtib15dc052015-10-19 23:17:16 +02001023 for byte in iterbytes(data):
cliechti32c10332009-08-05 13:23:43 +00001024 if byte == IAC:
1025 yield IAC
1026 yield IAC
1027 else:
1028 yield byte
1029
cliechti130d1f02009-08-04 02:10:58 +00001030 # - incoming data filter
1031
1032 def filter(self, data):
cliechtieada4fd2013-07-31 16:26:07 +00001033 """\
cliechti7d448562014-08-03 21:57:45 +00001034 Handle a bunch of incoming bytes. This is a generator. It will yield
cliechti044d8662009-08-11 21:40:31 +00001035 all characters not of interest for Telnet/RFC 2217.
cliechti130d1f02009-08-04 02:10:58 +00001036
1037 The idea is that the reader thread pushes data from the socket through
1038 this filter:
1039
1040 for byte in filter(socket.recv(1024)):
1041 # do things like CR/LF conversion/whatever
1042 # and write data to the serial port
1043 serial.write(byte)
1044
1045 (socket error handling code left as exercise for the reader)
1046 """
Chris Liechtif99cd5c2015-08-13 22:54:16 +02001047 for byte in iterbytes(data):
cliechti130d1f02009-08-04 02:10:58 +00001048 if self.mode == M_NORMAL:
1049 # interpret as command or as data
1050 if byte == IAC:
1051 self.mode = M_IAC_SEEN
1052 else:
1053 # store data in sub option buffer or pass it to our
1054 # consumer depending on state
1055 if self.suboption is not None:
Chris Liechti01587b12015-08-05 02:39:32 +02001056 self.suboption += byte
cliechti130d1f02009-08-04 02:10:58 +00001057 else:
1058 yield byte
1059 elif self.mode == M_IAC_SEEN:
1060 if byte == IAC:
1061 # interpret as command doubled -> insert character
1062 # itself
cliechtif325c032009-12-25 16:09:49 +00001063 if self.suboption is not None:
Chris Liechti01587b12015-08-05 02:39:32 +02001064 self.suboption += byte
cliechtif325c032009-12-25 16:09:49 +00001065 else:
1066 yield byte
cliechti130d1f02009-08-04 02:10:58 +00001067 self.mode = M_NORMAL
1068 elif byte == SB:
1069 # sub option start
1070 self.suboption = bytearray()
1071 self.mode = M_NORMAL
1072 elif byte == SE:
1073 # sub option end -> process it now
1074 self._telnetProcessSubnegotiation(bytes(self.suboption))
1075 self.suboption = None
1076 self.mode = M_NORMAL
1077 elif byte in (DO, DONT, WILL, WONT):
1078 # negotiation
1079 self.telnet_command = byte
1080 self.mode = M_NEGOTIATE
1081 else:
1082 # other telnet commands
1083 self._telnetProcessCommand(byte)
1084 self.mode = M_NORMAL
Chris Liechti033f17c2015-08-30 21:28:04 +02001085 elif self.mode == M_NEGOTIATE: # DO, DONT, WILL, WONT was received, option now following
cliechti130d1f02009-08-04 02:10:58 +00001086 self._telnetNegotiateOption(self.telnet_command, byte)
1087 self.mode = M_NORMAL
1088
1089 # - incoming telnet commands and options
1090
1091 def _telnetProcessCommand(self, command):
cliechti044d8662009-08-11 21:40:31 +00001092 """Process commands other than DO, DONT, WILL, WONT."""
cliechti130d1f02009-08-04 02:10:58 +00001093 # Currently none. RFC2217 only uses negotiation and subnegotiation.
cliechti6a300772009-08-12 02:28:56 +00001094 if self.logger:
Chris Liechti1f6643d2016-02-16 21:06:52 +01001095 self.logger.warning("ignoring Telnet command: {!r}".format(command))
cliechti130d1f02009-08-04 02:10:58 +00001096
1097 def _telnetNegotiateOption(self, command, option):
cliechti044d8662009-08-11 21:40:31 +00001098 """Process incoming DO, DONT, WILL, WONT."""
cliechti130d1f02009-08-04 02:10:58 +00001099 # check our registered telnet options and forward command to them
1100 # they know themselves if they have to answer or not
1101 known = False
1102 for item in self._telnet_options:
1103 # can have more than one match! as some options are duplicated for
1104 # 'us' and 'them'
1105 if item.option == option:
1106 item.process_incoming(command)
1107 known = True
1108 if not known:
1109 # handle unknown options
1110 # only answer to positive requests and deny them
1111 if command == WILL or command == DO:
Chris Liechti142ae562015-08-23 01:11:06 +02001112 self.telnetSendOption((DONT if command == WILL else WONT), option)
cliechti6a300772009-08-12 02:28:56 +00001113 if self.logger:
Chris Liechti1f6643d2016-02-16 21:06:52 +01001114 self.logger.warning("rejected Telnet option: {!r}".format(option))
cliechti130d1f02009-08-04 02:10:58 +00001115
cliechti130d1f02009-08-04 02:10:58 +00001116 def _telnetProcessSubnegotiation(self, suboption):
cliechti044d8662009-08-11 21:40:31 +00001117 """Process subnegotiation, the data between IAC SB and IAC SE."""
cliechti130d1f02009-08-04 02:10:58 +00001118 if suboption[0:1] == COM_PORT_OPTION:
cliechti6a300772009-08-12 02:28:56 +00001119 if self.logger:
Chris Liechti1f6643d2016-02-16 21:06:52 +01001120 self.logger.debug('received COM_PORT_OPTION: {!r}'.format(suboption))
cliechti130d1f02009-08-04 02:10:58 +00001121 if suboption[1:2] == SET_BAUDRATE:
1122 backup = self.serial.baudrate
1123 try:
Chris Liechti01587b12015-08-05 02:39:32 +02001124 (baudrate,) = struct.unpack(b"!I", suboption[2:6])
cliechtieada4fd2013-07-31 16:26:07 +00001125 if baudrate != 0:
1126 self.serial.baudrate = baudrate
Chris Liechtid2146002015-08-04 16:57:16 +02001127 except ValueError as e:
cliechti6a300772009-08-12 02:28:56 +00001128 if self.logger:
Chris Liechti1f6643d2016-02-16 21:06:52 +01001129 self.logger.error("failed to set baud rate: {}".format(e))
cliechti130d1f02009-08-04 02:10:58 +00001130 self.serial.baudrate = backup
cliechti5cc3eb12009-08-11 23:04:30 +00001131 else:
cliechti6a300772009-08-12 02:28:56 +00001132 if self.logger:
Chris Liechti1f6643d2016-02-16 21:06:52 +01001133 self.logger.info("{} baud rate: {}".format('set' if baudrate else 'get', self.serial.baudrate))
Chris Liechti01587b12015-08-05 02:39:32 +02001134 self.rfc2217SendSubnegotiation(SERVER_SET_BAUDRATE, struct.pack(b"!I", self.serial.baudrate))
cliechti130d1f02009-08-04 02:10:58 +00001135 elif suboption[1:2] == SET_DATASIZE:
1136 backup = self.serial.bytesize
1137 try:
Chris Liechti142ae562015-08-23 01:11:06 +02001138 (datasize,) = struct.unpack(b"!B", suboption[2:3])
cliechtieada4fd2013-07-31 16:26:07 +00001139 if datasize != 0:
1140 self.serial.bytesize = datasize
Chris Liechtid2146002015-08-04 16:57:16 +02001141 except ValueError as e:
cliechti6a300772009-08-12 02:28:56 +00001142 if self.logger:
Chris Liechti1f6643d2016-02-16 21:06:52 +01001143 self.logger.error("failed to set data size: {}".format(e))
cliechti130d1f02009-08-04 02:10:58 +00001144 self.serial.bytesize = backup
cliechti5cc3eb12009-08-11 23:04:30 +00001145 else:
cliechti6a300772009-08-12 02:28:56 +00001146 if self.logger:
Chris Liechti1f6643d2016-02-16 21:06:52 +01001147 self.logger.info("{} data size: {}".format('set' if datasize else 'get', self.serial.bytesize))
Chris Liechti01587b12015-08-05 02:39:32 +02001148 self.rfc2217SendSubnegotiation(SERVER_SET_DATASIZE, struct.pack(b"!B", self.serial.bytesize))
cliechti130d1f02009-08-04 02:10:58 +00001149 elif suboption[1:2] == SET_PARITY:
1150 backup = self.serial.parity
1151 try:
Chris Liechti01587b12015-08-05 02:39:32 +02001152 parity = struct.unpack(b"!B", suboption[2:3])[0]
cliechtieada4fd2013-07-31 16:26:07 +00001153 if parity != 0:
1154 self.serial.parity = RFC2217_REVERSE_PARITY_MAP[parity]
Chris Liechtid2146002015-08-04 16:57:16 +02001155 except ValueError as e:
cliechti6a300772009-08-12 02:28:56 +00001156 if self.logger:
Chris Liechti1f6643d2016-02-16 21:06:52 +01001157 self.logger.error("failed to set parity: {}".format(e))
cliechti130d1f02009-08-04 02:10:58 +00001158 self.serial.parity = backup
cliechti5cc3eb12009-08-11 23:04:30 +00001159 else:
cliechti6a300772009-08-12 02:28:56 +00001160 if self.logger:
Chris Liechti1f6643d2016-02-16 21:06:52 +01001161 self.logger.info("{} parity: {}".format('set' if parity else 'get', self.serial.parity))
cliechti130d1f02009-08-04 02:10:58 +00001162 self.rfc2217SendSubnegotiation(
Chris Liechticb6ce1b2016-02-02 01:53:56 +01001163 SERVER_SET_PARITY,
1164 struct.pack(b"!B", RFC2217_PARITY_MAP[self.serial.parity]))
cliechti130d1f02009-08-04 02:10:58 +00001165 elif suboption[1:2] == SET_STOPSIZE:
1166 backup = self.serial.stopbits
1167 try:
Chris Liechti01587b12015-08-05 02:39:32 +02001168 stopbits = struct.unpack(b"!B", suboption[2:3])[0]
cliechtieada4fd2013-07-31 16:26:07 +00001169 if stopbits != 0:
1170 self.serial.stopbits = RFC2217_REVERSE_STOPBIT_MAP[stopbits]
Chris Liechtid2146002015-08-04 16:57:16 +02001171 except ValueError as e:
cliechti6a300772009-08-12 02:28:56 +00001172 if self.logger:
Chris Liechti1f6643d2016-02-16 21:06:52 +01001173 self.logger.error("failed to set stop bits: {}".format(e))
cliechti130d1f02009-08-04 02:10:58 +00001174 self.serial.stopbits = backup
cliechti5cc3eb12009-08-11 23:04:30 +00001175 else:
cliechti6a300772009-08-12 02:28:56 +00001176 if self.logger:
Chris Liechti1f6643d2016-02-16 21:06:52 +01001177 self.logger.info("{} stop bits: {}".format('set' if stopbits else 'get', self.serial.stopbits))
cliechti130d1f02009-08-04 02:10:58 +00001178 self.rfc2217SendSubnegotiation(
Chris Liechticb6ce1b2016-02-02 01:53:56 +01001179 SERVER_SET_STOPSIZE,
1180 struct.pack(b"!B", RFC2217_STOPBIT_MAP[self.serial.stopbits]))
cliechti130d1f02009-08-04 02:10:58 +00001181 elif suboption[1:2] == SET_CONTROL:
1182 if suboption[2:3] == SET_CONTROL_REQ_FLOW_SETTING:
1183 if self.serial.xonxoff:
1184 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_SW_FLOW_CONTROL)
1185 elif self.serial.rtscts:
1186 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_HW_FLOW_CONTROL)
1187 else:
1188 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_NO_FLOW_CONTROL)
1189 elif suboption[2:3] == SET_CONTROL_USE_NO_FLOW_CONTROL:
1190 self.serial.xonxoff = False
1191 self.serial.rtscts = False
cliechti6a300772009-08-12 02:28:56 +00001192 if self.logger:
1193 self.logger.info("changed flow control to None")
cliechti130d1f02009-08-04 02:10:58 +00001194 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_NO_FLOW_CONTROL)
1195 elif suboption[2:3] == SET_CONTROL_USE_SW_FLOW_CONTROL:
1196 self.serial.xonxoff = True
cliechti6a300772009-08-12 02:28:56 +00001197 if self.logger:
1198 self.logger.info("changed flow control to XON/XOFF")
cliechti130d1f02009-08-04 02:10:58 +00001199 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_SW_FLOW_CONTROL)
1200 elif suboption[2:3] == SET_CONTROL_USE_HW_FLOW_CONTROL:
1201 self.serial.rtscts = True
cliechti6a300772009-08-12 02:28:56 +00001202 if self.logger:
1203 self.logger.info("changed flow control to RTS/CTS")
cliechti130d1f02009-08-04 02:10:58 +00001204 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_HW_FLOW_CONTROL)
1205 elif suboption[2:3] == SET_CONTROL_REQ_BREAK_STATE:
cliechti6a300772009-08-12 02:28:56 +00001206 if self.logger:
1207 self.logger.warning("requested break state - not implemented")
Chris Liechti033f17c2015-08-30 21:28:04 +02001208 pass # XXX needs cached value
cliechti130d1f02009-08-04 02:10:58 +00001209 elif suboption[2:3] == SET_CONTROL_BREAK_ON:
1210 self.serial.setBreak(True)
cliechti6a300772009-08-12 02:28:56 +00001211 if self.logger:
1212 self.logger.info("changed BREAK to active")
cliechti130d1f02009-08-04 02:10:58 +00001213 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_BREAK_ON)
1214 elif suboption[2:3] == SET_CONTROL_BREAK_OFF:
1215 self.serial.setBreak(False)
cliechti6a300772009-08-12 02:28:56 +00001216 if self.logger:
1217 self.logger.info("changed BREAK to inactive")
cliechti130d1f02009-08-04 02:10:58 +00001218 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_BREAK_OFF)
1219 elif suboption[2:3] == SET_CONTROL_REQ_DTR:
cliechti6a300772009-08-12 02:28:56 +00001220 if self.logger:
1221 self.logger.warning("requested DTR state - not implemented")
Chris Liechti033f17c2015-08-30 21:28:04 +02001222 pass # XXX needs cached value
cliechti130d1f02009-08-04 02:10:58 +00001223 elif suboption[2:3] == SET_CONTROL_DTR_ON:
1224 self.serial.setDTR(True)
cliechti6a300772009-08-12 02:28:56 +00001225 if self.logger:
1226 self.logger.info("changed DTR to active")
cliechti130d1f02009-08-04 02:10:58 +00001227 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_DTR_ON)
1228 elif suboption[2:3] == SET_CONTROL_DTR_OFF:
1229 self.serial.setDTR(False)
cliechti6a300772009-08-12 02:28:56 +00001230 if self.logger:
1231 self.logger.info("changed DTR to inactive")
cliechti130d1f02009-08-04 02:10:58 +00001232 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_DTR_OFF)
1233 elif suboption[2:3] == SET_CONTROL_REQ_RTS:
cliechti6a300772009-08-12 02:28:56 +00001234 if self.logger:
1235 self.logger.warning("requested RTS state - not implemented")
Chris Liechti033f17c2015-08-30 21:28:04 +02001236 pass # XXX needs cached value
cliechti130d1f02009-08-04 02:10:58 +00001237 #~ self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_RTS_ON)
1238 elif suboption[2:3] == SET_CONTROL_RTS_ON:
1239 self.serial.setRTS(True)
cliechti6a300772009-08-12 02:28:56 +00001240 if self.logger:
1241 self.logger.info("changed RTS to active")
cliechti130d1f02009-08-04 02:10:58 +00001242 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_RTS_ON)
1243 elif suboption[2:3] == SET_CONTROL_RTS_OFF:
1244 self.serial.setRTS(False)
cliechti6a300772009-08-12 02:28:56 +00001245 if self.logger:
1246 self.logger.info("changed RTS to inactive")
cliechti130d1f02009-08-04 02:10:58 +00001247 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_RTS_OFF)
1248 #~ elif suboption[2:3] == SET_CONTROL_REQ_FLOW_SETTING_IN:
1249 #~ elif suboption[2:3] == SET_CONTROL_USE_NO_FLOW_CONTROL_IN:
1250 #~ elif suboption[2:3] == SET_CONTROL_USE_SW_FLOW_CONTOL_IN:
1251 #~ elif suboption[2:3] == SET_CONTROL_USE_HW_FLOW_CONTOL_IN:
1252 #~ elif suboption[2:3] == SET_CONTROL_USE_DCD_FLOW_CONTROL:
1253 #~ elif suboption[2:3] == SET_CONTROL_USE_DTR_FLOW_CONTROL:
1254 #~ elif suboption[2:3] == SET_CONTROL_USE_DSR_FLOW_CONTROL:
1255 elif suboption[1:2] == NOTIFY_LINESTATE:
cliechti7cb78e82009-08-05 15:47:57 +00001256 # client polls for current state
1257 self.rfc2217SendSubnegotiation(
Chris Liechti142ae562015-08-23 01:11:06 +02001258 SERVER_NOTIFY_LINESTATE,
1259 to_bytes([0])) # sorry, nothing like that implemented
cliechti130d1f02009-08-04 02:10:58 +00001260 elif suboption[1:2] == NOTIFY_MODEMSTATE:
cliechti6a300772009-08-12 02:28:56 +00001261 if self.logger:
1262 self.logger.info("request for modem state")
cliechti7cb78e82009-08-05 15:47:57 +00001263 # client polls for current state
1264 self.check_modem_lines(force_notification=True)
cliechti130d1f02009-08-04 02:10:58 +00001265 elif suboption[1:2] == FLOWCONTROL_SUSPEND:
cliechti6a300772009-08-12 02:28:56 +00001266 if self.logger:
1267 self.logger.info("suspend")
cliechti130d1f02009-08-04 02:10:58 +00001268 self._remote_suspend_flow = True
1269 elif suboption[1:2] == FLOWCONTROL_RESUME:
cliechti6a300772009-08-12 02:28:56 +00001270 if self.logger:
1271 self.logger.info("resume")
cliechti130d1f02009-08-04 02:10:58 +00001272 self._remote_suspend_flow = False
1273 elif suboption[1:2] == SET_LINESTATE_MASK:
Chris Liechti033f17c2015-08-30 21:28:04 +02001274 self.linstate_mask = ord(suboption[2:3]) # ensure it is a number
cliechti6a300772009-08-12 02:28:56 +00001275 if self.logger:
Chris Liechti1f6643d2016-02-16 21:06:52 +01001276 self.logger.info("line state mask: 0x{:02x}".format(self.linstate_mask))
cliechti130d1f02009-08-04 02:10:58 +00001277 elif suboption[1:2] == SET_MODEMSTATE_MASK:
Chris Liechti033f17c2015-08-30 21:28:04 +02001278 self.modemstate_mask = ord(suboption[2:3]) # ensure it is a number
cliechti6a300772009-08-12 02:28:56 +00001279 if self.logger:
Chris Liechti1f6643d2016-02-16 21:06:52 +01001280 self.logger.info("modem state mask: 0x{:02x}".format(self.modemstate_mask))
cliechti130d1f02009-08-04 02:10:58 +00001281 elif suboption[1:2] == PURGE_DATA:
1282 if suboption[2:3] == PURGE_RECEIVE_BUFFER:
Chris Liechti3ad62fb2015-08-29 21:53:32 +02001283 self.serial.reset_input_buffer()
cliechti6a300772009-08-12 02:28:56 +00001284 if self.logger:
1285 self.logger.info("purge in")
cliechti130d1f02009-08-04 02:10:58 +00001286 self.rfc2217SendSubnegotiation(SERVER_PURGE_DATA, PURGE_RECEIVE_BUFFER)
1287 elif suboption[2:3] == PURGE_TRANSMIT_BUFFER:
Chris Liechti3ad62fb2015-08-29 21:53:32 +02001288 self.serial.reset_output_buffer()
cliechti6a300772009-08-12 02:28:56 +00001289 if self.logger:
1290 self.logger.info("purge out")
cliechti130d1f02009-08-04 02:10:58 +00001291 self.rfc2217SendSubnegotiation(SERVER_PURGE_DATA, PURGE_TRANSMIT_BUFFER)
1292 elif suboption[2:3] == PURGE_BOTH_BUFFERS:
Chris Liechti3ad62fb2015-08-29 21:53:32 +02001293 self.serial.reset_input_buffer()
1294 self.serial.reset_output_buffer()
cliechti6a300772009-08-12 02:28:56 +00001295 if self.logger:
1296 self.logger.info("purge both")
cliechti130d1f02009-08-04 02:10:58 +00001297 self.rfc2217SendSubnegotiation(SERVER_PURGE_DATA, PURGE_BOTH_BUFFERS)
1298 else:
cliechti6a300772009-08-12 02:28:56 +00001299 if self.logger:
Chris Liechti1f6643d2016-02-16 21:06:52 +01001300 self.logger.error("undefined PURGE_DATA: {!r}".format(list(suboption[2:])))
cliechti130d1f02009-08-04 02:10:58 +00001301 else:
cliechti6a300772009-08-12 02:28:56 +00001302 if self.logger:
Chris Liechti1f6643d2016-02-16 21:06:52 +01001303 self.logger.error("undefined COM_PORT_OPTION: {!r}".format(list(suboption[1:])))
cliechti130d1f02009-08-04 02:10:58 +00001304 else:
cliechti6a300772009-08-12 02:28:56 +00001305 if self.logger:
Chris Liechti1f6643d2016-02-16 21:06:52 +01001306 self.logger.warning("unknown subnegotiation: {!r}".format(suboption))
cliechti130d1f02009-08-04 02:10:58 +00001307
1308
1309# simple client test
cliechti8099bed2009-08-01 23:59:18 +00001310if __name__ == '__main__':
1311 import sys
1312 s = Serial('rfc2217://localhost:7000', 115200)
Chris Liechti1f6643d2016-02-16 21:06:52 +01001313 sys.stdout.write('{}\n'.format(s))
cliechti8099bed2009-08-01 23:59:18 +00001314
cliechti8099bed2009-08-01 23:59:18 +00001315 sys.stdout.write("write...\n")
Chris Liechtib4cda3a2015-08-08 17:12:08 +02001316 s.write(b"hello\n")
cliechti8099bed2009-08-01 23:59:18 +00001317 s.flush()
Chris Liechti1f6643d2016-02-16 21:06:52 +01001318 sys.stdout.write("read: {}\n".format(s.read(5)))
cliechti8099bed2009-08-01 23:59:18 +00001319 s.close()