Commit Graph
4 Commits
Author SHA1 Message Date
jensandClaude Sonnet 5 5a874e3869 test: add fixture-driven suite for config_validate
Complements the inline-dict unit tests in test_config_validate.py with
10 real JSON config files under tests/utils/fixtures/ (valid sim/real/
Normal configs, one with legacy unknown keys that must NOT fail, and
six broken variants covering each distinct failure mode: missing gain,
missing Inner branch, missing pid_type, missing TempCtrl entirely,
wrong-typed gain, TempCtrl not a dict).

test_config_fixtures.py drives all of them from one CASES table
(fixture name -> expected pass/ConfigError-regex), plus a guard test
that fails if fixtures/ and CASES ever drift apart.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F4oEE99rkKVU8L8tkzXWdG
2026-07-11 12:59:38 +02:00
jensandClaude Sonnet 5 0171cb20c7 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
2026-07-11 12:33:57 +02:00
jensandClaude Sonnet 5 2d032bf9b2 fix: allow gentle negative HOLD floor to fix steady-state overshoot
Testing the windup-fix rework live against sude/sud_0030.json surfaced a
distinct bug: a HOLD overshoot from grain-fill-in cooling never decayed -
process_pid()'s pid_outer_y floor clamped to exactly 0.0, so pid_inner
fought the pot's own ambient loss to hold the overshot temperature flat
instead of declining back to setpoint (see docs/overshoot2.png).

Replace the hardcoded 0.0 floor with a configurable Outer.y_hold_min
(default 0.0, backward compatible), set to -0.1 in config.json, both
.tpl templates, and the demo scripts - small enough to avoid
reintroducing the bb5af3c limit cycle while letting HOLD request a
gentle decline matching passive ambient cooling.

Adds TestHoldOvershootRecoversToSetpoint and documents the finding in
docs/overshoot_hold_windup.md's Follow-up section and
components/pid/TODO.md. Confirmed against a live sud_0030 re-run, not
just the unit test.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M2ierBoxW3v7nUbDw3M2pE
2026-07-06 08:19:10 +02:00
jensandClaude Sonnet 5 f69d63c31b fix: split inner-loop yi_max clamp to fix HOLD-state windup overshoot
Renames pid_hold/pid_heat/pid_cool to pid_outer/pid_inner/pid_inner_cool
to match what actually runs when, and splits inner-loop config into
Inner.Heat/Inner.Hold/Inner.Cool so the same PID instance gets a tight
yi_max ceiling only while HOLD drives it, without capping legitimate
1.5 K/min ramps. Fixes the overshoot from docs/overshoot_hold_windup.md
where a cold-water disturbance during HOLD wound up pid_heat's integral
term with no anti-windup engagement, taking ~35s+ to unwind naturally.

Breaking config change: Hold/Heat/Cool -> Outer/Inner.{Heat,Hold,Cool}
in config.json, both .tpl templates, the pid/sud demo scripts, and
replay_sim.py's CLI flags. Adds tests/components/pid/ (stdlib unittest)
covering the Pid clamp/recovery behavior and closed-loop disturbance,
ramp, and HOLD<->HEAT transition cases.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DGQhVQ2Y3yXAQTXhrxVd5u
2026-07-05 21:23:55 +02:00