blob: e56eacfd4faf83d3691e980654b164461d91a492 [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
202PURGE_BOTH_BUFFERS = b'\x03' # Purge both the access server receive data buffer and the access server transmit data buffer
cliechti8099bed2009-08-01 23:59:18 +0000203
204
205RFC2217_PARITY_MAP = {
Chris Liechti033f17c2015-08-30 21:28:04 +0200206 serial.PARITY_NONE: 1,
207 serial.PARITY_ODD: 2,
208 serial.PARITY_EVEN: 3,
209 serial.PARITY_MARK: 4,
210 serial.PARITY_SPACE: 5,
cliechti8099bed2009-08-01 23:59:18 +0000211}
Chris Liechti033f17c2015-08-30 21:28:04 +0200212RFC2217_REVERSE_PARITY_MAP = dict((v, k) for k, v in RFC2217_PARITY_MAP.items())
cliechti8099bed2009-08-01 23:59:18 +0000213
214RFC2217_STOPBIT_MAP = {
Chris Liechti033f17c2015-08-30 21:28:04 +0200215 serial.STOPBITS_ONE: 1,
216 serial.STOPBITS_ONE_POINT_FIVE: 3,
217 serial.STOPBITS_TWO: 2,
cliechti8099bed2009-08-01 23:59:18 +0000218}
Chris Liechti033f17c2015-08-30 21:28:04 +0200219RFC2217_REVERSE_STOPBIT_MAP = dict((v, k) for k, v in RFC2217_STOPBIT_MAP.items())
cliechti8099bed2009-08-01 23:59:18 +0000220
cliechti130d1f02009-08-04 02:10:58 +0000221# Telnet filter states
222M_NORMAL = 0
223M_IAC_SEEN = 1
224M_NEGOTIATE = 2
cliechti8099bed2009-08-01 23:59:18 +0000225
cliechti130d1f02009-08-04 02:10:58 +0000226# TelnetOption and TelnetSubnegotiation states
cliechtiac205322009-08-02 20:40:21 +0000227REQUESTED = 'REQUESTED'
228ACTIVE = 'ACTIVE'
229INACTIVE = 'INACTIVE'
230REALLY_INACTIVE = 'REALLY_INACTIVE'
231
Chris Liechti033f17c2015-08-30 21:28:04 +0200232
cliechtiac205322009-08-02 20:40:21 +0000233class TelnetOption(object):
cliechti1ef7e3e2009-08-03 02:38:43 +0000234 """Manage a single telnet option, keeps track of DO/DONT WILL/WONT."""
235
cliechti86b593e2009-08-05 16:28:12 +0000236 def __init__(self, connection, name, option, send_yes, send_no, ack_yes, ack_no, initial_state, activation_callback=None):
cliechtieada4fd2013-07-31 16:26:07 +0000237 """\
238 Initialize option.
cliechti1ef7e3e2009-08-03 02:38:43 +0000239 :param connection: connection used to transmit answers
240 :param name: a readable name for debug outputs
241 :param send_yes: what to send when option is to be enabled.
242 :param send_no: what to send when option is to be disabled.
243 :param ack_yes: what to expect when remote agrees on option.
244 :param ack_no: what to expect when remote disagrees on option.
245 :param initial_state: options initialized with REQUESTED are tried to
246 be enabled on startup. use INACTIVE for all others.
247 """
cliechti2b929b72009-08-02 23:49:02 +0000248 self.connection = connection
cliechtiac205322009-08-02 20:40:21 +0000249 self.name = name
250 self.option = option
251 self.send_yes = send_yes
252 self.send_no = send_no
253 self.ack_yes = ack_yes
254 self.ack_no = ack_no
255 self.state = initial_state
256 self.active = False
cliechti86b593e2009-08-05 16:28:12 +0000257 self.activation_callback = activation_callback
cliechtiac205322009-08-02 20:40:21 +0000258
259 def __repr__(self):
cliechti1ef7e3e2009-08-03 02:38:43 +0000260 """String for debug outputs"""
cliechtiac205322009-08-02 20:40:21 +0000261 return "%s:%s(%s)" % (self.name, self.active, self.state)
262
cliechti2b929b72009-08-02 23:49:02 +0000263 def process_incoming(self, command):
cliechti7d448562014-08-03 21:57:45 +0000264 """\
265 A DO/DONT/WILL/WONT was received for this option, update state and
266 answer when needed.
267 """
cliechtiac205322009-08-02 20:40:21 +0000268 if command == self.ack_yes:
269 if self.state is REQUESTED:
270 self.state = ACTIVE
271 self.active = True
cliechti86b593e2009-08-05 16:28:12 +0000272 if self.activation_callback is not None:
273 self.activation_callback()
cliechtiac205322009-08-02 20:40:21 +0000274 elif self.state is ACTIVE:
275 pass
276 elif self.state is INACTIVE:
277 self.state = ACTIVE
cliechti1ef7e3e2009-08-03 02:38:43 +0000278 self.connection.telnetSendOption(self.send_yes, self.option)
cliechtiac205322009-08-02 20:40:21 +0000279 self.active = True
cliechti86b593e2009-08-05 16:28:12 +0000280 if self.activation_callback is not None:
281 self.activation_callback()
cliechtiac205322009-08-02 20:40:21 +0000282 elif self.state is REALLY_INACTIVE:
cliechti1ef7e3e2009-08-03 02:38:43 +0000283 self.connection.telnetSendOption(self.send_no, self.option)
cliechtiac205322009-08-02 20:40:21 +0000284 else:
285 raise ValueError('option in illegal state %r' % self)
286 elif command == self.ack_no:
287 if self.state is REQUESTED:
288 self.state = INACTIVE
289 self.active = False
290 elif self.state is ACTIVE:
291 self.state = INACTIVE
cliechti1ef7e3e2009-08-03 02:38:43 +0000292 self.connection.telnetSendOption(self.send_no, self.option)
cliechtiac205322009-08-02 20:40:21 +0000293 self.active = False
294 elif self.state is INACTIVE:
295 pass
296 elif self.state is REALLY_INACTIVE:
297 pass
298 else:
299 raise ValueError('option in illegal state %r' % self)
300
301
cliechti2b929b72009-08-02 23:49:02 +0000302class TelnetSubnegotiation(object):
cliechtieada4fd2013-07-31 16:26:07 +0000303 """\
304 A object to handle subnegotiation of options. In this case actually
305 sub-sub options for RFC 2217. It is used to track com port options.
306 """
cliechti2b929b72009-08-02 23:49:02 +0000307
308 def __init__(self, connection, name, option, ack_option=None):
Chris Liechti033f17c2015-08-30 21:28:04 +0200309 if ack_option is None:
310 ack_option = option
cliechti2b929b72009-08-02 23:49:02 +0000311 self.connection = connection
312 self.name = name
313 self.option = option
314 self.value = None
315 self.ack_option = ack_option
316 self.state = INACTIVE
317
318 def __repr__(self):
cliechti044d8662009-08-11 21:40:31 +0000319 """String for debug outputs."""
cliechti2b929b72009-08-02 23:49:02 +0000320 return "%s:%s" % (self.name, self.state)
321
322 def set(self, value):
cliechtieada4fd2013-07-31 16:26:07 +0000323 """\
cliechti7d448562014-08-03 21:57:45 +0000324 Request a change of the value. a request is sent to the server. if
cliechti2b929b72009-08-02 23:49:02 +0000325 the client needs to know if the change is performed he has to check the
cliechtieada4fd2013-07-31 16:26:07 +0000326 state of this object.
327 """
cliechti2b929b72009-08-02 23:49:02 +0000328 self.value = value
329 self.state = REQUESTED
cliechti1ef7e3e2009-08-03 02:38:43 +0000330 self.connection.rfc2217SendSubnegotiation(self.option, self.value)
cliechti6a300772009-08-12 02:28:56 +0000331 if self.connection.logger:
332 self.connection.logger.debug("SB Requesting %s -> %r" % (self.name, self.value))
cliechti2b929b72009-08-02 23:49:02 +0000333
334 def isReady(self):
cliechtieada4fd2013-07-31 16:26:07 +0000335 """\
cliechti7d448562014-08-03 21:57:45 +0000336 Check if answer from server has been received. when server rejects
cliechtieada4fd2013-07-31 16:26:07 +0000337 the change, raise a ValueError.
338 """
cliechti2b929b72009-08-02 23:49:02 +0000339 if self.state == REALLY_INACTIVE:
340 raise ValueError("remote rejected value for option %r" % (self.name))
341 return self.state == ACTIVE
cliechti1ef7e3e2009-08-03 02:38:43 +0000342 # add property to have a similar interface as TelnetOption
cliechti2b929b72009-08-02 23:49:02 +0000343 active = property(isReady)
344
cliechti044d8662009-08-11 21:40:31 +0000345 def wait(self, timeout=3):
cliechtieada4fd2013-07-31 16:26:07 +0000346 """\
cliechti7d448562014-08-03 21:57:45 +0000347 Wait until the subnegotiation has been acknowledged or timeout. It
cliechti1ef7e3e2009-08-03 02:38:43 +0000348 can also throw a value error when the answer from the server does not
cliechtieada4fd2013-07-31 16:26:07 +0000349 match the value sent.
350 """
cliechti044d8662009-08-11 21:40:31 +0000351 timeout_time = time.time() + timeout
cliechti2b929b72009-08-02 23:49:02 +0000352 while time.time() < timeout_time:
353 time.sleep(0.05) # prevent 100% CPU load
354 if self.isReady():
355 break
356 else:
357 raise SerialException("timeout while waiting for option %r" % (self.name))
358
359 def checkAnswer(self, suboption):
cliechtieada4fd2013-07-31 16:26:07 +0000360 """\
cliechti7d448562014-08-03 21:57:45 +0000361 Check an incoming subnegotiation block. The parameter already has
cliechtieada4fd2013-07-31 16:26:07 +0000362 cut off the header like sub option number and com port option value.
363 """
cliechti2b929b72009-08-02 23:49:02 +0000364 if self.value == suboption[:len(self.value)]:
365 self.state = ACTIVE
366 else:
367 # error propagation done in isReady
368 self.state = REALLY_INACTIVE
cliechti6a300772009-08-12 02:28:56 +0000369 if self.connection.logger:
370 self.connection.logger.debug("SB Answer %s -> %r -> %s" % (self.name, suboption, self.state))
cliechti2b929b72009-08-02 23:49:02 +0000371
372
Chris Liechtief6b7b42015-08-06 22:19:26 +0200373class Serial(SerialBase):
cliechti044d8662009-08-11 21:40:31 +0000374 """Serial port implementation for RFC 2217 remote serial ports."""
cliechti8099bed2009-08-01 23:59:18 +0000375
376 BAUDRATES = (50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800,
377 9600, 19200, 38400, 57600, 115200)
378
Chris Liechtibb5341b2015-10-31 23:31:26 +0100379 def __init__(self, *args, **kwargs):
380 super(Serial, self).__init__(*args, **kwargs)
381 self._thread = None
382 self._socket = None
383
cliechti8099bed2009-08-01 23:59:18 +0000384 def open(self):
cliechtieada4fd2013-07-31 16:26:07 +0000385 """\
386 Open port with current settings. This may throw a SerialException
387 if the port cannot be opened.
388 """
cliechti6a300772009-08-12 02:28:56 +0000389 self.logger = None
cliechti81c54762009-08-03 23:53:27 +0000390 self._ignore_set_control_answer = False
cliechti7cb78e82009-08-05 15:47:57 +0000391 self._poll_modem_state = False
cliechtidfe2d272009-08-10 22:19:41 +0000392 self._network_timeout = 3
cliechti8099bed2009-08-01 23:59:18 +0000393 if self._port is None:
394 raise SerialException("Port must be configured before it can be used.")
Chris Liechti3ad62fb2015-08-29 21:53:32 +0200395 if self.is_open:
cliechti8f69e702011-03-19 00:22:32 +0000396 raise SerialException("Port is already open.")
cliechti8099bed2009-08-01 23:59:18 +0000397 try:
Chris Liechti3ad62fb2015-08-29 21:53:32 +0200398 self._socket = socket.create_connection(self.from_url(self.portstr))
cliechti6a300772009-08-12 02:28:56 +0000399 self._socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
Chris Liechti68340d72015-08-03 14:15:48 +0200400 except Exception as msg:
cliechti8099bed2009-08-01 23:59:18 +0000401 self._socket = None
402 raise SerialException("Could not open port %s: %s" % (self.portstr, msg))
403
Chris Liechti033f17c2015-08-30 21:28:04 +0200404 self._socket.settimeout(5) # XXX good value?
cliechti8099bed2009-08-01 23:59:18 +0000405
cliechti1ef7e3e2009-08-03 02:38:43 +0000406 # use a thread save queue as buffer. it also simplifies implementing
407 # the read timeout
cliechti8099bed2009-08-01 23:59:18 +0000408 self._read_buffer = Queue.Queue()
cliechti81c54762009-08-03 23:53:27 +0000409 # to ensure that user writes does not interfere with internal
410 # telnet/rfc2217 options establish a lock
411 self._write_lock = threading.Lock()
cliechtiac205322009-08-02 20:40:21 +0000412 # name the following separately so that, below, a check can be easily done
413 mandadory_options = [
cliechti2b929b72009-08-02 23:49:02 +0000414 TelnetOption(self, 'we-BINARY', BINARY, WILL, WONT, DO, DONT, INACTIVE),
cliechti2b929b72009-08-02 23:49:02 +0000415 TelnetOption(self, 'we-RFC2217', COM_PORT_OPTION, WILL, WONT, DO, DONT, REQUESTED),
cliechtiac205322009-08-02 20:40:21 +0000416 ]
417 # all supported telnet options
418 self._telnet_options = [
cliechtia29275e2009-08-03 00:08:04 +0000419 TelnetOption(self, 'ECHO', ECHO, DO, DONT, WILL, WONT, REQUESTED),
cliechti2b929b72009-08-02 23:49:02 +0000420 TelnetOption(self, 'we-SGA', SGA, WILL, WONT, DO, DONT, REQUESTED),
421 TelnetOption(self, 'they-SGA', SGA, DO, DONT, WILL, WONT, REQUESTED),
cliechti81c54762009-08-03 23:53:27 +0000422 TelnetOption(self, 'they-BINARY', BINARY, DO, DONT, WILL, WONT, INACTIVE),
423 TelnetOption(self, 'they-RFC2217', COM_PORT_OPTION, DO, DONT, WILL, WONT, REQUESTED),
cliechtiac205322009-08-02 20:40:21 +0000424 ] + mandadory_options
cliechti044d8662009-08-11 21:40:31 +0000425 # RFC 2217 specific states
cliechti2b929b72009-08-02 23:49:02 +0000426 # COM port settings
427 self._rfc2217_port_settings = {
428 'baudrate': TelnetSubnegotiation(self, 'baudrate', SET_BAUDRATE, SERVER_SET_BAUDRATE),
429 'datasize': TelnetSubnegotiation(self, 'datasize', SET_DATASIZE, SERVER_SET_DATASIZE),
430 'parity': TelnetSubnegotiation(self, 'parity', SET_PARITY, SERVER_SET_PARITY),
431 'stopsize': TelnetSubnegotiation(self, 'stopsize', SET_STOPSIZE, SERVER_SET_STOPSIZE),
Chris Liechticb6ce1b2016-02-02 01:53:56 +0100432 }
cliechticb20a4f2011-04-25 02:25:54 +0000433 # There are more subnegotiation objects, combine all in one dictionary
cliechti2b929b72009-08-02 23:49:02 +0000434 # for easy access
435 self._rfc2217_options = {
436 'purge': TelnetSubnegotiation(self, 'purge', PURGE_DATA, SERVER_PURGE_DATA),
cliechti81c54762009-08-03 23:53:27 +0000437 'control': TelnetSubnegotiation(self, 'control', SET_CONTROL, SERVER_SET_CONTROL),
Chris Liechticb6ce1b2016-02-02 01:53:56 +0100438 }
cliechti2b929b72009-08-02 23:49:02 +0000439 self._rfc2217_options.update(self._rfc2217_port_settings)
440 # cache for line and modem states that the server sends to us
cliechti8099bed2009-08-01 23:59:18 +0000441 self._linestate = 0
cliechti7cb78e82009-08-05 15:47:57 +0000442 self._modemstate = None
443 self._modemstate_expires = 0
cliechti044d8662009-08-11 21:40:31 +0000444 # RFC 2217 flow control between server and client
cliechti672d0292009-08-03 02:01:57 +0000445 self._remote_suspend_flow = False
cliechti8099bed2009-08-01 23:59:18 +0000446
Chris Liechti39d52172015-10-25 22:53:51 +0100447 self.is_open = True
cliechti1ef7e3e2009-08-03 02:38:43 +0000448 self._thread = threading.Thread(target=self._telnetReadLoop)
cliechti8099bed2009-08-01 23:59:18 +0000449 self._thread.setDaemon(True)
cliechti5cc3eb12009-08-11 23:04:30 +0000450 self._thread.setName('pySerial RFC 2217 reader thread for %s' % (self._port,))
cliechti8099bed2009-08-01 23:59:18 +0000451 self._thread.start()
452
Chris Liechti39d52172015-10-25 22:53:51 +0100453 try: # must clean-up if open fails
454 # negotiate Telnet/RFC 2217 -> send initial requests
455 for option in self._telnet_options:
456 if option.state is REQUESTED:
457 self.telnetSendOption(option.send_yes, option.option)
458 # now wait until important options are negotiated
459 timeout_time = time.time() + self._network_timeout
460 while time.time() < timeout_time:
461 time.sleep(0.05) # prevent 100% CPU load
462 if sum(o.active for o in mandadory_options) == sum(o.state != INACTIVE for o in mandadory_options):
463 break
464 else:
465 raise SerialException("Remote does not seem to support RFC2217 or BINARY mode %r" % mandadory_options)
466 if self.logger:
467 self.logger.info("Negotiated options: %s" % self._telnet_options)
cliechti8099bed2009-08-01 23:59:18 +0000468
Chris Liechti39d52172015-10-25 22:53:51 +0100469 # fine, go on, set RFC 2271 specific things
470 self._reconfigure_port()
471 # all things set up get, now a clean start
472 if not self._dsrdtr:
473 self._update_dtr_state()
474 if not self._rtscts:
475 self._update_rts_state()
476 self.reset_input_buffer()
477 self.reset_output_buffer()
478 except:
479 self.close()
480 raise
cliechti8099bed2009-08-01 23:59:18 +0000481
Chris Liechti3ad62fb2015-08-29 21:53:32 +0200482 def _reconfigure_port(self):
cliechti8099bed2009-08-01 23:59:18 +0000483 """Set communication parameters on opened port."""
484 if self._socket is None:
485 raise SerialException("Can only operate on open ports")
486
cliechti8099bed2009-08-01 23:59:18 +0000487 # if self._timeout != 0 and self._interCharTimeout is not None:
cliechti8099bed2009-08-01 23:59:18 +0000488 # XXX
489
Chris Liechti3ad62fb2015-08-29 21:53:32 +0200490 if self._write_timeout is not None:
491 raise NotImplementedError('write_timeout is currently not supported')
cliechti2b929b72009-08-02 23:49:02 +0000492 # XXX
cliechti8099bed2009-08-01 23:59:18 +0000493
cliechti2b929b72009-08-02 23:49:02 +0000494 # Setup the connection
cliechti1ef7e3e2009-08-03 02:38:43 +0000495 # to get good performance, all parameter changes are sent first...
Chris Liechti01587b12015-08-05 02:39:32 +0200496 if not 0 < self._baudrate < 2**32:
cliechti81c54762009-08-03 23:53:27 +0000497 raise ValueError("invalid baudrate: %r" % (self._baudrate))
Chris Liechti01587b12015-08-05 02:39:32 +0200498 self._rfc2217_port_settings['baudrate'].set(struct.pack(b'!I', self._baudrate))
499 self._rfc2217_port_settings['datasize'].set(struct.pack(b'!B', self._bytesize))
500 self._rfc2217_port_settings['parity'].set(struct.pack(b'!B', RFC2217_PARITY_MAP[self._parity]))
501 self._rfc2217_port_settings['stopsize'].set(struct.pack(b'!B', RFC2217_STOPBIT_MAP[self._stopbits]))
cliechti8099bed2009-08-01 23:59:18 +0000502
cliechti2b929b72009-08-02 23:49:02 +0000503 # and now wait until parameters are active
504 items = self._rfc2217_port_settings.values()
cliechti6a300772009-08-12 02:28:56 +0000505 if self.logger:
506 self.logger.debug("Negotiating settings: %s" % (items,))
cliechtidfe2d272009-08-10 22:19:41 +0000507 timeout_time = time.time() + self._network_timeout
cliechti2b929b72009-08-02 23:49:02 +0000508 while time.time() < timeout_time:
509 time.sleep(0.05) # prevent 100% CPU load
510 if sum(o.active for o in items) == len(items):
511 break
512 else:
513 raise SerialException("Remote does not accept parameter change (RFC2217): %r" % items)
cliechti6a300772009-08-12 02:28:56 +0000514 if self.logger:
515 self.logger.info("Negotiated settings: %s" % (items,))
cliechti8099bed2009-08-01 23:59:18 +0000516
517 if self._rtscts and self._xonxoff:
cliechti1ef7e3e2009-08-03 02:38:43 +0000518 raise ValueError('xonxoff and rtscts together are not supported')
cliechti8099bed2009-08-01 23:59:18 +0000519 elif self._rtscts:
cliechti1ef7e3e2009-08-03 02:38:43 +0000520 self.rfc2217SetControl(SET_CONTROL_USE_HW_FLOW_CONTROL)
cliechti8099bed2009-08-01 23:59:18 +0000521 elif self._xonxoff:
cliechti1ef7e3e2009-08-03 02:38:43 +0000522 self.rfc2217SetControl(SET_CONTROL_USE_SW_FLOW_CONTROL)
cliechti8099bed2009-08-01 23:59:18 +0000523 else:
cliechti1ef7e3e2009-08-03 02:38:43 +0000524 self.rfc2217SetControl(SET_CONTROL_USE_NO_FLOW_CONTROL)
cliechti8099bed2009-08-01 23:59:18 +0000525
526 def close(self):
527 """Close port"""
Chris Liechti39d52172015-10-25 22:53:51 +0100528 self.is_open = False
529 if self._socket:
530 try:
531 self._socket.shutdown(socket.SHUT_RDWR)
532 self._socket.close()
533 except:
534 # ignore errors.
535 pass
536 if self._thread:
537 self._thread.join(7) # XXX more than socket timeout
538 self._thread = None
cliechti8099bed2009-08-01 23:59:18 +0000539 # in case of quick reconnects, give the server some time
540 time.sleep(0.3)
Chris Liechti39d52172015-10-25 22:53:51 +0100541 self._socket = None
cliechti8099bed2009-08-01 23:59:18 +0000542
Chris Liechti3ad62fb2015-08-29 21:53:32 +0200543 def from_url(self, url):
cliechti1ef7e3e2009-08-03 02:38:43 +0000544 """extract host and port from an URL string"""
Chris Liechtia4222112015-08-07 01:03:12 +0200545 parts = urlparse.urlsplit(url)
Chris Liechtid14b1ab2015-08-21 00:28:53 +0200546 if parts.scheme != "rfc2217":
547 raise SerialException('expected a string in the form "rfc2217://<host>:<port>[?option[&option...]]": not starting with rfc2217:// (%r)' % (parts.scheme,))
cliechti8099bed2009-08-01 23:59:18 +0000548 try:
Chris Liechtia4222112015-08-07 01:03:12 +0200549 # process options now, directly altering self
Chris Liechtid14b1ab2015-08-21 00:28:53 +0200550 for option, values in urlparse.parse_qs(parts.query, True).items():
551 if option == 'logging':
Chris Liechtia4222112015-08-07 01:03:12 +0200552 logging.basicConfig() # XXX is that good to call it here?
553 self.logger = logging.getLogger('pySerial.rfc2217')
Chris Liechtid14b1ab2015-08-21 00:28:53 +0200554 self.logger.setLevel(LOGGER_LEVELS[values[0]])
Chris Liechtia4222112015-08-07 01:03:12 +0200555 self.logger.debug('enabled logging')
556 elif option == 'ign_set_control':
557 self._ignore_set_control_answer = True
558 elif option == 'poll_modem':
559 self._poll_modem_state = True
560 elif option == 'timeout':
Chris Liechtid14b1ab2015-08-21 00:28:53 +0200561 self._network_timeout = float(values[0])
Chris Liechtia4222112015-08-07 01:03:12 +0200562 else:
563 raise ValueError('unknown option: %r' % (option,))
cliechti81c54762009-08-03 23:53:27 +0000564 # get host and port
Chris Liechtia4222112015-08-07 01:03:12 +0200565 host, port = parts.hostname, parts.port
Chris Liechti033f17c2015-08-30 21:28:04 +0200566 if not 0 <= port < 65536:
567 raise ValueError("port not in range 0...65535")
Chris Liechti68340d72015-08-03 14:15:48 +0200568 except ValueError as e:
Chris Liechtid14b1ab2015-08-21 00:28:53 +0200569 raise SerialException('expected a string in the form "rfc2217://<host>:<port>[?option[&option...]]": %s' % e)
cliechti8099bed2009-08-01 23:59:18 +0000570 return (host, port)
571
572 # - - - - - - - - - - - - - - - - - - - - - - - -
573
Chris Liechtief1fe252015-08-27 23:25:21 +0200574 @property
575 def in_waiting(self):
Chris Liechti3ad62fb2015-08-29 21:53:32 +0200576 """Return the number of bytes currently in the input buffer."""
Chris Liechti033f17c2015-08-30 21:28:04 +0200577 if not self.is_open:
578 raise portNotOpenError
cliechti8099bed2009-08-01 23:59:18 +0000579 return self._read_buffer.qsize()
580
581 def read(self, size=1):
cliechtieada4fd2013-07-31 16:26:07 +0000582 """\
583 Read size bytes from the serial port. If a timeout is set it may
cliechti8099bed2009-08-01 23:59:18 +0000584 return less characters as requested. With no timeout it will block
cliechtieada4fd2013-07-31 16:26:07 +0000585 until the requested number of bytes is read.
586 """
Chris Liechti033f17c2015-08-30 21:28:04 +0200587 if not self.is_open:
588 raise portNotOpenError
cliechti8099bed2009-08-01 23:59:18 +0000589 data = bytearray()
590 try:
591 while len(data) < size:
cliechti81c54762009-08-03 23:53:27 +0000592 if self._thread is None:
593 raise SerialException('connection failed (reader thread died)')
Chris Liechti01587b12015-08-05 02:39:32 +0200594 data += self._read_buffer.get(True, self._timeout)
Chris Liechti033f17c2015-08-30 21:28:04 +0200595 except Queue.Empty: # -> timeout
cliechti8099bed2009-08-01 23:59:18 +0000596 pass
597 return bytes(data)
598
599 def write(self, data):
cliechtieada4fd2013-07-31 16:26:07 +0000600 """\
Chris Liechti3ad62fb2015-08-29 21:53:32 +0200601 Output the given byte string over the serial port. Can block if the
cliechti8099bed2009-08-01 23:59:18 +0000602 connection is blocked. May raise SerialException if the connection is
cliechtieada4fd2013-07-31 16:26:07 +0000603 closed.
604 """
Chris Liechti033f17c2015-08-30 21:28:04 +0200605 if not self.is_open:
606 raise portNotOpenError
Chris Liechti01587b12015-08-05 02:39:32 +0200607 with self._write_lock:
cliechti81c54762009-08-03 23:53:27 +0000608 try:
cliechti38077122013-10-16 02:57:27 +0000609 self._socket.sendall(to_bytes(data).replace(IAC, IAC_DOUBLED))
Chris Liechti68340d72015-08-03 14:15:48 +0200610 except socket.error as e:
Chris Liechti142ae562015-08-23 01:11:06 +0200611 raise SerialException("connection failed (socket error): %s" % (e,))
cliechti8099bed2009-08-01 23:59:18 +0000612 return len(data)
613
Chris Liechtief1fe252015-08-27 23:25:21 +0200614 def reset_input_buffer(self):
cliechti8099bed2009-08-01 23:59:18 +0000615 """Clear input buffer, discarding all that is in the buffer."""
Chris Liechti033f17c2015-08-30 21:28:04 +0200616 if not self.is_open:
617 raise portNotOpenError
cliechti1ef7e3e2009-08-03 02:38:43 +0000618 self.rfc2217SendPurge(PURGE_RECEIVE_BUFFER)
cliechti8099bed2009-08-01 23:59:18 +0000619 # empty read buffer
620 while self._read_buffer.qsize():
621 self._read_buffer.get(False)
622
Chris Liechtief1fe252015-08-27 23:25:21 +0200623 def reset_output_buffer(self):
cliechtieada4fd2013-07-31 16:26:07 +0000624 """\
625 Clear output buffer, aborting the current output and
626 discarding all that is in the buffer.
627 """
Chris Liechti033f17c2015-08-30 21:28:04 +0200628 if not self.is_open:
629 raise portNotOpenError
cliechti1ef7e3e2009-08-03 02:38:43 +0000630 self.rfc2217SendPurge(PURGE_TRANSMIT_BUFFER)
cliechti8099bed2009-08-01 23:59:18 +0000631
Chris Liechtief1fe252015-08-27 23:25:21 +0200632 def _update_break_state(self):
cliechtieada4fd2013-07-31 16:26:07 +0000633 """\
634 Set break: Controls TXD. When active, to transmitting is
635 possible.
636 """
Chris Liechti033f17c2015-08-30 21:28:04 +0200637 if not self.is_open:
638 raise portNotOpenError
cliechti6a300772009-08-12 02:28:56 +0000639 if self.logger:
Chris Liechtief1fe252015-08-27 23:25:21 +0200640 self.logger.info('set BREAK to %s' % ('active' if self._break_state else 'inactive'))
641 if self._break_state:
cliechti1ef7e3e2009-08-03 02:38:43 +0000642 self.rfc2217SetControl(SET_CONTROL_BREAK_ON)
cliechti8099bed2009-08-01 23:59:18 +0000643 else:
cliechti1ef7e3e2009-08-03 02:38:43 +0000644 self.rfc2217SetControl(SET_CONTROL_BREAK_OFF)
cliechti8099bed2009-08-01 23:59:18 +0000645
Chris Liechtief1fe252015-08-27 23:25:21 +0200646 def _update_rts_state(self):
cliechti044d8662009-08-11 21:40:31 +0000647 """Set terminal status line: Request To Send."""
Chris Liechti033f17c2015-08-30 21:28:04 +0200648 if not self.is_open:
649 raise portNotOpenError
cliechti6a300772009-08-12 02:28:56 +0000650 if self.logger:
Chris Liechtief1fe252015-08-27 23:25:21 +0200651 self.logger.info('set RTS to %s' % ('active' if self._rts_state else 'inactive'))
652 if self._rts_state:
cliechti1ef7e3e2009-08-03 02:38:43 +0000653 self.rfc2217SetControl(SET_CONTROL_RTS_ON)
cliechti8099bed2009-08-01 23:59:18 +0000654 else:
cliechti1ef7e3e2009-08-03 02:38:43 +0000655 self.rfc2217SetControl(SET_CONTROL_RTS_OFF)
cliechti8099bed2009-08-01 23:59:18 +0000656
Chris Liechtief1fe252015-08-27 23:25:21 +0200657 def _update_dtr_state(self, level=True):
cliechti044d8662009-08-11 21:40:31 +0000658 """Set terminal status line: Data Terminal Ready."""
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 Liechtief1fe252015-08-27 23:25:21 +0200662 self.logger.info('set DTR to %s' % ('active' if self._dtr_state else 'inactive'))
663 if self._dtr_state:
cliechti1ef7e3e2009-08-03 02:38:43 +0000664 self.rfc2217SetControl(SET_CONTROL_DTR_ON)
cliechti8099bed2009-08-01 23:59:18 +0000665 else:
cliechti1ef7e3e2009-08-03 02:38:43 +0000666 self.rfc2217SetControl(SET_CONTROL_DTR_OFF)
cliechti8099bed2009-08-01 23:59:18 +0000667
Chris Liechtief1fe252015-08-27 23:25:21 +0200668 @property
669 def cts(self):
cliechti044d8662009-08-11 21:40:31 +0000670 """Read terminal status line: Clear To Send."""
Chris Liechti033f17c2015-08-30 21:28:04 +0200671 if not self.is_open:
672 raise portNotOpenError
cliechti7cb78e82009-08-05 15:47:57 +0000673 return bool(self.getModemState() & MODEMSTATE_MASK_CTS)
cliechti8099bed2009-08-01 23:59:18 +0000674
Chris Liechtief1fe252015-08-27 23:25:21 +0200675 @property
676 def dsr(self):
cliechti044d8662009-08-11 21:40:31 +0000677 """Read terminal status line: Data Set Ready."""
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_DSR)
cliechti8099bed2009-08-01 23:59:18 +0000681
Chris Liechtief1fe252015-08-27 23:25:21 +0200682 @property
683 def ri(self):
cliechti044d8662009-08-11 21:40:31 +0000684 """Read terminal status line: Ring Indicator."""
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_RI)
cliechti8099bed2009-08-01 23:59:18 +0000688
Chris Liechtief1fe252015-08-27 23:25:21 +0200689 @property
690 def cd(self):
cliechti044d8662009-08-11 21:40:31 +0000691 """Read terminal status line: Carrier Detect."""
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_CD)
cliechti8099bed2009-08-01 23:59:18 +0000695
696 # - - - platform specific - - -
697 # None so far
698
699 # - - - RFC2217 specific - - -
700
cliechti1ef7e3e2009-08-03 02:38:43 +0000701 def _telnetReadLoop(self):
cliechti7d448562014-08-03 21:57:45 +0000702 """Read loop for the socket."""
cliechti8099bed2009-08-01 23:59:18 +0000703 mode = M_NORMAL
704 suboption = None
cliechti81c54762009-08-03 23:53:27 +0000705 try:
Chris Liechti39d52172015-10-25 22:53:51 +0100706 while self.is_open:
cliechti81c54762009-08-03 23:53:27 +0000707 try:
708 data = self._socket.recv(1024)
709 except socket.timeout:
710 # just need to get out of recv form time to time to check if
711 # still alive
712 continue
Chris Liechti68340d72015-08-03 14:15:48 +0200713 except socket.error as e:
cliechti81c54762009-08-03 23:53:27 +0000714 # connection fails -> terminate loop
cliechticb20a4f2011-04-25 02:25:54 +0000715 if self.logger:
716 self.logger.debug("socket error in reader thread: %s" % (e,))
cliechti81c54762009-08-03 23:53:27 +0000717 break
Chris Liechti033f17c2015-08-30 21:28:04 +0200718 if not data:
719 break # lost connection
Chris Liechtif99cd5c2015-08-13 22:54:16 +0200720 for byte in iterbytes(data):
cliechti81c54762009-08-03 23:53:27 +0000721 if mode == M_NORMAL:
722 # interpret as command or as data
723 if byte == IAC:
724 mode = M_IAC_SEEN
cliechti8099bed2009-08-01 23:59:18 +0000725 else:
cliechti81c54762009-08-03 23:53:27 +0000726 # store data in read buffer or sub option buffer
727 # depending on state
728 if suboption is not None:
Chris Liechti01587b12015-08-05 02:39:32 +0200729 suboption += byte
cliechti81c54762009-08-03 23:53:27 +0000730 else:
731 self._read_buffer.put(byte)
732 elif mode == M_IAC_SEEN:
733 if byte == IAC:
734 # interpret as command doubled -> insert character
735 # itself
cliechtif325c032009-12-25 16:09:49 +0000736 if suboption is not None:
Chris Liechti01587b12015-08-05 02:39:32 +0200737 suboption += IAC
cliechtif325c032009-12-25 16:09:49 +0000738 else:
739 self._read_buffer.put(IAC)
cliechti81c54762009-08-03 23:53:27 +0000740 mode = M_NORMAL
741 elif byte == SB:
742 # sub option start
743 suboption = bytearray()
744 mode = M_NORMAL
745 elif byte == SE:
746 # sub option end -> process it now
747 self._telnetProcessSubnegotiation(bytes(suboption))
748 suboption = None
749 mode = M_NORMAL
750 elif byte in (DO, DONT, WILL, WONT):
751 # negotiation
752 telnet_command = byte
753 mode = M_NEGOTIATE
754 else:
755 # other telnet commands
756 self._telnetProcessCommand(byte)
757 mode = M_NORMAL
Chris Liechti033f17c2015-08-30 21:28:04 +0200758 elif mode == M_NEGOTIATE: # DO, DONT, WILL, WONT was received, option now following
cliechti81c54762009-08-03 23:53:27 +0000759 self._telnetNegotiateOption(telnet_command, byte)
cliechti8099bed2009-08-01 23:59:18 +0000760 mode = M_NORMAL
cliechti81c54762009-08-03 23:53:27 +0000761 finally:
762 self._thread = None
cliechti6a300772009-08-12 02:28:56 +0000763 if self.logger:
764 self.logger.debug("read thread terminated")
cliechti8099bed2009-08-01 23:59:18 +0000765
766 # - incoming telnet commands and options
767
cliechti1ef7e3e2009-08-03 02:38:43 +0000768 def _telnetProcessCommand(self, command):
cliechti044d8662009-08-11 21:40:31 +0000769 """Process commands other than DO, DONT, WILL, WONT."""
cliechti1ef7e3e2009-08-03 02:38:43 +0000770 # Currently none. RFC2217 only uses negotiation and subnegotiation.
cliechti6a300772009-08-12 02:28:56 +0000771 if self.logger:
772 self.logger.warning("ignoring Telnet command: %r" % (command,))
cliechti8099bed2009-08-01 23:59:18 +0000773
cliechti1ef7e3e2009-08-03 02:38:43 +0000774 def _telnetNegotiateOption(self, command, option):
cliechti044d8662009-08-11 21:40:31 +0000775 """Process incoming DO, DONT, WILL, WONT."""
cliechti2b929b72009-08-02 23:49:02 +0000776 # check our registered telnet options and forward command to them
777 # they know themselves if they have to answer or not
cliechtiac205322009-08-02 20:40:21 +0000778 known = False
779 for item in self._telnet_options:
cliechti2b929b72009-08-02 23:49:02 +0000780 # can have more than one match! as some options are duplicated for
781 # 'us' and 'them'
cliechtiac205322009-08-02 20:40:21 +0000782 if item.option == option:
cliechti2b929b72009-08-02 23:49:02 +0000783 item.process_incoming(command)
cliechtiac205322009-08-02 20:40:21 +0000784 known = True
785 if not known:
786 # handle unknown options
787 # only answer to positive requests and deny them
788 if command == WILL or command == DO:
Chris Liechti142ae562015-08-23 01:11:06 +0200789 self.telnetSendOption((DONT if command == WILL else WONT), option)
cliechti6a300772009-08-12 02:28:56 +0000790 if self.logger:
791 self.logger.warning("rejected Telnet option: %r" % (option,))
cliechtiac205322009-08-02 20:40:21 +0000792
cliechti1ef7e3e2009-08-03 02:38:43 +0000793 def _telnetProcessSubnegotiation(self, suboption):
cliechti044d8662009-08-11 21:40:31 +0000794 """Process subnegotiation, the data between IAC SB and IAC SE."""
cliechti8099bed2009-08-01 23:59:18 +0000795 if suboption[0:1] == COM_PORT_OPTION:
796 if suboption[1:2] == SERVER_NOTIFY_LINESTATE and len(suboption) >= 3:
Chris Liechti033f17c2015-08-30 21:28:04 +0200797 self._linestate = ord(suboption[2:3]) # ensure it is a number
cliechti6a300772009-08-12 02:28:56 +0000798 if self.logger:
799 self.logger.info("NOTIFY_LINESTATE: %s" % self._linestate)
cliechti8099bed2009-08-01 23:59:18 +0000800 elif suboption[1:2] == SERVER_NOTIFY_MODEMSTATE and len(suboption) >= 3:
Chris Liechti033f17c2015-08-30 21:28:04 +0200801 self._modemstate = ord(suboption[2:3]) # ensure it is a number
cliechti6a300772009-08-12 02:28:56 +0000802 if self.logger:
803 self.logger.info("NOTIFY_MODEMSTATE: %s" % self._modemstate)
cliechti7cb78e82009-08-05 15:47:57 +0000804 # update time when we think that a poll would make sense
805 self._modemstate_expires = time.time() + 0.3
cliechti672d0292009-08-03 02:01:57 +0000806 elif suboption[1:2] == FLOWCONTROL_SUSPEND:
807 self._remote_suspend_flow = True
808 elif suboption[1:2] == FLOWCONTROL_RESUME:
809 self._remote_suspend_flow = False
cliechti8099bed2009-08-01 23:59:18 +0000810 else:
cliechti2b929b72009-08-02 23:49:02 +0000811 for item in self._rfc2217_options.values():
812 if item.ack_option == suboption[1:2]:
cliechti81c54762009-08-03 23:53:27 +0000813 #~ print "processing COM_PORT_OPTION: %r" % list(suboption[1:])
cliechti2b929b72009-08-02 23:49:02 +0000814 item.checkAnswer(bytes(suboption[2:]))
815 break
816 else:
cliechti6a300772009-08-12 02:28:56 +0000817 if self.logger:
818 self.logger.warning("ignoring COM_PORT_OPTION: %r" % (suboption,))
cliechti8099bed2009-08-01 23:59:18 +0000819 else:
cliechti6a300772009-08-12 02:28:56 +0000820 if self.logger:
821 self.logger.warning("ignoring subnegotiation: %r" % (suboption,))
cliechti8099bed2009-08-01 23:59:18 +0000822
823 # - outgoing telnet commands and options
824
cliechti81c54762009-08-03 23:53:27 +0000825 def _internal_raw_write(self, data):
cliechti044d8662009-08-11 21:40:31 +0000826 """internal socket write with no data escaping. used to send telnet stuff."""
Chris Liechti01587b12015-08-05 02:39:32 +0200827 with self._write_lock:
cliechti81c54762009-08-03 23:53:27 +0000828 self._socket.sendall(data)
cliechti81c54762009-08-03 23:53:27 +0000829
cliechti1ef7e3e2009-08-03 02:38:43 +0000830 def telnetSendOption(self, action, option):
cliechti044d8662009-08-11 21:40:31 +0000831 """Send DO, DONT, WILL, WONT."""
cliechti81c54762009-08-03 23:53:27 +0000832 self._internal_raw_write(to_bytes([IAC, action, option]))
cliechti8099bed2009-08-01 23:59:18 +0000833
Chris Liechtib4cda3a2015-08-08 17:12:08 +0200834 def rfc2217SendSubnegotiation(self, option, value=b''):
cliechti044d8662009-08-11 21:40:31 +0000835 """Subnegotiation of RFC2217 parameters."""
cliechtif325c032009-12-25 16:09:49 +0000836 value = value.replace(IAC, IAC_DOUBLED)
cliechti81c54762009-08-03 23:53:27 +0000837 self._internal_raw_write(to_bytes([IAC, SB, COM_PORT_OPTION, option] + list(value) + [IAC, SE]))
cliechti2b929b72009-08-02 23:49:02 +0000838
cliechti1ef7e3e2009-08-03 02:38:43 +0000839 def rfc2217SendPurge(self, value):
cliechti2b929b72009-08-02 23:49:02 +0000840 item = self._rfc2217_options['purge']
Chris Liechti033f17c2015-08-30 21:28:04 +0200841 item.set(value) # transmit desired purge type
842 item.wait(self._network_timeout) # wait for acknowledge from the server
cliechti2b929b72009-08-02 23:49:02 +0000843
cliechti1ef7e3e2009-08-03 02:38:43 +0000844 def rfc2217SetControl(self, value):
cliechti81c54762009-08-03 23:53:27 +0000845 item = self._rfc2217_options['control']
Chris Liechti033f17c2015-08-30 21:28:04 +0200846 item.set(value) # transmit desired control type
cliechti81c54762009-08-03 23:53:27 +0000847 if self._ignore_set_control_answer:
848 # answers are ignored when option is set. compatibility mode for
cliechticb20a4f2011-04-25 02:25:54 +0000849 # servers that answer, but not the expected one... (or no answer
cliechti81c54762009-08-03 23:53:27 +0000850 # at all) i.e. sredird
851 time.sleep(0.1) # this helps getting the unit tests passed
852 else:
cliechtidfe2d272009-08-10 22:19:41 +0000853 item.wait(self._network_timeout) # wait for acknowledge from the server
cliechti8099bed2009-08-01 23:59:18 +0000854
cliechti1ef7e3e2009-08-03 02:38:43 +0000855 def rfc2217FlowServerReady(self):
cliechtieada4fd2013-07-31 16:26:07 +0000856 """\
857 check if server is ready to receive data. block for some time when
858 not.
859 """
cliechti672d0292009-08-03 02:01:57 +0000860 #~ if self._remote_suspend_flow:
861 #~ wait---
862
cliechti7cb78e82009-08-05 15:47:57 +0000863 def getModemState(self):
cliechtieada4fd2013-07-31 16:26:07 +0000864 """\
cliechti7d448562014-08-03 21:57:45 +0000865 get last modem state (cached value. If value is "old", request a new
866 one. This cache helps that we don't issue to many requests when e.g. all
867 status lines, one after the other is queried by the user (getCTS, getDSR
cliechtieada4fd2013-07-31 16:26:07 +0000868 etc.)
869 """
cliechti7cb78e82009-08-05 15:47:57 +0000870 # active modem state polling enabled? is the value fresh enough?
871 if self._poll_modem_state and self._modemstate_expires < time.time():
cliechti6a300772009-08-12 02:28:56 +0000872 if self.logger:
873 self.logger.debug('polling modem state')
cliechti7cb78e82009-08-05 15:47:57 +0000874 # when it is older, request an update
875 self.rfc2217SendSubnegotiation(NOTIFY_MODEMSTATE)
cliechtidfe2d272009-08-10 22:19:41 +0000876 timeout_time = time.time() + self._network_timeout
cliechti7cb78e82009-08-05 15:47:57 +0000877 while time.time() < timeout_time:
878 time.sleep(0.05) # prevent 100% CPU load
879 # when expiration time is updated, it means that there is a new
880 # value
881 if self._modemstate_expires > time.time():
882 break
Chris Liechti01587b12015-08-05 02:39:32 +0200883 else:
884 if self.logger:
885 self.logger.warning('poll for modem state failed')
cliechti7cb78e82009-08-05 15:47:57 +0000886 # even when there is a timeout, do not generate an error just
887 # return the last known value. this way we can support buggy
888 # servers that do not respond to polls, but send automatic
889 # updates.
890 if self._modemstate is not None:
cliechti6a300772009-08-12 02:28:56 +0000891 if self.logger:
892 self.logger.debug('using cached modem state')
cliechti7cb78e82009-08-05 15:47:57 +0000893 return self._modemstate
894 else:
895 # never received a notification from the server
cliechti8fb119c2009-08-05 23:39:45 +0000896 raise SerialException("remote sends no NOTIFY_MODEMSTATE")
cliechti8099bed2009-08-01 23:59:18 +0000897
cliechti5cc3eb12009-08-11 23:04:30 +0000898
cliechti595ed5b2009-08-10 01:43:32 +0000899#############################################################################
cliechti5cc3eb12009-08-11 23:04:30 +0000900# The following is code that helps implementing an RFC 2217 server.
cliechti8099bed2009-08-01 23:59:18 +0000901
cliechti8ccc2ff2009-08-05 12:44:46 +0000902class PortManager(object):
cliechtieada4fd2013-07-31 16:26:07 +0000903 """\
904 This class manages the state of Telnet and RFC 2217. It needs a serial
cliechticb20a4f2011-04-25 02:25:54 +0000905 instance and a connection to work with. Connection is expected to implement
cliechtieada4fd2013-07-31 16:26:07 +0000906 a (thread safe) write function, that writes the string to the network.
907 """
cliechti130d1f02009-08-04 02:10:58 +0000908
cliechti6a300772009-08-12 02:28:56 +0000909 def __init__(self, serial_port, connection, logger=None):
cliechti130d1f02009-08-04 02:10:58 +0000910 self.serial = serial_port
911 self.connection = connection
cliechti6a300772009-08-12 02:28:56 +0000912 self.logger = logger
cliechti86b593e2009-08-05 16:28:12 +0000913 self._client_is_rfc2217 = False
cliechti130d1f02009-08-04 02:10:58 +0000914
915 # filter state machine
916 self.mode = M_NORMAL
917 self.suboption = None
918 self.telnet_command = None
919
920 # states for modem/line control events
921 self.modemstate_mask = 255
922 self.last_modemstate = None
923 self.linstate_mask = 0
924
925 # all supported telnet options
926 self._telnet_options = [
927 TelnetOption(self, 'ECHO', ECHO, WILL, WONT, DO, DONT, REQUESTED),
928 TelnetOption(self, 'we-SGA', SGA, WILL, WONT, DO, DONT, REQUESTED),
929 TelnetOption(self, 'they-SGA', SGA, DO, DONT, WILL, WONT, INACTIVE),
930 TelnetOption(self, 'we-BINARY', BINARY, WILL, WONT, DO, DONT, INACTIVE),
931 TelnetOption(self, 'they-BINARY', BINARY, DO, DONT, WILL, WONT, REQUESTED),
cliechti86b593e2009-08-05 16:28:12 +0000932 TelnetOption(self, 'we-RFC2217', COM_PORT_OPTION, WILL, WONT, DO, DONT, REQUESTED, self._client_ok),
933 TelnetOption(self, 'they-RFC2217', COM_PORT_OPTION, DO, DONT, WILL, WONT, INACTIVE, self._client_ok),
Chris Liechticb6ce1b2016-02-02 01:53:56 +0100934 ]
cliechti130d1f02009-08-04 02:10:58 +0000935
936 # negotiate Telnet/RFC2217 -> send initial requests
cliechti6a300772009-08-12 02:28:56 +0000937 if self.logger:
938 self.logger.debug("requesting initial Telnet/RFC 2217 options")
cliechti130d1f02009-08-04 02:10:58 +0000939 for option in self._telnet_options:
940 if option.state is REQUESTED:
941 self.telnetSendOption(option.send_yes, option.option)
942 # issue 1st modem state notification
cliechti86b593e2009-08-05 16:28:12 +0000943
944 def _client_ok(self):
cliechtieada4fd2013-07-31 16:26:07 +0000945 """\
cliechti7d448562014-08-03 21:57:45 +0000946 callback of telnet option. It gets called when option is activated.
947 This one here is used to detect when the client agrees on RFC 2217. A
cliechti86b593e2009-08-05 16:28:12 +0000948 flag is set so that other functions like check_modem_lines know if the
cliechti7d448562014-08-03 21:57:45 +0000949 client is OK.
cliechtieada4fd2013-07-31 16:26:07 +0000950 """
cliechti86b593e2009-08-05 16:28:12 +0000951 # The callback is used for we and they so if one party agrees, we're
952 # already happy. it seems not all servers do the negotiation correctly
953 # and i guess there are incorrect clients too.. so be happy if client
954 # answers one or the other positively.
955 self._client_is_rfc2217 = True
cliechti6a300772009-08-12 02:28:56 +0000956 if self.logger:
957 self.logger.info("client accepts RFC 2217")
cliechti8fb119c2009-08-05 23:39:45 +0000958 # this is to ensure that the client gets a notification, even if there
959 # was no change
960 self.check_modem_lines(force_notification=True)
cliechti130d1f02009-08-04 02:10:58 +0000961
962 # - outgoing telnet commands and options
963
964 def telnetSendOption(self, action, option):
cliechti044d8662009-08-11 21:40:31 +0000965 """Send DO, DONT, WILL, WONT."""
cliechti130d1f02009-08-04 02:10:58 +0000966 self.connection.write(to_bytes([IAC, action, option]))
967
Chris Liechtib4cda3a2015-08-08 17:12:08 +0200968 def rfc2217SendSubnegotiation(self, option, value=b''):
cliechti044d8662009-08-11 21:40:31 +0000969 """Subnegotiation of RFC 2217 parameters."""
cliechtif325c032009-12-25 16:09:49 +0000970 value = value.replace(IAC, IAC_DOUBLED)
cliechti130d1f02009-08-04 02:10:58 +0000971 self.connection.write(to_bytes([IAC, SB, COM_PORT_OPTION, option] + list(value) + [IAC, SE]))
972
973 # - check modem lines, needs to be called periodically from user to
974 # establish polling
975
cliechti7cb78e82009-08-05 15:47:57 +0000976 def check_modem_lines(self, force_notification=False):
cliechti130d1f02009-08-04 02:10:58 +0000977 modemstate = (
Chris Liechti142ae562015-08-23 01:11:06 +0200978 (self.serial.getCTS() and MODEMSTATE_MASK_CTS) |
979 (self.serial.getDSR() and MODEMSTATE_MASK_DSR) |
980 (self.serial.getRI() and MODEMSTATE_MASK_RI) |
981 (self.serial.getCD() and MODEMSTATE_MASK_CD))
cliechti7cb78e82009-08-05 15:47:57 +0000982 # check what has changed
Chris Liechti033f17c2015-08-30 21:28:04 +0200983 deltas = modemstate ^ (self.last_modemstate or 0) # when last is None -> 0
cliechti7cb78e82009-08-05 15:47:57 +0000984 if deltas & MODEMSTATE_MASK_CTS:
985 modemstate |= MODEMSTATE_MASK_CTS_CHANGE
986 if deltas & MODEMSTATE_MASK_DSR:
987 modemstate |= MODEMSTATE_MASK_DSR_CHANGE
988 if deltas & MODEMSTATE_MASK_RI:
989 modemstate |= MODEMSTATE_MASK_RI_CHANGE
990 if deltas & MODEMSTATE_MASK_CD:
991 modemstate |= MODEMSTATE_MASK_CD_CHANGE
992 # if new state is different and the mask allows this change, send
cliechti86b593e2009-08-05 16:28:12 +0000993 # notification. suppress notifications when client is not rfc2217
cliechti7cb78e82009-08-05 15:47:57 +0000994 if modemstate != self.last_modemstate or force_notification:
cliechti8fb119c2009-08-05 23:39:45 +0000995 if (self._client_is_rfc2217 and (modemstate & self.modemstate_mask)) or force_notification:
cliechti7cb78e82009-08-05 15:47:57 +0000996 self.rfc2217SendSubnegotiation(
Chris Liechticb6ce1b2016-02-02 01:53:56 +0100997 SERVER_NOTIFY_MODEMSTATE,
998 to_bytes([modemstate & self.modemstate_mask]))
cliechti6a300772009-08-12 02:28:56 +0000999 if self.logger:
1000 self.logger.info("NOTIFY_MODEMSTATE: %s" % (modemstate,))
cliechti7cb78e82009-08-05 15:47:57 +00001001 # save last state, but forget about deltas.
1002 # otherwise it would also notify about changing deltas which is
1003 # probably not very useful
1004 self.last_modemstate = modemstate & 0xf0
cliechti130d1f02009-08-04 02:10:58 +00001005
cliechti32c10332009-08-05 13:23:43 +00001006 # - outgoing data escaping
1007
1008 def escape(self, data):
cliechtieada4fd2013-07-31 16:26:07 +00001009 """\
cliechti7d448562014-08-03 21:57:45 +00001010 This generator function is for the user. All outgoing data has to be
cliechticb20a4f2011-04-25 02:25:54 +00001011 properly escaped, so that no IAC character in the data stream messes up
1012 the Telnet state machine in the server.
cliechti32c10332009-08-05 13:23:43 +00001013
1014 socket.sendall(escape(data))
1015 """
Chris Liechtib15dc052015-10-19 23:17:16 +02001016 for byte in iterbytes(data):
cliechti32c10332009-08-05 13:23:43 +00001017 if byte == IAC:
1018 yield IAC
1019 yield IAC
1020 else:
1021 yield byte
1022
cliechti130d1f02009-08-04 02:10:58 +00001023 # - incoming data filter
1024
1025 def filter(self, data):
cliechtieada4fd2013-07-31 16:26:07 +00001026 """\
cliechti7d448562014-08-03 21:57:45 +00001027 Handle a bunch of incoming bytes. This is a generator. It will yield
cliechti044d8662009-08-11 21:40:31 +00001028 all characters not of interest for Telnet/RFC 2217.
cliechti130d1f02009-08-04 02:10:58 +00001029
1030 The idea is that the reader thread pushes data from the socket through
1031 this filter:
1032
1033 for byte in filter(socket.recv(1024)):
1034 # do things like CR/LF conversion/whatever
1035 # and write data to the serial port
1036 serial.write(byte)
1037
1038 (socket error handling code left as exercise for the reader)
1039 """
Chris Liechtif99cd5c2015-08-13 22:54:16 +02001040 for byte in iterbytes(data):
cliechti130d1f02009-08-04 02:10:58 +00001041 if self.mode == M_NORMAL:
1042 # interpret as command or as data
1043 if byte == IAC:
1044 self.mode = M_IAC_SEEN
1045 else:
1046 # store data in sub option buffer or pass it to our
1047 # consumer depending on state
1048 if self.suboption is not None:
Chris Liechti01587b12015-08-05 02:39:32 +02001049 self.suboption += byte
cliechti130d1f02009-08-04 02:10:58 +00001050 else:
1051 yield byte
1052 elif self.mode == M_IAC_SEEN:
1053 if byte == IAC:
1054 # interpret as command doubled -> insert character
1055 # itself
cliechtif325c032009-12-25 16:09:49 +00001056 if self.suboption is not None:
Chris Liechti01587b12015-08-05 02:39:32 +02001057 self.suboption += byte
cliechtif325c032009-12-25 16:09:49 +00001058 else:
1059 yield byte
cliechti130d1f02009-08-04 02:10:58 +00001060 self.mode = M_NORMAL
1061 elif byte == SB:
1062 # sub option start
1063 self.suboption = bytearray()
1064 self.mode = M_NORMAL
1065 elif byte == SE:
1066 # sub option end -> process it now
1067 self._telnetProcessSubnegotiation(bytes(self.suboption))
1068 self.suboption = None
1069 self.mode = M_NORMAL
1070 elif byte in (DO, DONT, WILL, WONT):
1071 # negotiation
1072 self.telnet_command = byte
1073 self.mode = M_NEGOTIATE
1074 else:
1075 # other telnet commands
1076 self._telnetProcessCommand(byte)
1077 self.mode = M_NORMAL
Chris Liechti033f17c2015-08-30 21:28:04 +02001078 elif self.mode == M_NEGOTIATE: # DO, DONT, WILL, WONT was received, option now following
cliechti130d1f02009-08-04 02:10:58 +00001079 self._telnetNegotiateOption(self.telnet_command, byte)
1080 self.mode = M_NORMAL
1081
1082 # - incoming telnet commands and options
1083
1084 def _telnetProcessCommand(self, command):
cliechti044d8662009-08-11 21:40:31 +00001085 """Process commands other than DO, DONT, WILL, WONT."""
cliechti130d1f02009-08-04 02:10:58 +00001086 # Currently none. RFC2217 only uses negotiation and subnegotiation.
cliechti6a300772009-08-12 02:28:56 +00001087 if self.logger:
1088 self.logger.warning("ignoring Telnet command: %r" % (command,))
cliechti130d1f02009-08-04 02:10:58 +00001089
1090 def _telnetNegotiateOption(self, command, option):
cliechti044d8662009-08-11 21:40:31 +00001091 """Process incoming DO, DONT, WILL, WONT."""
cliechti130d1f02009-08-04 02:10:58 +00001092 # check our registered telnet options and forward command to them
1093 # they know themselves if they have to answer or not
1094 known = False
1095 for item in self._telnet_options:
1096 # can have more than one match! as some options are duplicated for
1097 # 'us' and 'them'
1098 if item.option == option:
1099 item.process_incoming(command)
1100 known = True
1101 if not known:
1102 # handle unknown options
1103 # only answer to positive requests and deny them
1104 if command == WILL or command == DO:
Chris Liechti142ae562015-08-23 01:11:06 +02001105 self.telnetSendOption((DONT if command == WILL else WONT), option)
cliechti6a300772009-08-12 02:28:56 +00001106 if self.logger:
1107 self.logger.warning("rejected Telnet option: %r" % (option,))
cliechti130d1f02009-08-04 02:10:58 +00001108
cliechti130d1f02009-08-04 02:10:58 +00001109 def _telnetProcessSubnegotiation(self, suboption):
cliechti044d8662009-08-11 21:40:31 +00001110 """Process subnegotiation, the data between IAC SB and IAC SE."""
cliechti130d1f02009-08-04 02:10:58 +00001111 if suboption[0:1] == COM_PORT_OPTION:
cliechti6a300772009-08-12 02:28:56 +00001112 if self.logger:
1113 self.logger.debug('received COM_PORT_OPTION: %r' % (suboption,))
cliechti130d1f02009-08-04 02:10:58 +00001114 if suboption[1:2] == SET_BAUDRATE:
1115 backup = self.serial.baudrate
1116 try:
Chris Liechti01587b12015-08-05 02:39:32 +02001117 (baudrate,) = struct.unpack(b"!I", suboption[2:6])
cliechtieada4fd2013-07-31 16:26:07 +00001118 if baudrate != 0:
1119 self.serial.baudrate = baudrate
Chris Liechtid2146002015-08-04 16:57:16 +02001120 except ValueError as e:
cliechti6a300772009-08-12 02:28:56 +00001121 if self.logger:
1122 self.logger.error("failed to set baud rate: %s" % (e,))
cliechti130d1f02009-08-04 02:10:58 +00001123 self.serial.baudrate = backup
cliechti5cc3eb12009-08-11 23:04:30 +00001124 else:
cliechti6a300772009-08-12 02:28:56 +00001125 if self.logger:
Chris Liechti142ae562015-08-23 01:11:06 +02001126 self.logger.info("%s baud rate: %s" % ('set' if baudrate else 'get', self.serial.baudrate))
Chris Liechti01587b12015-08-05 02:39:32 +02001127 self.rfc2217SendSubnegotiation(SERVER_SET_BAUDRATE, struct.pack(b"!I", self.serial.baudrate))
cliechti130d1f02009-08-04 02:10:58 +00001128 elif suboption[1:2] == SET_DATASIZE:
1129 backup = self.serial.bytesize
1130 try:
Chris Liechti142ae562015-08-23 01:11:06 +02001131 (datasize,) = struct.unpack(b"!B", suboption[2:3])
cliechtieada4fd2013-07-31 16:26:07 +00001132 if datasize != 0:
1133 self.serial.bytesize = datasize
Chris Liechtid2146002015-08-04 16:57:16 +02001134 except ValueError as e:
cliechti6a300772009-08-12 02:28:56 +00001135 if self.logger:
1136 self.logger.error("failed to set data size: %s" % (e,))
cliechti130d1f02009-08-04 02:10:58 +00001137 self.serial.bytesize = backup
cliechti5cc3eb12009-08-11 23:04:30 +00001138 else:
cliechti6a300772009-08-12 02:28:56 +00001139 if self.logger:
Chris Liechti142ae562015-08-23 01:11:06 +02001140 self.logger.info("%s data size: %s" % ('set' if datasize else 'get', self.serial.bytesize))
Chris Liechti01587b12015-08-05 02:39:32 +02001141 self.rfc2217SendSubnegotiation(SERVER_SET_DATASIZE, struct.pack(b"!B", self.serial.bytesize))
cliechti130d1f02009-08-04 02:10:58 +00001142 elif suboption[1:2] == SET_PARITY:
1143 backup = self.serial.parity
1144 try:
Chris Liechti01587b12015-08-05 02:39:32 +02001145 parity = struct.unpack(b"!B", suboption[2:3])[0]
cliechtieada4fd2013-07-31 16:26:07 +00001146 if parity != 0:
1147 self.serial.parity = RFC2217_REVERSE_PARITY_MAP[parity]
Chris Liechtid2146002015-08-04 16:57:16 +02001148 except ValueError as e:
cliechti6a300772009-08-12 02:28:56 +00001149 if self.logger:
1150 self.logger.error("failed to set parity: %s" % (e,))
cliechti130d1f02009-08-04 02:10:58 +00001151 self.serial.parity = backup
cliechti5cc3eb12009-08-11 23:04:30 +00001152 else:
cliechti6a300772009-08-12 02:28:56 +00001153 if self.logger:
Chris Liechti142ae562015-08-23 01:11:06 +02001154 self.logger.info("%s parity: %s" % ('set' if parity else 'get', self.serial.parity))
cliechti130d1f02009-08-04 02:10:58 +00001155 self.rfc2217SendSubnegotiation(
Chris Liechticb6ce1b2016-02-02 01:53:56 +01001156 SERVER_SET_PARITY,
1157 struct.pack(b"!B", RFC2217_PARITY_MAP[self.serial.parity]))
cliechti130d1f02009-08-04 02:10:58 +00001158 elif suboption[1:2] == SET_STOPSIZE:
1159 backup = self.serial.stopbits
1160 try:
Chris Liechti01587b12015-08-05 02:39:32 +02001161 stopbits = struct.unpack(b"!B", suboption[2:3])[0]
cliechtieada4fd2013-07-31 16:26:07 +00001162 if stopbits != 0:
1163 self.serial.stopbits = RFC2217_REVERSE_STOPBIT_MAP[stopbits]
Chris Liechtid2146002015-08-04 16:57:16 +02001164 except ValueError as e:
cliechti6a300772009-08-12 02:28:56 +00001165 if self.logger:
1166 self.logger.error("failed to set stop bits: %s" % (e,))
cliechti130d1f02009-08-04 02:10:58 +00001167 self.serial.stopbits = backup
cliechti5cc3eb12009-08-11 23:04:30 +00001168 else:
cliechti6a300772009-08-12 02:28:56 +00001169 if self.logger:
Chris Liechti142ae562015-08-23 01:11:06 +02001170 self.logger.info("%s stop bits: %s" % ('set' if stopbits else 'get', self.serial.stopbits))
cliechti130d1f02009-08-04 02:10:58 +00001171 self.rfc2217SendSubnegotiation(
Chris Liechticb6ce1b2016-02-02 01:53:56 +01001172 SERVER_SET_STOPSIZE,
1173 struct.pack(b"!B", RFC2217_STOPBIT_MAP[self.serial.stopbits]))
cliechti130d1f02009-08-04 02:10:58 +00001174 elif suboption[1:2] == SET_CONTROL:
1175 if suboption[2:3] == SET_CONTROL_REQ_FLOW_SETTING:
1176 if self.serial.xonxoff:
1177 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_SW_FLOW_CONTROL)
1178 elif self.serial.rtscts:
1179 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_HW_FLOW_CONTROL)
1180 else:
1181 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_NO_FLOW_CONTROL)
1182 elif suboption[2:3] == SET_CONTROL_USE_NO_FLOW_CONTROL:
1183 self.serial.xonxoff = False
1184 self.serial.rtscts = False
cliechti6a300772009-08-12 02:28:56 +00001185 if self.logger:
1186 self.logger.info("changed flow control to None")
cliechti130d1f02009-08-04 02:10:58 +00001187 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_NO_FLOW_CONTROL)
1188 elif suboption[2:3] == SET_CONTROL_USE_SW_FLOW_CONTROL:
1189 self.serial.xonxoff = True
cliechti6a300772009-08-12 02:28:56 +00001190 if self.logger:
1191 self.logger.info("changed flow control to XON/XOFF")
cliechti130d1f02009-08-04 02:10:58 +00001192 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_SW_FLOW_CONTROL)
1193 elif suboption[2:3] == SET_CONTROL_USE_HW_FLOW_CONTROL:
1194 self.serial.rtscts = True
cliechti6a300772009-08-12 02:28:56 +00001195 if self.logger:
1196 self.logger.info("changed flow control to RTS/CTS")
cliechti130d1f02009-08-04 02:10:58 +00001197 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_HW_FLOW_CONTROL)
1198 elif suboption[2:3] == SET_CONTROL_REQ_BREAK_STATE:
cliechti6a300772009-08-12 02:28:56 +00001199 if self.logger:
1200 self.logger.warning("requested break state - not implemented")
Chris Liechti033f17c2015-08-30 21:28:04 +02001201 pass # XXX needs cached value
cliechti130d1f02009-08-04 02:10:58 +00001202 elif suboption[2:3] == SET_CONTROL_BREAK_ON:
1203 self.serial.setBreak(True)
cliechti6a300772009-08-12 02:28:56 +00001204 if self.logger:
1205 self.logger.info("changed BREAK to active")
cliechti130d1f02009-08-04 02:10:58 +00001206 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_BREAK_ON)
1207 elif suboption[2:3] == SET_CONTROL_BREAK_OFF:
1208 self.serial.setBreak(False)
cliechti6a300772009-08-12 02:28:56 +00001209 if self.logger:
1210 self.logger.info("changed BREAK to inactive")
cliechti130d1f02009-08-04 02:10:58 +00001211 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_BREAK_OFF)
1212 elif suboption[2:3] == SET_CONTROL_REQ_DTR:
cliechti6a300772009-08-12 02:28:56 +00001213 if self.logger:
1214 self.logger.warning("requested DTR state - not implemented")
Chris Liechti033f17c2015-08-30 21:28:04 +02001215 pass # XXX needs cached value
cliechti130d1f02009-08-04 02:10:58 +00001216 elif suboption[2:3] == SET_CONTROL_DTR_ON:
1217 self.serial.setDTR(True)
cliechti6a300772009-08-12 02:28:56 +00001218 if self.logger:
1219 self.logger.info("changed DTR to active")
cliechti130d1f02009-08-04 02:10:58 +00001220 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_DTR_ON)
1221 elif suboption[2:3] == SET_CONTROL_DTR_OFF:
1222 self.serial.setDTR(False)
cliechti6a300772009-08-12 02:28:56 +00001223 if self.logger:
1224 self.logger.info("changed DTR to inactive")
cliechti130d1f02009-08-04 02:10:58 +00001225 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_DTR_OFF)
1226 elif suboption[2:3] == SET_CONTROL_REQ_RTS:
cliechti6a300772009-08-12 02:28:56 +00001227 if self.logger:
1228 self.logger.warning("requested RTS state - not implemented")
Chris Liechti033f17c2015-08-30 21:28:04 +02001229 pass # XXX needs cached value
cliechti130d1f02009-08-04 02:10:58 +00001230 #~ self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_RTS_ON)
1231 elif suboption[2:3] == SET_CONTROL_RTS_ON:
1232 self.serial.setRTS(True)
cliechti6a300772009-08-12 02:28:56 +00001233 if self.logger:
1234 self.logger.info("changed RTS to active")
cliechti130d1f02009-08-04 02:10:58 +00001235 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_RTS_ON)
1236 elif suboption[2:3] == SET_CONTROL_RTS_OFF:
1237 self.serial.setRTS(False)
cliechti6a300772009-08-12 02:28:56 +00001238 if self.logger:
1239 self.logger.info("changed RTS to inactive")
cliechti130d1f02009-08-04 02:10:58 +00001240 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_RTS_OFF)
1241 #~ elif suboption[2:3] == SET_CONTROL_REQ_FLOW_SETTING_IN:
1242 #~ elif suboption[2:3] == SET_CONTROL_USE_NO_FLOW_CONTROL_IN:
1243 #~ elif suboption[2:3] == SET_CONTROL_USE_SW_FLOW_CONTOL_IN:
1244 #~ elif suboption[2:3] == SET_CONTROL_USE_HW_FLOW_CONTOL_IN:
1245 #~ elif suboption[2:3] == SET_CONTROL_USE_DCD_FLOW_CONTROL:
1246 #~ elif suboption[2:3] == SET_CONTROL_USE_DTR_FLOW_CONTROL:
1247 #~ elif suboption[2:3] == SET_CONTROL_USE_DSR_FLOW_CONTROL:
1248 elif suboption[1:2] == NOTIFY_LINESTATE:
cliechti7cb78e82009-08-05 15:47:57 +00001249 # client polls for current state
1250 self.rfc2217SendSubnegotiation(
Chris Liechti142ae562015-08-23 01:11:06 +02001251 SERVER_NOTIFY_LINESTATE,
1252 to_bytes([0])) # sorry, nothing like that implemented
cliechti130d1f02009-08-04 02:10:58 +00001253 elif suboption[1:2] == NOTIFY_MODEMSTATE:
cliechti6a300772009-08-12 02:28:56 +00001254 if self.logger:
1255 self.logger.info("request for modem state")
cliechti7cb78e82009-08-05 15:47:57 +00001256 # client polls for current state
1257 self.check_modem_lines(force_notification=True)
cliechti130d1f02009-08-04 02:10:58 +00001258 elif suboption[1:2] == FLOWCONTROL_SUSPEND:
cliechti6a300772009-08-12 02:28:56 +00001259 if self.logger:
1260 self.logger.info("suspend")
cliechti130d1f02009-08-04 02:10:58 +00001261 self._remote_suspend_flow = True
1262 elif suboption[1:2] == FLOWCONTROL_RESUME:
cliechti6a300772009-08-12 02:28:56 +00001263 if self.logger:
1264 self.logger.info("resume")
cliechti130d1f02009-08-04 02:10:58 +00001265 self._remote_suspend_flow = False
1266 elif suboption[1:2] == SET_LINESTATE_MASK:
Chris Liechti033f17c2015-08-30 21:28:04 +02001267 self.linstate_mask = ord(suboption[2:3]) # ensure it is a number
cliechti6a300772009-08-12 02:28:56 +00001268 if self.logger:
cliechtif325c032009-12-25 16:09:49 +00001269 self.logger.info("line state mask: 0x%02x" % (self.linstate_mask,))
cliechti130d1f02009-08-04 02:10:58 +00001270 elif suboption[1:2] == SET_MODEMSTATE_MASK:
Chris Liechti033f17c2015-08-30 21:28:04 +02001271 self.modemstate_mask = ord(suboption[2:3]) # ensure it is a number
cliechti6a300772009-08-12 02:28:56 +00001272 if self.logger:
cliechtif325c032009-12-25 16:09:49 +00001273 self.logger.info("modem state mask: 0x%02x" % (self.modemstate_mask,))
cliechti130d1f02009-08-04 02:10:58 +00001274 elif suboption[1:2] == PURGE_DATA:
1275 if suboption[2:3] == PURGE_RECEIVE_BUFFER:
Chris Liechti3ad62fb2015-08-29 21:53:32 +02001276 self.serial.reset_input_buffer()
cliechti6a300772009-08-12 02:28:56 +00001277 if self.logger:
1278 self.logger.info("purge in")
cliechti130d1f02009-08-04 02:10:58 +00001279 self.rfc2217SendSubnegotiation(SERVER_PURGE_DATA, PURGE_RECEIVE_BUFFER)
1280 elif suboption[2:3] == PURGE_TRANSMIT_BUFFER:
Chris Liechti3ad62fb2015-08-29 21:53:32 +02001281 self.serial.reset_output_buffer()
cliechti6a300772009-08-12 02:28:56 +00001282 if self.logger:
1283 self.logger.info("purge out")
cliechti130d1f02009-08-04 02:10:58 +00001284 self.rfc2217SendSubnegotiation(SERVER_PURGE_DATA, PURGE_TRANSMIT_BUFFER)
1285 elif suboption[2:3] == PURGE_BOTH_BUFFERS:
Chris Liechti3ad62fb2015-08-29 21:53:32 +02001286 self.serial.reset_input_buffer()
1287 self.serial.reset_output_buffer()
cliechti6a300772009-08-12 02:28:56 +00001288 if self.logger:
1289 self.logger.info("purge both")
cliechti130d1f02009-08-04 02:10:58 +00001290 self.rfc2217SendSubnegotiation(SERVER_PURGE_DATA, PURGE_BOTH_BUFFERS)
1291 else:
cliechti6a300772009-08-12 02:28:56 +00001292 if self.logger:
1293 self.logger.error("undefined PURGE_DATA: %r" % list(suboption[2:]))
cliechti130d1f02009-08-04 02:10:58 +00001294 else:
cliechti6a300772009-08-12 02:28:56 +00001295 if self.logger:
1296 self.logger.error("undefined COM_PORT_OPTION: %r" % list(suboption[1:]))
cliechti130d1f02009-08-04 02:10:58 +00001297 else:
cliechti6a300772009-08-12 02:28:56 +00001298 if self.logger:
1299 self.logger.warning("unknown subnegotiation: %r" % (suboption,))
cliechti130d1f02009-08-04 02:10:58 +00001300
1301
1302# simple client test
cliechti8099bed2009-08-01 23:59:18 +00001303if __name__ == '__main__':
1304 import sys
1305 s = Serial('rfc2217://localhost:7000', 115200)
1306 sys.stdout.write('%s\n' % s)
1307
cliechti8099bed2009-08-01 23:59:18 +00001308 sys.stdout.write("write...\n")
Chris Liechtib4cda3a2015-08-08 17:12:08 +02001309 s.write(b"hello\n")
cliechti8099bed2009-08-01 23:59:18 +00001310 s.flush()
cliechti8099bed2009-08-01 23:59:18 +00001311 sys.stdout.write("read: %s\n" % s.read(5))
cliechti8099bed2009-08-01 23:59:18 +00001312 s.close()