feat(bootloader reset): Allow custom reset strategy setting with a config file

This commit is contained in:
radim.karnis
2023-01-06 17:15:31 +01:00
parent 3ad680a59d
commit a8586d02b1
5 changed files with 115 additions and 1 deletions

View File

@@ -107,3 +107,42 @@ Complete list configurable options:
+------------------------------+-----------------------------------------------------------+----------+
| reset_delay | Time to wait before the boot pin is released after reset | 0.05 s |
+------------------------------+-----------------------------------------------------------+----------+
| custom_reset_sequence | Custom reset sequence for resetting into the bootloader | |
+------------------------------+-----------------------------------------------------------+----------+
Custom Reset Sequence
---------------------
The ``custom_reset_sequence`` configuration option allows you to define a reset sequence which will get
used when an :ref:`automatic reset into the serial bootloader <automatic-bootloader>` is performed.
The sequence is defined with a string in the following format:
- Consists of individual commands divided by ``|`` (e.g. ``R0|D1|W0.5``).
- Commands (e.g. ``R0``) are defined by a code (``R``) and an argument (``0``).
+------+-----------------------------------------------------------+-----------------+
| Code | Action | Argument |
+======+===========================================================+=================+
| D | Set DTR control line | ``1``/``0`` |
+------+-----------------------------------------------------------+-----------------+
| R | Set RTS control line | ``1``/``0`` |
+------+-----------------------------------------------------------+-----------------+
| U | Set DTR and RTS control lines at the same time | ``0,0``/``0,1`` |
| | (Unix-like systems only) | ``1,0``/``1,1`` |
+------+-----------------------------------------------------------+-----------------+
| W | Wait for ``N`` seconds (where ``N`` is a float) | ``N`` |
+------+-----------------------------------------------------------+-----------------+
For example: ``D0|R1|W0.1|D1|R0|W0.5|D0`` represents the following classic reset sequence:
.. code-block:: python
_setDTR(False) # IO0=HIGH
_setRTS(True) # EN=LOW, chip in reset
time.sleep(0.1)
_setDTR(True) # IO0=LOW
_setRTS(False) # EN=HIGH, chip out of reset
time.sleep(0.05)
_setDTR(False) # IO0=HIGH, done

View File

@@ -19,6 +19,7 @@ CONFIG_OPTIONS = [
"connect_attempts",
"write_block_attempts",
"reset_delay",
"custom_reset_sequence",
]

View File

@@ -17,6 +17,7 @@ import time
from .config import load_config_file
from .reset import (
ClassicReset,
CustomReset,
DEFAULT_RESET_DELAY,
HardReset,
USBJTAGSerialReset,
@@ -570,7 +571,9 @@ class ESPLoader(object):
used ESP chip, external settings, and environment variables.
Returns a tuple of one or more reset strategies to be tried sequentially.
"""
# TODO: If config file defines custom reset sequence, parse it and return it
cfg_custom_reset_sequence = cfg.get("custom_reset_sequence")
if cfg_custom_reset_sequence is not None:
return (CustomReset(self._port, cfg_custom_reset_sequence),)
cfg_reset_delay = cfg.getfloat("reset_delay")
if cfg_reset_delay is not None:

View File

@@ -7,6 +7,8 @@ import os
import struct
import time
from .util import FatalError
# Used for resetting into bootloader on Unix-like systems
if os.name != "nt":
import fcntl
@@ -130,3 +132,47 @@ class HardReset(ResetStrategy):
else:
time.sleep(0.1)
self._setRTS(False)
class CustomReset(ResetStrategy):
"""
Custom reset strategy defined with a string.
CustomReset object is created as "rst = CustomReset(port, seq_str)"
and can be later executed simply with "rst()"
The seq_str input string consists of individual commands divided by "|".
Commands (e.g. R0) are defined by a code (R) and an argument (0).
The commands are:
D: setDTR - 1=True / 0=False
R: setRTS - 1=True / 0=False
U: setDTRandRTS (Unix-only) - 0,0 / 0,1 / 1,0 / or 1,1
W: Wait (time delay) - positive float number
e.g.
"D0|R1|W0.1|D1|R0|W0.05|D0" represents the ClassicReset strategy
"U1,1|U0,1|W0.1|U1,0|W0.05|U0,0" represents the UnixTightReset strategy
"""
format_dict = {
"D": "self.port.setDTR({})",
"R": "self.port.setRTS({})",
"W": "time.sleep({})",
"U": "self._setDTRandRTS({})",
}
def __call__(self):
exec(self.constructed_strategy)
def __init__(self, port, seq_str):
super().__init__(port)
self.constructed_strategy = self._parse_string_to_seq(seq_str)
def _parse_string_to_seq(self, seq_str):
try:
cmds = seq_str.split("|")
fn_calls_list = [self.format_dict[cmd[0]].format(cmd[1:]) for cmd in cmds]
except Exception as e:
raise FatalError(f'Invalid "custom_reset_sequence" option format: {e}')
return "\n".join(fn_calls_list)

View File

@@ -1124,3 +1124,28 @@ class TestConfigFile(EsptoolTestCase):
os.environ["ESPTOOL_CFGFILE"] = tmp
else:
os.environ.pop("ESPTOOL_CFGFILE", None)
def test_custom_reset_sequence(self):
# This reset sequence will fail to reset the chip to bootloader,
# the flash_id operation should therefore fail.
# Also tests the number of connection attempts.
reset_seq_config = (
"[esptool]\n"
"custom_reset_sequence = D0|W0.1|R1|R0|W0.1|R1|R0\n"
"connect_attempts = 1\n"
)
config_file_path = os.path.join(os.getcwd(), "esptool.cfg")
with self.ConfigFile(config_file_path, reset_seq_config):
output = self.run_esptool_error("flash_id")
assert f"Loaded custom configuration from {config_file_path}" in output
assert "A fatal error occurred: Failed to connect to" in output
# Connection attempts are represented with dots,
# there are enough dots for two attempts here, but only one is executed
assert "Connecting............." not in output
# Test invalid custom_reset_sequence format is not accepted
invalid_reset_seq_config = "[esptool]\n" "custom_reset_sequence = F0|R1|C0|A5\n"
with self.ConfigFile(config_file_path, invalid_reset_seq_config):
output = self.run_esptool_error("flash_id")
assert f"Loaded custom configuration from {config_file_path}" in output
assert 'Invalid "custom_reset_sequence" option format:' in output