Async serial communication/threads sharing data

Nick Craig-Wood nick at
Mon Mar 23 18:30:03 CET 2009

Jean-Paul Calderone <exarkun at> wrote:
>  On Mon, 23 Mar 2009 05:30:04 -0500, Nick Craig-Wood <nick at> wrote:
> >Jean-Paul Calderone <exarkun at> wrote:
> > [snip]
> >>
> >>  In the case of a TCP to serial forwarder, you don't actually have to
> >>  implement either a producer or a consumer, since both the TCP connection
> >>  and the serial connection are already both producers and consumers.  All
> >>  you need to do is hook them up to each other so that when the send buffer
> >>  of one fills up, the other one gets paused, and when the buffer is empty
> >>  again, it gets resumed.
> >
> >I eventually came up with this which seems to work, but I'm not sure
> >it is the best way of doing it as it had to mess about with the
> >twisted internals to get the number of bytes in the serial port
> >output buffer.
>  This is sort of on the right track.  Here's how to do it without
>  touching implementation details:

Thank you for that!

See below for the complete prog with your suggested modifications.

That seems to solve the problem - I can see the writer pausing and
unpausing at the serial port rate.

  write 16446
  write 6430
  pause producing
  resume producing
  write 65536
  write 56724
  pause producing
  resume producing
  write 65536
  write 65536
  pause producing

It has exposed a problem with the sender not throttling properly now,
but I guess that is progress!

Thanks for your help

Here is the complete program FYI with your suggested mods.

"""Transfer data between a serial port and one (or more) TCP
    -h, --help:        this help
    -p, --port=PORT: port, a number, default = 0 or a device name
    -b, --baud=BAUD: baudrate, default 115200
    -t, --tcp=PORT: TCP port number, default 1234
    -l, --log: log data streams to 'snifter-0', 'snifter-1'
    -L, --log_name=NAME: log data streams to '<NAME>-0', '<NAME>-1'

import sys
import getopt
from twisted.internet import reactor, protocol, serialport
from zope.interface import implements
from twisted.internet import protocol, interfaces

# FIXME set serial buffer size? SEND_LIMIT

class SerialPort(protocol.Protocol):
    """Create a serial port connection and pass data from it to a
    known list of
    TCP ports."""

    def __init__(self, port, reactor, baudrate, log_name=None):
        self.tcp_ports = []
        self.serial = serialport.SerialPort(self, reactor, port,
	baudrate, rtscts=0)
        self.serial.registerProducer(self, True)
        self.paused = False
        self.log = None
        if log_name is not None:
            self.log = file('%s-0' % log_name, 'w')

    def add_tcp(self, tcp_port):
        """Add a TCPPort to those receiving serial data."""
        if self.paused:

    def del_tcp(self, tcp_port):
        """Remove a TCPPort from the those receiving serial data."""

    def write(self, data):
        """Write data to the serial port."""
        if self.log:

    def pauseProducing(self):
        """Pause producing event"""
        print "pause producing"
        self.paused = True
        for port in self.tcp_ports:

    def resumeProducing(self):
        """Resume producing event"""
        print "resume producing"
        self.paused = False
        for port in self.tcp_ports:

    def stopProducing(self):
        """Stop producing event"""
        print "stop producing"

    def dataReceived(self, data):
        """Pass any received data to the list of TCPPorts."""
        for tcp_port in self.tcp_ports:

class TCPPort(protocol.Protocol):
    """Create a TCP server connection and pass data from it to the
    serial port."""

    def __init__(self, serial, log_name, index):
        """Add this TCPPort to the SerialPort."""
        self.serial = serial
        self.log = None
        if log_name is not None:
            self.log = file('%s-%d' % (log_name, index+1), 'w')

    def __del__(self):
        """Remove this TCPPort from the SerialPort."""

    def dataReceived(self, data):
        """Pass received data to the SerialPort."""
        print "write", len(data)

    def write(self, data):
        """Write data to the TCP port."""
        if self.log is not None:

class TCPPortFactory(protocol.ServerFactory):
    """Factory to create TCPPort protocol instances, an instanced
    SerialPort must be passed in."""

    def __init__(self, serial, log_name=None):
        self.serial = serial
        self.log_name = log_name
        self.index = 0

    def buildProtocol(self, addr):
        """Build a TCPPort, passing in the instanced SerialPort."""
        p = TCPPort(self.serial, self.log_name, self.index)
        self.index += 1
        p.factory = self
        return p

def usage(text=None):
    print >>sys.stderr, """Syntax: %s [options]\n%s""" % (sys.argv[0],
    if text:
        print >>sys.stderr, text

def main():
    """Parse the command line and run the UI"""
        opts, args = getopt.getopt(sys.argv[1:], "hp:b:t:lL:",
	["help", "port=", "baud=", "tcp=", "log", "log_name="])
    except getopt.GetoptError, e:
    tty_port = 0
    baudrate = 9600
    tcp_port = 1234
    log_name = None
    for o, a in opts:
        if o in ("-h", "--help"):
        elif o in ("-p", "--port"):
                tty_port = int(a)
            except ValueError:
                tty_port = a
        elif o in ("-b", "--baud"):
                baudrate = int(a)
            except ValueError:
                usage("Bad baud rate %r" % a)
        elif o in ("-t", "--tcp"):
                tcp_port = int(a)
            except ValueError:
                usage("Bad TCP port %r" % a)
        elif o in ("-l", "--log"):
            log_name = 'snifter'
        elif o in ("-L", "--log_name"):
            log_name = a
    serial_port = SerialPort(reactor, tty_port, baudrate, log_name)
    tcp_port_factory = TCPPortFactory(serial_port, log_name)
    reactor.listenTCP(tcp_port, tcp_port_factory)

if __name__ == "__main__": main()

Nick Craig-Wood <nick at> --

More information about the Python-list mailing list