diff --git a/components/sud.py b/components/sud.py index 2bc4ef3..b258f2a 100644 --- a/components/sud.py +++ b/components/sud.py @@ -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 diff --git a/components/sud_forecast.py b/components/sud_forecast.py index bd6bd27..f62a956 100644 --- a/components/sud_forecast.py +++ b/components/sud_forecast.py @@ -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, {} diff --git a/config-real.json.tpl b/config-real.json.tpl index 8fa8fbf..d1a161d 100644 --- a/config-real.json.tpl +++ b/config-real.json.tpl @@ -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 } } diff --git a/config-sim.json.tpl b/config-sim.json.tpl index db62fb8..8bd8421 100644 --- a/config-sim.json.tpl +++ b/config-sim.json.tpl @@ -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 } } diff --git a/server/brewpi.py b/server/brewpi.py index bf09af1..fbc889c 100755 --- a/server/brewpi.py +++ b/server/brewpi.py @@ -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 diff --git a/sude/sud_0010.json b/sude/sud_0010.json index fd2491e..2515d81 100644 --- a/sude/sud_0010.json +++ b/sude/sud_0010.json @@ -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