blob: d075e4e6a387fa871f9e9059d749c9d500ae1735 [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>
cliechti8099bed2009-08-01 23:59:18 +000011# this is distributed under a free software license, see license.txt
12
13# TODO:
14# - setting control line -> answer is not checked (had problems with one of the
15# severs). consider implementing a compatibility mode flag to make check
16# conditional
17# - write timeout not implemented at all
cliechti8099bed2009-08-01 23:59:18 +000018
cliechti81c54762009-08-03 23:53:27 +000019##############################################################################
20# observations and issues with servers
21#=============================================================================
22# sredird V2.2.1
23# - http://www.ibiblio.org/pub/Linux/system/serial/ sredird-2.2.2.tar.gz
24# - does not acknowledge SET_CONTROL (RTS/DTR) correctly, always responding
25# [105 1] instead of the actual value.
26# - SET_BAUDRATE answer contains 4 extra null bytes -> probably for larger
27# numbers than 2**32?
28# - To get the signature [COM_PORT_OPTION 0] has to be sent.
29# - run a server: while true; do nc -l -p 7000 -c "sredird debug /dev/ttyUSB0 /var/lock/sredir"; done
30#=============================================================================
31# telnetcpcd (untested)
32# - http://ftp.wayne.edu/kermit/sredird/telnetcpcd-1.09.tar.gz
33# - To get the signature [COM_PORT_OPTION] w/o data has to be sent.
34#=============================================================================
35# ser2net
36# - does not negotiate BINARY or COM_PORT_OPTION for his side but at least
37# acknowledges that the client activates these options
38# - The configuration may be that the server prints a banner. As this client
39# implementation does a flushInput on connect, this banner is hidden from
40# the user application.
41# - NOTIFY_MODEMSTATE: the poll interval of the server seems to be one
42# second.
43# - To get the signature [COM_PORT_OPTION 0] has to be sent.
44# - run a server: run ser2net daemon, in /etc/ser2net.conf:
45# 2000:telnet:0:/dev/ttyS0:9600 remctl banner
46##############################################################################
47
48# How to identify ports? pySerial might want to support other protocols in the
49# future, so lets use an URL scheme.
50# for RFC2217 compliant servers we will use this:
51# rfc2217://<host>:<port>[/option[/option...]]
52#
53# options:
Chris Liechti01587b12015-08-05 02:39:32 +020054# - "logging" set log level print diagnostic messages (e.g. "logging=debug")
cliechti81c54762009-08-03 23:53:27 +000055# - "ign_set_control": do not look at the answers to SET_CONTROL
cliechti7cb78e82009-08-05 15:47:57 +000056# - "poll_modem": issue NOTIFY_MODEMSTATE requests when CTS/DTR/RI/CD is read.
57# Without this option it expects that the server sends notifications
58# automatically on change (which most servers do and is according to the
59# RFC).
cliechti81c54762009-08-03 23:53:27 +000060# the order of the options is not relevant
61
cliechti5cc3eb12009-08-11 23:04:30 +000062import logging
Chris Liechtic4bca9e2015-08-07 14:40:41 +020063import socket
64import struct
65import threading
66import time
67try:
68 import urlparse
69except ImportError:
70 import urllib.parse as urlparse
Chris Liechtid2146002015-08-04 16:57:16 +020071try:
72 import Queue
73except ImportError:
74 import queue as Queue
75
Chris Liechtib4cda3a2015-08-08 17:12:08 +020076from serial.serialutil import *
77
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
cliechti86844e82009-08-12 00:05:33 +000083# map log level names to constants. used in fromURL()
cliechti5cc3eb12009-08-11 23:04:30 +000084LOGGER_LEVELS = {
Chris Liechtic4bca9e2015-08-07 14:40:41 +020085 '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 Liechtib4cda3a2015-08-08 17:12:08 +020093SE = b'\xf0' # Subnegotiation End
94NOP = b'\xf1' # No Operation
95DM = b'\xf2' # Data Mark
96BRK = b'\xf3' # Break
97IP = b'\xf4' # Interrupt process
98AO = b'\xf5' # Abort output
99AYT = b'\xf6' # Are You There
100EC = b'\xf7' # Erase Character
101EL = b'\xf8' # Erase Line
102GA = b'\xf9' # Go Ahead
103SB = b'\xfa' # Subnegotiation Begin
104WILL = b'\xfb'
105WONT = b'\xfc'
106DO = b'\xfd'
107DONT = b'\xfe'
108IAC = b'\xff' # Interpret As Command
109IAC_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 = {
206 PARITY_NONE: 1,
207 PARITY_ODD: 2,
208 PARITY_EVEN: 3,
209 PARITY_MARK: 4,
210 PARITY_SPACE: 5,
211}
cliechti130d1f02009-08-04 02:10:58 +0000212RFC2217_REVERSE_PARITY_MAP = dict((v,k) for k,v in RFC2217_PARITY_MAP.items())
cliechti8099bed2009-08-01 23:59:18 +0000213
214RFC2217_STOPBIT_MAP = {
215 STOPBITS_ONE: 1,
216 STOPBITS_ONE_POINT_FIVE: 3,
217 STOPBITS_TWO: 2,
218}
cliechti130d1f02009-08-04 02:10:58 +0000219RFC2217_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
232class TelnetOption(object):
cliechti1ef7e3e2009-08-03 02:38:43 +0000233 """Manage a single telnet option, keeps track of DO/DONT WILL/WONT."""
234
cliechti86b593e2009-08-05 16:28:12 +0000235 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 +0000236 """\
237 Initialize option.
cliechti1ef7e3e2009-08-03 02:38:43 +0000238 :param connection: connection used to transmit answers
239 :param name: a readable name for debug outputs
240 :param send_yes: what to send when option is to be enabled.
241 :param send_no: what to send when option is to be disabled.
242 :param ack_yes: what to expect when remote agrees on option.
243 :param ack_no: what to expect when remote disagrees on option.
244 :param initial_state: options initialized with REQUESTED are tried to
245 be enabled on startup. use INACTIVE for all others.
246 """
cliechti2b929b72009-08-02 23:49:02 +0000247 self.connection = connection
cliechtiac205322009-08-02 20:40:21 +0000248 self.name = name
249 self.option = option
250 self.send_yes = send_yes
251 self.send_no = send_no
252 self.ack_yes = ack_yes
253 self.ack_no = ack_no
254 self.state = initial_state
255 self.active = False
cliechti86b593e2009-08-05 16:28:12 +0000256 self.activation_callback = activation_callback
cliechtiac205322009-08-02 20:40:21 +0000257
258 def __repr__(self):
cliechti1ef7e3e2009-08-03 02:38:43 +0000259 """String for debug outputs"""
cliechtiac205322009-08-02 20:40:21 +0000260 return "%s:%s(%s)" % (self.name, self.active, self.state)
261
cliechti2b929b72009-08-02 23:49:02 +0000262 def process_incoming(self, command):
cliechti7d448562014-08-03 21:57:45 +0000263 """\
264 A DO/DONT/WILL/WONT was received for this option, update state and
265 answer when needed.
266 """
cliechtiac205322009-08-02 20:40:21 +0000267 if command == self.ack_yes:
268 if self.state is REQUESTED:
269 self.state = ACTIVE
270 self.active = True
cliechti86b593e2009-08-05 16:28:12 +0000271 if self.activation_callback is not None:
272 self.activation_callback()
cliechtiac205322009-08-02 20:40:21 +0000273 elif self.state is ACTIVE:
274 pass
275 elif self.state is INACTIVE:
276 self.state = ACTIVE
cliechti1ef7e3e2009-08-03 02:38:43 +0000277 self.connection.telnetSendOption(self.send_yes, self.option)
cliechtiac205322009-08-02 20:40:21 +0000278 self.active = True
cliechti86b593e2009-08-05 16:28:12 +0000279 if self.activation_callback is not None:
280 self.activation_callback()
cliechtiac205322009-08-02 20:40:21 +0000281 elif self.state is REALLY_INACTIVE:
cliechti1ef7e3e2009-08-03 02:38:43 +0000282 self.connection.telnetSendOption(self.send_no, self.option)
cliechtiac205322009-08-02 20:40:21 +0000283 else:
284 raise ValueError('option in illegal state %r' % self)
285 elif command == self.ack_no:
286 if self.state is REQUESTED:
287 self.state = INACTIVE
288 self.active = False
289 elif self.state is ACTIVE:
290 self.state = INACTIVE
cliechti1ef7e3e2009-08-03 02:38:43 +0000291 self.connection.telnetSendOption(self.send_no, self.option)
cliechtiac205322009-08-02 20:40:21 +0000292 self.active = False
293 elif self.state is INACTIVE:
294 pass
295 elif self.state is REALLY_INACTIVE:
296 pass
297 else:
298 raise ValueError('option in illegal state %r' % self)
299
300
cliechti2b929b72009-08-02 23:49:02 +0000301class TelnetSubnegotiation(object):
cliechtieada4fd2013-07-31 16:26:07 +0000302 """\
303 A object to handle subnegotiation of options. In this case actually
304 sub-sub options for RFC 2217. It is used to track com port options.
305 """
cliechti2b929b72009-08-02 23:49:02 +0000306
307 def __init__(self, connection, name, option, ack_option=None):
308 if ack_option is None: ack_option = option
309 self.connection = connection
310 self.name = name
311 self.option = option
312 self.value = None
313 self.ack_option = ack_option
314 self.state = INACTIVE
315
316 def __repr__(self):
cliechti044d8662009-08-11 21:40:31 +0000317 """String for debug outputs."""
cliechti2b929b72009-08-02 23:49:02 +0000318 return "%s:%s" % (self.name, self.state)
319
320 def set(self, value):
cliechtieada4fd2013-07-31 16:26:07 +0000321 """\
cliechti7d448562014-08-03 21:57:45 +0000322 Request a change of the value. a request is sent to the server. if
cliechti2b929b72009-08-02 23:49:02 +0000323 the client needs to know if the change is performed he has to check the
cliechtieada4fd2013-07-31 16:26:07 +0000324 state of this object.
325 """
cliechti2b929b72009-08-02 23:49:02 +0000326 self.value = value
327 self.state = REQUESTED
cliechti1ef7e3e2009-08-03 02:38:43 +0000328 self.connection.rfc2217SendSubnegotiation(self.option, self.value)
cliechti6a300772009-08-12 02:28:56 +0000329 if self.connection.logger:
330 self.connection.logger.debug("SB Requesting %s -> %r" % (self.name, self.value))
cliechti2b929b72009-08-02 23:49:02 +0000331
332 def isReady(self):
cliechtieada4fd2013-07-31 16:26:07 +0000333 """\
cliechti7d448562014-08-03 21:57:45 +0000334 Check if answer from server has been received. when server rejects
cliechtieada4fd2013-07-31 16:26:07 +0000335 the change, raise a ValueError.
336 """
cliechti2b929b72009-08-02 23:49:02 +0000337 if self.state == REALLY_INACTIVE:
338 raise ValueError("remote rejected value for option %r" % (self.name))
339 return self.state == ACTIVE
cliechti1ef7e3e2009-08-03 02:38:43 +0000340 # add property to have a similar interface as TelnetOption
cliechti2b929b72009-08-02 23:49:02 +0000341 active = property(isReady)
342
cliechti044d8662009-08-11 21:40:31 +0000343 def wait(self, timeout=3):
cliechtieada4fd2013-07-31 16:26:07 +0000344 """\
cliechti7d448562014-08-03 21:57:45 +0000345 Wait until the subnegotiation has been acknowledged or timeout. It
cliechti1ef7e3e2009-08-03 02:38:43 +0000346 can also throw a value error when the answer from the server does not
cliechtieada4fd2013-07-31 16:26:07 +0000347 match the value sent.
348 """
cliechti044d8662009-08-11 21:40:31 +0000349 timeout_time = time.time() + timeout
cliechti2b929b72009-08-02 23:49:02 +0000350 while time.time() < timeout_time:
351 time.sleep(0.05) # prevent 100% CPU load
352 if self.isReady():
353 break
354 else:
355 raise SerialException("timeout while waiting for option %r" % (self.name))
356
357 def checkAnswer(self, suboption):
cliechtieada4fd2013-07-31 16:26:07 +0000358 """\
cliechti7d448562014-08-03 21:57:45 +0000359 Check an incoming subnegotiation block. The parameter already has
cliechtieada4fd2013-07-31 16:26:07 +0000360 cut off the header like sub option number and com port option value.
361 """
cliechti2b929b72009-08-02 23:49:02 +0000362 if self.value == suboption[:len(self.value)]:
363 self.state = ACTIVE
364 else:
365 # error propagation done in isReady
366 self.state = REALLY_INACTIVE
cliechti6a300772009-08-12 02:28:56 +0000367 if self.connection.logger:
368 self.connection.logger.debug("SB Answer %s -> %r -> %s" % (self.name, suboption, self.state))
cliechti2b929b72009-08-02 23:49:02 +0000369
370
Chris Liechtief6b7b42015-08-06 22:19:26 +0200371class Serial(SerialBase):
cliechti044d8662009-08-11 21:40:31 +0000372 """Serial port implementation for RFC 2217 remote serial ports."""
cliechti8099bed2009-08-01 23:59:18 +0000373
374 BAUDRATES = (50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800,
375 9600, 19200, 38400, 57600, 115200)
376
377 def open(self):
cliechtieada4fd2013-07-31 16:26:07 +0000378 """\
379 Open port with current settings. This may throw a SerialException
380 if the port cannot be opened.
381 """
cliechti6a300772009-08-12 02:28:56 +0000382 self.logger = None
cliechti81c54762009-08-03 23:53:27 +0000383 self._ignore_set_control_answer = False
cliechti7cb78e82009-08-05 15:47:57 +0000384 self._poll_modem_state = False
cliechtidfe2d272009-08-10 22:19:41 +0000385 self._network_timeout = 3
cliechti8099bed2009-08-01 23:59:18 +0000386 if self._port is None:
387 raise SerialException("Port must be configured before it can be used.")
cliechti8f69e702011-03-19 00:22:32 +0000388 if self._isOpen:
389 raise SerialException("Port is already open.")
cliechti8099bed2009-08-01 23:59:18 +0000390 try:
Chris Liechtic4bca9e2015-08-07 14:40:41 +0200391 self._socket = socket.create_connection(self.fromURL(self.portstr))
cliechti6a300772009-08-12 02:28:56 +0000392 self._socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
Chris Liechti68340d72015-08-03 14:15:48 +0200393 except Exception as msg:
cliechti8099bed2009-08-01 23:59:18 +0000394 self._socket = None
395 raise SerialException("Could not open port %s: %s" % (self.portstr, msg))
396
cliechti1ef7e3e2009-08-03 02:38:43 +0000397 self._socket.settimeout(5) # XXX good value?
cliechti8099bed2009-08-01 23:59:18 +0000398
cliechti1ef7e3e2009-08-03 02:38:43 +0000399 # use a thread save queue as buffer. it also simplifies implementing
400 # the read timeout
cliechti8099bed2009-08-01 23:59:18 +0000401 self._read_buffer = Queue.Queue()
cliechti81c54762009-08-03 23:53:27 +0000402 # to ensure that user writes does not interfere with internal
403 # telnet/rfc2217 options establish a lock
404 self._write_lock = threading.Lock()
cliechtiac205322009-08-02 20:40:21 +0000405 # name the following separately so that, below, a check can be easily done
406 mandadory_options = [
cliechti2b929b72009-08-02 23:49:02 +0000407 TelnetOption(self, 'we-BINARY', BINARY, WILL, WONT, DO, DONT, INACTIVE),
cliechti2b929b72009-08-02 23:49:02 +0000408 TelnetOption(self, 'we-RFC2217', COM_PORT_OPTION, WILL, WONT, DO, DONT, REQUESTED),
cliechtiac205322009-08-02 20:40:21 +0000409 ]
410 # all supported telnet options
411 self._telnet_options = [
cliechtia29275e2009-08-03 00:08:04 +0000412 TelnetOption(self, 'ECHO', ECHO, DO, DONT, WILL, WONT, REQUESTED),
cliechti2b929b72009-08-02 23:49:02 +0000413 TelnetOption(self, 'we-SGA', SGA, WILL, WONT, DO, DONT, REQUESTED),
414 TelnetOption(self, 'they-SGA', SGA, DO, DONT, WILL, WONT, REQUESTED),
cliechti81c54762009-08-03 23:53:27 +0000415 TelnetOption(self, 'they-BINARY', BINARY, DO, DONT, WILL, WONT, INACTIVE),
416 TelnetOption(self, 'they-RFC2217', COM_PORT_OPTION, DO, DONT, WILL, WONT, REQUESTED),
cliechtiac205322009-08-02 20:40:21 +0000417 ] + mandadory_options
cliechti044d8662009-08-11 21:40:31 +0000418 # RFC 2217 specific states
cliechti2b929b72009-08-02 23:49:02 +0000419 # COM port settings
420 self._rfc2217_port_settings = {
421 'baudrate': TelnetSubnegotiation(self, 'baudrate', SET_BAUDRATE, SERVER_SET_BAUDRATE),
422 'datasize': TelnetSubnegotiation(self, 'datasize', SET_DATASIZE, SERVER_SET_DATASIZE),
423 'parity': TelnetSubnegotiation(self, 'parity', SET_PARITY, SERVER_SET_PARITY),
424 'stopsize': TelnetSubnegotiation(self, 'stopsize', SET_STOPSIZE, SERVER_SET_STOPSIZE),
425 }
cliechticb20a4f2011-04-25 02:25:54 +0000426 # There are more subnegotiation objects, combine all in one dictionary
cliechti2b929b72009-08-02 23:49:02 +0000427 # for easy access
428 self._rfc2217_options = {
429 'purge': TelnetSubnegotiation(self, 'purge', PURGE_DATA, SERVER_PURGE_DATA),
cliechti81c54762009-08-03 23:53:27 +0000430 'control': TelnetSubnegotiation(self, 'control', SET_CONTROL, SERVER_SET_CONTROL),
cliechti2b929b72009-08-02 23:49:02 +0000431 }
432 self._rfc2217_options.update(self._rfc2217_port_settings)
433 # cache for line and modem states that the server sends to us
cliechti8099bed2009-08-01 23:59:18 +0000434 self._linestate = 0
cliechti7cb78e82009-08-05 15:47:57 +0000435 self._modemstate = None
436 self._modemstate_expires = 0
cliechti044d8662009-08-11 21:40:31 +0000437 # RFC 2217 flow control between server and client
cliechti672d0292009-08-03 02:01:57 +0000438 self._remote_suspend_flow = False
cliechti8099bed2009-08-01 23:59:18 +0000439
cliechti1ef7e3e2009-08-03 02:38:43 +0000440 self._thread = threading.Thread(target=self._telnetReadLoop)
cliechti8099bed2009-08-01 23:59:18 +0000441 self._thread.setDaemon(True)
cliechti5cc3eb12009-08-11 23:04:30 +0000442 self._thread.setName('pySerial RFC 2217 reader thread for %s' % (self._port,))
cliechti8099bed2009-08-01 23:59:18 +0000443 self._thread.start()
444
cliechti044d8662009-08-11 21:40:31 +0000445 # negotiate Telnet/RFC 2217 -> send initial requests
cliechtiac205322009-08-02 20:40:21 +0000446 for option in self._telnet_options:
447 if option.state is REQUESTED:
cliechti1ef7e3e2009-08-03 02:38:43 +0000448 self.telnetSendOption(option.send_yes, option.option)
cliechtiac205322009-08-02 20:40:21 +0000449 # now wait until important options are negotiated
cliechtidfe2d272009-08-10 22:19:41 +0000450 timeout_time = time.time() + self._network_timeout
cliechtiac205322009-08-02 20:40:21 +0000451 while time.time() < timeout_time:
cliechtiac205322009-08-02 20:40:21 +0000452 time.sleep(0.05) # prevent 100% CPU load
cliechti7c213a92014-07-31 15:29:34 +0000453 if sum(o.active for o in mandadory_options) == sum(o.state != INACTIVE for o in mandadory_options):
cliechti2b929b72009-08-02 23:49:02 +0000454 break
455 else:
456 raise SerialException("Remote does not seem to support RFC2217 or BINARY mode %r" % mandadory_options)
cliechti6a300772009-08-12 02:28:56 +0000457 if self.logger:
458 self.logger.info("Negotiated options: %s" % self._telnet_options)
cliechti8099bed2009-08-01 23:59:18 +0000459
cliechti044d8662009-08-11 21:40:31 +0000460 # fine, go on, set RFC 2271 specific things
cliechti8099bed2009-08-01 23:59:18 +0000461 self._reconfigurePort()
cliechti2b929b72009-08-02 23:49:02 +0000462 # all things set up get, now a clean start
cliechti8099bed2009-08-01 23:59:18 +0000463 self._isOpen = True
464 if not self._rtscts:
465 self.setRTS(True)
466 self.setDTR(True)
467 self.flushInput()
468 self.flushOutput()
469
470 def _reconfigurePort(self):
471 """Set communication parameters on opened port."""
472 if self._socket is None:
473 raise SerialException("Can only operate on open ports")
474
cliechti8099bed2009-08-01 23:59:18 +0000475 # if self._timeout != 0 and self._interCharTimeout is not None:
cliechti8099bed2009-08-01 23:59:18 +0000476 # XXX
477
478 if self._writeTimeout is not None:
479 raise NotImplementedError('writeTimeout is currently not supported')
cliechti2b929b72009-08-02 23:49:02 +0000480 # XXX
cliechti8099bed2009-08-01 23:59:18 +0000481
cliechti2b929b72009-08-02 23:49:02 +0000482 # Setup the connection
cliechti1ef7e3e2009-08-03 02:38:43 +0000483 # to get good performance, all parameter changes are sent first...
Chris Liechti01587b12015-08-05 02:39:32 +0200484 if not 0 < self._baudrate < 2**32:
cliechti81c54762009-08-03 23:53:27 +0000485 raise ValueError("invalid baudrate: %r" % (self._baudrate))
Chris Liechti01587b12015-08-05 02:39:32 +0200486 self._rfc2217_port_settings['baudrate'].set(struct.pack(b'!I', self._baudrate))
487 self._rfc2217_port_settings['datasize'].set(struct.pack(b'!B', self._bytesize))
488 self._rfc2217_port_settings['parity'].set(struct.pack(b'!B', RFC2217_PARITY_MAP[self._parity]))
489 self._rfc2217_port_settings['stopsize'].set(struct.pack(b'!B', RFC2217_STOPBIT_MAP[self._stopbits]))
cliechti8099bed2009-08-01 23:59:18 +0000490
cliechti2b929b72009-08-02 23:49:02 +0000491 # and now wait until parameters are active
492 items = self._rfc2217_port_settings.values()
cliechti6a300772009-08-12 02:28:56 +0000493 if self.logger:
494 self.logger.debug("Negotiating settings: %s" % (items,))
cliechtidfe2d272009-08-10 22:19:41 +0000495 timeout_time = time.time() + self._network_timeout
cliechti2b929b72009-08-02 23:49:02 +0000496 while time.time() < timeout_time:
497 time.sleep(0.05) # prevent 100% CPU load
498 if sum(o.active for o in items) == len(items):
499 break
500 else:
501 raise SerialException("Remote does not accept parameter change (RFC2217): %r" % items)
cliechti6a300772009-08-12 02:28:56 +0000502 if self.logger:
503 self.logger.info("Negotiated settings: %s" % (items,))
cliechti8099bed2009-08-01 23:59:18 +0000504
505 if self._rtscts and self._xonxoff:
cliechti1ef7e3e2009-08-03 02:38:43 +0000506 raise ValueError('xonxoff and rtscts together are not supported')
cliechti8099bed2009-08-01 23:59:18 +0000507 elif self._rtscts:
cliechti1ef7e3e2009-08-03 02:38:43 +0000508 self.rfc2217SetControl(SET_CONTROL_USE_HW_FLOW_CONTROL)
cliechti8099bed2009-08-01 23:59:18 +0000509 elif self._xonxoff:
cliechti1ef7e3e2009-08-03 02:38:43 +0000510 self.rfc2217SetControl(SET_CONTROL_USE_SW_FLOW_CONTROL)
cliechti8099bed2009-08-01 23:59:18 +0000511 else:
cliechti1ef7e3e2009-08-03 02:38:43 +0000512 self.rfc2217SetControl(SET_CONTROL_USE_NO_FLOW_CONTROL)
cliechti8099bed2009-08-01 23:59:18 +0000513
514 def close(self):
515 """Close port"""
516 if self._isOpen:
517 if self._socket:
518 try:
519 self._socket.shutdown(socket.SHUT_RDWR)
520 self._socket.close()
521 except:
522 # ignore errors.
523 pass
524 self._socket = None
525 if self._thread:
526 self._thread.join()
527 self._isOpen = False
528 # in case of quick reconnects, give the server some time
529 time.sleep(0.3)
530
531 def makeDeviceName(self, port):
532 raise SerialException("there is no sensible way to turn numbers into URLs")
533
534 def fromURL(self, url):
cliechti1ef7e3e2009-08-03 02:38:43 +0000535 """extract host and port from an URL string"""
Chris Liechtia4222112015-08-07 01:03:12 +0200536 parts = urlparse.urlsplit(url)
537 if parts.scheme.lower() != "rfc2217":
Chris Liechtic4bca9e2015-08-07 14:40:41 +0200538 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 +0000539 try:
Chris Liechtia4222112015-08-07 01:03:12 +0200540 # process options now, directly altering self
541 for option in parts.path.lower().split('/'):
542 if '=' in option:
543 option, value = option.split('=', 1)
544 else:
545 value = None
546 if not option:
547 pass
548 elif option == 'logging':
549 logging.basicConfig() # XXX is that good to call it here?
550 self.logger = logging.getLogger('pySerial.rfc2217')
551 self.logger.setLevel(LOGGER_LEVELS[value])
552 self.logger.debug('enabled logging')
553 elif option == 'ign_set_control':
554 self._ignore_set_control_answer = True
555 elif option == 'poll_modem':
556 self._poll_modem_state = True
557 elif option == 'timeout':
558 self._network_timeout = float(value)
559 else:
560 raise ValueError('unknown option: %r' % (option,))
cliechti81c54762009-08-03 23:53:27 +0000561 # get host and port
Chris Liechtia4222112015-08-07 01:03:12 +0200562 host, port = parts.hostname, parts.port
cliechti8099bed2009-08-01 23:59:18 +0000563 if not 0 <= port < 65536: raise ValueError("port not in range 0...65535")
Chris Liechti68340d72015-08-03 14:15:48 +0200564 except ValueError as e:
Chris Liechtia4222112015-08-07 01:03:12 +0200565 raise SerialException('expected a string in the form "rfc2217://<host>:<port>[/option[/option...]]": %s' % e)
cliechti8099bed2009-08-01 23:59:18 +0000566 return (host, port)
567
568 # - - - - - - - - - - - - - - - - - - - - - - - -
569
570 def inWaiting(self):
571 """Return the number of characters currently in the input buffer."""
572 if not self._isOpen: raise portNotOpenError
573 return self._read_buffer.qsize()
574
575 def read(self, size=1):
cliechtieada4fd2013-07-31 16:26:07 +0000576 """\
577 Read size bytes from the serial port. If a timeout is set it may
cliechti8099bed2009-08-01 23:59:18 +0000578 return less characters as requested. With no timeout it will block
cliechtieada4fd2013-07-31 16:26:07 +0000579 until the requested number of bytes is read.
580 """
cliechti8099bed2009-08-01 23:59:18 +0000581 if not self._isOpen: raise portNotOpenError
582 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)
cliechti8099bed2009-08-01 23:59:18 +0000588 except Queue.Empty: # -> timeout
589 pass
590 return bytes(data)
591
592 def write(self, data):
cliechtieada4fd2013-07-31 16:26:07 +0000593 """\
594 Output the given 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 """
cliechti8099bed2009-08-01 23:59:18 +0000598 if not self._isOpen: raise portNotOpenError
Chris Liechti01587b12015-08-05 02:39:32 +0200599 with self._write_lock:
cliechti81c54762009-08-03 23:53:27 +0000600 try:
cliechti38077122013-10-16 02:57:27 +0000601 self._socket.sendall(to_bytes(data).replace(IAC, IAC_DOUBLED))
Chris Liechti68340d72015-08-03 14:15:48 +0200602 except socket.error as e:
cliechticb20a4f2011-04-25 02:25:54 +0000603 raise SerialException("connection failed (socket error): %s" % e) # XXX what exception if socket connection fails
cliechti8099bed2009-08-01 23:59:18 +0000604 return len(data)
605
606 def flushInput(self):
607 """Clear input buffer, discarding all that is in the buffer."""
608 if not self._isOpen: raise portNotOpenError
cliechti1ef7e3e2009-08-03 02:38:43 +0000609 self.rfc2217SendPurge(PURGE_RECEIVE_BUFFER)
cliechti8099bed2009-08-01 23:59:18 +0000610 # empty read buffer
611 while self._read_buffer.qsize():
612 self._read_buffer.get(False)
613
614 def flushOutput(self):
cliechtieada4fd2013-07-31 16:26:07 +0000615 """\
616 Clear output buffer, aborting the current output and
617 discarding all that is in the buffer.
618 """
cliechti8099bed2009-08-01 23:59:18 +0000619 if not self._isOpen: raise portNotOpenError
cliechti1ef7e3e2009-08-03 02:38:43 +0000620 self.rfc2217SendPurge(PURGE_TRANSMIT_BUFFER)
cliechti8099bed2009-08-01 23:59:18 +0000621
622 def sendBreak(self, duration=0.25):
cliechti7d448562014-08-03 21:57:45 +0000623 """\
624 Send break condition. Timed, returns to idle state after given
625 duration.
626 """
cliechti8099bed2009-08-01 23:59:18 +0000627 if not self._isOpen: raise portNotOpenError
628 self.setBreak(True)
629 time.sleep(duration)
630 self.setBreak(False)
631
632 def setBreak(self, level=True):
cliechtieada4fd2013-07-31 16:26:07 +0000633 """\
634 Set break: Controls TXD. When active, to transmitting is
635 possible.
636 """
cliechti8099bed2009-08-01 23:59:18 +0000637 if not self._isOpen: raise portNotOpenError
cliechti6a300772009-08-12 02:28:56 +0000638 if self.logger:
639 self.logger.info('set BREAK to %s' % ('inactive', 'active')[bool(level)])
cliechti8099bed2009-08-01 23:59:18 +0000640 if level:
cliechti1ef7e3e2009-08-03 02:38:43 +0000641 self.rfc2217SetControl(SET_CONTROL_BREAK_ON)
cliechti8099bed2009-08-01 23:59:18 +0000642 else:
cliechti1ef7e3e2009-08-03 02:38:43 +0000643 self.rfc2217SetControl(SET_CONTROL_BREAK_OFF)
cliechti8099bed2009-08-01 23:59:18 +0000644
645 def setRTS(self, level=True):
cliechti044d8662009-08-11 21:40:31 +0000646 """Set terminal status line: Request To Send."""
cliechti8099bed2009-08-01 23:59:18 +0000647 if not self._isOpen: raise portNotOpenError
cliechti6a300772009-08-12 02:28:56 +0000648 if self.logger:
649 self.logger.info('set RTS to %s' % ('inactive', 'active')[bool(level)])
cliechti8099bed2009-08-01 23:59:18 +0000650 if level:
cliechti1ef7e3e2009-08-03 02:38:43 +0000651 self.rfc2217SetControl(SET_CONTROL_RTS_ON)
cliechti8099bed2009-08-01 23:59:18 +0000652 else:
cliechti1ef7e3e2009-08-03 02:38:43 +0000653 self.rfc2217SetControl(SET_CONTROL_RTS_OFF)
cliechti8099bed2009-08-01 23:59:18 +0000654
655 def setDTR(self, level=True):
cliechti044d8662009-08-11 21:40:31 +0000656 """Set terminal status line: Data Terminal Ready."""
cliechti8099bed2009-08-01 23:59:18 +0000657 if not self._isOpen: raise portNotOpenError
cliechti6a300772009-08-12 02:28:56 +0000658 if self.logger:
659 self.logger.info('set DTR to %s' % ('inactive', 'active')[bool(level)])
cliechti8099bed2009-08-01 23:59:18 +0000660 if level:
cliechti1ef7e3e2009-08-03 02:38:43 +0000661 self.rfc2217SetControl(SET_CONTROL_DTR_ON)
cliechti8099bed2009-08-01 23:59:18 +0000662 else:
cliechti1ef7e3e2009-08-03 02:38:43 +0000663 self.rfc2217SetControl(SET_CONTROL_DTR_OFF)
cliechti8099bed2009-08-01 23:59:18 +0000664
665 def getCTS(self):
cliechti044d8662009-08-11 21:40:31 +0000666 """Read terminal status line: Clear To Send."""
cliechti8099bed2009-08-01 23:59:18 +0000667 if not self._isOpen: raise portNotOpenError
cliechti7cb78e82009-08-05 15:47:57 +0000668 return bool(self.getModemState() & MODEMSTATE_MASK_CTS)
cliechti8099bed2009-08-01 23:59:18 +0000669
670 def getDSR(self):
cliechti044d8662009-08-11 21:40:31 +0000671 """Read terminal status line: Data Set Ready."""
cliechti8099bed2009-08-01 23:59:18 +0000672 if not self._isOpen: raise portNotOpenError
cliechti7cb78e82009-08-05 15:47:57 +0000673 return bool(self.getModemState() & MODEMSTATE_MASK_DSR)
cliechti8099bed2009-08-01 23:59:18 +0000674
675 def getRI(self):
cliechti044d8662009-08-11 21:40:31 +0000676 """Read terminal status line: Ring Indicator."""
cliechti8099bed2009-08-01 23:59:18 +0000677 if not self._isOpen: raise portNotOpenError
cliechti7cb78e82009-08-05 15:47:57 +0000678 return bool(self.getModemState() & MODEMSTATE_MASK_RI)
cliechti8099bed2009-08-01 23:59:18 +0000679
680 def getCD(self):
cliechti044d8662009-08-11 21:40:31 +0000681 """Read terminal status line: Carrier Detect."""
cliechti8099bed2009-08-01 23:59:18 +0000682 if not self._isOpen: raise portNotOpenError
cliechti7cb78e82009-08-05 15:47:57 +0000683 return bool(self.getModemState() & MODEMSTATE_MASK_CD)
cliechti8099bed2009-08-01 23:59:18 +0000684
685 # - - - platform specific - - -
686 # None so far
687
688 # - - - RFC2217 specific - - -
689
cliechti1ef7e3e2009-08-03 02:38:43 +0000690 def _telnetReadLoop(self):
cliechti7d448562014-08-03 21:57:45 +0000691 """Read loop for the socket."""
cliechti8099bed2009-08-01 23:59:18 +0000692 mode = M_NORMAL
693 suboption = None
cliechti81c54762009-08-03 23:53:27 +0000694 try:
695 while self._socket is not None:
696 try:
697 data = self._socket.recv(1024)
698 except socket.timeout:
699 # just need to get out of recv form time to time to check if
700 # still alive
701 continue
Chris Liechti68340d72015-08-03 14:15:48 +0200702 except socket.error as e:
cliechti81c54762009-08-03 23:53:27 +0000703 # connection fails -> terminate loop
cliechticb20a4f2011-04-25 02:25:54 +0000704 if self.logger:
705 self.logger.debug("socket error in reader thread: %s" % (e,))
cliechti81c54762009-08-03 23:53:27 +0000706 break
cliechticb20a4f2011-04-25 02:25:54 +0000707 if not data: break # lost connection
Chris Liechti01587b12015-08-05 02:39:32 +0200708 #~ for byte in data: # fails for python3 as it returns ints instead of b''
709 for x in range(len(data)):
710 byte = data[x:x+1]
cliechti81c54762009-08-03 23:53:27 +0000711 if mode == M_NORMAL:
712 # interpret as command or as data
713 if byte == IAC:
714 mode = M_IAC_SEEN
cliechti8099bed2009-08-01 23:59:18 +0000715 else:
cliechti81c54762009-08-03 23:53:27 +0000716 # store data in read buffer or sub option buffer
717 # depending on state
718 if suboption is not None:
Chris Liechti01587b12015-08-05 02:39:32 +0200719 suboption += byte
cliechti81c54762009-08-03 23:53:27 +0000720 else:
721 self._read_buffer.put(byte)
722 elif mode == M_IAC_SEEN:
723 if byte == IAC:
724 # interpret as command doubled -> insert character
725 # itself
cliechtif325c032009-12-25 16:09:49 +0000726 if suboption is not None:
Chris Liechti01587b12015-08-05 02:39:32 +0200727 suboption += IAC
cliechtif325c032009-12-25 16:09:49 +0000728 else:
729 self._read_buffer.put(IAC)
cliechti81c54762009-08-03 23:53:27 +0000730 mode = M_NORMAL
731 elif byte == SB:
732 # sub option start
733 suboption = bytearray()
734 mode = M_NORMAL
735 elif byte == SE:
736 # sub option end -> process it now
737 self._telnetProcessSubnegotiation(bytes(suboption))
738 suboption = None
739 mode = M_NORMAL
740 elif byte in (DO, DONT, WILL, WONT):
741 # negotiation
742 telnet_command = byte
743 mode = M_NEGOTIATE
744 else:
745 # other telnet commands
746 self._telnetProcessCommand(byte)
747 mode = M_NORMAL
748 elif mode == M_NEGOTIATE: # DO, DONT, WILL, WONT was received, option now following
749 self._telnetNegotiateOption(telnet_command, byte)
cliechti8099bed2009-08-01 23:59:18 +0000750 mode = M_NORMAL
cliechti81c54762009-08-03 23:53:27 +0000751 finally:
752 self._thread = None
cliechti6a300772009-08-12 02:28:56 +0000753 if self.logger:
754 self.logger.debug("read thread terminated")
cliechti8099bed2009-08-01 23:59:18 +0000755
756 # - incoming telnet commands and options
757
cliechti1ef7e3e2009-08-03 02:38:43 +0000758 def _telnetProcessCommand(self, command):
cliechti044d8662009-08-11 21:40:31 +0000759 """Process commands other than DO, DONT, WILL, WONT."""
cliechti1ef7e3e2009-08-03 02:38:43 +0000760 # Currently none. RFC2217 only uses negotiation and subnegotiation.
cliechti6a300772009-08-12 02:28:56 +0000761 if self.logger:
762 self.logger.warning("ignoring Telnet command: %r" % (command,))
cliechti8099bed2009-08-01 23:59:18 +0000763
cliechti1ef7e3e2009-08-03 02:38:43 +0000764 def _telnetNegotiateOption(self, command, option):
cliechti044d8662009-08-11 21:40:31 +0000765 """Process incoming DO, DONT, WILL, WONT."""
cliechti2b929b72009-08-02 23:49:02 +0000766 # check our registered telnet options and forward command to them
767 # they know themselves if they have to answer or not
cliechtiac205322009-08-02 20:40:21 +0000768 known = False
769 for item in self._telnet_options:
cliechti2b929b72009-08-02 23:49:02 +0000770 # can have more than one match! as some options are duplicated for
771 # 'us' and 'them'
cliechtiac205322009-08-02 20:40:21 +0000772 if item.option == option:
cliechti2b929b72009-08-02 23:49:02 +0000773 item.process_incoming(command)
cliechtiac205322009-08-02 20:40:21 +0000774 known = True
775 if not known:
776 # handle unknown options
777 # only answer to positive requests and deny them
778 if command == WILL or command == DO:
cliechti1ef7e3e2009-08-03 02:38:43 +0000779 self.telnetSendOption((command == WILL and DONT or WONT), option)
cliechti6a300772009-08-12 02:28:56 +0000780 if self.logger:
781 self.logger.warning("rejected Telnet option: %r" % (option,))
cliechtiac205322009-08-02 20:40:21 +0000782
cliechti8099bed2009-08-01 23:59:18 +0000783
cliechti1ef7e3e2009-08-03 02:38:43 +0000784 def _telnetProcessSubnegotiation(self, suboption):
cliechti044d8662009-08-11 21:40:31 +0000785 """Process subnegotiation, the data between IAC SB and IAC SE."""
cliechti8099bed2009-08-01 23:59:18 +0000786 if suboption[0:1] == COM_PORT_OPTION:
787 if suboption[1:2] == SERVER_NOTIFY_LINESTATE and len(suboption) >= 3:
cliechti672d0292009-08-03 02:01:57 +0000788 self._linestate = ord(suboption[2:3]) # ensure it is a number
cliechti6a300772009-08-12 02:28:56 +0000789 if self.logger:
790 self.logger.info("NOTIFY_LINESTATE: %s" % self._linestate)
cliechti8099bed2009-08-01 23:59:18 +0000791 elif suboption[1:2] == SERVER_NOTIFY_MODEMSTATE and len(suboption) >= 3:
cliechti672d0292009-08-03 02:01:57 +0000792 self._modemstate = ord(suboption[2:3]) # ensure it is a number
cliechti6a300772009-08-12 02:28:56 +0000793 if self.logger:
794 self.logger.info("NOTIFY_MODEMSTATE: %s" % self._modemstate)
cliechti7cb78e82009-08-05 15:47:57 +0000795 # update time when we think that a poll would make sense
796 self._modemstate_expires = time.time() + 0.3
cliechti672d0292009-08-03 02:01:57 +0000797 elif suboption[1:2] == FLOWCONTROL_SUSPEND:
798 self._remote_suspend_flow = True
799 elif suboption[1:2] == FLOWCONTROL_RESUME:
800 self._remote_suspend_flow = False
cliechti8099bed2009-08-01 23:59:18 +0000801 else:
cliechti2b929b72009-08-02 23:49:02 +0000802 for item in self._rfc2217_options.values():
803 if item.ack_option == suboption[1:2]:
cliechti81c54762009-08-03 23:53:27 +0000804 #~ print "processing COM_PORT_OPTION: %r" % list(suboption[1:])
cliechti2b929b72009-08-02 23:49:02 +0000805 item.checkAnswer(bytes(suboption[2:]))
806 break
807 else:
cliechti6a300772009-08-12 02:28:56 +0000808 if self.logger:
809 self.logger.warning("ignoring COM_PORT_OPTION: %r" % (suboption,))
cliechti8099bed2009-08-01 23:59:18 +0000810 else:
cliechti6a300772009-08-12 02:28:56 +0000811 if self.logger:
812 self.logger.warning("ignoring subnegotiation: %r" % (suboption,))
cliechti8099bed2009-08-01 23:59:18 +0000813
814 # - outgoing telnet commands and options
815
cliechti81c54762009-08-03 23:53:27 +0000816 def _internal_raw_write(self, data):
cliechti044d8662009-08-11 21:40:31 +0000817 """internal socket write with no data escaping. used to send telnet stuff."""
Chris Liechti01587b12015-08-05 02:39:32 +0200818 with self._write_lock:
cliechti81c54762009-08-03 23:53:27 +0000819 self._socket.sendall(data)
cliechti81c54762009-08-03 23:53:27 +0000820
cliechti1ef7e3e2009-08-03 02:38:43 +0000821 def telnetSendOption(self, action, option):
cliechti044d8662009-08-11 21:40:31 +0000822 """Send DO, DONT, WILL, WONT."""
cliechti81c54762009-08-03 23:53:27 +0000823 self._internal_raw_write(to_bytes([IAC, action, option]))
cliechti8099bed2009-08-01 23:59:18 +0000824
Chris Liechtib4cda3a2015-08-08 17:12:08 +0200825 def rfc2217SendSubnegotiation(self, option, value=b''):
cliechti044d8662009-08-11 21:40:31 +0000826 """Subnegotiation of RFC2217 parameters."""
cliechtif325c032009-12-25 16:09:49 +0000827 value = value.replace(IAC, IAC_DOUBLED)
cliechti81c54762009-08-03 23:53:27 +0000828 self._internal_raw_write(to_bytes([IAC, SB, COM_PORT_OPTION, option] + list(value) + [IAC, SE]))
cliechti2b929b72009-08-02 23:49:02 +0000829
cliechti1ef7e3e2009-08-03 02:38:43 +0000830 def rfc2217SendPurge(self, value):
cliechti2b929b72009-08-02 23:49:02 +0000831 item = self._rfc2217_options['purge']
cliechti672d0292009-08-03 02:01:57 +0000832 item.set(value) # transmit desired purge type
cliechtidfe2d272009-08-10 22:19:41 +0000833 item.wait(self._network_timeout) # wait for acknowledge from the server
cliechti2b929b72009-08-02 23:49:02 +0000834
cliechti1ef7e3e2009-08-03 02:38:43 +0000835 def rfc2217SetControl(self, value):
cliechti81c54762009-08-03 23:53:27 +0000836 item = self._rfc2217_options['control']
cliechticb20a4f2011-04-25 02:25:54 +0000837 item.set(value) # transmit desired control type
cliechti81c54762009-08-03 23:53:27 +0000838 if self._ignore_set_control_answer:
839 # answers are ignored when option is set. compatibility mode for
cliechticb20a4f2011-04-25 02:25:54 +0000840 # servers that answer, but not the expected one... (or no answer
cliechti81c54762009-08-03 23:53:27 +0000841 # at all) i.e. sredird
842 time.sleep(0.1) # this helps getting the unit tests passed
843 else:
cliechtidfe2d272009-08-10 22:19:41 +0000844 item.wait(self._network_timeout) # wait for acknowledge from the server
cliechti8099bed2009-08-01 23:59:18 +0000845
cliechti1ef7e3e2009-08-03 02:38:43 +0000846 def rfc2217FlowServerReady(self):
cliechtieada4fd2013-07-31 16:26:07 +0000847 """\
848 check if server is ready to receive data. block for some time when
849 not.
850 """
cliechti672d0292009-08-03 02:01:57 +0000851 #~ if self._remote_suspend_flow:
852 #~ wait---
853
cliechti7cb78e82009-08-05 15:47:57 +0000854 def getModemState(self):
cliechtieada4fd2013-07-31 16:26:07 +0000855 """\
cliechti7d448562014-08-03 21:57:45 +0000856 get last modem state (cached value. If value is "old", request a new
857 one. This cache helps that we don't issue to many requests when e.g. all
858 status lines, one after the other is queried by the user (getCTS, getDSR
cliechtieada4fd2013-07-31 16:26:07 +0000859 etc.)
860 """
cliechti7cb78e82009-08-05 15:47:57 +0000861 # active modem state polling enabled? is the value fresh enough?
862 if self._poll_modem_state and self._modemstate_expires < time.time():
cliechti6a300772009-08-12 02:28:56 +0000863 if self.logger:
864 self.logger.debug('polling modem state')
cliechti7cb78e82009-08-05 15:47:57 +0000865 # when it is older, request an update
866 self.rfc2217SendSubnegotiation(NOTIFY_MODEMSTATE)
cliechtidfe2d272009-08-10 22:19:41 +0000867 timeout_time = time.time() + self._network_timeout
cliechti7cb78e82009-08-05 15:47:57 +0000868 while time.time() < timeout_time:
869 time.sleep(0.05) # prevent 100% CPU load
870 # when expiration time is updated, it means that there is a new
871 # value
872 if self._modemstate_expires > time.time():
873 break
Chris Liechti01587b12015-08-05 02:39:32 +0200874 else:
875 if self.logger:
876 self.logger.warning('poll for modem state failed')
cliechti7cb78e82009-08-05 15:47:57 +0000877 # even when there is a timeout, do not generate an error just
878 # return the last known value. this way we can support buggy
879 # servers that do not respond to polls, but send automatic
880 # updates.
881 if self._modemstate is not None:
cliechti6a300772009-08-12 02:28:56 +0000882 if self.logger:
883 self.logger.debug('using cached modem state')
cliechti7cb78e82009-08-05 15:47:57 +0000884 return self._modemstate
885 else:
886 # never received a notification from the server
cliechti8fb119c2009-08-05 23:39:45 +0000887 raise SerialException("remote sends no NOTIFY_MODEMSTATE")
cliechti8099bed2009-08-01 23:59:18 +0000888
cliechti5cc3eb12009-08-11 23:04:30 +0000889
cliechti595ed5b2009-08-10 01:43:32 +0000890#############################################################################
cliechti5cc3eb12009-08-11 23:04:30 +0000891# The following is code that helps implementing an RFC 2217 server.
cliechti8099bed2009-08-01 23:59:18 +0000892
cliechti8ccc2ff2009-08-05 12:44:46 +0000893class PortManager(object):
cliechtieada4fd2013-07-31 16:26:07 +0000894 """\
895 This class manages the state of Telnet and RFC 2217. It needs a serial
cliechticb20a4f2011-04-25 02:25:54 +0000896 instance and a connection to work with. Connection is expected to implement
cliechtieada4fd2013-07-31 16:26:07 +0000897 a (thread safe) write function, that writes the string to the network.
898 """
cliechti130d1f02009-08-04 02:10:58 +0000899
cliechti6a300772009-08-12 02:28:56 +0000900 def __init__(self, serial_port, connection, logger=None):
cliechti130d1f02009-08-04 02:10:58 +0000901 self.serial = serial_port
902 self.connection = connection
cliechti6a300772009-08-12 02:28:56 +0000903 self.logger = logger
cliechti86b593e2009-08-05 16:28:12 +0000904 self._client_is_rfc2217 = False
cliechti130d1f02009-08-04 02:10:58 +0000905
906 # filter state machine
907 self.mode = M_NORMAL
908 self.suboption = None
909 self.telnet_command = None
910
911 # states for modem/line control events
912 self.modemstate_mask = 255
913 self.last_modemstate = None
914 self.linstate_mask = 0
915
916 # all supported telnet options
917 self._telnet_options = [
918 TelnetOption(self, 'ECHO', ECHO, WILL, WONT, DO, DONT, REQUESTED),
919 TelnetOption(self, 'we-SGA', SGA, WILL, WONT, DO, DONT, REQUESTED),
920 TelnetOption(self, 'they-SGA', SGA, DO, DONT, WILL, WONT, INACTIVE),
921 TelnetOption(self, 'we-BINARY', BINARY, WILL, WONT, DO, DONT, INACTIVE),
922 TelnetOption(self, 'they-BINARY', BINARY, DO, DONT, WILL, WONT, REQUESTED),
cliechti86b593e2009-08-05 16:28:12 +0000923 TelnetOption(self, 'we-RFC2217', COM_PORT_OPTION, WILL, WONT, DO, DONT, REQUESTED, self._client_ok),
924 TelnetOption(self, 'they-RFC2217', COM_PORT_OPTION, DO, DONT, WILL, WONT, INACTIVE, self._client_ok),
cliechti130d1f02009-08-04 02:10:58 +0000925 ]
926
927 # negotiate Telnet/RFC2217 -> send initial requests
cliechti6a300772009-08-12 02:28:56 +0000928 if self.logger:
929 self.logger.debug("requesting initial Telnet/RFC 2217 options")
cliechti130d1f02009-08-04 02:10:58 +0000930 for option in self._telnet_options:
931 if option.state is REQUESTED:
932 self.telnetSendOption(option.send_yes, option.option)
933 # issue 1st modem state notification
cliechti86b593e2009-08-05 16:28:12 +0000934
935 def _client_ok(self):
cliechtieada4fd2013-07-31 16:26:07 +0000936 """\
cliechti7d448562014-08-03 21:57:45 +0000937 callback of telnet option. It gets called when option is activated.
938 This one here is used to detect when the client agrees on RFC 2217. A
cliechti86b593e2009-08-05 16:28:12 +0000939 flag is set so that other functions like check_modem_lines know if the
cliechti7d448562014-08-03 21:57:45 +0000940 client is OK.
cliechtieada4fd2013-07-31 16:26:07 +0000941 """
cliechti86b593e2009-08-05 16:28:12 +0000942 # The callback is used for we and they so if one party agrees, we're
943 # already happy. it seems not all servers do the negotiation correctly
944 # and i guess there are incorrect clients too.. so be happy if client
945 # answers one or the other positively.
946 self._client_is_rfc2217 = True
cliechti6a300772009-08-12 02:28:56 +0000947 if self.logger:
948 self.logger.info("client accepts RFC 2217")
cliechti8fb119c2009-08-05 23:39:45 +0000949 # this is to ensure that the client gets a notification, even if there
950 # was no change
951 self.check_modem_lines(force_notification=True)
cliechti130d1f02009-08-04 02:10:58 +0000952
953 # - outgoing telnet commands and options
954
955 def telnetSendOption(self, action, option):
cliechti044d8662009-08-11 21:40:31 +0000956 """Send DO, DONT, WILL, WONT."""
cliechti130d1f02009-08-04 02:10:58 +0000957 self.connection.write(to_bytes([IAC, action, option]))
958
Chris Liechtib4cda3a2015-08-08 17:12:08 +0200959 def rfc2217SendSubnegotiation(self, option, value=b''):
cliechti044d8662009-08-11 21:40:31 +0000960 """Subnegotiation of RFC 2217 parameters."""
cliechtif325c032009-12-25 16:09:49 +0000961 value = value.replace(IAC, IAC_DOUBLED)
cliechti130d1f02009-08-04 02:10:58 +0000962 self.connection.write(to_bytes([IAC, SB, COM_PORT_OPTION, option] + list(value) + [IAC, SE]))
963
964 # - check modem lines, needs to be called periodically from user to
965 # establish polling
966
cliechti7cb78e82009-08-05 15:47:57 +0000967 def check_modem_lines(self, force_notification=False):
cliechti130d1f02009-08-04 02:10:58 +0000968 modemstate = (
969 (self.serial.getCTS() and MODEMSTATE_MASK_CTS) |
970 (self.serial.getDSR() and MODEMSTATE_MASK_DSR) |
971 (self.serial.getRI() and MODEMSTATE_MASK_RI) |
972 (self.serial.getCD() and MODEMSTATE_MASK_CD)
973 )
cliechti7cb78e82009-08-05 15:47:57 +0000974 # check what has changed
975 deltas = modemstate ^ (self.last_modemstate or 0) # when last is None -> 0
976 if deltas & MODEMSTATE_MASK_CTS:
977 modemstate |= MODEMSTATE_MASK_CTS_CHANGE
978 if deltas & MODEMSTATE_MASK_DSR:
979 modemstate |= MODEMSTATE_MASK_DSR_CHANGE
980 if deltas & MODEMSTATE_MASK_RI:
981 modemstate |= MODEMSTATE_MASK_RI_CHANGE
982 if deltas & MODEMSTATE_MASK_CD:
983 modemstate |= MODEMSTATE_MASK_CD_CHANGE
984 # if new state is different and the mask allows this change, send
cliechti86b593e2009-08-05 16:28:12 +0000985 # notification. suppress notifications when client is not rfc2217
cliechti7cb78e82009-08-05 15:47:57 +0000986 if modemstate != self.last_modemstate or force_notification:
cliechti8fb119c2009-08-05 23:39:45 +0000987 if (self._client_is_rfc2217 and (modemstate & self.modemstate_mask)) or force_notification:
cliechti7cb78e82009-08-05 15:47:57 +0000988 self.rfc2217SendSubnegotiation(
989 SERVER_NOTIFY_MODEMSTATE,
990 to_bytes([modemstate & self.modemstate_mask])
991 )
cliechti6a300772009-08-12 02:28:56 +0000992 if self.logger:
993 self.logger.info("NOTIFY_MODEMSTATE: %s" % (modemstate,))
cliechti7cb78e82009-08-05 15:47:57 +0000994 # save last state, but forget about deltas.
995 # otherwise it would also notify about changing deltas which is
996 # probably not very useful
997 self.last_modemstate = modemstate & 0xf0
cliechti130d1f02009-08-04 02:10:58 +0000998
cliechti32c10332009-08-05 13:23:43 +0000999 # - outgoing data escaping
1000
1001 def escape(self, data):
cliechtieada4fd2013-07-31 16:26:07 +00001002 """\
cliechti7d448562014-08-03 21:57:45 +00001003 This generator function is for the user. All outgoing data has to be
cliechticb20a4f2011-04-25 02:25:54 +00001004 properly escaped, so that no IAC character in the data stream messes up
1005 the Telnet state machine in the server.
cliechti32c10332009-08-05 13:23:43 +00001006
1007 socket.sendall(escape(data))
1008 """
1009 for byte in data:
1010 if byte == IAC:
1011 yield IAC
1012 yield IAC
1013 else:
1014 yield byte
1015
cliechti130d1f02009-08-04 02:10:58 +00001016 # - incoming data filter
1017
1018 def filter(self, data):
cliechtieada4fd2013-07-31 16:26:07 +00001019 """\
cliechti7d448562014-08-03 21:57:45 +00001020 Handle a bunch of incoming bytes. This is a generator. It will yield
cliechti044d8662009-08-11 21:40:31 +00001021 all characters not of interest for Telnet/RFC 2217.
cliechti130d1f02009-08-04 02:10:58 +00001022
1023 The idea is that the reader thread pushes data from the socket through
1024 this filter:
1025
1026 for byte in filter(socket.recv(1024)):
1027 # do things like CR/LF conversion/whatever
1028 # and write data to the serial port
1029 serial.write(byte)
1030
1031 (socket error handling code left as exercise for the reader)
1032 """
Chris Liechti01587b12015-08-05 02:39:32 +02001033 #~ for byte in data: # XXX fails for python3 as it returns ints instead of bytes
1034 for x in range(len(data)):
1035 byte = data[x:x+1]
cliechti130d1f02009-08-04 02:10:58 +00001036 if self.mode == M_NORMAL:
1037 # interpret as command or as data
1038 if byte == IAC:
1039 self.mode = M_IAC_SEEN
1040 else:
1041 # store data in sub option buffer or pass it to our
1042 # consumer depending on state
1043 if self.suboption is not None:
Chris Liechti01587b12015-08-05 02:39:32 +02001044 self.suboption += byte
cliechti130d1f02009-08-04 02:10:58 +00001045 else:
1046 yield byte
1047 elif self.mode == M_IAC_SEEN:
1048 if byte == IAC:
1049 # interpret as command doubled -> insert character
1050 # itself
cliechtif325c032009-12-25 16:09:49 +00001051 if self.suboption is not None:
Chris Liechti01587b12015-08-05 02:39:32 +02001052 self.suboption += byte
cliechtif325c032009-12-25 16:09:49 +00001053 else:
1054 yield byte
cliechti130d1f02009-08-04 02:10:58 +00001055 self.mode = M_NORMAL
1056 elif byte == SB:
1057 # sub option start
1058 self.suboption = bytearray()
1059 self.mode = M_NORMAL
1060 elif byte == SE:
1061 # sub option end -> process it now
1062 self._telnetProcessSubnegotiation(bytes(self.suboption))
1063 self.suboption = None
1064 self.mode = M_NORMAL
1065 elif byte in (DO, DONT, WILL, WONT):
1066 # negotiation
1067 self.telnet_command = byte
1068 self.mode = M_NEGOTIATE
1069 else:
1070 # other telnet commands
1071 self._telnetProcessCommand(byte)
1072 self.mode = M_NORMAL
1073 elif self.mode == M_NEGOTIATE: # DO, DONT, WILL, WONT was received, option now following
1074 self._telnetNegotiateOption(self.telnet_command, byte)
1075 self.mode = M_NORMAL
1076
1077 # - incoming telnet commands and options
1078
1079 def _telnetProcessCommand(self, command):
cliechti044d8662009-08-11 21:40:31 +00001080 """Process commands other than DO, DONT, WILL, WONT."""
cliechti130d1f02009-08-04 02:10:58 +00001081 # Currently none. RFC2217 only uses negotiation and subnegotiation.
cliechti6a300772009-08-12 02:28:56 +00001082 if self.logger:
1083 self.logger.warning("ignoring Telnet command: %r" % (command,))
cliechti130d1f02009-08-04 02:10:58 +00001084
1085 def _telnetNegotiateOption(self, command, option):
cliechti044d8662009-08-11 21:40:31 +00001086 """Process incoming DO, DONT, WILL, WONT."""
cliechti130d1f02009-08-04 02:10:58 +00001087 # check our registered telnet options and forward command to them
1088 # they know themselves if they have to answer or not
1089 known = False
1090 for item in self._telnet_options:
1091 # can have more than one match! as some options are duplicated for
1092 # 'us' and 'them'
1093 if item.option == option:
1094 item.process_incoming(command)
1095 known = True
1096 if not known:
1097 # handle unknown options
1098 # only answer to positive requests and deny them
1099 if command == WILL or command == DO:
1100 self.telnetSendOption((command == WILL and DONT or WONT), option)
cliechti6a300772009-08-12 02:28:56 +00001101 if self.logger:
1102 self.logger.warning("rejected Telnet option: %r" % (option,))
cliechti130d1f02009-08-04 02:10:58 +00001103
1104
1105 def _telnetProcessSubnegotiation(self, suboption):
cliechti044d8662009-08-11 21:40:31 +00001106 """Process subnegotiation, the data between IAC SB and IAC SE."""
cliechti130d1f02009-08-04 02:10:58 +00001107 if suboption[0:1] == COM_PORT_OPTION:
cliechti6a300772009-08-12 02:28:56 +00001108 if self.logger:
1109 self.logger.debug('received COM_PORT_OPTION: %r' % (suboption,))
cliechti130d1f02009-08-04 02:10:58 +00001110 if suboption[1:2] == SET_BAUDRATE:
1111 backup = self.serial.baudrate
1112 try:
Chris Liechti01587b12015-08-05 02:39:32 +02001113 (baudrate,) = struct.unpack(b"!I", suboption[2:6])
cliechtieada4fd2013-07-31 16:26:07 +00001114 if baudrate != 0:
1115 self.serial.baudrate = baudrate
Chris Liechtid2146002015-08-04 16:57:16 +02001116 except ValueError as e:
cliechti6a300772009-08-12 02:28:56 +00001117 if self.logger:
1118 self.logger.error("failed to set baud rate: %s" % (e,))
cliechti130d1f02009-08-04 02:10:58 +00001119 self.serial.baudrate = backup
cliechti5cc3eb12009-08-11 23:04:30 +00001120 else:
cliechti6a300772009-08-12 02:28:56 +00001121 if self.logger:
cliechtieada4fd2013-07-31 16:26:07 +00001122 self.logger.info("%s baud rate: %s" % (baudrate and 'set' or 'get', self.serial.baudrate))
Chris Liechti01587b12015-08-05 02:39:32 +02001123 self.rfc2217SendSubnegotiation(SERVER_SET_BAUDRATE, struct.pack(b"!I", self.serial.baudrate))
cliechti130d1f02009-08-04 02:10:58 +00001124 elif suboption[1:2] == SET_DATASIZE:
1125 backup = self.serial.bytesize
1126 try:
cliechtieada4fd2013-07-31 16:26:07 +00001127 (datasize,) = struct.unpack("!B", suboption[2:3])
1128 if datasize != 0:
1129 self.serial.bytesize = datasize
Chris Liechtid2146002015-08-04 16:57:16 +02001130 except ValueError as e:
cliechti6a300772009-08-12 02:28:56 +00001131 if self.logger:
1132 self.logger.error("failed to set data size: %s" % (e,))
cliechti130d1f02009-08-04 02:10:58 +00001133 self.serial.bytesize = backup
cliechti5cc3eb12009-08-11 23:04:30 +00001134 else:
cliechti6a300772009-08-12 02:28:56 +00001135 if self.logger:
cliechtieada4fd2013-07-31 16:26:07 +00001136 self.logger.info("%s data size: %s" % (datasize and 'set' or 'get', self.serial.bytesize))
Chris Liechti01587b12015-08-05 02:39:32 +02001137 self.rfc2217SendSubnegotiation(SERVER_SET_DATASIZE, struct.pack(b"!B", self.serial.bytesize))
cliechti130d1f02009-08-04 02:10:58 +00001138 elif suboption[1:2] == SET_PARITY:
1139 backup = self.serial.parity
1140 try:
Chris Liechti01587b12015-08-05 02:39:32 +02001141 parity = struct.unpack(b"!B", suboption[2:3])[0]
cliechtieada4fd2013-07-31 16:26:07 +00001142 if parity != 0:
1143 self.serial.parity = RFC2217_REVERSE_PARITY_MAP[parity]
Chris Liechtid2146002015-08-04 16:57:16 +02001144 except ValueError as e:
cliechti6a300772009-08-12 02:28:56 +00001145 if self.logger:
1146 self.logger.error("failed to set parity: %s" % (e,))
cliechti130d1f02009-08-04 02:10:58 +00001147 self.serial.parity = backup
cliechti5cc3eb12009-08-11 23:04:30 +00001148 else:
cliechti6a300772009-08-12 02:28:56 +00001149 if self.logger:
cliechtieada4fd2013-07-31 16:26:07 +00001150 self.logger.info("%s parity: %s" % (parity and 'set' or 'get', self.serial.parity))
cliechti130d1f02009-08-04 02:10:58 +00001151 self.rfc2217SendSubnegotiation(
1152 SERVER_SET_PARITY,
Chris Liechtib4cda3a2015-08-08 17:12:08 +02001153 struct.pack(b"!B", RFC2217_PARITY_MAP[self.serial.parity])
cliechti130d1f02009-08-04 02:10:58 +00001154 )
1155 elif suboption[1:2] == SET_STOPSIZE:
1156 backup = self.serial.stopbits
1157 try:
Chris Liechti01587b12015-08-05 02:39:32 +02001158 stopbits = struct.unpack(b"!B", suboption[2:3])[0]
cliechtieada4fd2013-07-31 16:26:07 +00001159 if stopbits != 0:
1160 self.serial.stopbits = RFC2217_REVERSE_STOPBIT_MAP[stopbits]
Chris Liechtid2146002015-08-04 16:57:16 +02001161 except ValueError as e:
cliechti6a300772009-08-12 02:28:56 +00001162 if self.logger:
1163 self.logger.error("failed to set stop bits: %s" % (e,))
cliechti130d1f02009-08-04 02:10:58 +00001164 self.serial.stopbits = backup
cliechti5cc3eb12009-08-11 23:04:30 +00001165 else:
cliechti6a300772009-08-12 02:28:56 +00001166 if self.logger:
cliechtieada4fd2013-07-31 16:26:07 +00001167 self.logger.info("%s stop bits: %s" % (stopbits and 'set' or 'get', self.serial.stopbits))
cliechti130d1f02009-08-04 02:10:58 +00001168 self.rfc2217SendSubnegotiation(
1169 SERVER_SET_STOPSIZE,
Chris Liechti01587b12015-08-05 02:39:32 +02001170 struct.pack(b"!B", RFC2217_STOPBIT_MAP[self.serial.stopbits])
cliechti130d1f02009-08-04 02:10:58 +00001171 )
1172 elif suboption[1:2] == SET_CONTROL:
1173 if suboption[2:3] == SET_CONTROL_REQ_FLOW_SETTING:
1174 if self.serial.xonxoff:
1175 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_SW_FLOW_CONTROL)
1176 elif self.serial.rtscts:
1177 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_HW_FLOW_CONTROL)
1178 else:
1179 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_NO_FLOW_CONTROL)
1180 elif suboption[2:3] == SET_CONTROL_USE_NO_FLOW_CONTROL:
1181 self.serial.xonxoff = False
1182 self.serial.rtscts = False
cliechti6a300772009-08-12 02:28:56 +00001183 if self.logger:
1184 self.logger.info("changed flow control to None")
cliechti130d1f02009-08-04 02:10:58 +00001185 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_NO_FLOW_CONTROL)
1186 elif suboption[2:3] == SET_CONTROL_USE_SW_FLOW_CONTROL:
1187 self.serial.xonxoff = True
cliechti6a300772009-08-12 02:28:56 +00001188 if self.logger:
1189 self.logger.info("changed flow control to XON/XOFF")
cliechti130d1f02009-08-04 02:10:58 +00001190 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_SW_FLOW_CONTROL)
1191 elif suboption[2:3] == SET_CONTROL_USE_HW_FLOW_CONTROL:
1192 self.serial.rtscts = True
cliechti6a300772009-08-12 02:28:56 +00001193 if self.logger:
1194 self.logger.info("changed flow control to RTS/CTS")
cliechti130d1f02009-08-04 02:10:58 +00001195 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_HW_FLOW_CONTROL)
1196 elif suboption[2:3] == SET_CONTROL_REQ_BREAK_STATE:
cliechti6a300772009-08-12 02:28:56 +00001197 if self.logger:
1198 self.logger.warning("requested break state - not implemented")
cliechti130d1f02009-08-04 02:10:58 +00001199 pass # XXX needs cached value
1200 elif suboption[2:3] == SET_CONTROL_BREAK_ON:
1201 self.serial.setBreak(True)
cliechti6a300772009-08-12 02:28:56 +00001202 if self.logger:
1203 self.logger.info("changed BREAK to active")
cliechti130d1f02009-08-04 02:10:58 +00001204 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_BREAK_ON)
1205 elif suboption[2:3] == SET_CONTROL_BREAK_OFF:
1206 self.serial.setBreak(False)
cliechti6a300772009-08-12 02:28:56 +00001207 if self.logger:
1208 self.logger.info("changed BREAK to inactive")
cliechti130d1f02009-08-04 02:10:58 +00001209 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_BREAK_OFF)
1210 elif suboption[2:3] == SET_CONTROL_REQ_DTR:
cliechti6a300772009-08-12 02:28:56 +00001211 if self.logger:
1212 self.logger.warning("requested DTR state - not implemented")
cliechti130d1f02009-08-04 02:10:58 +00001213 pass # XXX needs cached value
1214 elif suboption[2:3] == SET_CONTROL_DTR_ON:
1215 self.serial.setDTR(True)
cliechti6a300772009-08-12 02:28:56 +00001216 if self.logger:
1217 self.logger.info("changed DTR to active")
cliechti130d1f02009-08-04 02:10:58 +00001218 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_DTR_ON)
1219 elif suboption[2:3] == SET_CONTROL_DTR_OFF:
1220 self.serial.setDTR(False)
cliechti6a300772009-08-12 02:28:56 +00001221 if self.logger:
1222 self.logger.info("changed DTR to inactive")
cliechti130d1f02009-08-04 02:10:58 +00001223 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_DTR_OFF)
1224 elif suboption[2:3] == SET_CONTROL_REQ_RTS:
cliechti6a300772009-08-12 02:28:56 +00001225 if self.logger:
1226 self.logger.warning("requested RTS state - not implemented")
cliechti130d1f02009-08-04 02:10:58 +00001227 pass # XXX needs cached value
1228 #~ self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_RTS_ON)
1229 elif suboption[2:3] == SET_CONTROL_RTS_ON:
1230 self.serial.setRTS(True)
cliechti6a300772009-08-12 02:28:56 +00001231 if self.logger:
1232 self.logger.info("changed RTS to active")
cliechti130d1f02009-08-04 02:10:58 +00001233 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_RTS_ON)
1234 elif suboption[2:3] == SET_CONTROL_RTS_OFF:
1235 self.serial.setRTS(False)
cliechti6a300772009-08-12 02:28:56 +00001236 if self.logger:
1237 self.logger.info("changed RTS to inactive")
cliechti130d1f02009-08-04 02:10:58 +00001238 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_RTS_OFF)
1239 #~ elif suboption[2:3] == SET_CONTROL_REQ_FLOW_SETTING_IN:
1240 #~ elif suboption[2:3] == SET_CONTROL_USE_NO_FLOW_CONTROL_IN:
1241 #~ elif suboption[2:3] == SET_CONTROL_USE_SW_FLOW_CONTOL_IN:
1242 #~ elif suboption[2:3] == SET_CONTROL_USE_HW_FLOW_CONTOL_IN:
1243 #~ elif suboption[2:3] == SET_CONTROL_USE_DCD_FLOW_CONTROL:
1244 #~ elif suboption[2:3] == SET_CONTROL_USE_DTR_FLOW_CONTROL:
1245 #~ elif suboption[2:3] == SET_CONTROL_USE_DSR_FLOW_CONTROL:
1246 elif suboption[1:2] == NOTIFY_LINESTATE:
cliechti7cb78e82009-08-05 15:47:57 +00001247 # client polls for current state
1248 self.rfc2217SendSubnegotiation(
1249 SERVER_NOTIFY_LINESTATE,
cliechti6a300772009-08-12 02:28:56 +00001250 to_bytes([0]) # sorry, nothing like that implemented
cliechti7cb78e82009-08-05 15:47:57 +00001251 )
cliechti130d1f02009-08-04 02:10:58 +00001252 elif suboption[1:2] == NOTIFY_MODEMSTATE:
cliechti6a300772009-08-12 02:28:56 +00001253 if self.logger:
1254 self.logger.info("request for modem state")
cliechti7cb78e82009-08-05 15:47:57 +00001255 # client polls for current state
1256 self.check_modem_lines(force_notification=True)
cliechti130d1f02009-08-04 02:10:58 +00001257 elif suboption[1:2] == FLOWCONTROL_SUSPEND:
cliechti6a300772009-08-12 02:28:56 +00001258 if self.logger:
1259 self.logger.info("suspend")
cliechti130d1f02009-08-04 02:10:58 +00001260 self._remote_suspend_flow = True
1261 elif suboption[1:2] == FLOWCONTROL_RESUME:
cliechti6a300772009-08-12 02:28:56 +00001262 if self.logger:
1263 self.logger.info("resume")
cliechti130d1f02009-08-04 02:10:58 +00001264 self._remote_suspend_flow = False
1265 elif suboption[1:2] == SET_LINESTATE_MASK:
1266 self.linstate_mask = ord(suboption[2:3]) # ensure it is a number
cliechti6a300772009-08-12 02:28:56 +00001267 if self.logger:
cliechtif325c032009-12-25 16:09:49 +00001268 self.logger.info("line state mask: 0x%02x" % (self.linstate_mask,))
cliechti130d1f02009-08-04 02:10:58 +00001269 elif suboption[1:2] == SET_MODEMSTATE_MASK:
1270 self.modemstate_mask = ord(suboption[2:3]) # ensure it is a number
cliechti6a300772009-08-12 02:28:56 +00001271 if self.logger:
cliechtif325c032009-12-25 16:09:49 +00001272 self.logger.info("modem state mask: 0x%02x" % (self.modemstate_mask,))
cliechti130d1f02009-08-04 02:10:58 +00001273 elif suboption[1:2] == PURGE_DATA:
1274 if suboption[2:3] == PURGE_RECEIVE_BUFFER:
1275 self.serial.flushInput()
cliechti6a300772009-08-12 02:28:56 +00001276 if self.logger:
1277 self.logger.info("purge in")
cliechti130d1f02009-08-04 02:10:58 +00001278 self.rfc2217SendSubnegotiation(SERVER_PURGE_DATA, PURGE_RECEIVE_BUFFER)
1279 elif suboption[2:3] == PURGE_TRANSMIT_BUFFER:
1280 self.serial.flushOutput()
cliechti6a300772009-08-12 02:28:56 +00001281 if self.logger:
1282 self.logger.info("purge out")
cliechti130d1f02009-08-04 02:10:58 +00001283 self.rfc2217SendSubnegotiation(SERVER_PURGE_DATA, PURGE_TRANSMIT_BUFFER)
1284 elif suboption[2:3] == PURGE_BOTH_BUFFERS:
1285 self.serial.flushInput()
1286 self.serial.flushOutput()
cliechti6a300772009-08-12 02:28:56 +00001287 if self.logger:
1288 self.logger.info("purge both")
cliechti130d1f02009-08-04 02:10:58 +00001289 self.rfc2217SendSubnegotiation(SERVER_PURGE_DATA, PURGE_BOTH_BUFFERS)
1290 else:
cliechti6a300772009-08-12 02:28:56 +00001291 if self.logger:
1292 self.logger.error("undefined PURGE_DATA: %r" % list(suboption[2:]))
cliechti130d1f02009-08-04 02:10:58 +00001293 else:
cliechti6a300772009-08-12 02:28:56 +00001294 if self.logger:
1295 self.logger.error("undefined COM_PORT_OPTION: %r" % list(suboption[1:]))
cliechti130d1f02009-08-04 02:10:58 +00001296 else:
cliechti6a300772009-08-12 02:28:56 +00001297 if self.logger:
1298 self.logger.warning("unknown subnegotiation: %r" % (suboption,))
cliechti130d1f02009-08-04 02:10:58 +00001299
1300
1301# simple client test
cliechti8099bed2009-08-01 23:59:18 +00001302if __name__ == '__main__':
1303 import sys
1304 s = Serial('rfc2217://localhost:7000', 115200)
1305 sys.stdout.write('%s\n' % s)
1306
cliechti2b929b72009-08-02 23:49:02 +00001307 #~ s.baudrate = 1898
1308
cliechti8099bed2009-08-01 23:59:18 +00001309 sys.stdout.write("write...\n")
Chris Liechtib4cda3a2015-08-08 17:12:08 +02001310 s.write(b"hello\n")
cliechti8099bed2009-08-01 23:59:18 +00001311 s.flush()
cliechti8099bed2009-08-01 23:59:18 +00001312 sys.stdout.write("read: %s\n" % s.read(5))
1313
1314 #~ s.baudrate = 19200
1315 #~ s.databits = 7
1316 s.close()