blob: 24a090c90742362d49801bc72306a87609a9d0f4 [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
cliechti81c54762009-08-03 23:53:27 +000020##############################################################################
21# observations and issues with servers
22#=============================================================================
23# 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
31#=============================================================================
32# 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.
35#=============================================================================
36# 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
47##############################################################################
48
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 Liechtib4cda3a2015-08-08 17:12:08 +020077from serial.serialutil import *
78
cliechti8099bed2009-08-01 23:59:18 +000079# port string is expected to be something like this:
80# rfc2217://host:port
81# host may be an IP or including domain, whatever.
82# port is 0...65535
83
Chris Liechti3ad62fb2015-08-29 21:53:32 +020084# map log level names to constants. used in from_url()
cliechti5cc3eb12009-08-11 23:04:30 +000085LOGGER_LEVELS = {
Chris Liechtic4bca9e2015-08-07 14:40:41 +020086 'debug': logging.DEBUG,
87 'info': logging.INFO,
88 'warning': logging.WARNING,
89 'error': logging.ERROR,
90 }
cliechti5cc3eb12009-08-11 23:04:30 +000091
92
cliechti8099bed2009-08-01 23:59:18 +000093# telnet protocol characters
Chris Liechtib4cda3a2015-08-08 17:12:08 +020094SE = b'\xf0' # Subnegotiation End
95NOP = b'\xf1' # No Operation
96DM = b'\xf2' # Data Mark
97BRK = b'\xf3' # Break
98IP = b'\xf4' # Interrupt process
99AO = b'\xf5' # Abort output
100AYT = b'\xf6' # Are You There
101EC = b'\xf7' # Erase Character
102EL = b'\xf8' # Erase Line
103GA = b'\xf9' # Go Ahead
104SB = b'\xfa' # Subnegotiation Begin
105WILL = b'\xfb'
106WONT = b'\xfc'
107DO = b'\xfd'
108DONT = b'\xfe'
109IAC = b'\xff' # Interpret As Command
110IAC_DOUBLED = b'\xff\xff'
cliechti8099bed2009-08-01 23:59:18 +0000111
112# selected telnet options
Chris Liechtib4cda3a2015-08-08 17:12:08 +0200113BINARY = b'\x00' # 8-bit data path
114ECHO = b'\x01' # echo
115SGA = b'\x03' # suppress go ahead
cliechti8099bed2009-08-01 23:59:18 +0000116
117# RFC2217
Chris Liechtib4cda3a2015-08-08 17:12:08 +0200118COM_PORT_OPTION = b'\x2c'
cliechti8099bed2009-08-01 23:59:18 +0000119
120# Client to Access Server
Chris Liechtib4cda3a2015-08-08 17:12:08 +0200121SET_BAUDRATE = b'\x01'
122SET_DATASIZE = b'\x02'
123SET_PARITY = b'\x03'
124SET_STOPSIZE = b'\x04'
125SET_CONTROL = b'\x05'
126NOTIFY_LINESTATE = b'\x06'
127NOTIFY_MODEMSTATE = b'\x07'
128FLOWCONTROL_SUSPEND = b'\x08'
129FLOWCONTROL_RESUME = b'\x09'
130SET_LINESTATE_MASK = b'\x0a'
131SET_MODEMSTATE_MASK = b'\x0b'
132PURGE_DATA = b'\x0c'
cliechti8099bed2009-08-01 23:59:18 +0000133
Chris Liechtib4cda3a2015-08-08 17:12:08 +0200134SERVER_SET_BAUDRATE = b'\x65'
135SERVER_SET_DATASIZE = b'\x66'
136SERVER_SET_PARITY = b'\x67'
137SERVER_SET_STOPSIZE = b'\x68'
138SERVER_SET_CONTROL = b'\x69'
139SERVER_NOTIFY_LINESTATE = b'\x6a'
140SERVER_NOTIFY_MODEMSTATE = b'\x6b'
141SERVER_FLOWCONTROL_SUSPEND = b'\x6c'
142SERVER_FLOWCONTROL_RESUME = b'\x6d'
143SERVER_SET_LINESTATE_MASK = b'\x6e'
144SERVER_SET_MODEMSTATE_MASK = b'\x6f'
145SERVER_PURGE_DATA = b'\x70'
cliechti8099bed2009-08-01 23:59:18 +0000146
147RFC2217_ANSWER_MAP = {
148 SET_BAUDRATE: SERVER_SET_BAUDRATE,
149 SET_DATASIZE: SERVER_SET_DATASIZE,
150 SET_PARITY: SERVER_SET_PARITY,
151 SET_STOPSIZE: SERVER_SET_STOPSIZE,
152 SET_CONTROL: SERVER_SET_CONTROL,
153 NOTIFY_LINESTATE: SERVER_NOTIFY_LINESTATE,
154 NOTIFY_MODEMSTATE: SERVER_NOTIFY_MODEMSTATE,
155 FLOWCONTROL_SUSPEND: SERVER_FLOWCONTROL_SUSPEND,
156 FLOWCONTROL_RESUME: SERVER_FLOWCONTROL_RESUME,
157 SET_LINESTATE_MASK: SERVER_SET_LINESTATE_MASK,
158 SET_MODEMSTATE_MASK: SERVER_SET_MODEMSTATE_MASK,
159 PURGE_DATA: SERVER_PURGE_DATA,
160}
161
Chris Liechtib4cda3a2015-08-08 17:12:08 +0200162SET_CONTROL_REQ_FLOW_SETTING = b'\x00' # Request Com Port Flow Control Setting (outbound/both)
163SET_CONTROL_USE_NO_FLOW_CONTROL = b'\x01' # Use No Flow Control (outbound/both)
164SET_CONTROL_USE_SW_FLOW_CONTROL = b'\x02' # Use XON/XOFF Flow Control (outbound/both)
165SET_CONTROL_USE_HW_FLOW_CONTROL = b'\x03' # Use HARDWARE Flow Control (outbound/both)
166SET_CONTROL_REQ_BREAK_STATE = b'\x04' # Request BREAK State
167SET_CONTROL_BREAK_ON = b'\x05' # Set BREAK State ON
168SET_CONTROL_BREAK_OFF = b'\x06' # Set BREAK State OFF
169SET_CONTROL_REQ_DTR = b'\x07' # Request DTR Signal State
170SET_CONTROL_DTR_ON = b'\x08' # Set DTR Signal State ON
171SET_CONTROL_DTR_OFF = b'\x09' # Set DTR Signal State OFF
172SET_CONTROL_REQ_RTS = b'\x0a' # Request RTS Signal State
173SET_CONTROL_RTS_ON = b'\x0b' # Set RTS Signal State ON
174SET_CONTROL_RTS_OFF = b'\x0c' # Set RTS Signal State OFF
175SET_CONTROL_REQ_FLOW_SETTING_IN = b'\x0d' # Request Com Port Flow Control Setting (inbound)
176SET_CONTROL_USE_NO_FLOW_CONTROL_IN = b'\x0e' # Use No Flow Control (inbound)
177SET_CONTROL_USE_SW_FLOW_CONTOL_IN = b'\x0f' # Use XON/XOFF Flow Control (inbound)
178SET_CONTROL_USE_HW_FLOW_CONTOL_IN = b'\x10' # Use HARDWARE Flow Control (inbound)
179SET_CONTROL_USE_DCD_FLOW_CONTROL = b'\x11' # Use DCD Flow Control (outbound/both)
180SET_CONTROL_USE_DTR_FLOW_CONTROL = b'\x12' # Use DTR Flow Control (inbound)
181SET_CONTROL_USE_DSR_FLOW_CONTROL = b'\x13' # Use DSR Flow Control (outbound/both)
cliechti8099bed2009-08-01 23:59:18 +0000182
Chris Liechtib4cda3a2015-08-08 17:12:08 +0200183LINESTATE_MASK_TIMEOUT = 128 # Time-out Error
184LINESTATE_MASK_SHIFTREG_EMPTY = 64 # Transfer Shift Register Empty
185LINESTATE_MASK_TRANSREG_EMPTY = 32 # Transfer Holding Register Empty
186LINESTATE_MASK_BREAK_DETECT = 16 # Break-detect Error
187LINESTATE_MASK_FRAMING_ERROR = 8 # Framing Error
188LINESTATE_MASK_PARTIY_ERROR = 4 # Parity Error
189LINESTATE_MASK_OVERRUN_ERROR = 2 # Overrun Error
190LINESTATE_MASK_DATA_READY = 1 # Data Ready
cliechti8099bed2009-08-01 23:59:18 +0000191
Chris Liechtib4cda3a2015-08-08 17:12:08 +0200192MODEMSTATE_MASK_CD = 128 # Receive Line Signal Detect (also known as Carrier Detect)
193MODEMSTATE_MASK_RI = 64 # Ring Indicator
194MODEMSTATE_MASK_DSR = 32 # Data-Set-Ready Signal State
195MODEMSTATE_MASK_CTS = 16 # Clear-To-Send Signal State
196MODEMSTATE_MASK_CD_CHANGE = 8 # Delta Receive Line Signal Detect
197MODEMSTATE_MASK_RI_CHANGE = 4 # Trailing-edge Ring Detector
198MODEMSTATE_MASK_DSR_CHANGE = 2 # Delta Data-Set-Ready
199MODEMSTATE_MASK_CTS_CHANGE = 1 # Delta Clear-To-Send
cliechti8099bed2009-08-01 23:59:18 +0000200
Chris Liechtib4cda3a2015-08-08 17:12:08 +0200201PURGE_RECEIVE_BUFFER = b'\x01' # Purge access server receive data buffer
202PURGE_TRANSMIT_BUFFER = b'\x02' # Purge access server transmit data buffer
203PURGE_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 +0000204
205
206RFC2217_PARITY_MAP = {
207 PARITY_NONE: 1,
208 PARITY_ODD: 2,
209 PARITY_EVEN: 3,
210 PARITY_MARK: 4,
211 PARITY_SPACE: 5,
212}
cliechti130d1f02009-08-04 02:10:58 +0000213RFC2217_REVERSE_PARITY_MAP = dict((v,k) for k,v in RFC2217_PARITY_MAP.items())
cliechti8099bed2009-08-01 23:59:18 +0000214
215RFC2217_STOPBIT_MAP = {
216 STOPBITS_ONE: 1,
217 STOPBITS_ONE_POINT_FIVE: 3,
218 STOPBITS_TWO: 2,
219}
cliechti130d1f02009-08-04 02:10:58 +0000220RFC2217_REVERSE_STOPBIT_MAP = dict((v,k) for k,v in RFC2217_STOPBIT_MAP.items())
cliechti8099bed2009-08-01 23:59:18 +0000221
cliechti130d1f02009-08-04 02:10:58 +0000222# Telnet filter states
223M_NORMAL = 0
224M_IAC_SEEN = 1
225M_NEGOTIATE = 2
cliechti8099bed2009-08-01 23:59:18 +0000226
cliechti130d1f02009-08-04 02:10:58 +0000227# TelnetOption and TelnetSubnegotiation states
cliechtiac205322009-08-02 20:40:21 +0000228REQUESTED = 'REQUESTED'
229ACTIVE = 'ACTIVE'
230INACTIVE = 'INACTIVE'
231REALLY_INACTIVE = 'REALLY_INACTIVE'
232
233class TelnetOption(object):
cliechti1ef7e3e2009-08-03 02:38:43 +0000234 """Manage a single telnet option, keeps track of DO/DONT WILL/WONT."""
235
cliechti86b593e2009-08-05 16:28:12 +0000236 def __init__(self, connection, name, option, send_yes, send_no, ack_yes, ack_no, initial_state, activation_callback=None):
cliechtieada4fd2013-07-31 16:26:07 +0000237 """\
238 Initialize option.
cliechti1ef7e3e2009-08-03 02:38:43 +0000239 :param connection: connection used to transmit answers
240 :param name: a readable name for debug outputs
241 :param send_yes: what to send when option is to be enabled.
242 :param send_no: what to send when option is to be disabled.
243 :param ack_yes: what to expect when remote agrees on option.
244 :param ack_no: what to expect when remote disagrees on option.
245 :param initial_state: options initialized with REQUESTED are tried to
246 be enabled on startup. use INACTIVE for all others.
247 """
cliechti2b929b72009-08-02 23:49:02 +0000248 self.connection = connection
cliechtiac205322009-08-02 20:40:21 +0000249 self.name = name
250 self.option = option
251 self.send_yes = send_yes
252 self.send_no = send_no
253 self.ack_yes = ack_yes
254 self.ack_no = ack_no
255 self.state = initial_state
256 self.active = False
cliechti86b593e2009-08-05 16:28:12 +0000257 self.activation_callback = activation_callback
cliechtiac205322009-08-02 20:40:21 +0000258
259 def __repr__(self):
cliechti1ef7e3e2009-08-03 02:38:43 +0000260 """String for debug outputs"""
cliechtiac205322009-08-02 20:40:21 +0000261 return "%s:%s(%s)" % (self.name, self.active, self.state)
262
cliechti2b929b72009-08-02 23:49:02 +0000263 def process_incoming(self, command):
cliechti7d448562014-08-03 21:57:45 +0000264 """\
265 A DO/DONT/WILL/WONT was received for this option, update state and
266 answer when needed.
267 """
cliechtiac205322009-08-02 20:40:21 +0000268 if command == self.ack_yes:
269 if self.state is REQUESTED:
270 self.state = ACTIVE
271 self.active = True
cliechti86b593e2009-08-05 16:28:12 +0000272 if self.activation_callback is not None:
273 self.activation_callback()
cliechtiac205322009-08-02 20:40:21 +0000274 elif self.state is ACTIVE:
275 pass
276 elif self.state is INACTIVE:
277 self.state = ACTIVE
cliechti1ef7e3e2009-08-03 02:38:43 +0000278 self.connection.telnetSendOption(self.send_yes, self.option)
cliechtiac205322009-08-02 20:40:21 +0000279 self.active = True
cliechti86b593e2009-08-05 16:28:12 +0000280 if self.activation_callback is not None:
281 self.activation_callback()
cliechtiac205322009-08-02 20:40:21 +0000282 elif self.state is REALLY_INACTIVE:
cliechti1ef7e3e2009-08-03 02:38:43 +0000283 self.connection.telnetSendOption(self.send_no, self.option)
cliechtiac205322009-08-02 20:40:21 +0000284 else:
285 raise ValueError('option in illegal state %r' % self)
286 elif command == self.ack_no:
287 if self.state is REQUESTED:
288 self.state = INACTIVE
289 self.active = False
290 elif self.state is ACTIVE:
291 self.state = INACTIVE
cliechti1ef7e3e2009-08-03 02:38:43 +0000292 self.connection.telnetSendOption(self.send_no, self.option)
cliechtiac205322009-08-02 20:40:21 +0000293 self.active = False
294 elif self.state is INACTIVE:
295 pass
296 elif self.state is REALLY_INACTIVE:
297 pass
298 else:
299 raise ValueError('option in illegal state %r' % self)
300
301
cliechti2b929b72009-08-02 23:49:02 +0000302class TelnetSubnegotiation(object):
cliechtieada4fd2013-07-31 16:26:07 +0000303 """\
304 A object to handle subnegotiation of options. In this case actually
305 sub-sub options for RFC 2217. It is used to track com port options.
306 """
cliechti2b929b72009-08-02 23:49:02 +0000307
308 def __init__(self, connection, name, option, ack_option=None):
309 if ack_option is None: ack_option = option
310 self.connection = connection
311 self.name = name
312 self.option = option
313 self.value = None
314 self.ack_option = ack_option
315 self.state = INACTIVE
316
317 def __repr__(self):
cliechti044d8662009-08-11 21:40:31 +0000318 """String for debug outputs."""
cliechti2b929b72009-08-02 23:49:02 +0000319 return "%s:%s" % (self.name, self.state)
320
321 def set(self, value):
cliechtieada4fd2013-07-31 16:26:07 +0000322 """\
cliechti7d448562014-08-03 21:57:45 +0000323 Request a change of the value. a request is sent to the server. if
cliechti2b929b72009-08-02 23:49:02 +0000324 the client needs to know if the change is performed he has to check the
cliechtieada4fd2013-07-31 16:26:07 +0000325 state of this object.
326 """
cliechti2b929b72009-08-02 23:49:02 +0000327 self.value = value
328 self.state = REQUESTED
cliechti1ef7e3e2009-08-03 02:38:43 +0000329 self.connection.rfc2217SendSubnegotiation(self.option, self.value)
cliechti6a300772009-08-12 02:28:56 +0000330 if self.connection.logger:
331 self.connection.logger.debug("SB Requesting %s -> %r" % (self.name, self.value))
cliechti2b929b72009-08-02 23:49:02 +0000332
333 def isReady(self):
cliechtieada4fd2013-07-31 16:26:07 +0000334 """\
cliechti7d448562014-08-03 21:57:45 +0000335 Check if answer from server has been received. when server rejects
cliechtieada4fd2013-07-31 16:26:07 +0000336 the change, raise a ValueError.
337 """
cliechti2b929b72009-08-02 23:49:02 +0000338 if self.state == REALLY_INACTIVE:
339 raise ValueError("remote rejected value for option %r" % (self.name))
340 return self.state == ACTIVE
cliechti1ef7e3e2009-08-03 02:38:43 +0000341 # add property to have a similar interface as TelnetOption
cliechti2b929b72009-08-02 23:49:02 +0000342 active = property(isReady)
343
cliechti044d8662009-08-11 21:40:31 +0000344 def wait(self, timeout=3):
cliechtieada4fd2013-07-31 16:26:07 +0000345 """\
cliechti7d448562014-08-03 21:57:45 +0000346 Wait until the subnegotiation has been acknowledged or timeout. It
cliechti1ef7e3e2009-08-03 02:38:43 +0000347 can also throw a value error when the answer from the server does not
cliechtieada4fd2013-07-31 16:26:07 +0000348 match the value sent.
349 """
cliechti044d8662009-08-11 21:40:31 +0000350 timeout_time = time.time() + timeout
cliechti2b929b72009-08-02 23:49:02 +0000351 while time.time() < timeout_time:
352 time.sleep(0.05) # prevent 100% CPU load
353 if self.isReady():
354 break
355 else:
356 raise SerialException("timeout while waiting for option %r" % (self.name))
357
358 def checkAnswer(self, suboption):
cliechtieada4fd2013-07-31 16:26:07 +0000359 """\
cliechti7d448562014-08-03 21:57:45 +0000360 Check an incoming subnegotiation block. The parameter already has
cliechtieada4fd2013-07-31 16:26:07 +0000361 cut off the header like sub option number and com port option value.
362 """
cliechti2b929b72009-08-02 23:49:02 +0000363 if self.value == suboption[:len(self.value)]:
364 self.state = ACTIVE
365 else:
366 # error propagation done in isReady
367 self.state = REALLY_INACTIVE
cliechti6a300772009-08-12 02:28:56 +0000368 if self.connection.logger:
369 self.connection.logger.debug("SB Answer %s -> %r -> %s" % (self.name, suboption, self.state))
cliechti2b929b72009-08-02 23:49:02 +0000370
371
Chris Liechtief6b7b42015-08-06 22:19:26 +0200372class Serial(SerialBase):
cliechti044d8662009-08-11 21:40:31 +0000373 """Serial port implementation for RFC 2217 remote serial ports."""
cliechti8099bed2009-08-01 23:59:18 +0000374
375 BAUDRATES = (50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800,
376 9600, 19200, 38400, 57600, 115200)
377
378 def open(self):
cliechtieada4fd2013-07-31 16:26:07 +0000379 """\
380 Open port with current settings. This may throw a SerialException
381 if the port cannot be opened.
382 """
cliechti6a300772009-08-12 02:28:56 +0000383 self.logger = None
cliechti81c54762009-08-03 23:53:27 +0000384 self._ignore_set_control_answer = False
cliechti7cb78e82009-08-05 15:47:57 +0000385 self._poll_modem_state = False
cliechtidfe2d272009-08-10 22:19:41 +0000386 self._network_timeout = 3
cliechti8099bed2009-08-01 23:59:18 +0000387 if self._port is None:
388 raise SerialException("Port must be configured before it can be used.")
Chris Liechti3ad62fb2015-08-29 21:53:32 +0200389 if self.is_open:
cliechti8f69e702011-03-19 00:22:32 +0000390 raise SerialException("Port is already open.")
cliechti8099bed2009-08-01 23:59:18 +0000391 try:
Chris Liechti3ad62fb2015-08-29 21:53:32 +0200392 self._socket = socket.create_connection(self.from_url(self.portstr))
cliechti6a300772009-08-12 02:28:56 +0000393 self._socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
Chris Liechti68340d72015-08-03 14:15:48 +0200394 except Exception as msg:
cliechti8099bed2009-08-01 23:59:18 +0000395 self._socket = None
396 raise SerialException("Could not open port %s: %s" % (self.portstr, msg))
397
cliechti1ef7e3e2009-08-03 02:38:43 +0000398 self._socket.settimeout(5) # XXX good value?
cliechti8099bed2009-08-01 23:59:18 +0000399
cliechti1ef7e3e2009-08-03 02:38:43 +0000400 # use a thread save queue as buffer. it also simplifies implementing
401 # the read timeout
cliechti8099bed2009-08-01 23:59:18 +0000402 self._read_buffer = Queue.Queue()
cliechti81c54762009-08-03 23:53:27 +0000403 # to ensure that user writes does not interfere with internal
404 # telnet/rfc2217 options establish a lock
405 self._write_lock = threading.Lock()
cliechtiac205322009-08-02 20:40:21 +0000406 # name the following separately so that, below, a check can be easily done
407 mandadory_options = [
cliechti2b929b72009-08-02 23:49:02 +0000408 TelnetOption(self, 'we-BINARY', BINARY, WILL, WONT, DO, DONT, INACTIVE),
cliechti2b929b72009-08-02 23:49:02 +0000409 TelnetOption(self, 'we-RFC2217', COM_PORT_OPTION, WILL, WONT, DO, DONT, REQUESTED),
cliechtiac205322009-08-02 20:40:21 +0000410 ]
411 # all supported telnet options
412 self._telnet_options = [
cliechtia29275e2009-08-03 00:08:04 +0000413 TelnetOption(self, 'ECHO', ECHO, DO, DONT, WILL, WONT, REQUESTED),
cliechti2b929b72009-08-02 23:49:02 +0000414 TelnetOption(self, 'we-SGA', SGA, WILL, WONT, DO, DONT, REQUESTED),
415 TelnetOption(self, 'they-SGA', SGA, DO, DONT, WILL, WONT, REQUESTED),
cliechti81c54762009-08-03 23:53:27 +0000416 TelnetOption(self, 'they-BINARY', BINARY, DO, DONT, WILL, WONT, INACTIVE),
417 TelnetOption(self, 'they-RFC2217', COM_PORT_OPTION, DO, DONT, WILL, WONT, REQUESTED),
cliechtiac205322009-08-02 20:40:21 +0000418 ] + mandadory_options
cliechti044d8662009-08-11 21:40:31 +0000419 # RFC 2217 specific states
cliechti2b929b72009-08-02 23:49:02 +0000420 # COM port settings
421 self._rfc2217_port_settings = {
422 'baudrate': TelnetSubnegotiation(self, 'baudrate', SET_BAUDRATE, SERVER_SET_BAUDRATE),
423 'datasize': TelnetSubnegotiation(self, 'datasize', SET_DATASIZE, SERVER_SET_DATASIZE),
424 'parity': TelnetSubnegotiation(self, 'parity', SET_PARITY, SERVER_SET_PARITY),
425 'stopsize': TelnetSubnegotiation(self, 'stopsize', SET_STOPSIZE, SERVER_SET_STOPSIZE),
426 }
cliechticb20a4f2011-04-25 02:25:54 +0000427 # There are more subnegotiation objects, combine all in one dictionary
cliechti2b929b72009-08-02 23:49:02 +0000428 # for easy access
429 self._rfc2217_options = {
430 'purge': TelnetSubnegotiation(self, 'purge', PURGE_DATA, SERVER_PURGE_DATA),
cliechti81c54762009-08-03 23:53:27 +0000431 'control': TelnetSubnegotiation(self, 'control', SET_CONTROL, SERVER_SET_CONTROL),
cliechti2b929b72009-08-02 23:49:02 +0000432 }
433 self._rfc2217_options.update(self._rfc2217_port_settings)
434 # cache for line and modem states that the server sends to us
cliechti8099bed2009-08-01 23:59:18 +0000435 self._linestate = 0
cliechti7cb78e82009-08-05 15:47:57 +0000436 self._modemstate = None
437 self._modemstate_expires = 0
cliechti044d8662009-08-11 21:40:31 +0000438 # RFC 2217 flow control between server and client
cliechti672d0292009-08-03 02:01:57 +0000439 self._remote_suspend_flow = False
cliechti8099bed2009-08-01 23:59:18 +0000440
cliechti1ef7e3e2009-08-03 02:38:43 +0000441 self._thread = threading.Thread(target=self._telnetReadLoop)
cliechti8099bed2009-08-01 23:59:18 +0000442 self._thread.setDaemon(True)
cliechti5cc3eb12009-08-11 23:04:30 +0000443 self._thread.setName('pySerial RFC 2217 reader thread for %s' % (self._port,))
cliechti8099bed2009-08-01 23:59:18 +0000444 self._thread.start()
445
cliechti044d8662009-08-11 21:40:31 +0000446 # negotiate Telnet/RFC 2217 -> send initial requests
cliechtiac205322009-08-02 20:40:21 +0000447 for option in self._telnet_options:
448 if option.state is REQUESTED:
cliechti1ef7e3e2009-08-03 02:38:43 +0000449 self.telnetSendOption(option.send_yes, option.option)
cliechtiac205322009-08-02 20:40:21 +0000450 # now wait until important options are negotiated
cliechtidfe2d272009-08-10 22:19:41 +0000451 timeout_time = time.time() + self._network_timeout
cliechtiac205322009-08-02 20:40:21 +0000452 while time.time() < timeout_time:
cliechtiac205322009-08-02 20:40:21 +0000453 time.sleep(0.05) # prevent 100% CPU load
cliechti7c213a92014-07-31 15:29:34 +0000454 if sum(o.active for o in mandadory_options) == sum(o.state != INACTIVE for o in mandadory_options):
cliechti2b929b72009-08-02 23:49:02 +0000455 break
456 else:
457 raise SerialException("Remote does not seem to support RFC2217 or BINARY mode %r" % mandadory_options)
cliechti6a300772009-08-12 02:28:56 +0000458 if self.logger:
459 self.logger.info("Negotiated options: %s" % self._telnet_options)
cliechti8099bed2009-08-01 23:59:18 +0000460
cliechti044d8662009-08-11 21:40:31 +0000461 # fine, go on, set RFC 2271 specific things
Chris Liechti3ad62fb2015-08-29 21:53:32 +0200462 self._reconfigure_port()
cliechti2b929b72009-08-02 23:49:02 +0000463 # all things set up get, now a clean start
Chris Liechti3ad62fb2015-08-29 21:53:32 +0200464 self.is_open = True
465 if not self._dsrdtr:
Chris Liechtief1fe252015-08-27 23:25:21 +0200466 self._update_dtr_state()
cliechti8099bed2009-08-01 23:59:18 +0000467 if not self._rtscts:
Chris Liechtief1fe252015-08-27 23:25:21 +0200468 self._update_rts_state()
469 self.reset_input_buffer()
470 self.reset_output_buffer()
cliechti8099bed2009-08-01 23:59:18 +0000471
Chris Liechti3ad62fb2015-08-29 21:53:32 +0200472 def _reconfigure_port(self):
cliechti8099bed2009-08-01 23:59:18 +0000473 """Set communication parameters on opened port."""
474 if self._socket is None:
475 raise SerialException("Can only operate on open ports")
476
cliechti8099bed2009-08-01 23:59:18 +0000477 # if self._timeout != 0 and self._interCharTimeout is not None:
cliechti8099bed2009-08-01 23:59:18 +0000478 # XXX
479
Chris Liechti3ad62fb2015-08-29 21:53:32 +0200480 if self._write_timeout is not None:
481 raise NotImplementedError('write_timeout is currently not supported')
cliechti2b929b72009-08-02 23:49:02 +0000482 # XXX
cliechti8099bed2009-08-01 23:59:18 +0000483
cliechti2b929b72009-08-02 23:49:02 +0000484 # Setup the connection
cliechti1ef7e3e2009-08-03 02:38:43 +0000485 # to get good performance, all parameter changes are sent first...
Chris Liechti01587b12015-08-05 02:39:32 +0200486 if not 0 < self._baudrate < 2**32:
cliechti81c54762009-08-03 23:53:27 +0000487 raise ValueError("invalid baudrate: %r" % (self._baudrate))
Chris Liechti01587b12015-08-05 02:39:32 +0200488 self._rfc2217_port_settings['baudrate'].set(struct.pack(b'!I', self._baudrate))
489 self._rfc2217_port_settings['datasize'].set(struct.pack(b'!B', self._bytesize))
490 self._rfc2217_port_settings['parity'].set(struct.pack(b'!B', RFC2217_PARITY_MAP[self._parity]))
491 self._rfc2217_port_settings['stopsize'].set(struct.pack(b'!B', RFC2217_STOPBIT_MAP[self._stopbits]))
cliechti8099bed2009-08-01 23:59:18 +0000492
cliechti2b929b72009-08-02 23:49:02 +0000493 # and now wait until parameters are active
494 items = self._rfc2217_port_settings.values()
cliechti6a300772009-08-12 02:28:56 +0000495 if self.logger:
496 self.logger.debug("Negotiating settings: %s" % (items,))
cliechtidfe2d272009-08-10 22:19:41 +0000497 timeout_time = time.time() + self._network_timeout
cliechti2b929b72009-08-02 23:49:02 +0000498 while time.time() < timeout_time:
499 time.sleep(0.05) # prevent 100% CPU load
500 if sum(o.active for o in items) == len(items):
501 break
502 else:
503 raise SerialException("Remote does not accept parameter change (RFC2217): %r" % items)
cliechti6a300772009-08-12 02:28:56 +0000504 if self.logger:
505 self.logger.info("Negotiated settings: %s" % (items,))
cliechti8099bed2009-08-01 23:59:18 +0000506
507 if self._rtscts and self._xonxoff:
cliechti1ef7e3e2009-08-03 02:38:43 +0000508 raise ValueError('xonxoff and rtscts together are not supported')
cliechti8099bed2009-08-01 23:59:18 +0000509 elif self._rtscts:
cliechti1ef7e3e2009-08-03 02:38:43 +0000510 self.rfc2217SetControl(SET_CONTROL_USE_HW_FLOW_CONTROL)
cliechti8099bed2009-08-01 23:59:18 +0000511 elif self._xonxoff:
cliechti1ef7e3e2009-08-03 02:38:43 +0000512 self.rfc2217SetControl(SET_CONTROL_USE_SW_FLOW_CONTROL)
cliechti8099bed2009-08-01 23:59:18 +0000513 else:
cliechti1ef7e3e2009-08-03 02:38:43 +0000514 self.rfc2217SetControl(SET_CONTROL_USE_NO_FLOW_CONTROL)
cliechti8099bed2009-08-01 23:59:18 +0000515
516 def close(self):
517 """Close port"""
Chris Liechti3ad62fb2015-08-29 21:53:32 +0200518 if self.is_open:
cliechti8099bed2009-08-01 23:59:18 +0000519 if self._socket:
520 try:
521 self._socket.shutdown(socket.SHUT_RDWR)
522 self._socket.close()
523 except:
524 # ignore errors.
525 pass
526 self._socket = None
527 if self._thread:
528 self._thread.join()
Chris Liechti3ad62fb2015-08-29 21:53:32 +0200529 self.is_open = False
cliechti8099bed2009-08-01 23:59:18 +0000530 # in case of quick reconnects, give the server some time
531 time.sleep(0.3)
532
Chris Liechti3ad62fb2015-08-29 21:53:32 +0200533 def from_url(self, url):
cliechti1ef7e3e2009-08-03 02:38:43 +0000534 """extract host and port from an URL string"""
Chris Liechtia4222112015-08-07 01:03:12 +0200535 parts = urlparse.urlsplit(url)
Chris Liechtid14b1ab2015-08-21 00:28:53 +0200536 if parts.scheme != "rfc2217":
537 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 +0000538 try:
Chris Liechtia4222112015-08-07 01:03:12 +0200539 # process options now, directly altering self
Chris Liechtid14b1ab2015-08-21 00:28:53 +0200540 for option, values in urlparse.parse_qs(parts.query, True).items():
541 if option == 'logging':
Chris Liechtia4222112015-08-07 01:03:12 +0200542 logging.basicConfig() # XXX is that good to call it here?
543 self.logger = logging.getLogger('pySerial.rfc2217')
Chris Liechtid14b1ab2015-08-21 00:28:53 +0200544 self.logger.setLevel(LOGGER_LEVELS[values[0]])
Chris Liechtia4222112015-08-07 01:03:12 +0200545 self.logger.debug('enabled logging')
546 elif option == 'ign_set_control':
547 self._ignore_set_control_answer = True
548 elif option == 'poll_modem':
549 self._poll_modem_state = True
550 elif option == 'timeout':
Chris Liechtid14b1ab2015-08-21 00:28:53 +0200551 self._network_timeout = float(values[0])
Chris Liechtia4222112015-08-07 01:03:12 +0200552 else:
553 raise ValueError('unknown option: %r' % (option,))
cliechti81c54762009-08-03 23:53:27 +0000554 # get host and port
Chris Liechtia4222112015-08-07 01:03:12 +0200555 host, port = parts.hostname, parts.port
cliechti8099bed2009-08-01 23:59:18 +0000556 if not 0 <= port < 65536: raise ValueError("port not in range 0...65535")
Chris Liechti68340d72015-08-03 14:15:48 +0200557 except ValueError as e:
Chris Liechtid14b1ab2015-08-21 00:28:53 +0200558 raise SerialException('expected a string in the form "rfc2217://<host>:<port>[?option[&option...]]": %s' % e)
cliechti8099bed2009-08-01 23:59:18 +0000559 return (host, port)
560
561 # - - - - - - - - - - - - - - - - - - - - - - - -
562
Chris Liechtief1fe252015-08-27 23:25:21 +0200563 @property
564 def in_waiting(self):
Chris Liechti3ad62fb2015-08-29 21:53:32 +0200565 """Return the number of bytes currently in the input buffer."""
566 if not self.is_open: raise portNotOpenError
cliechti8099bed2009-08-01 23:59:18 +0000567 return self._read_buffer.qsize()
568
569 def read(self, size=1):
cliechtieada4fd2013-07-31 16:26:07 +0000570 """\
571 Read size bytes from the serial port. If a timeout is set it may
cliechti8099bed2009-08-01 23:59:18 +0000572 return less characters as requested. With no timeout it will block
cliechtieada4fd2013-07-31 16:26:07 +0000573 until the requested number of bytes is read.
574 """
Chris Liechti3ad62fb2015-08-29 21:53:32 +0200575 if not self.is_open: raise portNotOpenError
cliechti8099bed2009-08-01 23:59:18 +0000576 data = bytearray()
577 try:
578 while len(data) < size:
cliechti81c54762009-08-03 23:53:27 +0000579 if self._thread is None:
580 raise SerialException('connection failed (reader thread died)')
Chris Liechti01587b12015-08-05 02:39:32 +0200581 data += self._read_buffer.get(True, self._timeout)
cliechti8099bed2009-08-01 23:59:18 +0000582 except Queue.Empty: # -> timeout
583 pass
584 return bytes(data)
585
586 def write(self, data):
cliechtieada4fd2013-07-31 16:26:07 +0000587 """\
Chris Liechti3ad62fb2015-08-29 21:53:32 +0200588 Output the given byte string over the serial port. Can block if the
cliechti8099bed2009-08-01 23:59:18 +0000589 connection is blocked. May raise SerialException if the connection is
cliechtieada4fd2013-07-31 16:26:07 +0000590 closed.
591 """
Chris Liechti3ad62fb2015-08-29 21:53:32 +0200592 if not self.is_open: raise portNotOpenError
Chris Liechti01587b12015-08-05 02:39:32 +0200593 with self._write_lock:
cliechti81c54762009-08-03 23:53:27 +0000594 try:
cliechti38077122013-10-16 02:57:27 +0000595 self._socket.sendall(to_bytes(data).replace(IAC, IAC_DOUBLED))
Chris Liechti68340d72015-08-03 14:15:48 +0200596 except socket.error as e:
Chris Liechti142ae562015-08-23 01:11:06 +0200597 raise SerialException("connection failed (socket error): %s" % (e,))
cliechti8099bed2009-08-01 23:59:18 +0000598 return len(data)
599
Chris Liechtief1fe252015-08-27 23:25:21 +0200600 def reset_input_buffer(self):
cliechti8099bed2009-08-01 23:59:18 +0000601 """Clear input buffer, discarding all that is in the buffer."""
Chris Liechti3ad62fb2015-08-29 21:53:32 +0200602 if not self.is_open: raise portNotOpenError
cliechti1ef7e3e2009-08-03 02:38:43 +0000603 self.rfc2217SendPurge(PURGE_RECEIVE_BUFFER)
cliechti8099bed2009-08-01 23:59:18 +0000604 # empty read buffer
605 while self._read_buffer.qsize():
606 self._read_buffer.get(False)
607
Chris Liechtief1fe252015-08-27 23:25:21 +0200608 def reset_output_buffer(self):
cliechtieada4fd2013-07-31 16:26:07 +0000609 """\
610 Clear output buffer, aborting the current output and
611 discarding all that is in the buffer.
612 """
Chris Liechti3ad62fb2015-08-29 21:53:32 +0200613 if not self.is_open: raise portNotOpenError
cliechti1ef7e3e2009-08-03 02:38:43 +0000614 self.rfc2217SendPurge(PURGE_TRANSMIT_BUFFER)
cliechti8099bed2009-08-01 23:59:18 +0000615
Chris Liechtief1fe252015-08-27 23:25:21 +0200616 def _update_break_state(self):
cliechtieada4fd2013-07-31 16:26:07 +0000617 """\
618 Set break: Controls TXD. When active, to transmitting is
619 possible.
620 """
Chris Liechti3ad62fb2015-08-29 21:53:32 +0200621 if not self.is_open: raise portNotOpenError
cliechti6a300772009-08-12 02:28:56 +0000622 if self.logger:
Chris Liechtief1fe252015-08-27 23:25:21 +0200623 self.logger.info('set BREAK to %s' % ('active' if self._break_state else 'inactive'))
624 if self._break_state:
cliechti1ef7e3e2009-08-03 02:38:43 +0000625 self.rfc2217SetControl(SET_CONTROL_BREAK_ON)
cliechti8099bed2009-08-01 23:59:18 +0000626 else:
cliechti1ef7e3e2009-08-03 02:38:43 +0000627 self.rfc2217SetControl(SET_CONTROL_BREAK_OFF)
cliechti8099bed2009-08-01 23:59:18 +0000628
Chris Liechtief1fe252015-08-27 23:25:21 +0200629 def _update_rts_state(self):
cliechti044d8662009-08-11 21:40:31 +0000630 """Set terminal status line: Request To Send."""
Chris Liechti3ad62fb2015-08-29 21:53:32 +0200631 if not self.is_open: raise portNotOpenError
cliechti6a300772009-08-12 02:28:56 +0000632 if self.logger:
Chris Liechtief1fe252015-08-27 23:25:21 +0200633 self.logger.info('set RTS to %s' % ('active' if self._rts_state else 'inactive'))
634 if self._rts_state:
cliechti1ef7e3e2009-08-03 02:38:43 +0000635 self.rfc2217SetControl(SET_CONTROL_RTS_ON)
cliechti8099bed2009-08-01 23:59:18 +0000636 else:
cliechti1ef7e3e2009-08-03 02:38:43 +0000637 self.rfc2217SetControl(SET_CONTROL_RTS_OFF)
cliechti8099bed2009-08-01 23:59:18 +0000638
Chris Liechtief1fe252015-08-27 23:25:21 +0200639 def _update_dtr_state(self, level=True):
cliechti044d8662009-08-11 21:40:31 +0000640 """Set terminal status line: Data Terminal Ready."""
Chris Liechti3ad62fb2015-08-29 21:53:32 +0200641 if not self.is_open: raise portNotOpenError
cliechti6a300772009-08-12 02:28:56 +0000642 if self.logger:
Chris Liechtief1fe252015-08-27 23:25:21 +0200643 self.logger.info('set DTR to %s' % ('active' if self._dtr_state else 'inactive'))
644 if self._dtr_state:
cliechti1ef7e3e2009-08-03 02:38:43 +0000645 self.rfc2217SetControl(SET_CONTROL_DTR_ON)
cliechti8099bed2009-08-01 23:59:18 +0000646 else:
cliechti1ef7e3e2009-08-03 02:38:43 +0000647 self.rfc2217SetControl(SET_CONTROL_DTR_OFF)
cliechti8099bed2009-08-01 23:59:18 +0000648
Chris Liechtief1fe252015-08-27 23:25:21 +0200649 @property
650 def cts(self):
cliechti044d8662009-08-11 21:40:31 +0000651 """Read terminal status line: Clear To Send."""
Chris Liechti3ad62fb2015-08-29 21:53:32 +0200652 if not self.is_open: raise portNotOpenError
cliechti7cb78e82009-08-05 15:47:57 +0000653 return bool(self.getModemState() & MODEMSTATE_MASK_CTS)
cliechti8099bed2009-08-01 23:59:18 +0000654
Chris Liechtief1fe252015-08-27 23:25:21 +0200655 @property
656 def dsr(self):
cliechti044d8662009-08-11 21:40:31 +0000657 """Read terminal status line: Data Set Ready."""
Chris Liechti3ad62fb2015-08-29 21:53:32 +0200658 if not self.is_open: raise portNotOpenError
cliechti7cb78e82009-08-05 15:47:57 +0000659 return bool(self.getModemState() & MODEMSTATE_MASK_DSR)
cliechti8099bed2009-08-01 23:59:18 +0000660
Chris Liechtief1fe252015-08-27 23:25:21 +0200661 @property
662 def ri(self):
cliechti044d8662009-08-11 21:40:31 +0000663 """Read terminal status line: Ring Indicator."""
Chris Liechti3ad62fb2015-08-29 21:53:32 +0200664 if not self.is_open: raise portNotOpenError
cliechti7cb78e82009-08-05 15:47:57 +0000665 return bool(self.getModemState() & MODEMSTATE_MASK_RI)
cliechti8099bed2009-08-01 23:59:18 +0000666
Chris Liechtief1fe252015-08-27 23:25:21 +0200667 @property
668 def cd(self):
cliechti044d8662009-08-11 21:40:31 +0000669 """Read terminal status line: Carrier Detect."""
Chris Liechti3ad62fb2015-08-29 21:53:32 +0200670 if not self.is_open: raise portNotOpenError
cliechti7cb78e82009-08-05 15:47:57 +0000671 return bool(self.getModemState() & MODEMSTATE_MASK_CD)
cliechti8099bed2009-08-01 23:59:18 +0000672
673 # - - - platform specific - - -
674 # None so far
675
676 # - - - RFC2217 specific - - -
677
cliechti1ef7e3e2009-08-03 02:38:43 +0000678 def _telnetReadLoop(self):
cliechti7d448562014-08-03 21:57:45 +0000679 """Read loop for the socket."""
cliechti8099bed2009-08-01 23:59:18 +0000680 mode = M_NORMAL
681 suboption = None
cliechti81c54762009-08-03 23:53:27 +0000682 try:
683 while self._socket is not None:
684 try:
685 data = self._socket.recv(1024)
686 except socket.timeout:
687 # just need to get out of recv form time to time to check if
688 # still alive
689 continue
Chris Liechti68340d72015-08-03 14:15:48 +0200690 except socket.error as e:
cliechti81c54762009-08-03 23:53:27 +0000691 # connection fails -> terminate loop
cliechticb20a4f2011-04-25 02:25:54 +0000692 if self.logger:
693 self.logger.debug("socket error in reader thread: %s" % (e,))
cliechti81c54762009-08-03 23:53:27 +0000694 break
cliechticb20a4f2011-04-25 02:25:54 +0000695 if not data: break # lost connection
Chris Liechtif99cd5c2015-08-13 22:54:16 +0200696 for byte in iterbytes(data):
cliechti81c54762009-08-03 23:53:27 +0000697 if mode == M_NORMAL:
698 # interpret as command or as data
699 if byte == IAC:
700 mode = M_IAC_SEEN
cliechti8099bed2009-08-01 23:59:18 +0000701 else:
cliechti81c54762009-08-03 23:53:27 +0000702 # store data in read buffer or sub option buffer
703 # depending on state
704 if suboption is not None:
Chris Liechti01587b12015-08-05 02:39:32 +0200705 suboption += byte
cliechti81c54762009-08-03 23:53:27 +0000706 else:
707 self._read_buffer.put(byte)
708 elif mode == M_IAC_SEEN:
709 if byte == IAC:
710 # interpret as command doubled -> insert character
711 # itself
cliechtif325c032009-12-25 16:09:49 +0000712 if suboption is not None:
Chris Liechti01587b12015-08-05 02:39:32 +0200713 suboption += IAC
cliechtif325c032009-12-25 16:09:49 +0000714 else:
715 self._read_buffer.put(IAC)
cliechti81c54762009-08-03 23:53:27 +0000716 mode = M_NORMAL
717 elif byte == SB:
718 # sub option start
719 suboption = bytearray()
720 mode = M_NORMAL
721 elif byte == SE:
722 # sub option end -> process it now
723 self._telnetProcessSubnegotiation(bytes(suboption))
724 suboption = None
725 mode = M_NORMAL
726 elif byte in (DO, DONT, WILL, WONT):
727 # negotiation
728 telnet_command = byte
729 mode = M_NEGOTIATE
730 else:
731 # other telnet commands
732 self._telnetProcessCommand(byte)
733 mode = M_NORMAL
734 elif mode == M_NEGOTIATE: # DO, DONT, WILL, WONT was received, option now following
735 self._telnetNegotiateOption(telnet_command, byte)
cliechti8099bed2009-08-01 23:59:18 +0000736 mode = M_NORMAL
cliechti81c54762009-08-03 23:53:27 +0000737 finally:
738 self._thread = None
cliechti6a300772009-08-12 02:28:56 +0000739 if self.logger:
740 self.logger.debug("read thread terminated")
cliechti8099bed2009-08-01 23:59:18 +0000741
742 # - incoming telnet commands and options
743
cliechti1ef7e3e2009-08-03 02:38:43 +0000744 def _telnetProcessCommand(self, command):
cliechti044d8662009-08-11 21:40:31 +0000745 """Process commands other than DO, DONT, WILL, WONT."""
cliechti1ef7e3e2009-08-03 02:38:43 +0000746 # Currently none. RFC2217 only uses negotiation and subnegotiation.
cliechti6a300772009-08-12 02:28:56 +0000747 if self.logger:
748 self.logger.warning("ignoring Telnet command: %r" % (command,))
cliechti8099bed2009-08-01 23:59:18 +0000749
cliechti1ef7e3e2009-08-03 02:38:43 +0000750 def _telnetNegotiateOption(self, command, option):
cliechti044d8662009-08-11 21:40:31 +0000751 """Process incoming DO, DONT, WILL, WONT."""
cliechti2b929b72009-08-02 23:49:02 +0000752 # check our registered telnet options and forward command to them
753 # they know themselves if they have to answer or not
cliechtiac205322009-08-02 20:40:21 +0000754 known = False
755 for item in self._telnet_options:
cliechti2b929b72009-08-02 23:49:02 +0000756 # can have more than one match! as some options are duplicated for
757 # 'us' and 'them'
cliechtiac205322009-08-02 20:40:21 +0000758 if item.option == option:
cliechti2b929b72009-08-02 23:49:02 +0000759 item.process_incoming(command)
cliechtiac205322009-08-02 20:40:21 +0000760 known = True
761 if not known:
762 # handle unknown options
763 # only answer to positive requests and deny them
764 if command == WILL or command == DO:
Chris Liechti142ae562015-08-23 01:11:06 +0200765 self.telnetSendOption((DONT if command == WILL else WONT), option)
cliechti6a300772009-08-12 02:28:56 +0000766 if self.logger:
767 self.logger.warning("rejected Telnet option: %r" % (option,))
cliechtiac205322009-08-02 20:40:21 +0000768
cliechti8099bed2009-08-01 23:59:18 +0000769
cliechti1ef7e3e2009-08-03 02:38:43 +0000770 def _telnetProcessSubnegotiation(self, suboption):
cliechti044d8662009-08-11 21:40:31 +0000771 """Process subnegotiation, the data between IAC SB and IAC SE."""
cliechti8099bed2009-08-01 23:59:18 +0000772 if suboption[0:1] == COM_PORT_OPTION:
773 if suboption[1:2] == SERVER_NOTIFY_LINESTATE and len(suboption) >= 3:
cliechti672d0292009-08-03 02:01:57 +0000774 self._linestate = ord(suboption[2:3]) # ensure it is a number
cliechti6a300772009-08-12 02:28:56 +0000775 if self.logger:
776 self.logger.info("NOTIFY_LINESTATE: %s" % self._linestate)
cliechti8099bed2009-08-01 23:59:18 +0000777 elif suboption[1:2] == SERVER_NOTIFY_MODEMSTATE and len(suboption) >= 3:
cliechti672d0292009-08-03 02:01:57 +0000778 self._modemstate = ord(suboption[2:3]) # ensure it is a number
cliechti6a300772009-08-12 02:28:56 +0000779 if self.logger:
780 self.logger.info("NOTIFY_MODEMSTATE: %s" % self._modemstate)
cliechti7cb78e82009-08-05 15:47:57 +0000781 # update time when we think that a poll would make sense
782 self._modemstate_expires = time.time() + 0.3
cliechti672d0292009-08-03 02:01:57 +0000783 elif suboption[1:2] == FLOWCONTROL_SUSPEND:
784 self._remote_suspend_flow = True
785 elif suboption[1:2] == FLOWCONTROL_RESUME:
786 self._remote_suspend_flow = False
cliechti8099bed2009-08-01 23:59:18 +0000787 else:
cliechti2b929b72009-08-02 23:49:02 +0000788 for item in self._rfc2217_options.values():
789 if item.ack_option == suboption[1:2]:
cliechti81c54762009-08-03 23:53:27 +0000790 #~ print "processing COM_PORT_OPTION: %r" % list(suboption[1:])
cliechti2b929b72009-08-02 23:49:02 +0000791 item.checkAnswer(bytes(suboption[2:]))
792 break
793 else:
cliechti6a300772009-08-12 02:28:56 +0000794 if self.logger:
795 self.logger.warning("ignoring COM_PORT_OPTION: %r" % (suboption,))
cliechti8099bed2009-08-01 23:59:18 +0000796 else:
cliechti6a300772009-08-12 02:28:56 +0000797 if self.logger:
798 self.logger.warning("ignoring subnegotiation: %r" % (suboption,))
cliechti8099bed2009-08-01 23:59:18 +0000799
800 # - outgoing telnet commands and options
801
cliechti81c54762009-08-03 23:53:27 +0000802 def _internal_raw_write(self, data):
cliechti044d8662009-08-11 21:40:31 +0000803 """internal socket write with no data escaping. used to send telnet stuff."""
Chris Liechti01587b12015-08-05 02:39:32 +0200804 with self._write_lock:
cliechti81c54762009-08-03 23:53:27 +0000805 self._socket.sendall(data)
cliechti81c54762009-08-03 23:53:27 +0000806
cliechti1ef7e3e2009-08-03 02:38:43 +0000807 def telnetSendOption(self, action, option):
cliechti044d8662009-08-11 21:40:31 +0000808 """Send DO, DONT, WILL, WONT."""
cliechti81c54762009-08-03 23:53:27 +0000809 self._internal_raw_write(to_bytes([IAC, action, option]))
cliechti8099bed2009-08-01 23:59:18 +0000810
Chris Liechtib4cda3a2015-08-08 17:12:08 +0200811 def rfc2217SendSubnegotiation(self, option, value=b''):
cliechti044d8662009-08-11 21:40:31 +0000812 """Subnegotiation of RFC2217 parameters."""
cliechtif325c032009-12-25 16:09:49 +0000813 value = value.replace(IAC, IAC_DOUBLED)
cliechti81c54762009-08-03 23:53:27 +0000814 self._internal_raw_write(to_bytes([IAC, SB, COM_PORT_OPTION, option] + list(value) + [IAC, SE]))
cliechti2b929b72009-08-02 23:49:02 +0000815
cliechti1ef7e3e2009-08-03 02:38:43 +0000816 def rfc2217SendPurge(self, value):
cliechti2b929b72009-08-02 23:49:02 +0000817 item = self._rfc2217_options['purge']
cliechti672d0292009-08-03 02:01:57 +0000818 item.set(value) # transmit desired purge type
cliechtidfe2d272009-08-10 22:19:41 +0000819 item.wait(self._network_timeout) # wait for acknowledge from the server
cliechti2b929b72009-08-02 23:49:02 +0000820
cliechti1ef7e3e2009-08-03 02:38:43 +0000821 def rfc2217SetControl(self, value):
cliechti81c54762009-08-03 23:53:27 +0000822 item = self._rfc2217_options['control']
cliechticb20a4f2011-04-25 02:25:54 +0000823 item.set(value) # transmit desired control type
cliechti81c54762009-08-03 23:53:27 +0000824 if self._ignore_set_control_answer:
825 # answers are ignored when option is set. compatibility mode for
cliechticb20a4f2011-04-25 02:25:54 +0000826 # servers that answer, but not the expected one... (or no answer
cliechti81c54762009-08-03 23:53:27 +0000827 # at all) i.e. sredird
828 time.sleep(0.1) # this helps getting the unit tests passed
829 else:
cliechtidfe2d272009-08-10 22:19:41 +0000830 item.wait(self._network_timeout) # wait for acknowledge from the server
cliechti8099bed2009-08-01 23:59:18 +0000831
cliechti1ef7e3e2009-08-03 02:38:43 +0000832 def rfc2217FlowServerReady(self):
cliechtieada4fd2013-07-31 16:26:07 +0000833 """\
834 check if server is ready to receive data. block for some time when
835 not.
836 """
cliechti672d0292009-08-03 02:01:57 +0000837 #~ if self._remote_suspend_flow:
838 #~ wait---
839
cliechti7cb78e82009-08-05 15:47:57 +0000840 def getModemState(self):
cliechtieada4fd2013-07-31 16:26:07 +0000841 """\
cliechti7d448562014-08-03 21:57:45 +0000842 get last modem state (cached value. If value is "old", request a new
843 one. This cache helps that we don't issue to many requests when e.g. all
844 status lines, one after the other is queried by the user (getCTS, getDSR
cliechtieada4fd2013-07-31 16:26:07 +0000845 etc.)
846 """
cliechti7cb78e82009-08-05 15:47:57 +0000847 # active modem state polling enabled? is the value fresh enough?
848 if self._poll_modem_state and self._modemstate_expires < time.time():
cliechti6a300772009-08-12 02:28:56 +0000849 if self.logger:
850 self.logger.debug('polling modem state')
cliechti7cb78e82009-08-05 15:47:57 +0000851 # when it is older, request an update
852 self.rfc2217SendSubnegotiation(NOTIFY_MODEMSTATE)
cliechtidfe2d272009-08-10 22:19:41 +0000853 timeout_time = time.time() + self._network_timeout
cliechti7cb78e82009-08-05 15:47:57 +0000854 while time.time() < timeout_time:
855 time.sleep(0.05) # prevent 100% CPU load
856 # when expiration time is updated, it means that there is a new
857 # value
858 if self._modemstate_expires > time.time():
859 break
Chris Liechti01587b12015-08-05 02:39:32 +0200860 else:
861 if self.logger:
862 self.logger.warning('poll for modem state failed')
cliechti7cb78e82009-08-05 15:47:57 +0000863 # even when there is a timeout, do not generate an error just
864 # return the last known value. this way we can support buggy
865 # servers that do not respond to polls, but send automatic
866 # updates.
867 if self._modemstate is not None:
cliechti6a300772009-08-12 02:28:56 +0000868 if self.logger:
869 self.logger.debug('using cached modem state')
cliechti7cb78e82009-08-05 15:47:57 +0000870 return self._modemstate
871 else:
872 # never received a notification from the server
cliechti8fb119c2009-08-05 23:39:45 +0000873 raise SerialException("remote sends no NOTIFY_MODEMSTATE")
cliechti8099bed2009-08-01 23:59:18 +0000874
cliechti5cc3eb12009-08-11 23:04:30 +0000875
cliechti595ed5b2009-08-10 01:43:32 +0000876#############################################################################
cliechti5cc3eb12009-08-11 23:04:30 +0000877# The following is code that helps implementing an RFC 2217 server.
cliechti8099bed2009-08-01 23:59:18 +0000878
cliechti8ccc2ff2009-08-05 12:44:46 +0000879class PortManager(object):
cliechtieada4fd2013-07-31 16:26:07 +0000880 """\
881 This class manages the state of Telnet and RFC 2217. It needs a serial
cliechticb20a4f2011-04-25 02:25:54 +0000882 instance and a connection to work with. Connection is expected to implement
cliechtieada4fd2013-07-31 16:26:07 +0000883 a (thread safe) write function, that writes the string to the network.
884 """
cliechti130d1f02009-08-04 02:10:58 +0000885
cliechti6a300772009-08-12 02:28:56 +0000886 def __init__(self, serial_port, connection, logger=None):
cliechti130d1f02009-08-04 02:10:58 +0000887 self.serial = serial_port
888 self.connection = connection
cliechti6a300772009-08-12 02:28:56 +0000889 self.logger = logger
cliechti86b593e2009-08-05 16:28:12 +0000890 self._client_is_rfc2217 = False
cliechti130d1f02009-08-04 02:10:58 +0000891
892 # filter state machine
893 self.mode = M_NORMAL
894 self.suboption = None
895 self.telnet_command = None
896
897 # states for modem/line control events
898 self.modemstate_mask = 255
899 self.last_modemstate = None
900 self.linstate_mask = 0
901
902 # all supported telnet options
903 self._telnet_options = [
904 TelnetOption(self, 'ECHO', ECHO, WILL, WONT, DO, DONT, REQUESTED),
905 TelnetOption(self, 'we-SGA', SGA, WILL, WONT, DO, DONT, REQUESTED),
906 TelnetOption(self, 'they-SGA', SGA, DO, DONT, WILL, WONT, INACTIVE),
907 TelnetOption(self, 'we-BINARY', BINARY, WILL, WONT, DO, DONT, INACTIVE),
908 TelnetOption(self, 'they-BINARY', BINARY, DO, DONT, WILL, WONT, REQUESTED),
cliechti86b593e2009-08-05 16:28:12 +0000909 TelnetOption(self, 'we-RFC2217', COM_PORT_OPTION, WILL, WONT, DO, DONT, REQUESTED, self._client_ok),
910 TelnetOption(self, 'they-RFC2217', COM_PORT_OPTION, DO, DONT, WILL, WONT, INACTIVE, self._client_ok),
cliechti130d1f02009-08-04 02:10:58 +0000911 ]
912
913 # negotiate Telnet/RFC2217 -> send initial requests
cliechti6a300772009-08-12 02:28:56 +0000914 if self.logger:
915 self.logger.debug("requesting initial Telnet/RFC 2217 options")
cliechti130d1f02009-08-04 02:10:58 +0000916 for option in self._telnet_options:
917 if option.state is REQUESTED:
918 self.telnetSendOption(option.send_yes, option.option)
919 # issue 1st modem state notification
cliechti86b593e2009-08-05 16:28:12 +0000920
921 def _client_ok(self):
cliechtieada4fd2013-07-31 16:26:07 +0000922 """\
cliechti7d448562014-08-03 21:57:45 +0000923 callback of telnet option. It gets called when option is activated.
924 This one here is used to detect when the client agrees on RFC 2217. A
cliechti86b593e2009-08-05 16:28:12 +0000925 flag is set so that other functions like check_modem_lines know if the
cliechti7d448562014-08-03 21:57:45 +0000926 client is OK.
cliechtieada4fd2013-07-31 16:26:07 +0000927 """
cliechti86b593e2009-08-05 16:28:12 +0000928 # The callback is used for we and they so if one party agrees, we're
929 # already happy. it seems not all servers do the negotiation correctly
930 # and i guess there are incorrect clients too.. so be happy if client
931 # answers one or the other positively.
932 self._client_is_rfc2217 = True
cliechti6a300772009-08-12 02:28:56 +0000933 if self.logger:
934 self.logger.info("client accepts RFC 2217")
cliechti8fb119c2009-08-05 23:39:45 +0000935 # this is to ensure that the client gets a notification, even if there
936 # was no change
937 self.check_modem_lines(force_notification=True)
cliechti130d1f02009-08-04 02:10:58 +0000938
939 # - outgoing telnet commands and options
940
941 def telnetSendOption(self, action, option):
cliechti044d8662009-08-11 21:40:31 +0000942 """Send DO, DONT, WILL, WONT."""
cliechti130d1f02009-08-04 02:10:58 +0000943 self.connection.write(to_bytes([IAC, action, option]))
944
Chris Liechtib4cda3a2015-08-08 17:12:08 +0200945 def rfc2217SendSubnegotiation(self, option, value=b''):
cliechti044d8662009-08-11 21:40:31 +0000946 """Subnegotiation of RFC 2217 parameters."""
cliechtif325c032009-12-25 16:09:49 +0000947 value = value.replace(IAC, IAC_DOUBLED)
cliechti130d1f02009-08-04 02:10:58 +0000948 self.connection.write(to_bytes([IAC, SB, COM_PORT_OPTION, option] + list(value) + [IAC, SE]))
949
950 # - check modem lines, needs to be called periodically from user to
951 # establish polling
952
cliechti7cb78e82009-08-05 15:47:57 +0000953 def check_modem_lines(self, force_notification=False):
cliechti130d1f02009-08-04 02:10:58 +0000954 modemstate = (
Chris Liechti142ae562015-08-23 01:11:06 +0200955 (self.serial.getCTS() and MODEMSTATE_MASK_CTS) |
956 (self.serial.getDSR() and MODEMSTATE_MASK_DSR) |
957 (self.serial.getRI() and MODEMSTATE_MASK_RI) |
958 (self.serial.getCD() and MODEMSTATE_MASK_CD))
cliechti7cb78e82009-08-05 15:47:57 +0000959 # check what has changed
960 deltas = modemstate ^ (self.last_modemstate or 0) # when last is None -> 0
961 if deltas & MODEMSTATE_MASK_CTS:
962 modemstate |= MODEMSTATE_MASK_CTS_CHANGE
963 if deltas & MODEMSTATE_MASK_DSR:
964 modemstate |= MODEMSTATE_MASK_DSR_CHANGE
965 if deltas & MODEMSTATE_MASK_RI:
966 modemstate |= MODEMSTATE_MASK_RI_CHANGE
967 if deltas & MODEMSTATE_MASK_CD:
968 modemstate |= MODEMSTATE_MASK_CD_CHANGE
969 # if new state is different and the mask allows this change, send
cliechti86b593e2009-08-05 16:28:12 +0000970 # notification. suppress notifications when client is not rfc2217
cliechti7cb78e82009-08-05 15:47:57 +0000971 if modemstate != self.last_modemstate or force_notification:
cliechti8fb119c2009-08-05 23:39:45 +0000972 if (self._client_is_rfc2217 and (modemstate & self.modemstate_mask)) or force_notification:
cliechti7cb78e82009-08-05 15:47:57 +0000973 self.rfc2217SendSubnegotiation(
974 SERVER_NOTIFY_MODEMSTATE,
975 to_bytes([modemstate & self.modemstate_mask])
976 )
cliechti6a300772009-08-12 02:28:56 +0000977 if self.logger:
978 self.logger.info("NOTIFY_MODEMSTATE: %s" % (modemstate,))
cliechti7cb78e82009-08-05 15:47:57 +0000979 # save last state, but forget about deltas.
980 # otherwise it would also notify about changing deltas which is
981 # probably not very useful
982 self.last_modemstate = modemstate & 0xf0
cliechti130d1f02009-08-04 02:10:58 +0000983
cliechti32c10332009-08-05 13:23:43 +0000984 # - outgoing data escaping
985
986 def escape(self, data):
cliechtieada4fd2013-07-31 16:26:07 +0000987 """\
cliechti7d448562014-08-03 21:57:45 +0000988 This generator function is for the user. All outgoing data has to be
cliechticb20a4f2011-04-25 02:25:54 +0000989 properly escaped, so that no IAC character in the data stream messes up
990 the Telnet state machine in the server.
cliechti32c10332009-08-05 13:23:43 +0000991
992 socket.sendall(escape(data))
993 """
994 for byte in data:
995 if byte == IAC:
996 yield IAC
997 yield IAC
998 else:
999 yield byte
1000
cliechti130d1f02009-08-04 02:10:58 +00001001 # - incoming data filter
1002
1003 def filter(self, data):
cliechtieada4fd2013-07-31 16:26:07 +00001004 """\
cliechti7d448562014-08-03 21:57:45 +00001005 Handle a bunch of incoming bytes. This is a generator. It will yield
cliechti044d8662009-08-11 21:40:31 +00001006 all characters not of interest for Telnet/RFC 2217.
cliechti130d1f02009-08-04 02:10:58 +00001007
1008 The idea is that the reader thread pushes data from the socket through
1009 this filter:
1010
1011 for byte in filter(socket.recv(1024)):
1012 # do things like CR/LF conversion/whatever
1013 # and write data to the serial port
1014 serial.write(byte)
1015
1016 (socket error handling code left as exercise for the reader)
1017 """
Chris Liechtif99cd5c2015-08-13 22:54:16 +02001018 for byte in iterbytes(data):
cliechti130d1f02009-08-04 02:10:58 +00001019 if self.mode == M_NORMAL:
1020 # interpret as command or as data
1021 if byte == IAC:
1022 self.mode = M_IAC_SEEN
1023 else:
1024 # store data in sub option buffer or pass it to our
1025 # consumer depending on state
1026 if self.suboption is not None:
Chris Liechti01587b12015-08-05 02:39:32 +02001027 self.suboption += byte
cliechti130d1f02009-08-04 02:10:58 +00001028 else:
1029 yield byte
1030 elif self.mode == M_IAC_SEEN:
1031 if byte == IAC:
1032 # interpret as command doubled -> insert character
1033 # itself
cliechtif325c032009-12-25 16:09:49 +00001034 if self.suboption is not None:
Chris Liechti01587b12015-08-05 02:39:32 +02001035 self.suboption += byte
cliechtif325c032009-12-25 16:09:49 +00001036 else:
1037 yield byte
cliechti130d1f02009-08-04 02:10:58 +00001038 self.mode = M_NORMAL
1039 elif byte == SB:
1040 # sub option start
1041 self.suboption = bytearray()
1042 self.mode = M_NORMAL
1043 elif byte == SE:
1044 # sub option end -> process it now
1045 self._telnetProcessSubnegotiation(bytes(self.suboption))
1046 self.suboption = None
1047 self.mode = M_NORMAL
1048 elif byte in (DO, DONT, WILL, WONT):
1049 # negotiation
1050 self.telnet_command = byte
1051 self.mode = M_NEGOTIATE
1052 else:
1053 # other telnet commands
1054 self._telnetProcessCommand(byte)
1055 self.mode = M_NORMAL
1056 elif self.mode == M_NEGOTIATE: # DO, DONT, WILL, WONT was received, option now following
1057 self._telnetNegotiateOption(self.telnet_command, byte)
1058 self.mode = M_NORMAL
1059
1060 # - incoming telnet commands and options
1061
1062 def _telnetProcessCommand(self, command):
cliechti044d8662009-08-11 21:40:31 +00001063 """Process commands other than DO, DONT, WILL, WONT."""
cliechti130d1f02009-08-04 02:10:58 +00001064 # Currently none. RFC2217 only uses negotiation and subnegotiation.
cliechti6a300772009-08-12 02:28:56 +00001065 if self.logger:
1066 self.logger.warning("ignoring Telnet command: %r" % (command,))
cliechti130d1f02009-08-04 02:10:58 +00001067
1068 def _telnetNegotiateOption(self, command, option):
cliechti044d8662009-08-11 21:40:31 +00001069 """Process incoming DO, DONT, WILL, WONT."""
cliechti130d1f02009-08-04 02:10:58 +00001070 # check our registered telnet options and forward command to them
1071 # they know themselves if they have to answer or not
1072 known = False
1073 for item in self._telnet_options:
1074 # can have more than one match! as some options are duplicated for
1075 # 'us' and 'them'
1076 if item.option == option:
1077 item.process_incoming(command)
1078 known = True
1079 if not known:
1080 # handle unknown options
1081 # only answer to positive requests and deny them
1082 if command == WILL or command == DO:
Chris Liechti142ae562015-08-23 01:11:06 +02001083 self.telnetSendOption((DONT if command == WILL else WONT), option)
cliechti6a300772009-08-12 02:28:56 +00001084 if self.logger:
1085 self.logger.warning("rejected Telnet option: %r" % (option,))
cliechti130d1f02009-08-04 02:10:58 +00001086
1087
1088 def _telnetProcessSubnegotiation(self, suboption):
cliechti044d8662009-08-11 21:40:31 +00001089 """Process subnegotiation, the data between IAC SB and IAC SE."""
cliechti130d1f02009-08-04 02:10:58 +00001090 if suboption[0:1] == COM_PORT_OPTION:
cliechti6a300772009-08-12 02:28:56 +00001091 if self.logger:
1092 self.logger.debug('received COM_PORT_OPTION: %r' % (suboption,))
cliechti130d1f02009-08-04 02:10:58 +00001093 if suboption[1:2] == SET_BAUDRATE:
1094 backup = self.serial.baudrate
1095 try:
Chris Liechti01587b12015-08-05 02:39:32 +02001096 (baudrate,) = struct.unpack(b"!I", suboption[2:6])
cliechtieada4fd2013-07-31 16:26:07 +00001097 if baudrate != 0:
1098 self.serial.baudrate = baudrate
Chris Liechtid2146002015-08-04 16:57:16 +02001099 except ValueError as e:
cliechti6a300772009-08-12 02:28:56 +00001100 if self.logger:
1101 self.logger.error("failed to set baud rate: %s" % (e,))
cliechti130d1f02009-08-04 02:10:58 +00001102 self.serial.baudrate = backup
cliechti5cc3eb12009-08-11 23:04:30 +00001103 else:
cliechti6a300772009-08-12 02:28:56 +00001104 if self.logger:
Chris Liechti142ae562015-08-23 01:11:06 +02001105 self.logger.info("%s baud rate: %s" % ('set' if baudrate else 'get', self.serial.baudrate))
Chris Liechti01587b12015-08-05 02:39:32 +02001106 self.rfc2217SendSubnegotiation(SERVER_SET_BAUDRATE, struct.pack(b"!I", self.serial.baudrate))
cliechti130d1f02009-08-04 02:10:58 +00001107 elif suboption[1:2] == SET_DATASIZE:
1108 backup = self.serial.bytesize
1109 try:
Chris Liechti142ae562015-08-23 01:11:06 +02001110 (datasize,) = struct.unpack(b"!B", suboption[2:3])
cliechtieada4fd2013-07-31 16:26:07 +00001111 if datasize != 0:
1112 self.serial.bytesize = datasize
Chris Liechtid2146002015-08-04 16:57:16 +02001113 except ValueError as e:
cliechti6a300772009-08-12 02:28:56 +00001114 if self.logger:
1115 self.logger.error("failed to set data size: %s" % (e,))
cliechti130d1f02009-08-04 02:10:58 +00001116 self.serial.bytesize = backup
cliechti5cc3eb12009-08-11 23:04:30 +00001117 else:
cliechti6a300772009-08-12 02:28:56 +00001118 if self.logger:
Chris Liechti142ae562015-08-23 01:11:06 +02001119 self.logger.info("%s data size: %s" % ('set' if datasize else 'get', self.serial.bytesize))
Chris Liechti01587b12015-08-05 02:39:32 +02001120 self.rfc2217SendSubnegotiation(SERVER_SET_DATASIZE, struct.pack(b"!B", self.serial.bytesize))
cliechti130d1f02009-08-04 02:10:58 +00001121 elif suboption[1:2] == SET_PARITY:
1122 backup = self.serial.parity
1123 try:
Chris Liechti01587b12015-08-05 02:39:32 +02001124 parity = struct.unpack(b"!B", suboption[2:3])[0]
cliechtieada4fd2013-07-31 16:26:07 +00001125 if parity != 0:
1126 self.serial.parity = RFC2217_REVERSE_PARITY_MAP[parity]
Chris Liechtid2146002015-08-04 16:57:16 +02001127 except ValueError as e:
cliechti6a300772009-08-12 02:28:56 +00001128 if self.logger:
1129 self.logger.error("failed to set parity: %s" % (e,))
cliechti130d1f02009-08-04 02:10:58 +00001130 self.serial.parity = backup
cliechti5cc3eb12009-08-11 23:04:30 +00001131 else:
cliechti6a300772009-08-12 02:28:56 +00001132 if self.logger:
Chris Liechti142ae562015-08-23 01:11:06 +02001133 self.logger.info("%s parity: %s" % ('set' if parity else 'get', self.serial.parity))
cliechti130d1f02009-08-04 02:10:58 +00001134 self.rfc2217SendSubnegotiation(
1135 SERVER_SET_PARITY,
Chris Liechtib4cda3a2015-08-08 17:12:08 +02001136 struct.pack(b"!B", RFC2217_PARITY_MAP[self.serial.parity])
cliechti130d1f02009-08-04 02:10:58 +00001137 )
1138 elif suboption[1:2] == SET_STOPSIZE:
1139 backup = self.serial.stopbits
1140 try:
Chris Liechti01587b12015-08-05 02:39:32 +02001141 stopbits = struct.unpack(b"!B", suboption[2:3])[0]
cliechtieada4fd2013-07-31 16:26:07 +00001142 if stopbits != 0:
1143 self.serial.stopbits = RFC2217_REVERSE_STOPBIT_MAP[stopbits]
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 stop bits: %s" % (e,))
cliechti130d1f02009-08-04 02:10:58 +00001147 self.serial.stopbits = backup
cliechti5cc3eb12009-08-11 23:04:30 +00001148 else:
cliechti6a300772009-08-12 02:28:56 +00001149 if self.logger:
Chris Liechti142ae562015-08-23 01:11:06 +02001150 self.logger.info("%s stop bits: %s" % ('set' if stopbits else 'get', self.serial.stopbits))
cliechti130d1f02009-08-04 02:10:58 +00001151 self.rfc2217SendSubnegotiation(
1152 SERVER_SET_STOPSIZE,
Chris Liechti01587b12015-08-05 02:39:32 +02001153 struct.pack(b"!B", RFC2217_STOPBIT_MAP[self.serial.stopbits])
cliechti130d1f02009-08-04 02:10:58 +00001154 )
1155 elif suboption[1:2] == SET_CONTROL:
1156 if suboption[2:3] == SET_CONTROL_REQ_FLOW_SETTING:
1157 if self.serial.xonxoff:
1158 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_SW_FLOW_CONTROL)
1159 elif self.serial.rtscts:
1160 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_HW_FLOW_CONTROL)
1161 else:
1162 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_NO_FLOW_CONTROL)
1163 elif suboption[2:3] == SET_CONTROL_USE_NO_FLOW_CONTROL:
1164 self.serial.xonxoff = False
1165 self.serial.rtscts = False
cliechti6a300772009-08-12 02:28:56 +00001166 if self.logger:
1167 self.logger.info("changed flow control to None")
cliechti130d1f02009-08-04 02:10:58 +00001168 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_NO_FLOW_CONTROL)
1169 elif suboption[2:3] == SET_CONTROL_USE_SW_FLOW_CONTROL:
1170 self.serial.xonxoff = True
cliechti6a300772009-08-12 02:28:56 +00001171 if self.logger:
1172 self.logger.info("changed flow control to XON/XOFF")
cliechti130d1f02009-08-04 02:10:58 +00001173 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_SW_FLOW_CONTROL)
1174 elif suboption[2:3] == SET_CONTROL_USE_HW_FLOW_CONTROL:
1175 self.serial.rtscts = True
cliechti6a300772009-08-12 02:28:56 +00001176 if self.logger:
1177 self.logger.info("changed flow control to RTS/CTS")
cliechti130d1f02009-08-04 02:10:58 +00001178 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_HW_FLOW_CONTROL)
1179 elif suboption[2:3] == SET_CONTROL_REQ_BREAK_STATE:
cliechti6a300772009-08-12 02:28:56 +00001180 if self.logger:
1181 self.logger.warning("requested break state - not implemented")
cliechti130d1f02009-08-04 02:10:58 +00001182 pass # XXX needs cached value
1183 elif suboption[2:3] == SET_CONTROL_BREAK_ON:
1184 self.serial.setBreak(True)
cliechti6a300772009-08-12 02:28:56 +00001185 if self.logger:
1186 self.logger.info("changed BREAK to active")
cliechti130d1f02009-08-04 02:10:58 +00001187 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_BREAK_ON)
1188 elif suboption[2:3] == SET_CONTROL_BREAK_OFF:
1189 self.serial.setBreak(False)
cliechti6a300772009-08-12 02:28:56 +00001190 if self.logger:
1191 self.logger.info("changed BREAK to inactive")
cliechti130d1f02009-08-04 02:10:58 +00001192 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_BREAK_OFF)
1193 elif suboption[2:3] == SET_CONTROL_REQ_DTR:
cliechti6a300772009-08-12 02:28:56 +00001194 if self.logger:
1195 self.logger.warning("requested DTR state - not implemented")
cliechti130d1f02009-08-04 02:10:58 +00001196 pass # XXX needs cached value
1197 elif suboption[2:3] == SET_CONTROL_DTR_ON:
1198 self.serial.setDTR(True)
cliechti6a300772009-08-12 02:28:56 +00001199 if self.logger:
1200 self.logger.info("changed DTR to active")
cliechti130d1f02009-08-04 02:10:58 +00001201 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_DTR_ON)
1202 elif suboption[2:3] == SET_CONTROL_DTR_OFF:
1203 self.serial.setDTR(False)
cliechti6a300772009-08-12 02:28:56 +00001204 if self.logger:
1205 self.logger.info("changed DTR to inactive")
cliechti130d1f02009-08-04 02:10:58 +00001206 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_DTR_OFF)
1207 elif suboption[2:3] == SET_CONTROL_REQ_RTS:
cliechti6a300772009-08-12 02:28:56 +00001208 if self.logger:
1209 self.logger.warning("requested RTS state - not implemented")
cliechti130d1f02009-08-04 02:10:58 +00001210 pass # XXX needs cached value
1211 #~ self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_RTS_ON)
1212 elif suboption[2:3] == SET_CONTROL_RTS_ON:
1213 self.serial.setRTS(True)
cliechti6a300772009-08-12 02:28:56 +00001214 if self.logger:
1215 self.logger.info("changed RTS to active")
cliechti130d1f02009-08-04 02:10:58 +00001216 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_RTS_ON)
1217 elif suboption[2:3] == SET_CONTROL_RTS_OFF:
1218 self.serial.setRTS(False)
cliechti6a300772009-08-12 02:28:56 +00001219 if self.logger:
1220 self.logger.info("changed RTS to inactive")
cliechti130d1f02009-08-04 02:10:58 +00001221 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_RTS_OFF)
1222 #~ elif suboption[2:3] == SET_CONTROL_REQ_FLOW_SETTING_IN:
1223 #~ elif suboption[2:3] == SET_CONTROL_USE_NO_FLOW_CONTROL_IN:
1224 #~ elif suboption[2:3] == SET_CONTROL_USE_SW_FLOW_CONTOL_IN:
1225 #~ elif suboption[2:3] == SET_CONTROL_USE_HW_FLOW_CONTOL_IN:
1226 #~ elif suboption[2:3] == SET_CONTROL_USE_DCD_FLOW_CONTROL:
1227 #~ elif suboption[2:3] == SET_CONTROL_USE_DTR_FLOW_CONTROL:
1228 #~ elif suboption[2:3] == SET_CONTROL_USE_DSR_FLOW_CONTROL:
1229 elif suboption[1:2] == NOTIFY_LINESTATE:
cliechti7cb78e82009-08-05 15:47:57 +00001230 # client polls for current state
1231 self.rfc2217SendSubnegotiation(
Chris Liechti142ae562015-08-23 01:11:06 +02001232 SERVER_NOTIFY_LINESTATE,
1233 to_bytes([0])) # sorry, nothing like that implemented
cliechti130d1f02009-08-04 02:10:58 +00001234 elif suboption[1:2] == NOTIFY_MODEMSTATE:
cliechti6a300772009-08-12 02:28:56 +00001235 if self.logger:
1236 self.logger.info("request for modem state")
cliechti7cb78e82009-08-05 15:47:57 +00001237 # client polls for current state
1238 self.check_modem_lines(force_notification=True)
cliechti130d1f02009-08-04 02:10:58 +00001239 elif suboption[1:2] == FLOWCONTROL_SUSPEND:
cliechti6a300772009-08-12 02:28:56 +00001240 if self.logger:
1241 self.logger.info("suspend")
cliechti130d1f02009-08-04 02:10:58 +00001242 self._remote_suspend_flow = True
1243 elif suboption[1:2] == FLOWCONTROL_RESUME:
cliechti6a300772009-08-12 02:28:56 +00001244 if self.logger:
1245 self.logger.info("resume")
cliechti130d1f02009-08-04 02:10:58 +00001246 self._remote_suspend_flow = False
1247 elif suboption[1:2] == SET_LINESTATE_MASK:
1248 self.linstate_mask = ord(suboption[2:3]) # ensure it is a number
cliechti6a300772009-08-12 02:28:56 +00001249 if self.logger:
cliechtif325c032009-12-25 16:09:49 +00001250 self.logger.info("line state mask: 0x%02x" % (self.linstate_mask,))
cliechti130d1f02009-08-04 02:10:58 +00001251 elif suboption[1:2] == SET_MODEMSTATE_MASK:
1252 self.modemstate_mask = ord(suboption[2:3]) # ensure it is a number
cliechti6a300772009-08-12 02:28:56 +00001253 if self.logger:
cliechtif325c032009-12-25 16:09:49 +00001254 self.logger.info("modem state mask: 0x%02x" % (self.modemstate_mask,))
cliechti130d1f02009-08-04 02:10:58 +00001255 elif suboption[1:2] == PURGE_DATA:
1256 if suboption[2:3] == PURGE_RECEIVE_BUFFER:
Chris Liechti3ad62fb2015-08-29 21:53:32 +02001257 self.serial.reset_input_buffer()
cliechti6a300772009-08-12 02:28:56 +00001258 if self.logger:
1259 self.logger.info("purge in")
cliechti130d1f02009-08-04 02:10:58 +00001260 self.rfc2217SendSubnegotiation(SERVER_PURGE_DATA, PURGE_RECEIVE_BUFFER)
1261 elif suboption[2:3] == PURGE_TRANSMIT_BUFFER:
Chris Liechti3ad62fb2015-08-29 21:53:32 +02001262 self.serial.reset_output_buffer()
cliechti6a300772009-08-12 02:28:56 +00001263 if self.logger:
1264 self.logger.info("purge out")
cliechti130d1f02009-08-04 02:10:58 +00001265 self.rfc2217SendSubnegotiation(SERVER_PURGE_DATA, PURGE_TRANSMIT_BUFFER)
1266 elif suboption[2:3] == PURGE_BOTH_BUFFERS:
Chris Liechti3ad62fb2015-08-29 21:53:32 +02001267 self.serial.reset_input_buffer()
1268 self.serial.reset_output_buffer()
cliechti6a300772009-08-12 02:28:56 +00001269 if self.logger:
1270 self.logger.info("purge both")
cliechti130d1f02009-08-04 02:10:58 +00001271 self.rfc2217SendSubnegotiation(SERVER_PURGE_DATA, PURGE_BOTH_BUFFERS)
1272 else:
cliechti6a300772009-08-12 02:28:56 +00001273 if self.logger:
1274 self.logger.error("undefined PURGE_DATA: %r" % list(suboption[2:]))
cliechti130d1f02009-08-04 02:10:58 +00001275 else:
cliechti6a300772009-08-12 02:28:56 +00001276 if self.logger:
1277 self.logger.error("undefined COM_PORT_OPTION: %r" % list(suboption[1:]))
cliechti130d1f02009-08-04 02:10:58 +00001278 else:
cliechti6a300772009-08-12 02:28:56 +00001279 if self.logger:
1280 self.logger.warning("unknown subnegotiation: %r" % (suboption,))
cliechti130d1f02009-08-04 02:10:58 +00001281
1282
1283# simple client test
cliechti8099bed2009-08-01 23:59:18 +00001284if __name__ == '__main__':
1285 import sys
1286 s = Serial('rfc2217://localhost:7000', 115200)
1287 sys.stdout.write('%s\n' % s)
1288
cliechti2b929b72009-08-02 23:49:02 +00001289 #~ s.baudrate = 1898
1290
cliechti8099bed2009-08-01 23:59:18 +00001291 sys.stdout.write("write...\n")
Chris Liechtib4cda3a2015-08-08 17:12:08 +02001292 s.write(b"hello\n")
cliechti8099bed2009-08-01 23:59:18 +00001293 s.flush()
cliechti8099bed2009-08-01 23:59:18 +00001294 sys.stdout.write("read: %s\n" % s.read(5))
1295
1296 #~ s.baudrate = 19200
1297 #~ s.databits = 7
1298 s.close()