blob: df01174be326142b69adffa782077d9f53db54ff [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
56# the order of the options is not relevant
57
cliechti8099bed2009-08-01 23:59:18 +000058from serialutil import *
59import time
60import struct
61import socket
62import threading
63import Queue
64
65def to_bytes(seq):
66 b = bytearray()
67 for item in seq:
68 b.append(item)
69 return bytes(b)
70
71# port string is expected to be something like this:
72# rfc2217://host:port
73# host may be an IP or including domain, whatever.
74# port is 0...65535
75
76# telnet protocol characters
77IAC = to_bytes([255]) # Interpret As Command
78DONT = to_bytes([254])
79DO = to_bytes([253])
80WONT = to_bytes([252])
81WILL = to_bytes([251])
82IAC_DOUBLED = to_bytes([IAC, IAC])
83
84SE = to_bytes([240]) # Subnegotiation End
85NOP = to_bytes([241]) # No Operation
86DM = to_bytes([242]) # Data Mark
87BRK = to_bytes([243]) # Break
88IP = to_bytes([244]) # Interrupt process
89AO = to_bytes([245]) # Abort output
90AYT = to_bytes([246]) # Are You There
91EC = to_bytes([247]) # Erase Character
92EL = to_bytes([248]) # Erase Line
93GA = to_bytes([249]) # Go Ahead
94SB = to_bytes([250]) # Subnegotiation Begin
95
96# selected telnet options
97BINARY = to_bytes([0]) # 8-bit data path
98ECHO = to_bytes([1]) # echo
99SGA = to_bytes([3]) # suppress go ahead
100
101# RFC2217
102COM_PORT_OPTION = to_bytes([44])
103
104# Client to Access Server
105#~ SIGNATURE text text
106SET_BAUDRATE = to_bytes([1])
107SET_DATASIZE = to_bytes([2])
108SET_PARITY = to_bytes([3])
109SET_STOPSIZE = to_bytes([4])
110SET_CONTROL = to_bytes([5])
111NOTIFY_LINESTATE = to_bytes([6])
112NOTIFY_MODEMSTATE = to_bytes([7])
113FLOWCONTROL_SUSPEND = to_bytes([8])
114FLOWCONTROL_RESUME = to_bytes([9])
115SET_LINESTATE_MASK = to_bytes([10])
116SET_MODEMSTATE_MASK = to_bytes([11])
117PURGE_DATA = to_bytes([12])
118
119SERVER_SET_BAUDRATE = to_bytes([101])
120SERVER_SET_DATASIZE = to_bytes([102])
121SERVER_SET_PARITY = to_bytes([103])
122SERVER_SET_STOPSIZE = to_bytes([104])
123SERVER_SET_CONTROL = to_bytes([105])
124SERVER_NOTIFY_LINESTATE = to_bytes([106])
125SERVER_NOTIFY_MODEMSTATE = to_bytes([107])
126SERVER_FLOWCONTROL_SUSPEND = to_bytes([108])
127SERVER_FLOWCONTROL_RESUME = to_bytes([109])
128SERVER_SET_LINESTATE_MASK = to_bytes([110])
129SERVER_SET_MODEMSTATE_MASK = to_bytes([111])
130SERVER_PURGE_DATA = to_bytes([112])
131
132RFC2217_ANSWER_MAP = {
133 SET_BAUDRATE: SERVER_SET_BAUDRATE,
134 SET_DATASIZE: SERVER_SET_DATASIZE,
135 SET_PARITY: SERVER_SET_PARITY,
136 SET_STOPSIZE: SERVER_SET_STOPSIZE,
137 SET_CONTROL: SERVER_SET_CONTROL,
138 NOTIFY_LINESTATE: SERVER_NOTIFY_LINESTATE,
139 NOTIFY_MODEMSTATE: SERVER_NOTIFY_MODEMSTATE,
140 FLOWCONTROL_SUSPEND: SERVER_FLOWCONTROL_SUSPEND,
141 FLOWCONTROL_RESUME: SERVER_FLOWCONTROL_RESUME,
142 SET_LINESTATE_MASK: SERVER_SET_LINESTATE_MASK,
143 SET_MODEMSTATE_MASK: SERVER_SET_MODEMSTATE_MASK,
144 PURGE_DATA: SERVER_PURGE_DATA,
145}
146
147SET_CONTROL_REQ_FLOW_SETTING = to_bytes([0]) # Request Com Port Flow Control Setting (outbound/both)
148SET_CONTROL_USE_NO_FLOW_CONTROL = to_bytes([1]) # Use No Flow Control (outbound/both)
149SET_CONTROL_USE_SW_FLOW_CONTROL = to_bytes([2]) # Use XON/XOFF Flow Control (outbound/both)
150SET_CONTROL_USE_HW_FLOW_CONTROL = to_bytes([3]) # Use HARDWARE Flow Control (outbound/both)
151SET_CONTROL_REQ_BREAK_STATE = to_bytes([4]) # Request BREAK State
152SET_CONTROL_BREAK_ON = to_bytes([5]) # Set BREAK State ON
153SET_CONTROL_BREAK_OFF = to_bytes([6]) # Set BREAK State OFF
154SET_CONTROL_REQ_DTR = to_bytes([7]) # Request DTR Signal State
155SET_CONTROL_DTR_ON = to_bytes([8]) # Set DTR Signal State ON
156SET_CONTROL_DTR_OFF = to_bytes([9]) # Set DTR Signal State OFF
157SET_CONTROL_REQ_RTS = to_bytes([10]) # Request RTS Signal State
158SET_CONTROL_RTS_ON = to_bytes([11]) # Set RTS Signal State ON
159SET_CONTROL_RTS_OFF = to_bytes([12]) # Set RTS Signal State OFF
160SET_CONTROL_REQ_FLOW_SETTING_IN = to_bytes([13]) # Request Com Port Flow Control Setting (inbound)
161SET_CONTROL_USE_NO_FLOW_CONTROL_IN = to_bytes([14]) # Use No Flow Control (inbound)
162SET_CONTROL_USE_SW_FLOW_CONTOL_IN = to_bytes([15]) # Use XON/XOFF Flow Control (inbound)
163SET_CONTROL_USE_HW_FLOW_CONTOL_IN = to_bytes([16]) # Use HARDWARE Flow Control (inbound)
164SET_CONTROL_USE_DCD_FLOW_CONTROL = to_bytes([17]) # Use DCD Flow Control (outbound/both)
165SET_CONTROL_USE_DTR_FLOW_CONTROL = to_bytes([18]) # Use DTR Flow Control (inbound)
166SET_CONTROL_USE_DSR_FLOW_CONTROL = to_bytes([19]) # Use DSR Flow Control (outbound/both)
167
168LINESTATE_MASK_TIMEOUT = 128 # Time-out Error
169LINESTATE_MASK_SHIFTREG_EMPTY = 64 # Transfer Shift Register Empty
170LINESTATE_MASK_TRANSREG_EMPTY = 32 # Transfer Holding Register Empty
171LINESTATE_MASK_BREAK_DETECT = 16 # Break-detect Error
172LINESTATE_MASK_FRAMING_ERROR = 8 # Framing Error
173LINESTATE_MASK_PARTIY_ERROR = 4 # Parity Error
174LINESTATE_MASK_OVERRUN_ERROR = 2 # Overrun Error
175LINESTATE_MASK_DATA_READY = 1 # Data Ready
176
177MODEMSTATE_MASK_CD = 128 # Receive Line Signal Detect (also known as Carrier Detect)
178MODEMSTATE_MASK_RI = 64 # Ring Indicator
179MODEMSTATE_MASK_DSR = 32 # Data-Set-Ready Signal State
180MODEMSTATE_MASK_CTS = 16 # Clear-To-Send Signal State
181MODEMSTATE_MASK_CD_CHANGE = 8 # Delta Receive Line Signal Detect
182MODEMSTATE_MASK_RI_CHANGE = 4 # Trailing-edge Ring Detector
183MODEMSTATE_MASK_DSR_CHANGE = 2 # Delta Data-Set-Ready
184MODEMSTATE_MASK_CTS_CHANGE = 1 # Delta Clear-To-Send
185
186PURGE_RECEIVE_BUFFER = to_bytes([1]) # Purge access server receive data buffer
187PURGE_TRANSMIT_BUFFER = to_bytes([2]) # Purge access server transmit data buffer
188PURGE_BOTH_BUFFERS = to_bytes([3]) # Purge both the access server receive data buffer and the access server transmit data buffer
189
190
191RFC2217_PARITY_MAP = {
192 PARITY_NONE: 1,
193 PARITY_ODD: 2,
194 PARITY_EVEN: 3,
195 PARITY_MARK: 4,
196 PARITY_SPACE: 5,
197}
198
199RFC2217_STOPBIT_MAP = {
200 STOPBITS_ONE: 1,
201 STOPBITS_ONE_POINT_FIVE: 3,
202 STOPBITS_TWO: 2,
203}
204
205TELNET_ACTION_SUCCESS_MAP = {
206 DO: (DO, WILL),
207 WILL: (DO, WILL),
208 DONT: (DONT, WONT),
209 WONT: (DONT, WONT),
210}
211
cliechtiac205322009-08-02 20:40:21 +0000212REQUESTED = 'REQUESTED'
213ACTIVE = 'ACTIVE'
214INACTIVE = 'INACTIVE'
215REALLY_INACTIVE = 'REALLY_INACTIVE'
216
217class TelnetOption(object):
cliechti1ef7e3e2009-08-03 02:38:43 +0000218 """Manage a single telnet option, keeps track of DO/DONT WILL/WONT."""
219
cliechti2b929b72009-08-02 23:49:02 +0000220 def __init__(self, connection, name, option, send_yes, send_no, ack_yes, ack_no, initial_state):
cliechti1ef7e3e2009-08-03 02:38:43 +0000221 """Init option.
222 :param connection: connection used to transmit answers
223 :param name: a readable name for debug outputs
224 :param send_yes: what to send when option is to be enabled.
225 :param send_no: what to send when option is to be disabled.
226 :param ack_yes: what to expect when remote agrees on option.
227 :param ack_no: what to expect when remote disagrees on option.
228 :param initial_state: options initialized with REQUESTED are tried to
229 be enabled on startup. use INACTIVE for all others.
230 """
cliechti2b929b72009-08-02 23:49:02 +0000231 self.connection = connection
cliechtiac205322009-08-02 20:40:21 +0000232 self.name = name
233 self.option = option
234 self.send_yes = send_yes
235 self.send_no = send_no
236 self.ack_yes = ack_yes
237 self.ack_no = ack_no
238 self.state = initial_state
239 self.active = False
240
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
252 elif self.state is ACTIVE:
253 pass
254 elif self.state is INACTIVE:
255 self.state = ACTIVE
cliechti1ef7e3e2009-08-03 02:38:43 +0000256 self.connection.telnetSendOption(self.send_yes, self.option)
cliechtiac205322009-08-02 20:40:21 +0000257 self.active = True
258 elif self.state is REALLY_INACTIVE:
cliechti1ef7e3e2009-08-03 02:38:43 +0000259 self.connection.telnetSendOption(self.send_no, self.option)
cliechtiac205322009-08-02 20:40:21 +0000260 else:
261 raise ValueError('option in illegal state %r' % self)
262 elif command == self.ack_no:
263 if self.state is REQUESTED:
264 self.state = INACTIVE
265 self.active = False
266 elif self.state is ACTIVE:
267 self.state = INACTIVE
cliechti1ef7e3e2009-08-03 02:38:43 +0000268 self.connection.telnetSendOption(self.send_no, self.option)
cliechtiac205322009-08-02 20:40:21 +0000269 self.active = False
270 elif self.state is INACTIVE:
271 pass
272 elif self.state is REALLY_INACTIVE:
273 pass
274 else:
275 raise ValueError('option in illegal state %r' % self)
276
277
cliechti2b929b72009-08-02 23:49:02 +0000278class TelnetSubnegotiation(object):
cliechti1ef7e3e2009-08-03 02:38:43 +0000279 """A object to handle subnegotiation of options. In this case actually
280 sub-sub options for RFC2217. It is used to track com port options."""
cliechti2b929b72009-08-02 23:49:02 +0000281
282 def __init__(self, connection, name, option, ack_option=None):
283 if ack_option is None: ack_option = option
284 self.connection = connection
285 self.name = name
286 self.option = option
287 self.value = None
288 self.ack_option = ack_option
289 self.state = INACTIVE
290
291 def __repr__(self):
cliechti1ef7e3e2009-08-03 02:38:43 +0000292 """String for debug outputs"""
cliechti2b929b72009-08-02 23:49:02 +0000293 return "%s:%s" % (self.name, self.state)
294
295 def set(self, value):
296 """request a change of the value. a request is sent to the server. if
297 the client needs to know if the change is performed he has to check the
298 state of this object."""
299 self.value = value
300 self.state = REQUESTED
cliechti1ef7e3e2009-08-03 02:38:43 +0000301 self.connection.rfc2217SendSubnegotiation(self.option, self.value)
cliechti81c54762009-08-03 23:53:27 +0000302 if self.connection.debug_output:
303 print "SB Requesting %s -> %r" % (self.name, self.value)
cliechti2b929b72009-08-02 23:49:02 +0000304
305 def isReady(self):
cliechti1ef7e3e2009-08-03 02:38:43 +0000306 """check if answer from server has been received. when server rejects
307 the change, raise a ValueError."""
cliechti2b929b72009-08-02 23:49:02 +0000308 if self.state == REALLY_INACTIVE:
309 raise ValueError("remote rejected value for option %r" % (self.name))
310 return self.state == ACTIVE
cliechti1ef7e3e2009-08-03 02:38:43 +0000311 # add property to have a similar interface as TelnetOption
cliechti2b929b72009-08-02 23:49:02 +0000312 active = property(isReady)
313
314 def wait(self, timeout):
cliechti1ef7e3e2009-08-03 02:38:43 +0000315 """wait until the subnegotiation has been acknowledged or timeout. It
316 can also throw a value error when the answer from the server does not
317 match the value sent."""
cliechti2b929b72009-08-02 23:49:02 +0000318 timeout_time = time.time() + 3
319 while time.time() < timeout_time:
320 time.sleep(0.05) # prevent 100% CPU load
321 if self.isReady():
322 break
323 else:
324 raise SerialException("timeout while waiting for option %r" % (self.name))
325
326 def checkAnswer(self, suboption):
327 """check an incoming subnegotiation block. the parameter already has
328 cut off the header like sub option number and com port option value"""
329 if self.value == suboption[:len(self.value)]:
330 self.state = ACTIVE
331 else:
332 # error propagation done in isReady
333 self.state = REALLY_INACTIVE
cliechti81c54762009-08-03 23:53:27 +0000334 if self.connection.debug_output:
335 print "SB Answer %s -> %r -> %s" % (self.name, suboption, self.state)
cliechti2b929b72009-08-02 23:49:02 +0000336
337
cliechti8099bed2009-08-01 23:59:18 +0000338class RFC2217Serial(SerialBase):
cliechti1ef7e3e2009-08-03 02:38:43 +0000339 """Serial port implementation for RFC2217 remote serial ports."""
cliechti8099bed2009-08-01 23:59:18 +0000340
341 BAUDRATES = (50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800,
342 9600, 19200, 38400, 57600, 115200)
343
344 def open(self):
345 """Open port with current settings. This may throw a SerialException
346 if the port cannot be opened."""
cliechti81c54762009-08-03 23:53:27 +0000347 self.debug_output = False
348 self._ignore_set_control_answer = False
cliechti8099bed2009-08-01 23:59:18 +0000349 if self._port is None:
350 raise SerialException("Port must be configured before it can be used.")
351 try:
352 self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
353 self._socket.connect(self.fromURL(self.portstr))
354 except Exception, msg:
355 self._socket = None
356 raise SerialException("Could not open port %s: %s" % (self.portstr, msg))
357
cliechti1ef7e3e2009-08-03 02:38:43 +0000358 self._socket.settimeout(5) # XXX good value?
cliechti8099bed2009-08-01 23:59:18 +0000359
cliechti1ef7e3e2009-08-03 02:38:43 +0000360 # use a thread save queue as buffer. it also simplifies implementing
361 # the read timeout
cliechti8099bed2009-08-01 23:59:18 +0000362 self._read_buffer = Queue.Queue()
cliechti81c54762009-08-03 23:53:27 +0000363 # to ensure that user writes does not interfere with internal
364 # telnet/rfc2217 options establish a lock
365 self._write_lock = threading.Lock()
cliechtiac205322009-08-02 20:40:21 +0000366 # name the following separately so that, below, a check can be easily done
367 mandadory_options = [
cliechti2b929b72009-08-02 23:49:02 +0000368 TelnetOption(self, 'we-BINARY', BINARY, WILL, WONT, DO, DONT, INACTIVE),
cliechti2b929b72009-08-02 23:49:02 +0000369 TelnetOption(self, 'we-RFC2217', COM_PORT_OPTION, WILL, WONT, DO, DONT, REQUESTED),
cliechtiac205322009-08-02 20:40:21 +0000370 ]
371 # all supported telnet options
372 self._telnet_options = [
cliechtia29275e2009-08-03 00:08:04 +0000373 TelnetOption(self, 'ECHO', ECHO, DO, DONT, WILL, WONT, REQUESTED),
cliechti2b929b72009-08-02 23:49:02 +0000374 TelnetOption(self, 'we-SGA', SGA, WILL, WONT, DO, DONT, REQUESTED),
375 TelnetOption(self, 'they-SGA', SGA, DO, DONT, WILL, WONT, REQUESTED),
cliechti81c54762009-08-03 23:53:27 +0000376 TelnetOption(self, 'they-BINARY', BINARY, DO, DONT, WILL, WONT, INACTIVE),
377 TelnetOption(self, 'they-RFC2217', COM_PORT_OPTION, DO, DONT, WILL, WONT, REQUESTED),
cliechtiac205322009-08-02 20:40:21 +0000378 ] + mandadory_options
379 # RFC2217 specific states
cliechti2b929b72009-08-02 23:49:02 +0000380 # COM port settings
381 self._rfc2217_port_settings = {
382 'baudrate': TelnetSubnegotiation(self, 'baudrate', SET_BAUDRATE, SERVER_SET_BAUDRATE),
383 'datasize': TelnetSubnegotiation(self, 'datasize', SET_DATASIZE, SERVER_SET_DATASIZE),
384 'parity': TelnetSubnegotiation(self, 'parity', SET_PARITY, SERVER_SET_PARITY),
385 'stopsize': TelnetSubnegotiation(self, 'stopsize', SET_STOPSIZE, SERVER_SET_STOPSIZE),
386 }
387 # There are more subnegotiation object, combine all in one dictionary
388 # for easy access
389 self._rfc2217_options = {
390 'purge': TelnetSubnegotiation(self, 'purge', PURGE_DATA, SERVER_PURGE_DATA),
cliechti81c54762009-08-03 23:53:27 +0000391 'control': TelnetSubnegotiation(self, 'control', SET_CONTROL, SERVER_SET_CONTROL),
cliechti2b929b72009-08-02 23:49:02 +0000392 }
393 self._rfc2217_options.update(self._rfc2217_port_settings)
394 # cache for line and modem states that the server sends to us
cliechti8099bed2009-08-01 23:59:18 +0000395 self._linestate = 0
396 self._modemstate = 0
cliechti672d0292009-08-03 02:01:57 +0000397 # RFC2217 flow control between server and client
398 self._remote_suspend_flow = False
cliechti8099bed2009-08-01 23:59:18 +0000399
cliechti1ef7e3e2009-08-03 02:38:43 +0000400 self._thread = threading.Thread(target=self._telnetReadLoop)
cliechti8099bed2009-08-01 23:59:18 +0000401 self._thread.setDaemon(True)
402 self._thread.setName('pySerial RFC2217 reader thread for %s' % (self._port,))
403 self._thread.start()
404
cliechtiac205322009-08-02 20:40:21 +0000405 # negotiate Telnet/RFC2217 -> send initial requests
406 for option in self._telnet_options:
407 if option.state is REQUESTED:
cliechti1ef7e3e2009-08-03 02:38:43 +0000408 self.telnetSendOption(option.send_yes, option.option)
cliechtiac205322009-08-02 20:40:21 +0000409 # now wait until important options are negotiated
410 timeout_time = time.time() + 3
411 while time.time() < timeout_time:
cliechtiac205322009-08-02 20:40:21 +0000412 time.sleep(0.05) # prevent 100% CPU load
cliechti2b929b72009-08-02 23:49:02 +0000413 if sum(o.active for o in mandadory_options) == len(mandadory_options):
414 break
415 else:
416 raise SerialException("Remote does not seem to support RFC2217 or BINARY mode %r" % mandadory_options)
cliechti81c54762009-08-03 23:53:27 +0000417 if self.debug_output:
418 print self._telnet_options
cliechti8099bed2009-08-01 23:59:18 +0000419
420 # fine, go on, set RFC2271 specific things
421 self._reconfigurePort()
cliechti2b929b72009-08-02 23:49:02 +0000422 # all things set up get, now a clean start
cliechti8099bed2009-08-01 23:59:18 +0000423 self._isOpen = True
424 if not self._rtscts:
425 self.setRTS(True)
426 self.setDTR(True)
427 self.flushInput()
428 self.flushOutput()
429
430 def _reconfigurePort(self):
431 """Set communication parameters on opened port."""
432 if self._socket is None:
433 raise SerialException("Can only operate on open ports")
434
cliechti8099bed2009-08-01 23:59:18 +0000435 # if self._timeout != 0 and self._interCharTimeout is not None:
cliechti8099bed2009-08-01 23:59:18 +0000436 # XXX
437
438 if self._writeTimeout is not None:
439 raise NotImplementedError('writeTimeout is currently not supported')
cliechti2b929b72009-08-02 23:49:02 +0000440 # XXX
cliechti8099bed2009-08-01 23:59:18 +0000441
cliechti2b929b72009-08-02 23:49:02 +0000442 # Setup the connection
cliechti1ef7e3e2009-08-03 02:38:43 +0000443 # to get good performance, all parameter changes are sent first...
cliechti81c54762009-08-03 23:53:27 +0000444 if not isinstance(self._baudrate, (int, long)) or not 0 < self._baudrate < 2**32:
445 raise ValueError("invalid baudrate: %r" % (self._baudrate))
cliechti2b929b72009-08-02 23:49:02 +0000446 self._rfc2217_port_settings['baudrate'].set(struct.pack('!I', self._baudrate))
447 self._rfc2217_port_settings['datasize'].set(struct.pack('!B', self._bytesize))
448 self._rfc2217_port_settings['parity'].set(struct.pack('!B', RFC2217_PARITY_MAP[self._parity]))
449 self._rfc2217_port_settings['stopsize'].set(struct.pack('!B', RFC2217_STOPBIT_MAP[self._stopbits]))
cliechti8099bed2009-08-01 23:59:18 +0000450
cliechti2b929b72009-08-02 23:49:02 +0000451 # and now wait until parameters are active
452 items = self._rfc2217_port_settings.values()
453 timeout_time = time.time() + 3
454 while time.time() < timeout_time:
455 time.sleep(0.05) # prevent 100% CPU load
456 if sum(o.active for o in items) == len(items):
457 break
458 else:
459 raise SerialException("Remote does not accept parameter change (RFC2217): %r" % items)
cliechti81c54762009-08-03 23:53:27 +0000460 if self.debug_output:
461 print items
cliechti2b929b72009-08-02 23:49:02 +0000462
cliechti8099bed2009-08-01 23:59:18 +0000463
464 if self._rtscts and self._xonxoff:
cliechti1ef7e3e2009-08-03 02:38:43 +0000465 raise ValueError('xonxoff and rtscts together are not supported')
cliechti8099bed2009-08-01 23:59:18 +0000466 elif self._rtscts:
cliechti1ef7e3e2009-08-03 02:38:43 +0000467 self.rfc2217SetControl(SET_CONTROL_USE_HW_FLOW_CONTROL)
cliechti8099bed2009-08-01 23:59:18 +0000468 elif self._xonxoff:
cliechti1ef7e3e2009-08-03 02:38:43 +0000469 self.rfc2217SetControl(SET_CONTROL_USE_SW_FLOW_CONTROL)
cliechti8099bed2009-08-01 23:59:18 +0000470 else:
cliechti1ef7e3e2009-08-03 02:38:43 +0000471 self.rfc2217SetControl(SET_CONTROL_USE_NO_FLOW_CONTROL)
cliechti8099bed2009-08-01 23:59:18 +0000472
473 def close(self):
474 """Close port"""
475 if self._isOpen:
476 if self._socket:
477 try:
478 self._socket.shutdown(socket.SHUT_RDWR)
479 self._socket.close()
480 except:
481 # ignore errors.
482 pass
483 self._socket = None
484 if self._thread:
485 self._thread.join()
486 self._isOpen = False
487 # in case of quick reconnects, give the server some time
488 time.sleep(0.3)
489
490 def makeDeviceName(self, port):
491 raise SerialException("there is no sensible way to turn numbers into URLs")
492
493 def fromURL(self, url):
cliechti1ef7e3e2009-08-03 02:38:43 +0000494 """extract host and port from an URL string"""
cliechti8099bed2009-08-01 23:59:18 +0000495 if url.lower().startswith("rfc2217://"): url = url[10:]
496 try:
cliechti81c54762009-08-03 23:53:27 +0000497 # is there a "path" (our options)?
498 if '/' in url:
499 # cut away options
500 url, options = url.split('/', 1)
501 # process options now, directly altering self
502 for option in options.split('/'):
503 if option == 'debug':
504 self.debug_output = True
505 elif option == 'ign_set_control':
506 self._ignore_set_control_answer = True
507 else:
508 raise ValueError('unknown option: %r' % (option,))
509 # get host and port
cliechti8099bed2009-08-01 23:59:18 +0000510 host, port = url.split(':', 1) # may raise ValueError because of unpacking
511 port = int(port) # and this if it's not a number
512 if not 0 <= port < 65536: raise ValueError("port not in range 0...65535")
513 except ValueError, e:
cliechti81c54762009-08-03 23:53:27 +0000514 raise SerialException('expected a string in the form "[rfc2217://]<host>:<port>[/option[/option...]]": %s' % e)
cliechti8099bed2009-08-01 23:59:18 +0000515 return (host, port)
516
517 # - - - - - - - - - - - - - - - - - - - - - - - -
518
519 def inWaiting(self):
520 """Return the number of characters currently in the input buffer."""
521 if not self._isOpen: raise portNotOpenError
522 return self._read_buffer.qsize()
523
524 def read(self, size=1):
525 """Read size bytes from the serial port. If a timeout is set it may
526 return less characters as requested. With no timeout it will block
527 until the requested number of bytes is read."""
528 if not self._isOpen: raise portNotOpenError
529 data = bytearray()
530 try:
531 while len(data) < size:
cliechti81c54762009-08-03 23:53:27 +0000532 if self._thread is None:
533 raise SerialException('connection failed (reader thread died)')
cliechti8099bed2009-08-01 23:59:18 +0000534 data.append(self._read_buffer.get(True, self._timeout))
535 except Queue.Empty: # -> timeout
536 pass
537 return bytes(data)
538
539 def write(self, data):
540 """Output the given string over the serial port. Can block if the
541 connection is blocked. May raise SerialException if the connection is
542 closed."""
543 if not self._isOpen: raise portNotOpenError
cliechti81c54762009-08-03 23:53:27 +0000544 self._write_lock.acquire()
cliechti8099bed2009-08-01 23:59:18 +0000545 try:
cliechti81c54762009-08-03 23:53:27 +0000546 try:
547 self._socket.sendall(data.replace(IAC, IAC_DOUBLED))
548 except socket.error, e:
549 raise SerialException("socket connection failed: %s" % e) # XXX what exception if socket connection fails
550 finally:
551 self._write_lock.release()
cliechti8099bed2009-08-01 23:59:18 +0000552 return len(data)
553
554 def flushInput(self):
555 """Clear input buffer, discarding all that is in the buffer."""
556 if not self._isOpen: raise portNotOpenError
cliechti1ef7e3e2009-08-03 02:38:43 +0000557 self.rfc2217SendPurge(PURGE_RECEIVE_BUFFER)
cliechti8099bed2009-08-01 23:59:18 +0000558 # empty read buffer
559 while self._read_buffer.qsize():
560 self._read_buffer.get(False)
561
562 def flushOutput(self):
563 """Clear output buffer, aborting the current output and
564 discarding all that is in the buffer."""
565 if not self._isOpen: raise portNotOpenError
cliechti1ef7e3e2009-08-03 02:38:43 +0000566 self.rfc2217SendPurge(PURGE_TRANSMIT_BUFFER)
cliechti8099bed2009-08-01 23:59:18 +0000567
568 def sendBreak(self, duration=0.25):
569 """Send break condition. Timed, returns to idle state after given
570 duration."""
571 if not self._isOpen: raise portNotOpenError
572 self.setBreak(True)
573 time.sleep(duration)
574 self.setBreak(False)
575
576 def setBreak(self, level=True):
577 """Set break: Controls TXD. When active, to transmitting is
578 possible."""
579 if not self._isOpen: raise portNotOpenError
580 if level:
cliechti1ef7e3e2009-08-03 02:38:43 +0000581 self.rfc2217SetControl(SET_CONTROL_BREAK_ON)
cliechti8099bed2009-08-01 23:59:18 +0000582 else:
cliechti1ef7e3e2009-08-03 02:38:43 +0000583 self.rfc2217SetControl(SET_CONTROL_BREAK_OFF)
cliechti8099bed2009-08-01 23:59:18 +0000584
585 def setRTS(self, level=True):
586 """Set terminal status line: Request To Send"""
587 if not self._isOpen: raise portNotOpenError
588 if level:
cliechti1ef7e3e2009-08-03 02:38:43 +0000589 self.rfc2217SetControl(SET_CONTROL_RTS_ON)
cliechti8099bed2009-08-01 23:59:18 +0000590 else:
cliechti1ef7e3e2009-08-03 02:38:43 +0000591 self.rfc2217SetControl(SET_CONTROL_RTS_OFF)
cliechti8099bed2009-08-01 23:59:18 +0000592
593 def setDTR(self, level=True):
594 """Set terminal status line: Data Terminal Ready"""
595 if not self._isOpen: raise portNotOpenError
596 if level:
cliechti1ef7e3e2009-08-03 02:38:43 +0000597 self.rfc2217SetControl(SET_CONTROL_DTR_ON)
cliechti8099bed2009-08-01 23:59:18 +0000598 else:
cliechti1ef7e3e2009-08-03 02:38:43 +0000599 self.rfc2217SetControl(SET_CONTROL_DTR_OFF)
cliechti8099bed2009-08-01 23:59:18 +0000600
601 def getCTS(self):
602 """Read terminal status line: Clear To Send"""
603 if not self._isOpen: raise portNotOpenError
604 return bool(self._modemstate & MODEMSTATE_MASK_CTS)
605
606 def getDSR(self):
607 """Read terminal status line: Data Set Ready"""
608 if not self._isOpen: raise portNotOpenError
609 return bool(self._modemstate & MODEMSTATE_MASK_DSR)
610
611 def getRI(self):
612 """Read terminal status line: Ring Indicator"""
613 if not self._isOpen: raise portNotOpenError
614 return bool(self._modemstate & MODEMSTATE_MASK_RI)
615
616 def getCD(self):
617 """Read terminal status line: Carrier Detect"""
618 if not self._isOpen: raise portNotOpenError
619 return bool(self._modemstate & MODEMSTATE_MASK_CD)
620
621 # - - - platform specific - - -
622 # None so far
623
624 # - - - RFC2217 specific - - -
625
cliechti1ef7e3e2009-08-03 02:38:43 +0000626 def _telnetReadLoop(self):
cliechti8099bed2009-08-01 23:59:18 +0000627 """read loop for the socket"""
628 M_NORMAL = 0
629 M_IAC_SEEN = 1
630 M_NEGOTIATE = 2
631 mode = M_NORMAL
632 suboption = None
cliechti81c54762009-08-03 23:53:27 +0000633 try:
634 while self._socket is not None:
635 try:
636 data = self._socket.recv(1024)
637 except socket.timeout:
638 # just need to get out of recv form time to time to check if
639 # still alive
640 continue
641 except socket.error:
642 # connection fails -> terminate loop
643 break
644 for byte in data:
645 if mode == M_NORMAL:
646 # interpret as command or as data
647 if byte == IAC:
648 mode = M_IAC_SEEN
cliechti8099bed2009-08-01 23:59:18 +0000649 else:
cliechti81c54762009-08-03 23:53:27 +0000650 # store data in read buffer or sub option buffer
651 # depending on state
652 if suboption is not None:
653 suboption.append(byte)
654 else:
655 self._read_buffer.put(byte)
656 elif mode == M_IAC_SEEN:
657 if byte == IAC:
658 # interpret as command doubled -> insert character
659 # itself
660 self._read_buffer.put(IAC)
661 mode = M_NORMAL
662 elif byte == SB:
663 # sub option start
664 suboption = bytearray()
665 mode = M_NORMAL
666 elif byte == SE:
667 # sub option end -> process it now
668 self._telnetProcessSubnegotiation(bytes(suboption))
669 suboption = None
670 mode = M_NORMAL
671 elif byte in (DO, DONT, WILL, WONT):
672 # negotiation
673 telnet_command = byte
674 mode = M_NEGOTIATE
675 else:
676 # other telnet commands
677 self._telnetProcessCommand(byte)
678 mode = M_NORMAL
679 elif mode == M_NEGOTIATE: # DO, DONT, WILL, WONT was received, option now following
680 self._telnetNegotiateOption(telnet_command, byte)
cliechti8099bed2009-08-01 23:59:18 +0000681 mode = M_NORMAL
cliechti81c54762009-08-03 23:53:27 +0000682 finally:
683 self._thread = None
684 if self.debug_output:
685 print "read thread terminated"
cliechti8099bed2009-08-01 23:59:18 +0000686
687 # - incoming telnet commands and options
688
cliechti1ef7e3e2009-08-03 02:38:43 +0000689 def _telnetProcessCommand(self, command):
cliechti8099bed2009-08-01 23:59:18 +0000690 """Process commands other than DO, DONT, WILL, WONT"""
cliechti1ef7e3e2009-08-03 02:38:43 +0000691 # Currently none. RFC2217 only uses negotiation and subnegotiation.
692 #~ print "_telnetProcessCommand %r" % ord(command)
cliechti8099bed2009-08-01 23:59:18 +0000693
cliechti1ef7e3e2009-08-03 02:38:43 +0000694 def _telnetNegotiateOption(self, command, option):
695 """Process incoming DO, DONT, WILL, WONT"""
cliechti2b929b72009-08-02 23:49:02 +0000696 # check our registered telnet options and forward command to them
697 # they know themselves if they have to answer or not
cliechtiac205322009-08-02 20:40:21 +0000698 known = False
699 for item in self._telnet_options:
cliechti2b929b72009-08-02 23:49:02 +0000700 # can have more than one match! as some options are duplicated for
701 # 'us' and 'them'
cliechtiac205322009-08-02 20:40:21 +0000702 if item.option == option:
cliechti2b929b72009-08-02 23:49:02 +0000703 item.process_incoming(command)
cliechtiac205322009-08-02 20:40:21 +0000704 known = True
705 if not known:
706 # handle unknown options
707 # only answer to positive requests and deny them
708 if command == WILL or command == DO:
cliechti1ef7e3e2009-08-03 02:38:43 +0000709 self.telnetSendOption((command == WILL and DONT or WONT), option)
cliechtiac205322009-08-02 20:40:21 +0000710
cliechti8099bed2009-08-01 23:59:18 +0000711
cliechti1ef7e3e2009-08-03 02:38:43 +0000712 def _telnetProcessSubnegotiation(self, suboption):
713 """Process subnegotiation, the data between IAC SB and IAC SE"""
cliechti8099bed2009-08-01 23:59:18 +0000714 if suboption[0:1] == COM_PORT_OPTION:
715 if suboption[1:2] == SERVER_NOTIFY_LINESTATE and len(suboption) >= 3:
cliechti672d0292009-08-03 02:01:57 +0000716 self._linestate = ord(suboption[2:3]) # ensure it is a number
cliechti81c54762009-08-03 23:53:27 +0000717 if self.debug_output:
718 print "NOTIFY_LINESTATE: %s" % self._linestate
cliechti8099bed2009-08-01 23:59:18 +0000719 elif suboption[1:2] == SERVER_NOTIFY_MODEMSTATE and len(suboption) >= 3:
cliechti672d0292009-08-03 02:01:57 +0000720 self._modemstate = ord(suboption[2:3]) # ensure it is a number
cliechti81c54762009-08-03 23:53:27 +0000721 if self.debug_output:
722 print "NOTIFY_MODEMSTATE: %s" % self._modemstate
cliechti672d0292009-08-03 02:01:57 +0000723 elif suboption[1:2] == FLOWCONTROL_SUSPEND:
724 self._remote_suspend_flow = True
725 elif suboption[1:2] == FLOWCONTROL_RESUME:
726 self._remote_suspend_flow = False
cliechti8099bed2009-08-01 23:59:18 +0000727 else:
cliechti2b929b72009-08-02 23:49:02 +0000728 for item in self._rfc2217_options.values():
729 if item.ack_option == suboption[1:2]:
cliechti81c54762009-08-03 23:53:27 +0000730 #~ print "processing COM_PORT_OPTION: %r" % list(suboption[1:])
cliechti2b929b72009-08-02 23:49:02 +0000731 item.checkAnswer(bytes(suboption[2:]))
732 break
733 else:
cliechti81c54762009-08-03 23:53:27 +0000734 if self.debug_output:
735 print "ignoring COM_PORT_OPTION: %r" % list(suboption[1:])
cliechti1ef7e3e2009-08-03 02:38:43 +0000736 #~ print "_telnetProcessSubnegotiation COM_PORT_OPTION %r" % suboption[1:]
cliechti8099bed2009-08-01 23:59:18 +0000737 else:
738 pass
cliechti1ef7e3e2009-08-03 02:38:43 +0000739 #~ print "_telnetProcessSubnegotiation unknown %r" % suboption
cliechti8099bed2009-08-01 23:59:18 +0000740
741 # - outgoing telnet commands and options
742
cliechti81c54762009-08-03 23:53:27 +0000743 def _internal_raw_write(self, data):
744 """internal socket write with no data escaping. used to send telnet stuff"""
745 self._write_lock.acquire()
746 try:
747 self._socket.sendall(data)
748 finally:
749 self._write_lock.release()
750
cliechti1ef7e3e2009-08-03 02:38:43 +0000751 def telnetSendOption(self, action, option):
cliechti8099bed2009-08-01 23:59:18 +0000752 """Send DO, DONT, WILL, WONT"""
cliechti81c54762009-08-03 23:53:27 +0000753 self._internal_raw_write(to_bytes([IAC, action, option]))
cliechti8099bed2009-08-01 23:59:18 +0000754
cliechti1ef7e3e2009-08-03 02:38:43 +0000755 def rfc2217SendSubnegotiation(self, option, value=[]):
cliechti8099bed2009-08-01 23:59:18 +0000756 """Subnegotiation of RFC2217 parameters"""
cliechti81c54762009-08-03 23:53:27 +0000757 self._internal_raw_write(to_bytes([IAC, SB, COM_PORT_OPTION, option] + list(value) + [IAC, SE]))
cliechti2b929b72009-08-02 23:49:02 +0000758
cliechti1ef7e3e2009-08-03 02:38:43 +0000759 def rfc2217SendPurge(self, value):
cliechti2b929b72009-08-02 23:49:02 +0000760 item = self._rfc2217_options['purge']
cliechti672d0292009-08-03 02:01:57 +0000761 item.set(value) # transmit desired purge type
cliechti81c54762009-08-03 23:53:27 +0000762 item.wait(3) # wait for acknowledge from the server
cliechti2b929b72009-08-02 23:49:02 +0000763
cliechti1ef7e3e2009-08-03 02:38:43 +0000764 def rfc2217SetControl(self, value):
cliechti81c54762009-08-03 23:53:27 +0000765 item = self._rfc2217_options['control']
766 item.set(value) # transmit desired purge type
767 if self._ignore_set_control_answer:
768 # answers are ignored when option is set. compatibility mode for
769 # servers that answers, but not the expected ones... (or no answer
770 # at all) i.e. sredird
771 time.sleep(0.1) # this helps getting the unit tests passed
772 else:
773 item.wait(3) # wait for acknowledge from the server
cliechti8099bed2009-08-01 23:59:18 +0000774
cliechti1ef7e3e2009-08-03 02:38:43 +0000775 def rfc2217FlowServerReady(self):
cliechti672d0292009-08-03 02:01:57 +0000776 """check if server is ready to receive data. block for some time when
777 not"""
778 #~ if self._remote_suspend_flow:
779 #~ wait---
780
cliechti8099bed2009-08-01 23:59:18 +0000781
782# assemble Serial class with the platform specific implementation and the base
783# for file-like behavior. for Python 2.6 and newer, that provide the new I/O
784# library, derive from io.RawIOBase
785try:
786 import io
787except ImportError:
788 # classic version with our own file-like emulation
789 class Serial(RFC2217Serial, FileLike):
790 pass
791else:
792 # io library present
793 class Serial(RFC2217Serial, io.RawIOBase):
794 pass
795
796
cliechti81c54762009-08-03 23:53:27 +0000797# simple test
cliechti8099bed2009-08-01 23:59:18 +0000798if __name__ == '__main__':
799 import sys
800 s = Serial('rfc2217://localhost:7000', 115200)
801 sys.stdout.write('%s\n' % s)
802
cliechti2b929b72009-08-02 23:49:02 +0000803 #~ s.baudrate = 1898
804
cliechti8099bed2009-08-01 23:59:18 +0000805 sys.stdout.write("write...\n")
806 s.write("hello\n")
807 s.flush()
cliechti8099bed2009-08-01 23:59:18 +0000808 sys.stdout.write("read: %s\n" % s.read(5))
809
810 #~ s.baudrate = 19200
811 #~ s.databits = 7
812 s.close()