config: fail fast on missing TempCtrl PID gains

Config access was unchecked dict[key] everywhere, so a missing/
misnamed gain (we've hit this once already: config.json.sim's gain/
Model nesting mismatch) surfaced as a bare KeyError from deep inside
some component's __init__ or first process() tick, with no indication
which config key was wrong.

utils/config_validate.py adds a small stdlib-only dotted-path checker
(no schema library - matches this project's stdlib-first leaning) and
validate_temp_ctrl(), scoped to the section that actually caused an
incident: TempCtrl.pid_type plus every kp/ki/kd/kt gain under
TempCtrl.Outer and TempCtrl.Inner.{Heat,Hold,Cool}. Called once in
server/brewpi.py right after json.load(), before any factory runs.
Optional-with-default keys (Outer.y_hold_min, Inner.Hold.yi_max,
Thresholds) stay optional here too.

Heater/Stirrer/TempSensor/Pot sections and unknown-key warnings are
deliberately left for later - see components/pid/TODO.md.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F4oEE99rkKVU8L8tkzXWdG
This commit is contained in:
2026-07-11 12:33:57 +02:00
co-authored by Claude Sonnet 5
parent c60fdff74d
commit 0171cb20c7
5 changed files with 129 additions and 11 deletions
+20 -11
View File
@@ -55,17 +55,26 @@ git history for `temp_controller.py`/`temp_controller_smith.py`).
at both call sites (`brewpi.py`, `components/sud_forecast.py`) were
removed along with it.
- [ ] **Config access is unchecked `dict[key]` everywhere.**
`params['Hold']`, `params['Td']`, etc., throughout, with no schema
validation at load time. We already hit this bug class once
(`config.json.sim`'s `gain`/`Model` nesting mismatch). A small
schema/dataclass validation layer at config-load would surface
errors immediately instead of mid-`__init__` — and would have caught
the dead `TempCtrl.Kalman` / `Model.kn` / `Plant.kn` keys that used
to sit unused in `config-sim.json.tpl`/`.sim` (since deleted), and the
`Model.gain`/`Plant.gain` keys that did the same in `config.json.sim`
before that file was removed entirely — `Pot` dropped `gain`
entirely, see `components/plant/TODO.md`.
- [x] **Config access is unchecked `dict[key]` everywhere.** Partially
fixed, scoped to the section that actually caused an incident
(`config.json.sim`'s `gain`/`Model` nesting mismatch): `utils/
config_validate.py`'s `require()` is a small stdlib-only dotted-path
checker (no schema library added - matches this project's no-pytest/
stdlib-first preference), and `validate_temp_ctrl()` requires
`TempCtrl.pid_type` plus every `kp`/`ki`/`kd`/`kt` gain under
`TempCtrl.Outer` and `TempCtrl.Inner.{Heat,Hold,Cool}` - the ones every
`pid_type` reads unconditionally. Called once in `server/brewpi.py`
right after `json.load()`, before any factory runs, so a missing/
misnamed gain now fails fast with `ConfigError: missing required
config key: TempCtrl.Inner.Hold.kp` instead of a bare `KeyError` from
inside some component's `__init__` or first `process()` tick. Covered
by `tests/utils/test_config_validate.py`.
Deliberately left for later, since neither has actually crashed
anything yet: `Heater`/`Stirrer`/`TempSensor`/`Pot` config sections
(all already default gracefully via `.get(..., default)` in
`PlantFactory` etc.), and erroring (vs. just `log.warning()`) on
unknown/stale keys like the old `TempCtrl.Kalman`/`Model.kn`/
`Plant.kn`/`Model.gain` leftovers - see `components/plant/TODO.md`.
- [x] **`pid_heat` can wind up during a `HOLD`-state disturbance with no
anti-windup engagement.** A cold-water disturbance while holding drove