fix(mesh): stop the flash-triggered device boot-loop, fix stuck/wedged jobs
Three real incidents from live-testing the flash feature on archy-x250-dev,
each traced through logs on the node and fixed at the root:
- A failed flash left the mesh listener auto-resuming into a reconnect loop
whose backoff reset to its 5s minimum on any momentary connection, even
mid-boot-loop — every retry's open() toggles DTR/RTS, which resets many
ESP32 boards, so the retries were themselves sustaining the loop. Backoff
now only resets after a session runs stably for 20s+, and the flash job
no longer auto-resumes the listener after a failure (only on success,
after a settle delay).
- The HTTP client's blanket 30s request timeout covered entire downloads
(Meshtastic's zip is ~170MB), killing large transfers mid-stream; fixed
with a per-chunk stall timeout instead of a fixed total-transfer cap. But
the download's initial request had no timeout at all, so a slow-to-start
server hung it forever — wedging the single-flash-job guard permanently
("already in progress" on every subsequent attempt). Fixed with a bounded
wait for the response to start, plus an absolute ceiling around the whole
job as a last-resort safety net.
- archy-rnodeconf's PyInstaller freeze broke its own internal esptool
invocation (sys.executable pointed at the frozen binary itself instead of
a real interpreter) — fixed via a new runtime hook. esptool's own
auto-reset-into-bootloader handshake is separately known to be flaky on
some CP2102/CH340 boards; it now retries once at a conservative baud
rate instead of failing outright, and failure logs show the full error
chain instead of just the outer context message.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -47,11 +47,16 @@ if [ -n "$RNODECONF_SRC" ] && [ -f "$RNODECONF_SRC" ]; then
|
||||
# exit()/quit() builtins, which only exist in interactive Python (site.py
|
||||
# injects them) — a frozen app hits NameError right as it tries to quit
|
||||
# cleanly, after all the real work already succeeded. See
|
||||
# pyi_rthook_exit_builtins.py.
|
||||
# pyi_rthook_exit_builtins.py. A second hook fixes rnodeconf's board-flash
|
||||
# step, which shells out to a bundled esptool.py via `sys.executable` —
|
||||
# under a frozen binary that's the binary itself, not a real interpreter,
|
||||
# so the flash subprocess call breaks. See
|
||||
# pyi_rthook_fix_flasher_executable.py.
|
||||
.venv/bin/pyinstaller --onefile --name archy-rnodeconf --clean --noconfirm \
|
||||
--collect-submodules RNS \
|
||||
--collect-data RNS \
|
||||
--runtime-hook pyi_rthook_exit_builtins.py \
|
||||
--runtime-hook pyi_rthook_fix_flasher_executable.py \
|
||||
-d noarchive \
|
||||
"$RNODECONF_SRC"
|
||||
echo "Built dist/archy-rnodeconf ($(du -h dist/archy-rnodeconf | cut -f1))"
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
# PyInstaller runtime hook — see build.sh.
|
||||
#
|
||||
# rnodeconf's own board-flashing code shells out to a bundled esptool.py as
|
||||
# `[sys.executable, flasher_path, "--chip", ..., "write_flash", ...]` (RNS's
|
||||
# rnodeconf.py, ~line 2794 as of RNS 1.3.5). That's correct for a normal
|
||||
# `python rnodeconf.py` invocation, but under a frozen PyInstaller binary
|
||||
# `sys.executable` is the frozen binary itself, not a real interpreter — so
|
||||
# the "subprocess" just re-invokes archy-rnodeconf's OWN argparse CLI with
|
||||
# esptool-shaped flags, which it doesn't recognize, and the flash step fails
|
||||
# immediately with "unrecognized arguments: --chip ...". Confirmed live
|
||||
# against a real Heltec V4 (2026-07-23): device selection, band selection,
|
||||
# and firmware download all worked; only the final `write_flash` subprocess
|
||||
# call broke this way.
|
||||
#
|
||||
# Fix: point sys.executable at a real Python interpreter that has rnodeconf's
|
||||
# own runtime deps available (esptool.py only needs pyserial, which RNS
|
||||
# already depends on) before any of rnodeconf's code runs. Prefer the build
|
||||
# venv this exact binary was frozen from — see build.sh — falling back to a
|
||||
# bare `python3` on PATH if that venv isn't present on this node.
|
||||
import os
|
||||
import sys
|
||||
|
||||
if getattr(sys, "frozen", False):
|
||||
_candidates = [
|
||||
os.environ.get("ARCHY_RNODECONF_PYTHON", ""),
|
||||
os.path.join(os.path.dirname(os.path.abspath(sys.argv[0])), "..", "reticulum-daemon", ".venv", "bin", "python3"),
|
||||
os.path.expanduser("~/archy/reticulum-daemon/.venv/bin/python3"),
|
||||
]
|
||||
for _candidate in _candidates:
|
||||
if _candidate and os.path.isfile(_candidate):
|
||||
sys.executable = _candidate
|
||||
break
|
||||
else:
|
||||
sys.executable = "python3"
|
||||
Reference in New Issue
Block a user