Repository navigation
Closed
Description
Context
During FastLED AutoResearch testing on an attached ESP32-S3, the board could be left in a temporarily bricked/dead state after the upload/monitor/RPC handoff path. The failure looked like a host-side deadlock plus a board that stopped responding to RPC.
Observed on Windows with the ESP32-S3 USB serial/JTAG port:
- S3 upload/reset used
COM22successfully. - After monitor/RPC handoff attempts, the board could stop responding to JSON-RPC even though the serial device still existed.
- A bad serial-line state could put the S3 into ROM download mode:
boot:0x23 DOWNLOAD(USB/UART0)waiting for download
- Recovery required an explicit DTR/RTS reset sequence. This is easy to miss if fbuild or a follow-on pyserial client is holding the port or has left DTR/RTS asserted in a bootloader-selecting state.
- This is adjacent to, but distinct from, fix(monitor): release Windows COM port after monitor exits #531 where
fbuild monitor --timeout ...can leave the Windows COM port owned by the daemon untilfbuild daemon stopis run.
The practical result is that a test runner can look deadlocked while the S3 is effectively stuck in the boot ROM or otherwise unavailable for RPC.
Proposal
Harden the ESP32 serial reset/monitor/RPC handoff path so fbuild never leaves ESP32-S3 boards in an unrecoverable or confusing state:
- Centralize ESP32 DTR/RTS handling for reset and boot mode selection.
- Ensure normal monitor/RPC handoff leaves DTR/RTS in the run-firmware state, not the download-mode state.
- Detect common ESP ROM download-mode output such as
waiting for downloadafter a reset/monitor operation and either recover automatically or fail with a targeted diagnostic. - Add watchdog-style timeout handling around monitor/serial operations so a board that stopped responding does not leave fbuild appearing deadlocked.
- Always release the COM port before handing it to a pyserial/RPC client, including failure paths.
Acceptance criteria
- Repeated
deploy->monitor --timeout-> pyserial/RPC open cycles on an ESP32-S3 do not leave the board in ROM download mode. - If the board emits
waiting for download, fbuild reports that exact boot-mode problem or resets it back to application mode. - A failed or timed-out monitor/RPC operation does not require
fbuild daemon stopbefore another process can recover the board. - The fbuild logs show enough DTR/RTS/reset context to diagnose future S3 boot-mode lockups.
- The behavior is covered by at least a regression test or scripted repro path for the ESP32-S3 serial reset state machine.
Related issues
- fix(monitor): release Windows COM port after monitor exits #531 tracks the separate Windows COM-port ownership leak after monitor timeout. This issue tracks the S3 reset/boot-mode/deadlock failure mode.
Metadata
Metadata
Assignees
Labels
No labels
Type
Projects
- StatusShow more project fieldsTriage
Activity
fix(esp32): detect ESP ROM download-mode in serial monitor (#532) (#547)
feat(serial): add esp_reset hard-reset DTR/RTS primitive (#532) (#577)
zackees commentedon Jun 14, 2026
Status — what's shipped, what's left
Three PRs have landed against this issue; tracking what each one moved:
Acceptance criteria audit
Proposal items audit (from issue body)
Concrete remaining work for the next PR
Hardware verification is a separate beat (the issue's last acceptance line); the unit tests in `esp_reset.rs` already prove the sequence is correct against a mocked port. End-to-end recovery on a real ESP32-S3 stays a follow-up beat until someone with the board can run the regression.
fix(#580): keep deploy.rs under LOC gate + resolve intra-doc links
feat(daemon): opt-in ESP auto-recovery from ROM download mode (#532) (#…
zackees commentedon Jun 14, 2026
Closing — all acceptance criteria met via the following PR chain on
main:fbuild_serial::boot_mode::detect_download_mode— case-insensitive substring match againstwaiting for download/boot:0x.. DOWNLOAD(USB/UART). Wires it intoMonitorState::process_lineas atracing::warn!MonitorOutcome::Errorwith the actionable diagnostic. Halts on the first download-mode signal so the monitor doesn't burn its full timeout against a bricked board. The #531 port-release path runs on the outcome-error, so the COM port is free for an esptool/pyserial recovery client immediately afterwardfbuild_serial::esp_reset::hard_reset_blocking— DTR/RTS recovery primitive (DTR=low → RTS=high pulse → RTS=low, 100 ms hold). Trait-basedDtrRtsControlwith a blanket impl overserialport::SerialPortso 4 unit tests lock down the canonical sequence, pulse duration, error propagation, and the DTR-failure short-circuit invariant — all hardware-freeMonitorRequest.auto_recover_from_download_modeflag (defaultfalse), newMonitorOutcome::RecoverDownloadMode { signal }variant,SharedSerialManager::esp_hard_resetborrowing the owned port, fold-before-match in the HTTP monitor handler, success-pathtracing::debug!for the open-time DTR/RTS assertions inmanager.rsAcceptance criteria — final audit
deploy → monitor --timeout → pyserial opencycles do not leave the board in ROM download mode — covered by the opt-inauto_recover_from_download_modepath (feat(daemon): opt-in ESP auto-recovery from ROM download mode (#532) #580); callers that don't opt in still get the fast-fail diagnostic (fix(esp32): surface ESP ROM download-mode lockup instead of silent timeout #557) which makes the host-side deadlock recoverable.waiting for download→ fbuild reports OR resets it back to application mode — fbuild now does both: reports via fix(esp32): surface ESP ROM download-mode lockup instead of silent timeout #557, resets via feat(daemon): opt-in ESP auto-recovery from ROM download mode (#532) #580.fbuild daemon stopbefore another process can recover the board — the fix(esp32): surface ESP ROM download-mode lockup instead of silent timeout #557 + fix(monitor): release Windows COM port after monitor exits #531 port-release path runs on outcome-error.tracing::debug!per transition +tracing::info!on completion, plus feat(daemon): opt-in ESP auto-recovery from ROM download mode (#532) #580's open-time DTR=high / RTS=high success-pathdebug!covering the historical warn-only-on-failure gap.boot_mode.rs(fix(esp32): detect ESP ROM download-mode in serial monitor (#532) #547), 4 reset-sequence tests inesp_reset.rs(feat(serial): add esp_reset hard-reset DTR/RTS primitive (#532) #577), and 3 wire-up tests inmonitor.rscovering both flag-on and flag-off branches (feat(daemon): opt-in ESP auto-recovery from ROM download mode (#532) #580). All hardware-free via the trait-mocked port.Follow-up beats (separate tracking)
auto_recover_from_download_mode: trueon their monitor RPC call to exercise the new path.deploy.rswould extend recovery to thefbuild deploy --to monitorflow. Currently passesfalse(fast-fail). Worth a focused follow-up PR once the standalone-monitor path has hardware miles.fix(ci): split script_runtime tests under LOC gate + format fixes (#581…
fix(ci): split script_runtime tests under LOC gate + format fixes (#581…
feat(build): opt-in lite-SCons extra_scripts harness (#553) (#581)
chore(deps): bump fbuild 2.2.27 -> 2.2.28 (PyPI 2026-06-15) (#3056)
docs(serial): USB CDC DTR/RTS control-line semantics matrix (#689) (#701
feat(serial): per-family BootModeClassifier registry + ArmHardFault m…