mirror of
https://github.com/espressif/esptool.git
synced 2025-10-16 23:06:31 +08:00
feat(esptool): Add option to retry connection in a loop
`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 env variable `DEFAULT_OPEN_PORT_ATTEMPTS` and cfg file entry `retry_open_serial` which attempts to open the port selected number of times (optionaly indefinitely) until the device shows up: $ export DEFAULT_OPEN_PORT_ATTEMPTS=1 $ esptool.py --chip esp32s3 -p /dev/cu.usbmodem6101 [...] write_flash 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) [...] Closes https://github.com/espressif/esptool/pull/995
This commit is contained in:

committed by
Peter Dragun

parent
b3022fabf7
commit
04045d6dcb
@@ -36,6 +36,27 @@ The ``--after`` argument allows you to specify whether the chip should be reset
|
||||
* ``--after no_reset`` leaves the chip in the serial bootloader, no reset is performed.
|
||||
* ``--after no_reset_stub`` leaves the chip in the stub bootloader, no reset is performed.
|
||||
|
||||
|
||||
Connect Loop
|
||||
------------
|
||||
|
||||
Esptool supports connection loops, where the user can specify how many times to try to open a port. The delay between retries is 0.1 seconds. This can be useful for example when the chip is in deep sleep or esptool was started before the chip was connected to the PC. A connection loop can be created by setting the ``ESPTOOL_OPEN_PORT_ATTEMPTS`` environment variable.
|
||||
This feature can also be enabled by using the ``open_port_attempts`` configuration option, for more details regarding config options see :ref:`Configuration file <config>` section.
|
||||
There are 3 possible values for this option:
|
||||
|
||||
.. list::
|
||||
|
||||
* ``0`` will keep trying to connect to the chip indefinitely
|
||||
* ``1`` will try to connect to the chip only once (default)
|
||||
* ``N`` will try to connect to the chip N times
|
||||
|
||||
|
||||
.. note::
|
||||
|
||||
This option is only available if both the ``--port`` and ``--chip`` arguments are set.
|
||||
|
||||
|
||||
|
||||
.. _disable_stub:
|
||||
|
||||
Disabling the Stub Loader
|
||||
|
@@ -78,7 +78,7 @@ Sample configuration file:
|
||||
Options
|
||||
-------
|
||||
|
||||
Complete list configurable options:
|
||||
Complete list of configurable options:
|
||||
|
||||
+------------------------------+-----------------------------------------------------------+----------+
|
||||
| Option | Description | Default |
|
||||
@@ -107,6 +107,8 @@ Complete list configurable options:
|
||||
+------------------------------+-----------------------------------------------------------+----------+
|
||||
| reset_delay | Time to wait before the boot pin is released after reset | 0.05 s |
|
||||
+------------------------------+-----------------------------------------------------------+----------+
|
||||
| open_port_attempts | Number of attempts to open the port (0 - infinite) | 1 |
|
||||
+------------------------------+-----------------------------------------------------------+----------+
|
||||
| custom_reset_sequence | Custom reset sequence for resetting into the bootloader | |
|
||||
+------------------------------+-----------------------------------------------------------+----------+
|
||||
|
||||
|
@@ -66,7 +66,13 @@ from esptool.cmds import (
|
||||
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,
|
||||
DEFAULT_OPEN_PORT_ATTEMPTS,
|
||||
StubFlasher,
|
||||
ESPLoader,
|
||||
list_ports,
|
||||
)
|
||||
from esptool.targets import CHIP_DEFS, CHIP_LIST, ESP32ROM
|
||||
from esptool.util import (
|
||||
FatalError,
|
||||
@@ -74,6 +80,7 @@ from esptool.util import (
|
||||
flash_size_bytes,
|
||||
strip_chip_name,
|
||||
)
|
||||
from itertools import chain, cycle, repeat
|
||||
|
||||
import serial
|
||||
|
||||
@@ -763,6 +770,27 @@ def main(argv=None, esp=None):
|
||||
print("Found %d serial ports" % len(ser_list))
|
||||
else:
|
||||
ser_list = [args.port]
|
||||
open_port_attempts = os.environ.get(
|
||||
"ESPTOOL_OPEN_PORT_ATTEMPTS", DEFAULT_OPEN_PORT_ATTEMPTS
|
||||
)
|
||||
try:
|
||||
open_port_attempts = int(open_port_attempts)
|
||||
except ValueError:
|
||||
raise SystemExit("Invalid value for ESPTOOL_OPEN_PORT_ATTEMPTS")
|
||||
if open_port_attempts != 1:
|
||||
if args.port is None or args.chip == "auto":
|
||||
print(
|
||||
"WARNING: The ESPTOOL_OPEN_PORT_ATTEMPTS (open_port_attempts) option can only be used with --port and --chip arguments."
|
||||
)
|
||||
else:
|
||||
esp = esp or connect_loop(
|
||||
args.port,
|
||||
initial_baud,
|
||||
args.chip,
|
||||
open_port_attempts,
|
||||
args.trace,
|
||||
args.before,
|
||||
)
|
||||
esp = esp or get_default_connected_device(
|
||||
ser_list,
|
||||
port=args.port,
|
||||
@@ -1092,6 +1120,53 @@ def expand_file_arguments(argv):
|
||||
return argv
|
||||
|
||||
|
||||
def connect_loop(
|
||||
port: str,
|
||||
initial_baud: int,
|
||||
chip: str,
|
||||
max_retries: int,
|
||||
trace: bool = False,
|
||||
before: str = "default_reset",
|
||||
):
|
||||
chip_class = CHIP_DEFS[chip]
|
||||
esp = None
|
||||
print(f"Serial port {port}")
|
||||
|
||||
first = True
|
||||
ten_cycle = cycle(chain(repeat(False, 9), (True,)))
|
||||
retry_loop = chain(
|
||||
repeat(False, max_retries - 1), (True,) if max_retries else cycle((False,))
|
||||
)
|
||||
|
||||
for last, every_tenth in zip(retry_loop, ten_cycle):
|
||||
try:
|
||||
esp = chip_class(port, initial_baud, trace)
|
||||
if not first:
|
||||
# break the retrying line
|
||||
print("")
|
||||
esp.connect(before)
|
||||
return esp
|
||||
except (
|
||||
FatalError,
|
||||
serial.serialutil.SerialException,
|
||||
IOError,
|
||||
OSError,
|
||||
) as err:
|
||||
if esp and esp._port:
|
||||
esp._port.close()
|
||||
esp = None
|
||||
if first:
|
||||
print(err)
|
||||
print("Retrying failed connection", end="", flush=True)
|
||||
first = False
|
||||
if last:
|
||||
raise err
|
||||
if every_tenth:
|
||||
# print a dot every second
|
||||
print(".", end="", flush=True)
|
||||
time.sleep(0.1)
|
||||
|
||||
|
||||
def get_default_connected_device(
|
||||
serial_list,
|
||||
port,
|
||||
|
@@ -19,6 +19,7 @@ CONFIG_OPTIONS = [
|
||||
"connect_attempts",
|
||||
"write_block_attempts",
|
||||
"reset_delay",
|
||||
"open_port_attempts",
|
||||
"custom_reset_sequence",
|
||||
]
|
||||
|
||||
|
@@ -98,6 +98,8 @@ DEFAULT_SERIAL_WRITE_TIMEOUT = cfg.getfloat("serial_write_timeout", 10)
|
||||
DEFAULT_CONNECT_ATTEMPTS = cfg.getint("connect_attempts", 7)
|
||||
# Number of times to try writing a data block
|
||||
WRITE_BLOCK_ATTEMPTS = cfg.getint("write_block_attempts", 3)
|
||||
# Number of times to try opening the serial port
|
||||
DEFAULT_OPEN_PORT_ATTEMPTS = cfg.getint("open_port_attempts", 1)
|
||||
|
||||
|
||||
def timeout_per_mb(seconds_per_mb, size_bytes):
|
||||
|
Reference in New Issue
Block a user