blob: b130f5e06bafd7b19aa4da36fc74fdfa0f6a8e3d [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#
10# (C) 2001-2009 Chris Liechti <cliechti@gmx.net>
11# 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:
54# - "debug" print diagnostic messages
55# - "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
cliechti8099bed2009-08-01 23:59:18 +000062from serialutil import *
63import time
64import struct
65import socket
66import threading
67import Queue
68
cliechti8099bed2009-08-01 23:59:18 +000069# port string is expected to be something like this:
70# rfc2217://host:port
71# host may be an IP or including domain, whatever.
72# port is 0...65535
73
74# telnet protocol characters
75IAC = to_bytes([255]) # Interpret As Command
76DONT = to_bytes([254])
77DO = to_bytes([253])
78WONT = to_bytes([252])
79WILL = to_bytes([251])
80IAC_DOUBLED = to_bytes([IAC, IAC])
81
82SE = to_bytes([240]) # Subnegotiation End
83NOP = to_bytes([241]) # No Operation
84DM = to_bytes([242]) # Data Mark
85BRK = to_bytes([243]) # Break
86IP = to_bytes([244]) # Interrupt process
87AO = to_bytes([245]) # Abort output
88AYT = to_bytes([246]) # Are You There
89EC = to_bytes([247]) # Erase Character
90EL = to_bytes([248]) # Erase Line
91GA = to_bytes([249]) # Go Ahead
92SB = to_bytes([250]) # Subnegotiation Begin
93
94# selected telnet options
95BINARY = to_bytes([0]) # 8-bit data path
96ECHO = to_bytes([1]) # echo
97SGA = to_bytes([3]) # suppress go ahead
98
99# RFC2217
100COM_PORT_OPTION = to_bytes([44])
101
102# Client to Access Server
103#~ SIGNATURE text text
104SET_BAUDRATE = to_bytes([1])
105SET_DATASIZE = to_bytes([2])
106SET_PARITY = to_bytes([3])
107SET_STOPSIZE = to_bytes([4])
108SET_CONTROL = to_bytes([5])
109NOTIFY_LINESTATE = to_bytes([6])
110NOTIFY_MODEMSTATE = to_bytes([7])
111FLOWCONTROL_SUSPEND = to_bytes([8])
112FLOWCONTROL_RESUME = to_bytes([9])
113SET_LINESTATE_MASK = to_bytes([10])
114SET_MODEMSTATE_MASK = to_bytes([11])
115PURGE_DATA = to_bytes([12])
116
117SERVER_SET_BAUDRATE = to_bytes([101])
118SERVER_SET_DATASIZE = to_bytes([102])
119SERVER_SET_PARITY = to_bytes([103])
120SERVER_SET_STOPSIZE = to_bytes([104])
121SERVER_SET_CONTROL = to_bytes([105])
122SERVER_NOTIFY_LINESTATE = to_bytes([106])
123SERVER_NOTIFY_MODEMSTATE = to_bytes([107])
124SERVER_FLOWCONTROL_SUSPEND = to_bytes([108])
125SERVER_FLOWCONTROL_RESUME = to_bytes([109])
126SERVER_SET_LINESTATE_MASK = to_bytes([110])
127SERVER_SET_MODEMSTATE_MASK = to_bytes([111])
128SERVER_PURGE_DATA = to_bytes([112])
129
130RFC2217_ANSWER_MAP = {
131 SET_BAUDRATE: SERVER_SET_BAUDRATE,
132 SET_DATASIZE: SERVER_SET_DATASIZE,
133 SET_PARITY: SERVER_SET_PARITY,
134 SET_STOPSIZE: SERVER_SET_STOPSIZE,
135 SET_CONTROL: SERVER_SET_CONTROL,
136 NOTIFY_LINESTATE: SERVER_NOTIFY_LINESTATE,
137 NOTIFY_MODEMSTATE: SERVER_NOTIFY_MODEMSTATE,
138 FLOWCONTROL_SUSPEND: SERVER_FLOWCONTROL_SUSPEND,
139 FLOWCONTROL_RESUME: SERVER_FLOWCONTROL_RESUME,
140 SET_LINESTATE_MASK: SERVER_SET_LINESTATE_MASK,
141 SET_MODEMSTATE_MASK: SERVER_SET_MODEMSTATE_MASK,
142 PURGE_DATA: SERVER_PURGE_DATA,
143}
144
145SET_CONTROL_REQ_FLOW_SETTING = to_bytes([0]) # Request Com Port Flow Control Setting (outbound/both)
146SET_CONTROL_USE_NO_FLOW_CONTROL = to_bytes([1]) # Use No Flow Control (outbound/both)
147SET_CONTROL_USE_SW_FLOW_CONTROL = to_bytes([2]) # Use XON/XOFF Flow Control (outbound/both)
148SET_CONTROL_USE_HW_FLOW_CONTROL = to_bytes([3]) # Use HARDWARE Flow Control (outbound/both)
149SET_CONTROL_REQ_BREAK_STATE = to_bytes([4]) # Request BREAK State
150SET_CONTROL_BREAK_ON = to_bytes([5]) # Set BREAK State ON
151SET_CONTROL_BREAK_OFF = to_bytes([6]) # Set BREAK State OFF
152SET_CONTROL_REQ_DTR = to_bytes([7]) # Request DTR Signal State
153SET_CONTROL_DTR_ON = to_bytes([8]) # Set DTR Signal State ON
154SET_CONTROL_DTR_OFF = to_bytes([9]) # Set DTR Signal State OFF
155SET_CONTROL_REQ_RTS = to_bytes([10]) # Request RTS Signal State
156SET_CONTROL_RTS_ON = to_bytes([11]) # Set RTS Signal State ON
157SET_CONTROL_RTS_OFF = to_bytes([12]) # Set RTS Signal State OFF
158SET_CONTROL_REQ_FLOW_SETTING_IN = to_bytes([13]) # Request Com Port Flow Control Setting (inbound)
159SET_CONTROL_USE_NO_FLOW_CONTROL_IN = to_bytes([14]) # Use No Flow Control (inbound)
160SET_CONTROL_USE_SW_FLOW_CONTOL_IN = to_bytes([15]) # Use XON/XOFF Flow Control (inbound)
161SET_CONTROL_USE_HW_FLOW_CONTOL_IN = to_bytes([16]) # Use HARDWARE Flow Control (inbound)
162SET_CONTROL_USE_DCD_FLOW_CONTROL = to_bytes([17]) # Use DCD Flow Control (outbound/both)
163SET_CONTROL_USE_DTR_FLOW_CONTROL = to_bytes([18]) # Use DTR Flow Control (inbound)
164SET_CONTROL_USE_DSR_FLOW_CONTROL = to_bytes([19]) # Use DSR Flow Control (outbound/both)
165
166LINESTATE_MASK_TIMEOUT = 128 # Time-out Error
167LINESTATE_MASK_SHIFTREG_EMPTY = 64 # Transfer Shift Register Empty
168LINESTATE_MASK_TRANSREG_EMPTY = 32 # Transfer Holding Register Empty
169LINESTATE_MASK_BREAK_DETECT = 16 # Break-detect Error
170LINESTATE_MASK_FRAMING_ERROR = 8 # Framing Error
171LINESTATE_MASK_PARTIY_ERROR = 4 # Parity Error
172LINESTATE_MASK_OVERRUN_ERROR = 2 # Overrun Error
173LINESTATE_MASK_DATA_READY = 1 # Data Ready
174
175MODEMSTATE_MASK_CD = 128 # Receive Line Signal Detect (also known as Carrier Detect)
176MODEMSTATE_MASK_RI = 64 # Ring Indicator
177MODEMSTATE_MASK_DSR = 32 # Data-Set-Ready Signal State
178MODEMSTATE_MASK_CTS = 16 # Clear-To-Send Signal State
179MODEMSTATE_MASK_CD_CHANGE = 8 # Delta Receive Line Signal Detect
180MODEMSTATE_MASK_RI_CHANGE = 4 # Trailing-edge Ring Detector
181MODEMSTATE_MASK_DSR_CHANGE = 2 # Delta Data-Set-Ready
182MODEMSTATE_MASK_CTS_CHANGE = 1 # Delta Clear-To-Send
183
184PURGE_RECEIVE_BUFFER = to_bytes([1]) # Purge access server receive data buffer
185PURGE_TRANSMIT_BUFFER = to_bytes([2]) # Purge access server transmit data buffer
186PURGE_BOTH_BUFFERS = to_bytes([3]) # Purge both the access server receive data buffer and the access server transmit data buffer
187
188
189RFC2217_PARITY_MAP = {
190 PARITY_NONE: 1,
191 PARITY_ODD: 2,
192 PARITY_EVEN: 3,
193 PARITY_MARK: 4,
194 PARITY_SPACE: 5,
195}
cliechti130d1f02009-08-04 02:10:58 +0000196RFC2217_REVERSE_PARITY_MAP = dict((v,k) for k,v in RFC2217_PARITY_MAP.items())
cliechti8099bed2009-08-01 23:59:18 +0000197
198RFC2217_STOPBIT_MAP = {
199 STOPBITS_ONE: 1,
200 STOPBITS_ONE_POINT_FIVE: 3,
201 STOPBITS_TWO: 2,
202}
cliechti130d1f02009-08-04 02:10:58 +0000203RFC2217_REVERSE_STOPBIT_MAP = dict((v,k) for k,v in RFC2217_STOPBIT_MAP.items())
cliechti8099bed2009-08-01 23:59:18 +0000204
cliechti130d1f02009-08-04 02:10:58 +0000205# Telnet filter states
206M_NORMAL = 0
207M_IAC_SEEN = 1
208M_NEGOTIATE = 2
cliechti8099bed2009-08-01 23:59:18 +0000209
cliechti130d1f02009-08-04 02:10:58 +0000210# TelnetOption and TelnetSubnegotiation states
cliechtiac205322009-08-02 20:40:21 +0000211REQUESTED = 'REQUESTED'
212ACTIVE = 'ACTIVE'
213INACTIVE = 'INACTIVE'
214REALLY_INACTIVE = 'REALLY_INACTIVE'
215
216class TelnetOption(object):
cliechti1ef7e3e2009-08-03 02:38:43 +0000217 """Manage a single telnet option, keeps track of DO/DONT WILL/WONT."""
218
cliechti86b593e2009-08-05 16:28:12 +0000219 def __init__(self, connection, name, option, send_yes, send_no, ack_yes, ack_no, initial_state, activation_callback=None):
cliechti1ef7e3e2009-08-03 02:38:43 +0000220 """Init option.
221 :param connection: connection used to transmit answers
222 :param name: a readable name for debug outputs
223 :param send_yes: what to send when option is to be enabled.
224 :param send_no: what to send when option is to be disabled.
225 :param ack_yes: what to expect when remote agrees on option.
226 :param ack_no: what to expect when remote disagrees on option.
227 :param initial_state: options initialized with REQUESTED are tried to
228 be enabled on startup. use INACTIVE for all others.
229 """
cliechti2b929b72009-08-02 23:49:02 +0000230 self.connection = connection
cliechtiac205322009-08-02 20:40:21 +0000231 self.name = name
232 self.option = option
233 self.send_yes = send_yes
234 self.send_no = send_no
235 self.ack_yes = ack_yes
236 self.ack_no = ack_no
237 self.state = initial_state
238 self.active = False
cliechti86b593e2009-08-05 16:28:12 +0000239 self.activation_callback = activation_callback
cliechtiac205322009-08-02 20:40:21 +0000240
241 def __repr__(self):
cliechti1ef7e3e2009-08-03 02:38:43 +0000242 """String for debug outputs"""
cliechtiac205322009-08-02 20:40:21 +0000243 return "%s:%s(%s)" % (self.name, self.active, self.state)
244
cliechti2b929b72009-08-02 23:49:02 +0000245 def process_incoming(self, command):
cliechti1ef7e3e2009-08-03 02:38:43 +0000246 """A DO/DONT/WILL/WONT was received for this option, update state and
247 answer when needed."""
cliechtiac205322009-08-02 20:40:21 +0000248 if command == self.ack_yes:
249 if self.state is REQUESTED:
250 self.state = ACTIVE
251 self.active = True
cliechti86b593e2009-08-05 16:28:12 +0000252 if self.activation_callback is not None:
253 self.activation_callback()
cliechtiac205322009-08-02 20:40:21 +0000254 elif self.state is ACTIVE:
255 pass
256 elif self.state is INACTIVE:
257 self.state = ACTIVE
cliechti1ef7e3e2009-08-03 02:38:43 +0000258 self.connection.telnetSendOption(self.send_yes, self.option)
cliechtiac205322009-08-02 20:40:21 +0000259 self.active = True
cliechti86b593e2009-08-05 16:28:12 +0000260 if self.activation_callback is not None:
261 self.activation_callback()
cliechtiac205322009-08-02 20:40:21 +0000262 elif self.state is REALLY_INACTIVE:
cliechti1ef7e3e2009-08-03 02:38:43 +0000263 self.connection.telnetSendOption(self.send_no, self.option)
cliechtiac205322009-08-02 20:40:21 +0000264 else:
265 raise ValueError('option in illegal state %r' % self)
266 elif command == self.ack_no:
267 if self.state is REQUESTED:
268 self.state = INACTIVE
269 self.active = False
270 elif self.state is ACTIVE:
271 self.state = INACTIVE
cliechti1ef7e3e2009-08-03 02:38:43 +0000272 self.connection.telnetSendOption(self.send_no, self.option)
cliechtiac205322009-08-02 20:40:21 +0000273 self.active = False
274 elif self.state is INACTIVE:
275 pass
276 elif self.state is REALLY_INACTIVE:
277 pass
278 else:
279 raise ValueError('option in illegal state %r' % self)
280
281
cliechti2b929b72009-08-02 23:49:02 +0000282class TelnetSubnegotiation(object):
cliechti1ef7e3e2009-08-03 02:38:43 +0000283 """A object to handle subnegotiation of options. In this case actually
284 sub-sub options for RFC2217. It is used to track com port options."""
cliechti2b929b72009-08-02 23:49:02 +0000285
286 def __init__(self, connection, name, option, ack_option=None):
287 if ack_option is None: ack_option = option
288 self.connection = connection
289 self.name = name
290 self.option = option
291 self.value = None
292 self.ack_option = ack_option
293 self.state = INACTIVE
294
295 def __repr__(self):
cliechti1ef7e3e2009-08-03 02:38:43 +0000296 """String for debug outputs"""
cliechti2b929b72009-08-02 23:49:02 +0000297 return "%s:%s" % (self.name, self.state)
298
299 def set(self, value):
300 """request a change of the value. a request is sent to the server. if
301 the client needs to know if the change is performed he has to check the
302 state of this object."""
303 self.value = value
304 self.state = REQUESTED
cliechti1ef7e3e2009-08-03 02:38:43 +0000305 self.connection.rfc2217SendSubnegotiation(self.option, self.value)
cliechti81c54762009-08-03 23:53:27 +0000306 if self.connection.debug_output:
307 print "SB Requesting %s -> %r" % (self.name, self.value)
cliechti2b929b72009-08-02 23:49:02 +0000308
309 def isReady(self):
cliechti1ef7e3e2009-08-03 02:38:43 +0000310 """check if answer from server has been received. when server rejects
311 the change, raise a ValueError."""
cliechti2b929b72009-08-02 23:49:02 +0000312 if self.state == REALLY_INACTIVE:
313 raise ValueError("remote rejected value for option %r" % (self.name))
314 return self.state == ACTIVE
cliechti1ef7e3e2009-08-03 02:38:43 +0000315 # add property to have a similar interface as TelnetOption
cliechti2b929b72009-08-02 23:49:02 +0000316 active = property(isReady)
317
318 def wait(self, timeout):
cliechti1ef7e3e2009-08-03 02:38:43 +0000319 """wait until the subnegotiation has been acknowledged or timeout. It
320 can also throw a value error when the answer from the server does not
321 match the value sent."""
cliechti2b929b72009-08-02 23:49:02 +0000322 timeout_time = time.time() + 3
323 while time.time() < timeout_time:
324 time.sleep(0.05) # prevent 100% CPU load
325 if self.isReady():
326 break
327 else:
328 raise SerialException("timeout while waiting for option %r" % (self.name))
329
330 def checkAnswer(self, suboption):
331 """check an incoming subnegotiation block. the parameter already has
332 cut off the header like sub option number and com port option value"""
333 if self.value == suboption[:len(self.value)]:
334 self.state = ACTIVE
335 else:
336 # error propagation done in isReady
337 self.state = REALLY_INACTIVE
cliechti81c54762009-08-03 23:53:27 +0000338 if self.connection.debug_output:
339 print "SB Answer %s -> %r -> %s" % (self.name, suboption, self.state)
cliechti2b929b72009-08-02 23:49:02 +0000340
341
cliechti8099bed2009-08-01 23:59:18 +0000342class RFC2217Serial(SerialBase):
cliechti1ef7e3e2009-08-03 02:38:43 +0000343 """Serial port implementation for RFC2217 remote serial ports."""
cliechti8099bed2009-08-01 23:59:18 +0000344
345 BAUDRATES = (50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800,
346 9600, 19200, 38400, 57600, 115200)
347
348 def open(self):
349 """Open port with current settings. This may throw a SerialException
350 if the port cannot be opened."""
cliechti81c54762009-08-03 23:53:27 +0000351 self.debug_output = False
352 self._ignore_set_control_answer = False
cliechti7cb78e82009-08-05 15:47:57 +0000353 self._poll_modem_state = False
cliechti8099bed2009-08-01 23:59:18 +0000354 if self._port is None:
355 raise SerialException("Port must be configured before it can be used.")
356 try:
357 self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
358 self._socket.connect(self.fromURL(self.portstr))
cliechtic63b3c02009-08-07 19:17:20 +0000359 self._socket.setsockopt( socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
cliechti8099bed2009-08-01 23:59:18 +0000360 except Exception, msg:
361 self._socket = None
362 raise SerialException("Could not open port %s: %s" % (self.portstr, msg))
363
cliechti1ef7e3e2009-08-03 02:38:43 +0000364 self._socket.settimeout(5) # XXX good value?
cliechti8099bed2009-08-01 23:59:18 +0000365
cliechti1ef7e3e2009-08-03 02:38:43 +0000366 # use a thread save queue as buffer. it also simplifies implementing
367 # the read timeout
cliechti8099bed2009-08-01 23:59:18 +0000368 self._read_buffer = Queue.Queue()
cliechti81c54762009-08-03 23:53:27 +0000369 # to ensure that user writes does not interfere with internal
370 # telnet/rfc2217 options establish a lock
371 self._write_lock = threading.Lock()
cliechtiac205322009-08-02 20:40:21 +0000372 # name the following separately so that, below, a check can be easily done
373 mandadory_options = [
cliechti2b929b72009-08-02 23:49:02 +0000374 TelnetOption(self, 'we-BINARY', BINARY, WILL, WONT, DO, DONT, INACTIVE),
cliechti2b929b72009-08-02 23:49:02 +0000375 TelnetOption(self, 'we-RFC2217', COM_PORT_OPTION, WILL, WONT, DO, DONT, REQUESTED),
cliechtiac205322009-08-02 20:40:21 +0000376 ]
377 # all supported telnet options
378 self._telnet_options = [
cliechtia29275e2009-08-03 00:08:04 +0000379 TelnetOption(self, 'ECHO', ECHO, DO, DONT, WILL, WONT, REQUESTED),
cliechti2b929b72009-08-02 23:49:02 +0000380 TelnetOption(self, 'we-SGA', SGA, WILL, WONT, DO, DONT, REQUESTED),
381 TelnetOption(self, 'they-SGA', SGA, DO, DONT, WILL, WONT, REQUESTED),
cliechti81c54762009-08-03 23:53:27 +0000382 TelnetOption(self, 'they-BINARY', BINARY, DO, DONT, WILL, WONT, INACTIVE),
383 TelnetOption(self, 'they-RFC2217', COM_PORT_OPTION, DO, DONT, WILL, WONT, REQUESTED),
cliechtiac205322009-08-02 20:40:21 +0000384 ] + mandadory_options
385 # RFC2217 specific states
cliechti2b929b72009-08-02 23:49:02 +0000386 # COM port settings
387 self._rfc2217_port_settings = {
388 'baudrate': TelnetSubnegotiation(self, 'baudrate', SET_BAUDRATE, SERVER_SET_BAUDRATE),
389 'datasize': TelnetSubnegotiation(self, 'datasize', SET_DATASIZE, SERVER_SET_DATASIZE),
390 'parity': TelnetSubnegotiation(self, 'parity', SET_PARITY, SERVER_SET_PARITY),
391 'stopsize': TelnetSubnegotiation(self, 'stopsize', SET_STOPSIZE, SERVER_SET_STOPSIZE),
392 }
393 # There are more subnegotiation object, combine all in one dictionary
394 # for easy access
395 self._rfc2217_options = {
396 'purge': TelnetSubnegotiation(self, 'purge', PURGE_DATA, SERVER_PURGE_DATA),
cliechti81c54762009-08-03 23:53:27 +0000397 'control': TelnetSubnegotiation(self, 'control', SET_CONTROL, SERVER_SET_CONTROL),
cliechti2b929b72009-08-02 23:49:02 +0000398 }
399 self._rfc2217_options.update(self._rfc2217_port_settings)
400 # cache for line and modem states that the server sends to us
cliechti8099bed2009-08-01 23:59:18 +0000401 self._linestate = 0
cliechti7cb78e82009-08-05 15:47:57 +0000402 self._modemstate = None
403 self._modemstate_expires = 0
cliechti672d0292009-08-03 02:01:57 +0000404 # RFC2217 flow control between server and client
405 self._remote_suspend_flow = False
cliechti8099bed2009-08-01 23:59:18 +0000406
cliechti1ef7e3e2009-08-03 02:38:43 +0000407 self._thread = threading.Thread(target=self._telnetReadLoop)
cliechti8099bed2009-08-01 23:59:18 +0000408 self._thread.setDaemon(True)
409 self._thread.setName('pySerial RFC2217 reader thread for %s' % (self._port,))
410 self._thread.start()
411
cliechtiac205322009-08-02 20:40:21 +0000412 # negotiate Telnet/RFC2217 -> send initial requests
413 for option in self._telnet_options:
414 if option.state is REQUESTED:
cliechti1ef7e3e2009-08-03 02:38:43 +0000415 self.telnetSendOption(option.send_yes, option.option)
cliechtiac205322009-08-02 20:40:21 +0000416 # now wait until important options are negotiated
417 timeout_time = time.time() + 3
418 while time.time() < timeout_time:
cliechtiac205322009-08-02 20:40:21 +0000419 time.sleep(0.05) # prevent 100% CPU load
cliechti2b929b72009-08-02 23:49:02 +0000420 if sum(o.active for o in mandadory_options) == len(mandadory_options):
421 break
422 else:
423 raise SerialException("Remote does not seem to support RFC2217 or BINARY mode %r" % mandadory_options)
cliechti81c54762009-08-03 23:53:27 +0000424 if self.debug_output:
425 print self._telnet_options
cliechti8099bed2009-08-01 23:59:18 +0000426
427 # fine, go on, set RFC2271 specific things
428 self._reconfigurePort()
cliechti2b929b72009-08-02 23:49:02 +0000429 # all things set up get, now a clean start
cliechti8099bed2009-08-01 23:59:18 +0000430 self._isOpen = True
431 if not self._rtscts:
432 self.setRTS(True)
433 self.setDTR(True)
434 self.flushInput()
435 self.flushOutput()
436
437 def _reconfigurePort(self):
438 """Set communication parameters on opened port."""
439 if self._socket is None:
440 raise SerialException("Can only operate on open ports")
441
cliechti8099bed2009-08-01 23:59:18 +0000442 # if self._timeout != 0 and self._interCharTimeout is not None:
cliechti8099bed2009-08-01 23:59:18 +0000443 # XXX
444
445 if self._writeTimeout is not None:
446 raise NotImplementedError('writeTimeout is currently not supported')
cliechti2b929b72009-08-02 23:49:02 +0000447 # XXX
cliechti8099bed2009-08-01 23:59:18 +0000448
cliechti2b929b72009-08-02 23:49:02 +0000449 # Setup the connection
cliechti1ef7e3e2009-08-03 02:38:43 +0000450 # to get good performance, all parameter changes are sent first...
cliechti81c54762009-08-03 23:53:27 +0000451 if not isinstance(self._baudrate, (int, long)) or not 0 < self._baudrate < 2**32:
452 raise ValueError("invalid baudrate: %r" % (self._baudrate))
cliechti2b929b72009-08-02 23:49:02 +0000453 self._rfc2217_port_settings['baudrate'].set(struct.pack('!I', self._baudrate))
454 self._rfc2217_port_settings['datasize'].set(struct.pack('!B', self._bytesize))
455 self._rfc2217_port_settings['parity'].set(struct.pack('!B', RFC2217_PARITY_MAP[self._parity]))
456 self._rfc2217_port_settings['stopsize'].set(struct.pack('!B', RFC2217_STOPBIT_MAP[self._stopbits]))
cliechti8099bed2009-08-01 23:59:18 +0000457
cliechti2b929b72009-08-02 23:49:02 +0000458 # and now wait until parameters are active
459 items = self._rfc2217_port_settings.values()
460 timeout_time = time.time() + 3
461 while time.time() < timeout_time:
462 time.sleep(0.05) # prevent 100% CPU load
463 if sum(o.active for o in items) == len(items):
464 break
465 else:
466 raise SerialException("Remote does not accept parameter change (RFC2217): %r" % items)
cliechti81c54762009-08-03 23:53:27 +0000467 if self.debug_output:
468 print items
cliechti2b929b72009-08-02 23:49:02 +0000469
cliechti8099bed2009-08-01 23:59:18 +0000470
471 if self._rtscts and self._xonxoff:
cliechti1ef7e3e2009-08-03 02:38:43 +0000472 raise ValueError('xonxoff and rtscts together are not supported')
cliechti8099bed2009-08-01 23:59:18 +0000473 elif self._rtscts:
cliechti1ef7e3e2009-08-03 02:38:43 +0000474 self.rfc2217SetControl(SET_CONTROL_USE_HW_FLOW_CONTROL)
cliechti8099bed2009-08-01 23:59:18 +0000475 elif self._xonxoff:
cliechti1ef7e3e2009-08-03 02:38:43 +0000476 self.rfc2217SetControl(SET_CONTROL_USE_SW_FLOW_CONTROL)
cliechti8099bed2009-08-01 23:59:18 +0000477 else:
cliechti1ef7e3e2009-08-03 02:38:43 +0000478 self.rfc2217SetControl(SET_CONTROL_USE_NO_FLOW_CONTROL)
cliechti8099bed2009-08-01 23:59:18 +0000479
480 def close(self):
481 """Close port"""
482 if self._isOpen:
483 if self._socket:
484 try:
485 self._socket.shutdown(socket.SHUT_RDWR)
486 self._socket.close()
487 except:
488 # ignore errors.
489 pass
490 self._socket = None
491 if self._thread:
492 self._thread.join()
493 self._isOpen = False
494 # in case of quick reconnects, give the server some time
495 time.sleep(0.3)
496
497 def makeDeviceName(self, port):
498 raise SerialException("there is no sensible way to turn numbers into URLs")
499
500 def fromURL(self, url):
cliechti1ef7e3e2009-08-03 02:38:43 +0000501 """extract host and port from an URL string"""
cliechti8099bed2009-08-01 23:59:18 +0000502 if url.lower().startswith("rfc2217://"): url = url[10:]
503 try:
cliechti81c54762009-08-03 23:53:27 +0000504 # is there a "path" (our options)?
505 if '/' in url:
506 # cut away options
507 url, options = url.split('/', 1)
508 # process options now, directly altering self
509 for option in options.split('/'):
510 if option == 'debug':
511 self.debug_output = True
512 elif option == 'ign_set_control':
513 self._ignore_set_control_answer = True
cliechti7cb78e82009-08-05 15:47:57 +0000514 elif option == 'poll_modem':
515 self._poll_modem_state = True
cliechti81c54762009-08-03 23:53:27 +0000516 else:
517 raise ValueError('unknown option: %r' % (option,))
518 # get host and port
cliechti8099bed2009-08-01 23:59:18 +0000519 host, port = url.split(':', 1) # may raise ValueError because of unpacking
520 port = int(port) # and this if it's not a number
521 if not 0 <= port < 65536: raise ValueError("port not in range 0...65535")
522 except ValueError, e:
cliechti81c54762009-08-03 23:53:27 +0000523 raise SerialException('expected a string in the form "[rfc2217://]<host>:<port>[/option[/option...]]": %s' % e)
cliechti8099bed2009-08-01 23:59:18 +0000524 return (host, port)
525
526 # - - - - - - - - - - - - - - - - - - - - - - - -
527
528 def inWaiting(self):
529 """Return the number of characters currently in the input buffer."""
530 if not self._isOpen: raise portNotOpenError
531 return self._read_buffer.qsize()
532
533 def read(self, size=1):
534 """Read size bytes from the serial port. If a timeout is set it may
535 return less characters as requested. With no timeout it will block
536 until the requested number of bytes is read."""
537 if not self._isOpen: raise portNotOpenError
538 data = bytearray()
539 try:
540 while len(data) < size:
cliechti81c54762009-08-03 23:53:27 +0000541 if self._thread is None:
542 raise SerialException('connection failed (reader thread died)')
cliechti8099bed2009-08-01 23:59:18 +0000543 data.append(self._read_buffer.get(True, self._timeout))
544 except Queue.Empty: # -> timeout
545 pass
546 return bytes(data)
547
548 def write(self, data):
549 """Output the given string over the serial port. Can block if the
550 connection is blocked. May raise SerialException if the connection is
551 closed."""
552 if not self._isOpen: raise portNotOpenError
cliechti81c54762009-08-03 23:53:27 +0000553 self._write_lock.acquire()
cliechti8099bed2009-08-01 23:59:18 +0000554 try:
cliechti81c54762009-08-03 23:53:27 +0000555 try:
556 self._socket.sendall(data.replace(IAC, IAC_DOUBLED))
557 except socket.error, e:
558 raise SerialException("socket connection failed: %s" % e) # XXX what exception if socket connection fails
559 finally:
560 self._write_lock.release()
cliechti8099bed2009-08-01 23:59:18 +0000561 return len(data)
562
563 def flushInput(self):
564 """Clear input buffer, discarding all that is in the buffer."""
565 if not self._isOpen: raise portNotOpenError
cliechti1ef7e3e2009-08-03 02:38:43 +0000566 self.rfc2217SendPurge(PURGE_RECEIVE_BUFFER)
cliechti8099bed2009-08-01 23:59:18 +0000567 # empty read buffer
568 while self._read_buffer.qsize():
569 self._read_buffer.get(False)
570
571 def flushOutput(self):
572 """Clear output buffer, aborting the current output and
573 discarding all that is in the buffer."""
574 if not self._isOpen: raise portNotOpenError
cliechti1ef7e3e2009-08-03 02:38:43 +0000575 self.rfc2217SendPurge(PURGE_TRANSMIT_BUFFER)
cliechti8099bed2009-08-01 23:59:18 +0000576
577 def sendBreak(self, duration=0.25):
578 """Send break condition. Timed, returns to idle state after given
579 duration."""
580 if not self._isOpen: raise portNotOpenError
581 self.setBreak(True)
582 time.sleep(duration)
583 self.setBreak(False)
584
585 def setBreak(self, level=True):
586 """Set break: Controls TXD. When active, to transmitting is
587 possible."""
588 if not self._isOpen: raise portNotOpenError
589 if level:
cliechti1ef7e3e2009-08-03 02:38:43 +0000590 self.rfc2217SetControl(SET_CONTROL_BREAK_ON)
cliechti8099bed2009-08-01 23:59:18 +0000591 else:
cliechti1ef7e3e2009-08-03 02:38:43 +0000592 self.rfc2217SetControl(SET_CONTROL_BREAK_OFF)
cliechti8099bed2009-08-01 23:59:18 +0000593
594 def setRTS(self, level=True):
595 """Set terminal status line: Request To Send"""
596 if not self._isOpen: raise portNotOpenError
597 if level:
cliechti1ef7e3e2009-08-03 02:38:43 +0000598 self.rfc2217SetControl(SET_CONTROL_RTS_ON)
cliechti8099bed2009-08-01 23:59:18 +0000599 else:
cliechti1ef7e3e2009-08-03 02:38:43 +0000600 self.rfc2217SetControl(SET_CONTROL_RTS_OFF)
cliechti8099bed2009-08-01 23:59:18 +0000601
602 def setDTR(self, level=True):
603 """Set terminal status line: Data Terminal Ready"""
604 if not self._isOpen: raise portNotOpenError
605 if level:
cliechti1ef7e3e2009-08-03 02:38:43 +0000606 self.rfc2217SetControl(SET_CONTROL_DTR_ON)
cliechti8099bed2009-08-01 23:59:18 +0000607 else:
cliechti1ef7e3e2009-08-03 02:38:43 +0000608 self.rfc2217SetControl(SET_CONTROL_DTR_OFF)
cliechti8099bed2009-08-01 23:59:18 +0000609
610 def getCTS(self):
611 """Read terminal status line: Clear To Send"""
612 if not self._isOpen: raise portNotOpenError
cliechti7cb78e82009-08-05 15:47:57 +0000613 return bool(self.getModemState() & MODEMSTATE_MASK_CTS)
cliechti8099bed2009-08-01 23:59:18 +0000614
615 def getDSR(self):
616 """Read terminal status line: Data Set Ready"""
617 if not self._isOpen: raise portNotOpenError
cliechti7cb78e82009-08-05 15:47:57 +0000618 return bool(self.getModemState() & MODEMSTATE_MASK_DSR)
cliechti8099bed2009-08-01 23:59:18 +0000619
620 def getRI(self):
621 """Read terminal status line: Ring Indicator"""
622 if not self._isOpen: raise portNotOpenError
cliechti7cb78e82009-08-05 15:47:57 +0000623 return bool(self.getModemState() & MODEMSTATE_MASK_RI)
cliechti8099bed2009-08-01 23:59:18 +0000624
625 def getCD(self):
626 """Read terminal status line: Carrier Detect"""
627 if not self._isOpen: raise portNotOpenError
cliechti7cb78e82009-08-05 15:47:57 +0000628 return bool(self.getModemState() & MODEMSTATE_MASK_CD)
cliechti8099bed2009-08-01 23:59:18 +0000629
630 # - - - platform specific - - -
631 # None so far
632
633 # - - - RFC2217 specific - - -
634
cliechti1ef7e3e2009-08-03 02:38:43 +0000635 def _telnetReadLoop(self):
cliechti8099bed2009-08-01 23:59:18 +0000636 """read loop for the socket"""
cliechti8099bed2009-08-01 23:59:18 +0000637 mode = M_NORMAL
638 suboption = None
cliechti81c54762009-08-03 23:53:27 +0000639 try:
640 while self._socket is not None:
641 try:
642 data = self._socket.recv(1024)
643 except socket.timeout:
644 # just need to get out of recv form time to time to check if
645 # still alive
646 continue
647 except socket.error:
648 # connection fails -> terminate loop
649 break
650 for byte in data:
651 if mode == M_NORMAL:
652 # interpret as command or as data
653 if byte == IAC:
654 mode = M_IAC_SEEN
cliechti8099bed2009-08-01 23:59:18 +0000655 else:
cliechti81c54762009-08-03 23:53:27 +0000656 # store data in read buffer or sub option buffer
657 # depending on state
658 if suboption is not None:
659 suboption.append(byte)
660 else:
661 self._read_buffer.put(byte)
662 elif mode == M_IAC_SEEN:
663 if byte == IAC:
664 # interpret as command doubled -> insert character
665 # itself
666 self._read_buffer.put(IAC)
667 mode = M_NORMAL
668 elif byte == SB:
669 # sub option start
670 suboption = bytearray()
671 mode = M_NORMAL
672 elif byte == SE:
673 # sub option end -> process it now
674 self._telnetProcessSubnegotiation(bytes(suboption))
675 suboption = None
676 mode = M_NORMAL
677 elif byte in (DO, DONT, WILL, WONT):
678 # negotiation
679 telnet_command = byte
680 mode = M_NEGOTIATE
681 else:
682 # other telnet commands
683 self._telnetProcessCommand(byte)
684 mode = M_NORMAL
685 elif mode == M_NEGOTIATE: # DO, DONT, WILL, WONT was received, option now following
686 self._telnetNegotiateOption(telnet_command, byte)
cliechti8099bed2009-08-01 23:59:18 +0000687 mode = M_NORMAL
cliechti81c54762009-08-03 23:53:27 +0000688 finally:
689 self._thread = None
690 if self.debug_output:
691 print "read thread terminated"
cliechti8099bed2009-08-01 23:59:18 +0000692
693 # - incoming telnet commands and options
694
cliechti1ef7e3e2009-08-03 02:38:43 +0000695 def _telnetProcessCommand(self, command):
cliechti8099bed2009-08-01 23:59:18 +0000696 """Process commands other than DO, DONT, WILL, WONT"""
cliechti1ef7e3e2009-08-03 02:38:43 +0000697 # Currently none. RFC2217 only uses negotiation and subnegotiation.
698 #~ print "_telnetProcessCommand %r" % ord(command)
cliechti8099bed2009-08-01 23:59:18 +0000699
cliechti1ef7e3e2009-08-03 02:38:43 +0000700 def _telnetNegotiateOption(self, command, option):
701 """Process incoming DO, DONT, WILL, WONT"""
cliechti2b929b72009-08-02 23:49:02 +0000702 # check our registered telnet options and forward command to them
703 # they know themselves if they have to answer or not
cliechtiac205322009-08-02 20:40:21 +0000704 known = False
705 for item in self._telnet_options:
cliechti2b929b72009-08-02 23:49:02 +0000706 # can have more than one match! as some options are duplicated for
707 # 'us' and 'them'
cliechtiac205322009-08-02 20:40:21 +0000708 if item.option == option:
cliechti2b929b72009-08-02 23:49:02 +0000709 item.process_incoming(command)
cliechtiac205322009-08-02 20:40:21 +0000710 known = True
711 if not known:
712 # handle unknown options
713 # only answer to positive requests and deny them
714 if command == WILL or command == DO:
cliechti1ef7e3e2009-08-03 02:38:43 +0000715 self.telnetSendOption((command == WILL and DONT or WONT), option)
cliechtiac205322009-08-02 20:40:21 +0000716
cliechti8099bed2009-08-01 23:59:18 +0000717
cliechti1ef7e3e2009-08-03 02:38:43 +0000718 def _telnetProcessSubnegotiation(self, suboption):
719 """Process subnegotiation, the data between IAC SB and IAC SE"""
cliechti8099bed2009-08-01 23:59:18 +0000720 if suboption[0:1] == COM_PORT_OPTION:
721 if suboption[1:2] == SERVER_NOTIFY_LINESTATE and len(suboption) >= 3:
cliechti672d0292009-08-03 02:01:57 +0000722 self._linestate = ord(suboption[2:3]) # ensure it is a number
cliechti81c54762009-08-03 23:53:27 +0000723 if self.debug_output:
724 print "NOTIFY_LINESTATE: %s" % self._linestate
cliechti8099bed2009-08-01 23:59:18 +0000725 elif suboption[1:2] == SERVER_NOTIFY_MODEMSTATE and len(suboption) >= 3:
cliechti672d0292009-08-03 02:01:57 +0000726 self._modemstate = ord(suboption[2:3]) # ensure it is a number
cliechti81c54762009-08-03 23:53:27 +0000727 if self.debug_output:
728 print "NOTIFY_MODEMSTATE: %s" % self._modemstate
cliechti7cb78e82009-08-05 15:47:57 +0000729 # update time when we think that a poll would make sense
730 self._modemstate_expires = time.time() + 0.3
cliechti672d0292009-08-03 02:01:57 +0000731 elif suboption[1:2] == FLOWCONTROL_SUSPEND:
732 self._remote_suspend_flow = True
733 elif suboption[1:2] == FLOWCONTROL_RESUME:
734 self._remote_suspend_flow = False
cliechti8099bed2009-08-01 23:59:18 +0000735 else:
cliechti2b929b72009-08-02 23:49:02 +0000736 for item in self._rfc2217_options.values():
737 if item.ack_option == suboption[1:2]:
cliechti81c54762009-08-03 23:53:27 +0000738 #~ print "processing COM_PORT_OPTION: %r" % list(suboption[1:])
cliechti2b929b72009-08-02 23:49:02 +0000739 item.checkAnswer(bytes(suboption[2:]))
740 break
741 else:
cliechti81c54762009-08-03 23:53:27 +0000742 if self.debug_output:
743 print "ignoring COM_PORT_OPTION: %r" % list(suboption[1:])
cliechti1ef7e3e2009-08-03 02:38:43 +0000744 #~ print "_telnetProcessSubnegotiation COM_PORT_OPTION %r" % suboption[1:]
cliechti8099bed2009-08-01 23:59:18 +0000745 else:
746 pass
cliechti1ef7e3e2009-08-03 02:38:43 +0000747 #~ print "_telnetProcessSubnegotiation unknown %r" % suboption
cliechti8099bed2009-08-01 23:59:18 +0000748
749 # - outgoing telnet commands and options
750
cliechti81c54762009-08-03 23:53:27 +0000751 def _internal_raw_write(self, data):
752 """internal socket write with no data escaping. used to send telnet stuff"""
753 self._write_lock.acquire()
754 try:
755 self._socket.sendall(data)
756 finally:
757 self._write_lock.release()
758
cliechti1ef7e3e2009-08-03 02:38:43 +0000759 def telnetSendOption(self, action, option):
cliechti8099bed2009-08-01 23:59:18 +0000760 """Send DO, DONT, WILL, WONT"""
cliechti81c54762009-08-03 23:53:27 +0000761 self._internal_raw_write(to_bytes([IAC, action, option]))
cliechti8099bed2009-08-01 23:59:18 +0000762
cliechti1ef7e3e2009-08-03 02:38:43 +0000763 def rfc2217SendSubnegotiation(self, option, value=[]):
cliechti8099bed2009-08-01 23:59:18 +0000764 """Subnegotiation of RFC2217 parameters"""
cliechti81c54762009-08-03 23:53:27 +0000765 self._internal_raw_write(to_bytes([IAC, SB, COM_PORT_OPTION, option] + list(value) + [IAC, SE]))
cliechti2b929b72009-08-02 23:49:02 +0000766
cliechti1ef7e3e2009-08-03 02:38:43 +0000767 def rfc2217SendPurge(self, value):
cliechti2b929b72009-08-02 23:49:02 +0000768 item = self._rfc2217_options['purge']
cliechti672d0292009-08-03 02:01:57 +0000769 item.set(value) # transmit desired purge type
cliechti81c54762009-08-03 23:53:27 +0000770 item.wait(3) # wait for acknowledge from the server
cliechti2b929b72009-08-02 23:49:02 +0000771
cliechti1ef7e3e2009-08-03 02:38:43 +0000772 def rfc2217SetControl(self, value):
cliechti81c54762009-08-03 23:53:27 +0000773 item = self._rfc2217_options['control']
774 item.set(value) # transmit desired purge type
775 if self._ignore_set_control_answer:
776 # answers are ignored when option is set. compatibility mode for
777 # servers that answers, but not the expected ones... (or no answer
778 # at all) i.e. sredird
779 time.sleep(0.1) # this helps getting the unit tests passed
780 else:
781 item.wait(3) # wait for acknowledge from the server
cliechti8099bed2009-08-01 23:59:18 +0000782
cliechti1ef7e3e2009-08-03 02:38:43 +0000783 def rfc2217FlowServerReady(self):
cliechti672d0292009-08-03 02:01:57 +0000784 """check if server is ready to receive data. block for some time when
785 not"""
786 #~ if self._remote_suspend_flow:
787 #~ wait---
788
cliechti7cb78e82009-08-05 15:47:57 +0000789 def getModemState(self):
790 """get last modem state (cached value. if value is "old", request a new
791 one. this cache helps that we don't issue to many requests when e.g. all
792 status lines, one after the other is queried by te user (getCTS, getDSR
793 etc.)"""
794 # active modem state polling enabled? is the value fresh enough?
795 if self._poll_modem_state and self._modemstate_expires < time.time():
796 # when it is older, request an update
797 self.rfc2217SendSubnegotiation(NOTIFY_MODEMSTATE)
798 timeout_time = time.time() + 3
799 while time.time() < timeout_time:
800 time.sleep(0.05) # prevent 100% CPU load
801 # when expiration time is updated, it means that there is a new
802 # value
803 if self._modemstate_expires > time.time():
804 break
805 # even when there is a timeout, do not generate an error just
806 # return the last known value. this way we can support buggy
807 # servers that do not respond to polls, but send automatic
808 # updates.
809 if self._modemstate is not None:
810 return self._modemstate
811 else:
812 # never received a notification from the server
cliechti8fb119c2009-08-05 23:39:45 +0000813 raise SerialException("remote sends no NOTIFY_MODEMSTATE")
cliechti8099bed2009-08-01 23:59:18 +0000814
815# assemble Serial class with the platform specific implementation and the base
816# for file-like behavior. for Python 2.6 and newer, that provide the new I/O
817# library, derive from io.RawIOBase
818try:
819 import io
820except ImportError:
821 # classic version with our own file-like emulation
822 class Serial(RFC2217Serial, FileLike):
823 pass
824else:
825 # io library present
826 class Serial(RFC2217Serial, io.RawIOBase):
827 pass
828
cliechti7cb78e82009-08-05 15:47:57 +0000829
cliechti130d1f02009-08-04 02:10:58 +0000830# ###
831# The following is code that helps implementing an RFC2217 server.
cliechti8099bed2009-08-01 23:59:18 +0000832
cliechti8ccc2ff2009-08-05 12:44:46 +0000833class PortManager(object):
cliechti130d1f02009-08-04 02:10:58 +0000834 """This class manages the state of Telnet and RFC2217. It needs a serial
835 instance and a connection to work with. connection is expected to implement
836 a thread safe write function"""
837
838 def __init__(self, serial_port, connection, debug_output=False):
839 self.serial = serial_port
840 self.connection = connection
841 self.debug_output = debug_output
cliechti86b593e2009-08-05 16:28:12 +0000842 self._client_is_rfc2217 = False
cliechti130d1f02009-08-04 02:10:58 +0000843
844 # filter state machine
845 self.mode = M_NORMAL
846 self.suboption = None
847 self.telnet_command = None
848
849 # states for modem/line control events
850 self.modemstate_mask = 255
851 self.last_modemstate = None
852 self.linstate_mask = 0
853
854 # all supported telnet options
855 self._telnet_options = [
856 TelnetOption(self, 'ECHO', ECHO, WILL, WONT, DO, DONT, REQUESTED),
857 TelnetOption(self, 'we-SGA', SGA, WILL, WONT, DO, DONT, REQUESTED),
858 TelnetOption(self, 'they-SGA', SGA, DO, DONT, WILL, WONT, INACTIVE),
859 TelnetOption(self, 'we-BINARY', BINARY, WILL, WONT, DO, DONT, INACTIVE),
860 TelnetOption(self, 'they-BINARY', BINARY, DO, DONT, WILL, WONT, REQUESTED),
cliechti86b593e2009-08-05 16:28:12 +0000861 TelnetOption(self, 'we-RFC2217', COM_PORT_OPTION, WILL, WONT, DO, DONT, REQUESTED, self._client_ok),
862 TelnetOption(self, 'they-RFC2217', COM_PORT_OPTION, DO, DONT, WILL, WONT, INACTIVE, self._client_ok),
cliechti130d1f02009-08-04 02:10:58 +0000863 ]
864
865 # negotiate Telnet/RFC2217 -> send initial requests
866 for option in self._telnet_options:
867 if option.state is REQUESTED:
868 self.telnetSendOption(option.send_yes, option.option)
869 # issue 1st modem state notification
cliechti86b593e2009-08-05 16:28:12 +0000870
871 def _client_ok(self):
872 """callback of telnet option. it gets called when option is activated.
873 this one here is used to detect when the client agrees on RFC 2217. a
874 flag is set so that other functions like check_modem_lines know if the
875 client is ok."""
876 # The callback is used for we and they so if one party agrees, we're
877 # already happy. it seems not all servers do the negotiation correctly
878 # and i guess there are incorrect clients too.. so be happy if client
879 # answers one or the other positively.
880 self._client_is_rfc2217 = True
cliechti8fb119c2009-08-05 23:39:45 +0000881 # this is to ensure that the client gets a notification, even if there
882 # was no change
883 self.check_modem_lines(force_notification=True)
cliechti130d1f02009-08-04 02:10:58 +0000884
885 # - outgoing telnet commands and options
886
887 def telnetSendOption(self, action, option):
888 """Send DO, DONT, WILL, WONT"""
889 self.connection.write(to_bytes([IAC, action, option]))
890
891 def rfc2217SendSubnegotiation(self, option, value=[]):
892 """Subnegotiation of RFC2217 parameters"""
893 self.connection.write(to_bytes([IAC, SB, COM_PORT_OPTION, option] + list(value) + [IAC, SE]))
894
895 # - check modem lines, needs to be called periodically from user to
896 # establish polling
897
cliechti7cb78e82009-08-05 15:47:57 +0000898 def check_modem_lines(self, force_notification=False):
cliechti130d1f02009-08-04 02:10:58 +0000899 modemstate = (
900 (self.serial.getCTS() and MODEMSTATE_MASK_CTS) |
901 (self.serial.getDSR() and MODEMSTATE_MASK_DSR) |
902 (self.serial.getRI() and MODEMSTATE_MASK_RI) |
903 (self.serial.getCD() and MODEMSTATE_MASK_CD)
904 )
cliechti7cb78e82009-08-05 15:47:57 +0000905 # check what has changed
906 deltas = modemstate ^ (self.last_modemstate or 0) # when last is None -> 0
907 if deltas & MODEMSTATE_MASK_CTS:
908 modemstate |= MODEMSTATE_MASK_CTS_CHANGE
909 if deltas & MODEMSTATE_MASK_DSR:
910 modemstate |= MODEMSTATE_MASK_DSR_CHANGE
911 if deltas & MODEMSTATE_MASK_RI:
912 modemstate |= MODEMSTATE_MASK_RI_CHANGE
913 if deltas & MODEMSTATE_MASK_CD:
914 modemstate |= MODEMSTATE_MASK_CD_CHANGE
915 # if new state is different and the mask allows this change, send
cliechti86b593e2009-08-05 16:28:12 +0000916 # notification. suppress notifications when client is not rfc2217
cliechti7cb78e82009-08-05 15:47:57 +0000917 if modemstate != self.last_modemstate or force_notification:
cliechti8fb119c2009-08-05 23:39:45 +0000918 if (self._client_is_rfc2217 and (modemstate & self.modemstate_mask)) or force_notification:
cliechti7cb78e82009-08-05 15:47:57 +0000919 self.rfc2217SendSubnegotiation(
920 SERVER_NOTIFY_MODEMSTATE,
921 to_bytes([modemstate & self.modemstate_mask])
922 )
923 if self.debug_output:
924 print "NOTIFY_MODEMSTATE: %s" % (modemstate,)
925 # save last state, but forget about deltas.
926 # otherwise it would also notify about changing deltas which is
927 # probably not very useful
928 self.last_modemstate = modemstate & 0xf0
cliechti130d1f02009-08-04 02:10:58 +0000929
cliechti32c10332009-08-05 13:23:43 +0000930 # - outgoing data escaping
931
932 def escape(self, data):
933 """this function is for the user. all outgoing data has to be properly
934 escaped, so that no IAC character in the data stream messes up the
935 Telnet state machine in the server.
936
937 socket.sendall(escape(data))
938 """
939 for byte in data:
940 if byte == IAC:
941 yield IAC
942 yield IAC
943 else:
944 yield byte
945
cliechti130d1f02009-08-04 02:10:58 +0000946 # - incoming data filter
947
948 def filter(self, data):
949 """handle a bunch of incoming bytes. this is a generator. it will yield
950 all characters not of interest for Telnet/RFC2217.
951
952 The idea is that the reader thread pushes data from the socket through
953 this filter:
954
955 for byte in filter(socket.recv(1024)):
956 # do things like CR/LF conversion/whatever
957 # and write data to the serial port
958 serial.write(byte)
959
960 (socket error handling code left as exercise for the reader)
961 """
962 for byte in data:
963 if self.mode == M_NORMAL:
964 # interpret as command or as data
965 if byte == IAC:
966 self.mode = M_IAC_SEEN
967 else:
968 # store data in sub option buffer or pass it to our
969 # consumer depending on state
970 if self.suboption is not None:
971 self.suboption.append(byte)
972 else:
973 yield byte
974 elif self.mode == M_IAC_SEEN:
975 if byte == IAC:
976 # interpret as command doubled -> insert character
977 # itself
cliechtib16e45b2009-08-05 17:59:53 +0000978 yield IAC
cliechti130d1f02009-08-04 02:10:58 +0000979 self.mode = M_NORMAL
980 elif byte == SB:
981 # sub option start
982 self.suboption = bytearray()
983 self.mode = M_NORMAL
984 elif byte == SE:
985 # sub option end -> process it now
986 self._telnetProcessSubnegotiation(bytes(self.suboption))
987 self.suboption = None
988 self.mode = M_NORMAL
989 elif byte in (DO, DONT, WILL, WONT):
990 # negotiation
991 self.telnet_command = byte
992 self.mode = M_NEGOTIATE
993 else:
994 # other telnet commands
995 self._telnetProcessCommand(byte)
996 self.mode = M_NORMAL
997 elif self.mode == M_NEGOTIATE: # DO, DONT, WILL, WONT was received, option now following
998 self._telnetNegotiateOption(self.telnet_command, byte)
999 self.mode = M_NORMAL
1000
1001 # - incoming telnet commands and options
1002
1003 def _telnetProcessCommand(self, command):
1004 """Process commands other than DO, DONT, WILL, WONT"""
1005 # Currently none. RFC2217 only uses negotiation and subnegotiation.
1006 #~ print "_telnetProcessCommand %r" % ord(command)
1007
1008 def _telnetNegotiateOption(self, command, option):
1009 """Process incoming DO, DONT, WILL, WONT"""
1010 # check our registered telnet options and forward command to them
1011 # they know themselves if they have to answer or not
1012 known = False
1013 for item in self._telnet_options:
1014 # can have more than one match! as some options are duplicated for
1015 # 'us' and 'them'
1016 if item.option == option:
1017 item.process_incoming(command)
1018 known = True
1019 if not known:
1020 # handle unknown options
1021 # only answer to positive requests and deny them
1022 if command == WILL or command == DO:
1023 self.telnetSendOption((command == WILL and DONT or WONT), option)
1024
1025
1026 def _telnetProcessSubnegotiation(self, suboption):
1027 """Process subnegotiation, the data between IAC SB and IAC SE"""
1028 if suboption[0:1] == COM_PORT_OPTION:
1029 if suboption[1:2] == SET_BAUDRATE:
1030 backup = self.serial.baudrate
1031 try:
1032 (self.serial.baudrate,) = struct.unpack("!I", suboption[2:6])
1033 except ValueError, e:
1034 if self.debug_output:
1035 print "failed to set baudrate: %s" % (e,)
1036 self.serial.baudrate = backup
1037 self.rfc2217SendSubnegotiation(SERVER_SET_BAUDRATE, struct.pack("!I", self.serial.baudrate))
1038 elif suboption[1:2] == SET_DATASIZE:
1039 backup = self.serial.bytesize
1040 try:
1041 (self.serial.bytesize,) = struct.unpack("!B", suboption[2:3])
1042 except ValueError, e:
1043 if self.debug_output:
1044 print "failed to set datasize: %s" % (e,)
1045 self.serial.bytesize = backup
1046 self.rfc2217SendSubnegotiation(SERVER_SET_DATASIZE, struct.pack("!B", self.serial.bytesize))
1047 elif suboption[1:2] == SET_PARITY:
1048 backup = self.serial.parity
1049 try:
1050 self.serial.parity = RFC2217_REVERSE_PARITY_MAP[struct.unpack("!B", suboption[2:3])[0]]
1051 except ValueError, e:
1052 if self.debug_output:
1053 print "failed to set parity: %s" % (e,)
1054 self.serial.parity = backup
1055 self.rfc2217SendSubnegotiation(
1056 SERVER_SET_PARITY,
1057 struct.pack("!B", RFC2217_PARITY_MAP[self.serial.parity])
1058 )
1059 elif suboption[1:2] == SET_STOPSIZE:
1060 backup = self.serial.stopbits
1061 try:
1062 self.serial.stopbits = RFC2217_REVERSE_STOPBIT_MAP[struct.unpack("!B", suboption[2:3])[0]]
1063 except ValueError, e:
1064 if self.debug_output:
1065 print "failed to set stopsize: %s" % (e,)
1066 self.serial.stopbits = backup
1067 self.rfc2217SendSubnegotiation(
1068 SERVER_SET_STOPSIZE,
1069 struct.pack("!B", RFC2217_STOPBIT_MAP[self.serial.stopbits])
1070 )
1071 elif suboption[1:2] == SET_CONTROL:
1072 if suboption[2:3] == SET_CONTROL_REQ_FLOW_SETTING:
1073 if self.serial.xonxoff:
1074 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_SW_FLOW_CONTROL)
1075 elif self.serial.rtscts:
1076 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_HW_FLOW_CONTROL)
1077 else:
1078 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_NO_FLOW_CONTROL)
1079 elif suboption[2:3] == SET_CONTROL_USE_NO_FLOW_CONTROL:
1080 self.serial.xonxoff = False
1081 self.serial.rtscts = False
1082 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_NO_FLOW_CONTROL)
1083 elif suboption[2:3] == SET_CONTROL_USE_SW_FLOW_CONTROL:
1084 self.serial.xonxoff = True
1085 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_SW_FLOW_CONTROL)
1086 elif suboption[2:3] == SET_CONTROL_USE_HW_FLOW_CONTROL:
1087 self.serial.rtscts = True
1088 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_HW_FLOW_CONTROL)
1089 elif suboption[2:3] == SET_CONTROL_REQ_BREAK_STATE:
1090 pass # XXX needs cached value
1091 elif suboption[2:3] == SET_CONTROL_BREAK_ON:
1092 self.serial.setBreak(True)
1093 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_BREAK_ON)
1094 elif suboption[2:3] == SET_CONTROL_BREAK_OFF:
1095 self.serial.setBreak(False)
1096 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_BREAK_OFF)
1097 elif suboption[2:3] == SET_CONTROL_REQ_DTR:
1098 pass # XXX needs cached value
1099 elif suboption[2:3] == SET_CONTROL_DTR_ON:
1100 self.serial.setDTR(True)
1101 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_DTR_ON)
1102 elif suboption[2:3] == SET_CONTROL_DTR_OFF:
1103 self.serial.setDTR(False)
1104 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_DTR_OFF)
1105 elif suboption[2:3] == SET_CONTROL_REQ_RTS:
1106 pass # XXX needs cached value
1107 #~ self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_RTS_ON)
1108 elif suboption[2:3] == SET_CONTROL_RTS_ON:
1109 self.serial.setRTS(True)
1110 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_RTS_ON)
1111 elif suboption[2:3] == SET_CONTROL_RTS_OFF:
1112 self.serial.setRTS(False)
1113 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_RTS_OFF)
1114 #~ elif suboption[2:3] == SET_CONTROL_REQ_FLOW_SETTING_IN:
1115 #~ elif suboption[2:3] == SET_CONTROL_USE_NO_FLOW_CONTROL_IN:
1116 #~ elif suboption[2:3] == SET_CONTROL_USE_SW_FLOW_CONTOL_IN:
1117 #~ elif suboption[2:3] == SET_CONTROL_USE_HW_FLOW_CONTOL_IN:
1118 #~ elif suboption[2:3] == SET_CONTROL_USE_DCD_FLOW_CONTROL:
1119 #~ elif suboption[2:3] == SET_CONTROL_USE_DTR_FLOW_CONTROL:
1120 #~ elif suboption[2:3] == SET_CONTROL_USE_DSR_FLOW_CONTROL:
1121 elif suboption[1:2] == NOTIFY_LINESTATE:
cliechti7cb78e82009-08-05 15:47:57 +00001122 # client polls for current state
1123 self.rfc2217SendSubnegotiation(
1124 SERVER_NOTIFY_LINESTATE,
1125 to_bytes([0]) # sorry, nothing like that imeplemented
1126 )
cliechti130d1f02009-08-04 02:10:58 +00001127 elif suboption[1:2] == NOTIFY_MODEMSTATE:
cliechti7cb78e82009-08-05 15:47:57 +00001128 # client polls for current state
1129 self.check_modem_lines(force_notification=True)
cliechti130d1f02009-08-04 02:10:58 +00001130 elif suboption[1:2] == FLOWCONTROL_SUSPEND:
1131 self._remote_suspend_flow = True
1132 elif suboption[1:2] == FLOWCONTROL_RESUME:
1133 self._remote_suspend_flow = False
1134 elif suboption[1:2] == SET_LINESTATE_MASK:
1135 self.linstate_mask = ord(suboption[2:3]) # ensure it is a number
1136 elif suboption[1:2] == SET_MODEMSTATE_MASK:
1137 self.modemstate_mask = ord(suboption[2:3]) # ensure it is a number
1138 elif suboption[1:2] == PURGE_DATA:
1139 if suboption[2:3] == PURGE_RECEIVE_BUFFER:
1140 self.serial.flushInput()
1141 self.rfc2217SendSubnegotiation(SERVER_PURGE_DATA, PURGE_RECEIVE_BUFFER)
1142 elif suboption[2:3] == PURGE_TRANSMIT_BUFFER:
1143 self.serial.flushOutput()
1144 self.rfc2217SendSubnegotiation(SERVER_PURGE_DATA, PURGE_TRANSMIT_BUFFER)
1145 elif suboption[2:3] == PURGE_BOTH_BUFFERS:
1146 self.serial.flushInput()
1147 self.serial.flushOutput()
1148 self.rfc2217SendSubnegotiation(SERVER_PURGE_DATA, PURGE_BOTH_BUFFERS)
1149 else:
1150 if self.debug_output:
1151 print "undefined PURGE_DATA: %r" % list(suboption[2:])
1152 else:
1153 if self.debug_output:
1154 print "undefined COM_PORT_OPTION: %r" % list(suboption[1:])
1155 else:
1156 pass
1157 #~ print "_telnetProcessSubnegotiation unknown %r" % suboption
1158
1159
1160# simple client test
cliechti8099bed2009-08-01 23:59:18 +00001161if __name__ == '__main__':
1162 import sys
1163 s = Serial('rfc2217://localhost:7000', 115200)
1164 sys.stdout.write('%s\n' % s)
1165
cliechti2b929b72009-08-02 23:49:02 +00001166 #~ s.baudrate = 1898
1167
cliechti8099bed2009-08-01 23:59:18 +00001168 sys.stdout.write("write...\n")
1169 s.write("hello\n")
1170 s.flush()
cliechti8099bed2009-08-01 23:59:18 +00001171 sys.stdout.write("read: %s\n" % s.read(5))
1172
1173 #~ s.baudrate = 19200
1174 #~ s.databits = 7
1175 s.close()