E6a Heizung und Smart-Home-Zentrale: Umsetzungsplan¶
Für ausführende Agenten: Pflicht-Skill:
superpowers:subagent-driven-development(empfohlen) odersuperpowers:executing-plans. Aufgaben Schritt für Schritt abarbeiten, Fortschritt über die Checkboxen (- [ ]) festhalten.
Ziel: Die Heizung läuft je Raum über Versatile Thermostat (VTherm) statt AHC und Better Thermostat. Die Aqara-E1-TRVs regeln auf die echte Raumtemperatur, jeder Befehl wird auf Zustellung geprüft, und ein Wand-iPad bedient alles.
Architektur: Vier Schichten:
- Raumklima: Sensorgruppen
- 7 VTherm-Raumregler (over_climate)
- eine eigene Bausteinschicht: Einspeisung, Zustellprüfung, Tagesprofil, Übersteuerung
- Bedienung: Dashboard
Die Umstellung läuft Raum für Raum über einen Migrationsschalter je Raum. Das Bad ist Testraum, das Wohnzimmer kommt zuletzt.
Technik: Home Assistant 2026.7.2 über ha-mcp (Config-API, keine YAML-Dateien), VTherm ≥ 10.5.0 (HACS), Zigbee2MQTT 2.12 über MQTT, Mushroom (installiert), native Karten.
Spec: heizung-design.md. Sie ist gemergt (homelab-documentation#65, Review vom 08.10.2026). Plan und Spec gehören zusammen, ausführende Agenten lesen beide.
Globale Vorgaben¶
- Änderungen in HA nur über ha-mcp (Config-API). Keine Datei unter
/configbearbeiten, kein.storage. - Vor jeder Automation, jedem Helfer und jedem Dashboard den Skill
home-assistant-best-practicesladen (ha-mcp-Ressourceskill://home-assistant-best-practices/SKILL.md). - Robins Hand (Claude bereitet vor, Robin führt aus):
- Einrichtungsdialoge von Integrationen (VTherm, HomeKit-Bridge-Filter)
- HA-Neustart
- iPad
- Batterien
- Einen HA-Neustart kann Claude per ha-mcp auslösen, aber nur mit Robins OK im Moment (#239: destruktive Werkzeuge).
- Z2M-Schreibzugriffe über
bridge/request/*(Availability-Timeouts) nur mit Robins ausdrücklichem OK. Gerätewerte (external_temperature_input,sensor) schreibt HA im Betrieb übermqtt.publishbzw.select.select_option. Das ist Teil des Designs und gilt als freigegeben. - Meldeweg:
notify.mobile_app_robins_iphone+persistent_notification. HA hat kein ntfy. - Nicht einsetzen: card-mod, ApexCharts, simple-thermostat, scheduler-component (Spec 7).
- Raumschlüssel (überall gleich):
abstellraum,arbeitszimmer,badezimmer,esszimmer,kueche,schlafzimmer,wohnzimmer. - TRVs:
climate.<raum>_heizung; im Wohnzimmerclimate.wohnzimmer_heizung_vorneundclimate.wohnzimmer_heizung_hinten. Je TRV gibt esselect.<trv>_sensor(internal/external),switch.<trv>_window_detectionundnumber.<trv>_external_temperature_input(HA-Schritt 1, deshalb per MQTT schreiben). - Raumtemperatur:
sensor.<raum>_sensor_temperature(Name bleibt). Fenstergruppen:binary_sensor.<raum>_sensor_window, Ausnahme Arbeitszimmer:binary_sensor.arbeitszimmer_sensor_fenster. - VTherm-Raumregler heißen „Heizung
“ . Erwartete Entität:climate.heizung_<raum>. Die tatsächliche ID wird nach dem Anlegen geprüft und in Task 4 festgehalten. - Preset-Werte (Spec 5.1): Komfort Abstellraum 20, Arbeitszimmer 21, Bad 21, Esszimmer 20, Küche 21, Schlafzimmer 20, Wohnzimmer 20. Eco 17, Arbeitszimmer 17,5. Boost Bad 24. Frost 7.
- Texte in der Oberfläche auf Deutsch mit echten Umlauten.
Review-Fokus¶
Fünf Fälle, die die Spec nahelegt, aber keine Aufgabe von selbst testet. Je Fall steht der Test in der zuständigen Aufgabe.
- HA-Neustart mit
unknown-Helfern: Das Tagesprofil darf keinen Raum umschalten, solange ein Uhrzeit-Helfer keinen gültigen Wert hat. Test in Task 6, Schritt 6. - Raumtemperatur
unavailable: Die Einspeisung sendet keinen Wert, setzt das TRV aufinternalund meldet „Notbetrieb“. Test in Task 3, Schritt 5. - Fenster offen während eines Profilwechsels: Die Zustellprüfung darf das TRV nicht auf Komfort „reparieren“. Das Soll ist der VTherm-Zielwert, also 7 °C. Test in Task 5, Schritt 6.
- Übersteuerung A überlebt die 5-Minuten-Schleife: Sie gilt bis zum nächsten Blockwechsel, nicht bis zum nächsten Durchlauf. Test in Task 6, Schritt 7.
- Ein Wohnzimmer-TRV ist offline: Die Prüfung meldet Störung genau einmal und wiederholt nicht endlos. Das zweite TRV wird weiter versorgt. Test in Task 9, Schritt 5.
Task 0: Sicherung und VTherm installieren¶
Dateien: keine (HA-Instanz)
Schnittstellen:
- Erzeugt: die Integration versatile_thermostat ≥ 10.5.0, verfügbar im Dialog „Integration hinzufügen“
- Schritt 1: HA-Backup anlegen. Per
ha_manage_backupein Voll-Backup erstellen, Namevor-e6a-2026-10. Erwartet: Backup in der Liste mit Größe > 0. - Schritt 2: Zustand vorher festhalten. Per
ha_config_get_automationdie 7 AHC-Automationen und die 7 Reset-Automationen lesen. Ihre IDs undconfig_hashkommen indocs/planung/2026-10-e6a/umsetzung-log.md(Abschnitt „Ausgangslage“). Ebenso Better Thermostat (ha_get_integration, Domainbetter_thermostat). - Schritt 3: VTherm installieren. Per
ha_manage_hacsdas Repositoryjmcollin78/versatile_thermostatinstallieren und die Version prüfen: ≥ 10.5.0, Release-Notes seit 10.5.0 lesen (Breaking Changes?). - Schritt 4: Neustart. Robin startet HA neu (oder gibt Claude das OK für
ha_restart). Erwartet: Unter Einstellungen → Integrationen → „Hinzufügen“ ist „Versatile Thermostat“ auffindbar. Prüfung perha_get_integration(Domain bekannt, keine Fehler im Log:ha_get_logs source=system level=ERROR search=versatile). - Schritt 5: Festhalten. Version, Backup-Name und Zeitpunkt in
umsetzung-log.mdeintragen.
Task 1: Raumklima-Modell¶
Dateien: keine (HA-Helfer)
Schnittstellen:
- Erzeugt: sensor.<raum>_sensor_temperature als Endwert, je nach Raum „erster verfügbarer“ über Primärgruppe und Ersatz. Erzeugt außerdem: sensor.<raum>_temperatur_primaer (Mittelwert der Aqara-Sensoren), nur in Räumen mit Ersatzsensor.
- Erzeugt: input_number.heizung_lueften_schwelle (%, Start 65)
- Schritt 1: Bestand lesen. Für jeden Raum die Config-Entry der Gruppe
sensor.<raum>_sensor_temperatureperha_get_entity→config_entry_id→ha_get_integration(entry_id=…, include_options=True)lesen. Festhalten: Typ (heutemean), Mitglieder, und ob das Options-Formulartypeänderbar macht. - Schritt 2: Räume ohne Ersatz bereinigen. Abstellraum, Bad, Esszimmer und Küche behalten Typ
meanmit genau ihrem Aqara-Sensor. Das ist heute schon so, also nur prüfen. - Schritt 3: Räume mit Ersatz umbauen (Arbeitszimmer, Schlafzimmer, Wohnzimmer):
- Per
ha_config_set_helper(helper_type=group,config={"next_step_id": "sensor", …}) eine neue Gruppe „Temperatur primär“ anlegen, Typ mean, Mitglieder:- Arbeitszimmer:
sensor.arbeitszimmer_sensor_luft_temperature,sensor.arbeitszimmer_w100_temperature - Schlafzimmer:
sensor.schlafzimmer_sensor_luft_temperature - Wohnzimmer:
sensor.wohnzimmer_sensor_luft_vorne_temperature(der zweite Sensor hinten kommt später dazu, sobald Robin ihn montiert hat)
- Arbeitszimmer:
- Die bestehende Gruppe
sensor.<raum>_sensor_temperatureper Options-Update auf Typfirst_availablemit den Mitgliedern[sensor.<raum>_temperatur_primaer, <VINDSTYRKA>]umstellen. VINDSTYRKA:sensor.arbeitszimmer_sensor_luft_schreibtisch_temperature,sensor.schlafzimmer_sensor_luft_ikea_temperature,sensor.wohnzimmer_sensor_luft_fernseher_temperature. - Lässt das Options-Formular keinen Typwechsel zu: die Gruppe löschen und mit derselben Entitäts-ID neu anlegen. Vorher per
ha_searchalle Nutzer der ID erfassen (AHC-Automationen, Dashboards). Die ID bleibt gleich, die Nutzer laufen also weiter. - Schritt 4: Luftfeuchte-Schwelle anlegen.
input_number.heizung_lueften_schwelle, Bereich 50–80, Schritt 1, Einheit%, Wert 65.initialnicht setzen. - Schritt 5: Testen. Per
ha_eval_templateje Raum prüfen:states('sensor.<raum>_sensor_temperature')ist numerisch und liegt innerhalb ±0,5 K des Primärsensors. Erwartet im Wohnzimmer: Wert =wohnzimmer_sensor_luft_vorne(nicht mehr der Mittelwert mit dem VINDSTYRKA). - Schritt 6: Ersatz testen (ohne Hardware anzufassen):
ha_eval_templatemit{{ state_attr('sensor.wohnzimmer_sensor_temperature','entity_id') }}. Erwartet: zuerst die Primärgruppe, dann der VINDSTYRKA. Der echte Ausfalltest läuft in Task 10 (T8). - Schritt 7: Festhalten in
umsetzung-log.md: Gruppen vorher und nachher.
Task 2: Offline-Timeouts der Raumsensoren (nur mit Robins OK)¶
Dateien: keine (Z2M-Konfiguration auf dem NUC, geschrieben durch Z2M selbst)
Schnittstellen: keine nach außen. Das Ergebnis ist ein geräteeigener availability.timeout: 180 für 21 Geräte.
- Schritt 1: Freigabe. Robin hat am 08.10.2026 zugestimmt, mit 180 min statt der in #237 vorgeschlagenen 150 min.
- Schritt 2: Setzen. Für jedes der 21 Geräte
zigbee2mqtt/bridge/request/device/optionsmit{"id": "<name>", "options": {"availability": {"timeout": 180}}}(Robin 08.10.: etwas mehr Luft als bei den TRVs, 3 × längste gemessene Pause von 60 min) senden. Die Antwort je Gerät mitschneiden, wie am 05.10. bei den TRVs: - 10 Kontakte:
abstellraum_sensor_fenster,arbeitszimmer_sensor_fenster_links,arbeitszimmer_sensor_fenster_rechts,badezimmer_sensor_fenster,esszimmer_sensor_tuer,kueche_sensor_fenster,schlafzimmer_sensor_fenster_links,schlafzimmer_sensor_fenster_rechts,wohnzimmer_sensor_tuer_mitte,wohnzimmer_sensor_tuer_rechts - 2 Rauchmelder:
abstellraum_sensor_rauch,server_sensor_rauch - Wasser:
kueche_sensor_wasser_spuelmaschine - 7 Klimasensoren:
abstellraum_sensor_luft,arbeitszimmer_sensor_luft,badezimmer_sensor_luft,esszimmer_sensor_luft,kueche_sensor_luft,schlafzimmer_sensor_luft,wohnzimmer_sensor_luft_vorne - W100:
arbeitszimmer_w100 - Schritt 3: Prüfen. Erwartet: 21 ×
status=ok,restart_required=false. Lesend auf dem NUC:grep -c "timeout: 180$" /mnt/ssd/container-data/zigbee2mqtt/configuration.yaml→ 21 undgrep -c "timeout: 150$" …→ 8 (TRVs). Mit$: Ohne Zeilenende-Anker trifft150auch die globale1500. - Schritt 4: Festhalten in #237 und
umsetzung-log.md.
Task 3: Bausteine Helfer, Einspeisung und Notbetrieb¶
Dateien: keine (HA)
Schnittstellen:
- Erzeugt je Raum: input_boolean.heizung_neu_<raum> („Heizung neu: input_boolean.heizung_halten_<raum>, input_datetime.heizung_uebersteuerung_bis_<raum> (Datum + Uhrzeit), input_text.heizung_block_<raum> (max. 20 Zeichen), input_boolean.heizung_stoerung_<raum>
- Erzeugt global: input_boolean.heizung_abwesend, input_boolean.heizung_wach_heute, input_boolean.heizung_wartung
- Erzeugt Uhrzeit-Helfer (nur Uhrzeit): input_datetime.heizung_aufstehen_lars 07:30, …_aufstehen_robin 09:00, …_wochenende_spaetestens 10:00, …_wohnzimmer_grund_ab 19:00, …_nachtruhe 23:00
- Erzeugt Zahlen-Helfer: input_number.heizung_wohnzimmer_grundtemperatur (15–23, Schritt 0,5, 19), input_number.heizung_vorlauf_bad_kueche (0–180 min, Schritt 5, 60), input_number.heizung_vorlauf_sonstige (0–180, Schritt 5, 30)
- Erzeugt: automation.heizung_e1_einspeisung
- Schritt 1: Helfer anlegen per
ha_config_set_helper, alle ohneinitial(Werte bleiben über Neustarts erhalten). Die Startwerte danach perinput_datetime.set_datetimebzw.input_number.set_valuesetzen. Alle Helfer bekommen das Labelheizung: perha_config_set_labelanlegen, falls noch nicht vorhanden. - Schritt 2: Einspeisung anlegen. Die Automation ist erst wirksam, wenn
heizung_neu_<raum>an ist.
{
"alias": "Heizung: Raumtemperatur an die TRVs (E1-Einspeisung)",
"description": "Schreibt die Raumtemperatur per MQTT mit Nachkommastelle an die Aqara E1 (HA-Number hat Schritt 1). Vorher Modus external sicherstellen; Z2M verwirft den Wert sonst still. Notbetrieb bei fehlender Raumtemperatur: TRV auf internal. Spec 5.2.",
"mode": "queued",
"max": 20,
"triggers": [
{"trigger": "state", "entity_id": ["sensor.abstellraum_sensor_temperature", "sensor.arbeitszimmer_sensor_temperature", "sensor.badezimmer_sensor_temperature", "sensor.esszimmer_sensor_temperature", "sensor.kueche_sensor_temperature", "sensor.schlafzimmer_sensor_temperature", "sensor.wohnzimmer_sensor_temperature"], "id": "aenderung"},
{"trigger": "time_pattern", "minutes": "/30", "id": "keepalive"},
{"trigger": "homeassistant", "event": "start", "id": "keepalive"},
{"trigger": "state", "entity_id": ["input_boolean.heizung_neu_abstellraum", "input_boolean.heizung_neu_arbeitszimmer", "input_boolean.heizung_neu_badezimmer", "input_boolean.heizung_neu_esszimmer", "input_boolean.heizung_neu_kueche", "input_boolean.heizung_neu_schlafzimmer", "input_boolean.heizung_neu_wohnzimmer"], "to": "on", "id": "keepalive"}
],
"variables": {
"trvs": {"abstellraum": ["abstellraum_heizung"], "arbeitszimmer": ["arbeitszimmer_heizung"], "badezimmer": ["badezimmer_heizung"], "esszimmer": ["esszimmer_heizung"], "kueche": ["kueche_heizung"], "schlafzimmer": ["schlafzimmer_heizung"], "wohnzimmer": ["wohnzimmer_heizung_vorne", "wohnzimmer_heizung_hinten"]},
"raeume": "{{ [trigger.entity_id.split('.')[1].split('_')[0]] if trigger.id == 'aenderung' else (trvs | list) }}"
},
"actions": [
{"repeat": {"for_each": "{{ raeume }}", "sequence": [
{"variables": {"raum": "{{ repeat.item }}", "temp": "{{ states('sensor.' ~ repeat.item ~ '_sensor_temperature') }}"}},
{"condition": "template", "value_template": "{{ is_state('input_boolean.heizung_neu_' ~ raum, 'on') }}"},
{"repeat": {"for_each": "{{ trvs[raum] }}", "sequence": [
{"variables": {"trv": "{{ repeat.item }}", "gesendet": "{{ states('number.' ~ repeat.item ~ '_external_temperature_input') }}"}},
{"if": [{"condition": "template", "value_template": "{{ temp | is_number }}"}],
"then": [
{"if": [{"condition": "template", "value_template": "{{ not is_state('select.' ~ trv ~ '_sensor', 'external') }}"}],
"then": [{"action": "select.select_option", "target": {"entity_id": "select.{{ trv }}_sensor"}, "data": {"option": "external"}}, {"delay": {"seconds": 5}}]},
{"if": [{"condition": "template", "value_template": "{{ trigger.id == 'keepalive' or not (gesendet | is_number) or ((temp | float) - (gesendet | float)) | abs >= 0.2 }}"}],
"then": [{"action": "mqtt.publish", "data": {"topic": "zigbee2mqtt/{{ trv }}/set", "payload": "{\"external_temperature_input\": {{ (temp | float) | round(2) }}}"}}]}
],
"else": [
{"action": "select.select_option", "target": {"entity_id": "select.{{ trv }}_sensor"}, "data": {"option": "internal"}},
{"if": [{"condition": "template", "value_template": "{{ state_attr('persistent_notification.heizung_notbetrieb_' ~ raum, 'title') is none }}"}],
"then": [{"action": "notify.mobile_app_robins_iphone", "data": {"title": "Heizung {{ raum | capitalize }}: Notbetrieb", "message": "Keine Raumtemperatur – das TRV regelt mit seinem eigenen Fühler (misst 1–3 K zu kalt)."}}]},
{"action": "persistent_notification.create", "data": {"notification_id": "heizung_notbetrieb_{{ raum }}", "title": "Heizung {{ raum | capitalize }}: Notbetrieb", "message": "Keine Raumtemperatur, das TRV regelt mit seinem eigenen Fühler (misst 1–3 K zu kalt)."}}
]}
]}}
]}}
]
}
Anmerkungen:
- raeume nimmt bei einer Änderung den Raumschlüssel aus sensor.<raum>_sensor_temperature. Alle Schlüssel sind ein Wort, split('_')[0] ist also eindeutig.
- Die drei Template-Bedingungen sind nötig, weil sie dynamische Entitätsnamen bzw. den Vergleich zweier Laufzeitwerte enthalten (Leitfaden: Templates nur dort, wo nichts Natives passt).
- Zusätzliche Aktion im then-Zweig vor dem Publish: persistent_notification.dismiss mit heizung_notbetrieb_{{ raum }}.
- Schritt 3: Ohne aktiven Raum prüfen. Automation per
automation.triggerauslösen (alle Schalterheizung_neu_*aus). Erwartet laut Trace (ha_get_automation_traces): alle Räume übersprungen, keinmqtt.publish. - Schritt 4: Einspeisung im Bad testen (gehört zu Task 4, Schritt 6). Hier nur vorbereitet.
- Schritt 5: Notbetrieb testen (Review-Fokus 2). Erst nach Task 4 (Bad aktiv): per
ha_eval_templatesicherstellen, dass der Zweig greift. Dazu die Automation mit einer Testkopie prüfen: In der Kopietempauf'unavailable'setzen und nurbadezimmerinraeume, dann auslösen. Erwartet:select.badezimmer_heizung_sensor = internal, Benachrichtigung „Notbetrieb“, kein Publish. Danach die Kopie löschen, die echte Automation auslösen. Erwartet: zurück aufexternal.
Task 4: Zentrale VTherm-Konfiguration und Testraum Bad¶
Dateien: keine (HA)
Schnittstellen:
- Erzeugt: die VTherm-Zentralkonfiguration, climate.heizung_badezimmer (tatsächliche ID festhalten!) und die Preset-Zahlen-Entitäten von VTherm für das Bad (Namen nach dem Anlegen per ha_search query=heizung_badezimmer domain_filter=number erfassen)
- Verbraucht: sensor.badezimmer_sensor_temperature, binary_sensor.badezimmer_sensor_window, climate.badezimmer_heizung, Task-3-Helfer
- Schritt 1: Zentralkonfiguration (Robin, Oberfläche). Integration hinzufügen → Versatile Thermostat → Centralized configuration. Felder:
| Bereich | Feld | Wert |
|---|---|---|
| Hauptattribute | Außentemperatursensor | sensor.openweathermap_temperature (später der Balkonsensor) |
| Min/Max/Schritt | 5 / 30 / 0,5 | |
| Fenster | Verzögerung | 30 s |
| Aktion | Frost protection | |
| Erweitert (Safety) | Max. Verzögerung bis Safety | 180 min (Herzschlag der Sensoren ≤ 60 min; Robin 08.10.: lieber etwas mehr Luft) |
| Außensensor prüfen | aus (OpenWeatherMap-Ausfall darf keinen Raum in Safety schicken) | |
| Presets | Komfort / Eco / Boost / Frost | 20 / 17 / 24 / 7 (Räume weichen über eigene Werte ab, Schritt 4) |
Die Feldnamen stammen aus documentation/en/creation.md, base-attributes.md, feature-window.md und feature-advanced.md von VTherm 10.5.0. Weicht die Oberfläche ab, gilt die Oberfläche. Die Abweichung kommt ins Log.
- Schritt 2: VTherm Bad (Robin, Oberfläche). Integration hinzufügen → Versatile Thermostat → Over climate:
| Bereich | Feld | Wert |
|---|---|---|
| Hauptattribute | Name | Heizung Badezimmer |
| Temperatursensor | sensor.badezimmer_sensor_temperature |
|
| Zykluszeit | 5 min | |
| Zentrale Zusatzparameter | an | |
Zentrale Steuerung (central_mode) |
an | |
| Features | Öffnungserkennung | an |
| Zugrunde liegende Geräte | Equipment | climate.badezimmer_heizung |
| AC-Modus | aus | |
| Temperatur-Sync der Geräte | aus (rundet bei der E1 auf ganze Grad, wir speisen selbst ein) | |
| Self-Regulation | keine | |
| Interne Temperatur kompensieren | aus | |
| Öffnung | Sensor | binary_sensor.badezimmer_sensor_window |
| Zentrale Fensterparameter | an | |
| Presets | zentrale Presets nutzen | an |
Nach dem Speichern: Die Entität „Follow underlying temp changes“ des Geräts bleibt aus.
- Schritt 3: Prüfen (Claude).
ha_get_state climate.heizung_badezimmer. Erwartet: existiert,hvac_modesenthältheatundoff,preset_modesenthältcomfort,eco,boost,frost. Die tatsächliche Entitäts-ID ins Log. Weicht sie ab, ersetzt sieclimate.heizung_badezimmerin allen folgenden Aufgaben. - Schritt 4: Bad-Presets setzen. Über die VTherm-Zahlen-Entitäten des Bads (
number.*, Namen aus Schritt 3) Komfort 21, Eco 17, Boost 24, Frost 7. Stehen die Presets nur zentral: Bad-eigene Presets in der Oberfläche aktivieren (Robin), dann setzen. - Schritt 5: AHC Bad aus, Bad umschalten. In dieser Reihenfolge, damit nie zwei Regler gleichzeitig auf das TRV schreiben:
automation.turn_offfürautomation.badezimmer_advanced_heating_controlundautomation.badezimmer_reset_heizung_to_schedule. Die Reset-ID perha_searchbestätigen, Musterautomation.<raum>_reset_heizung_to_schedule.switch.turn_offfürswitch.badezimmer_heizung_window_detection.input_boolean.turn_onfürinput_boolean.heizung_neu_badezimmer.- VTherm Bad auf
heat, Preseteco. - Schritt 6: Einspeisung prüfen. Nach höchstens 1 min erwartet:
select.badezimmer_heizung_sensor = externalnumber.badezimmer_heizung_external_temperature_input≈sensor.badezimmer_sensor_temperature, höchstens 0,2 K Abweichung- im Trace der Einspeisung genau ein
mqtt.publishanzigbee2mqtt/badezimmer_heizung/set - Schritt 7: Aufheizrate aufzeichnen (Spec-Prüfpunkt 6). Den
slope-Sensor von VTherm Bad perha_search domain_filter=sensor query=heizung_badezimmerfinden. Mitha_get_stateprüfen: Hat erstate_class: measurement? Dann landet er in der Langzeitstatistik. Hat er keine, eine Statistik-Hilfe ist nicht nötig, aber im Log vermerken: Die Aufheizraten-Auswertung für Stufe 2 läuft dann über die Recorder-Historie (10 Tage) und muss rechtzeitig geschehen. - Schritt 8: Festhalten in
umsetzung-log.md: Entitäts-IDs, Abweichungen der Oberfläche, Uhrzeit der Umstellung. - Später, sobald Robin den Balkonsensor montiert hat: In der Zentralkonfiguration den Außentemperatursensor von
sensor.openweathermap_temperatureauf den Balkonsensor umstellen (Robin, Oberfläche). Den Sensor mit Label „Überwacht“ versehen und ihm einen Timeout von 180 min geben (Task 2).
Task 5: Zustellprüfung¶
Dateien: keine (HA)
Schnittstellen:
- Verbraucht: VTherm-Entitäten climate.heizung_<raum>, TRVs, input_boolean.heizung_neu_<raum>, input_boolean.heizung_stoerung_<raum>, input_boolean.heizung_wartung
- Erzeugt: automation.heizung_zustellpruefung
- Schritt 1: Automation anlegen.
{
"alias": "Heizung: Zustellprüfung TRV",
"description": "Prüft, ob jedes TRV Ziel und Modus seines VTherm-Raumreglers übernommen hat (Z2M setzt den Zustand erst nach erfolgreichem Zigbee-Write). Bis zu 3 Wiederholungen im Abstand von 2 min, dann Push und Störungsstatus. Spec 5.5.",
"mode": "queued",
"max": 10,
"max_exceeded": "silent",
"triggers": [
{"trigger": "state", "entity_id": ["climate.heizung_abstellraum", "climate.heizung_arbeitszimmer", "climate.heizung_badezimmer", "climate.heizung_esszimmer", "climate.heizung_kueche", "climate.heizung_schlafzimmer", "climate.heizung_wohnzimmer"], "for": {"minutes": 2}, "id": "befehl"},
{"trigger": "state", "entity_id": ["climate.heizung_abstellraum", "climate.heizung_arbeitszimmer", "climate.heizung_badezimmer", "climate.heizung_esszimmer", "climate.heizung_kueche", "climate.heizung_schlafzimmer", "climate.heizung_wohnzimmer"], "attribute": "temperature", "for": {"minutes": 2}, "id": "befehl"},
{"trigger": "time_pattern", "minutes": "/15", "id": "abgleich"}
],
"conditions": [{"condition": "state", "entity_id": "input_boolean.heizung_wartung", "state": "off"}],
"variables": {
"trvs": {"abstellraum": ["abstellraum_heizung"], "arbeitszimmer": ["arbeitszimmer_heizung"], "badezimmer": ["badezimmer_heizung"], "esszimmer": ["esszimmer_heizung"], "kueche": ["kueche_heizung"], "schlafzimmer": ["schlafzimmer_heizung"], "wohnzimmer": ["wohnzimmer_heizung_vorne", "wohnzimmer_heizung_hinten"]},
"raeume": "{{ [trigger.entity_id.split('.')[1].replace('heizung_', '')] if trigger.id == 'befehl' else (trvs | list) }}"
},
"actions": [
{"repeat": {"for_each": "{{ raeume }}", "sequence": [
{"variables": {"raum": "{{ repeat.item }}", "vt": "climate.heizung_{{ repeat.item }}"}},
{"condition": "template", "value_template": "{{ is_state('input_boolean.heizung_neu_' ~ raum, 'on') and states(vt) not in ['unknown', 'unavailable'] }}"},
{"repeat": {"for_each": "{{ trvs[raum] }}", "sequence": [
{"variables": {"trv": "climate.{{ repeat.item }}"}},
{"repeat": {
"sequence": [
{"variables": {"soll_modus": "{{ states(vt) }}", "soll_temp": "{{ state_attr(vt, 'temperature') }}"}},
{"if": [{"condition": "template", "value_template": "{{ states(trv) != soll_modus or (soll_modus == 'heat' and ((state_attr(trv, 'temperature') | float(0)) - (soll_temp | float(0))) | abs > 0.26) }}"}],
"then": [
{"action": "climate.set_hvac_mode", "target": {"entity_id": "{{ trv }}"}, "data": {"hvac_mode": "{{ soll_modus }}"}},
{"if": [{"condition": "template", "value_template": "{{ soll_modus == 'heat' }}"}], "then": [{"action": "climate.set_temperature", "target": {"entity_id": "{{ trv }}"}, "data": {"temperature": "{{ soll_temp }}"}}]},
{"delay": {"minutes": 2}}
]}
],
"until": [{"condition": "template", "value_template": "{{ repeat.index >= 3 or (states(trv) == states(vt) and (states(vt) != 'heat' or ((state_attr(trv, 'temperature') | float(0)) - (state_attr(vt, 'temperature') | float(0))) | abs <= 0.26)) }}"}]
}},
{"if": [{"condition": "template", "value_template": "{{ states(trv) != states(vt) or (states(vt) == 'heat' and ((state_attr(trv, 'temperature') | float(0)) - (state_attr(vt, 'temperature') | float(0))) | abs > 0.26) }}"}],
"then": [
{"if": [{"condition": "template", "value_template": "{{ is_state('input_boolean.heizung_stoerung_' ~ raum, 'off') }}"}],
"then": [
{"action": "input_boolean.turn_on", "target": {"entity_id": "input_boolean.heizung_stoerung_{{ raum }}"}},
{"action": "notify.mobile_app_robins_iphone", "data": {"title": "Heizung {{ raum | capitalize }}: Befehl nicht angekommen", "message": "{{ trv }} meldet {{ states(trv) }} / {{ state_attr(trv, 'temperature') }} °C, Soll {{ states(vt) }} / {{ state_attr(vt, 'temperature') }} °C. 3 Wiederholungen ohne Erfolg."}}
]}
],
"else": [{"action": "input_boolean.turn_off", "target": {"entity_id": "input_boolean.heizung_stoerung_{{ raum }}"}}]}
]}}
]}}
]
}
Anmerkungen:
- Bedingungen mit dynamischem Entitätsnamen sind Template-Bedingungen, weil entity_id in condition: state keine Templates akzeptiert. Meldet ha-mcp beim Anlegen Best-Practice-Warnungen, jede einzeln begründen oder beheben.
- Die Toleranz von 0,26 K deckt die TRV-Schrittweite von 0,5 ab, falls VTherm einen Zwischenwert sendet. target_temp_step des TRVs vorher per ha_get_state prüfen und notieren.
- Ist VTherm wegen offenem Fenster auf Frost, zeigt state_attr(vt, 'temperature') 7 °C. Genau das soll das TRV haben (Review-Fokus 3).
- Schritt 2: Positivfall testen (Bad). VTherm Bad auf Preset
comfortsetzen. Erwartet nach ca. 2 min: Trace ohneclimate.set_*-Aufruf an das TRV,input_boolean.heizung_stoerung_badezimmer = off. - Schritt 3: Reparatur testen. Das TRV direkt verstellen:
climate.set_temperatureanclimate.badezimmer_heizungmit 26 °C. Beim nächsten 15-min-Abgleich erwartet: Trace mit einer Wiederholung, danach TRV = VTherm-Ziel. Kein Push. - Schritt 4: Störung testen (T4, braucht Robin). Robin nimmt am Bad-TRV die Batterie heraus. Dann VTherm Bad auf
boost. Erwartet nach ca. 8 min: genau ein Push „Befehl nicht angekommen“, Störungsschalter an. Batterie wieder rein. Beim nächsten Abgleich erwartet: Störungsschalter aus. - Schritt 5: Wartungssperre testen.
input_boolean.heizung_wartungan, Abgleich auslösen. Erwartet: Trace „condition failed“. Wieder aus. - Schritt 6: Fenster testen (Review-Fokus 3, T5). Robin öffnet das Badfenster. Erwartet nach 30 s: VTherm-Ziel 7 °C, Preset unverändert. Dann VTherm-Preset per HA auf
comfortsetzen. Erwartet: Das Ziel bleibt 7 °C, die Prüfung schreibt nicht auf Komfort. Fenster zu. Erwartet nach 30 s: Komfort-Ziel aktiv, TRV folgt.
Task 6: Tagesprofil und Übersteuerung¶
Dateien: keine (HA)
Schnittstellen:
- Erzeugt: script.heizung_raum_profil (Felder raum: Raumschlüssel, erzwingen: bool), script.heizung_uebersteuern_2h (raum), script.heizung_halten (raum), script.heizung_fortsetzen (raum), script.heizung_alle_eco, script.heizung_boost_bad, script.heizung_abwesend (an: bool)
- Erzeugt: automation.heizung_tagesprofil, automation.heizung_wach_erkennung, automation.heizung_uebersteuerung_ablauf
- Verbraucht: Helfer aus Task 3, binary_sensor.workday_sensor, binary_sensor.esszimmer_sensor_bewegung_occupancy, binary_sensor.kueche_sensor_bewegung_occupancy
Entschieden (Robin, 08.10.2026): Das Bad ist den ganzen Tag auf Komfort, ab Lars' Aufstehzeit (vorgeheizt) bis zur Nachtruhe, also wie die Küche. Den Helfer „Bad abends“ gibt es deshalb nicht.
- Schritt 1: Profil-Skript anlegen. Es berechnet den Soll-Block eines Raums und schreibt ihn nur, wenn er sich gegenüber
input_text.heizung_block_<raum>geändert hat (oder beierzwingen). Die Übersteuerung A bleibt so bis zum nächsten Blockwechsel erhalten.
{
"alias": "Heizung: Tagesprofil für einen Raum anwenden",
"mode": "parallel",
"max": 10,
"fields": {
"raum": {"selector": {"select": {"options": ["abstellraum", "arbeitszimmer", "badezimmer", "esszimmer", "kueche", "schlafzimmer", "wohnzimmer"]}}},
"erzwingen": {"default": false, "selector": {"boolean": {}}}
},
"sequence": [
{"variables": {
"vt": "climate.heizung_{{ raum }}",
"uhrzeiten_ok": "{{ ['aufstehen_lars','aufstehen_robin','wochenende_spaetestens','wohnzimmer_grund_ab','nachtruhe'] | map('regex_replace', '^', 'input_datetime.heizung_') | map('states') | select('match', '^\\d\\d:\\d\\d') | list | count == 5 }}",
"block": "{%- macro m(e) -%}{%- set p = (states('input_datetime.heizung_' ~ e) ~ ':0:0').split(':') -%}{{ p[0] | int(0) * 60 + p[1] | int(0) }}{%- endmacro -%}{%- set jetzt = now().hour * 60 + now().minute -%}{%- set werktag = is_state('binary_sensor.workday_sensor', 'on') -%}{%- set wach = is_state('input_boolean.heizung_wach_heute', 'on') -%}{%- set vl1 = states('input_number.heizung_vorlauf_bad_kueche') | int(60) -%}{%- set vl2 = states('input_number.heizung_vorlauf_sonstige') | int(30) -%}{%- if werktag -%}{%- set fruh = m('aufstehen_lars') | int - vl1 -%}{%- set tag = m('aufstehen_robin') | int - vl2 -%}{%- elif wach -%}{%- set fruh = 0 -%}{%- set tag = 0 -%}{%- else -%}{%- set fruh = m('wochenende_spaetestens') | int - vl1 -%}{%- set tag = m('wochenende_spaetestens') | int - vl2 -%}{%- endif -%}{%- if is_state('input_boolean.heizung_abwesend', 'on') -%}eco{%- elif jetzt >= m('nachtruhe') | int or jetzt < 300 -%}eco{%- elif raum in ['badezimmer', 'kueche'] -%}{{ 'comfort' if jetzt >= fruh else 'eco' }}{%- elif raum in ['arbeitszimmer', 'esszimmer'] -%}{{ 'comfort' if jetzt >= tag else 'eco' }}{%- elif raum == 'wohnzimmer' -%}{{ 'grund' if jetzt >= m('wohnzimmer_grund_ab') | int else ('comfort' if jetzt >= tag else 'eco') }}{%- else -%}eco{%- endif -%}"
}},
{"condition": "template", "value_template": "{{ uhrzeiten_ok }}"},
{"condition": "template", "value_template": "{{ is_state('input_boolean.heizung_neu_' ~ raum, 'on') }}"},
{"condition": "template", "value_template": "{{ erzwingen or (is_state('input_boolean.heizung_halten_' ~ raum, 'off') and (as_timestamp(states('input_datetime.heizung_uebersteuerung_bis_' ~ raum), 0) < as_timestamp(now()))) }}"},
{"condition": "template", "value_template": "{{ erzwingen or states('input_text.heizung_block_' ~ raum) != block }}"},
{"choose": [
{"conditions": [{"condition": "template", "value_template": "{{ block == 'grund' }}"}],
"sequence": [{"action": "climate.set_temperature", "target": {"entity_id": "{{ vt }}"}, "data": {"temperature": "{{ states('input_number.heizung_wohnzimmer_grundtemperatur') | float(19) }}"}}]}
],
"default": [{"action": "climate.set_preset_mode", "target": {"entity_id": "{{ vt }}"}, "data": {"preset_mode": "{{ block }}"}}]},
{"action": "input_text.set_value", "target": {"entity_id": "input_text.heizung_block_{{ raum }}"}, "data": {"value": "{{ block }}"}}
]
}
- Schritt 2: Taktgeber anlegen (
automation.heizung_tagesprofil,mode: single,max_exceeded: silent). Trigger: time_patternminutes: "/5"homeassistantstartstateauf alle 6 Uhrzeit-Helfer,input_boolean.heizung_wach_heute,input_boolean.heizung_abwesendundbinary_sensor.workday_sensor
Aktion: repeat for_each über die 7 Raumschlüssel → script.heizung_raum_profil mit raum: "{{ repeat.item }}", erzwingen: false, continue_on_error: true. Diese Ausnahme ist begründet: Ein fehlgeschlagener Raum darf die anderen nicht blockieren, die Zustellprüfung meldet Fehler ohnehin.
- [ ] Schritt 3: Wach-Erkennung anlegen (automation.heizung_wach_erkennung, mode: single). Trigger:
- state binary_sensor.esszimmer_sensor_bewegung_occupancy / …kueche… to: "on", id bewegung
- time at input_datetime.heizung_nachtruhe, id nacht
Aktionen, per choose nach Trigger-ID:
- bewegung mit Bedingungen condition: time after: "07:00:00" und before: "23:00:00", state binary_sensor.workday_sensor = off und state input_boolean.heizung_wach_heute = off → input_boolean.turn_on input_boolean.heizung_wach_heute
- nacht → input_boolean.turn_off input_boolean.heizung_wach_heute
- [ ] Schritt 4: Übersteuerungs-Skripte anlegen:
- script.heizung_uebersteuern_2h: input_datetime.set_datetime heizung_uebersteuerung_bis_<raum> = now() + timedelta(hours=2); input_text.set_value Block manuell. Der Zielwert bleibt, wie ihn der Nutzer gesetzt hat.
- script.heizung_halten: input_boolean.turn_on heizung_halten_<raum>.
- script.heizung_fortsetzen: input_boolean.turn_off heizung_halten_<raum>; heizung_uebersteuerung_bis_<raum> auf 1970-01-01 00:00:00; script.heizung_raum_profil mit erzwingen: true.
- Ablauf-Automation automation.heizung_uebersteuerung_ablauf: 7 time-Trigger at: input_datetime.heizung_uebersteuerung_bis_<raum> (Trigger-ID = Raum) → script.heizung_raum_profil mit raum: "{{ trigger.id }}", erzwingen: true.
- script.heizung_alle_eco: für alle 7 VTherm climate.set_preset_mode eco. Kein Block-Schreiben, damit der nächste Blockwechsel wieder übernimmt.
- script.heizung_boost_bad: versatile_thermostat.set_timed_preset an climate.heizung_badezimmer, preset: boost, duration_min: 30. Feldnamen per ha_list_services domain=versatile_thermostat detail_level=full prüfen.
- script.heizung_abwesend (Feld an): input_boolean.turn_on/off heizung_abwesend. Beim Ausschalten für alle Räume script.heizung_raum_profil mit erzwingen: true.
- [ ] Schritt 5: Profil im Bad testen (tagsüber, nach Lars' Aufstehzeit). Ausgangslage: Block comfort. input_datetime.heizung_nachtruhe auf jetzt − 1 min setzen. Erwartet nach höchstens 5 min: climate.heizung_badezimmer Preset eco, input_text.heizung_block_badezimmer = eco. Zurück auf 23:00. Erwartet beim nächsten Takt: Preset comfort.
- [ ] Schritt 6: Neustart-Schutz testen (Review-Fokus 1).
1. Per ha_eval_template die Ausdrücke {{ ['07:30:00', 'unknown'] | select('match', '^\d\d:\d\d') | list | count }} (erwartet 1) und {{ ['07:30:00', '09:00:00'] | select('match', '^\d\d:\d\d') | list | count }} (erwartet 2) prüfen. Damit ist belegt, dass ein unknown-Helfer uhrzeiten_ok auf false setzt.
2. Eine Testkopie des Skripts anlegen, in der uhrzeiten_ok fest auf false steht, und mit raum: badezimmer ausführen. Erwartet im Trace: Stopp an der ersten Bedingung, kein climate.*-Aufruf.
3. Die Testkopie löschen.
- [ ] Schritt 7: Übersteuerung A testen (Review-Fokus 4, T6). Im Bad bei Block eco den Zielwert per climate.set_temperature auf 22 °C setzen. Erwartet: Preset none. 15 min warten (drei Takte). Erwartet: Zielwert bleibt 22 °C. Danach einen Blockwechsel provozieren (nachtruhe auf jetzt − 1 min). Erwartet: Preset eco, die Übersteuerung ist damit beendet. nachtruhe zurück auf 23:00.
- [ ] Schritt 8: Übersteuerung B und C testen (T6).
- script.heizung_uebersteuern_2h mit raum: badezimmer, danach den Helfer per input_datetime.set_datetime auf jetzt + 2 min. Erwartet nach Ablauf: Profil-Block erzwungen.
- script.heizung_halten, dann den Blockwechsel provozieren. Erwartet: keine Änderung.
- script.heizung_fortsetzen. Erwartet: Profil sofort aktiv.
- [ ] Schritt 9: Wach-Erkennung testen. Nur prüfbar an einem Nicht-Arbeitstag nach 7:00. Im Log als „offen bis Wochenende“ vermerken und am ersten Samstag prüfen: erste Bewegung → heizung_wach_heute an → Bad und Küche comfort innerhalb von 5 min.
Task 7: Vorab-Tests T1 bis T3 und T7 im Bad¶
Dateien: docs/planung/2026-10-e6a/umsetzung-log.md (Ergebnisse)
- Schritt 1: T1 Dezimalwerte. Nach der ersten Einspeisung
number.badezimmer_heizung_external_temperature_inputundclimate.badezimmer_heizung→current_temperaturelesen. Erwartet: zeigt z. B. 20,65 und nicht 21. Wenn gerundet: In der Einspeisunground(2)durch Runden auf 0,5 ersetzen und die Schwelle auf 0,5 K anheben, im Log begründen. - T2 Rückfall (Schritt 2).
input_boolean.heizung_neu_badezimmerfür 3 h aus, dabei VTherm Bad auf Eco lassen. Alle 30 minselect.badezimmer_heizung_sensorlesen. Erwartet: Er bleibtexternaloder springt zu einem messbaren Zeitpunkt aufinternal. Den Zeitpunkt festhalten. Ist er kürzer als 60 min: Keep-alive in der Einspeisung auf/15stellen. - Schritt 3: T3 Display. Robin liest das TRV-Display ab und notiert, welche Temperatur es zeigt. Nur dokumentieren.
- Schritt 4: T7 Batterie. Nach 7 Tagen die Zahl der Einspeisungs-Traces für das Bad zählen (
ha_get_automation_traces, nur gespeicherte) undsensor.badezimmer_heizung_batterygegen den Startwert vergleichen. Richtwert (Annahme): ≤ 50 Schreibvorgänge pro Tag. Ist es mehr: Schwelle auf 0,3 K. - Schritt 5: Ergebnisse in
umsetzung-log.mdund als Kommentar in homeserver#185.
Task 8: Rollout Abstellraum, Arbeitszimmer, Esszimmer, Küche, Schlafzimmer¶
Voraussetzung: Task 4 bis 7 sind im Bad zwei Tage ohne Störung gelaufen.
Für jeden dieser fünf Räume, einzeln und nacheinander, mit mindestens einem Tag Abstand bei Küche und Schlafzimmer:
- Schritt 1 (Robin, Oberfläche): VTherm
Heizung <Raum>mit denselben Feldern wie Task 4, Schritt 2. Raum-Sensorsensor.<raum>_sensor_temperature, Fensterbinary_sensor.<raum>_sensor_window, im Arbeitszimmerbinary_sensor.arbeitszimmer_sensor_fenster, Gerätclimate.<raum>_heizung. - Schritt 2: Entitäts-ID prüfen und ins Log.
- Schritt 3: Presets setzen laut Tabelle in den globalen Vorgaben. Arbeitszimmer Eco 17,5; Schlafzimmer Eco 17.
- Schritt 4: In dieser Reihenfolge umschalten:
automation.turn_offfür die AHC- und die Reset-Automation des Raums (IDs aus Task 0) →switch.turn_off switch.<raum>_heizung_window_detection→input_boolean.turn_on input_boolean.heizung_neu_<raum>→script.heizung_raum_profilmiterzwingen: true. - Schritt 5: Prüfen. Nach 5 min erwartet:
select.<raum>_heizung_sensor = external- Einspeisungswert ≈ Raumtemperatur
- VTherm-Preset gleich
input_text.heizung_block_<raum> - Zustellprüfung ohne Störung
- Schritt 6: Log-Eintrag je Raum.
Die Küche bekommt zusätzlich: Die Automationen „Küche – Heizung Ventilator an/aus“ (heute aus) bleiben aus. Ob sie noch gebraucht werden, fragt der Ausführende Robin und vermerkt die Antwort für den Abbau (Task 14).
Task 9: Wohnzimmer (zwei TRVs, Better Thermostat ablösen)¶
- Schritt 1:
automation.turn_offfürautomation.wohnzimmer_advanced_heating_controlund die Reset-Automation. Better Thermostatclimate.wohnzimmer_heizungperclimate.set_hvac_mode offstilllegen. Die Integration erst in Task 14 entfernen. - Schritt 2 (Robin, Oberfläche): VTherm
Heizung Wohnzimmerwie Task 4, Schritt 2, aber mit zwei Geräten:climate.wohnzimmer_heizung_vorneundclimate.wohnzimmer_heizung_hinten. Sensorsensor.wohnzimmer_sensor_temperature, Fensterbinary_sensor.wohnzimmer_sensor_window(Dachterrassen-Türen). - Schritt 3: Presets: Komfort 20, Eco 17. Grundtemperatur kommt aus dem Helfer. Beide
switch.wohnzimmer_heizung_*_window_detectionaus. Danninput_boolean.heizung_neu_wohnzimmeran undscript.heizung_raum_profilmiterzwingen. - Schritt 4: Prüfen. Beide TRVs
external, beide bekommen eingespeist, VThermavailable. Ab 19:00 erwartet: Blockgrund, Zielwert 19 °C. - Schritt 5: Ausfall eines TRVs (Review-Fokus 5). Robin nimmt die Batterie aus
wohnzimmer_heizung_hinten. Ein Profilwechsel wird provoziert (Uhrzeit-Helfer). Erwartet: - genau ein Push für das Wohnzimmer,
heizung_stoerung_wohnzimmeran - bei den nächsten Abgleichen keine weiteren Pushes (der Schalter verhindert die Wiederholung)
wohnzimmer_heizung_vorneerhält weiter Einspeisung- Batterie wieder rein → Störung wird beim nächsten Abgleich gelöscht
VTherm wird laut Discussion #1791 bis zur Rückkehr eventuell unbrauchbar. Das Verhalten im Log festhalten.
Task 10: Offline-Test T8 und HomeKit¶
- Schritt 1: T8 (offen aus #237). Robin steckt den VINDSTYRKA am Schreibtisch aus. Erwartet:
- nach etwa 20 min Push „Gerät offline (1)“
sensor.arbeitszimmer_sensor_temperaturebleibt numerisch, weil die Primärgruppe greift- nach dem Einstecken und etwa 10 min Entwarnung
Ergebnis in #237. Ist die Abnahme damit vollständig, #237 schließen.
- [ ] Schritt 2: HomeKit (Robin, Oberfläche). Einstellungen → Integrationen → HomeKit Bridge „HomeAssistant Climate“ → Konfigurieren:
- Einschlussmodus auf Entitäten, nur die 7 climate.heizung_*
- die TRVs und climate.wohnzimmer_heizung (Better Thermostat) nicht mehr aufnehmen
Erwartet in der Home-App: 7 Thermostate. Die alten Einträge verschwinden nach dem Neuladen der Bridge.
- [ ] Schritt 3: Per ha_get_integration (HomeKit-Entry, include_options) prüfen, dass der Filter so gespeichert ist.
Task 11: Dashboard „Smart-Home-Zentrale“¶
Dateien: keine (HA-Dashboard smart-home-zentrale, Storage-Modus)
Schnittstellen:
- Verbraucht: alle VTherm-Entitäten, die Helfer aus Task 3/6, sensor.uberwachte_gerate_offline, input_number.batterie_warnschwelle, calendar.bremer_stadtreinigung, weather.openweathermap, die Skripte aus Task 6
- Erzeugt: das Dashboard /smart-home-zentrale mit den Ansichten start, raum-<raum> (7 Unterseiten, subview: true, back_path: /smart-home-zentrale/start) und einstellungen
- Schritt 1: Leitfaden laden.
references/dashboard-guide.mdaus dem HA-Skill lesen. Vorlage ist das Claude-Design-Mockup „Smart-Home-Zentrale“ (Projekt935d7814-5bab-4cc5-9c69-0075d0645ed3). Permcp__claude_design__read_filelesen. - Schritt 2: Status-Template testen. Die Raumkachel braucht einen Status aus VTherm und Helfern. Das Template vorher mit
ha_eval_templatefür das Bad prüfen:
{%- set vt = 'climate.heizung_badezimmer' -%}
{%- if is_state('input_boolean.heizung_stoerung_badezimmer', 'on') -%}Störung
{%- elif state_attr(vt, 'is_window_bypass') is none and (state_attr(vt, 'window_state') == 'on' or state_attr(vt, 'hvac_off_reason') == 'window') -%}Fenster offen
{%- elif is_state('input_boolean.heizung_halten_badezimmer', 'on') -%}Manuell · gehalten
{%- elif as_timestamp(states('input_datetime.heizung_uebersteuerung_bis_badezimmer'), 0) > as_timestamp(now()) -%}Manuell bis {{ as_datetime(states('input_datetime.heizung_uebersteuerung_bis_badezimmer')).strftime('%H:%M') }}
{%- elif state_attr(vt, 'preset_mode') == 'none' -%}Manuell bis Zeitplanwechsel
{%- elif state_attr(vt, 'hvac_action') == 'heating' -%}Heizt
{%- elif state_attr(vt, 'preset_mode') == 'eco' -%}Eco
{%- else -%}Ziel erreicht{%- endif -%}
Die tatsächlichen VTherm-Attributnamen für Fenster und Grund (window_state, hvac_off_reason o. ä.) vorher per ha_get_state climate.heizung_badezimmer (alle Attribute) prüfen und das Template anpassen. Den Fall „Fenster offen“ mit offenem Badfenster gegenprüfen.
- [ ] Schritt 3: Ansicht Start bauen (sections, max_columns: 4, Dark Mode über das Benutzerprofil des Wandtablets):
- Kopf: Markdown-Karte mit Uhrzeit ({{ now().strftime('%H:%M') }}) und Datum; Tile weather.openweathermap; Tile sensor.uberwachte_gerate_offline mit Aktion navigate → /geraete-zustand; Knopf Zahnrad → /smart-home-zentrale/einstellungen.
- Heizung: 7 × custom:mushroom-template-card, je Raum primary = Raumname, secondary = Status-Template aus Schritt 2 plus „Ziel {{ state_attr(vt,'temperature') }} °C“, icon_color nach Status (orange heizt, blau Fenster, lila manuell, rot Störung, grau Eco), tap_action → /smart-home-zentrale/raum-<raum>. Dazu eine Markdown-Zusammenfassung („2 heizen · 1 Fenster offen · …“).
- Schnellaktionen: 3 × Tile bzw. Knopf mit perform-action auf script.heizung_alle_eco, script.heizung_boost_bad, script.heizung_abwesend (Feld an: true); bei aktivem Abwesend ein Knopf „Zurück“ (an: false) mit visibility auf input_boolean.heizung_abwesend.
- Müllabfuhr: Markdown, das die nächsten zwei Termine aus calendar.bremer_stadtreinigung zeigt. Abruf per Template nicht möglich, deshalb native calendar-Karte (Ansicht listWeek, Höhe begrenzt). Alternativ Waste-Collection-Schedule-Sensoren je Fraktion, falls die Kalenderkarte zu groß ist. Entscheidung im Log.
- Platzhalter: 3 × Markdown „Licht / Medien / Sicherheit – noch nicht eingerichtet“.
- [ ] Schritt 4: Raum-Unterseiten bauen (7 ×, gleich aufgebaut, per Skript erzeugt wie das Gerätezustand-Dashboard am 05.10.):
- native thermostat-Karte auf climate.heizung_<raum>
- drei Knöpfe: „bis nächster Zeitplan“ (nur Hinweis, das ist der Standard), „für 2 Stunden“ → script.heizung_uebersteuern_2h, „halten“ → script.heizung_halten; dazu „Zeitplan fortsetzen“ → script.heizung_fortsetzen
- Markdown „Nächster Zeitplan“
- history-graph 24 h mit Raumtemperatur und VTherm-Ziel; zweite Karte Luftfeuchte
- Markdown-Lüftungshinweis, sichtbar wenn Feuchte > input_number.heizung_lueften_schwelle
- Tile auf den VTherm-slope-Sensor (Aufheizrate)
- [ ] Schritt 5: Ansicht Einstellungen bauen:
- entities-Karte mit den 5 Uhrzeit-Helfern (die Uhrzeiten sind dort direkt editierbar), input_number.heizung_wohnzimmer_grundtemperatur, den Vorlauf-Helfern und input_number.heizung_lueften_schwelle
- Tile auf select.central_mode (VTherm) mit Funktion select-options (Sommer = Frost, Winter = Auto)
- Preset-Zahlen-Entitäten von VTherm, zentral
- [ ] Schritt 6: Prüfen.
- ha_config_get_dashboard gibt das Dashboard ohne Fehler zurück
- per ha_eval_template jede verwendete Entität prüfen, ob sie existiert
- ist der Screenshot-Beta in ha-mcp aktiv: return_screenshot für Start, Bad und Einstellungen
- Robin prüft auf dem iPhone und dem iPad
Task 12: Wand-iPad (Robin) und Akku-Schutz¶
- Schritt 1 (Claude): HA-Benutzer „Wandtablet“ ohne Adminrechte. Benutzerverwaltung über ha-mcp ist nicht vorgesehen, das legt Robin unter Einstellungen → Personen → Benutzer an. Standard-Dashboard des Benutzers:
/smart-home-zentrale. - Schritt 2 (Robin): Auf dem iPad die HA-App installieren und als „Wandtablet“ anmelden. Kiosk-Modus der App aktivieren (App-Einstellungen, Labs). iPadOS „Geführter Zugriff“ aktivieren, Auto-Sperre aus, Dark Mode.
- Schritt 3 (Claude): Robin nennt den Zigbee-Stecker für das Ladegerät. Dann
automation.ipad_akku_schutzanlegen: - Trigger
numeric_stateauf den Batteriesensor des iPads (sensor.<ipad>_battery_levelaus der HA-App, Namen perha_search):below: 20→switch.turn_on,above: 80→switch.turn_off, je mitfor: {minutes: 2} - zusätzlich ein
homeassistant start-Trigger, der nach dem aktuellen Ladestand entscheidet - Schritt 4: Prüfen. Nach 24 h Ladestand-Verlauf: pendelt zwischen 20 und 80 %.
Task 13: Sommer-Ventilbewegung¶
- Schritt 1: Prüfen, ob die E1 das selbst kann. Die Z2M-Doku
SRTS-A01und die Exposes des Geräts durchsehen (Stichwörter: anti-scale, valve calibration, limescale). Kann sie es selbst: die Funktion einschalten, diese Aufgabe ist erledigt. Belegen und ins Log. - Schritt 2: Sonst eine Automation
automation.heizung_ventilbewegung_sommer: - Trigger:
time11:00, Bedingungcondition: time weekday: monundselect.central_modeim Sommermodus - Ablauf:
input_boolean.heizung_wartungan (Zustellprüfung pausiert); für alle 8 TRVsclimate.set_hvac_mode heatundclimate.set_temperature 30;delay10 min;climate.set_temperature 7, dannclimate.set_hvac_modeauf den VTherm-Modus;input_boolean.heizung_wartungaus - Schritt 3: Testen. Per
automation.triggermitskip_condition. Erwartet: Trace vollständig, danach alle TRVs wieder auf dem VTherm-Soll, Wartung aus.
Task 14: Abnahmephase und Abbau¶
- Schritt 1: Abnahme starten, sobald alle 7 Räume umgestellt sind und der Winterbetrieb läuft (
select.central_mode= Auto). Startdatum ins Log. Zwei Wochen beobachten: - jede Störungsmeldung samt Ursache ins Log
- jedes Nachdrehen, weil etwas falsch war, ins Log (gewollte Übersteuerungen zählen nicht)
- Schritt 2: Abnahme. Robin bestätigt:
- zwei Wochen ohne korrigierendes Eingreifen
- jede Störung wurde gemeldet
- Lars ist zufrieden
Ergebnis in #185.
- [ ] Schritt 3: Abbau, nur nach der Abnahme, je Entität vorher ha_search auf Nutzer:
- 7 AHC-Automationen und 7 Reset-Automationen löschen (ha_config_remove_automation)
- die Küchen-Ventilator-Automationen löschen, wenn Robin sie nicht mehr braucht (Antwort aus Task 8)
- Helfer löschen: input_number.<raum>_number_comfort_temperatur, …_comfort_temperatur_weekend, …_eco_temperatur, input_boolean.<raum>_toggle_party_mode, …_toggle_max_heat, input_button.<raum>_button_heizung_reset, schedule.<raum>_scheudler_heizung, die Holiday-Zeitpläne aus packages/heating.yaml, input_boolean.general_toggle_wintermode
- packages/heating.yaml ist eine YAML-Datei, die löscht Robin auf dem NUC
- ebenso die nicht eingebundene schedule.yaml
- Better Thermostat: Integration entfernen (Robin, Oberfläche), danach die HACS-Komponente entfernen
- Dashboard „Heizung“ (lovelace-heizung) löschen
- simple-thermostat als HACS-Ressource entfernen
- die Migrationsschalter input_boolean.heizung_neu_* bleiben (dienen als Notschalter je Raum)
- [ ] Schritt 4: Prüfen. ha_get_logs source=system level=WARNING zeigt keine Verweise auf gelöschte Entitäten, alle Dashboards laden.
Task 15: Dokumentation und Abschluss¶
Dateien:
- Erstellen: docs/projects/homeserver/smart-home/heizung.md (Betriebsdoku: Bausteine, Helfer, Notschalter, Fehlerbilder). Den Pfad an der bestehenden Struktur docs/projects/homeserver/… ausrichten, vorher ls und die mkdocs-Navigation prüfen.
- Ändern: mkdocs.yml (Navigation), docs/planung/2026-08/umsetzungs-log.md (Sitzungseintrag)
- Fortschreiben: docs/planung/2026-10-e6a/umsetzung-log.md
- Schritt 1: Betriebsdoku schreiben:
- Bausteine, Entitätsliste
- „Was tun, wenn …“: Störungs-Push, Notbetrieb, Raum zurück auf AHC (vor dem Abbau) bzw. Raum aus der Steuerung nehmen (
heizung_neu_<raum>aus) - Rückweg
- Schritt 2:
uv run --no-project --with mkdocs==1.6.1 --with mkdocs-material==9.7.6 --with mkdocs-likec4==1.2.2 mkdocs build -d /tmp/claude-1000/mkdocs-e6a→ keine Warnungen zu den neuen Seiten. - Schritt 3: Commit mit Conventional-Commit-Nachricht über eine Datei (
git commit -F), Branch und PR im Doku-Repo. Den PR merged Robin oder Claude mit seinem OK. - Schritt 4: Issues:
- Abschlusskommentar in #185, auf Deutsch: was gebaut wurde, Abweichungen vom Plan, Zurückgestelltes, Link zur Spec
-
185 schließen (englisch:
Closes #185im PR odergh issue close)¶ -
237 schließen, wenn T8 bestanden ist¶
- Schritt 5: Eintrag im Umsetzungs-Log (Board-Ritual) und Memory aktualisieren.
Abweichungen gegenüber der Spec (beim Planen gefunden)¶
| Punkt | Spec | Plan | Grund |
|---|---|---|---|
| Fenster schließen | „sofort zurück“ | 30 s Verzögerung auch beim Schließen | VTherm wendet die Verzögerung in beide Richtungen an (feature-window.md) |
| Safety-Modus | nicht behandelt | Verzögerung 150 min, Außensensor-Prüfung aus | Ohne Self-Regulation ist VTherm-Safety aktiv (Standard 60 min), die Sensoren melden nur ≤ 60 min |
| Lüftungshinweis | ab 60 % | Schwelle als Helfer, Start 65 % | Am 08.10. lagen alle Räume bei 61–67 % (Heizung aus) |
| Abwesend | eigenes Preset | Eco + Schalter heizung_abwesend |
VTherm-Preset away ist nur mit Präsenz-Feature wählbar; Abwesend = Eco-Werte |
| Heizfehler-Erkennung | – | nicht genutzt | VTherm-Funktion greift nur mit Ventilregelung, die E1 hat keine (feature-heating-failure-detection.md) |
| Bad | Komfort morgens + ab 21:30 erneut | ganztags Komfort ab Lars' Aufstehzeit bis Nachtruhe; kein Helfer „Bad abends“ | Robin 08.10.2026 |
| Offline-Timeout Sensoren / VTherm-Safety | – | 180 min (TRVs bleiben 150 min) | Robin 08.10.2026: mehr Luft gegen Fehlalarme |
Ausführungsreihenfolge und Abhängigkeiten¶
0 → 1 → (2 parallel, sobald OK) → 3 → 4 → 5 → 6 → 7 → 2 Tage Bad beobachten → 8 (Raum für Raum) → 9 → 10 → 11 → 12 → 13 → 14 (zwei Wochen) → 15.