Skip to content

Commit

Permalink
feat(esptool): Add --retry-open-serial flag, config file entry and envar
Browse files Browse the repository at this point in the history
`esptool` frequently fails when trying to open the serial port of a device
which deep-sleeps often:

$ esptool.py --chip esp32s3 -p /dev/cu.usbmodem6101 [...] write_flash foo.bin
Serial port /dev/cu.usbmodem6101

A fatal error occurred: Could not open /dev/cu.usbmodem6101, the port is busy or doesn't exist.
([Errno 35] Could not exclusively lock port [...]: [Errno 35] Resource temporarily unavailable)

This makes developers add unnecessarily long sleeps when the main CPU is awake, in order to give
`esptool` the chance to find the serial port.

This PR adds a new flag `--retry-open-serial` (with corresponding env variable and cfg file entry)
which retries opening the port indefinitely until the device shows up:

$ esptool.py --chip esp32s3 -p /dev/cu.usbmodem6101 [...] write_flash --retry-open-serial foo.bin
Serial port /dev/cu.usbmodem6101
[Errno 35] Could not exclusively lock port [...]: [Errno 35] Resource temporarily unavailable
Retrying to open port .........................
Connecting....
Chip is ESP32-S3 (QFN56) (revision v0.2)
[...]
  • Loading branch information
2opremio committed Aug 25, 2024
1 parent b3022fa commit 20c2943
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 4 deletions.
2 changes: 2 additions & 0 deletions docs/en/esptool/configuration-file.rst
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@ Complete list configurable options:
+------------------------------+-----------------------------------------------------------+----------+
| custom_reset_sequence | Custom reset sequence for resetting into the bootloader | |
+------------------------------+-----------------------------------------------------------+----------+
| retry_open_serial | Retry opening the serial port indefinitely | False |
+------------------------------+-----------------------------------------------------------+----------+

Custom Reset Sequence
---------------------
Expand Down
69 changes: 65 additions & 4 deletions esptool/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import os
import shlex
import sys
import termios
import time
import traceback

Expand Down Expand Up @@ -66,7 +67,7 @@
write_mem,
)
from esptool.config import load_config_file
from esptool.loader import DEFAULT_CONNECT_ATTEMPTS, StubFlasher, ESPLoader, list_ports
from esptool.loader import DEFAULT_CONNECT_ATTEMPTS, StubFlasher, ESPLoader, list_ports, cfg
from esptool.targets import CHIP_DEFS, CHIP_LIST, ESP32ROM
from esptool.util import (
FatalError,
Expand All @@ -77,6 +78,9 @@

import serial

# Retry opening the serial port indefinitely
DEFAULT_RETRY_OPEN_SERIAL = cfg.getboolean("retry_open_serial", False)


def main(argv=None, esp=None):
"""
Expand Down Expand Up @@ -186,6 +190,16 @@ def main(argv=None, esp=None):
default=os.environ.get("ESPTOOL_CONNECT_ATTEMPTS", DEFAULT_CONNECT_ATTEMPTS),
)

parser.add_argument(
"--retry-open-serial",
help=(
"Retry opening the serial port indefinitely. "
"Default: %s" % DEFAULT_RETRY_OPEN_SERIAL
),
default=os.environ.get("ESPTOOL_RETRY_OPEN_SERIAL", DEFAULT_RETRY_OPEN_SERIAL),
action="store_true",
)

subparsers = parser.add_subparsers(
dest="operation", help="Run esptool.py {command} -h for additional help"
)
Expand Down Expand Up @@ -768,6 +782,7 @@ def add_spi_flash_subparsers(
port=args.port,
connect_attempts=args.connect_attempts,
initial_baud=initial_baud,
retry_open_serial=args.retry_open_serial,
chip=args.chip,
trace=args.trace,
before=args.before,
Expand Down Expand Up @@ -1092,11 +1107,50 @@ def expand_file_arguments(argv):
return argv


def get_default_specific_connected_device(
chip, each_port, initial_baud, trace, before, connect_attempts, retry_open_serial
):
retry_attempts = 0
_esp = None
while True:
try:
chip_class = CHIP_DEFS[chip]
_esp = chip_class(each_port, initial_baud, trace)
_esp.connect(before, connect_attempts)
if retry_attempts > 0:
# break the retrying line
print("")
return _esp
except (
FatalError,
serial.serialutil.SerialException,
IOError,
OSError,
termios.error,
) as e:
if not retry_open_serial:
raise
if _esp and _esp._port:
_esp._port.close()
_esp = None
if retry_attempts == 0:
print(e)
print("Retrying failed connection ", end="", flush=True)
else:
if retry_attempts % 9 == 0:
# print a dot every second
print(".", end="", flush=True)
time.sleep(0.1)
retry_attempts += 1
continue


def get_default_connected_device(
serial_list,
port,
connect_attempts,
initial_baud,
retry_open_serial=False,
chip="auto",
trace=False,
before="default_reset",
Expand All @@ -1110,9 +1164,15 @@ def get_default_connected_device(
each_port, initial_baud, before, trace, connect_attempts
)
else:
chip_class = CHIP_DEFS[chip]
_esp = chip_class(each_port, initial_baud, trace)
_esp.connect(before, connect_attempts)
_esp = get_default_specific_connected_device(
chip,
each_port,
initial_baud,
trace,
before,
connect_attempts,
retry_open_serial,
)
break
except (FatalError, OSError) as err:
if port is not None:
Expand Down Expand Up @@ -1229,6 +1289,7 @@ def _main():
try:
main()
except FatalError as e:
print(traceback.format_exc())
print(f"\nA fatal error occurred: {e}")
sys.exit(2)
except serial.serialutil.SerialException as e:
Expand Down
1 change: 1 addition & 0 deletions esptool/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"write_block_attempts",
"reset_delay",
"custom_reset_sequence",
"retry_open_serial",
]


Expand Down

0 comments on commit 20c2943

Please sign in to comment.