179 lines
6.5 KiB
YAML
179 lines
6.5 KiB
YAML
blueprint:
|
||
name: Area Valve Temperature Offset Calibration
|
||
author: Andreas Gammelgaard Damsbo
|
||
description: |
|
||
Automatically calibrate the temperature offset of ALL smart radiator valves (TRVs)
|
||
in a specified area using readings from an external room temperature sensor.
|
||
|
||
The blueprint discovers all climate entities in the chosen area and updates their
|
||
corresponding temperature offset entities based on the difference between each
|
||
valve's internal sensor and the actual room temperature.
|
||
|
||
⚙️ Example use case:
|
||
- **area**: Living Room
|
||
- **external_sensor**: sensor.living_room_temperature
|
||
- **rounding_step**: 1.0
|
||
|
||
The blueprint will find all thermostats in "Living Room" and calibrate each one
|
||
to match the external sensor reading. Thermostats without offset entities are
|
||
automatically skipped.
|
||
|
||
➕ Manual correction:
|
||
Use the **manual_correction** slider to bias the calculated offset in cases where
|
||
valves tend to stop heating too early or overheat even after calibration.
|
||
|
||
- Positive value (e.g., +0.3 °C): Valves assume room is warmer → reduce heating sooner
|
||
- Negative value (e.g., −0.3 °C): Valves assume room is cooler → prolong heating
|
||
|
||
domain: automation
|
||
source_url:
|
||
|
||
input:
|
||
target_area:
|
||
name: Area
|
||
description: >
|
||
Select the area containing the thermostatic valves you want to calibrate.
|
||
All climate entities in this area will be automatically calibrated using
|
||
the external temperature sensor.
|
||
selector:
|
||
area: {}
|
||
|
||
external_sensor:
|
||
name: External temperature sensor
|
||
description: >
|
||
Select the temperature sensor that measures the actual room temperature
|
||
for this area. This should be a reliable sensor that provides accurate
|
||
ambient temperature readings.
|
||
Example: sensor.living_room_temperature
|
||
selector:
|
||
entity:
|
||
domain: sensor
|
||
device_class: temperature
|
||
|
||
min_interval:
|
||
name: Minimum time between updates
|
||
description: >
|
||
The minimum amount of time (in seconds) that must pass before offsets
|
||
can be updated again. This prevents valves from being recalibrated too often.
|
||
A typical value is 300 seconds (5 minutes).
|
||
default: 300
|
||
selector:
|
||
number:
|
||
min: 30
|
||
max: 3600
|
||
step: 1
|
||
mode: box
|
||
unit_of_measurement: s
|
||
|
||
rounding_step:
|
||
name: Rounding step for offset value
|
||
description: >
|
||
Define the step to which calculated offsets should be rounded.
|
||
Use 0.5 for valves that support half-degree steps, or 1.0 for full degrees.
|
||
default: 1.0
|
||
selector:
|
||
number:
|
||
min: 0.1
|
||
max: 2
|
||
step: 0.1
|
||
mode: box
|
||
unit_of_measurement: "°C"
|
||
|
||
manual_correction:
|
||
name: Manual correction (bias)
|
||
description: >
|
||
Additional manual bias applied to computed offsets **before rounding**.
|
||
Use this if valves still behave undesirably after calibration:
|
||
- **Positive value** (e.g., +0.1…+1.0 °C): Makes valves think it's **warmer** → **reduces** heating sooner
|
||
- **Negative value** (e.g., −0.1…−1.0 °C): Makes valves think it's **cooler** → **prolongs** heating
|
||
Recommended start: 0.0 °C. Adjust in small steps (±0.1 °C) and observe behavior.
|
||
default: 0.0
|
||
selector:
|
||
number:
|
||
min: -1.0
|
||
max: 1.0
|
||
step: 0.1
|
||
mode: slider
|
||
unit_of_measurement: "°C"
|
||
|
||
offset_entity_suffix:
|
||
name: Offset entity suffix pattern
|
||
description: >
|
||
The text pattern used to identify offset number entities.
|
||
By default, looks for entities containing "temperature_offset" or "local_temperature_offset".
|
||
Only change this if your devices use a different naming pattern.
|
||
default: "temperature_offset"
|
||
selector:
|
||
text: {}
|
||
|
||
mode: queued
|
||
max: 10
|
||
max_exceeded: silent
|
||
|
||
trigger:
|
||
- platform: state
|
||
entity_id: !input external_sensor
|
||
- platform: state
|
||
entity_id: climate.*
|
||
attribute: current_temperature
|
||
|
||
action:
|
||
# Rate limiting check
|
||
- condition: template
|
||
value_template: >
|
||
{% set last = state_attr(this.entity_id, 'last_triggered') %}
|
||
{{ last is none or (as_timestamp(now()) - as_timestamp(last)) > min_interval }}
|
||
|
||
# Process each climate entity in the area
|
||
- repeat:
|
||
for_each: >
|
||
{{ area_entities(target_area) | select('match', '^climate\.') | list }}
|
||
sequence:
|
||
- variables:
|
||
climate_entity: "{{ repeat.item }}"
|
||
climate_device: "{{ device_id(climate_entity) }}"
|
||
external_temp: "{{ states(external_sensor) | float(0) }}"
|
||
valve_temp: "{{ state_attr(climate_entity, 'current_temperature') | float(0) }}"
|
||
|
||
# Skip if climate entity is unavailable or has no valid temperature
|
||
- condition: template
|
||
value_template: >
|
||
{{ states(climate_entity) not in ['unknown', 'unavailable'] and valve_temp > 0 and external_temp > 0 }}
|
||
|
||
# Find offset entity for this device
|
||
- variables:
|
||
offset_entity: >
|
||
{% set entities = device_entities(climate_device) if climate_device else [] %}
|
||
{% set offset_entities = entities | select('match', '^number\.') | select('search', offset_entity_suffix) | list %}
|
||
{{ offset_entities[0] if offset_entities else '' }}
|
||
|
||
# Skip if no offset entity found
|
||
- condition: template
|
||
value_template: "{{ offset_entity != '' }}"
|
||
|
||
- condition: template
|
||
value_template: "{{ states(offset_entity) not in ['unknown', 'unavailable'] }}"
|
||
|
||
# Calculate and set new offset
|
||
- variables:
|
||
current_offset: "{{ states(offset_entity) | float(0) }}"
|
||
raw_offset: "{{ external_temp - (valve_temp - current_offset) + manual_correction }}"
|
||
new_offset: "{{ ((raw_offset / rounding_step) | round(0)) * rounding_step }}"
|
||
|
||
- service: number.set_value
|
||
target:
|
||
entity_id: "{{ offset_entity }}"
|
||
data:
|
||
value: "{{ new_offset }}"
|
||
|
||
# Small delay between updates to avoid overwhelming the system
|
||
- delay:
|
||
milliseconds: 100
|
||
|
||
variables:
|
||
target_area: !input target_area
|
||
external_sensor: !input external_sensor
|
||
min_interval: !input min_interval
|
||
rounding_step: !input rounding_step
|
||
manual_correction: !input manual_correction
|
||
offset_entity_suffix: !input offset_entity_suffix
|