Records the root cause of the cold-water overshoot in docs/overshoot.png (integral windup in pid_heat with no anti-windup engagement, since y never saturated) and the refined fix plan: gate pid_heat by FSM state instead of a flat yi_max clamp, which was rejected after the numbers showed it would also cripple legitimate high-rate ramps. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KefnwbGDM8CGrhb4sFhVq9
4.9 KiB
components/pid design backlog
Findings from a design review, refreshed against the current state of
master after the Kalman-filter removal and Smith-predictor rewrite (see
git history for temp_controller.py/temp_controller_smith.py).
-
FSM thresholds aren't configurable. Moved into an optional
TempCtrl.Thresholdsconfig section (HoldIdle,HoldHeat,IdleHeat,IdleHold,HeatHold,HeatIdle), merged overDEFAULT_THRESHOLDS(now intemp_controller_base.py, formerlytc_constants.py) so existing configs without the section keep working unchanged. -
matplotlib imported at module level in production code.
temp_controller.py,temp_controller_smith.py, andkalman.pyused tofrom matplotlib.pyplot import ...just to support their__main__self-test plots. The plotting demos (andkalman_eval.py) moved toscripts/demos/pid/demo_*.py; production modules no longer import matplotlib. -
Three Kalman filters share one tuning. No longer applicable:
temp_controller_smith.pywas rewritten to drop Kalman filtering entirely (see below), so there's no shared tuning to split anymore. -
Pid.scale()is a gain-scheduling hack, not anti-windup. Fixed: removedPid.scale()/self.kentirely.Pid.process(err, d, scale=1.0)now takes the gain multiplier as a plain per-call argument instead of mutable state, so there's nothing to go stale across areset().temp_controller*.pycomputehold_scale = 1.0/heatrate_soll_set if heatrate_soll_set > 0 else 1.0themselves and pass it throughprocess_pid(theta_err, heatrate_err, hold_scale)— the overshoot-compensation scheduling logic now lives entirely in the temp controller, not the genericPidblock. -
No automated tests.
tests/components/pid/was added once (stdlibunittest, no pytest) covering FSM transitions, anti-windup clamping, and Kalman convergence, but was removed again before the Kalman-filter removal/Smith rewrite, so none of the currenttemp_controller*.pybehavior (backward-difference + low-pass filtered heat rate, the two-model Smith correction) has test coverage. This drives a physical heater — worth re-adding. -
set_model_powerisn't defined on every controller, butbrewpi.pywires it unconditionally.brewpi.pyalways doesheater.set_on_changed("power_set", tc.set_model_power)regardless ofController.pid_type. Onlytemp_controller_smith.py(the Smith predictor) definesset_model_power;temp_controller.py("Normal") does not, so starting the server with"pid_type": "Normal"crashes at wiring time withAttributeError: 'TempController' object has no attribute 'set_model_power'. Either add a no-opset_model_powertoTempControllerBase, or only wire it when the configured controller actually exposes a model to feed. -
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'sgain/Modelnesting mismatch). A small schema/dataclass validation layer at config-load would surface errors immediately instead of mid-__init__— and would have caught the deadTempCtrl.Kalman/Model.kn/Plant.knkeys that used to sit unused inconfig-sim.json.tpl/.sim(since deleted), and theModel.gain/Plant.gainkeys that did the same inconfig.json.simbefore that file was removed entirely —Potdroppedgainentirely, seecomponents/plant/TODO.md. -
pid_heatcan wind up during aHOLD-state disturbance with no anti-windup engagement. A cold-water disturbance while holding drovepid_heat's integral term up without ever saturatingy(peaked aty≈0.74of the1.0ceiling), so the existing back-calculation anti-windup (Pid.process(),pid.py:45-48) never triggered — the FSM never even leftHOLD(diffstayed underHoldHeat=1.0). The resulting overshoot took ~35s+ to unwind naturally. A flatyi_maxclamp was considered and rejected: sustaining a genuine 1.5 K/min ramp needsy≈0.7-0.75fromyialone at steady state, the same range the disturbance itself peaked at, so no single clamp value can suppress the windup without also capping legitimate ramps. Plan is instead to gatepid_heat's activity by FSM state (frozen/offline whileHOLD, engaged by command or a newHoldHeatEngagethreshold) — seedocs/overshoot_hold_windup.mdfor the full writeup, open design question (threshold relationship withHoldHeat), and test plan. -
kalman.pyis now dead code in production. Neithertemp_controller.pynortemp_controller_smith.pyusesKalmananymore; the only remaining references arescripts/demos/pid/demo_kalman.pyanddemo_kalman_eval.py. Decide whether to keep it as a documented standalone filtering example or delete it along with the demos.