blob: e51ffc767a69382ebbdd513df5988d906ed73373 [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
71
72from serial.serialutil import *
cliechti8099bed2009-08-01 23:59:18 +000073
Chris Liechtid2146002015-08-04 16:57:16 +020074try:
75 import Queue
76except ImportError:
77 import queue as Queue
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
cliechti86844e82009-08-12 00:05:33 +000084# map log level names to constants. used in fromURL()
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
94IAC = to_bytes([255]) # Interpret As Command
95DONT = to_bytes([254])
96DO = to_bytes([253])
97WONT = to_bytes([252])
98WILL = to_bytes([251])
99IAC_DOUBLED = to_bytes([IAC, IAC])
100
101SE = to_bytes([240]) # Subnegotiation End
102NOP = to_bytes([241]) # No Operation
103DM = to_bytes([242]) # Data Mark
104BRK = to_bytes([243]) # Break
105IP = to_bytes([244]) # Interrupt process
106AO = to_bytes([245]) # Abort output
107AYT = to_bytes([246]) # Are You There
108EC = to_bytes([247]) # Erase Character
109EL = to_bytes([248]) # Erase Line
110GA = to_bytes([249]) # Go Ahead
111SB = to_bytes([250]) # Subnegotiation Begin
112
113# selected telnet options
114BINARY = to_bytes([0]) # 8-bit data path
115ECHO = to_bytes([1]) # echo
116SGA = to_bytes([3]) # suppress go ahead
117
118# RFC2217
119COM_PORT_OPTION = to_bytes([44])
120
121# Client to Access Server
cliechti8099bed2009-08-01 23:59:18 +0000122SET_BAUDRATE = to_bytes([1])
123SET_DATASIZE = to_bytes([2])
124SET_PARITY = to_bytes([3])
125SET_STOPSIZE = to_bytes([4])
126SET_CONTROL = to_bytes([5])
127NOTIFY_LINESTATE = to_bytes([6])
128NOTIFY_MODEMSTATE = to_bytes([7])
129FLOWCONTROL_SUSPEND = to_bytes([8])
130FLOWCONTROL_RESUME = to_bytes([9])
131SET_LINESTATE_MASK = to_bytes([10])
132SET_MODEMSTATE_MASK = to_bytes([11])
133PURGE_DATA = to_bytes([12])
134
135SERVER_SET_BAUDRATE = to_bytes([101])
136SERVER_SET_DATASIZE = to_bytes([102])
137SERVER_SET_PARITY = to_bytes([103])
138SERVER_SET_STOPSIZE = to_bytes([104])
139SERVER_SET_CONTROL = to_bytes([105])
140SERVER_NOTIFY_LINESTATE = to_bytes([106])
141SERVER_NOTIFY_MODEMSTATE = to_bytes([107])
142SERVER_FLOWCONTROL_SUSPEND = to_bytes([108])
143SERVER_FLOWCONTROL_RESUME = to_bytes([109])
144SERVER_SET_LINESTATE_MASK = to_bytes([110])
145SERVER_SET_MODEMSTATE_MASK = to_bytes([111])
146SERVER_PURGE_DATA = to_bytes([112])
147
148RFC2217_ANSWER_MAP = {
149 SET_BAUDRATE: SERVER_SET_BAUDRATE,
150 SET_DATASIZE: SERVER_SET_DATASIZE,
151 SET_PARITY: SERVER_SET_PARITY,
152 SET_STOPSIZE: SERVER_SET_STOPSIZE,
153 SET_CONTROL: SERVER_SET_CONTROL,
154 NOTIFY_LINESTATE: SERVER_NOTIFY_LINESTATE,
155 NOTIFY_MODEMSTATE: SERVER_NOTIFY_MODEMSTATE,
156 FLOWCONTROL_SUSPEND: SERVER_FLOWCONTROL_SUSPEND,
157 FLOWCONTROL_RESUME: SERVER_FLOWCONTROL_RESUME,
158 SET_LINESTATE_MASK: SERVER_SET_LINESTATE_MASK,
159 SET_MODEMSTATE_MASK: SERVER_SET_MODEMSTATE_MASK,
160 PURGE_DATA: SERVER_PURGE_DATA,
161}
162
163SET_CONTROL_REQ_FLOW_SETTING = to_bytes([0]) # Request Com Port Flow Control Setting (outbound/both)
164SET_CONTROL_USE_NO_FLOW_CONTROL = to_bytes([1]) # Use No Flow Control (outbound/both)
165SET_CONTROL_USE_SW_FLOW_CONTROL = to_bytes([2]) # Use XON/XOFF Flow Control (outbound/both)
166SET_CONTROL_USE_HW_FLOW_CONTROL = to_bytes([3]) # Use HARDWARE Flow Control (outbound/both)
167SET_CONTROL_REQ_BREAK_STATE = to_bytes([4]) # Request BREAK State
168SET_CONTROL_BREAK_ON = to_bytes([5]) # Set BREAK State ON
169SET_CONTROL_BREAK_OFF = to_bytes([6]) # Set BREAK State OFF
170SET_CONTROL_REQ_DTR = to_bytes([7]) # Request DTR Signal State
171SET_CONTROL_DTR_ON = to_bytes([8]) # Set DTR Signal State ON
172SET_CONTROL_DTR_OFF = to_bytes([9]) # Set DTR Signal State OFF
173SET_CONTROL_REQ_RTS = to_bytes([10]) # Request RTS Signal State
174SET_CONTROL_RTS_ON = to_bytes([11]) # Set RTS Signal State ON
175SET_CONTROL_RTS_OFF = to_bytes([12]) # Set RTS Signal State OFF
176SET_CONTROL_REQ_FLOW_SETTING_IN = to_bytes([13]) # Request Com Port Flow Control Setting (inbound)
177SET_CONTROL_USE_NO_FLOW_CONTROL_IN = to_bytes([14]) # Use No Flow Control (inbound)
178SET_CONTROL_USE_SW_FLOW_CONTOL_IN = to_bytes([15]) # Use XON/XOFF Flow Control (inbound)
179SET_CONTROL_USE_HW_FLOW_CONTOL_IN = to_bytes([16]) # Use HARDWARE Flow Control (inbound)
180SET_CONTROL_USE_DCD_FLOW_CONTROL = to_bytes([17]) # Use DCD Flow Control (outbound/both)
181SET_CONTROL_USE_DTR_FLOW_CONTROL = to_bytes([18]) # Use DTR Flow Control (inbound)
182SET_CONTROL_USE_DSR_FLOW_CONTROL = to_bytes([19]) # Use DSR Flow Control (outbound/both)
183
cliechti044d8662009-08-11 21:40:31 +0000184LINESTATE_MASK_TIMEOUT = 128 # Time-out Error
185LINESTATE_MASK_SHIFTREG_EMPTY = 64 # Transfer Shift Register Empty
186LINESTATE_MASK_TRANSREG_EMPTY = 32 # Transfer Holding Register Empty
187LINESTATE_MASK_BREAK_DETECT = 16 # Break-detect Error
188LINESTATE_MASK_FRAMING_ERROR = 8 # Framing Error
189LINESTATE_MASK_PARTIY_ERROR = 4 # Parity Error
190LINESTATE_MASK_OVERRUN_ERROR = 2 # Overrun Error
191LINESTATE_MASK_DATA_READY = 1 # Data Ready
cliechti8099bed2009-08-01 23:59:18 +0000192
cliechti044d8662009-08-11 21:40:31 +0000193MODEMSTATE_MASK_CD = 128 # Receive Line Signal Detect (also known as Carrier Detect)
194MODEMSTATE_MASK_RI = 64 # Ring Indicator
195MODEMSTATE_MASK_DSR = 32 # Data-Set-Ready Signal State
196MODEMSTATE_MASK_CTS = 16 # Clear-To-Send Signal State
197MODEMSTATE_MASK_CD_CHANGE = 8 # Delta Receive Line Signal Detect
198MODEMSTATE_MASK_RI_CHANGE = 4 # Trailing-edge Ring Detector
199MODEMSTATE_MASK_DSR_CHANGE = 2 # Delta Data-Set-Ready
200MODEMSTATE_MASK_CTS_CHANGE = 1 # Delta Clear-To-Send
cliechti8099bed2009-08-01 23:59:18 +0000201
202PURGE_RECEIVE_BUFFER = to_bytes([1]) # Purge access server receive data buffer
203PURGE_TRANSMIT_BUFFER = to_bytes([2]) # Purge access server transmit data buffer
204PURGE_BOTH_BUFFERS = to_bytes([3]) # Purge both the access server receive data buffer and the access server transmit data buffer
205
206
207RFC2217_PARITY_MAP = {
208 PARITY_NONE: 1,
209 PARITY_ODD: 2,
210 PARITY_EVEN: 3,
211 PARITY_MARK: 4,
212 PARITY_SPACE: 5,
213}
cliechti130d1f02009-08-04 02:10:58 +0000214RFC2217_REVERSE_PARITY_MAP = dict((v,k) for k,v in RFC2217_PARITY_MAP.items())
cliechti8099bed2009-08-01 23:59:18 +0000215
216RFC2217_STOPBIT_MAP = {
217 STOPBITS_ONE: 1,
218 STOPBITS_ONE_POINT_FIVE: 3,
219 STOPBITS_TWO: 2,
220}
cliechti130d1f02009-08-04 02:10:58 +0000221RFC2217_REVERSE_STOPBIT_MAP = dict((v,k) for k,v in RFC2217_STOPBIT_MAP.items())
cliechti8099bed2009-08-01 23:59:18 +0000222
cliechti130d1f02009-08-04 02:10:58 +0000223# Telnet filter states
224M_NORMAL = 0
225M_IAC_SEEN = 1
226M_NEGOTIATE = 2
cliechti8099bed2009-08-01 23:59:18 +0000227
cliechti130d1f02009-08-04 02:10:58 +0000228# TelnetOption and TelnetSubnegotiation states
cliechtiac205322009-08-02 20:40:21 +0000229REQUESTED = 'REQUESTED'
230ACTIVE = 'ACTIVE'
231INACTIVE = 'INACTIVE'
232REALLY_INACTIVE = 'REALLY_INACTIVE'
233
234class TelnetOption(object):
cliechti1ef7e3e2009-08-03 02:38:43 +0000235 """Manage a single telnet option, keeps track of DO/DONT WILL/WONT."""
236
cliechti86b593e2009-08-05 16:28:12 +0000237 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 +0000238 """\
239 Initialize option.
cliechti1ef7e3e2009-08-03 02:38:43 +0000240 :param connection: connection used to transmit answers
241 :param name: a readable name for debug outputs
242 :param send_yes: what to send when option is to be enabled.
243 :param send_no: what to send when option is to be disabled.
244 :param ack_yes: what to expect when remote agrees on option.
245 :param ack_no: what to expect when remote disagrees on option.
246 :param initial_state: options initialized with REQUESTED are tried to
247 be enabled on startup. use INACTIVE for all others.
248 """
cliechti2b929b72009-08-02 23:49:02 +0000249 self.connection = connection
cliechtiac205322009-08-02 20:40:21 +0000250 self.name = name
251 self.option = option
252 self.send_yes = send_yes
253 self.send_no = send_no
254 self.ack_yes = ack_yes
255 self.ack_no = ack_no
256 self.state = initial_state
257 self.active = False
cliechti86b593e2009-08-05 16:28:12 +0000258 self.activation_callback = activation_callback
cliechtiac205322009-08-02 20:40:21 +0000259
260 def __repr__(self):
cliechti1ef7e3e2009-08-03 02:38:43 +0000261 """String for debug outputs"""
cliechtiac205322009-08-02 20:40:21 +0000262 return "%s:%s(%s)" % (self.name, self.active, self.state)
263
cliechti2b929b72009-08-02 23:49:02 +0000264 def process_incoming(self, command):
cliechti7d448562014-08-03 21:57:45 +0000265 """\
266 A DO/DONT/WILL/WONT was received for this option, update state and
267 answer when needed.
268 """
cliechtiac205322009-08-02 20:40:21 +0000269 if command == self.ack_yes:
270 if self.state is REQUESTED:
271 self.state = ACTIVE
272 self.active = True
cliechti86b593e2009-08-05 16:28:12 +0000273 if self.activation_callback is not None:
274 self.activation_callback()
cliechtiac205322009-08-02 20:40:21 +0000275 elif self.state is ACTIVE:
276 pass
277 elif self.state is INACTIVE:
278 self.state = ACTIVE
cliechti1ef7e3e2009-08-03 02:38:43 +0000279 self.connection.telnetSendOption(self.send_yes, self.option)
cliechtiac205322009-08-02 20:40:21 +0000280 self.active = True
cliechti86b593e2009-08-05 16:28:12 +0000281 if self.activation_callback is not None:
282 self.activation_callback()
cliechtiac205322009-08-02 20:40:21 +0000283 elif self.state is REALLY_INACTIVE:
cliechti1ef7e3e2009-08-03 02:38:43 +0000284 self.connection.telnetSendOption(self.send_no, self.option)
cliechtiac205322009-08-02 20:40:21 +0000285 else:
286 raise ValueError('option in illegal state %r' % self)
287 elif command == self.ack_no:
288 if self.state is REQUESTED:
289 self.state = INACTIVE
290 self.active = False
291 elif self.state is ACTIVE:
292 self.state = INACTIVE
cliechti1ef7e3e2009-08-03 02:38:43 +0000293 self.connection.telnetSendOption(self.send_no, self.option)
cliechtiac205322009-08-02 20:40:21 +0000294 self.active = False
295 elif self.state is INACTIVE:
296 pass
297 elif self.state is REALLY_INACTIVE:
298 pass
299 else:
300 raise ValueError('option in illegal state %r' % self)
301
302
cliechti2b929b72009-08-02 23:49:02 +0000303class TelnetSubnegotiation(object):
cliechtieada4fd2013-07-31 16:26:07 +0000304 """\
305 A object to handle subnegotiation of options. In this case actually
306 sub-sub options for RFC 2217. It is used to track com port options.
307 """
cliechti2b929b72009-08-02 23:49:02 +0000308
309 def __init__(self, connection, name, option, ack_option=None):
310 if ack_option is None: ack_option = option
311 self.connection = connection
312 self.name = name
313 self.option = option
314 self.value = None
315 self.ack_option = ack_option
316 self.state = INACTIVE
317
318 def __repr__(self):
cliechti044d8662009-08-11 21:40:31 +0000319 """String for debug outputs."""
cliechti2b929b72009-08-02 23:49:02 +0000320 return "%s:%s" % (self.name, self.state)
321
322 def set(self, value):
cliechtieada4fd2013-07-31 16:26:07 +0000323 """\
cliechti7d448562014-08-03 21:57:45 +0000324 Request a change of the value. a request is sent to the server. if
cliechti2b929b72009-08-02 23:49:02 +0000325 the client needs to know if the change is performed he has to check the
cliechtieada4fd2013-07-31 16:26:07 +0000326 state of this object.
327 """
cliechti2b929b72009-08-02 23:49:02 +0000328 self.value = value
329 self.state = REQUESTED
cliechti1ef7e3e2009-08-03 02:38:43 +0000330 self.connection.rfc2217SendSubnegotiation(self.option, self.value)
cliechti6a300772009-08-12 02:28:56 +0000331 if self.connection.logger:
332 self.connection.logger.debug("SB Requesting %s -> %r" % (self.name, self.value))
cliechti2b929b72009-08-02 23:49:02 +0000333
334 def isReady(self):
cliechtieada4fd2013-07-31 16:26:07 +0000335 """\
cliechti7d448562014-08-03 21:57:45 +0000336 Check if answer from server has been received. when server rejects
cliechtieada4fd2013-07-31 16:26:07 +0000337 the change, raise a ValueError.
338 """
cliechti2b929b72009-08-02 23:49:02 +0000339 if self.state == REALLY_INACTIVE:
340 raise ValueError("remote rejected value for option %r" % (self.name))
341 return self.state == ACTIVE
cliechti1ef7e3e2009-08-03 02:38:43 +0000342 # add property to have a similar interface as TelnetOption
cliechti2b929b72009-08-02 23:49:02 +0000343 active = property(isReady)
344
cliechti044d8662009-08-11 21:40:31 +0000345 def wait(self, timeout=3):
cliechtieada4fd2013-07-31 16:26:07 +0000346 """\
cliechti7d448562014-08-03 21:57:45 +0000347 Wait until the subnegotiation has been acknowledged or timeout. It
cliechti1ef7e3e2009-08-03 02:38:43 +0000348 can also throw a value error when the answer from the server does not
cliechtieada4fd2013-07-31 16:26:07 +0000349 match the value sent.
350 """
cliechti044d8662009-08-11 21:40:31 +0000351 timeout_time = time.time() + timeout
cliechti2b929b72009-08-02 23:49:02 +0000352 while time.time() < timeout_time:
353 time.sleep(0.05) # prevent 100% CPU load
354 if self.isReady():
355 break
356 else:
357 raise SerialException("timeout while waiting for option %r" % (self.name))
358
359 def checkAnswer(self, suboption):
cliechtieada4fd2013-07-31 16:26:07 +0000360 """\
cliechti7d448562014-08-03 21:57:45 +0000361 Check an incoming subnegotiation block. The parameter already has
cliechtieada4fd2013-07-31 16:26:07 +0000362 cut off the header like sub option number and com port option value.
363 """
cliechti2b929b72009-08-02 23:49:02 +0000364 if self.value == suboption[:len(self.value)]:
365 self.state = ACTIVE
366 else:
367 # error propagation done in isReady
368 self.state = REALLY_INACTIVE
cliechti6a300772009-08-12 02:28:56 +0000369 if self.connection.logger:
370 self.connection.logger.debug("SB Answer %s -> %r -> %s" % (self.name, suboption, self.state))
cliechti2b929b72009-08-02 23:49:02 +0000371
372
Chris Liechtief6b7b42015-08-06 22:19:26 +0200373class Serial(SerialBase):
cliechti044d8662009-08-11 21:40:31 +0000374 """Serial port implementation for RFC 2217 remote serial ports."""
cliechti8099bed2009-08-01 23:59:18 +0000375
376 BAUDRATES = (50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800,
377 9600, 19200, 38400, 57600, 115200)
378
379 def open(self):
cliechtieada4fd2013-07-31 16:26:07 +0000380 """\
381 Open port with current settings. This may throw a SerialException
382 if the port cannot be opened.
383 """
cliechti6a300772009-08-12 02:28:56 +0000384 self.logger = None
cliechti81c54762009-08-03 23:53:27 +0000385 self._ignore_set_control_answer = False
cliechti7cb78e82009-08-05 15:47:57 +0000386 self._poll_modem_state = False
cliechtidfe2d272009-08-10 22:19:41 +0000387 self._network_timeout = 3
cliechti8099bed2009-08-01 23:59:18 +0000388 if self._port is None:
389 raise SerialException("Port must be configured before it can be used.")
cliechti8f69e702011-03-19 00:22:32 +0000390 if self._isOpen:
391 raise SerialException("Port is already open.")
cliechti8099bed2009-08-01 23:59:18 +0000392 try:
Chris Liechtic4bca9e2015-08-07 14:40:41 +0200393 self._socket = socket.create_connection(self.fromURL(self.portstr))
cliechti6a300772009-08-12 02:28:56 +0000394 self._socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
Chris Liechti68340d72015-08-03 14:15:48 +0200395 except Exception as msg:
cliechti8099bed2009-08-01 23:59:18 +0000396 self._socket = None
397 raise SerialException("Could not open port %s: %s" % (self.portstr, msg))
398
cliechti1ef7e3e2009-08-03 02:38:43 +0000399 self._socket.settimeout(5) # XXX good value?
cliechti8099bed2009-08-01 23:59:18 +0000400
cliechti1ef7e3e2009-08-03 02:38:43 +0000401 # use a thread save queue as buffer. it also simplifies implementing
402 # the read timeout
cliechti8099bed2009-08-01 23:59:18 +0000403 self._read_buffer = Queue.Queue()
cliechti81c54762009-08-03 23:53:27 +0000404 # to ensure that user writes does not interfere with internal
405 # telnet/rfc2217 options establish a lock
406 self._write_lock = threading.Lock()
cliechtiac205322009-08-02 20:40:21 +0000407 # name the following separately so that, below, a check can be easily done
408 mandadory_options = [
cliechti2b929b72009-08-02 23:49:02 +0000409 TelnetOption(self, 'we-BINARY', BINARY, WILL, WONT, DO, DONT, INACTIVE),
cliechti2b929b72009-08-02 23:49:02 +0000410 TelnetOption(self, 'we-RFC2217', COM_PORT_OPTION, WILL, WONT, DO, DONT, REQUESTED),
cliechtiac205322009-08-02 20:40:21 +0000411 ]
412 # all supported telnet options
413 self._telnet_options = [
cliechtia29275e2009-08-03 00:08:04 +0000414 TelnetOption(self, 'ECHO', ECHO, DO, DONT, WILL, WONT, REQUESTED),
cliechti2b929b72009-08-02 23:49:02 +0000415 TelnetOption(self, 'we-SGA', SGA, WILL, WONT, DO, DONT, REQUESTED),
416 TelnetOption(self, 'they-SGA', SGA, DO, DONT, WILL, WONT, REQUESTED),
cliechti81c54762009-08-03 23:53:27 +0000417 TelnetOption(self, 'they-BINARY', BINARY, DO, DONT, WILL, WONT, INACTIVE),
418 TelnetOption(self, 'they-RFC2217', COM_PORT_OPTION, DO, DONT, WILL, WONT, REQUESTED),
cliechtiac205322009-08-02 20:40:21 +0000419 ] + mandadory_options
cliechti044d8662009-08-11 21:40:31 +0000420 # RFC 2217 specific states
cliechti2b929b72009-08-02 23:49:02 +0000421 # COM port settings
422 self._rfc2217_port_settings = {
423 'baudrate': TelnetSubnegotiation(self, 'baudrate', SET_BAUDRATE, SERVER_SET_BAUDRATE),
424 'datasize': TelnetSubnegotiation(self, 'datasize', SET_DATASIZE, SERVER_SET_DATASIZE),
425 'parity': TelnetSubnegotiation(self, 'parity', SET_PARITY, SERVER_SET_PARITY),
426 'stopsize': TelnetSubnegotiation(self, 'stopsize', SET_STOPSIZE, SERVER_SET_STOPSIZE),
427 }
cliechticb20a4f2011-04-25 02:25:54 +0000428 # There are more subnegotiation objects, combine all in one dictionary
cliechti2b929b72009-08-02 23:49:02 +0000429 # for easy access
430 self._rfc2217_options = {
431 'purge': TelnetSubnegotiation(self, 'purge', PURGE_DATA, SERVER_PURGE_DATA),
cliechti81c54762009-08-03 23:53:27 +0000432 'control': TelnetSubnegotiation(self, 'control', SET_CONTROL, SERVER_SET_CONTROL),
cliechti2b929b72009-08-02 23:49:02 +0000433 }
434 self._rfc2217_options.update(self._rfc2217_port_settings)
435 # cache for line and modem states that the server sends to us
cliechti8099bed2009-08-01 23:59:18 +0000436 self._linestate = 0
cliechti7cb78e82009-08-05 15:47:57 +0000437 self._modemstate = None
438 self._modemstate_expires = 0
cliechti044d8662009-08-11 21:40:31 +0000439 # RFC 2217 flow control between server and client
cliechti672d0292009-08-03 02:01:57 +0000440 self._remote_suspend_flow = False
cliechti8099bed2009-08-01 23:59:18 +0000441
cliechti1ef7e3e2009-08-03 02:38:43 +0000442 self._thread = threading.Thread(target=self._telnetReadLoop)
cliechti8099bed2009-08-01 23:59:18 +0000443 self._thread.setDaemon(True)
cliechti5cc3eb12009-08-11 23:04:30 +0000444 self._thread.setName('pySerial RFC 2217 reader thread for %s' % (self._port,))
cliechti8099bed2009-08-01 23:59:18 +0000445 self._thread.start()
446
cliechti044d8662009-08-11 21:40:31 +0000447 # negotiate Telnet/RFC 2217 -> send initial requests
cliechtiac205322009-08-02 20:40:21 +0000448 for option in self._telnet_options:
449 if option.state is REQUESTED:
cliechti1ef7e3e2009-08-03 02:38:43 +0000450 self.telnetSendOption(option.send_yes, option.option)
cliechtiac205322009-08-02 20:40:21 +0000451 # now wait until important options are negotiated
cliechtidfe2d272009-08-10 22:19:41 +0000452 timeout_time = time.time() + self._network_timeout
cliechtiac205322009-08-02 20:40:21 +0000453 while time.time() < timeout_time:
cliechtiac205322009-08-02 20:40:21 +0000454 time.sleep(0.05) # prevent 100% CPU load
cliechti7c213a92014-07-31 15:29:34 +0000455 if sum(o.active for o in mandadory_options) == sum(o.state != INACTIVE for o in mandadory_options):
cliechti2b929b72009-08-02 23:49:02 +0000456 break
457 else:
458 raise SerialException("Remote does not seem to support RFC2217 or BINARY mode %r" % mandadory_options)
cliechti6a300772009-08-12 02:28:56 +0000459 if self.logger:
460 self.logger.info("Negotiated options: %s" % self._telnet_options)
cliechti8099bed2009-08-01 23:59:18 +0000461
cliechti044d8662009-08-11 21:40:31 +0000462 # fine, go on, set RFC 2271 specific things
cliechti8099bed2009-08-01 23:59:18 +0000463 self._reconfigurePort()
cliechti2b929b72009-08-02 23:49:02 +0000464 # all things set up get, now a clean start
cliechti8099bed2009-08-01 23:59:18 +0000465 self._isOpen = True
466 if not self._rtscts:
467 self.setRTS(True)
468 self.setDTR(True)
469 self.flushInput()
470 self.flushOutput()
471
472 def _reconfigurePort(self):
473 """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
480 if self._writeTimeout is not None:
481 raise NotImplementedError('writeTimeout 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"""
518 if self._isOpen:
519 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()
529 self._isOpen = False
530 # in case of quick reconnects, give the server some time
531 time.sleep(0.3)
532
533 def makeDeviceName(self, port):
534 raise SerialException("there is no sensible way to turn numbers into URLs")
535
536 def fromURL(self, url):
cliechti1ef7e3e2009-08-03 02:38:43 +0000537 """extract host and port from an URL string"""
Chris Liechtia4222112015-08-07 01:03:12 +0200538 parts = urlparse.urlsplit(url)
539 if parts.scheme.lower() != "rfc2217":
Chris Liechtic4bca9e2015-08-07 14:40:41 +0200540 raise SerialException('expected a string in the form "rfc2217://<host>:<port>[/option[/option...]]": not starting with rfc2217:// (%r)' % (parts.scheme,))
cliechti8099bed2009-08-01 23:59:18 +0000541 try:
Chris Liechtia4222112015-08-07 01:03:12 +0200542 # process options now, directly altering self
543 for option in parts.path.lower().split('/'):
544 if '=' in option:
545 option, value = option.split('=', 1)
546 else:
547 value = None
548 if not option:
549 pass
550 elif option == 'logging':
551 logging.basicConfig() # XXX is that good to call it here?
552 self.logger = logging.getLogger('pySerial.rfc2217')
553 self.logger.setLevel(LOGGER_LEVELS[value])
554 self.logger.debug('enabled logging')
555 elif option == 'ign_set_control':
556 self._ignore_set_control_answer = True
557 elif option == 'poll_modem':
558 self._poll_modem_state = True
559 elif option == 'timeout':
560 self._network_timeout = float(value)
561 else:
562 raise ValueError('unknown option: %r' % (option,))
cliechti81c54762009-08-03 23:53:27 +0000563 # get host and port
Chris Liechtia4222112015-08-07 01:03:12 +0200564 host, port = parts.hostname, parts.port
cliechti8099bed2009-08-01 23:59:18 +0000565 if not 0 <= port < 65536: raise ValueError("port not in range 0...65535")
Chris Liechti68340d72015-08-03 14:15:48 +0200566 except ValueError as e:
Chris Liechtia4222112015-08-07 01:03:12 +0200567 raise SerialException('expected a string in the form "rfc2217://<host>:<port>[/option[/option...]]": %s' % e)
cliechti8099bed2009-08-01 23:59:18 +0000568 return (host, port)
569
570 # - - - - - - - - - - - - - - - - - - - - - - - -
571
572 def inWaiting(self):
573 """Return the number of characters currently in the input buffer."""
574 if not self._isOpen: raise portNotOpenError
575 return self._read_buffer.qsize()
576
577 def read(self, size=1):
cliechtieada4fd2013-07-31 16:26:07 +0000578 """\
579 Read size bytes from the serial port. If a timeout is set it may
cliechti8099bed2009-08-01 23:59:18 +0000580 return less characters as requested. With no timeout it will block
cliechtieada4fd2013-07-31 16:26:07 +0000581 until the requested number of bytes is read.
582 """
cliechti8099bed2009-08-01 23:59:18 +0000583 if not self._isOpen: raise portNotOpenError
584 data = bytearray()
585 try:
586 while len(data) < size:
cliechti81c54762009-08-03 23:53:27 +0000587 if self._thread is None:
588 raise SerialException('connection failed (reader thread died)')
Chris Liechti01587b12015-08-05 02:39:32 +0200589 data += self._read_buffer.get(True, self._timeout)
cliechti8099bed2009-08-01 23:59:18 +0000590 except Queue.Empty: # -> timeout
591 pass
592 return bytes(data)
593
594 def write(self, data):
cliechtieada4fd2013-07-31 16:26:07 +0000595 """\
596 Output the given string over the serial port. Can block if the
cliechti8099bed2009-08-01 23:59:18 +0000597 connection is blocked. May raise SerialException if the connection is
cliechtieada4fd2013-07-31 16:26:07 +0000598 closed.
599 """
cliechti8099bed2009-08-01 23:59:18 +0000600 if not self._isOpen: raise portNotOpenError
Chris Liechti01587b12015-08-05 02:39:32 +0200601 with self._write_lock:
cliechti81c54762009-08-03 23:53:27 +0000602 try:
cliechti38077122013-10-16 02:57:27 +0000603 self._socket.sendall(to_bytes(data).replace(IAC, IAC_DOUBLED))
Chris Liechti68340d72015-08-03 14:15:48 +0200604 except socket.error as e:
cliechticb20a4f2011-04-25 02:25:54 +0000605 raise SerialException("connection failed (socket error): %s" % e) # XXX what exception if socket connection fails
cliechti8099bed2009-08-01 23:59:18 +0000606 return len(data)
607
608 def flushInput(self):
609 """Clear input buffer, discarding all that is in the buffer."""
610 if not self._isOpen: raise portNotOpenError
cliechti1ef7e3e2009-08-03 02:38:43 +0000611 self.rfc2217SendPurge(PURGE_RECEIVE_BUFFER)
cliechti8099bed2009-08-01 23:59:18 +0000612 # empty read buffer
613 while self._read_buffer.qsize():
614 self._read_buffer.get(False)
615
616 def flushOutput(self):
cliechtieada4fd2013-07-31 16:26:07 +0000617 """\
618 Clear output buffer, aborting the current output and
619 discarding all that is in the buffer.
620 """
cliechti8099bed2009-08-01 23:59:18 +0000621 if not self._isOpen: raise portNotOpenError
cliechti1ef7e3e2009-08-03 02:38:43 +0000622 self.rfc2217SendPurge(PURGE_TRANSMIT_BUFFER)
cliechti8099bed2009-08-01 23:59:18 +0000623
624 def sendBreak(self, duration=0.25):
cliechti7d448562014-08-03 21:57:45 +0000625 """\
626 Send break condition. Timed, returns to idle state after given
627 duration.
628 """
cliechti8099bed2009-08-01 23:59:18 +0000629 if not self._isOpen: raise portNotOpenError
630 self.setBreak(True)
631 time.sleep(duration)
632 self.setBreak(False)
633
634 def setBreak(self, level=True):
cliechtieada4fd2013-07-31 16:26:07 +0000635 """\
636 Set break: Controls TXD. When active, to transmitting is
637 possible.
638 """
cliechti8099bed2009-08-01 23:59:18 +0000639 if not self._isOpen: raise portNotOpenError
cliechti6a300772009-08-12 02:28:56 +0000640 if self.logger:
641 self.logger.info('set BREAK to %s' % ('inactive', 'active')[bool(level)])
cliechti8099bed2009-08-01 23:59:18 +0000642 if level:
cliechti1ef7e3e2009-08-03 02:38:43 +0000643 self.rfc2217SetControl(SET_CONTROL_BREAK_ON)
cliechti8099bed2009-08-01 23:59:18 +0000644 else:
cliechti1ef7e3e2009-08-03 02:38:43 +0000645 self.rfc2217SetControl(SET_CONTROL_BREAK_OFF)
cliechti8099bed2009-08-01 23:59:18 +0000646
647 def setRTS(self, level=True):
cliechti044d8662009-08-11 21:40:31 +0000648 """Set terminal status line: Request To Send."""
cliechti8099bed2009-08-01 23:59:18 +0000649 if not self._isOpen: raise portNotOpenError
cliechti6a300772009-08-12 02:28:56 +0000650 if self.logger:
651 self.logger.info('set RTS to %s' % ('inactive', 'active')[bool(level)])
cliechti8099bed2009-08-01 23:59:18 +0000652 if level:
cliechti1ef7e3e2009-08-03 02:38:43 +0000653 self.rfc2217SetControl(SET_CONTROL_RTS_ON)
cliechti8099bed2009-08-01 23:59:18 +0000654 else:
cliechti1ef7e3e2009-08-03 02:38:43 +0000655 self.rfc2217SetControl(SET_CONTROL_RTS_OFF)
cliechti8099bed2009-08-01 23:59:18 +0000656
657 def setDTR(self, level=True):
cliechti044d8662009-08-11 21:40:31 +0000658 """Set terminal status line: Data Terminal Ready."""
cliechti8099bed2009-08-01 23:59:18 +0000659 if not self._isOpen: raise portNotOpenError
cliechti6a300772009-08-12 02:28:56 +0000660 if self.logger:
661 self.logger.info('set DTR to %s' % ('inactive', 'active')[bool(level)])
cliechti8099bed2009-08-01 23:59:18 +0000662 if level:
cliechti1ef7e3e2009-08-03 02:38:43 +0000663 self.rfc2217SetControl(SET_CONTROL_DTR_ON)
cliechti8099bed2009-08-01 23:59:18 +0000664 else:
cliechti1ef7e3e2009-08-03 02:38:43 +0000665 self.rfc2217SetControl(SET_CONTROL_DTR_OFF)
cliechti8099bed2009-08-01 23:59:18 +0000666
667 def getCTS(self):
cliechti044d8662009-08-11 21:40:31 +0000668 """Read terminal status line: Clear To Send."""
cliechti8099bed2009-08-01 23:59:18 +0000669 if not self._isOpen: raise portNotOpenError
cliechti7cb78e82009-08-05 15:47:57 +0000670 return bool(self.getModemState() & MODEMSTATE_MASK_CTS)
cliechti8099bed2009-08-01 23:59:18 +0000671
672 def getDSR(self):
cliechti044d8662009-08-11 21:40:31 +0000673 """Read terminal status line: Data Set Ready."""
cliechti8099bed2009-08-01 23:59:18 +0000674 if not self._isOpen: raise portNotOpenError
cliechti7cb78e82009-08-05 15:47:57 +0000675 return bool(self.getModemState() & MODEMSTATE_MASK_DSR)
cliechti8099bed2009-08-01 23:59:18 +0000676
677 def getRI(self):
cliechti044d8662009-08-11 21:40:31 +0000678 """Read terminal status line: Ring Indicator."""
cliechti8099bed2009-08-01 23:59:18 +0000679 if not self._isOpen: raise portNotOpenError
cliechti7cb78e82009-08-05 15:47:57 +0000680 return bool(self.getModemState() & MODEMSTATE_MASK_RI)
cliechti8099bed2009-08-01 23:59:18 +0000681
682 def getCD(self):
cliechti044d8662009-08-11 21:40:31 +0000683 """Read terminal status line: Carrier Detect."""
cliechti8099bed2009-08-01 23:59:18 +0000684 if not self._isOpen: raise portNotOpenError
cliechti7cb78e82009-08-05 15:47:57 +0000685 return bool(self.getModemState() & MODEMSTATE_MASK_CD)
cliechti8099bed2009-08-01 23:59:18 +0000686
687 # - - - platform specific - - -
688 # None so far
689
690 # - - - RFC2217 specific - - -
691
cliechti1ef7e3e2009-08-03 02:38:43 +0000692 def _telnetReadLoop(self):
cliechti7d448562014-08-03 21:57:45 +0000693 """Read loop for the socket."""
cliechti8099bed2009-08-01 23:59:18 +0000694 mode = M_NORMAL
695 suboption = None
cliechti81c54762009-08-03 23:53:27 +0000696 try:
697 while self._socket is not None:
698 try:
699 data = self._socket.recv(1024)
700 except socket.timeout:
701 # just need to get out of recv form time to time to check if
702 # still alive
703 continue
Chris Liechti68340d72015-08-03 14:15:48 +0200704 except socket.error as e:
cliechti81c54762009-08-03 23:53:27 +0000705 # connection fails -> terminate loop
cliechticb20a4f2011-04-25 02:25:54 +0000706 if self.logger:
707 self.logger.debug("socket error in reader thread: %s" % (e,))
cliechti81c54762009-08-03 23:53:27 +0000708 break
cliechticb20a4f2011-04-25 02:25:54 +0000709 if not data: break # lost connection
Chris Liechti01587b12015-08-05 02:39:32 +0200710 #~ for byte in data: # fails for python3 as it returns ints instead of b''
711 for x in range(len(data)):
712 byte = data[x:x+1]
cliechti81c54762009-08-03 23:53:27 +0000713 if mode == M_NORMAL:
714 # interpret as command or as data
715 if byte == IAC:
716 mode = M_IAC_SEEN
cliechti8099bed2009-08-01 23:59:18 +0000717 else:
cliechti81c54762009-08-03 23:53:27 +0000718 # store data in read buffer or sub option buffer
719 # depending on state
720 if suboption is not None:
Chris Liechti01587b12015-08-05 02:39:32 +0200721 suboption += byte
cliechti81c54762009-08-03 23:53:27 +0000722 else:
723 self._read_buffer.put(byte)
724 elif mode == M_IAC_SEEN:
725 if byte == IAC:
726 # interpret as command doubled -> insert character
727 # itself
cliechtif325c032009-12-25 16:09:49 +0000728 if suboption is not None:
Chris Liechti01587b12015-08-05 02:39:32 +0200729 suboption += IAC
cliechtif325c032009-12-25 16:09:49 +0000730 else:
731 self._read_buffer.put(IAC)
cliechti81c54762009-08-03 23:53:27 +0000732 mode = M_NORMAL
733 elif byte == SB:
734 # sub option start
735 suboption = bytearray()
736 mode = M_NORMAL
737 elif byte == SE:
738 # sub option end -> process it now
739 self._telnetProcessSubnegotiation(bytes(suboption))
740 suboption = None
741 mode = M_NORMAL
742 elif byte in (DO, DONT, WILL, WONT):
743 # negotiation
744 telnet_command = byte
745 mode = M_NEGOTIATE
746 else:
747 # other telnet commands
748 self._telnetProcessCommand(byte)
749 mode = M_NORMAL
750 elif mode == M_NEGOTIATE: # DO, DONT, WILL, WONT was received, option now following
751 self._telnetNegotiateOption(telnet_command, byte)
cliechti8099bed2009-08-01 23:59:18 +0000752 mode = M_NORMAL
cliechti81c54762009-08-03 23:53:27 +0000753 finally:
754 self._thread = None
cliechti6a300772009-08-12 02:28:56 +0000755 if self.logger:
756 self.logger.debug("read thread terminated")
cliechti8099bed2009-08-01 23:59:18 +0000757
758 # - incoming telnet commands and options
759
cliechti1ef7e3e2009-08-03 02:38:43 +0000760 def _telnetProcessCommand(self, command):
cliechti044d8662009-08-11 21:40:31 +0000761 """Process commands other than DO, DONT, WILL, WONT."""
cliechti1ef7e3e2009-08-03 02:38:43 +0000762 # Currently none. RFC2217 only uses negotiation and subnegotiation.
cliechti6a300772009-08-12 02:28:56 +0000763 if self.logger:
764 self.logger.warning("ignoring Telnet command: %r" % (command,))
cliechti8099bed2009-08-01 23:59:18 +0000765
cliechti1ef7e3e2009-08-03 02:38:43 +0000766 def _telnetNegotiateOption(self, command, option):
cliechti044d8662009-08-11 21:40:31 +0000767 """Process incoming DO, DONT, WILL, WONT."""
cliechti2b929b72009-08-02 23:49:02 +0000768 # check our registered telnet options and forward command to them
769 # they know themselves if they have to answer or not
cliechtiac205322009-08-02 20:40:21 +0000770 known = False
771 for item in self._telnet_options:
cliechti2b929b72009-08-02 23:49:02 +0000772 # can have more than one match! as some options are duplicated for
773 # 'us' and 'them'
cliechtiac205322009-08-02 20:40:21 +0000774 if item.option == option:
cliechti2b929b72009-08-02 23:49:02 +0000775 item.process_incoming(command)
cliechtiac205322009-08-02 20:40:21 +0000776 known = True
777 if not known:
778 # handle unknown options
779 # only answer to positive requests and deny them
780 if command == WILL or command == DO:
cliechti1ef7e3e2009-08-03 02:38:43 +0000781 self.telnetSendOption((command == WILL and DONT or WONT), option)
cliechti6a300772009-08-12 02:28:56 +0000782 if self.logger:
783 self.logger.warning("rejected Telnet option: %r" % (option,))
cliechtiac205322009-08-02 20:40:21 +0000784
cliechti8099bed2009-08-01 23:59:18 +0000785
cliechti1ef7e3e2009-08-03 02:38:43 +0000786 def _telnetProcessSubnegotiation(self, suboption):
cliechti044d8662009-08-11 21:40:31 +0000787 """Process subnegotiation, the data between IAC SB and IAC SE."""
cliechti8099bed2009-08-01 23:59:18 +0000788 if suboption[0:1] == COM_PORT_OPTION:
789 if suboption[1:2] == SERVER_NOTIFY_LINESTATE and len(suboption) >= 3:
cliechti672d0292009-08-03 02:01:57 +0000790 self._linestate = ord(suboption[2:3]) # ensure it is a number
cliechti6a300772009-08-12 02:28:56 +0000791 if self.logger:
792 self.logger.info("NOTIFY_LINESTATE: %s" % self._linestate)
cliechti8099bed2009-08-01 23:59:18 +0000793 elif suboption[1:2] == SERVER_NOTIFY_MODEMSTATE and len(suboption) >= 3:
cliechti672d0292009-08-03 02:01:57 +0000794 self._modemstate = ord(suboption[2:3]) # ensure it is a number
cliechti6a300772009-08-12 02:28:56 +0000795 if self.logger:
796 self.logger.info("NOTIFY_MODEMSTATE: %s" % self._modemstate)
cliechti7cb78e82009-08-05 15:47:57 +0000797 # update time when we think that a poll would make sense
798 self._modemstate_expires = time.time() + 0.3
cliechti672d0292009-08-03 02:01:57 +0000799 elif suboption[1:2] == FLOWCONTROL_SUSPEND:
800 self._remote_suspend_flow = True
801 elif suboption[1:2] == FLOWCONTROL_RESUME:
802 self._remote_suspend_flow = False
cliechti8099bed2009-08-01 23:59:18 +0000803 else:
cliechti2b929b72009-08-02 23:49:02 +0000804 for item in self._rfc2217_options.values():
805 if item.ack_option == suboption[1:2]:
cliechti81c54762009-08-03 23:53:27 +0000806 #~ print "processing COM_PORT_OPTION: %r" % list(suboption[1:])
cliechti2b929b72009-08-02 23:49:02 +0000807 item.checkAnswer(bytes(suboption[2:]))
808 break
809 else:
cliechti6a300772009-08-12 02:28:56 +0000810 if self.logger:
811 self.logger.warning("ignoring COM_PORT_OPTION: %r" % (suboption,))
cliechti8099bed2009-08-01 23:59:18 +0000812 else:
cliechti6a300772009-08-12 02:28:56 +0000813 if self.logger:
814 self.logger.warning("ignoring subnegotiation: %r" % (suboption,))
cliechti8099bed2009-08-01 23:59:18 +0000815
816 # - outgoing telnet commands and options
817
cliechti81c54762009-08-03 23:53:27 +0000818 def _internal_raw_write(self, data):
cliechti044d8662009-08-11 21:40:31 +0000819 """internal socket write with no data escaping. used to send telnet stuff."""
Chris Liechti01587b12015-08-05 02:39:32 +0200820 with self._write_lock:
cliechti81c54762009-08-03 23:53:27 +0000821 self._socket.sendall(data)
cliechti81c54762009-08-03 23:53:27 +0000822
cliechti1ef7e3e2009-08-03 02:38:43 +0000823 def telnetSendOption(self, action, option):
cliechti044d8662009-08-11 21:40:31 +0000824 """Send DO, DONT, WILL, WONT."""
cliechti81c54762009-08-03 23:53:27 +0000825 self._internal_raw_write(to_bytes([IAC, action, option]))
cliechti8099bed2009-08-01 23:59:18 +0000826
cliechtif325c032009-12-25 16:09:49 +0000827 def rfc2217SendSubnegotiation(self, option, value=''):
cliechti044d8662009-08-11 21:40:31 +0000828 """Subnegotiation of RFC2217 parameters."""
cliechtif325c032009-12-25 16:09:49 +0000829 value = value.replace(IAC, IAC_DOUBLED)
cliechti81c54762009-08-03 23:53:27 +0000830 self._internal_raw_write(to_bytes([IAC, SB, COM_PORT_OPTION, option] + list(value) + [IAC, SE]))
cliechti2b929b72009-08-02 23:49:02 +0000831
cliechti1ef7e3e2009-08-03 02:38:43 +0000832 def rfc2217SendPurge(self, value):
cliechti2b929b72009-08-02 23:49:02 +0000833 item = self._rfc2217_options['purge']
cliechti672d0292009-08-03 02:01:57 +0000834 item.set(value) # transmit desired purge type
cliechtidfe2d272009-08-10 22:19:41 +0000835 item.wait(self._network_timeout) # wait for acknowledge from the server
cliechti2b929b72009-08-02 23:49:02 +0000836
cliechti1ef7e3e2009-08-03 02:38:43 +0000837 def rfc2217SetControl(self, value):
cliechti81c54762009-08-03 23:53:27 +0000838 item = self._rfc2217_options['control']
cliechticb20a4f2011-04-25 02:25:54 +0000839 item.set(value) # transmit desired control type
cliechti81c54762009-08-03 23:53:27 +0000840 if self._ignore_set_control_answer:
841 # answers are ignored when option is set. compatibility mode for
cliechticb20a4f2011-04-25 02:25:54 +0000842 # servers that answer, but not the expected one... (or no answer
cliechti81c54762009-08-03 23:53:27 +0000843 # at all) i.e. sredird
844 time.sleep(0.1) # this helps getting the unit tests passed
845 else:
cliechtidfe2d272009-08-10 22:19:41 +0000846 item.wait(self._network_timeout) # wait for acknowledge from the server
cliechti8099bed2009-08-01 23:59:18 +0000847
cliechti1ef7e3e2009-08-03 02:38:43 +0000848 def rfc2217FlowServerReady(self):
cliechtieada4fd2013-07-31 16:26:07 +0000849 """\
850 check if server is ready to receive data. block for some time when
851 not.
852 """
cliechti672d0292009-08-03 02:01:57 +0000853 #~ if self._remote_suspend_flow:
854 #~ wait---
855
cliechti7cb78e82009-08-05 15:47:57 +0000856 def getModemState(self):
cliechtieada4fd2013-07-31 16:26:07 +0000857 """\
cliechti7d448562014-08-03 21:57:45 +0000858 get last modem state (cached value. If value is "old", request a new
859 one. This cache helps that we don't issue to many requests when e.g. all
860 status lines, one after the other is queried by the user (getCTS, getDSR
cliechtieada4fd2013-07-31 16:26:07 +0000861 etc.)
862 """
cliechti7cb78e82009-08-05 15:47:57 +0000863 # active modem state polling enabled? is the value fresh enough?
864 if self._poll_modem_state and self._modemstate_expires < time.time():
cliechti6a300772009-08-12 02:28:56 +0000865 if self.logger:
866 self.logger.debug('polling modem state')
cliechti7cb78e82009-08-05 15:47:57 +0000867 # when it is older, request an update
868 self.rfc2217SendSubnegotiation(NOTIFY_MODEMSTATE)
cliechtidfe2d272009-08-10 22:19:41 +0000869 timeout_time = time.time() + self._network_timeout
cliechti7cb78e82009-08-05 15:47:57 +0000870 while time.time() < timeout_time:
871 time.sleep(0.05) # prevent 100% CPU load
872 # when expiration time is updated, it means that there is a new
873 # value
874 if self._modemstate_expires > time.time():
875 break
Chris Liechti01587b12015-08-05 02:39:32 +0200876 else:
877 if self.logger:
878 self.logger.warning('poll for modem state failed')
cliechti7cb78e82009-08-05 15:47:57 +0000879 # even when there is a timeout, do not generate an error just
880 # return the last known value. this way we can support buggy
881 # servers that do not respond to polls, but send automatic
882 # updates.
883 if self._modemstate is not None:
cliechti6a300772009-08-12 02:28:56 +0000884 if self.logger:
885 self.logger.debug('using cached modem state')
cliechti7cb78e82009-08-05 15:47:57 +0000886 return self._modemstate
887 else:
888 # never received a notification from the server
cliechti8fb119c2009-08-05 23:39:45 +0000889 raise SerialException("remote sends no NOTIFY_MODEMSTATE")
cliechti8099bed2009-08-01 23:59:18 +0000890
cliechti5cc3eb12009-08-11 23:04:30 +0000891
cliechti595ed5b2009-08-10 01:43:32 +0000892#############################################################################
cliechti5cc3eb12009-08-11 23:04:30 +0000893# The following is code that helps implementing an RFC 2217 server.
cliechti8099bed2009-08-01 23:59:18 +0000894
cliechti8ccc2ff2009-08-05 12:44:46 +0000895class PortManager(object):
cliechtieada4fd2013-07-31 16:26:07 +0000896 """\
897 This class manages the state of Telnet and RFC 2217. It needs a serial
cliechticb20a4f2011-04-25 02:25:54 +0000898 instance and a connection to work with. Connection is expected to implement
cliechtieada4fd2013-07-31 16:26:07 +0000899 a (thread safe) write function, that writes the string to the network.
900 """
cliechti130d1f02009-08-04 02:10:58 +0000901
cliechti6a300772009-08-12 02:28:56 +0000902 def __init__(self, serial_port, connection, logger=None):
cliechti130d1f02009-08-04 02:10:58 +0000903 self.serial = serial_port
904 self.connection = connection
cliechti6a300772009-08-12 02:28:56 +0000905 self.logger = logger
cliechti86b593e2009-08-05 16:28:12 +0000906 self._client_is_rfc2217 = False
cliechti130d1f02009-08-04 02:10:58 +0000907
908 # filter state machine
909 self.mode = M_NORMAL
910 self.suboption = None
911 self.telnet_command = None
912
913 # states for modem/line control events
914 self.modemstate_mask = 255
915 self.last_modemstate = None
916 self.linstate_mask = 0
917
918 # all supported telnet options
919 self._telnet_options = [
920 TelnetOption(self, 'ECHO', ECHO, WILL, WONT, DO, DONT, REQUESTED),
921 TelnetOption(self, 'we-SGA', SGA, WILL, WONT, DO, DONT, REQUESTED),
922 TelnetOption(self, 'they-SGA', SGA, DO, DONT, WILL, WONT, INACTIVE),
923 TelnetOption(self, 'we-BINARY', BINARY, WILL, WONT, DO, DONT, INACTIVE),
924 TelnetOption(self, 'they-BINARY', BINARY, DO, DONT, WILL, WONT, REQUESTED),
cliechti86b593e2009-08-05 16:28:12 +0000925 TelnetOption(self, 'we-RFC2217', COM_PORT_OPTION, WILL, WONT, DO, DONT, REQUESTED, self._client_ok),
926 TelnetOption(self, 'they-RFC2217', COM_PORT_OPTION, DO, DONT, WILL, WONT, INACTIVE, self._client_ok),
cliechti130d1f02009-08-04 02:10:58 +0000927 ]
928
929 # negotiate Telnet/RFC2217 -> send initial requests
cliechti6a300772009-08-12 02:28:56 +0000930 if self.logger:
931 self.logger.debug("requesting initial Telnet/RFC 2217 options")
cliechti130d1f02009-08-04 02:10:58 +0000932 for option in self._telnet_options:
933 if option.state is REQUESTED:
934 self.telnetSendOption(option.send_yes, option.option)
935 # issue 1st modem state notification
cliechti86b593e2009-08-05 16:28:12 +0000936
937 def _client_ok(self):
cliechtieada4fd2013-07-31 16:26:07 +0000938 """\
cliechti7d448562014-08-03 21:57:45 +0000939 callback of telnet option. It gets called when option is activated.
940 This one here is used to detect when the client agrees on RFC 2217. A
cliechti86b593e2009-08-05 16:28:12 +0000941 flag is set so that other functions like check_modem_lines know if the
cliechti7d448562014-08-03 21:57:45 +0000942 client is OK.
cliechtieada4fd2013-07-31 16:26:07 +0000943 """
cliechti86b593e2009-08-05 16:28:12 +0000944 # The callback is used for we and they so if one party agrees, we're
945 # already happy. it seems not all servers do the negotiation correctly
946 # and i guess there are incorrect clients too.. so be happy if client
947 # answers one or the other positively.
948 self._client_is_rfc2217 = True
cliechti6a300772009-08-12 02:28:56 +0000949 if self.logger:
950 self.logger.info("client accepts RFC 2217")
cliechti8fb119c2009-08-05 23:39:45 +0000951 # this is to ensure that the client gets a notification, even if there
952 # was no change
953 self.check_modem_lines(force_notification=True)
cliechti130d1f02009-08-04 02:10:58 +0000954
955 # - outgoing telnet commands and options
956
957 def telnetSendOption(self, action, option):
cliechti044d8662009-08-11 21:40:31 +0000958 """Send DO, DONT, WILL, WONT."""
cliechti130d1f02009-08-04 02:10:58 +0000959 self.connection.write(to_bytes([IAC, action, option]))
960
cliechtif325c032009-12-25 16:09:49 +0000961 def rfc2217SendSubnegotiation(self, option, value=''):
cliechti044d8662009-08-11 21:40:31 +0000962 """Subnegotiation of RFC 2217 parameters."""
cliechtif325c032009-12-25 16:09:49 +0000963 value = value.replace(IAC, IAC_DOUBLED)
cliechti130d1f02009-08-04 02:10:58 +0000964 self.connection.write(to_bytes([IAC, SB, COM_PORT_OPTION, option] + list(value) + [IAC, SE]))
965
966 # - check modem lines, needs to be called periodically from user to
967 # establish polling
968
cliechti7cb78e82009-08-05 15:47:57 +0000969 def check_modem_lines(self, force_notification=False):
cliechti130d1f02009-08-04 02:10:58 +0000970 modemstate = (
971 (self.serial.getCTS() and MODEMSTATE_MASK_CTS) |
972 (self.serial.getDSR() and MODEMSTATE_MASK_DSR) |
973 (self.serial.getRI() and MODEMSTATE_MASK_RI) |
974 (self.serial.getCD() and MODEMSTATE_MASK_CD)
975 )
cliechti7cb78e82009-08-05 15:47:57 +0000976 # check what has changed
977 deltas = modemstate ^ (self.last_modemstate or 0) # when last is None -> 0
978 if deltas & MODEMSTATE_MASK_CTS:
979 modemstate |= MODEMSTATE_MASK_CTS_CHANGE
980 if deltas & MODEMSTATE_MASK_DSR:
981 modemstate |= MODEMSTATE_MASK_DSR_CHANGE
982 if deltas & MODEMSTATE_MASK_RI:
983 modemstate |= MODEMSTATE_MASK_RI_CHANGE
984 if deltas & MODEMSTATE_MASK_CD:
985 modemstate |= MODEMSTATE_MASK_CD_CHANGE
986 # if new state is different and the mask allows this change, send
cliechti86b593e2009-08-05 16:28:12 +0000987 # notification. suppress notifications when client is not rfc2217
cliechti7cb78e82009-08-05 15:47:57 +0000988 if modemstate != self.last_modemstate or force_notification:
cliechti8fb119c2009-08-05 23:39:45 +0000989 if (self._client_is_rfc2217 and (modemstate & self.modemstate_mask)) or force_notification:
cliechti7cb78e82009-08-05 15:47:57 +0000990 self.rfc2217SendSubnegotiation(
991 SERVER_NOTIFY_MODEMSTATE,
992 to_bytes([modemstate & self.modemstate_mask])
993 )
cliechti6a300772009-08-12 02:28:56 +0000994 if self.logger:
995 self.logger.info("NOTIFY_MODEMSTATE: %s" % (modemstate,))
cliechti7cb78e82009-08-05 15:47:57 +0000996 # save last state, but forget about deltas.
997 # otherwise it would also notify about changing deltas which is
998 # probably not very useful
999 self.last_modemstate = modemstate & 0xf0
cliechti130d1f02009-08-04 02:10:58 +00001000
cliechti32c10332009-08-05 13:23:43 +00001001 # - outgoing data escaping
1002
1003 def escape(self, data):
cliechtieada4fd2013-07-31 16:26:07 +00001004 """\
cliechti7d448562014-08-03 21:57:45 +00001005 This generator function is for the user. All outgoing data has to be
cliechticb20a4f2011-04-25 02:25:54 +00001006 properly escaped, so that no IAC character in the data stream messes up
1007 the Telnet state machine in the server.
cliechti32c10332009-08-05 13:23:43 +00001008
1009 socket.sendall(escape(data))
1010 """
1011 for byte in data:
1012 if byte == IAC:
1013 yield IAC
1014 yield IAC
1015 else:
1016 yield byte
1017
cliechti130d1f02009-08-04 02:10:58 +00001018 # - incoming data filter
1019
1020 def filter(self, data):
cliechtieada4fd2013-07-31 16:26:07 +00001021 """\
cliechti7d448562014-08-03 21:57:45 +00001022 Handle a bunch of incoming bytes. This is a generator. It will yield
cliechti044d8662009-08-11 21:40:31 +00001023 all characters not of interest for Telnet/RFC 2217.
cliechti130d1f02009-08-04 02:10:58 +00001024
1025 The idea is that the reader thread pushes data from the socket through
1026 this filter:
1027
1028 for byte in filter(socket.recv(1024)):
1029 # do things like CR/LF conversion/whatever
1030 # and write data to the serial port
1031 serial.write(byte)
1032
1033 (socket error handling code left as exercise for the reader)
1034 """
Chris Liechti01587b12015-08-05 02:39:32 +02001035 #~ for byte in data: # XXX fails for python3 as it returns ints instead of bytes
1036 for x in range(len(data)):
1037 byte = data[x:x+1]
cliechti130d1f02009-08-04 02:10:58 +00001038 if self.mode == M_NORMAL:
1039 # interpret as command or as data
1040 if byte == IAC:
1041 self.mode = M_IAC_SEEN
1042 else:
1043 # store data in sub option buffer or pass it to our
1044 # consumer depending on state
1045 if self.suboption is not None:
Chris Liechti01587b12015-08-05 02:39:32 +02001046 self.suboption += byte
cliechti130d1f02009-08-04 02:10:58 +00001047 else:
1048 yield byte
1049 elif self.mode == M_IAC_SEEN:
1050 if byte == IAC:
1051 # interpret as command doubled -> insert character
1052 # itself
cliechtif325c032009-12-25 16:09:49 +00001053 if self.suboption is not None:
Chris Liechti01587b12015-08-05 02:39:32 +02001054 self.suboption += byte
cliechtif325c032009-12-25 16:09:49 +00001055 else:
1056 yield byte
cliechti130d1f02009-08-04 02:10:58 +00001057 self.mode = M_NORMAL
1058 elif byte == SB:
1059 # sub option start
1060 self.suboption = bytearray()
1061 self.mode = M_NORMAL
1062 elif byte == SE:
1063 # sub option end -> process it now
1064 self._telnetProcessSubnegotiation(bytes(self.suboption))
1065 self.suboption = None
1066 self.mode = M_NORMAL
1067 elif byte in (DO, DONT, WILL, WONT):
1068 # negotiation
1069 self.telnet_command = byte
1070 self.mode = M_NEGOTIATE
1071 else:
1072 # other telnet commands
1073 self._telnetProcessCommand(byte)
1074 self.mode = M_NORMAL
1075 elif self.mode == M_NEGOTIATE: # DO, DONT, WILL, WONT was received, option now following
1076 self._telnetNegotiateOption(self.telnet_command, byte)
1077 self.mode = M_NORMAL
1078
1079 # - incoming telnet commands and options
1080
1081 def _telnetProcessCommand(self, command):
cliechti044d8662009-08-11 21:40:31 +00001082 """Process commands other than DO, DONT, WILL, WONT."""
cliechti130d1f02009-08-04 02:10:58 +00001083 # Currently none. RFC2217 only uses negotiation and subnegotiation.
cliechti6a300772009-08-12 02:28:56 +00001084 if self.logger:
1085 self.logger.warning("ignoring Telnet command: %r" % (command,))
cliechti130d1f02009-08-04 02:10:58 +00001086
1087 def _telnetNegotiateOption(self, command, option):
cliechti044d8662009-08-11 21:40:31 +00001088 """Process incoming DO, DONT, WILL, WONT."""
cliechti130d1f02009-08-04 02:10:58 +00001089 # check our registered telnet options and forward command to them
1090 # they know themselves if they have to answer or not
1091 known = False
1092 for item in self._telnet_options:
1093 # can have more than one match! as some options are duplicated for
1094 # 'us' and 'them'
1095 if item.option == option:
1096 item.process_incoming(command)
1097 known = True
1098 if not known:
1099 # handle unknown options
1100 # only answer to positive requests and deny them
1101 if command == WILL or command == DO:
1102 self.telnetSendOption((command == WILL and DONT or WONT), option)
cliechti6a300772009-08-12 02:28:56 +00001103 if self.logger:
1104 self.logger.warning("rejected Telnet option: %r" % (option,))
cliechti130d1f02009-08-04 02:10:58 +00001105
1106
1107 def _telnetProcessSubnegotiation(self, suboption):
cliechti044d8662009-08-11 21:40:31 +00001108 """Process subnegotiation, the data between IAC SB and IAC SE."""
cliechti130d1f02009-08-04 02:10:58 +00001109 if suboption[0:1] == COM_PORT_OPTION:
cliechti6a300772009-08-12 02:28:56 +00001110 if self.logger:
1111 self.logger.debug('received COM_PORT_OPTION: %r' % (suboption,))
cliechti130d1f02009-08-04 02:10:58 +00001112 if suboption[1:2] == SET_BAUDRATE:
1113 backup = self.serial.baudrate
1114 try:
Chris Liechti01587b12015-08-05 02:39:32 +02001115 (baudrate,) = struct.unpack(b"!I", suboption[2:6])
cliechtieada4fd2013-07-31 16:26:07 +00001116 if baudrate != 0:
1117 self.serial.baudrate = baudrate
Chris Liechtid2146002015-08-04 16:57:16 +02001118 except ValueError as e:
cliechti6a300772009-08-12 02:28:56 +00001119 if self.logger:
1120 self.logger.error("failed to set baud rate: %s" % (e,))
cliechti130d1f02009-08-04 02:10:58 +00001121 self.serial.baudrate = backup
cliechti5cc3eb12009-08-11 23:04:30 +00001122 else:
cliechti6a300772009-08-12 02:28:56 +00001123 if self.logger:
cliechtieada4fd2013-07-31 16:26:07 +00001124 self.logger.info("%s baud rate: %s" % (baudrate and 'set' or 'get', self.serial.baudrate))
Chris Liechti01587b12015-08-05 02:39:32 +02001125 self.rfc2217SendSubnegotiation(SERVER_SET_BAUDRATE, struct.pack(b"!I", self.serial.baudrate))
cliechti130d1f02009-08-04 02:10:58 +00001126 elif suboption[1:2] == SET_DATASIZE:
1127 backup = self.serial.bytesize
1128 try:
cliechtieada4fd2013-07-31 16:26:07 +00001129 (datasize,) = struct.unpack("!B", suboption[2:3])
1130 if datasize != 0:
1131 self.serial.bytesize = datasize
Chris Liechtid2146002015-08-04 16:57:16 +02001132 except ValueError as e:
cliechti6a300772009-08-12 02:28:56 +00001133 if self.logger:
1134 self.logger.error("failed to set data size: %s" % (e,))
cliechti130d1f02009-08-04 02:10:58 +00001135 self.serial.bytesize = backup
cliechti5cc3eb12009-08-11 23:04:30 +00001136 else:
cliechti6a300772009-08-12 02:28:56 +00001137 if self.logger:
cliechtieada4fd2013-07-31 16:26:07 +00001138 self.logger.info("%s data size: %s" % (datasize and 'set' or 'get', self.serial.bytesize))
Chris Liechti01587b12015-08-05 02:39:32 +02001139 self.rfc2217SendSubnegotiation(SERVER_SET_DATASIZE, struct.pack(b"!B", self.serial.bytesize))
cliechti130d1f02009-08-04 02:10:58 +00001140 elif suboption[1:2] == SET_PARITY:
1141 backup = self.serial.parity
1142 try:
Chris Liechti01587b12015-08-05 02:39:32 +02001143 parity = struct.unpack(b"!B", suboption[2:3])[0]
cliechtieada4fd2013-07-31 16:26:07 +00001144 if parity != 0:
1145 self.serial.parity = RFC2217_REVERSE_PARITY_MAP[parity]
Chris Liechtid2146002015-08-04 16:57:16 +02001146 except ValueError as e:
cliechti6a300772009-08-12 02:28:56 +00001147 if self.logger:
1148 self.logger.error("failed to set parity: %s" % (e,))
cliechti130d1f02009-08-04 02:10:58 +00001149 self.serial.parity = backup
cliechti5cc3eb12009-08-11 23:04:30 +00001150 else:
cliechti6a300772009-08-12 02:28:56 +00001151 if self.logger:
cliechtieada4fd2013-07-31 16:26:07 +00001152 self.logger.info("%s parity: %s" % (parity and 'set' or 'get', self.serial.parity))
cliechti130d1f02009-08-04 02:10:58 +00001153 self.rfc2217SendSubnegotiation(
1154 SERVER_SET_PARITY,
1155 struct.pack("!B", RFC2217_PARITY_MAP[self.serial.parity])
1156 )
1157 elif suboption[1:2] == SET_STOPSIZE:
1158 backup = self.serial.stopbits
1159 try:
Chris Liechti01587b12015-08-05 02:39:32 +02001160 stopbits = struct.unpack(b"!B", suboption[2:3])[0]
cliechtieada4fd2013-07-31 16:26:07 +00001161 if stopbits != 0:
1162 self.serial.stopbits = RFC2217_REVERSE_STOPBIT_MAP[stopbits]
Chris Liechtid2146002015-08-04 16:57:16 +02001163 except ValueError as e:
cliechti6a300772009-08-12 02:28:56 +00001164 if self.logger:
1165 self.logger.error("failed to set stop bits: %s" % (e,))
cliechti130d1f02009-08-04 02:10:58 +00001166 self.serial.stopbits = backup
cliechti5cc3eb12009-08-11 23:04:30 +00001167 else:
cliechti6a300772009-08-12 02:28:56 +00001168 if self.logger:
cliechtieada4fd2013-07-31 16:26:07 +00001169 self.logger.info("%s stop bits: %s" % (stopbits and 'set' or 'get', self.serial.stopbits))
cliechti130d1f02009-08-04 02:10:58 +00001170 self.rfc2217SendSubnegotiation(
1171 SERVER_SET_STOPSIZE,
Chris Liechti01587b12015-08-05 02:39:32 +02001172 struct.pack(b"!B", RFC2217_STOPBIT_MAP[self.serial.stopbits])
cliechti130d1f02009-08-04 02:10:58 +00001173 )
1174 elif suboption[1:2] == SET_CONTROL:
1175 if suboption[2:3] == SET_CONTROL_REQ_FLOW_SETTING:
1176 if self.serial.xonxoff:
1177 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_SW_FLOW_CONTROL)
1178 elif self.serial.rtscts:
1179 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_HW_FLOW_CONTROL)
1180 else:
1181 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_NO_FLOW_CONTROL)
1182 elif suboption[2:3] == SET_CONTROL_USE_NO_FLOW_CONTROL:
1183 self.serial.xonxoff = False
1184 self.serial.rtscts = False
cliechti6a300772009-08-12 02:28:56 +00001185 if self.logger:
1186 self.logger.info("changed flow control to None")
cliechti130d1f02009-08-04 02:10:58 +00001187 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_NO_FLOW_CONTROL)
1188 elif suboption[2:3] == SET_CONTROL_USE_SW_FLOW_CONTROL:
1189 self.serial.xonxoff = True
cliechti6a300772009-08-12 02:28:56 +00001190 if self.logger:
1191 self.logger.info("changed flow control to XON/XOFF")
cliechti130d1f02009-08-04 02:10:58 +00001192 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_SW_FLOW_CONTROL)
1193 elif suboption[2:3] == SET_CONTROL_USE_HW_FLOW_CONTROL:
1194 self.serial.rtscts = True
cliechti6a300772009-08-12 02:28:56 +00001195 if self.logger:
1196 self.logger.info("changed flow control to RTS/CTS")
cliechti130d1f02009-08-04 02:10:58 +00001197 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_HW_FLOW_CONTROL)
1198 elif suboption[2:3] == SET_CONTROL_REQ_BREAK_STATE:
cliechti6a300772009-08-12 02:28:56 +00001199 if self.logger:
1200 self.logger.warning("requested break state - not implemented")
cliechti130d1f02009-08-04 02:10:58 +00001201 pass # XXX needs cached value
1202 elif suboption[2:3] == SET_CONTROL_BREAK_ON:
1203 self.serial.setBreak(True)
cliechti6a300772009-08-12 02:28:56 +00001204 if self.logger:
1205 self.logger.info("changed BREAK to active")
cliechti130d1f02009-08-04 02:10:58 +00001206 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_BREAK_ON)
1207 elif suboption[2:3] == SET_CONTROL_BREAK_OFF:
1208 self.serial.setBreak(False)
cliechti6a300772009-08-12 02:28:56 +00001209 if self.logger:
1210 self.logger.info("changed BREAK to inactive")
cliechti130d1f02009-08-04 02:10:58 +00001211 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_BREAK_OFF)
1212 elif suboption[2:3] == SET_CONTROL_REQ_DTR:
cliechti6a300772009-08-12 02:28:56 +00001213 if self.logger:
1214 self.logger.warning("requested DTR state - not implemented")
cliechti130d1f02009-08-04 02:10:58 +00001215 pass # XXX needs cached value
1216 elif suboption[2:3] == SET_CONTROL_DTR_ON:
1217 self.serial.setDTR(True)
cliechti6a300772009-08-12 02:28:56 +00001218 if self.logger:
1219 self.logger.info("changed DTR to active")
cliechti130d1f02009-08-04 02:10:58 +00001220 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_DTR_ON)
1221 elif suboption[2:3] == SET_CONTROL_DTR_OFF:
1222 self.serial.setDTR(False)
cliechti6a300772009-08-12 02:28:56 +00001223 if self.logger:
1224 self.logger.info("changed DTR to inactive")
cliechti130d1f02009-08-04 02:10:58 +00001225 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_DTR_OFF)
1226 elif suboption[2:3] == SET_CONTROL_REQ_RTS:
cliechti6a300772009-08-12 02:28:56 +00001227 if self.logger:
1228 self.logger.warning("requested RTS state - not implemented")
cliechti130d1f02009-08-04 02:10:58 +00001229 pass # XXX needs cached value
1230 #~ self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_RTS_ON)
1231 elif suboption[2:3] == SET_CONTROL_RTS_ON:
1232 self.serial.setRTS(True)
cliechti6a300772009-08-12 02:28:56 +00001233 if self.logger:
1234 self.logger.info("changed RTS to active")
cliechti130d1f02009-08-04 02:10:58 +00001235 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_RTS_ON)
1236 elif suboption[2:3] == SET_CONTROL_RTS_OFF:
1237 self.serial.setRTS(False)
cliechti6a300772009-08-12 02:28:56 +00001238 if self.logger:
1239 self.logger.info("changed RTS to inactive")
cliechti130d1f02009-08-04 02:10:58 +00001240 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_RTS_OFF)
1241 #~ elif suboption[2:3] == SET_CONTROL_REQ_FLOW_SETTING_IN:
1242 #~ elif suboption[2:3] == SET_CONTROL_USE_NO_FLOW_CONTROL_IN:
1243 #~ elif suboption[2:3] == SET_CONTROL_USE_SW_FLOW_CONTOL_IN:
1244 #~ elif suboption[2:3] == SET_CONTROL_USE_HW_FLOW_CONTOL_IN:
1245 #~ elif suboption[2:3] == SET_CONTROL_USE_DCD_FLOW_CONTROL:
1246 #~ elif suboption[2:3] == SET_CONTROL_USE_DTR_FLOW_CONTROL:
1247 #~ elif suboption[2:3] == SET_CONTROL_USE_DSR_FLOW_CONTROL:
1248 elif suboption[1:2] == NOTIFY_LINESTATE:
cliechti7cb78e82009-08-05 15:47:57 +00001249 # client polls for current state
1250 self.rfc2217SendSubnegotiation(
1251 SERVER_NOTIFY_LINESTATE,
cliechti6a300772009-08-12 02:28:56 +00001252 to_bytes([0]) # sorry, nothing like that implemented
cliechti7cb78e82009-08-05 15:47:57 +00001253 )
cliechti130d1f02009-08-04 02:10:58 +00001254 elif suboption[1:2] == NOTIFY_MODEMSTATE:
cliechti6a300772009-08-12 02:28:56 +00001255 if self.logger:
1256 self.logger.info("request for modem state")
cliechti7cb78e82009-08-05 15:47:57 +00001257 # client polls for current state
1258 self.check_modem_lines(force_notification=True)
cliechti130d1f02009-08-04 02:10:58 +00001259 elif suboption[1:2] == FLOWCONTROL_SUSPEND:
cliechti6a300772009-08-12 02:28:56 +00001260 if self.logger:
1261 self.logger.info("suspend")
cliechti130d1f02009-08-04 02:10:58 +00001262 self._remote_suspend_flow = True
1263 elif suboption[1:2] == FLOWCONTROL_RESUME:
cliechti6a300772009-08-12 02:28:56 +00001264 if self.logger:
1265 self.logger.info("resume")
cliechti130d1f02009-08-04 02:10:58 +00001266 self._remote_suspend_flow = False
1267 elif suboption[1:2] == SET_LINESTATE_MASK:
1268 self.linstate_mask = ord(suboption[2:3]) # ensure it is a number
cliechti6a300772009-08-12 02:28:56 +00001269 if self.logger:
cliechtif325c032009-12-25 16:09:49 +00001270 self.logger.info("line state mask: 0x%02x" % (self.linstate_mask,))
cliechti130d1f02009-08-04 02:10:58 +00001271 elif suboption[1:2] == SET_MODEMSTATE_MASK:
1272 self.modemstate_mask = ord(suboption[2:3]) # ensure it is a number
cliechti6a300772009-08-12 02:28:56 +00001273 if self.logger:
cliechtif325c032009-12-25 16:09:49 +00001274 self.logger.info("modem state mask: 0x%02x" % (self.modemstate_mask,))
cliechti130d1f02009-08-04 02:10:58 +00001275 elif suboption[1:2] == PURGE_DATA:
1276 if suboption[2:3] == PURGE_RECEIVE_BUFFER:
1277 self.serial.flushInput()
cliechti6a300772009-08-12 02:28:56 +00001278 if self.logger:
1279 self.logger.info("purge in")
cliechti130d1f02009-08-04 02:10:58 +00001280 self.rfc2217SendSubnegotiation(SERVER_PURGE_DATA, PURGE_RECEIVE_BUFFER)
1281 elif suboption[2:3] == PURGE_TRANSMIT_BUFFER:
1282 self.serial.flushOutput()
cliechti6a300772009-08-12 02:28:56 +00001283 if self.logger:
1284 self.logger.info("purge out")
cliechti130d1f02009-08-04 02:10:58 +00001285 self.rfc2217SendSubnegotiation(SERVER_PURGE_DATA, PURGE_TRANSMIT_BUFFER)
1286 elif suboption[2:3] == PURGE_BOTH_BUFFERS:
1287 self.serial.flushInput()
1288 self.serial.flushOutput()
cliechti6a300772009-08-12 02:28:56 +00001289 if self.logger:
1290 self.logger.info("purge both")
cliechti130d1f02009-08-04 02:10:58 +00001291 self.rfc2217SendSubnegotiation(SERVER_PURGE_DATA, PURGE_BOTH_BUFFERS)
1292 else:
cliechti6a300772009-08-12 02:28:56 +00001293 if self.logger:
1294 self.logger.error("undefined PURGE_DATA: %r" % list(suboption[2:]))
cliechti130d1f02009-08-04 02:10:58 +00001295 else:
cliechti6a300772009-08-12 02:28:56 +00001296 if self.logger:
1297 self.logger.error("undefined COM_PORT_OPTION: %r" % list(suboption[1:]))
cliechti130d1f02009-08-04 02:10:58 +00001298 else:
cliechti6a300772009-08-12 02:28:56 +00001299 if self.logger:
1300 self.logger.warning("unknown subnegotiation: %r" % (suboption,))
cliechti130d1f02009-08-04 02:10:58 +00001301
1302
1303# simple client test
cliechti8099bed2009-08-01 23:59:18 +00001304if __name__ == '__main__':
1305 import sys
1306 s = Serial('rfc2217://localhost:7000', 115200)
1307 sys.stdout.write('%s\n' % s)
1308
cliechti2b929b72009-08-02 23:49:02 +00001309 #~ s.baudrate = 1898
1310
cliechti8099bed2009-08-01 23:59:18 +00001311 sys.stdout.write("write...\n")
1312 s.write("hello\n")
1313 s.flush()
cliechti8099bed2009-08-01 23:59:18 +00001314 sys.stdout.write("read: %s\n" % s.read(5))
1315
1316 #~ s.baudrate = 19200
1317 #~ s.databits = 7
1318 s.close()