blob: 4aeecd5c267c56a519bac9ae2e8ff474474ce7e6 [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
cliechti39cfb7b2011-08-22 00:30:07 +000062from serial.serialutil import *
cliechti8099bed2009-08-01 23:59:18 +000063import time
64import struct
65import socket
66import threading
cliechti5cc3eb12009-08-11 23:04:30 +000067import logging
cliechti8099bed2009-08-01 23:59:18 +000068
Chris Liechtid2146002015-08-04 16:57:16 +020069try:
70 import Queue
71except ImportError:
72 import queue as Queue
73
cliechti8099bed2009-08-01 23:59:18 +000074# port string is expected to be something like this:
75# rfc2217://host:port
76# host may be an IP or including domain, whatever.
77# port is 0...65535
78
cliechti86844e82009-08-12 00:05:33 +000079# map log level names to constants. used in fromURL()
cliechti5cc3eb12009-08-11 23:04:30 +000080LOGGER_LEVELS = {
81 'debug': logging.DEBUG,
82 'info': logging.INFO,
83 'warning': logging.WARNING,
84 'error': logging.ERROR,
cliechti5cc3eb12009-08-11 23:04:30 +000085 }
86
87
cliechti8099bed2009-08-01 23:59:18 +000088# telnet protocol characters
89IAC = to_bytes([255]) # Interpret As Command
90DONT = to_bytes([254])
91DO = to_bytes([253])
92WONT = to_bytes([252])
93WILL = to_bytes([251])
94IAC_DOUBLED = to_bytes([IAC, IAC])
95
96SE = to_bytes([240]) # Subnegotiation End
97NOP = to_bytes([241]) # No Operation
98DM = to_bytes([242]) # Data Mark
99BRK = to_bytes([243]) # Break
100IP = to_bytes([244]) # Interrupt process
101AO = to_bytes([245]) # Abort output
102AYT = to_bytes([246]) # Are You There
103EC = to_bytes([247]) # Erase Character
104EL = to_bytes([248]) # Erase Line
105GA = to_bytes([249]) # Go Ahead
106SB = to_bytes([250]) # Subnegotiation Begin
107
108# selected telnet options
109BINARY = to_bytes([0]) # 8-bit data path
110ECHO = to_bytes([1]) # echo
111SGA = to_bytes([3]) # suppress go ahead
112
113# RFC2217
114COM_PORT_OPTION = to_bytes([44])
115
116# Client to Access Server
cliechti8099bed2009-08-01 23:59:18 +0000117SET_BAUDRATE = to_bytes([1])
118SET_DATASIZE = to_bytes([2])
119SET_PARITY = to_bytes([3])
120SET_STOPSIZE = to_bytes([4])
121SET_CONTROL = to_bytes([5])
122NOTIFY_LINESTATE = to_bytes([6])
123NOTIFY_MODEMSTATE = to_bytes([7])
124FLOWCONTROL_SUSPEND = to_bytes([8])
125FLOWCONTROL_RESUME = to_bytes([9])
126SET_LINESTATE_MASK = to_bytes([10])
127SET_MODEMSTATE_MASK = to_bytes([11])
128PURGE_DATA = to_bytes([12])
129
130SERVER_SET_BAUDRATE = to_bytes([101])
131SERVER_SET_DATASIZE = to_bytes([102])
132SERVER_SET_PARITY = to_bytes([103])
133SERVER_SET_STOPSIZE = to_bytes([104])
134SERVER_SET_CONTROL = to_bytes([105])
135SERVER_NOTIFY_LINESTATE = to_bytes([106])
136SERVER_NOTIFY_MODEMSTATE = to_bytes([107])
137SERVER_FLOWCONTROL_SUSPEND = to_bytes([108])
138SERVER_FLOWCONTROL_RESUME = to_bytes([109])
139SERVER_SET_LINESTATE_MASK = to_bytes([110])
140SERVER_SET_MODEMSTATE_MASK = to_bytes([111])
141SERVER_PURGE_DATA = to_bytes([112])
142
143RFC2217_ANSWER_MAP = {
144 SET_BAUDRATE: SERVER_SET_BAUDRATE,
145 SET_DATASIZE: SERVER_SET_DATASIZE,
146 SET_PARITY: SERVER_SET_PARITY,
147 SET_STOPSIZE: SERVER_SET_STOPSIZE,
148 SET_CONTROL: SERVER_SET_CONTROL,
149 NOTIFY_LINESTATE: SERVER_NOTIFY_LINESTATE,
150 NOTIFY_MODEMSTATE: SERVER_NOTIFY_MODEMSTATE,
151 FLOWCONTROL_SUSPEND: SERVER_FLOWCONTROL_SUSPEND,
152 FLOWCONTROL_RESUME: SERVER_FLOWCONTROL_RESUME,
153 SET_LINESTATE_MASK: SERVER_SET_LINESTATE_MASK,
154 SET_MODEMSTATE_MASK: SERVER_SET_MODEMSTATE_MASK,
155 PURGE_DATA: SERVER_PURGE_DATA,
156}
157
158SET_CONTROL_REQ_FLOW_SETTING = to_bytes([0]) # Request Com Port Flow Control Setting (outbound/both)
159SET_CONTROL_USE_NO_FLOW_CONTROL = to_bytes([1]) # Use No Flow Control (outbound/both)
160SET_CONTROL_USE_SW_FLOW_CONTROL = to_bytes([2]) # Use XON/XOFF Flow Control (outbound/both)
161SET_CONTROL_USE_HW_FLOW_CONTROL = to_bytes([3]) # Use HARDWARE Flow Control (outbound/both)
162SET_CONTROL_REQ_BREAK_STATE = to_bytes([4]) # Request BREAK State
163SET_CONTROL_BREAK_ON = to_bytes([5]) # Set BREAK State ON
164SET_CONTROL_BREAK_OFF = to_bytes([6]) # Set BREAK State OFF
165SET_CONTROL_REQ_DTR = to_bytes([7]) # Request DTR Signal State
166SET_CONTROL_DTR_ON = to_bytes([8]) # Set DTR Signal State ON
167SET_CONTROL_DTR_OFF = to_bytes([9]) # Set DTR Signal State OFF
168SET_CONTROL_REQ_RTS = to_bytes([10]) # Request RTS Signal State
169SET_CONTROL_RTS_ON = to_bytes([11]) # Set RTS Signal State ON
170SET_CONTROL_RTS_OFF = to_bytes([12]) # Set RTS Signal State OFF
171SET_CONTROL_REQ_FLOW_SETTING_IN = to_bytes([13]) # Request Com Port Flow Control Setting (inbound)
172SET_CONTROL_USE_NO_FLOW_CONTROL_IN = to_bytes([14]) # Use No Flow Control (inbound)
173SET_CONTROL_USE_SW_FLOW_CONTOL_IN = to_bytes([15]) # Use XON/XOFF Flow Control (inbound)
174SET_CONTROL_USE_HW_FLOW_CONTOL_IN = to_bytes([16]) # Use HARDWARE Flow Control (inbound)
175SET_CONTROL_USE_DCD_FLOW_CONTROL = to_bytes([17]) # Use DCD Flow Control (outbound/both)
176SET_CONTROL_USE_DTR_FLOW_CONTROL = to_bytes([18]) # Use DTR Flow Control (inbound)
177SET_CONTROL_USE_DSR_FLOW_CONTROL = to_bytes([19]) # Use DSR Flow Control (outbound/both)
178
cliechti044d8662009-08-11 21:40:31 +0000179LINESTATE_MASK_TIMEOUT = 128 # Time-out Error
180LINESTATE_MASK_SHIFTREG_EMPTY = 64 # Transfer Shift Register Empty
181LINESTATE_MASK_TRANSREG_EMPTY = 32 # Transfer Holding Register Empty
182LINESTATE_MASK_BREAK_DETECT = 16 # Break-detect Error
183LINESTATE_MASK_FRAMING_ERROR = 8 # Framing Error
184LINESTATE_MASK_PARTIY_ERROR = 4 # Parity Error
185LINESTATE_MASK_OVERRUN_ERROR = 2 # Overrun Error
186LINESTATE_MASK_DATA_READY = 1 # Data Ready
cliechti8099bed2009-08-01 23:59:18 +0000187
cliechti044d8662009-08-11 21:40:31 +0000188MODEMSTATE_MASK_CD = 128 # Receive Line Signal Detect (also known as Carrier Detect)
189MODEMSTATE_MASK_RI = 64 # Ring Indicator
190MODEMSTATE_MASK_DSR = 32 # Data-Set-Ready Signal State
191MODEMSTATE_MASK_CTS = 16 # Clear-To-Send Signal State
192MODEMSTATE_MASK_CD_CHANGE = 8 # Delta Receive Line Signal Detect
193MODEMSTATE_MASK_RI_CHANGE = 4 # Trailing-edge Ring Detector
194MODEMSTATE_MASK_DSR_CHANGE = 2 # Delta Data-Set-Ready
195MODEMSTATE_MASK_CTS_CHANGE = 1 # Delta Clear-To-Send
cliechti8099bed2009-08-01 23:59:18 +0000196
197PURGE_RECEIVE_BUFFER = to_bytes([1]) # Purge access server receive data buffer
198PURGE_TRANSMIT_BUFFER = to_bytes([2]) # Purge access server transmit data buffer
199PURGE_BOTH_BUFFERS = to_bytes([3]) # Purge both the access server receive data buffer and the access server transmit data buffer
200
201
202RFC2217_PARITY_MAP = {
203 PARITY_NONE: 1,
204 PARITY_ODD: 2,
205 PARITY_EVEN: 3,
206 PARITY_MARK: 4,
207 PARITY_SPACE: 5,
208}
cliechti130d1f02009-08-04 02:10:58 +0000209RFC2217_REVERSE_PARITY_MAP = dict((v,k) for k,v in RFC2217_PARITY_MAP.items())
cliechti8099bed2009-08-01 23:59:18 +0000210
211RFC2217_STOPBIT_MAP = {
212 STOPBITS_ONE: 1,
213 STOPBITS_ONE_POINT_FIVE: 3,
214 STOPBITS_TWO: 2,
215}
cliechti130d1f02009-08-04 02:10:58 +0000216RFC2217_REVERSE_STOPBIT_MAP = dict((v,k) for k,v in RFC2217_STOPBIT_MAP.items())
cliechti8099bed2009-08-01 23:59:18 +0000217
cliechti130d1f02009-08-04 02:10:58 +0000218# Telnet filter states
219M_NORMAL = 0
220M_IAC_SEEN = 1
221M_NEGOTIATE = 2
cliechti8099bed2009-08-01 23:59:18 +0000222
cliechti130d1f02009-08-04 02:10:58 +0000223# TelnetOption and TelnetSubnegotiation states
cliechtiac205322009-08-02 20:40:21 +0000224REQUESTED = 'REQUESTED'
225ACTIVE = 'ACTIVE'
226INACTIVE = 'INACTIVE'
227REALLY_INACTIVE = 'REALLY_INACTIVE'
228
229class TelnetOption(object):
cliechti1ef7e3e2009-08-03 02:38:43 +0000230 """Manage a single telnet option, keeps track of DO/DONT WILL/WONT."""
231
cliechti86b593e2009-08-05 16:28:12 +0000232 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 +0000233 """\
234 Initialize option.
cliechti1ef7e3e2009-08-03 02:38:43 +0000235 :param connection: connection used to transmit answers
236 :param name: a readable name for debug outputs
237 :param send_yes: what to send when option is to be enabled.
238 :param send_no: what to send when option is to be disabled.
239 :param ack_yes: what to expect when remote agrees on option.
240 :param ack_no: what to expect when remote disagrees on option.
241 :param initial_state: options initialized with REQUESTED are tried to
242 be enabled on startup. use INACTIVE for all others.
243 """
cliechti2b929b72009-08-02 23:49:02 +0000244 self.connection = connection
cliechtiac205322009-08-02 20:40:21 +0000245 self.name = name
246 self.option = option
247 self.send_yes = send_yes
248 self.send_no = send_no
249 self.ack_yes = ack_yes
250 self.ack_no = ack_no
251 self.state = initial_state
252 self.active = False
cliechti86b593e2009-08-05 16:28:12 +0000253 self.activation_callback = activation_callback
cliechtiac205322009-08-02 20:40:21 +0000254
255 def __repr__(self):
cliechti1ef7e3e2009-08-03 02:38:43 +0000256 """String for debug outputs"""
cliechtiac205322009-08-02 20:40:21 +0000257 return "%s:%s(%s)" % (self.name, self.active, self.state)
258
cliechti2b929b72009-08-02 23:49:02 +0000259 def process_incoming(self, command):
cliechti7d448562014-08-03 21:57:45 +0000260 """\
261 A DO/DONT/WILL/WONT was received for this option, update state and
262 answer when needed.
263 """
cliechtiac205322009-08-02 20:40:21 +0000264 if command == self.ack_yes:
265 if self.state is REQUESTED:
266 self.state = ACTIVE
267 self.active = True
cliechti86b593e2009-08-05 16:28:12 +0000268 if self.activation_callback is not None:
269 self.activation_callback()
cliechtiac205322009-08-02 20:40:21 +0000270 elif self.state is ACTIVE:
271 pass
272 elif self.state is INACTIVE:
273 self.state = ACTIVE
cliechti1ef7e3e2009-08-03 02:38:43 +0000274 self.connection.telnetSendOption(self.send_yes, self.option)
cliechtiac205322009-08-02 20:40:21 +0000275 self.active = True
cliechti86b593e2009-08-05 16:28:12 +0000276 if self.activation_callback is not None:
277 self.activation_callback()
cliechtiac205322009-08-02 20:40:21 +0000278 elif self.state is REALLY_INACTIVE:
cliechti1ef7e3e2009-08-03 02:38:43 +0000279 self.connection.telnetSendOption(self.send_no, self.option)
cliechtiac205322009-08-02 20:40:21 +0000280 else:
281 raise ValueError('option in illegal state %r' % self)
282 elif command == self.ack_no:
283 if self.state is REQUESTED:
284 self.state = INACTIVE
285 self.active = False
286 elif self.state is ACTIVE:
287 self.state = INACTIVE
cliechti1ef7e3e2009-08-03 02:38:43 +0000288 self.connection.telnetSendOption(self.send_no, self.option)
cliechtiac205322009-08-02 20:40:21 +0000289 self.active = False
290 elif self.state is INACTIVE:
291 pass
292 elif self.state is REALLY_INACTIVE:
293 pass
294 else:
295 raise ValueError('option in illegal state %r' % self)
296
297
cliechti2b929b72009-08-02 23:49:02 +0000298class TelnetSubnegotiation(object):
cliechtieada4fd2013-07-31 16:26:07 +0000299 """\
300 A object to handle subnegotiation of options. In this case actually
301 sub-sub options for RFC 2217. It is used to track com port options.
302 """
cliechti2b929b72009-08-02 23:49:02 +0000303
304 def __init__(self, connection, name, option, ack_option=None):
305 if ack_option is None: ack_option = option
306 self.connection = connection
307 self.name = name
308 self.option = option
309 self.value = None
310 self.ack_option = ack_option
311 self.state = INACTIVE
312
313 def __repr__(self):
cliechti044d8662009-08-11 21:40:31 +0000314 """String for debug outputs."""
cliechti2b929b72009-08-02 23:49:02 +0000315 return "%s:%s" % (self.name, self.state)
316
317 def set(self, value):
cliechtieada4fd2013-07-31 16:26:07 +0000318 """\
cliechti7d448562014-08-03 21:57:45 +0000319 Request a change of the value. a request is sent to the server. if
cliechti2b929b72009-08-02 23:49:02 +0000320 the client needs to know if the change is performed he has to check the
cliechtieada4fd2013-07-31 16:26:07 +0000321 state of this object.
322 """
cliechti2b929b72009-08-02 23:49:02 +0000323 self.value = value
324 self.state = REQUESTED
cliechti1ef7e3e2009-08-03 02:38:43 +0000325 self.connection.rfc2217SendSubnegotiation(self.option, self.value)
cliechti6a300772009-08-12 02:28:56 +0000326 if self.connection.logger:
327 self.connection.logger.debug("SB Requesting %s -> %r" % (self.name, self.value))
cliechti2b929b72009-08-02 23:49:02 +0000328
329 def isReady(self):
cliechtieada4fd2013-07-31 16:26:07 +0000330 """\
cliechti7d448562014-08-03 21:57:45 +0000331 Check if answer from server has been received. when server rejects
cliechtieada4fd2013-07-31 16:26:07 +0000332 the change, raise a ValueError.
333 """
cliechti2b929b72009-08-02 23:49:02 +0000334 if self.state == REALLY_INACTIVE:
335 raise ValueError("remote rejected value for option %r" % (self.name))
336 return self.state == ACTIVE
cliechti1ef7e3e2009-08-03 02:38:43 +0000337 # add property to have a similar interface as TelnetOption
cliechti2b929b72009-08-02 23:49:02 +0000338 active = property(isReady)
339
cliechti044d8662009-08-11 21:40:31 +0000340 def wait(self, timeout=3):
cliechtieada4fd2013-07-31 16:26:07 +0000341 """\
cliechti7d448562014-08-03 21:57:45 +0000342 Wait until the subnegotiation has been acknowledged or timeout. It
cliechti1ef7e3e2009-08-03 02:38:43 +0000343 can also throw a value error when the answer from the server does not
cliechtieada4fd2013-07-31 16:26:07 +0000344 match the value sent.
345 """
cliechti044d8662009-08-11 21:40:31 +0000346 timeout_time = time.time() + timeout
cliechti2b929b72009-08-02 23:49:02 +0000347 while time.time() < timeout_time:
348 time.sleep(0.05) # prevent 100% CPU load
349 if self.isReady():
350 break
351 else:
352 raise SerialException("timeout while waiting for option %r" % (self.name))
353
354 def checkAnswer(self, suboption):
cliechtieada4fd2013-07-31 16:26:07 +0000355 """\
cliechti7d448562014-08-03 21:57:45 +0000356 Check an incoming subnegotiation block. The parameter already has
cliechtieada4fd2013-07-31 16:26:07 +0000357 cut off the header like sub option number and com port option value.
358 """
cliechti2b929b72009-08-02 23:49:02 +0000359 if self.value == suboption[:len(self.value)]:
360 self.state = ACTIVE
361 else:
362 # error propagation done in isReady
363 self.state = REALLY_INACTIVE
cliechti6a300772009-08-12 02:28:56 +0000364 if self.connection.logger:
365 self.connection.logger.debug("SB Answer %s -> %r -> %s" % (self.name, suboption, self.state))
cliechti2b929b72009-08-02 23:49:02 +0000366
367
Chris Liechtief6b7b42015-08-06 22:19:26 +0200368class Serial(SerialBase):
cliechti044d8662009-08-11 21:40:31 +0000369 """Serial port implementation for RFC 2217 remote serial ports."""
cliechti8099bed2009-08-01 23:59:18 +0000370
371 BAUDRATES = (50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800,
372 9600, 19200, 38400, 57600, 115200)
373
374 def open(self):
cliechtieada4fd2013-07-31 16:26:07 +0000375 """\
376 Open port with current settings. This may throw a SerialException
377 if the port cannot be opened.
378 """
cliechti6a300772009-08-12 02:28:56 +0000379 self.logger = None
cliechti81c54762009-08-03 23:53:27 +0000380 self._ignore_set_control_answer = False
cliechti7cb78e82009-08-05 15:47:57 +0000381 self._poll_modem_state = False
cliechtidfe2d272009-08-10 22:19:41 +0000382 self._network_timeout = 3
cliechti8099bed2009-08-01 23:59:18 +0000383 if self._port is None:
384 raise SerialException("Port must be configured before it can be used.")
cliechti8f69e702011-03-19 00:22:32 +0000385 if self._isOpen:
386 raise SerialException("Port is already open.")
cliechti8099bed2009-08-01 23:59:18 +0000387 try:
388 self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
389 self._socket.connect(self.fromURL(self.portstr))
cliechti6a300772009-08-12 02:28:56 +0000390 self._socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
Chris Liechti68340d72015-08-03 14:15:48 +0200391 except Exception as msg:
cliechti8099bed2009-08-01 23:59:18 +0000392 self._socket = None
393 raise SerialException("Could not open port %s: %s" % (self.portstr, msg))
394
cliechti1ef7e3e2009-08-03 02:38:43 +0000395 self._socket.settimeout(5) # XXX good value?
cliechti8099bed2009-08-01 23:59:18 +0000396
cliechti1ef7e3e2009-08-03 02:38:43 +0000397 # use a thread save queue as buffer. it also simplifies implementing
398 # the read timeout
cliechti8099bed2009-08-01 23:59:18 +0000399 self._read_buffer = Queue.Queue()
cliechti81c54762009-08-03 23:53:27 +0000400 # to ensure that user writes does not interfere with internal
401 # telnet/rfc2217 options establish a lock
402 self._write_lock = threading.Lock()
cliechtiac205322009-08-02 20:40:21 +0000403 # name the following separately so that, below, a check can be easily done
404 mandadory_options = [
cliechti2b929b72009-08-02 23:49:02 +0000405 TelnetOption(self, 'we-BINARY', BINARY, WILL, WONT, DO, DONT, INACTIVE),
cliechti2b929b72009-08-02 23:49:02 +0000406 TelnetOption(self, 'we-RFC2217', COM_PORT_OPTION, WILL, WONT, DO, DONT, REQUESTED),
cliechtiac205322009-08-02 20:40:21 +0000407 ]
408 # all supported telnet options
409 self._telnet_options = [
cliechtia29275e2009-08-03 00:08:04 +0000410 TelnetOption(self, 'ECHO', ECHO, DO, DONT, WILL, WONT, REQUESTED),
cliechti2b929b72009-08-02 23:49:02 +0000411 TelnetOption(self, 'we-SGA', SGA, WILL, WONT, DO, DONT, REQUESTED),
412 TelnetOption(self, 'they-SGA', SGA, DO, DONT, WILL, WONT, REQUESTED),
cliechti81c54762009-08-03 23:53:27 +0000413 TelnetOption(self, 'they-BINARY', BINARY, DO, DONT, WILL, WONT, INACTIVE),
414 TelnetOption(self, 'they-RFC2217', COM_PORT_OPTION, DO, DONT, WILL, WONT, REQUESTED),
cliechtiac205322009-08-02 20:40:21 +0000415 ] + mandadory_options
cliechti044d8662009-08-11 21:40:31 +0000416 # RFC 2217 specific states
cliechti2b929b72009-08-02 23:49:02 +0000417 # COM port settings
418 self._rfc2217_port_settings = {
419 'baudrate': TelnetSubnegotiation(self, 'baudrate', SET_BAUDRATE, SERVER_SET_BAUDRATE),
420 'datasize': TelnetSubnegotiation(self, 'datasize', SET_DATASIZE, SERVER_SET_DATASIZE),
421 'parity': TelnetSubnegotiation(self, 'parity', SET_PARITY, SERVER_SET_PARITY),
422 'stopsize': TelnetSubnegotiation(self, 'stopsize', SET_STOPSIZE, SERVER_SET_STOPSIZE),
423 }
cliechticb20a4f2011-04-25 02:25:54 +0000424 # There are more subnegotiation objects, combine all in one dictionary
cliechti2b929b72009-08-02 23:49:02 +0000425 # for easy access
426 self._rfc2217_options = {
427 'purge': TelnetSubnegotiation(self, 'purge', PURGE_DATA, SERVER_PURGE_DATA),
cliechti81c54762009-08-03 23:53:27 +0000428 'control': TelnetSubnegotiation(self, 'control', SET_CONTROL, SERVER_SET_CONTROL),
cliechti2b929b72009-08-02 23:49:02 +0000429 }
430 self._rfc2217_options.update(self._rfc2217_port_settings)
431 # cache for line and modem states that the server sends to us
cliechti8099bed2009-08-01 23:59:18 +0000432 self._linestate = 0
cliechti7cb78e82009-08-05 15:47:57 +0000433 self._modemstate = None
434 self._modemstate_expires = 0
cliechti044d8662009-08-11 21:40:31 +0000435 # RFC 2217 flow control between server and client
cliechti672d0292009-08-03 02:01:57 +0000436 self._remote_suspend_flow = False
cliechti8099bed2009-08-01 23:59:18 +0000437
cliechti1ef7e3e2009-08-03 02:38:43 +0000438 self._thread = threading.Thread(target=self._telnetReadLoop)
cliechti8099bed2009-08-01 23:59:18 +0000439 self._thread.setDaemon(True)
cliechti5cc3eb12009-08-11 23:04:30 +0000440 self._thread.setName('pySerial RFC 2217 reader thread for %s' % (self._port,))
cliechti8099bed2009-08-01 23:59:18 +0000441 self._thread.start()
442
cliechti044d8662009-08-11 21:40:31 +0000443 # negotiate Telnet/RFC 2217 -> send initial requests
cliechtiac205322009-08-02 20:40:21 +0000444 for option in self._telnet_options:
445 if option.state is REQUESTED:
cliechti1ef7e3e2009-08-03 02:38:43 +0000446 self.telnetSendOption(option.send_yes, option.option)
cliechtiac205322009-08-02 20:40:21 +0000447 # now wait until important options are negotiated
cliechtidfe2d272009-08-10 22:19:41 +0000448 timeout_time = time.time() + self._network_timeout
cliechtiac205322009-08-02 20:40:21 +0000449 while time.time() < timeout_time:
cliechtiac205322009-08-02 20:40:21 +0000450 time.sleep(0.05) # prevent 100% CPU load
cliechti7c213a92014-07-31 15:29:34 +0000451 if sum(o.active for o in mandadory_options) == sum(o.state != INACTIVE for o in mandadory_options):
cliechti2b929b72009-08-02 23:49:02 +0000452 break
453 else:
454 raise SerialException("Remote does not seem to support RFC2217 or BINARY mode %r" % mandadory_options)
cliechti6a300772009-08-12 02:28:56 +0000455 if self.logger:
456 self.logger.info("Negotiated options: %s" % self._telnet_options)
cliechti8099bed2009-08-01 23:59:18 +0000457
cliechti044d8662009-08-11 21:40:31 +0000458 # fine, go on, set RFC 2271 specific things
cliechti8099bed2009-08-01 23:59:18 +0000459 self._reconfigurePort()
cliechti2b929b72009-08-02 23:49:02 +0000460 # all things set up get, now a clean start
cliechti8099bed2009-08-01 23:59:18 +0000461 self._isOpen = True
462 if not self._rtscts:
463 self.setRTS(True)
464 self.setDTR(True)
465 self.flushInput()
466 self.flushOutput()
467
468 def _reconfigurePort(self):
469 """Set communication parameters on opened port."""
470 if self._socket is None:
471 raise SerialException("Can only operate on open ports")
472
cliechti8099bed2009-08-01 23:59:18 +0000473 # if self._timeout != 0 and self._interCharTimeout is not None:
cliechti8099bed2009-08-01 23:59:18 +0000474 # XXX
475
476 if self._writeTimeout is not None:
477 raise NotImplementedError('writeTimeout is currently not supported')
cliechti2b929b72009-08-02 23:49:02 +0000478 # XXX
cliechti8099bed2009-08-01 23:59:18 +0000479
cliechti2b929b72009-08-02 23:49:02 +0000480 # Setup the connection
cliechti1ef7e3e2009-08-03 02:38:43 +0000481 # to get good performance, all parameter changes are sent first...
Chris Liechti01587b12015-08-05 02:39:32 +0200482 if not 0 < self._baudrate < 2**32:
cliechti81c54762009-08-03 23:53:27 +0000483 raise ValueError("invalid baudrate: %r" % (self._baudrate))
Chris Liechti01587b12015-08-05 02:39:32 +0200484 self._rfc2217_port_settings['baudrate'].set(struct.pack(b'!I', self._baudrate))
485 self._rfc2217_port_settings['datasize'].set(struct.pack(b'!B', self._bytesize))
486 self._rfc2217_port_settings['parity'].set(struct.pack(b'!B', RFC2217_PARITY_MAP[self._parity]))
487 self._rfc2217_port_settings['stopsize'].set(struct.pack(b'!B', RFC2217_STOPBIT_MAP[self._stopbits]))
cliechti8099bed2009-08-01 23:59:18 +0000488
cliechti2b929b72009-08-02 23:49:02 +0000489 # and now wait until parameters are active
490 items = self._rfc2217_port_settings.values()
cliechti6a300772009-08-12 02:28:56 +0000491 if self.logger:
492 self.logger.debug("Negotiating settings: %s" % (items,))
cliechtidfe2d272009-08-10 22:19:41 +0000493 timeout_time = time.time() + self._network_timeout
cliechti2b929b72009-08-02 23:49:02 +0000494 while time.time() < timeout_time:
495 time.sleep(0.05) # prevent 100% CPU load
496 if sum(o.active for o in items) == len(items):
497 break
498 else:
499 raise SerialException("Remote does not accept parameter change (RFC2217): %r" % items)
cliechti6a300772009-08-12 02:28:56 +0000500 if self.logger:
501 self.logger.info("Negotiated settings: %s" % (items,))
cliechti8099bed2009-08-01 23:59:18 +0000502
503 if self._rtscts and self._xonxoff:
cliechti1ef7e3e2009-08-03 02:38:43 +0000504 raise ValueError('xonxoff and rtscts together are not supported')
cliechti8099bed2009-08-01 23:59:18 +0000505 elif self._rtscts:
cliechti1ef7e3e2009-08-03 02:38:43 +0000506 self.rfc2217SetControl(SET_CONTROL_USE_HW_FLOW_CONTROL)
cliechti8099bed2009-08-01 23:59:18 +0000507 elif self._xonxoff:
cliechti1ef7e3e2009-08-03 02:38:43 +0000508 self.rfc2217SetControl(SET_CONTROL_USE_SW_FLOW_CONTROL)
cliechti8099bed2009-08-01 23:59:18 +0000509 else:
cliechti1ef7e3e2009-08-03 02:38:43 +0000510 self.rfc2217SetControl(SET_CONTROL_USE_NO_FLOW_CONTROL)
cliechti8099bed2009-08-01 23:59:18 +0000511
512 def close(self):
513 """Close port"""
514 if self._isOpen:
515 if self._socket:
516 try:
517 self._socket.shutdown(socket.SHUT_RDWR)
518 self._socket.close()
519 except:
520 # ignore errors.
521 pass
522 self._socket = None
523 if self._thread:
524 self._thread.join()
525 self._isOpen = False
526 # in case of quick reconnects, give the server some time
527 time.sleep(0.3)
528
529 def makeDeviceName(self, port):
530 raise SerialException("there is no sensible way to turn numbers into URLs")
531
532 def fromURL(self, url):
cliechti1ef7e3e2009-08-03 02:38:43 +0000533 """extract host and port from an URL string"""
cliechti8099bed2009-08-01 23:59:18 +0000534 if url.lower().startswith("rfc2217://"): url = url[10:]
535 try:
cliechti81c54762009-08-03 23:53:27 +0000536 # is there a "path" (our options)?
537 if '/' in url:
538 # cut away options
539 url, options = url.split('/', 1)
540 # process options now, directly altering self
541 for option in options.split('/'):
cliechtidfe2d272009-08-10 22:19:41 +0000542 if '=' in option:
543 option, value = option.split('=', 1)
544 else:
545 value = None
cliechti5cc3eb12009-08-11 23:04:30 +0000546 if option == 'logging':
547 logging.basicConfig() # XXX is that good to call it here?
cliechti6a300772009-08-12 02:28:56 +0000548 self.logger = logging.getLogger('pySerial.rfc2217')
549 self.logger.setLevel(LOGGER_LEVELS[value])
550 self.logger.debug('enabled logging')
cliechti81c54762009-08-03 23:53:27 +0000551 elif option == 'ign_set_control':
552 self._ignore_set_control_answer = True
cliechti7cb78e82009-08-05 15:47:57 +0000553 elif option == 'poll_modem':
554 self._poll_modem_state = True
cliechtidfe2d272009-08-10 22:19:41 +0000555 elif option == 'timeout':
556 self._network_timeout = float(value)
cliechti81c54762009-08-03 23:53:27 +0000557 else:
558 raise ValueError('unknown option: %r' % (option,))
559 # get host and port
cliechti8099bed2009-08-01 23:59:18 +0000560 host, port = url.split(':', 1) # may raise ValueError because of unpacking
561 port = int(port) # and this if it's not a number
562 if not 0 <= port < 65536: raise ValueError("port not in range 0...65535")
Chris Liechti68340d72015-08-03 14:15:48 +0200563 except ValueError as e:
cliechti81c54762009-08-03 23:53:27 +0000564 raise SerialException('expected a string in the form "[rfc2217://]<host>:<port>[/option[/option...]]": %s' % e)
cliechti8099bed2009-08-01 23:59:18 +0000565 return (host, port)
566
567 # - - - - - - - - - - - - - - - - - - - - - - - -
568
569 def inWaiting(self):
570 """Return the number of characters currently in the input buffer."""
571 if not self._isOpen: raise portNotOpenError
572 return self._read_buffer.qsize()
573
574 def read(self, size=1):
cliechtieada4fd2013-07-31 16:26:07 +0000575 """\
576 Read size bytes from the serial port. If a timeout is set it may
cliechti8099bed2009-08-01 23:59:18 +0000577 return less characters as requested. With no timeout it will block
cliechtieada4fd2013-07-31 16:26:07 +0000578 until the requested number of bytes is read.
579 """
cliechti8099bed2009-08-01 23:59:18 +0000580 if not self._isOpen: raise portNotOpenError
581 data = bytearray()
582 try:
583 while len(data) < size:
cliechti81c54762009-08-03 23:53:27 +0000584 if self._thread is None:
585 raise SerialException('connection failed (reader thread died)')
Chris Liechti01587b12015-08-05 02:39:32 +0200586 data += self._read_buffer.get(True, self._timeout)
cliechti8099bed2009-08-01 23:59:18 +0000587 except Queue.Empty: # -> timeout
588 pass
589 return bytes(data)
590
591 def write(self, data):
cliechtieada4fd2013-07-31 16:26:07 +0000592 """\
593 Output the given string over the serial port. Can block if the
cliechti8099bed2009-08-01 23:59:18 +0000594 connection is blocked. May raise SerialException if the connection is
cliechtieada4fd2013-07-31 16:26:07 +0000595 closed.
596 """
cliechti8099bed2009-08-01 23:59:18 +0000597 if not self._isOpen: raise portNotOpenError
Chris Liechti01587b12015-08-05 02:39:32 +0200598 with self._write_lock:
cliechti81c54762009-08-03 23:53:27 +0000599 try:
cliechti38077122013-10-16 02:57:27 +0000600 self._socket.sendall(to_bytes(data).replace(IAC, IAC_DOUBLED))
Chris Liechti68340d72015-08-03 14:15:48 +0200601 except socket.error as e:
cliechticb20a4f2011-04-25 02:25:54 +0000602 raise SerialException("connection failed (socket error): %s" % e) # XXX what exception if socket connection fails
cliechti8099bed2009-08-01 23:59:18 +0000603 return len(data)
604
605 def flushInput(self):
606 """Clear input buffer, discarding all that is in the buffer."""
607 if not self._isOpen: raise portNotOpenError
cliechti1ef7e3e2009-08-03 02:38:43 +0000608 self.rfc2217SendPurge(PURGE_RECEIVE_BUFFER)
cliechti8099bed2009-08-01 23:59:18 +0000609 # empty read buffer
610 while self._read_buffer.qsize():
611 self._read_buffer.get(False)
612
613 def flushOutput(self):
cliechtieada4fd2013-07-31 16:26:07 +0000614 """\
615 Clear output buffer, aborting the current output and
616 discarding all that is in the buffer.
617 """
cliechti8099bed2009-08-01 23:59:18 +0000618 if not self._isOpen: raise portNotOpenError
cliechti1ef7e3e2009-08-03 02:38:43 +0000619 self.rfc2217SendPurge(PURGE_TRANSMIT_BUFFER)
cliechti8099bed2009-08-01 23:59:18 +0000620
621 def sendBreak(self, duration=0.25):
cliechti7d448562014-08-03 21:57:45 +0000622 """\
623 Send break condition. Timed, returns to idle state after given
624 duration.
625 """
cliechti8099bed2009-08-01 23:59:18 +0000626 if not self._isOpen: raise portNotOpenError
627 self.setBreak(True)
628 time.sleep(duration)
629 self.setBreak(False)
630
631 def setBreak(self, level=True):
cliechtieada4fd2013-07-31 16:26:07 +0000632 """\
633 Set break: Controls TXD. When active, to transmitting is
634 possible.
635 """
cliechti8099bed2009-08-01 23:59:18 +0000636 if not self._isOpen: raise portNotOpenError
cliechti6a300772009-08-12 02:28:56 +0000637 if self.logger:
638 self.logger.info('set BREAK to %s' % ('inactive', 'active')[bool(level)])
cliechti8099bed2009-08-01 23:59:18 +0000639 if level:
cliechti1ef7e3e2009-08-03 02:38:43 +0000640 self.rfc2217SetControl(SET_CONTROL_BREAK_ON)
cliechti8099bed2009-08-01 23:59:18 +0000641 else:
cliechti1ef7e3e2009-08-03 02:38:43 +0000642 self.rfc2217SetControl(SET_CONTROL_BREAK_OFF)
cliechti8099bed2009-08-01 23:59:18 +0000643
644 def setRTS(self, level=True):
cliechti044d8662009-08-11 21:40:31 +0000645 """Set terminal status line: Request To Send."""
cliechti8099bed2009-08-01 23:59:18 +0000646 if not self._isOpen: raise portNotOpenError
cliechti6a300772009-08-12 02:28:56 +0000647 if self.logger:
648 self.logger.info('set RTS to %s' % ('inactive', 'active')[bool(level)])
cliechti8099bed2009-08-01 23:59:18 +0000649 if level:
cliechti1ef7e3e2009-08-03 02:38:43 +0000650 self.rfc2217SetControl(SET_CONTROL_RTS_ON)
cliechti8099bed2009-08-01 23:59:18 +0000651 else:
cliechti1ef7e3e2009-08-03 02:38:43 +0000652 self.rfc2217SetControl(SET_CONTROL_RTS_OFF)
cliechti8099bed2009-08-01 23:59:18 +0000653
654 def setDTR(self, level=True):
cliechti044d8662009-08-11 21:40:31 +0000655 """Set terminal status line: Data Terminal Ready."""
cliechti8099bed2009-08-01 23:59:18 +0000656 if not self._isOpen: raise portNotOpenError
cliechti6a300772009-08-12 02:28:56 +0000657 if self.logger:
658 self.logger.info('set DTR to %s' % ('inactive', 'active')[bool(level)])
cliechti8099bed2009-08-01 23:59:18 +0000659 if level:
cliechti1ef7e3e2009-08-03 02:38:43 +0000660 self.rfc2217SetControl(SET_CONTROL_DTR_ON)
cliechti8099bed2009-08-01 23:59:18 +0000661 else:
cliechti1ef7e3e2009-08-03 02:38:43 +0000662 self.rfc2217SetControl(SET_CONTROL_DTR_OFF)
cliechti8099bed2009-08-01 23:59:18 +0000663
664 def getCTS(self):
cliechti044d8662009-08-11 21:40:31 +0000665 """Read terminal status line: Clear To Send."""
cliechti8099bed2009-08-01 23:59:18 +0000666 if not self._isOpen: raise portNotOpenError
cliechti7cb78e82009-08-05 15:47:57 +0000667 return bool(self.getModemState() & MODEMSTATE_MASK_CTS)
cliechti8099bed2009-08-01 23:59:18 +0000668
669 def getDSR(self):
cliechti044d8662009-08-11 21:40:31 +0000670 """Read terminal status line: Data Set Ready."""
cliechti8099bed2009-08-01 23:59:18 +0000671 if not self._isOpen: raise portNotOpenError
cliechti7cb78e82009-08-05 15:47:57 +0000672 return bool(self.getModemState() & MODEMSTATE_MASK_DSR)
cliechti8099bed2009-08-01 23:59:18 +0000673
674 def getRI(self):
cliechti044d8662009-08-11 21:40:31 +0000675 """Read terminal status line: Ring Indicator."""
cliechti8099bed2009-08-01 23:59:18 +0000676 if not self._isOpen: raise portNotOpenError
cliechti7cb78e82009-08-05 15:47:57 +0000677 return bool(self.getModemState() & MODEMSTATE_MASK_RI)
cliechti8099bed2009-08-01 23:59:18 +0000678
679 def getCD(self):
cliechti044d8662009-08-11 21:40:31 +0000680 """Read terminal status line: Carrier Detect."""
cliechti8099bed2009-08-01 23:59:18 +0000681 if not self._isOpen: raise portNotOpenError
cliechti7cb78e82009-08-05 15:47:57 +0000682 return bool(self.getModemState() & MODEMSTATE_MASK_CD)
cliechti8099bed2009-08-01 23:59:18 +0000683
684 # - - - platform specific - - -
685 # None so far
686
687 # - - - RFC2217 specific - - -
688
cliechti1ef7e3e2009-08-03 02:38:43 +0000689 def _telnetReadLoop(self):
cliechti7d448562014-08-03 21:57:45 +0000690 """Read loop for the socket."""
cliechti8099bed2009-08-01 23:59:18 +0000691 mode = M_NORMAL
692 suboption = None
cliechti81c54762009-08-03 23:53:27 +0000693 try:
694 while self._socket is not None:
695 try:
696 data = self._socket.recv(1024)
697 except socket.timeout:
698 # just need to get out of recv form time to time to check if
699 # still alive
700 continue
Chris Liechti68340d72015-08-03 14:15:48 +0200701 except socket.error as e:
cliechti81c54762009-08-03 23:53:27 +0000702 # connection fails -> terminate loop
cliechticb20a4f2011-04-25 02:25:54 +0000703 if self.logger:
704 self.logger.debug("socket error in reader thread: %s" % (e,))
cliechti81c54762009-08-03 23:53:27 +0000705 break
cliechticb20a4f2011-04-25 02:25:54 +0000706 if not data: break # lost connection
Chris Liechti01587b12015-08-05 02:39:32 +0200707 #~ for byte in data: # fails for python3 as it returns ints instead of b''
708 for x in range(len(data)):
709 byte = data[x:x+1]
cliechti81c54762009-08-03 23:53:27 +0000710 if mode == M_NORMAL:
711 # interpret as command or as data
712 if byte == IAC:
713 mode = M_IAC_SEEN
cliechti8099bed2009-08-01 23:59:18 +0000714 else:
cliechti81c54762009-08-03 23:53:27 +0000715 # store data in read buffer or sub option buffer
716 # depending on state
717 if suboption is not None:
Chris Liechti01587b12015-08-05 02:39:32 +0200718 suboption += byte
cliechti81c54762009-08-03 23:53:27 +0000719 else:
720 self._read_buffer.put(byte)
721 elif mode == M_IAC_SEEN:
722 if byte == IAC:
723 # interpret as command doubled -> insert character
724 # itself
cliechtif325c032009-12-25 16:09:49 +0000725 if suboption is not None:
Chris Liechti01587b12015-08-05 02:39:32 +0200726 suboption += IAC
cliechtif325c032009-12-25 16:09:49 +0000727 else:
728 self._read_buffer.put(IAC)
cliechti81c54762009-08-03 23:53:27 +0000729 mode = M_NORMAL
730 elif byte == SB:
731 # sub option start
732 suboption = bytearray()
733 mode = M_NORMAL
734 elif byte == SE:
735 # sub option end -> process it now
736 self._telnetProcessSubnegotiation(bytes(suboption))
737 suboption = None
738 mode = M_NORMAL
739 elif byte in (DO, DONT, WILL, WONT):
740 # negotiation
741 telnet_command = byte
742 mode = M_NEGOTIATE
743 else:
744 # other telnet commands
745 self._telnetProcessCommand(byte)
746 mode = M_NORMAL
747 elif mode == M_NEGOTIATE: # DO, DONT, WILL, WONT was received, option now following
748 self._telnetNegotiateOption(telnet_command, byte)
cliechti8099bed2009-08-01 23:59:18 +0000749 mode = M_NORMAL
cliechti81c54762009-08-03 23:53:27 +0000750 finally:
751 self._thread = None
cliechti6a300772009-08-12 02:28:56 +0000752 if self.logger:
753 self.logger.debug("read thread terminated")
cliechti8099bed2009-08-01 23:59:18 +0000754
755 # - incoming telnet commands and options
756
cliechti1ef7e3e2009-08-03 02:38:43 +0000757 def _telnetProcessCommand(self, command):
cliechti044d8662009-08-11 21:40:31 +0000758 """Process commands other than DO, DONT, WILL, WONT."""
cliechti1ef7e3e2009-08-03 02:38:43 +0000759 # Currently none. RFC2217 only uses negotiation and subnegotiation.
cliechti6a300772009-08-12 02:28:56 +0000760 if self.logger:
761 self.logger.warning("ignoring Telnet command: %r" % (command,))
cliechti8099bed2009-08-01 23:59:18 +0000762
cliechti1ef7e3e2009-08-03 02:38:43 +0000763 def _telnetNegotiateOption(self, command, option):
cliechti044d8662009-08-11 21:40:31 +0000764 """Process incoming DO, DONT, WILL, WONT."""
cliechti2b929b72009-08-02 23:49:02 +0000765 # check our registered telnet options and forward command to them
766 # they know themselves if they have to answer or not
cliechtiac205322009-08-02 20:40:21 +0000767 known = False
768 for item in self._telnet_options:
cliechti2b929b72009-08-02 23:49:02 +0000769 # can have more than one match! as some options are duplicated for
770 # 'us' and 'them'
cliechtiac205322009-08-02 20:40:21 +0000771 if item.option == option:
cliechti2b929b72009-08-02 23:49:02 +0000772 item.process_incoming(command)
cliechtiac205322009-08-02 20:40:21 +0000773 known = True
774 if not known:
775 # handle unknown options
776 # only answer to positive requests and deny them
777 if command == WILL or command == DO:
cliechti1ef7e3e2009-08-03 02:38:43 +0000778 self.telnetSendOption((command == WILL and DONT or WONT), option)
cliechti6a300772009-08-12 02:28:56 +0000779 if self.logger:
780 self.logger.warning("rejected Telnet option: %r" % (option,))
cliechtiac205322009-08-02 20:40:21 +0000781
cliechti8099bed2009-08-01 23:59:18 +0000782
cliechti1ef7e3e2009-08-03 02:38:43 +0000783 def _telnetProcessSubnegotiation(self, suboption):
cliechti044d8662009-08-11 21:40:31 +0000784 """Process subnegotiation, the data between IAC SB and IAC SE."""
cliechti8099bed2009-08-01 23:59:18 +0000785 if suboption[0:1] == COM_PORT_OPTION:
786 if suboption[1:2] == SERVER_NOTIFY_LINESTATE and len(suboption) >= 3:
cliechti672d0292009-08-03 02:01:57 +0000787 self._linestate = ord(suboption[2:3]) # ensure it is a number
cliechti6a300772009-08-12 02:28:56 +0000788 if self.logger:
789 self.logger.info("NOTIFY_LINESTATE: %s" % self._linestate)
cliechti8099bed2009-08-01 23:59:18 +0000790 elif suboption[1:2] == SERVER_NOTIFY_MODEMSTATE and len(suboption) >= 3:
cliechti672d0292009-08-03 02:01:57 +0000791 self._modemstate = ord(suboption[2:3]) # ensure it is a number
cliechti6a300772009-08-12 02:28:56 +0000792 if self.logger:
793 self.logger.info("NOTIFY_MODEMSTATE: %s" % self._modemstate)
cliechti7cb78e82009-08-05 15:47:57 +0000794 # update time when we think that a poll would make sense
795 self._modemstate_expires = time.time() + 0.3
cliechti672d0292009-08-03 02:01:57 +0000796 elif suboption[1:2] == FLOWCONTROL_SUSPEND:
797 self._remote_suspend_flow = True
798 elif suboption[1:2] == FLOWCONTROL_RESUME:
799 self._remote_suspend_flow = False
cliechti8099bed2009-08-01 23:59:18 +0000800 else:
cliechti2b929b72009-08-02 23:49:02 +0000801 for item in self._rfc2217_options.values():
802 if item.ack_option == suboption[1:2]:
cliechti81c54762009-08-03 23:53:27 +0000803 #~ print "processing COM_PORT_OPTION: %r" % list(suboption[1:])
cliechti2b929b72009-08-02 23:49:02 +0000804 item.checkAnswer(bytes(suboption[2:]))
805 break
806 else:
cliechti6a300772009-08-12 02:28:56 +0000807 if self.logger:
808 self.logger.warning("ignoring COM_PORT_OPTION: %r" % (suboption,))
cliechti8099bed2009-08-01 23:59:18 +0000809 else:
cliechti6a300772009-08-12 02:28:56 +0000810 if self.logger:
811 self.logger.warning("ignoring subnegotiation: %r" % (suboption,))
cliechti8099bed2009-08-01 23:59:18 +0000812
813 # - outgoing telnet commands and options
814
cliechti81c54762009-08-03 23:53:27 +0000815 def _internal_raw_write(self, data):
cliechti044d8662009-08-11 21:40:31 +0000816 """internal socket write with no data escaping. used to send telnet stuff."""
Chris Liechti01587b12015-08-05 02:39:32 +0200817 with self._write_lock:
cliechti81c54762009-08-03 23:53:27 +0000818 self._socket.sendall(data)
cliechti81c54762009-08-03 23:53:27 +0000819
cliechti1ef7e3e2009-08-03 02:38:43 +0000820 def telnetSendOption(self, action, option):
cliechti044d8662009-08-11 21:40:31 +0000821 """Send DO, DONT, WILL, WONT."""
cliechti81c54762009-08-03 23:53:27 +0000822 self._internal_raw_write(to_bytes([IAC, action, option]))
cliechti8099bed2009-08-01 23:59:18 +0000823
cliechtif325c032009-12-25 16:09:49 +0000824 def rfc2217SendSubnegotiation(self, option, value=''):
cliechti044d8662009-08-11 21:40:31 +0000825 """Subnegotiation of RFC2217 parameters."""
cliechtif325c032009-12-25 16:09:49 +0000826 value = value.replace(IAC, IAC_DOUBLED)
cliechti81c54762009-08-03 23:53:27 +0000827 self._internal_raw_write(to_bytes([IAC, SB, COM_PORT_OPTION, option] + list(value) + [IAC, SE]))
cliechti2b929b72009-08-02 23:49:02 +0000828
cliechti1ef7e3e2009-08-03 02:38:43 +0000829 def rfc2217SendPurge(self, value):
cliechti2b929b72009-08-02 23:49:02 +0000830 item = self._rfc2217_options['purge']
cliechti672d0292009-08-03 02:01:57 +0000831 item.set(value) # transmit desired purge type
cliechtidfe2d272009-08-10 22:19:41 +0000832 item.wait(self._network_timeout) # wait for acknowledge from the server
cliechti2b929b72009-08-02 23:49:02 +0000833
cliechti1ef7e3e2009-08-03 02:38:43 +0000834 def rfc2217SetControl(self, value):
cliechti81c54762009-08-03 23:53:27 +0000835 item = self._rfc2217_options['control']
cliechticb20a4f2011-04-25 02:25:54 +0000836 item.set(value) # transmit desired control type
cliechti81c54762009-08-03 23:53:27 +0000837 if self._ignore_set_control_answer:
838 # answers are ignored when option is set. compatibility mode for
cliechticb20a4f2011-04-25 02:25:54 +0000839 # servers that answer, but not the expected one... (or no answer
cliechti81c54762009-08-03 23:53:27 +0000840 # at all) i.e. sredird
841 time.sleep(0.1) # this helps getting the unit tests passed
842 else:
cliechtidfe2d272009-08-10 22:19:41 +0000843 item.wait(self._network_timeout) # wait for acknowledge from the server
cliechti8099bed2009-08-01 23:59:18 +0000844
cliechti1ef7e3e2009-08-03 02:38:43 +0000845 def rfc2217FlowServerReady(self):
cliechtieada4fd2013-07-31 16:26:07 +0000846 """\
847 check if server is ready to receive data. block for some time when
848 not.
849 """
cliechti672d0292009-08-03 02:01:57 +0000850 #~ if self._remote_suspend_flow:
851 #~ wait---
852
cliechti7cb78e82009-08-05 15:47:57 +0000853 def getModemState(self):
cliechtieada4fd2013-07-31 16:26:07 +0000854 """\
cliechti7d448562014-08-03 21:57:45 +0000855 get last modem state (cached value. If value is "old", request a new
856 one. This cache helps that we don't issue to many requests when e.g. all
857 status lines, one after the other is queried by the user (getCTS, getDSR
cliechtieada4fd2013-07-31 16:26:07 +0000858 etc.)
859 """
cliechti7cb78e82009-08-05 15:47:57 +0000860 # active modem state polling enabled? is the value fresh enough?
861 if self._poll_modem_state and self._modemstate_expires < time.time():
cliechti6a300772009-08-12 02:28:56 +0000862 if self.logger:
863 self.logger.debug('polling modem state')
cliechti7cb78e82009-08-05 15:47:57 +0000864 # when it is older, request an update
865 self.rfc2217SendSubnegotiation(NOTIFY_MODEMSTATE)
cliechtidfe2d272009-08-10 22:19:41 +0000866 timeout_time = time.time() + self._network_timeout
cliechti7cb78e82009-08-05 15:47:57 +0000867 while time.time() < timeout_time:
868 time.sleep(0.05) # prevent 100% CPU load
869 # when expiration time is updated, it means that there is a new
870 # value
871 if self._modemstate_expires > time.time():
872 break
Chris Liechti01587b12015-08-05 02:39:32 +0200873 else:
874 if self.logger:
875 self.logger.warning('poll for modem state failed')
cliechti7cb78e82009-08-05 15:47:57 +0000876 # even when there is a timeout, do not generate an error just
877 # return the last known value. this way we can support buggy
878 # servers that do not respond to polls, but send automatic
879 # updates.
880 if self._modemstate is not None:
cliechti6a300772009-08-12 02:28:56 +0000881 if self.logger:
882 self.logger.debug('using cached modem state')
cliechti7cb78e82009-08-05 15:47:57 +0000883 return self._modemstate
884 else:
885 # never received a notification from the server
cliechti8fb119c2009-08-05 23:39:45 +0000886 raise SerialException("remote sends no NOTIFY_MODEMSTATE")
cliechti8099bed2009-08-01 23:59:18 +0000887
cliechti5cc3eb12009-08-11 23:04:30 +0000888
cliechti595ed5b2009-08-10 01:43:32 +0000889#############################################################################
cliechti5cc3eb12009-08-11 23:04:30 +0000890# The following is code that helps implementing an RFC 2217 server.
cliechti8099bed2009-08-01 23:59:18 +0000891
cliechti8ccc2ff2009-08-05 12:44:46 +0000892class PortManager(object):
cliechtieada4fd2013-07-31 16:26:07 +0000893 """\
894 This class manages the state of Telnet and RFC 2217. It needs a serial
cliechticb20a4f2011-04-25 02:25:54 +0000895 instance and a connection to work with. Connection is expected to implement
cliechtieada4fd2013-07-31 16:26:07 +0000896 a (thread safe) write function, that writes the string to the network.
897 """
cliechti130d1f02009-08-04 02:10:58 +0000898
cliechti6a300772009-08-12 02:28:56 +0000899 def __init__(self, serial_port, connection, logger=None):
cliechti130d1f02009-08-04 02:10:58 +0000900 self.serial = serial_port
901 self.connection = connection
cliechti6a300772009-08-12 02:28:56 +0000902 self.logger = logger
cliechti86b593e2009-08-05 16:28:12 +0000903 self._client_is_rfc2217 = False
cliechti130d1f02009-08-04 02:10:58 +0000904
905 # filter state machine
906 self.mode = M_NORMAL
907 self.suboption = None
908 self.telnet_command = None
909
910 # states for modem/line control events
911 self.modemstate_mask = 255
912 self.last_modemstate = None
913 self.linstate_mask = 0
914
915 # all supported telnet options
916 self._telnet_options = [
917 TelnetOption(self, 'ECHO', ECHO, WILL, WONT, DO, DONT, REQUESTED),
918 TelnetOption(self, 'we-SGA', SGA, WILL, WONT, DO, DONT, REQUESTED),
919 TelnetOption(self, 'they-SGA', SGA, DO, DONT, WILL, WONT, INACTIVE),
920 TelnetOption(self, 'we-BINARY', BINARY, WILL, WONT, DO, DONT, INACTIVE),
921 TelnetOption(self, 'they-BINARY', BINARY, DO, DONT, WILL, WONT, REQUESTED),
cliechti86b593e2009-08-05 16:28:12 +0000922 TelnetOption(self, 'we-RFC2217', COM_PORT_OPTION, WILL, WONT, DO, DONT, REQUESTED, self._client_ok),
923 TelnetOption(self, 'they-RFC2217', COM_PORT_OPTION, DO, DONT, WILL, WONT, INACTIVE, self._client_ok),
cliechti130d1f02009-08-04 02:10:58 +0000924 ]
925
926 # negotiate Telnet/RFC2217 -> send initial requests
cliechti6a300772009-08-12 02:28:56 +0000927 if self.logger:
928 self.logger.debug("requesting initial Telnet/RFC 2217 options")
cliechti130d1f02009-08-04 02:10:58 +0000929 for option in self._telnet_options:
930 if option.state is REQUESTED:
931 self.telnetSendOption(option.send_yes, option.option)
932 # issue 1st modem state notification
cliechti86b593e2009-08-05 16:28:12 +0000933
934 def _client_ok(self):
cliechtieada4fd2013-07-31 16:26:07 +0000935 """\
cliechti7d448562014-08-03 21:57:45 +0000936 callback of telnet option. It gets called when option is activated.
937 This one here is used to detect when the client agrees on RFC 2217. A
cliechti86b593e2009-08-05 16:28:12 +0000938 flag is set so that other functions like check_modem_lines know if the
cliechti7d448562014-08-03 21:57:45 +0000939 client is OK.
cliechtieada4fd2013-07-31 16:26:07 +0000940 """
cliechti86b593e2009-08-05 16:28:12 +0000941 # The callback is used for we and they so if one party agrees, we're
942 # already happy. it seems not all servers do the negotiation correctly
943 # and i guess there are incorrect clients too.. so be happy if client
944 # answers one or the other positively.
945 self._client_is_rfc2217 = True
cliechti6a300772009-08-12 02:28:56 +0000946 if self.logger:
947 self.logger.info("client accepts RFC 2217")
cliechti8fb119c2009-08-05 23:39:45 +0000948 # this is to ensure that the client gets a notification, even if there
949 # was no change
950 self.check_modem_lines(force_notification=True)
cliechti130d1f02009-08-04 02:10:58 +0000951
952 # - outgoing telnet commands and options
953
954 def telnetSendOption(self, action, option):
cliechti044d8662009-08-11 21:40:31 +0000955 """Send DO, DONT, WILL, WONT."""
cliechti130d1f02009-08-04 02:10:58 +0000956 self.connection.write(to_bytes([IAC, action, option]))
957
cliechtif325c032009-12-25 16:09:49 +0000958 def rfc2217SendSubnegotiation(self, option, value=''):
cliechti044d8662009-08-11 21:40:31 +0000959 """Subnegotiation of RFC 2217 parameters."""
cliechtif325c032009-12-25 16:09:49 +0000960 value = value.replace(IAC, IAC_DOUBLED)
cliechti130d1f02009-08-04 02:10:58 +0000961 self.connection.write(to_bytes([IAC, SB, COM_PORT_OPTION, option] + list(value) + [IAC, SE]))
962
963 # - check modem lines, needs to be called periodically from user to
964 # establish polling
965
cliechti7cb78e82009-08-05 15:47:57 +0000966 def check_modem_lines(self, force_notification=False):
cliechti130d1f02009-08-04 02:10:58 +0000967 modemstate = (
968 (self.serial.getCTS() and MODEMSTATE_MASK_CTS) |
969 (self.serial.getDSR() and MODEMSTATE_MASK_DSR) |
970 (self.serial.getRI() and MODEMSTATE_MASK_RI) |
971 (self.serial.getCD() and MODEMSTATE_MASK_CD)
972 )
cliechti7cb78e82009-08-05 15:47:57 +0000973 # check what has changed
974 deltas = modemstate ^ (self.last_modemstate or 0) # when last is None -> 0
975 if deltas & MODEMSTATE_MASK_CTS:
976 modemstate |= MODEMSTATE_MASK_CTS_CHANGE
977 if deltas & MODEMSTATE_MASK_DSR:
978 modemstate |= MODEMSTATE_MASK_DSR_CHANGE
979 if deltas & MODEMSTATE_MASK_RI:
980 modemstate |= MODEMSTATE_MASK_RI_CHANGE
981 if deltas & MODEMSTATE_MASK_CD:
982 modemstate |= MODEMSTATE_MASK_CD_CHANGE
983 # if new state is different and the mask allows this change, send
cliechti86b593e2009-08-05 16:28:12 +0000984 # notification. suppress notifications when client is not rfc2217
cliechti7cb78e82009-08-05 15:47:57 +0000985 if modemstate != self.last_modemstate or force_notification:
cliechti8fb119c2009-08-05 23:39:45 +0000986 if (self._client_is_rfc2217 and (modemstate & self.modemstate_mask)) or force_notification:
cliechti7cb78e82009-08-05 15:47:57 +0000987 self.rfc2217SendSubnegotiation(
988 SERVER_NOTIFY_MODEMSTATE,
989 to_bytes([modemstate & self.modemstate_mask])
990 )
cliechti6a300772009-08-12 02:28:56 +0000991 if self.logger:
992 self.logger.info("NOTIFY_MODEMSTATE: %s" % (modemstate,))
cliechti7cb78e82009-08-05 15:47:57 +0000993 # save last state, but forget about deltas.
994 # otherwise it would also notify about changing deltas which is
995 # probably not very useful
996 self.last_modemstate = modemstate & 0xf0
cliechti130d1f02009-08-04 02:10:58 +0000997
cliechti32c10332009-08-05 13:23:43 +0000998 # - outgoing data escaping
999
1000 def escape(self, data):
cliechtieada4fd2013-07-31 16:26:07 +00001001 """\
cliechti7d448562014-08-03 21:57:45 +00001002 This generator function is for the user. All outgoing data has to be
cliechticb20a4f2011-04-25 02:25:54 +00001003 properly escaped, so that no IAC character in the data stream messes up
1004 the Telnet state machine in the server.
cliechti32c10332009-08-05 13:23:43 +00001005
1006 socket.sendall(escape(data))
1007 """
1008 for byte in data:
1009 if byte == IAC:
1010 yield IAC
1011 yield IAC
1012 else:
1013 yield byte
1014
cliechti130d1f02009-08-04 02:10:58 +00001015 # - incoming data filter
1016
1017 def filter(self, data):
cliechtieada4fd2013-07-31 16:26:07 +00001018 """\
cliechti7d448562014-08-03 21:57:45 +00001019 Handle a bunch of incoming bytes. This is a generator. It will yield
cliechti044d8662009-08-11 21:40:31 +00001020 all characters not of interest for Telnet/RFC 2217.
cliechti130d1f02009-08-04 02:10:58 +00001021
1022 The idea is that the reader thread pushes data from the socket through
1023 this filter:
1024
1025 for byte in filter(socket.recv(1024)):
1026 # do things like CR/LF conversion/whatever
1027 # and write data to the serial port
1028 serial.write(byte)
1029
1030 (socket error handling code left as exercise for the reader)
1031 """
Chris Liechti01587b12015-08-05 02:39:32 +02001032 #~ for byte in data: # XXX fails for python3 as it returns ints instead of bytes
1033 for x in range(len(data)):
1034 byte = data[x:x+1]
cliechti130d1f02009-08-04 02:10:58 +00001035 if self.mode == M_NORMAL:
1036 # interpret as command or as data
1037 if byte == IAC:
1038 self.mode = M_IAC_SEEN
1039 else:
1040 # store data in sub option buffer or pass it to our
1041 # consumer depending on state
1042 if self.suboption is not None:
Chris Liechti01587b12015-08-05 02:39:32 +02001043 self.suboption += byte
cliechti130d1f02009-08-04 02:10:58 +00001044 else:
1045 yield byte
1046 elif self.mode == M_IAC_SEEN:
1047 if byte == IAC:
1048 # interpret as command doubled -> insert character
1049 # itself
cliechtif325c032009-12-25 16:09:49 +00001050 if self.suboption is not None:
Chris Liechti01587b12015-08-05 02:39:32 +02001051 self.suboption += byte
cliechtif325c032009-12-25 16:09:49 +00001052 else:
1053 yield byte
cliechti130d1f02009-08-04 02:10:58 +00001054 self.mode = M_NORMAL
1055 elif byte == SB:
1056 # sub option start
1057 self.suboption = bytearray()
1058 self.mode = M_NORMAL
1059 elif byte == SE:
1060 # sub option end -> process it now
1061 self._telnetProcessSubnegotiation(bytes(self.suboption))
1062 self.suboption = None
1063 self.mode = M_NORMAL
1064 elif byte in (DO, DONT, WILL, WONT):
1065 # negotiation
1066 self.telnet_command = byte
1067 self.mode = M_NEGOTIATE
1068 else:
1069 # other telnet commands
1070 self._telnetProcessCommand(byte)
1071 self.mode = M_NORMAL
1072 elif self.mode == M_NEGOTIATE: # DO, DONT, WILL, WONT was received, option now following
1073 self._telnetNegotiateOption(self.telnet_command, byte)
1074 self.mode = M_NORMAL
1075
1076 # - incoming telnet commands and options
1077
1078 def _telnetProcessCommand(self, command):
cliechti044d8662009-08-11 21:40:31 +00001079 """Process commands other than DO, DONT, WILL, WONT."""
cliechti130d1f02009-08-04 02:10:58 +00001080 # Currently none. RFC2217 only uses negotiation and subnegotiation.
cliechti6a300772009-08-12 02:28:56 +00001081 if self.logger:
1082 self.logger.warning("ignoring Telnet command: %r" % (command,))
cliechti130d1f02009-08-04 02:10:58 +00001083
1084 def _telnetNegotiateOption(self, command, option):
cliechti044d8662009-08-11 21:40:31 +00001085 """Process incoming DO, DONT, WILL, WONT."""
cliechti130d1f02009-08-04 02:10:58 +00001086 # check our registered telnet options and forward command to them
1087 # they know themselves if they have to answer or not
1088 known = False
1089 for item in self._telnet_options:
1090 # can have more than one match! as some options are duplicated for
1091 # 'us' and 'them'
1092 if item.option == option:
1093 item.process_incoming(command)
1094 known = True
1095 if not known:
1096 # handle unknown options
1097 # only answer to positive requests and deny them
1098 if command == WILL or command == DO:
1099 self.telnetSendOption((command == WILL and DONT or WONT), option)
cliechti6a300772009-08-12 02:28:56 +00001100 if self.logger:
1101 self.logger.warning("rejected Telnet option: %r" % (option,))
cliechti130d1f02009-08-04 02:10:58 +00001102
1103
1104 def _telnetProcessSubnegotiation(self, suboption):
cliechti044d8662009-08-11 21:40:31 +00001105 """Process subnegotiation, the data between IAC SB and IAC SE."""
cliechti130d1f02009-08-04 02:10:58 +00001106 if suboption[0:1] == COM_PORT_OPTION:
cliechti6a300772009-08-12 02:28:56 +00001107 if self.logger:
1108 self.logger.debug('received COM_PORT_OPTION: %r' % (suboption,))
cliechti130d1f02009-08-04 02:10:58 +00001109 if suboption[1:2] == SET_BAUDRATE:
1110 backup = self.serial.baudrate
1111 try:
Chris Liechti01587b12015-08-05 02:39:32 +02001112 (baudrate,) = struct.unpack(b"!I", suboption[2:6])
cliechtieada4fd2013-07-31 16:26:07 +00001113 if baudrate != 0:
1114 self.serial.baudrate = baudrate
Chris Liechtid2146002015-08-04 16:57:16 +02001115 except ValueError as e:
cliechti6a300772009-08-12 02:28:56 +00001116 if self.logger:
1117 self.logger.error("failed to set baud rate: %s" % (e,))
cliechti130d1f02009-08-04 02:10:58 +00001118 self.serial.baudrate = backup
cliechti5cc3eb12009-08-11 23:04:30 +00001119 else:
cliechti6a300772009-08-12 02:28:56 +00001120 if self.logger:
cliechtieada4fd2013-07-31 16:26:07 +00001121 self.logger.info("%s baud rate: %s" % (baudrate and 'set' or 'get', self.serial.baudrate))
Chris Liechti01587b12015-08-05 02:39:32 +02001122 self.rfc2217SendSubnegotiation(SERVER_SET_BAUDRATE, struct.pack(b"!I", self.serial.baudrate))
cliechti130d1f02009-08-04 02:10:58 +00001123 elif suboption[1:2] == SET_DATASIZE:
1124 backup = self.serial.bytesize
1125 try:
cliechtieada4fd2013-07-31 16:26:07 +00001126 (datasize,) = struct.unpack("!B", suboption[2:3])
1127 if datasize != 0:
1128 self.serial.bytesize = datasize
Chris Liechtid2146002015-08-04 16:57:16 +02001129 except ValueError as e:
cliechti6a300772009-08-12 02:28:56 +00001130 if self.logger:
1131 self.logger.error("failed to set data size: %s" % (e,))
cliechti130d1f02009-08-04 02:10:58 +00001132 self.serial.bytesize = backup
cliechti5cc3eb12009-08-11 23:04:30 +00001133 else:
cliechti6a300772009-08-12 02:28:56 +00001134 if self.logger:
cliechtieada4fd2013-07-31 16:26:07 +00001135 self.logger.info("%s data size: %s" % (datasize and 'set' or 'get', self.serial.bytesize))
Chris Liechti01587b12015-08-05 02:39:32 +02001136 self.rfc2217SendSubnegotiation(SERVER_SET_DATASIZE, struct.pack(b"!B", self.serial.bytesize))
cliechti130d1f02009-08-04 02:10:58 +00001137 elif suboption[1:2] == SET_PARITY:
1138 backup = self.serial.parity
1139 try:
Chris Liechti01587b12015-08-05 02:39:32 +02001140 parity = struct.unpack(b"!B", suboption[2:3])[0]
cliechtieada4fd2013-07-31 16:26:07 +00001141 if parity != 0:
1142 self.serial.parity = RFC2217_REVERSE_PARITY_MAP[parity]
Chris Liechtid2146002015-08-04 16:57:16 +02001143 except ValueError as e:
cliechti6a300772009-08-12 02:28:56 +00001144 if self.logger:
1145 self.logger.error("failed to set parity: %s" % (e,))
cliechti130d1f02009-08-04 02:10:58 +00001146 self.serial.parity = backup
cliechti5cc3eb12009-08-11 23:04:30 +00001147 else:
cliechti6a300772009-08-12 02:28:56 +00001148 if self.logger:
cliechtieada4fd2013-07-31 16:26:07 +00001149 self.logger.info("%s parity: %s" % (parity and 'set' or 'get', self.serial.parity))
cliechti130d1f02009-08-04 02:10:58 +00001150 self.rfc2217SendSubnegotiation(
1151 SERVER_SET_PARITY,
1152 struct.pack("!B", RFC2217_PARITY_MAP[self.serial.parity])
1153 )
1154 elif suboption[1:2] == SET_STOPSIZE:
1155 backup = self.serial.stopbits
1156 try:
Chris Liechti01587b12015-08-05 02:39:32 +02001157 stopbits = struct.unpack(b"!B", suboption[2:3])[0]
cliechtieada4fd2013-07-31 16:26:07 +00001158 if stopbits != 0:
1159 self.serial.stopbits = RFC2217_REVERSE_STOPBIT_MAP[stopbits]
Chris Liechtid2146002015-08-04 16:57:16 +02001160 except ValueError as e:
cliechti6a300772009-08-12 02:28:56 +00001161 if self.logger:
1162 self.logger.error("failed to set stop bits: %s" % (e,))
cliechti130d1f02009-08-04 02:10:58 +00001163 self.serial.stopbits = backup
cliechti5cc3eb12009-08-11 23:04:30 +00001164 else:
cliechti6a300772009-08-12 02:28:56 +00001165 if self.logger:
cliechtieada4fd2013-07-31 16:26:07 +00001166 self.logger.info("%s stop bits: %s" % (stopbits and 'set' or 'get', self.serial.stopbits))
cliechti130d1f02009-08-04 02:10:58 +00001167 self.rfc2217SendSubnegotiation(
1168 SERVER_SET_STOPSIZE,
Chris Liechti01587b12015-08-05 02:39:32 +02001169 struct.pack(b"!B", RFC2217_STOPBIT_MAP[self.serial.stopbits])
cliechti130d1f02009-08-04 02:10:58 +00001170 )
1171 elif suboption[1:2] == SET_CONTROL:
1172 if suboption[2:3] == SET_CONTROL_REQ_FLOW_SETTING:
1173 if self.serial.xonxoff:
1174 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_SW_FLOW_CONTROL)
1175 elif self.serial.rtscts:
1176 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_HW_FLOW_CONTROL)
1177 else:
1178 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_NO_FLOW_CONTROL)
1179 elif suboption[2:3] == SET_CONTROL_USE_NO_FLOW_CONTROL:
1180 self.serial.xonxoff = False
1181 self.serial.rtscts = False
cliechti6a300772009-08-12 02:28:56 +00001182 if self.logger:
1183 self.logger.info("changed flow control to None")
cliechti130d1f02009-08-04 02:10:58 +00001184 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_NO_FLOW_CONTROL)
1185 elif suboption[2:3] == SET_CONTROL_USE_SW_FLOW_CONTROL:
1186 self.serial.xonxoff = True
cliechti6a300772009-08-12 02:28:56 +00001187 if self.logger:
1188 self.logger.info("changed flow control to XON/XOFF")
cliechti130d1f02009-08-04 02:10:58 +00001189 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_SW_FLOW_CONTROL)
1190 elif suboption[2:3] == SET_CONTROL_USE_HW_FLOW_CONTROL:
1191 self.serial.rtscts = True
cliechti6a300772009-08-12 02:28:56 +00001192 if self.logger:
1193 self.logger.info("changed flow control to RTS/CTS")
cliechti130d1f02009-08-04 02:10:58 +00001194 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_HW_FLOW_CONTROL)
1195 elif suboption[2:3] == SET_CONTROL_REQ_BREAK_STATE:
cliechti6a300772009-08-12 02:28:56 +00001196 if self.logger:
1197 self.logger.warning("requested break state - not implemented")
cliechti130d1f02009-08-04 02:10:58 +00001198 pass # XXX needs cached value
1199 elif suboption[2:3] == SET_CONTROL_BREAK_ON:
1200 self.serial.setBreak(True)
cliechti6a300772009-08-12 02:28:56 +00001201 if self.logger:
1202 self.logger.info("changed BREAK to active")
cliechti130d1f02009-08-04 02:10:58 +00001203 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_BREAK_ON)
1204 elif suboption[2:3] == SET_CONTROL_BREAK_OFF:
1205 self.serial.setBreak(False)
cliechti6a300772009-08-12 02:28:56 +00001206 if self.logger:
1207 self.logger.info("changed BREAK to inactive")
cliechti130d1f02009-08-04 02:10:58 +00001208 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_BREAK_OFF)
1209 elif suboption[2:3] == SET_CONTROL_REQ_DTR:
cliechti6a300772009-08-12 02:28:56 +00001210 if self.logger:
1211 self.logger.warning("requested DTR state - not implemented")
cliechti130d1f02009-08-04 02:10:58 +00001212 pass # XXX needs cached value
1213 elif suboption[2:3] == SET_CONTROL_DTR_ON:
1214 self.serial.setDTR(True)
cliechti6a300772009-08-12 02:28:56 +00001215 if self.logger:
1216 self.logger.info("changed DTR to active")
cliechti130d1f02009-08-04 02:10:58 +00001217 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_DTR_ON)
1218 elif suboption[2:3] == SET_CONTROL_DTR_OFF:
1219 self.serial.setDTR(False)
cliechti6a300772009-08-12 02:28:56 +00001220 if self.logger:
1221 self.logger.info("changed DTR to inactive")
cliechti130d1f02009-08-04 02:10:58 +00001222 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_DTR_OFF)
1223 elif suboption[2:3] == SET_CONTROL_REQ_RTS:
cliechti6a300772009-08-12 02:28:56 +00001224 if self.logger:
1225 self.logger.warning("requested RTS state - not implemented")
cliechti130d1f02009-08-04 02:10:58 +00001226 pass # XXX needs cached value
1227 #~ self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_RTS_ON)
1228 elif suboption[2:3] == SET_CONTROL_RTS_ON:
1229 self.serial.setRTS(True)
cliechti6a300772009-08-12 02:28:56 +00001230 if self.logger:
1231 self.logger.info("changed RTS to active")
cliechti130d1f02009-08-04 02:10:58 +00001232 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_RTS_ON)
1233 elif suboption[2:3] == SET_CONTROL_RTS_OFF:
1234 self.serial.setRTS(False)
cliechti6a300772009-08-12 02:28:56 +00001235 if self.logger:
1236 self.logger.info("changed RTS to inactive")
cliechti130d1f02009-08-04 02:10:58 +00001237 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_RTS_OFF)
1238 #~ elif suboption[2:3] == SET_CONTROL_REQ_FLOW_SETTING_IN:
1239 #~ elif suboption[2:3] == SET_CONTROL_USE_NO_FLOW_CONTROL_IN:
1240 #~ elif suboption[2:3] == SET_CONTROL_USE_SW_FLOW_CONTOL_IN:
1241 #~ elif suboption[2:3] == SET_CONTROL_USE_HW_FLOW_CONTOL_IN:
1242 #~ elif suboption[2:3] == SET_CONTROL_USE_DCD_FLOW_CONTROL:
1243 #~ elif suboption[2:3] == SET_CONTROL_USE_DTR_FLOW_CONTROL:
1244 #~ elif suboption[2:3] == SET_CONTROL_USE_DSR_FLOW_CONTROL:
1245 elif suboption[1:2] == NOTIFY_LINESTATE:
cliechti7cb78e82009-08-05 15:47:57 +00001246 # client polls for current state
1247 self.rfc2217SendSubnegotiation(
1248 SERVER_NOTIFY_LINESTATE,
cliechti6a300772009-08-12 02:28:56 +00001249 to_bytes([0]) # sorry, nothing like that implemented
cliechti7cb78e82009-08-05 15:47:57 +00001250 )
cliechti130d1f02009-08-04 02:10:58 +00001251 elif suboption[1:2] == NOTIFY_MODEMSTATE:
cliechti6a300772009-08-12 02:28:56 +00001252 if self.logger:
1253 self.logger.info("request for modem state")
cliechti7cb78e82009-08-05 15:47:57 +00001254 # client polls for current state
1255 self.check_modem_lines(force_notification=True)
cliechti130d1f02009-08-04 02:10:58 +00001256 elif suboption[1:2] == FLOWCONTROL_SUSPEND:
cliechti6a300772009-08-12 02:28:56 +00001257 if self.logger:
1258 self.logger.info("suspend")
cliechti130d1f02009-08-04 02:10:58 +00001259 self._remote_suspend_flow = True
1260 elif suboption[1:2] == FLOWCONTROL_RESUME:
cliechti6a300772009-08-12 02:28:56 +00001261 if self.logger:
1262 self.logger.info("resume")
cliechti130d1f02009-08-04 02:10:58 +00001263 self._remote_suspend_flow = False
1264 elif suboption[1:2] == SET_LINESTATE_MASK:
1265 self.linstate_mask = ord(suboption[2:3]) # ensure it is a number
cliechti6a300772009-08-12 02:28:56 +00001266 if self.logger:
cliechtif325c032009-12-25 16:09:49 +00001267 self.logger.info("line state mask: 0x%02x" % (self.linstate_mask,))
cliechti130d1f02009-08-04 02:10:58 +00001268 elif suboption[1:2] == SET_MODEMSTATE_MASK:
1269 self.modemstate_mask = ord(suboption[2:3]) # ensure it is a number
cliechti6a300772009-08-12 02:28:56 +00001270 if self.logger:
cliechtif325c032009-12-25 16:09:49 +00001271 self.logger.info("modem state mask: 0x%02x" % (self.modemstate_mask,))
cliechti130d1f02009-08-04 02:10:58 +00001272 elif suboption[1:2] == PURGE_DATA:
1273 if suboption[2:3] == PURGE_RECEIVE_BUFFER:
1274 self.serial.flushInput()
cliechti6a300772009-08-12 02:28:56 +00001275 if self.logger:
1276 self.logger.info("purge in")
cliechti130d1f02009-08-04 02:10:58 +00001277 self.rfc2217SendSubnegotiation(SERVER_PURGE_DATA, PURGE_RECEIVE_BUFFER)
1278 elif suboption[2:3] == PURGE_TRANSMIT_BUFFER:
1279 self.serial.flushOutput()
cliechti6a300772009-08-12 02:28:56 +00001280 if self.logger:
1281 self.logger.info("purge out")
cliechti130d1f02009-08-04 02:10:58 +00001282 self.rfc2217SendSubnegotiation(SERVER_PURGE_DATA, PURGE_TRANSMIT_BUFFER)
1283 elif suboption[2:3] == PURGE_BOTH_BUFFERS:
1284 self.serial.flushInput()
1285 self.serial.flushOutput()
cliechti6a300772009-08-12 02:28:56 +00001286 if self.logger:
1287 self.logger.info("purge both")
cliechti130d1f02009-08-04 02:10:58 +00001288 self.rfc2217SendSubnegotiation(SERVER_PURGE_DATA, PURGE_BOTH_BUFFERS)
1289 else:
cliechti6a300772009-08-12 02:28:56 +00001290 if self.logger:
1291 self.logger.error("undefined PURGE_DATA: %r" % list(suboption[2:]))
cliechti130d1f02009-08-04 02:10:58 +00001292 else:
cliechti6a300772009-08-12 02:28:56 +00001293 if self.logger:
1294 self.logger.error("undefined COM_PORT_OPTION: %r" % list(suboption[1:]))
cliechti130d1f02009-08-04 02:10:58 +00001295 else:
cliechti6a300772009-08-12 02:28:56 +00001296 if self.logger:
1297 self.logger.warning("unknown subnegotiation: %r" % (suboption,))
cliechti130d1f02009-08-04 02:10:58 +00001298
1299
1300# simple client test
cliechti8099bed2009-08-01 23:59:18 +00001301if __name__ == '__main__':
1302 import sys
1303 s = Serial('rfc2217://localhost:7000', 115200)
1304 sys.stdout.write('%s\n' % s)
1305
cliechti2b929b72009-08-02 23:49:02 +00001306 #~ s.baudrate = 1898
1307
cliechti8099bed2009-08-01 23:59:18 +00001308 sys.stdout.write("write...\n")
1309 s.write("hello\n")
1310 s.flush()
cliechti8099bed2009-08-01 23:59:18 +00001311 sys.stdout.write("read: %s\n" % s.read(5))
1312
1313 #~ s.baudrate = 19200
1314 #~ s.databits = 7
1315 s.close()