Files
haos-blueprints/Common/Thermostat Controller.yaml
alexei.dolgolyov 535881abee Add Thermostat Controller blueprint
- Schedule-based heating control using HA schedule helpers
- Multiple schedules support with OR logic
- Working and standby temperatures with input_number overrides
- Window/door sensor integration (disable heating when open)
- External temperature sensor support
- Force heating override switch
- Minimum on-time to prevent rapid cycling
- Configurable disabled behavior (turn off vs standby)
- Debug notifications for troubleshooting
2026-01-25 15:28:48 +03:00

685 lines
23 KiB
YAML

# =============================================================================
# Thermostat Controller Blueprint
# =============================================================================
# This blueprint controls a thermostat/climate entity based on schedules,
# presence, and various conditions.
#
# Features:
# - Schedule-based heating control (using HA schedule helper)
# - Working and standby temperature modes
# - Optional input_number overrides for temperatures
# - External temperature sensor support
# - Window/door sensor integration (disable when open)
# - Force heating override
# - Minimum on-time to prevent rapid cycling
# - Configurable behavior when disabled (off vs standby)
# - Debug notifications for troubleshooting
#
# Temperature Priority:
# 1. Windows open → Turn off (or standby based on setting)
# 2. Force heating ON → Working temperature
# 3. Schedule active → Working temperature
# 4. Schedule inactive → Standby temperature (or off)
#
# Author: Alexei Dolgolyov (dolgolyov.alexei@gmail.com)
# =============================================================================
blueprint:
name: "Custom: Thermostat Controller"
description: >
Controls a thermostat based on schedules, with support for working/standby
temperatures, window sensors, force heating override, and more.
domain: automation
input:
# -------------------------------------------------------------------------
# Thermostat Configuration
# -------------------------------------------------------------------------
thermostat_group:
name: Thermostat
collapsed: false
input:
thermostat_entity:
name: Thermostat
description: The climate entity to control
selector:
entity:
domain: climate
control_switch:
name: Control Switch
description: >
Master switch to enable/disable thermostat control.
When OFF, thermostat will be turned off or set to standby (based on setting).
selector:
entity:
domain:
- input_boolean
- switch
- binary_sensor
hvac_mode:
name: HVAC Mode
description: The HVAC mode to use when heating is active
default: heat
selector:
select:
options:
- heat
- cool
- heat_cool
- auto
# -------------------------------------------------------------------------
# Temperature Configuration
# -------------------------------------------------------------------------
temperature_group:
name: Temperature
collapsed: false
input:
working_temperature:
name: Working Temperature
description: Target temperature when schedule is active
default: 21
selector:
number:
min: 5
max: 35
step: 0.5
unit_of_measurement: °C
mode: slider
working_temperature_override:
name: Working Temperature Override (optional)
description: >
Input number entity to override working temperature.
If set and valid, this value will be used instead of the static value.
default: []
selector:
entity:
domain: input_number
multiple: false
standby_temperature:
name: Standby Temperature
description: Target temperature when schedule is inactive (if not turning off)
default: 16
selector:
number:
min: 5
max: 35
step: 0.5
unit_of_measurement: °C
mode: slider
standby_temperature_override:
name: Standby Temperature Override (optional)
description: >
Input number entity to override standby temperature.
If set and valid, this value will be used instead of the static value.
default: []
selector:
entity:
domain: input_number
multiple: false
external_temperature_sensor:
name: External Temperature Sensor (optional)
description: >
Use an external temperature sensor instead of the thermostat's built-in sensor.
Leave empty to use the thermostat's own temperature reading.
default: []
selector:
entity:
domain: sensor
device_class: temperature
multiple: false
# -------------------------------------------------------------------------
# Schedule Configuration
# -------------------------------------------------------------------------
schedule_group:
name: Schedule
collapsed: false
input:
schedule_entities:
name: Schedule(s)
description: >
Schedule helpers that define when heating should be active.
Multiple schedules are combined (OR logic) - heating is active if ANY schedule is ON.
Create schedule helpers in Home Assistant UI with your desired time slots.
default: []
selector:
entity:
domain: schedule
multiple: true
# -------------------------------------------------------------------------
# Behavior Configuration
# -------------------------------------------------------------------------
behavior_group:
name: Behavior
collapsed: false
input:
disabled_behavior:
name: Behavior When Disabled/Inactive
description: >
What to do when control switch is OFF or schedule is inactive.
"Turn off" completely disables the thermostat.
"Set standby" keeps thermostat running at standby temperature.
default: standby
selector:
select:
options:
- label: Turn off thermostat
value: "off"
- label: Set standby temperature
value: standby
minimum_on_time:
name: Minimum On Time
description: >
Minimum time the thermostat must stay on before it can be turned off.
Prevents rapid cycling which can damage equipment.
default: 5
selector:
number:
min: 0
max: 60
unit_of_measurement: minutes
mode: slider
# -------------------------------------------------------------------------
# Window/Door Sensors
# -------------------------------------------------------------------------
window_group:
name: Window/Door Sensors
collapsed: true
input:
window_sensors:
name: Window/Door Sensors (optional)
description: >
Binary sensors for windows or doors. When any sensor is ON (open),
heating will be disabled to save energy.
default: []
selector:
entity:
domain: binary_sensor
device_class:
- window
- door
- opening
multiple: true
window_reaction_delay:
name: Window Reaction Delay
description: >
Time to wait after window opens before disabling heating.
Prevents turning off heating for brief window openings.
default: 30
selector:
number:
min: 0
max: 300
unit_of_measurement: seconds
mode: slider
# -------------------------------------------------------------------------
# Force Heating Override
# -------------------------------------------------------------------------
force_group:
name: Force Heating
collapsed: true
input:
force_heating_switch:
name: Force Heating Switch (optional)
description: >
When this switch is ON, heating will be forced to working temperature
regardless of schedule or other conditions (except open windows).
default: []
selector:
entity:
domain:
- input_boolean
- switch
- binary_sensor
multiple: false
# -------------------------------------------------------------------------
# Debug Configuration
# -------------------------------------------------------------------------
debug_group:
name: Debug
collapsed: true
input:
enable_debug_notifications:
name: Enable Debug Notifications
description: >
Send persistent notifications for debugging automation behavior.
Shows trigger details, conditions, and thermostat actions.
default: false
selector:
boolean:
# Restart mode ensures latest state is always evaluated
mode: restart
# =============================================================================
# Triggers
# =============================================================================
trigger:
# Home Assistant startup
- platform: homeassistant
event: start
id: 'startup_trigger'
# Control switch state change
- platform: state
entity_id: !input control_switch
id: 'control_trigger'
not_from:
- unknown
- unavailable
not_to:
- unknown
- unavailable
# Schedule state change (on/off) - any schedule
- platform: state
entity_id: !input schedule_entities
id: 'schedule_trigger'
not_from:
- unknown
- unavailable
not_to:
- unknown
- unavailable
# Force heating switch state change
- platform: state
entity_id: !input force_heating_switch
id: 'force_trigger'
not_from:
- unknown
- unavailable
not_to:
- unknown
- unavailable
# Window sensor state change
- platform: state
entity_id: !input window_sensors
id: 'window_trigger'
not_from:
- unknown
- unavailable
not_to:
- unknown
- unavailable
# Working temperature override change
- platform: state
entity_id: !input working_temperature_override
id: 'temp_override_trigger'
not_from:
- unknown
- unavailable
not_to:
- unknown
- unavailable
# Standby temperature override change
- platform: state
entity_id: !input standby_temperature_override
id: 'temp_override_trigger'
not_from:
- unknown
- unavailable
not_to:
- unknown
- unavailable
# External temperature sensor change (for display/logging)
- platform: state
entity_id: !input external_temperature_sensor
id: 'external_temp_trigger'
not_from:
- unknown
- unavailable
not_to:
- unknown
- unavailable
# =============================================================================
# Variables
# =============================================================================
variables:
# ---------------------------------------------------------------------------
# Input References
# ---------------------------------------------------------------------------
thermostat_entity: !input thermostat_entity
control_switch: !input control_switch
schedule_entities: !input schedule_entities
hvac_mode: !input hvac_mode
working_temperature_static: !input working_temperature
working_temperature_override: !input working_temperature_override
standby_temperature_static: !input standby_temperature
standby_temperature_override: !input standby_temperature_override
external_temperature_sensor: !input external_temperature_sensor
disabled_behavior: !input disabled_behavior
minimum_on_time: !input minimum_on_time
window_sensors: !input window_sensors
window_reaction_delay: !input window_reaction_delay
force_heating_switch: !input force_heating_switch
enable_debug_notifications: !input enable_debug_notifications
# ---------------------------------------------------------------------------
# Computed Values
# ---------------------------------------------------------------------------
# Control switch state (default to ON if unavailable)
control_on: >
{% set state = states(control_switch) %}
{% if state in ['unknown', 'unavailable'] %}
{{ false }}
{% else %}
{{ is_state(control_switch, 'on') }}
{% endif %}
# Schedule state - active if ANY schedule is ON
schedule_active: >
{% if schedule_entities | length > 0 %}
{% set active_schedules = schedule_entities | select('is_state', 'on') | list %}
{{ active_schedules | length > 0 }}
{% else %}
{{ false }}
{% endif %}
# Force heating state
force_heating_on: >
{% if force_heating_switch is not none and force_heating_switch | length > 0 %}
{{ is_state(force_heating_switch, 'on') }}
{% else %}
{{ false }}
{% endif %}
# Window sensors - check if any window is open
windows_open: >
{% if window_sensors | length > 0 %}
{% set open_windows = window_sensors | select('is_state', 'on') | list %}
{{ open_windows | length > 0 }}
{% else %}
{{ false }}
{% endif %}
# Get effective working temperature (override or static)
effective_working_temp: >
{% if working_temperature_override is not none and working_temperature_override | length > 0 %}
{% set override_val = states(working_temperature_override) %}
{% if override_val not in ['unknown', 'unavailable'] %}
{{ override_val | float(working_temperature_static) }}
{% else %}
{{ working_temperature_static }}
{% endif %}
{% else %}
{{ working_temperature_static }}
{% endif %}
# Get effective standby temperature (override or static)
effective_standby_temp: >
{% if standby_temperature_override is not none and standby_temperature_override | length > 0 %}
{% set override_val = states(standby_temperature_override) %}
{% if override_val not in ['unknown', 'unavailable'] %}
{{ override_val | float(standby_temperature_static) }}
{% else %}
{{ standby_temperature_static }}
{% endif %}
{% else %}
{{ standby_temperature_static }}
{% endif %}
# Get external temperature reading (if configured)
external_temperature: >
{% if external_temperature_sensor is not none and external_temperature_sensor | length > 0 %}
{% set temp = states(external_temperature_sensor) %}
{% if temp not in ['unknown', 'unavailable'] %}
{{ temp | float }}
{% else %}
{{ 'N/A' }}
{% endif %}
{% else %}
{{ 'N/A' }}
{% endif %}
# Current thermostat state
thermostat_current_state: >
{{ states(thermostat_entity) }}
# Current thermostat temperature setting
thermostat_current_temp: >
{{ state_attr(thermostat_entity, 'temperature') | float(0) }}
# Time since thermostat was last turned on (for minimum on-time check)
thermostat_on_duration: >
{% if states(thermostat_entity) != 'off' %}
{{ (now() - states[thermostat_entity].last_changed).total_seconds() / 60 }}
{% else %}
{{ 0 }}
{% endif %}
# Check if minimum on-time has elapsed
min_on_time_elapsed: >
{{ thermostat_on_duration >= minimum_on_time or states(thermostat_entity) == 'off' }}
# ---------------------------------------------------------------------------
# Decision Logic
# ---------------------------------------------------------------------------
# Determine target action: 'working', 'standby', or 'off'
target_action: >
{% if not control_on %}
{# Control switch is OFF #}
{% if disabled_behavior == 'off' %}
{{ 'off' }}
{% else %}
{{ 'standby' }}
{% endif %}
{% elif windows_open %}
{# Windows are open - disable heating #}
{% if disabled_behavior == 'off' %}
{{ 'off' }}
{% else %}
{{ 'standby' }}
{% endif %}
{% elif force_heating_on %}
{# Force heating is ON #}
{{ 'working' }}
{% elif schedule_active %}
{# Schedule is active #}
{{ 'working' }}
{% else %}
{# Schedule is inactive #}
{% if disabled_behavior == 'off' %}
{{ 'off' }}
{% else %}
{{ 'standby' }}
{% endif %}
{% endif %}
# Determine target temperature based on action
target_temperature: >
{% if target_action == 'working' %}
{{ effective_working_temp }}
{% elif target_action == 'standby' %}
{{ effective_standby_temp }}
{% else %}
{{ 0 }}
{% endif %}
# Check if thermostat needs to be updated
needs_update: >
{% if target_action == 'off' %}
{{ thermostat_current_state != 'off' and min_on_time_elapsed }}
{% else %}
{{ thermostat_current_state == 'off' or thermostat_current_temp != target_temperature | float }}
{% endif %}
# =============================================================================
# Actions
# =============================================================================
action:
# ---------------------------------------------------------------------------
# Debug Logging
# ---------------------------------------------------------------------------
- choose:
- conditions:
- condition: template
value_template: "{{ enable_debug_notifications }}"
sequence:
- service: persistent_notification.create
data:
title: "Thermostat Controller Debug"
message: >
Trigger: {{ trigger.id | default('manual') }}
Time: {{ now().strftime('%H:%M:%S') }}
Input States:
- control_on: {{ control_on }}
- schedule_active: {{ schedule_active }}
- force_heating_on: {{ force_heating_on }}
- windows_open: {{ windows_open }}
Temperatures:
- effective_working: {{ effective_working_temp }}°C
- effective_standby: {{ effective_standby_temp }}°C
- external_sensor: {{ external_temperature }}
- thermostat_current: {{ thermostat_current_temp }}°C
Decision:
- target_action: {{ target_action }}
- target_temperature: {{ target_temperature }}°C
- needs_update: {{ needs_update }}
- min_on_time_elapsed: {{ min_on_time_elapsed }}
# ---------------------------------------------------------------------------
# Window Reaction Delay
# ---------------------------------------------------------------------------
# If triggered by window opening, wait for reaction delay
- choose:
- conditions:
- condition: template
value_template: "{{ trigger.id == 'window_trigger' and windows_open and window_reaction_delay > 0 }}"
sequence:
- delay:
seconds: "{{ window_reaction_delay }}"
# Re-check if windows are still open after delay
- condition: template
value_template: >
{% set open_windows = window_sensors | select('is_state', 'on') | list %}
{{ open_windows | length > 0 }}
# ---------------------------------------------------------------------------
# Apply Thermostat Settings
# ---------------------------------------------------------------------------
- choose:
# CASE 1: Turn OFF thermostat
- conditions:
- condition: template
value_template: "{{ target_action == 'off' and min_on_time_elapsed }}"
sequence:
- choose:
- conditions:
- condition: template
value_template: "{{ thermostat_current_state != 'off' }}"
sequence:
- service: climate.turn_off
target:
entity_id: "{{ thermostat_entity }}"
- choose:
- conditions:
- condition: template
value_template: "{{ enable_debug_notifications }}"
sequence:
- service: persistent_notification.create
data:
title: "Thermostat Controller"
message: "Action: Turned OFF thermostat"
# CASE 2: Set WORKING temperature
- conditions:
- condition: template
value_template: "{{ target_action == 'working' }}"
sequence:
- choose:
# Turn on if currently off
- conditions:
- condition: template
value_template: "{{ thermostat_current_state == 'off' }}"
sequence:
- service: climate.set_hvac_mode
target:
entity_id: "{{ thermostat_entity }}"
data:
hvac_mode: "{{ hvac_mode }}"
# Set temperature
- choose:
- conditions:
- condition: template
value_template: "{{ thermostat_current_temp != effective_working_temp | float }}"
sequence:
- service: climate.set_temperature
target:
entity_id: "{{ thermostat_entity }}"
data:
temperature: "{{ effective_working_temp }}"
- choose:
- conditions:
- condition: template
value_template: "{{ enable_debug_notifications }}"
sequence:
- service: persistent_notification.create
data:
title: "Thermostat Controller"
message: "Action: Set WORKING temperature to {{ effective_working_temp }}°C"
# CASE 3: Set STANDBY temperature
- conditions:
- condition: template
value_template: "{{ target_action == 'standby' }}"
sequence:
- choose:
# Turn on if currently off
- conditions:
- condition: template
value_template: "{{ thermostat_current_state == 'off' }}"
sequence:
- service: climate.set_hvac_mode
target:
entity_id: "{{ thermostat_entity }}"
data:
hvac_mode: "{{ hvac_mode }}"
# Set temperature
- choose:
- conditions:
- condition: template
value_template: "{{ thermostat_current_temp != effective_standby_temp | float }}"
sequence:
- service: climate.set_temperature
target:
entity_id: "{{ thermostat_entity }}"
data:
temperature: "{{ effective_standby_temp }}"
- choose:
- conditions:
- condition: template
value_template: "{{ enable_debug_notifications }}"
sequence:
- service: persistent_notification.create
data:
title: "Thermostat Controller"
message: "Action: Set STANDBY temperature to {{ effective_standby_temp }}°C"