Created
December 14, 2023 14:36
-
-
Save nagydavid/8b08ebc59a16f1cbea5268afffde89d1 to your computer and use it in GitHub Desktop.
Heating schedule for Danfoss icon.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
blueprint: | |
name: 'Heating Control' | |
description: 'Automatic heating based on a scheduler, presence of people and optional dependencies such as window opening, winter mode or party mode.' | |
domain: automation | |
input: | |
heating: | |
name: 'Thermostats / Valves' | |
description: 'Thermostats to be controlled' | |
selector: | |
entity: | |
domain: climate | |
multiple: true | |
persons: | |
name: 'Persons' | |
description: 'If a person is at home and the scheduler state is on, the comfort temperature will be set.' | |
selector: | |
entity: | |
domain: person | |
multiple: true | |
scheduler: | |
name: 'Scheduler' | |
description: 'Scheduler that specifies when the comfort temperature can be set (scheduler state on). Scheduler can be added in the helpers section in your home assistant settings.' | |
selector: | |
entity: | |
domain: schedule | |
multiple: false | |
away_temp: | |
name: 'Away temperature' | |
description: 'If no one is at home or the scheduler state is off, the away temperature is set.' | |
selector: | |
entity: | |
domain: input_number | |
set_temp: | |
name: 'Comfort temperature' | |
description: 'If someone is at home and the scheduler / party mode is on, the comfort temperature (input number) is set. An input number can be added in the helpers section in your home assistant settings.' | |
selector: | |
entity: | |
domain: input_number | |
party_mode: | |
name: 'Party mode (optional)' | |
description: 'Overwrites winter mode and scheduler. If on, heating is activated.' | |
default: | |
selector: | |
entity: | |
domain: input_boolean | |
guest_mode: | |
name: 'Guest mode (optional)' | |
description: 'Activates the scheduler and ignores if anybody is at home or not.' | |
default: | |
selector: | |
entity: | |
domain: input_boolean | |
window: | |
name: 'Window (optional)' | |
description: 'Window that turns off the thermostats if it is open.' | |
default: | |
selector: | |
entity: | |
domain: binary_sensor | |
window_reaction_time: | |
name: 'Window reaction time' | |
description: 'Duration that the window must be open for the thermostats to be turned off.' | |
default: 10 | |
selector: | |
number: | |
min: 0 | |
max: 60 | |
step: 1 | |
mode: slider | |
unit_of_measurement: 's' | |
winter_mode: | |
name: 'Winter mode (optional)' | |
description: 'If this entity is off, heating will never occur. Be sure the entity is a binary input or binary sensor.' | |
default: | |
selector: | |
entity: | |
holiday_scheduler: | |
name: 'Holiday scheduler (optional)' | |
description: 'Scheduler for holidays' | |
default: | |
selector: | |
entity: | |
domain: schedule | |
multiple: false | |
holiday_mode: | |
name: 'Holiday mode (optional)' | |
description: 'If this input boolean is on, the holiday scheduler will be used instead of the regular one.' | |
default: | |
selector: | |
entity: | |
domain: input_boolean | |
presence_sensor: | |
name: 'Presence Sensor (optional)' | |
description: 'If no presence is detected before the scheduler switches to off the temperature will be turned down to minimum. This saves energy e.g. when you go to bed earlier. This could also be a binary sensor based on your lights.' | |
default: | |
selector: | |
entity: | |
domain: binary_sensor | |
presence_time_window: | |
name: 'Presence time window' | |
description: 'If a presence sensor is defined it will only be observed X hours before the scheduler turns off.' | |
default: 1 | |
selector: | |
number: | |
min: 1 | |
max: 5 | |
step: 1 | |
mode: slider | |
unit_of_measurement: h | |
presence_reaction_time: | |
name: 'Presence reaction time' | |
description: 'Define how many minutes the presence sensor must be off so the temperature turns to minimum.' | |
default: 5 | |
selector: | |
number: | |
min: 1 | |
max: 10 | |
step: 1 | |
mode: slider | |
unit_of_measurement: min | |
external_temp_sensor: | |
name: 'External Temperature Sensor (optional)' | |
description: 'Set this if you want temperature calibration for your thermostat. Be sure your valve has a calibration number entity that contains "calibration" or "offset" in its id.' | |
default: | |
selector: | |
entity: | |
domain: sensor | |
device_class: temperature | |
force_minimum_temperature: | |
name: "Force mimimum temperature" | |
description: "Activate this option if you have problems with your TRVs with off mode when window is open. All TRVs will be set to their minimum temperature if this option is enabled." | |
default: false | |
selector: | |
boolean: | |
service_call_delay: | |
name: 'Service call delay' | |
description: 'Some TRVs have problems with setting temperature. You can add a delay between the service calls that control the mode and temperature of your TRVs. This could fix your problem.' | |
default: 2 | |
selector: | |
number: | |
min: 0 | |
max: 10 | |
step: 1 | |
mode: slider | |
unit_of_measurement: s | |
variables: | |
trvs: !input 'heating' | |
valves: "{{ expand(trvs) | map(attribute='entity_id') | list }}" | |
min_temp: "{{ states('number.thermostat_min_heat_setpoint_limit_' + valves[0].split('_')[1])|int(default=10) }}" | |
valves_off_mode: > | |
{% set climates_off = namespace(name=[]) %} | |
{% for climate in valves %} | |
{% if state_attr(climate,'hvac_modes') | regex_search('off', ignorecase=True) %} | |
{% set climates_off.name = climates_off.name + [climate] %} | |
{% endif %} | |
{% endfor %} | |
{{ climates_off.name }} | |
valves_without_off_mode: > | |
{% set climates_not_off = namespace(name=[]) %} | |
{% for climate in valves %} | |
{% if climate not in valves_off_mode %} | |
{% set climates_not_off.name = climates_not_off.name + [climate] %} | |
{% endif %} | |
{% endfor %} | |
{{ climates_not_off.name }} | |
force_minimum_temperature: !input force_minimum_temperature | |
set_temp: !input 'set_temp' | |
away_temp: !input 'away_temp' | |
scheduler_regular: !input 'scheduler' | |
scheduler_holiday: !input 'holiday_scheduler' | |
holiday_mode: !input 'holiday_mode' | |
window: !input 'window' | |
winter_mode: !input 'winter_mode' | |
party_mode: !input 'party_mode' | |
guest_mode: !input 'guest_mode' | |
persons: !input 'persons' | |
party_state: "{{ party_mode != none and is_state(party_mode, 'on') }}" | |
guest_state: "{{ guest_mode != none and is_state(guest_mode, 'on') }}" | |
winter_state: "{{ winter_mode == none or (winter_mode != none and is_state(winter_mode, 'on')) }}" | |
window_state: "{{ window != none and is_state(window, 'on') }}" | |
mode: > | |
{% if (winter_state == true or party_state == true) and window_state == false %} | |
heat | |
{% else %} | |
off | |
{% endif %} | |
persons_home_count: "{{ expand(persons) | selectattr('state', 'eq', 'home') | list | count }}" | |
holiday_mode_state: "{{ scheduler_holiday != none and holiday_mode != none and is_state(holiday_mode, 'on') }}" | |
active_scheduler: "{{ iif(holiday_mode_state,scheduler_holiday,scheduler_regular) }}" | |
presence_sensor: !input presence_sensor | |
presence_reaction_time: !input presence_reaction_time | |
presence_detected: "{{ presence_sensor == none or (presence_sensor != none and is_state(presence_sensor,'on') and (as_timestamp(now()) - as_timestamp(states[presence_sensor].last_changed, default=as_timestamp(now()))) / 60 >= presence_reaction_time) }}" | |
presence_time_window: !input presence_time_window | |
is_presence_time_window: "{{ (as_timestamp(state_attr(active_scheduler,'next_event'),as_timestamp(now())) - as_timestamp(now())) / (60 * 60) <= presence_time_window }}" | |
temperatur: > | |
{% if ( states(active_scheduler) == 'on' and ( persons_home_count | int > 0 or guest_state == true)) %} | |
{% if ( presence_detected == false and is_presence_time_window == true ) %} | |
{{ states(away_temp) }} | |
{% else %} | |
{{ states(set_temp) }} | |
{% endif %} | |
{% else %} | |
{% if party_state == true %} | |
{{ states(set_temp) }} | |
{% else %} | |
{{ states(away_temp) }} | |
{% endif %} | |
{% endif %} | |
actual_local_temperature: !input 'external_temp_sensor' | |
trigger_variables: | |
winter_mode_t: !input winter_mode | |
party_mode_t: !input party_mode | |
guest_mode_t: !input guest_mode | |
holiday_mode_t: !input holiday_mode | |
window_t: !input window | |
regular_scheduler_t: !input scheduler | |
holiday_scheduler_t: !input holiday_scheduler | |
persons_t: !input 'persons' | |
presence_sensor_t: !input presence_sensor | |
trigger: | |
- platform: homeassistant | |
event: start | |
- platform: event | |
event_type: automation_reloaded | |
- platform: state | |
entity_id: !input 'set_temp' | |
for: | |
seconds: 2 | |
- platform: state | |
entity_id: !input 'away_temp' | |
for: | |
seconds: 2 | |
# SCHEDULER | |
- platform: template | |
value_template: > | |
{% set active_scheduler_t = iif(holiday_scheduler_t != none and holiday_mode_t != none and is_state(holiday_mode_t,'on'), holiday_scheduler_t, regular_scheduler_t) %} | |
{{ is_state(active_scheduler_t, 'on') }} | |
- platform: template | |
value_template: > | |
{% set active_scheduler_t = iif(holiday_scheduler_t != none and holiday_mode_t != none and is_state(holiday_mode_t,'on'), holiday_scheduler_t, regular_scheduler_t) %} | |
{{ is_state(active_scheduler_t, 'off') }} | |
# PERSONS HOME / NOT HOME | |
- platform: template | |
value_template: "{{ expand(persons_t) | selectattr('state', 'eq', 'home') | list | count > 0 }}" | |
for: | |
seconds: !input 'window_reaction_time' | |
- platform: template | |
value_template: "{{ expand(persons_t) | selectattr('state', 'eq', 'home') | list | count == 0 }}" | |
for: | |
seconds: !input 'window_reaction_time' | |
# WINDOW TRIGGER | |
- platform: template | |
value_template: "{{ window_t != none and is_state(window_t, 'on') }}" | |
for: | |
seconds: !input 'window_reaction_time' | |
- platform: template | |
value_template: "{{ window_t != none and is_state(window_t, 'off') }}" | |
for: | |
seconds: !input 'window_reaction_time' | |
# HOLIDAY MODE TRIGGER | |
- platform: template | |
value_template: "{{ holiday_scheduler_t != none and holiday_mode_t != none and is_state(holiday_mode_t, 'on') }}" | |
for: | |
seconds: !input 'window_reaction_time' | |
- platform: template | |
value_template: "{{ holiday_scheduler_t != none and holiday_mode_t != none and is_state(holiday_mode_t, 'off') }}" | |
for: | |
seconds: !input 'window_reaction_time' | |
# WINTER MODE TRIGGER | |
- platform: template | |
value_template: "{{ winter_mode_t != none and is_state(winter_mode_t, 'on') }}" | |
for: | |
seconds: !input 'window_reaction_time' | |
- platform: template | |
value_template: "{{ winter_mode_t != none and is_state(winter_mode_t, 'off') }}" | |
for: | |
seconds: !input 'window_reaction_time' | |
# PARTY MODE TRIGGER | |
- platform: template | |
value_template: "{{ party_mode_t != none and is_state(party_mode_t, 'on') }}" | |
for: | |
seconds: !input 'window_reaction_time' | |
- platform: template | |
value_template: "{{ party_mode_t != none and is_state(party_mode_t, 'off') }}" | |
for: | |
seconds: !input 'window_reaction_time' | |
# GUEST MODE TRIGGER | |
- platform: template | |
value_template: "{{ guest_mode_t != none and is_state(guest_mode_t, 'on') }}" | |
for: | |
seconds: !input 'window_reaction_time' | |
- platform: template | |
value_template: "{{ guest_mode_t != none and is_state(guest_mode_t, 'off') }}" | |
for: | |
seconds: !input 'window_reaction_time' | |
# PRESENCE SENSOR TRIGGER | |
- platform: template | |
value_template: "{{ presence_sensor_t != none and is_state(presence_sensor_t, 'on') }}" | |
for: | |
seconds: !input 'presence_reaction_time' | |
- platform: template | |
value_template: "{{ presence_sensor_t != none and is_state(presence_sensor_t, 'off') }}" | |
for: | |
seconds: !input 'presence_reaction_time' | |
# TIME TRIGGER FOR CALIBRATION | |
- platform: time_pattern | |
minutes: "/10" | |
seconds: "30" | |
condition: | |
- condition: or | |
conditions: | |
- condition: template | |
value_template: "{{ trigger.platform == 'time_pattern' and actual_local_temperature != none and mode == 'heat' }}" | |
- condition: template | |
value_template: "{{ trigger.platform != 'time_pattern' }}" | |
action: | |
- if: | |
- condition: template | |
value_template: "{{ trigger.platform == 'time_pattern' }}" | |
then: | |
- repeat: | |
count: "{{ valves | count | int }}" | |
sequence: | |
- variables: | |
current_valve: "{{ valves[repeat.index-1] }}" | |
calibration_entity: > | |
{% set entities = device_entities(device_id(current_valve)) %} | |
{% set calibration_entity_id = namespace(id=[]) %} | |
{% for s in entities %} | |
{% if (('calibration' in s) or ('offset' in s)) %} | |
{% set calibration_entity_id.id = s %} | |
{% endif %} | |
{% endfor %} | |
{{ iif (calibration_entity_id.id[0] is defined, calibration_entity_id.id, NULL) }} | |
- if: | |
- condition: template | |
value_template: "{{ calibration_entity is defined }}" | |
then: | |
- variables: | |
offset_old: "{{ states(calibration_entity) }}" | |
offset_new: > | |
{% set current_calibration_value = states(calibration_entity) %} | |
{% set step = state_attr(calibration_entity,'step') %} | |
{% set local_temperature = state_attr(current_valve,'current_temperature') %} | |
{% set actual_trv_temperature = (local_temperature | float(0) - current_calibration_value | float(0)) | round(1) %} | |
{% set actual_sensor_temperature = states(actual_local_temperature) | float(0) | round(1) %} | |
{% set new_calibration_value = actual_sensor_temperature - actual_trv_temperature %} | |
{% set min_calibration_value = state_attr(calibration_entity,'min') %} | |
{% set max_calibration_value = state_attr(calibration_entity,'max') %} | |
{% if(new_calibration_value > max_calibration_value) %} | |
{% set new_calibration_value = max_calibration_value %} | |
{% elif (new_calibration_value < min_calibration_value) %} | |
{% set new_calibration_value = min_calibration_value %} | |
{% endif %} | |
{{ (new_calibration_value | float(0) / step) | round(0) * step }} | |
- if: | |
- condition: template | |
value_template: "{{ float(offset_old) != float(offset_new) }}" | |
then: | |
- service: number.set_value | |
data: | |
value: "{{ float(offset_new) }}" | |
target: | |
entity_id: "{{ calibration_entity }}" | |
else: | |
- if: | |
- condition: template | |
value_template: "{{ mode == 'off' }}" | |
then: | |
- variables: | |
low_temp_valves: "{{ iif(force_minimum_temperature == true, valves, valves_without_off_mode) }} " | |
- repeat: | |
count: "{{ low_temp_valves | count | int }}" | |
sequence: | |
- variables: | |
current_valve: "{{ low_temp_valves[repeat.index-1] }}" | |
off_temperature: "{{ min_temp }}" | |
- service: climate.set_temperature | |
data: | |
entity_id: "{{ current_valve }}" | |
temperature: "{{ off_temperature | float }}" | |
- if: | |
- condition: template | |
value_template: "{{ valves_off_mode | count > 0 }}" | |
- condition: template | |
value_template: "{{ force_minimum_temperature == false }}" | |
then: | |
- service: climate.set_hvac_mode | |
target: | |
entity_id: "{{ valves_off_mode }}" | |
data: | |
hvac_mode: "off" | |
else: | |
- service: climate.set_hvac_mode | |
target: | |
entity_id: !input 'heating' | |
data: | |
hvac_mode: "heat" | |
- delay: | |
seconds: !input service_call_delay | |
- service: climate.set_temperature | |
data: | |
entity_id: !input 'heating' | |
temperature: "{{ temperatur | float }}" | |
mode: parallel |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment