diff --git a/server/brewpi.py b/server/brewpi.py index 5d4b9ab..cfd84ee 100755 --- a/server/brewpi.py +++ b/server/brewpi.py @@ -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. diff --git a/tasks/sud.py b/tasks/sud.py index ef24744..a9331b2 100644 --- a/tasks/sud.py +++ b/tasks/sud.py @@ -61,7 +61,10 @@ class SudTask(ATask): from it (constant for the whole brew), while M/C also fold in the step's grain_mass/water_mass, which vary over the brew's course (malt going in, water boiling off) - mirrors demo_sud.py's - apply_plant_params().""" + apply_plant_params(). Called both on every real step transition + (via on_step_changed()) and once immediately on Load (see recv()), + so the controller's behavior already matches the expected plant + as soon as a Sud is loaded, not just once a run actually starts.""" params = self.sud.derive_plant_params(step.get('grain_mass', 0), step.get('water_mass', 0)) self.pot.set_plant_params(params) if hasattr(self.tc, 'set_model_plant_params'): @@ -258,6 +261,15 @@ class SudTask(ATask): if self.sud.load(pair[1]): await self.send({'Name': self.sud.name, 'Description': self.sud.description}) await self.send({'Json': pair[1]}) + # on_step_changed() only re-applies plant params once a + # real step starts (Start) - apply them right away too, + # so the controller already matches this Sud's own pot/ + # L/Td (and its first step's expected grain/water mass) + # from the moment it's loaded, rather than whatever the + # previously loaded Sud (or the generic startup + # baseline - see server/brewpi.py) left behind. + if self.sud.schedule: + self.apply_plant_params(self.sud.schedule[0]) await self.send_forecast(pair[1]) else: # Sud.load() refuses while a run is in progress (state