blob: 2012ea71c11667f3d93fb6ea1b634a8553b8c56b [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#
cliechtieada4fd2013-07-31 16:26:07 +000010# (C) 2001-2013 Chris Liechti <cliechti@gmx.net>
cliechti8099bed2009-08-01 23:59:18 +000011# this is distributed under a free software license, see license.txt
12
13# TODO:
14# - setting control line -> answer is not checked (had problems with one of the
15# severs). consider implementing a compatibility mode flag to make check
16# conditional
17# - write timeout not implemented at all
cliechti8099bed2009-08-01 23:59:18 +000018
cliechti81c54762009-08-03 23:53:27 +000019##############################################################################
20# observations and issues with servers
21#=============================================================================
22# sredird V2.2.1
23# - http://www.ibiblio.org/pub/Linux/system/serial/ sredird-2.2.2.tar.gz
24# - does not acknowledge SET_CONTROL (RTS/DTR) correctly, always responding
25# [105 1] instead of the actual value.
26# - SET_BAUDRATE answer contains 4 extra null bytes -> probably for larger
27# numbers than 2**32?
28# - To get the signature [COM_PORT_OPTION 0] has to be sent.
29# - run a server: while true; do nc -l -p 7000 -c "sredird debug /dev/ttyUSB0 /var/lock/sredir"; done
30#=============================================================================
31# telnetcpcd (untested)
32# - http://ftp.wayne.edu/kermit/sredird/telnetcpcd-1.09.tar.gz
33# - To get the signature [COM_PORT_OPTION] w/o data has to be sent.
34#=============================================================================
35# ser2net
36# - does not negotiate BINARY or COM_PORT_OPTION for his side but at least
37# acknowledges that the client activates these options
38# - The configuration may be that the server prints a banner. As this client
39# implementation does a flushInput on connect, this banner is hidden from
40# the user application.
41# - NOTIFY_MODEMSTATE: the poll interval of the server seems to be one
42# second.
43# - To get the signature [COM_PORT_OPTION 0] has to be sent.
44# - run a server: run ser2net daemon, in /etc/ser2net.conf:
45# 2000:telnet:0:/dev/ttyS0:9600 remctl banner
46##############################################################################
47
48# How to identify ports? pySerial might want to support other protocols in the
49# future, so lets use an URL scheme.
50# for RFC2217 compliant servers we will use this:
51# rfc2217://<host>:<port>[/option[/option...]]
52#
53# options:
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
cliechti39cfb7b2011-08-22 00:30:07 +000062from serial.serialutil import *
cliechti8099bed2009-08-01 23:59:18 +000063import 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
cliechti86844e82009-08-12 00:05:33 +000075# map log level names to constants. used in fromURL()
cliechti5cc3eb12009-08-11 23:04:30 +000076LOGGER_LEVELS = {
77 'debug': logging.DEBUG,
78 'info': logging.INFO,
79 'warning': logging.WARNING,
80 'error': logging.ERROR,
cliechti5cc3eb12009-08-11 23:04:30 +000081 }
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):
cliechtieada4fd2013-07-31 16:26:07 +0000229 """\
230 Initialize option.
cliechti1ef7e3e2009-08-03 02:38:43 +0000231 :param connection: connection used to transmit answers
232 :param name: a readable name for debug outputs
233 :param send_yes: what to send when option is to be enabled.
234 :param send_no: what to send when option is to be disabled.
235 :param ack_yes: what to expect when remote agrees on option.
236 :param ack_no: what to expect when remote disagrees on option.
237 :param initial_state: options initialized with REQUESTED are tried to
238 be enabled on startup. use INACTIVE for all others.
239 """
cliechti2b929b72009-08-02 23:49:02 +0000240 self.connection = connection
cliechtiac205322009-08-02 20:40:21 +0000241 self.name = name
242 self.option = option
243 self.send_yes = send_yes
244 self.send_no = send_no
245 self.ack_yes = ack_yes
246 self.ack_no = ack_no
247 self.state = initial_state
248 self.active = False
cliechti86b593e2009-08-05 16:28:12 +0000249 self.activation_callback = activation_callback
cliechtiac205322009-08-02 20:40:21 +0000250
251 def __repr__(self):
cliechti1ef7e3e2009-08-03 02:38:43 +0000252 """String for debug outputs"""
cliechtiac205322009-08-02 20:40:21 +0000253 return "%s:%s(%s)" % (self.name, self.active, self.state)
254
cliechti2b929b72009-08-02 23:49:02 +0000255 def process_incoming(self, command):
cliechti1ef7e3e2009-08-03 02:38:43 +0000256 """A DO/DONT/WILL/WONT was received for this option, update state and
257 answer when needed."""
cliechtiac205322009-08-02 20:40:21 +0000258 if command == self.ack_yes:
259 if self.state is REQUESTED:
260 self.state = ACTIVE
261 self.active = True
cliechti86b593e2009-08-05 16:28:12 +0000262 if self.activation_callback is not None:
263 self.activation_callback()
cliechtiac205322009-08-02 20:40:21 +0000264 elif self.state is ACTIVE:
265 pass
266 elif self.state is INACTIVE:
267 self.state = ACTIVE
cliechti1ef7e3e2009-08-03 02:38:43 +0000268 self.connection.telnetSendOption(self.send_yes, self.option)
cliechtiac205322009-08-02 20:40:21 +0000269 self.active = True
cliechti86b593e2009-08-05 16:28:12 +0000270 if self.activation_callback is not None:
271 self.activation_callback()
cliechtiac205322009-08-02 20:40:21 +0000272 elif self.state is REALLY_INACTIVE:
cliechti1ef7e3e2009-08-03 02:38:43 +0000273 self.connection.telnetSendOption(self.send_no, self.option)
cliechtiac205322009-08-02 20:40:21 +0000274 else:
275 raise ValueError('option in illegal state %r' % self)
276 elif command == self.ack_no:
277 if self.state is REQUESTED:
278 self.state = INACTIVE
279 self.active = False
280 elif self.state is ACTIVE:
281 self.state = INACTIVE
cliechti1ef7e3e2009-08-03 02:38:43 +0000282 self.connection.telnetSendOption(self.send_no, self.option)
cliechtiac205322009-08-02 20:40:21 +0000283 self.active = False
284 elif self.state is INACTIVE:
285 pass
286 elif self.state is REALLY_INACTIVE:
287 pass
288 else:
289 raise ValueError('option in illegal state %r' % self)
290
291
cliechti2b929b72009-08-02 23:49:02 +0000292class TelnetSubnegotiation(object):
cliechtieada4fd2013-07-31 16:26:07 +0000293 """\
294 A object to handle subnegotiation of options. In this case actually
295 sub-sub options for RFC 2217. It is used to track com port options.
296 """
cliechti2b929b72009-08-02 23:49:02 +0000297
298 def __init__(self, connection, name, option, ack_option=None):
299 if ack_option is None: ack_option = option
300 self.connection = connection
301 self.name = name
302 self.option = option
303 self.value = None
304 self.ack_option = ack_option
305 self.state = INACTIVE
306
307 def __repr__(self):
cliechti044d8662009-08-11 21:40:31 +0000308 """String for debug outputs."""
cliechti2b929b72009-08-02 23:49:02 +0000309 return "%s:%s" % (self.name, self.state)
310
311 def set(self, value):
cliechtieada4fd2013-07-31 16:26:07 +0000312 """\
313 request a change of the value. a request is sent to the server. if
cliechti2b929b72009-08-02 23:49:02 +0000314 the client needs to know if the change is performed he has to check the
cliechtieada4fd2013-07-31 16:26:07 +0000315 state of this object.
316 """
cliechti2b929b72009-08-02 23:49:02 +0000317 self.value = value
318 self.state = REQUESTED
cliechti1ef7e3e2009-08-03 02:38:43 +0000319 self.connection.rfc2217SendSubnegotiation(self.option, self.value)
cliechti6a300772009-08-12 02:28:56 +0000320 if self.connection.logger:
321 self.connection.logger.debug("SB Requesting %s -> %r" % (self.name, self.value))
cliechti2b929b72009-08-02 23:49:02 +0000322
323 def isReady(self):
cliechtieada4fd2013-07-31 16:26:07 +0000324 """\
325 check if answer from server has been received. when server rejects
326 the change, raise a ValueError.
327 """
cliechti2b929b72009-08-02 23:49:02 +0000328 if self.state == REALLY_INACTIVE:
329 raise ValueError("remote rejected value for option %r" % (self.name))
330 return self.state == ACTIVE
cliechti1ef7e3e2009-08-03 02:38:43 +0000331 # add property to have a similar interface as TelnetOption
cliechti2b929b72009-08-02 23:49:02 +0000332 active = property(isReady)
333
cliechti044d8662009-08-11 21:40:31 +0000334 def wait(self, timeout=3):
cliechtieada4fd2013-07-31 16:26:07 +0000335 """\
336 wait until the subnegotiation has been acknowledged or timeout. It
cliechti1ef7e3e2009-08-03 02:38:43 +0000337 can also throw a value error when the answer from the server does not
cliechtieada4fd2013-07-31 16:26:07 +0000338 match the value sent.
339 """
cliechti044d8662009-08-11 21:40:31 +0000340 timeout_time = time.time() + timeout
cliechti2b929b72009-08-02 23:49:02 +0000341 while time.time() < timeout_time:
342 time.sleep(0.05) # prevent 100% CPU load
343 if self.isReady():
344 break
345 else:
346 raise SerialException("timeout while waiting for option %r" % (self.name))
347
348 def checkAnswer(self, suboption):
cliechtieada4fd2013-07-31 16:26:07 +0000349 """\
350 check an incoming subnegotiation block. the parameter already has
351 cut off the header like sub option number and com port option value.
352 """
cliechti2b929b72009-08-02 23:49:02 +0000353 if self.value == suboption[:len(self.value)]:
354 self.state = ACTIVE
355 else:
356 # error propagation done in isReady
357 self.state = REALLY_INACTIVE
cliechti6a300772009-08-12 02:28:56 +0000358 if self.connection.logger:
359 self.connection.logger.debug("SB Answer %s -> %r -> %s" % (self.name, suboption, self.state))
cliechti2b929b72009-08-02 23:49:02 +0000360
361
cliechti8099bed2009-08-01 23:59:18 +0000362class RFC2217Serial(SerialBase):
cliechti044d8662009-08-11 21:40:31 +0000363 """Serial port implementation for RFC 2217 remote serial ports."""
cliechti8099bed2009-08-01 23:59:18 +0000364
365 BAUDRATES = (50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800,
366 9600, 19200, 38400, 57600, 115200)
367
368 def open(self):
cliechtieada4fd2013-07-31 16:26:07 +0000369 """\
370 Open port with current settings. This may throw a SerialException
371 if the port cannot be opened.
372 """
cliechti6a300772009-08-12 02:28:56 +0000373 self.logger = None
cliechti81c54762009-08-03 23:53:27 +0000374 self._ignore_set_control_answer = False
cliechti7cb78e82009-08-05 15:47:57 +0000375 self._poll_modem_state = False
cliechtidfe2d272009-08-10 22:19:41 +0000376 self._network_timeout = 3
cliechti8099bed2009-08-01 23:59:18 +0000377 if self._port is None:
378 raise SerialException("Port must be configured before it can be used.")
cliechti8f69e702011-03-19 00:22:32 +0000379 if self._isOpen:
380 raise SerialException("Port is already open.")
cliechti8099bed2009-08-01 23:59:18 +0000381 try:
382 self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
383 self._socket.connect(self.fromURL(self.portstr))
cliechti6a300772009-08-12 02:28:56 +0000384 self._socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
cliechti8099bed2009-08-01 23:59:18 +0000385 except Exception, msg:
386 self._socket = None
387 raise SerialException("Could not open port %s: %s" % (self.portstr, msg))
388
cliechti1ef7e3e2009-08-03 02:38:43 +0000389 self._socket.settimeout(5) # XXX good value?
cliechti8099bed2009-08-01 23:59:18 +0000390
cliechti1ef7e3e2009-08-03 02:38:43 +0000391 # use a thread save queue as buffer. it also simplifies implementing
392 # the read timeout
cliechti8099bed2009-08-01 23:59:18 +0000393 self._read_buffer = Queue.Queue()
cliechti81c54762009-08-03 23:53:27 +0000394 # to ensure that user writes does not interfere with internal
395 # telnet/rfc2217 options establish a lock
396 self._write_lock = threading.Lock()
cliechtiac205322009-08-02 20:40:21 +0000397 # name the following separately so that, below, a check can be easily done
398 mandadory_options = [
cliechti2b929b72009-08-02 23:49:02 +0000399 TelnetOption(self, 'we-BINARY', BINARY, WILL, WONT, DO, DONT, INACTIVE),
cliechti2b929b72009-08-02 23:49:02 +0000400 TelnetOption(self, 'we-RFC2217', COM_PORT_OPTION, WILL, WONT, DO, DONT, REQUESTED),
cliechtiac205322009-08-02 20:40:21 +0000401 ]
402 # all supported telnet options
403 self._telnet_options = [
cliechtia29275e2009-08-03 00:08:04 +0000404 TelnetOption(self, 'ECHO', ECHO, DO, DONT, WILL, WONT, REQUESTED),
cliechti2b929b72009-08-02 23:49:02 +0000405 TelnetOption(self, 'we-SGA', SGA, WILL, WONT, DO, DONT, REQUESTED),
406 TelnetOption(self, 'they-SGA', SGA, DO, DONT, WILL, WONT, REQUESTED),
cliechti81c54762009-08-03 23:53:27 +0000407 TelnetOption(self, 'they-BINARY', BINARY, DO, DONT, WILL, WONT, INACTIVE),
408 TelnetOption(self, 'they-RFC2217', COM_PORT_OPTION, DO, DONT, WILL, WONT, REQUESTED),
cliechtiac205322009-08-02 20:40:21 +0000409 ] + mandadory_options
cliechti044d8662009-08-11 21:40:31 +0000410 # RFC 2217 specific states
cliechti2b929b72009-08-02 23:49:02 +0000411 # COM port settings
412 self._rfc2217_port_settings = {
413 'baudrate': TelnetSubnegotiation(self, 'baudrate', SET_BAUDRATE, SERVER_SET_BAUDRATE),
414 'datasize': TelnetSubnegotiation(self, 'datasize', SET_DATASIZE, SERVER_SET_DATASIZE),
415 'parity': TelnetSubnegotiation(self, 'parity', SET_PARITY, SERVER_SET_PARITY),
416 'stopsize': TelnetSubnegotiation(self, 'stopsize', SET_STOPSIZE, SERVER_SET_STOPSIZE),
417 }
cliechticb20a4f2011-04-25 02:25:54 +0000418 # There are more subnegotiation objects, combine all in one dictionary
cliechti2b929b72009-08-02 23:49:02 +0000419 # for easy access
420 self._rfc2217_options = {
421 'purge': TelnetSubnegotiation(self, 'purge', PURGE_DATA, SERVER_PURGE_DATA),
cliechti81c54762009-08-03 23:53:27 +0000422 'control': TelnetSubnegotiation(self, 'control', SET_CONTROL, SERVER_SET_CONTROL),
cliechti2b929b72009-08-02 23:49:02 +0000423 }
424 self._rfc2217_options.update(self._rfc2217_port_settings)
425 # cache for line and modem states that the server sends to us
cliechti8099bed2009-08-01 23:59:18 +0000426 self._linestate = 0
cliechti7cb78e82009-08-05 15:47:57 +0000427 self._modemstate = None
428 self._modemstate_expires = 0
cliechti044d8662009-08-11 21:40:31 +0000429 # RFC 2217 flow control between server and client
cliechti672d0292009-08-03 02:01:57 +0000430 self._remote_suspend_flow = False
cliechti8099bed2009-08-01 23:59:18 +0000431
cliechti1ef7e3e2009-08-03 02:38:43 +0000432 self._thread = threading.Thread(target=self._telnetReadLoop)
cliechti8099bed2009-08-01 23:59:18 +0000433 self._thread.setDaemon(True)
cliechti5cc3eb12009-08-11 23:04:30 +0000434 self._thread.setName('pySerial RFC 2217 reader thread for %s' % (self._port,))
cliechti8099bed2009-08-01 23:59:18 +0000435 self._thread.start()
436
cliechti044d8662009-08-11 21:40:31 +0000437 # negotiate Telnet/RFC 2217 -> send initial requests
cliechtiac205322009-08-02 20:40:21 +0000438 for option in self._telnet_options:
439 if option.state is REQUESTED:
cliechti1ef7e3e2009-08-03 02:38:43 +0000440 self.telnetSendOption(option.send_yes, option.option)
cliechtiac205322009-08-02 20:40:21 +0000441 # now wait until important options are negotiated
cliechtidfe2d272009-08-10 22:19:41 +0000442 timeout_time = time.time() + self._network_timeout
cliechtiac205322009-08-02 20:40:21 +0000443 while time.time() < timeout_time:
cliechtiac205322009-08-02 20:40:21 +0000444 time.sleep(0.05) # prevent 100% CPU load
cliechti2b929b72009-08-02 23:49:02 +0000445 if sum(o.active for o in mandadory_options) == len(mandadory_options):
446 break
447 else:
448 raise SerialException("Remote does not seem to support RFC2217 or BINARY mode %r" % mandadory_options)
cliechti6a300772009-08-12 02:28:56 +0000449 if self.logger:
450 self.logger.info("Negotiated options: %s" % self._telnet_options)
cliechti8099bed2009-08-01 23:59:18 +0000451
cliechti044d8662009-08-11 21:40:31 +0000452 # fine, go on, set RFC 2271 specific things
cliechti8099bed2009-08-01 23:59:18 +0000453 self._reconfigurePort()
cliechti2b929b72009-08-02 23:49:02 +0000454 # all things set up get, now a clean start
cliechti8099bed2009-08-01 23:59:18 +0000455 self._isOpen = True
456 if not self._rtscts:
457 self.setRTS(True)
458 self.setDTR(True)
459 self.flushInput()
460 self.flushOutput()
461
462 def _reconfigurePort(self):
463 """Set communication parameters on opened port."""
464 if self._socket is None:
465 raise SerialException("Can only operate on open ports")
466
cliechti8099bed2009-08-01 23:59:18 +0000467 # if self._timeout != 0 and self._interCharTimeout is not None:
cliechti8099bed2009-08-01 23:59:18 +0000468 # XXX
469
470 if self._writeTimeout is not None:
471 raise NotImplementedError('writeTimeout is currently not supported')
cliechti2b929b72009-08-02 23:49:02 +0000472 # XXX
cliechti8099bed2009-08-01 23:59:18 +0000473
cliechti2b929b72009-08-02 23:49:02 +0000474 # Setup the connection
cliechti1ef7e3e2009-08-03 02:38:43 +0000475 # to get good performance, all parameter changes are sent first...
cliechti81c54762009-08-03 23:53:27 +0000476 if not isinstance(self._baudrate, (int, long)) or not 0 < self._baudrate < 2**32:
477 raise ValueError("invalid baudrate: %r" % (self._baudrate))
cliechti2b929b72009-08-02 23:49:02 +0000478 self._rfc2217_port_settings['baudrate'].set(struct.pack('!I', self._baudrate))
479 self._rfc2217_port_settings['datasize'].set(struct.pack('!B', self._bytesize))
480 self._rfc2217_port_settings['parity'].set(struct.pack('!B', RFC2217_PARITY_MAP[self._parity]))
481 self._rfc2217_port_settings['stopsize'].set(struct.pack('!B', RFC2217_STOPBIT_MAP[self._stopbits]))
cliechti8099bed2009-08-01 23:59:18 +0000482
cliechti2b929b72009-08-02 23:49:02 +0000483 # and now wait until parameters are active
484 items = self._rfc2217_port_settings.values()
cliechti6a300772009-08-12 02:28:56 +0000485 if self.logger:
486 self.logger.debug("Negotiating settings: %s" % (items,))
cliechtidfe2d272009-08-10 22:19:41 +0000487 timeout_time = time.time() + self._network_timeout
cliechti2b929b72009-08-02 23:49:02 +0000488 while time.time() < timeout_time:
489 time.sleep(0.05) # prevent 100% CPU load
490 if sum(o.active for o in items) == len(items):
491 break
492 else:
493 raise SerialException("Remote does not accept parameter change (RFC2217): %r" % items)
cliechti6a300772009-08-12 02:28:56 +0000494 if self.logger:
495 self.logger.info("Negotiated settings: %s" % (items,))
cliechti8099bed2009-08-01 23:59:18 +0000496
497 if self._rtscts and self._xonxoff:
cliechti1ef7e3e2009-08-03 02:38:43 +0000498 raise ValueError('xonxoff and rtscts together are not supported')
cliechti8099bed2009-08-01 23:59:18 +0000499 elif self._rtscts:
cliechti1ef7e3e2009-08-03 02:38:43 +0000500 self.rfc2217SetControl(SET_CONTROL_USE_HW_FLOW_CONTROL)
cliechti8099bed2009-08-01 23:59:18 +0000501 elif self._xonxoff:
cliechti1ef7e3e2009-08-03 02:38:43 +0000502 self.rfc2217SetControl(SET_CONTROL_USE_SW_FLOW_CONTROL)
cliechti8099bed2009-08-01 23:59:18 +0000503 else:
cliechti1ef7e3e2009-08-03 02:38:43 +0000504 self.rfc2217SetControl(SET_CONTROL_USE_NO_FLOW_CONTROL)
cliechti8099bed2009-08-01 23:59:18 +0000505
506 def close(self):
507 """Close port"""
508 if self._isOpen:
509 if self._socket:
510 try:
511 self._socket.shutdown(socket.SHUT_RDWR)
512 self._socket.close()
513 except:
514 # ignore errors.
515 pass
516 self._socket = None
517 if self._thread:
518 self._thread.join()
519 self._isOpen = False
520 # in case of quick reconnects, give the server some time
521 time.sleep(0.3)
522
523 def makeDeviceName(self, port):
524 raise SerialException("there is no sensible way to turn numbers into URLs")
525
526 def fromURL(self, url):
cliechti1ef7e3e2009-08-03 02:38:43 +0000527 """extract host and port from an URL string"""
cliechti8099bed2009-08-01 23:59:18 +0000528 if url.lower().startswith("rfc2217://"): url = url[10:]
529 try:
cliechti81c54762009-08-03 23:53:27 +0000530 # is there a "path" (our options)?
531 if '/' in url:
532 # cut away options
533 url, options = url.split('/', 1)
534 # process options now, directly altering self
535 for option in options.split('/'):
cliechtidfe2d272009-08-10 22:19:41 +0000536 if '=' in option:
537 option, value = option.split('=', 1)
538 else:
539 value = None
cliechti5cc3eb12009-08-11 23:04:30 +0000540 if option == 'logging':
541 logging.basicConfig() # XXX is that good to call it here?
cliechti6a300772009-08-12 02:28:56 +0000542 self.logger = logging.getLogger('pySerial.rfc2217')
543 self.logger.setLevel(LOGGER_LEVELS[value])
544 self.logger.debug('enabled logging')
cliechti81c54762009-08-03 23:53:27 +0000545 elif option == 'ign_set_control':
546 self._ignore_set_control_answer = True
cliechti7cb78e82009-08-05 15:47:57 +0000547 elif option == 'poll_modem':
548 self._poll_modem_state = True
cliechtidfe2d272009-08-10 22:19:41 +0000549 elif option == 'timeout':
550 self._network_timeout = float(value)
cliechti81c54762009-08-03 23:53:27 +0000551 else:
552 raise ValueError('unknown option: %r' % (option,))
553 # get host and port
cliechti8099bed2009-08-01 23:59:18 +0000554 host, port = url.split(':', 1) # may raise ValueError because of unpacking
555 port = int(port) # and this if it's not a number
556 if not 0 <= port < 65536: raise ValueError("port not in range 0...65535")
557 except ValueError, e:
cliechti81c54762009-08-03 23:53:27 +0000558 raise SerialException('expected a string in the form "[rfc2217://]<host>:<port>[/option[/option...]]": %s' % e)
cliechti8099bed2009-08-01 23:59:18 +0000559 return (host, port)
560
561 # - - - - - - - - - - - - - - - - - - - - - - - -
562
563 def inWaiting(self):
564 """Return the number of characters currently in the input buffer."""
565 if not self._isOpen: raise portNotOpenError
566 return self._read_buffer.qsize()
567
568 def read(self, size=1):
cliechtieada4fd2013-07-31 16:26:07 +0000569 """\
570 Read size bytes from the serial port. If a timeout is set it may
cliechti8099bed2009-08-01 23:59:18 +0000571 return less characters as requested. With no timeout it will block
cliechtieada4fd2013-07-31 16:26:07 +0000572 until the requested number of bytes is read.
573 """
cliechti8099bed2009-08-01 23:59:18 +0000574 if not self._isOpen: raise portNotOpenError
575 data = bytearray()
576 try:
577 while len(data) < size:
cliechti81c54762009-08-03 23:53:27 +0000578 if self._thread is None:
579 raise SerialException('connection failed (reader thread died)')
cliechti8099bed2009-08-01 23:59:18 +0000580 data.append(self._read_buffer.get(True, self._timeout))
581 except Queue.Empty: # -> timeout
582 pass
583 return bytes(data)
584
585 def write(self, data):
cliechtieada4fd2013-07-31 16:26:07 +0000586 """\
587 Output the given string over the serial port. Can block if the
cliechti8099bed2009-08-01 23:59:18 +0000588 connection is blocked. May raise SerialException if the connection is
cliechtieada4fd2013-07-31 16:26:07 +0000589 closed.
590 """
cliechti8099bed2009-08-01 23:59:18 +0000591 if not self._isOpen: raise portNotOpenError
cliechti81c54762009-08-03 23:53:27 +0000592 self._write_lock.acquire()
cliechti8099bed2009-08-01 23:59:18 +0000593 try:
cliechti81c54762009-08-03 23:53:27 +0000594 try:
cliechti38077122013-10-16 02:57:27 +0000595 self._socket.sendall(to_bytes(data).replace(IAC, IAC_DOUBLED))
cliechti81c54762009-08-03 23:53:27 +0000596 except socket.error, e:
cliechticb20a4f2011-04-25 02:25:54 +0000597 raise SerialException("connection failed (socket error): %s" % e) # XXX what exception if socket connection fails
cliechti81c54762009-08-03 23:53:27 +0000598 finally:
599 self._write_lock.release()
cliechti8099bed2009-08-01 23:59:18 +0000600 return len(data)
601
602 def flushInput(self):
603 """Clear input buffer, discarding all that is in the buffer."""
604 if not self._isOpen: raise portNotOpenError
cliechti1ef7e3e2009-08-03 02:38:43 +0000605 self.rfc2217SendPurge(PURGE_RECEIVE_BUFFER)
cliechti8099bed2009-08-01 23:59:18 +0000606 # empty read buffer
607 while self._read_buffer.qsize():
608 self._read_buffer.get(False)
609
610 def flushOutput(self):
cliechtieada4fd2013-07-31 16:26:07 +0000611 """\
612 Clear output buffer, aborting the current output and
613 discarding all that is in the buffer.
614 """
cliechti8099bed2009-08-01 23:59:18 +0000615 if not self._isOpen: raise portNotOpenError
cliechti1ef7e3e2009-08-03 02:38:43 +0000616 self.rfc2217SendPurge(PURGE_TRANSMIT_BUFFER)
cliechti8099bed2009-08-01 23:59:18 +0000617
618 def sendBreak(self, duration=0.25):
619 """Send break condition. Timed, returns to idle state after given
620 duration."""
621 if not self._isOpen: raise portNotOpenError
622 self.setBreak(True)
623 time.sleep(duration)
624 self.setBreak(False)
625
626 def setBreak(self, level=True):
cliechtieada4fd2013-07-31 16:26:07 +0000627 """\
628 Set break: Controls TXD. When active, to transmitting is
629 possible.
630 """
cliechti8099bed2009-08-01 23:59:18 +0000631 if not self._isOpen: raise portNotOpenError
cliechti6a300772009-08-12 02:28:56 +0000632 if self.logger:
633 self.logger.info('set BREAK to %s' % ('inactive', 'active')[bool(level)])
cliechti8099bed2009-08-01 23:59:18 +0000634 if level:
cliechti1ef7e3e2009-08-03 02:38:43 +0000635 self.rfc2217SetControl(SET_CONTROL_BREAK_ON)
cliechti8099bed2009-08-01 23:59:18 +0000636 else:
cliechti1ef7e3e2009-08-03 02:38:43 +0000637 self.rfc2217SetControl(SET_CONTROL_BREAK_OFF)
cliechti8099bed2009-08-01 23:59:18 +0000638
639 def setRTS(self, level=True):
cliechti044d8662009-08-11 21:40:31 +0000640 """Set terminal status line: Request To Send."""
cliechti8099bed2009-08-01 23:59:18 +0000641 if not self._isOpen: raise portNotOpenError
cliechti6a300772009-08-12 02:28:56 +0000642 if self.logger:
643 self.logger.info('set RTS to %s' % ('inactive', 'active')[bool(level)])
cliechti8099bed2009-08-01 23:59:18 +0000644 if level:
cliechti1ef7e3e2009-08-03 02:38:43 +0000645 self.rfc2217SetControl(SET_CONTROL_RTS_ON)
cliechti8099bed2009-08-01 23:59:18 +0000646 else:
cliechti1ef7e3e2009-08-03 02:38:43 +0000647 self.rfc2217SetControl(SET_CONTROL_RTS_OFF)
cliechti8099bed2009-08-01 23:59:18 +0000648
649 def setDTR(self, level=True):
cliechti044d8662009-08-11 21:40:31 +0000650 """Set terminal status line: Data Terminal Ready."""
cliechti8099bed2009-08-01 23:59:18 +0000651 if not self._isOpen: raise portNotOpenError
cliechti6a300772009-08-12 02:28:56 +0000652 if self.logger:
653 self.logger.info('set DTR to %s' % ('inactive', 'active')[bool(level)])
cliechti8099bed2009-08-01 23:59:18 +0000654 if level:
cliechti1ef7e3e2009-08-03 02:38:43 +0000655 self.rfc2217SetControl(SET_CONTROL_DTR_ON)
cliechti8099bed2009-08-01 23:59:18 +0000656 else:
cliechti1ef7e3e2009-08-03 02:38:43 +0000657 self.rfc2217SetControl(SET_CONTROL_DTR_OFF)
cliechti8099bed2009-08-01 23:59:18 +0000658
659 def getCTS(self):
cliechti044d8662009-08-11 21:40:31 +0000660 """Read terminal status line: Clear To Send."""
cliechti8099bed2009-08-01 23:59:18 +0000661 if not self._isOpen: raise portNotOpenError
cliechti7cb78e82009-08-05 15:47:57 +0000662 return bool(self.getModemState() & MODEMSTATE_MASK_CTS)
cliechti8099bed2009-08-01 23:59:18 +0000663
664 def getDSR(self):
cliechti044d8662009-08-11 21:40:31 +0000665 """Read terminal status line: Data Set Ready."""
cliechti8099bed2009-08-01 23:59:18 +0000666 if not self._isOpen: raise portNotOpenError
cliechti7cb78e82009-08-05 15:47:57 +0000667 return bool(self.getModemState() & MODEMSTATE_MASK_DSR)
cliechti8099bed2009-08-01 23:59:18 +0000668
669 def getRI(self):
cliechti044d8662009-08-11 21:40:31 +0000670 """Read terminal status line: Ring Indicator."""
cliechti8099bed2009-08-01 23:59:18 +0000671 if not self._isOpen: raise portNotOpenError
cliechti7cb78e82009-08-05 15:47:57 +0000672 return bool(self.getModemState() & MODEMSTATE_MASK_RI)
cliechti8099bed2009-08-01 23:59:18 +0000673
674 def getCD(self):
cliechti044d8662009-08-11 21:40:31 +0000675 """Read terminal status line: Carrier Detect."""
cliechti8099bed2009-08-01 23:59:18 +0000676 if not self._isOpen: raise portNotOpenError
cliechti7cb78e82009-08-05 15:47:57 +0000677 return bool(self.getModemState() & MODEMSTATE_MASK_CD)
cliechti8099bed2009-08-01 23:59:18 +0000678
679 # - - - platform specific - - -
680 # None so far
681
682 # - - - RFC2217 specific - - -
683
cliechti1ef7e3e2009-08-03 02:38:43 +0000684 def _telnetReadLoop(self):
cliechti044d8662009-08-11 21:40:31 +0000685 """read loop for the socket."""
cliechti8099bed2009-08-01 23:59:18 +0000686 mode = M_NORMAL
687 suboption = None
cliechti81c54762009-08-03 23:53:27 +0000688 try:
689 while self._socket is not None:
690 try:
691 data = self._socket.recv(1024)
692 except socket.timeout:
693 # just need to get out of recv form time to time to check if
694 # still alive
695 continue
cliechticb20a4f2011-04-25 02:25:54 +0000696 except socket.error, e:
cliechti81c54762009-08-03 23:53:27 +0000697 # connection fails -> terminate loop
cliechticb20a4f2011-04-25 02:25:54 +0000698 if self.logger:
699 self.logger.debug("socket error in reader thread: %s" % (e,))
cliechti81c54762009-08-03 23:53:27 +0000700 break
cliechticb20a4f2011-04-25 02:25:54 +0000701 if not data: break # lost connection
cliechti81c54762009-08-03 23:53:27 +0000702 for byte in data:
703 if mode == M_NORMAL:
704 # interpret as command or as data
705 if byte == IAC:
706 mode = M_IAC_SEEN
cliechti8099bed2009-08-01 23:59:18 +0000707 else:
cliechti81c54762009-08-03 23:53:27 +0000708 # store data in read buffer or sub option buffer
709 # depending on state
710 if suboption is not None:
711 suboption.append(byte)
712 else:
713 self._read_buffer.put(byte)
714 elif mode == M_IAC_SEEN:
715 if byte == IAC:
716 # interpret as command doubled -> insert character
717 # itself
cliechtif325c032009-12-25 16:09:49 +0000718 if suboption is not None:
719 suboption.append(IAC)
720 else:
721 self._read_buffer.put(IAC)
cliechti81c54762009-08-03 23:53:27 +0000722 mode = M_NORMAL
723 elif byte == SB:
724 # sub option start
725 suboption = bytearray()
726 mode = M_NORMAL
727 elif byte == SE:
728 # sub option end -> process it now
729 self._telnetProcessSubnegotiation(bytes(suboption))
730 suboption = None
731 mode = M_NORMAL
732 elif byte in (DO, DONT, WILL, WONT):
733 # negotiation
734 telnet_command = byte
735 mode = M_NEGOTIATE
736 else:
737 # other telnet commands
738 self._telnetProcessCommand(byte)
739 mode = M_NORMAL
740 elif mode == M_NEGOTIATE: # DO, DONT, WILL, WONT was received, option now following
741 self._telnetNegotiateOption(telnet_command, byte)
cliechti8099bed2009-08-01 23:59:18 +0000742 mode = M_NORMAL
cliechti81c54762009-08-03 23:53:27 +0000743 finally:
744 self._thread = None
cliechti6a300772009-08-12 02:28:56 +0000745 if self.logger:
746 self.logger.debug("read thread terminated")
cliechti8099bed2009-08-01 23:59:18 +0000747
748 # - incoming telnet commands and options
749
cliechti1ef7e3e2009-08-03 02:38:43 +0000750 def _telnetProcessCommand(self, command):
cliechti044d8662009-08-11 21:40:31 +0000751 """Process commands other than DO, DONT, WILL, WONT."""
cliechti1ef7e3e2009-08-03 02:38:43 +0000752 # Currently none. RFC2217 only uses negotiation and subnegotiation.
cliechti6a300772009-08-12 02:28:56 +0000753 if self.logger:
754 self.logger.warning("ignoring Telnet command: %r" % (command,))
cliechti8099bed2009-08-01 23:59:18 +0000755
cliechti1ef7e3e2009-08-03 02:38:43 +0000756 def _telnetNegotiateOption(self, command, option):
cliechti044d8662009-08-11 21:40:31 +0000757 """Process incoming DO, DONT, WILL, WONT."""
cliechti2b929b72009-08-02 23:49:02 +0000758 # check our registered telnet options and forward command to them
759 # they know themselves if they have to answer or not
cliechtiac205322009-08-02 20:40:21 +0000760 known = False
761 for item in self._telnet_options:
cliechti2b929b72009-08-02 23:49:02 +0000762 # can have more than one match! as some options are duplicated for
763 # 'us' and 'them'
cliechtiac205322009-08-02 20:40:21 +0000764 if item.option == option:
cliechti2b929b72009-08-02 23:49:02 +0000765 item.process_incoming(command)
cliechtiac205322009-08-02 20:40:21 +0000766 known = True
767 if not known:
768 # handle unknown options
769 # only answer to positive requests and deny them
770 if command == WILL or command == DO:
cliechti1ef7e3e2009-08-03 02:38:43 +0000771 self.telnetSendOption((command == WILL and DONT or WONT), option)
cliechti6a300772009-08-12 02:28:56 +0000772 if self.logger:
773 self.logger.warning("rejected Telnet option: %r" % (option,))
cliechtiac205322009-08-02 20:40:21 +0000774
cliechti8099bed2009-08-01 23:59:18 +0000775
cliechti1ef7e3e2009-08-03 02:38:43 +0000776 def _telnetProcessSubnegotiation(self, suboption):
cliechti044d8662009-08-11 21:40:31 +0000777 """Process subnegotiation, the data between IAC SB and IAC SE."""
cliechti8099bed2009-08-01 23:59:18 +0000778 if suboption[0:1] == COM_PORT_OPTION:
779 if suboption[1:2] == SERVER_NOTIFY_LINESTATE and len(suboption) >= 3:
cliechti672d0292009-08-03 02:01:57 +0000780 self._linestate = ord(suboption[2:3]) # ensure it is a number
cliechti6a300772009-08-12 02:28:56 +0000781 if self.logger:
782 self.logger.info("NOTIFY_LINESTATE: %s" % self._linestate)
cliechti8099bed2009-08-01 23:59:18 +0000783 elif suboption[1:2] == SERVER_NOTIFY_MODEMSTATE and len(suboption) >= 3:
cliechti672d0292009-08-03 02:01:57 +0000784 self._modemstate = ord(suboption[2:3]) # ensure it is a number
cliechti6a300772009-08-12 02:28:56 +0000785 if self.logger:
786 self.logger.info("NOTIFY_MODEMSTATE: %s" % self._modemstate)
cliechti7cb78e82009-08-05 15:47:57 +0000787 # update time when we think that a poll would make sense
788 self._modemstate_expires = time.time() + 0.3
cliechti672d0292009-08-03 02:01:57 +0000789 elif suboption[1:2] == FLOWCONTROL_SUSPEND:
790 self._remote_suspend_flow = True
791 elif suboption[1:2] == FLOWCONTROL_RESUME:
792 self._remote_suspend_flow = False
cliechti8099bed2009-08-01 23:59:18 +0000793 else:
cliechti2b929b72009-08-02 23:49:02 +0000794 for item in self._rfc2217_options.values():
795 if item.ack_option == suboption[1:2]:
cliechti81c54762009-08-03 23:53:27 +0000796 #~ print "processing COM_PORT_OPTION: %r" % list(suboption[1:])
cliechti2b929b72009-08-02 23:49:02 +0000797 item.checkAnswer(bytes(suboption[2:]))
798 break
799 else:
cliechti6a300772009-08-12 02:28:56 +0000800 if self.logger:
801 self.logger.warning("ignoring COM_PORT_OPTION: %r" % (suboption,))
cliechti8099bed2009-08-01 23:59:18 +0000802 else:
cliechti6a300772009-08-12 02:28:56 +0000803 if self.logger:
804 self.logger.warning("ignoring subnegotiation: %r" % (suboption,))
cliechti8099bed2009-08-01 23:59:18 +0000805
806 # - outgoing telnet commands and options
807
cliechti81c54762009-08-03 23:53:27 +0000808 def _internal_raw_write(self, data):
cliechti044d8662009-08-11 21:40:31 +0000809 """internal socket write with no data escaping. used to send telnet stuff."""
cliechti81c54762009-08-03 23:53:27 +0000810 self._write_lock.acquire()
811 try:
812 self._socket.sendall(data)
813 finally:
814 self._write_lock.release()
815
cliechti1ef7e3e2009-08-03 02:38:43 +0000816 def telnetSendOption(self, action, option):
cliechti044d8662009-08-11 21:40:31 +0000817 """Send DO, DONT, WILL, WONT."""
cliechti81c54762009-08-03 23:53:27 +0000818 self._internal_raw_write(to_bytes([IAC, action, option]))
cliechti8099bed2009-08-01 23:59:18 +0000819
cliechtif325c032009-12-25 16:09:49 +0000820 def rfc2217SendSubnegotiation(self, option, value=''):
cliechti044d8662009-08-11 21:40:31 +0000821 """Subnegotiation of RFC2217 parameters."""
cliechtif325c032009-12-25 16:09:49 +0000822 value = value.replace(IAC, IAC_DOUBLED)
cliechti81c54762009-08-03 23:53:27 +0000823 self._internal_raw_write(to_bytes([IAC, SB, COM_PORT_OPTION, option] + list(value) + [IAC, SE]))
cliechti2b929b72009-08-02 23:49:02 +0000824
cliechti1ef7e3e2009-08-03 02:38:43 +0000825 def rfc2217SendPurge(self, value):
cliechti2b929b72009-08-02 23:49:02 +0000826 item = self._rfc2217_options['purge']
cliechti672d0292009-08-03 02:01:57 +0000827 item.set(value) # transmit desired purge type
cliechtidfe2d272009-08-10 22:19:41 +0000828 item.wait(self._network_timeout) # wait for acknowledge from the server
cliechti2b929b72009-08-02 23:49:02 +0000829
cliechti1ef7e3e2009-08-03 02:38:43 +0000830 def rfc2217SetControl(self, value):
cliechti81c54762009-08-03 23:53:27 +0000831 item = self._rfc2217_options['control']
cliechticb20a4f2011-04-25 02:25:54 +0000832 item.set(value) # transmit desired control type
cliechti81c54762009-08-03 23:53:27 +0000833 if self._ignore_set_control_answer:
834 # answers are ignored when option is set. compatibility mode for
cliechticb20a4f2011-04-25 02:25:54 +0000835 # servers that answer, but not the expected one... (or no answer
cliechti81c54762009-08-03 23:53:27 +0000836 # at all) i.e. sredird
837 time.sleep(0.1) # this helps getting the unit tests passed
838 else:
cliechtidfe2d272009-08-10 22:19:41 +0000839 item.wait(self._network_timeout) # wait for acknowledge from the server
cliechti8099bed2009-08-01 23:59:18 +0000840
cliechti1ef7e3e2009-08-03 02:38:43 +0000841 def rfc2217FlowServerReady(self):
cliechtieada4fd2013-07-31 16:26:07 +0000842 """\
843 check if server is ready to receive data. block for some time when
844 not.
845 """
cliechti672d0292009-08-03 02:01:57 +0000846 #~ if self._remote_suspend_flow:
847 #~ wait---
848
cliechti7cb78e82009-08-05 15:47:57 +0000849 def getModemState(self):
cliechtieada4fd2013-07-31 16:26:07 +0000850 """\
851 get last modem state (cached value. if value is "old", request a new
cliechti7cb78e82009-08-05 15:47:57 +0000852 one. this cache helps that we don't issue to many requests when e.g. all
853 status lines, one after the other is queried by te user (getCTS, getDSR
cliechtieada4fd2013-07-31 16:26:07 +0000854 etc.)
855 """
cliechti7cb78e82009-08-05 15:47:57 +0000856 # active modem state polling enabled? is the value fresh enough?
857 if self._poll_modem_state and self._modemstate_expires < time.time():
cliechti6a300772009-08-12 02:28:56 +0000858 if self.logger:
859 self.logger.debug('polling modem state')
cliechti7cb78e82009-08-05 15:47:57 +0000860 # when it is older, request an update
861 self.rfc2217SendSubnegotiation(NOTIFY_MODEMSTATE)
cliechtidfe2d272009-08-10 22:19:41 +0000862 timeout_time = time.time() + self._network_timeout
cliechti7cb78e82009-08-05 15:47:57 +0000863 while time.time() < timeout_time:
864 time.sleep(0.05) # prevent 100% CPU load
865 # when expiration time is updated, it means that there is a new
866 # value
867 if self._modemstate_expires > time.time():
cliechti6a300772009-08-12 02:28:56 +0000868 if self.logger:
869 self.logger.warning('poll for modem state failed')
cliechti7cb78e82009-08-05 15:47:57 +0000870 break
871 # even when there is a timeout, do not generate an error just
872 # return the last known value. this way we can support buggy
873 # servers that do not respond to polls, but send automatic
874 # updates.
875 if self._modemstate is not None:
cliechti6a300772009-08-12 02:28:56 +0000876 if self.logger:
877 self.logger.debug('using cached modem state')
cliechti7cb78e82009-08-05 15:47:57 +0000878 return self._modemstate
879 else:
880 # never received a notification from the server
cliechti8fb119c2009-08-05 23:39:45 +0000881 raise SerialException("remote sends no NOTIFY_MODEMSTATE")
cliechti8099bed2009-08-01 23:59:18 +0000882
cliechti5cc3eb12009-08-11 23:04:30 +0000883
cliechti8099bed2009-08-01 23:59:18 +0000884# assemble Serial class with the platform specific implementation and the base
885# for file-like behavior. for Python 2.6 and newer, that provide the new I/O
886# library, derive from io.RawIOBase
887try:
888 import io
889except ImportError:
890 # classic version with our own file-like emulation
891 class Serial(RFC2217Serial, FileLike):
892 pass
893else:
894 # io library present
895 class Serial(RFC2217Serial, io.RawIOBase):
896 pass
897
cliechti7cb78e82009-08-05 15:47:57 +0000898
cliechti595ed5b2009-08-10 01:43:32 +0000899#############################################################################
cliechti5cc3eb12009-08-11 23:04:30 +0000900# The following is code that helps implementing an RFC 2217 server.
cliechti8099bed2009-08-01 23:59:18 +0000901
cliechti8ccc2ff2009-08-05 12:44:46 +0000902class PortManager(object):
cliechtieada4fd2013-07-31 16:26:07 +0000903 """\
904 This class manages the state of Telnet and RFC 2217. It needs a serial
cliechticb20a4f2011-04-25 02:25:54 +0000905 instance and a connection to work with. Connection is expected to implement
cliechtieada4fd2013-07-31 16:26:07 +0000906 a (thread safe) write function, that writes the string to the network.
907 """
cliechti130d1f02009-08-04 02:10:58 +0000908
cliechti6a300772009-08-12 02:28:56 +0000909 def __init__(self, serial_port, connection, logger=None):
cliechti130d1f02009-08-04 02:10:58 +0000910 self.serial = serial_port
911 self.connection = connection
cliechti6a300772009-08-12 02:28:56 +0000912 self.logger = logger
cliechti86b593e2009-08-05 16:28:12 +0000913 self._client_is_rfc2217 = False
cliechti130d1f02009-08-04 02:10:58 +0000914
915 # filter state machine
916 self.mode = M_NORMAL
917 self.suboption = None
918 self.telnet_command = None
919
920 # states for modem/line control events
921 self.modemstate_mask = 255
922 self.last_modemstate = None
923 self.linstate_mask = 0
924
925 # all supported telnet options
926 self._telnet_options = [
927 TelnetOption(self, 'ECHO', ECHO, WILL, WONT, DO, DONT, REQUESTED),
928 TelnetOption(self, 'we-SGA', SGA, WILL, WONT, DO, DONT, REQUESTED),
929 TelnetOption(self, 'they-SGA', SGA, DO, DONT, WILL, WONT, INACTIVE),
930 TelnetOption(self, 'we-BINARY', BINARY, WILL, WONT, DO, DONT, INACTIVE),
931 TelnetOption(self, 'they-BINARY', BINARY, DO, DONT, WILL, WONT, REQUESTED),
cliechti86b593e2009-08-05 16:28:12 +0000932 TelnetOption(self, 'we-RFC2217', COM_PORT_OPTION, WILL, WONT, DO, DONT, REQUESTED, self._client_ok),
933 TelnetOption(self, 'they-RFC2217', COM_PORT_OPTION, DO, DONT, WILL, WONT, INACTIVE, self._client_ok),
cliechti130d1f02009-08-04 02:10:58 +0000934 ]
935
936 # negotiate Telnet/RFC2217 -> send initial requests
cliechti6a300772009-08-12 02:28:56 +0000937 if self.logger:
938 self.logger.debug("requesting initial Telnet/RFC 2217 options")
cliechti130d1f02009-08-04 02:10:58 +0000939 for option in self._telnet_options:
940 if option.state is REQUESTED:
941 self.telnetSendOption(option.send_yes, option.option)
942 # issue 1st modem state notification
cliechti86b593e2009-08-05 16:28:12 +0000943
944 def _client_ok(self):
cliechtieada4fd2013-07-31 16:26:07 +0000945 """\
946 callback of telnet option. it gets called when option is activated.
cliechti86b593e2009-08-05 16:28:12 +0000947 this one here is used to detect when the client agrees on RFC 2217. a
948 flag is set so that other functions like check_modem_lines know if the
cliechtieada4fd2013-07-31 16:26:07 +0000949 client is ok.
950 """
cliechti86b593e2009-08-05 16:28:12 +0000951 # The callback is used for we and they so if one party agrees, we're
952 # already happy. it seems not all servers do the negotiation correctly
953 # and i guess there are incorrect clients too.. so be happy if client
954 # answers one or the other positively.
955 self._client_is_rfc2217 = True
cliechti6a300772009-08-12 02:28:56 +0000956 if self.logger:
957 self.logger.info("client accepts RFC 2217")
cliechti8fb119c2009-08-05 23:39:45 +0000958 # this is to ensure that the client gets a notification, even if there
959 # was no change
960 self.check_modem_lines(force_notification=True)
cliechti130d1f02009-08-04 02:10:58 +0000961
962 # - outgoing telnet commands and options
963
964 def telnetSendOption(self, action, option):
cliechti044d8662009-08-11 21:40:31 +0000965 """Send DO, DONT, WILL, WONT."""
cliechti130d1f02009-08-04 02:10:58 +0000966 self.connection.write(to_bytes([IAC, action, option]))
967
cliechtif325c032009-12-25 16:09:49 +0000968 def rfc2217SendSubnegotiation(self, option, value=''):
cliechti044d8662009-08-11 21:40:31 +0000969 """Subnegotiation of RFC 2217 parameters."""
cliechtif325c032009-12-25 16:09:49 +0000970 value = value.replace(IAC, IAC_DOUBLED)
cliechti130d1f02009-08-04 02:10:58 +0000971 self.connection.write(to_bytes([IAC, SB, COM_PORT_OPTION, option] + list(value) + [IAC, SE]))
972
973 # - check modem lines, needs to be called periodically from user to
974 # establish polling
975
cliechti7cb78e82009-08-05 15:47:57 +0000976 def check_modem_lines(self, force_notification=False):
cliechti130d1f02009-08-04 02:10:58 +0000977 modemstate = (
978 (self.serial.getCTS() and MODEMSTATE_MASK_CTS) |
979 (self.serial.getDSR() and MODEMSTATE_MASK_DSR) |
980 (self.serial.getRI() and MODEMSTATE_MASK_RI) |
981 (self.serial.getCD() and MODEMSTATE_MASK_CD)
982 )
cliechti7cb78e82009-08-05 15:47:57 +0000983 # check what has changed
984 deltas = modemstate ^ (self.last_modemstate or 0) # when last is None -> 0
985 if deltas & MODEMSTATE_MASK_CTS:
986 modemstate |= MODEMSTATE_MASK_CTS_CHANGE
987 if deltas & MODEMSTATE_MASK_DSR:
988 modemstate |= MODEMSTATE_MASK_DSR_CHANGE
989 if deltas & MODEMSTATE_MASK_RI:
990 modemstate |= MODEMSTATE_MASK_RI_CHANGE
991 if deltas & MODEMSTATE_MASK_CD:
992 modemstate |= MODEMSTATE_MASK_CD_CHANGE
993 # if new state is different and the mask allows this change, send
cliechti86b593e2009-08-05 16:28:12 +0000994 # notification. suppress notifications when client is not rfc2217
cliechti7cb78e82009-08-05 15:47:57 +0000995 if modemstate != self.last_modemstate or force_notification:
cliechti8fb119c2009-08-05 23:39:45 +0000996 if (self._client_is_rfc2217 and (modemstate & self.modemstate_mask)) or force_notification:
cliechti7cb78e82009-08-05 15:47:57 +0000997 self.rfc2217SendSubnegotiation(
998 SERVER_NOTIFY_MODEMSTATE,
999 to_bytes([modemstate & self.modemstate_mask])
1000 )
cliechti6a300772009-08-12 02:28:56 +00001001 if self.logger:
1002 self.logger.info("NOTIFY_MODEMSTATE: %s" % (modemstate,))
cliechti7cb78e82009-08-05 15:47:57 +00001003 # save last state, but forget about deltas.
1004 # otherwise it would also notify about changing deltas which is
1005 # probably not very useful
1006 self.last_modemstate = modemstate & 0xf0
cliechti130d1f02009-08-04 02:10:58 +00001007
cliechti32c10332009-08-05 13:23:43 +00001008 # - outgoing data escaping
1009
1010 def escape(self, data):
cliechtieada4fd2013-07-31 16:26:07 +00001011 """\
1012 this generator function is for the user. all outgoing data has to be
cliechticb20a4f2011-04-25 02:25:54 +00001013 properly escaped, so that no IAC character in the data stream messes up
1014 the Telnet state machine in the server.
cliechti32c10332009-08-05 13:23:43 +00001015
1016 socket.sendall(escape(data))
1017 """
1018 for byte in data:
1019 if byte == IAC:
1020 yield IAC
1021 yield IAC
1022 else:
1023 yield byte
1024
cliechti130d1f02009-08-04 02:10:58 +00001025 # - incoming data filter
1026
1027 def filter(self, data):
cliechtieada4fd2013-07-31 16:26:07 +00001028 """\
1029 handle a bunch of incoming bytes. this is a generator. it will yield
cliechti044d8662009-08-11 21:40:31 +00001030 all characters not of interest for Telnet/RFC 2217.
cliechti130d1f02009-08-04 02:10:58 +00001031
1032 The idea is that the reader thread pushes data from the socket through
1033 this filter:
1034
1035 for byte in filter(socket.recv(1024)):
1036 # do things like CR/LF conversion/whatever
1037 # and write data to the serial port
1038 serial.write(byte)
1039
1040 (socket error handling code left as exercise for the reader)
1041 """
1042 for byte in data:
1043 if self.mode == M_NORMAL:
1044 # interpret as command or as data
1045 if byte == IAC:
1046 self.mode = M_IAC_SEEN
1047 else:
1048 # store data in sub option buffer or pass it to our
1049 # consumer depending on state
1050 if self.suboption is not None:
1051 self.suboption.append(byte)
1052 else:
1053 yield byte
1054 elif self.mode == M_IAC_SEEN:
1055 if byte == IAC:
1056 # interpret as command doubled -> insert character
1057 # itself
cliechtif325c032009-12-25 16:09:49 +00001058 if self.suboption is not None:
1059 self.suboption.append(byte)
1060 else:
1061 yield byte
cliechti130d1f02009-08-04 02:10:58 +00001062 self.mode = M_NORMAL
1063 elif byte == SB:
1064 # sub option start
1065 self.suboption = bytearray()
1066 self.mode = M_NORMAL
1067 elif byte == SE:
1068 # sub option end -> process it now
1069 self._telnetProcessSubnegotiation(bytes(self.suboption))
1070 self.suboption = None
1071 self.mode = M_NORMAL
1072 elif byte in (DO, DONT, WILL, WONT):
1073 # negotiation
1074 self.telnet_command = byte
1075 self.mode = M_NEGOTIATE
1076 else:
1077 # other telnet commands
1078 self._telnetProcessCommand(byte)
1079 self.mode = M_NORMAL
1080 elif self.mode == M_NEGOTIATE: # DO, DONT, WILL, WONT was received, option now following
1081 self._telnetNegotiateOption(self.telnet_command, byte)
1082 self.mode = M_NORMAL
1083
1084 # - incoming telnet commands and options
1085
1086 def _telnetProcessCommand(self, command):
cliechti044d8662009-08-11 21:40:31 +00001087 """Process commands other than DO, DONT, WILL, WONT."""
cliechti130d1f02009-08-04 02:10:58 +00001088 # Currently none. RFC2217 only uses negotiation and subnegotiation.
cliechti6a300772009-08-12 02:28:56 +00001089 if self.logger:
1090 self.logger.warning("ignoring Telnet command: %r" % (command,))
cliechti130d1f02009-08-04 02:10:58 +00001091
1092 def _telnetNegotiateOption(self, command, option):
cliechti044d8662009-08-11 21:40:31 +00001093 """Process incoming DO, DONT, WILL, WONT."""
cliechti130d1f02009-08-04 02:10:58 +00001094 # check our registered telnet options and forward command to them
1095 # they know themselves if they have to answer or not
1096 known = False
1097 for item in self._telnet_options:
1098 # can have more than one match! as some options are duplicated for
1099 # 'us' and 'them'
1100 if item.option == option:
1101 item.process_incoming(command)
1102 known = True
1103 if not known:
1104 # handle unknown options
1105 # only answer to positive requests and deny them
1106 if command == WILL or command == DO:
1107 self.telnetSendOption((command == WILL and DONT or WONT), option)
cliechti6a300772009-08-12 02:28:56 +00001108 if self.logger:
1109 self.logger.warning("rejected Telnet option: %r" % (option,))
cliechti130d1f02009-08-04 02:10:58 +00001110
1111
1112 def _telnetProcessSubnegotiation(self, suboption):
cliechti044d8662009-08-11 21:40:31 +00001113 """Process subnegotiation, the data between IAC SB and IAC SE."""
cliechti130d1f02009-08-04 02:10:58 +00001114 if suboption[0:1] == COM_PORT_OPTION:
cliechti6a300772009-08-12 02:28:56 +00001115 if self.logger:
1116 self.logger.debug('received COM_PORT_OPTION: %r' % (suboption,))
cliechti130d1f02009-08-04 02:10:58 +00001117 if suboption[1:2] == SET_BAUDRATE:
1118 backup = self.serial.baudrate
1119 try:
cliechtieada4fd2013-07-31 16:26:07 +00001120 (baudrate,) = struct.unpack("!I", suboption[2:6])
1121 if baudrate != 0:
1122 self.serial.baudrate = baudrate
cliechti130d1f02009-08-04 02:10:58 +00001123 except ValueError, e:
cliechti6a300772009-08-12 02:28:56 +00001124 if self.logger:
1125 self.logger.error("failed to set baud rate: %s" % (e,))
cliechti130d1f02009-08-04 02:10:58 +00001126 self.serial.baudrate = backup
cliechti5cc3eb12009-08-11 23:04:30 +00001127 else:
cliechti6a300772009-08-12 02:28:56 +00001128 if self.logger:
cliechtieada4fd2013-07-31 16:26:07 +00001129 self.logger.info("%s baud rate: %s" % (baudrate and 'set' or 'get', self.serial.baudrate))
cliechti130d1f02009-08-04 02:10:58 +00001130 self.rfc2217SendSubnegotiation(SERVER_SET_BAUDRATE, struct.pack("!I", self.serial.baudrate))
1131 elif suboption[1:2] == SET_DATASIZE:
1132 backup = self.serial.bytesize
1133 try:
cliechtieada4fd2013-07-31 16:26:07 +00001134 (datasize,) = struct.unpack("!B", suboption[2:3])
1135 if datasize != 0:
1136 self.serial.bytesize = datasize
cliechti130d1f02009-08-04 02:10:58 +00001137 except ValueError, e:
cliechti6a300772009-08-12 02:28:56 +00001138 if self.logger:
1139 self.logger.error("failed to set data size: %s" % (e,))
cliechti130d1f02009-08-04 02:10:58 +00001140 self.serial.bytesize = backup
cliechti5cc3eb12009-08-11 23:04:30 +00001141 else:
cliechti6a300772009-08-12 02:28:56 +00001142 if self.logger:
cliechtieada4fd2013-07-31 16:26:07 +00001143 self.logger.info("%s data size: %s" % (datasize and 'set' or 'get', self.serial.bytesize))
cliechti130d1f02009-08-04 02:10:58 +00001144 self.rfc2217SendSubnegotiation(SERVER_SET_DATASIZE, struct.pack("!B", self.serial.bytesize))
1145 elif suboption[1:2] == SET_PARITY:
1146 backup = self.serial.parity
1147 try:
cliechtieada4fd2013-07-31 16:26:07 +00001148 parity = struct.unpack("!B", suboption[2:3])[0]
1149 if parity != 0:
1150 self.serial.parity = RFC2217_REVERSE_PARITY_MAP[parity]
cliechti130d1f02009-08-04 02:10:58 +00001151 except ValueError, e:
cliechti6a300772009-08-12 02:28:56 +00001152 if self.logger:
1153 self.logger.error("failed to set parity: %s" % (e,))
cliechti130d1f02009-08-04 02:10:58 +00001154 self.serial.parity = backup
cliechti5cc3eb12009-08-11 23:04:30 +00001155 else:
cliechti6a300772009-08-12 02:28:56 +00001156 if self.logger:
cliechtieada4fd2013-07-31 16:26:07 +00001157 self.logger.info("%s parity: %s" % (parity and 'set' or 'get', self.serial.parity))
cliechti130d1f02009-08-04 02:10:58 +00001158 self.rfc2217SendSubnegotiation(
1159 SERVER_SET_PARITY,
1160 struct.pack("!B", RFC2217_PARITY_MAP[self.serial.parity])
1161 )
1162 elif suboption[1:2] == SET_STOPSIZE:
1163 backup = self.serial.stopbits
1164 try:
cliechtieada4fd2013-07-31 16:26:07 +00001165 stopbits = struct.unpack("!B", suboption[2:3])[0]
1166 if stopbits != 0:
1167 self.serial.stopbits = RFC2217_REVERSE_STOPBIT_MAP[stopbits]
cliechti130d1f02009-08-04 02:10:58 +00001168 except ValueError, e:
cliechti6a300772009-08-12 02:28:56 +00001169 if self.logger:
1170 self.logger.error("failed to set stop bits: %s" % (e,))
cliechti130d1f02009-08-04 02:10:58 +00001171 self.serial.stopbits = backup
cliechti5cc3eb12009-08-11 23:04:30 +00001172 else:
cliechti6a300772009-08-12 02:28:56 +00001173 if self.logger:
cliechtieada4fd2013-07-31 16:26:07 +00001174 self.logger.info("%s stop bits: %s" % (stopbits and 'set' or 'get', self.serial.stopbits))
cliechti130d1f02009-08-04 02:10:58 +00001175 self.rfc2217SendSubnegotiation(
1176 SERVER_SET_STOPSIZE,
1177 struct.pack("!B", RFC2217_STOPBIT_MAP[self.serial.stopbits])
1178 )
1179 elif suboption[1:2] == SET_CONTROL:
1180 if suboption[2:3] == SET_CONTROL_REQ_FLOW_SETTING:
1181 if self.serial.xonxoff:
1182 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_SW_FLOW_CONTROL)
1183 elif self.serial.rtscts:
1184 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_HW_FLOW_CONTROL)
1185 else:
1186 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_NO_FLOW_CONTROL)
1187 elif suboption[2:3] == SET_CONTROL_USE_NO_FLOW_CONTROL:
1188 self.serial.xonxoff = False
1189 self.serial.rtscts = False
cliechti6a300772009-08-12 02:28:56 +00001190 if self.logger:
1191 self.logger.info("changed flow control to None")
cliechti130d1f02009-08-04 02:10:58 +00001192 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_NO_FLOW_CONTROL)
1193 elif suboption[2:3] == SET_CONTROL_USE_SW_FLOW_CONTROL:
1194 self.serial.xonxoff = True
cliechti6a300772009-08-12 02:28:56 +00001195 if self.logger:
1196 self.logger.info("changed flow control to XON/XOFF")
cliechti130d1f02009-08-04 02:10:58 +00001197 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_SW_FLOW_CONTROL)
1198 elif suboption[2:3] == SET_CONTROL_USE_HW_FLOW_CONTROL:
1199 self.serial.rtscts = True
cliechti6a300772009-08-12 02:28:56 +00001200 if self.logger:
1201 self.logger.info("changed flow control to RTS/CTS")
cliechti130d1f02009-08-04 02:10:58 +00001202 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_HW_FLOW_CONTROL)
1203 elif suboption[2:3] == SET_CONTROL_REQ_BREAK_STATE:
cliechti6a300772009-08-12 02:28:56 +00001204 if self.logger:
1205 self.logger.warning("requested break state - not implemented")
cliechti130d1f02009-08-04 02:10:58 +00001206 pass # XXX needs cached value
1207 elif suboption[2:3] == SET_CONTROL_BREAK_ON:
1208 self.serial.setBreak(True)
cliechti6a300772009-08-12 02:28:56 +00001209 if self.logger:
1210 self.logger.info("changed BREAK to active")
cliechti130d1f02009-08-04 02:10:58 +00001211 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_BREAK_ON)
1212 elif suboption[2:3] == SET_CONTROL_BREAK_OFF:
1213 self.serial.setBreak(False)
cliechti6a300772009-08-12 02:28:56 +00001214 if self.logger:
1215 self.logger.info("changed BREAK to inactive")
cliechti130d1f02009-08-04 02:10:58 +00001216 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_BREAK_OFF)
1217 elif suboption[2:3] == SET_CONTROL_REQ_DTR:
cliechti6a300772009-08-12 02:28:56 +00001218 if self.logger:
1219 self.logger.warning("requested DTR state - not implemented")
cliechti130d1f02009-08-04 02:10:58 +00001220 pass # XXX needs cached value
1221 elif suboption[2:3] == SET_CONTROL_DTR_ON:
1222 self.serial.setDTR(True)
cliechti6a300772009-08-12 02:28:56 +00001223 if self.logger:
1224 self.logger.info("changed DTR to active")
cliechti130d1f02009-08-04 02:10:58 +00001225 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_DTR_ON)
1226 elif suboption[2:3] == SET_CONTROL_DTR_OFF:
1227 self.serial.setDTR(False)
cliechti6a300772009-08-12 02:28:56 +00001228 if self.logger:
1229 self.logger.info("changed DTR to inactive")
cliechti130d1f02009-08-04 02:10:58 +00001230 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_DTR_OFF)
1231 elif suboption[2:3] == SET_CONTROL_REQ_RTS:
cliechti6a300772009-08-12 02:28:56 +00001232 if self.logger:
1233 self.logger.warning("requested RTS state - not implemented")
cliechti130d1f02009-08-04 02:10:58 +00001234 pass # XXX needs cached value
1235 #~ self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_RTS_ON)
1236 elif suboption[2:3] == SET_CONTROL_RTS_ON:
1237 self.serial.setRTS(True)
cliechti6a300772009-08-12 02:28:56 +00001238 if self.logger:
1239 self.logger.info("changed RTS to active")
cliechti130d1f02009-08-04 02:10:58 +00001240 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_RTS_ON)
1241 elif suboption[2:3] == SET_CONTROL_RTS_OFF:
1242 self.serial.setRTS(False)
cliechti6a300772009-08-12 02:28:56 +00001243 if self.logger:
1244 self.logger.info("changed RTS to inactive")
cliechti130d1f02009-08-04 02:10:58 +00001245 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_RTS_OFF)
1246 #~ elif suboption[2:3] == SET_CONTROL_REQ_FLOW_SETTING_IN:
1247 #~ elif suboption[2:3] == SET_CONTROL_USE_NO_FLOW_CONTROL_IN:
1248 #~ elif suboption[2:3] == SET_CONTROL_USE_SW_FLOW_CONTOL_IN:
1249 #~ elif suboption[2:3] == SET_CONTROL_USE_HW_FLOW_CONTOL_IN:
1250 #~ elif suboption[2:3] == SET_CONTROL_USE_DCD_FLOW_CONTROL:
1251 #~ elif suboption[2:3] == SET_CONTROL_USE_DTR_FLOW_CONTROL:
1252 #~ elif suboption[2:3] == SET_CONTROL_USE_DSR_FLOW_CONTROL:
1253 elif suboption[1:2] == NOTIFY_LINESTATE:
cliechti7cb78e82009-08-05 15:47:57 +00001254 # client polls for current state
1255 self.rfc2217SendSubnegotiation(
1256 SERVER_NOTIFY_LINESTATE,
cliechti6a300772009-08-12 02:28:56 +00001257 to_bytes([0]) # sorry, nothing like that implemented
cliechti7cb78e82009-08-05 15:47:57 +00001258 )
cliechti130d1f02009-08-04 02:10:58 +00001259 elif suboption[1:2] == NOTIFY_MODEMSTATE:
cliechti6a300772009-08-12 02:28:56 +00001260 if self.logger:
1261 self.logger.info("request for modem state")
cliechti7cb78e82009-08-05 15:47:57 +00001262 # client polls for current state
1263 self.check_modem_lines(force_notification=True)
cliechti130d1f02009-08-04 02:10:58 +00001264 elif suboption[1:2] == FLOWCONTROL_SUSPEND:
cliechti6a300772009-08-12 02:28:56 +00001265 if self.logger:
1266 self.logger.info("suspend")
cliechti130d1f02009-08-04 02:10:58 +00001267 self._remote_suspend_flow = True
1268 elif suboption[1:2] == FLOWCONTROL_RESUME:
cliechti6a300772009-08-12 02:28:56 +00001269 if self.logger:
1270 self.logger.info("resume")
cliechti130d1f02009-08-04 02:10:58 +00001271 self._remote_suspend_flow = False
1272 elif suboption[1:2] == SET_LINESTATE_MASK:
1273 self.linstate_mask = ord(suboption[2:3]) # ensure it is a number
cliechti6a300772009-08-12 02:28:56 +00001274 if self.logger:
cliechtif325c032009-12-25 16:09:49 +00001275 self.logger.info("line state mask: 0x%02x" % (self.linstate_mask,))
cliechti130d1f02009-08-04 02:10:58 +00001276 elif suboption[1:2] == SET_MODEMSTATE_MASK:
1277 self.modemstate_mask = ord(suboption[2:3]) # ensure it is a number
cliechti6a300772009-08-12 02:28:56 +00001278 if self.logger:
cliechtif325c032009-12-25 16:09:49 +00001279 self.logger.info("modem state mask: 0x%02x" % (self.modemstate_mask,))
cliechti130d1f02009-08-04 02:10:58 +00001280 elif suboption[1:2] == PURGE_DATA:
1281 if suboption[2:3] == PURGE_RECEIVE_BUFFER:
1282 self.serial.flushInput()
cliechti6a300772009-08-12 02:28:56 +00001283 if self.logger:
1284 self.logger.info("purge in")
cliechti130d1f02009-08-04 02:10:58 +00001285 self.rfc2217SendSubnegotiation(SERVER_PURGE_DATA, PURGE_RECEIVE_BUFFER)
1286 elif suboption[2:3] == PURGE_TRANSMIT_BUFFER:
1287 self.serial.flushOutput()
cliechti6a300772009-08-12 02:28:56 +00001288 if self.logger:
1289 self.logger.info("purge out")
cliechti130d1f02009-08-04 02:10:58 +00001290 self.rfc2217SendSubnegotiation(SERVER_PURGE_DATA, PURGE_TRANSMIT_BUFFER)
1291 elif suboption[2:3] == PURGE_BOTH_BUFFERS:
1292 self.serial.flushInput()
1293 self.serial.flushOutput()
cliechti6a300772009-08-12 02:28:56 +00001294 if self.logger:
1295 self.logger.info("purge both")
cliechti130d1f02009-08-04 02:10:58 +00001296 self.rfc2217SendSubnegotiation(SERVER_PURGE_DATA, PURGE_BOTH_BUFFERS)
1297 else:
cliechti6a300772009-08-12 02:28:56 +00001298 if self.logger:
1299 self.logger.error("undefined PURGE_DATA: %r" % list(suboption[2:]))
cliechti130d1f02009-08-04 02:10:58 +00001300 else:
cliechti6a300772009-08-12 02:28:56 +00001301 if self.logger:
1302 self.logger.error("undefined COM_PORT_OPTION: %r" % list(suboption[1:]))
cliechti130d1f02009-08-04 02:10:58 +00001303 else:
cliechti6a300772009-08-12 02:28:56 +00001304 if self.logger:
1305 self.logger.warning("unknown subnegotiation: %r" % (suboption,))
cliechti130d1f02009-08-04 02:10:58 +00001306
1307
1308# simple client test
cliechti8099bed2009-08-01 23:59:18 +00001309if __name__ == '__main__':
1310 import sys
1311 s = Serial('rfc2217://localhost:7000', 115200)
1312 sys.stdout.write('%s\n' % s)
1313
cliechti2b929b72009-08-02 23:49:02 +00001314 #~ s.baudrate = 1898
1315
cliechti8099bed2009-08-01 23:59:18 +00001316 sys.stdout.write("write...\n")
1317 s.write("hello\n")
1318 s.flush()
cliechti8099bed2009-08-01 23:59:18 +00001319 sys.stdout.write("read: %s\n" % s.read(5))
1320
1321 #~ s.baudrate = 19200
1322 #~ s.databits = 7
1323 s.close()