Skip to content

Commit

Permalink
feat(esp-idf-monitor): Add --open-port-attempts flag
Browse files Browse the repository at this point in the history
esp-idf-monitor frequently fails when trying to open the serial port of a device
which deep-sleeps often:

$ idf.py monitor -p /dev/cu.usbmodem6101
--- esp-idf-monitor 1.4.0 on /dev/cu.usbmodem6101 115200 ---
--- Quit: Ctrl+] | Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H ---
[Errno 2] could not open port: [...] No such file or directory: '/dev/cu.usbmodem6101'
Connection to /dev/cu.usbmodem6101 failed. Available ports:
/dev/cu.usbmodemF554393F21402

This commit adds a new flag `--open-port-attempts` which retries opening the port indefinitely
until the device shows up:

$ idf.py monitor -p /dev/cu.usbmodem6101 --open-port-attempts 0
--- esp-idf-monitor 1.4.0 on /dev/cu.usbmodem6101 115200 ---
--- Quit: Ctrl+] | Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H ---
[Errno 2] could not open port: [...] No such file or directory: '/dev/cu.usbmodem6101'
Retrying to open port ......
ESP-ROM:esp32s3-20210327

Also, add option to reconfigure reopening retry period.
This will greatly increase the chance of catching more logs in devices which deep-sleep.

Closes #15
  • Loading branch information
2opremio authored and peterdragun committed Sep 2, 2024
1 parent 4535e7f commit c34c83d
Show file tree
Hide file tree
Showing 5 changed files with 53 additions and 26 deletions.
10 changes: 9 additions & 1 deletion esp_idf_monitor/base/argument_parser.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD
# SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0

import argparse
Expand Down Expand Up @@ -139,4 +139,12 @@ def get_parser(): # type: () -> argparse.ArgumentParser
default=False,
action='store_true')

parser.add_argument(
'--open-port-attempts',
help='Number of attempts to wait for the port to appear (useful if the device is not connected or in deep sleep).'
'Use 0 for infinite attempts.',
default=1,
type=int
)

return parser
7 changes: 5 additions & 2 deletions esp_idf_monitor/base/constants.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD
# SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0

from esp_idf_monitor.base.key_config import cfg

# Control-key characters
CTRL_C = '\x03'
CTRL_H = '\x08'
Expand Down Expand Up @@ -45,6 +47,7 @@
EVENT_QUEUE_TIMEOUT = 0.03 # timeout before raising queue.Empty exception in case of empty event queue

ESPPORT_ENVIRON = str('ESPPORT')
ESPTOOL_OPEN_PORT_ATTEMPTS_ENVIRON = str('ESPTOOL_OPEN_PORT_ATTEMPTS')
MAKEFLAGS_ENVIRON = 'MAKEFLAGS'

GDB_UART_CONTINUE_COMMAND = '+$c#63'
Expand All @@ -55,7 +58,7 @@
LAST_LINE_THREAD_INTERVAL = 0.1

MINIMAL_EN_LOW_DELAY = 0.005
RECONNECT_DELAY = 0.5 # timeout between reconnect tries
RECONNECT_DELAY = cfg.getint('reconnect_delay', 0.5) # timeout between reconnect tries
CHECK_ALIVE_FLAG_TIMEOUT = 0.25 # timeout for checking alive flags (currently used by serial reader)

# closing wait timeout for serial port
Expand Down
50 changes: 31 additions & 19 deletions esp_idf_monitor/base/serial_reader.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
# SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0

