1# SPDX-FileCopyrightText: 2009-2015 Chris Liechti
2# SPDX-FileContributor: 2020-2024 Espressif Systems (Shanghai) CO LTD
3# SPDX-License-Identifier: BSD-3-Clause
4#
5# Redirect data from a TCP/IP connection to a serial port and vice versa using RFC 2217.
6
7###################################################################################
8# redirect data from a TCP/IP connection to a serial port and vice versa
9# using RFC 2217
10#
11# (C) 2009-2015 Chris Liechti <cliechti@gmx.net>
12#
13# SPDX-License-Identifier: BSD-3-Clause
14
15import logging
16import socket
17import sys
18import serial
19
20from esp_rfc2217_server.redirector import Redirector
21
22
23def main():
24    import argparse
25
26    parser = argparse.ArgumentParser(
27        description="RFC 2217 Serial to Network (TCP/IP) redirector.",
28        epilog="NOTE: no security measures are implemented. "
29        "Anyone can remotely connect to this service over the network.\n"
30        "Only one connection at once is supported. "
31        "When the connection is terminated it waits for the next connect.",
32    )
33
34    parser.add_argument("SERIALPORT")
35
36    parser.add_argument(
37        "-p",
38        "--localport",
39        type=int,
40        help="local TCP port, default: %(default)s",
41        metavar="TCPPORT",
42        default=2217,
43    )
44
45    parser.add_argument(
46        "-v",
47        "--verbose",
48        dest="verbosity",
49        action="count",
50        help="print more diagnostic messages (option can be given multiple times)",
51        default=0,
52    )
53
54    parser.add_argument(
55        "--r0",
56        help="Use delays necessary for ESP32 revision 0 chips",
57        action="store_true",
58    )
59
60    args = parser.parse_args()
61
62    if args.verbosity > 3:
63        args.verbosity = 3
64    level = (logging.WARNING, logging.INFO, logging.DEBUG, logging.NOTSET)[
65        args.verbosity
66    ]
67    logging.basicConfig(level=logging.INFO)
68    # logging.getLogger('root').setLevel(logging.INFO)
69    logging.getLogger("rfc2217").setLevel(level)
70
71    # connect to serial port
72    ser = serial.serial_for_url(args.SERIALPORT, do_not_open=True, exclusive=True)
73    ser.timeout = 3  # required so that the reader thread can exit
74    # reset control line as no _remote_ "terminal" has been connected yet
75    ser.dtr = False
76    ser.rts = False
77
78    logging.info(" RFC 2217 TCP/IP to Serial redirector - type Ctrl-C / BREAK to quit")
79
80    try:
81        ser.open()
82    except serial.SerialException as e:
83        logging.error(" Could not open serial port {}: {}".format(ser.name, e))
84        sys.exit(1)
85
86    logging.info(" Serving serial port: {}".format(ser.name))
87    settings = ser.get_settings()
88
89    srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
90    srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
91    srv.bind(("", args.localport))
92    srv.listen(1)
93    logging.info(" TCP/IP port: {}".format(args.localport))
94    while True:
95        try:
96            client_socket, addr = srv.accept()
97            logging.info("Connected by {}:{}".format(addr[0], addr[1]))
98            client_socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
99            ser.rts = True
100            ser.dtr = True
101            # enter network <-> serial loop
102            r = Redirector(ser, client_socket, args.verbosity > 0, args.r0)
103            try:
104                r.shortcircuit()
105            finally:
106                logging.info("Disconnected")
107                r.stop()
108                client_socket.close()
109                ser.dtr = False
110                ser.rts = False
111                # Restore port settings (may have been changed by RFC 2217
112                # capable client)
113                ser.apply_settings(settings)
114        except KeyboardInterrupt:
115            sys.stdout.write("\n")
116            break
117        except socket.error as msg:
118            logging.error(str(msg))
119
120    logging.info("--- exit ---")
121
122
123if __name__ == "__main__":
124    main()
125