Skip to content

Commit 04045d6

Browse files
2opremiopeterdragun
authored andcommitted
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 espressif#995
1 parent b3022fa commit 04045d6

File tree

5 files changed

+103
-2
lines changed

5 files changed

+103
-2
lines changed

docs/en/esptool/advanced-options.rst

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,27 @@ The ``--after`` argument allows you to specify whether the chip should be reset
3636
* ``--after no_reset`` leaves the chip in the serial bootloader, no reset is performed.
3737
* ``--after no_reset_stub`` leaves the chip in the stub bootloader, no reset is performed.
3838

39+
40+
Connect Loop
41+
------------
42+
43+
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.
44+
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.
45+
There are 3 possible values for this option:
46+
47+
.. list::
48+
49+
* ``0`` will keep trying to connect to the chip indefinitely
50+
* ``1`` will try to connect to the chip only once (default)
51+
* ``N`` will try to connect to the chip N times
52+
53+
54+
.. note::
55+
56+
This option is only available if both the ``--port`` and ``--chip`` arguments are set.
57+
58+
59+
3960
.. _disable_stub:
4061

4162
Disabling the Stub Loader

docs/en/esptool/configuration-file.rst

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ Sample configuration file:
7878
Options
7979
-------
8080

81-
Complete list configurable options:
81+
Complete list of configurable options:
8282

8383
+------------------------------+-----------------------------------------------------------+----------+
8484
| Option | Description | Default |
@@ -107,6 +107,8 @@ Complete list configurable options:
107107
+------------------------------+-----------------------------------------------------------+----------+
108108
| reset_delay | Time to wait before the boot pin is released after reset | 0.05 s |
109109
+------------------------------+-----------------------------------------------------------+----------+
110+
| open_port_attempts | Number of attempts to open the port (0 - infinite) | 1 |
111+
+------------------------------+-----------------------------------------------------------+----------+
110112
| custom_reset_sequence | Custom reset sequence for resetting into the bootloader | |
111113
+------------------------------+-----------------------------------------------------------+----------+
112114

esptool/__init__.py

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,14 +66,21 @@
6666
write_mem,
6767
)
6868
from esptool.config import load_config_file
69-
from esptool.loader import DEFAULT_CONNECT_ATTEMPTS, StubFlasher, ESPLoader, list_ports
69+
from esptool.loader import (
70+
DEFAULT_CONNECT_ATTEMPTS,
71+
DEFAULT_OPEN_PORT_ATTEMPTS,
72+
StubFlasher,
73+
ESPLoader,
74+
list_ports,
75+
)
7076
from esptool.targets import CHIP_DEFS, CHIP_LIST, ESP32ROM
7177
from esptool.util import (
7278
FatalError,
7379
NotImplementedInROMError,
7480
flash_size_bytes,
7581
strip_chip_name,
7682
)
83+
from itertools import chain, cycle, repeat
7784

7885
import serial
7986

@@ -763,6 +770,27 @@ def add_spi_flash_subparsers(
763770
print("Found %d serial ports" % len(ser_list))
764771
else:
765772
ser_list = [args.port]
773+
open_port_attempts = os.environ.get(
774+
"ESPTOOL_OPEN_PORT_ATTEMPTS", DEFAULT_OPEN_PORT_ATTEMPTS
775+
)
776+
try:
777+
open_port_attempts = int(open_port_attempts)
778+
except ValueError:
779+
raise SystemExit("Invalid value for ESPTOOL_OPEN_PORT_ATTEMPTS")
780+
if open_port_attempts != 1:
781+
if args.port is None or args.chip == "auto":
782+
print(
783+
"WARNING: The ESPTOOL_OPEN_PORT_ATTEMPTS (open_port_attempts) option can only be used with --port and --chip arguments."
784+
)
785+
else:
786+
esp = esp or connect_loop(
787+
args.port,
788+
initial_baud,
789+
args.chip,
790+
open_port_attempts,
791+
args.trace,
792+
args.before,
793+
)
766794
esp = esp or get_default_connected_device(
767795
ser_list,
768796
port=args.port,
@@ -1092,6 +1120,53 @@ def expand_file_arguments(argv):
10921120
return argv
10931121

10941122

1123+
def connect_loop(
1124+
port: str,
1125+
initial_baud: int,
1126+
chip: str,
1127+
max_retries: int,
1128+
trace: bool = False,
1129+
before: str = "default_reset",
1130+
):
1131+
chip_class = CHIP_DEFS[chip]
1132+
esp = None
1133+
print(f"Serial port {port}")
1134+
1135+
first = True
1136+
ten_cycle = cycle(chain(repeat(False, 9), (True,)))
1137+
retry_loop = chain(
1138+
repeat(False, max_retries - 1), (True,) if max_retries else cycle((False,))
1139+
)
1140+
1141+
for last, every_tenth in zip(retry_loop, ten_cycle):
1142+
try:
1143+
esp = chip_class(port, initial_baud, trace)
1144+
if not first:
1145+
# break the retrying line
1146+
print("")
1147+
esp.connect(before)
1148+
return esp
1149+
except (
1150+
FatalError,
1151+
serial.serialutil.SerialException,
1152+
IOError,
1153+
OSError,
1154+
) as err:
1155+
if esp and esp._port:
1156+
esp._port.close()
1157+
esp = None
1158+
if first:
1159+
print(err)
1160+
print("Retrying failed connection", end="", flush=True)
1161+
first = False
1162+
if last:
1163+
raise err
1164+
if every_tenth:
1165+
# print a dot every second
1166+
print(".", end="", flush=True)
1167+
time.sleep(0.1)
1168+
1169+
10951170
def get_default_connected_device(
10961171
serial_list,
10971172
port,

esptool/config.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"connect_attempts",
2020
"write_block_attempts",
2121
"reset_delay",
22+
"open_port_attempts",
2223
"custom_reset_sequence",
2324
]
2425

esptool/loader.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,8 @@
9898
DEFAULT_CONNECT_ATTEMPTS = cfg.getint("connect_attempts", 7)
9999
# Number of times to try writing a data block
100100
WRITE_BLOCK_ATTEMPTS = cfg.getint("write_block_attempts", 3)
101+
# Number of times to try opening the serial port
102+
DEFAULT_OPEN_PORT_ATTEMPTS = cfg.getint("open_port_attempts", 1)
101103

102104

103105
def timeout_per_mb(seconds_per_mb, size_bytes):

0 commit comments

Comments
 (0)