1# SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
2# SPDX-License-Identifier: Apache-2.0
3
4import queue
5import subprocess
6import sys
7import time
8
9import serial
10
11from .constants import CHECK_ALIVE_FLAG_TIMEOUT, MINIMAL_EN_LOW_DELAY, RECONNECT_DELAY, TAG_SERIAL
12from .output_helpers import red_print, yellow_print
13from .stoppable_thread import StoppableThread
14
15
16class Reader(StoppableThread):
17    """ Output Reader base class """
18
19
20class SerialReader(Reader):
21    """ Read serial data from the serial port and push to the
22    event queue, until stopped.
23    """
24
25    def __init__(self, serial_instance, event_queue):
26        #  type: (serial.Serial, queue.Queue) -> None
27        super(SerialReader, self).__init__()
28        self.baud = serial_instance.baudrate
29        self.serial = serial_instance
30        self.event_queue = event_queue
31        self.gdb_exit = False
32        if not hasattr(self.serial, 'cancel_read'):
33            # enable timeout for checking alive flag,
34            # if cancel_read not available
35            self.serial.timeout = CHECK_ALIVE_FLAG_TIMEOUT
36
37    def run(self):
38        #  type: () -> None
39        if not self.serial.is_open:
40            self.serial.baudrate = self.baud
41            # We can come to this thread at startup or from external application line GDB.
42            # If we come from GDB we would like to continue to run without reset.
43
44            high = False
45            low = True
46
47            self.serial.dtr = low      # Non reset state
48            self.serial.rts = high     # IO0=HIGH
49            self.serial.dtr = self.serial.dtr   # usbser.sys workaround
50            # Current state not reset the target!
51            self.serial.open()
52            if not self.gdb_exit:
53                self.serial.dtr = high     # Set dtr to reset state (affected by rts)
54                self.serial.rts = low      # Set rts/dtr to the reset state
55                self.serial.dtr = self.serial.dtr   # usbser.sys workaround
56
57                # Add a delay to meet the requirements of minimal EN low time (2ms for ESP32-C3)
58                time.sleep(MINIMAL_EN_LOW_DELAY)
59            self.gdb_exit = False
60            self.serial.rts = high             # Set rts/dtr to the working state
61            self.serial.dtr = self.serial.dtr   # usbser.sys workaround
62        try:
63            while self.alive:
64                try:
65                    data = self.serial.read(self.serial.in_waiting or 1)
66                except (serial.serialutil.SerialException, IOError) as e:
67                    data = b''
68                    # self.serial.open() was successful before, therefore, this is an issue related to
69                    # the disappearance of the device
70                    red_print(e)
71                    yellow_print('Waiting for the device to reconnect', newline='')
72                    self.serial.close()
73                    while self.alive:  # so that exiting monitor works while waiting
74                        try:
75                            time.sleep(RECONNECT_DELAY)
76                            self.serial.open()
77                            break  # device connected
78                        except serial.serialutil.SerialException:
79                            yellow_print('.', newline='')
80                            sys.stderr.flush()
81                    yellow_print('')  # go to new line
82                if data:
83                    self.event_queue.put((TAG_SERIAL, data), False)
84        finally:
85            self.serial.close()
86
87    def _cancel(self):
88        #  type: () -> None
89        if hasattr(self.serial, 'cancel_read'):
90            try:
91                self.serial.cancel_read()
92            except Exception:  # noqa
93                pass
94
95
96class LinuxReader(Reader):
97    """ Read data from the subprocess that runs runnable and push to the
98    event queue, until stopped.
99    """
100
101    def __init__(self, process, event_queue):
102        #  type: (subprocess.Popen, queue.Queue) -> None
103        super().__init__()
104        self.proc = process
105        self.event_queue = event_queue
106
107        self._stdout = iter(self.proc.stdout.readline, b'')  # type: ignore
108
109    def run(self):  # type: () -> None
110        try:
111            while self.alive:
112                for line in self._stdout:
113                    if line:
114                        self.event_queue.put((TAG_SERIAL, line), False)
115        finally:
116            self.proc.terminate()
117
118    def _cancel(self):  # type: () -> None
119        self.proc.terminate()
120