This fixture proves the Latch embedded path on a physical ESP32 and an acknowledging collector:
- initialize an atomic mirrored-flash backend;
- capture telemetry and an error before an intentional panic;
- reboot and recover the queued LEP envelope from flash;
- send it over the Latch stream framing on UART;
- wait for the collector's durable
LSAKacknowledgement; - capture and deliver a second post-recovery event.
The success marker is:
HIL:PASS:LATCH_RELAY_ESP32
This specifically validates the portable Latch runtime, ESP-IDF reset-reason adapter, on-device flash persistence, stream framing, collector ingestion, deduplication-compatible durable ACK, and post-reboot recovery. It does not yet install an ESP32/Xtensa panic-frame hook; the panic is intentional and the pre-panic event is captured from normal runtime immediately before it.
This fixture was verified on 2026-07-29 against the production LastState Relay.
Relay accepts framed LEP envelopes, validates and persists them before replying
with LSAK_STORED or LSAK_DUPLICATE, and exposes spool health checks.
Relay is a private LastState component and, as of 2026-07-29, has not been
released publicly. It is not a dependency of the Latch library or build. A
different collector can reproduce the test by implementing the public
latch-stream and LSAK v1 formats documented below.
The commands below are for PowerShell on Windows. Install Python 3.11 or newer, then create an isolated tool environment:
py -3.11 -m venv "$env:LOCALAPPDATA\LastState\esp32-tools"
& "$env:LOCALAPPDATA\LastState\esp32-tools\Scripts\python.exe" `
-m ensurepip --upgrade
& "$env:LOCALAPPDATA\LastState\esp32-tools\Scripts\python.exe" `
-m pip install platformio esptool pyserialIdentify the board and verify its real flash geometry before using the partition table:
$python = "$env:LOCALAPPDATA\LastState\esp32-tools\Scripts\python.exe"
& $python -m esptool --port COM3 chip-id
& $python -m esptool --port COM3 flash-idThe qualified board reported ESP32-D0WD-V3 revision 3.1 and 4 MB of flash.
Change monitor_port, upload_port, and the flash/partition configuration
when using different hardware.
From this directory, build, erase the selected board, and upload:
erasedestroys the complete contents of the selected device. Confirm the port and keep production credentials or calibration data off the test board.
& $python -m platformio run
& $python -m platformio run --target erase
& $python -m platformio run --target uploadImmediately start an acknowledging collector. The internal qualification used:
<laststate-relay> collect --serial COM3 --baud 115200 `
--framing latch-stream --ack lsak-v1 --data-dir .\.lab\esp32-relayThe firmware waits before delivery and retries, so Relay can acquire the port after the uploader releases it. Some FTDI auto-reset circuits reset the ESP32 when the collector opens the port; the fixture handles this and may produce an additional valid pre-panic envelope.
The device writes:
"LS" | version=1 | flags=0 | envelope_length:u32le | LEP | crc32:u32le
After validating and durably storing the LEP envelope, the collector replies:
"LSAK" | version=1 | status | reserved:u16le | event_id:u32le
LSAK_STORED and LSAK_DUPLICATE are successful durable outcomes. The
definitions and parser are in
stream_transport.h.
- the console shows phase 0 capture and an intentional
abort(); - the next Latch boot reports reset reason
LS_RESET_LOCKUP; - the collector stores the recovered pre-panic event;
- the device captures a post-recovery confirmation only after the first durable ACK;
- the collector ends with no pending, corrupt, quarantined, or dead-letter objects;
- the device emits
HIL:PASS:LATCH_RELAY_ESP32.
The first physical result and decoded event IDs are retained in
EVIDENCE.md.
| Problem | Effect | Resolution |
|---|---|---|
| Generated ESP-IDF config selected 2 MB while the chip reported 4 MB | Image metadata and the partition plan disagreed with hardware | Pinned the fixture to 4 MB after verifying it with esptool flash-id |
| BOYA flash support was not linked | ESP-IDF fell back to its generic flash driver | Enabled CONFIG_SPI_FLASH_SUPPORT_BOYA_CHIP |
CONFIGURE_DEPENDS was used while ESP-IDF evaluated component requirements in script mode |
CMake configuration failed | Removed CONFIGURE_DEPENDS from the fixture source discovery |
The first fixture relied on RTC_DATA_ATTR to select the recovery phase |
The board repeatedly captured and panicked instead of reaching delivery | Select recovery from ESP_RST_PANIC/ESP_RST_SW, which is the reset evidence exposed by ESP-IDF |
| Opening the FTDI port caused an external reset | A second pre-panic envelope was queued | Made the flow tolerate the reset and verified recovery of multiple queued records |
The AEAD size guard was constant-false on 32-bit size_t |
Xtensa builds emitted a Latch warning | Compile the large-input guard only where size_t can represent such a length |
| Package mirrors returned partial tool archives during first-time setup | Toolchain installation stalled or lacked files | Re-ran the PlatformIO package installation with mirror fallback and verified package manifests before building |
The isolated Python environment initially lacked pip |
PlatformIO could not install esptool's Python dependencies | Bootstrapped it with python -m ensurepip --upgrade |
The remaining product limitation is physical qualification of automatic Xtensa panic-frame capture. An ESP-IDF 5.5 wrapper now exists and is host-tested and firmware compile-checked, but it was not part of this earlier physical run. Reset classification and post-reset recovery are verified here; re-run this fixture with the wrapper and decode the promoted register event before treating automatic register/stack capture as hardware-qualified.