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