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 load_cooling_thermostats(self): # type: () -> List[ThermostatDTO] return [ ThermostatMapper.orm_to_dto(thermostat, 'cooling') for thermostat in Thermostat.select() ]
def load_cooling_thermostat(self, thermostat_id): # type: (int) -> ThermostatDTO thermostat = Thermostat.get(number=thermostat_id) return ThermostatMapper.orm_to_dto(thermostat, 'cooling')
def get_current_preset(self, thermostat_number): # type: (int) -> Preset thermostat = Thermostat.get(number=thermostat_number) return thermostat.active_preset
def test_save(self): temperatures = {} def _get_temperature(sensor_id): return temperatures[sensor_id] controller = GatewayThermostatMappingTests._create_controller( get_sensor_temperature_status=_get_temperature) room = Room(number=5) room.save() thermostat_group = ThermostatGroup(number=0, name='global') thermostat_group.save() thermostat = Thermostat( number=10, start=0, # 0 is on a thursday name='thermostat', thermostat_group=thermostat_group) thermostat.save() heating_thermostats = controller.load_heating_thermostats() self.assertEqual(1, len(heating_thermostats)) dto = heating_thermostats[0] # type: ThermostatDTO default_schedule_dto = ThermostatScheduleDTO(temp_day_1=20.0, start_day_1='07:00', end_day_1='09:00', temp_day_2=21.0, start_day_2='17:00', end_day_2='22:00', temp_night=16.0) Sensor.create(number=15) dto.room = 5 dto.sensor = 15 dto.output0 = 5 dto.name = 'changed' dto.auto_thu = ThermostatScheduleDTO(temp_night=10, temp_day_1=15, temp_day_2=30, start_day_1='08:00', end_day_1='10:30', start_day_2='16:00', end_day_2='18:45') temperatures[15] = 5.0 controller.save_heating_thermostats([dto]) heating_thermostats = controller.load_heating_thermostats() self.assertEqual(1, len(heating_thermostats)) dto = heating_thermostats[0] # type: ThermostatDTO self.assertEqual( ThermostatDTO(id=10, name='changed', setp3=16.0, setp4=15.0, setp5=22.0, sensor=15, pid_p=120.0, pid_i=0.0, pid_d=0.0, room=5, output0=5, permanent_manual=True, auto_mon=default_schedule_dto, auto_tue=default_schedule_dto, auto_wed=default_schedule_dto, auto_thu=ThermostatScheduleDTO(temp_night=10.0, temp_day_1=15.0, temp_day_2=30.0, start_day_1='08:00', end_day_1='10:30', start_day_2='16:00', end_day_2='18:45'), auto_fri=default_schedule_dto, auto_sat=default_schedule_dto, auto_sun=default_schedule_dto), dto)
def test_orm_to_dto_mapping(self): controller = GatewayThermostatMappingTests._create_controller() group, _ = ThermostatGroup.get_or_create( number=0, name='Default', on=True, mode=ThermostatGroup.Modes.HEATING) thermostat = Thermostat( number=10, start=0, # 0 is on a thursday name='thermostat', thermostat_group=group) thermostat.save() heating_thermostats = controller.load_heating_thermostats() self.assertEqual(1, len(heating_thermostats)) dto = heating_thermostats[0] # type: ThermostatDTO schedule_dto = ThermostatScheduleDTO(temp_day_1=20.0, start_day_1='07:00', end_day_1='09:00', temp_day_2=21.0, start_day_2='17:00', end_day_2='22:00', temp_night=16.0) self.assertEqual( ThermostatDTO(id=10, name='thermostat', setp3=16.0, setp4=15.0, setp5=22.0, sensor=None, pid_p=120.0, pid_i=0.0, pid_d=0.0, room=None, permanent_manual=True, auto_mon=schedule_dto, auto_tue=schedule_dto, auto_wed=schedule_dto, auto_thu=schedule_dto, auto_fri=schedule_dto, auto_sat=schedule_dto, auto_sun=schedule_dto), dto) day_schedule = thermostat.heating_schedules()[0] # type: DaySchedule day_schedule.schedule_data = { 0: 5.0, 120: 5.5, # 120 and 1200 are selected because 120 < 1200, 1200: 6.0, # but str(120) > str(1200) 3600: 6.5, 7500: 7.0 } day_schedule.save() heating_thermostats = controller.load_heating_thermostats() self.assertEqual(1, len(heating_thermostats)) dto = heating_thermostats[0] # type: ThermostatDTO self.assertEqual( ThermostatScheduleDTO(temp_night=5.0, temp_day_1=5.5, temp_day_2=6.5, start_day_1='00:02', end_day_1='00:20', start_day_2='01:00', end_day_2='02:05'), dto.auto_thu)
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_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_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 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())
def test_save(self): temperatures = {} def _get_temperature(sensor_id): return temperatures[sensor_id] controller = GatewayThermostatMappingTests._create_controller( get_sensor_temperature_status=_get_temperature) thermostat_group = ThermostatGroup(number=0, name='global') thermostat_group.save() thermostat = Thermostat( number=10, start=0, # 0 is on a thursday name='thermostat', thermostat_group=thermostat_group) thermostat.save() for i in range(7): day_schedule = DaySchedule(index=i, content='{}', mode='heating') day_schedule.thermostat = thermostat day_schedule.save() heating_thermostats = controller.load_heating_thermostats() self.assertEqual(1, len(heating_thermostats)) dto = heating_thermostats[0] # type: ThermostatDTO Sensor.create(number=15) dto.room = 5 # This field won't be saved dto.sensor = 15 dto.output0 = 5 dto.name = 'changed' dto.auto_thu = ThermostatScheduleDTO(temp_night=10, temp_day_1=15, temp_day_2=30, start_day_1='08:00', end_day_1='10:30', start_day_2='16:00', end_day_2='18:45') temperatures[15] = 5.0 controller.save_heating_thermostats([ (dto, ['sensor', 'output0', 'name', 'auto_thu']) ]) heating_thermostats = controller.load_heating_thermostats() self.assertEqual(1, len(heating_thermostats)) dto = heating_thermostats[0] # type: ThermostatDTO self.assertEqual( ThermostatDTO( id=10, name='changed', setp3=14.0, setp4=14.0, setp5=14.0, sensor=15, pid_p=120.0, pid_i=0.0, pid_d=0.0, room=None, # Unchanged output0=5, permanent_manual=True, auto_thu=ThermostatScheduleDTO(temp_night=10.0, temp_day_1=15.0, temp_day_2=30.0, start_day_1='08:00', end_day_1='10:30', start_day_2='16:00', end_day_2='18:45')), dto)