Der Trockner stand für September mit 1.089 kWh in der Statistik. Ein Trockner, ein Haushalt, ein Monat. Gegen den Leistungssensor gerechnet lag der Faktor bei 99,9 — real waren es etwa 10,9 kWh. Die Zahl war nicht kaputt. Sie war falsch skaliert, und sie stand in einer Datenbank, die niemand mehr von Hand anschaut.

Das ist die unangenehme Eigenschaft von Energie-Dashboards: Sie sehen nach Messung aus, auch wenn sie nur Rechnung sind. Ein Template multipliziert, eine Long-Term-Statistik summiert, ein Preis rechnet Euro daraus. Jede dieser Stufen nimmt den Fehler der vorigen mit, ohne ihn zu vergrößern — bis auf eine.

Das Problem

Drei Steckdosen an Trockner, Waschmaschine und Geschirrspüler hängen als Zigbee-Energiezähler der Tuya/GreenSun-Klasse im Netz. Zigbee2MQTT läuft auf srv-coruscant, Home Assistant auf srv-rebellion. Die Geräte melden ihre Energie in 0,01 kWh. Home Assistant liest den Wert als kWh. Aus 10,9 werden 1.090, und niemand stolpert darüber, weil 1.090 kWh für ein Jahr Trockner nicht völlig absurd klingt — für einen Monat schon.

Korrigiert wurde zuerst nicht die Skala, sondern das Ergebnis. In configuration.yaml standen drei Template-Sensoren, die einen festen Wert abzogen: raw - 35, raw - 34, raw - 111, je nach Gerät. Das sieht aus wie eine Kalibrierung. Es ist eine Subtraktion mit drei Geräten und drei Zufallszahlen.

Der Zähler selbst ist ein total_increasing-Sensor, und das ist wichtig für das, was später passiert. Home Assistant erwartet bei dieser Klasse, dass der Wert monoton steigt. Fällt er, wird das als Zählerreset gewertet: Der neue Stand wird auf den alten aufaddiert, statt ihn zu ersetzen. Das ist für echte Stromzähler richtig. Für einen Sensor, dessen Basis niemand kennt, ist es ein Verstärker.

Die Diagnose

Der Leistungssensor war die Gegenprobe. Leistung über die Laufzeit integriert ergibt bei einem Trocknerlauf Bruchteile einer kWh. Der Zähler zeigte das Hundertfache. Der Faktor zwischen beiden Wegen lag bei 99,9 — nicht bei exakt 100.

Nach der Reparatur der Skala standen für September 10,89 kWh beim Trockner, 3,14 kWh bei der Waschmaschine und 20,8 kWh beim Geschirrspüler. Das sind Größenordnungen, die zu den Laufzeiten passen.

Auf dem Kosten-Dashboard sah es zu diesem Zeitpunkt noch schlimmer aus als bei den Geräten: −13,15 Mio. € beim Bezug, +72,5 k € bei der Einspeisung. Zwei Kosten-Sensoren trugen Reset-Add-Phantome aus einem alten Zählerreset, die nie jemand bereinigt hatte. Jeder Reset hatte den neuen Summenstand auf den alten gelegt. Über Monate wurde daraus eine Treppe nach oben, und weil der Preis daraufmultipliziert wurde, wuchs der Fehler mit jeder Stufe.

Wichtig für die Einordnung: Das Dashboard war nicht „um Faktor 100 falsch". Es zeigte die Summe mehrerer Fehler, von denen jeder eine andere Ursache hatte. Die Skalierung war einer davon, die Reset-Adds ein anderer.

Die Ursache

Zwei Fehler, die sich gegenseitig verdeckt haben.

Der erste ist eine Einheitenverwechslung. Das Gerät sendet Hundertstel-kWh, die Integration interpretiert kWh. Kein Parserfehler, keine kaputte Firmware — eine fehlende Division, die man nur sieht, wenn man gegen eine zweite Quelle prüft.

Der zweite ist die Reparatur durch Offset. Eine Subtraktion kann zufällig einen historischen Wert treffen. Sie kann nicht eine Steigung korrigieren. Ein Zähler, der 100-mal zu schnell wächst, bleibt 100-mal zu schnell, egal wie viel man am Anfang abzieht. Und nach dem nächsten Reset ist auch der Offset weg, weil die Basis dann eine andere ist.

