fix: latch the watchdog trip until an explicit clear - #14
Conversation
on_crumbs_message ran for every CRC-valid inbound command frame and cleared wdTripped. Any traffic at all released the trip. The controller's e-stop ladder drives its safe state as ordinary command frames. So on a board holding a watchdog brake, pressing e-stop cleared the trip, motorControlLogic left the `eStop || wdTripped` hold, and the OPEN_LOOP write(0) that followed released the brake — LMD18200::write() calls releaseBrake() before driving. The emergency stop left the machine LESS stopped than it found it (anolishq/anolis#261). A trip means the master went away while this board was driving something. Traffic arriving afterwards proves the master is back; it does not prove anyone decided it is safe to release a brake holding a load. Those are different facts and only the second should release the hold. Keep stamping wdLastRxMs so liveness still tracks real traffic — the watchdog must not re-trip spuriously once the master returns. Only the trip latch changes. Cleared now by SET_WATCHDOG (handler_set_watchdog) or a local serial command, both deliberate acts. anolis-provider-bread arms on startup (runtime_state.cpp:99,167) and again on address-recovery (bread_provider_runtime.cpp:38), so a controller restart or a recovered bus fault still clears it with no operator action. Reply builders are unaffected: they stamp wdLastRxMs only and never touched the trip, so the health poll's GET_WATCHDOG read cannot clear it either. Builds clean on all four board variants. Not yet validated on hardware — the decisive test is pressing e-stop on a tripped board and checking the shaft by hand, which is what the 08-08 session could not reach because that board predates #11 and coasted instead of braking. Refs anolishq/anolis#261
|
Held, and narrowed — see anolishq/anolis#283. Not closing this: the defect is real and the fix is right in shape. But its scope is much smaller than the PR body claims, and it is blocked behind a prerequisite. Scope. The reference machine's emergency stop is Category 0 — a contactor cutting backplane power. Under it the board is unpowered, so neither the watchdog nor the trip latch participates. This PR therefore does nothing for the emergency stop. It matters only for a powered-bus loss of supervision: a runtime crash, a provider crash, an unplugged I2C cable. Blocker. So the order is: split re-arm from clear-trip first, in firmware and in the provider. Then this latch becomes safe to land — here and in the identical case at feastorg/Slice_RLHT#9. |
Firmware half of anolishq/anolis#261.
The bug
on_crumbs_message(firmware/src/main.cpp:236-244) ran for every CRC-valid inbound command frame and did:The controller's e-stop ladder drives its safe state as ordinary command frames. So on a board holding a watchdog brake:
set_modearrives →wdTripped = falsemotorControlLogicleaves theif (local.eStop || wdTrippedNow)holdset_open_loop{0,0}follows →LMD18200::write()callsreleaseBrake()before driving → brake drops, motor free-wheelsThe emergency stop left the machine less stopped than it found it.
The fix
Stop clearing the trip there. Keep stamping
wdLastRxMs, so liveness still tracks real traffic and the watchdog cannot re-trip spuriously once the master returns — only the trip latch changes.The reasoning: a trip means the master went away while this board was driving something. Traffic arriving afterwards proves the master is back; it does not prove anyone decided it is safe to release a brake holding a load. Those are different facts, and only the second should release the hold.
What still clears it
SET_WATCHDOG→handler_set_watchdog(dcmt_handlers.cpp:152)serialCommands.cpp:61,217)wdTrippedinitialises false, watchdog boots disarmedanolis-provider-breadarms on startup (src/core/runtime_state.cpp:99,167) and again on address-recovery (src/core/bread_provider_runtime.cpp:38), so a controller restart or a recovered bus fault still clears the latch with no operator action.Reply builders are unaffected — they stamp
wdLastRxMsonly and never touched the trip, so the health poll'sGET_WATCHDOGread cannot clear it either. Verified: the only writers ofwdTrippedare the four above pluswatchdogLogicsetting it true.State of validation
Builds clean on all four board variants (
gen1_nano,gen2_nano,gen1_nanoevery,gen2_nanoevery).Not yet validated on hardware. The decisive test is pressing the e-stop on a tripped board and checking the shaft by hand. The 2026-08-08 bench session could not reach that step: the board on the rig predates #11 and coasted on a watchdog trip, so there was no brake for the e-stop to release. This change is going onto the rig together with #11 for the first time.
Two desk traces on this same safety path have already been wrong this campaign (anolishq/anolis#259 and #260 were both closed after review), so this should be treated as a hypothesis until the shaft is checked by hand.
The risk this introduces
A latched trip is, by design, sticky. The failure mode worth watching for is the opposite of #261: a board stuck braked because nothing re-arms it. The provider's two arming paths should cover it, but the bench procedure explicitly checks recovery after the e-stop test and treats "does not recover without a power cycle" as worse than the bug being fixed.
Attribution
module=1.0.0will not change —DCMT_MODULE_VER_*lives inbread-crumbs-contracts(pinned from the PlatformIO registry at^0.4.5), andbread_check_module_compatignores patch anyway. Making the flashed build self-identifying needs a contracts release; until then the git SHA must be recorded by hand at flash time. That is #13.