Build the heater/pot/sensor rig as one unit, picked by plant_name alone

heater_name/sensor_name/plant_name were three independent config keys,
each picked via its own factory - in practice sim and real hardware
are never actually mixed and matched, so this could (nonsensically)
disagree, e.g. a real heater paired with a simulated sensor.

Add PlantFactory (components/plant/plant_factory.py): plant_name alone
now builds the whole rig together. "sim" gets HeaterSim/Pot (the
modeled plant, as before)/TempSensorSim. Anything else gets
HeaterHendi/PotReal/TempSensor_max31865 - PotReal is a new, deliberately
unmodeled Pot for the real, physical kettle (components/plant/pot_real.py):
the real temperature comes straight from the real sensor, not from a
model, so it just accepts and ignores set_plant_params()/
set_ambient_temperature()/initial() and reports no temperature of its
own, satisfying PotTask/SudTask's interface with nothing to actually
simulate.

heater_name/sensor_name are gone from config.json.templ; server/brewpi.py
delegates to HeaterFactory/TempSensorFactory lazily from inside
PlantFactory, same as before, so real hardware's spidev/pyserial deps
still aren't needed just to import the module.
This commit is contained in:
2026-06-23 20:22:51 +02:00
parent 67cf5fd00b
commit ead900b7b1
6 changed files with 116 additions and 29 deletions
+21 -16
View File
@@ -7,10 +7,9 @@ import time
from utils import ChangedFloat
from ws.message import MessageDispatcher
from ws.server.ws_server_multi_user import WsServerMultiUser
from components.sensor import TempSensorFactory
from components.pid import PidFactory
from components.plant import Pot
from components.actor import HeaterFactory, StirrerFactory
from components.plant import PlantFactory
from components.actor import StirrerFactory
from components.sud import Sud
from components.sud_forecast import SudForecastEstimator
from tasks import TaskManager, TempSensorTask, HeaterTask, PotTask, TcTask, StirrerTask, SudTask, SudLogTask
@@ -64,24 +63,29 @@ if __name__ == '__main__':
# several schedules onto it later (see components/sud.py's Sud).
sud = Sud()
# Sensor
sensor = TempSensorFactory.create(config['Controller']['sensor_name'], temp_offset=-0.15, variance=0.01)
# Heater/Pot/Sensor - built together as one consistent rig by a single
# Controller.plant_name, rather than three independently-named
# components that in practice are never actually mixed and matched
# (see components/plant/plant_factory.py): "sim" gets HeaterSim/Pot
# (the modeled plant)/TempSensorSim; anything else gets HeaterHendi/
# PotReal (no model - the real kettle does its own physics)/
# TempSensor_max31865.
heater, pot, sensor = PlantFactory.create(config['Controller']['plant_name'], DT, config['Heater'])
sensor_task = TempSensorTask(sensor, DT_TASK, dispatcher.msgio_get("Sensor"))
taskmgr.add(sensor_task)
# Plant - seeded with the not-yet-loaded sud's own (now physically
# sane, see Sud.EMPTY_SUD) plant params right away, same as ambient
# below, so the simulated Pot already runs - manual heater
# power/Sud disabled or not - instead of sitting inert until a real
# Sud loads. A real Sud's own doc still overrides these the moment
# one is loaded (see tasks/sud.py's SudTask.apply_plant_params()).
pot = Pot(DT)
# Pot - seeded with the not-yet-loaded sud's own (now physically sane,
# see Sud.EMPTY_SUD) plant params right away, same as ambient below, so
# a simulated Pot already runs - manual heater power/Sud disabled or
# not - instead of sitting inert until a real Sud loads (PotReal just
# ignores this - see there). A real Sud's own doc still overrides
# these the moment one is loaded (see tasks/sud.py's SudTask.
# apply_plant_params()).
pot.set_ambient_temperature(theta_amb)
pot.set_plant_params(sud.derive_plant_params(sud.grain_mass, sud.water_mass))
taskmgr.add(PotTask(pot, DT_TASK, dispatcher.msgio_get("Pot")))
# Heater
heater = HeaterFactory.create(config['Controller']['heater_name'], config['Heater'])
heater.set_on_changed("power_eff", ChangedFloat(pot.set_power, prec=0).set)
heater_task = HeaterTask(heater, DT_TASK, dispatcher.msgio_get("Heater"))
taskmgr.add(heater_task)
@@ -128,8 +132,9 @@ if __name__ == '__main__':
if hasattr(tc, "set_model_power"):
heater.set_on_changed("power_set", tc.set_model_power)
# For simulation
if "sim" in config['Controller']['sensor_name']:
# For simulation - the sim sensor has no real plant to read, so it
# just reports back whatever the modeled Pot's own temperature is.
if plant_sim:
pot.set_on_changed("temp", ChangedFloat(sensor.set_fake_temp, prec=3).set)
sensor.set_fake_temp(pot.temp)