def set_thermostat_mode(self, thermostat_on, cooling_mode=False, cooling_on=False, automatic=None, setpoint=None): # type: (bool, bool, bool, Optional[bool], Optional[int]) -> None mode = ThermostatMode.COOLING if cooling_mode else ThermostatMode.HEATING # type: Literal['cooling', 'heating'] global_thermosat = ThermostatGroup.get(number=0) global_thermosat.on = thermostat_on global_thermosat.mode = mode global_thermosat.save() for thermostat_number, thermostat_pid in self.thermostat_pids.items(): thermostat = Thermostat.get(number=thermostat_number) if thermostat is not None: if automatic is False and setpoint is not None and 3 <= setpoint <= 5: preset = thermostat.get_preset( preset_type=Preset.SETPOINT_TO_TYPE.get( setpoint, Preset.Types.SCHEDULE)) thermostat.active_preset = preset else: thermostat.active_preset = thermostat.get_preset( preset_type=Preset.Types.SCHEDULE) thermostat_pid.update_thermostat(thermostat) thermostat_pid.tick()
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 load_thermostat_group(self): # type: () -> ThermostatGroupDTO thermostat_group = ThermostatGroup.get(number=0) pump_delay = None for thermostat in thermostat_group.thermostats: for valve in thermostat.valves: pump_delay = valve.delay break sensor_number = None if thermostat_group.sensor is None else thermostat_group.sensor.number thermostat_group_dto = ThermostatGroupDTO( id=0, outside_sensor_id=sensor_number, threshold_temperature=thermostat_group.threshold_temperature, pump_delay=pump_delay) for link in OutputToThermostatGroup.select(OutputToThermostatGroup, Output) \ .join_from(OutputToThermostatGroup, Output) \ .where(OutputToThermostatGroup.thermostat_group == thermostat_group): if link.index > 3 or link.output is None: continue field = 'switch_to_{0}_{1}'.format(link.mode, link.index) setattr(thermostat_group_dto, field, (link.output.number, link.value)) return thermostat_group_dto
def get_thermostat_status(self): # type: () -> ThermostatGroupStatusDTO def get_output_level(output_number): if output_number is None: return 0 # we are returning 0 if outputs are not configured try: output = self._output_controller.get_output_status( output_number) except ValueError: logger.info( 'Output {0} state not yet available'.format(output_number)) return 0 # Output state is not yet cached (during startup) if output.dimmer is None: status_ = output.status output_level = 0 if status_ is None else int(status_) * 100 else: output_level = output.dimmer return output_level global_thermostat = ThermostatGroup.get(number=0) if global_thermostat is None: raise RuntimeError('Global thermostat not found!') group_status = ThermostatGroupStatusDTO( id=0, on=global_thermostat.on, automatic=True, # Default, will be updated below setpoint=0, # Default, will be updated below cooling=global_thermostat.mode == ThermostatMode.COOLING) for thermostat in global_thermostat.thermostats: valves = thermostat.cooling_valves if global_thermostat.mode == 'cooling' else thermostat.heating_valves db_outputs = [valve.output.number for valve in valves] number_of_outputs = len(db_outputs) if number_of_outputs > 2: logger.warning( 'Only 2 outputs are supported in the old format. Total: {0} outputs.' .format(number_of_outputs)) output0 = db_outputs[0] if number_of_outputs > 0 else None output1 = db_outputs[1] if number_of_outputs > 1 else None active_preset = thermostat.active_preset if global_thermostat.mode == ThermostatMode.COOLING: setpoint_temperature = active_preset.cooling_setpoint else: setpoint_temperature = active_preset.heating_setpoint group_status.statusses.append( ThermostatStatusDTO( id=thermostat.number, actual_temperature=self._gateway_api. get_sensor_temperature_status(thermostat.sensor), setpoint_temperature=setpoint_temperature, outside_temperature=self._gateway_api. get_sensor_temperature_status(global_thermostat.sensor), mode=0, # TODO: Need to be fixed automatic=active_preset.type == Preset.Types.SCHEDULE, setpoint=Preset.TYPE_TO_SETPOINT.get( active_preset.type, 0), name=thermostat.name, sensor_id=thermostat.sensor.number, airco=0, # TODO: Check if still used output_0_level=get_output_level(output0), output_1_level=get_output_level(output1))) # Update global references group_status.automatic = all(status.automatic for status in group_status.statusses) used_setpoints = set(status.setpoint for status in group_status.statusses) group_status.setpoint = next(iter(used_setpoints)) if len( used_setpoints) == 1 else 0 # 0 is a fallback return group_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_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())