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
This commit is contained in:
2026-06-22 18:31:58 +02:00
co-authored by Claude Sonnet 4.6
parent 0a6d3e6462
commit 96fe7ce90c
8 changed files with 50 additions and 13 deletions
+3 -4
View File
@@ -2,16 +2,15 @@ from components.pid.temp_controller_base import TempControllerBase
class TempController(TempControllerBase):
def __init__(self, dt, model_params=None, theta_amb=20):
# model_params/theta_amb are accepted (but unused) for constructor
# compatibility with TempControllerSmith - PidFactory.create() calls
# either with the same arguments; "Normal" has no internal model.
def __init__(self, dt):
TempControllerBase.__init__(self, dt)
self.dt = dt
self.last_theta_ist = 20
self.heatrate_ist = 0
def process(self):
self._require_params()
# Process Kalman
self.theta_ist = self.theta_ist_set
heatrate = (self.theta_ist - self.last_theta_ist)/self.dt*60
+10
View File
@@ -65,6 +65,16 @@ class TempControllerBase(APid):
self.pid_heat.set_params(params['Heat'])
self.pid_cool.set_params(params['Cool'])
def _require_params(self):
"""Called by each subclass's process() before doing anything else -
without it, a missing set_params() call would otherwise only
surface as an unhelpful "'NoneType' object is not subscriptable"
buried inside Pid.process() (components/pid/pid.py), once
process_pid() below gets to it."""
if self.params is None:
raise RuntimeError(
"{}.process(): PID gains not set - call set_params() first".format(type(self).__name__))
def on_state_entered(self, state):
pass
+11 -5
View File
@@ -3,7 +3,7 @@ from components.pid.temp_controller_base import TempControllerBase, States
class TempController(TempControllerBase):
def __init__(self, dt, model_params, theta_amb=20):
def __init__(self, dt):
TempControllerBase.__init__(self, dt)
self.dt = dt
self.last_theta_ist = 20
@@ -12,18 +12,18 @@ class TempController(TempControllerBase):
# Fast model: same plant model but with zero transport delay, used
# to predict the current temperature without the dead time.
self.model = Pot(dt)
self.model.set_plant_params({**model_params, 'Td': 0})
self.model.set_ambient_temperature(theta_amb)
# Delayed model: keeps the plant's assumed transport delay, so it
# can be compared like-for-like against the real (delayed) measurement.
self.model_delay = Pot(dt)
self.model_delay.set_plant_params(model_params)
self.model_delay.set_ambient_temperature(theta_amb)
self.theta_ist_plant = 0
self.theta_ist_model = 0
self.theta_ist_model_delay = 0
def set_model_plant_params(self, model_params):
self.model.set_plant_params({**model_params, 'Td': 0})
self.model_delay.set_plant_params(model_params)
def set_model_power(self, power):
self.model.set_power(power)
self.model_delay.set_power(power)
@@ -46,6 +46,12 @@ class TempController(TempControllerBase):
self.model_delay.process()
def process(self):
self._require_params()
if not (self.model.is_configured() and self.model_delay.is_configured()):
raise RuntimeError(
"{}.process(): model plant params and/or ambient temperature not set - "
"call set_model_plant_params() and set_ambient_temperature() first".format(type(self).__name__))
self.theta_ist_plant = self.theta_ist_set
self.theta_ist_model = self.model.get_temperature()
self.theta_ist_model_delay = self.model_delay.get_temperature()