Commit Graph
52 Commits
Author SHA1 Message Date
jensandClaude Sonnet 4.6 5be1d4e862 pid: option A heat-rate pre-filter (beta IIR before differentiation)
Adds a configurable IIR pre-filter (beta, default 0.05, τ≈19 s at dt=1 s)
applied to theta_ist before the derivative, so stirrer-induced temperature
noise is attenuated before being amplified by differentiation rather than
after.  The shared filter/derivative/post-filter logic is factored into
TempControllerBase._compute_heatrate(); both Normal and Smith subclasses
call it, replacing their duplicated inline rate blocks.

beta is read from TempCtrl.beta in the config (default 0.05 if absent) and
added to config-sim.json.tpl and config-real.json.tpl.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30 17:02:14 +02:00
jensandClaude Sonnet 4.6 d981c36194 Fix bogus first-tick heatrate from hardcoded last_theta_ist=20
last_theta_ist was hardcoded to 20 in __init__, used to compute the very
first process() tick's heatrate as (theta_ist - 20)/dt*60 - a wildly bogus
spike whenever the real starting temperature wasn't actually 20 (e.g.
1200 deg/min for a 40-degree start). The exponentially-smoothed
heatrate_ist (alpha=0.1) takes several ticks to recover from that, during
which it distorts the PID's rate-error term and visibly throws off the
heating trajectory.

This was invisible everywhere this assumption was implicitly tested,
since ambient (and every start_theta used) was always exactly 20. It
surfaced once SudForecastEstimator.estimate() started being called with a
real, non-20 start_theta: it builds a fresh TempController on every call,
so its first tick always hits this bug fresh - unlike the real controller,
which only ever goes through this once near server startup and has long
since self-corrected (last_theta_ist = theta_ist runs every tick) by the
time any forecast comparison matters. That's exactly what showed up as a
forecast-vs-actual gap during the initial ramp.

last_theta_ist now starts None; process() treats the first tick's
heatrate as 0 (no real history yet) instead of computing it against a
hardcoded, usually-wrong baseline.

Verified: ambient=20 (the only case ever exercised before) is byte-
identical to before the fix. ambient=40/start=40 - reproducing the old
hardcoded-20 behavior side by side - shows the old version visibly lagging
~12 minutes behind the fixed one during the initial ramp before they
converge, matching the reported gap exactly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DkkuG48uHFCGKe6dPSERFk
2026-06-22 22:41:14 +02:00
jensandClaude Sonnet 4.6 96fe7ce90c Require TempController(Smith)'s model params/ambient via setters; add comprehensive process() checks
TempController(Smith).__init__ no longer takes model_params/theta_amb -
set_model_plant_params() and the existing set_ambient_temperature() must be
called instead (mirrors components/plant/pot.py's own Pot constructor
refactor). temp_controller.py's now-pointless model_params/theta_amb
constructor placeholders (kept only for "compatibility" with Smith) are
dropped too.

