blob: c50c31ae83a80911423ba3cd4db9992e8a6f2a5c [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))
359 except Exception, msg:
360 self._socket = None
361 raise SerialException("Could not open port %s: %s" % (self.portstr, msg))
362
cliechti1ef7e3e2009-08-03 02:38:43 +0000363 self._socket.settimeout(5) # XXX good value?
cliechti8099bed2009-08-01 23:59:18 +0000364
cliechti1ef7e3e2009-08-03 02:38:43 +0000365 # use a thread save queue as buffer. it also simplifies implementing
366 # the read timeout
cliechti8099bed2009-08-01 23:59:18 +0000367 self._read_buffer = Queue.Queue()
cliechti81c54762009-08-03 23:53:27 +0000368 # to ensure that user writes does not interfere with internal
369 # telnet/rfc2217 options establish a lock
370 self._write_lock = threading.Lock()
cliechtiac205322009-08-02 20:40:21 +0000371 # name the following separately so that, below, a check can be easily done
372 mandadory_options = [
cliechti2b929b72009-08-02 23:49:02 +0000373 TelnetOption(self, 'we-BINARY', BINARY, WILL, WONT, DO, DONT, INACTIVE),
cliechti2b929b72009-08-02 23:49:02 +0000374 TelnetOption(self, 'we-RFC2217', COM_PORT_OPTION, WILL, WONT, DO, DONT, REQUESTED),
cliechtiac205322009-08-02 20:40:21 +0000375 ]
376 # all supported telnet options
377 self._telnet_options = [
cliechtia29275e2009-08-03 00:08:04 +0000378 TelnetOption(self, 'ECHO', ECHO, DO, DONT, WILL, WONT, REQUESTED),
cliechti2b929b72009-08-02 23:49:02 +0000379 TelnetOption(self, 'we-SGA', SGA, WILL, WONT, DO, DONT, REQUESTED),
380 TelnetOption(self, 'they-SGA', SGA, DO, DONT, WILL, WONT, REQUESTED),
cliechti81c54762009-08-03 23:53:27 +0000381 TelnetOption(self, 'they-BINARY', BINARY, DO, DONT, WILL, WONT, INACTIVE),
382 TelnetOption(self, 'they-RFC2217', COM_PORT_OPTION, DO, DONT, WILL, WONT, REQUESTED),
cliechtiac205322009-08-02 20:40:21 +0000383 ] + mandadory_options
384 # RFC2217 specific states
cliechti2b929b72009-08-02 23:49:02 +0000385 # COM port settings
386 self._rfc2217_port_settings = {
387 'baudrate': TelnetSubnegotiation(self, 'baudrate', SET_BAUDRATE, SERVER_SET_BAUDRATE),
388 'datasize': TelnetSubnegotiation(self, 'datasize', SET_DATASIZE, SERVER_SET_DATASIZE),
389 'parity': TelnetSubnegotiation(self, 'parity', SET_PARITY, SERVER_SET_PARITY),
390 'stopsize': TelnetSubnegotiation(self, 'stopsize', SET_STOPSIZE, SERVER_SET_STOPSIZE),
391 }
392 # There are more subnegotiation object, combine all in one dictionary
393 # for easy access
394 self._rfc2217_options = {
395 'purge': TelnetSubnegotiation(self, 'purge', PURGE_DATA, SERVER_PURGE_DATA),
cliechti81c54762009-08-03 23:53:27 +0000396 'control': TelnetSubnegotiation(self, 'control', SET_CONTROL, SERVER_SET_CONTROL),
cliechti2b929b72009-08-02 23:49:02 +0000397 }
398 self._rfc2217_options.update(self._rfc2217_port_settings)
399 # cache for line and modem states that the server sends to us
cliechti8099bed2009-08-01 23:59:18 +0000400 self._linestate = 0
cliechti7cb78e82009-08-05 15:47:57 +0000401 self._modemstate = None
402 self._modemstate_expires = 0
cliechti672d0292009-08-03 02:01:57 +0000403 # RFC2217 flow control between server and client
404 self._remote_suspend_flow = False
cliechti8099bed2009-08-01 23:59:18 +0000405
cliechti1ef7e3e2009-08-03 02:38:43 +0000406 self._thread = threading.Thread(target=self._telnetReadLoop)
cliechti8099bed2009-08-01 23:59:18 +0000407 self._thread.setDaemon(True)
408 self._thread.setName('pySerial RFC2217 reader thread for %s' % (self._port,))
409 self._thread.start()
410
cliechtiac205322009-08-02 20:40:21 +0000411 # negotiate Telnet/RFC2217 -> send initial requests
412 for option in self._telnet_options:
413 if option.state is REQUESTED:
cliechti1ef7e3e2009-08-03 02:38:43 +0000414 self.telnetSendOption(option.send_yes, option.option)
cliechtiac205322009-08-02 20:40:21 +0000415 # now wait until important options are negotiated
416 timeout_time = time.time() + 3
417 while time.time() < timeout_time:
cliechtiac205322009-08-02 20:40:21 +0000418 time.sleep(0.05) # prevent 100% CPU load
cliechti2b929b72009-08-02 23:49:02 +0000419 if sum(o.active for o in mandadory_options) == len(mandadory_options):
420 break
421 else:
422 raise SerialException("Remote does not seem to support RFC2217 or BINARY mode %r" % mandadory_options)
cliechti81c54762009-08-03 23:53:27 +0000423 if self.debug_output:
424 print self._telnet_options
cliechti8099bed2009-08-01 23:59:18 +0000425
426 # fine, go on, set RFC2271 specific things
427 self._reconfigurePort()
cliechti2b929b72009-08-02 23:49:02 +0000428 # all things set up get, now a clean start
cliechti8099bed2009-08-01 23:59:18 +0000429 self._isOpen = True
430 if not self._rtscts:
431 self.setRTS(True)
432 self.setDTR(True)
433 self.flushInput()
434 self.flushOutput()
435
436 def _reconfigurePort(self):
437 """Set communication parameters on opened port."""
438 if self._socket is None:
439 raise SerialException("Can only operate on open ports")
440
cliechti8099bed2009-08-01 23:59:18 +0000441 # if self._timeout != 0 and self._interCharTimeout is not None:
cliechti8099bed2009-08-01 23:59:18 +0000442 # XXX
443
444 if self._writeTimeout is not None:
445 raise NotImplementedError('writeTimeout is currently not supported')
cliechti2b929b72009-08-02 23:49:02 +0000446 # XXX
cliechti8099bed2009-08-01 23:59:18 +0000447
cliechti2b929b72009-08-02 23:49:02 +0000448 # Setup the connection
cliechti1ef7e3e2009-08-03 02:38:43 +0000449 # to get good performance, all parameter changes are sent first...
cliechti81c54762009-08-03 23:53:27 +0000450 if not isinstance(self._baudrate, (int, long)) or not 0 < self._baudrate < 2**32:
451 raise ValueError("invalid baudrate: %r" % (self._baudrate))
cliechti2b929b72009-08-02 23:49:02 +0000452 self._rfc2217_port_settings['baudrate'].set(struct.pack('!I', self._baudrate))
453 self._rfc2217_port_settings['datasize'].set(struct.pack('!B', self._bytesize))
454 self._rfc2217_port_settings['parity'].set(struct.pack('!B', RFC2217_PARITY_MAP[self._parity]))
455 self._rfc2217_port_settings['stopsize'].set(struct.pack('!B', RFC2217_STOPBIT_MAP[self._stopbits]))
cliechti8099bed2009-08-01 23:59:18 +0000456
cliechti2b929b72009-08-02 23:49:02 +0000457 # and now wait until parameters are active
458 items = self._rfc2217_port_settings.values()
459 timeout_time = time.time() + 3
460 while time.time() < timeout_time:
461 time.sleep(0.05) # prevent 100% CPU load
462 if sum(o.active for o in items) == len(items):
463 break
464 else:
465 raise SerialException("Remote does not accept parameter change (RFC2217): %r" % items)
cliechti81c54762009-08-03 23:53:27 +0000466 if self.debug_output:
467 print items
cliechti2b929b72009-08-02 23:49:02 +0000468
cliechti8099bed2009-08-01 23:59:18 +0000469
470 if self._rtscts and self._xonxoff:
cliechti1ef7e3e2009-08-03 02:38:43 +0000471 raise ValueError('xonxoff and rtscts together are not supported')
cliechti8099bed2009-08-01 23:59:18 +0000472 elif self._rtscts:
cliechti1ef7e3e2009-08-03 02:38:43 +0000473 self.rfc2217SetControl(SET_CONTROL_USE_HW_FLOW_CONTROL)
cliechti8099bed2009-08-01 23:59:18 +0000474 elif self._xonxoff:
cliechti1ef7e3e2009-08-03 02:38:43 +0000475 self.rfc2217SetControl(SET_CONTROL_USE_SW_FLOW_CONTROL)
cliechti8099bed2009-08-01 23:59:18 +0000476 else:
cliechti1ef7e3e2009-08-03 02:38:43 +0000477 self.rfc2217SetControl(SET_CONTROL_USE_NO_FLOW_CONTROL)
cliechti8099bed2009-08-01 23:59:18 +0000478
479 def close(self):
480 """Close port"""
481 if self._isOpen:
482 if self._socket:
483 try:
484 self._socket.shutdown(socket.SHUT_RDWR)
485 self._socket.close()
486 except:
487 # ignore errors.
488 pass
489 self._socket = None
490 if self._thread:
491 self._thread.join()
492 self._isOpen = False
493 # in case of quick reconnects, give the server some time
494 time.sleep(0.3)
495
496 def makeDeviceName(self, port):
497 raise SerialException("there is no sensible way to turn numbers into URLs")
498
499 def fromURL(self, url):
cliechti1ef7e3e2009-08-03 02:38:43 +0000500 """extract host and port from an URL string"""
cliechti8099bed2009-08-01 23:59:18 +0000501 if url.lower().startswith("rfc2217://"): url = url[10:]
502 try:
cliechti81c54762009-08-03 23:53:27 +0000503 # is there a "path" (our options)?
504 if '/' in url:
505 # cut away options
506 url, options = url.split('/', 1)
507 # process options now, directly altering self
508 for option in options.split('/'):
509 if option == 'debug':
510 self.debug_output = True
511 elif option == 'ign_set_control':
512 self._ignore_set_control_answer = True
cliechti7cb78e82009-08-05 15:47:57 +0000513 elif option == 'poll_modem':
514 self._poll_modem_state = True
cliechti81c54762009-08-03 23:53:27 +0000515 else:
516 raise ValueError('unknown option: %r' % (option,))
517 # get host and port
cliechti8099bed2009-08-01 23:59:18 +0000518 host, port = url.split(':', 1) # may raise ValueError because of unpacking
519 port = int(port) # and this if it's not a number
520 if not 0 <= port < 65536: raise ValueError("port not in range 0...65535")
521 except ValueError, e:
cliechti81c54762009-08-03 23:53:27 +0000522 raise SerialException('expected a string in the form "[rfc2217://]<host>:<port>[/option[/option...]]": %s' % e)
cliechti8099bed2009-08-01 23:59:18 +0000523 return (host, port)
524
525 # - - - - - - - - - - - - - - - - - - - - - - - -
526
527 def inWaiting(self):
528 """Return the number of characters currently in the input buffer."""
529 if not self._isOpen: raise portNotOpenError
530 return self._read_buffer.qsize()
531
532 def read(self, size=1):
533 """Read size bytes from the serial port. If a timeout is set it may
534 return less characters as requested. With no timeout it will block
535 until the requested number of bytes is read."""
536 if not self._isOpen: raise portNotOpenError
537 data = bytearray()
538 try:
539 while len(data) < size:
cliechti81c54762009-08-03 23:53:27 +0000540 if self._thread is None:
541 raise SerialException('connection failed (reader thread died)')
cliechti8099bed2009-08-01 23:59:18 +0000542 data.append(self._read_buffer.get(True, self._timeout))
543 except Queue.Empty: # -> timeout
544 pass
545 return bytes(data)
546
547 def write(self, data):
548 """Output the given string over the serial port. Can block if the
549 connection is blocked. May raise SerialException if the connection is
550 closed."""
551 if not self._isOpen: raise portNotOpenError
cliechti81c54762009-08-03 23:53:27 +0000552 self._write_lock.acquire()
cliechti8099bed2009-08-01 23:59:18 +0000553 try:
cliechti81c54762009-08-03 23:53:27 +0000554 try:
555 self._socket.sendall(data.replace(IAC, IAC_DOUBLED))
556 except socket.error, e:
557 raise SerialException("socket connection failed: %s" % e) # XXX what exception if socket connection fails
558 finally:
559 self._write_lock.release()
cliechti8099bed2009-08-01 23:59:18 +0000560 return len(data)
561
562 def flushInput(self):
563 """Clear input buffer, discarding all that is in the buffer."""
564 if not self._isOpen: raise portNotOpenError
cliechti1ef7e3e2009-08-03 02:38:43 +0000565 self.rfc2217SendPurge(PURGE_RECEIVE_BUFFER)
cliechti8099bed2009-08-01 23:59:18 +0000566 # empty read buffer
567 while self._read_buffer.qsize():
568 self._read_buffer.get(False)
569
570 def flushOutput(self):
571 """Clear output buffer, aborting the current output and
572 discarding all that is in the buffer."""
573 if not self._isOpen: raise portNotOpenError
cliechti1ef7e3e2009-08-03 02:38:43 +0000574 self.rfc2217SendPurge(PURGE_TRANSMIT_BUFFER)
cliechti8099bed2009-08-01 23:59:18 +0000575
576 def sendBreak(self, duration=0.25):
577 """Send break condition. Timed, returns to idle state after given
578 duration."""
579 if not self._isOpen: raise portNotOpenError
580 self.setBreak(True)
581 time.sleep(duration)
582 self.setBreak(False)
583
584 def setBreak(self, level=True):
585 """Set break: Controls TXD. When active, to transmitting is
586 possible."""
587 if not self._isOpen: raise portNotOpenError
588 if level:
cliechti1ef7e3e2009-08-03 02:38:43 +0000589 self.rfc2217SetControl(SET_CONTROL_BREAK_ON)
cliechti8099bed2009-08-01 23:59:18 +0000590 else:
cliechti1ef7e3e2009-08-03 02:38:43 +0000591 self.rfc2217SetControl(SET_CONTROL_BREAK_OFF)
cliechti8099bed2009-08-01 23:59:18 +0000592
593 def setRTS(self, level=True):
594 """Set terminal status line: Request To Send"""
595 if not self._isOpen: raise portNotOpenError
596 if level:
cliechti1ef7e3e2009-08-03 02:38:43 +0000597 self.rfc2217SetControl(SET_CONTROL_RTS_ON)
cliechti8099bed2009-08-01 23:59:18 +0000598 else:
cliechti1ef7e3e2009-08-03 02:38:43 +0000599 self.rfc2217SetControl(SET_CONTROL_RTS_OFF)
cliechti8099bed2009-08-01 23:59:18 +0000600
601 def setDTR(self, level=True):
602 """Set terminal status line: Data Terminal Ready"""
603 if not self._isOpen: raise portNotOpenError
604 if level:
cliechti1ef7e3e2009-08-03 02:38:43 +0000605 self.rfc2217SetControl(SET_CONTROL_DTR_ON)
cliechti8099bed2009-08-01 23:59:18 +0000606 else:
cliechti1ef7e3e2009-08-03 02:38:43 +0000607 self.rfc2217SetControl(SET_CONTROL_DTR_OFF)
cliechti8099bed2009-08-01 23:59:18 +0000608
609 def getCTS(self):
610 """Read terminal status line: Clear To Send"""
611 if not self._isOpen: raise portNotOpenError
cliechti7cb78e82009-08-05 15:47:57 +0000612 return bool(self.getModemState() & MODEMSTATE_MASK_CTS)
cliechti8099bed2009-08-01 23:59:18 +0000613
614 def getDSR(self):
615 """Read terminal status line: Data Set Ready"""
616 if not self._isOpen: raise portNotOpenError
cliechti7cb78e82009-08-05 15:47:57 +0000617 return bool(self.getModemState() & MODEMSTATE_MASK_DSR)
cliechti8099bed2009-08-01 23:59:18 +0000618
619 def getRI(self):
620 """Read terminal status line: Ring Indicator"""
621 if not self._isOpen: raise portNotOpenError
cliechti7cb78e82009-08-05 15:47:57 +0000622 return bool(self.getModemState() & MODEMSTATE_MASK_RI)
cliechti8099bed2009-08-01 23:59:18 +0000623
624 def getCD(self):
625 """Read terminal status line: Carrier Detect"""
626 if not self._isOpen: raise portNotOpenError
cliechti7cb78e82009-08-05 15:47:57 +0000627 return bool(self.getModemState() & MODEMSTATE_MASK_CD)
cliechti8099bed2009-08-01 23:59:18 +0000628
629 # - - - platform specific - - -
630 # None so far
631
632 # - - - RFC2217 specific - - -
633
cliechti1ef7e3e2009-08-03 02:38:43 +0000634 def _telnetReadLoop(self):
cliechti8099bed2009-08-01 23:59:18 +0000635 """read loop for the socket"""
cliechti8099bed2009-08-01 23:59:18 +0000636 mode = M_NORMAL
637 suboption = None
cliechti81c54762009-08-03 23:53:27 +0000638 try:
639 while self._socket is not None:
640 try:
641 data = self._socket.recv(1024)
642 except socket.timeout:
643 # just need to get out of recv form time to time to check if
644 # still alive
645 continue
646 except socket.error:
647 # connection fails -> terminate loop
648 break
649 for byte in data:
650 if mode == M_NORMAL:
651 # interpret as command or as data
652 if byte == IAC:
653 mode = M_IAC_SEEN
cliechti8099bed2009-08-01 23:59:18 +0000654 else:
cliechti81c54762009-08-03 23:53:27 +0000655 # store data in read buffer or sub option buffer
656 # depending on state
657 if suboption is not None:
658 suboption.append(byte)
659 else:
660 self._read_buffer.put(byte)
661 elif mode == M_IAC_SEEN:
662 if byte == IAC:
663 # interpret as command doubled -> insert character
664 # itself
665 self._read_buffer.put(IAC)
666 mode = M_NORMAL
667 elif byte == SB:
668 # sub option start
669 suboption = bytearray()
670 mode = M_NORMAL
671 elif byte == SE:
672 # sub option end -> process it now
673 self._telnetProcessSubnegotiation(bytes(suboption))
674 suboption = None
675 mode = M_NORMAL
676 elif byte in (DO, DONT, WILL, WONT):
677 # negotiation
678 telnet_command = byte
679 mode = M_NEGOTIATE
680 else:
681 # other telnet commands
682 self._telnetProcessCommand(byte)
683 mode = M_NORMAL
684 elif mode == M_NEGOTIATE: # DO, DONT, WILL, WONT was received, option now following
685 self._telnetNegotiateOption(telnet_command, byte)
cliechti8099bed2009-08-01 23:59:18 +0000686 mode = M_NORMAL
cliechti81c54762009-08-03 23:53:27 +0000687 finally:
688 self._thread = None
689 if self.debug_output:
690 print "read thread terminated"
cliechti8099bed2009-08-01 23:59:18 +0000691
692 # - incoming telnet commands and options
693
cliechti1ef7e3e2009-08-03 02:38:43 +0000694 def _telnetProcessCommand(self, command):
cliechti8099bed2009-08-01 23:59:18 +0000695 """Process commands other than DO, DONT, WILL, WONT"""
cliechti1ef7e3e2009-08-03 02:38:43 +0000696 # Currently none. RFC2217 only uses negotiation and subnegotiation.
697 #~ print "_telnetProcessCommand %r" % ord(command)
cliechti8099bed2009-08-01 23:59:18 +0000698
cliechti1ef7e3e2009-08-03 02:38:43 +0000699 def _telnetNegotiateOption(self, command, option):
700 """Process incoming DO, DONT, WILL, WONT"""
cliechti2b929b72009-08-02 23:49:02 +0000701 # check our registered telnet options and forward command to them
702 # they know themselves if they have to answer or not
cliechtiac205322009-08-02 20:40:21 +0000703 known = False
704 for item in self._telnet_options:
cliechti2b929b72009-08-02 23:49:02 +0000705 # can have more than one match! as some options are duplicated for
706 # 'us' and 'them'
cliechtiac205322009-08-02 20:40:21 +0000707 if item.option == option:
cliechti2b929b72009-08-02 23:49:02 +0000708 item.process_incoming(command)
cliechtiac205322009-08-02 20:40:21 +0000709 known = True
710 if not known:
711 # handle unknown options
712 # only answer to positive requests and deny them
713 if command == WILL or command == DO:
cliechti1ef7e3e2009-08-03 02:38:43 +0000714 self.telnetSendOption((command == WILL and DONT or WONT), option)
cliechtiac205322009-08-02 20:40:21 +0000715
cliechti8099bed2009-08-01 23:59:18 +0000716
cliechti1ef7e3e2009-08-03 02:38:43 +0000717 def _telnetProcessSubnegotiation(self, suboption):
718 """Process subnegotiation, the data between IAC SB and IAC SE"""
cliechti8099bed2009-08-01 23:59:18 +0000719 if suboption[0:1] == COM_PORT_OPTION:
720 if suboption[1:2] == SERVER_NOTIFY_LINESTATE and len(suboption) >= 3:
cliechti672d0292009-08-03 02:01:57 +0000721 self._linestate = ord(suboption[2:3]) # ensure it is a number
cliechti81c54762009-08-03 23:53:27 +0000722 if self.debug_output:
723 print "NOTIFY_LINESTATE: %s" % self._linestate
cliechti8099bed2009-08-01 23:59:18 +0000724 elif suboption[1:2] == SERVER_NOTIFY_MODEMSTATE and len(suboption) >= 3:
cliechti672d0292009-08-03 02:01:57 +0000725 self._modemstate = ord(suboption[2:3]) # ensure it is a number
cliechti81c54762009-08-03 23:53:27 +0000726 if self.debug_output:
727 print "NOTIFY_MODEMSTATE: %s" % self._modemstate
cliechti7cb78e82009-08-05 15:47:57 +0000728 # update time when we think that a poll would make sense
729 self._modemstate_expires = time.time() + 0.3
cliechti672d0292009-08-03 02:01:57 +0000730 elif suboption[1:2] == FLOWCONTROL_SUSPEND:
731 self._remote_suspend_flow = True
732 elif suboption[1:2] == FLOWCONTROL_RESUME:
733 self._remote_suspend_flow = False
cliechti8099bed2009-08-01 23:59:18 +0000734 else:
cliechti2b929b72009-08-02 23:49:02 +0000735 for item in self._rfc2217_options.values():
736 if item.ack_option == suboption[1:2]:
cliechti81c54762009-08-03 23:53:27 +0000737 #~ print "processing COM_PORT_OPTION: %r" % list(suboption[1:])
cliechti2b929b72009-08-02 23:49:02 +0000738 item.checkAnswer(bytes(suboption[2:]))
739 break
740 else:
cliechti81c54762009-08-03 23:53:27 +0000741 if self.debug_output:
742 print "ignoring COM_PORT_OPTION: %r" % list(suboption[1:])
cliechti1ef7e3e2009-08-03 02:38:43 +0000743 #~ print "_telnetProcessSubnegotiation COM_PORT_OPTION %r" % suboption[1:]
cliechti8099bed2009-08-01 23:59:18 +0000744 else:
745 pass
cliechti1ef7e3e2009-08-03 02:38:43 +0000746 #~ print "_telnetProcessSubnegotiation unknown %r" % suboption
cliechti8099bed2009-08-01 23:59:18 +0000747
748 # - outgoing telnet commands and options
749
cliechti81c54762009-08-03 23:53:27 +0000750 def _internal_raw_write(self, data):
751 """internal socket write with no data escaping. used to send telnet stuff"""
752 self._write_lock.acquire()
753 try:
754 self._socket.sendall(data)
755 finally:
756 self._write_lock.release()
757
cliechti1ef7e3e2009-08-03 02:38:43 +0000758 def telnetSendOption(self, action, option):
cliechti8099bed2009-08-01 23:59:18 +0000759 """Send DO, DONT, WILL, WONT"""
cliechti81c54762009-08-03 23:53:27 +0000760 self._internal_raw_write(to_bytes([IAC, action, option]))
cliechti8099bed2009-08-01 23:59:18 +0000761
cliechti1ef7e3e2009-08-03 02:38:43 +0000762 def rfc2217SendSubnegotiation(self, option, value=[]):
cliechti8099bed2009-08-01 23:59:18 +0000763 """Subnegotiation of RFC2217 parameters"""
cliechti81c54762009-08-03 23:53:27 +0000764 self._internal_raw_write(to_bytes([IAC, SB, COM_PORT_OPTION, option] + list(value) + [IAC, SE]))
cliechti2b929b72009-08-02 23:49:02 +0000765
cliechti1ef7e3e2009-08-03 02:38:43 +0000766 def rfc2217SendPurge(self, value):
cliechti2b929b72009-08-02 23:49:02 +0000767 item = self._rfc2217_options['purge']
cliechti672d0292009-08-03 02:01:57 +0000768 item.set(value) # transmit desired purge type
cliechti81c54762009-08-03 23:53:27 +0000769 item.wait(3) # wait for acknowledge from the server
cliechti2b929b72009-08-02 23:49:02 +0000770
cliechti1ef7e3e2009-08-03 02:38:43 +0000771 def rfc2217SetControl(self, value):
cliechti81c54762009-08-03 23:53:27 +0000772 item = self._rfc2217_options['control']
773 item.set(value) # transmit desired purge type
774 if self._ignore_set_control_answer:
775 # answers are ignored when option is set. compatibility mode for
776 # servers that answers, but not the expected ones... (or no answer
777 # at all) i.e. sredird
778 time.sleep(0.1) # this helps getting the unit tests passed
779 else:
780 item.wait(3) # wait for acknowledge from the server
cliechti8099bed2009-08-01 23:59:18 +0000781
cliechti1ef7e3e2009-08-03 02:38:43 +0000782 def rfc2217FlowServerReady(self):
cliechti672d0292009-08-03 02:01:57 +0000783 """check if server is ready to receive data. block for some time when
784 not"""
785 #~ if self._remote_suspend_flow:
786 #~ wait---
787
cliechti7cb78e82009-08-05 15:47:57 +0000788 def getModemState(self):
789 """get last modem state (cached value. if value is "old", request a new
790 one. this cache helps that we don't issue to many requests when e.g. all
791 status lines, one after the other is queried by te user (getCTS, getDSR
792 etc.)"""
793 # active modem state polling enabled? is the value fresh enough?
794 if self._poll_modem_state and self._modemstate_expires < time.time():
795 # when it is older, request an update
796 self.rfc2217SendSubnegotiation(NOTIFY_MODEMSTATE)
797 timeout_time = time.time() + 3
798 while time.time() < timeout_time:
799 time.sleep(0.05) # prevent 100% CPU load
800 # when expiration time is updated, it means that there is a new
801 # value
802 if self._modemstate_expires > time.time():
803 break
804 # even when there is a timeout, do not generate an error just
805 # return the last known value. this way we can support buggy
806 # servers that do not respond to polls, but send automatic
807 # updates.
808 if self._modemstate is not None:
809 return self._modemstate
810 else:
811 # never received a notification from the server
812 raise SerialException("remote sends no NOTIFY_MODEMSTATE" % (self.name))
cliechti8099bed2009-08-01 23:59:18 +0000813
814# assemble Serial class with the platform specific implementation and the base
815# for file-like behavior. for Python 2.6 and newer, that provide the new I/O
816# library, derive from io.RawIOBase
817try:
818 import io
819except ImportError:
820 # classic version with our own file-like emulation
821 class Serial(RFC2217Serial, FileLike):
822 pass
823else:
824 # io library present
825 class Serial(RFC2217Serial, io.RawIOBase):
826 pass
827
cliechti7cb78e82009-08-05 15:47:57 +0000828
cliechti130d1f02009-08-04 02:10:58 +0000829# ###
830# The following is code that helps implementing an RFC2217 server.
cliechti8099bed2009-08-01 23:59:18 +0000831
cliechti8ccc2ff2009-08-05 12:44:46 +0000832class PortManager(object):
cliechti130d1f02009-08-04 02:10:58 +0000833 """This class manages the state of Telnet and RFC2217. It needs a serial
834 instance and a connection to work with. connection is expected to implement
835 a thread safe write function"""
836
837 def __init__(self, serial_port, connection, debug_output=False):
838 self.serial = serial_port
839 self.connection = connection
840 self.debug_output = debug_output
cliechti86b593e2009-08-05 16:28:12 +0000841 self._client_is_rfc2217 = False
cliechti130d1f02009-08-04 02:10:58 +0000842
843 # filter state machine
844 self.mode = M_NORMAL
845 self.suboption = None
846 self.telnet_command = None
847
848 # states for modem/line control events
849 self.modemstate_mask = 255
850 self.last_modemstate = None
851 self.linstate_mask = 0
852
853 # all supported telnet options
854 self._telnet_options = [
855 TelnetOption(self, 'ECHO', ECHO, WILL, WONT, DO, DONT, REQUESTED),
856 TelnetOption(self, 'we-SGA', SGA, WILL, WONT, DO, DONT, REQUESTED),
857 TelnetOption(self, 'they-SGA', SGA, DO, DONT, WILL, WONT, INACTIVE),
858 TelnetOption(self, 'we-BINARY', BINARY, WILL, WONT, DO, DONT, INACTIVE),
859 TelnetOption(self, 'they-BINARY', BINARY, DO, DONT, WILL, WONT, REQUESTED),
cliechti86b593e2009-08-05 16:28:12 +0000860 TelnetOption(self, 'we-RFC2217', COM_PORT_OPTION, WILL, WONT, DO, DONT, REQUESTED, self._client_ok),
861 TelnetOption(self, 'they-RFC2217', COM_PORT_OPTION, DO, DONT, WILL, WONT, INACTIVE, self._client_ok),
cliechti130d1f02009-08-04 02:10:58 +0000862 ]
863
864 # negotiate Telnet/RFC2217 -> send initial requests
865 for option in self._telnet_options:
866 if option.state is REQUESTED:
867 self.telnetSendOption(option.send_yes, option.option)
868 # issue 1st modem state notification
cliechti86b593e2009-08-05 16:28:12 +0000869
870 def _client_ok(self):
871 """callback of telnet option. it gets called when option is activated.
872 this one here is used to detect when the client agrees on RFC 2217. a
873 flag is set so that other functions like check_modem_lines know if the
874 client is ok."""
875 # The callback is used for we and they so if one party agrees, we're
876 # already happy. it seems not all servers do the negotiation correctly
877 # and i guess there are incorrect clients too.. so be happy if client
878 # answers one or the other positively.
879 self._client_is_rfc2217 = True
cliechti130d1f02009-08-04 02:10:58 +0000880
881 # - outgoing telnet commands and options
882
883 def telnetSendOption(self, action, option):
884 """Send DO, DONT, WILL, WONT"""
885 self.connection.write(to_bytes([IAC, action, option]))
886
887 def rfc2217SendSubnegotiation(self, option, value=[]):
888 """Subnegotiation of RFC2217 parameters"""
889 self.connection.write(to_bytes([IAC, SB, COM_PORT_OPTION, option] + list(value) + [IAC, SE]))
890
891 # - check modem lines, needs to be called periodically from user to
892 # establish polling
893
cliechti7cb78e82009-08-05 15:47:57 +0000894 def check_modem_lines(self, force_notification=False):
cliechti130d1f02009-08-04 02:10:58 +0000895 modemstate = (
896 (self.serial.getCTS() and MODEMSTATE_MASK_CTS) |
897 (self.serial.getDSR() and MODEMSTATE_MASK_DSR) |
898 (self.serial.getRI() and MODEMSTATE_MASK_RI) |
899 (self.serial.getCD() and MODEMSTATE_MASK_CD)
900 )
cliechti7cb78e82009-08-05 15:47:57 +0000901 # check what has changed
902 deltas = modemstate ^ (self.last_modemstate or 0) # when last is None -> 0
903 if deltas & MODEMSTATE_MASK_CTS:
904 modemstate |= MODEMSTATE_MASK_CTS_CHANGE
905 if deltas & MODEMSTATE_MASK_DSR:
906 modemstate |= MODEMSTATE_MASK_DSR_CHANGE
907 if deltas & MODEMSTATE_MASK_RI:
908 modemstate |= MODEMSTATE_MASK_RI_CHANGE
909 if deltas & MODEMSTATE_MASK_CD:
910 modemstate |= MODEMSTATE_MASK_CD_CHANGE
911 # if new state is different and the mask allows this change, send
cliechti86b593e2009-08-05 16:28:12 +0000912 # notification. suppress notifications when client is not rfc2217
cliechti7cb78e82009-08-05 15:47:57 +0000913 if modemstate != self.last_modemstate or force_notification:
cliechti86b593e2009-08-05 16:28:12 +0000914 if (self._client_is_rfc2217 and modemstate & self.modemstate_mask) or force_notification:
cliechti7cb78e82009-08-05 15:47:57 +0000915 self.rfc2217SendSubnegotiation(
916 SERVER_NOTIFY_MODEMSTATE,
917 to_bytes([modemstate & self.modemstate_mask])
918 )
919 if self.debug_output:
920 print "NOTIFY_MODEMSTATE: %s" % (modemstate,)
921 # save last state, but forget about deltas.
922 # otherwise it would also notify about changing deltas which is
923 # probably not very useful
924 self.last_modemstate = modemstate & 0xf0
cliechti130d1f02009-08-04 02:10:58 +0000925
cliechti32c10332009-08-05 13:23:43 +0000926 # - outgoing data escaping
927
928 def escape(self, data):
929 """this function is for the user. all outgoing data has to be properly
930 escaped, so that no IAC character in the data stream messes up the
931 Telnet state machine in the server.
932
933 socket.sendall(escape(data))
934 """
935 for byte in data:
936 if byte == IAC:
937 yield IAC
938 yield IAC
939 else:
940 yield byte
941
cliechti130d1f02009-08-04 02:10:58 +0000942 # - incoming data filter
943
944 def filter(self, data):
945 """handle a bunch of incoming bytes. this is a generator. it will yield
946 all characters not of interest for Telnet/RFC2217.
947
948 The idea is that the reader thread pushes data from the socket through
949 this filter:
950
951 for byte in filter(socket.recv(1024)):
952 # do things like CR/LF conversion/whatever
953 # and write data to the serial port
954 serial.write(byte)
955
956 (socket error handling code left as exercise for the reader)
957 """
958 for byte in data:
959 if self.mode == M_NORMAL:
960 # interpret as command or as data
961 if byte == IAC:
962 self.mode = M_IAC_SEEN
963 else:
964 # store data in sub option buffer or pass it to our
965 # consumer depending on state
966 if self.suboption is not None:
967 self.suboption.append(byte)
968 else:
969 yield byte
970 elif self.mode == M_IAC_SEEN:
971 if byte == IAC:
972 # interpret as command doubled -> insert character
973 # itself
974 self._read_buffer.put(IAC)
975 self.mode = M_NORMAL
976 elif byte == SB:
977 # sub option start
978 self.suboption = bytearray()
979 self.mode = M_NORMAL
980 elif byte == SE:
981 # sub option end -> process it now
982 self._telnetProcessSubnegotiation(bytes(self.suboption))
983 self.suboption = None
984 self.mode = M_NORMAL
985 elif byte in (DO, DONT, WILL, WONT):
986 # negotiation
987 self.telnet_command = byte
988 self.mode = M_NEGOTIATE
989 else:
990 # other telnet commands
991 self._telnetProcessCommand(byte)
992 self.mode = M_NORMAL
993 elif self.mode == M_NEGOTIATE: # DO, DONT, WILL, WONT was received, option now following
994 self._telnetNegotiateOption(self.telnet_command, byte)
995 self.mode = M_NORMAL
996
997 # - incoming telnet commands and options
998
999 def _telnetProcessCommand(self, command):
1000 """Process commands other than DO, DONT, WILL, WONT"""
1001 # Currently none. RFC2217 only uses negotiation and subnegotiation.
1002 #~ print "_telnetProcessCommand %r" % ord(command)
1003
1004 def _telnetNegotiateOption(self, command, option):
1005 """Process incoming DO, DONT, WILL, WONT"""
1006 # check our registered telnet options and forward command to them
1007 # they know themselves if they have to answer or not
1008 known = False
1009 for item in self._telnet_options:
1010 # can have more than one match! as some options are duplicated for
1011 # 'us' and 'them'
1012 if item.option == option:
1013 item.process_incoming(command)
1014 known = True
1015 if not known:
1016 # handle unknown options
1017 # only answer to positive requests and deny them
1018 if command == WILL or command == DO:
1019 self.telnetSendOption((command == WILL and DONT or WONT), option)
1020
1021
1022 def _telnetProcessSubnegotiation(self, suboption):
1023 """Process subnegotiation, the data between IAC SB and IAC SE"""
1024 if suboption[0:1] == COM_PORT_OPTION:
1025 if suboption[1:2] == SET_BAUDRATE:
1026 backup = self.serial.baudrate
1027 try:
1028 (self.serial.baudrate,) = struct.unpack("!I", suboption[2:6])
1029 except ValueError, e:
1030 if self.debug_output:
1031 print "failed to set baudrate: %s" % (e,)
1032 self.serial.baudrate = backup
1033 self.rfc2217SendSubnegotiation(SERVER_SET_BAUDRATE, struct.pack("!I", self.serial.baudrate))
1034 elif suboption[1:2] == SET_DATASIZE:
1035 backup = self.serial.bytesize
1036 try:
1037 (self.serial.bytesize,) = struct.unpack("!B", suboption[2:3])
1038 except ValueError, e:
1039 if self.debug_output:
1040 print "failed to set datasize: %s" % (e,)
1041 self.serial.bytesize = backup
1042 self.rfc2217SendSubnegotiation(SERVER_SET_DATASIZE, struct.pack("!B", self.serial.bytesize))
1043 elif suboption[1:2] == SET_PARITY:
1044 backup = self.serial.parity
1045 try:
1046 self.serial.parity = RFC2217_REVERSE_PARITY_MAP[struct.unpack("!B", suboption[2:3])[0]]
1047 except ValueError, e:
1048 if self.debug_output:
1049 print "failed to set parity: %s" % (e,)
1050 self.serial.parity = backup
1051 self.rfc2217SendSubnegotiation(
1052 SERVER_SET_PARITY,
1053 struct.pack("!B", RFC2217_PARITY_MAP[self.serial.parity])
1054 )
1055 elif suboption[1:2] == SET_STOPSIZE:
1056 backup = self.serial.stopbits
1057 try:
1058 self.serial.stopbits = RFC2217_REVERSE_STOPBIT_MAP[struct.unpack("!B", suboption[2:3])[0]]
1059 except ValueError, e:
1060 if self.debug_output:
1061 print "failed to set stopsize: %s" % (e,)
1062 self.serial.stopbits = backup
1063 self.rfc2217SendSubnegotiation(
1064 SERVER_SET_STOPSIZE,
1065 struct.pack("!B", RFC2217_STOPBIT_MAP[self.serial.stopbits])
1066 )
1067 elif suboption[1:2] == SET_CONTROL:
1068 if suboption[2:3] == SET_CONTROL_REQ_FLOW_SETTING:
1069 if self.serial.xonxoff:
1070 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_SW_FLOW_CONTROL)
1071 elif self.serial.rtscts:
1072 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_HW_FLOW_CONTROL)
1073 else:
1074 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_NO_FLOW_CONTROL)
1075 elif suboption[2:3] == SET_CONTROL_USE_NO_FLOW_CONTROL:
1076 self.serial.xonxoff = False
1077 self.serial.rtscts = False
1078 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_NO_FLOW_CONTROL)
1079 elif suboption[2:3] == SET_CONTROL_USE_SW_FLOW_CONTROL:
1080 self.serial.xonxoff = True
1081 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_SW_FLOW_CONTROL)
1082 elif suboption[2:3] == SET_CONTROL_USE_HW_FLOW_CONTROL:
1083 self.serial.rtscts = True
1084 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_HW_FLOW_CONTROL)
1085 elif suboption[2:3] == SET_CONTROL_REQ_BREAK_STATE:
1086 pass # XXX needs cached value
1087 elif suboption[2:3] == SET_CONTROL_BREAK_ON:
1088 self.serial.setBreak(True)
1089 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_BREAK_ON)
1090 elif suboption[2:3] == SET_CONTROL_BREAK_OFF:
1091 self.serial.setBreak(False)
1092 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_BREAK_OFF)
1093 elif suboption[2:3] == SET_CONTROL_REQ_DTR:
1094 pass # XXX needs cached value
1095 elif suboption[2:3] == SET_CONTROL_DTR_ON:
1096 self.serial.setDTR(True)
1097 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_DTR_ON)
1098 elif suboption[2:3] == SET_CONTROL_DTR_OFF:
1099 self.serial.setDTR(False)
1100 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_DTR_OFF)
1101 elif suboption[2:3] == SET_CONTROL_REQ_RTS:
1102 pass # XXX needs cached value
1103 #~ self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_RTS_ON)
1104 elif suboption[2:3] == SET_CONTROL_RTS_ON:
1105 self.serial.setRTS(True)
1106 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_RTS_ON)
1107 elif suboption[2:3] == SET_CONTROL_RTS_OFF:
1108 self.serial.setRTS(False)
1109 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_RTS_OFF)
1110 #~ elif suboption[2:3] == SET_CONTROL_REQ_FLOW_SETTING_IN:
1111 #~ elif suboption[2:3] == SET_CONTROL_USE_NO_FLOW_CONTROL_IN:
1112 #~ elif suboption[2:3] == SET_CONTROL_USE_SW_FLOW_CONTOL_IN:
1113 #~ elif suboption[2:3] == SET_CONTROL_USE_HW_FLOW_CONTOL_IN:
1114 #~ elif suboption[2:3] == SET_CONTROL_USE_DCD_FLOW_CONTROL:
1115 #~ elif suboption[2:3] == SET_CONTROL_USE_DTR_FLOW_CONTROL:
1116 #~ elif suboption[2:3] == SET_CONTROL_USE_DSR_FLOW_CONTROL:
1117 elif suboption[1:2] == NOTIFY_LINESTATE:
cliechti7cb78e82009-08-05 15:47:57 +00001118 # client polls for current state
1119 self.rfc2217SendSubnegotiation(
1120 SERVER_NOTIFY_LINESTATE,
1121 to_bytes([0]) # sorry, nothing like that imeplemented
1122 )
cliechti130d1f02009-08-04 02:10:58 +00001123 elif suboption[1:2] == NOTIFY_MODEMSTATE:
cliechti7cb78e82009-08-05 15:47:57 +00001124 # client polls for current state
1125 self.check_modem_lines(force_notification=True)
cliechti130d1f02009-08-04 02:10:58 +00001126 elif suboption[1:2] == FLOWCONTROL_SUSPEND:
1127 self._remote_suspend_flow = True
1128 elif suboption[1:2] == FLOWCONTROL_RESUME:
1129 self._remote_suspend_flow = False
1130 elif suboption[1:2] == SET_LINESTATE_MASK:
1131 self.linstate_mask = ord(suboption[2:3]) # ensure it is a number
1132 elif suboption[1:2] == SET_MODEMSTATE_MASK:
1133 self.modemstate_mask = ord(suboption[2:3]) # ensure it is a number
1134 elif suboption[1:2] == PURGE_DATA:
1135 if suboption[2:3] == PURGE_RECEIVE_BUFFER:
1136 self.serial.flushInput()
1137 self.rfc2217SendSubnegotiation(SERVER_PURGE_DATA, PURGE_RECEIVE_BUFFER)
1138 elif suboption[2:3] == PURGE_TRANSMIT_BUFFER:
1139 self.serial.flushOutput()
1140 self.rfc2217SendSubnegotiation(SERVER_PURGE_DATA, PURGE_TRANSMIT_BUFFER)
1141 elif suboption[2:3] == PURGE_BOTH_BUFFERS:
1142 self.serial.flushInput()
1143 self.serial.flushOutput()
1144 self.rfc2217SendSubnegotiation(SERVER_PURGE_DATA, PURGE_BOTH_BUFFERS)
1145 else:
1146 if self.debug_output:
1147 print "undefined PURGE_DATA: %r" % list(suboption[2:])
1148 else:
1149 if self.debug_output:
1150 print "undefined COM_PORT_OPTION: %r" % list(suboption[1:])
1151 else:
1152 pass
1153 #~ print "_telnetProcessSubnegotiation unknown %r" % suboption
1154
1155
1156# simple client test
cliechti8099bed2009-08-01 23:59:18 +00001157if __name__ == '__main__':
1158 import sys
1159 s = Serial('rfc2217://localhost:7000', 115200)
1160 sys.stdout.write('%s\n' % s)
1161
cliechti2b929b72009-08-02 23:49:02 +00001162 #~ s.baudrate = 1898
1163
cliechti8099bed2009-08-01 23:59:18 +00001164 sys.stdout.write("write...\n")
1165 s.write("hello\n")
1166 s.flush()
cliechti8099bed2009-08-01 23:59:18 +00001167 sys.stdout.write("read: %s\n" % s.read(5))
1168
1169 #~ s.baudrate = 19200
1170 #~ s.databits = 7
1171 s.close()