· DevOps  · 6 Min. Lesezeit

485 Liter in einer Stunde, und mein Smart Home schwieg

Eine Home-Assistant-Automatisierung sollte bei Wasserrohrbruch alarmieren. Wochenlang tat sie es nicht. Die Fehlersuche führte über ein 100-Zeichen-Limit, einen hängenden Template-Trigger und die Erkenntnis, dass die bearbeitete Config gar nicht die war, die lief.

Eine Home-Assistant-Automatisierung sollte bei Wasserrohrbruch alarmieren. Wochenlang tat sie es nicht. Die Fehlersuche führte über ein 100-Zeichen-Limit, einen hängenden Template-Trigger und die Erkenntnis, dass die bearbeitete Config gar nicht die war, die lief.

Es gibt Bugs, die kosten dich eine Stunde. Und es gibt Bugs, die dich im Ernstfall einen Wasserschaden im Keller kosten. Dieser hier gehörte zur zweiten Sorte. Das Fiese daran: Von außen sah alles gesund aus. Die Automatisierung war „an“, sie lief brav alle zehn Minuten, ihr last_triggered war taufrisch. Nur melden tat sie sich nicht.

Der Auslöser war eine harmlose Beobachtung. An einem Nachmittag flossen innerhalb einer Stunde 485 Liter durch den Zähler, fast das Fünffache der Alarmschwelle. Auf dem Handy: nichts.

Der Screenshot oben zeigt den absoluten Zählerstand am 17.07., direkt aus der Home-Assistant-History. Der fast senkrechte Sprung gegen 15:30 sind rund 485 Liter in einer Stunde, und er blieb ohne Push-Meldung.

Der Aufbau

Die Idee der Automatisierung ist simpel und eigentlich robust:

  • Ein Funk-Wasserzähler (WMBus) liefert den Zählerstand als sensor.wasserzahler_wasser in m³ (total_increasing).
  • Alle zehn Minuten schreibt ein tick-Zweig den aktuellen Stand in einen kleinen Ringpuffer, ein input_text.wasser_puffer_60min, das die letzten 7 Werte kommagetrennt hält. Sieben Werte im 10-Minuten-Takt ergeben ungefähr die rollende Stunde.
  • Ein Template-Trigger volumen vergleicht laufend cur - buf[0], also den aktuellen Stand minus dem ältesten Pufferwert. Übersteigt die Differenz 0,1 m³ (100 L) in der rollenden Stunde, geht eine Push-Meldung raus: „Wasser: Hoher Verbrauch, möglicher Rohrbruch?“

Klingt gut. Lief auch. Bis zu einem bestimmten Tag im Juni.

Phase 1: Beweise, keine Vermutungen

Erster Reflex bei so etwas: nicht raten, sondern den Live-Zustand ansehen. Drei Abfragen über die HA-REST-API genügten, um den Fall aufzureißen:

sensor.wasserzahler_wasser      = 233,74 m3    (aktualisiert vor Minuten, Sensor lebt)
automation.wasser_...           = "on", last_triggered heute 16:00 (laeuft)
input_text.wasser_puffer_60min  = last_changed 24.06. 14:20  (seit 3 Wochen eingefroren)

Der Puffer hatte sich seit dem 24. Juni nicht mehr verändert. Er enthielt noch sechs alte Werte um 225,4, während der Zähler längst bei 233,7 stand. Der Sensor lief, die Automatisierung feuerte, aber der Puffer war tot.

Root Cause 1: Ein 100-Zeichen-Limit tötet den Puffer

Der entscheidende Blick galt den Attributen des Helpers: max: 100. Ein input_text erlaubt standardmäßig maximal 100 Zeichen. Und jetzt kommt der WMBus-Zähler ins Spiel, der seine Werte mit voller Fließkomma-Präzision liefert:

225.440994262695   (16 Zeichen, pro Messwert)

Rechnen wir nach:

Werte im PufferString-Länge
6 Werteexakt 100 Zeichen
7 Werte117 Zeichen, von Home Assistant abgewiesen

Sobald der Puffer sechs Einträge hatte, versuchte der tick-Zweig alle zehn Minuten, sieben Werte (117 Zeichen) zu schreiben. input_text.set_value lehnte das jedes Mal still ab. Der Ringpuffer fror bei sechs Werten ein und blieb es. Wochenlang.

Root Cause 2: Ein Edge-Trigger, der auf „true“ hängen bleibt

Damit war auch klar, warum nie wieder eine Meldung kam. Der volumen-Trigger ist ein Template-Trigger, und die feuern nur bei der Flanke von false auf true.

Er vergleicht cur - buf[0]. Mit eingefrorenem buf[0] = 225,44 und einem Zähler, der nur steigt, war diese Differenz seit dem 24. Juni dauerhaft über 0,1. Heute sind es 8,3 m³. Der Trigger flankte also einmal Ende Juni auf „true“, schickte eine letzte Meldung und blieb danach für immer auf „true“ stehen. Kein Flanken mehr, keine Meldung mehr. Der 485-Liter-Peak lief ins Leere.

Home-Assistant-History des Wasserzählers über zehn Tage: eine stetig steigende Linie von 229,6 auf 233,7 m³.

Zehn Tage Zählerstand aus der Home-Assistant-History, ganz normaler, kontinuierlicher Verbrauch. Genau diesen Anstieg hätte der Ringpuffer mitschreiben sollen. Er war seit dem 24. Juni eingefroren und bekam davon nichts mit. buf[0] stand fest bei 225,44, während der Zähler auf 233,7 kletterte. Die Differenz lag damit dauerhaft bei 8,3 m³, weit über der 0,1-m³-Schwelle.

