1# SPDX-FileCopyrightText: 2014-2024 Fredrik Ahlberg, Angus Gratton,
2# Espressif Systems (Shanghai) CO LTD, other contributors as noted.
3#
4# SPDX-License-Identifier: BSD-3-Clause
5import threading
6import time
7import logging
8import socket
9
10from esp_rfc2217_server.esp_port_manager import EspPortManager
11
12
13class Redirector(object):
14    def __init__(self, serial_instance, socket, debug=False, esp32r0delay=False):
15        self.serial = serial_instance
16        self.socket = socket
17        self._write_lock = threading.Lock()
18        self.rfc2217 = EspPortManager(
19            self.serial,
20            self,
21            esp32r0delay,
22            logger=logging.getLogger("rfc2217.server") if debug else None,
23        )
24        self.log = logging.getLogger("redirector")
25        self.force_exit = False
26
27    def statusline_poller(self):
28        self.log.debug("status line poll thread started")
29        while self.alive:
30            time.sleep(1)
31            self.rfc2217.check_modem_lines()
32        self.log.debug("status line poll thread terminated")
33
34    def shortcircuit(self):
35        """connect the serial port to the TCP port by copying everything
36        from one side to the other"""
37        self.alive = True
38        self.thread_read = threading.Thread(target=self.reader)
39        self.thread_read.daemon = True
40        self.thread_read.name = "serial->socket"
41        self.thread_read.start()
42        self.thread_poll = threading.Thread(target=self.statusline_poller)
43        self.thread_poll.daemon = True
44        self.thread_poll.name = "status line poll"
45        self.thread_poll.start()
46        self.writer()
47
48    def reader(self):
49        """loop forever and copy serial->socket"""
50        self.log.debug("reader thread started")
51        while self.alive:
52            try:
53                data = self.serial.read(self.serial.in_waiting or 1)
54                if data:
55                    # escape outgoing data when needed (Telnet IAC (0xff) character)
56                    self.write(b"".join(self.rfc2217.escape(data)))
57            except socket.error as msg:
58                self.log.error("{}".format(msg))
59                # probably got disconnected
60                break
61        self.alive = False
62        self.log.debug("reader thread terminated")
63
64    def write(self, data):
65        """thread safe socket write with no data escaping. used to send telnet stuff"""
66        with self._write_lock:
67            self.socket.sendall(data)
68
69    def writer(self):
70        """loop forever and copy socket->serial"""
71        while self.alive:
72            try:
73                data = self.socket.recv(1024)
74                if not data:
75                    break
76                self.serial.write(b"".join(self.rfc2217.filter(data)))
77            except socket.error as msg:
78                self.log.error("{}".format(msg))
79                # probably got disconnected
80                break
81        self.stop()
82
83    def stop(self):
84        """Stop copying"""
85        self.log.debug("stopping")
86        if self.alive:
87            self.alive = False
88            self.thread_read.join()
89            self.thread_poll.join()
90