From b27e902b2402a0d091d8fa3a188b2f9738f726f9 Mon Sep 17 00:00:00 2001 From: Pierre Date: Sun, 24 May 2026 22:10:54 +0200 Subject: [PATCH] Add heating sensor configuration and smart heat pump logic --- apps/global/heating.yaml | 78 ++++++++++++++++++++++++++++++ apps/smartheatpump.py | 102 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 180 insertions(+) create mode 100644 apps/global/heating.yaml create mode 100644 apps/smartheatpump.py diff --git a/apps/global/heating.yaml b/apps/global/heating.yaml new file mode 100644 index 0000000..192ad9d --- /dev/null +++ b/apps/global/heating.yaml @@ -0,0 +1,78 @@ +heating_sensors: + module: virtualsensors + class: VirtualSensorsApp + priority: 10 # default priority app is 50, since most of them require sensors created by virtual sensors, it's important that virtual_sensors start first + + sensors: + binary_sensor.heatpump_winter_mode: + trigger_conditions: false #sensor.month >= 10 or sensor.month <= 4 + value_selector.target_temperature_heatpump: + attributes: + unit_of_measurement: °C + device_class: temperature + icon: mdi:thermometer + values: + winter_mode: + conditions: binary_sensor.heatpump_winter_mode + value: 18 + default: + conditions: True + value: 27 + +smart_heating_templates_library: + module: templatelibrary + class: TemplateLibrary + priority: 1 + + templates_library: + clim_conditions: + trigger_conditions: + - "not binary_sensor.heatpump_winter_mode and (ext_temperature >= room_temperature or ext_temperature >= target_temperature or cant_open_window) and room_temperature >= (quick_resume ? target_temperature : (target_temperature + 1))" + - not binary_sensor.heatpump_winter_mode and self and (ext_temperature >= target_temperature or cant_open_window) + - binary_sensor.heatpump_winter_mode and room_temperature <= (target_temperature - 1) and ext_temperature <= 10 + - binary_sensor.heatpump_winter_mode and self and room_temperature < target_temperature + blocking_conditions: + - binary_sensor.is_everybody_sleeping and not sleeping_in_the_room and not stay_on_while_sleeping + - not room_occupancy + - not binary_sensor.someone_home #and not (binary_sensor.someone_in_town and sensor.day_interval == 'Nuit') + - window_open + +desk_heatpump: + module: smartheatpump + class: SmartHeatpump + entity: climate.desk_heatpump + + templates_library: smart_heating_templates_library + + constants: + window_open: binary_sensor.desk_window + ext_temperature: sensor.ext_front_temperature + room_temperature: sensor.desk_temperature + room_occupancy: binary_sensor.desk_occupancy + target_temperature: sensor.target_temperature_heatpump + cant_open_window: false #binary_sensor.cant_open_window + sleeping_in_the_room: false #binary_sensor.is_bureau_sleeping + quick_resume: binary_sensor.desk_heatpump_quick_resume + stay_on_while_sleeping: false + + virtual_sensors: + sensors: + value_selector.desk_heatpump_fan_mode: + auto: binary_sensor.heatpump_winter_mode + quiet: True + + target_temperature: sensor.target_temperature_heatpump + hvac_mode: "binary_sensor.heatpump_winter_mode ? 'heat' : 'cool'" + fan_mode: sensor.desk_heatpump_fan_mode + + smart_conditions: + callback_delay: 10 # on the morning it can happen that we ask to start and stop the heat pump immediately afterwards which in result in the heat pump ignoring the second command + template_conditions: + + quick_resume: + sensor: binary_sensor.desk_heatpump_quick_resume + reset_event: good_morning + + #input_buttons: + # turn_on: input_button.clim_du_bureau_turn_on + # turn_off: input_button.clim_du_bureau_turn_off diff --git a/apps/smartheatpump.py b/apps/smartheatpump.py new file mode 100644 index 0000000..d097d4c --- /dev/null +++ b/apps/smartheatpump.py @@ -0,0 +1,102 @@ +import ad_toolbox.smartcondition as SmartCondition +import ad_toolbox.smartvalue as SmartValue +from ad_toolbox.smartobject import SmartObject +import time + +class SmartHeatpump(SmartObject): + + def on_initialize_smart_object(self): + super().on_initialize_smart_object() + + if 'quick_resume' in self.args: + self.quick_resume_sensor = self.create_entity(self.args['quick_resume']['sensor']) + #is_on = 'on' if self.entity.get_state() != 'off' else 'off' + #if self.get_state(self.args['quick_resume']['sensor']) != 'on': # if the sensor doesn't exist I wan't to set it as well + # self.set_state(self.args['quick_resume']['sensor'],state = is_on) + self.listen_event(lambda event_name, data, kwargs: self.set_state(self.args['quick_resume']['sensor'],state = 'off'),self.args['quick_resume']['reset_event']) + + self.target_temperature = SmartValue.Evaluator(self,self.args['target_temperature'], expression_name = 'target_temperature', on_change_cb = self.on_change_target_temperature, constants = self.constants) + self.hvac_mode = SmartValue.Evaluator(self,self.args['hvac_mode'], expression_name = 'hvac_mode', on_change_cb = self.on_change_hvac_mode, constants = self.constants) + if 'horizontal_vane' in self.args: + self.horizontal_vane = SmartValue.Evaluator(self,self.args['horizontal_vane'], expression_name = 'horizontal_vane', on_change_cb = self.on_change_horizontal_vane, constants = self.constants) + else: + self.horizontal_vane = None + + self.fan_mode = SmartValue.Evaluator(self,self.args['fan_mode'], expression_name = 'fan_mode', on_change_cb = self.on_change_fan_mode, constants = self.constants) + self.smart_conditions_evaluator = SmartCondition.Evaluator(self,self.args['smart_conditions'],condition_name = "smart_conditions",on_change_cb = self.on_smart_conditions_change,constants = self.constants, templates_library = self.templates_library) + + if 'input_buttons' in self.args: + if 'turn_on' in self.args['input_buttons']: + self.listen_state(lambda entity, attribute, old, new, kwargs: self.on_turn_on_button(),self.args['input_buttons']['turn_on']) + if 'turn_off' in self.args['input_buttons']: + self.listen_state(lambda entity, attribute, old, new, kwargs: self.on_turn_off_button(),self.args['input_buttons']['turn_off']) + + def on_turn_on_button(self): self.start_cooling() + + def on_turn_off_button(self): + self.stop_cooling() + # we don't wan't quick resum if we force turn off the a/c + if 'quick_resume' in self.args: + self.set_state(self.args['quick_resume']['sensor'],state = 'off') + + def start_cooling(self): + if 'quick_resume' in self.args: + self.set_state(self.args['quick_resume']['sensor'],state = 'on') + temperature = self.target_temperature.evaluate(False) + hvac_mode = self.hvac_mode.evaluate(False) + fan_mode = self.fan_mode.evaluate(False) + self.log_info(f"Starting Heatpump in {hvac_mode} mode, with target_temperature of {temperature}° and {fan_mode} fan speed") + + #self.turn_on(self.entity_id) + #time.sleep(2) + + self.call_service("climate/set_hvac_mode", entity_id = self.entity_id,hvac_mode = hvac_mode) + time.sleep(1) + self.call_service("climate/set_temperature", entity_id = self.entity_id,temperature = temperature,hvac_mode = hvac_mode) + time.sleep(1) + self.call_service("climate/set_fan_mode", entity_id = self.entity_id,fan_mode = fan_mode) + if self.horizontal_vane: + time.sleep(1) + horizontal_vane = self.horizontal_vane.evaluate(False) + self.set_horizontal_vane(horizontal_vane) + + def stop_cooling(self): + self.log_info("Setting Heatpump hvac_mode to off") + self.call_service("climate/set_hvac_mode", entity_id = self.entity_id,hvac_mode = "off") + + + def on_smart_conditions_change(self,prev_result,result): + if result == SmartCondition.Result.Succeeded: + self.start_cooling() + else: + self.log_info("Turning Off Heatpump") + self.stop_cooling() + + def on_change_target_temperature(self,prev_result,result): + if self.entity.get_state() != 'off': + self.log_info(f"Setting target temperature to {result}°") + self.call_service("climate/set_temperature", entity_id = self.entity_id,temperature = result) + + def on_change_hvac_mode(self,prev_result,result): + if self.entity.get_state() != 'off': + self.log_info(f"Setting hvac_mode to {result}") + self.call_service("climate/set_hvac_mode", entity_id = self.entity_id,hvac_mode = result) + + def on_change_fan_mode(self,prev_result,result): + if self.entity.get_state() != 'off': + self.log_info(f"Setting fan_mode to {result}") + self.call_service("climate/set_fan_mode", entity_id = self.entity_id,fan_mode = result) + + def set_horizontal_vane(self,value): + device, entity = self.split_entity(self.entity_id) + self.call_service("select/select_option", entity_id = f"select.{entity}_horizontal_vane",option = value) + #self.set_state(f"select.{entity}_horizontal_vane",state = value) + + def on_change_horizontal_vane(self,prev_result,result): + if self.entity.get_state() != 'off': + self.log_info(f"Setting horizontal_vane to {result}") + self.set_horizontal_vane(result) + + + +