/pyserial/examples/tcp_serial_redirect.py
Python | 326 lines | 316 code | 5 blank | 5 comment | 11 complexity | 37e51f08a201f55ceaa2d79f32e4d5ff MD5 | raw file
- #!/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')