diff options
author | ttt | 2017-05-13 00:29:47 +0530 |
---|---|---|
committer | ttt | 2017-05-13 00:29:47 +0530 |
commit | abf599be33b383a6a5baf9493093b2126a622ac8 (patch) | |
tree | 4c5ab6e0d935d5e65fabcf0258e4a00dd20a5afa /lib/python2.7/site-packages/serial/rfc2217.py | |
download | SBHS-2018-Rpi-abf599be33b383a6a5baf9493093b2126a622ac8.tar.gz SBHS-2018-Rpi-abf599be33b383a6a5baf9493093b2126a622ac8.tar.bz2 SBHS-2018-Rpi-abf599be33b383a6a5baf9493093b2126a622ac8.zip |
added all server files
Diffstat (limited to 'lib/python2.7/site-packages/serial/rfc2217.py')
-rw-r--r-- | lib/python2.7/site-packages/serial/rfc2217.py | 1323 |
1 files changed, 1323 insertions, 0 deletions
diff --git a/lib/python2.7/site-packages/serial/rfc2217.py b/lib/python2.7/site-packages/serial/rfc2217.py new file mode 100644 index 0000000..2012ea7 --- /dev/null +++ b/lib/python2.7/site-packages/serial/rfc2217.py @@ -0,0 +1,1323 @@ +#! python +# +# Python Serial Port Extension for Win32, Linux, BSD, Jython +# see __init__.py +# +# This module implements a RFC2217 compatible client. RF2217 descibes a +# protocol to access serial ports over TCP/IP and allows setting the baud rate, +# modem control lines etc. +# +# (C) 2001-2013 Chris Liechti <cliechti@gmx.net> +# this is distributed under a free software license, see license.txt + +# TODO: +# - setting control line -> answer is not checked (had problems with one of the +# severs). consider implementing a compatibility mode flag to make check +# conditional +# - write timeout not implemented at all + +############################################################################## +# observations and issues with servers +#============================================================================= +# sredird V2.2.1 +# - http://www.ibiblio.org/pub/Linux/system/serial/ sredird-2.2.2.tar.gz +# - does not acknowledge SET_CONTROL (RTS/DTR) correctly, always responding +# [105 1] instead of the actual value. +# - SET_BAUDRATE answer contains 4 extra null bytes -> probably for larger +# numbers than 2**32? +# - To get the signature [COM_PORT_OPTION 0] has to be sent. +# - run a server: while true; do nc -l -p 7000 -c "sredird debug /dev/ttyUSB0 /var/lock/sredir"; done +#============================================================================= +# telnetcpcd (untested) +# - http://ftp.wayne.edu/kermit/sredird/telnetcpcd-1.09.tar.gz +# - To get the signature [COM_PORT_OPTION] w/o data has to be sent. +#============================================================================= +# ser2net +# - does not negotiate BINARY or COM_PORT_OPTION for his side but at least +# acknowledges that the client activates these options +# - The configuration may be that the server prints a banner. As this client +# implementation does a flushInput on connect, this banner is hidden from +# the user application. +# - NOTIFY_MODEMSTATE: the poll interval of the server seems to be one +# second. +# - To get the signature [COM_PORT_OPTION 0] has to be sent. +# - run a server: run ser2net daemon, in /etc/ser2net.conf: +# 2000:telnet:0:/dev/ttyS0:9600 remctl banner +############################################################################## + +# How to identify ports? pySerial might want to support other protocols in the +# future, so lets use an URL scheme. +# for RFC2217 compliant servers we will use this: +# rfc2217://<host>:<port>[/option[/option...]] +# +# options: +# - "debug" print diagnostic messages +# - "ign_set_control": do not look at the answers to SET_CONTROL +# - "poll_modem": issue NOTIFY_MODEMSTATE requests when CTS/DTR/RI/CD is read. +# Without this option it expects that the server sends notifications +# automatically on change (which most servers do and is according to the +# RFC). +# the order of the options is not relevant + +from serial.serialutil import * +import time +import struct +import socket +import threading +import Queue +import logging + +# port string is expected to be something like this: +# rfc2217://host:port +# host may be an IP or including domain, whatever. +# port is 0...65535 + +# map log level names to constants. used in fromURL() +LOGGER_LEVELS = { + 'debug': logging.DEBUG, + 'info': logging.INFO, + 'warning': logging.WARNING, + 'error': logging.ERROR, + } + + +# telnet protocol characters +IAC = to_bytes([255]) # Interpret As Command +DONT = to_bytes([254]) +DO = to_bytes([253]) +WONT = to_bytes([252]) +WILL = to_bytes([251]) +IAC_DOUBLED = to_bytes([IAC, IAC]) + +SE = to_bytes([240]) # Subnegotiation End +NOP = to_bytes([241]) # No Operation +DM = to_bytes([242]) # Data Mark +BRK = to_bytes([243]) # Break +IP = to_bytes([244]) # Interrupt process +AO = to_bytes([245]) # Abort output +AYT = to_bytes([246]) # Are You There +EC = to_bytes([247]) # Erase Character +EL = to_bytes([248]) # Erase Line +GA = to_bytes([249]) # Go Ahead +SB = to_bytes([250]) # Subnegotiation Begin + +# selected telnet options +BINARY = to_bytes([0]) # 8-bit data path +ECHO = to_bytes([1]) # echo +SGA = to_bytes([3]) # suppress go ahead + +# RFC2217 +COM_PORT_OPTION = to_bytes([44]) + +# Client to Access Server +SET_BAUDRATE = to_bytes([1]) +SET_DATASIZE = to_bytes([2]) +SET_PARITY = to_bytes([3]) +SET_STOPSIZE = to_bytes([4]) +SET_CONTROL = to_bytes([5]) +NOTIFY_LINESTATE = to_bytes([6]) +NOTIFY_MODEMSTATE = to_bytes([7]) +FLOWCONTROL_SUSPEND = to_bytes([8]) +FLOWCONTROL_RESUME = to_bytes([9]) +SET_LINESTATE_MASK = to_bytes([10]) +SET_MODEMSTATE_MASK = to_bytes([11]) +PURGE_DATA = to_bytes([12]) + +SERVER_SET_BAUDRATE = to_bytes([101]) +SERVER_SET_DATASIZE = to_bytes([102]) +SERVER_SET_PARITY = to_bytes([103]) +SERVER_SET_STOPSIZE = to_bytes([104]) +SERVER_SET_CONTROL = to_bytes([105]) +SERVER_NOTIFY_LINESTATE = to_bytes([106]) +SERVER_NOTIFY_MODEMSTATE = to_bytes([107]) +SERVER_FLOWCONTROL_SUSPEND = to_bytes([108]) +SERVER_FLOWCONTROL_RESUME = to_bytes([109]) +SERVER_SET_LINESTATE_MASK = to_bytes([110]) +SERVER_SET_MODEMSTATE_MASK = to_bytes([111]) +SERVER_PURGE_DATA = to_bytes([112]) + +RFC2217_ANSWER_MAP = { + SET_BAUDRATE: SERVER_SET_BAUDRATE, + SET_DATASIZE: SERVER_SET_DATASIZE, + SET_PARITY: SERVER_SET_PARITY, + SET_STOPSIZE: SERVER_SET_STOPSIZE, + SET_CONTROL: SERVER_SET_CONTROL, + NOTIFY_LINESTATE: SERVER_NOTIFY_LINESTATE, + NOTIFY_MODEMSTATE: SERVER_NOTIFY_MODEMSTATE, + FLOWCONTROL_SUSPEND: SERVER_FLOWCONTROL_SUSPEND, + FLOWCONTROL_RESUME: SERVER_FLOWCONTROL_RESUME, + SET_LINESTATE_MASK: SERVER_SET_LINESTATE_MASK, + SET_MODEMSTATE_MASK: SERVER_SET_MODEMSTATE_MASK, + PURGE_DATA: SERVER_PURGE_DATA, +} + +SET_CONTROL_REQ_FLOW_SETTING = to_bytes([0]) # Request Com Port Flow Control Setting (outbound/both) +SET_CONTROL_USE_NO_FLOW_CONTROL = to_bytes([1]) # Use No Flow Control (outbound/both) +SET_CONTROL_USE_SW_FLOW_CONTROL = to_bytes([2]) # Use XON/XOFF Flow Control (outbound/both) +SET_CONTROL_USE_HW_FLOW_CONTROL = to_bytes([3]) # Use HARDWARE Flow Control (outbound/both) +SET_CONTROL_REQ_BREAK_STATE = to_bytes([4]) # Request BREAK State +SET_CONTROL_BREAK_ON = to_bytes([5]) # Set BREAK State ON +SET_CONTROL_BREAK_OFF = to_bytes([6]) # Set BREAK State OFF +SET_CONTROL_REQ_DTR = to_bytes([7]) # Request DTR Signal State +SET_CONTROL_DTR_ON = to_bytes([8]) # Set DTR Signal State ON +SET_CONTROL_DTR_OFF = to_bytes([9]) # Set DTR Signal State OFF +SET_CONTROL_REQ_RTS = to_bytes([10]) # Request RTS Signal State +SET_CONTROL_RTS_ON = to_bytes([11]) # Set RTS Signal State ON +SET_CONTROL_RTS_OFF = to_bytes([12]) # Set RTS Signal State OFF +SET_CONTROL_REQ_FLOW_SETTING_IN = to_bytes([13]) # Request Com Port Flow Control Setting (inbound) +SET_CONTROL_USE_NO_FLOW_CONTROL_IN = to_bytes([14]) # Use No Flow Control (inbound) +SET_CONTROL_USE_SW_FLOW_CONTOL_IN = to_bytes([15]) # Use XON/XOFF Flow Control (inbound) +SET_CONTROL_USE_HW_FLOW_CONTOL_IN = to_bytes([16]) # Use HARDWARE Flow Control (inbound) +SET_CONTROL_USE_DCD_FLOW_CONTROL = to_bytes([17]) # Use DCD Flow Control (outbound/both) +SET_CONTROL_USE_DTR_FLOW_CONTROL = to_bytes([18]) # Use DTR Flow Control (inbound) +SET_CONTROL_USE_DSR_FLOW_CONTROL = to_bytes([19]) # Use DSR Flow Control (outbound/both) + +LINESTATE_MASK_TIMEOUT = 128 # Time-out Error +LINESTATE_MASK_SHIFTREG_EMPTY = 64 # Transfer Shift Register Empty +LINESTATE_MASK_TRANSREG_EMPTY = 32 # Transfer Holding Register Empty +LINESTATE_MASK_BREAK_DETECT = 16 # Break-detect Error +LINESTATE_MASK_FRAMING_ERROR = 8 # Framing Error +LINESTATE_MASK_PARTIY_ERROR = 4 # Parity Error +LINESTATE_MASK_OVERRUN_ERROR = 2 # Overrun Error +LINESTATE_MASK_DATA_READY = 1 # Data Ready + +MODEMSTATE_MASK_CD = 128 # Receive Line Signal Detect (also known as Carrier Detect) +MODEMSTATE_MASK_RI = 64 # Ring Indicator +MODEMSTATE_MASK_DSR = 32 # Data-Set-Ready Signal State +MODEMSTATE_MASK_CTS = 16 # Clear-To-Send Signal State +MODEMSTATE_MASK_CD_CHANGE = 8 # Delta Receive Line Signal Detect +MODEMSTATE_MASK_RI_CHANGE = 4 # Trailing-edge Ring Detector +MODEMSTATE_MASK_DSR_CHANGE = 2 # Delta Data-Set-Ready +MODEMSTATE_MASK_CTS_CHANGE = 1 # Delta Clear-To-Send + +PURGE_RECEIVE_BUFFER = to_bytes([1]) # Purge access server receive data buffer +PURGE_TRANSMIT_BUFFER = to_bytes([2]) # Purge access server transmit data buffer +PURGE_BOTH_BUFFERS = to_bytes([3]) # Purge both the access server receive data buffer and the access server transmit data buffer + + +RFC2217_PARITY_MAP = { + PARITY_NONE: 1, + PARITY_ODD: 2, + PARITY_EVEN: 3, + PARITY_MARK: 4, + PARITY_SPACE: 5, +} +RFC2217_REVERSE_PARITY_MAP = dict((v,k) for k,v in RFC2217_PARITY_MAP.items()) + +RFC2217_STOPBIT_MAP = { + STOPBITS_ONE: 1, + STOPBITS_ONE_POINT_FIVE: 3, + STOPBITS_TWO: 2, +} +RFC2217_REVERSE_STOPBIT_MAP = dict((v,k) for k,v in RFC2217_STOPBIT_MAP.items()) + +# Telnet filter states +M_NORMAL = 0 +M_IAC_SEEN = 1 +M_NEGOTIATE = 2 + +# TelnetOption and TelnetSubnegotiation states +REQUESTED = 'REQUESTED' +ACTIVE = 'ACTIVE' +INACTIVE = 'INACTIVE' +REALLY_INACTIVE = 'REALLY_INACTIVE' + +class TelnetOption(object): + """Manage a single telnet option, keeps track of DO/DONT WILL/WONT.""" + + def __init__(self, connection, name, option, send_yes, send_no, ack_yes, ack_no, initial_state, activation_callback=None): + """\ + Initialize option. + :param connection: connection used to transmit answers + :param name: a readable name for debug outputs + :param send_yes: what to send when option is to be enabled. + :param send_no: what to send when option is to be disabled. + :param ack_yes: what to expect when remote agrees on option. + :param ack_no: what to expect when remote disagrees on option. + :param initial_state: options initialized with REQUESTED are tried to + be enabled on startup. use INACTIVE for all others. + """ + self.connection = connection + self.name = name + self.option = option + self.send_yes = send_yes + self.send_no = send_no + self.ack_yes = ack_yes + self.ack_no = ack_no + self.state = initial_state + self.active = False + self.activation_callback = activation_callback + + def __repr__(self): + """String for debug outputs""" + return "%s:%s(%s)" % (self.name, self.active, self.state) + + def process_incoming(self, command): + """A DO/DONT/WILL/WONT was received for this option, update state and + answer when needed.""" + if command == self.ack_yes: + if self.state is REQUESTED: + self.state = ACTIVE + self.active = True + if self.activation_callback is not None: + self.activation_callback() + elif self.state is ACTIVE: + pass + elif self.state is INACTIVE: + self.state = ACTIVE + self.connection.telnetSendOption(self.send_yes, self.option) + self.active = True + if self.activation_callback is not None: + self.activation_callback() + elif self.state is REALLY_INACTIVE: + self.connection.telnetSendOption(self.send_no, self.option) + else: + raise ValueError('option in illegal state %r' % self) + elif command == self.ack_no: + if self.state is REQUESTED: + self.state = INACTIVE + self.active = False + elif self.state is ACTIVE: + self.state = INACTIVE + self.connection.telnetSendOption(self.send_no, self.option) + self.active = False + elif self.state is INACTIVE: + pass + elif self.state is REALLY_INACTIVE: + pass + else: + raise ValueError('option in illegal state %r' % self) + + +class TelnetSubnegotiation(object): + """\ + A object to handle subnegotiation of options. In this case actually + sub-sub options for RFC 2217. It is used to track com port options. + """ + + def __init__(self, connection, name, option, ack_option=None): + if ack_option is None: ack_option = option + self.connection = connection + self.name = name + self.option = option + self.value = None + self.ack_option = ack_option + self.state = INACTIVE + + def __repr__(self): + """String for debug outputs.""" + return "%s:%s" % (self.name, self.state) + + def set(self, value): + """\ + request a change of the value. a request is sent to the server. if + the client needs to know if the change is performed he has to check the + state of this object. + """ + self.value = value + self.state = REQUESTED + self.connection.rfc2217SendSubnegotiation(self.option, self.value) + if self.connection.logger: + self.connection.logger.debug("SB Requesting %s -> %r" % (self.name, self.value)) + + def isReady(self): + """\ + check if answer from server has been received. when server rejects + the change, raise a ValueError. + """ + if self.state == REALLY_INACTIVE: + raise ValueError("remote rejected value for option %r" % (self.name)) + return self.state == ACTIVE + # add property to have a similar interface as TelnetOption + active = property(isReady) + + def wait(self, timeout=3): + """\ + wait until the subnegotiation has been acknowledged or timeout. It + can also throw a value error when the answer from the server does not + match the value sent. + """ + timeout_time = time.time() + timeout + while time.time() < timeout_time: + time.sleep(0.05) # prevent 100% CPU load + if self.isReady(): + break + else: + raise SerialException("timeout while waiting for option %r" % (self.name)) + + def checkAnswer(self, suboption): + """\ + check an incoming subnegotiation block. the parameter already has + cut off the header like sub option number and com port option value. + """ + if self.value == suboption[:len(self.value)]: + self.state = ACTIVE + else: + # error propagation done in isReady + self.state = REALLY_INACTIVE + if self.connection.logger: + self.connection.logger.debug("SB Answer %s -> %r -> %s" % (self.name, suboption, self.state)) + + +class RFC2217Serial(SerialBase): + """Serial port implementation for RFC 2217 remote serial ports.""" + + BAUDRATES = (50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800, + 9600, 19200, 38400, 57600, 115200) + + def open(self): + """\ + Open port with current settings. This may throw a SerialException + if the port cannot be opened. + """ + self.logger = None + self._ignore_set_control_answer = False + self._poll_modem_state = False + self._network_timeout = 3 + if self._port is None: + raise SerialException("Port must be configured before it can be used.") + if self._isOpen: + raise SerialException("Port is already open.") + try: + self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self._socket.connect(self.fromURL(self.portstr)) + self._socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) + except Exception, msg: + self._socket = None + raise SerialException("Could not open port %s: %s" % (self.portstr, msg)) + + self._socket.settimeout(5) # XXX good value? + + # use a thread save queue as buffer. it also simplifies implementing + # the read timeout + self._read_buffer = Queue.Queue() + # to ensure that user writes does not interfere with internal + # telnet/rfc2217 options establish a lock + self._write_lock = threading.Lock() + # name the following separately so that, below, a check can be easily done + mandadory_options = [ + TelnetOption(self, 'we-BINARY', BINARY, WILL, WONT, DO, DONT, INACTIVE), + TelnetOption(self, 'we-RFC2217', COM_PORT_OPTION, WILL, WONT, DO, DONT, REQUESTED), + ] + # all supported telnet options + self._telnet_options = [ + TelnetOption(self, 'ECHO', ECHO, DO, DONT, WILL, WONT, REQUESTED), + TelnetOption(self, 'we-SGA', SGA, WILL, WONT, DO, DONT, REQUESTED), + TelnetOption(self, 'they-SGA', SGA, DO, DONT, WILL, WONT, REQUESTED), + TelnetOption(self, 'they-BINARY', BINARY, DO, DONT, WILL, WONT, INACTIVE), + TelnetOption(self, 'they-RFC2217', COM_PORT_OPTION, DO, DONT, WILL, WONT, REQUESTED), + ] + mandadory_options + # RFC 2217 specific states + # COM port settings + self._rfc2217_port_settings = { + 'baudrate': TelnetSubnegotiation(self, 'baudrate', SET_BAUDRATE, SERVER_SET_BAUDRATE), + 'datasize': TelnetSubnegotiation(self, 'datasize', SET_DATASIZE, SERVER_SET_DATASIZE), + 'parity': TelnetSubnegotiation(self, 'parity', SET_PARITY, SERVER_SET_PARITY), + 'stopsize': TelnetSubnegotiation(self, 'stopsize', SET_STOPSIZE, SERVER_SET_STOPSIZE), + } + # There are more subnegotiation objects, combine all in one dictionary + # for easy access + self._rfc2217_options = { + 'purge': TelnetSubnegotiation(self, 'purge', PURGE_DATA, SERVER_PURGE_DATA), + 'control': TelnetSubnegotiation(self, 'control', SET_CONTROL, SERVER_SET_CONTROL), + } + self._rfc2217_options.update(self._rfc2217_port_settings) + # cache for line and modem states that the server sends to us + self._linestate = 0 + self._modemstate = None + self._modemstate_expires = 0 + # RFC 2217 flow control between server and client + self._remote_suspend_flow = False + + self._thread = threading.Thread(target=self._telnetReadLoop) + self._thread.setDaemon(True) + self._thread.setName('pySerial RFC 2217 reader thread for %s' % (self._port,)) + self._thread.start() + + # negotiate Telnet/RFC 2217 -> send initial requests + for option in self._telnet_options: + if option.state is REQUESTED: + self.telnetSendOption(option.send_yes, option.option) + # now wait until important options are negotiated + timeout_time = time.time() + self._network_timeout + while time.time() < timeout_time: + time.sleep(0.05) # prevent 100% CPU load + if sum(o.active for o in mandadory_options) == len(mandadory_options): + break + else: + raise SerialException("Remote does not seem to support RFC2217 or BINARY mode %r" % mandadory_options) + if self.logger: + self.logger.info("Negotiated options: %s" % self._telnet_options) + + # fine, go on, set RFC 2271 specific things + self._reconfigurePort() + # all things set up get, now a clean start + self._isOpen = True + if not self._rtscts: + self.setRTS(True) + self.setDTR(True) + self.flushInput() + self.flushOutput() + + def _reconfigurePort(self): + """Set communication parameters on opened port.""" + if self._socket is None: + raise SerialException("Can only operate on open ports") + + # if self._timeout != 0 and self._interCharTimeout is not None: + # XXX + + if self._writeTimeout is not None: + raise NotImplementedError('writeTimeout is currently not supported') + # XXX + + # Setup the connection + # to get good performance, all parameter changes are sent first... + if not isinstance(self._baudrate, (int, long)) or not 0 < self._baudrate < 2**32: + raise ValueError("invalid baudrate: %r" % (self._baudrate)) + self._rfc2217_port_settings['baudrate'].set(struct.pack('!I', self._baudrate)) + self._rfc2217_port_settings['datasize'].set(struct.pack('!B', self._bytesize)) + self._rfc2217_port_settings['parity'].set(struct.pack('!B', RFC2217_PARITY_MAP[self._parity])) + self._rfc2217_port_settings['stopsize'].set(struct.pack('!B', RFC2217_STOPBIT_MAP[self._stopbits])) + + # and now wait until parameters are active + items = self._rfc2217_port_settings.values() + if self.logger: + self.logger.debug("Negotiating settings: %s" % (items,)) + timeout_time = time.time() + self._network_timeout + while time.time() < timeout_time: + time.sleep(0.05) # prevent 100% CPU load + if sum(o.active for o in items) == len(items): + break + else: + raise SerialException("Remote does not accept parameter change (RFC2217): %r" % items) + if self.logger: + self.logger.info("Negotiated settings: %s" % (items,)) + + if self._rtscts and self._xonxoff: + raise ValueError('xonxoff and rtscts together are not supported') + elif self._rtscts: + self.rfc2217SetControl(SET_CONTROL_USE_HW_FLOW_CONTROL) + elif self._xonxoff: + self.rfc2217SetControl(SET_CONTROL_USE_SW_FLOW_CONTROL) + else: + self.rfc2217SetControl(SET_CONTROL_USE_NO_FLOW_CONTROL) + + def close(self): + """Close port""" + if self._isOpen: + if self._socket: + try: + self._socket.shutdown(socket.SHUT_RDWR) + self._socket.close() + except: + # ignore errors. + pass + self._socket = None + if self._thread: + self._thread.join() + self._isOpen = False + # in case of quick reconnects, give the server some time + time.sleep(0.3) + + def makeDeviceName(self, port): + raise SerialException("there is no sensible way to turn numbers into URLs") + + def fromURL(self, url): + """extract host and port from an URL string""" + if url.lower().startswith("rfc2217://"): url = url[10:] + try: + # is there a "path" (our options)? + if '/' in url: + # cut away options + url, options = url.split('/', 1) + # process options now, directly altering self + for option in options.split('/'): + if '=' in option: + option, value = option.split('=', 1) + else: + value = None + if option == 'logging': + logging.basicConfig() # XXX is that good to call it here? + self.logger = logging.getLogger('pySerial.rfc2217') + self.logger.setLevel(LOGGER_LEVELS[value]) + self.logger.debug('enabled logging') + elif option == 'ign_set_control': + self._ignore_set_control_answer = True + elif option == 'poll_modem': + self._poll_modem_state = True + elif option == 'timeout': + self._network_timeout = float(value) + else: + raise ValueError('unknown option: %r' % (option,)) + # get host and port + host, port = url.split(':', 1) # may raise ValueError because of unpacking + port = int(port) # and this if it's not a number + if not 0 <= port < 65536: raise ValueError("port not in range 0...65535") + except ValueError, e: + raise SerialException('expected a string in the form "[rfc2217://]<host>:<port>[/option[/option...]]": %s' % e) + return (host, port) + + # - - - - - - - - - - - - - - - - - - - - - - - - + + def inWaiting(self): + """Return the number of characters currently in the input buffer.""" + if not self._isOpen: raise portNotOpenError + return self._read_buffer.qsize() + + def read(self, size=1): + """\ + Read size bytes from the serial port. If a timeout is set it may + return less characters as requested. With no timeout it will block + until the requested number of bytes is read. + """ + if not self._isOpen: raise portNotOpenError + data = bytearray() + try: + while len(data) < size: + if self._thread is None: + raise SerialException('connection failed (reader thread died)') + data.append(self._read_buffer.get(True, self._timeout)) + except Queue.Empty: # -> timeout + pass + return bytes(data) + + def write(self, data): + """\ + Output the given string over the serial port. Can block if the + connection is blocked. May raise SerialException if the connection is + closed. + """ + if not self._isOpen: raise portNotOpenError + self._write_lock.acquire() + try: + try: + self._socket.sendall(to_bytes(data).replace(IAC, IAC_DOUBLED)) + except socket.error, e: + raise SerialException("connection failed (socket error): %s" % e) # XXX what exception if socket connection fails + finally: + self._write_lock.release() + return len(data) + + def flushInput(self): + """Clear input buffer, discarding all that is in the buffer.""" + if not self._isOpen: raise portNotOpenError + self.rfc2217SendPurge(PURGE_RECEIVE_BUFFER) + # empty read buffer + while self._read_buffer.qsize(): + self._read_buffer.get(False) + + def flushOutput(self): + """\ + Clear output buffer, aborting the current output and + discarding all that is in the buffer. + """ + if not self._isOpen: raise portNotOpenError + self.rfc2217SendPurge(PURGE_TRANSMIT_BUFFER) + + def sendBreak(self, duration=0.25): + """Send break condition. Timed, returns to idle state after given + duration.""" + if not self._isOpen: raise portNotOpenError + self.setBreak(True) + time.sleep(duration) + self.setBreak(False) + + def setBreak(self, level=True): + """\ + Set break: Controls TXD. When active, to transmitting is + possible. + """ + if not self._isOpen: raise portNotOpenError + if self.logger: + self.logger.info('set BREAK to %s' % ('inactive', 'active')[bool(level)]) + if level: + self.rfc2217SetControl(SET_CONTROL_BREAK_ON) + else: + self.rfc2217SetControl(SET_CONTROL_BREAK_OFF) + + def setRTS(self, level=True): + """Set terminal status line: Request To Send.""" + if not self._isOpen: raise portNotOpenError + if self.logger: + self.logger.info('set RTS to %s' % ('inactive', 'active')[bool(level)]) + if level: + self.rfc2217SetControl(SET_CONTROL_RTS_ON) + else: + self.rfc2217SetControl(SET_CONTROL_RTS_OFF) + + def setDTR(self, level=True): + """Set terminal status line: Data Terminal Ready.""" + if not self._isOpen: raise portNotOpenError + if self.logger: + self.logger.info('set DTR to %s' % ('inactive', 'active')[bool(level)]) + if level: + self.rfc2217SetControl(SET_CONTROL_DTR_ON) + else: + self.rfc2217SetControl(SET_CONTROL_DTR_OFF) + + def getCTS(self): + """Read terminal status line: Clear To Send.""" + if not self._isOpen: raise portNotOpenError + return bool(self.getModemState() & MODEMSTATE_MASK_CTS) + + def getDSR(self): + """Read terminal status line: Data Set Ready.""" + if not self._isOpen: raise portNotOpenError + return bool(self.getModemState() & MODEMSTATE_MASK_DSR) + + def getRI(self): + """Read terminal status line: Ring Indicator.""" + if not self._isOpen: raise portNotOpenError + return bool(self.getModemState() & MODEMSTATE_MASK_RI) + + def getCD(self): + """Read terminal status line: Carrier Detect.""" + if not self._isOpen: raise portNotOpenError + return bool(self.getModemState() & MODEMSTATE_MASK_CD) + + # - - - platform specific - - - + # None so far + + # - - - RFC2217 specific - - - + + def _telnetReadLoop(self): + """read loop for the socket.""" + mode = M_NORMAL + suboption = None + try: + while self._socket is not None: + try: + data = self._socket.recv(1024) + except socket.timeout: + # just need to get out of recv form time to time to check if + # still alive + continue + except socket.error, e: + # connection fails -> terminate loop + if self.logger: + self.logger.debug("socket error in reader thread: %s" % (e,)) + break + if not data: break # lost connection + for byte in data: + if mode == M_NORMAL: + # interpret as command or as data + if byte == IAC: + mode = M_IAC_SEEN + else: + # store data in read buffer or sub option buffer + # depending on state + if suboption is not None: + suboption.append(byte) + else: + self._read_buffer.put(byte) + elif mode == M_IAC_SEEN: + if byte == IAC: + # interpret as command doubled -> insert character + # itself + if suboption is not None: + suboption.append(IAC) + else: + self._read_buffer.put(IAC) + mode = M_NORMAL + elif byte == SB: + # sub option start + suboption = bytearray() + mode = M_NORMAL + elif byte == SE: + # sub option end -> process it now + self._telnetProcessSubnegotiation(bytes(suboption)) + suboption = None + mode = M_NORMAL + elif byte in (DO, DONT, WILL, WONT): + # negotiation + telnet_command = byte + mode = M_NEGOTIATE + else: + # other telnet commands + self._telnetProcessCommand(byte) + mode = M_NORMAL + elif mode == M_NEGOTIATE: # DO, DONT, WILL, WONT was received, option now following + self._telnetNegotiateOption(telnet_command, byte) + mode = M_NORMAL + finally: + self._thread = None + if self.logger: + self.logger.debug("read thread terminated") + + # - incoming telnet commands and options + + def _telnetProcessCommand(self, command): + """Process commands other than DO, DONT, WILL, WONT.""" + # Currently none. RFC2217 only uses negotiation and subnegotiation. + if self.logger: + self.logger.warning("ignoring Telnet command: %r" % (command,)) + + def _telnetNegotiateOption(self, command, option): + """Process incoming DO, DONT, WILL, WONT.""" + # check our registered telnet options and forward command to them + # they know themselves if they have to answer or not + known = False + for item in self._telnet_options: + # can have more than one match! as some options are duplicated for + # 'us' and 'them' + if item.option == option: + item.process_incoming(command) + known = True + if not known: + # handle unknown options + # only answer to positive requests and deny them + if command == WILL or command == DO: + self.telnetSendOption((command == WILL and DONT or WONT), option) + if self.logger: + self.logger.warning("rejected Telnet option: %r" % (option,)) + + + def _telnetProcessSubnegotiation(self, suboption): + """Process subnegotiation, the data between IAC SB and IAC SE.""" + if suboption[0:1] == COM_PORT_OPTION: + if suboption[1:2] == SERVER_NOTIFY_LINESTATE and len(suboption) >= 3: + self._linestate = ord(suboption[2:3]) # ensure it is a number + if self.logger: + self.logger.info("NOTIFY_LINESTATE: %s" % self._linestate) + elif suboption[1:2] == SERVER_NOTIFY_MODEMSTATE and len(suboption) >= 3: + self._modemstate = ord(suboption[2:3]) # ensure it is a number + if self.logger: + self.logger.info("NOTIFY_MODEMSTATE: %s" % self._modemstate) + # update time when we think that a poll would make sense + self._modemstate_expires = time.time() + 0.3 + elif suboption[1:2] == FLOWCONTROL_SUSPEND: + self._remote_suspend_flow = True + elif suboption[1:2] == FLOWCONTROL_RESUME: + self._remote_suspend_flow = False + else: + for item in self._rfc2217_options.values(): + if item.ack_option == suboption[1:2]: + #~ print "processing COM_PORT_OPTION: %r" % list(suboption[1:]) + item.checkAnswer(bytes(suboption[2:])) + break + else: + if self.logger: + self.logger.warning("ignoring COM_PORT_OPTION: %r" % (suboption,)) + else: + if self.logger: + self.logger.warning("ignoring subnegotiation: %r" % (suboption,)) + + # - outgoing telnet commands and options + + def _internal_raw_write(self, data): + """internal socket write with no data escaping. used to send telnet stuff.""" + self._write_lock.acquire() + try: + self._socket.sendall(data) + finally: + self._write_lock.release() + + def telnetSendOption(self, action, option): + """Send DO, DONT, WILL, WONT.""" + self._internal_raw_write(to_bytes([IAC, action, option])) + + def rfc2217SendSubnegotiation(self, option, value=''): + """Subnegotiation of RFC2217 parameters.""" + value = value.replace(IAC, IAC_DOUBLED) + self._internal_raw_write(to_bytes([IAC, SB, COM_PORT_OPTION, option] + list(value) + [IAC, SE])) + + def rfc2217SendPurge(self, value): + item = self._rfc2217_options['purge'] + item.set(value) # transmit desired purge type + item.wait(self._network_timeout) # wait for acknowledge from the server + + def rfc2217SetControl(self, value): + item = self._rfc2217_options['control'] + item.set(value) # transmit desired control type + if self._ignore_set_control_answer: + # answers are ignored when option is set. compatibility mode for + # servers that answer, but not the expected one... (or no answer + # at all) i.e. sredird + time.sleep(0.1) # this helps getting the unit tests passed + else: + item.wait(self._network_timeout) # wait for acknowledge from the server + + def rfc2217FlowServerReady(self): + """\ + check if server is ready to receive data. block for some time when + not. + """ + #~ if self._remote_suspend_flow: + #~ wait--- + + def getModemState(self): + """\ + get last modem state (cached value. if value is "old", request a new + one. this cache helps that we don't issue to many requests when e.g. all + status lines, one after the other is queried by te user (getCTS, getDSR + etc.) + """ + # active modem state polling enabled? is the value fresh enough? + if self._poll_modem_state and self._modemstate_expires < time.time(): + if self.logger: + self.logger.debug('polling modem state') + # when it is older, request an update + self.rfc2217SendSubnegotiation(NOTIFY_MODEMSTATE) + timeout_time = time.time() + self._network_timeout + while time.time() < timeout_time: + time.sleep(0.05) # prevent 100% CPU load + # when expiration time is updated, it means that there is a new + # value + if self._modemstate_expires > time.time(): + if self.logger: + self.logger.warning('poll for modem state failed') + break + # even when there is a timeout, do not generate an error just + # return the last known value. this way we can support buggy + # servers that do not respond to polls, but send automatic + # updates. + if self._modemstate is not None: + if self.logger: + self.logger.debug('using cached modem state') + return self._modemstate + else: + # never received a notification from the server + raise SerialException("remote sends no NOTIFY_MODEMSTATE") + + +# assemble Serial class with the platform specific implementation and the base +# for file-like behavior. for Python 2.6 and newer, that provide the new I/O +# library, derive from io.RawIOBase +try: + import io +except ImportError: + # classic version with our own file-like emulation + class Serial(RFC2217Serial, FileLike): + pass +else: + # io library present + class Serial(RFC2217Serial, io.RawIOBase): + pass + + +############################################################################# +# The following is code that helps implementing an RFC 2217 server. + +class PortManager(object): + """\ + This class manages the state of Telnet and RFC 2217. It needs a serial + instance and a connection to work with. Connection is expected to implement + a (thread safe) write function, that writes the string to the network. + """ + + def __init__(self, serial_port, connection, logger=None): + self.serial = serial_port + self.connection = connection + self.logger = logger + self._client_is_rfc2217 = False + + # filter state machine + self.mode = M_NORMAL + self.suboption = None + self.telnet_command = None + + # states for modem/line control events + self.modemstate_mask = 255 + self.last_modemstate = None + self.linstate_mask = 0 + + # all supported telnet options + self._telnet_options = [ + TelnetOption(self, 'ECHO', ECHO, WILL, WONT, DO, DONT, REQUESTED), + TelnetOption(self, 'we-SGA', SGA, WILL, WONT, DO, DONT, REQUESTED), + TelnetOption(self, 'they-SGA', SGA, DO, DONT, WILL, WONT, INACTIVE), + TelnetOption(self, 'we-BINARY', BINARY, WILL, WONT, DO, DONT, INACTIVE), + TelnetOption(self, 'they-BINARY', BINARY, DO, DONT, WILL, WONT, REQUESTED), + TelnetOption(self, 'we-RFC2217', COM_PORT_OPTION, WILL, WONT, DO, DONT, REQUESTED, self._client_ok), + TelnetOption(self, 'they-RFC2217', COM_PORT_OPTION, DO, DONT, WILL, WONT, INACTIVE, self._client_ok), + ] + + # negotiate Telnet/RFC2217 -> send initial requests + if self.logger: + self.logger.debug("requesting initial Telnet/RFC 2217 options") + for option in self._telnet_options: + if option.state is REQUESTED: + self.telnetSendOption(option.send_yes, option.option) + # issue 1st modem state notification + + def _client_ok(self): + """\ + callback of telnet option. it gets called when option is activated. + this one here is used to detect when the client agrees on RFC 2217. a + flag is set so that other functions like check_modem_lines know if the + client is ok. + """ + # The callback is used for we and they so if one party agrees, we're + # already happy. it seems not all servers do the negotiation correctly + # and i guess there are incorrect clients too.. so be happy if client + # answers one or the other positively. + self._client_is_rfc2217 = True + if self.logger: + self.logger.info("client accepts RFC 2217") + # this is to ensure that the client gets a notification, even if there + # was no change + self.check_modem_lines(force_notification=True) + + # - outgoing telnet commands and options + + def telnetSendOption(self, action, option): + """Send DO, DONT, WILL, WONT.""" + self.connection.write(to_bytes([IAC, action, option])) + + def rfc2217SendSubnegotiation(self, option, value=''): + """Subnegotiation of RFC 2217 parameters.""" + value = value.replace(IAC, IAC_DOUBLED) + self.connection.write(to_bytes([IAC, SB, COM_PORT_OPTION, option] + list(value) + [IAC, SE])) + + # - check modem lines, needs to be called periodically from user to + # establish polling + + def check_modem_lines(self, force_notification=False): + modemstate = ( + (self.serial.getCTS() and MODEMSTATE_MASK_CTS) | + (self.serial.getDSR() and MODEMSTATE_MASK_DSR) | + (self.serial.getRI() and MODEMSTATE_MASK_RI) | + (self.serial.getCD() and MODEMSTATE_MASK_CD) + ) + # check what has changed + deltas = modemstate ^ (self.last_modemstate or 0) # when last is None -> 0 + if deltas & MODEMSTATE_MASK_CTS: + modemstate |= MODEMSTATE_MASK_CTS_CHANGE + if deltas & MODEMSTATE_MASK_DSR: + modemstate |= MODEMSTATE_MASK_DSR_CHANGE + if deltas & MODEMSTATE_MASK_RI: + modemstate |= MODEMSTATE_MASK_RI_CHANGE + if deltas & MODEMSTATE_MASK_CD: + modemstate |= MODEMSTATE_MASK_CD_CHANGE + # if new state is different and the mask allows this change, send + # notification. suppress notifications when client is not rfc2217 + if modemstate != self.last_modemstate or force_notification: + if (self._client_is_rfc2217 and (modemstate & self.modemstate_mask)) or force_notification: + self.rfc2217SendSubnegotiation( + SERVER_NOTIFY_MODEMSTATE, + to_bytes([modemstate & self.modemstate_mask]) + ) + if self.logger: + self.logger.info("NOTIFY_MODEMSTATE: %s" % (modemstate,)) + # save last state, but forget about deltas. + # otherwise it would also notify about changing deltas which is + # probably not very useful + self.last_modemstate = modemstate & 0xf0 + + # - outgoing data escaping + + def escape(self, data): + """\ + this generator function is for the user. all outgoing data has to be + properly escaped, so that no IAC character in the data stream messes up + the Telnet state machine in the server. + + socket.sendall(escape(data)) + """ + for byte in data: + if byte == IAC: + yield IAC + yield IAC + else: + yield byte + + # - incoming data filter + + def filter(self, data): + """\ + handle a bunch of incoming bytes. this is a generator. it will yield + all characters not of interest for Telnet/RFC 2217. + + The idea is that the reader thread pushes data from the socket through + this filter: + + for byte in filter(socket.recv(1024)): + # do things like CR/LF conversion/whatever + # and write data to the serial port + serial.write(byte) + + (socket error handling code left as exercise for the reader) + """ + for byte in data: + if self.mode == M_NORMAL: + # interpret as command or as data + if byte == IAC: + self.mode = M_IAC_SEEN + else: + # store data in sub option buffer or pass it to our + # consumer depending on state + if self.suboption is not None: + self.suboption.append(byte) + else: + yield byte + elif self.mode == M_IAC_SEEN: + if byte == IAC: + # interpret as command doubled -> insert character + # itself + if self.suboption is not None: + self.suboption.append(byte) + else: + yield byte + self.mode = M_NORMAL + elif byte == SB: + # sub option start + self.suboption = bytearray() + self.mode = M_NORMAL + elif byte == SE: + # sub option end -> process it now + self._telnetProcessSubnegotiation(bytes(self.suboption)) + self.suboption = None + self.mode = M_NORMAL + elif byte in (DO, DONT, WILL, WONT): + # negotiation + self.telnet_command = byte + self.mode = M_NEGOTIATE + else: + # other telnet commands + self._telnetProcessCommand(byte) + self.mode = M_NORMAL + elif self.mode == M_NEGOTIATE: # DO, DONT, WILL, WONT was received, option now following + self._telnetNegotiateOption(self.telnet_command, byte) + self.mode = M_NORMAL + + # - incoming telnet commands and options + + def _telnetProcessCommand(self, command): + """Process commands other than DO, DONT, WILL, WONT.""" + # Currently none. RFC2217 only uses negotiation and subnegotiation. + if self.logger: + self.logger.warning("ignoring Telnet command: %r" % (command,)) + + def _telnetNegotiateOption(self, command, option): + """Process incoming DO, DONT, WILL, WONT.""" + # check our registered telnet options and forward command to them + # they know themselves if they have to answer or not + known = False + for item in self._telnet_options: + # can have more than one match! as some options are duplicated for + # 'us' and 'them' + if item.option == option: + item.process_incoming(command) + known = True + if not known: + # handle unknown options + # only answer to positive requests and deny them + if command == WILL or command == DO: + self.telnetSendOption((command == WILL and DONT or WONT), option) + if self.logger: + self.logger.warning("rejected Telnet option: %r" % (option,)) + + + def _telnetProcessSubnegotiation(self, suboption): + """Process subnegotiation, the data between IAC SB and IAC SE.""" + if suboption[0:1] == COM_PORT_OPTION: + if self.logger: + self.logger.debug('received COM_PORT_OPTION: %r' % (suboption,)) + if suboption[1:2] == SET_BAUDRATE: + backup = self.serial.baudrate + try: + (baudrate,) = struct.unpack("!I", suboption[2:6]) + if baudrate != 0: + self.serial.baudrate = baudrate + except ValueError, e: + if self.logger: + self.logger.error("failed to set baud rate: %s" % (e,)) + self.serial.baudrate = backup + else: + if self.logger: + self.logger.info("%s baud rate: %s" % (baudrate and 'set' or 'get', self.serial.baudrate)) + self.rfc2217SendSubnegotiation(SERVER_SET_BAUDRATE, struct.pack("!I", self.serial.baudrate)) + elif suboption[1:2] == SET_DATASIZE: + backup = self.serial.bytesize + try: + (datasize,) = struct.unpack("!B", suboption[2:3]) + if datasize != 0: + self.serial.bytesize = datasize + except ValueError, e: + if self.logger: + self.logger.error("failed to set data size: %s" % (e,)) + self.serial.bytesize = backup + else: + if self.logger: + self.logger.info("%s data size: %s" % (datasize and 'set' or 'get', self.serial.bytesize)) + self.rfc2217SendSubnegotiation(SERVER_SET_DATASIZE, struct.pack("!B", self.serial.bytesize)) + elif suboption[1:2] == SET_PARITY: + backup = self.serial.parity + try: + parity = struct.unpack("!B", suboption[2:3])[0] + if parity != 0: + self.serial.parity = RFC2217_REVERSE_PARITY_MAP[parity] + except ValueError, e: + if self.logger: + self.logger.error("failed to set parity: %s" % (e,)) + self.serial.parity = backup + else: + if self.logger: + self.logger.info("%s parity: %s" % (parity and 'set' or 'get', self.serial.parity)) + self.rfc2217SendSubnegotiation( + SERVER_SET_PARITY, + struct.pack("!B", RFC2217_PARITY_MAP[self.serial.parity]) + ) + elif suboption[1:2] == SET_STOPSIZE: + backup = self.serial.stopbits + try: + stopbits = struct.unpack("!B", suboption[2:3])[0] + if stopbits != 0: + self.serial.stopbits = RFC2217_REVERSE_STOPBIT_MAP[stopbits] + except ValueError, e: + if self.logger: + self.logger.error("failed to set stop bits: %s" % (e,)) + self.serial.stopbits = backup + else: + if self.logger: + self.logger.info("%s stop bits: %s" % (stopbits and 'set' or 'get', self.serial.stopbits)) + self.rfc2217SendSubnegotiation( + SERVER_SET_STOPSIZE, + struct.pack("!B", RFC2217_STOPBIT_MAP[self.serial.stopbits]) + ) + elif suboption[1:2] == SET_CONTROL: + if suboption[2:3] == SET_CONTROL_REQ_FLOW_SETTING: + if self.serial.xonxoff: + self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_SW_FLOW_CONTROL) + elif self.serial.rtscts: + self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_HW_FLOW_CONTROL) + else: + self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_NO_FLOW_CONTROL) + elif suboption[2:3] == SET_CONTROL_USE_NO_FLOW_CONTROL: + self.serial.xonxoff = False + self.serial.rtscts = False + if self.logger: + self.logger.info("changed flow control to None") + self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_NO_FLOW_CONTROL) + elif suboption[2:3] == SET_CONTROL_USE_SW_FLOW_CONTROL: + self.serial.xonxoff = True + if self.logger: + self.logger.info("changed flow control to XON/XOFF") + self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_SW_FLOW_CONTROL) + elif suboption[2:3] == SET_CONTROL_USE_HW_FLOW_CONTROL: + self.serial.rtscts = True + if self.logger: + self.logger.info("changed flow control to RTS/CTS") + self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_USE_HW_FLOW_CONTROL) + elif suboption[2:3] == SET_CONTROL_REQ_BREAK_STATE: + if self.logger: + self.logger.warning("requested break state - not implemented") + pass # XXX needs cached value + elif suboption[2:3] == SET_CONTROL_BREAK_ON: + self.serial.setBreak(True) + if self.logger: + self.logger.info("changed BREAK to active") + self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_BREAK_ON) + elif suboption[2:3] == SET_CONTROL_BREAK_OFF: + self.serial.setBreak(False) + if self.logger: + self.logger.info("changed BREAK to inactive") + self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_BREAK_OFF) + elif suboption[2:3] == SET_CONTROL_REQ_DTR: + if self.logger: + self.logger.warning("requested DTR state - not implemented") + pass # XXX needs cached value + elif suboption[2:3] == SET_CONTROL_DTR_ON: + self.serial.setDTR(True) + if self.logger: + self.logger.info("changed DTR to active") + self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_DTR_ON) + elif suboption[2:3] == SET_CONTROL_DTR_OFF: + self.serial.setDTR(False) + if self.logger: + self.logger.info("changed DTR to inactive") + self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_DTR_OFF) + elif suboption[2:3] == SET_CONTROL_REQ_RTS: + if self.logger: + self.logger.warning("requested RTS state - not implemented") + pass # XXX needs cached value + #~ self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_RTS_ON) + elif suboption[2:3] == SET_CONTROL_RTS_ON: + self.serial.setRTS(True) + if self.logger: + self.logger.info("changed RTS to active") + self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_RTS_ON) + elif suboption[2:3] == SET_CONTROL_RTS_OFF: + self.serial.setRTS(False) + if self.logger: + self.logger.info("changed RTS to inactive") + self.rfc2217SendSubnegotiation(SERVER_SET_CONTROL, SET_CONTROL_RTS_OFF) + #~ elif suboption[2:3] == SET_CONTROL_REQ_FLOW_SETTING_IN: + #~ elif suboption[2:3] == SET_CONTROL_USE_NO_FLOW_CONTROL_IN: + #~ elif suboption[2:3] == SET_CONTROL_USE_SW_FLOW_CONTOL_IN: + #~ elif suboption[2:3] == SET_CONTROL_USE_HW_FLOW_CONTOL_IN: + #~ elif suboption[2:3] == SET_CONTROL_USE_DCD_FLOW_CONTROL: + #~ elif suboption[2:3] == SET_CONTROL_USE_DTR_FLOW_CONTROL: + #~ elif suboption[2:3] == SET_CONTROL_USE_DSR_FLOW_CONTROL: + elif suboption[1:2] == NOTIFY_LINESTATE: + # client polls for current state + self.rfc2217SendSubnegotiation( + SERVER_NOTIFY_LINESTATE, + to_bytes([0]) # sorry, nothing like that implemented + ) + elif suboption[1:2] == NOTIFY_MODEMSTATE: + if self.logger: + self.logger.info("request for modem state") + # client polls for current state + self.check_modem_lines(force_notification=True) + elif suboption[1:2] == FLOWCONTROL_SUSPEND: + if self.logger: + self.logger.info("suspend") + self._remote_suspend_flow = True + elif suboption[1:2] == FLOWCONTROL_RESUME: + if self.logger: + self.logger.info("resume") + self._remote_suspend_flow = False + elif suboption[1:2] == SET_LINESTATE_MASK: + self.linstate_mask = ord(suboption[2:3]) # ensure it is a number + if self.logger: + self.logger.info("line state mask: 0x%02x" % (self.linstate_mask,)) + elif suboption[1:2] == SET_MODEMSTATE_MASK: + self.modemstate_mask = ord(suboption[2:3]) # ensure it is a number + if self.logger: + self.logger.info("modem state mask: 0x%02x" % (self.modemstate_mask,)) + elif suboption[1:2] == PURGE_DATA: + if suboption[2:3] == PURGE_RECEIVE_BUFFER: + self.serial.flushInput() + if self.logger: + self.logger.info("purge in") + self.rfc2217SendSubnegotiation(SERVER_PURGE_DATA, PURGE_RECEIVE_BUFFER) + elif suboption[2:3] == PURGE_TRANSMIT_BUFFER: + self.serial.flushOutput() + if self.logger: + self.logger.info("purge out") + self.rfc2217SendSubnegotiation(SERVER_PURGE_DATA, PURGE_TRANSMIT_BUFFER) + elif suboption[2:3] == PURGE_BOTH_BUFFERS: + self.serial.flushInput() + self.serial.flushOutput() + if self.logger: + self.logger.info("purge both") + self.rfc2217SendSubnegotiation(SERVER_PURGE_DATA, PURGE_BOTH_BUFFERS) + else: + if self.logger: + self.logger.error("undefined PURGE_DATA: %r" % list(suboption[2:])) + else: + if self.logger: + self.logger.error("undefined COM_PORT_OPTION: %r" % list(suboption[1:])) + else: + if self.logger: + self.logger.warning("unknown subnegotiation: %r" % (suboption,)) + + +# simple client test +if __name__ == '__main__': + import sys + s = Serial('rfc2217://localhost:7000', 115200) + sys.stdout.write('%s\n' % s) + + #~ s.baudrate = 1898 + + sys.stdout.write("write...\n") + s.write("hello\n") + s.flush() + sys.stdout.write("read: %s\n" % s.read(5)) + + #~ s.baudrate = 19200 + #~ s.databits = 7 + s.close() |