def test_output_actions(self): floor = Floor.create(number=5) room = Room.create(number=10, floor=floor) Output.create(number=2, room=room) Output.create(number=3) with mock.patch.object(self.master_controller, 'set_all_lights') as call: self.controller.set_all_lights(action='OFF') call.assert_called_once_with(action='OFF') with mock.patch.object(self.master_controller, 'set_all_lights') as call: self.controller.set_all_lights(action='ON') call.assert_called_once_with(action='ON') with mock.patch.object(self.master_controller, 'set_all_lights') as call: self.controller.set_all_lights(action='OFF', floor_id=1) call.assert_called_once_with(action='OFF', floor_id=1, output_ids=[]) with mock.patch.object(self.master_controller, 'set_all_lights') as call: self.controller.set_all_lights(action='OFF', floor_id=5) call.assert_called_once_with(action='OFF', floor_id=5, output_ids=[2]) with mock.patch.object(self.master_controller, 'set_all_lights') as call: self.controller.set_all_lights(action='ON', floor_id=5) call.assert_called_once_with(action='ON', floor_id=5, output_ids=[2])
def _save_pump_groups( mode, pump_groups ): # type: (str, List[Tuple[PumpGroupDTO, List[str]]]) -> None for pump_group_dto, fields in pump_groups: if 'pump_output_id' in fields and 'valve_output_ids' in fields: valve_output_ids = pump_group_dto.valve_output_ids pump = Pump.get(id=pump_group_dto.id) # type: Pump pump.output = Output.get(number=pump_group_dto.pump_output_id) links = { pump_to_valve.valve.output.number: pump_to_valve for pump_to_valve in PumpToValve.select( PumpToValve, Pump, Valve, Output).join_from( PumpToValve, Valve).join_from( PumpToValve, Pump).join_from(Valve, Output). join_from(Valve, ValveToThermostat).where(( ValveToThermostat.mode == mode) & (Pump.id == pump.id)) } for output_id in list(links.keys()): if output_id not in valve_output_ids: pump_to_valve = links.pop( output_id) # type: PumpToValve pump_to_valve.delete_instance() else: valve_output_ids.remove(output_id) for output_id in valve_output_ids: output = Output.get(number=output_id) valve = Valve.get_or_none(output=output) if valve is None: valve = Valve(name=output.name, output=output) valve.save() PumpToValve.create(pump=pump, valve=valve)
def test_get_output_status(self): select_mock = mock.Mock() select_mock.join_from.return_value = [ Output(id=0, number=2), Output(id=1, number=40, room=Room(id=2, number=3)) ] with mock.patch.object(Output, 'select', return_value=select_mock), \ mock.patch.object(self.master_controller, 'load_output', side_effect=lambda output_id: OutputDTO(id=output_id)), \ mock.patch.object(self.master_controller, 'load_output_status', return_value=[OutputStateDTO(id=2, status=False), OutputStateDTO(id=40, status=True)]): self.controller._sync_state() status = self.controller.get_output_status(40) assert status == OutputStateDTO(id=40, status=True)
def test_get_output_statuses(self): select_mock = mock.Mock() select_mock.join_from.return_value = [ Output(id=0, number=2), Output(id=1, number=40, module_type='D', room=Room(id=2, number=3)) ] with mock.patch.object(Output, 'select', return_value=select_mock), \ mock.patch.object(self.master_controller, 'load_output', side_effect=lambda output_id: OutputDTO(id=output_id)), \ mock.patch.object(self.master_controller, 'load_output_status', return_value=[OutputStateDTO(id=2, status=False, dimmer=0), OutputStateDTO(id=40, status=True, dimmer=50)]): self.controller._sync_state() status = self.controller.get_output_statuses() assert len(status) == 2 assert OutputStateDTO(id=2, status=False) in status assert OutputStateDTO(id=40, status=True, dimmer=50) in status
def load_output(self, output_id): # type: (int) -> OutputDTO output = Output.select(Room) \ .join_from(Output, Room, join_type=JOIN.LEFT_OUTER) \ .where(Output.number == output_id) \ .get() # type: Output # TODO: Load dict output_dto = self._master_controller.load_output(output_id=output_id) output_dto.room = output.room.number if output.room is not None else None return output_dto
def test_load_outputs(self): select_mock = mock.Mock() select_mock.join_from.return_value = [ Output(id=1, number=42, room=Room(id=2, number=3)) ] with mock.patch.object(Output, 'select', return_value=select_mock), \ mock.patch.object(self.master_controller, 'load_output', return_value=OutputDTO(id=42)) as load: outputs = self.controller.load_outputs() assert OutputDTO(id=42, room=3) in outputs load.assert_called_with(output_id=42)
def load_outputs(self): # type: () -> List[OutputDTO] output_dtos = [] for output in list( Output.select(Output, Room).join_from( Output, Room, join_type=JOIN.LEFT_OUTER)): # TODO: Load dicts output_dto = self._master_controller.load_output( output_id=output.number) output_dto.room = output.room.number if output.room is not None else None output_dtos.append(output_dto) self._cache.update_outputs(output_dtos) return output_dtos
def save_thermostat_group(self, thermostat_group): # type: (Tuple[ThermostatGroupDTO, List[str]]) -> None thermostat_group_dto, fields = thermostat_group # Update thermostat group configuration orm_object = ThermostatGroup.get(number=0) # type: ThermostatGroup if 'outside_sensor_id' in fields: orm_object.sensor = Sensor.get( number=thermostat_group_dto.outside_sensor_id) if 'threshold_temperature' in fields: orm_object.threshold_temperature = thermostat_group_dto.threshold_temperature # type: ignore orm_object.save() # Link configuration outputs to global thermostat config for mode in ['cooling', 'heating']: links = { link.index: link for link in OutputToThermostatGroup.select().where( (OutputToThermostatGroup.thermostat_group == orm_object) & (OutputToThermostatGroup.mode == mode)) } for i in range(4): field = 'switch_to_{0}_{1}'.format(mode, i) if field not in fields: continue link = links.get(i) data = getattr(thermostat_group_dto, field) if data is None: if link is not None: link.delete_instance() else: output_number, value = data output = Output.get(number=output_number) if link is None: OutputToThermostatGroup.create( output=output, thermostat_group=orm_object, mode=mode, index=i, value=value) else: link.output = output link.value = value link.save() if 'pump_delay' in fields: # Set valve delay for all valves in this group for thermostat in orm_object.thermostats: for valve in thermostat.valves: valve.delay = thermostat_group_dto.pump_delay # type: ignore valve.save()
def test_load_output(self): where_mock = mock.Mock() where_mock.get.return_value = Output(id=1, number=42, room=Room(id=2, number=3)) join_from_mock = mock.Mock() join_from_mock.where.return_value = where_mock select_mock = mock.Mock() select_mock.join_from.return_value = join_from_mock with mock.patch.object(Output, 'select', return_value=select_mock), \ mock.patch.object(self.master_controller, 'load_output', return_value=OutputDTO(id=42)) as load: output = self.controller.load_output(42) assert output == OutputDTO(id=42, room=3) load.assert_called_with(output_id=42)
def test_open_valves(self): Valve.create(number=1, name='valve 1', delay=30, output=Output.create(number=1)) Valve.create(number=2, name='valve 2', delay=30, output=Output.create(number=2)) Valve.create(number=3, name='valve 3', delay=30, output=Output.create(number=3)) SetUpTestInjections(output_controller=mock.Mock(OutputController)) controller = PumpValveController() controller.refresh_from_db() self.assertIn(1, controller._valve_drivers) valve_driver_1 = controller.get_valve_driver(1) self.assertIn(2, controller._valve_drivers) valve_driver_2 = controller.get_valve_driver(2) self.assertIn(3, controller._valve_drivers) valve_driver_3 = controller.get_valve_driver(3) for percentage, mode, results in [(100, 'equal', [100, 100]), (50, 'equal', [50, 50]), (0, 'equal', [0, 0]), (100, 'cascade', [100, 100]), (75, 'cascade', [100, 50]), (50, 'cascade', [100, 0]), (0, 'cascade', [0, 0])]: controller.set_valves(percentage, [1, 2], mode) self.assertEqual(results[0], valve_driver_1._desired_percentage) self.assertEqual(results[1], valve_driver_2._desired_percentage) self.assertEqual(0, valve_driver_3._desired_percentage)
def _get_thermostat_pid(self): thermostat = Thermostat.create(number=1, name='thermostat 1', sensor=Sensor.create(number=10), pid_heating_p=200, pid_heating_i=100, pid_heating_d=50, pid_cooling_p=200, pid_cooling_i=100, pid_cooling_d=50, automatic=True, room=None, start=0, valve_config='equal', thermostat_group=self._thermostat_group) ValveToThermostat.create(thermostat=thermostat, valve=Valve.create( number=1, name='valve 1', output=Output.create(number=1)), mode=ThermostatGroup.Modes.HEATING, priority=0) ValveToThermostat.create(thermostat=thermostat, valve=Valve.create( number=2, name='valve 2', output=Output.create(number=2)), mode=ThermostatGroup.Modes.COOLING, priority=0) Preset.create(type=Preset.Types.SCHEDULE, heating_setpoint=20.0, cooling_setpoint=25.0, active=True, thermostat=thermostat) return ThermostatPid(thermostat=thermostat, pump_valve_controller=self._pump_valve_controller)
def save_outputs(self, outputs): # type: (List[OutputDTO]) -> None outputs_to_save = [] for output_dto in outputs: output = Output.get_or_none(number=output_dto.id) # type: Output if output is None: logger.info('Ignored saving non-existing Output {0}'.format( output_dto.id)) if 'room' in output_dto.loaded_fields: if output_dto.room is None: output.room = None elif 0 <= output_dto.room <= 100: output.room, _ = Room.get_or_create(number=output_dto.room) output.save() outputs_to_save.append(output_dto) self._master_controller.save_outputs(outputs_to_save)
def test_orm_sync(self): events = [] def handle_event(gateway_event): events.append(gateway_event) self.pubsub.subscribe_gateway_events(PubSub.GatewayTopics.CONFIG, handle_event) output_dto = OutputDTO(id=42) with mock.patch.object(self.master_controller, 'load_outputs', return_value=[output_dto]): self.controller.run_sync_orm() self.pubsub._publish_all_events() assert Output.select().where( Output.number == output_dto.id).count() == 1 assert GatewayEvent(GatewayEvent.Types.CONFIG_CHANGE, {'type': 'output'}) in events assert len(events) == 1
def test_pump_driver(self): output = Output.create(number=0) pump = Pump.create(number=1, name='pump', output=output) SetUpTestInjections(output_controller=mock.Mock(OutputController)) driver = PumpDriver(pump) self.assertIsNone(driver.state) self.assertFalse(driver.error) self.assertEqual(pump.id, driver.id) driver.turn_on() self.assertTrue(driver.state) self.assertFalse(driver.error) driver.turn_off() self.assertFalse(driver.state) self.assertFalse(driver.error) driver._output_controller.set_output_status.side_effect = RuntimeError() with self.assertRaises(RuntimeError): driver.turn_on() self.assertFalse(driver.state) self.assertTrue(driver.error) driver._output_controller.set_output_status.side_effect = None driver.turn_on() self.assertTrue(driver.state) self.assertFalse(driver.error) driver._output_controller.set_output_status.side_effect = RuntimeError() with self.assertRaises(RuntimeError): driver.turn_off() self.assertTrue(driver.state) self.assertTrue(driver.error) driver._output_controller.set_output_status.side_effect = None driver.turn_off() self.assertFalse(driver.state) self.assertFalse(driver.error)
def set_all_lights( self, action, floor_id=None ): # type: (Literal['ON', 'OFF', 'TOGGLE'], Optional[int]) -> None # TODO: Also include other sources (e.g. plugins) once implemented if floor_id is None: self._master_controller.set_all_lights(action=action) return # TODO: Filter on output type "light" once available query = Output.select(Output.number) \ .join_from(Output, Room, join_type=JOIN.INNER) \ .join_from(Room, Floor, join_type=JOIN.INNER) \ .where(Floor.number == floor_id) output_ids = [output['number'] for output in query.dicts()] # It is unknown whether `floor` is known to the Master implementation. So pass both the floor_id # and the list of Output ids to the MasterController self._master_controller.set_all_lights(action=action, floor_id=floor_id, output_ids=output_ids)
def test_valve_driver(self): valve_output_1 = Output.create(number=2) valve_1 = Valve.create(number=1, name='valve 1', delay=30, output=valve_output_1) SetUpTestInjections(output_controller=mock.Mock(OutputController)) driver_1 = ValveDriver(valve_1) self.assertEqual(valve_1.id, driver_1.id) self.assertEqual(0, driver_1.percentage) self.assertEqual(0, driver_1._desired_percentage) self.assertFalse(driver_1.is_open) self.assertFalse(driver_1.in_transition) driver_1.set(50) self.assertEqual(50, driver_1._desired_percentage) driver_1.close() self.assertEqual(0, driver_1._desired_percentage) driver_1.open() self.assertEqual(100, driver_1._desired_percentage) self.assertTrue(driver_1.will_open) driver_1.steer_output() driver_1._output_controller.set_output_status.assert_called_once() self.assertFalse(driver_1.will_open) self.assertEqual(100, driver_1.percentage) self.assertFalse(driver_1.is_open) self.assertTrue(driver_1.in_transition) time.sleep(20) self.assertFalse(driver_1.is_open) self.assertTrue(driver_1.in_transition) time.sleep(15) self.assertTrue(driver_1.is_open) self.assertFalse(driver_1.in_transition)
def test_thermostat_control(self): thermostat = Thermostat.create(number=1, name='thermostat 1', sensor=Sensor.create(number=10), pid_heating_p=200, pid_heating_i=100, pid_heating_d=50, pid_cooling_p=200, pid_cooling_i=100, pid_cooling_d=50, automatic=True, room=None, start=0, valve_config='equal', thermostat_group=self._thermostat_group) Output.create(number=1) Output.create(number=2) Output.create(number=3) valve_output = Output.create(number=4) valve = Valve.create(number=1, name='valve 1', output=valve_output) ValveToThermostat.create(thermostat=thermostat, valve=valve, mode=ThermostatGroup.Modes.HEATING, priority=0) self._thermostat_controller.refresh_config_from_db() expected = ThermostatGroupStatusDTO(id=0, on=True, setpoint=0, cooling=False, automatic=True, statusses=[ThermostatStatusDTO(id=1, name='thermostat 1', automatic=True, setpoint=0, sensor_id=10, actual_temperature=10.0, setpoint_temperature=14.0, outside_temperature=10.0, output_0_level=0, output_1_level=0, mode=0, airco=0)]) self.assertEqual(expected, self._thermostat_controller.get_thermostat_status()) self._thermostat_controller.set_current_setpoint(thermostat_number=1, heating_temperature=15.0) expected.statusses[0].setpoint_temperature = 15.0 self.assertEqual(expected, self._thermostat_controller.get_thermostat_status()) self._thermostat_controller.set_per_thermostat_mode(thermostat_number=1, automatic=True, setpoint=16.0) expected.statusses[0].setpoint_temperature = 16.0 self.assertEqual(expected, self._thermostat_controller.get_thermostat_status()) preset = self._thermostat_controller.get_current_preset(thermostat_number=1) self.assertTrue(preset.active) self.assertEqual(30.0, preset.cooling_setpoint) self.assertEqual(16.0, preset.heating_setpoint) self.assertEqual(Preset.Types.SCHEDULE, preset.type) self._thermostat_controller.set_current_preset(thermostat_number=1, preset_type=Preset.Types.PARTY) expected.statusses[0].setpoint_temperature = 14.0 expected.statusses[0].setpoint = expected.setpoint = 5 # PARTY = legacy `5` setpoint expected.statusses[0].automatic = expected.automatic = False self.assertEqual(expected, self._thermostat_controller.get_thermostat_status()) self._thermostat_controller.set_thermostat_mode(thermostat_on=True, cooling_mode=True, cooling_on=True, automatic=False, setpoint=4) expected.statusses[0].setpoint_temperature = 30.0 expected.statusses[0].setpoint = expected.setpoint = 4 # VACATION = legacy `4` setpoint expected.cooling = True self.assertEqual(expected, self._thermostat_controller.get_thermostat_status()) self._thermostat_controller.set_thermostat_mode(thermostat_on=True, cooling_mode=False, cooling_on=True, automatic=True) expected.statusses[0].setpoint_temperature = 16.0 expected.statusses[0].setpoint = expected.setpoint = 0 # AUTO = legacy `0/1/2` setpoint expected.statusses[0].automatic = expected.automatic = True expected.cooling = False self.assertEqual(expected, self._thermostat_controller.get_thermostat_status())
def dto_to_orm( thermostat_dto, fields, mode): # type: (ThermostatDTO, List[str], str) -> Thermostat # TODO: A mapper should not alter the database, but instead give an in-memory # structure back to the caller to process objects = {} # type: Dict[str, Dict[int, Any]] def _load_object(orm_type, number): if number is None: return None return objects.setdefault(orm_type.__name__, {}).setdefault( number, orm_type.get(number=number)) # We don't get a start date, calculate last monday night to map the schedules now = int(time.time()) day_of_week = (now / 86400 - 4) % 7 # 0: Monday, 1: Tuesday, ... last_monday_night = now - now % 86400 - day_of_week * 86400 # Update/save thermostat configuration try: thermostat = Thermostat.get(number=thermostat_dto.id) except Thermostat.DoesNotExist: thermostat_group = ThermostatGroup.get(number=0) thermostat = Thermostat(number=thermostat_dto.id) thermostat.thermostat_group = thermostat_group for orm_field, (dto_field, mapping) in { 'name': ('name', None), 'sensor': ('sensor', lambda n: _load_object(Sensor, n)), 'room': ('room', lambda n: _load_object(Room, n)), 'pid_{0}_p'.format(mode): ('pid_p', float), 'pid_{0}_i'.format(mode): ('pid_i', float), 'pid_{0}_d'.format(mode): ('pid_d', float) }.items(): if dto_field not in fields: continue value = getattr(thermostat_dto, dto_field) if value is None: continue if mapping is not None: value = mapping(value) setattr(thermostat, orm_field, value) thermostat.start = last_monday_night thermostat.save() # Update/save output configuration output_config_present = 'output0' in fields or 'output1' in fields if output_config_present: # Unlink all previously linked valve_ids, we are resetting this with the new outputs we got from the API deleted = ValveToThermostat \ .delete() \ .where(ValveToThermostat.thermostat == thermostat) \ .where(ValveToThermostat.mode == mode) \ .execute() logger.info('Unlinked {0} valve_ids from thermostat {1}'.format( deleted, thermostat.name)) for field in ['output0', 'output1']: dto_data = getattr(thermostat_dto, field) if dto_data is None: continue # 1. Get or create output, creation also saves to db output_number = int(dto_data) output, output_created = Output.get_or_create( number=output_number) # 2. Get or create the valve and link to this output try: valve = Valve.get(output=output) except DoesNotExist: valve = Valve(output=output) valve.name = 'Valve (output {0})'.format(output_number) valve.save() # 3. Link the valve to the thermostat, set properties try: valve_to_thermostat = ValveToThermostat.get( valve=valve, thermostat=thermostat, mode=mode) except DoesNotExist: valve_to_thermostat = ValveToThermostat( valve=valve, thermostat=thermostat, mode=mode) # TODO: Decide if this is a cooling thermostat or heating thermostat valve_to_thermostat.priority = 0 if field == 'output0' else 1 valve_to_thermostat.save() # Update/save scheduling configuration for day_index, key in [ (0, 'auto_mon'), (1, 'auto_tue'), (2, 'auto_wed'), (3, 'auto_thu'), (4, 'auto_fri'), (5, 'auto_sat'), (6, 'auto_sun') ]: if key not in fields: continue dto_data = getattr(thermostat_dto, key) if dto_data is None: continue try: day_schedule = DaySchedule.get(thermostat=thermostat, index=day_index, mode=mode) except DoesNotExist: day_schedule = DaySchedule(thermostat=thermostat, index=day_index, mode=mode) day_schedule.schedule_data = ThermostatMapper._schedule_dto_to_orm( dto_data) day_schedule.save() # Presets for field, preset_type in [('setp3', Preset.Types.AWAY), ('setp4', Preset.Types.VACATION), ('setp5', Preset.Types.PARTY)]: if field not in fields: continue dto_data = getattr(thermostat_dto, field) if dto_data is None: continue try: preset = Preset.get(type=preset_type, thermostat=thermostat) except DoesNotExist: preset = Preset(type=preset_type, thermostat=thermostat) setattr(preset, '{0}_setpoint'.format(mode), float(dto_data)) preset.active = False preset.save() # TODO: Map missing [permanent_manual, setp0, setp1, setp2, pid_int] return thermostat
def test_output_sync_change(self): events = [] def on_change(gateway_event): events.append(gateway_event) self.pubsub.subscribe_gateway_events(PubSub.GatewayTopics.STATE, on_change) outputs = {2: OutputDTO(id=2), 40: OutputDTO(id=40, module_type='D')} select_mock = mock.Mock() select_mock.join_from.return_value = [ Output(id=0, number=2), Output(id=1, number=40, room=Room(id=2, number=3)) ] with mock.patch.object(Output, 'select', return_value=select_mock), \ mock.patch.object(self.master_controller, 'load_output', side_effect=lambda output_id: outputs.get(output_id)), \ mock.patch.object(self.master_controller, 'load_output_status', return_value=[OutputStateDTO(id=2, status=True), OutputStateDTO(id=40, status=True)]): self.controller._sync_state() self.pubsub._publish_all_events() assert [ GatewayEvent( 'OUTPUT_CHANGE', { 'id': 2, 'status': { 'on': True, 'locked': False }, 'location': { 'room_id': 255 } }), GatewayEvent( 'OUTPUT_CHANGE', { 'id': 40, 'status': { 'on': True, 'value': 0, 'locked': False }, 'location': { 'room_id': 3 } }) ] == events select_mock = mock.Mock() select_mock.join_from.return_value = [ Output(id=0, number=2), Output(id=1, number=40, room=Room(id=2, number=3)) ] with mock.patch.object(Output, 'select', return_value=select_mock), \ mock.patch.object(self.master_controller, 'load_output', side_effect=lambda output_id: outputs.get(output_id)), \ mock.patch.object(self.master_controller, 'load_output_status', return_value=[OutputStateDTO(id=2, status=True, dimmer=0), OutputStateDTO(id=40, status=True, dimmer=50)]): events = [] self.controller._sync_state() self.pubsub._publish_all_events() assert [ GatewayEvent( 'OUTPUT_CHANGE', { 'id': 2, 'status': { 'on': True, 'locked': False }, 'location': { 'room_id': 255 } }), GatewayEvent( 'OUTPUT_CHANGE', { 'id': 40, 'status': { 'on': True, 'value': 50, 'locked': False }, 'location': { 'room_id': 3 } }) ] == events
def test_save_pumpgroups(self): thermostat = Thermostat.create(number=1, name='thermostat 1', sensor=Sensor.create(number=10), pid_heating_p=200, pid_heating_i=100, pid_heating_d=50, pid_cooling_p=200, pid_cooling_i=100, pid_cooling_d=50, automatic=True, room=None, start=0, valve_config='equal', thermostat_group=self._thermostat_group) valve_1_output = Output.create(number=1) valve_1 = Valve.create(number=1, name='valve 1', output=valve_1_output) valve_2_output = Output.create(number=2) valve_2 = Valve.create(number=2, name='valve 2', output=valve_2_output) valve_3_output = Output.create(number=3) valve_3 = Valve.create(number=3, name='valve 3', output=valve_3_output) ValveToThermostat.create(thermostat=thermostat, valve=valve_1, mode=ThermostatGroup.Modes.HEATING, priority=0) ValveToThermostat.create(thermostat=thermostat, valve=valve_2, mode=ThermostatGroup.Modes.COOLING, priority=0) ValveToThermostat.create(thermostat=thermostat, valve=valve_3, mode=ThermostatGroup.Modes.HEATING, priority=0) Preset.create(type=Preset.Types.SCHEDULE, heating_setpoint=20.0, cooling_setpoint=25.0, active=True, thermostat=thermostat) pump_output = Output.create(number=4) pump = Pump.create(name='pump 1', output=pump_output) heating_pump_groups = self._thermostat_controller.load_heating_pump_groups() self.assertEqual([PumpGroupDTO(id=pump.id, pump_output_id=pump_output.id, valve_output_ids=[], room_id=None)], heating_pump_groups) PumpToValve.create(pump=pump, valve=valve_1) PumpToValve.create(pump=pump, valve=valve_2) pump_groups = self._thermostat_controller.load_heating_pump_groups() self.assertEqual([PumpGroupDTO(id=pump.id, pump_output_id=pump_output.id, valve_output_ids=[valve_1_output.id], room_id=None)], pump_groups) pump_groups = self._thermostat_controller.load_cooling_pump_groups() self.assertEqual([PumpGroupDTO(id=pump.id, pump_output_id=pump_output.id, valve_output_ids=[valve_2_output.id], room_id=None)], pump_groups) self._thermostat_controller._save_pump_groups(ThermostatGroup.Modes.HEATING, [(PumpGroupDTO(id=pump.id, pump_output_id=pump_output.id, valve_output_ids=[valve_1_output.id, valve_3_output.id]), ['pump_output_id', 'valve_output_ids'])]) pump_groups = self._thermostat_controller.load_heating_pump_groups() self.assertEqual([PumpGroupDTO(id=pump.id, pump_output_id=pump_output.id, valve_output_ids=[valve_1_output.id, valve_3_output.id], room_id=None)], pump_groups) pump_groups = self._thermostat_controller.load_cooling_pump_groups() self.assertEqual([PumpGroupDTO(id=pump.id, pump_output_id=pump_output.id, valve_output_ids=[valve_2_output.id], room_id=None)], pump_groups)
def test_transitions(self): pump_1 = Pump.create(number=1, name='pump 1', output=Output.create(number=1)) pump_2 = Pump.create(number=2, name='pump 2', output=Output.create(number=2)) valve_1 = Valve.create(number=1, name='valve 1', delay=30, output=Output.create(number=11)) valve_2 = Valve.create(number=2, name='valve 2', delay=15, output=Output.create(number=12)) valve_3 = Valve.create(number=3, name='valve 3', delay=15, output=Output.create(number=13)) PumpToValve.create(pump=pump_1, valve=valve_1) PumpToValve.create(pump=pump_1, valve=valve_2) PumpToValve.create(pump=pump_2, valve=valve_3) SetUpTestInjections(output_controller=mock.Mock(OutputController)) controller = PumpValveController() controller.refresh_from_db() valve_driver_1 = controller.get_valve_driver(1) valve_driver_2 = controller.get_valve_driver(2) valve_driver_3 = controller.get_valve_driver(3) pump_driver_1 = controller._pump_drivers[1] pump_driver_2 = controller._pump_drivers[2] # Initial state, everything is off self.assertFalse(pump_driver_1.state) self.assertEqual(0, valve_driver_1.percentage) self.assertEqual(0, valve_driver_2.percentage) self.assertFalse(pump_driver_2.state) self.assertEqual(0, valve_driver_3.percentage) # Set the second valve to 50% # The pump should only be turned on after 15s valve_driver_2.set(50) controller.steer() self.assertFalse(pump_driver_1.state) self.assertEqual(0, valve_driver_1.percentage) self.assertEqual(50, valve_driver_2.percentage) self.assertFalse(pump_driver_2.state) self.assertEqual(0, valve_driver_3.percentage) # Pump still off after 10s time.sleep(10) controller.steer() self.assertFalse(pump_driver_1.state) self.assertEqual(0, valve_driver_1.percentage) self.assertEqual(50, valve_driver_2.percentage) self.assertFalse(pump_driver_2.state) self.assertEqual(0, valve_driver_3.percentage) # Pump is on after 10s time.sleep(10) controller.steer() self.assertTrue(pump_driver_1.state) self.assertEqual(0, valve_driver_1.percentage) self.assertEqual(50, valve_driver_2.percentage) self.assertFalse(pump_driver_2.state) self.assertEqual(0, valve_driver_3.percentage) # Other valves are also opened valve_driver_1.set(100) valve_driver_3.set(100) controller.steer() self.assertTrue(pump_driver_1.state) self.assertEqual(100, valve_driver_1.percentage) self.assertEqual(50, valve_driver_2.percentage) self.assertFalse(pump_driver_2.state) self.assertEqual(100, valve_driver_3.percentage) # After a time, both valves are fully open time.sleep(40) controller.steer() self.assertTrue(pump_driver_1.state) self.assertEqual(100, valve_driver_1.percentage) self.assertEqual(50, valve_driver_2.percentage) self.assertTrue(pump_driver_2.state) self.assertEqual(100, valve_driver_3.percentage) # Two valves are closed again # When valves are closed, the pumps are stopped immediately valve_driver_2.set(0) valve_driver_3.set(0) time.sleep(10) controller.steer() self.assertTrue(pump_driver_1.state) self.assertEqual(100, valve_driver_1.percentage) self.assertEqual(0, valve_driver_2.percentage) self.assertFalse(pump_driver_2.state) self.assertEqual(0, valve_driver_3.percentage)
def test_thermostat_group_crud(self): thermostat = Thermostat.create(number=1, name='thermostat 1', sensor=Sensor.create(number=10), pid_heating_p=200, pid_heating_i=100, pid_heating_d=50, pid_cooling_p=200, pid_cooling_i=100, pid_cooling_d=50, automatic=True, room=None, start=0, valve_config='equal', thermostat_group=self._thermostat_group) Output.create(number=1) Output.create(number=2) Output.create(number=3) valve_output = Output.create(number=4) valve = Valve.create(number=1, name='valve 1', output=valve_output) ValveToThermostat.create(thermostat=thermostat, valve=valve, mode=ThermostatGroup.Modes.HEATING, priority=0) thermostat_group = ThermostatGroup.get(number=0) # type: ThermostatGroup self.assertEqual(10.0, thermostat_group.threshold_temperature) self.assertEqual(0, OutputToThermostatGroup.select() .where(OutputToThermostatGroup.thermostat_group == thermostat_group) .count()) self._thermostat_controller.save_thermostat_group((ThermostatGroupDTO(id=0, outside_sensor_id=1, pump_delay=30, threshold_temperature=15, switch_to_heating_0=(1, 0), switch_to_heating_1=(2, 100), switch_to_cooling_0=(1, 100)), ['outside_sensor_id', 'pump_delay', 'threshold_temperature', 'switch_to_heating_0', 'switch_to_heating_1', 'switch_to_cooling_0'])) thermostat_group = ThermostatGroup.get(number=0) self.assertEqual(15.0, thermostat_group.threshold_temperature) links = [{'index': link.index, 'value': link.value, 'mode': link.mode, 'output': link.output_id} for link in (OutputToThermostatGroup.select() .where(OutputToThermostatGroup.thermostat_group == thermostat_group))] self.assertEqual(3, len(links)) self.assertIn({'index': 0, 'value': 0, 'mode': 'heating', 'output': 1}, links) self.assertIn({'index': 1, 'value': 100, 'mode': 'heating', 'output': 2}, links) self.assertIn({'index': 0, 'value': 100, 'mode': 'cooling', 'output': 1}, links) new_thermostat_group_dto = ThermostatGroupDTO(id=0, outside_sensor_id=1, pump_delay=60, threshold_temperature=10, switch_to_heating_0=(1, 50), switch_to_cooling_0=(2, 0)) self._thermostat_controller.save_thermostat_group((new_thermostat_group_dto, ['outside_sensor_id', 'pump_delay', 'threshold_temperature', 'switch_to_heating_0', 'switch_to_heating_1', 'switch_to_cooling_0'])) thermostat_group = ThermostatGroup.get(number=0) self.assertEqual(10.0, thermostat_group.threshold_temperature) links = [{'index': link.index, 'value': link.value, 'mode': link.mode, 'output': link.output_id} for link in (OutputToThermostatGroup.select() .where(OutputToThermostatGroup.thermostat_group == thermostat_group))] self.assertEqual(2, len(links)) self.assertIn({'index': 0, 'value': 50, 'mode': 'heating', 'output': 1}, links) self.assertIn({'index': 0, 'value': 0, 'mode': 'cooling', 'output': 2}, links) self.assertEqual(new_thermostat_group_dto, self._thermostat_controller.load_thermostat_group())