Files
haos-blueprints/Common/Climate Device Controller.yaml
alexei.dolgolyov 9885d3f8e9
All checks were successful
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 3s
Add hysteresis and force ON override to Climate Device Controller
- Add hysteresis window to prevent rapid on/off cycling
  Device turns OFF at target, ON at (target - window)
- Add Force ON switch for manual override capability
- Restructure control logic with explicit priority checks
- Default action now maintains state in hysteresis zone
- Update documentation with hysteresis example
2026-01-25 15:31:13 +03:00

672 lines
27 KiB
YAML

# =============================================================================
# Climate Device Controller Blueprint for Home Assistant
# =============================================================================
# This blueprint controls climate devices (heaters, AC, humidifiers, etc.)
# based on environmental sensors, window/door states, and schedules.
#
# Supports any controllable device type through custom actions:
# - Switches (traditional on/off control)
# - Climate entities (HVAC, air conditioners)
# - Smart remotes (IR/RF commands for AC units)
# - Scripts and scenes
# - Any other controllable entity
#
# Features:
# - Automatic on/off based on target value (temperature, humidity, etc.)
# - Hysteresis window to prevent rapid on/off cycling
# - Force ON override (manual override to keep device always on)
# - Window/door sensor integration (turns off when open for efficiency)
# - Decay duration (waits before reacting to door/window changes)
# - Emergency threshold (forces device ON below critical value)
# - Schedule support (only runs during scheduled times)
# - Power monitoring (detects device malfunction)
# - House-wide and room-specific window/door sensors
#
# Control Logic (in priority order):
# 1. If Force ON switch is ON → FORCE ON (manual override)
# 2. If value is below emergency threshold → FORCE ON (safety override)
# 3. If control switch is OFF → FORCE OFF
# 4. If environment not sealed OR schedule not active → OFF
# 5. If value at or above target → OFF (target reached)
# 6. If value below turn-on threshold (target - hysteresis) → ON
# 7. Otherwise → maintain current state (in hysteresis zone)
#
# Hysteresis Example (prevents rapid cycling):
# - Target humidity: 30%, Hysteresis window: 5%
# - Device turns OFF when humidity reaches 30%
# - Device turns ON when humidity drops to 25% (30% - 5%)
# - Between 25-30%: device maintains its current state
#
# Window/Door Logic:
# - "House closed" = ALL house windows are closed (for decay duration)
# - "Room closed" = ALL room windows AND doors are closed (for decay duration)
# - Device can run if EITHER house OR room is fully closed
#
# Power Monitoring:
# - If device is ON but power consumption is below threshold for
# decay duration, it's flagged as problematic (possible malfunction)
#
# Requirements:
# - Device entity to monitor state (switch, climate, etc.)
# - Custom actions for turning device on/off
# - Control switch (master enable/disable)
# - Environment sensor(s) for current value
# - Target value entity (input_number)
#
# Device Control Flexibility:
# - Supports any controllable device type via custom actions
# - Examples: switches, climate entities, smart remotes, scripts, scenes
# - Define your own turn_on and turn_off actions for maximum flexibility
#
# Author: Alexei Dolgolyov (dolgolyov.alexei@gmail.com)
# =============================================================================
blueprint:
name: "Custom: Climate Device Control"
description: >
Controls climate devices based on window/door sensors with decay duration
and value threshold for temperature, humidity, or other environmental control.
Supports any device type (switches, climate entities, smart remotes, etc.)
through customizable turn on/off actions.
domain: automation
# ===========================================================================
# INPUT CONFIGURATION
# ===========================================================================
input:
# -------------------------------------------------------------------------
# General Settings
# -------------------------------------------------------------------------
primary_group:
name: "General"
collapsed: false
input:
device_entity:
name: Device Entity
description: >
Entity used to monitor the device state (on/off).
Can be a switch, climate entity, or any entity with on/off states.
This is used for power monitoring and state checks.
selector:
entity:
domain:
- switch
- climate
- fan
- light
- input_boolean
turn_on_action:
name: Turn On Action
description: >
Action to execute when the device should be turned ON.
Examples:
- For switch: switch.turn_on with entity_id
- For climate: climate.turn_on or climate.set_hvac_mode
- For smart remote: remote.send_command with your IR/RF code
- For script: script.turn_on
selector:
action: {}
turn_off_action:
name: Turn Off Action
description: >
Action to execute when the device should be turned OFF.
Examples:
- For switch: switch.turn_off with entity_id
- For climate: climate.turn_off or climate.set_hvac_mode to 'off'
- For smart remote: remote.send_command with your IR/RF code
- For script: script.turn_on (for off script)
selector:
action: {}
control_switch:
name: Control Switch
description: >
Master switch that enables/disables this automation.
When OFF, the device will be turned off regardless of other conditions.
selector:
entity:
domain:
- binary_sensor
- input_boolean
force_on_entity:
name: Force ON Switch (optional)
description: >
When this entity is ON, the device will be FORCED ON regardless of
all other conditions (except being higher priority than emergency).
Useful for manual override when you want the device always running.
default:
selector:
entity:
domain:
- input_boolean
- switch
- binary_sensor
# -------------------------------------------------------------------------
# Doors & Windows Configuration
# -------------------------------------------------------------------------
doors_group:
name: "Doors & Windows"
collapsed: false
input:
house_windows:
name: House Window Sensors
description: >
Window sensors for the entire house. If ANY is open, device may turn off.
Leave empty if not using house-wide window monitoring.
default: []
selector:
entity:
domain: binary_sensor
multiple: true
room_windows:
name: Room Window Sensors
description: >
Window sensors for the room where the device is located.
Device can run when room is sealed (all closed).
default: []
selector:
entity:
domain: binary_sensor
multiple: true
room_doors:
name: Room Door Sensors
description: >
Door sensors for the room where the device is located.
Combined with room windows to determine if room is sealed.
default: []
selector:
entity:
domain: binary_sensor
multiple: true
decay_duration:
name: Decay Duration (seconds)
description: >
Time to wait after a door/window state change before reacting.
Prevents toggling when briefly opening doors.
default: 3
selector:
number:
min: 0
max: 600
unit_of_measurement: seconds
mode: slider
# -------------------------------------------------------------------------
# Environment Sensors & Target
# -------------------------------------------------------------------------
env_group:
name: "Environment"
collapsed: false
input:
env_sensors:
name: Environment Sensors
description: >
Sensors that measure the current environmental value
(temperature, humidity, etc.) in the room.
default: []
selector:
entity:
domain: sensor
multiple: true
target_value_entity:
name: Target Value Entity
description: >
Entity (e.g., input_number) that defines the target value.
Device turns OFF when sensor value reaches this target.
selector:
entity:
domain: input_number
hysteresis_window:
name: Hysteresis Window
description: >
Buffer zone to prevent rapid on/off cycling.
Device turns ON when value drops below (target - window).
Example: Target 30%, window 5% → turns OFF at 30%, turns ON at 25%.
Set to 0 to disable hysteresis (device toggles exactly at target).
default: 0
selector:
number:
min: 0
max: 50
step: 0.5
mode: slider
value_threshold:
name: Emergency Threshold
description: >
If ANY sensor value falls below this threshold, device will be
FORCED ON regardless of other conditions (emergency override).
Set to 0 to disable this feature.
default: 0
selector:
number:
min: 0
max: 100
mode: slider
value_is_low_entity:
name: "Low Value Indicator Entity (optional)"
description: >
Optional input_boolean that will be turned ON when value is
below the emergency threshold. Useful for dashboard indicators.
default: null
selector:
entity:
domain:
- input_boolean
# -------------------------------------------------------------------------
# Schedule Configuration
# -------------------------------------------------------------------------
schedule_group:
name: "Schedules"
collapsed: false
input:
schedule_entities:
name: Schedules (optional)
description: >
Schedule entities that define when the device may run.
Device only operates when ANY schedule is active (ON).
Leave empty to allow running at any time.
default: []
selector:
entity:
domain: schedule
multiple: true
# -------------------------------------------------------------------------
# Power Monitoring
# -------------------------------------------------------------------------
power_group:
name: "Power"
collapsed: false
input:
power_sensor:
name: Power Sensor
description: "Sensor reporting device power consumption (W)"
selector:
entity:
domain: sensor
power_threshold:
name: Power Threshold (W)
description: >
If device is ON but power consumption is below this value,
it may indicate a malfunction (e.g., heater element failure).
default: 10
selector:
number:
min: 0
max: 50
unit_of_measurement: "W"
power_decay_duration:
name: Power Decay Duration (s)
description: >
Time to wait after power reading changes before flagging
as problematic. Prevents false alarms during startup.
default: 10
selector:
number:
min: 1
max: 50
unit_of_measurement: "s"
power_problematic_indicator_entity:
name: Power Problem Indicator Entity (optional)
description: >
Optional input_boolean that will be turned ON when the device
appears to be malfunctioning (ON but low power consumption).
default: null
selector:
entity:
domain: input_boolean
# =============================================================================
# AUTOMATION MODE
# =============================================================================
# Single mode prevents multiple simultaneous executions
mode: single
# =============================================================================
# TRIGGERS
# =============================================================================
trigger:
# Control switch state changed
- platform: state
entity_id: !input control_switch
# Force ON switch state changed
- platform: state
entity_id: !input force_on_entity
# House window sensor changed (with decay delay)
- platform: state
entity_id: !input house_windows
for:
seconds: !input decay_duration
# Room window sensor changed (with decay delay)
- platform: state
entity_id: !input room_windows
for:
seconds: !input decay_duration
# Room door sensor changed (with decay delay)
- platform: state
entity_id: !input room_doors
for:
seconds: !input decay_duration
# Target value changed
- platform: state
entity_id: !input target_value_entity
# Environment sensor value changed
- platform: state
entity_id: !input env_sensors
# Power sensor dropped below threshold (potential malfunction)
- platform: numeric_state
entity_id: !input power_sensor
below: !input power_threshold
for:
seconds: !input power_decay_duration
# =============================================================================
# CONDITIONS
# =============================================================================
condition: []
# =============================================================================
# ACTIONS
# =============================================================================
action:
- variables:
# -----------------------------------------------------------------------
# Input Variables
# -----------------------------------------------------------------------
env_sensors: !input env_sensors
control_switch: !input control_switch
device_entity: !input device_entity
threshold: !input value_threshold
value_is_low_entity: !input value_is_low_entity
force_on_entity: !input force_on_entity
hysteresis_window: !input hysteresis_window
# -----------------------------------------------------------------------
# Force ON Check
# -----------------------------------------------------------------------
is_force_on: >
{{ force_on_entity is not none and is_state(force_on_entity, 'on') }}
# -----------------------------------------------------------------------
# Target Value & Hysteresis
# -----------------------------------------------------------------------
target_value_entity: !input target_value_entity
target_value: "{{ states(target_value_entity) | float(0) }}"
# Turn-on threshold accounts for hysteresis window
turn_on_threshold: "{{ target_value - hysteresis_window }}"
# -----------------------------------------------------------------------
# Value Statistics
# -----------------------------------------------------------------------
# Calculate sensor counts for different thresholds
# Returns: [count_below_emergency, count_below_turn_on, count_at_or_above_target]
value_stats: >
{% set result = [] %}
{% if env_sensors | length > 0 %}
{% set values = expand(env_sensors) | map(attribute='state') | map('float', default=0) | list %}
{# Count sensors below emergency threshold #}
{% if threshold != 0 %}
{% set result = result + [values | select('lt', threshold) | list | count] %}
{% else %}
{% set result = result + [0] %}
{% endif %}
{# Count sensors below turn-on threshold (target - hysteresis) #}
{% if turn_on_threshold != 0 %}
{% set result = result + [values | select('lt', turn_on_threshold) | list | count] %}
{% else %}
{% set result = result + [0] %}
{% endif %}
{# Count sensors at or above target value (for turn-off decision) #}
{% if target_value != 0 %}
{% set result = result + [values | select('ge', target_value) | list | count] %}
{% else %}
{% set result = result + [0] %}
{% endif %}
{% else %}
{% set result = [0, 0, 0] %}
{% endif %}
{{ result }}
# True if ANY sensor is below the emergency threshold
is_value_below_threshold: "{{ (value_stats[0] | int) > 0 }}"
# True if ANY sensor is below the turn-on threshold (target - hysteresis)
is_value_below_turn_on_threshold: "{{ (value_stats[1] | int) > 0 }}"
# True if ALL sensors are at or above the target value
is_value_at_or_above_target: "{{ (value_stats[2] | int) == (env_sensors | length) and (env_sensors | length) > 0 }}"
# -----------------------------------------------------------------------
# Power Monitoring
# -----------------------------------------------------------------------
power_threshold: !input power_threshold
power_sensor: !input power_sensor
power_problematic_indicator_entity: !input power_problematic_indicator_entity
power_decay_duration: !input power_decay_duration
power: "{{ states(power_sensor) | float(0) }}"
# Device is problematic if it's consuming power but below threshold
is_power_not_ok: "{{ (power > 0 and power < power_threshold) if power_threshold != 0 else false }}"
# -----------------------------------------------------------------------
# Window/Door State
# -----------------------------------------------------------------------
house_windows: !input house_windows
room_windows: !input room_windows
room_doors: !input room_doors
decay: !input decay_duration
# Check if ALL house windows are closed (for decay duration)
house_closed: >
{% if house_windows | length == 0 %}
{{ false }}
{% else %}
{% set ns = namespace(res = true) %}
{% for it in house_windows %}
{% set time_closed = (now() - states[it].last_changed).total_seconds() %}
{% set ns.res = ns.res and (is_state(it, 'off') and time_closed > decay) %}
{% endfor %}
{{ ns.res }}
{% endif %}
# Check if ALL room windows AND doors are closed (for decay duration)
# BUG FIX: Original had wrong operator precedence in condition
room_closed: >
{% set window_count = room_windows | length %}
{% set door_count = room_doors | length %}
{% if window_count == 0 and door_count == 0 %}
{{ false }}
{% else %}
{% set ns = namespace(res = true) %}
{# Check all room windows #}
{% for it in room_windows %}
{% set time_closed = (now() - states[it].last_changed).total_seconds() %}
{% set ns.res = ns.res and (is_state(it, 'off') and time_closed > decay) %}
{% endfor %}
{# Check all room doors #}
{% for it in room_doors %}
{% set time_closed = (now() - states[it].last_changed).total_seconds() %}
{% set ns.res = ns.res and (is_state(it, 'off') and time_closed > decay) %}
{% endfor %}
{{ ns.res }}
{% endif %}
# -----------------------------------------------------------------------
# Schedule Check
# -----------------------------------------------------------------------
schedule_entities: !input schedule_entities
# True if any schedule is active, or if no schedules are configured
schedule_active: >
{% if schedule_entities | length > 0 %}
{{ schedule_entities | select('is_state', 'on') | list | length > 0 }}
{% else %}
{{ true }}
{% endif %}
# -----------------------------------------------------------------------
# Debug Flag
# -----------------------------------------------------------------------
is_debug: false
# ---------------------------------------------------------------------------
# DEBUG: Log current state
# ---------------------------------------------------------------------------
- choose:
- conditions: "{{ is_debug }}"
sequence:
- service: persistent_notification.create
data:
title: "Climate Controller (debug)"
message: >
is_force_on = {{ is_force_on }},
room_closed = {{ room_closed }},
house_closed = {{ house_closed }},
is_value_below_threshold = {{ is_value_below_threshold }},
is_value_below_turn_on_threshold = {{ is_value_below_turn_on_threshold }},
is_value_at_or_above_target = {{ is_value_at_or_above_target }},
target_value = {{ target_value }},
turn_on_threshold = {{ turn_on_threshold }},
schedule_active = {{ schedule_active }},
control_switch = {{ states(control_switch) }}
# ---------------------------------------------------------------------------
# POWER MONITORING: Flag device if malfunctioning
# ---------------------------------------------------------------------------
# Note: For climate entities, 'on' state check works for most HVAC modes.
# For entities that don't use 'on' state, power monitoring may need adjustment.
- choose:
- conditions:
- condition: template
value_template: "{{ states(device_entity) not in ['off', 'unavailable', 'unknown'] and power_problematic_indicator_entity is not none }}"
sequence:
- variables:
# Check if enough time has passed since last power reading
timeout_elapsed: >
{% set last = as_timestamp(states[power_sensor].last_changed) %}
{{ (as_timestamp(now()) - last) > power_decay_duration }}
- condition: template
value_template: "{{ timeout_elapsed }}"
- choose:
# Power is problematic - flag the indicator
- conditions: "{{ is_power_not_ok }}"
sequence:
- service: input_boolean.turn_on
target:
entity_id: "{{ power_problematic_indicator_entity }}"
# Power is OK - clear the indicator
default:
- service: input_boolean.turn_off
target:
entity_id: "{{ power_problematic_indicator_entity }}"
# ---------------------------------------------------------------------------
# LOW VALUE INDICATOR: Update the low value indicator entity
# ---------------------------------------------------------------------------
- choose:
- conditions:
- condition: template
value_template: "{{ value_is_low_entity is not none }}"
sequence:
- choose:
# Value is below threshold - turn on indicator
- conditions:
- condition: template
value_template: "{{ is_value_below_threshold }}"
sequence:
- service: input_boolean.turn_on
target:
entity_id: !input value_is_low_entity
# Value is OK - turn off indicator
default:
- service: input_boolean.turn_off
target:
entity_id: !input value_is_low_entity
# ===========================================================================
# MAIN CONTROL LOGIC (Priority Order)
# ===========================================================================
- choose:
# -----------------------------------------------------------------------
# PRIORITY 1: Force ON Override (Manual)
# -----------------------------------------------------------------------
# If force ON switch is enabled, FORCE device ON regardless of all else
- conditions:
- condition: template
value_template: "{{ is_force_on }}"
sequence: !input turn_on_action
# -----------------------------------------------------------------------
# PRIORITY 2: Emergency Override (Safety)
# -----------------------------------------------------------------------
# If value is below emergency threshold, FORCE device ON
- conditions:
- condition: template
value_template: "{{ is_value_below_threshold }}"
sequence: !input turn_on_action
# -----------------------------------------------------------------------
# PRIORITY 3: Control Switch Off
# -----------------------------------------------------------------------
# If master control is disabled, FORCE device OFF
- conditions:
- condition: template
value_template: "{{ is_state(control_switch, 'off') }}"
sequence: !input turn_off_action
# -----------------------------------------------------------------------
# PRIORITY 4: Environment Not Ready
# -----------------------------------------------------------------------
# If environment not sealed OR schedule not active, turn OFF
- conditions:
- condition: template
value_template: "{{ not (house_closed or room_closed) or not schedule_active }}"
sequence: !input turn_off_action
# -----------------------------------------------------------------------
# PRIORITY 5: Target Reached
# -----------------------------------------------------------------------
# If all sensors at or above target value, turn OFF
- conditions:
- condition: template
value_template: "{{ is_value_at_or_above_target }}"
sequence: !input turn_off_action
# -----------------------------------------------------------------------
# PRIORITY 6: Below Turn-On Threshold
# -----------------------------------------------------------------------
# If any sensor below turn-on threshold (target - hysteresis), turn ON
- conditions:
- condition: template
value_template: "{{ is_value_below_turn_on_threshold }}"
sequence: !input turn_on_action
# -------------------------------------------------------------------------
# DEFAULT: Maintain Current State (Hysteresis Zone)
# -------------------------------------------------------------------------
# Value is between turn-on threshold and target - don't change device state
# This prevents rapid on/off cycling when value is near the target
default: []