Apply a loaded Sud's plant params immediately, not just once a run starts

on_step_changed() only re-applies plant params (pot mass/material, L, Td,
grain/water mass) once Sud.step actually changes to a real step - which
doesn't happen on Load (Sud.load() resets step to None), so the controller
kept using whatever the previously loaded Sud (or the generic startup
baseline) had until Start. SudTask.recv()'s Load handler now calls
apply_plant_params() against the new schedule's first step right away, so
the controller already matches the expected plant the moment a Sud is
loaded.

Also replaces server/brewpi.py's hardcoded DEFAULT_PLANT_PARAMS with a
baseline derived from Sud's own (still-unloaded) generic defaults
(EMPTY_SUD's pot_mass/L/Td), used only for the brief startup window before
any real Sud is ever loaded - numerically identical to the old constant.

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 19:37:20 +02:00
co-authored by Claude Sonnet 4.6
parent 868c35f4e8
commit 51add6fd56
2 changed files with 26 additions and 15 deletions
+13 -14
View File
@@ -17,15 +17,6 @@ from tasks import TaskManager, TempSensorTask, HeaterTask, PotTask, TcTask, Stir
import argparse as ap
# Default lumped thermal params, shared by both the real Pot plant and the
# temperature controller's internal Smith-predictor model.
DEFAULT_PLANT_PARAMS = {
"C": 4190,
"M": 25,
"L": 0.2,
"Td": 30
}
class Tee:
"""Duplicates writes to multiple streams - used to mirror stdout/stderr
@@ -70,6 +61,17 @@ if __name__ == '__main__':
theta_amb = config['ambient_temperature']
plant_sim = "sim" in config['Controller']['plant_name']
# Mash schedule - starts out empty; a client loads one of sude/*.json's
# several schedules onto it later (see components/sud.py's Sud). Also
# doubles as the source of baseline plant params for the Plant/
# Temperature Controller below, before any real schedule is loaded -
# derive_plant_params() falls back to Sud's own (generic) defaults
# while unloaded (see components/sud.py's EMPTY_SUD); water_mass=25
# is just a placeholder thermal mass standing in for "some water in
# the pot," not a literal recipe.
sud = Sud()
baseline_plant_params = sud.derive_plant_params(0, 25)
# Sensor
sensor = TempSensorFactory.create(config['Controller']['sensor_name'], temp_offset=-0.15, variance=0.01)
sensor_task = TempSensorTask(sensor, DT_TASK, dispatcher.msgio_get("Sensor"))
@@ -77,7 +79,7 @@ if __name__ == '__main__':
# Plant
pot = Pot(DT)
pot.set_plant_params(DEFAULT_PLANT_PARAMS)
pot.set_plant_params(baseline_plant_params)
pot.set_ambient_temperature(theta_amb)
taskmgr.add(PotTask(pot, DT_TASK, dispatcher.msgio_get("Pot")))
@@ -93,7 +95,7 @@ if __name__ == '__main__':
# "Normal" has no internal model/ambient - see temp_controller.py vs.
# temp_controller_smith.py.
if hasattr(tc, 'set_model_plant_params'):
tc.set_model_plant_params(DEFAULT_PLANT_PARAMS)
tc.set_model_plant_params(baseline_plant_params)
if hasattr(tc, 'set_ambient_temperature'):
tc.set_ambient_temperature(theta_amb)
tc_task = TcTask(tc, DT_TASK, dispatcher.msgio_get("TempCtrl"))
@@ -104,9 +106,6 @@ if __name__ == '__main__':
stirrer_task = StirrerTask(stirrer, DT_TASK, dispatcher.msgio_get("Stirrer"))
taskmgr.add(stirrer_task)
# Mash schedule - starts out empty; a client loads one of sude/*.json's
# several schedules onto it later (see components/sud.py's Sud).
sud = Sud()
# Predicts how long a loaded schedule will actually take, by simulating
# it with the same kind of plant/controller (and params) as above -
# see components/sud_forecast.py.