2025-12-04 10:17:15 +01:00
|
|
|
blueprint:
|
|
|
|
|
name: "Smart Thermostat Controller"
|
|
|
|
|
author: Andreas Gammelgaard Damsbo
|
2025-12-05 11:27:53 +01:00
|
|
|
description: |
|
|
|
|
|
Advanced thermostat automation with window/door sensors, presence detection, time schedules, and holiday calendar integration.
|
2025-12-05 11:41:33 +01:00
|
|
|
|
2025-12-05 11:27:53 +01:00
|
|
|
SETUP REQUIRED:
|
|
|
|
|
1. Create an input_boolean helper (Settings > Devices & Services > Helpers > Toggle) for manual override tracking
|
|
|
|
|
2. Create a timer helper (Settings > Devices & Services > Helpers > Timer) for override duration
|
2025-12-04 10:17:15 +01:00
|
|
|
domain: automation
|
2025-12-04 10:17:54 +01:00
|
|
|
source_url: https://gdamsbo.dk/forgejo/andreas/ha-smart-thermostat-control/raw/branch/main/ha-smart-thermostat-control.yaml
|
2025-12-04 10:17:15 +01:00
|
|
|
input:
|
2025-12-04 13:17:07 +01:00
|
|
|
thermostat:
|
|
|
|
|
name: Thermostat
|
|
|
|
|
description: Select the thermostat(s) to control.
|
|
|
|
|
selector:
|
|
|
|
|
entity:
|
|
|
|
|
domain:
|
|
|
|
|
- climate
|
|
|
|
|
multiple: true
|
2025-12-05 11:01:40 +01:00
|
|
|
manual_override_helper:
|
|
|
|
|
name: Manual Override Helper
|
|
|
|
|
description: Select an input_boolean helper to track manual temperature overrides. Create one in Settings > Devices & Services > Helpers.
|
|
|
|
|
selector:
|
|
|
|
|
entity:
|
|
|
|
|
domain:
|
|
|
|
|
- input_boolean
|
|
|
|
|
multiple: false
|
2025-12-05 11:27:53 +01:00
|
|
|
override_timer:
|
|
|
|
|
name: Override Timer
|
|
|
|
|
description: Select a timer helper to manage override duration. Create one in Settings > Devices & Services > Helpers > Timer.
|
|
|
|
|
selector:
|
|
|
|
|
entity:
|
|
|
|
|
domain:
|
|
|
|
|
- timer
|
|
|
|
|
multiple: false
|
2025-12-05 11:01:40 +01:00
|
|
|
override_duration:
|
|
|
|
|
name: Manual Override Duration
|
2025-12-05 11:27:53 +01:00
|
|
|
description: 'How long manual temperature changes should override the schedule. This sets the timer duration. (Default = 120 minutes)'
|
2025-12-05 11:01:40 +01:00
|
|
|
default: 120
|
|
|
|
|
selector:
|
|
|
|
|
number:
|
|
|
|
|
mode: box
|
|
|
|
|
min: 30.0
|
|
|
|
|
max: 480.0
|
|
|
|
|
unit_of_measurement: minutes
|
|
|
|
|
step: 30.0
|
2025-12-04 10:17:15 +01:00
|
|
|
window_sensor:
|
|
|
|
|
name: Window / Door Sensor Group
|
|
|
|
|
description: Select your grouped or single window / door sensor.
|
|
|
|
|
selector:
|
|
|
|
|
entity:
|
|
|
|
|
domain:
|
|
|
|
|
- binary_sensor
|
|
|
|
|
multiple: true
|
|
|
|
|
window_delay:
|
|
|
|
|
name: Window / Door Sensor Delay
|
|
|
|
|
description: 'Time the sensor needs to stay the same after change to trigger the automation. (Default = 5s)'
|
|
|
|
|
default: 5
|
|
|
|
|
selector:
|
|
|
|
|
number:
|
|
|
|
|
mode: box
|
|
|
|
|
min: 0.0
|
|
|
|
|
max: 600.0
|
|
|
|
|
unit_of_measurement: seconds
|
|
|
|
|
step: 5.0
|
|
|
|
|
outdoor:
|
|
|
|
|
name: Outdoor Temperature Sensor
|
|
|
|
|
description: Select your outdoor temperature sensor.
|
|
|
|
|
selector:
|
|
|
|
|
entity:
|
|
|
|
|
domain:
|
|
|
|
|
- sensor
|
|
|
|
|
multiple: false
|
|
|
|
|
wintermode:
|
|
|
|
|
name: Winter Mode Threshold
|
|
|
|
|
description: 'The outside temperature needs to be below this to activate winter mode. (Default = 16°C)'
|
|
|
|
|
default: 16
|
|
|
|
|
selector:
|
|
|
|
|
number:
|
|
|
|
|
step: 1
|
|
|
|
|
min: 10.0
|
|
|
|
|
max: 25.0
|
|
|
|
|
unit_of_measurement: °C or °F
|
2025-12-05 11:12:40 +01:00
|
|
|
mode: slider
|
2025-12-04 10:17:15 +01:00
|
|
|
wintermode_delay:
|
|
|
|
|
name: Winter Mode Delay
|
|
|
|
|
description: 'Time the outside temperature needs to stay above the winter mode temperature to turn the heating off.'
|
|
|
|
|
default: 30
|
|
|
|
|
selector:
|
|
|
|
|
number:
|
|
|
|
|
mode: box
|
|
|
|
|
min: 1.0
|
|
|
|
|
max: 1440.0
|
|
|
|
|
unit_of_measurement: minutes
|
|
|
|
|
step: 5.0
|
|
|
|
|
schedule_helper:
|
|
|
|
|
name: Schedule Helper
|
|
|
|
|
description: Select the schedule helper entity to determine day/night heating times.
|
|
|
|
|
selector:
|
|
|
|
|
entity:
|
|
|
|
|
domain:
|
|
|
|
|
- schedule
|
|
|
|
|
multiple: false
|
|
|
|
|
day_temp:
|
|
|
|
|
name: Day Time Temperature Target
|
|
|
|
|
default: 21
|
|
|
|
|
selector:
|
|
|
|
|
number:
|
|
|
|
|
step: 0.5
|
|
|
|
|
min: 10.0
|
|
|
|
|
max: 30.0
|
|
|
|
|
unit_of_measurement: °C or °F
|
2025-12-05 11:12:40 +01:00
|
|
|
mode: slider
|
2025-12-04 10:17:15 +01:00
|
|
|
night_temp:
|
|
|
|
|
name: Night Time Temperature Target
|
|
|
|
|
default: 18
|
|
|
|
|
selector:
|
|
|
|
|
number:
|
|
|
|
|
step: 0.5
|
|
|
|
|
min: 10.0
|
|
|
|
|
max: 30.0
|
|
|
|
|
unit_of_measurement: °C or °F
|
2025-12-05 11:12:40 +01:00
|
|
|
mode: slider
|
2025-12-04 10:17:15 +01:00
|
|
|
away_temp:
|
|
|
|
|
name: Away Temperature Target
|
|
|
|
|
description: Temperature when away/on holiday.
|
|
|
|
|
default: 16
|
|
|
|
|
selector:
|
|
|
|
|
number:
|
|
|
|
|
step: 0.5
|
|
|
|
|
min: 10.0
|
|
|
|
|
max: 30.0
|
|
|
|
|
unit_of_measurement: °C or °F
|
2025-12-05 11:12:40 +01:00
|
|
|
mode: slider
|
2025-12-04 10:17:15 +01:00
|
|
|
away_calendar:
|
|
|
|
|
name: Holiday or Away Calendar
|
|
|
|
|
description: Calendar entity that indicates away/holiday periods.
|
|
|
|
|
selector:
|
|
|
|
|
entity:
|
|
|
|
|
domain:
|
|
|
|
|
- calendar
|
|
|
|
|
multiple: false
|
|
|
|
|
return_offset:
|
|
|
|
|
name: Return Offset
|
|
|
|
|
description: 'Time offset to start heating before returning from away. Negative value = minutes before return.'
|
|
|
|
|
default: -120
|
|
|
|
|
selector:
|
|
|
|
|
number:
|
|
|
|
|
mode: box
|
|
|
|
|
min: -1440.0
|
|
|
|
|
max: 0.0
|
|
|
|
|
unit_of_measurement: minutes
|
|
|
|
|
step: 30.0
|
|
|
|
|
|
|
|
|
|
trigger:
|
|
|
|
|
- platform: state
|
|
|
|
|
entity_id: !input window_sensor
|
|
|
|
|
for:
|
|
|
|
|
seconds: !input window_delay
|
|
|
|
|
id: window_change
|
|
|
|
|
- platform: numeric_state
|
|
|
|
|
entity_id: !input outdoor
|
|
|
|
|
below: !input wintermode
|
|
|
|
|
for:
|
|
|
|
|
minutes: !input wintermode_delay
|
|
|
|
|
id: cold_weather
|
|
|
|
|
- platform: numeric_state
|
|
|
|
|
entity_id: !input outdoor
|
|
|
|
|
above: !input wintermode
|
|
|
|
|
for:
|
|
|
|
|
minutes: !input wintermode_delay
|
|
|
|
|
id: warm_weather
|
|
|
|
|
- platform: state
|
|
|
|
|
entity_id: !input schedule_helper
|
|
|
|
|
id: schedule_change
|
|
|
|
|
- platform: state
|
|
|
|
|
entity_id: !input thermostat
|
|
|
|
|
attribute: temperature
|
|
|
|
|
id: manual_adjustment
|
2025-12-05 10:46:29 +01:00
|
|
|
- platform: state
|
2025-12-05 11:01:40 +01:00
|
|
|
entity_id: !input manual_override_helper
|
|
|
|
|
to: 'off'
|
|
|
|
|
id: override_expired
|
2025-12-05 11:27:53 +01:00
|
|
|
- platform: state
|
|
|
|
|
entity_id: !input override_timer
|
|
|
|
|
to: 'idle'
|
|
|
|
|
id: timer_finished
|
2025-12-04 10:17:15 +01:00
|
|
|
- platform: calendar
|
|
|
|
|
event: end
|
|
|
|
|
entity_id: !input away_calendar
|
|
|
|
|
offset: !input return_offset
|
|
|
|
|
id: return_soon
|
|
|
|
|
- platform: calendar
|
|
|
|
|
event: start
|
|
|
|
|
entity_id: !input away_calendar
|
|
|
|
|
id: leaving
|
|
|
|
|
- platform: homeassistant
|
|
|
|
|
event: start
|
|
|
|
|
id: ha_start
|
|
|
|
|
- platform: event
|
|
|
|
|
event_type: automation_reloaded
|
|
|
|
|
id: reload
|
|
|
|
|
|
|
|
|
|
variables:
|
|
|
|
|
thermostat_entities: !input thermostat
|
2025-12-05 11:41:33 +01:00
|
|
|
triggered_thermostat: "{{ trigger.entity_id | default('none') }}"
|
2025-12-05 11:51:59 +01:00
|
|
|
# Access temperature safely:
|
|
|
|
|
# 1. Get the 'to_state', defaulting to a simple object/dict if missing.
|
|
|
|
|
# 2. Get the 'attributes' from that object, defaulting to a dict if missing.
|
|
|
|
|
# 3. Get the 'temperature' from the attributes, defaulting to 0, then convert to float.
|
|
|
|
|
new_temperature: >
|
|
|
|
|
{{ trigger.to_state.attributes.temperature | default(0) | float(0)
|
|
|
|
|
if trigger.to_state is defined and trigger.to_state.attributes is defined
|
|
|
|
|
else 0 }}
|
|
|
|
|
old_temperature: >
|
|
|
|
|
{{ trigger.from_state.attributes.temperature | default(0) | float(0)
|
|
|
|
|
if trigger.from_state is defined and trigger.from_state.attributes is defined
|
|
|
|
|
else 0 }}
|
2025-12-05 11:01:40 +01:00
|
|
|
override_helper: !input manual_override_helper
|
|
|
|
|
day_temp_input: !input day_temp
|
|
|
|
|
night_temp_input: !input night_temp
|
|
|
|
|
away_temp_input: !input away_temp
|
2025-12-04 10:17:15 +01:00
|
|
|
|
|
|
|
|
condition: []
|
|
|
|
|
|
|
|
|
|
action:
|
|
|
|
|
- choose:
|
2025-12-05 11:01:40 +01:00
|
|
|
# Priority 0: Manual Adjustment - Detect and enable override
|
2025-12-04 10:17:15 +01:00
|
|
|
- conditions:
|
2025-12-05 11:01:40 +01:00
|
|
|
- condition: trigger
|
|
|
|
|
id: manual_adjustment
|
|
|
|
|
# Only if temperature actually changed
|
2025-12-05 10:40:38 +01:00
|
|
|
- condition: template
|
|
|
|
|
value_template: "{{ (new_temperature - old_temperature) | abs > 0.1 }}"
|
2025-12-05 11:01:40 +01:00
|
|
|
# Only if the new temp is different from current schedule targets
|
2025-12-05 10:27:26 +01:00
|
|
|
- condition: template
|
|
|
|
|
value_template: >
|
2025-12-05 11:01:40 +01:00
|
|
|
{% if is_state(input_away_calendar, 'on') %}
|
|
|
|
|
{{ (new_temperature - away_temp_input) | abs > 0.1 }}
|
|
|
|
|
{% elif is_state(input_schedule_helper, 'on') %}
|
|
|
|
|
{{ (new_temperature - day_temp_input) | abs > 0.1 }}
|
2025-12-05 10:40:38 +01:00
|
|
|
{% else %}
|
2025-12-05 11:01:40 +01:00
|
|
|
{{ (new_temperature - night_temp_input) | abs > 0.1 }}
|
2025-12-05 10:40:38 +01:00
|
|
|
{% endif %}
|
2025-12-04 10:17:15 +01:00
|
|
|
sequence:
|
2025-12-05 11:12:40 +01:00
|
|
|
- service: logbook.log
|
|
|
|
|
data:
|
|
|
|
|
name: Smart Thermostat
|
2025-12-05 11:27:53 +01:00
|
|
|
message: 'Manual Override: {{ triggered_thermostat }} changed to {{ new_temperature }}°, syncing others'
|
|
|
|
|
# Sync temperature to all other thermostats
|
2025-12-05 10:40:38 +01:00
|
|
|
- repeat:
|
|
|
|
|
for_each: "{{ thermostat_entities | reject('eq', triggered_thermostat) | list }}"
|
|
|
|
|
sequence:
|
|
|
|
|
- service: climate.set_temperature
|
|
|
|
|
target:
|
|
|
|
|
entity_id: "{{ repeat.item }}"
|
|
|
|
|
data:
|
|
|
|
|
temperature: "{{ new_temperature }}"
|
2025-12-05 11:27:53 +01:00
|
|
|
# Enable manual override mode
|
2025-12-05 11:12:40 +01:00
|
|
|
- service: input_boolean.turn_on
|
|
|
|
|
target:
|
|
|
|
|
entity_id: !input manual_override_helper
|
2025-12-05 11:27:53 +01:00
|
|
|
# Start timer
|
|
|
|
|
- service: timer.start
|
|
|
|
|
target:
|
|
|
|
|
entity_id: !input override_timer
|
|
|
|
|
data:
|
|
|
|
|
duration: "{{ (input_override_duration * 60) | int }}"
|
|
|
|
|
|
|
|
|
|
# Priority 0b: Timer Finished - Disable override and resume schedule
|
|
|
|
|
- conditions:
|
|
|
|
|
- condition: trigger
|
|
|
|
|
id: timer_finished
|
|
|
|
|
sequence:
|
2025-12-05 11:01:40 +01:00
|
|
|
- service: input_boolean.turn_off
|
|
|
|
|
target:
|
|
|
|
|
entity_id: !input manual_override_helper
|
2025-12-04 10:17:15 +01:00
|
|
|
- service: logbook.log
|
|
|
|
|
data:
|
|
|
|
|
name: Smart Thermostat
|
2025-12-05 11:27:53 +01:00
|
|
|
message: 'Manual Override: Timer expired, resuming schedule'
|
2025-12-04 10:17:15 +01:00
|
|
|
|
2025-12-05 11:01:40 +01:00
|
|
|
# Priority 1: Windows/Doors Open - Turn OFF heating (overrides manual)
|
2025-12-04 10:17:15 +01:00
|
|
|
- conditions:
|
|
|
|
|
- condition: state
|
|
|
|
|
entity_id: !input window_sensor
|
|
|
|
|
state: 'on'
|
|
|
|
|
match: any
|
|
|
|
|
sequence:
|
|
|
|
|
- service: climate.turn_off
|
|
|
|
|
target:
|
|
|
|
|
entity_id: !input thermostat
|
2025-12-05 11:01:40 +01:00
|
|
|
- service: input_boolean.turn_off
|
|
|
|
|
target:
|
|
|
|
|
entity_id: !input manual_override_helper
|
2025-12-05 11:27:53 +01:00
|
|
|
- service: timer.cancel
|
|
|
|
|
target:
|
|
|
|
|
entity_id: !input override_timer
|
2025-12-04 10:17:15 +01:00
|
|
|
- service: logbook.log
|
|
|
|
|
data:
|
|
|
|
|
name: Smart Thermostat
|
|
|
|
|
message: 'Heating OFF: Window or door is open'
|
|
|
|
|
|
2025-12-05 11:01:40 +01:00
|
|
|
# Priority 2: Too Warm Outside - Turn OFF heating (overrides manual)
|
2025-12-04 10:17:15 +01:00
|
|
|
- conditions:
|
|
|
|
|
- condition: numeric_state
|
|
|
|
|
entity_id: !input outdoor
|
|
|
|
|
above: !input wintermode
|
|
|
|
|
sequence:
|
|
|
|
|
- service: climate.turn_off
|
|
|
|
|
target:
|
|
|
|
|
entity_id: !input thermostat
|
2025-12-05 11:01:40 +01:00
|
|
|
- service: input_boolean.turn_off
|
|
|
|
|
target:
|
|
|
|
|
entity_id: !input manual_override_helper
|
2025-12-05 11:27:53 +01:00
|
|
|
- service: timer.cancel
|
|
|
|
|
target:
|
|
|
|
|
entity_id: !input override_timer
|
2025-12-04 10:17:15 +01:00
|
|
|
- service: logbook.log
|
|
|
|
|
data:
|
|
|
|
|
name: Smart Thermostat
|
|
|
|
|
message: 'Heating OFF: Outside temperature above threshold'
|
|
|
|
|
|
2025-12-05 11:01:40 +01:00
|
|
|
# Priority 3: Manual Override Active - Do nothing, keep manual temperature
|
|
|
|
|
- conditions:
|
|
|
|
|
- condition: state
|
|
|
|
|
entity_id: !input manual_override_helper
|
|
|
|
|
state: 'on'
|
|
|
|
|
sequence:
|
|
|
|
|
- service: logbook.log
|
|
|
|
|
data:
|
|
|
|
|
name: Smart Thermostat
|
|
|
|
|
message: 'Manual override active - maintaining user-set temperature'
|
|
|
|
|
|
|
|
|
|
# Priority 4: Away/Holiday Mode - Set to away temperature
|
2025-12-04 10:17:15 +01:00
|
|
|
- conditions:
|
|
|
|
|
- condition: state
|
|
|
|
|
entity_id: !input away_calendar
|
|
|
|
|
state: 'on'
|
|
|
|
|
- condition: numeric_state
|
|
|
|
|
entity_id: !input outdoor
|
|
|
|
|
below: !input wintermode
|
|
|
|
|
- condition: state
|
|
|
|
|
entity_id: !input window_sensor
|
|
|
|
|
state: 'off'
|
|
|
|
|
match: all
|
|
|
|
|
sequence:
|
|
|
|
|
- service: climate.set_temperature
|
|
|
|
|
target:
|
|
|
|
|
entity_id: !input thermostat
|
|
|
|
|
data:
|
|
|
|
|
temperature: !input away_temp
|
|
|
|
|
hvac_mode: heat
|
|
|
|
|
- service: logbook.log
|
|
|
|
|
data:
|
|
|
|
|
name: Smart Thermostat
|
|
|
|
|
message: 'Away Mode: Heating set to away temperature'
|
|
|
|
|
|
2025-12-05 11:01:40 +01:00
|
|
|
# Priority 5: Schedule Day Time - Set to day temperature
|
2025-12-04 10:17:15 +01:00
|
|
|
- conditions:
|
|
|
|
|
- condition: state
|
|
|
|
|
entity_id: !input schedule_helper
|
|
|
|
|
state: 'on'
|
|
|
|
|
- condition: numeric_state
|
|
|
|
|
entity_id: !input outdoor
|
|
|
|
|
below: !input wintermode
|
|
|
|
|
- condition: state
|
|
|
|
|
entity_id: !input window_sensor
|
|
|
|
|
state: 'off'
|
|
|
|
|
match: all
|
|
|
|
|
- condition: state
|
|
|
|
|
entity_id: !input away_calendar
|
|
|
|
|
state: 'off'
|
|
|
|
|
sequence:
|
|
|
|
|
- service: climate.set_temperature
|
|
|
|
|
target:
|
|
|
|
|
entity_id: !input thermostat
|
|
|
|
|
data:
|
|
|
|
|
temperature: !input day_temp
|
|
|
|
|
hvac_mode: heat
|
|
|
|
|
- service: logbook.log
|
|
|
|
|
data:
|
|
|
|
|
name: Smart Thermostat
|
|
|
|
|
message: 'Day Mode: Heating set to day temperature'
|
|
|
|
|
|
2025-12-05 11:01:40 +01:00
|
|
|
# Priority 6: Schedule Night Time - Set to night temperature
|
2025-12-04 10:17:15 +01:00
|
|
|
- conditions:
|
|
|
|
|
- condition: state
|
|
|
|
|
entity_id: !input schedule_helper
|
|
|
|
|
state: 'off'
|
|
|
|
|
- condition: numeric_state
|
|
|
|
|
entity_id: !input outdoor
|
|
|
|
|
below: !input wintermode
|
|
|
|
|
- condition: state
|
|
|
|
|
entity_id: !input window_sensor
|
|
|
|
|
state: 'off'
|
|
|
|
|
match: all
|
|
|
|
|
- condition: state
|
|
|
|
|
entity_id: !input away_calendar
|
|
|
|
|
state: 'off'
|
|
|
|
|
sequence:
|
|
|
|
|
- service: climate.set_temperature
|
|
|
|
|
target:
|
|
|
|
|
entity_id: !input thermostat
|
|
|
|
|
data:
|
|
|
|
|
temperature: !input night_temp
|
|
|
|
|
hvac_mode: heat
|
|
|
|
|
- service: logbook.log
|
|
|
|
|
data:
|
|
|
|
|
name: Smart Thermostat
|
|
|
|
|
message: 'Night Mode: Heating set to night temperature'
|
|
|
|
|
|
|
|
|
|
# Default: Turn off if no conditions match
|
|
|
|
|
default:
|
|
|
|
|
- service: climate.turn_off
|
|
|
|
|
target:
|
|
|
|
|
entity_id: !input thermostat
|
|
|
|
|
- service: logbook.log
|
|
|
|
|
data:
|
|
|
|
|
name: Smart Thermostat
|
|
|
|
|
message: 'Heating OFF: No active heating conditions met'
|
|
|
|
|
|
2025-12-05 12:03:22 +01:00
|
|
|
mode: single
|
2025-12-04 10:17:15 +01:00
|
|
|
max_exceeded: silent
|