Die Kosten-Sensoren haben den dritten Fehler geerbt. Sie waren aus der kaputten Energie und einem festen Preis gerechnet. Solange die Energie falsch war, war der Preis pro kWh nur eine Konstante auf einem falschen Integral.

Der Fix

Die Skala wird dort korrigiert, wo sie entsteht: im Template. Aus der Subtraktion wird eine Division.

- name: "Trockner Energy Corrected"
  unique_id: "trockner_energy_corrected"
  unit_of_measurement: "kWh"
  device_class: "energy"
  state_class: "total_increasing"
  state: >-
    {% set raw_value = states('sensor.trockner_energy') | float(0) %}
    {{ (raw_value / 100) | round(3) }}
  availability: >-
    {{ states('sensor.trockner_energy') not in ['unknown', 'unavailable', 'None'] }}

Das ändert aber nur die Zukunft. Die Vergangenheit liegt in der Statistik-Datenbank, und die kennt das Template nicht mehr. Deshalb wurde die Historie per recorder/import_statistics überschrieben — Zustand und Summe durch 100 geteilt.

Die Alternative wäre gewesen, die betroffenen Statistikzeilen zu löschen und neu sammeln zu lassen. Das ist schneller und falsch: Es wirft die gesamte Historie weg und reißt eine Lücke in jede Monatsauswertung. Der Import schreibt die vorhandenen Zeilen auf der richtigen Skala fort, statt sie zu entfernen.

Danach wanderte das Dashboard per energy/save_prefs von den Rohsensoren auf die korrigierten Entities. Erst damit verschwinden die alten Werte aus der Anzeige, nicht aus der Datenbank.

Die beiden Kosten-Sensoren wurden aus der korrigierten Energie und dem Preis neu berechnet und neu importiert. Ergebnis: 0,320 €/kWh für den Bezug, 0,300 €/kWh für die Einspeisung, in allen Monaten ohne Ausreißer.

Wer eine einzelne Steckdose gegenprüfen will, kommt mit einem zweiten Messpunkt weiter als mit jeder Rechnung. Eine schaltbare Zwischensteckdose mit eigenem Leistungsmesser liefert diesen Punkt: Shelly Wave 1PM. Nicht als Ersatz für den Zigbee-Zähler, sondern als unabhängige zweite Meinung — genau die hat hier gefehlt.

Story 2: Der Sprung um zwölf Uhr

Der Import lief am Vormittag, die Geräte zeigten danach plausible Werte, die Kosten waren saniert. Um 12:00 war der Sprung zurück. Nicht irgendwo am Rand: in der Tagesstatistik, auf die das Dashboard schaut. recorder.get_statistics für den Tag lieferte −2705 € beim Bezug und −2522 € bei der Einspeisung. Größenordnung: Tausende Euro, an einem Tag, in einem Haushalt.

Der Import war nicht rückgängig gemacht worden. Er war überschrieben worden — von Home Assistant selbst, pünktlich zur vollen Stunde.

Die zweite Ursache

Home Assistant führt zwei Statistik-Tabellen. statistics hält die Langzeitwerte, stündlich. statistics_short_term hält die 5-Minuten-Serie und wird nach etwa zehn Tagen beim Purge gelöscht. Der Import am Vormittag hatte nur statistics geschrieben. statistics_short_term behielt die alte Basis — den Faktor 100 und den Offset.

Der Stunden-Compile um 12:00 baut die Langzeitzeile nicht aus der vorigen Langzeitzeile. Er kompiliert sie aus den Kurzzeitwerten des vergangenen Intervalls. Solange dort die alte Basis stand, war die korrigierte Summe in statistics nur eine Zwischenstation. Der nächste Compile rechnete den Fehler wieder hinein, und die Reparatur war weg.

Das ist der Punkt, an dem die eigene Arbeit wie ein fremder Fehler aussieht. Man prüft das Dashboard, sieht die korrigierten Werte, ist zufrieden. Der Fehler ist zu diesem Zeitpunkt schon eingeplant — er wartet auf den nächsten Stundenwechsel.

Der zweite Fix

