Repository navigation
L072 RS-12.21: FHSS scan recovers from FAILED (option b + L + 2 s dwell), firmware park command, 0x0D split — built, not flashed - #168
Merged
Conversation
…x0D split
The design owner's choice (DECISIONS.md D-FHSS1, 2026-10-24), from
FHSS_SCAN_FAILED_OPTIONS.md section 5:
- (b): a node holds scan FAILED only while it holds originator
authority. A final FAIL on any other node keeps scanning (and still
reports 0x0D final every 30 s); a node in FAILED that loses authority
scans again on its next tick. Leg 6's stranded base and the tractor
twin both recover. The pure scan policy is unchanged.
- L: a LOCK starts the 2 s loss timer at the locking frame.
- d2: 2 s dwell; the walk continues across retries and (b)'s restarts.
- RS-12.21 park: HOST_TYPE_RADIO_PARK_REQ 0x04 -> RADIO_PARK_URC 0x84
{status, RegOpMode}. Sets host_parked: modem to SLEEP, every RX arm
path gated (scan frozen, slot follower, gamma-1, post-TX re-arm,
sx1276_rx_arm backstop), TX refused; ended by a raw RXCONT, a profile
activation or reset. Capability bit 7 (bitmap 0xFF) marks the image.
- 0x0D split: TX_FIFO_RB_MISMATCH moves to 0x0F; compile-time duplicate
check for fault codes. mh_wire.h mirrors the new values.
- SIL: check-rx-scan-failed-follower is now (b)'s acceptance check
(fails if any part is reverted); new check-radio-park drives the real
host_cmd.c; the shared sim follows the firmware's recorded scan
transitions. SIL numbers match the memo (leg-6 replay 100/100, median
17.9 s; 2 fps cold start 1.3 %).
- Host: 0x0F / 0x0D decode (old-image FIFO faults named as such),
radio_park.py uses the park command when the image advertises it
(register path on e083ae46), tighter fault:0x0D:final trigger, the
leg report tells the restart image from the absorbing one.
- md5 records: production 2c5e055a, bench 0daae279 (25 324 B); built,
NOT flashed: the boards still run e083ae46. Flash-and-verify plan in
the memo section 6.1 and BENCH_RUNBOOK.
make check passes; base_station 1931 passed (10 Windows-only failures
as on main); harness offline 197/197; mh_wire sync 65 constants.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
🔵 Needs a closer look
Cross-layer radio recovery and persistent parking need final human review and explicit operator authorization, with on-board validation still pending.
No open issues 🎉
What changed in this PR
Implements the FHSS recovery package selected from #167’s decision memo, allowing nodes without originator authority to resume scanning instead of remaining in FAILED. It pairs recovery with persistent firmware parking and updates host diagnostics and bench procedures. The images are built but not flashed.
Changes:
- Adds scan recovery, a locking-frame loss timer, and continuous channel walking with a 2-second dwell.
- Adds the radio park command, separates scan and FIFO fault codes, and updates host decoding, triggering, and scoring.
- Extends simulation checks and records build hashes, rollout steps, and operator-controlled validation.
Paths below are relative to LifeTrac-v25/DESIGN-CONTROLLER/, except .github/.
| File | Description |
|---|---|
tools/check_mh_wire_sync.py |
Checks mirrored park and fault constants. |
TODO.md |
Records implementation status and pending radio validation. |
firmware/x8_lora_bootloader_helper/run_live_radio_monitor.ps1 |
Restricts final-fault triggers to valid scan encodings. |
firmware/x8_lora_bootloader_helper/README.md |
Distinguishes new builds from flashed images. |
firmware/x8_lora_bootloader_helper/method_g_stage1_probe.py |
Adds park requests and split fault decoding. |
firmware/x8_lora_bootloader_helper/FLASH_RUNBOOK.md |
Documents image hashes and persistent-park verification. |
firmware/x8_lora_bootloader_helper/bench_tools/test_run_live_radio_monitor.ps1 |
Tests scan-final and FIFO fault triggers. |
firmware/x8_lora_bootloader_helper/bench_tools/rs13_probe_score.py |
Makes acquisition scoring scan-image aware. |
firmware/x8_lora_bootloader_helper/bench_tools/radio_park.py |
Selects firmware parking or legacy register fallback. |
firmware/x8_lora_bootloader_helper/bench_tools/provision_bench_board.sh |
Updates firmware provisioning guidance. |
firmware/x8_lora_bootloader_helper/bench_tools/PC_SETUP.md |
Updates reproducible-build references. |
firmware/x8_lora_bootloader_helper/bench_tools/legs/README.md |
Documents firmware-path parking expectations. |
firmware/x8_lora_bootloader_helper/bench_tools/flash_l072.sh |
Recognizes the new firmware hashes. |
firmware/x8_lora_bootloader_helper/bench_tools/bracket_deltas.py |
Updates diagnostics for scanning without acquisition. |
firmware/x8_lora_bootloader_helper/bench_tools/BENCH_SETUP.md |
Updates build and parking instructions. |
firmware/x8_lora_bootloader_helper/bench_tools/BENCH_RUNBOOK.md |
Documents new diagnostics, parking, and first-leg checks. |
firmware/x8_lora_bootloader_helper/bench_tools/BENCH_QUICKSTART.md |
Updates build, acquisition, and shutdown guidance. |
firmware/x8_lora_bootloader_helper/bench_tools/BENCH_BOARDS.md |
Separates built images from board firmware inventory. |
firmware/tractor_h7/murata_host/mh_wire.h |
Mirrors park messages and split fault codes. |
firmware/murata_l072/README.md |
Records new build hashes and unflashed status. |
firmware/murata_l072/radio/sx1276_tx.c |
Refuses transmission while parked. |
firmware/murata_l072/radio/sx1276_rx.c |
Implements scan recovery, loss timing, and park gates. |
firmware/murata_l072/Makefile |
Integrates park and recovery acceptance checks. |
firmware/murata_l072/include/sx1276_rx.h |
Declares parking APIs and recovery contracts. |
firmware/murata_l072/include/sx1276_rx_scan_walker.h |
Documents channel walking across retries. |
firmware/murata_l072/include/sx1276_rx_scan_policy.h |
Sets the 2-second dwell and documents dispatcher recovery. |
firmware/murata_l072/include/sx1276_rx_scan_fail.h |
Clarifies final-failure reporting semantics. |
firmware/murata_l072/include/host_types.h |
Defines park protocol, capability, and unique fault codes. |
firmware/murata_l072/host/host_cmd.c |
Dispatches parking and refuses parked TX requests. |
firmware/murata_l072/host/host_cfg_profile.c |
Ends parking on profile activation. |
firmware/murata_l072/FHSS_SCAN_FAILED_OPTIONS.md |
Records the adopted package and staged validation plan. |
firmware/murata_l072/bench/host_proto/tx_len_guard.c |
Adds the parked-state test stub. |
firmware/murata_l072/bench/host_proto/rx_reverse_delivery.c |
Extends simulation tracking and recovery invariants. |
firmware/murata_l072/bench/host_proto/fhss_stub.c |
Adds the unpark test stub. |
DESIGN-LORAFIRMWARE/04_Hardware_Interface_and_Recovery.md |
Documents park messages and the fault-code split. |
DECISIONS.md |
Records D-FHSS1 and its accepted trade-offs. |
base_station/tests/test_rs13_scoring_sil.py |
Tests scoring for the restarting scan image. |
base_station/tests/test_rs12_leg_report_sil.py |
Tests image-aware acquisition and fault reporting. |
base_station/tests/test_fault_urc_log_sil.py |
Tests fault decoding, constants, and capability logging. |
base_station/image_rx_daemon.py |
Logs the firmware capability bitmap at startup. |
.github/workflows/l072-bench-md5.yml |
Updates expected firmware build hashes. |
Review effort: Balanced
xchuck35
pushed a commit
to xchuck35/LifeTrac
that referenced
this pull request
Oct 26, 2026
… R2) Operator: "proceed with any merges and you have a GO for any radio testing". After OpenSourceEcology#168 and OpenSourceEcology#164 merged (main d597b09): - Flash: both L072s now run bench 0daae279 (option (b) + L + 2 s dwell, firmware park 0x04/0x84, 0x0D split). Verify OK, flash_rc=0, DONE prep=0 flash=0 revive=0 on both. Post-flash: rs116 STATS-OK, tx_fifo_rb_bad 0; radio_park.py cap 0xFF, path firmware, PARKED, persistent, 0x80 held at the 2-minute re-read. - R1 (FHSS mono_g4, 300 s, reactive PROBEs): first frame 11.0 s after activation, ACQUIRED (599/609); PROBE 204/204; leg-end firmware park held 2+ min with profile 1 active, the RS-12.21 park gate on hardware. - R2 (480 s, -InjectOn fault:0x0D:final): tractor 0x0D attempts 0x00/0x01/0x02/0x83 30 s apart, all name=RX_SCAN_FAILED; the injector fired on the final only; tractor heard 93/95 base commands in FAILED; RS-12.15 criteria PASS. - End of session 15:02Z: both radios 0x80. Records: the boards' build (0daae279; e083ae46 the rollback image) in README / FLASH_RUNBOOK / BENCH_* / flash_l072.sh / DECISIONS / SETTINGS_REFERENCE / TODO rows (in place) and the memo. Procedure: youtube_window.ps1 -Front retries a refused focus change once with the Alt-key workaround and exits 6 when the bench window still is not in front; scene_check.sh then takes no frame (exit 5), so no other window is photographed or put on the air. PIPELINE-EXIT is console only: the success line is DONE prep=0 flash=0 revive=0. Privacy: a diagnostic frame that showed a non-bench window was deleted and never committed; every scene frame and a full-coverage render of both base captures show YouTube content only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this is
This implements the design owner's choice from #167's memo (
FHSS_SCAN_FAILED_OPTIONS.mdsection 5, recorded as DECISIONS.md D-FHSS1). The package is option (b), the loss-timer fix L, a 2 s dwell, the RS-12.21 firmware park command, and the 0x0D fault-code split. The decay-window trade-off in memo section 5 item 3 is accepted with the choice.Built, not flashed. Both boards still run
e083ae46. The operator kept the radios off today; flashing and radio legs are planned for tomorrow, each on its own GO.Firmware (L072)
FAILEDonly while it holds originator authority.0x0Dfinal every 30 s.sx1276_rx.cdecides whether to hold FAILED.HOST_TYPE_RADIO_PARK_REQ 0x04is answered withRADIO_PARK_URC 0x84= {status 0 PARKED / 1 ALREADY / 2 REFUSED, RegOpMode readback}.sx1276_rx_arm()backstop are all gated. TX is refused too.0xFF) marks this image.TX_FIFO_RB_MISMATCHmoves to0x0F, andRX_SCAN_FAILEDkeeps0x0D. A compile-time duplicate check now covers fault codes.HOST_PROTOCOL_VERandHOST_WIRE_SCHEMA_VER, per the contract now written intohost_types.h.mh_wire.hmirrors the new values, andcheck_mh_wire_sync.pypasses with 65 constants.SIL
check-rx-scan-failed-followeris now (b)'s acceptance check.check-radio-parkdrives the realhost_cmd.c: nothing re-arms for minutes, each way out of the park works, and TX is refused.rx_reverse_deliverysim now follows the firmware's recorded scan transitions. It adds four structural checks on every scenario.Host
0x0Fdecodes as the FIFO fault.0x0Ddecodes asRX_SCAN_FAILEDwhen the sub-byte is valid; otherwise it is named as an old-image FIFO fault.radio_park.pyuses the park command when the image advertises it. One083ae46it falls back to the register path and says so. The settle time is now 2.5 s.-InjectOn fault:0x0D:finalnow requires a valid attempt-3 scan sub, andfault:<other>:finalis refused.0xFF) from the absorbing one. A final0x0Dis no longer read as FAILED on the new image.Builds (clean, reproducible)
build/firmware.bin2c5e055abff95126309a62409fafc654build/firmware_bench_diag.bin(untracked, by policy)0daae27978089eb97ab35c18718b62b0These are recorded in README,
l072-bench-md5.yml, FLASH_RUNBOOK,flash_l072.sh, BENCH_*, PC_SETUP, DECISIONS and TODO.check_size_budget.pypasses (APP 25 324 of 188 416 B).Verification
make check: full pass, run from PowerShell with WinLibsmingw32-make.test_x8_watchdog_ownership_sil.pycases.git diff --checkis clean, apart from the linker map's*fill*lines, which were already there.0x0Das FAILED on this image. A final integration review followed, and its minor findings are fixed here.Tomorrow: flash and verify (memo section 6.1, BENCH_RUNBOOK "First flash and legs on the RS-12.21 image")
EVIDENCE_DIR. Runmake checkand a clean build: the bench md5 must be0daae279…. The harness offline tests must show 0 failed.flash_l072.sh <board> $FWwithout--go, then flash the tractor, then the base, with--go. Each must show Verify OK and PIPELINE-EXIT=0.rs116_health_probe.py: STATS-OK,tx_fifo_rb_bad0.radio_park.pyon both boards. Want PARK_OK withcap_bitmap 0x000000FF,path firmware,persistent true, and 0x80 readbacks.path registeror0x7Fmeans the old image is still on: stop.radio_state.py2 minutes or more later must read 0x80.cap=0x000000FF.confirmed acquisition: ACQUIREDwithin 120 s.0x0Dline isname=RX_SCAN_FAILED, there is no0x0Fline, and Δtx_fifo_rb_badis 0.--check-onlyreads 0x80 two minutes later. This is the real test of the park gate, because profile 1 is still active then.!! ACQUISITION FAILEDor!! BASE NEVER LOCKED.bench-evidence/RS_12_15_reverse_delivery_2026-10-18/firmware_flash/firmware_bench_diag_e083ae46.bin.Open points for the design owner (non-blocking)
Merge only on the operator's word.
🤖 Generated with Claude Code