ADR 0009: Pump safety guard and latched faults
Status: Accepted Date: 2026-10-30
Context
The pump is the one actuator that can damage the hardware: running dry burns the pump, and Gardyn’s board browns out above ~30 % duty. garden-of-eden’s issue tracker documents every way its scattered checks were bypassed (#108, #109, #111, #114). sleepypod ADR-0022 established the model we want: detect, fail safe by de-energising, stay off until a human acknowledges, auto-recovery only as an explicit opt-in.
Decision
hal_core::safety::PumpGuard is the only path to the pump driver.
- Duty clamp to
limits.pump_max_duty(default 30 %). - Bounded runs — every start has a deadline ≤
pump_max_run_s(default 900 s). There is no unbounded “on”. - Dry-run interlock — refuse to start when the median of N ultrasonic
samples ≥
water_low_cm; fail safe: if the interlock is configured and the sensor is unavailable, refuse. - Over-temperature — refuse to start and stop a running pump at
over_temp_c(PCB sensor, default 65 °C; raised from 36 °C on 2026-11-01 after the stock Gardyn 3.0 board read 47 °C with the lights at 100 %). - Latched faults — a stop caused by (3) or (4) while running latches the
pump off.
acknowledgeclears it; nothing else does. No auto-recovery. - Stop always works, latched or not.
The guard is pure and clock-injected; every rule has a unit test. SimHal
mirrors the same rules so the demo never shows behaviour the unit would refuse.
Consequences
- Core, MCP, HA and the button cannot start the pump outside these rules.
- A dry tank produces a
pump_faultalert pluswater_low, and the UI/HA/MCP must acknowledge after refilling; this is deliberate friction. - Dosing pumps (Phase 6.3) get their own guard with daily volume caps (ADR-0014).
Authors: @ng
Sources: sleepypod/core (conventions and ADR precedents) · iot-root/garden-of-eden (reference implementation, GPL-3; no code copied) · sleepypod ADR 0022 pump stall safety
Imported from docs/adr/0009-pump-safety-guard.md in seedypod/core. Edit it there, not here.