refactor: move pot hardware params and pid_type out of per-brew docs
Pot hardware constants (mass, material, L, Td) moved from sude/*.json to config.json's Pot section; Sud/SudForecastEstimator accept pot_config as baseline, per-brew pot sections may still override. Controller key removed; pid_type moved into TempCtrl. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+20
-10
@@ -82,28 +82,38 @@ EMPTY_SUD = {
|
||||
|
||||
|
||||
class Sud(AttributeChange):
|
||||
def __init__(self):
|
||||
def __init__(self, pot_config=None):
|
||||
"""Starts out with no schedule loaded - one of potentially several
|
||||
sude/*.json files on disk is brought in later via load(), kept
|
||||
purely in memory (see load()) until replaced or the server
|
||||
restarts."""
|
||||
restarts.
|
||||
|
||||
pot_config provides hardware baseline values (mass, material, L, Td)
|
||||
from config.json's Pot section - a loaded sud.json may override any
|
||||
of them per-brew, but config is the source of truth for the physical
|
||||
kettle."""
|
||||
AttributeChange.__init__(self)
|
||||
self._pot_config = pot_config or {}
|
||||
self._data = EMPTY_SUD
|
||||
|
||||
(self.name, self.description, self.schedule, self.pot_mass,
|
||||
self.pot_material, self.L, self.Td, self.grain_mass,
|
||||
self.water_mass) = self._parse_data(self._data)
|
||||
self.water_mass) = self._parse_data(self._data, self._pot_config)
|
||||
|
||||
self._paused_from = None
|
||||
self._reset_run_state()
|
||||
|
||||
@staticmethod
|
||||
def _parse_data(data):
|
||||
def _parse_data(data, pot_config=None):
|
||||
"""Parses a sud.json document into the (name, description, schedule,
|
||||
pot_mass, pot_material, L, Td, grain_mass, water_mass) tuple Sud
|
||||
needs. Computed up front rather than assigned straight onto self,
|
||||
so a malformed load() can't leave a half-applied schedule in
|
||||
place."""
|
||||
place.
|
||||
|
||||
pot_config (from config.json's Pot section) provides hardware baseline
|
||||
values; a sud.json's own pot section overrides any of them per-brew."""
|
||||
pot_config = pot_config or {}
|
||||
name = data.get('Name', '')
|
||||
description = data.get('Description', '')
|
||||
|
||||
@@ -111,10 +121,10 @@ class Sud(AttributeChange):
|
||||
schedule = [_build_step(i + 1, step_defaults, raw) for i, raw in enumerate(data['steps'])]
|
||||
|
||||
pot_data = data.get('pot', {})
|
||||
pot_mass = pot_data.get('mass', 0)
|
||||
pot_material = pot_data.get('material')
|
||||
L = pot_data.get('L', EMPTY_SUD['pot']['L'])
|
||||
Td = pot_data.get('Td', EMPTY_SUD['pot']['Td'])
|
||||
pot_mass = pot_data.get('mass', pot_config.get('mass', 0))
|
||||
pot_material = pot_data.get('material', pot_config.get('material'))
|
||||
L = pot_data.get('L', pot_config.get('L', EMPTY_SUD['pot']['L']))
|
||||
Td = pot_data.get('Td', pot_config.get('Td', EMPTY_SUD['pot']['Td']))
|
||||
grain_mass = pot_data.get('grain_mass', EMPTY_SUD['pot']['grain_mass'])
|
||||
water_mass = pot_data.get('water_mass', EMPTY_SUD['pot']['water_mass'])
|
||||
|
||||
@@ -147,7 +157,7 @@ class Sud(AttributeChange):
|
||||
if self.state not in (SudState.IDLE, SudState.DONE):
|
||||
return False
|
||||
try:
|
||||
parsed = self._parse_data(data)
|
||||
parsed = self._parse_data(data, self._pot_config)
|
||||
except (KeyError, TypeError):
|
||||
return False
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ class SudForecastEstimator:
|
||||
real run actually is (the discrete steps end up duty-cycling it back
|
||||
down to something close to the commanded average)."""
|
||||
|
||||
def __init__(self, dt, theta_amb, pid_type, tempctrl_params, heater_powers, sim_warp_factor):
|
||||
def __init__(self, dt, theta_amb, pid_type, tempctrl_params, heater_powers, sim_warp_factor, pot_config=None):
|
||||
self.dt = dt
|
||||
self.theta_amb = theta_amb
|
||||
self.pid_type = pid_type
|
||||
@@ -61,6 +61,7 @@ class SudForecastEstimator:
|
||||
# pair straddling the commanded power.
|
||||
self.heater_powers = heater_powers
|
||||
self.sim_warp_factor = sim_warp_factor
|
||||
self.pot_config = pot_config or {}
|
||||
|
||||
def set_ambient_temperature(self, theta_amb):
|
||||
self.theta_amb = theta_amb
|
||||
@@ -93,7 +94,7 @@ class SudForecastEstimator:
|
||||
if start_theta is None:
|
||||
start_theta = self.theta_amb
|
||||
|
||||
sud = Sud()
|
||||
sud = Sud(self.pot_config)
|
||||
if not sud.load(doc) or not sud.schedule:
|
||||
return [0.0], [start_theta], SudState.DONE, {}
|
||||
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
{
|
||||
"ambient_temperature": 20,
|
||||
"Controller" : {
|
||||
"pid_type": "Smith"
|
||||
},
|
||||
"TempCtrl": {
|
||||
"pid_type": "Smith",
|
||||
"beta": 0.05,
|
||||
"Hold": {
|
||||
"kp": 0.4,
|
||||
@@ -45,5 +43,11 @@
|
||||
"TempSensor": {
|
||||
"type": "max31865",
|
||||
"temp_offset": -0.15
|
||||
},
|
||||
"Pot": {
|
||||
"mass": 5.96,
|
||||
"material": "Edelstahl 18/10",
|
||||
"L": 0.2,
|
||||
"Td": 17
|
||||
}
|
||||
}
|
||||
|
||||
+7
-3
@@ -1,9 +1,7 @@
|
||||
{
|
||||
"ambient_temperature": 20,
|
||||
"Controller" : {
|
||||
"pid_type": "Smith"
|
||||
},
|
||||
"TempCtrl": {
|
||||
"pid_type": "Smith",
|
||||
"beta": 0.05,
|
||||
"Hold": {
|
||||
"kp": 0.4,
|
||||
@@ -44,5 +42,11 @@
|
||||
"sigma": 0.05,
|
||||
"stirrer_sigma": 0.0,
|
||||
"stirrer_tau": 20.0
|
||||
},
|
||||
"Pot": {
|
||||
"mass": 5.96,
|
||||
"material": "Edelstahl 18/10",
|
||||
"L": 0.2,
|
||||
"Td": 17
|
||||
}
|
||||
}
|
||||
|
||||
+4
-4
@@ -81,7 +81,7 @@ if __name__ == '__main__':
|
||||
|
||||
# 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()
|
||||
sud = Sud(config.get('Pot', {}))
|
||||
|
||||
# Heater/Pot/Sensor - built together as one consistent rig; each
|
||||
# component's type comes from its own config section. Heater.type==sim
|
||||
@@ -113,7 +113,7 @@ if __name__ == '__main__':
|
||||
# temp_controller_smith.py.), and Smith's model plant params are -
|
||||
# like the real Pot's above - deliberately left unset here, only
|
||||
# ever coming from a Sud's own doc.
|
||||
tc = PidFactory.create(config['Controller']['pid_type'], DT)
|
||||
tc = PidFactory.create(config['TempCtrl']['pid_type'], DT)
|
||||
tc.set_params(config['TempCtrl'])
|
||||
tc.set_ambient_temperature(theta_amb)
|
||||
tc_task = TcTask(tc, DT_TASK, dispatcher.msgio_get("TempCtrl"))
|
||||
@@ -128,8 +128,8 @@ if __name__ == '__main__':
|
||||
# it with the same kind of plant/controller (and params) as above -
|
||||
# see components/sud_forecast.py.
|
||||
forecast_estimator = SudForecastEstimator(
|
||||
DT, theta_amb, config['Controller']['pid_type'], config['TempCtrl'],
|
||||
heater.get_powers(), args.sim_warp_factor)
|
||||
DT, theta_amb, config['TempCtrl']['pid_type'], config['TempCtrl'],
|
||||
heater.get_powers(), args.sim_warp_factor, config.get('Pot', {}))
|
||||
sud_task = SudTask(sud, tc, stirrer, pot, DT, DT_TASK, dispatcher.msgio_get("Sud"), forecast_estimator)
|
||||
taskmgr.add(sud_task)
|
||||
# Records every Sud run's measured data and forecast to their own
|
||||
|
||||
@@ -2,10 +2,6 @@
|
||||
"Name": "Sud-0010",
|
||||
"Description": "Rotfraenkisch, Dunkles Lager",
|
||||
"pot": {
|
||||
"mass": 5.96,
|
||||
"material": "Edelstahl 18/10",
|
||||
"L": 0.2,
|
||||
"Td": 17,
|
||||
"grain_mass": 0,
|
||||
"water_mass": 22,
|
||||
"volumen": 30
|
||||
|
||||
Reference in New Issue
Block a user