| Index: tools/telemetry/third_party/pyserial/serial/rfc2217.py
|
| diff --git a/tools/telemetry/third_party/pyserial/serial/rfc2217.py b/tools/telemetry/third_party/pyserial/serial/rfc2217.py
|
| deleted file mode 100644
|
| index 2012ea71c11667f3d93fb6ea1b634a8553b8c56b..0000000000000000000000000000000000000000
|
| --- a/tools/telemetry/third_party/pyserial/serial/rfc2217.py
|
| +++ /dev/null
|
| @@ -1,1323 +0,0 @@
|
| -#! 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()
|
|
|