Update ha-thermostat-calibration.yaml

revising logic to follow time pattern
This commit is contained in:
Andreas Gammelgaard Damsbo 2025-12-06 12:03:47 +01:00
parent 26e8744b59
commit d92a8734ed

View file

@ -1,10 +1,13 @@
blueprint: blueprint:
name: Area Valve Temperature Offset Calibration name: Area Valve Temperature Offset Calibration
author: Andreas Gammelgaard Damsbo author: Andreas Gammelgaard Damsbo (Revised by AI)
description: | description: |
Automatically calibrate the temperature offset of ALL smart radiator valves (TRVs) Automatically calibrate the temperature offset of ALL smart radiator valves (TRVs)
in a specified area using readings from an external room temperature sensor. in a specified area using readings from an external room temperature sensor.
This version runs on a time schedule and only updates the offset if the calculated
value differs from the current value on the valve, reducing unnecessary communication.
The blueprint discovers all climate entities in the chosen area and updates their The blueprint discovers all climate entities in the chosen area and updates their
corresponding temperature offset entities based on the difference between each corresponding temperature offset entities based on the difference between each
valve's internal sensor and the actual room temperature. valve's internal sensor and the actual room temperature.
@ -12,18 +15,7 @@ blueprint:
Example use case: Example use case:
- **area**: Living Room - **area**: Living Room
- **external_sensor**: sensor.living_room_temperature - **external_sensor**: sensor.living_room_temperature
- **rounding_step**: 1.0 - **run_interval**: /10 (every 10 minutes)
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 domain: automation
source_url: https://gdamsbo.dk/forgejo/andreas/ha-thermostat-calibration/raw/branch/main/ha-thermostat-calibration.yaml source_url: https://gdamsbo.dk/forgejo/andreas/ha-thermostat-calibration/raw/branch/main/ha-thermostat-calibration.yaml
@ -33,8 +25,7 @@ blueprint:
name: Area name: Area
description: > description: >
Select the area containing the thermostatic valves you want to calibrate. Select the area containing the thermostatic valves you want to calibrate.
All climate entities in this area will be automatically calibrated using All climate entities in this area will be automatically calibrated.
the external temperature sensor.
selector: selector:
area: {} area: {}
@ -42,28 +33,20 @@ blueprint:
name: External temperature sensor name: External temperature sensor
description: > description: >
Select the temperature sensor that measures the actual room temperature Select the temperature sensor that measures the actual room temperature
for this area. This should be a reliable sensor that provides accurate for this area.
ambient temperature readings.
Example: sensor.living_room_temperature
selector: selector:
entity: entity:
domain: sensor domain: sensor
device_class: temperature device_class: temperature
min_interval: run_interval:
name: Minimum time between updates name: Run Interval
description: > description: >
The minimum amount of time (in seconds) that must pass before offsets How often to check for offset updates. Format is a time pattern string
can be updated again. This prevents valves from being recalibrated too often. (e.g., '/10' for every 10 minutes, '0' for every hour on the hour).
A typical value is 300 seconds (5 minutes). default: "/10"
default: 300
selector: selector:
number: text: {}
min: 30
max: 3600
step: 1
mode: box
unit_of_measurement: s
rounding_step: rounding_step:
name: Rounding step for offset value name: Rounding step for offset value
@ -83,10 +66,8 @@ blueprint:
name: Manual correction (bias) name: Manual correction (bias)
description: > description: >
Additional manual bias applied to computed offsets **before rounding**. Additional manual bias applied to computed offsets **before rounding**.
Use this if valves still behave undesirably after calibration: Positive value makes valves think it's warmer (reduces heating sooner).
- **Positive value** (e.g., +0.1…+1.0 °C): Makes valves think it's **warmer** → **reduces** heating sooner Negative value makes valves think it's cooler (prolongs heating).
- **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 default: 0.0
selector: selector:
number: number:
@ -100,8 +81,7 @@ blueprint:
name: Offset entity suffix pattern name: Offset entity suffix pattern
description: > description: >
The text pattern used to identify offset number entities. The text pattern used to identify offset number entities.
By default, looks for entities containing "_local_temperature_offset". Default: "_local_temperature_offset"
Only change this if your devices use a different naming pattern.
default: "_local_temperature_offset" default: "_local_temperature_offset"
selector: selector:
text: {} text: {}
@ -111,19 +91,10 @@ max: 10
max_exceeded: silent max_exceeded: silent
trigger: trigger:
- platform: state - platform: time_pattern
entity_id: !input external_sensor minutes: !input run_interval
- platform: state
entity_id: climate.*
attribute: current_temperature
action: 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 # Process each climate entity in the area
- repeat: - repeat:
for_each: > for_each: >
@ -147,19 +118,30 @@ action:
{% set offset_entities = entities | select('match', '^number\.') | select('search', offset_entity_suffix) | list %} {% set offset_entities = entities | select('match', '^number\.') | select('search', offset_entity_suffix) | list %}
{{ offset_entities[0] if offset_entities else '' }} {{ offset_entities[0] if offset_entities else '' }}
# Skip if no offset entity found # Skip if no offset entity found or is unavailable
- condition: template - condition: template
value_template: "{{ offset_entity != '' }}" value_template: "{{ offset_entity != '' and states(offset_entity) not in ['unknown', 'unavailable'] }}"
- condition: template
value_template: "{{ states(offset_entity) not in ['unknown', 'unavailable'] }}"
# Calculate and set new offset # Calculate and set new offset
- variables: - variables:
current_offset: "{{ states(offset_entity) | float(0) }}" 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 }}" # 1. Calculate the required offset based on the difference
# Required Offset = External Temp - (Valve Internal Temp - Current Offset) + Manual Correction
raw_offset_required: "{{ external_temp - (valve_temp - current_offset) + manual_correction }}"
# 2. Round the raw offset to the nearest rounding_step
# Safely scale, round, and scale back to minimize floating point errors
new_offset: >
{% set step = rounding_step | float(1.0) %}
{% set scale = (1 / step) | round(0) %}
{{ (raw_offset_required * scale) | round(0) / scale }}
# Only update if the calculated value is different from the current value
- condition: template
value_template: "{{ new_offset | round(3) != current_offset | round(3) }}"
# Set new offset
- service: number.set_value - service: number.set_value
target: target:
entity_id: "{{ offset_entity }}" entity_id: "{{ offset_entity }}"
@ -173,7 +155,7 @@ action:
variables: variables:
target_area: !input target_area target_area: !input target_area
external_sensor: !input external_sensor external_sensor: !input external_sensor
min_interval: !input min_interval run_interval: !input run_interval
rounding_step: !input rounding_step rounding_step: !input rounding_step
manual_correction: !input manual_correction manual_correction: !input manual_correction
offset_entity_suffix: !input offset_entity_suffix offset_entity_suffix: !input offset_entity_suffix