Both TempController variants now raise a clear RuntimeError from process()
itself if set_params() wasn't called, instead of letting it surface deep
inside Pid.process() as an opaque "'NoneType' object is not subscriptable".
Smith additionally checks its internal models via Pot.is_configured() (new)
and raises if set_model_plant_params()/set_ambient_temperature() weren't
called either.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DkkuG48uHFCGKe6dPSERFk
2026-06-22 18:31:58 +02:00
jensandClaude Sonnet 4.6 0a6d3e6462 TempControllerBase: require PID gains via set_params(), not the constructor
Mirrors components/pid/pid.py's own Pid.set_params() pattern: params/
thresholds start out None, and set_params() configures pid_hold/pid_heat/
pid_cool from them. process()/process_fsm() fail naturally (TypeError on
None) if called before set_params() - same as Pid.process() already does -
rather than a new bespoke check. Updates all callers (server/brewpi.py,
SudForecastEstimator, and the pid/sud demo scripts) to call set_params()
right after construction.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DkkuG48uHFCGKe6dPSERFk
2026-06-22 18:21:20 +02:00
jens b72977d6cb Fix pid_type "Normal" crashing on startup
PidFactory.create() calls both controllers with the same (dt, params,
model_params, theta_amb=...) signature, but TempController ("Normal")
only accepted (dt, params) - any config using pid_type "Normal" would
crash with a TypeError on startup. It has no internal model, so just
accepts and ignores the extra arguments.
2026-06-21 09:36:40 +02:00
jensandClaude Sonnet 4.6 1e89a58370 Replace Pid.scale()'s mutable gain factor with a stateless process() arg
Pid.scale(k) set self.k as persistent state, mutated from outside the
class and never reset in reset(), so a stale scale factor could survive
a state-transition reset. Replace it with a plain scale=1.0 argument on
Pid.process(), and have temp_controller.py/temp_controller_smith.py
compute the heat-rate-overshoot compensation factor themselves and pass
it through process_pid() each tick instead of mutating pid_hold's state.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-19 17:37:55 +02:00
jensandClaude Sonnet 4.6 ac8de9da55 Add sensor variance config and smooth heat-rate estimate
[Sensor] - replace TempSensorSim's hardcoded k_noise with configurable
temp_offset/variance, accept **kwargs on Max31865 too so both sensors
share a constructor signature usable from TempSensorFactory.create()
[Temp Controller] - low-pass filter the backward-difference heat-rate
estimate (alpha=0.1) in both temp_controller.py and
temp_controller_smith.py instead of using the raw, noisy derivative
[Pot] - drop kn from demo_pot.py's params, matching the unused-field
removal already made to pot.py itself

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-19 15:47:24 +02:00
jens 5c7cffa3a7 [Pot]
- remove HeatDiffusion
- use delay line for heat propagation modeling

[Temp Controller]
- remove Kalman
2026-06-19 14:21:23 +02:00
jensandClaude Sonnet 4.6 81e66dbcd1 Move PID matplotlib demo harnesses out of production modules
temp_controller.py, temp_controller_smith.py, and kalman.py imported
matplotlib at module level just to support eyeballed-plot __main__
blocks, coupling the live server's import graph to a GUI plotting lib
it never uses at runtime. Relocate those demos (and kalman_eval.py) to
scripts/demos/pid/ and strip the now-unused imports/__main__ blocks
from the production files.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-19 08:21:47 +02:00
jensandClaude Sonnet 4.6 1bf3929b60 Drop model_params from base, enable Smith predictor delay correction
TempControllerBase no longer threads model_params through (only the
Smith subclass needs it, for its own Pot model and Kalman filters).

