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):
|
class Sud(AttributeChange):
|
||||||
def __init__(self):
|
def __init__(self, pot_config=None):
|
||||||
"""Starts out with no schedule loaded - one of potentially several
|
"""Starts out with no schedule loaded - one of potentially several
|
||||||
sude/*.json files on disk is brought in later via load(), kept
|
sude/*.json files on disk is brought in later via load(), kept
|
||||||
purely in memory (see load()) until replaced or the server
|
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)
|
AttributeChange.__init__(self)
|
||||||
|
self._pot_config = pot_config or {}
|
||||||
self._data = EMPTY_SUD
|
self._data = EMPTY_SUD
|
||||||
|
|
||||||
(self.name, self.description, self.schedule, self.pot_mass,
|
(self.name, self.description, self.schedule, self.pot_mass,
|
||||||
self.pot_material, self.L, self.Td, self.grain_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._paused_from = None
|
||||||
self._reset_run_state()
|
self._reset_run_state()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _parse_data(data):
|
def _parse_data(data, pot_config=None):
|
||||||
"""Parses a sud.json document into the (name, description, schedule,
|
"""Parses a sud.json document into the (name, description, schedule,
|
||||||
pot_mass, pot_material, L, Td, grain_mass, water_mass) tuple Sud
|
pot_mass, pot_material, L, Td, grain_mass, water_mass) tuple Sud
|
||||||
needs. Computed up front rather than assigned straight onto self,
|
needs. Computed up front rather than assigned straight onto self,
|
||||||
so a malformed load() can't leave a half-applied schedule in
|
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', '')
|
name = data.get('Name', '')
|
||||||
description = data.get('Description', '')
|
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'])]
|
schedule = [_build_step(i + 1, step_defaults, raw) for i, raw in enumerate(data['steps'])]
|
||||||
|
|
||||||
pot_data = data.get('pot', {})
|
pot_data = data.get('pot', {})
|
||||||
pot_mass = pot_data.get('mass', 0)
|
pot_mass = pot_data.get('mass', pot_config.get('mass', 0))
|
||||||
pot_material = pot_data.get('material')
|
pot_material = pot_data.get('material', pot_config.get('material'))
|
||||||
L = pot_data.get('L', EMPTY_SUD['pot']['L'])
|
L = pot_data.get('L', pot_config.get('L', EMPTY_SUD['pot']['L']))
|
||||||
Td = pot_data.get('Td', EMPTY_SUD['pot']['Td'])
|
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'])
|
grain_mass = pot_data.get('grain_mass', EMPTY_SUD['pot']['grain_mass'])
|
||||||
water_mass = pot_data.get('water_mass', EMPTY_SUD['pot']['water_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):
|
if self.state not in (SudState.IDLE, SudState.DONE):
|
||||||
return False
|
return False
|
||||||
try:
|
try:
|
||||||
parsed = self._parse_data(data)
|
parsed = self._parse_data(data, self._pot_config)
|
||||||
except (KeyError, TypeError):
|
except (KeyError, TypeError):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ class SudForecastEstimator:
|
|||||||
real run actually is (the discrete steps end up duty-cycling it back
|
real run actually is (the discrete steps end up duty-cycling it back
|
||||||
down to something close to the commanded average)."""
|
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.dt = dt
|
||||||
self.theta_amb = theta_amb
|
self.theta_amb = theta_amb
|
||||||
self.pid_type = pid_type
|
self.pid_type = pid_type
|
||||||
@@ -61,6 +61,7 @@ class SudForecastEstimator:
|
|||||||
# pair straddling the commanded power.
|
# pair straddling the commanded power.
|
||||||
self.heater_powers = heater_powers
|
self.heater_powers = heater_powers
|
||||||
self.sim_warp_factor = sim_warp_factor
|
self.sim_warp_factor = sim_warp_factor
|
||||||
|
self.pot_config = pot_config or {}
|
||||||
|
|
||||||
def set_ambient_temperature(self, theta_amb):
|
def set_ambient_temperature(self, theta_amb):
|
||||||
self.theta_amb = theta_amb
|
self.theta_amb = theta_amb
|
||||||
@@ -93,7 +94,7 @@ class SudForecastEstimator:
|
|||||||
if start_theta is None:
|
if start_theta is None:
|
||||||
start_theta = self.theta_amb
|
start_theta = self.theta_amb
|
||||||
|
|
||||||
sud = Sud()
|
sud = Sud(self.pot_config)
|
||||||
if not sud.load(doc) or not sud.schedule:
|
if not sud.load(doc) or not sud.schedule:
|
||||||
return [0.0], [start_theta], SudState.DONE, {}
|
return [0.0], [start_theta], SudState.DONE, {}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
{
|
{
|
||||||
"ambient_temperature": 20,
|
"ambient_temperature": 20,
|
||||||
"Controller" : {
|
|
||||||
"pid_type": "Smith"
|
|
||||||
},
|
|
||||||
"TempCtrl": {
|
"TempCtrl": {
|
||||||
|
"pid_type": "Smith",
|
||||||
"beta": 0.05,
|
"beta": 0.05,
|
||||||
"Hold": {
|
"Hold": {
|
||||||
"kp": 0.4,
|
"kp": 0.4,
|
||||||
@@ -45,5 +43,11 @@
|
|||||||
"TempSensor": {
|
"TempSensor": {
|
||||||
"type": "max31865",
|
"type": "max31865",
|
||||||
"temp_offset": -0.15
|
"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,
|
"ambient_temperature": 20,
|
||||||
"Controller" : {
|
|
||||||
"pid_type": "Smith"
|
|
||||||
},
|
|
||||||
"TempCtrl": {
|
"TempCtrl": {
|
||||||
|
"pid_type": "Smith",
|
||||||
"beta": 0.05,
|
"beta": 0.05,
|
||||||
"Hold": {
|
"Hold": {
|
||||||
"kp": 0.4,
|
"kp": 0.4,
|
||||||
@@ -44,5 +42,11 @@
|
|||||||
"sigma": 0.05,
|
"sigma": 0.05,
|
||||||
"stirrer_sigma": 0.0,
|
"stirrer_sigma": 0.0,
|
||||||
"stirrer_tau": 20.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
|
# Mash schedule - starts out empty; a client loads one of sude/*.json's
|
||||||
# several schedules onto it later (see components/sud.py's Sud).
|
# 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
|
# Heater/Pot/Sensor - built together as one consistent rig; each
|
||||||
# component's type comes from its own config section. Heater.type==sim
|
# 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 -
|
# temp_controller_smith.py.), and Smith's model plant params are -
|
||||||
# like the real Pot's above - deliberately left unset here, only
|
# like the real Pot's above - deliberately left unset here, only
|
||||||
# ever coming from a Sud's own doc.
|
# 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_params(config['TempCtrl'])
|
||||||
tc.set_ambient_temperature(theta_amb)
|
tc.set_ambient_temperature(theta_amb)
|
||||||
tc_task = TcTask(tc, DT_TASK, dispatcher.msgio_get("TempCtrl"))
|
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 -
|
# it with the same kind of plant/controller (and params) as above -
|
||||||
# see components/sud_forecast.py.
|
# see components/sud_forecast.py.
|
||||||
forecast_estimator = SudForecastEstimator(
|
forecast_estimator = SudForecastEstimator(
|
||||||
DT, theta_amb, config['Controller']['pid_type'], config['TempCtrl'],
|
DT, theta_amb, config['TempCtrl']['pid_type'], config['TempCtrl'],
|
||||||
heater.get_powers(), args.sim_warp_factor)
|
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)
|
sud_task = SudTask(sud, tc, stirrer, pot, DT, DT_TASK, dispatcher.msgio_get("Sud"), forecast_estimator)
|
||||||
taskmgr.add(sud_task)
|
taskmgr.add(sud_task)
|
||||||
# Records every Sud run's measured data and forecast to their own
|
# Records every Sud run's measured data and forecast to their own
|
||||||
|
|||||||
@@ -2,10 +2,6 @@
|
|||||||
"Name": "Sud-0010",
|
"Name": "Sud-0010",
|
||||||
"Description": "Rotfraenkisch, Dunkles Lager",
|
"Description": "Rotfraenkisch, Dunkles Lager",
|
||||||
"pot": {
|
"pot": {
|
||||||
"mass": 5.96,
|
|
||||||
"material": "Edelstahl 18/10",
|
|
||||||
"L": 0.2,
|
|
||||||
"Td": 17,
|
|
||||||
"grain_mass": 0,
|
"grain_mass": 0,
|
||||||
"water_mass": 22,
|
"water_mass": 22,
|
||||||
"volumen": 30
|
"volumen": 30
|
||||||
|
|||||||
Reference in New Issue
Block a user