Die Pointe: Die Config, die ich las, war nicht die, die lief

Der Fix schien trivial: die Werte beim Speichern runden, dann bleibt der String kurz. Also | round(3) in den tick-Zweig, Datei speichern, automation.reload, fertig.

Nur: Der nächste Live-Tick speicherte weiter einen ungerundeten 16-stelligen Wert. Der Fix wirkte nicht.

Also fragte ich nicht die Datei, sondern das laufende System, was es geladen hatte:

GET /api/config/automation/config/<id>

Der Abgleich zwischen der bearbeiteten Datei und dieser Live-Antwort brachte die eigentliche Überraschung. Die laufende Automatisierung enthielt weder meine Rundung noch einen älteren, längst „gefixten“ Bugfix. Im volumen-Zweig stand dort sogar noch ein komplett verstümmelter Ausdruck:

{{ states('input_text.wasser_puff0-9.]+') | map('float') | list }}

Mit anderen Worten: Der gemountete Config-Ordner, in dem seit Monaten „aufgeräumt“ und „gefixt“ wurde, war gar nicht die Konfiguration, die das Gerät ausführte. Ein Edit plus reload änderte am Live-Verhalten nichts. Selbst wenn der eingefrorene Puffer nicht gewesen wäre, hätte dieser kaputte Template-Ausdruck die Meldung ein zweites Mal verhindert.

Der Fix, am richtigen Objekt

Die Lösung bestand aus drei kleinen Template-Korrekturen, ausgespielt direkt ins laufende System über die HA-Config-API, den einzigen Weg, der das Gerät wirklich erreichte:

# tick-Zweig: Werte runden, 7 Werte ergeben 55 statt 117 Zeichen
cur: "{{ states('sensor.wasserzahler_wasser') | float | round(3) }}"
buf: "{{ states('input_text.wasser_puffer_60min')
        | regex_findall('[0-9.]+') | map('float') | map('round', 3) | list }}"

# volumen-Zweig: den verstuemmelten Ausdruck reparieren
buf: "{{ states('input_text.wasser_puffer_60min')
        | regex_findall('[0-9.]+') | map('float') | list }}"

Dazu einmalig den Puffer auf den aktuellen Zählerstand zurücksetzen. Das räumt die alten Langwerte weg und entriegelt den Edge-Trigger: cur - buf[0] wird wieder 0, die Bedingung fällt auf „false“ und kann bei der nächsten echten Spitze erneut flanken.

Verifikation: erst glauben, wenn es beweisbar ist

Ein Fix ohne Beweis ist eine Hoffnung. Also:

  • Nächster Live-Tick: Puffer jetzt 233.737,233.737, 15 statt 117 Zeichen, sauber gerundet. Der Überlauf ist weg.
  • Trigger-Logik per Template-Engine gegengerechnet: bei 0,15 m³ über dem Pufferstart liefert die Bedingung true, der Meldungstext rechnet „150 L“.
  • End-to-End: Puffer kurz 0,151 m³ unter den Zählerstand gesetzt, die Automatisierung feuerte zwei Sekunden später, außerhalb des Zehn-Minuten-Rasters. Die „Hoher Verbrauch“-Push kam real auf dem Handy an. Danach wieder scharf gestellt.

Drei Lektionen zum Mitnehmen

  1. input_text ist kein Datenspeicher. 100 Zeichen sind schnell voll, wenn Sensoren mit voller Fließkomma-Präzision liefern. Wer Werte serialisiert, sollte vorher runden und die Länge deterministisch deckeln. Für echte Historie sind input_number, Attribute oder der Recorder die bessere Wahl.
  2. Template-Trigger sind flankengesteuert. Eine Bedingung, die dauerhaft „true“ ist, feuert nie wieder. Wenn der Referenzwert einfrieren kann, ist der Alarm still, ganz ohne Fehlermeldung.
  3. Verifiziere gegen das laufende System, nicht gegen deine Datei. Der teuerste Teil dieser Fehlersuche war die stille Annahme, dass „gespeichert“ gleich „deployed“ sei. Ein GET auf den echten Live-Stand hätte das in dreißig Sekunden entlarvt.

Das Beruhigende: Seit dem Fix läuft der Puffer wieder mit, und der nächste 485-Liter-Ausreißer wird sich melden. Das Lehrreiche: Eine Automatisierung, die „an“ ist und brav triggert, kann trotzdem seit Wochen komplett wirkungslos sein. Monitoring, das sich selbst nicht überwacht, ist nur ein gutes Gefühl.

Sie verlassen sich auf eine Automatisierung, die im Ernstfall Alarm schlagen soll? Ich prüfe solche Ketten end-to-end und baue sie so, dass ein stiller Ausfall auffällt, statt im Verborgenen zu bleiben. Wie ich Überwachung und Wartung aufsetze, lesen Sie unter Monitoring & Wartung, das Erstgespräch ist unverbindlich.

Alex Jabi, Jabi IT

Alex Jabi

Ich betreue IT, Informationssicherheit und Datenschutz für KMU und Praxen an der Bergstraße und im Odenwald, persönlich, dokumentiert und ohne Cloud-Zwang.

Mehr über mich
Zurück zum Blog

Passende Artikel

Alle Artikel »