Files
HomeAssistantBlueprints/Climate/bosch_bth-ra_control.yaml
2026-01-07 20:39:30 +01:00

536 lines
21 KiB
YAML

mode: queued
max: 10
blueprint:
name: Bosch BTH-RA Radiator Control
homeassistant:
min_version: "2024.6.0"
description: >
Eine Automation zur automatischen Steuerung eines Bosch TRV Heizkörperthermostats.
domain: automation
author: Me
input:
trv:
name: Thermostat
description: Thermostat muss ein Bosch BTH-RA sein
selector:
entity:
multiple: false
filter:
- domain: climate
temperature_sensor:
name: Temperatursensor
selector:
entity:
multiple: false
filter:
- domain: sensor
device_class: temperature
sensor_sync_threshold:
name: Sensor-Synchronisations-Schwellenwert
description: Minimale Temperaturdifferenz in °C, um eine Sensor-Synchronisation auszulösen (Default = 0.5°C). Temperatur wird weiterhin spätestens nach 25 Minuten synchronisiert.
default: 0.5
selector:
number:
mode: box
min: 0.1
max: 1.0
unit_of_measurement: "°C"
step: 0.1
heating_period_switch:
name: Heizperiode Switch
description: Optional - Input Boolean der angibt ob Heizperiode aktiv ist. Wenn nicht gesetzt, ist die Heizperiode immer aktiv.
selector:
entity:
multiple: false
filter:
- domain: input_boolean
min_temperature:
name: Minimale Temperatur
description: Minimale erlaubte Solltemperatur als Sicherheitsgrenze (Default = 15°C)
default: 15
selector:
number:
mode: box
min: 10.0
max: 20.0
unit_of_measurement: "°C"
step: 0.5
max_temperature:
name: Maximale Temperatur
description: Maximale erlaubte Solltemperatur als Sicherheitsgrenze (Default = 28°C)
default: 23
selector:
number:
mode: box
min: 20.0
max: 25.0
unit_of_measurement: "°C"
step: 0.5
window_section:
name: Fenster-/Türsensor Konfiguration
description: Konfiguration für den Fenster-/Türsensor.
collapsed: true
input:
window_sensor:
name: Fenster-/Türsensor (oder Gruppe)
selector:
entity:
multiple: false
filter:
- domain: binary_sensor
window_delay_open:
name: Fenster-/Türsensor Verzögerung
description: Zeit die das Fenster offen bleiben muss, um die den Radiator in den "Fenster offen"-Modus zu versetzen (Default = 30s)
default: 30
selector:
number:
mode: box
min: 0.0
max: 3600.0
unit_of_measurement: seconds
step: 1.0
window_delay_close:
name: Fenster-/Türsensor Verzögerung
description: Zeit die das Fenster geschlossen bleiben muss, um die den Radiator in den "Fenster geschlossen"-Modus zu versetzen (Default = 5s)
default: 5
selector:
number:
mode: box
min: 0.0
max: 3600.0
unit_of_measurement: seconds
step: 1.0
schedule_section:
name: Heizplan Konfiguration
description: Konfiguration der Heizpläne die für den Radiator berücksichtigt werden sollen
collapsed: true
input:
radiator_schedules:
name: Heizpläne
description: Alle Heizpläne die für den Radiator berücksichtigt werden sollen. Sind mehrere Schedules aktiv, wird der erste mit einer gültigen Temperatur verwendet.
default: []
selector:
entity:
multiple: true
filter:
- domain: schedule
active_scheduler_selector:
name: Aktiver Scheduler Selector
description: >
Input Select der den aktuell aktiven Scheduler enthält (per Friendly Name).
Wird verwendet um zu bestimmen welcher Schedule für die Temperatur verwendet werden soll.
**WICHTIG**: Die Options müssen die Friendly Names der Scheduler aus der Scheduler-Liste enthalten.
**HINWEIS**: Wenn der angezeigte Scheduler inaktiv ist (off), wird automatisch
auf die Abwesenheitstemperatur zurückgegriffen.
Beispiel: Wird typischerweise vom "Heizplan Selector" Blueprint gesteuert.
selector:
entity:
filter:
- domain: input_select
away_section:
name: Konfiguration für Abwesenheitsmodus
description: Konfiguration für das Absenken der Heizung im Abwesenheitsmodus eines Alarmsystems
collapsed: true
input:
alarm_control_panel:
name: Alarm Control Panel
description: Optional - Alarm Control Panel um Heizung abzusenken, wenn Alarm im Abwesendmodus ist
selector:
entity:
multiple: false
filter:
- domain: alarm_control_panel
away_temperature:
name: Abwesenheitstemperatur
description: Temperatur die eingestellt wird, wenn der Alarm im Abwesendmodus ist (Default = 18°C)
default: 18
selector:
number:
mode: box
min: 15.0
max: 25.0
unit_of_measurement: "°C"
step: 0.5
override_section:
name: Override Reset Konfiguration
description: Automatisches Zurücksetzen der Solltemperatur nach manueller Änderung am Thermostat
collapsed: true
input:
override_reset_duration:
name: Override Reset Zeitraum
description: >
Zeit nach der eine manuelle Temperaturänderung automatisch auf den Heizplan zurückgesetzt wird.
**Feature deaktiviert wenn auf 00:00:00 gesetzt** (Default = 00:00:00)
**Verhalten:**
- Nur manuelle Änderungen am Thermostat werden zurückgesetzt (nicht über Home Assistant)
- Timer startet neu bei jeder weiteren manuellen Änderung
- Reset erfolgt nur wenn Solltemperatur vom Heizplan abweicht
- Wenn kein Schedule aktiv ist, wird auf Abwesenheitstemperatur zurückgesetzt
- Alarm-Modus hat Priorität: Bei aktivem Alarm erfolgt kein Reset (Abwesenheitstemperatur gilt)
Beispiel: Nach 2 Stunden wird die Temperatur wieder auf den aktiven Heizplan-Wert gesetzt.
default:
hours: 0
minutes: 0
seconds: 0
selector:
duration:
enable_day: false
variables:
trv: !input trv
temperature_sensor: !input temperature_sensor
sensor_sync_threshold: !input sensor_sync_threshold
heating_period_switch: !input heating_period_switch
radiator_schedules: !input radiator_schedules
active_scheduler_selector: !input active_scheduler_selector
away_temperature: !input away_temperature
min_temperature: !input min_temperature
max_temperature: !input max_temperature
alarm_control_panel: !input alarm_control_panel
override_reset_duration: !input override_reset_duration
temperature_change_tolerance: 0.4 # °C
sensor_sync_max_age: 1499 # Sekunden (25 Minuten - 1 Sekunde)
is_heating_period: >
{{ heating_period_switch in [none, ''] or is_state(heating_period_switch, 'on') }}
remote_temperature_entity: >
{{ device_entities(device_id(trv)) | select('search', 'remote_temperature') | list | first | default('') }}
remote_temperature_entity_valid: >
{{ remote_temperature_entity and states(remote_temperature_entity) not in ['unknown', 'unavailable', none] }}
setpoint_change_source_entity_valid: >
{{ setpoint_change_source_entity and states(setpoint_change_source_entity) not in ['unknown', 'unavailable', none] }}
current_remote_temperature: >
{{ states(remote_temperature_entity) | float(0) if remote_temperature_entity_valid else 0 }}
remote_temperature_last_change: >
{{ states[remote_temperature_entity].last_changed if remote_temperature_entity_valid else none }}
window_detection_entity: >
{{ device_entities(device_id(trv)) | select('search', 'window_detection') | list | first | default('') }}
setpoint_change_source_entity: >
{{ device_entities(device_id(trv)) | select('search', 'setpoint_change_source') | list | first | default('') }}
scheduled_temperature: >
{% set ns = namespace(current_temperature = none) %}
{% set selected_friendly_name = states(active_scheduler_selector) %}
{% if selected_friendly_name is not none and selected_friendly_name != 'unknown' %}
{% for schedule in radiator_schedules %}
{% if state_attr(schedule, 'friendly_name') == selected_friendly_name and states(schedule) == 'on' %}
{% set temp = state_attr(schedule, 'temp') %}
{% if temp is not none and temp | is_number %}
{% set ns.current_temperature = temp %}
{% break %}
{% endif %}
{% endif %}
{% endfor %}
{% endif %}
{{ ns.current_temperature }}
target_temperature: >
{% if alarm_control_panel and is_state(alarm_control_panel, 'armed_away') %}
{{ away_temperature }}
{% elif scheduled_temperature != none %}
{{ scheduled_temperature }}
{% else %}
{{ away_temperature }}
{% endif %}
safe_temperature: >
{{ [min_temperature, [max_temperature, target_temperature | float(18)] | min] | max }}
is_valid_temperature: >
{{ safe_temperature is not none and safe_temperature | is_number }}
is_temperature_change_needed: >
{{ (safe_temperature | float(0) - state_attr(trv, 'temperature') | float(0)) | abs >= temperature_change_tolerance }}
is_sensor_sync_needed: >
{% set new_sensor_temp = states(temperature_sensor) | float(0) %}
{% set target_temp = state_attr(trv, 'temperature') | float(0) %}
{% set diff = (new_sensor_temp - current_remote_temperature) | abs %}
{{ diff >= sensor_sync_threshold or
(new_sensor_temp < target_temp and current_remote_temperature >= target_temp) or
(new_sensor_temp > target_temp and current_remote_temperature <= target_temp) }}
scheduler_mismatch: >
{% set selected = states(active_scheduler_selector) %}
{% if selected not in ['unknown', '', none] %}
{% set found = namespace(value=false) %}
{% for schedule in radiator_schedules %}
{% if state_attr(schedule, 'friendly_name') == selected %}
{% set found.value = true %}
{% endif %}
{% endfor %}
{{ not found.value }}
{% else %}
false
{% endif %}
is_manual_override: >
{{ (state_attr(trv, 'temperature') | float(0) - safe_temperature | float(0)) | abs >= temperature_change_tolerance }}
override_reset_duration_seconds: >
{{ (override_reset_duration.hours | default(0) | int) * 3600 +
(override_reset_duration.minutes | default(0) | int) * 60 +
(override_reset_duration.seconds | default(0) | int) if override_reset_duration is mapping else 0 }}
override_last_change: >
{{ states[setpoint_change_source_entity].last_updated if setpoint_change_source_entity_valid else none }}
override_duration_exceeded: >
{% if override_last_change == none or override_reset_duration_seconds == 0 %}
false
{% elif not setpoint_change_source_entity_valid or states(setpoint_change_source_entity) != 'manual' %}
false
{% else %}
{% set last_change = as_datetime(override_last_change) %}
{{ last_change != none and (now() - last_change).total_seconds() > override_reset_duration_seconds }}
{% endif %}
triggers:
- platform: state
entity_id:
- !input window_sensor
from: "off"
to: "on"
for: !input window_delay_open
id: FENSTER_OPEN
- platform: state
entity_id:
- !input window_sensor
from: "on"
to: "off"
for: !input window_delay_close
id: FENSTER_CLOSED
- platform: time_pattern
# Periodische Überprüfung alle 5 Minuten (Temperatursynchronisation, Override-Check)
minutes: "/5"
id: PERIODIC_CHECK
- platform: state
entity_id: !input temperature_sensor
id: TEMP_CHANGED
- platform: state
entity_id: !input alarm_control_panel
to: "armed_away"
id: ALARM_ARMED_AWAY
- platform: state
entity_id: !input alarm_control_panel
from: "armed_away"
to: "disarmed"
id: ALARM_DISARMED_AWAY
- platform: state
entity_id: !input heating_period_switch
to: "on"
id: HEATING_PERIOD_ON
- platform: state
entity_id: !input heating_period_switch
to: "off"
id: HEATING_PERIOD_OFF
- platform: state
entity_id: !input radiator_schedules
attribute: temp
id: SCHEDULE_TEMP_CHANGED
- platform: state
entity_id: !input active_scheduler_selector
id: SCHEDULER_CHANGED
actions:
- choose:
# Heizperiode Switch Aktionen
- conditions:
- condition: trigger
id:
- HEATING_PERIOD_ON
- HEATING_PERIOD_OFF
sequence:
- choose:
- conditions:
- condition: trigger
id:
- HEATING_PERIOD_ON
sequence:
- service: climate.set_hvac_mode
target:
entity_id: !input trv
data:
hvac_mode: "heat"
- if:
- condition: state
entity_id: !input window_sensor
state: "on"
then:
- service: switch.turn_on
target:
entity_id: "{{ window_detection_entity }}"
- if:
- condition: template
value_template: >
{{ is_valid_temperature and is_temperature_change_needed }}
then:
- service: climate.set_temperature
target:
entity_id: !input trv
data:
temperature: "{{ safe_temperature | float }}"
alias: Heizperiode aktiviert - Setze Modus auf heat, synchronisiere Fensterstatus und Solltemperatur
- conditions:
- condition: trigger
id:
- HEATING_PERIOD_OFF
sequence:
- service: switch.turn_off
target:
entity_id: "{{ window_detection_entity }}"
- service: climate.set_hvac_mode
target:
entity_id: !input trv
data:
hvac_mode: "off"
alias: Heizperiode deaktiviert - Setze Fenster geschlossen und Modus auf off
alias: Heizperiode Switch Änderung
alias: Heizperiode Switch Aktionen
# Alle bestehenden Aktionen nur ausführen wenn Heizperiode aktiv ist
- conditions:
- condition: template
value_template: "{{ is_heating_period }}"
sequence:
# setze Fenster auf offen/geschlossen
- choose:
- conditions:
- condition: trigger
id:
- FENSTER_OPEN
- condition: template
value_template: >
{{ is_state(window_detection_entity, 'off') }}
sequence:
- service: switch.turn_on
target:
entity_id: "{{ window_detection_entity }}"
alias: Setze Fenster auf offen
- conditions:
- condition: trigger
id:
- FENSTER_CLOSED
- condition: template
value_template: >
{{ is_state(window_detection_entity, 'on') }}
sequence:
- service: switch.turn_off
target:
entity_id: "{{ window_detection_entity }}"
- if:
- condition: template
value_template: >
{{ is_valid_temperature and is_temperature_change_needed }}
then:
- service: climate.set_temperature
target:
entity_id: !input trv
data:
temperature: "{{ safe_temperature | float }}"
alias: Setze Fenster auf geschlossen und setze Solltemperatur auf Wert aus Zeitplan (wenn vorhanden)
alias: Fensterstatus Änderung
# temperature sensor sync
- choose:
- conditions:
- condition: trigger
id:
- TEMP_CHANGED
- condition: template
value_template: >
{{ temperature_sensor is defined and states(temperature_sensor) | is_number }}
- condition: template
value_template: "{{ is_sensor_sync_needed }}"
sequence:
- service: number.set_value
data:
value: "{{ states(temperature_sensor) | float }}"
target:
entity_id: "{{ remote_temperature_entity }}"
alias: Synchronisiere Temperatur am TRV (bei Änderung)
- conditions:
- condition: trigger
id:
- PERIODIC_CHECK
- condition: template
value_template: >
{{ temperature_sensor is defined and states(temperature_sensor) | is_number }}
- condition: template
value_template: >
{% if remote_temperature_last_change != none %}
{% set last_change = as_datetime(remote_temperature_last_change) %}
{{ last_change != none and (now() - last_change).total_seconds() > sensor_sync_max_age }}
{% else %}
false
{% endif %}
sequence:
- service: number.set_value
data:
value: "{{ states(temperature_sensor) | float }}"
target:
entity_id: "{{ remote_temperature_entity }}"
alias: Synchronisiere Temperatur am TRV (zeitbasiert, wenn länger als 25min unverändert)
alias: Temperatursynchronisation
# override reset
- choose:
- conditions:
- condition: trigger
id:
- PERIODIC_CHECK
- condition: template
value_template: "{{ override_reset_duration_seconds > 0 }}"
- condition: template
value_template: "{{ is_manual_override }}"
- condition: template
value_template: "{{ override_duration_exceeded }}"
sequence:
- service: climate.set_temperature
target:
entity_id: !input trv
data:
temperature: "{{ safe_temperature | float }}"
alias: Setze Solltemperatur zurück nach manuellem Override
alias: Override Reset
# setze Solltemperatur bei Schedule und Alarm-Status-Änderungen
- choose:
- conditions:
- condition: trigger
id:
- ALARM_ARMED_AWAY
- ALARM_DISARMED_AWAY
- SCHEDULE_TEMP_CHANGED
- SCHEDULER_CHANGED
- condition: template
value_template: >
{% if trigger.id == 'SCHEDULE_TEMP_CHANGED' %}
{% set selected_friendly_name = states(active_scheduler_selector) %}
{{ state_attr(trigger.entity_id, 'friendly_name') == selected_friendly_name }}
{% else %}
true
{% endif %}
sequence:
- if:
- condition: template
value_template: >
{{ is_valid_temperature and is_temperature_change_needed }}
then:
- service: climate.set_temperature
target:
entity_id: !input trv
data:
temperature: "{{ safe_temperature | float }}"
# Notification bei Scheduler-Mismatch
- if:
- condition: template
value_template: "{{ scheduler_mismatch and trigger.id == 'SCHEDULER_CHANGED' }}"
then:
- service: persistent_notification.create
data:
title: "⚠️ Radiator Control - Scheduler nicht gefunden"
message: >
Der ausgewählte Scheduler '{{ states(active_scheduler_selector) }}' wurde nicht in der
Scheduler-Liste gefunden.
TRV '{{ state_attr(trv, 'friendly_name') }}' nutzt Fallback-Temperatur: {{ safe_temperature }}°C
(Original: {{ target_temperature }}°C, Limits: {{ min_temperature }}-{{ max_temperature }}°C)
Bitte Konfiguration überprüfen.
notification_id: "radiator_control_scheduler_mismatch_{{ trv }}"
alias: Setze Solltemperatur
alias: Solltemperatur bei Änderungen
alias: Aktionen während Heizperiode
alias: Hauptsteuerung