From 4256622e9da9d00731d1343e6f8dde66e2803c10 Mon Sep 17 00:00:00 2001 From: Jens Ahrensfeld Date: Thu, 18 Jun 2026 22:09:39 +0200 Subject: [PATCH] Make FSM hysteresis thresholds configurable THRESH_HOLD_IDLE/HOLD_HEAT/IDLE_HEAT/IDLE_HOLD/HEAT_HOLD/HEAT_IDLE in tc_constants.py were hardcoded module-level constants shared by every controller instance, so tuning the IDLE/HOLD/HEAT hysteresis required a code change. Replace them with DEFAULT_THRESHOLDS plus an optional TempCtrl.Thresholds config section, merged at construction time in TempControllerBase.__init__ so configs that omit the section keep behaving exactly as before. Documented the new section in config.json.templ; left config.json.sim without it to exercise the default-fallback path. Co-Authored-By: Claude Sonnet 4.6 --- components/pid/TODO.md | 11 +++++------ components/pid/tc_constants.py | 14 ++++++++------ components/pid/temp_controller_base.py | 16 ++++++++-------- config.json.templ | 8 ++++++++ 4 files changed, 29 insertions(+), 20 deletions(-) diff --git a/components/pid/TODO.md b/components/pid/TODO.md index f4ac028..460adc1 100644 --- a/components/pid/TODO.md +++ b/components/pid/TODO.md @@ -5,12 +5,11 @@ Findings from a design review, not yet acted on. See git history on correction enabled, the two TempController classes deduplicated into `temp_controller_base.py`). -- [ ] **FSM thresholds aren't configurable.** `tc_constants.py`'s - `THRESH_HOLD_IDLE`, `THRESH_HOLD_HEAT`, `THRESH_IDLE_HEAT`, - `THRESH_IDLE_HOLD`, `THRESH_HEAT_HOLD`, `THRESH_HEAT_IDLE` are - hardcoded module-level constants shared by every controller - instance. Move them into `config.json`'s `TempCtrl` section like the - PID gains, so mash schedules can tune hysteresis without code edits. +- [x] **FSM thresholds aren't configurable.** Moved into an optional + `TempCtrl.Thresholds` config section (`HoldIdle`, `HoldHeat`, + `IdleHeat`, `IdleHold`, `HeatHold`, `HeatIdle`), merged over + `DEFAULT_THRESHOLDS` in `tc_constants.py` so existing configs without + the section keep working unchanged. - [ ] **`Pid.scale()` is a gain-scheduling hack, not anti-windup.** `pid.py`'s `scale(k)` multiplies every gain by `1/heatrate_soll_set` diff --git a/components/pid/tc_constants.py b/components/pid/tc_constants.py index 9703336..0809cd5 100644 --- a/components/pid/tc_constants.py +++ b/components/pid/tc_constants.py @@ -8,12 +8,14 @@ class States(Enum): HOLD = 2 -THRESH_HOLD_IDLE = 0.1 -THRESH_HOLD_HEAT = 1.0 -THRESH_IDLE_HEAT = 1.0 -THRESH_IDLE_HOLD = 0.1 -THRESH_HEAT_HOLD = 1.0 -THRESH_HEAT_IDLE = 1.0 +DEFAULT_THRESHOLDS = { + "HoldIdle": 0.1, + "HoldHeat": 1.0, + "IdleHeat": 1.0, + "IdleHold": 0.1, + "HeatHold": 1.0, + "HeatIdle": 1.0 +} class Test: diff --git a/components/pid/temp_controller_base.py b/components/pid/temp_controller_base.py index c81cae4..fa03fdf 100644 --- a/components/pid/temp_controller_base.py +++ b/components/pid/temp_controller_base.py @@ -1,7 +1,6 @@ from components import APid from components.pid.pid import Pid -from components.pid.tc_constants import States, THRESH_HOLD_IDLE, THRESH_HOLD_HEAT, \ - THRESH_IDLE_HEAT, THRESH_IDLE_HOLD, THRESH_HEAT_HOLD, THRESH_HEAT_IDLE +from components.pid.tc_constants import States, DEFAULT_THRESHOLDS class TempControllerBase(APid): @@ -17,6 +16,7 @@ class TempControllerBase(APid): self.theta_ist = 0 self.heatrate_ist = 0 self.params = params + self.thresholds = {**DEFAULT_THRESHOLDS, **params.get('Thresholds', {})} self.y = -1 self.state = States.INIT self.use_kalman = True @@ -72,22 +72,22 @@ class TempControllerBase(APid): if not self.is_startup: state_next = States.IDLE elif self.state == States.IDLE: - if diff >= THRESH_IDLE_HEAT: + if diff >= self.thresholds['IdleHeat']: state_next = States.HEAT self.pid_rate.reset() - elif diff >= -THRESH_IDLE_HOLD: + elif diff >= -self.thresholds['IdleHold']: state_next = States.HOLD self.pid_rate.reset() elif self.state == States.HOLD: - if diff >= THRESH_HOLD_HEAT: + if diff >= self.thresholds['HoldHeat']: state_next = States.HEAT self.pid_rate.reset() - elif diff <= -THRESH_HOLD_IDLE: + elif diff <= -self.thresholds['HoldIdle']: state_next = States.IDLE elif self.state == States.HEAT: - if diff <= -THRESH_HEAT_IDLE: + if diff <= -self.thresholds['HeatIdle']: state_next = States.IDLE - elif diff <= THRESH_HEAT_HOLD: + elif diff <= self.thresholds['HeatHold']: state_next = States.HOLD self.pid_hold.reset() diff --git a/config.json.templ b/config.json.templ index 889b62c..c0a323c 100644 --- a/config.json.templ +++ b/config.json.templ @@ -25,6 +25,14 @@ "ki": 0.01, "kd": 0.0, "kt": 1.5 + }, + "Thresholds": { + "HoldIdle": 0.1, + "HoldHeat": 1.0, + "IdleHeat": 1.0, + "IdleHold": 0.1, + "HeatHold": 1.0, + "HeatIdle": 1.0 } }, "Model": {