Async serial communication/threads sharing data
Nick Craig-Wood
nick at craig-wood.com
Mon Mar 23 13:30:03 EDT 2009
Jean-Paul Calderone <exarkun at divmod.com> wrote:
> On Mon, 23 Mar 2009 05:30:04 -0500, Nick Craig-Wood <nick at craig-wood.com> wrote:
> >Jean-Paul Calderone <exarkun at divmod.com> 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.
#!/usr/bin/python
"""Transfer data between a serial port and one (or more) TCP
connections.
options:
-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:
tcp_port.transport.pauseProducing()
self.tcp_ports.append(tcp_port)
def del_tcp(self, tcp_port):
"""Remove a TCPPort from the those receiving serial data."""
self.tcp_ports.remove(tcp_port)
def write(self, data):
"""Write data to the serial port."""
self.serial.write(data)
if self.log:
self.log.write(data)
def pauseProducing(self):
"""Pause producing event"""
print "pause producing"
self.paused = True
for port in self.tcp_ports:
port.transport.pauseProducing()
def resumeProducing(self):
"""Resume producing event"""
print "resume producing"
self.paused = False
for port in self.tcp_ports:
port.transport.resumeProducing()
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:
tcp_port.write(data)
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.serial.add_tcp(self)
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."""
self.serial.del_tcp(self)
def dataReceived(self, data):
"""Pass received data to the SerialPort."""
print "write", len(data)
self.serial.write(data)
def write(self, data):
"""Write data to the TCP port."""
self.transport.write(data)
if self.log is not None:
self.log.write(data)
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],
>>__doc__)
if text:
print >>sys.stderr, text
def main():
"""Parse the command line and run the UI"""
try:
opts, args = getopt.getopt(sys.argv[1:], "hp:b:t:lL:",
["help", "port=", "baud=", "tcp=", "log", "log_name="])
except getopt.GetoptError, e:
usage(e)
sys.exit(2)
tty_port = 0
baudrate = 9600
tcp_port = 1234
log_name = None
for o, a in opts:
if o in ("-h", "--help"):
usage()
sys.exit()
elif o in ("-p", "--port"):
try:
tty_port = int(a)
except ValueError:
tty_port = a
elif o in ("-b", "--baud"):
try:
baudrate = int(a)
except ValueError:
usage("Bad baud rate %r" % a)
elif o in ("-t", "--tcp"):
try:
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)
reactor.run()
if __name__ == "__main__": main()
--
Nick Craig-Wood <nick at craig-wood.com> -- http://www.craig-wood.com/nick
More information about the Python-list
mailing list