Enable the Smith predictor's actual delay-compensated error term
(theta_err now uses theta_ist_plant - theta_ist_model_delay +
theta_ist_model instead of the plain plant reading), which is the
correction this controller is named for.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 21:06:35 +02:00
jensandClaude Sonnet 4.6 78ee80f96d Deduplicate TempController/TempController_smith into a shared base
Both controllers had nearly identical process_fsm(), process_pid(),
and all getters/setters; only Kalman/model setup and process()
genuinely differed, and the duplication had already drifted (the
Smith variant resets model state on entering HEAT, the plain one
didn't).

Add TempControllerBase with the shared logic and three small hooks
(init_kalman, on_state_entered, post_pid) subclasses use to plug in
their own Kalman/model behavior. Each subclass now contains only what
makes it different.

Also fixes a latent crash: TempController's constructor only accepted
(dt, params), but PidFactory/brewpi.py always call it with a third
model_params arg, so pid_type "Normal" would have raised TypeError.
The shared base's model_params=None default fixes this. Also
initializes the Smith controller's trace attributes in __init__
instead of leaving them undefined until the first process() call.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 20:50:01 +02:00
jens e8fa90e753 - PID/TC goes Idle state on first theat_ist measurement 2021-10-12 17:13:19 +02:00
jens bf7ec2beaf - PID: introduced INIT State
- refactored
2021-10-12 16:47:05 +02:00
jens 425567f3db - refactored
- increased Hold.Pid.kp
- decrease state change HEAT TO HOLD windows to 1°C
2021-10-12 09:11:07 +02:00
jens ff90c6d90c - added initial for filling Delay line
- added gain parameter for Pot sim
- from state change to heat init model temp with plant temp
- updated config template
2021-10-11 19:54:03 +02:00
jens c5c39732cf - do data signal wiring at one central place
- fixed initializations
2021-10-11 13:05:18 +02:00
jens dce3caa1eb - refactored 2021-10-09 12:27:36 +02:00
jens f6394e9e7a - refactored 2021-10-09 10:52:35 +02:00
jens b83ec71346 - refactored 2021-10-09 10:07:45 +02:00
jens 5923212055 - refactored 2021-10-09 09:46:52 +02:00
jens 529ae7ad2f - adjusted params for TC tests 2021-10-02 13:10:10 +02:00
jens 2e143ee03f - implemented APid
- choose Tempcontroller impl. from config
2021-10-02 12:24:42 +02:00
jens bf019733b2 - added abstarct PID APid
- added PidFactory
- use variable args for factory
2021-10-02 11:10:43 +02:00
jens f2aff73f40 - refactored temp controllers 2020-12-22 10:51:38 +01:00
jens 4c93e85dd8 - improved temp controllers 2020-12-22 10:45:51 +01:00
jens d777a955a4 - refactored dt 2020-12-22 10:26:56 +01:00
jens b5a0e6a7ac - removed dt from params 2020-12-21 16:06:17 +01:00
jens 972e447897 - no kd 2020-12-21 12:43:50 +01:00
jens 5865923854 - refactored params 2020-12-21 12:24:42 +01:00
jens 5f60312d4e - Pid: D-Part need not to be error based
- increased pot delay to 30 s
2020-12-20 18:23:14 +01:00
jens 24437329f0 - Compensate for max heat rate to reduce overshoot 2020-12-16 19:54:38 +01:00
jens 71cd87085f - load params from config.json 2020-12-16 18:42:05 +01:00
jens 6dd0920010 - refactored 2020-12-16 17:13:57 +01:00
jens fa93a3911e - refactored 2020-12-16 17:10:46 +01:00
jens 2327b214fc - increased kp 2020-12-13 16:31:20 +01:00
jens 0633e464ca - refactored
- created TempSensorFactory
2020-12-13 16:21:08 +01:00
jens 8d57299d83 -added anti windup 2020-12-11 15:17:52 +01:00
jens e5139c5695 - PID-T doesn't need integral term 2020-12-11 14:23:43 +01:00
jens ada81935e3 - temp controller: use series PID for T and dT. PID-T sets setpoint on PIT-dT 2020-12-11 12:43:12 +01:00
jens 52e3cf733d - TC: use offset 2020-12-10 12:46:13 +01:00
jens 424f1d0d0f - fixed initial value for temp_controller 2020-12-10 09:40:38 +01:00
jens a80cc2bf01 - TC: prepared for Rate set and get
- improved TC state machine
2020-12-06 12:05:55 +01:00
jens 493af820dc - TC: state IDLE introduced
- TC: send states
2020-12-06 11:34:54 +01:00
jens 448afb3e9a - refactored PID params 2020-12-05 18:40:23 +01:00
jens 55aa043efb - tempcontroller: added heat rate control 2020-12-05 18:33:16 +01:00
jens 6939650708 - PID: use pot for testing 2020-12-02 11:24:31 +01:00
jens 5102ad935c - fixed water model 2020-11-29 19:12:17 +01:00
jens d9c587476f - added Temp controller
- use AttributeChange for Processing objects
2020-11-29 14:35:44 +01:00
jens 63e4115db8 - improved temp controller test 2020-11-27 12:04:45 +01:00
jens db464080d9 - improved temp controller test 2020-11-26 22:04:22 +01:00