Vor dem Eingriff steht ein Backup der Datenbank: /config/home-assistant_v2.db.pre-stepsfix-20261003. Eine Statistik-Reparatur ist ein Schreibvorgang auf die Historie. Ohne Sicherung ist der Ausgangszustand nicht mehr rekonstruierbar, und „ich weiß noch, was drinstand" ist kein Rollback.

Korrigiert wurde per WebSocket mit recorder/adjust_sum_statistics, wirksam ab 2026-10-03T10:00:00+00:00 — also der 12-Uhr-Zeile in lokaler Zeit:

{
  "id": 1,
  "type": "recorder/adjust_sum_statistics",
  "statistic_id": "sensor.<name>_energy_corrected",
  "adjustment": -<delta>
}

Die betroffenen Summen wurden um die Differenz verschoben: die beiden Kosten-Sensoren positiv, die drei Geräte-Energien negativ, weil dort zu viel aufaddiert worden war. Danach zeigte der Tag +1,80 € beim Bezug, +3,02 € bei der Einspeisung und 0,0 / 0,0 / 0,78 kWh bei den Geräten. Die nach dem Fix kompilierte Zeile führt die korrigierte Basis weiter, ein erneuter Sprung blieb aus.

Ein Rest bleibt bewusst stehen: Die statistics_short_term-Zeilen zwischen 11 und 12 Uhr tragen den Sprung weiter. Sie betreffen nur die 5-Minuten-Charts und verschwinden mit dem nächsten Purge nach rund zehn Tagen. Wer dort nachsieht, sieht den alten Fehler — kein neuer.

Was offen bleibt

Der Heizungs-Energiesensor liegt rund 12-mal zu hoch. Das ist eine andere Geräteklasse (TS011F), ein anderer Fehler, und er ist nicht behoben. Dazu kommen 65535-Ausschläge an den Leistungssensoren von Waschmaschine und Geschirrspüler — der 16-Bit-Grenzwert, der als kurzer Peak durchschlägt. Beides ist bekannt und beides steht noch offen.

Offen ist auch die Reproduzierbarkeit des Imports selbst: Das Payload, mit dem die Historie überschrieben wurde, liegt nicht im Repo. Überliefert sind das Ergebnis und die Templates, nicht der abgesetzte Aufruf. Wer den Fall nachbauen will, muss den Import aus der Dokumentation von recorder/import_statistics selbst zusammensetzen — und dabei dieselbe Falle beachten, die hier zum zweiten Phantom geführt hat.

Was du mitnimmst

Der erste Fix erzeugt das zweite Phantom. Wer nur statistics importiert und statistics_short_term stehen lässt, sieht um 12 Uhr genau den Sprung, den er gerade wegrechnen wollte. Die beiden Tabellen sind keine Kopien voneinander; die Kurzzeitserie ist die Quelle, aus der die Langzeitzeile kompiliert wird. Reparieren heißt: beide Ebenen, sonst wartet der Fehler auf den nächsten Stundenwechsel.

Zweitens: Ein Offset ist keine Kalibrierung. Wenn ein Sensor um einen Faktor falsch ist, gehört eine Division hin, keine Subtraktion, die zufällig einen alten Wert trifft. Und wenn ein total_increasing-Sensor Resets sieht, während die Basis falsch ist, wird aus jedem Reset eine weitere Stufe auf der Treppe. Das ist der Unterschied zwischen einem falschen Messwert und einer Statistik, die Monate später noch wächst.

Drittens: Die Gegenprobe entscheidet, nicht das Dashboard. Ein zweiter Messpunkt — Leistungssensor, Messsteckdose, ein Blick auf die Laufzeit — zeigt den Faktor in einer Minute. Ohne ihn bleibt nur die Plausibilität, und 1.089 kWh für einen Monat Trockner ist genau so plausibel, dass es niemandem auffällt.

Home Assistant läuft hier auf einem kleinen Mini-PC; für diese Größenordnung reicht Mini-PC N100 Beelink S12 Pro. Wer zusätzlich Kameras, Transcoding oder mehrere Add-ons stemmt, ist mit einem Minisforum MS-01 (i5-12600H, Barebone ohne RAM/SSD) in der oberen Klasse besser bedient. Das Entscheidende ist nicht die Hardware, sondern die Frage, die man an sie stellt. Diese Statistik ist nicht die, die du suchst — jedenfalls nicht die, die du repariert hast.