[PATCH 4/8] include pyserial trunk

Yegor Yefremov yegorslists at googlemail.com
Fri Jan 8 03:57:54 PST 2016


On Fri, Jan 8, 2016 at 12:13 PM, Sascha Hauer <s.hauer at pengutronix.de> wrote:
> From: Jan Luebbe <jlu at pengutronix.de>
>
> The current pyserial is broken, this version contains the fix for:
> http://sourceforge.net/p/pyserial/bugs/166/

Have you tried the newest version from
https://github.com/pyserial/pyserial/releases

Yegor

> Signed-off-by: Jan Luebbe <jlu at pengutronix.de>
> ---
>  .gitignore                                    |    1 +
>  scripts/serial/__init__.py                    |   79 ++
>  scripts/serial/rfc2217.py                     | 1327 +++++++++++++++++++++++++
>  scripts/serial/serialcli.py                   |  284 ++++++
>  scripts/serial/serialposix.py                 |  730 ++++++++++++++
>  scripts/serial/serialutil.py                  |  572 +++++++++++
>  scripts/serial/tools/__init__.py              |    0
>  scripts/serial/tools/list_ports.py            |  103 ++
>  scripts/serial/tools/list_ports_linux.py      |  152 +++
>  scripts/serial/urlhandler/__init__.py         |    0
>  scripts/serial/urlhandler/protocol_hwgrep.py  |   45 +
>  scripts/serial/urlhandler/protocol_loop.py    |  279 ++++++
>  scripts/serial/urlhandler/protocol_rfc2217.py |   11 +
>  scripts/serial/urlhandler/protocol_socket.py  |  291 ++++++
>  14 files changed, 3874 insertions(+)
>  create mode 100644 scripts/serial/__init__.py
>  create mode 100644 scripts/serial/rfc2217.py
>  create mode 100644 scripts/serial/serialcli.py
>  create mode 100644 scripts/serial/serialposix.py
>  create mode 100644 scripts/serial/serialutil.py
>  create mode 100644 scripts/serial/tools/__init__.py
>  create mode 100644 scripts/serial/tools/list_ports.py
>  create mode 100644 scripts/serial/tools/list_ports_linux.py
>  create mode 100644 scripts/serial/urlhandler/__init__.py
>  create mode 100644 scripts/serial/urlhandler/protocol_hwgrep.py
>  create mode 100644 scripts/serial/urlhandler/protocol_loop.py
>  create mode 100644 scripts/serial/urlhandler/protocol_rfc2217.py
>  create mode 100644 scripts/serial/urlhandler/protocol_socket.py
>
> diff --git a/.gitignore b/.gitignore
> index ce2be8a..bbcfa22 100644
> --- a/.gitignore
> +++ b/.gitignore
> @@ -23,6 +23,7 @@
>  *.symtypes
>  *.elf
>  *.patch
> +*.pyc
>  *.mcp
>  *.bct
>  *.dcd
> diff --git a/scripts/serial/__init__.py b/scripts/serial/__init__.py
> new file mode 100644
> index 0000000..33ae52e
> --- /dev/null
> +++ b/scripts/serial/__init__.py
> @@ -0,0 +1,79 @@
> +#!/usr/bin/env python
> +
> +# portable serial port access with python
> +# this is a wrapper module for different platform implementations
> +#
> +# (C) 2001-2010 Chris Liechti <cliechti at gmx.net>
> +# this is distributed under a free software license, see license.txt
> +
> +VERSION = '2.7'
> +
> +import sys
> +
> +if sys.platform == 'cli':
> +    from serial.serialcli import *
> +else:
> +    import os
> +    # chose an implementation, depending on os
> +    if os.name == 'nt': #sys.platform == 'win32':
> +        from serial.serialwin32 import *
> +    elif os.name == 'posix':
> +        from serial.serialposix import *
> +    elif os.name == 'java':
> +        from serial.serialjava import *
> +    else:
> +        raise ImportError("Sorry: no implementation for your platform ('%s') available" % (os.name,))
> +
> +
> +protocol_handler_packages = [
> +        'serial.urlhandler',
> +        ]
> +
> +def serial_for_url(url, *args, **kwargs):
> +    """\
> +    Get an instance of the Serial class, depending on port/url. The port is not
> +    opened when the keyword parameter 'do_not_open' is true, by default it
> +    is. All other parameters are directly passed to the __init__ method when
> +    the port is instantiated.
> +
> +    The list of package names that is searched for protocol handlers is kept in
> +    ``protocol_handler_packages``.
> +
> +    e.g. we want to support a URL ``foobar://``. A module
> +    ``my_handlers.protocol_foobar`` is provided by the user. Then
> +    ``protocol_handler_packages.append("my_handlers")`` would extend the search
> +    path so that ``serial_for_url("foobar://"))`` would work.
> +    """
> +    # check remove extra parameter to not confuse the Serial class
> +    do_open = 'do_not_open' not in kwargs or not kwargs['do_not_open']
> +    if 'do_not_open' in kwargs: del kwargs['do_not_open']
> +    # the default is to use the native version
> +    klass = Serial   # 'native' implementation
> +    # check port type and get class
> +    try:
> +        url_nocase = url.lower()
> +    except AttributeError:
> +        # it's not a string, use default
> +        pass
> +    else:
> +        if '://' in url_nocase:
> +            protocol = url_nocase.split('://', 1)[0]
> +            for package_name in protocol_handler_packages:
> +                module_name = '%s.protocol_%s' % (package_name, protocol,)
> +                try:
> +                    handler_module = __import__(module_name)
> +                except ImportError:
> +                    pass
> +                else:
> +                    klass = sys.modules[module_name].Serial
> +                    break
> +            else:
> +                raise ValueError('invalid URL, protocol %r not known' % (protocol,))
> +        else:
> +            klass = Serial   # 'native' implementation
> +    # instantiate and open when desired
> +    instance = klass(None, *args, **kwargs)
> +    instance.port = url
> +    if do_open:
> +        instance.open()
> +    return instance
> diff --git a/scripts/serial/rfc2217.py b/scripts/serial/rfc2217.py
> new file mode 100644
> index 0000000..4fe1a72
> --- /dev/null
> +++ b/scripts/serial/rfc2217.py
> @@ -0,0 +1,1327 @@
> +#! 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 at 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) == sum(o.state != INACTIVE for o in 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 the 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()
> diff --git a/scripts/serial/serialcli.py b/scripts/serial/serialcli.py
> new file mode 100644
> index 0000000..9ab3876
> --- /dev/null
> +++ b/scripts/serial/serialcli.py
> @@ -0,0 +1,284 @@
> +#! python
> +# Python Serial Port Extension for Win32, Linux, BSD, Jython and .NET/Mono
> +# serial driver for .NET/Mono (IronPython), .NET >= 2
> +# see __init__.py
> +#
> +# (C) 2008 Chris Liechti <cliechti at gmx.net>
> +# this is distributed under a free software license, see license.txt
> +
> +import clr
> +import System
> +import System.IO.Ports
> +from serial.serialutil import *
> +
> +
> +def device(portnum):
> +    """Turn a port number into a device name"""
> +    return System.IO.Ports.SerialPort.GetPortNames()[portnum]
> +
> +
> +# must invoke function with byte array, make a helper to convert strings
> +# to byte arrays
> +sab = System.Array[System.Byte]
> +def as_byte_array(string):
> +    return sab([ord(x) for x in string])  # XXX will require adaption when run with a 3.x compatible IronPython
> +
> +class IronSerial(SerialBase):
> +    """Serial port implementation for .NET/Mono."""
> +
> +    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.
> +        """
> +        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._port_handle = System.IO.Ports.SerialPort(self.portstr)
> +        except Exception, msg:
> +            self._port_handle = None
> +            raise SerialException("could not open port %s: %s" % (self.portstr, msg))
> +
> +        self._reconfigurePort()
> +        self._port_handle.Open()
> +        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 not self._port_handle:
> +            raise SerialException("Can only operate on a valid port handle")
> +
> +        #~ self._port_handle.ReceivedBytesThreshold = 1
> +
> +        if self._timeout is None:
> +            self._port_handle.ReadTimeout = System.IO.Ports.SerialPort.InfiniteTimeout
> +        else:
> +            self._port_handle.ReadTimeout = int(self._timeout*1000)
> +
> +        # if self._timeout != 0 and self._interCharTimeout is not None:
> +            # timeouts = (int(self._interCharTimeout * 1000),) + timeouts[1:]
> +
> +        if self._writeTimeout is None:
> +            self._port_handle.WriteTimeout = System.IO.Ports.SerialPort.InfiniteTimeout
> +        else:
> +            self._port_handle.WriteTimeout = int(self._writeTimeout*1000)
> +
> +
> +        # Setup the connection info.
> +        try:
> +            self._port_handle.BaudRate = self._baudrate
> +        except IOError, e:
> +            # catch errors from illegal baudrate settings
> +            raise ValueError(str(e))
> +
> +        if self._bytesize == FIVEBITS:
> +            self._port_handle.DataBits     = 5
> +        elif self._bytesize == SIXBITS:
> +            self._port_handle.DataBits     = 6
> +        elif self._bytesize == SEVENBITS:
> +            self._port_handle.DataBits     = 7
> +        elif self._bytesize == EIGHTBITS:
> +            self._port_handle.DataBits     = 8
> +        else:
> +            raise ValueError("Unsupported number of data bits: %r" % self._bytesize)
> +
> +        if self._parity == PARITY_NONE:
> +            self._port_handle.Parity       = getattr(System.IO.Ports.Parity, 'None') # reserved keyword in Py3k
> +        elif self._parity == PARITY_EVEN:
> +            self._port_handle.Parity       = System.IO.Ports.Parity.Even
> +        elif self._parity == PARITY_ODD:
> +            self._port_handle.Parity       = System.IO.Ports.Parity.Odd
> +        elif self._parity == PARITY_MARK:
> +            self._port_handle.Parity       = System.IO.Ports.Parity.Mark
> +        elif self._parity == PARITY_SPACE:
> +            self._port_handle.Parity       = System.IO.Ports.Parity.Space
> +        else:
> +            raise ValueError("Unsupported parity mode: %r" % self._parity)
> +
> +        if self._stopbits == STOPBITS_ONE:
> +            self._port_handle.StopBits     = System.IO.Ports.StopBits.One
> +        elif self._stopbits == STOPBITS_ONE_POINT_FIVE:
> +            self._port_handle.StopBits     = System.IO.Ports.StopBits.OnePointFive
> +        elif self._stopbits == STOPBITS_TWO:
> +            self._port_handle.StopBits     = System.IO.Ports.StopBits.Two
> +        else:
> +            raise ValueError("Unsupported number of stop bits: %r" % self._stopbits)
> +
> +        if self._rtscts and self._xonxoff:
> +            self._port_handle.Handshake  = System.IO.Ports.Handshake.RequestToSendXOnXOff
> +        elif self._rtscts:
> +            self._port_handle.Handshake  = System.IO.Ports.Handshake.RequestToSend
> +        elif self._xonxoff:
> +            self._port_handle.Handshake  = System.IO.Ports.Handshake.XOnXOff
> +        else:
> +            self._port_handle.Handshake  = getattr(System.IO.Ports.Handshake, 'None')   # reserved keyword in Py3k
> +
> +    #~ def __del__(self):
> +        #~ self.close()
> +
> +    def close(self):
> +        """Close port"""
> +        if self._isOpen:
> +            if self._port_handle:
> +                try:
> +                    self._port_handle.Close()
> +                except System.IO.Ports.InvalidOperationException:
> +                    # ignore errors. can happen for unplugged USB serial devices
> +                    pass
> +                self._port_handle = None
> +            self._isOpen = False
> +
> +    def makeDeviceName(self, port):
> +        try:
> +            return device(port)
> +        except TypeError, e:
> +            raise SerialException(str(e))
> +
> +    #  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -
> +
> +    def inWaiting(self):
> +        """Return the number of characters currently in the input buffer."""
> +        if not self._port_handle: raise portNotOpenError
> +        return self._port_handle.BytesToRead
> +
> +    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._port_handle: raise portNotOpenError
> +        # must use single byte reads as this is the only way to read
> +        # without applying encodings
> +        data = bytearray()
> +        while size:
> +            try:
> +                data.append(self._port_handle.ReadByte())
> +            except System.TimeoutException, e:
> +                break
> +            else:
> +                size -= 1
> +        return bytes(data)
> +
> +    def write(self, data):
> +        """Output the given string over the serial port."""
> +        if not self._port_handle: raise portNotOpenError
> +        #~ if not isinstance(data, (bytes, bytearray)):
> +            #~ raise TypeError('expected %s or bytearray, got %s' % (bytes, type(data)))
> +        try:
> +            # must call overloaded method with byte array argument
> +            # as this is the only one not applying encodings
> +            self._port_handle.Write(as_byte_array(data), 0, len(data))
> +        except System.TimeoutException, e:
> +            raise writeTimeoutError
> +        return len(data)
> +
> +    def flushInput(self):
> +        """Clear input buffer, discarding all that is in the buffer."""
> +        if not self._port_handle: raise portNotOpenError
> +        self._port_handle.DiscardInBuffer()
> +
> +    def flushOutput(self):
> +        """\
> +        Clear output buffer, aborting the current output and
> +        discarding all that is in the buffer.
> +        """
> +        if not self._port_handle: raise portNotOpenError
> +        self._port_handle.DiscardOutBuffer()
> +
> +    def sendBreak(self, duration=0.25):
> +        """\
> +        Send break condition. Timed, returns to idle state after given
> +        duration.
> +        """
> +        if not self._port_handle: raise portNotOpenError
> +        import time
> +        self._port_handle.BreakState = True
> +        time.sleep(duration)
> +        self._port_handle.BreakState = False
> +
> +    def setBreak(self, level=True):
> +        """
> +        Set break: Controls TXD. When active, to transmitting is possible.
> +        """
> +        if not self._port_handle: raise portNotOpenError
> +        self._port_handle.BreakState = bool(level)
> +
> +    def setRTS(self, level=True):
> +        """Set terminal status line: Request To Send"""
> +        if not self._port_handle: raise portNotOpenError
> +        self._port_handle.RtsEnable = bool(level)
> +
> +    def setDTR(self, level=True):
> +        """Set terminal status line: Data Terminal Ready"""
> +        if not self._port_handle: raise portNotOpenError
> +        self._port_handle.DtrEnable = bool(level)
> +
> +    def getCTS(self):
> +        """Read terminal status line: Clear To Send"""
> +        if not self._port_handle: raise portNotOpenError
> +        return self._port_handle.CtsHolding
> +
> +    def getDSR(self):
> +        """Read terminal status line: Data Set Ready"""
> +        if not self._port_handle: raise portNotOpenError
> +        return self._port_handle.DsrHolding
> +
> +    def getRI(self):
> +        """Read terminal status line: Ring Indicator"""
> +        if not self._port_handle: raise portNotOpenError
> +        #~ return self._port_handle.XXX
> +        return False #XXX an error would be better
> +
> +    def getCD(self):
> +        """Read terminal status line: Carrier Detect"""
> +        if not self._port_handle: raise portNotOpenError
> +        return self._port_handle.CDHolding
> +
> +    # - - platform specific - - - -
> +    # none
> +
> +
> +# 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(IronSerial, FileLike):
> +        pass
> +else:
> +    # io library present
> +    class Serial(IronSerial, io.RawIOBase):
> +        pass
> +
> +
> +# Nur Testfunktion!!
> +if __name__ == '__main__':
> +    import sys
> +
> +    s = Serial(0)
> +    sys.stdio.write('%s\n' % s)
> +
> +    s = Serial()
> +    sys.stdio.write('%s\n' % s)
> +
> +
> +    s.baudrate = 19200
> +    s.databits = 7
> +    s.close()
> +    s.port = 0
> +    s.open()
> +    sys.stdio.write('%s\n' % s)
> +
> diff --git a/scripts/serial/serialposix.py b/scripts/serial/serialposix.py
> new file mode 100644
> index 0000000..359ad1b
> --- /dev/null
> +++ b/scripts/serial/serialposix.py
> @@ -0,0 +1,730 @@
> +#!/usr/bin/env python
> +#
> +# Python Serial Port Extension for Win32, Linux, BSD, Jython
> +# module for serial IO for POSIX compatible systems, like Linux
> +# see __init__.py
> +#
> +# (C) 2001-2010 Chris Liechti <cliechti at gmx.net>
> +# this is distributed under a free software license, see license.txt
> +#
> +# parts based on code from Grant B. Edwards  <grante at visi.com>:
> +#  ftp://ftp.visi.com/users/grante/python/PosixSerial.py
> +#
> +# references: http://www.easysw.com/~mike/serial/serial.html
> +
> +import sys, os, fcntl, termios, struct, select, errno, time
> +from serial.serialutil import *
> +
> +# Do check the Python version as some constants have moved.
> +if (sys.hexversion < 0x020100f0):
> +    import TERMIOS
> +else:
> +    TERMIOS = termios
> +
> +if (sys.hexversion < 0x020200f0):
> +    import FCNTL
> +else:
> +    FCNTL = fcntl
> +
> +# try to detect the OS so that a device can be selected...
> +# this code block should supply a device() and set_special_baudrate() function
> +# for the platform
> +plat = sys.platform.lower()
> +
> +if   plat[:5] == 'linux':    # Linux (confirmed)
> +
> +    def device(port):
> +        return '/dev/ttyS%d' % port
> +
> +    TCGETS2 = 0x802C542A
> +    TCSETS2 = 0x402C542B
> +    BOTHER = 0o010000
> +
> +    def set_special_baudrate(port, baudrate):
> +        # right size is 44 on x86_64, allow for some growth
> +        import array
> +        buf = array.array('i', [0] * 64)
> +
> +        try:
> +            # get serial_struct
> +            FCNTL.ioctl(port.fd, TCGETS2, buf)
> +            # set custom speed
> +            buf[2] &= ~TERMIOS.CBAUD
> +            buf[2] |= BOTHER
> +            buf[9] = buf[10] = baudrate
> +
> +            # set serial_struct
> +            res = FCNTL.ioctl(port.fd, TCSETS2, buf)
> +        except IOError, e:
> +            raise ValueError('Failed to set custom baud rate (%s): %s' % (baudrate, e))
> +
> +    baudrate_constants = {
> +        0:       0000000,  # hang up
> +        50:      0000001,
> +        75:      0000002,
> +        110:     0000003,
> +        134:     0000004,
> +        150:     0000005,
> +        200:     0000006,
> +        300:     0000007,
> +        600:     0000010,
> +        1200:    0000011,
> +        1800:    0000012,
> +        2400:    0000013,
> +        4800:    0000014,
> +        9600:    0000015,
> +        19200:   0000016,
> +        38400:   0000017,
> +        57600:   0010001,
> +        115200:  0010002,
> +        230400:  0010003,
> +        460800:  0010004,
> +        500000:  0010005,
> +        576000:  0010006,
> +        921600:  0010007,
> +        1000000: 0010010,
> +        1152000: 0010011,
> +        1500000: 0010012,
> +        2000000: 0010013,
> +        2500000: 0010014,
> +        3000000: 0010015,
> +        3500000: 0010016,
> +        4000000: 0010017
> +    }
> +
> +elif plat == 'cygwin':       # cygwin/win32 (confirmed)
> +
> +    def device(port):
> +        return '/dev/com%d' % (port + 1)
> +
> +    def set_special_baudrate(port, baudrate):
> +        raise ValueError("sorry don't know how to handle non standard baud rate on this platform")
> +
> +    baudrate_constants = {
> +        128000: 0x01003,
> +        256000: 0x01005,
> +        500000: 0x01007,
> +        576000: 0x01008,
> +        921600: 0x01009,
> +        1000000: 0x0100a,
> +        1152000: 0x0100b,
> +        1500000: 0x0100c,
> +        2000000: 0x0100d,
> +        2500000: 0x0100e,
> +        3000000: 0x0100f
> +    }
> +
> +elif plat[:7] == 'openbsd':    # OpenBSD
> +
> +    def device(port):
> +        return '/dev/cua%02d' % port
> +
> +    def set_special_baudrate(port, baudrate):
> +        raise ValueError("sorry don't know how to handle non standard baud rate on this platform")
> +
> +    baudrate_constants = {}
> +
> +elif plat[:3] == 'bsd' or  \
> +    plat[:7] == 'freebsd':
> +
> +    def device(port):
> +        return '/dev/cuad%d' % port
> +
> +    def set_special_baudrate(port, baudrate):
> +        raise ValueError("sorry don't know how to handle non standard baud rate on this platform")
> +
> +    baudrate_constants = {}
> +
> +elif plat[:6] == 'darwin':   # OS X
> +
> +    version = os.uname()[2].split('.')
> +    # Tiger or above can support arbitrary serial speeds
> +    if int(version[0]) >= 8:
> +        def set_special_baudrate(port, baudrate):
> +            # use IOKit-specific call to set up high speeds
> +            import array, fcntl
> +            buf = array.array('i', [baudrate])
> +            IOSSIOSPEED = 0x80045402 #_IOW('T', 2, speed_t)
> +            fcntl.ioctl(port.fd, IOSSIOSPEED, buf, 1)
> +    else: # version < 8
> +        def set_special_baudrate(port, baudrate):
> +            raise ValueError("baud rate not supported")
> +
> +    def device(port):
> +        return '/dev/cuad%d' % port
> +
> +    baudrate_constants = {}
> +
> +
> +elif plat[:6] == 'netbsd':   # NetBSD 1.6 testing by Erk
> +
> +    def device(port):
> +        return '/dev/dty%02d' % port
> +
> +    def set_special_baudrate(port, baudrate):
> +        raise ValueError("sorry don't know how to handle non standard baud rate on this platform")
> +
> +    baudrate_constants = {}
> +
> +elif plat[:4] == 'irix':     # IRIX (partially tested)
> +
> +    def device(port):
> +        return '/dev/ttyf%d' % (port+1) #XXX different device names depending on flow control
> +
> +    def set_special_baudrate(port, baudrate):
> +        raise ValueError("sorry don't know how to handle non standard baud rate on this platform")
> +
> +    baudrate_constants = {}
> +
> +elif plat[:2] == 'hp':       # HP-UX (not tested)
> +
> +    def device(port):
> +        return '/dev/tty%dp0' % (port+1)
> +
> +    def set_special_baudrate(port, baudrate):
> +        raise ValueError("sorry don't know how to handle non standard baud rate on this platform")
> +
> +    baudrate_constants = {}
> +
> +elif plat[:5] == 'sunos':    # Solaris/SunOS (confirmed)
> +
> +    def device(port):
> +        return '/dev/tty%c' % (ord('a')+port)
> +
> +    def set_special_baudrate(port, baudrate):
> +        raise ValueError("sorry don't know how to handle non standard baud rate on this platform")
> +
> +    baudrate_constants = {}
> +
> +elif plat[:3] == 'aix':      # AIX
> +
> +    def device(port):
> +        return '/dev/tty%d' % (port)
> +
> +    def set_special_baudrate(port, baudrate):
> +        raise ValueError("sorry don't know how to handle non standard baud rate on this platform")
> +
> +    baudrate_constants = {}
> +
> +else:
> +    # platform detection has failed...
> +    sys.stderr.write("""\
> +don't know how to number ttys on this system.
> +! Use an explicit path (eg /dev/ttyS1) or send this information to
> +! the author of this module:
> +
> +sys.platform = %r
> +os.name = %r
> +serialposix.py version = %s
> +
> +also add the device name of the serial port and where the
> +counting starts for the first serial port.
> +e.g. 'first serial port: /dev/ttyS0'
> +and with a bit luck you can get this module running...
> +""" % (sys.platform, os.name, VERSION))
> +    # no exception, just continue with a brave attempt to build a device name
> +    # even if the device name is not correct for the platform it has chances
> +    # to work using a string with the real device name as port parameter.
> +    def device(portum):
> +        return '/dev/ttyS%d' % portnum
> +    def set_special_baudrate(port, baudrate):
> +        raise SerialException("sorry don't know how to handle non standard baud rate on this platform")
> +    baudrate_constants = {}
> +    #~ raise Exception, "this module does not run on this platform, sorry."
> +
> +# whats up with "aix", "beos", ....
> +# they should work, just need to know the device names.
> +
> +
> +# load some constants for later use.
> +# try to use values from TERMIOS, use defaults from linux otherwise
> +TIOCMGET  = hasattr(TERMIOS, 'TIOCMGET') and TERMIOS.TIOCMGET or 0x5415
> +TIOCMBIS  = hasattr(TERMIOS, 'TIOCMBIS') and TERMIOS.TIOCMBIS or 0x5416
> +TIOCMBIC  = hasattr(TERMIOS, 'TIOCMBIC') and TERMIOS.TIOCMBIC or 0x5417
> +TIOCMSET  = hasattr(TERMIOS, 'TIOCMSET') and TERMIOS.TIOCMSET or 0x5418
> +
> +#TIOCM_LE = hasattr(TERMIOS, 'TIOCM_LE') and TERMIOS.TIOCM_LE or 0x001
> +TIOCM_DTR = hasattr(TERMIOS, 'TIOCM_DTR') and TERMIOS.TIOCM_DTR or 0x002
> +TIOCM_RTS = hasattr(TERMIOS, 'TIOCM_RTS') and TERMIOS.TIOCM_RTS or 0x004
> +#TIOCM_ST = hasattr(TERMIOS, 'TIOCM_ST') and TERMIOS.TIOCM_ST or 0x008
> +#TIOCM_SR = hasattr(TERMIOS, 'TIOCM_SR') and TERMIOS.TIOCM_SR or 0x010
> +
> +TIOCM_CTS = hasattr(TERMIOS, 'TIOCM_CTS') and TERMIOS.TIOCM_CTS or 0x020
> +TIOCM_CAR = hasattr(TERMIOS, 'TIOCM_CAR') and TERMIOS.TIOCM_CAR or 0x040
> +TIOCM_RNG = hasattr(TERMIOS, 'TIOCM_RNG') and TERMIOS.TIOCM_RNG or 0x080
> +TIOCM_DSR = hasattr(TERMIOS, 'TIOCM_DSR') and TERMIOS.TIOCM_DSR or 0x100
> +TIOCM_CD  = hasattr(TERMIOS, 'TIOCM_CD') and TERMIOS.TIOCM_CD or TIOCM_CAR
> +TIOCM_RI  = hasattr(TERMIOS, 'TIOCM_RI') and TERMIOS.TIOCM_RI or TIOCM_RNG
> +#TIOCM_OUT1 = hasattr(TERMIOS, 'TIOCM_OUT1') and TERMIOS.TIOCM_OUT1 or 0x2000
> +#TIOCM_OUT2 = hasattr(TERMIOS, 'TIOCM_OUT2') and TERMIOS.TIOCM_OUT2 or 0x4000
> +if hasattr(TERMIOS, 'TIOCINQ'):
> +    TIOCINQ = TERMIOS.TIOCINQ
> +else:
> +    TIOCINQ = hasattr(TERMIOS, 'FIONREAD') and TERMIOS.FIONREAD or 0x541B
> +TIOCOUTQ   = hasattr(TERMIOS, 'TIOCOUTQ') and TERMIOS.TIOCOUTQ or 0x5411
> +
> +TIOCM_zero_str = struct.pack('I', 0)
> +TIOCM_RTS_str = struct.pack('I', TIOCM_RTS)
> +TIOCM_DTR_str = struct.pack('I', TIOCM_DTR)
> +
> +TIOCSBRK  = hasattr(TERMIOS, 'TIOCSBRK') and TERMIOS.TIOCSBRK or 0x5427
> +TIOCCBRK  = hasattr(TERMIOS, 'TIOCCBRK') and TERMIOS.TIOCCBRK or 0x5428
> +
> +CMSPAR = 010000000000 # Use "stick" (mark/space) parity
> +
> +
> +class PosixSerial(SerialBase):
> +    """\
> +    Serial port class POSIX implementation. Serial port configuration is
> +    done with termios and fcntl. Runs on Linux and many other Un*x like
> +    systems.
> +    """
> +
> +    def open(self):
> +        """\
> +        Open port with current settings. This may throw a SerialException
> +        if the port cannot be opened."""
> +        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.")
> +        self.fd = None
> +        # open
> +        try:
> +            self.fd = os.open(self.portstr, os.O_RDWR|os.O_NOCTTY|os.O_NONBLOCK)
> +        except OSError, msg:
> +            self.fd = None
> +            raise SerialException(msg.errno, "could not open port %s: %s" % (self._port, msg))
> +        #~ fcntl.fcntl(self.fd, FCNTL.F_SETFL, 0)  # set blocking
> +
> +        try:
> +            self._reconfigurePort()
> +        except:
> +            try:
> +                os.close(self.fd)
> +            except:
> +                # ignore any exception when closing the port
> +                # also to keep original exception that happened when setting up
> +                pass
> +            self.fd = None
> +            raise
> +        else:
> +            self._isOpen = True
> +        self.flushInput()
> +
> +
> +    def _reconfigurePort(self):
> +        """Set communication parameters on opened port."""
> +        if self.fd is None:
> +            raise SerialException("Can only operate on a valid file descriptor")
> +        custom_baud = None
> +
> +        vmin = vtime = 0                # timeout is done via select
> +        if self._interCharTimeout is not None:
> +            vmin = 1
> +            vtime = int(self._interCharTimeout * 10)
> +        try:
> +            orig_attr = termios.tcgetattr(self.fd)
> +            iflag, oflag, cflag, lflag, ispeed, ospeed, cc = orig_attr
> +        except termios.error, msg:      # if a port is nonexistent but has a /dev file, it'll fail here
> +            raise SerialException("Could not configure port: %s" % msg)
> +        # set up raw mode / no echo / binary
> +        cflag |=  (TERMIOS.CLOCAL|TERMIOS.CREAD)
> +        lflag &= ~(TERMIOS.ICANON|TERMIOS.ECHO|TERMIOS.ECHOE|TERMIOS.ECHOK|TERMIOS.ECHONL|
> +                     TERMIOS.ISIG|TERMIOS.IEXTEN) #|TERMIOS.ECHOPRT
> +        for flag in ('ECHOCTL', 'ECHOKE'): # netbsd workaround for Erk
> +            if hasattr(TERMIOS, flag):
> +                lflag &= ~getattr(TERMIOS, flag)
> +
> +        oflag &= ~(TERMIOS.OPOST)
> +        iflag &= ~(TERMIOS.INLCR|TERMIOS.IGNCR|TERMIOS.ICRNL|TERMIOS.IGNBRK)
> +        if hasattr(TERMIOS, 'IUCLC'):
> +            iflag &= ~TERMIOS.IUCLC
> +        if hasattr(TERMIOS, 'PARMRK'):
> +            iflag &= ~TERMIOS.PARMRK
> +
> +        # setup baud rate
> +        try:
> +            ispeed = ospeed = getattr(TERMIOS, 'B%s' % (self._baudrate))
> +        except AttributeError:
> +            try:
> +                ispeed = ospeed = baudrate_constants[self._baudrate]
> +            except KeyError:
> +                #~ raise ValueError('Invalid baud rate: %r' % self._baudrate)
> +                # may need custom baud rate, it isn't in our list.
> +                ispeed = ospeed = getattr(TERMIOS, 'B38400')
> +                try:
> +                    custom_baud = int(self._baudrate) # store for later
> +                except ValueError:
> +                    raise ValueError('Invalid baud rate: %r' % self._baudrate)
> +                else:
> +                    if custom_baud < 0:
> +                        raise ValueError('Invalid baud rate: %r' % self._baudrate)
> +
> +        # setup char len
> +        cflag &= ~TERMIOS.CSIZE
> +        if self._bytesize == 8:
> +            cflag |= TERMIOS.CS8
> +        elif self._bytesize == 7:
> +            cflag |= TERMIOS.CS7
> +        elif self._bytesize == 6:
> +            cflag |= TERMIOS.CS6
> +        elif self._bytesize == 5:
> +            cflag |= TERMIOS.CS5
> +        else:
> +            raise ValueError('Invalid char len: %r' % self._bytesize)
> +        # setup stop bits
> +        if self._stopbits == STOPBITS_ONE:
> +            cflag &= ~(TERMIOS.CSTOPB)
> +        elif self._stopbits == STOPBITS_ONE_POINT_FIVE:
> +            cflag |=  (TERMIOS.CSTOPB)  # XXX same as TWO.. there is no POSIX support for 1.5
> +        elif self._stopbits == STOPBITS_TWO:
> +            cflag |=  (TERMIOS.CSTOPB)
> +        else:
> +            raise ValueError('Invalid stop bit specification: %r' % self._stopbits)
> +        # setup parity
> +        iflag &= ~(TERMIOS.INPCK|TERMIOS.ISTRIP)
> +        if self._parity == PARITY_NONE:
> +            cflag &= ~(TERMIOS.PARENB|TERMIOS.PARODD)
> +        elif self._parity == PARITY_EVEN:
> +            cflag &= ~(TERMIOS.PARODD)
> +            cflag |=  (TERMIOS.PARENB)
> +        elif self._parity == PARITY_ODD:
> +            cflag |=  (TERMIOS.PARENB|TERMIOS.PARODD)
> +        elif self._parity == PARITY_MARK and plat[:5] == 'linux':
> +            cflag |=  (TERMIOS.PARENB|CMSPAR|TERMIOS.PARODD)
> +        elif self._parity == PARITY_SPACE and plat[:5] == 'linux':
> +            cflag |=  (TERMIOS.PARENB|CMSPAR)
> +            cflag &= ~(TERMIOS.PARODD)
> +        else:
> +            raise ValueError('Invalid parity: %r' % self._parity)
> +        # setup flow control
> +        # xonxoff
> +        if hasattr(TERMIOS, 'IXANY'):
> +            if self._xonxoff:
> +                iflag |=  (TERMIOS.IXON|TERMIOS.IXOFF) #|TERMIOS.IXANY)
> +            else:
> +                iflag &= ~(TERMIOS.IXON|TERMIOS.IXOFF|TERMIOS.IXANY)
> +        else:
> +            if self._xonxoff:
> +                iflag |=  (TERMIOS.IXON|TERMIOS.IXOFF)
> +            else:
> +                iflag &= ~(TERMIOS.IXON|TERMIOS.IXOFF)
> +        # rtscts
> +        if hasattr(TERMIOS, 'CRTSCTS'):
> +            if self._rtscts:
> +                cflag |=  (TERMIOS.CRTSCTS)
> +            else:
> +                cflag &= ~(TERMIOS.CRTSCTS)
> +        elif hasattr(TERMIOS, 'CNEW_RTSCTS'):   # try it with alternate constant name
> +            if self._rtscts:
> +                cflag |=  (TERMIOS.CNEW_RTSCTS)
> +            else:
> +                cflag &= ~(TERMIOS.CNEW_RTSCTS)
> +        # XXX should there be a warning if setting up rtscts (and xonxoff etc) fails??
> +
> +        # buffer
> +        # vmin "minimal number of characters to be read. 0 for non blocking"
> +        if vmin < 0 or vmin > 255:
> +            raise ValueError('Invalid vmin: %r ' % vmin)
> +        cc[TERMIOS.VMIN] = vmin
> +        # vtime
> +        if vtime < 0 or vtime > 255:
> +            raise ValueError('Invalid vtime: %r' % vtime)
> +        cc[TERMIOS.VTIME] = vtime
> +        # activate settings
> +        if [iflag, oflag, cflag, lflag, ispeed, ospeed, cc] != orig_attr:
> +            termios.tcsetattr(self.fd, TERMIOS.TCSANOW, [iflag, oflag, cflag, lflag, ispeed, ospeed, cc])
> +
> +        # apply custom baud rate, if any
> +        if custom_baud is not None:
> +            set_special_baudrate(self, custom_baud)
> +
> +    def close(self):
> +        """Close port"""
> +        if self._isOpen:
> +            if self.fd is not None:
> +                os.close(self.fd)
> +                self.fd = None
> +            self._isOpen = False
> +
> +    def makeDeviceName(self, port):
> +        return device(port)
> +
> +    #  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -
> +
> +    def inWaiting(self):
> +        """Return the number of characters currently in the input buffer."""
> +        #~ s = fcntl.ioctl(self.fd, TERMIOS.FIONREAD, TIOCM_zero_str)
> +        s = fcntl.ioctl(self.fd, TIOCINQ, TIOCM_zero_str)
> +        return struct.unpack('I',s)[0]
> +
> +    # select based implementation, proved to work on many systems
> +    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
> +        read = bytearray()
> +        while len(read) < size:
> +            try:
> +                ready,_,_ = select.select([self.fd],[],[], self._timeout)
> +                # If select was used with a timeout, and the timeout occurs, it
> +                # returns with empty lists -> thus abort read operation.
> +                # For timeout == 0 (non-blocking operation) also abort when there
> +                # is nothing to read.
> +                if not ready:
> +                    break   # timeout
> +                buf = os.read(self.fd, size-len(read))
> +                # read should always return some data as select reported it was
> +                # ready to read when we get to this point.
> +                if not buf:
> +                    # Disconnected devices, at least on Linux, show the
> +                    # behavior that they are always ready to read immediately
> +                    # but reading returns nothing.
> +                    raise SerialException('device reports readiness to read but returned no data (device disconnected or multiple access on port?)')
> +                read.extend(buf)
> +            except OSError, e:
> +                # this is for Python 3.x where select.error is a subclass of OSError
> +                # ignore EAGAIN errors. all other errors are shown
> +                if e.errno != errno.EAGAIN:
> +                    raise SerialException('read failed: %s' % (e,))
> +            except select.error, e:
> +                # this is for Python 2.x
> +                # ignore EAGAIN errors. all other errors are shown
> +                # see also http://www.python.org/dev/peps/pep-3151/#select
> +                if e[0] != errno.EAGAIN:
> +                    raise SerialException('read failed: %s' % (e,))
> +        return bytes(read)
> +
> +    def write(self, data):
> +        """Output the given string over the serial port."""
> +        if not self._isOpen: raise portNotOpenError
> +        d = to_bytes(data)
> +        tx_len = len(d)
> +        if self._writeTimeout is not None and self._writeTimeout > 0:
> +            timeout = time.time() + self._writeTimeout
> +        else:
> +            timeout = None
> +        while tx_len > 0:
> +            try:
> +                n = os.write(self.fd, d)
> +                if timeout:
> +                    # when timeout is set, use select to wait for being ready
> +                    # with the time left as timeout
> +                    timeleft = timeout - time.time()
> +                    if timeleft < 0:
> +                        raise writeTimeoutError
> +                    _, ready, _ = select.select([], [self.fd], [], timeleft)
> +                    if not ready:
> +                        raise writeTimeoutError
> +                else:
> +                    # wait for write operation
> +                    _, ready, _ = select.select([], [self.fd], [], None)
> +                    if not ready:
> +                        raise SerialException('write failed (select)')
> +                d = d[n:]
> +                tx_len -= n
> +            except OSError, v:
> +                if v.errno != errno.EAGAIN:
> +                    raise SerialException('write failed: %s' % (v,))
> +        return len(data)
> +
> +    def flush(self):
> +        """\
> +        Flush of file like objects. In this case, wait until all data
> +        is written.
> +        """
> +        self.drainOutput()
> +
> +    def flushInput(self):
> +        """Clear input buffer, discarding all that is in the buffer."""
> +        if not self._isOpen: raise portNotOpenError
> +        termios.tcflush(self.fd, TERMIOS.TCIFLUSH)
> +
> +    def flushOutput(self):
> +        """\
> +        Clear output buffer, aborting the current output and discarding all
> +        that is in the buffer.
> +        """
> +        if not self._isOpen: raise portNotOpenError
> +        termios.tcflush(self.fd, TERMIOS.TCOFLUSH)
> +
> +    def sendBreak(self, duration=0.25):
> +        """\
> +        Send break condition. Timed, returns to idle state after given
> +        duration.
> +        """
> +        if not self._isOpen: raise portNotOpenError
> +        termios.tcsendbreak(self.fd, int(duration/0.25))
> +
> +    def setBreak(self, level=1):
> +        """\
> +        Set break: Controls TXD. When active, no transmitting is possible.
> +        """
> +        if self.fd is None: raise portNotOpenError
> +        if level:
> +            fcntl.ioctl(self.fd, TIOCSBRK)
> +        else:
> +            fcntl.ioctl(self.fd, TIOCCBRK)
> +
> +    def setRTS(self, level=1):
> +        """Set terminal status line: Request To Send"""
> +        if not self._isOpen: raise portNotOpenError
> +        if level:
> +            fcntl.ioctl(self.fd, TIOCMBIS, TIOCM_RTS_str)
> +        else:
> +            fcntl.ioctl(self.fd, TIOCMBIC, TIOCM_RTS_str)
> +
> +    def setDTR(self, level=1):
> +        """Set terminal status line: Data Terminal Ready"""
> +        if not self._isOpen: raise portNotOpenError
> +        if level:
> +            fcntl.ioctl(self.fd, TIOCMBIS, TIOCM_DTR_str)
> +        else:
> +            fcntl.ioctl(self.fd, TIOCMBIC, TIOCM_DTR_str)
> +
> +    def getCTS(self):
> +        """Read terminal status line: Clear To Send"""
> +        if not self._isOpen: raise portNotOpenError
> +        s = fcntl.ioctl(self.fd, TIOCMGET, TIOCM_zero_str)
> +        return struct.unpack('I',s)[0] & TIOCM_CTS != 0
> +
> +    def getDSR(self):
> +        """Read terminal status line: Data Set Ready"""
> +        if not self._isOpen: raise portNotOpenError
> +        s = fcntl.ioctl(self.fd, TIOCMGET, TIOCM_zero_str)
> +        return struct.unpack('I',s)[0] & TIOCM_DSR != 0
> +
> +    def getRI(self):
> +        """Read terminal status line: Ring Indicator"""
> +        if not self._isOpen: raise portNotOpenError
> +        s = fcntl.ioctl(self.fd, TIOCMGET, TIOCM_zero_str)
> +        return struct.unpack('I',s)[0] & TIOCM_RI != 0
> +
> +    def getCD(self):
> +        """Read terminal status line: Carrier Detect"""
> +        if not self._isOpen: raise portNotOpenError
> +        s = fcntl.ioctl(self.fd, TIOCMGET, TIOCM_zero_str)
> +        return struct.unpack('I',s)[0] & TIOCM_CD != 0
> +
> +    # - - platform specific - - - -
> +
> +    def outWaiting(self):
> +        """Return the number of characters currently in the output buffer."""
> +        #~ s = fcntl.ioctl(self.fd, TERMIOS.FIONREAD, TIOCM_zero_str)
> +        s = fcntl.ioctl(self.fd, TIOCOUTQ, TIOCM_zero_str)
> +        return struct.unpack('I',s)[0]
> +
> +    def drainOutput(self):
> +        """internal - not portable!"""
> +        if not self._isOpen: raise portNotOpenError
> +        termios.tcdrain(self.fd)
> +
> +    def nonblocking(self):
> +        """internal - not portable!"""
> +        if not self._isOpen: raise portNotOpenError
> +        fcntl.fcntl(self.fd, FCNTL.F_SETFL, os.O_NONBLOCK)
> +
> +    def fileno(self):
> +        """\
> +        For easier use of the serial port instance with select.
> +        WARNING: this function is not portable to different platforms!
> +        """
> +        if not self._isOpen: raise portNotOpenError
> +        return self.fd
> +
> +    def setXON(self, level=True):
> +        """\
> +        Manually control flow - when software flow control is enabled.
> +        This will send XON (true) and XOFF (false) to the other device.
> +        WARNING: this function is not portable to different platforms!
> +        """
> +        if not self.hComPort: raise portNotOpenError
> +        if enable:
> +            termios.tcflow(self.fd, TERMIOS.TCION)
> +        else:
> +            termios.tcflow(self.fd, TERMIOS.TCIOFF)
> +
> +    def flowControlOut(self, enable):
> +        """\
> +        Manually control flow of outgoing data - when hardware or software flow
> +        control is enabled.
> +        WARNING: this function is not portable to different platforms!
> +        """
> +        if not self._isOpen: raise portNotOpenError
> +        if enable:
> +            termios.tcflow(self.fd, TERMIOS.TCOON)
> +        else:
> +            termios.tcflow(self.fd, TERMIOS.TCOOFF)
> +
> +
> +# 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(PosixSerial, FileLike):
> +        pass
> +else:
> +    # io library present
> +    class Serial(PosixSerial, io.RawIOBase):
> +        pass
> +
> +class PosixPollSerial(Serial):
> +    """\
> +    Poll based read implementation. Not all systems support poll properly.
> +    However this one has better handling of errors, such as a device
> +    disconnecting while it's in use (e.g. USB-serial unplugged).
> +    """
> +
> +    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 self.fd is None: raise portNotOpenError
> +        read = bytearray()
> +        poll = select.poll()
> +        poll.register(self.fd, select.POLLIN|select.POLLERR|select.POLLHUP|select.POLLNVAL)
> +        if size > 0:
> +            while len(read) < size:
> +                # print "\tread(): size",size, "have", len(read)    #debug
> +                # wait until device becomes ready to read (or something fails)
> +                for fd, event in poll.poll(self._timeout*1000):
> +                    if event & (select.POLLERR|select.POLLHUP|select.POLLNVAL):
> +                        raise SerialException('device reports error (poll)')
> +                    #  we don't care if it is select.POLLIN or timeout, that's
> +                    #  handled below
> +                buf = os.read(self.fd, size - len(read))
> +                read.extend(buf)
> +                if ((self._timeout is not None and self._timeout >= 0) or
> +                    (self._interCharTimeout is not None and self._interCharTimeout > 0)) and not buf:
> +                    break   # early abort on timeout
> +        return bytes(read)
> +
> +
> +if __name__ == '__main__':
> +    s = Serial(0,
> +                 baudrate=19200,        # baud rate
> +                 bytesize=EIGHTBITS,    # number of data bits
> +                 parity=PARITY_EVEN,    # enable parity checking
> +                 stopbits=STOPBITS_ONE, # number of stop bits
> +                 timeout=3,             # set a timeout value, None for waiting forever
> +                 xonxoff=0,             # enable software flow control
> +                 rtscts=0,              # enable RTS/CTS flow control
> +               )
> +    s.setRTS(1)
> +    s.setDTR(1)
> +    s.flushInput()
> +    s.flushOutput()
> +    s.write('hello')
> +    sys.stdout.write('%r\n' % s.read(5))
> +    sys.stdout.write('%s\n' % s.inWaiting())
> +    del s
> +
> diff --git a/scripts/serial/serialutil.py b/scripts/serial/serialutil.py
> new file mode 100644
> index 0000000..af0d2f6
> --- /dev/null
> +++ b/scripts/serial/serialutil.py
> @@ -0,0 +1,572 @@
> +#! python
> +# Python Serial Port Extension for Win32, Linux, BSD, Jython
> +# see __init__.py
> +#
> +# (C) 2001-2010 Chris Liechti <cliechti at gmx.net>
> +# this is distributed under a free software license, see license.txt
> +
> +# compatibility for older Python < 2.6
> +try:
> +    bytes
> +    bytearray
> +except (NameError, AttributeError):
> +    # Python older than 2.6 do not have these types. Like for Python 2.6 they
> +    # should behave like str. For Python older than 3.0 we want to work with
> +    # strings anyway, only later versions have a true bytes type.
> +    bytes = str
> +    # bytearray is a mutable type that is easily turned into an instance of
> +    # bytes
> +    class bytearray(list):
> +        # for bytes(bytearray()) usage
> +        def __str__(self): return ''.join(self)
> +        def __repr__(self): return 'bytearray(%r)' % ''.join(self)
> +        # append automatically converts integers to characters
> +        def append(self, item):
> +            if isinstance(item, str):
> +                list.append(self, item)
> +            else:
> +                list.append(self, chr(item))
> +        # +=
> +        def __iadd__(self, other):
> +            for byte in other:
> +                self.append(byte)
> +            return self
> +
> +        def __getslice__(self, i, j):
> +            return bytearray(list.__getslice__(self, i, j))
> +
> +        def __getitem__(self, item):
> +            if isinstance(item, slice):
> +                return bytearray(list.__getitem__(self, item))
> +            else:
> +                return ord(list.__getitem__(self, item))
> +
> +        def __eq__(self, other):
> +            if isinstance(other, basestring):
> +                other = bytearray(other)
> +            return list.__eq__(self, other)
> +
> +# ``memoryview`` was introduced in Python 2.7 and ``bytes(some_memoryview)``
> +# isn't returning the contents (very unfortunate). Therefore we need special
> +# cases and test for it. Ensure that there is a ``memoryview`` object for older
> +# Python versions. This is easier than making every test dependent on its
> +# existence.
> +try:
> +    memoryview
> +except (NameError, AttributeError):
> +    # implementation does not matter as we do not realy use it.
> +    # it just must not inherit from something else we might care for.
> +    class memoryview:
> +        pass
> +
> +
> +# all Python versions prior 3.x convert ``str([17])`` to '[17]' instead of '\x11'
> +# so a simple ``bytes(sequence)`` doesn't work for all versions
> +def to_bytes(seq):
> +    """convert a sequence to a bytes type"""
> +    if isinstance(seq, bytes):
> +        return seq
> +    elif isinstance(seq, bytearray):
> +        return bytes(seq)
> +    elif isinstance(seq, memoryview):
> +        return seq.tobytes()
> +    else:
> +        b = bytearray()
> +        for item in seq:
> +            b.append(item)  # this one handles int and str for our emulation and ints for Python 3.x
> +        return bytes(b)
> +
> +# create control bytes
> +XON  = to_bytes([17])
> +XOFF = to_bytes([19])
> +
> +CR = to_bytes([13])
> +LF = to_bytes([10])
> +
> +
> +PARITY_NONE, PARITY_EVEN, PARITY_ODD, PARITY_MARK, PARITY_SPACE = 'N', 'E', 'O', 'M', 'S'
> +STOPBITS_ONE, STOPBITS_ONE_POINT_FIVE, STOPBITS_TWO = (1, 1.5, 2)
> +FIVEBITS, SIXBITS, SEVENBITS, EIGHTBITS = (5, 6, 7, 8)
> +
> +PARITY_NAMES = {
> +    PARITY_NONE:  'None',
> +    PARITY_EVEN:  'Even',
> +    PARITY_ODD:   'Odd',
> +    PARITY_MARK:  'Mark',
> +    PARITY_SPACE: 'Space',
> +}
> +
> +
> +class SerialException(IOError):
> +    """Base class for serial port related exceptions."""
> +
> +
> +class SerialTimeoutException(SerialException):
> +    """Write timeouts give an exception"""
> +
> +
> +writeTimeoutError = SerialTimeoutException('Write timeout')
> +portNotOpenError = SerialException('Attempting to use a port that is not open')
> +
> +
> +class FileLike(object):
> +    """\
> +    An abstract file like class.
> +
> +    This class implements readline and readlines based on read and
> +    writelines based on write.
> +    This class is used to provide the above functions for to Serial
> +    port objects.
> +
> +    Note that when the serial port was opened with _NO_ timeout that
> +    readline blocks until it sees a newline (or the specified size is
> +    reached) and that readlines would never return and therefore
> +    refuses to work (it raises an exception in this case)!
> +    """
> +
> +    def __init__(self):
> +        self.closed = True
> +
> +    def close(self):
> +        self.closed = True
> +
> +    # so that ports are closed when objects are discarded
> +    def __del__(self):
> +        """Destructor.  Calls close()."""
> +        # The try/except block is in case this is called at program
> +        # exit time, when it's possible that globals have already been
> +        # deleted, and then the close() call might fail.  Since
> +        # there's nothing we can do about such failures and they annoy
> +        # the end users, we suppress the traceback.
> +        try:
> +            self.close()
> +        except:
> +            pass
> +
> +    def writelines(self, sequence):
> +        for line in sequence:
> +            self.write(line)
> +
> +    def flush(self):
> +        """flush of file like objects"""
> +        pass
> +
> +    # iterator for e.g. "for line in Serial(0): ..." usage
> +    def next(self):
> +        line = self.readline()
> +        if not line: raise StopIteration
> +        return line
> +
> +    def __iter__(self):
> +        return self
> +
> +    def readline(self, size=None, eol=LF):
> +        """\
> +        Read a line which is terminated with end-of-line (eol) character
> +        ('\n' by default) or until timeout.
> +        """
> +        leneol = len(eol)
> +        line = bytearray()
> +        while True:
> +            c = self.read(1)
> +            if c:
> +                line += c
> +                if line[-leneol:] == eol:
> +                    break
> +                if size is not None and len(line) >= size:
> +                    break
> +            else:
> +                break
> +        return bytes(line)
> +
> +    def readlines(self, sizehint=None, eol=LF):
> +        """\
> +        Read a list of lines, until timeout.
> +        sizehint is ignored.
> +        """
> +        if self.timeout is None:
> +            raise ValueError("Serial port MUST have enabled timeout for this function!")
> +        leneol = len(eol)
> +        lines = []
> +        while True:
> +            line = self.readline(eol=eol)
> +            if line:
> +                lines.append(line)
> +                if line[-leneol:] != eol:    # was the line received with a timeout?
> +                    break
> +            else:
> +                break
> +        return lines
> +
> +    def xreadlines(self, sizehint=None):
> +        """\
> +        Read lines, implemented as generator. It will raise StopIteration on
> +        timeout (empty read). sizehint is ignored.
> +        """
> +        while True:
> +            line = self.readline()
> +            if not line: break
> +            yield line
> +
> +    # other functions of file-likes - not used by pySerial
> +
> +    #~ readinto(b)
> +
> +    def seek(self, pos, whence=0):
> +        raise IOError("file is not seekable")
> +
> +    def tell(self):
> +        raise IOError("file is not seekable")
> +
> +    def truncate(self, n=None):
> +        raise IOError("file is not seekable")
> +
> +    def isatty(self):
> +        return False
> +
> +
> +class SerialBase(object):
> +    """\
> +    Serial port base class. Provides __init__ function and properties to
> +    get/set port settings.
> +    """
> +
> +    # default values, may be overridden in subclasses that do not support all values
> +    BAUDRATES = (50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800,
> +                 9600, 19200, 38400, 57600, 115200, 230400, 460800, 500000,
> +                 576000, 921600, 1000000, 1152000, 1500000, 2000000, 2500000,
> +                 3000000, 3500000, 4000000)
> +    BYTESIZES = (FIVEBITS, SIXBITS, SEVENBITS, EIGHTBITS)
> +    PARITIES  = (PARITY_NONE, PARITY_EVEN, PARITY_ODD, PARITY_MARK, PARITY_SPACE)
> +    STOPBITS  = (STOPBITS_ONE, STOPBITS_ONE_POINT_FIVE, STOPBITS_TWO)
> +
> +    def __init__(self,
> +                 port = None,           # number of device, numbering starts at
> +                                        # zero. if everything fails, the user
> +                                        # can specify a device string, note
> +                                        # that this isn't portable anymore
> +                                        # port will be opened if one is specified
> +                 baudrate=9600,         # baud rate
> +                 bytesize=EIGHTBITS,    # number of data bits
> +                 parity=PARITY_NONE,    # enable parity checking
> +                 stopbits=STOPBITS_ONE, # number of stop bits
> +                 timeout=None,          # set a timeout value, None to wait forever
> +                 xonxoff=False,         # enable software flow control
> +                 rtscts=False,          # enable RTS/CTS flow control
> +                 writeTimeout=None,     # set a timeout for writes
> +                 dsrdtr=False,          # None: use rtscts setting, dsrdtr override if True or False
> +                 interCharTimeout=None  # Inter-character timeout, None to disable
> +                 ):
> +        """\
> +        Initialize comm port object. If a port is given, then the port will be
> +        opened immediately. Otherwise a Serial port object in closed state
> +        is returned.
> +        """
> +
> +        self._isOpen   = False
> +        self._port     = None           # correct value is assigned below through properties
> +        self._baudrate = None           # correct value is assigned below through properties
> +        self._bytesize = None           # correct value is assigned below through properties
> +        self._parity   = None           # correct value is assigned below through properties
> +        self._stopbits = None           # correct value is assigned below through properties
> +        self._timeout  = None           # correct value is assigned below through properties
> +        self._writeTimeout = None       # correct value is assigned below through properties
> +        self._xonxoff  = None           # correct value is assigned below through properties
> +        self._rtscts   = None           # correct value is assigned below through properties
> +        self._dsrdtr   = None           # correct value is assigned below through properties
> +        self._interCharTimeout = None   # correct value is assigned below through properties
> +
> +        # assign values using get/set methods using the properties feature
> +        self.port     = port
> +        self.baudrate = baudrate
> +        self.bytesize = bytesize
> +        self.parity   = parity
> +        self.stopbits = stopbits
> +        self.timeout  = timeout
> +        self.writeTimeout = writeTimeout
> +        self.xonxoff  = xonxoff
> +        self.rtscts   = rtscts
> +        self.dsrdtr   = dsrdtr
> +        self.interCharTimeout = interCharTimeout
> +
> +        if port is not None:
> +            self.open()
> +
> +    def isOpen(self):
> +        """Check if the port is opened."""
> +        return self._isOpen
> +
> +    #  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -
> +
> +    # TODO: these are not really needed as the is the BAUDRATES etc. attribute...
> +    # maybe i remove them before the final release...
> +
> +    def getSupportedBaudrates(self):
> +        return [(str(b), b) for b in self.BAUDRATES]
> +
> +    def getSupportedByteSizes(self):
> +        return [(str(b), b) for b in self.BYTESIZES]
> +
> +    def getSupportedStopbits(self):
> +        return [(str(b), b) for b in self.STOPBITS]
> +
> +    def getSupportedParities(self):
> +        return [(PARITY_NAMES[b], b) for b in self.PARITIES]
> +
> +    #  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -
> +
> +    def setPort(self, port):
> +        """\
> +        Change the port. The attribute portstr is set to a string that
> +        contains the name of the port.
> +        """
> +
> +        was_open = self._isOpen
> +        if was_open: self.close()
> +        if port is not None:
> +            if isinstance(port, basestring):
> +                self.portstr = port
> +            else:
> +                self.portstr = self.makeDeviceName(port)
> +        else:
> +            self.portstr = None
> +        self._port = port
> +        self.name = self.portstr
> +        if was_open: self.open()
> +
> +    def getPort(self):
> +        """\
> +        Get the current port setting. The value that was passed on init or using
> +        setPort() is passed back. See also the attribute portstr which contains
> +        the name of the port as a string.
> +        """
> +        return self._port
> +
> +    port = property(getPort, setPort, doc="Port setting")
> +
> +
> +    def setBaudrate(self, baudrate):
> +        """\
> +        Change baud rate. It raises a ValueError if the port is open and the
> +        baud rate is not possible. If the port is closed, then the value is
> +        accepted and the exception is raised when the port is opened.
> +        """
> +        try:
> +            b = int(baudrate)
> +        except TypeError:
> +            raise ValueError("Not a valid baudrate: %r" % (baudrate,))
> +        else:
> +            if b <= 0:
> +                raise ValueError("Not a valid baudrate: %r" % (baudrate,))
> +            self._baudrate = b
> +            if self._isOpen:  self._reconfigurePort()
> +
> +    def getBaudrate(self):
> +        """Get the current baud rate setting."""
> +        return self._baudrate
> +
> +    baudrate = property(getBaudrate, setBaudrate, doc="Baud rate setting")
> +
> +
> +    def setByteSize(self, bytesize):
> +        """Change byte size."""
> +        if bytesize not in self.BYTESIZES: raise ValueError("Not a valid byte size: %r" % (bytesize,))
> +        self._bytesize = bytesize
> +        if self._isOpen: self._reconfigurePort()
> +
> +    def getByteSize(self):
> +        """Get the current byte size setting."""
> +        return self._bytesize
> +
> +    bytesize = property(getByteSize, setByteSize, doc="Byte size setting")
> +
> +
> +    def setParity(self, parity):
> +        """Change parity setting."""
> +        if parity not in self.PARITIES: raise ValueError("Not a valid parity: %r" % (parity,))
> +        self._parity = parity
> +        if self._isOpen: self._reconfigurePort()
> +
> +    def getParity(self):
> +        """Get the current parity setting."""
> +        return self._parity
> +
> +    parity = property(getParity, setParity, doc="Parity setting")
> +
> +
> +    def setStopbits(self, stopbits):
> +        """Change stop bits size."""
> +        if stopbits not in self.STOPBITS: raise ValueError("Not a valid stop bit size: %r" % (stopbits,))
> +        self._stopbits = stopbits
> +        if self._isOpen: self._reconfigurePort()
> +
> +    def getStopbits(self):
> +        """Get the current stop bits setting."""
> +        return self._stopbits
> +
> +    stopbits = property(getStopbits, setStopbits, doc="Stop bits setting")
> +
> +
> +    def setTimeout(self, timeout):
> +        """Change timeout setting."""
> +        if timeout is not None:
> +            try:
> +                timeout + 1     # test if it's a number, will throw a TypeError if not...
> +            except TypeError:
> +                raise ValueError("Not a valid timeout: %r" % (timeout,))
> +            if timeout < 0: raise ValueError("Not a valid timeout: %r" % (timeout,))
> +        self._timeout = timeout
> +        if self._isOpen: self._reconfigurePort()
> +
> +    def getTimeout(self):
> +        """Get the current timeout setting."""
> +        return self._timeout
> +
> +    timeout = property(getTimeout, setTimeout, doc="Timeout setting for read()")
> +
> +
> +    def setWriteTimeout(self, timeout):
> +        """Change timeout setting."""
> +        if timeout is not None:
> +            if timeout < 0: raise ValueError("Not a valid timeout: %r" % (timeout,))
> +            try:
> +                timeout + 1     #test if it's a number, will throw a TypeError if not...
> +            except TypeError:
> +                raise ValueError("Not a valid timeout: %r" % timeout)
> +
> +        self._writeTimeout = timeout
> +        if self._isOpen: self._reconfigurePort()
> +
> +    def getWriteTimeout(self):
> +        """Get the current timeout setting."""
> +        return self._writeTimeout
> +
> +    writeTimeout = property(getWriteTimeout, setWriteTimeout, doc="Timeout setting for write()")
> +
> +
> +    def setXonXoff(self, xonxoff):
> +        """Change XON/XOFF setting."""
> +        self._xonxoff = xonxoff
> +        if self._isOpen: self._reconfigurePort()
> +
> +    def getXonXoff(self):
> +        """Get the current XON/XOFF setting."""
> +        return self._xonxoff
> +
> +    xonxoff = property(getXonXoff, setXonXoff, doc="XON/XOFF setting")
> +
> +    def setRtsCts(self, rtscts):
> +        """Change RTS/CTS flow control setting."""
> +        self._rtscts = rtscts
> +        if self._isOpen: self._reconfigurePort()
> +
> +    def getRtsCts(self):
> +        """Get the current RTS/CTS flow control setting."""
> +        return self._rtscts
> +
> +    rtscts = property(getRtsCts, setRtsCts, doc="RTS/CTS flow control setting")
> +
> +    def setDsrDtr(self, dsrdtr=None):
> +        """Change DsrDtr flow control setting."""
> +        if dsrdtr is None:
> +            # if not set, keep backwards compatibility and follow rtscts setting
> +            self._dsrdtr = self._rtscts
> +        else:
> +            # if defined independently, follow its value
> +            self._dsrdtr = dsrdtr
> +        if self._isOpen: self._reconfigurePort()
> +
> +    def getDsrDtr(self):
> +        """Get the current DSR/DTR flow control setting."""
> +        return self._dsrdtr
> +
> +    dsrdtr = property(getDsrDtr, setDsrDtr, "DSR/DTR flow control setting")
> +
> +    def setInterCharTimeout(self, interCharTimeout):
> +        """Change inter-character timeout setting."""
> +        if interCharTimeout is not None:
> +            if interCharTimeout < 0: raise ValueError("Not a valid timeout: %r" % interCharTimeout)
> +            try:
> +                interCharTimeout + 1     # test if it's a number, will throw a TypeError if not...
> +            except TypeError:
> +                raise ValueError("Not a valid timeout: %r" % interCharTimeout)
> +
> +        self._interCharTimeout = interCharTimeout
> +        if self._isOpen: self._reconfigurePort()
> +
> +    def getInterCharTimeout(self):
> +        """Get the current inter-character timeout setting."""
> +        return self._interCharTimeout
> +
> +    interCharTimeout = property(getInterCharTimeout, setInterCharTimeout, doc="Inter-character timeout setting for read()")
> +
> +    #  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -
> +
> +    _SETTINGS = ('baudrate', 'bytesize', 'parity', 'stopbits', 'xonxoff',
> +            'dsrdtr', 'rtscts', 'timeout', 'writeTimeout', 'interCharTimeout')
> +
> +    def getSettingsDict(self):
> +        """\
> +        Get current port settings as a dictionary. For use with
> +        applySettingsDict.
> +        """
> +        return dict([(key, getattr(self, '_'+key)) for key in self._SETTINGS])
> +
> +    def applySettingsDict(self, d):
> +        """\
> +        apply stored settings from a dictionary returned from
> +        getSettingsDict. it's allowed to delete keys from the dictionary. these
> +        values will simply left unchanged.
> +        """
> +        for key in self._SETTINGS:
> +            if d[key] != getattr(self, '_'+key):   # check against internal "_" value
> +                setattr(self, key, d[key])          # set non "_" value to use properties write function
> +
> +    #  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -
> +
> +    def __repr__(self):
> +        """String representation of the current port settings and its state."""
> +        return "%s<id=0x%x, open=%s>(port=%r, baudrate=%r, bytesize=%r, parity=%r, stopbits=%r, timeout=%r, xonxoff=%r, rtscts=%r, dsrdtr=%r)" % (
> +            self.__class__.__name__,
> +            id(self),
> +            self._isOpen,
> +            self.portstr,
> +            self.baudrate,
> +            self.bytesize,
> +            self.parity,
> +            self.stopbits,
> +            self.timeout,
> +            self.xonxoff,
> +            self.rtscts,
> +            self.dsrdtr,
> +        )
> +
> +
> +    #  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -
> +    # compatibility with io library
> +
> +    def readable(self): return True
> +    def writable(self): return True
> +    def seekable(self): return False
> +    def readinto(self, b):
> +        data = self.read(len(b))
> +        n = len(data)
> +        try:
> +            b[:n] = data
> +        except TypeError, err:
> +            import array
> +            if not isinstance(b, array.array):
> +                raise err
> +            b[:n] = array.array('b', data)
> +        return n
> +
> +
> +if __name__ == '__main__':
> +    import sys
> +    s = SerialBase()
> +    sys.stdout.write('port name:  %s\n' % s.portstr)
> +    sys.stdout.write('baud rates: %s\n' % s.getSupportedBaudrates())
> +    sys.stdout.write('byte sizes: %s\n' % s.getSupportedByteSizes())
> +    sys.stdout.write('parities:   %s\n' % s.getSupportedParities())
> +    sys.stdout.write('stop bits:  %s\n' % s.getSupportedStopbits())
> +    sys.stdout.write('%s\n' % s)
> diff --git a/scripts/serial/tools/__init__.py b/scripts/serial/tools/__init__.py
> new file mode 100644
> index 0000000..e69de29
> diff --git a/scripts/serial/tools/list_ports.py b/scripts/serial/tools/list_ports.py
> new file mode 100644
> index 0000000..d373a55
> --- /dev/null
> +++ b/scripts/serial/tools/list_ports.py
> @@ -0,0 +1,103 @@
> +#!/usr/bin/env python
> +
> +# portable serial port access with python
> +# this is a wrapper module for different platform implementations of the
> +# port enumeration feature
> +#
> +# (C) 2011-2013 Chris Liechti <cliechti at gmx.net>
> +# this is distributed under a free software license, see license.txt
> +
> +"""\
> +This module will provide a function called comports that returns an
> +iterable (generator or list) that will enumerate available com ports. Note that
> +on some systems non-existent ports may be listed.
> +
> +Additionally a grep function is supplied that can be used to search for ports
> +based on their descriptions or hardware ID.
> +"""
> +
> +import sys, os, re
> +
> +# chose an implementation, depending on os
> +#~ if sys.platform == 'cli':
> +#~ else:
> +import os
> +# chose an implementation, depending on os
> +if os.name == 'nt': #sys.platform == 'win32':
> +    from serial.tools.list_ports_windows import *
> +elif os.name == 'posix':
> +    from serial.tools.list_ports_posix import *
> +#~ elif os.name == 'java':
> +else:
> +    raise ImportError("Sorry: no implementation for your platform ('%s') available" % (os.name,))
> +
> +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
> +
> +def grep(regexp):
> +    """\
> +    Search for ports using a regular expression. Port name, description and
> +    hardware ID are searched. The function returns an iterable that returns the
> +    same tuples as comport() would do.
> +    """
> +    r = re.compile(regexp, re.I)
> +    for port, desc, hwid in comports():
> +        if r.search(port) or r.search(desc) or r.search(hwid):
> +            yield port, desc, hwid
> +
> +
> +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
> +def main():
> +    import optparse
> +
> +    parser = optparse.OptionParser(
> +        usage = "%prog [options] [<regexp>]",
> +        description = "Miniterm - A simple terminal program for the serial port."
> +    )
> +
> +    parser.add_option("--debug",
> +            help="print debug messages and tracebacks (development mode)",
> +            dest="debug",
> +            default=False,
> +            action='store_true')
> +
> +    parser.add_option("-v", "--verbose",
> +            help="show more messages (can be given multiple times)",
> +            dest="verbose",
> +            default=1,
> +            action='count')
> +
> +    parser.add_option("-q", "--quiet",
> +            help="suppress all messages",
> +            dest="verbose",
> +            action='store_const',
> +            const=0)
> +
> +    (options, args) = parser.parse_args()
> +
> +
> +    hits = 0
> +    # get iteraror w/ or w/o filter
> +    if args:
> +        if len(args) > 1:
> +            parser.error('more than one regexp not supported')
> +        print "Filtered list with regexp: %r" % (args[0],)
> +        iterator = sorted(grep(args[0]))
> +    else:
> +        iterator = sorted(comports())
> +    # list them
> +    for port, desc, hwid in iterator:
> +        print("%-20s" % (port,))
> +        if options.verbose > 1:
> +            print("    desc: %s" % (desc,))
> +            print("    hwid: %s" % (hwid,))
> +        hits += 1
> +    if options.verbose:
> +        if hits:
> +            print("%d ports found" % (hits,))
> +        else:
> +            print("no ports found")
> +
> +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
> +# test
> +if __name__ == '__main__':
> +    main()
> diff --git a/scripts/serial/tools/list_ports_linux.py b/scripts/serial/tools/list_ports_linux.py
> new file mode 100644
> index 0000000..955761e
> --- /dev/null
> +++ b/scripts/serial/tools/list_ports_linux.py
> @@ -0,0 +1,152 @@
> +#!/usr/bin/env python
> +
> +# portable serial port access with python
> +#
> +# This is a module that gathers a list of serial ports including details on
> +# GNU/Linux systems
> +#
> +# (C) 2011-2013 Chris Liechti <cliechti at gmx.net>
> +# this is distributed under a free software license, see license.txt
> +
> +import glob
> +import sys
> +import os
> +import re
> +
> +try:
> +    import subprocess
> +except ImportError:
> +    def popen(argv):
> +        try:
> +            si, so =  os.popen4(' '.join(argv))
> +            return so.read().strip()
> +        except:
> +            raise IOError('lsusb failed')
> +else:
> +    def popen(argv):
> +        try:
> +            return subprocess.check_output(argv, stderr=subprocess.STDOUT).strip()
> +        except:
> +            raise IOError('lsusb failed')
> +
> +
> +# The comports function is expected to return an iterable that yields tuples of
> +# 3 strings: port name, human readable description and a hardware ID.
> +#
> +# as currently no method is known to get the second two strings easily, they
> +# are currently just identical to the port name.
> +
> +# try to detect the OS so that a device can be selected...
> +plat = sys.platform.lower()
> +
> +def read_line(filename):
> +    """\
> +    Helper function to read a single line from a file.
> +    Returns None on errors..
> +    """
> +    try:
> +        f = open(filename)
> +        line = f.readline().strip()
> +        f.close()
> +        return line
> +    except IOError:
> +        return None
> +
> +def re_group(regexp, text):
> +    """search for regexp in text, return 1st group on match"""
> +    if sys.version < '3':
> +        m = re.search(regexp, text)
> +    else:
> +        # text is bytes-like
> +        m = re.search(regexp, text.decode('ascii', 'replace'))
> +    if m: return m.group(1)
> +
> +
> +# try to extract descriptions from sysfs. this was done by experimenting,
> +# no guarantee that it works for all devices or in the future...
> +
> +def usb_sysfs_hw_string(sysfs_path):
> +    """given a path to a usb device in sysfs, return a string describing it"""
> +    bus, dev = os.path.basename(os.path.realpath(sysfs_path)).split('-')
> +    snr = read_line(sysfs_path+'/serial')
> +    if snr:
> +        snr_txt = ' SNR=%s' % (snr,)
> +    else:
> +        snr_txt = ''
> +    return 'USB VID:PID=%s:%s%s' % (
> +            read_line(sysfs_path+'/idVendor'),
> +            read_line(sysfs_path+'/idProduct'),
> +            snr_txt
> +            )
> +
> +def usb_lsusb_string(sysfs_path):
> +    base = os.path.basename(os.path.realpath(sysfs_path))
> +    bus = base.split('-')[0]
> +    try:
> +        dev = int(read_line(os.path.join(sysfs_path, 'devnum')))
> +        desc = popen(['lsusb', '-v', '-s', '%s:%s' % (bus, dev)])
> +        # descriptions from device
> +        iManufacturer = re_group('iManufacturer\s+\w+ (.+)', desc)
> +        iProduct = re_group('iProduct\s+\w+ (.+)', desc)
> +        iSerial = re_group('iSerial\s+\w+ (.+)', desc) or ''
> +        # descriptions from kernel
> +        idVendor = re_group('idVendor\s+0x\w+ (.+)', desc)
> +        idProduct = re_group('idProduct\s+0x\w+ (.+)', desc)
> +        # create descriptions. prefer text from device, fall back to the others
> +        return '%s %s %s' % (iManufacturer or idVendor, iProduct or idProduct, iSerial)
> +    except IOError:
> +        return base
> +
> +def describe(device):
> +    """\
> +    Get a human readable description.
> +    For USB-Serial devices try to run lsusb to get a human readable description.
> +    For USB-CDC devices read the description from sysfs.
> +    """
> +    base = os.path.basename(device)
> +    # USB-Serial devices
> +    sys_dev_path = '/sys/class/tty/%s/device/driver/%s' % (base, base)
> +    if os.path.exists(sys_dev_path):
> +        sys_usb = os.path.dirname(os.path.dirname(os.path.realpath(sys_dev_path)))
> +        return usb_lsusb_string(sys_usb)
> +    # USB-CDC devices
> +    sys_dev_path = '/sys/class/tty/%s/device/interface' % (base,)
> +    if os.path.exists(sys_dev_path):
> +        return read_line(sys_dev_path)
> +    # USB Product Information
> +    sys_dev_path = '/sys/class/tty/%s/device' % (base,)
> +    if os.path.exists(sys_dev_path):
> +        product_name_file = os.path.dirname(os.path.realpath(sys_dev_path)) + "/product"
> +        if os.path.exists(product_name_file):
> +            return read_line(product_name_file)
> +    return base
> +
> +def hwinfo(device):
> +    """Try to get a HW identification using sysfs"""
> +    base = os.path.basename(device)
> +    if os.path.exists('/sys/class/tty/%s/device' % (base,)):
> +        # PCI based devices
> +        sys_id_path = '/sys/class/tty/%s/device/id' % (base,)
> +        if os.path.exists(sys_id_path):
> +            return read_line(sys_id_path)
> +        # USB-Serial devices
> +        sys_dev_path = '/sys/class/tty/%s/device/driver/%s' % (base, base)
> +        if os.path.exists(sys_dev_path):
> +            sys_usb = os.path.dirname(os.path.dirname(os.path.realpath(sys_dev_path)))
> +            return usb_sysfs_hw_string(sys_usb)
> +        # USB-CDC devices
> +        if base.startswith('ttyACM'):
> +            sys_dev_path = '/sys/class/tty/%s/device' % (base,)
> +            if os.path.exists(sys_dev_path):
> +                return usb_sysfs_hw_string(sys_dev_path + '/..')
> +    return 'n/a'    # XXX directly remove these from the list?
> +
> +def comports():
> +    devices = glob.glob('/dev/ttyS*') + glob.glob('/dev/ttyUSB*') + glob.glob('/dev/ttyACM*')
> +    return [(d, describe(d), hwinfo(d)) for d in devices]
> +
> +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
> +# test
> +if __name__ == '__main__':
> +    for port, desc, hwid in sorted(comports()):
> +        print "%s: %s [%s]" % (port, desc, hwid)
> diff --git a/scripts/serial/urlhandler/__init__.py b/scripts/serial/urlhandler/__init__.py
> new file mode 100644
> index 0000000..e69de29
> diff --git a/scripts/serial/urlhandler/protocol_hwgrep.py b/scripts/serial/urlhandler/protocol_hwgrep.py
> new file mode 100644
> index 0000000..62cda43
> --- /dev/null
> +++ b/scripts/serial/urlhandler/protocol_hwgrep.py
> @@ -0,0 +1,45 @@
> +#! python
> +#
> +# Python Serial Port Extension for Win32, Linux, BSD, Jython
> +# see __init__.py
> +#
> +# This module implements a special URL handler that uses the port listing to
> +# find ports by searching the string descriptions.
> +#
> +# (C) 2011 Chris Liechti <cliechti at gmx.net>
> +# this is distributed under a free software license, see license.txt
> +#
> +# URL format:    hwgrep://regexp
> +
> +import serial
> +import serial.tools.list_ports
> +
> +class Serial(serial.Serial):
> +    """Just inherit the native Serial port implementation and patch the open function."""
> +
> +    def setPort(self, value):
> +        """translate port name before storing it"""
> +        if isinstance(value, basestring) and value.startswith('hwgrep://'):
> +            serial.Serial.setPort(self, self.fromURL(value))
> +        else:
> +            serial.Serial.setPort(self, value)
> +
> +    def fromURL(self, url):
> +        """extract host and port from an URL string"""
> +        if url.lower().startswith("hwgrep://"): url = url[9:]
> +        # use a for loop to get the 1st element from the generator
> +        for port, desc, hwid in serial.tools.list_ports.grep(url):
> +            return port
> +        else:
> +            raise serial.SerialException('no ports found matching regexp %r' % (url,))
> +
> +    # override property
> +    port = property(serial.Serial.getPort, setPort, doc="Port setting")
> +
> +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
> +if __name__ == '__main__':
> +    #~ s = Serial('hwgrep://ttyS0')
> +    s = Serial(None)
> +    s.port = 'hwgrep://ttyS0'
> +    print s
> +
> diff --git a/scripts/serial/urlhandler/protocol_loop.py b/scripts/serial/urlhandler/protocol_loop.py
> new file mode 100644
> index 0000000..a414839
> --- /dev/null
> +++ b/scripts/serial/urlhandler/protocol_loop.py
> @@ -0,0 +1,279 @@
> +#! python
> +#
> +# Python Serial Port Extension for Win32, Linux, BSD, Jython
> +# see __init__.py
> +#
> +# This module implements a loop back connection receiving itself what it sent.
> +#
> +# The purpose of this module is.. well... You can run the unit tests with it.
> +# and it was so easy to implement ;-)
> +#
> +# (C) 2001-2011 Chris Liechti <cliechti at gmx.net>
> +# this is distributed under a free software license, see license.txt
> +#
> +# URL format:    loop://[option[/option...]]
> +# options:
> +# - "debug" print diagnostic messages
> +
> +from serial.serialutil import *
> +import threading
> +import time
> +import logging
> +
> +# map log level names to constants. used in fromURL()
> +LOGGER_LEVELS = {
> +    'debug': logging.DEBUG,
> +    'info': logging.INFO,
> +    'warning': logging.WARNING,
> +    'error': logging.ERROR,
> +    }
> +
> +
> +class LoopbackSerial(SerialBase):
> +    """Serial port implementation that simulates a loop back connection in plain software."""
> +
> +    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.
> +        """
> +        if self._isOpen:
> +            raise SerialException("Port is already open.")
> +        self.logger = None
> +        self.buffer_lock = threading.Lock()
> +        self.loop_buffer = bytearray()
> +        self.cts = False
> +        self.dsr = False
> +
> +        if self._port is None:
> +            raise SerialException("Port must be configured before it can be used.")
> +        # not that there is anything to open, but the function applies the
> +        # options found in the URL
> +        self.fromURL(self.port)
> +
> +        # not that there anything to configure...
> +        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. For the loop://
> +        protocol all settings are ignored!
> +        """
> +        # not that's it of any real use, but it helps in the unit tests
> +        if not isinstance(self._baudrate, (int, long)) or not 0 < self._baudrate < 2**32:
> +            raise ValueError("invalid baudrate: %r" % (self._baudrate))
> +        if self.logger:
> +            self.logger.info('_reconfigurePort()')
> +
> +    def close(self):
> +        """Close port"""
> +        if self._isOpen:
> +            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("loop://"): url = url[7:]
> +        try:
> +            # process options now, directly altering self
> +            for option in url.split('/'):
> +                if '=' in option:
> +                    option, value = option.split('=', 1)
> +                else:
> +                    value = None
> +                if not option:
> +                    pass
> +                elif option == 'logging':
> +                    logging.basicConfig()   # XXX is that good to call it here?
> +                    self.logger = logging.getLogger('pySerial.loop')
> +                    self.logger.setLevel(LOGGER_LEVELS[value])
> +                    self.logger.debug('enabled logging')
> +                else:
> +                    raise ValueError('unknown option: %r' % (option,))
> +        except ValueError, e:
> +            raise SerialException('expected a string in the form "[loop://][option[/option...]]": %s' % e)
> +
> +    #  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -
> +
> +    def inWaiting(self):
> +        """Return the number of characters currently in the input buffer."""
> +        if not self._isOpen: raise portNotOpenError
> +        if self.logger:
> +            # attention the logged value can differ from return value in
> +            # threaded environments...
> +            self.logger.debug('inWaiting() -> %d' % (len(self.loop_buffer),))
> +        return len(self.loop_buffer)
> +
> +    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
> +        if self._timeout is not None:
> +            timeout = time.time() + self._timeout
> +        else:
> +            timeout = None
> +        data = bytearray()
> +        while size > 0:
> +            self.buffer_lock.acquire()
> +            try:
> +                block = to_bytes(self.loop_buffer[:size])
> +                del self.loop_buffer[:size]
> +            finally:
> +                self.buffer_lock.release()
> +            data += block
> +            size -= len(block)
> +            # check for timeout now, after data has been read.
> +            # useful for timeout = 0 (non blocking) read
> +            if timeout and time.time() > timeout:
> +                break
> +        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
> +        # ensure we're working with bytes
> +        data = to_bytes(data)
> +        # calculate aprox time that would be used to send the data
> +        time_used_to_send = 10.0*len(data) / self._baudrate
> +        # when a write timeout is configured check if we would be successful
> +        # (not sending anything, not even the part that would have time)
> +        if self._writeTimeout is not None and time_used_to_send > self._writeTimeout:
> +            time.sleep(self._writeTimeout) # must wait so that unit test succeeds
> +            raise writeTimeoutError
> +        self.buffer_lock.acquire()
> +        try:
> +            self.loop_buffer += data
> +        finally:
> +            self.buffer_lock.release()
> +        return len(data)
> +
> +    def flushInput(self):
> +        """Clear input buffer, discarding all that is in the buffer."""
> +        if not self._isOpen: raise portNotOpenError
> +        if self.logger:
> +            self.logger.info('flushInput()')
> +        self.buffer_lock.acquire()
> +        try:
> +            del self.loop_buffer[:]
> +        finally:
> +            self.buffer_lock.release()
> +
> +    def flushOutput(self):
> +        """\
> +        Clear output buffer, aborting the current output and
> +        discarding all that is in the buffer.
> +        """
> +        if not self._isOpen: raise portNotOpenError
> +        if self.logger:
> +            self.logger.info('flushOutput()')
> +
> +    def sendBreak(self, duration=0.25):
> +        """\
> +        Send break condition. Timed, returns to idle state after given
> +        duration.
> +        """
> +        if not self._isOpen: raise portNotOpenError
> +
> +    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('setBreak(%r)' % (level,))
> +
> +    def setRTS(self, level=True):
> +        """Set terminal status line: Request To Send"""
> +        if not self._isOpen: raise portNotOpenError
> +        if self.logger:
> +            self.logger.info('setRTS(%r) -> state of CTS' % (level,))
> +        self.cts = level
> +
> +    def setDTR(self, level=True):
> +        """Set terminal status line: Data Terminal Ready"""
> +        if not self._isOpen: raise portNotOpenError
> +        if self.logger:
> +            self.logger.info('setDTR(%r) -> state of DSR' % (level,))
> +        self.dsr = level
> +
> +    def getCTS(self):
> +        """Read terminal status line: Clear To Send"""
> +        if not self._isOpen: raise portNotOpenError
> +        if self.logger:
> +            self.logger.info('getCTS() -> state of RTS (%r)' % (self.cts,))
> +        return self.cts
> +
> +    def getDSR(self):
> +        """Read terminal status line: Data Set Ready"""
> +        if not self._isOpen: raise portNotOpenError
> +        if self.logger:
> +            self.logger.info('getDSR() -> state of DTR (%r)' % (self.dsr,))
> +        return self.dsr
> +
> +    def getRI(self):
> +        """Read terminal status line: Ring Indicator"""
> +        if not self._isOpen: raise portNotOpenError
> +        if self.logger:
> +            self.logger.info('returning dummy for getRI()')
> +        return False
> +
> +    def getCD(self):
> +        """Read terminal status line: Carrier Detect"""
> +        if not self._isOpen: raise portNotOpenError
> +        if self.logger:
> +            self.logger.info('returning dummy for getCD()')
> +        return True
> +
> +    # - - - platform specific - - -
> +    # None so far
> +
> +
> +# 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(LoopbackSerial, FileLike):
> +        pass
> +else:
> +    # io library present
> +    class Serial(LoopbackSerial, io.RawIOBase):
> +        pass
> +
> +
> +# simple client test
> +if __name__ == '__main__':
> +    import sys
> +    s = Serial('loop://')
> +    sys.stdout.write('%s\n' % s)
> +
> +    sys.stdout.write("write...\n")
> +    s.write("hello\n")
> +    s.flush()
> +    sys.stdout.write("read: %s\n" % s.read(5))
> +
> +    s.close()
> diff --git a/scripts/serial/urlhandler/protocol_rfc2217.py b/scripts/serial/urlhandler/protocol_rfc2217.py
> new file mode 100644
> index 0000000..981ba45
> --- /dev/null
> +++ b/scripts/serial/urlhandler/protocol_rfc2217.py
> @@ -0,0 +1,11 @@
> +#! python
> +#
> +# Python Serial Port Extension for Win32, Linux, BSD, Jython
> +# see ../__init__.py
> +#
> +# This is a thin wrapper to load the rfc2271 implementation.
> +#
> +# (C) 2011 Chris Liechti <cliechti at gmx.net>
> +# this is distributed under a free software license, see license.txt
> +
> +from serial.rfc2217 import Serial
> diff --git a/scripts/serial/urlhandler/protocol_socket.py b/scripts/serial/urlhandler/protocol_socket.py
> new file mode 100644
> index 0000000..dc59923
> --- /dev/null
> +++ b/scripts/serial/urlhandler/protocol_socket.py
> @@ -0,0 +1,291 @@
> +#! python
> +#
> +# Python Serial Port Extension for Win32, Linux, BSD, Jython
> +# see __init__.py
> +#
> +# This module implements a simple socket based client.
> +# It does not support changing any port parameters and will silently ignore any
> +# requests to do so.
> +#
> +# The purpose of this module is that applications using pySerial can connect to
> +# TCP/IP to serial port converters that do not support RFC 2217.
> +#
> +# (C) 2001-2011 Chris Liechti <cliechti at gmx.net>
> +# this is distributed under a free software license, see license.txt
> +#
> +# URL format:    socket://<host>:<port>[/option[/option...]]
> +# options:
> +# - "debug" print diagnostic messages
> +
> +from serial.serialutil import *
> +import time
> +import socket
> +import select
> +import logging
> +
> +# map log level names to constants. used in fromURL()
> +LOGGER_LEVELS = {
> +    'debug': logging.DEBUG,
> +    'info': logging.INFO,
> +    'warning': logging.WARNING,
> +    'error': logging.ERROR,
> +    }
> +
> +POLL_TIMEOUT = 2
> +
> +class SocketSerial(SerialBase):
> +    """Serial port implementation for plain sockets."""
> +
> +    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
> +        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:
> +            # XXX in future replace with create_connection (py >=2.6)
> +            self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
> +            self._socket.connect(self.fromURL(self.portstr))
> +        except Exception, msg:
> +            self._socket = None
> +            raise SerialException("Could not open port %s: %s" % (self.portstr, msg))
> +
> +        self._socket.settimeout(POLL_TIMEOUT) # used for write timeout support :/
> +
> +        # not that there anything to configure...
> +        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. For the socket://
> +        protocol all settings are ignored!
> +        """
> +        if self._socket is None:
> +            raise SerialException("Can only operate on open ports")
> +        if self.logger:
> +            self.logger.info('ignored port configuration change')
> +
> +    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
> +            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("socket://"): url = url[9:]
> +        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.socket')
> +                        self.logger.setLevel(LOGGER_LEVELS[value])
> +                        self.logger.debug('enabled logging')
> +                    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
> +        # Poll the socket to see if it is ready for reading.
> +        # If ready, at least one byte will be to read.
> +        lr, lw, lx = select.select([self._socket], [], [], 0)
> +        return len(lr)
> +
> +    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()
> +        if self._timeout is not None:
> +            timeout = time.time() + self._timeout
> +        else:
> +            timeout = None
> +        while len(data) < size and (timeout is None or time.time() < timeout):
> +            try:
> +                # an implementation with internal buffer would be better
> +                # performing...
> +                t = time.time()
> +                block = self._socket.recv(size - len(data))
> +                duration = time.time() - t
> +                if block:
> +                    data.extend(block)
> +                else:
> +                    # no data -> EOF (connection probably closed)
> +                    break
> +            except socket.timeout:
> +                # just need to get out of recv from time to time to check if
> +                # still alive
> +                continue
> +            except socket.error, e:
> +                # connection fails -> terminate loop
> +                raise SerialException('connection failed (%s)' % e)
> +        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
> +        try:
> +            self._socket.sendall(to_bytes(data))
> +        except socket.error, e:
> +            # XXX what exception if socket connection fails
> +            raise SerialException("socket connection failed: %s" % e)
> +        return len(data)
> +
> +    def flushInput(self):
> +        """Clear input buffer, discarding all that is in the buffer."""
> +        if not self._isOpen: raise portNotOpenError
> +        if self.logger:
> +            self.logger.info('ignored flushInput')
> +
> +    def flushOutput(self):
> +        """\
> +        Clear output buffer, aborting the current output and
> +        discarding all that is in the buffer.
> +        """
> +        if not self._isOpen: raise portNotOpenError
> +        if self.logger:
> +            self.logger.info('ignored flushOutput')
> +
> +    def sendBreak(self, duration=0.25):
> +        """\
> +        Send break condition. Timed, returns to idle state after given
> +        duration.
> +        """
> +        if not self._isOpen: raise portNotOpenError
> +        if self.logger:
> +            self.logger.info('ignored sendBreak(%r)' % (duration,))
> +
> +    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('ignored setBreak(%r)' % (level,))
> +
> +    def setRTS(self, level=True):
> +        """Set terminal status line: Request To Send"""
> +        if not self._isOpen: raise portNotOpenError
> +        if self.logger:
> +            self.logger.info('ignored setRTS(%r)' % (level,))
> +
> +    def setDTR(self, level=True):
> +        """Set terminal status line: Data Terminal Ready"""
> +        if not self._isOpen: raise portNotOpenError
> +        if self.logger:
> +            self.logger.info('ignored setDTR(%r)' % (level,))
> +
> +    def getCTS(self):
> +        """Read terminal status line: Clear To Send"""
> +        if not self._isOpen: raise portNotOpenError
> +        if self.logger:
> +            self.logger.info('returning dummy for getCTS()')
> +        return True
> +
> +    def getDSR(self):
> +        """Read terminal status line: Data Set Ready"""
> +        if not self._isOpen: raise portNotOpenError
> +        if self.logger:
> +            self.logger.info('returning dummy for getDSR()')
> +        return True
> +
> +    def getRI(self):
> +        """Read terminal status line: Ring Indicator"""
> +        if not self._isOpen: raise portNotOpenError
> +        if self.logger:
> +            self.logger.info('returning dummy for getRI()')
> +        return False
> +
> +    def getCD(self):
> +        """Read terminal status line: Carrier Detect"""
> +        if not self._isOpen: raise portNotOpenError
> +        if self.logger:
> +            self.logger.info('returning dummy for getCD()')
> +        return True
> +
> +    # - - - platform specific - - -
> +
> +    # works on Linux and probably all the other POSIX systems
> +    def fileno(self):
> +        """Get the file handle of the underlying socket for use with select"""
> +        return self._socket.fileno()
> +
> +
> +# 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(SocketSerial, FileLike):
> +        pass
> +else:
> +    # io library present
> +    class Serial(SocketSerial, io.RawIOBase):
> +        pass
> +
> +
> +# simple client test
> +if __name__ == '__main__':
> +    import sys
> +    s = Serial('socket://localhost:7000')
> +    sys.stdout.write('%s\n' % s)
> +
> +    sys.stdout.write("write...\n")
> +    s.write("hello\n")
> +    s.flush()
> +    sys.stdout.write("read: %s\n" % s.read(5))
> +
> +    s.close()
> --
> 2.6.4
>
>
> _______________________________________________
> barebox mailing list
> barebox at lists.infradead.org
> http://lists.infradead.org/mailman/listinfo/barebox



More information about the barebox mailing list