diff options
Diffstat (limited to 'examples/tcp_serial_redirect.py')
-rw-r--r-- | examples/tcp_serial_redirect.py | 326 |
1 files changed, 326 insertions, 0 deletions
diff --git a/examples/tcp_serial_redirect.py b/examples/tcp_serial_redirect.py new file mode 100644 index 0000000..8900ca9 --- /dev/null +++ b/examples/tcp_serial_redirect.py @@ -0,0 +1,326 @@ +#!/usr/bin/env python + +# (C) 2002-2009 Chris Liechti <cliechti@gmx.net> +# redirect data from a TCP/IP connection to a serial port and vice versa +# requires Python 2.2 'cause socket.sendall is used + + +import sys +import os +import time +import threading +import socket +import codecs +import serial +try: + True +except NameError: + True = 1 + False = 0 + +class Redirector: + def __init__(self, serial_instance, socket, ser_newline=None, net_newline=None, spy=False): + self.serial = serial_instance + self.socket = socket + self.ser_newline = ser_newline + self.net_newline = net_newline + self.spy = spy + self._write_lock = threading.Lock() + + def shortcut(self): + """connect the serial port to the TCP port by copying everything + from one side to the other""" + self.alive = True + self.thread_read = threading.Thread(target=self.reader) + self.thread_read.setDaemon(True) + self.thread_read.setName('serial->socket') + self.thread_read.start() + self.writer() + + def reader(self): + """loop forever and copy serial->socket""" + while self.alive: + try: + data = self.serial.read(1) # read one, blocking + n = self.serial.inWaiting() # look if there is more + if n: + data = data + self.serial.read(n) # and get as much as possible + if data: + # the spy shows what's on the serial port, so log it before converting newlines + if self.spy: + sys.stdout.write(codecs.escape_encode(data)[0]) + sys.stdout.flush() + if self.ser_newline and self.net_newline: + # do the newline conversion + # XXX fails for CR+LF in input when it is cut in half at the begin or end of the string + data = net_newline.join(data.split(ser_newline)) + # escape outgoing data when needed (Telnet IAC (0xff) character) + self._write_lock.acquire() + try: + self.socket.sendall(data) # send it over TCP + finally: + self._write_lock.release() + except socket.error, msg: + sys.stderr.write('ERROR: %s\n' % msg) + # probably got disconnected + break + self.alive = False + + def write(self, data): + """thread safe 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 writer(self): + """loop forever and copy socket->serial""" + while self.alive: + try: + data = self.socket.recv(1024) + if not data: + break + if self.ser_newline and self.net_newline: + # do the newline conversion + # XXX fails for CR+LF in input when it is cut in half at the begin or end of the string + data = ser_newline.join(data.split(net_newline)) + self.serial.write(data) # get a bunch of bytes and send them + # the spy shows what's on the serial port, so log it after converting newlines + if self.spy: + sys.stdout.write(codecs.escape_encode(data)[0]) + sys.stdout.flush() + except socket.error, msg: + sys.stderr.write('ERROR: %s\n' % msg) + # probably got disconnected + break + self.alive = False + self.thread_read.join() + + def stop(self): + """Stop copying""" + if self.alive: + self.alive = False + self.thread_read.join() + + +if __name__ == '__main__': + import optparse + + parser = optparse.OptionParser( + usage = "%prog [options] [port [baudrate]]", + description = "Simple Serial to Network (TCP/IP) redirector.", + epilog = """\ +NOTE: no security measures are implemented. Anyone can remotely connect +to this service over the network. + +Only one connection at once is supported. When the connection is terminated +it waits for the next connect. +""") + + parser.add_option("-q", "--quiet", + dest = "quiet", + action = "store_true", + help = "suppress non error messages", + default = False + ) + + parser.add_option("--spy", + dest = "spy", + action = "store_true", + help = "peek at the communication and print all data to the console", + default = False + ) + + group = optparse.OptionGroup(parser, + "Serial Port", + "Serial port settings" + ) + parser.add_option_group(group) + + group.add_option("-p", "--port", + dest = "port", + help = "port, a number (default 0) or a device name", + default = None + ) + + group.add_option("-b", "--baud", + dest = "baudrate", + action = "store", + type = 'int', + help = "set baud rate, default: %default", + default = 9600 + ) + + group.add_option("", "--parity", + dest = "parity", + action = "store", + help = "set parity, one of [N, E, O], default=%default", + default = 'N' + ) + + group.add_option("--rtscts", + dest = "rtscts", + action = "store_true", + help = "enable RTS/CTS flow control (default off)", + default = False + ) + + group.add_option("--xonxoff", + dest = "xonxoff", + action = "store_true", + help = "enable software flow control (default off)", + default = False + ) + + group.add_option("--rts", + dest = "rts_state", + action = "store", + type = 'int', + help = "set initial RTS line state (possible values: 0, 1)", + default = None + ) + + group.add_option("--dtr", + dest = "dtr_state", + action = "store", + type = 'int', + help = "set initial DTR line state (possible values: 0, 1)", + default = None + ) + + group = optparse.OptionGroup(parser, + "Network settings", + "Network configuration." + ) + parser.add_option_group(group) + + group.add_option("-P", "--localport", + dest = "local_port", + action = "store", + type = 'int', + help = "local TCP port", + default = 7777 + ) + + group = optparse.OptionGroup(parser, + "Newline Settings", + "Convert newlines between network and serial port. Conversion is normally disabled and can be enabled by --convert." + ) + parser.add_option_group(group) + + group.add_option("-c", "--convert", + dest = "convert", + action = "store_true", + help = "enable newline conversion (default off)", + default = False + ) + + group.add_option("--net-nl", + dest = "net_newline", + action = "store", + help = "type of newlines that are expected on the network (default: %default)", + default = "LF" + ) + + group.add_option("--ser-nl", + dest = "ser_newline", + action = "store", + help = "type of newlines that are expected on the serial port (default: %default)", + default = "CR+LF" + ) + + (options, args) = parser.parse_args() + + # get port and baud rate from command line arguments or the option switches + port = options.port + baudrate = options.baudrate + if args: + if options.port is not None: + parser.error("no arguments are allowed, options only when --port is given") + port = args.pop(0) + if args: + try: + baudrate = int(args[0]) + except ValueError: + parser.error("baud rate must be a number, not %r" % args[0]) + args.pop(0) + if args: + parser.error("too many arguments") + else: + if port is None: port = 0 + + # check newline modes for network connection + mode = options.net_newline.upper() + if mode == 'CR': + net_newline = '\r' + elif mode == 'LF': + net_newline = '\n' + elif mode == 'CR+LF' or mode == 'CRLF': + net_newline = '\r\n' + else: + parser.error("Invalid value for --net-nl. Valid are 'CR', 'LF' and 'CR+LF'/'CRLF'.") + + # check newline modes for serial connection + mode = options.ser_newline.upper() + if mode == 'CR': + ser_newline = '\r' + elif mode == 'LF': + ser_newline = '\n' + elif mode == 'CR+LF' or mode == 'CRLF': + ser_newline = '\r\n' + else: + parser.error("Invalid value for --ser-nl. Valid are 'CR', 'LF' and 'CR+LF'/'CRLF'.") + + # connect to serial port + ser = serial.Serial() + ser.port = port + ser.baudrate = baudrate + ser.parity = options.parity + ser.rtscts = options.rtscts + ser.xonxoff = options.xonxoff + ser.timeout = 1 # required so that the reader thread can exit + + if not options.quiet: + sys.stderr.write("--- TCP/IP to Serial redirector --- type Ctrl-C / BREAK to quit\n") + sys.stderr.write("--- %s %s,%s,%s,%s ---\n" % (ser.portstr, ser.baudrate, 8, ser.parity, 1)) + + try: + ser.open() + except serial.SerialException, e: + sys.stderr.write("Could not open serial port %s: %s\n" % (ser.portstr, e)) + sys.exit(1) + + if options.rts_state is not None: + ser.setRTS(options.rts_state) + + if options.dtr_state is not None: + ser.setDTR(options.dtr_state) + + srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + srv.bind( ('', options.local_port) ) + srv.listen(1) + while True: + try: + sys.stderr.write("Waiting for connection on %s...\n" % options.local_port) + connection, addr = srv.accept() + sys.stderr.write('Connected by %s\n' % (addr,)) + # enter network <-> serial loop + r = Redirector( + ser, + connection, + options.convert and ser_newline or None, + options.convert and net_newline or None, + options.spy, + ) + r.shortcut() + if options.spy: sys.stdout.write('\n') + sys.stderr.write('Disconnected\n') + connection.close() + except KeyboardInterrupt: + break + except socket.error, msg: + sys.stderr.write('ERROR: %s\n' % msg) + + sys.stderr.write('\n--- exit ---\n') + |