blob: 5ad839cf3e2f858c0ead3fb33a4f7632c09c1125 [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
cliechti5cc3eb12009-08-11 23:04:30 +000068import logging
cliechti8099bed2009-08-01 23:59:18 +000069
cliechti8099bed2009-08-01 23:59:18 +000070# port string is expected to be something like this:
71# rfc2217://host:port
72# host may be an IP or including domain, whatever.
73# port is 0...65535
74
cliechti5cc3eb12009-08-11 23:04:30 +000075LOGGER_LEVELS = {
76 'debug': logging.DEBUG,
77 'info': logging.INFO,
78 'warning': logging.WARNING,
79 'error': logging.ERROR,
80 'critical': logging.CRITICAL
81 }
82
83
cliechti8099bed2009-08-01 23:59:18 +000084# telnet protocol characters
85IAC = to_bytes([255]) # Interpret As Command
86DONT = to_bytes([254])
87DO = to_bytes([253])
88WONT = to_bytes([252])
89WILL = to_bytes([251])
90IAC_DOUBLED = to_bytes([IAC, IAC])
91
92SE = to_bytes([240]) # Subnegotiation End
93NOP = to_bytes([241]) # No Operation
94DM = to_bytes([242]) # Data Mark
95BRK = to_bytes([243]) # Break
96IP = to_bytes([244]) # Interrupt process
97AO = to_bytes([245]) # Abort output
98AYT = to_bytes([246]) # Are You There
99EC = to_bytes([247]) # Erase Character
100EL = to_bytes([248]) # Erase Line
101GA = to_bytes([249]) # Go Ahead
102SB = to_bytes([250]) # Subnegotiation Begin
103
104# selected telnet options
105BINARY = to_bytes([0]) # 8-bit data path
106ECHO = to_bytes([1]) # echo
107SGA = to_bytes([3]) # suppress go ahead
108
109# RFC2217
110COM_PORT_OPTION = to_bytes([44])
111
112# Client to Access Server
cliechti8099bed2009-08-01 23:59:18 +0000113SET_BAUDRATE = to_bytes([1])
114SET_DATASIZE = to_bytes([2])
115SET_PARITY = to_bytes([3])
116SET_STOPSIZE = to_bytes([4])
117SET_CONTROL = to_bytes([5])
118NOTIFY_LINESTATE = to_bytes([6])
119NOTIFY_MODEMSTATE = to_bytes([7])
120FLOWCONTROL_SUSPEND = to_bytes([8])
121FLOWCONTROL_RESUME = to_bytes([9])
122SET_LINESTATE_MASK = to_bytes([10])
123SET_MODEMSTATE_MASK = to_bytes([11])
124PURGE_DATA = to_bytes([12])
125
126SERVER_SET_BAUDRATE = to_bytes([101])
127SERVER_SET_DATASIZE = to_bytes([102])
128SERVER_SET_PARITY = to_bytes([103])
129SERVER_SET_STOPSIZE = to_bytes([104])
130SERVER_SET_CONTROL = to_bytes([105])
131SERVER_NOTIFY_LINESTATE = to_bytes([106])
132SERVER_NOTIFY_MODEMSTATE = to_bytes([107])
133SERVER_FLOWCONTROL_SUSPEND = to_bytes([108])
134SERVER_FLOWCONTROL_RESUME = to_bytes([109])
135SERVER_SET_LINESTATE_MASK = to_bytes([110])
136SERVER_SET_MODEMSTATE_MASK = to_bytes([111])
137SERVER_PURGE_DATA = to_bytes([112])
138
139RFC2217_ANSWER_MAP = {
140 SET_BAUDRATE: SERVER_SET_BAUDRATE,
141 SET_DATASIZE: SERVER_SET_DATASIZE,
142 SET_PARITY: SERVER_SET_PARITY,
143 SET_STOPSIZE: SERVER_SET_STOPSIZE,
144 SET_CONTROL: SERVER_SET_CONTROL,
145 NOTIFY_LINESTATE: SERVER_NOTIFY_LINESTATE,
146 NOTIFY_MODEMSTATE: SERVER_NOTIFY_MODEMSTATE,
147 FLOWCONTROL_SUSPEND: SERVER_FLOWCONTROL_SUSPEND,
148 FLOWCONTROL_RESUME: SERVER_FLOWCONTROL_RESUME,
149 SET_LINESTATE_MASK: SERVER_SET_LINESTATE_MASK,
150 SET_MODEMSTATE_MASK: SERVER_SET_MODEMSTATE_MASK,
151 PURGE_DATA: SERVER_PURGE_DATA,
152}
153
154SET_CONTROL_REQ_FLOW_SETTING = to_bytes([0]) # Request Com Port Flow Control Setting (outbound/both)
155SET_CONTROL_USE_NO_FLOW_CONTROL = to_bytes([1]) # Use No Flow Control (outbound/both)
156SET_CONTROL_USE_SW_FLOW_CONTROL = to_bytes([2]) # Use XON/XOFF Flow Control (outbound/both)
157SET_CONTROL_USE_HW_FLOW_CONTROL = to_bytes([3]) # Use HARDWARE Flow Control (outbound/both)
158SET_CONTROL_REQ_BREAK_STATE = to_bytes([4]) # Request BREAK State
159SET_CONTROL_BREAK_ON = to_bytes([5]) # Set BREAK State ON
160SET_CONTROL_BREAK_OFF = to_bytes([6]) # Set BREAK State OFF
161SET_CONTROL_REQ_DTR = to_bytes([7]) # Request DTR Signal State
162SET_CONTROL_DTR_ON = to_bytes([8]) # Set DTR Signal State ON
163SET_CONTROL_DTR_OFF = to_bytes([9]) # Set DTR Signal State OFF
164SET_CONTROL_REQ_RTS = to_bytes([10]) # Request RTS Signal State
165SET_CONTROL_RTS_ON = to_bytes([11]) # Set RTS Signal State ON
166SET_CONTROL_RTS_OFF = to_bytes([12]) # Set RTS Signal State OFF
167SET_CONTROL_REQ_FLOW_SETTING_IN = to_bytes([13]) # Request Com Port Flow Control Setting (inbound)
168SET_CONTROL_USE_NO_FLOW_CONTROL_IN = to_bytes([14]) # Use No Flow Control (inbound)
169SET_CONTROL_USE_SW_FLOW_CONTOL_IN = to_bytes([15]) # Use XON/XOFF Flow Control (inbound)
170SET_CONTROL_USE_HW_FLOW_CONTOL_IN = to_bytes([16]) # Use HARDWARE Flow Control (inbound)
171SET_CONTROL_USE_DCD_FLOW_CONTROL = to_bytes([17]) # Use DCD Flow Control (outbound/both)
172SET_CONTROL_USE_DTR_FLOW_CONTROL = to_bytes([18]) # Use DTR Flow Control (inbound)
173SET_CONTROL_USE_DSR_FLOW_CONTROL = to_bytes([19]) # Use DSR Flow Control (outbound/both)
174
cliechti044d8662009-08-11 21:40:31 +0000175LINESTATE_MASK_TIMEOUT = 128 # Time-out Error
176LINESTATE_MASK_SHIFTREG_EMPTY = 64 # Transfer Shift Register Empty
177LINESTATE_MASK_TRANSREG_EMPTY = 32 # Transfer Holding Register Empty
178LINESTATE_MASK_BREAK_DETECT = 16 # Break-detect Error
179LINESTATE_MASK_FRAMING_ERROR = 8 # Framing Error
180LINESTATE_MASK_PARTIY_ERROR = 4 # Parity Error
181LINESTATE_MASK_OVERRUN_ERROR = 2 # Overrun Error
182LINESTATE_MASK_DATA_READY = 1 # Data Ready
cliechti8099bed2009-08-01 23:59:18 +0000183
cliechti044d8662009-08-11 21:40:31 +0000184MODEMSTATE_MASK_CD = 128 # Receive Line Signal Detect (also known as Carrier Detect)
185MODEMSTATE_MASK_RI = 64 # Ring Indicator
186MODEMSTATE_MASK_DSR = 32 # Data-Set-Ready Signal State
187MODEMSTATE_MASK_CTS = 16 # Clear-To-Send Signal State
188MODEMSTATE_MASK_CD_CHANGE = 8 # Delta Receive Line Signal Detect
189MODEMSTATE_MASK_RI_CHANGE = 4 # Trailing-edge Ring Detector
190MODEMSTATE_MASK_DSR_CHANGE = 2 # Delta Data-Set-Ready
191MODEMSTATE_MASK_CTS_CHANGE = 1 # Delta Clear-To-Send
cliechti8099bed2009-08-01 23:59:18 +0000192
193PURGE_RECEIVE_BUFFER = to_bytes([1]) # Purge access server receive data buffer
194PURGE_TRANSMIT_BUFFER = to_bytes([2]) # Purge access server transmit data buffer
195PURGE_BOTH_BUFFERS = to_bytes([3]) # Purge both the access server receive data buffer and the access server transmit data buffer
196
197
198RFC2217_PARITY_MAP = {
199 PARITY_NONE: 1,
200 PARITY_ODD: 2,
201 PARITY_EVEN: 3,
202 PARITY_MARK: 4,
203 PARITY_SPACE: 5,
204}
cliechti130d1f02009-08-04 02:10:58 +0000205RFC2217_REVERSE_PARITY_MAP = dict((v,k) for k,v in RFC2217_PARITY_MAP.items())
cliechti8099bed2009-08-01 23:59:18 +0000206
207RFC2217_STOPBIT_MAP = {
208 STOPBITS_ONE: 1,
209 STOPBITS_ONE_POINT_FIVE: 3,
210 STOPBITS_TWO: 2,
211}
cliechti130d1f02009-08-04 02:10:58 +0000212RFC2217_REVERSE_STOPBIT_MAP = dict((v,k) for k,v in RFC2217_STOPBIT_MAP.items())
cliechti8099bed2009-08-01 23:59:18 +0000213
cliechti130d1f02009-08-04 02:10:58 +0000214# Telnet filter states
215M_NORMAL = 0
216M_IAC_SEEN = 1
217M_NEGOTIATE = 2
cliechti8099bed2009-08-01 23:59:18 +0000218
cliechti130d1f02009-08-04 02:10:58 +0000219# TelnetOption and TelnetSubnegotiation states
cliechtiac205322009-08-02 20:40:21 +0000220REQUESTED = 'REQUESTED'
221ACTIVE = 'ACTIVE'
222INACTIVE = 'INACTIVE'
223REALLY_INACTIVE = 'REALLY_INACTIVE'
224
225class TelnetOption(object):
cliechti1ef7e3e2009-08-03 02:38:43 +0000226 """Manage a single telnet option, keeps track of DO/DONT WILL/WONT."""
227
cliechti86b593e2009-08-05 16:28:12 +0000228 def __init__(self, connection, name, option, send_yes, send_no, ack_yes, ack_no, initial_state, activation_callback=None):
cliechti044d8662009-08-11 21:40:31 +0000229 """Init option.
cliechti1ef7e3e2009-08-03 02:38:43 +0000230 :param connection: connection used to transmit answers
231 :param name: a readable name for debug outputs
232 :param send_yes: what to send when option is to be enabled.
233 :param send_no: what to send when option is to be disabled.
234 :param ack_yes: what to expect when remote agrees on option.
235 :param ack_no: what to expect when remote disagrees on option.
236 :param initial_state: options initialized with REQUESTED are tried to
237 be enabled on startup. use INACTIVE for all others.
238 """
cliechti2b929b72009-08-02 23:49:02 +0000239 self.connection = connection
cliechtiac205322009-08-02 20:40:21 +0000240 self.name = name
241 self.option = option
242 self.send_yes = send_yes
243 self.send_no = send_no
244 self.ack_yes = ack_yes
245 self.ack_no = ack_no
246 self.state = initial_state
247 self.active = False
cliechti86b593e2009-08-05 16:28:12 +0000248 self.activation_callback = activation_callback
cliechtiac205322009-08-02 20:40:21 +0000249
250 def __repr__(self):
cliechti1ef7e3e2009-08-03 02:38:43 +0000251 """String for debug outputs"""
cliechtiac205322009-08-02 20:40:21 +0000252 return "%s:%s(%s)" % (self.name, self.active, self.state)
253
cliechti2b929b72009-08-02 23:49:02 +0000254 def process_incoming(self, command):
cliechti1ef7e3e2009-08-03 02:38:43 +0000255 """A DO/DONT/WILL/WONT was received for this option, update state and
256 answer when needed."""
cliechtiac205322009-08-02 20:40:21 +0000257 if command == self.ack_yes:
258 if self.state is REQUESTED:
259 self.state = ACTIVE
260 self.active = True
cliechti86b593e2009-08-05 16:28:12 +0000261 if self.activation_callback is not None:
262 self.activation_callback()
cliechtiac205322009-08-02 20:40:21 +0000263 elif self.state is ACTIVE:
264 pass
265 elif self.state is INACTIVE:
266 self.state = ACTIVE
cliechti1ef7e3e2009-08-03 02:38:43 +0000267 self.connection.telnetSendOption(self.send_yes, self.option)
cliechtiac205322009-08-02 20:40:21 +0000268 self.active = True
cliechti86b593e2009-08-05 16:28:12 +0000269 if self.activation_callback is not None:
270 self.activation_callback()
cliechtiac205322009-08-02 20:40:21 +0000271 elif self.state is REALLY_INACTIVE:
cliechti1ef7e3e2009-08-03 02:38:43 +0000272 self.connection.telnetSendOption(self.send_no, self.option)
cliechtiac205322009-08-02 20:40:21 +0000273 else:
274 raise ValueError('option in illegal state %r' % self)
275 elif command == self.ack_no:
276 if self.state is REQUESTED:
277 self.state = INACTIVE
278 self.active = False
279 elif self.state is ACTIVE:
280 self.state = INACTIVE
cliechti1ef7e3e2009-08-03 02:38:43 +0000281 self.connection.telnetSendOption(self.send_no, self.option)
cliechtiac205322009-08-02 20:40:21 +0000282 self.active = False
283 elif self.state is INACTIVE:
284 pass
285 elif self.state is REALLY_INACTIVE:
286 pass
287 else:
288 raise ValueError('option in illegal state %r' % self)
289
290
cliechti2b929b72009-08-02 23:49:02 +0000291class TelnetSubnegotiation(object):
cliechti1ef7e3e2009-08-03 02:38:43 +0000292 """A object to handle subnegotiation of options. In this case actually
293 sub-sub options for RFC2217. It is used to track com port options."""
cliechti2b929b72009-08-02 23:49:02 +0000294
295 def __init__(self, connection, name, option, ack_option=None):
296 if ack_option is None: ack_option = option
297 self.connection = connection
298 self.name = name
299 self.option = option
300 self.value = None
301 self.ack_option = ack_option
302 self.state = INACTIVE
303
304 def __repr__(self):
cliechti044d8662009-08-11 21:40:31 +0000305 """String for debug outputs."""
cliechti2b929b72009-08-02 23:49:02 +0000306 return "%s:%s" % (self.name, self.state)
307
308 def set(self, value):
309 """request a change of the value. a request is sent to the server. if
310 the client needs to know if the change is performed he has to check the
311 state of this object."""
312 self.value = value
313 self.state = REQUESTED
cliechti1ef7e3e2009-08-03 02:38:43 +0000314 self.connection.rfc2217SendSubnegotiation(self.option, self.value)
cliechti81c54762009-08-03 23:53:27 +0000315 if self.connection.debug_output:
cliechti5cc3eb12009-08-11 23:04:30 +0000316 self.connection.debug_output.debug("SB Requesting %s -> %r" % (self.name, self.value))
cliechti2b929b72009-08-02 23:49:02 +0000317
318 def isReady(self):
cliechti1ef7e3e2009-08-03 02:38:43 +0000319 """check if answer from server has been received. when server rejects
320 the change, raise a ValueError."""
cliechti2b929b72009-08-02 23:49:02 +0000321 if self.state == REALLY_INACTIVE:
322 raise ValueError("remote rejected value for option %r" % (self.name))
323 return self.state == ACTIVE
cliechti1ef7e3e2009-08-03 02:38:43 +0000324 # add property to have a similar interface as TelnetOption
cliechti2b929b72009-08-02 23:49:02 +0000325 active = property(isReady)
326
cliechti044d8662009-08-11 21:40:31 +0000327 def wait(self, timeout=3):
cliechti1ef7e3e2009-08-03 02:38:43 +0000328 """wait until the subnegotiation has been acknowledged or timeout. It
329 can also throw a value error when the answer from the server does not
330 match the value sent."""
cliechti044d8662009-08-11 21:40:31 +0000331 timeout_time = time.time() + timeout
cliechti2b929b72009-08-02 23:49:02 +0000332 while time.time() < timeout_time:
333 time.sleep(0.05) # prevent 100% CPU load
334 if self.isReady():
335 break
336 else:
337 raise SerialException("timeout while waiting for option %r" % (self.name))
338
339 def checkAnswer(self, suboption):
340 """check an incoming subnegotiation block. the parameter already has
cliechti044d8662009-08-11 21:40:31 +0000341 cut off the header like sub option number and com port option value."""
cliechti2b929b72009-08-02 23:49:02 +0000342 if self.value == suboption[:len(self.value)]:
343 self.state = ACTIVE
344 else:
345 # error propagation done in isReady
346 self.state = REALLY_INACTIVE
cliechti81c54762009-08-03 23:53:27 +0000347 if self.connection.debug_output:
cliechti5cc3eb12009-08-11 23:04:30 +0000348 self.connection.debug_output.debug("SB Answer %s -> %r -> %s" % (self.name, suboption, self.state))
cliechti2b929b72009-08-02 23:49:02 +0000349
350
cliechti8099bed2009-08-01 23:59:18 +0000351class RFC2217Serial(SerialBase):
cliechti044d8662009-08-11 21:40:31 +0000352 """Serial port implementation for RFC 2217 remote serial ports."""
cliechti8099bed2009-08-01 23:59:18 +0000353
354 BAUDRATES = (50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800,
355 9600, 19200, 38400, 57600, 115200)
356
357 def open(self):
358 """Open port with current settings. This may throw a SerialException
359 if the port cannot be opened."""
cliechti5cc3eb12009-08-11 23:04:30 +0000360 self.debug_output = None
cliechti81c54762009-08-03 23:53:27 +0000361 self._ignore_set_control_answer = False
cliechti7cb78e82009-08-05 15:47:57 +0000362 self._poll_modem_state = False
cliechtidfe2d272009-08-10 22:19:41 +0000363 self._network_timeout = 3
cliechti8099bed2009-08-01 23:59:18 +0000364 if self._port is None:
365 raise SerialException("Port must be configured before it can be used.")
366 try:
367 self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
368 self._socket.connect(self.fromURL(self.portstr))
cliechtic63b3c02009-08-07 19:17:20 +0000369 self._socket.setsockopt( socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
cliechti8099bed2009-08-01 23:59:18 +0000370 except Exception, msg:
371 self._socket = None
372 raise SerialException("Could not open port %s: %s" % (self.portstr, msg))
373
cliechti1ef7e3e2009-08-03 02:38:43 +0000374 self._socket.settimeout(5) # XXX good value?
cliechti8099bed2009-08-01 23:59:18 +0000375
cliechti1ef7e3e2009-08-03 02:38:43 +0000376 # use a thread save queue as buffer. it also simplifies implementing
377 # the read timeout
cliechti8099bed2009-08-01 23:59:18 +0000378 self._read_buffer = Queue.Queue()
cliechti81c54762009-08-03 23:53:27 +0000379 # to ensure that user writes does not interfere with internal
380 # telnet/rfc2217 options establish a lock
381 self._write_lock = threading.Lock()
cliechtiac205322009-08-02 20:40:21 +0000382 # name the following separately so that, below, a check can be easily done
383 mandadory_options = [
cliechti2b929b72009-08-02 23:49:02 +0000384 TelnetOption(self, 'we-BINARY', BINARY, WILL, WONT, DO, DONT, INACTIVE),
cliechti2b929b72009-08-02 23:49:02 +0000385 TelnetOption(self, 'we-RFC2217', COM_PORT_OPTION, WILL, WONT, DO, DONT, REQUESTED),
cliechtiac205322009-08-02 20:40:21 +0000386 ]
387 # all supported telnet options
388 self._telnet_options = [
cliechtia29275e2009-08-03 00:08:04 +0000389 TelnetOption(self, 'ECHO', ECHO, DO, DONT, WILL, WONT, REQUESTED),
cliechti2b929b72009-08-02 23:49:02 +0000390 TelnetOption(self, 'we-SGA', SGA, WILL, WONT, DO, DONT, REQUESTED),
391 TelnetOption(self, 'they-SGA', SGA, DO, DONT, WILL, WONT, REQUESTED),
cliechti81c54762009-08-03 23:53:27 +0000392 TelnetOption(self, 'they-BINARY', BINARY, DO, DONT, WILL, WONT, INACTIVE),
393 TelnetOption(self, 'they-RFC2217', COM_PORT_OPTION, DO, DONT, WILL, WONT, REQUESTED),
cliechtiac205322009-08-02 20:40:21 +0000394 ] + mandadory_options
cliechti044d8662009-08-11 21:40:31 +0000395 # RFC 2217 specific states
cliechti2b929b72009-08-02 23:49:02 +0000396 # COM port settings
397 self._rfc2217_port_settings = {
398 'baudrate': TelnetSubnegotiation(self, 'baudrate', SET_BAUDRATE, SERVER_SET_BAUDRATE),
399 'datasize': TelnetSubnegotiation(self, 'datasize', SET_DATASIZE, SERVER_SET_DATASIZE),
400 'parity': TelnetSubnegotiation(self, 'parity', SET_PARITY, SERVER_SET_PARITY),
401 'stopsize': TelnetSubnegotiation(self, 'stopsize', SET_STOPSIZE, SERVER_SET_STOPSIZE),
402 }
403 # There are more subnegotiation object, combine all in one dictionary
404 # for easy access
405 self._rfc2217_options = {
406 'purge': TelnetSubnegotiation(self, 'purge', PURGE_DATA, SERVER_PURGE_DATA),
cliechti81c54762009-08-03 23:53:27 +0000407 'control': TelnetSubnegotiation(self, 'control', SET_CONTROL, SERVER_SET_CONTROL),
cliechti2b929b72009-08-02 23:49:02 +0000408 }
409 self._rfc2217_options.update(self._rfc2217_port_settings)
410 # cache for line and modem states that the server sends to us
cliechti8099bed2009-08-01 23:59:18 +0000411 self._linestate = 0
cliechti7cb78e82009-08-05 15:47:57 +0000412 self._modemstate = None
413 self._modemstate_expires = 0
cliechti044d8662009-08-11 21:40:31 +0000414 # RFC 2217 flow control between server and client
cliechti672d0292009-08-03 02:01:57 +0000415 self._remote_suspend_flow = False
cliechti8099bed2009-08-01 23:59:18 +0000416
cliechti1ef7e3e2009-08-03 02:38:43 +0000417 self._thread = threading.Thread(target=self._telnetReadLoop)
cliechti8099bed2009-08-01 23:59:18 +0000418 self._thread.setDaemon(True)
cliechti5cc3eb12009-08-11 23:04:30 +0000419 self._thread.setName('pySerial RFC 2217 reader thread for %s' % (self._port,))
cliechti8099bed2009-08-01 23:59:18 +0000420 self._thread.start()
421
cliechti044d8662009-08-11 21:40:31 +0000422 # negotiate Telnet/RFC 2217 -> send initial requests
cliechtiac205322009-08-02 20:40:21 +0000423 for option in self._telnet_options:
424 if option.state is REQUESTED:
cliechti1ef7e3e2009-08-03 02:38:43 +0000425 self.telnetSendOption(option.send_yes, option.option)
cliechtiac205322009-08-02 20:40:21 +0000426 # now wait until important options are negotiated
cliechtidfe2d272009-08-10 22:19:41 +0000427 timeout_time = time.time() + self._network_timeout
cliechtiac205322009-08-02 20:40:21 +0000428 while time.time() < timeout_time:
cliechtiac205322009-08-02 20:40:21 +0000429 time.sleep(0.05) # prevent 100% CPU load
cliechti2b929b72009-08-02 23:49:02 +0000430 if sum(o.active for o in mandadory_options) == len(mandadory_options):
431 break
432 else:
433 raise SerialException("Remote does not seem to support RFC2217 or BINARY mode %r" % mandadory_options)
cliechti81c54762009-08-03 23:53:27 +0000434 if self.debug_output:
cliechti5cc3eb12009-08-11 23:04:30 +0000435 self.debug_output.info("Negotiated options: %s" % self._telnet_options)
cliechti8099bed2009-08-01 23:59:18 +0000436
cliechti044d8662009-08-11 21:40:31 +0000437 # fine, go on, set RFC 2271 specific things
cliechti8099bed2009-08-01 23:59:18 +0000438 self._reconfigurePort()
cliechti2b929b72009-08-02 23:49:02 +0000439 # all things set up get, now a clean start
cliechti8099bed2009-08-01 23:59:18 +0000440 self._isOpen = True
441 if not self._rtscts:
442 self.setRTS(True)
443 self.setDTR(True)
444 self.flushInput()
445 self.flushOutput()
446
447 def _reconfigurePort(self):
448 """Set communication parameters on opened port."""
449 if self._socket is None:
450 raise SerialException("Can only operate on open ports")
451
cliechti8099bed2009-08-01 23:59:18 +0000452 # if self._timeout != 0 and self._interCharTimeout is not None:
cliechti8099bed2009-08-01 23:59:18 +0000453 # XXX
454
455 if self._writeTimeout is not None:
456 raise NotImplementedError('writeTimeout is currently not supported')
cliechti2b929b72009-08-02 23:49:02 +0000457 # XXX
cliechti8099bed2009-08-01 23:59:18 +0000458
cliechti2b929b72009-08-02 23:49:02 +0000459 # Setup the connection
cliechti1ef7e3e2009-08-03 02:38:43 +0000460 # to get good performance, all parameter changes are sent first...
cliechti81c54762009-08-03 23:53:27 +0000461 if not isinstance(self._baudrate, (int, long)) or not 0 < self._baudrate < 2**32:
462 raise ValueError("invalid baudrate: %r" % (self._baudrate))
cliechti2b929b72009-08-02 23:49:02 +0000463 self._rfc2217_port_settings['baudrate'].set(struct.pack('!I', self._baudrate))
464 self._rfc2217_port_settings['datasize'].set(struct.pack('!B', self._bytesize))
465 self._rfc2217_port_settings['parity'].set(struct.pack('!B', RFC2217_PARITY_MAP[self._parity]))
466 self._rfc2217_port_settings['stopsize'].set(struct.pack('!B', RFC2217_STOPBIT_MAP[self._stopbits]))
cliechti8099bed2009-08-01 23:59:18 +0000467
cliechti2b929b72009-08-02 23:49:02 +0000468 # and now wait until parameters are active
469 items = self._rfc2217_port_settings.values()
cliechti5cc3eb12009-08-11 23:04:30 +0000470 if self.debug_output:
471 self.debug_output.debug("Negotiating settings: %s" % (items,))
cliechtidfe2d272009-08-10 22:19:41 +0000472 timeout_time = time.time() + self._network_timeout
cliechti2b929b72009-08-02 23:49:02 +0000473 while time.time() < timeout_time:
474 time.sleep(0.05) # prevent 100% CPU load
475 if sum(o.active for o in items) == len(items):
476 break
477 else:
478 raise SerialException("Remote does not accept parameter change (RFC2217): %r" % items)
cliechti81c54762009-08-03 23:53:27 +0000479 if self.debug_output:
cliechti5cc3eb12009-08-11 23:04:30 +0000480 self.debug_output.info("Negotiated settings: %s" % (items,))
cliechti8099bed2009-08-01 23:59:18 +0000481
482 if self._rtscts and self._xonxoff:
cliechti1ef7e3e2009-08-03 02:38:43 +0000483 raise ValueError('xonxoff and rtscts together are not supported')
cliechti8099bed2009-08-01 23:59:18 +0000484 elif self._rtscts:
cliechti1ef7e3e2009-08-03 02:38:43 +0000485 self.rfc2217SetControl(SET_CONTROL_USE_HW_FLOW_CONTROL)
cliechti8099bed2009-08-01 23:59:18 +0000486 elif self._xonxoff:
cliechti1ef7e3e2009-08-03 02:38:43 +0000487 self.rfc2217SetControl(SET_CONTROL_USE_SW_FLOW_CONTROL)
cliechti8099bed2009-08-01 23:59:18 +0000488 else:
cliechti1ef7e3e2009-08-03 02:38:43 +0000489 self.rfc2217SetControl(SET_CONTROL_USE_NO_FLOW_CONTROL)
cliechti8099bed2009-08-01 23:59:18 +0000490
491 def close(self):
492 """Close port"""
493 if self._isOpen:
494 if self._socket:
495 try:
496 self._socket.shutdown(socket.SHUT_RDWR)
497 self._socket.close()
498 except:
499 # ignore errors.
500 pass
501 self._socket = None
502 if self._thread:
503 self._thread.join()
504 self._isOpen = False
505 # in case of quick reconnects, give the server some time
506 time.sleep(0.3)
507
508 def makeDeviceName(self, port):
509 raise SerialException("there is no sensible way to turn numbers into URLs")
510
511 def fromURL(self, url):
cliechti1ef7e3e2009-08-03 02:38:43 +0000512 """extract host and port from an URL string"""
cliechti8099bed2009-08-01 23:59:18 +0000513 if url.lower().startswith("rfc2217://"): url = url[10:]
514 try:
cliechti81c54762009-08-03 23:53:27 +0000515 # is there a "path" (our options)?
516 if '/' in url:
517 # cut away options
518 url, options = url.split('/', 1)
519 # process options now, directly altering self
520 for option in options.split('/'):
cliechtidfe2d272009-08-10 22:19:41 +0000521 if '=' in option:
522 option, value = option.split('=', 1)
523 else:
524 value = None
cliechti5cc3eb12009-08-11 23:04:30 +0000525 if option == 'logging':
526 logging.basicConfig() # XXX is that good to call it here?
527 self.debug_output = logging.getLogger('rfc2217.client')
528 self.debug_output.setLevel(LOGGER_LEVELS[value])
529 self.debug_output.debug('enabled logging')
cliechti81c54762009-08-03 23:53:27 +0000530 elif option == 'ign_set_control':
531 self._ignore_set_control_answer = True
cliechti7cb78e82009-08-05 15:47:57 +0000532 elif option == 'poll_modem':
533 self._poll_modem_state = True
cliechtidfe2d272009-08-10 22:19:41 +0000534 elif option == 'timeout':
535 self._network_timeout = float(value)
cliechti81c54762009-08-03 23:53:27 +0000536 else:
537 raise ValueError('unknown option: %r' % (option,))
538 # get host and port
cliechti8099bed2009-08-01 23:59:18 +0000539 host, port = url.split(':', 1) # may raise ValueError because of unpacking
540 port = int(port) # and this if it's not a number
541 if not 0 <= port < 65536: raise ValueError("port not in range 0...65535")
542 except ValueError, e:
cliechti81c54762009-08-03 23:53:27 +0000543 raise SerialException('expected a string in the form "[rfc2217://]<host>:<port>[/option[/option...]]": %s' % e)
cliechti8099bed2009-08-01 23:59:18 +0000544 return (host, port)
545
546 # - - - - - - - - - - - - - - - - - - - - - - - -
547
548 def inWaiting(self):
549 """Return the number of characters currently in the input buffer."""
550 if not self._isOpen: raise portNotOpenError
551 return self._read_buffer.qsize()
552
553 def read(self, size=1):
554 """Read size bytes from the serial port. If a timeout is set it may
555 return less characters as requested. With no timeout it will block
556 until the requested number of bytes is read."""
557 if not self._isOpen: raise portNotOpenError
558 data = bytearray()
559 try:
560 while len(data) < size:
cliechti81c54762009-08-03 23:53:27 +0000561 if self._thread is None:
562 raise SerialException('connection failed (reader thread died)')
cliechti8099bed2009-08-01 23:59:18 +0000563 data.append(self._read_buffer.get(True, self._timeout))
564 except Queue.Empty: # -> timeout
565 pass
566 return bytes(data)
567
568 def write(self, data):
569 """Output the given string over the serial port. Can block if the
570 connection is blocked. May raise SerialException if the connection is
571 closed."""
572 if not self._isOpen: raise portNotOpenError
cliechti81c54762009-08-03 23:53:27 +0000573 self._write_lock.acquire()
cliechti8099bed2009-08-01 23:59:18 +0000574 try:
cliechti81c54762009-08-03 23:53:27 +0000575 try:
576 self._socket.sendall(data.replace(IAC, IAC_DOUBLED))
577 except socket.error, e:
578 raise SerialException("socket connection failed: %s" % e) # XXX what exception if socket connection fails
579 finally:
580 self._write_lock.release()
cliechti8099bed2009-08-01 23:59:18 +0000581 return len(data)
582
583 def flushInput(self):
584 """Clear input buffer, discarding all that is in the buffer."""
585 if not self._isOpen: raise portNotOpenError
cliechti1ef7e3e2009-08-03 02:38:43 +0000586 self.rfc2217SendPurge(PURGE_RECEIVE_BUFFER)
cliechti8099bed2009-08-01 23:59:18 +0000587 # empty read buffer
588 while self._read_buffer.qsize():
589 self._read_buffer.get(False)
590
591 def flushOutput(self):
592 """Clear output buffer, aborting the current output and
593 discarding all that is in the buffer."""
594 if not self._isOpen: raise portNotOpenError
cliechti1ef7e3e2009-08-03 02:38:43 +0000595 self.rfc2217SendPurge(PURGE_TRANSMIT_BUFFER)
cliechti8099bed2009-08-01 23:59:18 +0000596
597 def sendBreak(self, duration=0.25):
598 """Send break condition. Timed, returns to idle state after given
599 duration."""
600 if not self._isOpen: raise portNotOpenError
601 self.setBreak(True)
602 time.sleep(duration)
603 self.setBreak(False)
604
605 def setBreak(self, level=True):
606 """Set break: Controls TXD. When active, to transmitting is
607 possible."""
608 if not self._isOpen: raise portNotOpenError
cliechti5cc3eb12009-08-11 23:04:30 +0000609 if self.debug_output:
610 self.debug_output.info('set BREAK to %s' % ('inactive', 'active')[bool(level)])
cliechti8099bed2009-08-01 23:59:18 +0000611 if level:
cliechti1ef7e3e2009-08-03 02:38:43 +0000612 self.rfc2217SetControl(SET_CONTROL_BREAK_ON)
cliechti8099bed2009-08-01 23:59:18 +0000613 else:
cliechti1ef7e3e2009-08-03 02:38:43 +0000614 self.rfc2217SetControl(SET_CONTROL_BREAK_OFF)
cliechti8099bed2009-08-01 23:59:18 +0000615
616 def setRTS(self, level=True):
cliechti044d8662009-08-11 21:40:31 +0000617 """Set terminal status line: Request To Send."""
cliechti8099bed2009-08-01 23:59:18 +0000618 if not self._isOpen: raise portNotOpenError
cliechti5cc3eb12009-08-11 23:04:30 +0000619 if self.debug_output:
620 self.debug_output.info('set RTS to %s' % ('inactive', 'active')[bool(level)])
cliechti8099bed2009-08-01 23:59:18 +0000621 if level:
cliechti1ef7e3e2009-08-03 02:38:43 +0000622 self.rfc2217SetControl(SET_CONTROL_RTS_ON)
cliechti8099bed2009-08-01 23:59:18 +0000623 else:
cliechti1ef7e3e2009-08-03 02:38:43 +0000624 self.rfc2217SetControl(SET_CONTROL_RTS_OFF)
cliechti8099bed2009-08-01 23:59:18 +0000625
626 def setDTR(self, level=True):
cliechti044d8662009-08-11 21:40:31 +0000627 """Set terminal status line: Data Terminal Ready."""
cliechti8099bed2009-08-01 23:59:18 +0000628 if not self._isOpen: raise portNotOpenError
cliechti5cc3eb12009-08-11 23:04:30 +0000629 if self.debug_output:
630 self.debug_output.info('set DTR to %s' % ('inactive', 'active')[bool(level)])
cliechti8099bed2009-08-01 23:59:18 +0000631 if level:
cliechti1ef7e3e2009-08-03 02:38:43 +0000632 self.rfc2217SetControl(SET_CONTROL_DTR_ON)
cliechti8099bed2009-08-01 23:59:18 +0000633 else:
cliechti1ef7e3e2009-08-03 02:38:43 +0000634 self.rfc2217SetControl(SET_CONTROL_DTR_OFF)
cliechti8099bed2009-08-01 23:59:18 +0000635
636 def getCTS(self):
cliechti044d8662009-08-11 21:40:31 +0000637 """Read terminal status line: Clear To Send."""
cliechti8099bed2009-08-01 23:59:18 +0000638 if not self._isOpen: raise portNotOpenError
cliechti7cb78e82009-08-05 15:47:57 +0000639 return bool(self.getModemState() & MODEMSTATE_MASK_CTS)
cliechti8099bed2009-08-01 23:59:18 +0000640
641 def getDSR(self):
cliechti044d8662009-08-11 21:40:31 +0000642 """Read terminal status line: Data Set Ready."""
cliechti8099bed2009-08-01 23:59:18 +0000643 if not self._isOpen: raise portNotOpenError
cliechti7cb78e82009-08-05 15:47:57 +0000644 return bool(self.getModemState() & MODEMSTATE_MASK_DSR)
cliechti8099bed2009-08-01 23:59:18 +0000645
646 def getRI(self):
cliechti044d8662009-08-11 21:40:31 +0000647 """Read terminal status line: Ring Indicator."""
cliechti8099bed2009-08-01 23:59:18 +0000648 if not self._isOpen: raise portNotOpenError
cliechti7cb78e82009-08-05 15:47:57 +0000649 return bool(self.getModemState() & MODEMSTATE_MASK_RI)
cliechti8099bed2009-08-01 23:59:18 +0000650
651 def getCD(self):
cliechti044d8662009-08-11 21:40:31 +0000652 """Read terminal status line: Carrier Detect."""
cliechti8099bed2009-08-01 23:59:18 +0000653 if not self._isOpen: raise portNotOpenError
cliechti7cb78e82009-08-05 15:47:57 +0000654 return bool(self.getModemState() & MODEMSTATE_MASK_CD)
cliechti8099bed2009-08-01 23:59:18 +0000655
656 # - - - platform specific - - -
657 # None so far
658
659 # - - - RFC2217 specific - - -
660
cliechti1ef7e3e2009-08-03 02:38:43 +0000661 def _telnetReadLoop(self):
cliechti044d8662009-08-11 21:40:31 +0000662 """read loop for the socket."""
cliechti8099bed2009-08-01 23:59:18 +0000663 mode = M_NORMAL
664 suboption = None
cliechti81c54762009-08-03 23:53:27 +0000665 try:
666 while self._socket is not None:
667 try:
668 data = self._socket.recv(1024)
669 except socket.timeout:
670 # just need to get out of recv form time to time to check if
671 # still alive
672 continue
673 except socket.error:
674 # connection fails -> terminate loop
675 break
676 for byte in data:
677 if mode == M_NORMAL:
678 # interpret as command or as data
679 if byte == IAC:
680 mode = M_IAC_SEEN
cliechti8099bed2009-08-01 23:59:18 +0000681 else:
cliechti81c54762009-08-03 23:53:27 +0000682 # store data in read buffer or sub option buffer
683 # depending on state
684 if suboption is not None:
685 suboption.append(byte)
686 else:
687 self._read_buffer.put(byte)
688 elif mode == M_IAC_SEEN:
689 if byte == IAC:
690 # interpret as command doubled -> insert character
691 # itself
692 self._read_buffer.put(IAC)
693 mode = M_NORMAL
694 elif byte == SB:
695 # sub option start
696 suboption = bytearray()
697 mode = M_NORMAL
698 elif byte == SE:
699 # sub option end -> process it now
700 self._telnetProcessSubnegotiation(bytes(suboption))
701 suboption = None
702 mode = M_NORMAL
703 elif byte in (DO, DONT, WILL, WONT):
704 # negotiation
705 telnet_command = byte
706 mode = M_NEGOTIATE
707 else:
708 # other telnet commands
709 self._telnetProcessCommand(byte)
710 mode = M_NORMAL
711 elif mode == M_NEGOTIATE: # DO, DONT, WILL, WONT was received, option now following
712 self._telnetNegotiateOption(telnet_command, byte)
cliechti8099bed2009-08-01 23:59:18 +0000713 mode = M_NORMAL
cliechti81c54762009-08-03 23:53:27 +0000714 finally:
715 self._thread = None
716 if self.debug_output:
cliechti5cc3eb12009-08-11 23:04:30 +0000717 self.debug_output.debug("read thread terminated")
cliechti8099bed2009-08-01 23:59:18 +0000718
719 # - incoming telnet commands and options
720
cliechti1ef7e3e2009-08-03 02:38:43 +0000721 def _telnetProcessCommand(self, command):
cliechti044d8662009-08-11 21:40:31 +0000722 """Process commands other than DO, DONT, WILL, WONT."""
cliechti1ef7e3e2009-08-03 02:38:43 +0000723 # Currently none. RFC2217 only uses negotiation and subnegotiation.
cliechti5cc3eb12009-08-11 23:04:30 +0000724 if self.debug_output:
725 self.debug_output.warning("ignoring Telnet command: %r" % (command,))
cliechti8099bed2009-08-01 23:59:18 +0000726
cliechti1ef7e3e2009-08-03 02:38:43 +0000727 def _telnetNegotiateOption(self, command, option):
cliechti044d8662009-08-11 21:40:31 +0000728 """Process incoming DO, DONT, WILL, WONT."""
cliechti2b929b72009-08-02 23:49:02 +0000729 # check our registered telnet options and forward command to them
730 # they know themselves if they have to answer or not
cliechtiac205322009-08-02 20:40:21 +0000731 known = False
732 for item in self._telnet_options:
cliechti2b929b72009-08-02 23:49:02 +0000733 # can have more than one match! as some options are duplicated for
734 # 'us' and 'them'
cliechtiac205322009-08-02 20:40:21 +0000735 if item.option == option:
cliechti2b929b72009-08-02 23:49:02 +0000736 item.process_incoming(command)
cliechtiac205322009-08-02 20:40:21 +0000737 known = True
738 if not known:
739 # handle unknown options
740 # only answer to positive requests and deny them
741 if command == WILL or command == DO:
cliechti1ef7e3e2009-08-03 02:38:43 +0000742 self.telnetSendOption((command == WILL and DONT or WONT), option)
cliechti5cc3eb12009-08-11 23:04:30 +0000743 if self.debug_output:
744 self.debug_output.warning("rejected Telnet option: %r" % (option,))
cliechtiac205322009-08-02 20:40:21 +0000745
cliechti8099bed2009-08-01 23:59:18 +0000746
cliechti1ef7e3e2009-08-03 02:38:43 +0000747 def _telnetProcessSubnegotiation(self, suboption):
cliechti044d8662009-08-11 21:40:31 +0000748 """Process subnegotiation, the data between IAC SB and IAC SE."""
cliechti8099bed2009-08-01 23:59:18 +0000749 if suboption[0:1] == COM_PORT_OPTION:
750 if suboption[1:2] == SERVER_NOTIFY_LINESTATE and len(suboption) >= 3:
cliechti672d0292009-08-03 02:01:57 +0000751 self._linestate = ord(suboption[2:3]) # ensure it is a number
cliechti81c54762009-08-03 23:53:27 +0000752 if self.debug_output:
cliechti5cc3eb12009-08-11 23:04:30 +0000753 self.debug_output.info("NOTIFY_LINESTATE: %s" % self._linestate)
cliechti8099bed2009-08-01 23:59:18 +0000754 elif suboption[1:2] == SERVER_NOTIFY_MODEMSTATE and len(suboption) >= 3:
cliechti672d0292009-08-03 02:01:57 +0000755 self._modemstate = ord(suboption[2:3]) # ensure it is a number
cliechti81c54762009-08-03 23:53:27 +0000756 if self.debug_output:
cliechti5cc3eb12009-08-11 23:04:30 +0000757 self.debug_output.info("NOTIFY_MODEMSTATE: %s" % self._modemstate)
cliechti7cb78e82009-08-05 15:47:57 +0000758 # update time when we think that a poll would make sense
759 self._modemstate_expires = time.time() + 0.3
cliechti672d0292009-08-03 02:01:57 +0000760 elif suboption[1:2] == FLOWCONTROL_SUSPEND:
761 self._remote_suspend_flow = True
762 elif suboption[1:2] == FLOWCONTROL_RESUME:
763 self._remote_suspend_flow = False
cliechti8099bed2009-08-01 23:59:18 +0000764 else:
cliechti2b929b72009-08-02 23:49:02 +0000765 for item in self._rfc2217_options.values():
766 if item.ack_option == suboption[1:2]:
cliechti81c54762009-08-03 23:53:27 +0000767 #~ print "processing COM_PORT_OPTION: %r" % list(suboption[1:])
cliechti2b929b72009-08-02 23:49:02 +0000768 item.checkAnswer(bytes(suboption[2:]))
769 break
770 else:
cliechti81c54762009-08-03 23:53:27 +0000771 if self.debug_output:
cliechti5cc3eb12009-08-11 23:04:30 +0000772 self.debug_output.warning("ignoring COM_PORT_OPTION: %r" % (suboption,))
cliechti8099bed2009-08-01 23:59:18 +0000773 else:
cliechti5cc3eb12009-08-11 23:04:30 +0000774 if self.debug_output:
775 self.debug_output.warning("ignoring subnegotiation: %r" % (suboption,))
cliechti8099bed2009-08-01 23:59:18 +0000776
777 # - outgoing telnet commands and options
778
cliechti81c54762009-08-03 23:53:27 +0000779 def _internal_raw_write(self, data):
cliechti044d8662009-08-11 21:40:31 +0000780 """internal socket write with no data escaping. used to send telnet stuff."""
cliechti81c54762009-08-03 23:53:27 +0000781 self._write_lock.acquire()
782 try:
783 self._socket.sendall(data)
784 finally:
785 self._write_lock.release()
786
cliechti1ef7e3e2009-08-03 02:38:43 +0000787 def telnetSendOption(self, action, option):
cliechti044d8662009-08-11 21:40:31 +0000788 """Send DO, DONT, WILL, WONT."""
cliechti81c54762009-08-03 23:53:27 +0000789 self._internal_raw_write(to_bytes([IAC, action, option]))
cliechti8099bed2009-08-01 23:59:18 +0000790
cliechti1ef7e3e2009-08-03 02:38:43 +0000791 def rfc2217SendSubnegotiation(self, option, value=[]):
cliechti044d8662009-08-11 21:40:31 +0000792 """Subnegotiation of RFC2217 parameters."""
cliechti81c54762009-08-03 23:53:27 +0000793 self._internal_raw_write(to_bytes([IAC, SB, COM_PORT_OPTION, option] + list(value) + [IAC, SE]))
cliechti2b929b72009-08-02 23:49:02 +0000794
cliechti1ef7e3e2009-08-03 02:38:43 +0000795 def rfc2217SendPurge(self, value):
cliechti2b929b72009-08-02 23:49:02 +0000796 item = self._rfc2217_options['purge']
cliechti672d0292009-08-03 02:01:57 +0000797 item.set(value) # transmit desired purge type
cliechtidfe2d272009-08-10 22:19:41 +0000798 item.wait(self._network_timeout) # wait for acknowledge from the server
cliechti2b929b72009-08-02 23:49:02 +0000799
cliechti1ef7e3e2009-08-03 02:38:43 +0000800 def rfc2217SetControl(self, value):
cliechti81c54762009-08-03 23:53:27 +0000801 item = self._rfc2217_options['control']
802 item.set(value) # transmit desired purge type
803 if self._ignore_set_control_answer:
804 # answers are ignored when option is set. compatibility mode for
805 # servers that answers, but not the expected ones... (or no answer
806 # at all) i.e. sredird
807 time.sleep(0.1) # this helps getting the unit tests passed
808 else:
cliechtidfe2d272009-08-10 22:19:41 +0000809 item.wait(self._network_timeout) # wait for acknowledge from the server
cliechti8099bed2009-08-01 23:59:18 +0000810
cliechti1ef7e3e2009-08-03 02:38:43 +0000811 def rfc2217FlowServerReady(self):
cliechti672d0292009-08-03 02:01:57 +0000812 """check if server is ready to receive data. block for some time when
cliechti044d8662009-08-11 21:40:31 +0000813 not."""
cliechti672d0292009-08-03 02:01:57 +0000814 #~ if self._remote_suspend_flow:
815 #~ wait---
816
cliechti7cb78e82009-08-05 15:47:57 +0000817 def getModemState(self):
818 """get last modem state (cached value. if value is "old", request a new
819 one. this cache helps that we don't issue to many requests when e.g. all
820 status lines, one after the other is queried by te user (getCTS, getDSR
821 etc.)"""
822 # active modem state polling enabled? is the value fresh enough?
823 if self._poll_modem_state and self._modemstate_expires < time.time():
cliechti5cc3eb12009-08-11 23:04:30 +0000824 if self.debug_output:
825 self.debug_output.debug('polling modem state')
cliechti7cb78e82009-08-05 15:47:57 +0000826 # when it is older, request an update
827 self.rfc2217SendSubnegotiation(NOTIFY_MODEMSTATE)
cliechtidfe2d272009-08-10 22:19:41 +0000828 timeout_time = time.time() + self._network_timeout
cliechti7cb78e82009-08-05 15:47:57 +0000829 while time.time() < timeout_time:
830 time.sleep(0.05) # prevent 100% CPU load
831 # when expiration time is updated, it means that there is a new
832 # value
833 if self._modemstate_expires > time.time():
cliechti5cc3eb12009-08-11 23:04:30 +0000834 if self.debug_output:
835 self.debug_output.warning('poll for modem state failed')
cliechti7cb78e82009-08-05 15:47:57 +0000836 break
837 # even when there is a timeout, do not generate an error just
838 # return the last known value. this way we can support buggy
839 # servers that do not respond to polls, but send automatic
840 # updates.
841 if self._modemstate is not None:
cliechti5cc3eb12009-08-11 23:04:30 +0000842 if self.debug_output:
843 self.debug_output.debug('using cached modem state')
cliechti7cb78e82009-08-05 15:47:57 +0000844 return self._modemstate
845 else:
846 # never received a notification from the server
cliechti8fb119c2009-08-05 23:39:45 +0000847 raise SerialException("remote sends no NOTIFY_MODEMSTATE")
cliechti8099bed2009-08-01 23:59:18 +0000848
cliechti5cc3eb12009-08-11 23:04:30 +0000849
cliechti8099bed2009-08-01 23:59:18 +0000850# assemble Serial class with the platform specific implementation and the base
851# for file-like behavior. for Python 2.6 and newer, that provide the new I/O
852# library, derive from io.RawIOBase
853try:
854 import io
855except ImportError:
856 # classic version with our own file-like emulation
857 class Serial(RFC2217Serial, FileLike):
858 pass
859else:
860 # io library present
861 class Serial(RFC2217Serial, io.RawIOBase):
862 pass
863
cliechti7cb78e82009-08-05 15:47:57 +0000864
cliechti595ed5b2009-08-10 01:43:32 +0000865#############################################################################
cliechti5cc3eb12009-08-11 23:04:30 +0000866# The following is code that helps implementing an RFC 2217 server.
cliechti8099bed2009-08-01 23:59:18 +0000867
cliechti8ccc2ff2009-08-05 12:44:46 +0000868class PortManager(object):
cliechti044d8662009-08-11 21:40:31 +0000869 """This class manages the state of Telnet and RFC 2217. It needs a serial
cliechti130d1f02009-08-04 02:10:58 +0000870 instance and a connection to work with. connection is expected to implement
cliechti595ed5b2009-08-10 01:43:32 +0000871 a (thread safe) write function, that writes the string to the network."""
cliechti130d1f02009-08-04 02:10:58 +0000872
cliechti5cc3eb12009-08-11 23:04:30 +0000873 def __init__(self, serial_port, connection, debug_output=None):
cliechti130d1f02009-08-04 02:10:58 +0000874 self.serial = serial_port
875 self.connection = connection
876 self.debug_output = debug_output
cliechti86b593e2009-08-05 16:28:12 +0000877 self._client_is_rfc2217 = False
cliechti130d1f02009-08-04 02:10:58 +0000878
879 # filter state machine
880 self.mode = M_NORMAL
881 self.suboption = None
882 self.telnet_command = None
883
884 # states for modem/line control events
885 self.modemstate_mask = 255
886 self.last_modemstate = None
887 self.linstate_mask = 0
888
889 # all supported telnet options
890 self._telnet_options = [
891 TelnetOption(self, 'ECHO', ECHO, WILL, WONT, DO, DONT, REQUESTED),
892 TelnetOption(self, 'we-SGA', SGA, WILL, WONT, DO, DONT, REQUESTED),
893 TelnetOption(self, 'they-SGA', SGA, DO, DONT, WILL, WONT, INACTIVE),
894 TelnetOption(self, 'we-BINARY', BINARY, WILL, WONT, DO, DONT, INACTIVE),
895 TelnetOption(self, 'they-BINARY', BINARY, DO, DONT, WILL, WONT, REQUESTED),
cliechti86b593e2009-08-05 16:28:12 +0000896 TelnetOption(self, 'we-RFC2217', COM_PORT_OPTION, WILL, WONT, DO, DONT, REQUESTED, self._client_ok),
897 TelnetOption(self, 'they-RFC2217', COM_PORT_OPTION, DO, DONT, WILL, WONT, INACTIVE, self._client_ok),
cliechti130d1f02009-08-04 02:10:58 +0000898 ]
899
900 # negotiate Telnet/RFC2217 -> send initial requests
cliechti5cc3eb12009-08-11 23:04:30 +0000901 if self.debug_output:
902 self.debug_output.debug("requesting initial Telnet/RFC 2217 options")
cliechti130d1f02009-08-04 02:10:58 +0000903 for option in self._telnet_options:
904 if option.state is REQUESTED:
905 self.telnetSendOption(option.send_yes, option.option)
906 # issue 1st modem state notification
cliechti86b593e2009-08-05 16:28:12 +0000907
908 def _client_ok(self):
909 """callback of telnet option. it gets called when option is activated.
910 this one here is used to detect when the client agrees on RFC 2217. a
911 flag is set so that other functions like check_modem_lines know if the
912 client is ok."""
913 # The callback is used for we and they so if one party agrees, we're
914 # already happy. it seems not all servers do the negotiation correctly
915 # and i guess there are incorrect clients too.. so be happy if client
916 # answers one or the other positively.
917 self._client_is_rfc2217 = True
cliechti5cc3eb12009-08-11 23:04:30 +0000918 if self.debug_output:
919 self.debug_output.info("client accepts RFC 2217")
cliechti8fb119c2009-08-05 23:39:45 +0000920 # this is to ensure that the client gets a notification, even if there
921 # was no change
922 self.check_modem_lines(force_notification=True)
cliechti130d1f02009-08-04 02:10:58 +0000923
924 # - outgoing telnet commands and options
925
926 def telnetSendOption(self, action, option):
cliechti044d8662009-08-11 21:40:31 +0000927 """Send DO, DONT, WILL, WONT."""
cliechti130d1f02009-08-04 02:10:58 +0000928 self.connection.write(to_bytes([IAC, action, option]))
929
930 def rfc2217SendSubnegotiation(self, option, value=[]):
cliechti044d8662009-08-11 21:40:31 +0000931 """Subnegotiation of RFC 2217 parameters."""
cliechti130d1f02009-08-04 02:10:58 +0000932 self.connection.write(to_bytes([IAC, SB, COM_PORT_OPTION, option] + list(value) + [IAC, SE]))
933
934 # - check modem lines, needs to be called periodically from user to
935 # establish polling
936
cliechti7cb78e82009-08-05 15:47:57 +0000937 def check_modem_lines(self, force_notification=False):
cliechti130d1f02009-08-04 02:10:58 +0000938 modemstate = (
939 (self.serial.getCTS() and MODEMSTATE_MASK_CTS) |
940 (self.serial.getDSR() and MODEMSTATE_MASK_DSR) |
941 (self.serial.getRI() and MODEMSTATE_MASK_RI) |
942 (self.serial.getCD() and MODEMSTATE_MASK_CD)
943 )
cliechti7cb78e82009-08-05 15:47:57 +0000944 # check what has changed
945 deltas = modemstate ^ (self.last_modemstate or 0) # when last is None -> 0
946 if deltas & MODEMSTATE_MASK_CTS:
947 modemstate |= MODEMSTATE_MASK_CTS_CHANGE
948 if deltas & MODEMSTATE_MASK_DSR:
949 modemstate |= MODEMSTATE_MASK_DSR_CHANGE
950 if deltas & MODEMSTATE_MASK_RI:
951 modemstate |= MODEMSTATE_MASK_RI_CHANGE
952 if deltas & MODEMSTATE_MASK_CD:
953 modemstate |= MODEMSTATE_MASK_CD_CHANGE
954 # if new state is different and the mask allows this change, send
cliechti86b593e2009-08-05 16:28:12 +0000955 # notification. suppress notifications when client is not rfc2217
cliechti7cb78e82009-08-05 15:47:57 +0000956 if modemstate != self.last_modemstate or force_notification:
cliechti8fb119c2009-08-05 23:39:45 +0000957 if (self._client_is_rfc2217 and (modemstate & self.modemstate_mask)) or force_notification:
cliechti7cb78e82009-08-05 15:47:57 +0000958 self.rfc2217SendSubnegotiation(
959 SERVER_NOTIFY_MODEMSTATE,
960 to_bytes([modemstate & self.modemstate_mask])
961 )
962 if self.debug_output:
cliechti5cc3eb12009-08-11 23:04:30 +0000963 self.debug_output.info("NOTIFY_MODEMSTATE: %s" % (modemstate,))
cliechti7cb78e82009-08-05 15:47:57 +0000964 # save last state, but forget about deltas.
965 # otherwise it would also notify about changing deltas which is
966 # probably not very useful
967 self.last_modemstate = modemstate & 0xf0
cliechti130d1f02009-08-04 02:10:58 +0000968
cliechti32c10332009-08-05 13:23:43 +0000969 # - outgoing data escaping
970
971 def escape(self, data):
972 """this function is for the user. all outgoing data has to be properly
973 escaped, so that no IAC character in the data stream messes up the
974 Telnet state machine in the server.
975
976 socket.sendall(escape(data))
977 """
978 for byte in data:
979 if byte == IAC:
980 yield IAC
981 yield IAC
982 else:
983 yield byte
984
cliechti130d1f02009-08-04 02:10:58 +0000985 # - incoming data filter
986
987 def filter(self, data):
988 """handle a bunch of incoming bytes. this is a generator. it will yield
cliechti044d8662009-08-11 21:40:31 +0000989 all characters not of interest for Telnet/RFC 2217.
cliechti130d1f02009-08-04 02:10:58 +0000990
991 The idea is that the reader thread pushes data from the socket through
992 this filter:
993
994 for byte in filter(socket.recv(1024)):
995 # do things like CR/LF conversion/whatever
996 # and write data to the serial port
997 serial.write(byte)
998
999 (socket error handling code left as exercise for the reader)
1000 """
1001 for byte in data:
1002 if self.mode == M_NORMAL:
1003 # interpret as command or as data
1004 if byte == IAC:
1005 self.mode = M_IAC_SEEN
1006 else:
1007 # store data in sub option buffer or pass it to our
1008 # consumer depending on state
1009 if self.suboption is not None:
1010 self.suboption.append(byte)
1011 else:
1012 yield byte
1013 elif self.mode == M_IAC_SEEN:
1014 if byte == IAC:
1015 # interpret as command doubled -> insert character
1016 # itself
cliechtib16e45b2009-08-05 17:59:53 +00001017 yield IAC
cliechti130d1f02009-08-04 02:10:58 +00001018 self.mode = M_NORMAL
1019 elif byte == SB:
1020 # sub option start
1021 self.suboption = bytearray()
1022 self.mode = M_NORMAL
1023 elif byte == SE:
1024 # sub option end -> process it now
1025 self._telnetProcessSubnegotiation(bytes(self.suboption))
1026 self.suboption = None
1027 self.mode = M_NORMAL
1028 elif byte in (DO, DONT, WILL, WONT):
1029 # negotiation
1030 self.telnet_command = byte
1031 self.mode = M_NEGOTIATE
1032 else:
1033 # other telnet commands
1034 self._telnetProcessCommand(byte)
1035 self.mode = M_NORMAL
1036 elif self.mode == M_NEGOTIATE: # DO, DONT, WILL, WONT was received, option now following
1037 self._telnetNegotiateOption(self.telnet_command, byte)
1038 self.mode = M_NORMAL
1039
1040 # - incoming telnet commands and options
1041
1042 def _telnetProcessCommand(self, command):
cliechti044d8662009-08-11 21:40:31 +00001043 """Process commands other than DO, DONT, WILL, WONT."""
cliechti130d1f02009-08-04 02:10:58 +00001044 # Currently none. RFC2217 only uses negotiation and subnegotiation.
1045 #~ print "_telnetProcessCommand %r" % ord(command)
1046
1047 def _telnetNegotiateOption(self, command, option):
cliechti044d8662009-08-11 21:40:31 +00001048 """Process incoming DO, DONT, WILL, WONT."""
cliechti130d1f02009-08-04 02:10:58 +00001049 # check our registered telnet options and forward command to them
1050 # they know themselves if they have to answer or not
1051 known = False
1052 for item in self._telnet_options:
1053 # can have more than one match! as some options are duplicated for
1054 # 'us' and 'them'
1055 if item.option == option:
1056 item.process_incoming(command)
1057 known = True
1058 if not known:
1059 # handle unknown options
1060 # only answer to positive requests and deny them
1061 if command == WILL or command == DO:
1062 self.telnetSendOption((command == WILL and DONT or WONT), option)
1063
1064
1065 def _telnetProcessSubnegotiation(self, suboption):
cliechti044d8662009-08-11 21:40:31 +00001066 """Process subnegotiation, the data between IAC SB and IAC SE."""
cliechti130d1f02009-08-04 02:10:58 +00001067 if suboption[0:1] == COM_PORT_OPTION:
cliechti5cc3eb12009-08-11 23:04:30 +00001068 if self.debug_output:
1069 self.debug_output.debug('recevied COM_PORT_OPTION: %r' % (suboption,))
cliechti130d1f02009-08-04 02:10:58 +00001070 if suboption[1:2] == SET_BAUDRATE:
1071 backup = self.serial.baudrate
1072 try:
1073 (self.serial.baudrate,) = struct.unpack("!I", suboption[2:6])
1074 except ValueError, e:
1075 if self.debug_output:
cliechti5cc3eb12009-08-11 23:04:30 +00001076 self.debug_output.error("failed to set baud rate: %s" % (e,))
cliechti130d1f02009-08-04 02:10:58 +00001077 self.serial.baudrate = backup
cliechti5cc3eb12009-08-11 23:04:30 +00001078 else:
1079 if self.debug_output:
1080 self.debug_output.info("changed baud rate: %s" % (self.serial.baudrate,))
cliechti130d1f02009-08-04 02:10:58 +00001081 self.rfc2217SendSubnegotiation(SERVER_SET_BAUDRATE, struct.pack("!I", self.serial.baudrate))
1082 elif suboption[1:2] == SET_DATASIZE:
1083 backup = self.serial.bytesize
1084 try:
1085 (self.serial.bytesize,) = struct.unpack("!B", suboption[2:3])
1086 except ValueError, e:
1087 if self.debug_output:
cliechti5cc3eb12009-08-11 23:04:30 +00001088 self.debug_output.error("failed to set data size: %s" % (e,))
cliechti130d1f02009-08-04 02:10:58 +00001089 self.serial.bytesize = backup
cliechti5cc3eb12009-08-11 23:04:30 +00001090 else:
1091 if self.debug_output:
1092 self.debug_output.info("changed data size: %s" % (self.serial.bytesize,))
cliechti130d1f02009-08-04 02:10:58 +00001093 self.rfc2217SendSubnegotiation(SERVER_SET_DATASIZE, struct.pack("!B", self.serial.bytesize))
1094 elif suboption[1:2] == SET_PARITY:
1095 backup = self.serial.parity
1096 try:
1097 self.serial.parity = RFC2217_REVERSE_PARITY_MAP[struct.unpack("!B", suboption[2:3])[0]]
1098 except ValueError, e:
1099 if self.debug_output:
cliechti5cc3eb12009-08-11 23:04:30 +00001100 self.debug_output.error("failed to set parity: %s" % (e,))
cliechti130d1f02009-08-04 02:10:58 +00001101 self.serial.parity = backup
cliechti5cc3eb12009-08-11 23:04:30 +00001102 else:
1103 if self.debug_output:
1104 self.debug_output.info("changed parity: %s" % (self.serial.parity,))
cliechti130d1f02009-08-04 02:10:58 +00001105 self.rfc2217SendSubnegotiation(
1106 SERVER_SET_PARITY,
1107 struct.pack("!B", RFC2217_PARITY_MAP[self.serial.parity])
1108 )
1109 elif suboption[1:2] == SET_STOPSIZE:
1110 backup = self.serial.stopbits
1111 try:
1112 self.serial.stopbits = RFC2217_REVERSE_STOPBIT_MAP[struct.unpack("!B", suboption[2:3])[0]]
1113 except ValueError, e:
1114 if self.debug_output:
cliechti5cc3eb12009-08-11 23:04:30 +00001115 self.debug_output.error("failed to set stopsize: %s" % (e,))
cliechti130d1f02009-08-04 02:10:58 +00001116 self.serial.stopbits = backup
cliechti5cc3eb12009-08-11 23:04:30 +00001117 else:
1118 if self.debug_output:
1119 self.debug_output.info("changed stop bits: %s" % (self.serial.stopbits,))
cliechti130d1f02009-08-04 02:10:58 +00001120 self.rfc2217SendSubnegotiation(
1121 SERVER_SET_STOPSIZE,
1122 struct.pack("!B", RFC2217_STOPBIT_MAP[self.serial.stopbits])
1123 )
1124 elif suboption[1:2] == SET_CONTROL:
1125 if suboption[2:3] == SET_CONTROL_REQ_FLOW_SETTING:
1126 if self.serial.xonxoff:
1127 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_SW_FLOW_CONTROL)
1128 elif self.serial.rtscts:
1129 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_HW_FLOW_CONTROL)
1130 else:
1131 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_NO_FLOW_CONTROL)
1132 elif suboption[2:3] == SET_CONTROL_USE_NO_FLOW_CONTROL:
1133 self.serial.xonxoff = False
1134 self.serial.rtscts = False
cliechti5cc3eb12009-08-11 23:04:30 +00001135 if self.debug_output:
1136 self.debug_output.info("changed flow control to None")
cliechti130d1f02009-08-04 02:10:58 +00001137 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_NO_FLOW_CONTROL)
1138 elif suboption[2:3] == SET_CONTROL_USE_SW_FLOW_CONTROL:
1139 self.serial.xonxoff = True
cliechti5cc3eb12009-08-11 23:04:30 +00001140 if self.debug_output:
1141 self.debug_output.info("changed flow control to XON/XOFF")
cliechti130d1f02009-08-04 02:10:58 +00001142 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_SW_FLOW_CONTROL)
1143 elif suboption[2:3] == SET_CONTROL_USE_HW_FLOW_CONTROL:
1144 self.serial.rtscts = True
cliechti5cc3eb12009-08-11 23:04:30 +00001145 if self.debug_output:
1146 self.debug_output.info("changed flow control to RTS/CTS")
cliechti130d1f02009-08-04 02:10:58 +00001147 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_HW_FLOW_CONTROL)
1148 elif suboption[2:3] == SET_CONTROL_REQ_BREAK_STATE:
cliechti5cc3eb12009-08-11 23:04:30 +00001149 if self.debug_output:
1150 self.debug_output.warning("requested break state - not implemented")
cliechti130d1f02009-08-04 02:10:58 +00001151 pass # XXX needs cached value
1152 elif suboption[2:3] == SET_CONTROL_BREAK_ON:
1153 self.serial.setBreak(True)
cliechti5cc3eb12009-08-11 23:04:30 +00001154 if self.debug_output:
1155 self.debug_output.info("changed BREAK to active")
cliechti130d1f02009-08-04 02:10:58 +00001156 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_BREAK_ON)
1157 elif suboption[2:3] == SET_CONTROL_BREAK_OFF:
1158 self.serial.setBreak(False)
cliechti5cc3eb12009-08-11 23:04:30 +00001159 if self.debug_output:
1160 self.debug_output.info("changed BREAK to inactive")
cliechti130d1f02009-08-04 02:10:58 +00001161 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_BREAK_OFF)
1162 elif suboption[2:3] == SET_CONTROL_REQ_DTR:
cliechti5cc3eb12009-08-11 23:04:30 +00001163 if self.debug_output:
1164 self.debug_output.warning("requested DTR state - not implemented")
cliechti130d1f02009-08-04 02:10:58 +00001165 pass # XXX needs cached value
1166 elif suboption[2:3] == SET_CONTROL_DTR_ON:
1167 self.serial.setDTR(True)
cliechti5cc3eb12009-08-11 23:04:30 +00001168 if self.debug_output:
1169 self.debug_output.info("changed DTR to active")
cliechti130d1f02009-08-04 02:10:58 +00001170 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_DTR_ON)
1171 elif suboption[2:3] == SET_CONTROL_DTR_OFF:
1172 self.serial.setDTR(False)
cliechti5cc3eb12009-08-11 23:04:30 +00001173 if self.debug_output:
1174 self.debug_output.info("changed DTR to inactive")
cliechti130d1f02009-08-04 02:10:58 +00001175 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_DTR_OFF)
1176 elif suboption[2:3] == SET_CONTROL_REQ_RTS:
cliechti5cc3eb12009-08-11 23:04:30 +00001177 if self.debug_output:
1178 self.debug_output.warning("requested RTS state - not implemented")
cliechti130d1f02009-08-04 02:10:58 +00001179 pass # XXX needs cached value
1180 #~ self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_RTS_ON)
1181 elif suboption[2:3] == SET_CONTROL_RTS_ON:
1182 self.serial.setRTS(True)
cliechti5cc3eb12009-08-11 23:04:30 +00001183 if self.debug_output:
1184 self.debug_output.info("changed RTS to active")
cliechti130d1f02009-08-04 02:10:58 +00001185 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_RTS_ON)
1186 elif suboption[2:3] == SET_CONTROL_RTS_OFF:
1187 self.serial.setRTS(False)
cliechti5cc3eb12009-08-11 23:04:30 +00001188 if self.debug_output:
1189 self.debug_output.info("changed RTS to inactive")
cliechti130d1f02009-08-04 02:10:58 +00001190 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_RTS_OFF)
1191 #~ elif suboption[2:3] == SET_CONTROL_REQ_FLOW_SETTING_IN:
1192 #~ elif suboption[2:3] == SET_CONTROL_USE_NO_FLOW_CONTROL_IN:
1193 #~ elif suboption[2:3] == SET_CONTROL_USE_SW_FLOW_CONTOL_IN:
1194 #~ elif suboption[2:3] == SET_CONTROL_USE_HW_FLOW_CONTOL_IN:
1195 #~ elif suboption[2:3] == SET_CONTROL_USE_DCD_FLOW_CONTROL:
1196 #~ elif suboption[2:3] == SET_CONTROL_USE_DTR_FLOW_CONTROL:
1197 #~ elif suboption[2:3] == SET_CONTROL_USE_DSR_FLOW_CONTROL:
1198 elif suboption[1:2] == NOTIFY_LINESTATE:
cliechti7cb78e82009-08-05 15:47:57 +00001199 # client polls for current state
1200 self.rfc2217SendSubnegotiation(
1201 SERVER_NOTIFY_LINESTATE,
1202 to_bytes([0]) # sorry, nothing like that imeplemented
1203 )
cliechti130d1f02009-08-04 02:10:58 +00001204 elif suboption[1:2] == NOTIFY_MODEMSTATE:
cliechti5cc3eb12009-08-11 23:04:30 +00001205 if self.debug_output:
1206 self.debug_output.info("request for modem state")
cliechti7cb78e82009-08-05 15:47:57 +00001207 # client polls for current state
1208 self.check_modem_lines(force_notification=True)
cliechti130d1f02009-08-04 02:10:58 +00001209 elif suboption[1:2] == FLOWCONTROL_SUSPEND:
cliechti5cc3eb12009-08-11 23:04:30 +00001210 if self.debug_output:
1211 self.debug_output.info("suspend")
cliechti130d1f02009-08-04 02:10:58 +00001212 self._remote_suspend_flow = True
1213 elif suboption[1:2] == FLOWCONTROL_RESUME:
cliechti5cc3eb12009-08-11 23:04:30 +00001214 if self.debug_output:
1215 self.debug_output.info("resume")
cliechti130d1f02009-08-04 02:10:58 +00001216 self._remote_suspend_flow = False
1217 elif suboption[1:2] == SET_LINESTATE_MASK:
1218 self.linstate_mask = ord(suboption[2:3]) # ensure it is a number
cliechti5cc3eb12009-08-11 23:04:30 +00001219 if self.debug_output:
1220 self.debug_output.info("line state mask: 0x%02" % (self.linstate_mask,))
cliechti130d1f02009-08-04 02:10:58 +00001221 elif suboption[1:2] == SET_MODEMSTATE_MASK:
1222 self.modemstate_mask = ord(suboption[2:3]) # ensure it is a number
cliechti5cc3eb12009-08-11 23:04:30 +00001223 if self.debug_output:
1224 self.debug_output.info("modem state mask: 0x%02" % (self.modemstate_mask,))
cliechti130d1f02009-08-04 02:10:58 +00001225 elif suboption[1:2] == PURGE_DATA:
1226 if suboption[2:3] == PURGE_RECEIVE_BUFFER:
1227 self.serial.flushInput()
cliechti5cc3eb12009-08-11 23:04:30 +00001228 if self.debug_output:
1229 self.debug_output.info("purge in")
cliechti130d1f02009-08-04 02:10:58 +00001230 self.rfc2217SendSubnegotiation(SERVER_PURGE_DATA, PURGE_RECEIVE_BUFFER)
1231 elif suboption[2:3] == PURGE_TRANSMIT_BUFFER:
1232 self.serial.flushOutput()
cliechti5cc3eb12009-08-11 23:04:30 +00001233 if self.debug_output:
1234 self.debug_output.info("purge out")
cliechti130d1f02009-08-04 02:10:58 +00001235 self.rfc2217SendSubnegotiation(SERVER_PURGE_DATA, PURGE_TRANSMIT_BUFFER)
1236 elif suboption[2:3] == PURGE_BOTH_BUFFERS:
1237 self.serial.flushInput()
1238 self.serial.flushOutput()
cliechti5cc3eb12009-08-11 23:04:30 +00001239 if self.debug_output:
1240 self.debug_output.info("purge both")
cliechti130d1f02009-08-04 02:10:58 +00001241 self.rfc2217SendSubnegotiation(SERVER_PURGE_DATA, PURGE_BOTH_BUFFERS)
1242 else:
1243 if self.debug_output:
cliechti5cc3eb12009-08-11 23:04:30 +00001244 self.debug_output.error("undefined PURGE_DATA: %r" % list(suboption[2:]))
cliechti130d1f02009-08-04 02:10:58 +00001245 else:
1246 if self.debug_output:
cliechti5cc3eb12009-08-11 23:04:30 +00001247 self.debug_output.error("undefined COM_PORT_OPTION: %r" % list(suboption[1:]))
cliechti130d1f02009-08-04 02:10:58 +00001248 else:
cliechti5cc3eb12009-08-11 23:04:30 +00001249 if self.debug_output:
1250 self.debug_output.warning("unknown subnegotiation: %r" % (suboption,))
cliechti130d1f02009-08-04 02:10:58 +00001251
1252
1253# simple client test
cliechti8099bed2009-08-01 23:59:18 +00001254if __name__ == '__main__':
1255 import sys
1256 s = Serial('rfc2217://localhost:7000', 115200)
1257 sys.stdout.write('%s\n' % s)
1258
cliechti2b929b72009-08-02 23:49:02 +00001259 #~ s.baudrate = 1898
1260
cliechti8099bed2009-08-01 23:59:18 +00001261 sys.stdout.write("write...\n")
1262 s.write("hello\n")
1263 s.flush()
cliechti8099bed2009-08-01 23:59:18 +00001264 sys.stdout.write("read: %s\n" % s.read(5))
1265
1266 #~ s.baudrate = 19200
1267 #~ s.databits = 7
1268 s.close()