import queue # noqa: F401
Expand All @@ -25,14 +25,15 @@ class SerialReader(Reader):
event queue, until stopped.
"""

def __init__(self, serial_instance, event_queue, reset, target):
# type: (serial.Serial, queue.Queue, bool, str) -> None
def __init__(self, serial_instance, event_queue, reset, open_port_attempts, target):
# type: (serial.Serial, queue.Queue, bool, int, str) -> None
super(SerialReader, self).__init__()
self.baud = serial_instance.baudrate
self.serial = serial_instance
self.event_queue = event_queue
self.gdb_exit = False
self.reset = reset
self.open_port_attempts = open_port_attempts
self.reset_strategy = Reset(serial_instance, target)
if not hasattr(self.serial, 'cancel_read'):
# enable timeout for checking alive flag,
Expand All @@ -46,40 +47,51 @@ def run(self):
try:
# We can come to this thread at startup or from external application line GDB.
# If we come from GDB we would like to continue to run without reset.
self.open_serial(reset=not self.gdb_exit and self.reset)
self.reset = not self.gdb_exit and self.reset
self.open_serial(reset=self.reset)
# Successfully connected, so any further reconnections should occur without a reset.
self.reset = False
except serial.SerialException as e:
print(e)
# if connection to port fails suggest other available ports
port_list = '\n'.join(
[
p.device
for p in list_ports.comports()
if not p.device.endswith(FILTERED_PORTS)
]
)
yellow_print(f'Connection to {self.serial.portstr} failed. Available ports:\n{port_list}')
return
if self.open_port_attempts == 1:
# If the connection to the port fails and --open-port-attempts was not specified,
# recommend other available ports and exit.
port_list = '\n'.join(
[
p.device
for p in list_ports.comports()
if not p.device.endswith(FILTERED_PORTS)
]
)
yellow_print(f'Connection to {self.serial.portstr} failed. Available ports:\n{port_list}')
return
self.gdb_exit = False
try:
while self.alive:
try:
data = self.serial.read(self.serial.in_waiting or 1)
except (serial.SerialException, IOError) as e:
if self.serial.is_open:
# in_waiting assumes the port is already open
data = self.serial.read(self.serial.in_waiting or 1)
else:
raise serial.PortNotOpenError
except (serial.SerialException, IOError, OSError) as e:
data = b''
# self.serial.open() was successful before, therefore, this is an issue related to
# the disappearance of the device
red_print(e.strerror)
red_print(str(e))
yellow_print('Waiting for the device to reconnect', newline='')
self.close_serial()
while self.alive: # so that exiting monitor works while waiting
try:
time.sleep(RECONNECT_DELAY)
# reset on reconnect can be unexpected for wakeup from deepsleep using JTAG
self.open_serial(reset=False)
self.open_serial(reset=self.reset)
self.reset = False
break # device connected
except serial.SerialException:
except (serial.SerialException, IOError, OSError):
yellow_print('.', newline='')
sys.stderr.flush()

yellow_print('') # go to new line
if data:
self.event_queue.put((TAG_SERIAL, data), False)
Expand Down
3 changes: 2 additions & 1 deletion esp_idf_monitor/config.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD,
# SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD,
# other contributors as noted.
#
# SPDX-License-Identifier: Apache-2.0
Expand All @@ -20,6 +20,7 @@
'chip_reset_bootloader_key',
'exit_menu_key',
'skip_menu_key',
'reconnect_delay',
'custom_reset_sequence', # from esptool config
]

Expand Down
9 changes: 6 additions & 3 deletions esp_idf_monitor/idf_monitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
# - If core dump output is detected, it is converted to a human-readable report
# by espcoredump.py.
#
# SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD
# SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
#
# Contains elements taken from miniterm "Very simple serial terminal" which
Expand Down Expand Up @@ -44,6 +44,7 @@
DEFAULT_TARGET_RESET,
DEFAULT_TOOLCHAIN_PREFIX,
ESPPORT_ENVIRON,
ESPTOOL_OPEN_PORT_ATTEMPTS_ENVIRON,
EVENT_QUEUE_TIMEOUT,
FILTERED_PORTS, GDB_EXIT_TIMEOUT,
GDB_UART_CONTINUE_COMMAND,
Expand Down Expand Up @@ -90,6 +91,7 @@ def __init__(
make='make', # type: str
encrypted=False, # type: bool
reset=DEFAULT_TARGET_RESET, # type: bool
open_port_attempts=1, # type: int
toolchain_prefix=DEFAULT_TOOLCHAIN_PREFIX, # type: str
eol='CRLF', # type: str
decode_coredumps=COREDUMP_DECODE_INFO, # type: str
Expand Down Expand Up @@ -128,7 +130,7 @@ def __init__(
# testing hook: when running tests, input from console is ignored
socket_test_mode = os.environ.get('ESP_IDF_MONITOR_TEST') == '1'
self.serial = serial_instance
self.serial_reader = SerialReader(self.serial, self.event_queue, reset, target) # type: Reader
self.serial_reader = SerialReader(self.serial, self.event_queue, reset, open_port_attempts, target) # type: Reader

self.gdb_helper = GDBHelper(toolchain_prefix, websocket_client, self.elf_file, self.serial.port,
self.serial.baudrate) if self.elf_exists else None
Expand Down Expand Up @@ -403,7 +405,7 @@ def main() -> None:
# has a check for this).
# To make sure the key as well as the value are str type, by the requirements of subprocess
espport_val = str(args.port)
os.environ.update({ESPPORT_ENVIRON: espport_val})
os.environ.update({ESPPORT_ENVIRON: espport_val, ESPTOOL_OPEN_PORT_ATTEMPTS_ENVIRON: str(args.open_port_attempts)})

cls = SerialMonitor
yellow_print('--- esp-idf-monitor {v} on {p.name} {p.baudrate} ---'.format(v=__version__, p=serial_instance))
Expand All @@ -414,6 +416,7 @@ def main() -> None:
args.make,
args.encrypted,
not args.no_reset,
args.open_port_attempts,
args.toolchain_prefix,
args.eol,
args.decode_coredumps,
Expand Down

0 comments on commit c34c83d

Please sign in to comment.