blob: 1c157a942439fc6dcd82645a79dc79bc33503436 [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):
cliechti7d448562014-08-03 21:57:45 +0000256 """\
257 A DO/DONT/WILL/WONT was received for this option, update state and
258 answer when needed.
259 """
cliechtiac205322009-08-02 20:40:21 +0000260 if command == self.ack_yes:
261 if self.state is REQUESTED:
262 self.state = ACTIVE
263 self.active = True
cliechti86b593e2009-08-05 16:28:12 +0000264 if self.activation_callback is not None:
265 self.activation_callback()
cliechtiac205322009-08-02 20:40:21 +0000266 elif self.state is ACTIVE:
267 pass
268 elif self.state is INACTIVE:
269 self.state = ACTIVE
cliechti1ef7e3e2009-08-03 02:38:43 +0000270 self.connection.telnetSendOption(self.send_yes, self.option)
cliechtiac205322009-08-02 20:40:21 +0000271 self.active = True
cliechti86b593e2009-08-05 16:28:12 +0000272 if self.activation_callback is not None:
273 self.activation_callback()
cliechtiac205322009-08-02 20:40:21 +0000274 elif self.state is REALLY_INACTIVE:
cliechti1ef7e3e2009-08-03 02:38:43 +0000275 self.connection.telnetSendOption(self.send_no, self.option)
cliechtiac205322009-08-02 20:40:21 +0000276 else:
277 raise ValueError('option in illegal state %r' % self)
278 elif command == self.ack_no:
279 if self.state is REQUESTED:
280 self.state = INACTIVE
281 self.active = False
282 elif self.state is ACTIVE:
283 self.state = INACTIVE
cliechti1ef7e3e2009-08-03 02:38:43 +0000284 self.connection.telnetSendOption(self.send_no, self.option)
cliechtiac205322009-08-02 20:40:21 +0000285 self.active = False
286 elif self.state is INACTIVE:
287 pass
288 elif self.state is REALLY_INACTIVE:
289 pass
290 else:
291 raise ValueError('option in illegal state %r' % self)
292
293
cliechti2b929b72009-08-02 23:49:02 +0000294class TelnetSubnegotiation(object):
cliechtieada4fd2013-07-31 16:26:07 +0000295 """\
296 A object to handle subnegotiation of options. In this case actually
297 sub-sub options for RFC 2217. It is used to track com port options.
298 """
cliechti2b929b72009-08-02 23:49:02 +0000299
300 def __init__(self, connection, name, option, ack_option=None):
301 if ack_option is None: ack_option = option
302 self.connection = connection
303 self.name = name
304 self.option = option
305 self.value = None
306 self.ack_option = ack_option
307 self.state = INACTIVE
308
309 def __repr__(self):
cliechti044d8662009-08-11 21:40:31 +0000310 """String for debug outputs."""
cliechti2b929b72009-08-02 23:49:02 +0000311 return "%s:%s" % (self.name, self.state)
312
313 def set(self, value):
cliechtieada4fd2013-07-31 16:26:07 +0000314 """\
cliechti7d448562014-08-03 21:57:45 +0000315 Request a change of the value. a request is sent to the server. if
cliechti2b929b72009-08-02 23:49:02 +0000316 the client needs to know if the change is performed he has to check the
cliechtieada4fd2013-07-31 16:26:07 +0000317 state of this object.
318 """
cliechti2b929b72009-08-02 23:49:02 +0000319 self.value = value
320 self.state = REQUESTED
cliechti1ef7e3e2009-08-03 02:38:43 +0000321 self.connection.rfc2217SendSubnegotiation(self.option, self.value)
cliechti6a300772009-08-12 02:28:56 +0000322 if self.connection.logger:
323 self.connection.logger.debug("SB Requesting %s -> %r" % (self.name, self.value))
cliechti2b929b72009-08-02 23:49:02 +0000324
325 def isReady(self):
cliechtieada4fd2013-07-31 16:26:07 +0000326 """\
cliechti7d448562014-08-03 21:57:45 +0000327 Check if answer from server has been received. when server rejects
cliechtieada4fd2013-07-31 16:26:07 +0000328 the change, raise a ValueError.
329 """
cliechti2b929b72009-08-02 23:49:02 +0000330 if self.state == REALLY_INACTIVE:
331 raise ValueError("remote rejected value for option %r" % (self.name))
332 return self.state == ACTIVE
cliechti1ef7e3e2009-08-03 02:38:43 +0000333 # add property to have a similar interface as TelnetOption
cliechti2b929b72009-08-02 23:49:02 +0000334 active = property(isReady)
335
cliechti044d8662009-08-11 21:40:31 +0000336 def wait(self, timeout=3):
cliechtieada4fd2013-07-31 16:26:07 +0000337 """\
cliechti7d448562014-08-03 21:57:45 +0000338 Wait until the subnegotiation has been acknowledged or timeout. It
cliechti1ef7e3e2009-08-03 02:38:43 +0000339 can also throw a value error when the answer from the server does not
cliechtieada4fd2013-07-31 16:26:07 +0000340 match the value sent.
341 """
cliechti044d8662009-08-11 21:40:31 +0000342 timeout_time = time.time() + timeout
cliechti2b929b72009-08-02 23:49:02 +0000343 while time.time() < timeout_time:
344 time.sleep(0.05) # prevent 100% CPU load
345 if self.isReady():
346 break
347 else:
348 raise SerialException("timeout while waiting for option %r" % (self.name))
349
350 def checkAnswer(self, suboption):
cliechtieada4fd2013-07-31 16:26:07 +0000351 """\
cliechti7d448562014-08-03 21:57:45 +0000352 Check an incoming subnegotiation block. The parameter already has
cliechtieada4fd2013-07-31 16:26:07 +0000353 cut off the header like sub option number and com port option value.
354 """
cliechti2b929b72009-08-02 23:49:02 +0000355 if self.value == suboption[:len(self.value)]:
356 self.state = ACTIVE
357 else:
358 # error propagation done in isReady
359 self.state = REALLY_INACTIVE
cliechti6a300772009-08-12 02:28:56 +0000360 if self.connection.logger:
361 self.connection.logger.debug("SB Answer %s -> %r -> %s" % (self.name, suboption, self.state))
cliechti2b929b72009-08-02 23:49:02 +0000362
363
cliechti8099bed2009-08-01 23:59:18 +0000364class RFC2217Serial(SerialBase):
cliechti044d8662009-08-11 21:40:31 +0000365 """Serial port implementation for RFC 2217 remote serial ports."""
cliechti8099bed2009-08-01 23:59:18 +0000366
367 BAUDRATES = (50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800,
368 9600, 19200, 38400, 57600, 115200)
369
370 def open(self):
cliechtieada4fd2013-07-31 16:26:07 +0000371 """\
372 Open port with current settings. This may throw a SerialException
373 if the port cannot be opened.
374 """
cliechti6a300772009-08-12 02:28:56 +0000375 self.logger = None
cliechti81c54762009-08-03 23:53:27 +0000376 self._ignore_set_control_answer = False
cliechti7cb78e82009-08-05 15:47:57 +0000377 self._poll_modem_state = False
cliechtidfe2d272009-08-10 22:19:41 +0000378 self._network_timeout = 3
cliechti8099bed2009-08-01 23:59:18 +0000379 if self._port is None:
380 raise SerialException("Port must be configured before it can be used.")
cliechti8f69e702011-03-19 00:22:32 +0000381 if self._isOpen:
382 raise SerialException("Port is already open.")
cliechti8099bed2009-08-01 23:59:18 +0000383 try:
384 self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
385 self._socket.connect(self.fromURL(self.portstr))
cliechti6a300772009-08-12 02:28:56 +0000386 self._socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
Chris Liechti68340d72015-08-03 14:15:48 +0200387 except Exception as msg:
cliechti8099bed2009-08-01 23:59:18 +0000388 self._socket = None
389 raise SerialException("Could not open port %s: %s" % (self.portstr, msg))
390
cliechti1ef7e3e2009-08-03 02:38:43 +0000391 self._socket.settimeout(5) # XXX good value?
cliechti8099bed2009-08-01 23:59:18 +0000392
cliechti1ef7e3e2009-08-03 02:38:43 +0000393 # use a thread save queue as buffer. it also simplifies implementing
394 # the read timeout
cliechti8099bed2009-08-01 23:59:18 +0000395 self._read_buffer = Queue.Queue()
cliechti81c54762009-08-03 23:53:27 +0000396 # to ensure that user writes does not interfere with internal
397 # telnet/rfc2217 options establish a lock
398 self._write_lock = threading.Lock()
cliechtiac205322009-08-02 20:40:21 +0000399 # name the following separately so that, below, a check can be easily done
400 mandadory_options = [
cliechti2b929b72009-08-02 23:49:02 +0000401 TelnetOption(self, 'we-BINARY', BINARY, WILL, WONT, DO, DONT, INACTIVE),
cliechti2b929b72009-08-02 23:49:02 +0000402 TelnetOption(self, 'we-RFC2217', COM_PORT_OPTION, WILL, WONT, DO, DONT, REQUESTED),
cliechtiac205322009-08-02 20:40:21 +0000403 ]
404 # all supported telnet options
405 self._telnet_options = [
cliechtia29275e2009-08-03 00:08:04 +0000406 TelnetOption(self, 'ECHO', ECHO, DO, DONT, WILL, WONT, REQUESTED),
cliechti2b929b72009-08-02 23:49:02 +0000407 TelnetOption(self, 'we-SGA', SGA, WILL, WONT, DO, DONT, REQUESTED),
408 TelnetOption(self, 'they-SGA', SGA, DO, DONT, WILL, WONT, REQUESTED),
cliechti81c54762009-08-03 23:53:27 +0000409 TelnetOption(self, 'they-BINARY', BINARY, DO, DONT, WILL, WONT, INACTIVE),
410 TelnetOption(self, 'they-RFC2217', COM_PORT_OPTION, DO, DONT, WILL, WONT, REQUESTED),
cliechtiac205322009-08-02 20:40:21 +0000411 ] + mandadory_options
cliechti044d8662009-08-11 21:40:31 +0000412 # RFC 2217 specific states
cliechti2b929b72009-08-02 23:49:02 +0000413 # COM port settings
414 self._rfc2217_port_settings = {
415 'baudrate': TelnetSubnegotiation(self, 'baudrate', SET_BAUDRATE, SERVER_SET_BAUDRATE),
416 'datasize': TelnetSubnegotiation(self, 'datasize', SET_DATASIZE, SERVER_SET_DATASIZE),
417 'parity': TelnetSubnegotiation(self, 'parity', SET_PARITY, SERVER_SET_PARITY),
418 'stopsize': TelnetSubnegotiation(self, 'stopsize', SET_STOPSIZE, SERVER_SET_STOPSIZE),
419 }
cliechticb20a4f2011-04-25 02:25:54 +0000420 # There are more subnegotiation objects, combine all in one dictionary
cliechti2b929b72009-08-02 23:49:02 +0000421 # for easy access
422 self._rfc2217_options = {
423 'purge': TelnetSubnegotiation(self, 'purge', PURGE_DATA, SERVER_PURGE_DATA),
cliechti81c54762009-08-03 23:53:27 +0000424 'control': TelnetSubnegotiation(self, 'control', SET_CONTROL, SERVER_SET_CONTROL),
cliechti2b929b72009-08-02 23:49:02 +0000425 }
426 self._rfc2217_options.update(self._rfc2217_port_settings)
427 # cache for line and modem states that the server sends to us
cliechti8099bed2009-08-01 23:59:18 +0000428 self._linestate = 0
cliechti7cb78e82009-08-05 15:47:57 +0000429 self._modemstate = None
430 self._modemstate_expires = 0
cliechti044d8662009-08-11 21:40:31 +0000431 # RFC 2217 flow control between server and client
cliechti672d0292009-08-03 02:01:57 +0000432 self._remote_suspend_flow = False
cliechti8099bed2009-08-01 23:59:18 +0000433
cliechti1ef7e3e2009-08-03 02:38:43 +0000434 self._thread = threading.Thread(target=self._telnetReadLoop)
cliechti8099bed2009-08-01 23:59:18 +0000435 self._thread.setDaemon(True)
cliechti5cc3eb12009-08-11 23:04:30 +0000436 self._thread.setName('pySerial RFC 2217 reader thread for %s' % (self._port,))
cliechti8099bed2009-08-01 23:59:18 +0000437 self._thread.start()
438
cliechti044d8662009-08-11 21:40:31 +0000439 # negotiate Telnet/RFC 2217 -> send initial requests
cliechtiac205322009-08-02 20:40:21 +0000440 for option in self._telnet_options:
441 if option.state is REQUESTED:
cliechti1ef7e3e2009-08-03 02:38:43 +0000442 self.telnetSendOption(option.send_yes, option.option)
cliechtiac205322009-08-02 20:40:21 +0000443 # now wait until important options are negotiated
cliechtidfe2d272009-08-10 22:19:41 +0000444 timeout_time = time.time() + self._network_timeout
cliechtiac205322009-08-02 20:40:21 +0000445 while time.time() < timeout_time:
cliechtiac205322009-08-02 20:40:21 +0000446 time.sleep(0.05) # prevent 100% CPU load
cliechti7c213a92014-07-31 15:29:34 +0000447 if sum(o.active for o in mandadory_options) == sum(o.state != INACTIVE for o in mandadory_options):
cliechti2b929b72009-08-02 23:49:02 +0000448 break
449 else:
450 raise SerialException("Remote does not seem to support RFC2217 or BINARY mode %r" % mandadory_options)
cliechti6a300772009-08-12 02:28:56 +0000451 if self.logger:
452 self.logger.info("Negotiated options: %s" % self._telnet_options)
cliechti8099bed2009-08-01 23:59:18 +0000453
cliechti044d8662009-08-11 21:40:31 +0000454 # fine, go on, set RFC 2271 specific things
cliechti8099bed2009-08-01 23:59:18 +0000455 self._reconfigurePort()
cliechti2b929b72009-08-02 23:49:02 +0000456 # all things set up get, now a clean start
cliechti8099bed2009-08-01 23:59:18 +0000457 self._isOpen = True
458 if not self._rtscts:
459 self.setRTS(True)
460 self.setDTR(True)
461 self.flushInput()
462 self.flushOutput()
463
464 def _reconfigurePort(self):
465 """Set communication parameters on opened port."""
466 if self._socket is None:
467 raise SerialException("Can only operate on open ports")
468
cliechti8099bed2009-08-01 23:59:18 +0000469 # if self._timeout != 0 and self._interCharTimeout is not None:
cliechti8099bed2009-08-01 23:59:18 +0000470 # XXX
471
472 if self._writeTimeout is not None:
473 raise NotImplementedError('writeTimeout is currently not supported')
cliechti2b929b72009-08-02 23:49:02 +0000474 # XXX
cliechti8099bed2009-08-01 23:59:18 +0000475
cliechti2b929b72009-08-02 23:49:02 +0000476 # Setup the connection
cliechti1ef7e3e2009-08-03 02:38:43 +0000477 # to get good performance, all parameter changes are sent first...
cliechti81c54762009-08-03 23:53:27 +0000478 if not isinstance(self._baudrate, (int, long)) or not 0 < self._baudrate < 2**32:
479 raise ValueError("invalid baudrate: %r" % (self._baudrate))
cliechti2b929b72009-08-02 23:49:02 +0000480 self._rfc2217_port_settings['baudrate'].set(struct.pack('!I', self._baudrate))
481 self._rfc2217_port_settings['datasize'].set(struct.pack('!B', self._bytesize))
482 self._rfc2217_port_settings['parity'].set(struct.pack('!B', RFC2217_PARITY_MAP[self._parity]))
483 self._rfc2217_port_settings['stopsize'].set(struct.pack('!B', RFC2217_STOPBIT_MAP[self._stopbits]))
cliechti8099bed2009-08-01 23:59:18 +0000484
cliechti2b929b72009-08-02 23:49:02 +0000485 # and now wait until parameters are active
486 items = self._rfc2217_port_settings.values()
cliechti6a300772009-08-12 02:28:56 +0000487 if self.logger:
488 self.logger.debug("Negotiating settings: %s" % (items,))
cliechtidfe2d272009-08-10 22:19:41 +0000489 timeout_time = time.time() + self._network_timeout
cliechti2b929b72009-08-02 23:49:02 +0000490 while time.time() < timeout_time:
491 time.sleep(0.05) # prevent 100% CPU load
492 if sum(o.active for o in items) == len(items):
493 break
494 else:
495 raise SerialException("Remote does not accept parameter change (RFC2217): %r" % items)
cliechti6a300772009-08-12 02:28:56 +0000496 if self.logger:
497 self.logger.info("Negotiated settings: %s" % (items,))
cliechti8099bed2009-08-01 23:59:18 +0000498
499 if self._rtscts and self._xonxoff:
cliechti1ef7e3e2009-08-03 02:38:43 +0000500 raise ValueError('xonxoff and rtscts together are not supported')
cliechti8099bed2009-08-01 23:59:18 +0000501 elif self._rtscts:
cliechti1ef7e3e2009-08-03 02:38:43 +0000502 self.rfc2217SetControl(SET_CONTROL_USE_HW_FLOW_CONTROL)
cliechti8099bed2009-08-01 23:59:18 +0000503 elif self._xonxoff:
cliechti1ef7e3e2009-08-03 02:38:43 +0000504 self.rfc2217SetControl(SET_CONTROL_USE_SW_FLOW_CONTROL)
cliechti8099bed2009-08-01 23:59:18 +0000505 else:
cliechti1ef7e3e2009-08-03 02:38:43 +0000506 self.rfc2217SetControl(SET_CONTROL_USE_NO_FLOW_CONTROL)
cliechti8099bed2009-08-01 23:59:18 +0000507
508 def close(self):
509 """Close port"""
510 if self._isOpen:
511 if self._socket:
512 try:
513 self._socket.shutdown(socket.SHUT_RDWR)
514 self._socket.close()
515 except:
516 # ignore errors.
517 pass
518 self._socket = None
519 if self._thread:
520 self._thread.join()
521 self._isOpen = False
522 # in case of quick reconnects, give the server some time
523 time.sleep(0.3)
524
525 def makeDeviceName(self, port):
526 raise SerialException("there is no sensible way to turn numbers into URLs")
527
528 def fromURL(self, url):
cliechti1ef7e3e2009-08-03 02:38:43 +0000529 """extract host and port from an URL string"""
cliechti8099bed2009-08-01 23:59:18 +0000530 if url.lower().startswith("rfc2217://"): url = url[10:]
531 try:
cliechti81c54762009-08-03 23:53:27 +0000532 # is there a "path" (our options)?
533 if '/' in url:
534 # cut away options
535 url, options = url.split('/', 1)
536 # process options now, directly altering self
537 for option in options.split('/'):
cliechtidfe2d272009-08-10 22:19:41 +0000538 if '=' in option:
539 option, value = option.split('=', 1)
540 else:
541 value = None
cliechti5cc3eb12009-08-11 23:04:30 +0000542 if option == 'logging':
543 logging.basicConfig() # XXX is that good to call it here?
cliechti6a300772009-08-12 02:28:56 +0000544 self.logger = logging.getLogger('pySerial.rfc2217')
545 self.logger.setLevel(LOGGER_LEVELS[value])
546 self.logger.debug('enabled logging')
cliechti81c54762009-08-03 23:53:27 +0000547 elif option == 'ign_set_control':
548 self._ignore_set_control_answer = True
cliechti7cb78e82009-08-05 15:47:57 +0000549 elif option == 'poll_modem':
550 self._poll_modem_state = True
cliechtidfe2d272009-08-10 22:19:41 +0000551 elif option == 'timeout':
552 self._network_timeout = float(value)
cliechti81c54762009-08-03 23:53:27 +0000553 else:
554 raise ValueError('unknown option: %r' % (option,))
555 # get host and port
cliechti8099bed2009-08-01 23:59:18 +0000556 host, port = url.split(':', 1) # may raise ValueError because of unpacking
557 port = int(port) # and this if it's not a number
558 if not 0 <= port < 65536: raise ValueError("port not in range 0...65535")
Chris Liechti68340d72015-08-03 14:15:48 +0200559 except ValueError as e:
cliechti81c54762009-08-03 23:53:27 +0000560 raise SerialException('expected a string in the form "[rfc2217://]<host>:<port>[/option[/option...]]": %s' % e)
cliechti8099bed2009-08-01 23:59:18 +0000561 return (host, port)
562
563 # - - - - - - - - - - - - - - - - - - - - - - - -
564
565 def inWaiting(self):
566 """Return the number of characters currently in the input buffer."""
567 if not self._isOpen: raise portNotOpenError
568 return self._read_buffer.qsize()
569
570 def read(self, size=1):
cliechtieada4fd2013-07-31 16:26:07 +0000571 """\
572 Read size bytes from the serial port. If a timeout is set it may
cliechti8099bed2009-08-01 23:59:18 +0000573 return less characters as requested. With no timeout it will block
cliechtieada4fd2013-07-31 16:26:07 +0000574 until the requested number of bytes is read.
575 """
cliechti8099bed2009-08-01 23:59:18 +0000576 if not self._isOpen: raise portNotOpenError
577 data = bytearray()
578 try:
579 while len(data) < size:
cliechti81c54762009-08-03 23:53:27 +0000580 if self._thread is None:
581 raise SerialException('connection failed (reader thread died)')
cliechti8099bed2009-08-01 23:59:18 +0000582 data.append(self._read_buffer.get(True, self._timeout))
583 except Queue.Empty: # -> timeout
584 pass
585 return bytes(data)
586
587 def write(self, data):
cliechtieada4fd2013-07-31 16:26:07 +0000588 """\
589 Output the given string over the serial port. Can block if the
cliechti8099bed2009-08-01 23:59:18 +0000590 connection is blocked. May raise SerialException if the connection is
cliechtieada4fd2013-07-31 16:26:07 +0000591 closed.
592 """
cliechti8099bed2009-08-01 23:59:18 +0000593 if not self._isOpen: raise portNotOpenError
cliechti81c54762009-08-03 23:53:27 +0000594 self._write_lock.acquire()
cliechti8099bed2009-08-01 23:59:18 +0000595 try:
cliechti81c54762009-08-03 23:53:27 +0000596 try:
cliechti38077122013-10-16 02:57:27 +0000597 self._socket.sendall(to_bytes(data).replace(IAC, IAC_DOUBLED))
Chris Liechti68340d72015-08-03 14:15:48 +0200598 except socket.error as e:
cliechticb20a4f2011-04-25 02:25:54 +0000599 raise SerialException("connection failed (socket error): %s" % e) # XXX what exception if socket connection fails
cliechti81c54762009-08-03 23:53:27 +0000600 finally:
601 self._write_lock.release()
cliechti8099bed2009-08-01 23:59:18 +0000602 return len(data)
603
604 def flushInput(self):
605 """Clear input buffer, discarding all that is in the buffer."""
606 if not self._isOpen: raise portNotOpenError
cliechti1ef7e3e2009-08-03 02:38:43 +0000607 self.rfc2217SendPurge(PURGE_RECEIVE_BUFFER)
cliechti8099bed2009-08-01 23:59:18 +0000608 # empty read buffer
609 while self._read_buffer.qsize():
610 self._read_buffer.get(False)
611
612 def flushOutput(self):
cliechtieada4fd2013-07-31 16:26:07 +0000613 """\
614 Clear output buffer, aborting the current output and
615 discarding all that is in the buffer.
616 """
cliechti8099bed2009-08-01 23:59:18 +0000617 if not self._isOpen: raise portNotOpenError
cliechti1ef7e3e2009-08-03 02:38:43 +0000618 self.rfc2217SendPurge(PURGE_TRANSMIT_BUFFER)
cliechti8099bed2009-08-01 23:59:18 +0000619
620 def sendBreak(self, duration=0.25):
cliechti7d448562014-08-03 21:57:45 +0000621 """\
622 Send break condition. Timed, returns to idle state after given
623 duration.
624 """
cliechti8099bed2009-08-01 23:59:18 +0000625 if not self._isOpen: raise portNotOpenError
626 self.setBreak(True)
627 time.sleep(duration)
628 self.setBreak(False)
629
630 def setBreak(self, level=True):
cliechtieada4fd2013-07-31 16:26:07 +0000631 """\
632 Set break: Controls TXD. When active, to transmitting is
633 possible.
634 """
cliechti8099bed2009-08-01 23:59:18 +0000635 if not self._isOpen: raise portNotOpenError
cliechti6a300772009-08-12 02:28:56 +0000636 if self.logger:
637 self.logger.info('set BREAK to %s' % ('inactive', 'active')[bool(level)])
cliechti8099bed2009-08-01 23:59:18 +0000638 if level:
cliechti1ef7e3e2009-08-03 02:38:43 +0000639 self.rfc2217SetControl(SET_CONTROL_BREAK_ON)
cliechti8099bed2009-08-01 23:59:18 +0000640 else:
cliechti1ef7e3e2009-08-03 02:38:43 +0000641 self.rfc2217SetControl(SET_CONTROL_BREAK_OFF)
cliechti8099bed2009-08-01 23:59:18 +0000642
643 def setRTS(self, level=True):
cliechti044d8662009-08-11 21:40:31 +0000644 """Set terminal status line: Request To Send."""
cliechti8099bed2009-08-01 23:59:18 +0000645 if not self._isOpen: raise portNotOpenError
cliechti6a300772009-08-12 02:28:56 +0000646 if self.logger:
647 self.logger.info('set RTS to %s' % ('inactive', 'active')[bool(level)])
cliechti8099bed2009-08-01 23:59:18 +0000648 if level:
cliechti1ef7e3e2009-08-03 02:38:43 +0000649 self.rfc2217SetControl(SET_CONTROL_RTS_ON)
cliechti8099bed2009-08-01 23:59:18 +0000650 else:
cliechti1ef7e3e2009-08-03 02:38:43 +0000651 self.rfc2217SetControl(SET_CONTROL_RTS_OFF)
cliechti8099bed2009-08-01 23:59:18 +0000652
653 def setDTR(self, level=True):
cliechti044d8662009-08-11 21:40:31 +0000654 """Set terminal status line: Data Terminal Ready."""
cliechti8099bed2009-08-01 23:59:18 +0000655 if not self._isOpen: raise portNotOpenError
cliechti6a300772009-08-12 02:28:56 +0000656 if self.logger:
657 self.logger.info('set DTR to %s' % ('inactive', 'active')[bool(level)])
cliechti8099bed2009-08-01 23:59:18 +0000658 if level:
cliechti1ef7e3e2009-08-03 02:38:43 +0000659 self.rfc2217SetControl(SET_CONTROL_DTR_ON)
cliechti8099bed2009-08-01 23:59:18 +0000660 else:
cliechti1ef7e3e2009-08-03 02:38:43 +0000661 self.rfc2217SetControl(SET_CONTROL_DTR_OFF)
cliechti8099bed2009-08-01 23:59:18 +0000662
663 def getCTS(self):
cliechti044d8662009-08-11 21:40:31 +0000664 """Read terminal status line: Clear To Send."""
cliechti8099bed2009-08-01 23:59:18 +0000665 if not self._isOpen: raise portNotOpenError
cliechti7cb78e82009-08-05 15:47:57 +0000666 return bool(self.getModemState() & MODEMSTATE_MASK_CTS)
cliechti8099bed2009-08-01 23:59:18 +0000667
668 def getDSR(self):
cliechti044d8662009-08-11 21:40:31 +0000669 """Read terminal status line: Data Set Ready."""
cliechti8099bed2009-08-01 23:59:18 +0000670 if not self._isOpen: raise portNotOpenError
cliechti7cb78e82009-08-05 15:47:57 +0000671 return bool(self.getModemState() & MODEMSTATE_MASK_DSR)
cliechti8099bed2009-08-01 23:59:18 +0000672
673 def getRI(self):
cliechti044d8662009-08-11 21:40:31 +0000674 """Read terminal status line: Ring Indicator."""
cliechti8099bed2009-08-01 23:59:18 +0000675 if not self._isOpen: raise portNotOpenError
cliechti7cb78e82009-08-05 15:47:57 +0000676 return bool(self.getModemState() & MODEMSTATE_MASK_RI)
cliechti8099bed2009-08-01 23:59:18 +0000677
678 def getCD(self):
cliechti044d8662009-08-11 21:40:31 +0000679 """Read terminal status line: Carrier Detect."""
cliechti8099bed2009-08-01 23:59:18 +0000680 if not self._isOpen: raise portNotOpenError
cliechti7cb78e82009-08-05 15:47:57 +0000681 return bool(self.getModemState() & MODEMSTATE_MASK_CD)
cliechti8099bed2009-08-01 23:59:18 +0000682
683 # - - - platform specific - - -
684 # None so far
685
686 # - - - RFC2217 specific - - -
687
cliechti1ef7e3e2009-08-03 02:38:43 +0000688 def _telnetReadLoop(self):
cliechti7d448562014-08-03 21:57:45 +0000689 """Read loop for the socket."""
cliechti8099bed2009-08-01 23:59:18 +0000690 mode = M_NORMAL
691 suboption = None
cliechti81c54762009-08-03 23:53:27 +0000692 try:
693 while self._socket is not None:
694 try:
695 data = self._socket.recv(1024)
696 except socket.timeout:
697 # just need to get out of recv form time to time to check if
698 # still alive
699 continue
Chris Liechti68340d72015-08-03 14:15:48 +0200700 except socket.error as e:
cliechti81c54762009-08-03 23:53:27 +0000701 # connection fails -> terminate loop
cliechticb20a4f2011-04-25 02:25:54 +0000702 if self.logger:
703 self.logger.debug("socket error in reader thread: %s" % (e,))
cliechti81c54762009-08-03 23:53:27 +0000704 break
cliechticb20a4f2011-04-25 02:25:54 +0000705 if not data: break # lost connection
cliechti81c54762009-08-03 23:53:27 +0000706 for byte in data:
707 if mode == M_NORMAL:
708 # interpret as command or as data
709 if byte == IAC:
710 mode = M_IAC_SEEN
cliechti8099bed2009-08-01 23:59:18 +0000711 else:
cliechti81c54762009-08-03 23:53:27 +0000712 # store data in read buffer or sub option buffer
713 # depending on state
714 if suboption is not None:
715 suboption.append(byte)
716 else:
717 self._read_buffer.put(byte)
718 elif mode == M_IAC_SEEN:
719 if byte == IAC:
720 # interpret as command doubled -> insert character
721 # itself
cliechtif325c032009-12-25 16:09:49 +0000722 if suboption is not None:
723 suboption.append(IAC)
724 else:
725 self._read_buffer.put(IAC)
cliechti81c54762009-08-03 23:53:27 +0000726 mode = M_NORMAL
727 elif byte == SB:
728 # sub option start
729 suboption = bytearray()
730 mode = M_NORMAL
731 elif byte == SE:
732 # sub option end -> process it now
733 self._telnetProcessSubnegotiation(bytes(suboption))
734 suboption = None
735 mode = M_NORMAL
736 elif byte in (DO, DONT, WILL, WONT):
737 # negotiation
738 telnet_command = byte
739 mode = M_NEGOTIATE
740 else:
741 # other telnet commands
742 self._telnetProcessCommand(byte)
743 mode = M_NORMAL
744 elif mode == M_NEGOTIATE: # DO, DONT, WILL, WONT was received, option now following
745 self._telnetNegotiateOption(telnet_command, byte)
cliechti8099bed2009-08-01 23:59:18 +0000746 mode = M_NORMAL
cliechti81c54762009-08-03 23:53:27 +0000747 finally:
748 self._thread = None
cliechti6a300772009-08-12 02:28:56 +0000749 if self.logger:
750 self.logger.debug("read thread terminated")
cliechti8099bed2009-08-01 23:59:18 +0000751
752 # - incoming telnet commands and options
753
cliechti1ef7e3e2009-08-03 02:38:43 +0000754 def _telnetProcessCommand(self, command):
cliechti044d8662009-08-11 21:40:31 +0000755 """Process commands other than DO, DONT, WILL, WONT."""
cliechti1ef7e3e2009-08-03 02:38:43 +0000756 # Currently none. RFC2217 only uses negotiation and subnegotiation.
cliechti6a300772009-08-12 02:28:56 +0000757 if self.logger:
758 self.logger.warning("ignoring Telnet command: %r" % (command,))
cliechti8099bed2009-08-01 23:59:18 +0000759
cliechti1ef7e3e2009-08-03 02:38:43 +0000760 def _telnetNegotiateOption(self, command, option):
cliechti044d8662009-08-11 21:40:31 +0000761 """Process incoming DO, DONT, WILL, WONT."""
cliechti2b929b72009-08-02 23:49:02 +0000762 # check our registered telnet options and forward command to them
763 # they know themselves if they have to answer or not
cliechtiac205322009-08-02 20:40:21 +0000764 known = False
765 for item in self._telnet_options:
cliechti2b929b72009-08-02 23:49:02 +0000766 # can have more than one match! as some options are duplicated for
767 # 'us' and 'them'
cliechtiac205322009-08-02 20:40:21 +0000768 if item.option == option:
cliechti2b929b72009-08-02 23:49:02 +0000769 item.process_incoming(command)
cliechtiac205322009-08-02 20:40:21 +0000770 known = True
771 if not known:
772 # handle unknown options
773 # only answer to positive requests and deny them
774 if command == WILL or command == DO:
cliechti1ef7e3e2009-08-03 02:38:43 +0000775 self.telnetSendOption((command == WILL and DONT or WONT), option)
cliechti6a300772009-08-12 02:28:56 +0000776 if self.logger:
777 self.logger.warning("rejected Telnet option: %r" % (option,))
cliechtiac205322009-08-02 20:40:21 +0000778
cliechti8099bed2009-08-01 23:59:18 +0000779
cliechti1ef7e3e2009-08-03 02:38:43 +0000780 def _telnetProcessSubnegotiation(self, suboption):
cliechti044d8662009-08-11 21:40:31 +0000781 """Process subnegotiation, the data between IAC SB and IAC SE."""
cliechti8099bed2009-08-01 23:59:18 +0000782 if suboption[0:1] == COM_PORT_OPTION:
783 if suboption[1:2] == SERVER_NOTIFY_LINESTATE and len(suboption) >= 3:
cliechti672d0292009-08-03 02:01:57 +0000784 self._linestate = ord(suboption[2:3]) # ensure it is a number
cliechti6a300772009-08-12 02:28:56 +0000785 if self.logger:
786 self.logger.info("NOTIFY_LINESTATE: %s" % self._linestate)
cliechti8099bed2009-08-01 23:59:18 +0000787 elif suboption[1:2] == SERVER_NOTIFY_MODEMSTATE and len(suboption) >= 3:
cliechti672d0292009-08-03 02:01:57 +0000788 self._modemstate = ord(suboption[2:3]) # ensure it is a number
cliechti6a300772009-08-12 02:28:56 +0000789 if self.logger:
790 self.logger.info("NOTIFY_MODEMSTATE: %s" % self._modemstate)
cliechti7cb78e82009-08-05 15:47:57 +0000791 # update time when we think that a poll would make sense
792 self._modemstate_expires = time.time() + 0.3
cliechti672d0292009-08-03 02:01:57 +0000793 elif suboption[1:2] == FLOWCONTROL_SUSPEND:
794 self._remote_suspend_flow = True
795 elif suboption[1:2] == FLOWCONTROL_RESUME:
796 self._remote_suspend_flow = False
cliechti8099bed2009-08-01 23:59:18 +0000797 else:
cliechti2b929b72009-08-02 23:49:02 +0000798 for item in self._rfc2217_options.values():
799 if item.ack_option == suboption[1:2]:
cliechti81c54762009-08-03 23:53:27 +0000800 #~ print "processing COM_PORT_OPTION: %r" % list(suboption[1:])
cliechti2b929b72009-08-02 23:49:02 +0000801 item.checkAnswer(bytes(suboption[2:]))
802 break
803 else:
cliechti6a300772009-08-12 02:28:56 +0000804 if self.logger:
805 self.logger.warning("ignoring COM_PORT_OPTION: %r" % (suboption,))
cliechti8099bed2009-08-01 23:59:18 +0000806 else:
cliechti6a300772009-08-12 02:28:56 +0000807 if self.logger:
808 self.logger.warning("ignoring subnegotiation: %r" % (suboption,))
cliechti8099bed2009-08-01 23:59:18 +0000809
810 # - outgoing telnet commands and options
811
cliechti81c54762009-08-03 23:53:27 +0000812 def _internal_raw_write(self, data):
cliechti044d8662009-08-11 21:40:31 +0000813 """internal socket write with no data escaping. used to send telnet stuff."""
cliechti81c54762009-08-03 23:53:27 +0000814 self._write_lock.acquire()
815 try:
816 self._socket.sendall(data)
817 finally:
818 self._write_lock.release()
819
cliechti1ef7e3e2009-08-03 02:38:43 +0000820 def telnetSendOption(self, action, option):
cliechti044d8662009-08-11 21:40:31 +0000821 """Send DO, DONT, WILL, WONT."""
cliechti81c54762009-08-03 23:53:27 +0000822 self._internal_raw_write(to_bytes([IAC, action, option]))
cliechti8099bed2009-08-01 23:59:18 +0000823
cliechtif325c032009-12-25 16:09:49 +0000824 def rfc2217SendSubnegotiation(self, option, value=''):
cliechti044d8662009-08-11 21:40:31 +0000825 """Subnegotiation of RFC2217 parameters."""
cliechtif325c032009-12-25 16:09:49 +0000826 value = value.replace(IAC, IAC_DOUBLED)
cliechti81c54762009-08-03 23:53:27 +0000827 self._internal_raw_write(to_bytes([IAC, SB, COM_PORT_OPTION, option] + list(value) + [IAC, SE]))
cliechti2b929b72009-08-02 23:49:02 +0000828
cliechti1ef7e3e2009-08-03 02:38:43 +0000829 def rfc2217SendPurge(self, value):
cliechti2b929b72009-08-02 23:49:02 +0000830 item = self._rfc2217_options['purge']
cliechti672d0292009-08-03 02:01:57 +0000831 item.set(value) # transmit desired purge type
cliechtidfe2d272009-08-10 22:19:41 +0000832 item.wait(self._network_timeout) # wait for acknowledge from the server
cliechti2b929b72009-08-02 23:49:02 +0000833
cliechti1ef7e3e2009-08-03 02:38:43 +0000834 def rfc2217SetControl(self, value):
cliechti81c54762009-08-03 23:53:27 +0000835 item = self._rfc2217_options['control']
cliechticb20a4f2011-04-25 02:25:54 +0000836 item.set(value) # transmit desired control type
cliechti81c54762009-08-03 23:53:27 +0000837 if self._ignore_set_control_answer:
838 # answers are ignored when option is set. compatibility mode for
cliechticb20a4f2011-04-25 02:25:54 +0000839 # servers that answer, but not the expected one... (or no answer
cliechti81c54762009-08-03 23:53:27 +0000840 # at all) i.e. sredird
841 time.sleep(0.1) # this helps getting the unit tests passed
842 else:
cliechtidfe2d272009-08-10 22:19:41 +0000843 item.wait(self._network_timeout) # wait for acknowledge from the server
cliechti8099bed2009-08-01 23:59:18 +0000844
cliechti1ef7e3e2009-08-03 02:38:43 +0000845 def rfc2217FlowServerReady(self):
cliechtieada4fd2013-07-31 16:26:07 +0000846 """\
847 check if server is ready to receive data. block for some time when
848 not.
849 """
cliechti672d0292009-08-03 02:01:57 +0000850 #~ if self._remote_suspend_flow:
851 #~ wait---
852
cliechti7cb78e82009-08-05 15:47:57 +0000853 def getModemState(self):
cliechtieada4fd2013-07-31 16:26:07 +0000854 """\
cliechti7d448562014-08-03 21:57:45 +0000855 get last modem state (cached value. If value is "old", request a new
856 one. This cache helps that we don't issue to many requests when e.g. all
857 status lines, one after the other is queried by the user (getCTS, getDSR
cliechtieada4fd2013-07-31 16:26:07 +0000858 etc.)
859 """
cliechti7cb78e82009-08-05 15:47:57 +0000860 # active modem state polling enabled? is the value fresh enough?
861 if self._poll_modem_state and self._modemstate_expires < time.time():
cliechti6a300772009-08-12 02:28:56 +0000862 if self.logger:
863 self.logger.debug('polling modem state')
cliechti7cb78e82009-08-05 15:47:57 +0000864 # when it is older, request an update
865 self.rfc2217SendSubnegotiation(NOTIFY_MODEMSTATE)
cliechtidfe2d272009-08-10 22:19:41 +0000866 timeout_time = time.time() + self._network_timeout
cliechti7cb78e82009-08-05 15:47:57 +0000867 while time.time() < timeout_time:
868 time.sleep(0.05) # prevent 100% CPU load
869 # when expiration time is updated, it means that there is a new
870 # value
871 if self._modemstate_expires > time.time():
cliechti6a300772009-08-12 02:28:56 +0000872 if self.logger:
873 self.logger.warning('poll for modem state failed')
cliechti7cb78e82009-08-05 15:47:57 +0000874 break
875 # even when there is a timeout, do not generate an error just
876 # return the last known value. this way we can support buggy
877 # servers that do not respond to polls, but send automatic
878 # updates.
879 if self._modemstate is not None:
cliechti6a300772009-08-12 02:28:56 +0000880 if self.logger:
881 self.logger.debug('using cached modem state')
cliechti7cb78e82009-08-05 15:47:57 +0000882 return self._modemstate
883 else:
884 # never received a notification from the server
cliechti8fb119c2009-08-05 23:39:45 +0000885 raise SerialException("remote sends no NOTIFY_MODEMSTATE")
cliechti8099bed2009-08-01 23:59:18 +0000886
cliechti5cc3eb12009-08-11 23:04:30 +0000887
cliechti8099bed2009-08-01 23:59:18 +0000888# assemble Serial class with the platform specific implementation and the base
889# for file-like behavior. for Python 2.6 and newer, that provide the new I/O
890# library, derive from io.RawIOBase
891try:
892 import io
893except ImportError:
894 # classic version with our own file-like emulation
895 class Serial(RFC2217Serial, FileLike):
896 pass
897else:
898 # io library present
899 class Serial(RFC2217Serial, io.RawIOBase):
900 pass
901
cliechti7cb78e82009-08-05 15:47:57 +0000902
cliechti595ed5b2009-08-10 01:43:32 +0000903#############################################################################
cliechti5cc3eb12009-08-11 23:04:30 +0000904# The following is code that helps implementing an RFC 2217 server.
cliechti8099bed2009-08-01 23:59:18 +0000905
cliechti8ccc2ff2009-08-05 12:44:46 +0000906class PortManager(object):
cliechtieada4fd2013-07-31 16:26:07 +0000907 """\
908 This class manages the state of Telnet and RFC 2217. It needs a serial
cliechticb20a4f2011-04-25 02:25:54 +0000909 instance and a connection to work with. Connection is expected to implement
cliechtieada4fd2013-07-31 16:26:07 +0000910 a (thread safe) write function, that writes the string to the network.
911 """
cliechti130d1f02009-08-04 02:10:58 +0000912
cliechti6a300772009-08-12 02:28:56 +0000913 def __init__(self, serial_port, connection, logger=None):
cliechti130d1f02009-08-04 02:10:58 +0000914 self.serial = serial_port
915 self.connection = connection
cliechti6a300772009-08-12 02:28:56 +0000916 self.logger = logger
cliechti86b593e2009-08-05 16:28:12 +0000917 self._client_is_rfc2217 = False
cliechti130d1f02009-08-04 02:10:58 +0000918
919 # filter state machine
920 self.mode = M_NORMAL
921 self.suboption = None
922 self.telnet_command = None
923
924 # states for modem/line control events
925 self.modemstate_mask = 255
926 self.last_modemstate = None
927 self.linstate_mask = 0
928
929 # all supported telnet options
930 self._telnet_options = [
931 TelnetOption(self, 'ECHO', ECHO, WILL, WONT, DO, DONT, REQUESTED),
932 TelnetOption(self, 'we-SGA', SGA, WILL, WONT, DO, DONT, REQUESTED),
933 TelnetOption(self, 'they-SGA', SGA, DO, DONT, WILL, WONT, INACTIVE),
934 TelnetOption(self, 'we-BINARY', BINARY, WILL, WONT, DO, DONT, INACTIVE),
935 TelnetOption(self, 'they-BINARY', BINARY, DO, DONT, WILL, WONT, REQUESTED),
cliechti86b593e2009-08-05 16:28:12 +0000936 TelnetOption(self, 'we-RFC2217', COM_PORT_OPTION, WILL, WONT, DO, DONT, REQUESTED, self._client_ok),
937 TelnetOption(self, 'they-RFC2217', COM_PORT_OPTION, DO, DONT, WILL, WONT, INACTIVE, self._client_ok),
cliechti130d1f02009-08-04 02:10:58 +0000938 ]
939
940 # negotiate Telnet/RFC2217 -> send initial requests
cliechti6a300772009-08-12 02:28:56 +0000941 if self.logger:
942 self.logger.debug("requesting initial Telnet/RFC 2217 options")
cliechti130d1f02009-08-04 02:10:58 +0000943 for option in self._telnet_options:
944 if option.state is REQUESTED:
945 self.telnetSendOption(option.send_yes, option.option)
946 # issue 1st modem state notification
cliechti86b593e2009-08-05 16:28:12 +0000947
948 def _client_ok(self):
cliechtieada4fd2013-07-31 16:26:07 +0000949 """\
cliechti7d448562014-08-03 21:57:45 +0000950 callback of telnet option. It gets called when option is activated.
951 This one here is used to detect when the client agrees on RFC 2217. A
cliechti86b593e2009-08-05 16:28:12 +0000952 flag is set so that other functions like check_modem_lines know if the
cliechti7d448562014-08-03 21:57:45 +0000953 client is OK.
cliechtieada4fd2013-07-31 16:26:07 +0000954 """
cliechti86b593e2009-08-05 16:28:12 +0000955 # The callback is used for we and they so if one party agrees, we're
956 # already happy. it seems not all servers do the negotiation correctly
957 # and i guess there are incorrect clients too.. so be happy if client
958 # answers one or the other positively.
959 self._client_is_rfc2217 = True
cliechti6a300772009-08-12 02:28:56 +0000960 if self.logger:
961 self.logger.info("client accepts RFC 2217")
cliechti8fb119c2009-08-05 23:39:45 +0000962 # this is to ensure that the client gets a notification, even if there
963 # was no change
964 self.check_modem_lines(force_notification=True)
cliechti130d1f02009-08-04 02:10:58 +0000965
966 # - outgoing telnet commands and options
967
968 def telnetSendOption(self, action, option):
cliechti044d8662009-08-11 21:40:31 +0000969 """Send DO, DONT, WILL, WONT."""
cliechti130d1f02009-08-04 02:10:58 +0000970 self.connection.write(to_bytes([IAC, action, option]))
971
cliechtif325c032009-12-25 16:09:49 +0000972 def rfc2217SendSubnegotiation(self, option, value=''):
cliechti044d8662009-08-11 21:40:31 +0000973 """Subnegotiation of RFC 2217 parameters."""
cliechtif325c032009-12-25 16:09:49 +0000974 value = value.replace(IAC, IAC_DOUBLED)
cliechti130d1f02009-08-04 02:10:58 +0000975 self.connection.write(to_bytes([IAC, SB, COM_PORT_OPTION, option] + list(value) + [IAC, SE]))
976
977 # - check modem lines, needs to be called periodically from user to
978 # establish polling
979
cliechti7cb78e82009-08-05 15:47:57 +0000980 def check_modem_lines(self, force_notification=False):
cliechti130d1f02009-08-04 02:10:58 +0000981 modemstate = (
982 (self.serial.getCTS() and MODEMSTATE_MASK_CTS) |
983 (self.serial.getDSR() and MODEMSTATE_MASK_DSR) |
984 (self.serial.getRI() and MODEMSTATE_MASK_RI) |
985 (self.serial.getCD() and MODEMSTATE_MASK_CD)
986 )
cliechti7cb78e82009-08-05 15:47:57 +0000987 # check what has changed
988 deltas = modemstate ^ (self.last_modemstate or 0) # when last is None -> 0
989 if deltas & MODEMSTATE_MASK_CTS:
990 modemstate |= MODEMSTATE_MASK_CTS_CHANGE
991 if deltas & MODEMSTATE_MASK_DSR:
992 modemstate |= MODEMSTATE_MASK_DSR_CHANGE
993 if deltas & MODEMSTATE_MASK_RI:
994 modemstate |= MODEMSTATE_MASK_RI_CHANGE
995 if deltas & MODEMSTATE_MASK_CD:
996 modemstate |= MODEMSTATE_MASK_CD_CHANGE
997 # if new state is different and the mask allows this change, send
cliechti86b593e2009-08-05 16:28:12 +0000998 # notification. suppress notifications when client is not rfc2217
cliechti7cb78e82009-08-05 15:47:57 +0000999 if modemstate != self.last_modemstate or force_notification:
cliechti8fb119c2009-08-05 23:39:45 +00001000 if (self._client_is_rfc2217 and (modemstate & self.modemstate_mask)) or force_notification:
cliechti7cb78e82009-08-05 15:47:57 +00001001 self.rfc2217SendSubnegotiation(
1002 SERVER_NOTIFY_MODEMSTATE,
1003 to_bytes([modemstate & self.modemstate_mask])
1004 )
cliechti6a300772009-08-12 02:28:56 +00001005 if self.logger:
1006 self.logger.info("NOTIFY_MODEMSTATE: %s" % (modemstate,))
cliechti7cb78e82009-08-05 15:47:57 +00001007 # save last state, but forget about deltas.
1008 # otherwise it would also notify about changing deltas which is
1009 # probably not very useful
1010 self.last_modemstate = modemstate & 0xf0
cliechti130d1f02009-08-04 02:10:58 +00001011
cliechti32c10332009-08-05 13:23:43 +00001012 # - outgoing data escaping
1013
1014 def escape(self, data):
cliechtieada4fd2013-07-31 16:26:07 +00001015 """\
cliechti7d448562014-08-03 21:57:45 +00001016 This generator function is for the user. All outgoing data has to be
cliechticb20a4f2011-04-25 02:25:54 +00001017 properly escaped, so that no IAC character in the data stream messes up
1018 the Telnet state machine in the server.
cliechti32c10332009-08-05 13:23:43 +00001019
1020 socket.sendall(escape(data))
1021 """
1022 for byte in data:
1023 if byte == IAC:
1024 yield IAC
1025 yield IAC
1026 else:
1027 yield byte
1028
cliechti130d1f02009-08-04 02:10:58 +00001029 # - incoming data filter
1030
1031 def filter(self, data):
cliechtieada4fd2013-07-31 16:26:07 +00001032 """\
cliechti7d448562014-08-03 21:57:45 +00001033 Handle a bunch of incoming bytes. This is a generator. It will yield
cliechti044d8662009-08-11 21:40:31 +00001034 all characters not of interest for Telnet/RFC 2217.
cliechti130d1f02009-08-04 02:10:58 +00001035
1036 The idea is that the reader thread pushes data from the socket through
1037 this filter:
1038
1039 for byte in filter(socket.recv(1024)):
1040 # do things like CR/LF conversion/whatever
1041 # and write data to the serial port
1042 serial.write(byte)
1043
1044 (socket error handling code left as exercise for the reader)
1045 """
1046 for byte in data:
1047 if self.mode == M_NORMAL:
1048 # interpret as command or as data
1049 if byte == IAC:
1050 self.mode = M_IAC_SEEN
1051 else:
1052 # store data in sub option buffer or pass it to our
1053 # consumer depending on state
1054 if self.suboption is not None:
1055 self.suboption.append(byte)
1056 else:
1057 yield byte
1058 elif self.mode == M_IAC_SEEN:
1059 if byte == IAC:
1060 # interpret as command doubled -> insert character
1061 # itself
cliechtif325c032009-12-25 16:09:49 +00001062 if self.suboption is not None:
1063 self.suboption.append(byte)
1064 else:
1065 yield byte
cliechti130d1f02009-08-04 02:10:58 +00001066 self.mode = M_NORMAL
1067 elif byte == SB:
1068 # sub option start
1069 self.suboption = bytearray()
1070 self.mode = M_NORMAL
1071 elif byte == SE:
1072 # sub option end -> process it now
1073 self._telnetProcessSubnegotiation(bytes(self.suboption))
1074 self.suboption = None
1075 self.mode = M_NORMAL
1076 elif byte in (DO, DONT, WILL, WONT):
1077 # negotiation
1078 self.telnet_command = byte
1079 self.mode = M_NEGOTIATE
1080 else:
1081 # other telnet commands
1082 self._telnetProcessCommand(byte)
1083 self.mode = M_NORMAL
1084 elif self.mode == M_NEGOTIATE: # DO, DONT, WILL, WONT was received, option now following
1085 self._telnetNegotiateOption(self.telnet_command, byte)
1086 self.mode = M_NORMAL
1087
1088 # - incoming telnet commands and options
1089
1090 def _telnetProcessCommand(self, command):
cliechti044d8662009-08-11 21:40:31 +00001091 """Process commands other than DO, DONT, WILL, WONT."""
cliechti130d1f02009-08-04 02:10:58 +00001092 # Currently none. RFC2217 only uses negotiation and subnegotiation.
cliechti6a300772009-08-12 02:28:56 +00001093 if self.logger:
1094 self.logger.warning("ignoring Telnet command: %r" % (command,))
cliechti130d1f02009-08-04 02:10:58 +00001095
1096 def _telnetNegotiateOption(self, command, option):
cliechti044d8662009-08-11 21:40:31 +00001097 """Process incoming DO, DONT, WILL, WONT."""
cliechti130d1f02009-08-04 02:10:58 +00001098 # check our registered telnet options and forward command to them
1099 # they know themselves if they have to answer or not
1100 known = False
1101 for item in self._telnet_options:
1102 # can have more than one match! as some options are duplicated for
1103 # 'us' and 'them'
1104 if item.option == option:
1105 item.process_incoming(command)
1106 known = True
1107 if not known:
1108 # handle unknown options
1109 # only answer to positive requests and deny them
1110 if command == WILL or command == DO:
1111 self.telnetSendOption((command == WILL and DONT or WONT), option)
cliechti6a300772009-08-12 02:28:56 +00001112 if self.logger:
1113 self.logger.warning("rejected Telnet option: %r" % (option,))
cliechti130d1f02009-08-04 02:10:58 +00001114
1115
1116 def _telnetProcessSubnegotiation(self, suboption):
cliechti044d8662009-08-11 21:40:31 +00001117 """Process subnegotiation, the data between IAC SB and IAC SE."""
cliechti130d1f02009-08-04 02:10:58 +00001118 if suboption[0:1] == COM_PORT_OPTION:
cliechti6a300772009-08-12 02:28:56 +00001119 if self.logger:
1120 self.logger.debug('received COM_PORT_OPTION: %r' % (suboption,))
cliechti130d1f02009-08-04 02:10:58 +00001121 if suboption[1:2] == SET_BAUDRATE:
1122 backup = self.serial.baudrate
1123 try:
cliechtieada4fd2013-07-31 16:26:07 +00001124 (baudrate,) = struct.unpack("!I", suboption[2:6])
1125 if baudrate != 0:
1126 self.serial.baudrate = baudrate
cliechti130d1f02009-08-04 02:10:58 +00001127 except ValueError, e:
cliechti6a300772009-08-12 02:28:56 +00001128 if self.logger:
1129 self.logger.error("failed to set baud rate: %s" % (e,))
cliechti130d1f02009-08-04 02:10:58 +00001130 self.serial.baudrate = backup
cliechti5cc3eb12009-08-11 23:04:30 +00001131 else:
cliechti6a300772009-08-12 02:28:56 +00001132 if self.logger:
cliechtieada4fd2013-07-31 16:26:07 +00001133 self.logger.info("%s baud rate: %s" % (baudrate and 'set' or 'get', self.serial.baudrate))
cliechti130d1f02009-08-04 02:10:58 +00001134 self.rfc2217SendSubnegotiation(SERVER_SET_BAUDRATE, struct.pack("!I", self.serial.baudrate))
1135 elif suboption[1:2] == SET_DATASIZE:
1136 backup = self.serial.bytesize
1137 try:
cliechtieada4fd2013-07-31 16:26:07 +00001138 (datasize,) = struct.unpack("!B", suboption[2:3])
1139 if datasize != 0:
1140 self.serial.bytesize = datasize
cliechti130d1f02009-08-04 02:10:58 +00001141 except ValueError, e:
cliechti6a300772009-08-12 02:28:56 +00001142 if self.logger:
1143 self.logger.error("failed to set data size: %s" % (e,))
cliechti130d1f02009-08-04 02:10:58 +00001144 self.serial.bytesize = backup
cliechti5cc3eb12009-08-11 23:04:30 +00001145 else:
cliechti6a300772009-08-12 02:28:56 +00001146 if self.logger:
cliechtieada4fd2013-07-31 16:26:07 +00001147 self.logger.info("%s data size: %s" % (datasize and 'set' or 'get', self.serial.bytesize))
cliechti130d1f02009-08-04 02:10:58 +00001148 self.rfc2217SendSubnegotiation(SERVER_SET_DATASIZE, struct.pack("!B", self.serial.bytesize))
1149 elif suboption[1:2] == SET_PARITY:
1150 backup = self.serial.parity
1151 try:
cliechtieada4fd2013-07-31 16:26:07 +00001152 parity = struct.unpack("!B", suboption[2:3])[0]
1153 if parity != 0:
1154 self.serial.parity = RFC2217_REVERSE_PARITY_MAP[parity]
cliechti130d1f02009-08-04 02:10:58 +00001155 except ValueError, e:
cliechti6a300772009-08-12 02:28:56 +00001156 if self.logger:
1157 self.logger.error("failed to set parity: %s" % (e,))
cliechti130d1f02009-08-04 02:10:58 +00001158 self.serial.parity = backup
cliechti5cc3eb12009-08-11 23:04:30 +00001159 else:
cliechti6a300772009-08-12 02:28:56 +00001160 if self.logger:
cliechtieada4fd2013-07-31 16:26:07 +00001161 self.logger.info("%s parity: %s" % (parity and 'set' or 'get', self.serial.parity))
cliechti130d1f02009-08-04 02:10:58 +00001162 self.rfc2217SendSubnegotiation(
1163 SERVER_SET_PARITY,
1164 struct.pack("!B", RFC2217_PARITY_MAP[self.serial.parity])
1165 )
1166 elif suboption[1:2] == SET_STOPSIZE:
1167 backup = self.serial.stopbits
1168 try:
cliechtieada4fd2013-07-31 16:26:07 +00001169 stopbits = struct.unpack("!B", suboption[2:3])[0]
1170 if stopbits != 0:
1171 self.serial.stopbits = RFC2217_REVERSE_STOPBIT_MAP[stopbits]
cliechti130d1f02009-08-04 02:10:58 +00001172 except ValueError, e:
cliechti6a300772009-08-12 02:28:56 +00001173 if self.logger:
1174 self.logger.error("failed to set stop bits: %s" % (e,))
cliechti130d1f02009-08-04 02:10:58 +00001175 self.serial.stopbits = backup
cliechti5cc3eb12009-08-11 23:04:30 +00001176 else:
cliechti6a300772009-08-12 02:28:56 +00001177 if self.logger:
cliechtieada4fd2013-07-31 16:26:07 +00001178 self.logger.info("%s stop bits: %s" % (stopbits and 'set' or 'get', self.serial.stopbits))
cliechti130d1f02009-08-04 02:10:58 +00001179 self.rfc2217SendSubnegotiation(
1180 SERVER_SET_STOPSIZE,
1181 struct.pack("!B", RFC2217_STOPBIT_MAP[self.serial.stopbits])
1182 )
1183 elif suboption[1:2] == SET_CONTROL:
1184 if suboption[2:3] == SET_CONTROL_REQ_FLOW_SETTING:
1185 if self.serial.xonxoff:
1186 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_SW_FLOW_CONTROL)
1187 elif self.serial.rtscts:
1188 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_HW_FLOW_CONTROL)
1189 else:
1190 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_NO_FLOW_CONTROL)
1191 elif suboption[2:3] == SET_CONTROL_USE_NO_FLOW_CONTROL:
1192 self.serial.xonxoff = False
1193 self.serial.rtscts = False
cliechti6a300772009-08-12 02:28:56 +00001194 if self.logger:
1195 self.logger.info("changed flow control to None")
cliechti130d1f02009-08-04 02:10:58 +00001196 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_NO_FLOW_CONTROL)
1197 elif suboption[2:3] == SET_CONTROL_USE_SW_FLOW_CONTROL:
1198 self.serial.xonxoff = True
cliechti6a300772009-08-12 02:28:56 +00001199 if self.logger:
1200 self.logger.info("changed flow control to XON/XOFF")
cliechti130d1f02009-08-04 02:10:58 +00001201 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_SW_FLOW_CONTROL)
1202 elif suboption[2:3] == SET_CONTROL_USE_HW_FLOW_CONTROL:
1203 self.serial.rtscts = True
cliechti6a300772009-08-12 02:28:56 +00001204 if self.logger:
1205 self.logger.info("changed flow control to RTS/CTS")
cliechti130d1f02009-08-04 02:10:58 +00001206 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_HW_FLOW_CONTROL)
1207 elif suboption[2:3] == SET_CONTROL_REQ_BREAK_STATE:
cliechti6a300772009-08-12 02:28:56 +00001208 if self.logger:
1209 self.logger.warning("requested break state - not implemented")
cliechti130d1f02009-08-04 02:10:58 +00001210 pass # XXX needs cached value
1211 elif suboption[2:3] == SET_CONTROL_BREAK_ON:
1212 self.serial.setBreak(True)
cliechti6a300772009-08-12 02:28:56 +00001213 if self.logger:
1214 self.logger.info("changed BREAK to active")
cliechti130d1f02009-08-04 02:10:58 +00001215 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_BREAK_ON)
1216 elif suboption[2:3] == SET_CONTROL_BREAK_OFF:
1217 self.serial.setBreak(False)
cliechti6a300772009-08-12 02:28:56 +00001218 if self.logger:
1219 self.logger.info("changed BREAK to inactive")
cliechti130d1f02009-08-04 02:10:58 +00001220 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_BREAK_OFF)
1221 elif suboption[2:3] == SET_CONTROL_REQ_DTR:
cliechti6a300772009-08-12 02:28:56 +00001222 if self.logger:
1223 self.logger.warning("requested DTR state - not implemented")
cliechti130d1f02009-08-04 02:10:58 +00001224 pass # XXX needs cached value
1225 elif suboption[2:3] == SET_CONTROL_DTR_ON:
1226 self.serial.setDTR(True)
cliechti6a300772009-08-12 02:28:56 +00001227 if self.logger:
1228 self.logger.info("changed DTR to active")
cliechti130d1f02009-08-04 02:10:58 +00001229 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_DTR_ON)
1230 elif suboption[2:3] == SET_CONTROL_DTR_OFF:
1231 self.serial.setDTR(False)
cliechti6a300772009-08-12 02:28:56 +00001232 if self.logger:
1233 self.logger.info("changed DTR to inactive")
cliechti130d1f02009-08-04 02:10:58 +00001234 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_DTR_OFF)
1235 elif suboption[2:3] == SET_CONTROL_REQ_RTS:
cliechti6a300772009-08-12 02:28:56 +00001236 if self.logger:
1237 self.logger.warning("requested RTS state - not implemented")
cliechti130d1f02009-08-04 02:10:58 +00001238 pass # XXX needs cached value
1239 #~ self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_RTS_ON)
1240 elif suboption[2:3] == SET_CONTROL_RTS_ON:
1241 self.serial.setRTS(True)
cliechti6a300772009-08-12 02:28:56 +00001242 if self.logger:
1243 self.logger.info("changed RTS to active")
cliechti130d1f02009-08-04 02:10:58 +00001244 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_RTS_ON)
1245 elif suboption[2:3] == SET_CONTROL_RTS_OFF:
1246 self.serial.setRTS(False)
cliechti6a300772009-08-12 02:28:56 +00001247 if self.logger:
1248 self.logger.info("changed RTS to inactive")
cliechti130d1f02009-08-04 02:10:58 +00001249 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_RTS_OFF)
1250 #~ elif suboption[2:3] == SET_CONTROL_REQ_FLOW_SETTING_IN:
1251 #~ elif suboption[2:3] == SET_CONTROL_USE_NO_FLOW_CONTROL_IN:
1252 #~ elif suboption[2:3] == SET_CONTROL_USE_SW_FLOW_CONTOL_IN:
1253 #~ elif suboption[2:3] == SET_CONTROL_USE_HW_FLOW_CONTOL_IN:
1254 #~ elif suboption[2:3] == SET_CONTROL_USE_DCD_FLOW_CONTROL:
1255 #~ elif suboption[2:3] == SET_CONTROL_USE_DTR_FLOW_CONTROL:
1256 #~ elif suboption[2:3] == SET_CONTROL_USE_DSR_FLOW_CONTROL:
1257 elif suboption[1:2] == NOTIFY_LINESTATE:
cliechti7cb78e82009-08-05 15:47:57 +00001258 # client polls for current state
1259 self.rfc2217SendSubnegotiation(
1260 SERVER_NOTIFY_LINESTATE,
cliechti6a300772009-08-12 02:28:56 +00001261 to_bytes([0]) # sorry, nothing like that implemented
cliechti7cb78e82009-08-05 15:47:57 +00001262 )
cliechti130d1f02009-08-04 02:10:58 +00001263 elif suboption[1:2] == NOTIFY_MODEMSTATE:
cliechti6a300772009-08-12 02:28:56 +00001264 if self.logger:
1265 self.logger.info("request for modem state")
cliechti7cb78e82009-08-05 15:47:57 +00001266 # client polls for current state
1267 self.check_modem_lines(force_notification=True)
cliechti130d1f02009-08-04 02:10:58 +00001268 elif suboption[1:2] == FLOWCONTROL_SUSPEND:
cliechti6a300772009-08-12 02:28:56 +00001269 if self.logger:
1270 self.logger.info("suspend")
cliechti130d1f02009-08-04 02:10:58 +00001271 self._remote_suspend_flow = True
1272 elif suboption[1:2] == FLOWCONTROL_RESUME:
cliechti6a300772009-08-12 02:28:56 +00001273 if self.logger:
1274 self.logger.info("resume")
cliechti130d1f02009-08-04 02:10:58 +00001275 self._remote_suspend_flow = False
1276 elif suboption[1:2] == SET_LINESTATE_MASK:
1277 self.linstate_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("line state mask: 0x%02x" % (self.linstate_mask,))
cliechti130d1f02009-08-04 02:10:58 +00001280 elif suboption[1:2] == SET_MODEMSTATE_MASK:
1281 self.modemstate_mask = ord(suboption[2:3]) # ensure it is a number
cliechti6a300772009-08-12 02:28:56 +00001282 if self.logger:
cliechtif325c032009-12-25 16:09:49 +00001283 self.logger.info("modem state mask: 0x%02x" % (self.modemstate_mask,))
cliechti130d1f02009-08-04 02:10:58 +00001284 elif suboption[1:2] == PURGE_DATA:
1285 if suboption[2:3] == PURGE_RECEIVE_BUFFER:
1286 self.serial.flushInput()
cliechti6a300772009-08-12 02:28:56 +00001287 if self.logger:
1288 self.logger.info("purge in")
cliechti130d1f02009-08-04 02:10:58 +00001289 self.rfc2217SendSubnegotiation(SERVER_PURGE_DATA, PURGE_RECEIVE_BUFFER)
1290 elif suboption[2:3] == PURGE_TRANSMIT_BUFFER:
1291 self.serial.flushOutput()
cliechti6a300772009-08-12 02:28:56 +00001292 if self.logger:
1293 self.logger.info("purge out")
cliechti130d1f02009-08-04 02:10:58 +00001294 self.rfc2217SendSubnegotiation(SERVER_PURGE_DATA, PURGE_TRANSMIT_BUFFER)
1295 elif suboption[2:3] == PURGE_BOTH_BUFFERS:
1296 self.serial.flushInput()
1297 self.serial.flushOutput()
cliechti6a300772009-08-12 02:28:56 +00001298 if self.logger:
1299 self.logger.info("purge both")
cliechti130d1f02009-08-04 02:10:58 +00001300 self.rfc2217SendSubnegotiation(SERVER_PURGE_DATA, PURGE_BOTH_BUFFERS)
1301 else:
cliechti6a300772009-08-12 02:28:56 +00001302 if self.logger:
1303 self.logger.error("undefined PURGE_DATA: %r" % list(suboption[2:]))
cliechti130d1f02009-08-04 02:10:58 +00001304 else:
cliechti6a300772009-08-12 02:28:56 +00001305 if self.logger:
1306 self.logger.error("undefined COM_PORT_OPTION: %r" % list(suboption[1:]))
cliechti130d1f02009-08-04 02:10:58 +00001307 else:
cliechti6a300772009-08-12 02:28:56 +00001308 if self.logger:
1309 self.logger.warning("unknown subnegotiation: %r" % (suboption,))
cliechti130d1f02009-08-04 02:10:58 +00001310
1311
1312# simple client test
cliechti8099bed2009-08-01 23:59:18 +00001313if __name__ == '__main__':
1314 import sys
1315 s = Serial('rfc2217://localhost:7000', 115200)
1316 sys.stdout.write('%s\n' % s)
1317
cliechti2b929b72009-08-02 23:49:02 +00001318 #~ s.baudrate = 1898
1319
cliechti8099bed2009-08-01 23:59:18 +00001320 sys.stdout.write("write...\n")
1321 s.write("hello\n")
1322 s.flush()
cliechti8099bed2009-08-01 23:59:18 +00001323 sys.stdout.write("read: %s\n" % s.read(5))
1324
1325 #~ s.baudrate = 19200
1326 #~ s.databits = 7
1327 s.close()