blob: 3ddc33da48fd2add467f59f9f1fecd59ee62807d [file] [log] [blame]
cliechti8099bed2009-08-01 23:59:18 +00001#! python
2#
3# Python Serial Port Extension for Win32, Linux, BSD, Jython
4# see __init__.py
5#
6# This module implements a RFC2217 compatible client. RF2217 descibes a
7# protocol to access serial ports over TCP/IP and allows setting the baud rate,
8# modem control lines etc.
9#
Chris Liechti01587b12015-08-05 02:39:32 +020010# (C) 2001-2015 Chris Liechti <cliechti@gmx.net>
Chris Liechtifbdd8a02015-08-09 02:37:45 +020011#
12# SPDX-License-Identifier: BSD-3-Clause
cliechti8099bed2009-08-01 23:59:18 +000013
14# TODO:
15# - setting control line -> answer is not checked (had problems with one of the
16# severs). consider implementing a compatibility mode flag to make check
17# conditional
18# - write timeout not implemented at all
cliechti8099bed2009-08-01 23:59:18 +000019
Chris Liechti033f17c2015-08-30 21:28:04 +020020# ###########################################################################
cliechti81c54762009-08-03 23:53:27 +000021# observations and issues with servers
Chris Liechti033f17c2015-08-30 21:28:04 +020022# ===========================================================================
cliechti81c54762009-08-03 23:53:27 +000023# sredird V2.2.1
24# - http://www.ibiblio.org/pub/Linux/system/serial/ sredird-2.2.2.tar.gz
25# - does not acknowledge SET_CONTROL (RTS/DTR) correctly, always responding
26# [105 1] instead of the actual value.
27# - SET_BAUDRATE answer contains 4 extra null bytes -> probably for larger
28# numbers than 2**32?
29# - To get the signature [COM_PORT_OPTION 0] has to be sent.
30# - 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 +020031# ===========================================================================
cliechti81c54762009-08-03 23:53:27 +000032# telnetcpcd (untested)
33# - http://ftp.wayne.edu/kermit/sredird/telnetcpcd-1.09.tar.gz
34# - To get the signature [COM_PORT_OPTION] w/o data has to be sent.
Chris Liechti033f17c2015-08-30 21:28:04 +020035# ===========================================================================
cliechti81c54762009-08-03 23:53:27 +000036# ser2net
37# - does not negotiate BINARY or COM_PORT_OPTION for his side but at least
38# acknowledges that the client activates these options
39# - The configuration may be that the server prints a banner. As this client
40# implementation does a flushInput on connect, this banner is hidden from
41# the user application.
42# - NOTIFY_MODEMSTATE: the poll interval of the server seems to be one
43# second.
44# - To get the signature [COM_PORT_OPTION 0] has to be sent.
45# - run a server: run ser2net daemon, in /etc/ser2net.conf:
46# 2000:telnet:0:/dev/ttyS0:9600 remctl banner
Chris Liechti033f17c2015-08-30 21:28:04 +020047# ###########################################################################
cliechti81c54762009-08-03 23:53:27 +000048
49# How to identify ports? pySerial might want to support other protocols in the
50# future, so lets use an URL scheme.
51# for RFC2217 compliant servers we will use this:
Chris Liechti142ae562015-08-23 01:11:06 +020052# rfc2217://<host>:<port>[?option[&option...]]
cliechti81c54762009-08-03 23:53:27 +000053#
54# options:
Chris Liechti01587b12015-08-05 02:39:32 +020055# - "logging" set log level print diagnostic messages (e.g. "logging=debug")
cliechti81c54762009-08-03 23:53:27 +000056# - "ign_set_control": do not look at the answers to SET_CONTROL
cliechti7cb78e82009-08-05 15:47:57 +000057# - "poll_modem": issue NOTIFY_MODEMSTATE requests when CTS/DTR/RI/CD is read.
58# Without this option it expects that the server sends notifications
59# automatically on change (which most servers do and is according to the
60# RFC).
cliechti81c54762009-08-03 23:53:27 +000061# the order of the options is not relevant
62
cliechti5cc3eb12009-08-11 23:04:30 +000063import logging
Chris Liechtic4bca9e2015-08-07 14:40:41 +020064import socket
65import struct
66import threading
67import time
68try:
69 import urlparse
70except ImportError:
71 import urllib.parse as urlparse
Chris Liechtid2146002015-08-04 16:57:16 +020072try:
73 import Queue
74except ImportError:
75 import queue as Queue
76
Chris Liechti033f17c2015-08-30 21:28:04 +020077import serial
78from serial.serialutil import SerialBase, SerialException, to_bytes, iterbytes, portNotOpenError
Chris Liechtib4cda3a2015-08-08 17:12:08 +020079
cliechti8099bed2009-08-01 23:59:18 +000080# port string is expected to be something like this:
81# rfc2217://host:port
82# host may be an IP or including domain, whatever.
83# port is 0...65535
84
Chris Liechti3ad62fb2015-08-29 21:53:32 +020085# map log level names to constants. used in from_url()
cliechti5cc3eb12009-08-11 23:04:30 +000086LOGGER_LEVELS = {
Chris Liechtic4bca9e2015-08-07 14:40:41 +020087 'debug': logging.DEBUG,
88 'info': logging.INFO,
89 'warning': logging.WARNING,
90 'error': logging.ERROR,
91 }
cliechti5cc3eb12009-08-11 23:04:30 +000092
93
cliechti8099bed2009-08-01 23:59:18 +000094# telnet protocol characters
Chris Liechti033f17c2015-08-30 21:28:04 +020095SE = b'\xf0' # Subnegotiation End
Chris Liechtib4cda3a2015-08-08 17:12:08 +020096NOP = b'\xf1' # No Operation
Chris Liechti033f17c2015-08-30 21:28:04 +020097DM = b'\xf2' # Data Mark
Chris Liechtib4cda3a2015-08-08 17:12:08 +020098BRK = b'\xf3' # Break
Chris Liechti033f17c2015-08-30 21:28:04 +020099IP = b'\xf4' # Interrupt process
100AO = b'\xf5' # Abort output
Chris Liechtib4cda3a2015-08-08 17:12:08 +0200101AYT = b'\xf6' # Are You There
Chris Liechti033f17c2015-08-30 21:28:04 +0200102EC = b'\xf7' # Erase Character
103EL = b'\xf8' # Erase Line
104GA = b'\xf9' # Go Ahead
105SB = b'\xfa' # Subnegotiation Begin
Chris Liechtib4cda3a2015-08-08 17:12:08 +0200106WILL = b'\xfb'
107WONT = b'\xfc'
Chris Liechti033f17c2015-08-30 21:28:04 +0200108DO = b'\xfd'
Chris Liechtib4cda3a2015-08-08 17:12:08 +0200109DONT = b'\xfe'
Chris Liechti033f17c2015-08-30 21:28:04 +0200110IAC = b'\xff' # Interpret As Command
Chris Liechtib4cda3a2015-08-08 17:12:08 +0200111IAC_DOUBLED = b'\xff\xff'
cliechti8099bed2009-08-01 23:59:18 +0000112
113# selected telnet options
Chris Liechtib4cda3a2015-08-08 17:12:08 +0200114BINARY = b'\x00' # 8-bit data path
115ECHO = b'\x01' # echo
116SGA = b'\x03' # suppress go ahead
cliechti8099bed2009-08-01 23:59:18 +0000117
118# RFC2217
Chris Liechtib4cda3a2015-08-08 17:12:08 +0200119COM_PORT_OPTION = b'\x2c'
cliechti8099bed2009-08-01 23:59:18 +0000120
121# Client to Access Server
Chris Liechtib4cda3a2015-08-08 17:12:08 +0200122SET_BAUDRATE = b'\x01'
123SET_DATASIZE = b'\x02'
124SET_PARITY = b'\x03'
125SET_STOPSIZE = b'\x04'
126SET_CONTROL = b'\x05'
127NOTIFY_LINESTATE = b'\x06'
128NOTIFY_MODEMSTATE = b'\x07'
129FLOWCONTROL_SUSPEND = b'\x08'
130FLOWCONTROL_RESUME = b'\x09'
131SET_LINESTATE_MASK = b'\x0a'
132SET_MODEMSTATE_MASK = b'\x0b'
133PURGE_DATA = b'\x0c'
cliechti8099bed2009-08-01 23:59:18 +0000134
Chris Liechtib4cda3a2015-08-08 17:12:08 +0200135SERVER_SET_BAUDRATE = b'\x65'
136SERVER_SET_DATASIZE = b'\x66'
137SERVER_SET_PARITY = b'\x67'
138SERVER_SET_STOPSIZE = b'\x68'
139SERVER_SET_CONTROL = b'\x69'
140SERVER_NOTIFY_LINESTATE = b'\x6a'
141SERVER_NOTIFY_MODEMSTATE = b'\x6b'
142SERVER_FLOWCONTROL_SUSPEND = b'\x6c'
143SERVER_FLOWCONTROL_RESUME = b'\x6d'
144SERVER_SET_LINESTATE_MASK = b'\x6e'
145SERVER_SET_MODEMSTATE_MASK = b'\x6f'
146SERVER_PURGE_DATA = b'\x70'
cliechti8099bed2009-08-01 23:59:18 +0000147
148RFC2217_ANSWER_MAP = {
149 SET_BAUDRATE: SERVER_SET_BAUDRATE,
150 SET_DATASIZE: SERVER_SET_DATASIZE,
151 SET_PARITY: SERVER_SET_PARITY,
152 SET_STOPSIZE: SERVER_SET_STOPSIZE,
153 SET_CONTROL: SERVER_SET_CONTROL,
154 NOTIFY_LINESTATE: SERVER_NOTIFY_LINESTATE,
155 NOTIFY_MODEMSTATE: SERVER_NOTIFY_MODEMSTATE,
156 FLOWCONTROL_SUSPEND: SERVER_FLOWCONTROL_SUSPEND,
157 FLOWCONTROL_RESUME: SERVER_FLOWCONTROL_RESUME,
158 SET_LINESTATE_MASK: SERVER_SET_LINESTATE_MASK,
159 SET_MODEMSTATE_MASK: SERVER_SET_MODEMSTATE_MASK,
160 PURGE_DATA: SERVER_PURGE_DATA,
161}
162
Chris Liechtib4cda3a2015-08-08 17:12:08 +0200163SET_CONTROL_REQ_FLOW_SETTING = b'\x00' # Request Com Port Flow Control Setting (outbound/both)
164SET_CONTROL_USE_NO_FLOW_CONTROL = b'\x01' # Use No Flow Control (outbound/both)
165SET_CONTROL_USE_SW_FLOW_CONTROL = b'\x02' # Use XON/XOFF Flow Control (outbound/both)
166SET_CONTROL_USE_HW_FLOW_CONTROL = b'\x03' # Use HARDWARE Flow Control (outbound/both)
167SET_CONTROL_REQ_BREAK_STATE = b'\x04' # Request BREAK State
168SET_CONTROL_BREAK_ON = b'\x05' # Set BREAK State ON
169SET_CONTROL_BREAK_OFF = b'\x06' # Set BREAK State OFF
170SET_CONTROL_REQ_DTR = b'\x07' # Request DTR Signal State
171SET_CONTROL_DTR_ON = b'\x08' # Set DTR Signal State ON
172SET_CONTROL_DTR_OFF = b'\x09' # Set DTR Signal State OFF
173SET_CONTROL_REQ_RTS = b'\x0a' # Request RTS Signal State
174SET_CONTROL_RTS_ON = b'\x0b' # Set RTS Signal State ON
175SET_CONTROL_RTS_OFF = b'\x0c' # Set RTS Signal State OFF
176SET_CONTROL_REQ_FLOW_SETTING_IN = b'\x0d' # Request Com Port Flow Control Setting (inbound)
177SET_CONTROL_USE_NO_FLOW_CONTROL_IN = b'\x0e' # Use No Flow Control (inbound)
178SET_CONTROL_USE_SW_FLOW_CONTOL_IN = b'\x0f' # Use XON/XOFF Flow Control (inbound)
179SET_CONTROL_USE_HW_FLOW_CONTOL_IN = b'\x10' # Use HARDWARE Flow Control (inbound)
180SET_CONTROL_USE_DCD_FLOW_CONTROL = b'\x11' # Use DCD Flow Control (outbound/both)
181SET_CONTROL_USE_DTR_FLOW_CONTROL = b'\x12' # Use DTR Flow Control (inbound)
182SET_CONTROL_USE_DSR_FLOW_CONTROL = b'\x13' # Use DSR Flow Control (outbound/both)
cliechti8099bed2009-08-01 23:59:18 +0000183
Chris Liechtib4cda3a2015-08-08 17:12:08 +0200184LINESTATE_MASK_TIMEOUT = 128 # Time-out Error
185LINESTATE_MASK_SHIFTREG_EMPTY = 64 # Transfer Shift Register Empty
186LINESTATE_MASK_TRANSREG_EMPTY = 32 # Transfer Holding Register Empty
187LINESTATE_MASK_BREAK_DETECT = 16 # Break-detect Error
188LINESTATE_MASK_FRAMING_ERROR = 8 # Framing Error
189LINESTATE_MASK_PARTIY_ERROR = 4 # Parity Error
190LINESTATE_MASK_OVERRUN_ERROR = 2 # Overrun Error
191LINESTATE_MASK_DATA_READY = 1 # Data Ready
cliechti8099bed2009-08-01 23:59:18 +0000192
Chris Liechtib4cda3a2015-08-08 17:12:08 +0200193MODEMSTATE_MASK_CD = 128 # Receive Line Signal Detect (also known as Carrier Detect)
194MODEMSTATE_MASK_RI = 64 # Ring Indicator
195MODEMSTATE_MASK_DSR = 32 # Data-Set-Ready Signal State
196MODEMSTATE_MASK_CTS = 16 # Clear-To-Send Signal State
197MODEMSTATE_MASK_CD_CHANGE = 8 # Delta Receive Line Signal Detect
198MODEMSTATE_MASK_RI_CHANGE = 4 # Trailing-edge Ring Detector
199MODEMSTATE_MASK_DSR_CHANGE = 2 # Delta Data-Set-Ready
200MODEMSTATE_MASK_CTS_CHANGE = 1 # Delta Clear-To-Send
cliechti8099bed2009-08-01 23:59:18 +0000201
Chris Liechtib4cda3a2015-08-08 17:12:08 +0200202PURGE_RECEIVE_BUFFER = b'\x01' # Purge access server receive data buffer
203PURGE_TRANSMIT_BUFFER = b'\x02' # Purge access server transmit data buffer
204PURGE_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 +0000205
206
207RFC2217_PARITY_MAP = {
Chris Liechti033f17c2015-08-30 21:28:04 +0200208 serial.PARITY_NONE: 1,
209 serial.PARITY_ODD: 2,
210 serial.PARITY_EVEN: 3,
211 serial.PARITY_MARK: 4,
212 serial.PARITY_SPACE: 5,
cliechti8099bed2009-08-01 23:59:18 +0000213}
Chris Liechti033f17c2015-08-30 21:28:04 +0200214RFC2217_REVERSE_PARITY_MAP = dict((v, k) for k, v in RFC2217_PARITY_MAP.items())
cliechti8099bed2009-08-01 23:59:18 +0000215
216RFC2217_STOPBIT_MAP = {
Chris Liechti033f17c2015-08-30 21:28:04 +0200217 serial.STOPBITS_ONE: 1,
218 serial.STOPBITS_ONE_POINT_FIVE: 3,
219 serial.STOPBITS_TWO: 2,
cliechti8099bed2009-08-01 23:59:18 +0000220}
Chris Liechti033f17c2015-08-30 21:28:04 +0200221RFC2217_REVERSE_STOPBIT_MAP = dict((v, k) for k, v in RFC2217_STOPBIT_MAP.items())
cliechti8099bed2009-08-01 23:59:18 +0000222
cliechti130d1f02009-08-04 02:10:58 +0000223# Telnet filter states
224M_NORMAL = 0
225M_IAC_SEEN = 1
226M_NEGOTIATE = 2
cliechti8099bed2009-08-01 23:59:18 +0000227
cliechti130d1f02009-08-04 02:10:58 +0000228# TelnetOption and TelnetSubnegotiation states
cliechtiac205322009-08-02 20:40:21 +0000229REQUESTED = 'REQUESTED'
230ACTIVE = 'ACTIVE'
231INACTIVE = 'INACTIVE'
232REALLY_INACTIVE = 'REALLY_INACTIVE'
233
Chris Liechti033f17c2015-08-30 21:28:04 +0200234
cliechtiac205322009-08-02 20:40:21 +0000235class TelnetOption(object):
cliechti1ef7e3e2009-08-03 02:38:43 +0000236 """Manage a single telnet option, keeps track of DO/DONT WILL/WONT."""
237
cliechti86b593e2009-08-05 16:28:12 +0000238 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 +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"""
cliechtiac205322009-08-02 20:40:21 +0000263 return "%s:%s(%s)" % (self.name, self.active, self.state)
264
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:
287 raise ValueError('option in illegal state %r' % self)
288 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:
301 raise ValueError('option in illegal state %r' % self)
302
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."""
cliechti2b929b72009-08-02 23:49:02 +0000322 return "%s:%s" % (self.name, self.state)
323
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:
334 self.connection.logger.debug("SB Requesting %s -> %r" % (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:
342 raise ValueError("remote rejected value for option %r" % (self.name))
343 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:
359 raise SerialException("timeout while waiting for option %r" % (self.name))
360
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:
372 self.connection.logger.debug("SB Answer %s -> %r -> %s" % (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
381 def open(self):
cliechtieada4fd2013-07-31 16:26:07 +0000382 """\
383 Open port with current settings. This may throw a SerialException
384 if the port cannot be opened.
385 """
cliechti6a300772009-08-12 02:28:56 +0000386 self.logger = None
cliechti81c54762009-08-03 23:53:27 +0000387 self._ignore_set_control_answer = False
cliechti7cb78e82009-08-05 15:47:57 +0000388 self._poll_modem_state = False
cliechtidfe2d272009-08-10 22:19:41 +0000389 self._network_timeout = 3
cliechti8099bed2009-08-01 23:59:18 +0000390 if self._port is None:
391 raise SerialException("Port must be configured before it can be used.")
Chris Liechti3ad62fb2015-08-29 21:53:32 +0200392 if self.is_open:
cliechti8f69e702011-03-19 00:22:32 +0000393 raise SerialException("Port is already open.")
cliechti8099bed2009-08-01 23:59:18 +0000394 try:
Chris Liechti3ad62fb2015-08-29 21:53:32 +0200395 self._socket = socket.create_connection(self.from_url(self.portstr))
cliechti6a300772009-08-12 02:28:56 +0000396 self._socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
Chris Liechti68340d72015-08-03 14:15:48 +0200397 except Exception as msg:
cliechti8099bed2009-08-01 23:59:18 +0000398 self._socket = None
399 raise SerialException("Could not open port %s: %s" % (self.portstr, msg))
400
Chris Liechti033f17c2015-08-30 21:28:04 +0200401 self._socket.settimeout(5) # XXX good value?
cliechti8099bed2009-08-01 23:59:18 +0000402
cliechti1ef7e3e2009-08-03 02:38:43 +0000403 # use a thread save queue as buffer. it also simplifies implementing
404 # the read timeout
cliechti8099bed2009-08-01 23:59:18 +0000405 self._read_buffer = Queue.Queue()
cliechti81c54762009-08-03 23:53:27 +0000406 # to ensure that user writes does not interfere with internal
407 # telnet/rfc2217 options establish a lock
408 self._write_lock = threading.Lock()
cliechtiac205322009-08-02 20:40:21 +0000409 # name the following separately so that, below, a check can be easily done
410 mandadory_options = [
cliechti2b929b72009-08-02 23:49:02 +0000411 TelnetOption(self, 'we-BINARY', BINARY, WILL, WONT, DO, DONT, INACTIVE),
cliechti2b929b72009-08-02 23:49:02 +0000412 TelnetOption(self, 'we-RFC2217', COM_PORT_OPTION, WILL, WONT, DO, DONT, REQUESTED),
cliechtiac205322009-08-02 20:40:21 +0000413 ]
414 # all supported telnet options
415 self._telnet_options = [
cliechtia29275e2009-08-03 00:08:04 +0000416 TelnetOption(self, 'ECHO', ECHO, DO, DONT, WILL, WONT, REQUESTED),
cliechti2b929b72009-08-02 23:49:02 +0000417 TelnetOption(self, 'we-SGA', SGA, WILL, WONT, DO, DONT, REQUESTED),
418 TelnetOption(self, 'they-SGA', SGA, DO, DONT, WILL, WONT, REQUESTED),
cliechti81c54762009-08-03 23:53:27 +0000419 TelnetOption(self, 'they-BINARY', BINARY, DO, DONT, WILL, WONT, INACTIVE),
420 TelnetOption(self, 'they-RFC2217', COM_PORT_OPTION, DO, DONT, WILL, WONT, REQUESTED),
cliechtiac205322009-08-02 20:40:21 +0000421 ] + mandadory_options
cliechti044d8662009-08-11 21:40:31 +0000422 # RFC 2217 specific states
cliechti2b929b72009-08-02 23:49:02 +0000423 # COM port settings
424 self._rfc2217_port_settings = {
425 'baudrate': TelnetSubnegotiation(self, 'baudrate', SET_BAUDRATE, SERVER_SET_BAUDRATE),
426 'datasize': TelnetSubnegotiation(self, 'datasize', SET_DATASIZE, SERVER_SET_DATASIZE),
427 'parity': TelnetSubnegotiation(self, 'parity', SET_PARITY, SERVER_SET_PARITY),
428 'stopsize': TelnetSubnegotiation(self, 'stopsize', SET_STOPSIZE, SERVER_SET_STOPSIZE),
429 }
cliechticb20a4f2011-04-25 02:25:54 +0000430 # There are more subnegotiation objects, combine all in one dictionary
cliechti2b929b72009-08-02 23:49:02 +0000431 # for easy access
432 self._rfc2217_options = {
433 'purge': TelnetSubnegotiation(self, 'purge', PURGE_DATA, SERVER_PURGE_DATA),
cliechti81c54762009-08-03 23:53:27 +0000434 'control': TelnetSubnegotiation(self, 'control', SET_CONTROL, SERVER_SET_CONTROL),
cliechti2b929b72009-08-02 23:49:02 +0000435 }
436 self._rfc2217_options.update(self._rfc2217_port_settings)
437 # cache for line and modem states that the server sends to us
cliechti8099bed2009-08-01 23:59:18 +0000438 self._linestate = 0
cliechti7cb78e82009-08-05 15:47:57 +0000439 self._modemstate = None
440 self._modemstate_expires = 0
cliechti044d8662009-08-11 21:40:31 +0000441 # RFC 2217 flow control between server and client
cliechti672d0292009-08-03 02:01:57 +0000442 self._remote_suspend_flow = False
cliechti8099bed2009-08-01 23:59:18 +0000443
cliechti1ef7e3e2009-08-03 02:38:43 +0000444 self._thread = threading.Thread(target=self._telnetReadLoop)
cliechti8099bed2009-08-01 23:59:18 +0000445 self._thread.setDaemon(True)
cliechti5cc3eb12009-08-11 23:04:30 +0000446 self._thread.setName('pySerial RFC 2217 reader thread for %s' % (self._port,))
cliechti8099bed2009-08-01 23:59:18 +0000447 self._thread.start()
448
cliechti044d8662009-08-11 21:40:31 +0000449 # negotiate Telnet/RFC 2217 -> send initial requests
cliechtiac205322009-08-02 20:40:21 +0000450 for option in self._telnet_options:
451 if option.state is REQUESTED:
cliechti1ef7e3e2009-08-03 02:38:43 +0000452 self.telnetSendOption(option.send_yes, option.option)
cliechtiac205322009-08-02 20:40:21 +0000453 # now wait until important options are negotiated
cliechtidfe2d272009-08-10 22:19:41 +0000454 timeout_time = time.time() + self._network_timeout
cliechtiac205322009-08-02 20:40:21 +0000455 while time.time() < timeout_time:
cliechtiac205322009-08-02 20:40:21 +0000456 time.sleep(0.05) # prevent 100% CPU load
cliechti7c213a92014-07-31 15:29:34 +0000457 if sum(o.active for o in mandadory_options) == sum(o.state != INACTIVE for o in mandadory_options):
cliechti2b929b72009-08-02 23:49:02 +0000458 break
459 else:
460 raise SerialException("Remote does not seem to support RFC2217 or BINARY mode %r" % mandadory_options)
cliechti6a300772009-08-12 02:28:56 +0000461 if self.logger:
462 self.logger.info("Negotiated options: %s" % self._telnet_options)
cliechti8099bed2009-08-01 23:59:18 +0000463
cliechti044d8662009-08-11 21:40:31 +0000464 # fine, go on, set RFC 2271 specific things
Chris Liechti3ad62fb2015-08-29 21:53:32 +0200465 self._reconfigure_port()
cliechti2b929b72009-08-02 23:49:02 +0000466 # all things set up get, now a clean start
Chris Liechti3ad62fb2015-08-29 21:53:32 +0200467 self.is_open = True
468 if not self._dsrdtr:
Chris Liechtief1fe252015-08-27 23:25:21 +0200469 self._update_dtr_state()
cliechti8099bed2009-08-01 23:59:18 +0000470 if not self._rtscts:
Chris Liechtief1fe252015-08-27 23:25:21 +0200471 self._update_rts_state()
472 self.reset_input_buffer()
473 self.reset_output_buffer()
cliechti8099bed2009-08-01 23:59:18 +0000474
Chris Liechti3ad62fb2015-08-29 21:53:32 +0200475 def _reconfigure_port(self):
cliechti8099bed2009-08-01 23:59:18 +0000476 """Set communication parameters on opened port."""
477 if self._socket is None:
478 raise SerialException("Can only operate on open ports")
479
cliechti8099bed2009-08-01 23:59:18 +0000480 # if self._timeout != 0 and self._interCharTimeout is not None:
cliechti8099bed2009-08-01 23:59:18 +0000481 # XXX
482
Chris Liechti3ad62fb2015-08-29 21:53:32 +0200483 if self._write_timeout is not None:
484 raise NotImplementedError('write_timeout is currently not supported')
cliechti2b929b72009-08-02 23:49:02 +0000485 # XXX
cliechti8099bed2009-08-01 23:59:18 +0000486
cliechti2b929b72009-08-02 23:49:02 +0000487 # Setup the connection
cliechti1ef7e3e2009-08-03 02:38:43 +0000488 # to get good performance, all parameter changes are sent first...
Chris Liechti01587b12015-08-05 02:39:32 +0200489 if not 0 < self._baudrate < 2**32:
cliechti81c54762009-08-03 23:53:27 +0000490 raise ValueError("invalid baudrate: %r" % (self._baudrate))
Chris Liechti01587b12015-08-05 02:39:32 +0200491 self._rfc2217_port_settings['baudrate'].set(struct.pack(b'!I', self._baudrate))
492 self._rfc2217_port_settings['datasize'].set(struct.pack(b'!B', self._bytesize))
493 self._rfc2217_port_settings['parity'].set(struct.pack(b'!B', RFC2217_PARITY_MAP[self._parity]))
494 self._rfc2217_port_settings['stopsize'].set(struct.pack(b'!B', RFC2217_STOPBIT_MAP[self._stopbits]))
cliechti8099bed2009-08-01 23:59:18 +0000495
cliechti2b929b72009-08-02 23:49:02 +0000496 # and now wait until parameters are active
497 items = self._rfc2217_port_settings.values()
cliechti6a300772009-08-12 02:28:56 +0000498 if self.logger:
499 self.logger.debug("Negotiating settings: %s" % (items,))
cliechtidfe2d272009-08-10 22:19:41 +0000500 timeout_time = time.time() + self._network_timeout
cliechti2b929b72009-08-02 23:49:02 +0000501 while time.time() < timeout_time:
502 time.sleep(0.05) # prevent 100% CPU load
503 if sum(o.active for o in items) == len(items):
504 break
505 else:
506 raise SerialException("Remote does not accept parameter change (RFC2217): %r" % items)
cliechti6a300772009-08-12 02:28:56 +0000507 if self.logger:
508 self.logger.info("Negotiated settings: %s" % (items,))
cliechti8099bed2009-08-01 23:59:18 +0000509
510 if self._rtscts and self._xonxoff:
cliechti1ef7e3e2009-08-03 02:38:43 +0000511 raise ValueError('xonxoff and rtscts together are not supported')
cliechti8099bed2009-08-01 23:59:18 +0000512 elif self._rtscts:
cliechti1ef7e3e2009-08-03 02:38:43 +0000513 self.rfc2217SetControl(SET_CONTROL_USE_HW_FLOW_CONTROL)
cliechti8099bed2009-08-01 23:59:18 +0000514 elif self._xonxoff:
cliechti1ef7e3e2009-08-03 02:38:43 +0000515 self.rfc2217SetControl(SET_CONTROL_USE_SW_FLOW_CONTROL)
cliechti8099bed2009-08-01 23:59:18 +0000516 else:
cliechti1ef7e3e2009-08-03 02:38:43 +0000517 self.rfc2217SetControl(SET_CONTROL_USE_NO_FLOW_CONTROL)
cliechti8099bed2009-08-01 23:59:18 +0000518
519 def close(self):
520 """Close port"""
Chris Liechti3ad62fb2015-08-29 21:53:32 +0200521 if self.is_open:
cliechti8099bed2009-08-01 23:59:18 +0000522 if self._socket:
523 try:
524 self._socket.shutdown(socket.SHUT_RDWR)
525 self._socket.close()
526 except:
527 # ignore errors.
528 pass
529 self._socket = None
530 if self._thread:
531 self._thread.join()
Chris Liechti3ad62fb2015-08-29 21:53:32 +0200532 self.is_open = False
cliechti8099bed2009-08-01 23:59:18 +0000533 # in case of quick reconnects, give the server some time
534 time.sleep(0.3)
535
Chris Liechti3ad62fb2015-08-29 21:53:32 +0200536 def from_url(self, url):
cliechti1ef7e3e2009-08-03 02:38:43 +0000537 """extract host and port from an URL string"""
Chris Liechtia4222112015-08-07 01:03:12 +0200538 parts = urlparse.urlsplit(url)
Chris Liechtid14b1ab2015-08-21 00:28:53 +0200539 if parts.scheme != "rfc2217":
540 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 +0000541 try:
Chris Liechtia4222112015-08-07 01:03:12 +0200542 # process options now, directly altering self
Chris Liechtid14b1ab2015-08-21 00:28:53 +0200543 for option, values in urlparse.parse_qs(parts.query, True).items():
544 if option == 'logging':
Chris Liechtia4222112015-08-07 01:03:12 +0200545 logging.basicConfig() # XXX is that good to call it here?
546 self.logger = logging.getLogger('pySerial.rfc2217')
Chris Liechtid14b1ab2015-08-21 00:28:53 +0200547 self.logger.setLevel(LOGGER_LEVELS[values[0]])
Chris Liechtia4222112015-08-07 01:03:12 +0200548 self.logger.debug('enabled logging')
549 elif option == 'ign_set_control':
550 self._ignore_set_control_answer = True
551 elif option == 'poll_modem':
552 self._poll_modem_state = True
553 elif option == 'timeout':
Chris Liechtid14b1ab2015-08-21 00:28:53 +0200554 self._network_timeout = float(values[0])
Chris Liechtia4222112015-08-07 01:03:12 +0200555 else:
556 raise ValueError('unknown option: %r' % (option,))
cliechti81c54762009-08-03 23:53:27 +0000557 # get host and port
Chris Liechtia4222112015-08-07 01:03:12 +0200558 host, port = parts.hostname, parts.port
Chris Liechti033f17c2015-08-30 21:28:04 +0200559 if not 0 <= port < 65536:
560 raise ValueError("port not in range 0...65535")
Chris Liechti68340d72015-08-03 14:15:48 +0200561 except ValueError as e:
Chris Liechtid14b1ab2015-08-21 00:28:53 +0200562 raise SerialException('expected a string in the form "rfc2217://<host>:<port>[?option[&option...]]": %s' % e)
cliechti8099bed2009-08-01 23:59:18 +0000563 return (host, port)
564
565 # - - - - - - - - - - - - - - - - - - - - - - - -
566
Chris Liechtief1fe252015-08-27 23:25:21 +0200567 @property
568 def in_waiting(self):
Chris Liechti3ad62fb2015-08-29 21:53:32 +0200569 """Return the number of bytes currently in the input buffer."""
Chris Liechti033f17c2015-08-30 21:28:04 +0200570 if not self.is_open:
571 raise portNotOpenError
cliechti8099bed2009-08-01 23:59:18 +0000572 return self._read_buffer.qsize()
573
574 def read(self, size=1):
cliechtieada4fd2013-07-31 16:26:07 +0000575 """\
576 Read size bytes from the serial port. If a timeout is set it may
cliechti8099bed2009-08-01 23:59:18 +0000577 return less characters as requested. With no timeout it will block
cliechtieada4fd2013-07-31 16:26:07 +0000578 until the requested number of bytes is read.
579 """
Chris Liechti033f17c2015-08-30 21:28:04 +0200580 if not self.is_open:
581 raise portNotOpenError
cliechti8099bed2009-08-01 23:59:18 +0000582 data = bytearray()
583 try:
584 while len(data) < size:
cliechti81c54762009-08-03 23:53:27 +0000585 if self._thread is None:
586 raise SerialException('connection failed (reader thread died)')
Chris Liechti01587b12015-08-05 02:39:32 +0200587 data += self._read_buffer.get(True, self._timeout)
Chris Liechti033f17c2015-08-30 21:28:04 +0200588 except Queue.Empty: # -> timeout
cliechti8099bed2009-08-01 23:59:18 +0000589 pass
590 return bytes(data)
591
592 def write(self, data):
cliechtieada4fd2013-07-31 16:26:07 +0000593 """\
Chris Liechti3ad62fb2015-08-29 21:53:32 +0200594 Output the given byte string over the serial port. Can block if the
cliechti8099bed2009-08-01 23:59:18 +0000595 connection is blocked. May raise SerialException if the connection is
cliechtieada4fd2013-07-31 16:26:07 +0000596 closed.
597 """
Chris Liechti033f17c2015-08-30 21:28:04 +0200598 if not self.is_open:
599 raise portNotOpenError
Chris Liechti01587b12015-08-05 02:39:32 +0200600 with self._write_lock:
cliechti81c54762009-08-03 23:53:27 +0000601 try:
cliechti38077122013-10-16 02:57:27 +0000602 self._socket.sendall(to_bytes(data).replace(IAC, IAC_DOUBLED))
Chris Liechti68340d72015-08-03 14:15:48 +0200603 except socket.error as e:
Chris Liechti142ae562015-08-23 01:11:06 +0200604 raise SerialException("connection failed (socket error): %s" % (e,))
cliechti8099bed2009-08-01 23:59:18 +0000605 return len(data)
606
Chris Liechtief1fe252015-08-27 23:25:21 +0200607 def reset_input_buffer(self):
cliechti8099bed2009-08-01 23:59:18 +0000608 """Clear input buffer, discarding all that is in the buffer."""
Chris Liechti033f17c2015-08-30 21:28:04 +0200609 if not self.is_open:
610 raise portNotOpenError
cliechti1ef7e3e2009-08-03 02:38:43 +0000611 self.rfc2217SendPurge(PURGE_RECEIVE_BUFFER)
cliechti8099bed2009-08-01 23:59:18 +0000612 # empty read buffer
613 while self._read_buffer.qsize():
614 self._read_buffer.get(False)
615
Chris Liechtief1fe252015-08-27 23:25:21 +0200616 def reset_output_buffer(self):
cliechtieada4fd2013-07-31 16:26:07 +0000617 """\
618 Clear output buffer, aborting the current output and
619 discarding all that is in the buffer.
620 """
Chris Liechti033f17c2015-08-30 21:28:04 +0200621 if not self.is_open:
622 raise portNotOpenError
cliechti1ef7e3e2009-08-03 02:38:43 +0000623 self.rfc2217SendPurge(PURGE_TRANSMIT_BUFFER)
cliechti8099bed2009-08-01 23:59:18 +0000624
Chris Liechtief1fe252015-08-27 23:25:21 +0200625 def _update_break_state(self):
cliechtieada4fd2013-07-31 16:26:07 +0000626 """\
627 Set break: Controls TXD. When active, to transmitting is
628 possible.
629 """
Chris Liechti033f17c2015-08-30 21:28:04 +0200630 if not self.is_open:
631 raise portNotOpenError
cliechti6a300772009-08-12 02:28:56 +0000632 if self.logger:
Chris Liechtief1fe252015-08-27 23:25:21 +0200633 self.logger.info('set BREAK to %s' % ('active' if self._break_state else 'inactive'))
634 if self._break_state:
cliechti1ef7e3e2009-08-03 02:38:43 +0000635 self.rfc2217SetControl(SET_CONTROL_BREAK_ON)
cliechti8099bed2009-08-01 23:59:18 +0000636 else:
cliechti1ef7e3e2009-08-03 02:38:43 +0000637 self.rfc2217SetControl(SET_CONTROL_BREAK_OFF)
cliechti8099bed2009-08-01 23:59:18 +0000638
Chris Liechtief1fe252015-08-27 23:25:21 +0200639 def _update_rts_state(self):
cliechti044d8662009-08-11 21:40:31 +0000640 """Set terminal status line: Request To Send."""
Chris Liechti033f17c2015-08-30 21:28:04 +0200641 if not self.is_open:
642 raise portNotOpenError
cliechti6a300772009-08-12 02:28:56 +0000643 if self.logger:
Chris Liechtief1fe252015-08-27 23:25:21 +0200644 self.logger.info('set RTS to %s' % ('active' if self._rts_state else 'inactive'))
645 if self._rts_state:
cliechti1ef7e3e2009-08-03 02:38:43 +0000646 self.rfc2217SetControl(SET_CONTROL_RTS_ON)
cliechti8099bed2009-08-01 23:59:18 +0000647 else:
cliechti1ef7e3e2009-08-03 02:38:43 +0000648 self.rfc2217SetControl(SET_CONTROL_RTS_OFF)
cliechti8099bed2009-08-01 23:59:18 +0000649
Chris Liechtief1fe252015-08-27 23:25:21 +0200650 def _update_dtr_state(self, level=True):
cliechti044d8662009-08-11 21:40:31 +0000651 """Set terminal status line: Data Terminal Ready."""
Chris Liechti033f17c2015-08-30 21:28:04 +0200652 if not self.is_open:
653 raise portNotOpenError
cliechti6a300772009-08-12 02:28:56 +0000654 if self.logger:
Chris Liechtief1fe252015-08-27 23:25:21 +0200655 self.logger.info('set DTR to %s' % ('active' if self._dtr_state else 'inactive'))
656 if self._dtr_state:
cliechti1ef7e3e2009-08-03 02:38:43 +0000657 self.rfc2217SetControl(SET_CONTROL_DTR_ON)
cliechti8099bed2009-08-01 23:59:18 +0000658 else:
cliechti1ef7e3e2009-08-03 02:38:43 +0000659 self.rfc2217SetControl(SET_CONTROL_DTR_OFF)
cliechti8099bed2009-08-01 23:59:18 +0000660
Chris Liechtief1fe252015-08-27 23:25:21 +0200661 @property
662 def cts(self):
cliechti044d8662009-08-11 21:40:31 +0000663 """Read terminal status line: Clear To Send."""
Chris Liechti033f17c2015-08-30 21:28:04 +0200664 if not self.is_open:
665 raise portNotOpenError
cliechti7cb78e82009-08-05 15:47:57 +0000666 return bool(self.getModemState() & MODEMSTATE_MASK_CTS)
cliechti8099bed2009-08-01 23:59:18 +0000667
Chris Liechtief1fe252015-08-27 23:25:21 +0200668 @property
669 def dsr(self):
cliechti044d8662009-08-11 21:40:31 +0000670 """Read terminal status line: Data Set Ready."""
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_DSR)
cliechti8099bed2009-08-01 23:59:18 +0000674
Chris Liechtief1fe252015-08-27 23:25:21 +0200675 @property
676 def ri(self):
cliechti044d8662009-08-11 21:40:31 +0000677 """Read terminal status line: Ring Indicator."""
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_RI)
cliechti8099bed2009-08-01 23:59:18 +0000681
Chris Liechtief1fe252015-08-27 23:25:21 +0200682 @property
683 def cd(self):
cliechti044d8662009-08-11 21:40:31 +0000684 """Read terminal status line: Carrier Detect."""
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_CD)
cliechti8099bed2009-08-01 23:59:18 +0000688
689 # - - - platform specific - - -
690 # None so far
691
692 # - - - RFC2217 specific - - -
693
cliechti1ef7e3e2009-08-03 02:38:43 +0000694 def _telnetReadLoop(self):
cliechti7d448562014-08-03 21:57:45 +0000695 """Read loop for the socket."""
cliechti8099bed2009-08-01 23:59:18 +0000696 mode = M_NORMAL
697 suboption = None
cliechti81c54762009-08-03 23:53:27 +0000698 try:
699 while self._socket is not None:
700 try:
701 data = self._socket.recv(1024)
702 except socket.timeout:
703 # just need to get out of recv form time to time to check if
704 # still alive
705 continue
Chris Liechti68340d72015-08-03 14:15:48 +0200706 except socket.error as e:
cliechti81c54762009-08-03 23:53:27 +0000707 # connection fails -> terminate loop
cliechticb20a4f2011-04-25 02:25:54 +0000708 if self.logger:
709 self.logger.debug("socket error in reader thread: %s" % (e,))
cliechti81c54762009-08-03 23:53:27 +0000710 break
Chris Liechti033f17c2015-08-30 21:28:04 +0200711 if not data:
712 break # lost connection
Chris Liechtif99cd5c2015-08-13 22:54:16 +0200713 for byte in iterbytes(data):
cliechti81c54762009-08-03 23:53:27 +0000714 if mode == M_NORMAL:
715 # interpret as command or as data
716 if byte == IAC:
717 mode = M_IAC_SEEN
cliechti8099bed2009-08-01 23:59:18 +0000718 else:
cliechti81c54762009-08-03 23:53:27 +0000719 # store data in read buffer or sub option buffer
720 # depending on state
721 if suboption is not None:
Chris Liechti01587b12015-08-05 02:39:32 +0200722 suboption += byte
cliechti81c54762009-08-03 23:53:27 +0000723 else:
724 self._read_buffer.put(byte)
725 elif mode == M_IAC_SEEN:
726 if byte == IAC:
727 # interpret as command doubled -> insert character
728 # itself
cliechtif325c032009-12-25 16:09:49 +0000729 if suboption is not None:
Chris Liechti01587b12015-08-05 02:39:32 +0200730 suboption += IAC
cliechtif325c032009-12-25 16:09:49 +0000731 else:
732 self._read_buffer.put(IAC)
cliechti81c54762009-08-03 23:53:27 +0000733 mode = M_NORMAL
734 elif byte == SB:
735 # sub option start
736 suboption = bytearray()
737 mode = M_NORMAL
738 elif byte == SE:
739 # sub option end -> process it now
740 self._telnetProcessSubnegotiation(bytes(suboption))
741 suboption = None
742 mode = M_NORMAL
743 elif byte in (DO, DONT, WILL, WONT):
744 # negotiation
745 telnet_command = byte
746 mode = M_NEGOTIATE
747 else:
748 # other telnet commands
749 self._telnetProcessCommand(byte)
750 mode = M_NORMAL
Chris Liechti033f17c2015-08-30 21:28:04 +0200751 elif mode == M_NEGOTIATE: # DO, DONT, WILL, WONT was received, option now following
cliechti81c54762009-08-03 23:53:27 +0000752 self._telnetNegotiateOption(telnet_command, byte)
cliechti8099bed2009-08-01 23:59:18 +0000753 mode = M_NORMAL
cliechti81c54762009-08-03 23:53:27 +0000754 finally:
755 self._thread = None
cliechti6a300772009-08-12 02:28:56 +0000756 if self.logger:
757 self.logger.debug("read thread terminated")
cliechti8099bed2009-08-01 23:59:18 +0000758
759 # - incoming telnet commands and options
760
cliechti1ef7e3e2009-08-03 02:38:43 +0000761 def _telnetProcessCommand(self, command):
cliechti044d8662009-08-11 21:40:31 +0000762 """Process commands other than DO, DONT, WILL, WONT."""
cliechti1ef7e3e2009-08-03 02:38:43 +0000763 # Currently none. RFC2217 only uses negotiation and subnegotiation.
cliechti6a300772009-08-12 02:28:56 +0000764 if self.logger:
765 self.logger.warning("ignoring Telnet command: %r" % (command,))
cliechti8099bed2009-08-01 23:59:18 +0000766
cliechti1ef7e3e2009-08-03 02:38:43 +0000767 def _telnetNegotiateOption(self, command, option):
cliechti044d8662009-08-11 21:40:31 +0000768 """Process incoming DO, DONT, WILL, WONT."""
cliechti2b929b72009-08-02 23:49:02 +0000769 # check our registered telnet options and forward command to them
770 # they know themselves if they have to answer or not
cliechtiac205322009-08-02 20:40:21 +0000771 known = False
772 for item in self._telnet_options:
cliechti2b929b72009-08-02 23:49:02 +0000773 # can have more than one match! as some options are duplicated for
774 # 'us' and 'them'
cliechtiac205322009-08-02 20:40:21 +0000775 if item.option == option:
cliechti2b929b72009-08-02 23:49:02 +0000776 item.process_incoming(command)
cliechtiac205322009-08-02 20:40:21 +0000777 known = True
778 if not known:
779 # handle unknown options
780 # only answer to positive requests and deny them
781 if command == WILL or command == DO:
Chris Liechti142ae562015-08-23 01:11:06 +0200782 self.telnetSendOption((DONT if command == WILL else WONT), option)
cliechti6a300772009-08-12 02:28:56 +0000783 if self.logger:
784 self.logger.warning("rejected Telnet option: %r" % (option,))
cliechtiac205322009-08-02 20:40:21 +0000785
cliechti1ef7e3e2009-08-03 02:38:43 +0000786 def _telnetProcessSubnegotiation(self, suboption):
cliechti044d8662009-08-11 21:40:31 +0000787 """Process subnegotiation, the data between IAC SB and IAC SE."""
cliechti8099bed2009-08-01 23:59:18 +0000788 if suboption[0:1] == COM_PORT_OPTION:
789 if suboption[1:2] == SERVER_NOTIFY_LINESTATE and len(suboption) >= 3:
Chris Liechti033f17c2015-08-30 21:28:04 +0200790 self._linestate = ord(suboption[2:3]) # ensure it is a number
cliechti6a300772009-08-12 02:28:56 +0000791 if self.logger:
792 self.logger.info("NOTIFY_LINESTATE: %s" % self._linestate)
cliechti8099bed2009-08-01 23:59:18 +0000793 elif suboption[1:2] == SERVER_NOTIFY_MODEMSTATE and len(suboption) >= 3:
Chris Liechti033f17c2015-08-30 21:28:04 +0200794 self._modemstate = ord(suboption[2:3]) # ensure it is a number
cliechti6a300772009-08-12 02:28:56 +0000795 if self.logger:
796 self.logger.info("NOTIFY_MODEMSTATE: %s" % self._modemstate)
cliechti7cb78e82009-08-05 15:47:57 +0000797 # update time when we think that a poll would make sense
798 self._modemstate_expires = time.time() + 0.3
cliechti672d0292009-08-03 02:01:57 +0000799 elif suboption[1:2] == FLOWCONTROL_SUSPEND:
800 self._remote_suspend_flow = True
801 elif suboption[1:2] == FLOWCONTROL_RESUME:
802 self._remote_suspend_flow = False
cliechti8099bed2009-08-01 23:59:18 +0000803 else:
cliechti2b929b72009-08-02 23:49:02 +0000804 for item in self._rfc2217_options.values():
805 if item.ack_option == suboption[1:2]:
cliechti81c54762009-08-03 23:53:27 +0000806 #~ print "processing COM_PORT_OPTION: %r" % list(suboption[1:])
cliechti2b929b72009-08-02 23:49:02 +0000807 item.checkAnswer(bytes(suboption[2:]))
808 break
809 else:
cliechti6a300772009-08-12 02:28:56 +0000810 if self.logger:
811 self.logger.warning("ignoring COM_PORT_OPTION: %r" % (suboption,))
cliechti8099bed2009-08-01 23:59:18 +0000812 else:
cliechti6a300772009-08-12 02:28:56 +0000813 if self.logger:
814 self.logger.warning("ignoring subnegotiation: %r" % (suboption,))
cliechti8099bed2009-08-01 23:59:18 +0000815
816 # - outgoing telnet commands and options
817
cliechti81c54762009-08-03 23:53:27 +0000818 def _internal_raw_write(self, data):
cliechti044d8662009-08-11 21:40:31 +0000819 """internal socket write with no data escaping. used to send telnet stuff."""
Chris Liechti01587b12015-08-05 02:39:32 +0200820 with self._write_lock:
cliechti81c54762009-08-03 23:53:27 +0000821 self._socket.sendall(data)
cliechti81c54762009-08-03 23:53:27 +0000822
cliechti1ef7e3e2009-08-03 02:38:43 +0000823 def telnetSendOption(self, action, option):
cliechti044d8662009-08-11 21:40:31 +0000824 """Send DO, DONT, WILL, WONT."""
cliechti81c54762009-08-03 23:53:27 +0000825 self._internal_raw_write(to_bytes([IAC, action, option]))
cliechti8099bed2009-08-01 23:59:18 +0000826
Chris Liechtib4cda3a2015-08-08 17:12:08 +0200827 def rfc2217SendSubnegotiation(self, option, value=b''):
cliechti044d8662009-08-11 21:40:31 +0000828 """Subnegotiation of RFC2217 parameters."""
cliechtif325c032009-12-25 16:09:49 +0000829 value = value.replace(IAC, IAC_DOUBLED)
cliechti81c54762009-08-03 23:53:27 +0000830 self._internal_raw_write(to_bytes([IAC, SB, COM_PORT_OPTION, option] + list(value) + [IAC, SE]))
cliechti2b929b72009-08-02 23:49:02 +0000831
cliechti1ef7e3e2009-08-03 02:38:43 +0000832 def rfc2217SendPurge(self, value):
cliechti2b929b72009-08-02 23:49:02 +0000833 item = self._rfc2217_options['purge']
Chris Liechti033f17c2015-08-30 21:28:04 +0200834 item.set(value) # transmit desired purge type
835 item.wait(self._network_timeout) # wait for acknowledge from the server
cliechti2b929b72009-08-02 23:49:02 +0000836
cliechti1ef7e3e2009-08-03 02:38:43 +0000837 def rfc2217SetControl(self, value):
cliechti81c54762009-08-03 23:53:27 +0000838 item = self._rfc2217_options['control']
Chris Liechti033f17c2015-08-30 21:28:04 +0200839 item.set(value) # transmit desired control type
cliechti81c54762009-08-03 23:53:27 +0000840 if self._ignore_set_control_answer:
841 # answers are ignored when option is set. compatibility mode for
cliechticb20a4f2011-04-25 02:25:54 +0000842 # servers that answer, but not the expected one... (or no answer
cliechti81c54762009-08-03 23:53:27 +0000843 # at all) i.e. sredird
844 time.sleep(0.1) # this helps getting the unit tests passed
845 else:
cliechtidfe2d272009-08-10 22:19:41 +0000846 item.wait(self._network_timeout) # wait for acknowledge from the server
cliechti8099bed2009-08-01 23:59:18 +0000847
cliechti1ef7e3e2009-08-03 02:38:43 +0000848 def rfc2217FlowServerReady(self):
cliechtieada4fd2013-07-31 16:26:07 +0000849 """\
850 check if server is ready to receive data. block for some time when
851 not.
852 """
cliechti672d0292009-08-03 02:01:57 +0000853 #~ if self._remote_suspend_flow:
854 #~ wait---
855
cliechti7cb78e82009-08-05 15:47:57 +0000856 def getModemState(self):
cliechtieada4fd2013-07-31 16:26:07 +0000857 """\
cliechti7d448562014-08-03 21:57:45 +0000858 get last modem state (cached value. If value is "old", request a new
859 one. This cache helps that we don't issue to many requests when e.g. all
860 status lines, one after the other is queried by the user (getCTS, getDSR
cliechtieada4fd2013-07-31 16:26:07 +0000861 etc.)
862 """
cliechti7cb78e82009-08-05 15:47:57 +0000863 # active modem state polling enabled? is the value fresh enough?
864 if self._poll_modem_state and self._modemstate_expires < time.time():
cliechti6a300772009-08-12 02:28:56 +0000865 if self.logger:
866 self.logger.debug('polling modem state')
cliechti7cb78e82009-08-05 15:47:57 +0000867 # when it is older, request an update
868 self.rfc2217SendSubnegotiation(NOTIFY_MODEMSTATE)
cliechtidfe2d272009-08-10 22:19:41 +0000869 timeout_time = time.time() + self._network_timeout
cliechti7cb78e82009-08-05 15:47:57 +0000870 while time.time() < timeout_time:
871 time.sleep(0.05) # prevent 100% CPU load
872 # when expiration time is updated, it means that there is a new
873 # value
874 if self._modemstate_expires > time.time():
875 break
Chris Liechti01587b12015-08-05 02:39:32 +0200876 else:
877 if self.logger:
878 self.logger.warning('poll for modem state failed')
cliechti7cb78e82009-08-05 15:47:57 +0000879 # even when there is a timeout, do not generate an error just
880 # return the last known value. this way we can support buggy
881 # servers that do not respond to polls, but send automatic
882 # updates.
883 if self._modemstate is not None:
cliechti6a300772009-08-12 02:28:56 +0000884 if self.logger:
885 self.logger.debug('using cached modem state')
cliechti7cb78e82009-08-05 15:47:57 +0000886 return self._modemstate
887 else:
888 # never received a notification from the server
cliechti8fb119c2009-08-05 23:39:45 +0000889 raise SerialException("remote sends no NOTIFY_MODEMSTATE")
cliechti8099bed2009-08-01 23:59:18 +0000890
cliechti5cc3eb12009-08-11 23:04:30 +0000891
cliechti595ed5b2009-08-10 01:43:32 +0000892#############################################################################
cliechti5cc3eb12009-08-11 23:04:30 +0000893# The following is code that helps implementing an RFC 2217 server.
cliechti8099bed2009-08-01 23:59:18 +0000894
cliechti8ccc2ff2009-08-05 12:44:46 +0000895class PortManager(object):
cliechtieada4fd2013-07-31 16:26:07 +0000896 """\
897 This class manages the state of Telnet and RFC 2217. It needs a serial
cliechticb20a4f2011-04-25 02:25:54 +0000898 instance and a connection to work with. Connection is expected to implement
cliechtieada4fd2013-07-31 16:26:07 +0000899 a (thread safe) write function, that writes the string to the network.
900 """
cliechti130d1f02009-08-04 02:10:58 +0000901
cliechti6a300772009-08-12 02:28:56 +0000902 def __init__(self, serial_port, connection, logger=None):
cliechti130d1f02009-08-04 02:10:58 +0000903 self.serial = serial_port
904 self.connection = connection
cliechti6a300772009-08-12 02:28:56 +0000905 self.logger = logger
cliechti86b593e2009-08-05 16:28:12 +0000906 self._client_is_rfc2217 = False
cliechti130d1f02009-08-04 02:10:58 +0000907
908 # filter state machine
909 self.mode = M_NORMAL
910 self.suboption = None
911 self.telnet_command = None
912
913 # states for modem/line control events
914 self.modemstate_mask = 255
915 self.last_modemstate = None
916 self.linstate_mask = 0
917
918 # all supported telnet options
919 self._telnet_options = [
920 TelnetOption(self, 'ECHO', ECHO, WILL, WONT, DO, DONT, REQUESTED),
921 TelnetOption(self, 'we-SGA', SGA, WILL, WONT, DO, DONT, REQUESTED),
922 TelnetOption(self, 'they-SGA', SGA, DO, DONT, WILL, WONT, INACTIVE),
923 TelnetOption(self, 'we-BINARY', BINARY, WILL, WONT, DO, DONT, INACTIVE),
924 TelnetOption(self, 'they-BINARY', BINARY, DO, DONT, WILL, WONT, REQUESTED),
cliechti86b593e2009-08-05 16:28:12 +0000925 TelnetOption(self, 'we-RFC2217', COM_PORT_OPTION, WILL, WONT, DO, DONT, REQUESTED, self._client_ok),
926 TelnetOption(self, 'they-RFC2217', COM_PORT_OPTION, DO, DONT, WILL, WONT, INACTIVE, self._client_ok),
cliechti130d1f02009-08-04 02:10:58 +0000927 ]
928
929 # negotiate Telnet/RFC2217 -> send initial requests
cliechti6a300772009-08-12 02:28:56 +0000930 if self.logger:
931 self.logger.debug("requesting initial Telnet/RFC 2217 options")
cliechti130d1f02009-08-04 02:10:58 +0000932 for option in self._telnet_options:
933 if option.state is REQUESTED:
934 self.telnetSendOption(option.send_yes, option.option)
935 # issue 1st modem state notification
cliechti86b593e2009-08-05 16:28:12 +0000936
937 def _client_ok(self):
cliechtieada4fd2013-07-31 16:26:07 +0000938 """\
cliechti7d448562014-08-03 21:57:45 +0000939 callback of telnet option. It gets called when option is activated.
940 This one here is used to detect when the client agrees on RFC 2217. A
cliechti86b593e2009-08-05 16:28:12 +0000941 flag is set so that other functions like check_modem_lines know if the
cliechti7d448562014-08-03 21:57:45 +0000942 client is OK.
cliechtieada4fd2013-07-31 16:26:07 +0000943 """
cliechti86b593e2009-08-05 16:28:12 +0000944 # The callback is used for we and they so if one party agrees, we're
945 # already happy. it seems not all servers do the negotiation correctly
946 # and i guess there are incorrect clients too.. so be happy if client
947 # answers one or the other positively.
948 self._client_is_rfc2217 = True
cliechti6a300772009-08-12 02:28:56 +0000949 if self.logger:
950 self.logger.info("client accepts RFC 2217")
cliechti8fb119c2009-08-05 23:39:45 +0000951 # this is to ensure that the client gets a notification, even if there
952 # was no change
953 self.check_modem_lines(force_notification=True)
cliechti130d1f02009-08-04 02:10:58 +0000954
955 # - outgoing telnet commands and options
956
957 def telnetSendOption(self, action, option):
cliechti044d8662009-08-11 21:40:31 +0000958 """Send DO, DONT, WILL, WONT."""
cliechti130d1f02009-08-04 02:10:58 +0000959 self.connection.write(to_bytes([IAC, action, option]))
960
Chris Liechtib4cda3a2015-08-08 17:12:08 +0200961 def rfc2217SendSubnegotiation(self, option, value=b''):
cliechti044d8662009-08-11 21:40:31 +0000962 """Subnegotiation of RFC 2217 parameters."""
cliechtif325c032009-12-25 16:09:49 +0000963 value = value.replace(IAC, IAC_DOUBLED)
cliechti130d1f02009-08-04 02:10:58 +0000964 self.connection.write(to_bytes([IAC, SB, COM_PORT_OPTION, option] + list(value) + [IAC, SE]))
965
966 # - check modem lines, needs to be called periodically from user to
967 # establish polling
968
cliechti7cb78e82009-08-05 15:47:57 +0000969 def check_modem_lines(self, force_notification=False):
cliechti130d1f02009-08-04 02:10:58 +0000970 modemstate = (
Chris Liechti142ae562015-08-23 01:11:06 +0200971 (self.serial.getCTS() and MODEMSTATE_MASK_CTS) |
972 (self.serial.getDSR() and MODEMSTATE_MASK_DSR) |
973 (self.serial.getRI() and MODEMSTATE_MASK_RI) |
974 (self.serial.getCD() and MODEMSTATE_MASK_CD))
cliechti7cb78e82009-08-05 15:47:57 +0000975 # check what has changed
Chris Liechti033f17c2015-08-30 21:28:04 +0200976 deltas = modemstate ^ (self.last_modemstate or 0) # when last is None -> 0
cliechti7cb78e82009-08-05 15:47:57 +0000977 if deltas & MODEMSTATE_MASK_CTS:
978 modemstate |= MODEMSTATE_MASK_CTS_CHANGE
979 if deltas & MODEMSTATE_MASK_DSR:
980 modemstate |= MODEMSTATE_MASK_DSR_CHANGE
981 if deltas & MODEMSTATE_MASK_RI:
982 modemstate |= MODEMSTATE_MASK_RI_CHANGE
983 if deltas & MODEMSTATE_MASK_CD:
984 modemstate |= MODEMSTATE_MASK_CD_CHANGE
985 # if new state is different and the mask allows this change, send
cliechti86b593e2009-08-05 16:28:12 +0000986 # notification. suppress notifications when client is not rfc2217
cliechti7cb78e82009-08-05 15:47:57 +0000987 if modemstate != self.last_modemstate or force_notification:
cliechti8fb119c2009-08-05 23:39:45 +0000988 if (self._client_is_rfc2217 and (modemstate & self.modemstate_mask)) or force_notification:
cliechti7cb78e82009-08-05 15:47:57 +0000989 self.rfc2217SendSubnegotiation(
990 SERVER_NOTIFY_MODEMSTATE,
991 to_bytes([modemstate & self.modemstate_mask])
992 )
cliechti6a300772009-08-12 02:28:56 +0000993 if self.logger:
994 self.logger.info("NOTIFY_MODEMSTATE: %s" % (modemstate,))
cliechti7cb78e82009-08-05 15:47:57 +0000995 # save last state, but forget about deltas.
996 # otherwise it would also notify about changing deltas which is
997 # probably not very useful
998 self.last_modemstate = modemstate & 0xf0
cliechti130d1f02009-08-04 02:10:58 +0000999
cliechti32c10332009-08-05 13:23:43 +00001000 # - outgoing data escaping
1001
1002 def escape(self, data):
cliechtieada4fd2013-07-31 16:26:07 +00001003 """\
cliechti7d448562014-08-03 21:57:45 +00001004 This generator function is for the user. All outgoing data has to be
cliechticb20a4f2011-04-25 02:25:54 +00001005 properly escaped, so that no IAC character in the data stream messes up
1006 the Telnet state machine in the server.
cliechti32c10332009-08-05 13:23:43 +00001007
1008 socket.sendall(escape(data))
1009 """
1010 for byte in data:
1011 if byte == IAC:
1012 yield IAC
1013 yield IAC
1014 else:
1015 yield byte
1016
cliechti130d1f02009-08-04 02:10:58 +00001017 # - incoming data filter
1018
1019 def filter(self, data):
cliechtieada4fd2013-07-31 16:26:07 +00001020 """\
cliechti7d448562014-08-03 21:57:45 +00001021 Handle a bunch of incoming bytes. This is a generator. It will yield
cliechti044d8662009-08-11 21:40:31 +00001022 all characters not of interest for Telnet/RFC 2217.
cliechti130d1f02009-08-04 02:10:58 +00001023
1024 The idea is that the reader thread pushes data from the socket through
1025 this filter:
1026
1027 for byte in filter(socket.recv(1024)):
1028 # do things like CR/LF conversion/whatever
1029 # and write data to the serial port
1030 serial.write(byte)
1031
1032 (socket error handling code left as exercise for the reader)
1033 """
Chris Liechtif99cd5c2015-08-13 22:54:16 +02001034 for byte in iterbytes(data):
cliechti130d1f02009-08-04 02:10:58 +00001035 if self.mode == M_NORMAL:
1036 # interpret as command or as data
1037 if byte == IAC:
1038 self.mode = M_IAC_SEEN
1039 else:
1040 # store data in sub option buffer or pass it to our
1041 # consumer depending on state
1042 if self.suboption is not None:
Chris Liechti01587b12015-08-05 02:39:32 +02001043 self.suboption += byte
cliechti130d1f02009-08-04 02:10:58 +00001044 else:
1045 yield byte
1046 elif self.mode == M_IAC_SEEN:
1047 if byte == IAC:
1048 # interpret as command doubled -> insert character
1049 # itself
cliechtif325c032009-12-25 16:09:49 +00001050 if self.suboption is not None:
Chris Liechti01587b12015-08-05 02:39:32 +02001051 self.suboption += byte
cliechtif325c032009-12-25 16:09:49 +00001052 else:
1053 yield byte
cliechti130d1f02009-08-04 02:10:58 +00001054 self.mode = M_NORMAL
1055 elif byte == SB:
1056 # sub option start
1057 self.suboption = bytearray()
1058 self.mode = M_NORMAL
1059 elif byte == SE:
1060 # sub option end -> process it now
1061 self._telnetProcessSubnegotiation(bytes(self.suboption))
1062 self.suboption = None
1063 self.mode = M_NORMAL
1064 elif byte in (DO, DONT, WILL, WONT):
1065 # negotiation
1066 self.telnet_command = byte
1067 self.mode = M_NEGOTIATE
1068 else:
1069 # other telnet commands
1070 self._telnetProcessCommand(byte)
1071 self.mode = M_NORMAL
Chris Liechti033f17c2015-08-30 21:28:04 +02001072 elif self.mode == M_NEGOTIATE: # DO, DONT, WILL, WONT was received, option now following
cliechti130d1f02009-08-04 02:10:58 +00001073 self._telnetNegotiateOption(self.telnet_command, byte)
1074 self.mode = M_NORMAL
1075
1076 # - incoming telnet commands and options
1077
1078 def _telnetProcessCommand(self, command):
cliechti044d8662009-08-11 21:40:31 +00001079 """Process commands other than DO, DONT, WILL, WONT."""
cliechti130d1f02009-08-04 02:10:58 +00001080 # Currently none. RFC2217 only uses negotiation and subnegotiation.
cliechti6a300772009-08-12 02:28:56 +00001081 if self.logger:
1082 self.logger.warning("ignoring Telnet command: %r" % (command,))
cliechti130d1f02009-08-04 02:10:58 +00001083
1084 def _telnetNegotiateOption(self, command, option):
cliechti044d8662009-08-11 21:40:31 +00001085 """Process incoming DO, DONT, WILL, WONT."""
cliechti130d1f02009-08-04 02:10:58 +00001086 # check our registered telnet options and forward command to them
1087 # they know themselves if they have to answer or not
1088 known = False
1089 for item in self._telnet_options:
1090 # can have more than one match! as some options are duplicated for
1091 # 'us' and 'them'
1092 if item.option == option:
1093 item.process_incoming(command)
1094 known = True
1095 if not known:
1096 # handle unknown options
1097 # only answer to positive requests and deny them
1098 if command == WILL or command == DO:
Chris Liechti142ae562015-08-23 01:11:06 +02001099 self.telnetSendOption((DONT if command == WILL else WONT), option)
cliechti6a300772009-08-12 02:28:56 +00001100 if self.logger:
1101 self.logger.warning("rejected Telnet option: %r" % (option,))
cliechti130d1f02009-08-04 02:10:58 +00001102
cliechti130d1f02009-08-04 02:10:58 +00001103 def _telnetProcessSubnegotiation(self, suboption):
cliechti044d8662009-08-11 21:40:31 +00001104 """Process subnegotiation, the data between IAC SB and IAC SE."""
cliechti130d1f02009-08-04 02:10:58 +00001105 if suboption[0:1] == COM_PORT_OPTION:
cliechti6a300772009-08-12 02:28:56 +00001106 if self.logger:
1107 self.logger.debug('received COM_PORT_OPTION: %r' % (suboption,))
cliechti130d1f02009-08-04 02:10:58 +00001108 if suboption[1:2] == SET_BAUDRATE:
1109 backup = self.serial.baudrate
1110 try:
Chris Liechti01587b12015-08-05 02:39:32 +02001111 (baudrate,) = struct.unpack(b"!I", suboption[2:6])
cliechtieada4fd2013-07-31 16:26:07 +00001112 if baudrate != 0:
1113 self.serial.baudrate = baudrate
Chris Liechtid2146002015-08-04 16:57:16 +02001114 except ValueError as e:
cliechti6a300772009-08-12 02:28:56 +00001115 if self.logger:
1116 self.logger.error("failed to set baud rate: %s" % (e,))
cliechti130d1f02009-08-04 02:10:58 +00001117 self.serial.baudrate = backup
cliechti5cc3eb12009-08-11 23:04:30 +00001118 else:
cliechti6a300772009-08-12 02:28:56 +00001119 if self.logger:
Chris Liechti142ae562015-08-23 01:11:06 +02001120 self.logger.info("%s baud rate: %s" % ('set' if baudrate else 'get', self.serial.baudrate))
Chris Liechti01587b12015-08-05 02:39:32 +02001121 self.rfc2217SendSubnegotiation(SERVER_SET_BAUDRATE, struct.pack(b"!I", self.serial.baudrate))
cliechti130d1f02009-08-04 02:10:58 +00001122 elif suboption[1:2] == SET_DATASIZE:
1123 backup = self.serial.bytesize
1124 try:
Chris Liechti142ae562015-08-23 01:11:06 +02001125 (datasize,) = struct.unpack(b"!B", suboption[2:3])
cliechtieada4fd2013-07-31 16:26:07 +00001126 if datasize != 0:
1127 self.serial.bytesize = datasize
Chris Liechtid2146002015-08-04 16:57:16 +02001128 except ValueError as e:
cliechti6a300772009-08-12 02:28:56 +00001129 if self.logger:
1130 self.logger.error("failed to set data size: %s" % (e,))
cliechti130d1f02009-08-04 02:10:58 +00001131 self.serial.bytesize = backup
cliechti5cc3eb12009-08-11 23:04:30 +00001132 else:
cliechti6a300772009-08-12 02:28:56 +00001133 if self.logger:
Chris Liechti142ae562015-08-23 01:11:06 +02001134 self.logger.info("%s data size: %s" % ('set' if datasize else 'get', self.serial.bytesize))
Chris Liechti01587b12015-08-05 02:39:32 +02001135 self.rfc2217SendSubnegotiation(SERVER_SET_DATASIZE, struct.pack(b"!B", self.serial.bytesize))
cliechti130d1f02009-08-04 02:10:58 +00001136 elif suboption[1:2] == SET_PARITY:
1137 backup = self.serial.parity
1138 try:
Chris Liechti01587b12015-08-05 02:39:32 +02001139 parity = struct.unpack(b"!B", suboption[2:3])[0]
cliechtieada4fd2013-07-31 16:26:07 +00001140 if parity != 0:
1141 self.serial.parity = RFC2217_REVERSE_PARITY_MAP[parity]
Chris Liechtid2146002015-08-04 16:57:16 +02001142 except ValueError as e:
cliechti6a300772009-08-12 02:28:56 +00001143 if self.logger:
1144 self.logger.error("failed to set parity: %s" % (e,))
cliechti130d1f02009-08-04 02:10:58 +00001145 self.serial.parity = backup
cliechti5cc3eb12009-08-11 23:04:30 +00001146 else:
cliechti6a300772009-08-12 02:28:56 +00001147 if self.logger:
Chris Liechti142ae562015-08-23 01:11:06 +02001148 self.logger.info("%s parity: %s" % ('set' if parity else 'get', self.serial.parity))
cliechti130d1f02009-08-04 02:10:58 +00001149 self.rfc2217SendSubnegotiation(
1150 SERVER_SET_PARITY,
Chris Liechtib4cda3a2015-08-08 17:12:08 +02001151 struct.pack(b"!B", RFC2217_PARITY_MAP[self.serial.parity])
cliechti130d1f02009-08-04 02:10:58 +00001152 )
1153 elif suboption[1:2] == SET_STOPSIZE:
1154 backup = self.serial.stopbits
1155 try:
Chris Liechti01587b12015-08-05 02:39:32 +02001156 stopbits = struct.unpack(b"!B", suboption[2:3])[0]
cliechtieada4fd2013-07-31 16:26:07 +00001157 if stopbits != 0:
1158 self.serial.stopbits = RFC2217_REVERSE_STOPBIT_MAP[stopbits]
Chris Liechtid2146002015-08-04 16:57:16 +02001159 except ValueError as e:
cliechti6a300772009-08-12 02:28:56 +00001160 if self.logger:
1161 self.logger.error("failed to set stop bits: %s" % (e,))
cliechti130d1f02009-08-04 02:10:58 +00001162 self.serial.stopbits = backup
cliechti5cc3eb12009-08-11 23:04:30 +00001163 else:
cliechti6a300772009-08-12 02:28:56 +00001164 if self.logger:
Chris Liechti142ae562015-08-23 01:11:06 +02001165 self.logger.info("%s stop bits: %s" % ('set' if stopbits else 'get', self.serial.stopbits))
cliechti130d1f02009-08-04 02:10:58 +00001166 self.rfc2217SendSubnegotiation(
1167 SERVER_SET_STOPSIZE,
Chris Liechti01587b12015-08-05 02:39:32 +02001168 struct.pack(b"!B", RFC2217_STOPBIT_MAP[self.serial.stopbits])
cliechti130d1f02009-08-04 02:10:58 +00001169 )
1170 elif suboption[1:2] == SET_CONTROL:
1171 if suboption[2:3] == SET_CONTROL_REQ_FLOW_SETTING:
1172 if self.serial.xonxoff:
1173 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_SW_FLOW_CONTROL)
1174 elif self.serial.rtscts:
1175 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_HW_FLOW_CONTROL)
1176 else:
1177 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_NO_FLOW_CONTROL)
1178 elif suboption[2:3] == SET_CONTROL_USE_NO_FLOW_CONTROL:
1179 self.serial.xonxoff = False
1180 self.serial.rtscts = False
cliechti6a300772009-08-12 02:28:56 +00001181 if self.logger:
1182 self.logger.info("changed flow control to None")
cliechti130d1f02009-08-04 02:10:58 +00001183 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_NO_FLOW_CONTROL)
1184 elif suboption[2:3] == SET_CONTROL_USE_SW_FLOW_CONTROL:
1185 self.serial.xonxoff = True
cliechti6a300772009-08-12 02:28:56 +00001186 if self.logger:
1187 self.logger.info("changed flow control to XON/XOFF")
cliechti130d1f02009-08-04 02:10:58 +00001188 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_SW_FLOW_CONTROL)
1189 elif suboption[2:3] == SET_CONTROL_USE_HW_FLOW_CONTROL:
1190 self.serial.rtscts = True
cliechti6a300772009-08-12 02:28:56 +00001191 if self.logger:
1192 self.logger.info("changed flow control to RTS/CTS")
cliechti130d1f02009-08-04 02:10:58 +00001193 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_HW_FLOW_CONTROL)
1194 elif suboption[2:3] == SET_CONTROL_REQ_BREAK_STATE:
cliechti6a300772009-08-12 02:28:56 +00001195 if self.logger:
1196 self.logger.warning("requested break state - not implemented")
Chris Liechti033f17c2015-08-30 21:28:04 +02001197 pass # XXX needs cached value
cliechti130d1f02009-08-04 02:10:58 +00001198 elif suboption[2:3] == SET_CONTROL_BREAK_ON:
1199 self.serial.setBreak(True)
cliechti6a300772009-08-12 02:28:56 +00001200 if self.logger:
1201 self.logger.info("changed BREAK to active")
cliechti130d1f02009-08-04 02:10:58 +00001202 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_BREAK_ON)
1203 elif suboption[2:3] == SET_CONTROL_BREAK_OFF:
1204 self.serial.setBreak(False)
cliechti6a300772009-08-12 02:28:56 +00001205 if self.logger:
1206 self.logger.info("changed BREAK to inactive")
cliechti130d1f02009-08-04 02:10:58 +00001207 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_BREAK_OFF)
1208 elif suboption[2:3] == SET_CONTROL_REQ_DTR:
cliechti6a300772009-08-12 02:28:56 +00001209 if self.logger:
1210 self.logger.warning("requested DTR state - not implemented")
Chris Liechti033f17c2015-08-30 21:28:04 +02001211 pass # XXX needs cached value
cliechti130d1f02009-08-04 02:10:58 +00001212 elif suboption[2:3] == SET_CONTROL_DTR_ON:
1213 self.serial.setDTR(True)
cliechti6a300772009-08-12 02:28:56 +00001214 if self.logger:
1215 self.logger.info("changed DTR to active")
cliechti130d1f02009-08-04 02:10:58 +00001216 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_DTR_ON)
1217 elif suboption[2:3] == SET_CONTROL_DTR_OFF:
1218 self.serial.setDTR(False)
cliechti6a300772009-08-12 02:28:56 +00001219 if self.logger:
1220 self.logger.info("changed DTR to inactive")
cliechti130d1f02009-08-04 02:10:58 +00001221 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_DTR_OFF)
1222 elif suboption[2:3] == SET_CONTROL_REQ_RTS:
cliechti6a300772009-08-12 02:28:56 +00001223 if self.logger:
1224 self.logger.warning("requested RTS state - not implemented")
Chris Liechti033f17c2015-08-30 21:28:04 +02001225 pass # XXX needs cached value
cliechti130d1f02009-08-04 02:10:58 +00001226 #~ self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_RTS_ON)
1227 elif suboption[2:3] == SET_CONTROL_RTS_ON:
1228 self.serial.setRTS(True)
cliechti6a300772009-08-12 02:28:56 +00001229 if self.logger:
1230 self.logger.info("changed RTS to active")
cliechti130d1f02009-08-04 02:10:58 +00001231 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_RTS_ON)
1232 elif suboption[2:3] == SET_CONTROL_RTS_OFF:
1233 self.serial.setRTS(False)
cliechti6a300772009-08-12 02:28:56 +00001234 if self.logger:
1235 self.logger.info("changed RTS to inactive")
cliechti130d1f02009-08-04 02:10:58 +00001236 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_RTS_OFF)
1237 #~ elif suboption[2:3] == SET_CONTROL_REQ_FLOW_SETTING_IN:
1238 #~ elif suboption[2:3] == SET_CONTROL_USE_NO_FLOW_CONTROL_IN:
1239 #~ elif suboption[2:3] == SET_CONTROL_USE_SW_FLOW_CONTOL_IN:
1240 #~ elif suboption[2:3] == SET_CONTROL_USE_HW_FLOW_CONTOL_IN:
1241 #~ elif suboption[2:3] == SET_CONTROL_USE_DCD_FLOW_CONTROL:
1242 #~ elif suboption[2:3] == SET_CONTROL_USE_DTR_FLOW_CONTROL:
1243 #~ elif suboption[2:3] == SET_CONTROL_USE_DSR_FLOW_CONTROL:
1244 elif suboption[1:2] == NOTIFY_LINESTATE:
cliechti7cb78e82009-08-05 15:47:57 +00001245 # client polls for current state
1246 self.rfc2217SendSubnegotiation(
Chris Liechti142ae562015-08-23 01:11:06 +02001247 SERVER_NOTIFY_LINESTATE,
1248 to_bytes([0])) # sorry, nothing like that implemented
cliechti130d1f02009-08-04 02:10:58 +00001249 elif suboption[1:2] == NOTIFY_MODEMSTATE:
cliechti6a300772009-08-12 02:28:56 +00001250 if self.logger:
1251 self.logger.info("request for modem state")
cliechti7cb78e82009-08-05 15:47:57 +00001252 # client polls for current state
1253 self.check_modem_lines(force_notification=True)
cliechti130d1f02009-08-04 02:10:58 +00001254 elif suboption[1:2] == FLOWCONTROL_SUSPEND:
cliechti6a300772009-08-12 02:28:56 +00001255 if self.logger:
1256 self.logger.info("suspend")
cliechti130d1f02009-08-04 02:10:58 +00001257 self._remote_suspend_flow = True
1258 elif suboption[1:2] == FLOWCONTROL_RESUME:
cliechti6a300772009-08-12 02:28:56 +00001259 if self.logger:
1260 self.logger.info("resume")
cliechti130d1f02009-08-04 02:10:58 +00001261 self._remote_suspend_flow = False
1262 elif suboption[1:2] == SET_LINESTATE_MASK:
Chris Liechti033f17c2015-08-30 21:28:04 +02001263 self.linstate_mask = ord(suboption[2:3]) # ensure it is a number
cliechti6a300772009-08-12 02:28:56 +00001264 if self.logger:
cliechtif325c032009-12-25 16:09:49 +00001265 self.logger.info("line state mask: 0x%02x" % (self.linstate_mask,))
cliechti130d1f02009-08-04 02:10:58 +00001266 elif suboption[1:2] == SET_MODEMSTATE_MASK:
Chris Liechti033f17c2015-08-30 21:28:04 +02001267 self.modemstate_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("modem state mask: 0x%02x" % (self.modemstate_mask,))
cliechti130d1f02009-08-04 02:10:58 +00001270 elif suboption[1:2] == PURGE_DATA:
1271 if suboption[2:3] == PURGE_RECEIVE_BUFFER:
Chris Liechti3ad62fb2015-08-29 21:53:32 +02001272 self.serial.reset_input_buffer()
cliechti6a300772009-08-12 02:28:56 +00001273 if self.logger:
1274 self.logger.info("purge in")
cliechti130d1f02009-08-04 02:10:58 +00001275 self.rfc2217SendSubnegotiation(SERVER_PURGE_DATA, PURGE_RECEIVE_BUFFER)
1276 elif suboption[2:3] == PURGE_TRANSMIT_BUFFER:
Chris Liechti3ad62fb2015-08-29 21:53:32 +02001277 self.serial.reset_output_buffer()
cliechti6a300772009-08-12 02:28:56 +00001278 if self.logger:
1279 self.logger.info("purge out")
cliechti130d1f02009-08-04 02:10:58 +00001280 self.rfc2217SendSubnegotiation(SERVER_PURGE_DATA, PURGE_TRANSMIT_BUFFER)
1281 elif suboption[2:3] == PURGE_BOTH_BUFFERS:
Chris Liechti3ad62fb2015-08-29 21:53:32 +02001282 self.serial.reset_input_buffer()
1283 self.serial.reset_output_buffer()
cliechti6a300772009-08-12 02:28:56 +00001284 if self.logger:
1285 self.logger.info("purge both")
cliechti130d1f02009-08-04 02:10:58 +00001286 self.rfc2217SendSubnegotiation(SERVER_PURGE_DATA, PURGE_BOTH_BUFFERS)
1287 else:
cliechti6a300772009-08-12 02:28:56 +00001288 if self.logger:
1289 self.logger.error("undefined PURGE_DATA: %r" % list(suboption[2:]))
cliechti130d1f02009-08-04 02:10:58 +00001290 else:
cliechti6a300772009-08-12 02:28:56 +00001291 if self.logger:
1292 self.logger.error("undefined COM_PORT_OPTION: %r" % list(suboption[1:]))
cliechti130d1f02009-08-04 02:10:58 +00001293 else:
cliechti6a300772009-08-12 02:28:56 +00001294 if self.logger:
1295 self.logger.warning("unknown subnegotiation: %r" % (suboption,))
cliechti130d1f02009-08-04 02:10:58 +00001296
1297
1298# simple client test
cliechti8099bed2009-08-01 23:59:18 +00001299if __name__ == '__main__':
1300 import sys
1301 s = Serial('rfc2217://localhost:7000', 115200)
1302 sys.stdout.write('%s\n' % s)
1303
cliechti8099bed2009-08-01 23:59:18 +00001304 sys.stdout.write("write...\n")
Chris Liechtib4cda3a2015-08-08 17:12:08 +02001305 s.write(b"hello\n")
cliechti8099bed2009-08-01 23:59:18 +00001306 s.flush()
cliechti8099bed2009-08-01 23:59:18 +00001307 sys.stdout.write("read: %s\n" % s.read(5))
cliechti8099bed2009-08-01 23:59:18 +00001308 s.close()