Zum Inhalt

E6a Heizung und Smart-Home-Zentrale: Umsetzungsplan

Für ausführende Agenten: Pflicht-Skill: superpowers:subagent-driven-development (empfohlen) oder superpowers: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 /config bearbeiten, kein .storage.
  • Vor jeder Automation, jedem Helfer und jedem Dashboard den Skill home-assistant-best-practices laden (ha-mcp-Ressource skill://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 über mqtt.publish bzw. 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 Wohnzimmer climate.wohnzimmer_heizung_vorne und climate.wohnzimmer_heizung_hinten. Je TRV gibt es select.<trv>_sensor (internal/external), switch.<trv>_window_detection und number.<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.

  1. 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.
  2. Raumtemperatur unavailable: Die Einspeisung sendet keinen Wert, setzt das TRV auf internal und meldet „Notbetrieb“. Test in Task 3, Schritt 5.
  3. 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.
  4. Ü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.
  5. 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_backup ein Voll-Backup erstellen, Name vor-e6a-2026-10. Erwartet: Backup in der Liste mit Größe > 0.
  • Schritt 2: Zustand vorher festhalten. Per ha_config_get_automation die 7 AHC-Automationen und die 7 Reset-Automationen lesen. Ihre IDs und config_hash kommen in docs/planung/2026-10-e6a/umsetzung-log.md (Abschnitt „Ausgangslage“). Ebenso Better Thermostat (ha_get_integration, Domain better_thermostat).
  • Schritt 3: VTherm installieren. Per ha_manage_hacs das Repository jmcollin78/versatile_thermostat installieren 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 per ha_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.md eintragen.

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_temperature per ha_get_entity → config_entry_id → ha_get_integration(entry_id=…, include_options=True) lesen. Festhalten: Typ (heute mean), Mitglieder, und ob das Options-Formular type änderbar macht.
  • Schritt 2: Räume ohne Ersatz bereinigen. Abstellraum, Bad, Esszimmer und Küche behalten Typ mean mit 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)
  • Die bestehende Gruppe sensor.<raum>_sensor_temperature per Options-Update auf Typ first_available mit 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_search alle 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. initial nicht setzen.
  • Schritt 5: Testen. Per ha_eval_template je 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_template mit {{ 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/options mit {"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 und grep -c "timeout: 150$" … → 8 (TRVs). Mit $: Ohne Zeilenende-Anker trifft 150 auch die globale 1500.
  • 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: “, der Migrationsschalter, aus = AHC-Welt), 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 ohne initial (Werte bleiben über Neustarts erhalten). Die Startwerte danach per input_datetime.set_datetime bzw. input_number.set_value setzen. Alle Helfer bekommen das Label heizung: per ha_config_set_label anlegen, 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.trigger auslösen (alle Schalter heizung_neu_* aus). Erwartet laut Trace (ha_get_automation_traces): alle Räume übersprungen, kein mqtt.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_template sicherstellen, dass der Zweig greift. Dazu die Automation mit einer Testkopie prüfen: In der Kopie temp auf 'unavailable' setzen und nur badezimmer in raeume, 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 auf external.

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_modes enthält heat und off, preset_modes enthält comfort, eco, boost, frost. Die tatsächliche Entitäts-ID ins Log. Weicht sie ab, ersetzt sie climate.heizung_badezimmer in 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_off für automation.badezimmer_advanced_heating_control und automation.badezimmer_reset_heizung_to_schedule. Die Reset-ID per ha_search bestätigen, Muster automation.<raum>_reset_heizung_to_schedule.
  • switch.turn_off für switch.badezimmer_heizung_window_detection.
  • input_boolean.turn_on für input_boolean.heizung_neu_badezimmer.
  • VTherm Bad auf heat, Preset eco.
  • Schritt 6: Einspeisung prüfen. Nach höchstens 1 min erwartet:
  • select.badezimmer_heizung_sensor = external
  • number.badezimmer_heizung_external_temperature_input ≈ sensor.badezimmer_sensor_temperature, höchstens 0,2 K Abweichung
  • im Trace der Einspeisung genau ein mqtt.publish an zigbee2mqtt/badezimmer_heizung/set
  • Schritt 7: Aufheizrate aufzeichnen (Spec-Prüfpunkt 6). Den slope-Sensor von VTherm Bad per ha_search domain_filter=sensor query=heizung_badezimmer finden. Mit ha_get_state prüfen: Hat er state_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_temperature auf 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 comfort setzen. Erwartet nach ca. 2 min: Trace ohne climate.set_*-Aufruf an das TRV, input_boolean.heizung_stoerung_badezimmer = off.
  • Schritt 3: Reparatur testen. Das TRV direkt verstellen: climate.set_temperature an climate.badezimmer_heizung mit 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_wartung an, 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 comfort setzen. 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 bei erzwingen). 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_pattern minutes: "/5"
  • homeassistant start
  • state auf alle 6 Uhrzeit-Helfer, input_boolean.heizung_wach_heute, input_boolean.heizung_abwesend und binary_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_input und climate.badezimmer_heizung → current_temperature lesen. Erwartet: zeigt z. B. 20,65 und nicht 21. Wenn gerundet: In der Einspeisung round(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_badezimmer für 3 h aus, dabei VTherm Bad auf Eco lassen. Alle 30 min select.badezimmer_heizung_sensor lesen. Erwartet: Er bleibt external oder springt zu einem messbaren Zeitpunkt auf internal. Den Zeitpunkt festhalten. Ist er kürzer als 60 min: Keep-alive in der Einspeisung auf /15 stellen.
  • 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) und sensor.badezimmer_heizung_battery gegen den Startwert vergleichen. Richtwert (Annahme): ≤ 50 Schreibvorgänge pro Tag. Ist es mehr: Schwelle auf 0,3 K.
  • Schritt 5: Ergebnisse in umsetzung-log.md und 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-Sensor sensor.<raum>_sensor_temperature, Fenster binary_sensor.<raum>_sensor_window, im Arbeitszimmer binary_sensor.arbeitszimmer_sensor_fenster, Gerät climate.<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_off fü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_profil mit erzwingen: 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_off für automation.wohnzimmer_advanced_heating_control und die Reset-Automation. Better Thermostat climate.wohnzimmer_heizung per climate.set_hvac_mode off stilllegen. Die Integration erst in Task 14 entfernen.
  • Schritt 2 (Robin, Oberfläche): VTherm Heizung Wohnzimmer wie Task 4, Schritt 2, aber mit zwei Geräten: climate.wohnzimmer_heizung_vorne und climate.wohnzimmer_heizung_hinten. Sensor sensor.wohnzimmer_sensor_temperature, Fenster binary_sensor.wohnzimmer_sensor_window (Dachterrassen-Türen).
  • Schritt 3: Presets: Komfort 20, Eco 17. Grundtemperatur kommt aus dem Helfer. Beide switch.wohnzimmer_heizung_*_window_detection aus. Dann input_boolean.heizung_neu_wohnzimmer an und script.heizung_raum_profil mit erzwingen.
  • Schritt 4: Prüfen. Beide TRVs external, beide bekommen eingespeist, VTherm available. Ab 19:00 erwartet: Block grund, 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_wohnzimmer an
  • bei den nächsten Abgleichen keine weiteren Pushes (der Schalter verhindert die Wiederholung)
  • wohnzimmer_heizung_vorne erhä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_temperature bleibt 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.md aus dem HA-Skill lesen. Vorlage ist das Claude-Design-Mockup „Smart-Home-Zentrale“ (Projekt 935d7814-5bab-4cc5-9c69-0075d0645ed3). Per mcp__claude_design__read_file lesen.
  • Schritt 2: Status-Template testen. Die Raumkachel braucht einen Status aus VTherm und Helfern. Das Template vorher mit ha_eval_template fü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_schutz anlegen:
  • Trigger numeric_state auf den Batteriesensor des iPads (sensor.<ipad>_battery_level aus der HA-App, Namen per ha_search): below: 20 → switch.turn_on, above: 80 → switch.turn_off, je mit for: {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-A01 und 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: time 11:00, Bedingung condition: time weekday: mon und select.central_mode im Sommermodus
  • Ablauf: input_boolean.heizung_wartung an (Zustellprüfung pausiert); für alle 8 TRVs climate.set_hvac_mode heat und climate.set_temperature 30; delay 10 min; climate.set_temperature 7, dann climate.set_hvac_mode auf den VTherm-Modus; input_boolean.heizung_wartung aus
  • Schritt 3: Testen. Per automation.trigger mit skip_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 #185 im PR oder gh 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.