aboutsummaryrefslogtreecommitdiff
path: root/examples/tcp_serial_redirect.py
diff options
context:
space:
mode:
Diffstat (limited to 'examples/tcp_serial_redirect.py')
-rwxr-xr-xexamples/tcp_serial_redirect.py230
1 files changed, 230 insertions, 0 deletions
diff --git a/examples/tcp_serial_redirect.py b/examples/tcp_serial_redirect.py
new file mode 100755
index 0000000..bd7db77
--- /dev/null
+++ b/examples/tcp_serial_redirect.py
@@ -0,0 +1,230 @@
+#!/usr/bin/env python
+#
+# Redirect data from a TCP/IP connection to a serial port and vice versa.
+#
+# (C) 2002-2020 Chris Liechti <cliechti@gmx.net>
+#
+# SPDX-License-Identifier: BSD-3-Clause
+
+import sys
+import socket
+import serial
+import serial.threaded
+import time
+
+
+class SerialToNet(serial.threaded.Protocol):
+ """serial->socket"""
+
+ def __init__(self):
+ self.socket = None
+
+ def __call__(self):
+ return self
+
+ def data_received(self, data):
+ if self.socket is not None:
+ self.socket.sendall(data)
+
+
+if __name__ == '__main__': # noqa
+ import argparse
+
+ parser = argparse.ArgumentParser(
+ 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_argument(
+ 'SERIALPORT',
+ help="serial port name")
+
+ parser.add_argument(
+ 'BAUDRATE',
+ type=int,
+ nargs='?',
+ help='set baud rate, default: %(default)s',
+ default=9600)
+
+ parser.add_argument(
+ '-q', '--quiet',
+ action='store_true',
+ help='suppress non error messages',
+ default=False)
+
+ parser.add_argument(
+ '--develop',
+ action='store_true',
+ help='Development mode, prints Python internals on errors',
+ default=False)
+
+ group = parser.add_argument_group('serial port')
+
+ group.add_argument(
+ "--bytesize",
+ choices=[5, 6, 7, 8],
+ type=int,
+ help="set bytesize, one of {5 6 7 8}, default: 8",
+ default=8)
+
+ group.add_argument(
+ "--parity",
+ choices=['N', 'E', 'O', 'S', 'M'],
+ type=lambda c: c.upper(),
+ help="set parity, one of {N E O S M}, default: N",
+ default='N')
+
+ group.add_argument(
+ "--stopbits",
+ choices=[1, 1.5, 2],
+ type=float,
+ help="set stopbits, one of {1 1.5 2}, default: 1",
+ default=1)
+
+ group.add_argument(
+ '--rtscts',
+ action='store_true',
+ help='enable RTS/CTS flow control (default off)',
+ default=False)
+
+ group.add_argument(
+ '--xonxoff',
+ action='store_true',
+ help='enable software flow control (default off)',
+ default=False)
+
+ group.add_argument(
+ '--rts',
+ type=int,
+ help='set initial RTS line state (possible values: 0, 1)',
+ default=None)
+
+ group.add_argument(
+ '--dtr',
+ type=int,
+ help='set initial DTR line state (possible values: 0, 1)',
+ default=None)
+
+ group = parser.add_argument_group('network settings')
+
+ exclusive_group = group.add_mutually_exclusive_group()
+
+ exclusive_group.add_argument(
+ '-P', '--localport',
+ type=int,
+ help='local TCP port',
+ default=7777)
+
+ exclusive_group.add_argument(
+ '-c', '--client',
+ metavar='HOST:PORT',
+ help='make the connection as a client, instead of running a server',
+ default=False)
+
+ args = parser.parse_args()
+
+ # connect to serial port
+ ser = serial.serial_for_url(args.SERIALPORT, do_not_open=True)
+ ser.baudrate = args.BAUDRATE
+ ser.bytesize = args.bytesize
+ ser.parity = args.parity
+ ser.stopbits = args.stopbits
+ ser.rtscts = args.rtscts
+ ser.xonxoff = args.xonxoff
+
+ if args.rts is not None:
+ ser.rts = args.rts
+
+ if args.dtr is not None:
+ ser.dtr = args.dtr
+
+ if not args.quiet:
+ sys.stderr.write(
+ '--- TCP/IP to Serial redirect on {p.name} {p.baudrate},{p.bytesize},{p.parity},{p.stopbits} ---\n'
+ '--- type Ctrl-C / BREAK to quit\n'.format(p=ser))
+
+ try:
+ ser.open()
+ except serial.SerialException as e:
+ sys.stderr.write('Could not open serial port {}: {}\n'.format(ser.name, e))
+ sys.exit(1)
+
+ ser_to_net = SerialToNet()
+ serial_worker = serial.threaded.ReaderThread(ser, ser_to_net)
+ serial_worker.start()
+
+ if not args.client:
+ srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ srv.bind(('', args.localport))
+ srv.listen(1)
+ try:
+ intentional_exit = False
+ while True:
+ if args.client:
+ host, port = args.client.split(':')
+ sys.stderr.write("Opening connection to {}:{}...\n".format(host, port))
+ client_socket = socket.socket()
+ try:
+ client_socket.connect((host, int(port)))
+ except socket.error as msg:
+ sys.stderr.write('WARNING: {}\n'.format(msg))
+ time.sleep(5) # intentional delay on reconnection as client
+ continue
+ sys.stderr.write('Connected\n')
+ client_socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
+ #~ client_socket.settimeout(5)
+ else:
+ sys.stderr.write('Waiting for connection on {}...\n'.format(args.localport))
+ client_socket, addr = srv.accept()
+ sys.stderr.write('Connected by {}\n'.format(addr))
+ # More quickly detect bad clients who quit without closing the
+ # connection: After 1 second of idle, start sending TCP keep-alive
+ # packets every 1 second. If 3 consecutive keep-alive packets
+ # fail, assume the client is gone and close the connection.
+ try:
+ client_socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, 1)
+ client_socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, 1)
+ client_socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, 3)
+ client_socket.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
+ except AttributeError:
+ pass # XXX not available on windows
+ client_socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
+ try:
+ ser_to_net.socket = client_socket
+ # enter network <-> serial loop
+ while True:
+ try:
+ data = client_socket.recv(1024)
+ if not data:
+ break
+ ser.write(data) # get a bunch of bytes and send them
+ except socket.error as msg:
+ if args.develop:
+ raise
+ sys.stderr.write('ERROR: {}\n'.format(msg))
+ # probably got disconnected
+ break
+ except KeyboardInterrupt:
+ intentional_exit = True
+ raise
+ except socket.error as msg:
+ if args.develop:
+ raise
+ sys.stderr.write('ERROR: {}\n'.format(msg))
+ finally:
+ ser_to_net.socket = None
+ sys.stderr.write('Disconnected\n')
+ client_socket.close()
+ if args.client and not intentional_exit:
+ time.sleep(5) # intentional delay on reconnection as client
+ except KeyboardInterrupt:
+ pass
+
+ sys.stderr.write('\n--- exit ---\n')
+ serial_worker.stop()