blob: 64bcdff95707efee2155e2fe935703d687356442 [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 *
Chris Liechtid2146002015-08-04 16:57:16 +020063import io
cliechti8099bed2009-08-01 23:59:18 +000064import time
65import struct
66import socket
67import threading
cliechti5cc3eb12009-08-11 23:04:30 +000068import logging
cliechti8099bed2009-08-01 23:59:18 +000069
Chris Liechtid2146002015-08-04 16:57:16 +020070try:
71 import Queue
72except ImportError:
73 import queue as Queue
74
cliechti8099bed2009-08-01 23:59:18 +000075# port string is expected to be something like this:
76# rfc2217://host:port
77# host may be an IP or including domain, whatever.
78# port is 0...65535
79
cliechti86844e82009-08-12 00:05:33 +000080# map log level names to constants. used in fromURL()
cliechti5cc3eb12009-08-11 23:04:30 +000081LOGGER_LEVELS = {
82 'debug': logging.DEBUG,
83 'info': logging.INFO,
84 'warning': logging.WARNING,
85 'error': logging.ERROR,
cliechti5cc3eb12009-08-11 23:04:30 +000086 }
87
88
cliechti8099bed2009-08-01 23:59:18 +000089# telnet protocol characters
90IAC = to_bytes([255]) # Interpret As Command
91DONT = to_bytes([254])
92DO = to_bytes([253])
93WONT = to_bytes([252])
94WILL = to_bytes([251])
95IAC_DOUBLED = to_bytes([IAC, IAC])
96
97SE = to_bytes([240]) # Subnegotiation End
98NOP = to_bytes([241]) # No Operation
99DM = to_bytes([242]) # Data Mark
100BRK = to_bytes([243]) # Break
101IP = to_bytes([244]) # Interrupt process
102AO = to_bytes([245]) # Abort output
103AYT = to_bytes([246]) # Are You There
104EC = to_bytes([247]) # Erase Character
105EL = to_bytes([248]) # Erase Line
106GA = to_bytes([249]) # Go Ahead
107SB = to_bytes([250]) # Subnegotiation Begin
108
109# selected telnet options
110BINARY = to_bytes([0]) # 8-bit data path
111ECHO = to_bytes([1]) # echo
112SGA = to_bytes([3]) # suppress go ahead
113
114# RFC2217
115COM_PORT_OPTION = to_bytes([44])
116
117# Client to Access Server
cliechti8099bed2009-08-01 23:59:18 +0000118SET_BAUDRATE = to_bytes([1])
119SET_DATASIZE = to_bytes([2])
120SET_PARITY = to_bytes([3])
121SET_STOPSIZE = to_bytes([4])
122SET_CONTROL = to_bytes([5])
123NOTIFY_LINESTATE = to_bytes([6])
124NOTIFY_MODEMSTATE = to_bytes([7])
125FLOWCONTROL_SUSPEND = to_bytes([8])
126FLOWCONTROL_RESUME = to_bytes([9])
127SET_LINESTATE_MASK = to_bytes([10])
128SET_MODEMSTATE_MASK = to_bytes([11])
129PURGE_DATA = to_bytes([12])
130
131SERVER_SET_BAUDRATE = to_bytes([101])
132SERVER_SET_DATASIZE = to_bytes([102])
133SERVER_SET_PARITY = to_bytes([103])
134SERVER_SET_STOPSIZE = to_bytes([104])
135SERVER_SET_CONTROL = to_bytes([105])
136SERVER_NOTIFY_LINESTATE = to_bytes([106])
137SERVER_NOTIFY_MODEMSTATE = to_bytes([107])
138SERVER_FLOWCONTROL_SUSPEND = to_bytes([108])
139SERVER_FLOWCONTROL_RESUME = to_bytes([109])
140SERVER_SET_LINESTATE_MASK = to_bytes([110])
141SERVER_SET_MODEMSTATE_MASK = to_bytes([111])
142SERVER_PURGE_DATA = to_bytes([112])
143
144RFC2217_ANSWER_MAP = {
145 SET_BAUDRATE: SERVER_SET_BAUDRATE,
146 SET_DATASIZE: SERVER_SET_DATASIZE,
147 SET_PARITY: SERVER_SET_PARITY,
148 SET_STOPSIZE: SERVER_SET_STOPSIZE,
149 SET_CONTROL: SERVER_SET_CONTROL,
150 NOTIFY_LINESTATE: SERVER_NOTIFY_LINESTATE,
151 NOTIFY_MODEMSTATE: SERVER_NOTIFY_MODEMSTATE,
152 FLOWCONTROL_SUSPEND: SERVER_FLOWCONTROL_SUSPEND,
153 FLOWCONTROL_RESUME: SERVER_FLOWCONTROL_RESUME,
154 SET_LINESTATE_MASK: SERVER_SET_LINESTATE_MASK,
155 SET_MODEMSTATE_MASK: SERVER_SET_MODEMSTATE_MASK,
156 PURGE_DATA: SERVER_PURGE_DATA,
157}
158
159SET_CONTROL_REQ_FLOW_SETTING = to_bytes([0]) # Request Com Port Flow Control Setting (outbound/both)
160SET_CONTROL_USE_NO_FLOW_CONTROL = to_bytes([1]) # Use No Flow Control (outbound/both)
161SET_CONTROL_USE_SW_FLOW_CONTROL = to_bytes([2]) # Use XON/XOFF Flow Control (outbound/both)
162SET_CONTROL_USE_HW_FLOW_CONTROL = to_bytes([3]) # Use HARDWARE Flow Control (outbound/both)
163SET_CONTROL_REQ_BREAK_STATE = to_bytes([4]) # Request BREAK State
164SET_CONTROL_BREAK_ON = to_bytes([5]) # Set BREAK State ON
165SET_CONTROL_BREAK_OFF = to_bytes([6]) # Set BREAK State OFF
166SET_CONTROL_REQ_DTR = to_bytes([7]) # Request DTR Signal State
167SET_CONTROL_DTR_ON = to_bytes([8]) # Set DTR Signal State ON
168SET_CONTROL_DTR_OFF = to_bytes([9]) # Set DTR Signal State OFF
169SET_CONTROL_REQ_RTS = to_bytes([10]) # Request RTS Signal State
170SET_CONTROL_RTS_ON = to_bytes([11]) # Set RTS Signal State ON
171SET_CONTROL_RTS_OFF = to_bytes([12]) # Set RTS Signal State OFF
172SET_CONTROL_REQ_FLOW_SETTING_IN = to_bytes([13]) # Request Com Port Flow Control Setting (inbound)
173SET_CONTROL_USE_NO_FLOW_CONTROL_IN = to_bytes([14]) # Use No Flow Control (inbound)
174SET_CONTROL_USE_SW_FLOW_CONTOL_IN = to_bytes([15]) # Use XON/XOFF Flow Control (inbound)
175SET_CONTROL_USE_HW_FLOW_CONTOL_IN = to_bytes([16]) # Use HARDWARE Flow Control (inbound)
176SET_CONTROL_USE_DCD_FLOW_CONTROL = to_bytes([17]) # Use DCD Flow Control (outbound/both)
177SET_CONTROL_USE_DTR_FLOW_CONTROL = to_bytes([18]) # Use DTR Flow Control (inbound)
178SET_CONTROL_USE_DSR_FLOW_CONTROL = to_bytes([19]) # Use DSR Flow Control (outbound/both)
179
cliechti044d8662009-08-11 21:40:31 +0000180LINESTATE_MASK_TIMEOUT = 128 # Time-out Error
181LINESTATE_MASK_SHIFTREG_EMPTY = 64 # Transfer Shift Register Empty
182LINESTATE_MASK_TRANSREG_EMPTY = 32 # Transfer Holding Register Empty
183LINESTATE_MASK_BREAK_DETECT = 16 # Break-detect Error
184LINESTATE_MASK_FRAMING_ERROR = 8 # Framing Error
185LINESTATE_MASK_PARTIY_ERROR = 4 # Parity Error
186LINESTATE_MASK_OVERRUN_ERROR = 2 # Overrun Error
187LINESTATE_MASK_DATA_READY = 1 # Data Ready
cliechti8099bed2009-08-01 23:59:18 +0000188
cliechti044d8662009-08-11 21:40:31 +0000189MODEMSTATE_MASK_CD = 128 # Receive Line Signal Detect (also known as Carrier Detect)
190MODEMSTATE_MASK_RI = 64 # Ring Indicator
191MODEMSTATE_MASK_DSR = 32 # Data-Set-Ready Signal State
192MODEMSTATE_MASK_CTS = 16 # Clear-To-Send Signal State
193MODEMSTATE_MASK_CD_CHANGE = 8 # Delta Receive Line Signal Detect
194MODEMSTATE_MASK_RI_CHANGE = 4 # Trailing-edge Ring Detector
195MODEMSTATE_MASK_DSR_CHANGE = 2 # Delta Data-Set-Ready
196MODEMSTATE_MASK_CTS_CHANGE = 1 # Delta Clear-To-Send
cliechti8099bed2009-08-01 23:59:18 +0000197
198PURGE_RECEIVE_BUFFER = to_bytes([1]) # Purge access server receive data buffer
199PURGE_TRANSMIT_BUFFER = to_bytes([2]) # Purge access server transmit data buffer
200PURGE_BOTH_BUFFERS = to_bytes([3]) # Purge both the access server receive data buffer and the access server transmit data buffer
201
202
203RFC2217_PARITY_MAP = {
204 PARITY_NONE: 1,
205 PARITY_ODD: 2,
206 PARITY_EVEN: 3,
207 PARITY_MARK: 4,
208 PARITY_SPACE: 5,
209}
cliechti130d1f02009-08-04 02:10:58 +0000210RFC2217_REVERSE_PARITY_MAP = dict((v,k) for k,v in RFC2217_PARITY_MAP.items())
cliechti8099bed2009-08-01 23:59:18 +0000211
212RFC2217_STOPBIT_MAP = {
213 STOPBITS_ONE: 1,
214 STOPBITS_ONE_POINT_FIVE: 3,
215 STOPBITS_TWO: 2,
216}
cliechti130d1f02009-08-04 02:10:58 +0000217RFC2217_REVERSE_STOPBIT_MAP = dict((v,k) for k,v in RFC2217_STOPBIT_MAP.items())
cliechti8099bed2009-08-01 23:59:18 +0000218
cliechti130d1f02009-08-04 02:10:58 +0000219# Telnet filter states
220M_NORMAL = 0
221M_IAC_SEEN = 1
222M_NEGOTIATE = 2
cliechti8099bed2009-08-01 23:59:18 +0000223
cliechti130d1f02009-08-04 02:10:58 +0000224# TelnetOption and TelnetSubnegotiation states
cliechtiac205322009-08-02 20:40:21 +0000225REQUESTED = 'REQUESTED'
226ACTIVE = 'ACTIVE'
227INACTIVE = 'INACTIVE'
228REALLY_INACTIVE = 'REALLY_INACTIVE'
229
230class TelnetOption(object):
cliechti1ef7e3e2009-08-03 02:38:43 +0000231 """Manage a single telnet option, keeps track of DO/DONT WILL/WONT."""
232
cliechti86b593e2009-08-05 16:28:12 +0000233 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 +0000234 """\
235 Initialize option.
cliechti1ef7e3e2009-08-03 02:38:43 +0000236 :param connection: connection used to transmit answers
237 :param name: a readable name for debug outputs
238 :param send_yes: what to send when option is to be enabled.
239 :param send_no: what to send when option is to be disabled.
240 :param ack_yes: what to expect when remote agrees on option.
241 :param ack_no: what to expect when remote disagrees on option.
242 :param initial_state: options initialized with REQUESTED are tried to
243 be enabled on startup. use INACTIVE for all others.
244 """
cliechti2b929b72009-08-02 23:49:02 +0000245 self.connection = connection
cliechtiac205322009-08-02 20:40:21 +0000246 self.name = name
247 self.option = option
248 self.send_yes = send_yes
249 self.send_no = send_no
250 self.ack_yes = ack_yes
251 self.ack_no = ack_no
252 self.state = initial_state
253 self.active = False
cliechti86b593e2009-08-05 16:28:12 +0000254 self.activation_callback = activation_callback
cliechtiac205322009-08-02 20:40:21 +0000255
256 def __repr__(self):
cliechti1ef7e3e2009-08-03 02:38:43 +0000257 """String for debug outputs"""
cliechtiac205322009-08-02 20:40:21 +0000258 return "%s:%s(%s)" % (self.name, self.active, self.state)
259
cliechti2b929b72009-08-02 23:49:02 +0000260 def process_incoming(self, command):
cliechti7d448562014-08-03 21:57:45 +0000261 """\
262 A DO/DONT/WILL/WONT was received for this option, update state and
263 answer when needed.
264 """
cliechtiac205322009-08-02 20:40:21 +0000265 if command == self.ack_yes:
266 if self.state is REQUESTED:
267 self.state = ACTIVE
268 self.active = True
cliechti86b593e2009-08-05 16:28:12 +0000269 if self.activation_callback is not None:
270 self.activation_callback()
cliechtiac205322009-08-02 20:40:21 +0000271 elif self.state is ACTIVE:
272 pass
273 elif self.state is INACTIVE:
274 self.state = ACTIVE
cliechti1ef7e3e2009-08-03 02:38:43 +0000275 self.connection.telnetSendOption(self.send_yes, self.option)
cliechtiac205322009-08-02 20:40:21 +0000276 self.active = True
cliechti86b593e2009-08-05 16:28:12 +0000277 if self.activation_callback is not None:
278 self.activation_callback()
cliechtiac205322009-08-02 20:40:21 +0000279 elif self.state is REALLY_INACTIVE:
cliechti1ef7e3e2009-08-03 02:38:43 +0000280 self.connection.telnetSendOption(self.send_no, self.option)
cliechtiac205322009-08-02 20:40:21 +0000281 else:
282 raise ValueError('option in illegal state %r' % self)
283 elif command == self.ack_no:
284 if self.state is REQUESTED:
285 self.state = INACTIVE
286 self.active = False
287 elif self.state is ACTIVE:
288 self.state = INACTIVE
cliechti1ef7e3e2009-08-03 02:38:43 +0000289 self.connection.telnetSendOption(self.send_no, self.option)
cliechtiac205322009-08-02 20:40:21 +0000290 self.active = False
291 elif self.state is INACTIVE:
292 pass
293 elif self.state is REALLY_INACTIVE:
294 pass
295 else:
296 raise ValueError('option in illegal state %r' % self)
297
298
cliechti2b929b72009-08-02 23:49:02 +0000299class TelnetSubnegotiation(object):
cliechtieada4fd2013-07-31 16:26:07 +0000300 """\
301 A object to handle subnegotiation of options. In this case actually
302 sub-sub options for RFC 2217. It is used to track com port options.
303 """
cliechti2b929b72009-08-02 23:49:02 +0000304
305 def __init__(self, connection, name, option, ack_option=None):
306 if ack_option is None: ack_option = option
307 self.connection = connection
308 self.name = name
309 self.option = option
310 self.value = None
311 self.ack_option = ack_option
312 self.state = INACTIVE
313
314 def __repr__(self):
cliechti044d8662009-08-11 21:40:31 +0000315 """String for debug outputs."""
cliechti2b929b72009-08-02 23:49:02 +0000316 return "%s:%s" % (self.name, self.state)
317
318 def set(self, value):
cliechtieada4fd2013-07-31 16:26:07 +0000319 """\
cliechti7d448562014-08-03 21:57:45 +0000320 Request a change of the value. a request is sent to the server. if
cliechti2b929b72009-08-02 23:49:02 +0000321 the client needs to know if the change is performed he has to check the
cliechtieada4fd2013-07-31 16:26:07 +0000322 state of this object.
323 """
cliechti2b929b72009-08-02 23:49:02 +0000324 self.value = value
325 self.state = REQUESTED
cliechti1ef7e3e2009-08-03 02:38:43 +0000326 self.connection.rfc2217SendSubnegotiation(self.option, self.value)
cliechti6a300772009-08-12 02:28:56 +0000327 if self.connection.logger:
328 self.connection.logger.debug("SB Requesting %s -> %r" % (self.name, self.value))
cliechti2b929b72009-08-02 23:49:02 +0000329
330 def isReady(self):
cliechtieada4fd2013-07-31 16:26:07 +0000331 """\
cliechti7d448562014-08-03 21:57:45 +0000332 Check if answer from server has been received. when server rejects
cliechtieada4fd2013-07-31 16:26:07 +0000333 the change, raise a ValueError.
334 """
cliechti2b929b72009-08-02 23:49:02 +0000335 if self.state == REALLY_INACTIVE:
336 raise ValueError("remote rejected value for option %r" % (self.name))
337 return self.state == ACTIVE
cliechti1ef7e3e2009-08-03 02:38:43 +0000338 # add property to have a similar interface as TelnetOption
cliechti2b929b72009-08-02 23:49:02 +0000339 active = property(isReady)
340
cliechti044d8662009-08-11 21:40:31 +0000341 def wait(self, timeout=3):
cliechtieada4fd2013-07-31 16:26:07 +0000342 """\
cliechti7d448562014-08-03 21:57:45 +0000343 Wait until the subnegotiation has been acknowledged or timeout. It
cliechti1ef7e3e2009-08-03 02:38:43 +0000344 can also throw a value error when the answer from the server does not
cliechtieada4fd2013-07-31 16:26:07 +0000345 match the value sent.
346 """
cliechti044d8662009-08-11 21:40:31 +0000347 timeout_time = time.time() + timeout
cliechti2b929b72009-08-02 23:49:02 +0000348 while time.time() < timeout_time:
349 time.sleep(0.05) # prevent 100% CPU load
350 if self.isReady():
351 break
352 else:
353 raise SerialException("timeout while waiting for option %r" % (self.name))
354
355 def checkAnswer(self, suboption):
cliechtieada4fd2013-07-31 16:26:07 +0000356 """\
cliechti7d448562014-08-03 21:57:45 +0000357 Check an incoming subnegotiation block. The parameter already has
cliechtieada4fd2013-07-31 16:26:07 +0000358 cut off the header like sub option number and com port option value.
359 """
cliechti2b929b72009-08-02 23:49:02 +0000360 if self.value == suboption[:len(self.value)]:
361 self.state = ACTIVE
362 else:
363 # error propagation done in isReady
364 self.state = REALLY_INACTIVE
cliechti6a300772009-08-12 02:28:56 +0000365 if self.connection.logger:
366 self.connection.logger.debug("SB Answer %s -> %r -> %s" % (self.name, suboption, self.state))
cliechti2b929b72009-08-02 23:49:02 +0000367
368
Chris Liechtid2146002015-08-04 16:57:16 +0200369class Serial(SerialBase, io.RawIOBase):
cliechti044d8662009-08-11 21:40:31 +0000370 """Serial port implementation for RFC 2217 remote serial ports."""
cliechti8099bed2009-08-01 23:59:18 +0000371
372 BAUDRATES = (50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800,
373 9600, 19200, 38400, 57600, 115200)
374
375 def open(self):
cliechtieada4fd2013-07-31 16:26:07 +0000376 """\
377 Open port with current settings. This may throw a SerialException
378 if the port cannot be opened.
379 """
cliechti6a300772009-08-12 02:28:56 +0000380 self.logger = None
cliechti81c54762009-08-03 23:53:27 +0000381 self._ignore_set_control_answer = False
cliechti7cb78e82009-08-05 15:47:57 +0000382 self._poll_modem_state = False
cliechtidfe2d272009-08-10 22:19:41 +0000383 self._network_timeout = 3
cliechti8099bed2009-08-01 23:59:18 +0000384 if self._port is None:
385 raise SerialException("Port must be configured before it can be used.")
cliechti8f69e702011-03-19 00:22:32 +0000386 if self._isOpen:
387 raise SerialException("Port is already open.")
cliechti8099bed2009-08-01 23:59:18 +0000388 try:
389 self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
390 self._socket.connect(self.fromURL(self.portstr))
cliechti6a300772009-08-12 02:28:56 +0000391 self._socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
Chris Liechti68340d72015-08-03 14:15:48 +0200392 except Exception as msg:
cliechti8099bed2009-08-01 23:59:18 +0000393 self._socket = None
394 raise SerialException("Could not open port %s: %s" % (self.portstr, msg))
395
cliechti1ef7e3e2009-08-03 02:38:43 +0000396 self._socket.settimeout(5) # XXX good value?
cliechti8099bed2009-08-01 23:59:18 +0000397
cliechti1ef7e3e2009-08-03 02:38:43 +0000398 # use a thread save queue as buffer. it also simplifies implementing
399 # the read timeout
cliechti8099bed2009-08-01 23:59:18 +0000400 self._read_buffer = Queue.Queue()
cliechti81c54762009-08-03 23:53:27 +0000401 # to ensure that user writes does not interfere with internal
402 # telnet/rfc2217 options establish a lock
403 self._write_lock = threading.Lock()
cliechtiac205322009-08-02 20:40:21 +0000404 # name the following separately so that, below, a check can be easily done
405 mandadory_options = [
cliechti2b929b72009-08-02 23:49:02 +0000406 TelnetOption(self, 'we-BINARY', BINARY, WILL, WONT, DO, DONT, INACTIVE),
cliechti2b929b72009-08-02 23:49:02 +0000407 TelnetOption(self, 'we-RFC2217', COM_PORT_OPTION, WILL, WONT, DO, DONT, REQUESTED),
cliechtiac205322009-08-02 20:40:21 +0000408 ]
409 # all supported telnet options
410 self._telnet_options = [
cliechtia29275e2009-08-03 00:08:04 +0000411 TelnetOption(self, 'ECHO', ECHO, DO, DONT, WILL, WONT, REQUESTED),
cliechti2b929b72009-08-02 23:49:02 +0000412 TelnetOption(self, 'we-SGA', SGA, WILL, WONT, DO, DONT, REQUESTED),
413 TelnetOption(self, 'they-SGA', SGA, DO, DONT, WILL, WONT, REQUESTED),
cliechti81c54762009-08-03 23:53:27 +0000414 TelnetOption(self, 'they-BINARY', BINARY, DO, DONT, WILL, WONT, INACTIVE),
415 TelnetOption(self, 'they-RFC2217', COM_PORT_OPTION, DO, DONT, WILL, WONT, REQUESTED),
cliechtiac205322009-08-02 20:40:21 +0000416 ] + mandadory_options
cliechti044d8662009-08-11 21:40:31 +0000417 # RFC 2217 specific states
cliechti2b929b72009-08-02 23:49:02 +0000418 # COM port settings
419 self._rfc2217_port_settings = {
420 'baudrate': TelnetSubnegotiation(self, 'baudrate', SET_BAUDRATE, SERVER_SET_BAUDRATE),
421 'datasize': TelnetSubnegotiation(self, 'datasize', SET_DATASIZE, SERVER_SET_DATASIZE),
422 'parity': TelnetSubnegotiation(self, 'parity', SET_PARITY, SERVER_SET_PARITY),
423 'stopsize': TelnetSubnegotiation(self, 'stopsize', SET_STOPSIZE, SERVER_SET_STOPSIZE),
424 }
cliechticb20a4f2011-04-25 02:25:54 +0000425 # There are more subnegotiation objects, combine all in one dictionary
cliechti2b929b72009-08-02 23:49:02 +0000426 # for easy access
427 self._rfc2217_options = {
428 'purge': TelnetSubnegotiation(self, 'purge', PURGE_DATA, SERVER_PURGE_DATA),
cliechti81c54762009-08-03 23:53:27 +0000429 'control': TelnetSubnegotiation(self, 'control', SET_CONTROL, SERVER_SET_CONTROL),
cliechti2b929b72009-08-02 23:49:02 +0000430 }
431 self._rfc2217_options.update(self._rfc2217_port_settings)
432 # cache for line and modem states that the server sends to us
cliechti8099bed2009-08-01 23:59:18 +0000433 self._linestate = 0
cliechti7cb78e82009-08-05 15:47:57 +0000434 self._modemstate = None
435 self._modemstate_expires = 0
cliechti044d8662009-08-11 21:40:31 +0000436 # RFC 2217 flow control between server and client
cliechti672d0292009-08-03 02:01:57 +0000437 self._remote_suspend_flow = False
cliechti8099bed2009-08-01 23:59:18 +0000438
cliechti1ef7e3e2009-08-03 02:38:43 +0000439 self._thread = threading.Thread(target=self._telnetReadLoop)
cliechti8099bed2009-08-01 23:59:18 +0000440 self._thread.setDaemon(True)
cliechti5cc3eb12009-08-11 23:04:30 +0000441 self._thread.setName('pySerial RFC 2217 reader thread for %s' % (self._port,))
cliechti8099bed2009-08-01 23:59:18 +0000442 self._thread.start()
443
cliechti044d8662009-08-11 21:40:31 +0000444 # negotiate Telnet/RFC 2217 -> send initial requests
cliechtiac205322009-08-02 20:40:21 +0000445 for option in self._telnet_options:
446 if option.state is REQUESTED:
cliechti1ef7e3e2009-08-03 02:38:43 +0000447 self.telnetSendOption(option.send_yes, option.option)
cliechtiac205322009-08-02 20:40:21 +0000448 # now wait until important options are negotiated
cliechtidfe2d272009-08-10 22:19:41 +0000449 timeout_time = time.time() + self._network_timeout
cliechtiac205322009-08-02 20:40:21 +0000450 while time.time() < timeout_time:
cliechtiac205322009-08-02 20:40:21 +0000451 time.sleep(0.05) # prevent 100% CPU load
cliechti7c213a92014-07-31 15:29:34 +0000452 if sum(o.active for o in mandadory_options) == sum(o.state != INACTIVE for o in mandadory_options):
cliechti2b929b72009-08-02 23:49:02 +0000453 break
454 else:
455 raise SerialException("Remote does not seem to support RFC2217 or BINARY mode %r" % mandadory_options)
cliechti6a300772009-08-12 02:28:56 +0000456 if self.logger:
457 self.logger.info("Negotiated options: %s" % self._telnet_options)
cliechti8099bed2009-08-01 23:59:18 +0000458
cliechti044d8662009-08-11 21:40:31 +0000459 # fine, go on, set RFC 2271 specific things
cliechti8099bed2009-08-01 23:59:18 +0000460 self._reconfigurePort()
cliechti2b929b72009-08-02 23:49:02 +0000461 # all things set up get, now a clean start
cliechti8099bed2009-08-01 23:59:18 +0000462 self._isOpen = True
463 if not self._rtscts:
464 self.setRTS(True)
465 self.setDTR(True)
466 self.flushInput()
467 self.flushOutput()
468
469 def _reconfigurePort(self):
470 """Set communication parameters on opened port."""
471 if self._socket is None:
472 raise SerialException("Can only operate on open ports")
473
cliechti8099bed2009-08-01 23:59:18 +0000474 # if self._timeout != 0 and self._interCharTimeout is not None:
cliechti8099bed2009-08-01 23:59:18 +0000475 # XXX
476
477 if self._writeTimeout is not None:
478 raise NotImplementedError('writeTimeout is currently not supported')
cliechti2b929b72009-08-02 23:49:02 +0000479 # XXX
cliechti8099bed2009-08-01 23:59:18 +0000480
cliechti2b929b72009-08-02 23:49:02 +0000481 # Setup the connection
cliechti1ef7e3e2009-08-03 02:38:43 +0000482 # to get good performance, all parameter changes are sent first...
cliechti81c54762009-08-03 23:53:27 +0000483 if not isinstance(self._baudrate, (int, long)) or not 0 < self._baudrate < 2**32:
484 raise ValueError("invalid baudrate: %r" % (self._baudrate))
cliechti2b929b72009-08-02 23:49:02 +0000485 self._rfc2217_port_settings['baudrate'].set(struct.pack('!I', self._baudrate))
486 self._rfc2217_port_settings['datasize'].set(struct.pack('!B', self._bytesize))
487 self._rfc2217_port_settings['parity'].set(struct.pack('!B', RFC2217_PARITY_MAP[self._parity]))
488 self._rfc2217_port_settings['stopsize'].set(struct.pack('!B', RFC2217_STOPBIT_MAP[self._stopbits]))
cliechti8099bed2009-08-01 23:59:18 +0000489
cliechti2b929b72009-08-02 23:49:02 +0000490 # and now wait until parameters are active
491 items = self._rfc2217_port_settings.values()
cliechti6a300772009-08-12 02:28:56 +0000492 if self.logger:
493 self.logger.debug("Negotiating settings: %s" % (items,))
cliechtidfe2d272009-08-10 22:19:41 +0000494 timeout_time = time.time() + self._network_timeout
cliechti2b929b72009-08-02 23:49:02 +0000495 while time.time() < timeout_time:
496 time.sleep(0.05) # prevent 100% CPU load
497 if sum(o.active for o in items) == len(items):
498 break
499 else:
500 raise SerialException("Remote does not accept parameter change (RFC2217): %r" % items)
cliechti6a300772009-08-12 02:28:56 +0000501 if self.logger:
502 self.logger.info("Negotiated settings: %s" % (items,))
cliechti8099bed2009-08-01 23:59:18 +0000503
504 if self._rtscts and self._xonxoff:
cliechti1ef7e3e2009-08-03 02:38:43 +0000505 raise ValueError('xonxoff and rtscts together are not supported')
cliechti8099bed2009-08-01 23:59:18 +0000506 elif self._rtscts:
cliechti1ef7e3e2009-08-03 02:38:43 +0000507 self.rfc2217SetControl(SET_CONTROL_USE_HW_FLOW_CONTROL)
cliechti8099bed2009-08-01 23:59:18 +0000508 elif self._xonxoff:
cliechti1ef7e3e2009-08-03 02:38:43 +0000509 self.rfc2217SetControl(SET_CONTROL_USE_SW_FLOW_CONTROL)
cliechti8099bed2009-08-01 23:59:18 +0000510 else:
cliechti1ef7e3e2009-08-03 02:38:43 +0000511 self.rfc2217SetControl(SET_CONTROL_USE_NO_FLOW_CONTROL)
cliechti8099bed2009-08-01 23:59:18 +0000512
513 def close(self):
514 """Close port"""
515 if self._isOpen:
516 if self._socket:
517 try:
518 self._socket.shutdown(socket.SHUT_RDWR)
519 self._socket.close()
520 except:
521 # ignore errors.
522 pass
523 self._socket = None
524 if self._thread:
525 self._thread.join()
526 self._isOpen = False
527 # in case of quick reconnects, give the server some time
528 time.sleep(0.3)
529
530 def makeDeviceName(self, port):
531 raise SerialException("there is no sensible way to turn numbers into URLs")
532
533 def fromURL(self, url):
cliechti1ef7e3e2009-08-03 02:38:43 +0000534 """extract host and port from an URL string"""
cliechti8099bed2009-08-01 23:59:18 +0000535 if url.lower().startswith("rfc2217://"): url = url[10:]
536 try:
cliechti81c54762009-08-03 23:53:27 +0000537 # is there a "path" (our options)?
538 if '/' in url:
539 # cut away options
540 url, options = url.split('/', 1)
541 # process options now, directly altering self
542 for option in options.split('/'):
cliechtidfe2d272009-08-10 22:19:41 +0000543 if '=' in option:
544 option, value = option.split('=', 1)
545 else:
546 value = None
cliechti5cc3eb12009-08-11 23:04:30 +0000547 if option == 'logging':
548 logging.basicConfig() # XXX is that good to call it here?
cliechti6a300772009-08-12 02:28:56 +0000549 self.logger = logging.getLogger('pySerial.rfc2217')
550 self.logger.setLevel(LOGGER_LEVELS[value])
551 self.logger.debug('enabled logging')
cliechti81c54762009-08-03 23:53:27 +0000552 elif option == 'ign_set_control':
553 self._ignore_set_control_answer = True
cliechti7cb78e82009-08-05 15:47:57 +0000554 elif option == 'poll_modem':
555 self._poll_modem_state = True
cliechtidfe2d272009-08-10 22:19:41 +0000556 elif option == 'timeout':
557 self._network_timeout = float(value)
cliechti81c54762009-08-03 23:53:27 +0000558 else:
559 raise ValueError('unknown option: %r' % (option,))
560 # get host and port
cliechti8099bed2009-08-01 23:59:18 +0000561 host, port = url.split(':', 1) # may raise ValueError because of unpacking
562 port = int(port) # and this if it's not a number
563 if not 0 <= port < 65536: raise ValueError("port not in range 0...65535")
Chris Liechti68340d72015-08-03 14:15:48 +0200564 except ValueError as e:
cliechti81c54762009-08-03 23:53:27 +0000565 raise SerialException('expected a string in the form "[rfc2217://]<host>:<port>[/option[/option...]]": %s' % e)
cliechti8099bed2009-08-01 23:59:18 +0000566 return (host, port)
567
568 # - - - - - - - - - - - - - - - - - - - - - - - -
569
570 def inWaiting(self):
571 """Return the number of characters currently in the input buffer."""
572 if not self._isOpen: raise portNotOpenError
573 return self._read_buffer.qsize()
574
575 def read(self, size=1):
cliechtieada4fd2013-07-31 16:26:07 +0000576 """\
577 Read size bytes from the serial port. If a timeout is set it may
cliechti8099bed2009-08-01 23:59:18 +0000578 return less characters as requested. With no timeout it will block
cliechtieada4fd2013-07-31 16:26:07 +0000579 until the requested number of bytes is read.
580 """
cliechti8099bed2009-08-01 23:59:18 +0000581 if not self._isOpen: raise portNotOpenError
582 data = bytearray()
583 try:
584 while len(data) < size:
cliechti81c54762009-08-03 23:53:27 +0000585 if self._thread is None:
586 raise SerialException('connection failed (reader thread died)')
cliechti8099bed2009-08-01 23:59:18 +0000587 data.append(self._read_buffer.get(True, self._timeout))
588 except Queue.Empty: # -> timeout
589 pass
590 return bytes(data)
591
592 def write(self, data):
cliechtieada4fd2013-07-31 16:26:07 +0000593 """\
594 Output the given string over the serial port. Can block if the
cliechti8099bed2009-08-01 23:59:18 +0000595 connection is blocked. May raise SerialException if the connection is
cliechtieada4fd2013-07-31 16:26:07 +0000596 closed.
597 """
cliechti8099bed2009-08-01 23:59:18 +0000598 if not self._isOpen: raise portNotOpenError
cliechti81c54762009-08-03 23:53:27 +0000599 self._write_lock.acquire()
cliechti8099bed2009-08-01 23:59:18 +0000600 try:
cliechti81c54762009-08-03 23:53:27 +0000601 try:
cliechti38077122013-10-16 02:57:27 +0000602 self._socket.sendall(to_bytes(data).replace(IAC, IAC_DOUBLED))
Chris Liechti68340d72015-08-03 14:15:48 +0200603 except socket.error as e:
cliechticb20a4f2011-04-25 02:25:54 +0000604 raise SerialException("connection failed (socket error): %s" % e) # XXX what exception if socket connection fails
cliechti81c54762009-08-03 23:53:27 +0000605 finally:
606 self._write_lock.release()
cliechti8099bed2009-08-01 23:59:18 +0000607 return len(data)
608
609 def flushInput(self):
610 """Clear input buffer, discarding all that is in the buffer."""
611 if not self._isOpen: raise portNotOpenError
cliechti1ef7e3e2009-08-03 02:38:43 +0000612 self.rfc2217SendPurge(PURGE_RECEIVE_BUFFER)
cliechti8099bed2009-08-01 23:59:18 +0000613 # empty read buffer
614 while self._read_buffer.qsize():
615 self._read_buffer.get(False)
616
617 def flushOutput(self):
cliechtieada4fd2013-07-31 16:26:07 +0000618 """\
619 Clear output buffer, aborting the current output and
620 discarding all that is in the buffer.
621 """
cliechti8099bed2009-08-01 23:59:18 +0000622 if not self._isOpen: raise portNotOpenError
cliechti1ef7e3e2009-08-03 02:38:43 +0000623 self.rfc2217SendPurge(PURGE_TRANSMIT_BUFFER)
cliechti8099bed2009-08-01 23:59:18 +0000624
625 def sendBreak(self, duration=0.25):
cliechti7d448562014-08-03 21:57:45 +0000626 """\
627 Send break condition. Timed, returns to idle state after given
628 duration.
629 """
cliechti8099bed2009-08-01 23:59:18 +0000630 if not self._isOpen: raise portNotOpenError
631 self.setBreak(True)
632 time.sleep(duration)
633 self.setBreak(False)
634
635 def setBreak(self, level=True):
cliechtieada4fd2013-07-31 16:26:07 +0000636 """\
637 Set break: Controls TXD. When active, to transmitting is
638 possible.
639 """
cliechti8099bed2009-08-01 23:59:18 +0000640 if not self._isOpen: raise portNotOpenError
cliechti6a300772009-08-12 02:28:56 +0000641 if self.logger:
642 self.logger.info('set BREAK to %s' % ('inactive', 'active')[bool(level)])
cliechti8099bed2009-08-01 23:59:18 +0000643 if level:
cliechti1ef7e3e2009-08-03 02:38:43 +0000644 self.rfc2217SetControl(SET_CONTROL_BREAK_ON)
cliechti8099bed2009-08-01 23:59:18 +0000645 else:
cliechti1ef7e3e2009-08-03 02:38:43 +0000646 self.rfc2217SetControl(SET_CONTROL_BREAK_OFF)
cliechti8099bed2009-08-01 23:59:18 +0000647
648 def setRTS(self, level=True):
cliechti044d8662009-08-11 21:40:31 +0000649 """Set terminal status line: Request To Send."""
cliechti8099bed2009-08-01 23:59:18 +0000650 if not self._isOpen: raise portNotOpenError
cliechti6a300772009-08-12 02:28:56 +0000651 if self.logger:
652 self.logger.info('set RTS to %s' % ('inactive', 'active')[bool(level)])
cliechti8099bed2009-08-01 23:59:18 +0000653 if level:
cliechti1ef7e3e2009-08-03 02:38:43 +0000654 self.rfc2217SetControl(SET_CONTROL_RTS_ON)
cliechti8099bed2009-08-01 23:59:18 +0000655 else:
cliechti1ef7e3e2009-08-03 02:38:43 +0000656 self.rfc2217SetControl(SET_CONTROL_RTS_OFF)
cliechti8099bed2009-08-01 23:59:18 +0000657
658 def setDTR(self, level=True):
cliechti044d8662009-08-11 21:40:31 +0000659 """Set terminal status line: Data Terminal Ready."""
cliechti8099bed2009-08-01 23:59:18 +0000660 if not self._isOpen: raise portNotOpenError
cliechti6a300772009-08-12 02:28:56 +0000661 if self.logger:
662 self.logger.info('set DTR to %s' % ('inactive', 'active')[bool(level)])
cliechti8099bed2009-08-01 23:59:18 +0000663 if level:
cliechti1ef7e3e2009-08-03 02:38:43 +0000664 self.rfc2217SetControl(SET_CONTROL_DTR_ON)
cliechti8099bed2009-08-01 23:59:18 +0000665 else:
cliechti1ef7e3e2009-08-03 02:38:43 +0000666 self.rfc2217SetControl(SET_CONTROL_DTR_OFF)
cliechti8099bed2009-08-01 23:59:18 +0000667
668 def getCTS(self):
cliechti044d8662009-08-11 21:40:31 +0000669 """Read terminal status line: Clear To Send."""
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_CTS)
cliechti8099bed2009-08-01 23:59:18 +0000672
673 def getDSR(self):
cliechti044d8662009-08-11 21:40:31 +0000674 """Read terminal status line: Data Set Ready."""
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_DSR)
cliechti8099bed2009-08-01 23:59:18 +0000677
678 def getRI(self):
cliechti044d8662009-08-11 21:40:31 +0000679 """Read terminal status line: Ring Indicator."""
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_RI)
cliechti8099bed2009-08-01 23:59:18 +0000682
683 def getCD(self):
cliechti044d8662009-08-11 21:40:31 +0000684 """Read terminal status line: Carrier Detect."""
cliechti8099bed2009-08-01 23:59:18 +0000685 if not self._isOpen: raise portNotOpenError
cliechti7cb78e82009-08-05 15:47:57 +0000686 return bool(self.getModemState() & MODEMSTATE_MASK_CD)
cliechti8099bed2009-08-01 23:59:18 +0000687
688 # - - - platform specific - - -
689 # None so far
690
691 # - - - RFC2217 specific - - -
692
cliechti1ef7e3e2009-08-03 02:38:43 +0000693 def _telnetReadLoop(self):
cliechti7d448562014-08-03 21:57:45 +0000694 """Read loop for the socket."""
cliechti8099bed2009-08-01 23:59:18 +0000695 mode = M_NORMAL
696 suboption = None
cliechti81c54762009-08-03 23:53:27 +0000697 try:
698 while self._socket is not None:
699 try:
700 data = self._socket.recv(1024)
701 except socket.timeout:
702 # just need to get out of recv form time to time to check if
703 # still alive
704 continue
Chris Liechti68340d72015-08-03 14:15:48 +0200705 except socket.error as e:
cliechti81c54762009-08-03 23:53:27 +0000706 # connection fails -> terminate loop
cliechticb20a4f2011-04-25 02:25:54 +0000707 if self.logger:
708 self.logger.debug("socket error in reader thread: %s" % (e,))
cliechti81c54762009-08-03 23:53:27 +0000709 break
cliechticb20a4f2011-04-25 02:25:54 +0000710 if not data: break # lost connection
cliechti81c54762009-08-03 23:53:27 +0000711 for byte in data:
712 if mode == M_NORMAL:
713 # interpret as command or as data
714 if byte == IAC:
715 mode = M_IAC_SEEN
cliechti8099bed2009-08-01 23:59:18 +0000716 else:
cliechti81c54762009-08-03 23:53:27 +0000717 # store data in read buffer or sub option buffer
718 # depending on state
719 if suboption is not None:
720 suboption.append(byte)
721 else:
722 self._read_buffer.put(byte)
723 elif mode == M_IAC_SEEN:
724 if byte == IAC:
725 # interpret as command doubled -> insert character
726 # itself
cliechtif325c032009-12-25 16:09:49 +0000727 if suboption is not None:
728 suboption.append(IAC)
729 else:
730 self._read_buffer.put(IAC)
cliechti81c54762009-08-03 23:53:27 +0000731 mode = M_NORMAL
732 elif byte == SB:
733 # sub option start
734 suboption = bytearray()
735 mode = M_NORMAL
736 elif byte == SE:
737 # sub option end -> process it now
738 self._telnetProcessSubnegotiation(bytes(suboption))
739 suboption = None
740 mode = M_NORMAL
741 elif byte in (DO, DONT, WILL, WONT):
742 # negotiation
743 telnet_command = byte
744 mode = M_NEGOTIATE
745 else:
746 # other telnet commands
747 self._telnetProcessCommand(byte)
748 mode = M_NORMAL
749 elif mode == M_NEGOTIATE: # DO, DONT, WILL, WONT was received, option now following
750 self._telnetNegotiateOption(telnet_command, byte)
cliechti8099bed2009-08-01 23:59:18 +0000751 mode = M_NORMAL
cliechti81c54762009-08-03 23:53:27 +0000752 finally:
753 self._thread = None
cliechti6a300772009-08-12 02:28:56 +0000754 if self.logger:
755 self.logger.debug("read thread terminated")
cliechti8099bed2009-08-01 23:59:18 +0000756
757 # - incoming telnet commands and options
758
cliechti1ef7e3e2009-08-03 02:38:43 +0000759 def _telnetProcessCommand(self, command):
cliechti044d8662009-08-11 21:40:31 +0000760 """Process commands other than DO, DONT, WILL, WONT."""
cliechti1ef7e3e2009-08-03 02:38:43 +0000761 # Currently none. RFC2217 only uses negotiation and subnegotiation.
cliechti6a300772009-08-12 02:28:56 +0000762 if self.logger:
763 self.logger.warning("ignoring Telnet command: %r" % (command,))
cliechti8099bed2009-08-01 23:59:18 +0000764
cliechti1ef7e3e2009-08-03 02:38:43 +0000765 def _telnetNegotiateOption(self, command, option):
cliechti044d8662009-08-11 21:40:31 +0000766 """Process incoming DO, DONT, WILL, WONT."""
cliechti2b929b72009-08-02 23:49:02 +0000767 # check our registered telnet options and forward command to them
768 # they know themselves if they have to answer or not
cliechtiac205322009-08-02 20:40:21 +0000769 known = False
770 for item in self._telnet_options:
cliechti2b929b72009-08-02 23:49:02 +0000771 # can have more than one match! as some options are duplicated for
772 # 'us' and 'them'
cliechtiac205322009-08-02 20:40:21 +0000773 if item.option == option:
cliechti2b929b72009-08-02 23:49:02 +0000774 item.process_incoming(command)
cliechtiac205322009-08-02 20:40:21 +0000775 known = True
776 if not known:
777 # handle unknown options
778 # only answer to positive requests and deny them
779 if command == WILL or command == DO:
cliechti1ef7e3e2009-08-03 02:38:43 +0000780 self.telnetSendOption((command == WILL and DONT or WONT), option)
cliechti6a300772009-08-12 02:28:56 +0000781 if self.logger:
782 self.logger.warning("rejected Telnet option: %r" % (option,))
cliechtiac205322009-08-02 20:40:21 +0000783
cliechti8099bed2009-08-01 23:59:18 +0000784
cliechti1ef7e3e2009-08-03 02:38:43 +0000785 def _telnetProcessSubnegotiation(self, suboption):
cliechti044d8662009-08-11 21:40:31 +0000786 """Process subnegotiation, the data between IAC SB and IAC SE."""
cliechti8099bed2009-08-01 23:59:18 +0000787 if suboption[0:1] == COM_PORT_OPTION:
788 if suboption[1:2] == SERVER_NOTIFY_LINESTATE and len(suboption) >= 3:
cliechti672d0292009-08-03 02:01:57 +0000789 self._linestate = ord(suboption[2:3]) # ensure it is a number
cliechti6a300772009-08-12 02:28:56 +0000790 if self.logger:
791 self.logger.info("NOTIFY_LINESTATE: %s" % self._linestate)
cliechti8099bed2009-08-01 23:59:18 +0000792 elif suboption[1:2] == SERVER_NOTIFY_MODEMSTATE and len(suboption) >= 3:
cliechti672d0292009-08-03 02:01:57 +0000793 self._modemstate = ord(suboption[2:3]) # ensure it is a number
cliechti6a300772009-08-12 02:28:56 +0000794 if self.logger:
795 self.logger.info("NOTIFY_MODEMSTATE: %s" % self._modemstate)
cliechti7cb78e82009-08-05 15:47:57 +0000796 # update time when we think that a poll would make sense
797 self._modemstate_expires = time.time() + 0.3
cliechti672d0292009-08-03 02:01:57 +0000798 elif suboption[1:2] == FLOWCONTROL_SUSPEND:
799 self._remote_suspend_flow = True
800 elif suboption[1:2] == FLOWCONTROL_RESUME:
801 self._remote_suspend_flow = False
cliechti8099bed2009-08-01 23:59:18 +0000802 else:
cliechti2b929b72009-08-02 23:49:02 +0000803 for item in self._rfc2217_options.values():
804 if item.ack_option == suboption[1:2]:
cliechti81c54762009-08-03 23:53:27 +0000805 #~ print "processing COM_PORT_OPTION: %r" % list(suboption[1:])
cliechti2b929b72009-08-02 23:49:02 +0000806 item.checkAnswer(bytes(suboption[2:]))
807 break
808 else:
cliechti6a300772009-08-12 02:28:56 +0000809 if self.logger:
810 self.logger.warning("ignoring COM_PORT_OPTION: %r" % (suboption,))
cliechti8099bed2009-08-01 23:59:18 +0000811 else:
cliechti6a300772009-08-12 02:28:56 +0000812 if self.logger:
813 self.logger.warning("ignoring subnegotiation: %r" % (suboption,))
cliechti8099bed2009-08-01 23:59:18 +0000814
815 # - outgoing telnet commands and options
816
cliechti81c54762009-08-03 23:53:27 +0000817 def _internal_raw_write(self, data):
cliechti044d8662009-08-11 21:40:31 +0000818 """internal socket write with no data escaping. used to send telnet stuff."""
cliechti81c54762009-08-03 23:53:27 +0000819 self._write_lock.acquire()
820 try:
821 self._socket.sendall(data)
822 finally:
823 self._write_lock.release()
824
cliechti1ef7e3e2009-08-03 02:38:43 +0000825 def telnetSendOption(self, action, option):
cliechti044d8662009-08-11 21:40:31 +0000826 """Send DO, DONT, WILL, WONT."""
cliechti81c54762009-08-03 23:53:27 +0000827 self._internal_raw_write(to_bytes([IAC, action, option]))
cliechti8099bed2009-08-01 23:59:18 +0000828
cliechtif325c032009-12-25 16:09:49 +0000829 def rfc2217SendSubnegotiation(self, option, value=''):
cliechti044d8662009-08-11 21:40:31 +0000830 """Subnegotiation of RFC2217 parameters."""
cliechtif325c032009-12-25 16:09:49 +0000831 value = value.replace(IAC, IAC_DOUBLED)
cliechti81c54762009-08-03 23:53:27 +0000832 self._internal_raw_write(to_bytes([IAC, SB, COM_PORT_OPTION, option] + list(value) + [IAC, SE]))
cliechti2b929b72009-08-02 23:49:02 +0000833
cliechti1ef7e3e2009-08-03 02:38:43 +0000834 def rfc2217SendPurge(self, value):
cliechti2b929b72009-08-02 23:49:02 +0000835 item = self._rfc2217_options['purge']
cliechti672d0292009-08-03 02:01:57 +0000836 item.set(value) # transmit desired purge type
cliechtidfe2d272009-08-10 22:19:41 +0000837 item.wait(self._network_timeout) # wait for acknowledge from the server
cliechti2b929b72009-08-02 23:49:02 +0000838
cliechti1ef7e3e2009-08-03 02:38:43 +0000839 def rfc2217SetControl(self, value):
cliechti81c54762009-08-03 23:53:27 +0000840 item = self._rfc2217_options['control']
cliechticb20a4f2011-04-25 02:25:54 +0000841 item.set(value) # transmit desired control type
cliechti81c54762009-08-03 23:53:27 +0000842 if self._ignore_set_control_answer:
843 # answers are ignored when option is set. compatibility mode for
cliechticb20a4f2011-04-25 02:25:54 +0000844 # servers that answer, but not the expected one... (or no answer
cliechti81c54762009-08-03 23:53:27 +0000845 # at all) i.e. sredird
846 time.sleep(0.1) # this helps getting the unit tests passed
847 else:
cliechtidfe2d272009-08-10 22:19:41 +0000848 item.wait(self._network_timeout) # wait for acknowledge from the server
cliechti8099bed2009-08-01 23:59:18 +0000849
cliechti1ef7e3e2009-08-03 02:38:43 +0000850 def rfc2217FlowServerReady(self):
cliechtieada4fd2013-07-31 16:26:07 +0000851 """\
852 check if server is ready to receive data. block for some time when
853 not.
854 """
cliechti672d0292009-08-03 02:01:57 +0000855 #~ if self._remote_suspend_flow:
856 #~ wait---
857
cliechti7cb78e82009-08-05 15:47:57 +0000858 def getModemState(self):
cliechtieada4fd2013-07-31 16:26:07 +0000859 """\
cliechti7d448562014-08-03 21:57:45 +0000860 get last modem state (cached value. If value is "old", request a new
861 one. This cache helps that we don't issue to many requests when e.g. all
862 status lines, one after the other is queried by the user (getCTS, getDSR
cliechtieada4fd2013-07-31 16:26:07 +0000863 etc.)
864 """
cliechti7cb78e82009-08-05 15:47:57 +0000865 # active modem state polling enabled? is the value fresh enough?
866 if self._poll_modem_state and self._modemstate_expires < time.time():
cliechti6a300772009-08-12 02:28:56 +0000867 if self.logger:
868 self.logger.debug('polling modem state')
cliechti7cb78e82009-08-05 15:47:57 +0000869 # when it is older, request an update
870 self.rfc2217SendSubnegotiation(NOTIFY_MODEMSTATE)
cliechtidfe2d272009-08-10 22:19:41 +0000871 timeout_time = time.time() + self._network_timeout
cliechti7cb78e82009-08-05 15:47:57 +0000872 while time.time() < timeout_time:
873 time.sleep(0.05) # prevent 100% CPU load
874 # when expiration time is updated, it means that there is a new
875 # value
876 if self._modemstate_expires > time.time():
cliechti6a300772009-08-12 02:28:56 +0000877 if self.logger:
878 self.logger.warning('poll for modem state failed')
cliechti7cb78e82009-08-05 15:47:57 +0000879 break
880 # even when there is a timeout, do not generate an error just
881 # return the last known value. this way we can support buggy
882 # servers that do not respond to polls, but send automatic
883 # updates.
884 if self._modemstate is not None:
cliechti6a300772009-08-12 02:28:56 +0000885 if self.logger:
886 self.logger.debug('using cached modem state')
cliechti7cb78e82009-08-05 15:47:57 +0000887 return self._modemstate
888 else:
889 # never received a notification from the server
cliechti8fb119c2009-08-05 23:39:45 +0000890 raise SerialException("remote sends no NOTIFY_MODEMSTATE")
cliechti8099bed2009-08-01 23:59:18 +0000891
cliechti5cc3eb12009-08-11 23:04:30 +0000892
cliechti595ed5b2009-08-10 01:43:32 +0000893#############################################################################
cliechti5cc3eb12009-08-11 23:04:30 +0000894# The following is code that helps implementing an RFC 2217 server.
cliechti8099bed2009-08-01 23:59:18 +0000895
cliechti8ccc2ff2009-08-05 12:44:46 +0000896class PortManager(object):
cliechtieada4fd2013-07-31 16:26:07 +0000897 """\
898 This class manages the state of Telnet and RFC 2217. It needs a serial
cliechticb20a4f2011-04-25 02:25:54 +0000899 instance and a connection to work with. Connection is expected to implement
cliechtieada4fd2013-07-31 16:26:07 +0000900 a (thread safe) write function, that writes the string to the network.
901 """
cliechti130d1f02009-08-04 02:10:58 +0000902
cliechti6a300772009-08-12 02:28:56 +0000903 def __init__(self, serial_port, connection, logger=None):
cliechti130d1f02009-08-04 02:10:58 +0000904 self.serial = serial_port
905 self.connection = connection
cliechti6a300772009-08-12 02:28:56 +0000906 self.logger = logger
cliechti86b593e2009-08-05 16:28:12 +0000907 self._client_is_rfc2217 = False
cliechti130d1f02009-08-04 02:10:58 +0000908
909 # filter state machine
910 self.mode = M_NORMAL
911 self.suboption = None
912 self.telnet_command = None
913
914 # states for modem/line control events
915 self.modemstate_mask = 255
916 self.last_modemstate = None
917 self.linstate_mask = 0
918
919 # all supported telnet options
920 self._telnet_options = [
921 TelnetOption(self, 'ECHO', ECHO, WILL, WONT, DO, DONT, REQUESTED),
922 TelnetOption(self, 'we-SGA', SGA, WILL, WONT, DO, DONT, REQUESTED),
923 TelnetOption(self, 'they-SGA', SGA, DO, DONT, WILL, WONT, INACTIVE),
924 TelnetOption(self, 'we-BINARY', BINARY, WILL, WONT, DO, DONT, INACTIVE),
925 TelnetOption(self, 'they-BINARY', BINARY, DO, DONT, WILL, WONT, REQUESTED),
cliechti86b593e2009-08-05 16:28:12 +0000926 TelnetOption(self, 'we-RFC2217', COM_PORT_OPTION, WILL, WONT, DO, DONT, REQUESTED, self._client_ok),
927 TelnetOption(self, 'they-RFC2217', COM_PORT_OPTION, DO, DONT, WILL, WONT, INACTIVE, self._client_ok),
cliechti130d1f02009-08-04 02:10:58 +0000928 ]
929
930 # negotiate Telnet/RFC2217 -> send initial requests
cliechti6a300772009-08-12 02:28:56 +0000931 if self.logger:
932 self.logger.debug("requesting initial Telnet/RFC 2217 options")
cliechti130d1f02009-08-04 02:10:58 +0000933 for option in self._telnet_options:
934 if option.state is REQUESTED:
935 self.telnetSendOption(option.send_yes, option.option)
936 # issue 1st modem state notification
cliechti86b593e2009-08-05 16:28:12 +0000937
938 def _client_ok(self):
cliechtieada4fd2013-07-31 16:26:07 +0000939 """\
cliechti7d448562014-08-03 21:57:45 +0000940 callback of telnet option. It gets called when option is activated.
941 This one here is used to detect when the client agrees on RFC 2217. A
cliechti86b593e2009-08-05 16:28:12 +0000942 flag is set so that other functions like check_modem_lines know if the
cliechti7d448562014-08-03 21:57:45 +0000943 client is OK.
cliechtieada4fd2013-07-31 16:26:07 +0000944 """
cliechti86b593e2009-08-05 16:28:12 +0000945 # The callback is used for we and they so if one party agrees, we're
946 # already happy. it seems not all servers do the negotiation correctly
947 # and i guess there are incorrect clients too.. so be happy if client
948 # answers one or the other positively.
949 self._client_is_rfc2217 = True
cliechti6a300772009-08-12 02:28:56 +0000950 if self.logger:
951 self.logger.info("client accepts RFC 2217")
cliechti8fb119c2009-08-05 23:39:45 +0000952 # this is to ensure that the client gets a notification, even if there
953 # was no change
954 self.check_modem_lines(force_notification=True)
cliechti130d1f02009-08-04 02:10:58 +0000955
956 # - outgoing telnet commands and options
957
958 def telnetSendOption(self, action, option):
cliechti044d8662009-08-11 21:40:31 +0000959 """Send DO, DONT, WILL, WONT."""
cliechti130d1f02009-08-04 02:10:58 +0000960 self.connection.write(to_bytes([IAC, action, option]))
961
cliechtif325c032009-12-25 16:09:49 +0000962 def rfc2217SendSubnegotiation(self, option, value=''):
cliechti044d8662009-08-11 21:40:31 +0000963 """Subnegotiation of RFC 2217 parameters."""
cliechtif325c032009-12-25 16:09:49 +0000964 value = value.replace(IAC, IAC_DOUBLED)
cliechti130d1f02009-08-04 02:10:58 +0000965 self.connection.write(to_bytes([IAC, SB, COM_PORT_OPTION, option] + list(value) + [IAC, SE]))
966
967 # - check modem lines, needs to be called periodically from user to
968 # establish polling
969
cliechti7cb78e82009-08-05 15:47:57 +0000970 def check_modem_lines(self, force_notification=False):
cliechti130d1f02009-08-04 02:10:58 +0000971 modemstate = (
972 (self.serial.getCTS() and MODEMSTATE_MASK_CTS) |
973 (self.serial.getDSR() and MODEMSTATE_MASK_DSR) |
974 (self.serial.getRI() and MODEMSTATE_MASK_RI) |
975 (self.serial.getCD() and MODEMSTATE_MASK_CD)
976 )
cliechti7cb78e82009-08-05 15:47:57 +0000977 # check what has changed
978 deltas = modemstate ^ (self.last_modemstate or 0) # when last is None -> 0
979 if deltas & MODEMSTATE_MASK_CTS:
980 modemstate |= MODEMSTATE_MASK_CTS_CHANGE
981 if deltas & MODEMSTATE_MASK_DSR:
982 modemstate |= MODEMSTATE_MASK_DSR_CHANGE
983 if deltas & MODEMSTATE_MASK_RI:
984 modemstate |= MODEMSTATE_MASK_RI_CHANGE
985 if deltas & MODEMSTATE_MASK_CD:
986 modemstate |= MODEMSTATE_MASK_CD_CHANGE
987 # if new state is different and the mask allows this change, send
cliechti86b593e2009-08-05 16:28:12 +0000988 # notification. suppress notifications when client is not rfc2217
cliechti7cb78e82009-08-05 15:47:57 +0000989 if modemstate != self.last_modemstate or force_notification:
cliechti8fb119c2009-08-05 23:39:45 +0000990 if (self._client_is_rfc2217 and (modemstate & self.modemstate_mask)) or force_notification:
cliechti7cb78e82009-08-05 15:47:57 +0000991 self.rfc2217SendSubnegotiation(
992 SERVER_NOTIFY_MODEMSTATE,
993 to_bytes([modemstate & self.modemstate_mask])
994 )
cliechti6a300772009-08-12 02:28:56 +0000995 if self.logger:
996 self.logger.info("NOTIFY_MODEMSTATE: %s" % (modemstate,))
cliechti7cb78e82009-08-05 15:47:57 +0000997 # save last state, but forget about deltas.
998 # otherwise it would also notify about changing deltas which is
999 # probably not very useful
1000 self.last_modemstate = modemstate & 0xf0
cliechti130d1f02009-08-04 02:10:58 +00001001
cliechti32c10332009-08-05 13:23:43 +00001002 # - outgoing data escaping
1003
1004 def escape(self, data):
cliechtieada4fd2013-07-31 16:26:07 +00001005 """\
cliechti7d448562014-08-03 21:57:45 +00001006 This generator function is for the user. All outgoing data has to be
cliechticb20a4f2011-04-25 02:25:54 +00001007 properly escaped, so that no IAC character in the data stream messes up
1008 the Telnet state machine in the server.
cliechti32c10332009-08-05 13:23:43 +00001009
1010 socket.sendall(escape(data))
1011 """
1012 for byte in data:
1013 if byte == IAC:
1014 yield IAC
1015 yield IAC
1016 else:
1017 yield byte
1018
cliechti130d1f02009-08-04 02:10:58 +00001019 # - incoming data filter
1020
1021 def filter(self, data):
cliechtieada4fd2013-07-31 16:26:07 +00001022 """\
cliechti7d448562014-08-03 21:57:45 +00001023 Handle a bunch of incoming bytes. This is a generator. It will yield
cliechti044d8662009-08-11 21:40:31 +00001024 all characters not of interest for Telnet/RFC 2217.
cliechti130d1f02009-08-04 02:10:58 +00001025
1026 The idea is that the reader thread pushes data from the socket through
1027 this filter:
1028
1029 for byte in filter(socket.recv(1024)):
1030 # do things like CR/LF conversion/whatever
1031 # and write data to the serial port
1032 serial.write(byte)
1033
1034 (socket error handling code left as exercise for the reader)
1035 """
1036 for byte in data:
1037 if self.mode == M_NORMAL:
1038 # interpret as command or as data
1039 if byte == IAC:
1040 self.mode = M_IAC_SEEN
1041 else:
1042 # store data in sub option buffer or pass it to our
1043 # consumer depending on state
1044 if self.suboption is not None:
1045 self.suboption.append(byte)
1046 else:
1047 yield byte
1048 elif self.mode == M_IAC_SEEN:
1049 if byte == IAC:
1050 # interpret as command doubled -> insert character
1051 # itself
cliechtif325c032009-12-25 16:09:49 +00001052 if self.suboption is not None:
1053 self.suboption.append(byte)
1054 else:
1055 yield byte
cliechti130d1f02009-08-04 02:10:58 +00001056 self.mode = M_NORMAL
1057 elif byte == SB:
1058 # sub option start
1059 self.suboption = bytearray()
1060 self.mode = M_NORMAL
1061 elif byte == SE:
1062 # sub option end -> process it now
1063 self._telnetProcessSubnegotiation(bytes(self.suboption))
1064 self.suboption = None
1065 self.mode = M_NORMAL
1066 elif byte in (DO, DONT, WILL, WONT):
1067 # negotiation
1068 self.telnet_command = byte
1069 self.mode = M_NEGOTIATE
1070 else:
1071 # other telnet commands
1072 self._telnetProcessCommand(byte)
1073 self.mode = M_NORMAL
1074 elif self.mode == M_NEGOTIATE: # DO, DONT, WILL, WONT was received, option now following
1075 self._telnetNegotiateOption(self.telnet_command, byte)
1076 self.mode = M_NORMAL
1077
1078 # - incoming telnet commands and options
1079
1080 def _telnetProcessCommand(self, command):
cliechti044d8662009-08-11 21:40:31 +00001081 """Process commands other than DO, DONT, WILL, WONT."""
cliechti130d1f02009-08-04 02:10:58 +00001082 # Currently none. RFC2217 only uses negotiation and subnegotiation.
cliechti6a300772009-08-12 02:28:56 +00001083 if self.logger:
1084 self.logger.warning("ignoring Telnet command: %r" % (command,))
cliechti130d1f02009-08-04 02:10:58 +00001085
1086 def _telnetNegotiateOption(self, command, option):
cliechti044d8662009-08-11 21:40:31 +00001087 """Process incoming DO, DONT, WILL, WONT."""
cliechti130d1f02009-08-04 02:10:58 +00001088 # check our registered telnet options and forward command to them
1089 # they know themselves if they have to answer or not
1090 known = False
1091 for item in self._telnet_options:
1092 # can have more than one match! as some options are duplicated for
1093 # 'us' and 'them'
1094 if item.option == option:
1095 item.process_incoming(command)
1096 known = True
1097 if not known:
1098 # handle unknown options
1099 # only answer to positive requests and deny them
1100 if command == WILL or command == DO:
1101 self.telnetSendOption((command == WILL and DONT or WONT), option)
cliechti6a300772009-08-12 02:28:56 +00001102 if self.logger:
1103 self.logger.warning("rejected Telnet option: %r" % (option,))
cliechti130d1f02009-08-04 02:10:58 +00001104
1105
1106 def _telnetProcessSubnegotiation(self, suboption):
cliechti044d8662009-08-11 21:40:31 +00001107 """Process subnegotiation, the data between IAC SB and IAC SE."""
cliechti130d1f02009-08-04 02:10:58 +00001108 if suboption[0:1] == COM_PORT_OPTION:
cliechti6a300772009-08-12 02:28:56 +00001109 if self.logger:
1110 self.logger.debug('received COM_PORT_OPTION: %r' % (suboption,))
cliechti130d1f02009-08-04 02:10:58 +00001111 if suboption[1:2] == SET_BAUDRATE:
1112 backup = self.serial.baudrate
1113 try:
cliechtieada4fd2013-07-31 16:26:07 +00001114 (baudrate,) = struct.unpack("!I", suboption[2:6])
1115 if baudrate != 0:
1116 self.serial.baudrate = baudrate
Chris Liechtid2146002015-08-04 16:57:16 +02001117 except ValueError as e:
cliechti6a300772009-08-12 02:28:56 +00001118 if self.logger:
1119 self.logger.error("failed to set baud rate: %s" % (e,))
cliechti130d1f02009-08-04 02:10:58 +00001120 self.serial.baudrate = backup
cliechti5cc3eb12009-08-11 23:04:30 +00001121 else:
cliechti6a300772009-08-12 02:28:56 +00001122 if self.logger:
cliechtieada4fd2013-07-31 16:26:07 +00001123 self.logger.info("%s baud rate: %s" % (baudrate and 'set' or 'get', self.serial.baudrate))
cliechti130d1f02009-08-04 02:10:58 +00001124 self.rfc2217SendSubnegotiation(SERVER_SET_BAUDRATE, struct.pack("!I", self.serial.baudrate))
1125 elif suboption[1:2] == SET_DATASIZE:
1126 backup = self.serial.bytesize
1127 try:
cliechtieada4fd2013-07-31 16:26:07 +00001128 (datasize,) = struct.unpack("!B", suboption[2:3])
1129 if datasize != 0:
1130 self.serial.bytesize = datasize
Chris Liechtid2146002015-08-04 16:57:16 +02001131 except ValueError as e:
cliechti6a300772009-08-12 02:28:56 +00001132 if self.logger:
1133 self.logger.error("failed to set data size: %s" % (e,))
cliechti130d1f02009-08-04 02:10:58 +00001134 self.serial.bytesize = backup
cliechti5cc3eb12009-08-11 23:04:30 +00001135 else:
cliechti6a300772009-08-12 02:28:56 +00001136 if self.logger:
cliechtieada4fd2013-07-31 16:26:07 +00001137 self.logger.info("%s data size: %s" % (datasize and 'set' or 'get', self.serial.bytesize))
cliechti130d1f02009-08-04 02:10:58 +00001138 self.rfc2217SendSubnegotiation(SERVER_SET_DATASIZE, struct.pack("!B", self.serial.bytesize))
1139 elif suboption[1:2] == SET_PARITY:
1140 backup = self.serial.parity
1141 try:
cliechtieada4fd2013-07-31 16:26:07 +00001142 parity = struct.unpack("!B", suboption[2:3])[0]
1143 if parity != 0:
1144 self.serial.parity = RFC2217_REVERSE_PARITY_MAP[parity]
Chris Liechtid2146002015-08-04 16:57:16 +02001145 except ValueError as e:
cliechti6a300772009-08-12 02:28:56 +00001146 if self.logger:
1147 self.logger.error("failed to set parity: %s" % (e,))
cliechti130d1f02009-08-04 02:10:58 +00001148 self.serial.parity = backup
cliechti5cc3eb12009-08-11 23:04:30 +00001149 else:
cliechti6a300772009-08-12 02:28:56 +00001150 if self.logger:
cliechtieada4fd2013-07-31 16:26:07 +00001151 self.logger.info("%s parity: %s" % (parity and 'set' or 'get', self.serial.parity))
cliechti130d1f02009-08-04 02:10:58 +00001152 self.rfc2217SendSubnegotiation(
1153 SERVER_SET_PARITY,
1154 struct.pack("!B", RFC2217_PARITY_MAP[self.serial.parity])
1155 )
1156 elif suboption[1:2] == SET_STOPSIZE:
1157 backup = self.serial.stopbits
1158 try:
cliechtieada4fd2013-07-31 16:26:07 +00001159 stopbits = struct.unpack("!B", suboption[2:3])[0]
1160 if stopbits != 0:
1161 self.serial.stopbits = RFC2217_REVERSE_STOPBIT_MAP[stopbits]
Chris Liechtid2146002015-08-04 16:57:16 +02001162 except ValueError as e:
cliechti6a300772009-08-12 02:28:56 +00001163 if self.logger:
1164 self.logger.error("failed to set stop bits: %s" % (e,))
cliechti130d1f02009-08-04 02:10:58 +00001165 self.serial.stopbits = backup
cliechti5cc3eb12009-08-11 23:04:30 +00001166 else:
cliechti6a300772009-08-12 02:28:56 +00001167 if self.logger:
cliechtieada4fd2013-07-31 16:26:07 +00001168 self.logger.info("%s stop bits: %s" % (stopbits and 'set' or 'get', self.serial.stopbits))
cliechti130d1f02009-08-04 02:10:58 +00001169 self.rfc2217SendSubnegotiation(
1170 SERVER_SET_STOPSIZE,
1171 struct.pack("!B", RFC2217_STOPBIT_MAP[self.serial.stopbits])
1172 )
1173 elif suboption[1:2] == SET_CONTROL:
1174 if suboption[2:3] == SET_CONTROL_REQ_FLOW_SETTING:
1175 if self.serial.xonxoff:
1176 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_SW_FLOW_CONTROL)
1177 elif self.serial.rtscts:
1178 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_HW_FLOW_CONTROL)
1179 else:
1180 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_NO_FLOW_CONTROL)
1181 elif suboption[2:3] == SET_CONTROL_USE_NO_FLOW_CONTROL:
1182 self.serial.xonxoff = False
1183 self.serial.rtscts = False
cliechti6a300772009-08-12 02:28:56 +00001184 if self.logger:
1185 self.logger.info("changed flow control to None")
cliechti130d1f02009-08-04 02:10:58 +00001186 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_NO_FLOW_CONTROL)
1187 elif suboption[2:3] == SET_CONTROL_USE_SW_FLOW_CONTROL:
1188 self.serial.xonxoff = True
cliechti6a300772009-08-12 02:28:56 +00001189 if self.logger:
1190 self.logger.info("changed flow control to XON/XOFF")
cliechti130d1f02009-08-04 02:10:58 +00001191 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_SW_FLOW_CONTROL)
1192 elif suboption[2:3] == SET_CONTROL_USE_HW_FLOW_CONTROL:
1193 self.serial.rtscts = True
cliechti6a300772009-08-12 02:28:56 +00001194 if self.logger:
1195 self.logger.info("changed flow control to RTS/CTS")
cliechti130d1f02009-08-04 02:10:58 +00001196 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_HW_FLOW_CONTROL)
1197 elif suboption[2:3] == SET_CONTROL_REQ_BREAK_STATE:
cliechti6a300772009-08-12 02:28:56 +00001198 if self.logger:
1199 self.logger.warning("requested break state - not implemented")
cliechti130d1f02009-08-04 02:10:58 +00001200 pass # XXX needs cached value
1201 elif suboption[2:3] == SET_CONTROL_BREAK_ON:
1202 self.serial.setBreak(True)
cliechti6a300772009-08-12 02:28:56 +00001203 if self.logger:
1204 self.logger.info("changed BREAK to active")
cliechti130d1f02009-08-04 02:10:58 +00001205 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_BREAK_ON)
1206 elif suboption[2:3] == SET_CONTROL_BREAK_OFF:
1207 self.serial.setBreak(False)
cliechti6a300772009-08-12 02:28:56 +00001208 if self.logger:
1209 self.logger.info("changed BREAK to inactive")
cliechti130d1f02009-08-04 02:10:58 +00001210 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_BREAK_OFF)
1211 elif suboption[2:3] == SET_CONTROL_REQ_DTR:
cliechti6a300772009-08-12 02:28:56 +00001212 if self.logger:
1213 self.logger.warning("requested DTR state - not implemented")
cliechti130d1f02009-08-04 02:10:58 +00001214 pass # XXX needs cached value
1215 elif suboption[2:3] == SET_CONTROL_DTR_ON:
1216 self.serial.setDTR(True)
cliechti6a300772009-08-12 02:28:56 +00001217 if self.logger:
1218 self.logger.info("changed DTR to active")
cliechti130d1f02009-08-04 02:10:58 +00001219 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_DTR_ON)
1220 elif suboption[2:3] == SET_CONTROL_DTR_OFF:
1221 self.serial.setDTR(False)
cliechti6a300772009-08-12 02:28:56 +00001222 if self.logger:
1223 self.logger.info("changed DTR to inactive")
cliechti130d1f02009-08-04 02:10:58 +00001224 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_DTR_OFF)
1225 elif suboption[2:3] == SET_CONTROL_REQ_RTS:
cliechti6a300772009-08-12 02:28:56 +00001226 if self.logger:
1227 self.logger.warning("requested RTS state - not implemented")
cliechti130d1f02009-08-04 02:10:58 +00001228 pass # XXX needs cached value
1229 #~ self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_RTS_ON)
1230 elif suboption[2:3] == SET_CONTROL_RTS_ON:
1231 self.serial.setRTS(True)
cliechti6a300772009-08-12 02:28:56 +00001232 if self.logger:
1233 self.logger.info("changed RTS to active")
cliechti130d1f02009-08-04 02:10:58 +00001234 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_RTS_ON)
1235 elif suboption[2:3] == SET_CONTROL_RTS_OFF:
1236 self.serial.setRTS(False)
cliechti6a300772009-08-12 02:28:56 +00001237 if self.logger:
1238 self.logger.info("changed RTS to inactive")
cliechti130d1f02009-08-04 02:10:58 +00001239 self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_RTS_OFF)
1240 #~ elif suboption[2:3] == SET_CONTROL_REQ_FLOW_SETTING_IN:
1241 #~ elif suboption[2:3] == SET_CONTROL_USE_NO_FLOW_CONTROL_IN:
1242 #~ elif suboption[2:3] == SET_CONTROL_USE_SW_FLOW_CONTOL_IN:
1243 #~ elif suboption[2:3] == SET_CONTROL_USE_HW_FLOW_CONTOL_IN:
1244 #~ elif suboption[2:3] == SET_CONTROL_USE_DCD_FLOW_CONTROL:
1245 #~ elif suboption[2:3] == SET_CONTROL_USE_DTR_FLOW_CONTROL:
1246 #~ elif suboption[2:3] == SET_CONTROL_USE_DSR_FLOW_CONTROL:
1247 elif suboption[1:2] == NOTIFY_LINESTATE:
cliechti7cb78e82009-08-05 15:47:57 +00001248 # client polls for current state
1249 self.rfc2217SendSubnegotiation(
1250 SERVER_NOTIFY_LINESTATE,
cliechti6a300772009-08-12 02:28:56 +00001251 to_bytes([0]) # sorry, nothing like that implemented
cliechti7cb78e82009-08-05 15:47:57 +00001252 )
cliechti130d1f02009-08-04 02:10:58 +00001253 elif suboption[1:2] == NOTIFY_MODEMSTATE:
cliechti6a300772009-08-12 02:28:56 +00001254 if self.logger:
1255 self.logger.info("request for modem state")
cliechti7cb78e82009-08-05 15:47:57 +00001256 # client polls for current state
1257 self.check_modem_lines(force_notification=True)
cliechti130d1f02009-08-04 02:10:58 +00001258 elif suboption[1:2] == FLOWCONTROL_SUSPEND:
cliechti6a300772009-08-12 02:28:56 +00001259 if self.logger:
1260 self.logger.info("suspend")
cliechti130d1f02009-08-04 02:10:58 +00001261 self._remote_suspend_flow = True
1262 elif suboption[1:2] == FLOWCONTROL_RESUME:
cliechti6a300772009-08-12 02:28:56 +00001263 if self.logger:
1264 self.logger.info("resume")
cliechti130d1f02009-08-04 02:10:58 +00001265 self._remote_suspend_flow = False
1266 elif suboption[1:2] == SET_LINESTATE_MASK:
1267 self.linstate_mask = ord(suboption[2:3]) # ensure it is a number
cliechti6a300772009-08-12 02:28:56 +00001268 if self.logger:
cliechtif325c032009-12-25 16:09:49 +00001269 self.logger.info("line state mask: 0x%02x" % (self.linstate_mask,))
cliechti130d1f02009-08-04 02:10:58 +00001270 elif suboption[1:2] == SET_MODEMSTATE_MASK:
1271 self.modemstate_mask = ord(suboption[2:3]) # ensure it is a number
cliechti6a300772009-08-12 02:28:56 +00001272 if self.logger:
cliechtif325c032009-12-25 16:09:49 +00001273 self.logger.info("modem state mask: 0x%02x" % (self.modemstate_mask,))
cliechti130d1f02009-08-04 02:10:58 +00001274 elif suboption[1:2] == PURGE_DATA:
1275 if suboption[2:3] == PURGE_RECEIVE_BUFFER:
1276 self.serial.flushInput()
cliechti6a300772009-08-12 02:28:56 +00001277 if self.logger:
1278 self.logger.info("purge in")
cliechti130d1f02009-08-04 02:10:58 +00001279 self.rfc2217SendSubnegotiation(SERVER_PURGE_DATA, PURGE_RECEIVE_BUFFER)
1280 elif suboption[2:3] == PURGE_TRANSMIT_BUFFER:
1281 self.serial.flushOutput()
cliechti6a300772009-08-12 02:28:56 +00001282 if self.logger:
1283 self.logger.info("purge out")
cliechti130d1f02009-08-04 02:10:58 +00001284 self.rfc2217SendSubnegotiation(SERVER_PURGE_DATA, PURGE_TRANSMIT_BUFFER)
1285 elif suboption[2:3] == PURGE_BOTH_BUFFERS:
1286 self.serial.flushInput()
1287 self.serial.flushOutput()
cliechti6a300772009-08-12 02:28:56 +00001288 if self.logger:
1289 self.logger.info("purge both")
cliechti130d1f02009-08-04 02:10:58 +00001290 self.rfc2217SendSubnegotiation(SERVER_PURGE_DATA, PURGE_BOTH_BUFFERS)
1291 else:
cliechti6a300772009-08-12 02:28:56 +00001292 if self.logger:
1293 self.logger.error("undefined PURGE_DATA: %r" % list(suboption[2:]))
cliechti130d1f02009-08-04 02:10:58 +00001294 else:
cliechti6a300772009-08-12 02:28:56 +00001295 if self.logger:
1296 self.logger.error("undefined COM_PORT_OPTION: %r" % list(suboption[1:]))
cliechti130d1f02009-08-04 02:10:58 +00001297 else:
cliechti6a300772009-08-12 02:28:56 +00001298 if self.logger:
1299 self.logger.warning("unknown subnegotiation: %r" % (suboption,))
cliechti130d1f02009-08-04 02:10:58 +00001300
1301
1302# simple client test
cliechti8099bed2009-08-01 23:59:18 +00001303if __name__ == '__main__':
1304 import sys
1305 s = Serial('rfc2217://localhost:7000', 115200)
1306 sys.stdout.write('%s\n' % s)
1307
cliechti2b929b72009-08-02 23:49:02 +00001308 #~ s.baudrate = 1898
1309
cliechti8099bed2009-08-01 23:59:18 +00001310 sys.stdout.write("write...\n")
1311 s.write("hello\n")
1312 s.flush()
cliechti8099bed2009-08-01 23:59:18 +00001313 sys.stdout.write("read: %s\n" % s.read(5))
1314
1315 #~ s.baudrate = 19200
1316 #~ s.databits = 7
1317 s.close()