Skip to content

Instantly share code, notes, and snippets.

@casdr
Last active December 17, 2024 21:36
Show Gist options
  • Save casdr/d027da007e6523533906df58eb498f71 to your computer and use it in GitHub Desktop.
Save casdr/d027da007e6523533906df58eb498f71 to your computer and use it in GitHub Desktop.
Heating control using automations

Heating control

This Jinja template generates automations, groups and schedules for my heating system at home. I use Bosch Radiator Thermostat II valves and Zigbee temperature sensors.

The end result is generated automatically using the ha-jinja2config addon.

{% set rooms = [
{
'name': 'Living Room',
'external_source': 'living_room_climate_temperature',
'radiators': [ 'living_room_radiator' ],
'temperature': {
'off': 14,
'on': 17
},
'schedule': {
'weekdays': [
{ 'from': '07:15', 'to': '22:00' }
],
'weekend': [
{ 'from': '09:00', 'to': '23:00' }
]
}
},
{
'name': 'Bedroom',
'external_source': 'bedroom_climate_temperature',
'radiators': [ 'bedroom_radiator' ],
'temperature': {
'off': 14,
'on': 17
},
'schedule': {
'weekdays': [
{ 'from': '07:00', 'to': '08:00' },
{ 'from': '22:00', 'to': '23:00' }
],
'friday': [
{ 'from': '07:00', 'to': '08:00' },
{ 'from': '22:30', 'to': '23:30' }
],
'weekend': [
{ 'from': '09:00', 'to': '10:00' },
{ 'from': '22:30', 'to': '23:30' }
],
'sunday': [
{ 'from': '09:00', 'to': '10:00' },
{ 'from': '22:00', 'to': '23:30' }
]
}
}
] %}
{%- set ns = namespace(radiators=[]) -%}
{%- for room in rooms -%}
{%- set ns.radiators = ns.radiators + room.radiators -%}
{%- endfor -%}
automation heating:
- alias: "Heating - Control"
id: "heating_control"
description: >
Control the thermostats based on the schedule and home state.
mode: restart
triggers:
- trigger: state
entity_id:
- binary_sensor.home_state_nearby_anyone
{%- for room in rooms %}
- schedule.{{ room.name|lower|replace(' ', '_') }}_heating{% endfor %}
actions:
{% for room in rooms %}
- if:
- condition: and
conditions:
- condition: state
entity_id: binary_sensor.home_state_nearby_anyone
state: "on"
- condition: state
entity_id: schedule.{{ room.name|lower|replace(' ', '_') }}_heating
state: "on"
then:
- action: climate.set_temperature
data:
temperature: "{{ "{{" }} state_attr('schedule.{{ room.name|lower|replace(' ', '_') }}_heating', 'temperature') {{ "}}" }}"
hvac_mode: heat
target:
entity_id: group.{{ room.name|lower|replace(' ', '_') }}_thermostat
else:
- action: climate.set_temperature
data:
temperature: {{ room.temperature.off }}
hvac_mode: heat
target:
entity_id: group.{{ room.name|lower|replace(' ', '_') }}_thermostat
{% endfor %}
- alias: "Heating - External Temperature"
id: "heating_set_external_temperature"
description: >
This will sync the temperature on the radiator valves to the external sensors.
mode: restart
triggers:
{% for room in rooms %}
- trigger: state
entity_id:
- "sensor.{{ room.external_source }}"
variables:
room: "{{ room.name|lower|replace(' ', '_') }}"
radiators: {{ room.radiators }}
external_source: "{{ room.external_source }}"
external_sensor: "{{ "sensor." + room.external_source }}"
schedule: "{{ room.name|lower|replace(' ', '_') }}_heating"
- trigger: time
at: input_datetime.{{ room.name|lower|replace(' ', '_') }}_heating_next_update
variables:
room: "{{ room.name|lower|replace(' ', '_') }}"
radiators: {{ room.radiators }}
external_source: "{{ room.external_source }}"
external_sensor: "{{ "sensor." + room.external_source }}"
schedule: "schedule.{{ room.name|lower|replace(' ', '_') }}_heating"
{% endfor %}
conditions:
- condition: template
value_template: >
{{ "{{" }} (as_timestamp(now()) - as_timestamp(states.sensor[external_source].last_changed)) < 5400 {{ "}}" }}
actions:
- action: input_datetime.set_datetime
target:
entity_id: "{{ "{{" }} 'input_datetime.' + room + '_heating_next_update' {{ "}}" }}"
data:
timestamp: "{{ "{{" }} as_timestamp(now() + timedelta(minutes=29)) {{ "}}" }}"
- action: number.set_value
data:
value: "{{ "{{" }} states(external_sensor) {{ "}}" }}"
target:
entity_id: "{{ "{{" }} radiators|map('regex_replace', '^', 'number.')|map('regex_replace', '$', '_remote_temperature')|list {{ "}}" }}"
- alias: "Heating - Calibration"
id: "heating_calibration"
description: >
When a radiator valve has reconnected and is waiting for a calbration,
start the calibration process automatically.
initial_state: true
mode: restart
triggers:
{% for radiator in ns.radiators %}
- trigger: state
entity_id: sensor.{{ radiator }}_valve_adapt_status
to: ready_to_calibrate
{%- endfor %}
actions:
- action: switch.turn_on
target:
entity_id: "{{ "{{" }} trigger.entity_id|regex_replace('^sensor.', 'switch.')|regex_replace('_status$', '_process') {{ "}}" }}"
- alias: "Heating - Error state"
id: "heating_error"
description: >
Notify people whenever a radiator valve is in error state
mode: restart
triggers:
{% for radiator in ns.radiators %}
- trigger: state
entity_id: sensor.{{ radiator }}_valve_adapt_status
to: error
{%- endfor %}
actions:
- action: notify.notify
data:
title: "Radiator valve error: {{ "{{" }} trigger.entity_id|regex_replace('^sensor.', '')|regex_replace('_status$', '') {{ "}}" }}"
message: Please check the valve
data:
push:
sound:
critical: 1
group:
{% for room in rooms %}
{{ room.name|lower|replace(' ', '_') }}_thermostat:
name: "{{ room.name }} Thermostat"
entities:
{% for radiator in room.radiators %}
- climate.{{ radiator }}{% endfor %}
{% endfor %}
schedule:
{% for room in rooms %}
{{ room.name|lower|replace(' ', '_') }}_heating:
name: "{{ room.name }}: Heating Schedule"
{%- for day in ["monday", "tuesday", "wednesday", "thursday", "friday"] %}
{{ day }}:
{% for _block in room.schedule[day]|default(room.schedule.weekdays) %}
- from: "{{ _block.from }}"
to: "{{ _block.to }}"
data:
temperature: {{ _block.temperature|default(room.temperature.on) }}{% endfor %}
{%- endfor %}
{%- for day in ["saturday", "sunday"] %}
{{ day }}:
{% for _block in room.schedule[day]|default(room.schedule.weekend) %}
- from: "{{ _block.from }}"
to: "{{ _block.to }}"
data:
temperature: {{ _block.temperature|default(room.temperature.on) }}{% endfor %}
{%- endfor %}
{% endfor %}
input_datetime:
{% for room in rooms %}
{{ room.name|lower|replace(' ', '_') }}_heating_next_update:
name: "{{ room.name }}: Heating - Next forced update"
has_date: true
has_time: true
{% endfor %}
automation heating:
- alias: "Heating - Control"
id: "heating_control"
description: >
Control the thermostats based on the schedule and home state.
mode: restart
triggers:
- trigger: state
entity_id:
- binary_sensor.home_state_nearby_anyone
- schedule.living_room_heating
- schedule.bedroom_heating
actions:
- if:
- condition: and
conditions:
- condition: state
entity_id: binary_sensor.home_state_nearby_anyone
state: "on"
- condition: state
entity_id: schedule.living_room_heating
state: "on"
then:
- action: climate.set_temperature
data:
temperature: "{{ state_attr('schedule.living_room_heating', 'temperature') }}"
hvac_mode: heat
target:
entity_id: group.living_room_thermostat
else:
- action: climate.set_temperature
data:
temperature: 14
hvac_mode: heat
target:
entity_id: group.living_room_thermostat
- if:
- condition: and
conditions:
- condition: state
entity_id: binary_sensor.home_state_nearby_anyone
state: "on"
- condition: state
entity_id: schedule.bedroom_heating
state: "on"
then:
- action: climate.set_temperature
data:
temperature: "{{ state_attr('schedule.bedroom_heating', 'temperature') }}"
hvac_mode: heat
target:
entity_id: group.bedroom_thermostat
else:
- action: climate.set_temperature
data:
temperature: 14
hvac_mode: heat
target:
entity_id: group.bedroom_thermostat
- alias: "Heating - External Temperature"
id: "heating_set_external_temperature"
description: >
This will sync the temperature on the radiator valves to the external sensors.
mode: restart
triggers:
- trigger: state
entity_id:
- "sensor.living_room_climate_temperature"
variables:
room: "living_room"
radiators: ["living_room_radiator"]
external_source: "living_room_climate_temperature"
external_sensor: "sensor.living_room_climate_temperature"
schedule: "living_room_heating"
- trigger: time
at: input_datetime.living_room_heating_next_update
variables:
room: "living_room"
radiators: ["living_room_radiator"]
external_source: "living_room_climate_temperature"
external_sensor: "sensor.living_room_climate_temperature"
schedule: "schedule.living_room_heating"
- trigger: state
entity_id:
- "sensor.bedroom_climate_temperature"
variables:
room: "bedroom"
radiators: ["bedroom_radiator"]
external_source: "bedroom_climate_temperature"
external_sensor: "sensor.bedroom_climate_temperature"
schedule: "bedroom_heating"
- trigger: time
at: input_datetime.bedroom_heating_next_update
variables:
room: "bedroom"
radiators: ["bedroom_radiator"]
external_source: "bedroom_climate_temperature"
external_sensor: "sensor.bedroom_climate_temperature"
schedule: "schedule.bedroom_heating"
conditions:
- condition: template
value_template: >
{{ (as_timestamp(now()) - as_timestamp(states.sensor[external_source].last_changed)) < 5400 }}
actions:
- action: input_datetime.set_datetime
target:
entity_id: "{{ 'input_datetime.' + room + '_heating_next_update' }}"
data:
timestamp: "{{ as_timestamp(now() + timedelta(minutes=29)) }}"
- action: number.set_value
data:
value: "{{ states(external_sensor) }}"
target:
entity_id: "{{ radiators|map('regex_replace', '^', 'number.')|map('regex_replace', '$', '_remote_temperature')|list }}"
- alias: "Heating - Calibration"
id: "heating_calibration"
description: >
When a radiator valve has reconnected and is waiting for a calbration,
start the calibration process automatically.
initial_state: true
mode: restart
triggers:
- trigger: state
entity_id: sensor.living_room_radiator_valve_adapt_status
to: ready_to_calibrate
- trigger: state
entity_id: sensor.bedroom_radiator_valve_adapt_status
to: ready_to_calibrate
actions:
- action: switch.turn_on
target:
entity_id: "{{ trigger.entity_id|regex_replace('^sensor.', 'switch.')|regex_replace('_status$', '_process') }}"
- alias: "Heating - Error state"
id: "heating_error"
description: >
Notify people whenever a radiator valve is in error state
mode: restart
triggers:
- trigger: state
entity_id: sensor.living_room_radiator_valve_adapt_status
to: error
- trigger: state
entity_id: sensor.bedroom_radiator_valve_adapt_status
to: error
actions:
- action: notify.notify
data:
title: "Radiator valve error: {{ trigger.entity_id|regex_replace('^sensor.', '')|regex_replace('_status$', '') }}"
message: Please check the valve
data:
push:
sound:
critical: 1
group:
living_room_thermostat:
name: "Living Room Thermostat"
entities:
- climate.living_room_radiator
bedroom_thermostat:
name: "Bedroom Thermostat"
entities:
- climate.bedroom_radiator
schedule:
living_room_heating:
name: "Living Room: Heating Schedule"
monday:
- from: "07:15"
to: "22:00"
data:
temperature: 17
tuesday:
- from: "07:15"
to: "22:00"
data:
temperature: 17
wednesday:
- from: "07:15"
to: "22:00"
data:
temperature: 17
thursday:
- from: "07:15"
to: "22:00"
data:
temperature: 17
friday:
- from: "07:15"
to: "22:00"
data:
temperature: 17
saturday:
- from: "09:00"
to: "23:00"
data:
temperature: 17
sunday:
- from: "09:00"
to: "23:00"
data:
temperature: 17
bedroom_heating:
name: "Bedroom: Heating Schedule"
monday:
- from: "07:00"
to: "08:00"
data:
temperature: 17
- from: "22:00"
to: "23:00"
data:
temperature: 17
tuesday:
- from: "07:00"
to: "08:00"
data:
temperature: 17
- from: "22:00"
to: "23:00"
data:
temperature: 17
wednesday:
- from: "07:00"
to: "08:00"
data:
temperature: 17
- from: "22:00"
to: "23:00"
data:
temperature: 17
thursday:
- from: "07:00"
to: "08:00"
data:
temperature: 17
- from: "22:00"
to: "23:00"
data:
temperature: 17
friday:
- from: "07:00"
to: "08:00"
data:
temperature: 17
- from: "22:30"
to: "23:30"
data:
temperature: 17
saturday:
- from: "09:00"
to: "10:00"
data:
temperature: 17
- from: "22:30"
to: "23:30"
data:
temperature: 17
sunday:
- from: "09:00"
to: "10:00"
data:
temperature: 17
- from: "22:00"
to: "23:30"
data:
temperature: 17
input_datetime:
living_room_heating_next_update:
name: "Living Room: Heating - Next forced update"
has_date: true
has_time: true
bedroom_heating_next_update:
name: "Bedroom: Heating - Next forced update"
has_date: true
has_time: true
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment