Skip to content

Instantly share code, notes, and snippets.

@nagydavid
Created December 14, 2023 14:36
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nagydavid/8b08ebc59a16f1cbea5268afffde89d1 to your computer and use it in GitHub Desktop.
Save nagydavid/8b08ebc59a16f1cbea5268afffde89d1 to your computer and use it in GitHub Desktop.
Heating schedule for Danfoss icon.
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