diff --git a/tests/test_coordinator.py b/tests/test_coordinator.py index a88c10e..0f194e9 100644 --- a/tests/test_coordinator.py +++ b/tests/test_coordinator.py @@ -1033,3 +1033,36 @@ class BoundaryTests(unittest.IsolatedAsyncioTestCase): for zone, other in (('living', 'bed'), ('bed', 'living')): with self.subTest(zone=zone): self.assertLess(c.extended[zone].coefficient(f'couple:{other}'), 0.002) + + async def test_new_model_layers_survive_a_restart(self): + self.c = self.bedroom_with_window() + self.c.entry.subentries['h'] = types.SimpleNamespace(subentry_id='h', subentry_type='heat_source', data={ + 'key': 'ups', 'name': 'UPS', 'sensor': 'sensor.ups_power', 'zone': 'bed'}) + self.set_state('sensor.ups_power', '200', unit_of_measurement='W') + self.c = coordinator.HeatingMPCCoordinator(self.hass, self.entry) + for k in range(20): + self.fresh('sensor.room', 'sensor.outside', 'sensor.wind', 'sensor.wind_dir', 'sensor.ups_power') + if k == 18: + self.open_window('on', 240) + await self.sample() + extended, vent = self.c.extended['bed'], self.c.ventilation['bed'] + self.assertGreater(extended.count, 0) + self.assertGreater(vent.count, 0) + payload = self.c._store.async_save.call_args.args[0] + restored = coordinator.HeatingMPCCoordinator(self.hass, self.entry) + restored._store.async_load.return_value = payload + await restored.async_restore_model() + self.assertEqual((restored.extended['bed'].theta, restored.extended['bed'].count), (extended.theta, extended.count)) + self.assertEqual((restored.ventilation['bed'].theta, restored.ventilation['bed'].count), (vent.theta, vent.count)) + self.assertEqual(restored._heat_sources._baselines['ups'].value(), 200) # no hour of re-learning + # Adding a feature keeps what was learned for the others. + self.entry.data['ventilation_cross'] = True + self.entry.subentries['z2'] = types.SimpleNamespace(subentry_id='z2', subentry_type='zone', data={ + 'key': 'living', 'name': 'Living', 'sensors': ['sensor.living']}) + self.entry.subentries['w2'] = types.SimpleNamespace(subentry_id='w2', subentry_type='window', data={ + 'key': 'living_window', 'name': 'Living window', 'sensor': 'binary_sensor.living_window', 'zone': 'living'}) + grown = coordinator.HeatingMPCCoordinator(self.hass, self.entry) + grown._store.async_load.return_value = payload + await grown.async_restore_model() + self.assertEqual(grown.ventilation['bed'].features[-1], 'vent_cross') + self.assertEqual(grown.ventilation['bed'].theta[:-1], vent.theta) diff --git a/tests/test_heat_sources.py b/tests/test_heat_sources.py index f88380a..ec3b58e 100644 --- a/tests/test_heat_sources.py +++ b/tests/test_heat_sources.py @@ -93,6 +93,14 @@ class TrackerTests(unittest.TestCase): self.assertEqual(tracker.zone_input('office'), states[-1]['ups']['response_kw']) self.assertEqual(tracker.zone_input('living'), 0.0) + def test_short_spike_barely_moves_response_or_baseline(self): + tracker = hs.HeatSourceTracker(sources(UPS), {'baselines': {'ups': [200.0] * 24}}) + states = run(tracker, [{'ups': 200.0}] * 3 + [{'ups': 900.0}] + [{'ups': 200.0}] * 4) + peak = max(s['ups']['response_kw'] for s in states) + self.assertAlmostEqual(peak, 0.7 * (1 - exp(-1 / 3))) # one 5-minute interval of +700 W + self.assertLess(peak, 0.2) + self.assertEqual(states[-1]['ups']['baseline_w'], 200) + def test_unknown_power_resets_until_settled_again(self): tracker = hs.HeatSourceTracker(sources(UPS), {'baselines': {'ups': [200.0] * 24}}) run(tracker, [{'ups': 200.0}] * 7) diff --git a/tests/test_windows.py b/tests/test_windows.py index 9728048..dfc1e01 100644 --- a/tests/test_windows.py +++ b/tests/test_windows.py @@ -55,6 +55,10 @@ class GeometryTests(unittest.TestCase): [-9.0, -36.0, 21.6, -45.0, -9.0, 0.0]): self.assertAlmostEqual(value, expected) self.assertIsNone(wm.ventilation_vector(['vent_wind'], 20, 2, {**inputs, 'wind': None})) + # Calm air: wind terms vanish, natural ventilation stays. + calm = wm.ventilation_vector(features, 20.0, 2.0, {**inputs, 'wind': 0.0, 'gust': 0.0}) + self.assertEqual([round(v, 9) for v in calm[1:4]], [0.0, 0.0, 0.0]) + self.assertEqual(calm[0], -9.0) self.assertEqual(wm.ventilation_vector(['vent_natural'], 20, 2, {**inputs, 'wind': None}), [-9.0]) self.assertIsNone(wm.ventilation_vector(['recovery'], 20, 2, {**inputs, 'recovery': 0.5})) self.assertEqual(wm.ventilation_vector(['recovery'], 19, 2, {**inputs, 'recovery': 0.5, 't_ref': 21}),