Skip to content

Commit 2b69467

Browse files
README: was die Robustheitsrunde gebracht hat (3.20.0)
Der Aufwand fuer den Langzeitbetrieb stand bisher nur in den Commits und in den beiden Planungsdateien. Jetzt steht er da, wo ihn jemand findet: - Neuer Abschnitt "Damit es Monate durchhaelt - und man merkt, wenn nicht (3.20.0)" mit den vier Punkten: Notbetriebswerte ueberleben den Neustart im RTC-Speicher (aber keinen Stromausfall - das ist die bewusste Grenze), Bootzaehler und Reset-Ursache gegen unbemerkte Neustarts, Logring mit /log und Telnet-Rueckfall, Zeitstempel aus der Systemuhr. - Ein eigener Unterabschnitt fuer die Messung, weil sie der ehrlichste Teil ist: Die im Bericht vorgeschlagene Pruefung liefert gruen und prueft dabei nichts. Die Systemuhr ueberlebt einen Software-Neustart, und der ioBroker-Adapter trennt stille Verbindungen nach rund 30 s selbst - erst mit verkuerzter NTP-Frist laeuft der Schwall wirklich durch (15 ausgefuehrte Set-Kommandos, mit 3.20.0 null). Dass die Begruendung im Bericht nicht stimmte, steht mit drin. - Fuenf Zeilen in der Vergleichstabelle und ein Absatz in "Worum es geht", der die Zeitachse als eigene Frage aufmacht: nicht "funktioniert es", sondern "wuerde ich es merken, wenn nicht". - Die Betriebszahlen, seit es sie zu sehen gibt, samt der Erklaerung fuer den Unterschied zwischen den Stufen und dem Testboard. Mitgenommen: Die Bildunterschrift behauptete Envs <code>…_h1b_ota</code> und <code>…_h2b_ota</code> fuer die Ersatzplatinen. Die gibt es nicht - sie tragen denselben Build wie ihre Stufe und unterscheiden sich nur im Hostnamen und im toten MQTT-Port. Berichtigt. Alle internen Anker gegengeprueft, Tabellenzeilen dreispaltig. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent 102a481 commit 2b69467

1 file changed

Lines changed: 153 additions & 3 deletions

File tree

‎README.md‎

Lines changed: 153 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,9 +87,11 @@ Kommandos in die Gegenrichtung.
8787
<em><b>Die Geschwister.</b> Sie sehen nicht nur gleich aus, sie tragen auch
8888
denselben Quelltext — der ganze Unterschied zwischen Stufe 1 und Stufe 2 sind
8989
drei Build-Flags (MQTT-Präfix, Web-Titel, Hostname). Im Projekt laufen sie
90-
deshalb unter <code>…_h1_ota</code> und <code>…_h2_ota</code>, die beiden
91-
Ersatzplatinen unter <code>…_h1b_ota</code> und <code>…_h2b_ota</code>; die
92-
Aufkleber auf dem Foto sind nur für das Foto. Genau diese Verdopplung hat die
90+
deshalb unter <code>…_h1_ota</code> und <code>…_h2_ota</code>. Die beiden
91+
Ersatzplatinen tragen <b>denselben</b> Build wie ihre Stufe — eigene Envs
92+
gibt es dafür nicht, sie unterscheiden sich nur im Hostnamen und in einem
93+
toten MQTT-Port, der sie stilllegt. Die Aufkleber auf dem Foto sind nur für
94+
das Foto. Genau diese Verdopplung hat die
9395
Fehler ans Licht gebracht, die weiter unten stehen: Ein Gerät, das jemand von
9496
Hand bedient, verzeiht vieles. Zwei Geräte, die alle fünf Minuten den
9597
kompletten Sollzustand aufgedrückt bekommen, verzeihen nichts.</em>
@@ -105,6 +107,12 @@ und wiederholt den kompletten Sollzustand **alle 5 Minuten** (Re-Assert). Genau
105107
dieses Muster hat Fehler sichtbar gemacht, die im Einzelbetrieb mit
106108
Handbedienung niemandem auffallen.
107109

110+
Und dann ist da noch die Zeitachse. Die beiden Bridges hängen im Keller, ohne
111+
Bildschirm, ohne dass jemand hinschaut, und sollen **Monate am Stück** laufen.
112+
Die spannende Frage ist dabei nicht „funktioniert es?" — sondern „würde ich es
113+
überhaupt mitbekommen, wenn nicht?". Was daraus geworden ist, steht unter
114+
[Damit es Monate durchhält](#damit-es-monate-durchhält--und-man-merkt-wenn-nicht-3200).
115+
108116
## Anpassen ist der Normalfall, nicht der Sonderfall
109117

110118
Diese Firmware ist für *eine* Anlage konfiguriert — aber sie ist so gebaut, dass
@@ -199,9 +207,151 @@ Zu holen ist hier aber noch mehr — für alle, die eine eigene Umsetzung bauen:
199207
| Broker weg | nur eine Zeile im MQTT-Log, die niemanden erreicht | die Weboberfläche sagt es: „Hausteuerung seit 14 Minuten nicht erreichbar" |
200208
| Steuerung rechnet nicht mehr | fällt gar nicht auf, von außen sieht alles gesund aus | erkannt am ausbleibenden 5-min-Re-Assert, eigener Text auf der Seite |
201209
| Heizstab-Auftrag beim Notbetrieb | kennt keinen Notbetrieb | wird als eigener Schritt zurückgenommen, bevor die Anlage anläuft |
210+
| Unbemerkte Neustarts | keine Spur — das Gerät ist binnen Sekunden wieder „Online" | Bootzähler und Reset-Ursache als Topics, ein Watchdog-Neustart fällt auf |
211+
| Speicherverbrauch | über Telnet als Prozentwert relativ zum Boot | vier Zahlen als Topics, in InfluxDB auftragbar |
212+
| Notbetriebswerte nach einem Neustart | kennt keinen Notbetrieb | überleben im RTC-Speicher; erst ein Stromausfall räumt sie weg |
213+
| Logzeilen bei weggefallenem Broker | verschwinden still | Ringpuffer im RAM, Route `/log`, zusätzlich Telnet |
214+
| Logzeitstempel | driften, kennen keine Sommerzeitumstellung | aus der von SNTP nachgeführten Systemuhr |
202215
203216
Im Detail:
204217
218+
### Damit es Monate durchhält — und man merkt, wenn nicht (3.20.0)
219+
220+
Irgendwann ist der spannende Teil vorbei. Die Topics stimmen, die Kommandos
221+
kommen an, der Notbetriebsknopf tut, was er soll. Und dann steht das Ding im
222+
Keller und soll einfach laufen. Ein halbes Jahr. Ohne dass jemand hinschaut.
223+
224+
Genau dafür ist die ganze Firmware im September 2026 noch einmal durchgelesen
225+
worden — nicht mit der Frage „ist das richtig?", sondern mit der deutlich
226+
unangenehmeren: **„was davon würde ich merken, wenn es schiefgeht?"** Der
227+
Befundbericht liegt als
228+
[`Massnahmenplan-Codedurchsicht-2026-09-02.md`](Massnahmenplan-Codedurchsicht-2026-09-02.md)
229+
im Repo, die Umsetzung samt Messprotokoll als
230+
[`Arbeitsplan-Robustheit-3.20.0.md`](Arbeitsplan-Robustheit-3.20.0.md).
231+
Herausgekommen sind vier Dinge.
232+
233+
**Die Notbetriebswerte überleben jetzt einen Neustart.** Sie lagen bis dahin
234+
nur im RAM — mit der durchaus vernünftigen Begründung: Wenn die Bridge neu
235+
startet, während der Broker weg ist, war der Strom weg, und dann läuft ohnehin
236+
nichts. Nur stimmt das nicht mehr, seit die Firmware sich *selbst* neu startet:
237+
WLAN-Watchdog nach fünf Minuten ohne Netz, `/reboot`, jedes OTA. Der ungünstige
238+
Fall ist unangenehm konkret — der Server im Keller ist tot (das *ist* der
239+
Notbetriebsfall), und gleichzeitig zieht sich der Router ein Update rein, das
240+
länger als fünf Minuten dauert. Bridge startet neu, Werte weg, und der Knopf
241+
sagt „Nicht bereit" in genau dem Moment, für den er gebaut wurde.
242+
243+
Jetzt liegt eine Kopie im RTC-Speicher — dem kleinen Stück RAM, das der ESP32
244+
über einen Software-Reset hinweg nicht anfasst. Sie überlebt Neustart, Watchdog
245+
und OTA, und **einen Stromausfall überlebt sie nicht**. Das ist kein Versehen,
246+
sondern die Grenze, die bewusst so gezogen wurde: kein Schreiben ins Flash,
247+
keine Datei, nichts, was Monate alte Werte in eine Anlage tragen könnte, die
248+
inzwischen ganz woanders steht. Die Prüfung, ob dem Inhalt zu trauen ist, steht
249+
arduino-frei in [`src/rtcspiegel.h`](src/rtcspiegel.h) und wird von
250+
[`test/rtcspiegel_test.cpp`](test/rtcspiegel_test.cpp) mit 42 Zusicherungen
251+
auseinandergenommen — Bitkipper, falsche Rolle, Spiegel der Vorversion nach
252+
einem Update, Zähler am Überlauf.
253+
254+
**Das Gerät sagt jetzt, wenn es neu gestartet ist.** Vorher: gar nichts. Ein
255+
Watchdog-Neustart, ein Brownout, eine Panic — nichts davon hinterließ eine
256+
Spur, das LWT war binnen Sekunden wieder `Online`, und von außen sah alles
257+
gesund aus. Ein Gerät, das alle drei Tage neu startet, wäre schlicht nicht
258+
aufgefallen. Unter `<prefix>/info/` stehen deshalb jetzt acht Zahlen:
259+
Bootzähler, Reset-Ursache und Version einmal je Verbindung, dazu Laufzeit,
260+
freier Heap, kleinster je gesehener Heap, größter zusammenhängender Block und
261+
der Rest-Stack im Fünf-Minuten-Takt.
262+
263+
Der Bootzähler liegt im selben RTC-Block wie die Notbetriebswerte und zählt
264+
damit genau das, was ein Stromausfall wegräumt. Steht er auf 1, war das Gerät
265+
stromlos. Steht er höher, hat die Firmware sich selbst neu gestartet — und die
266+
Reset-Ursache sagt, warum. Alles als einzelne Zahlen und nicht als JSON-Zeile,
267+
damit ioBroker sie als Zahlen führt und man sie in InfluxDB auftragen kann: Ein
268+
Sprung in der Kurve ist ein Neustart, ein über Tage fallender Heap-Tiefpunkt
269+
ein Leck.
270+
271+
**Das Log verschwindet nicht mehr genau dann, wenn man es braucht.** Alle
272+
Meldungen des Notbetriebs — „Schritt 3/10", „ROT: … kam nicht zurück", die
273+
Antwort des Hydraulik-Schalters mit HTTP-Code — gingen ins MQTT-Log und sonst
274+
nirgends. Im echten Notbetriebsfall ist der Broker aber weg. Das Publish
275+
scheitert still, und nach dem 15-Minuten-Anzeigeverfall war nirgends mehr
276+
nachlesbar, warum ein Lauf rot war. Jetzt landet jede Ereigniszeile zuerst in
277+
einem Ringpuffer im RAM (32 Zeilen, fest, kein Heap), geht dann ins MQTT-Log —
278+
und wenn das nicht klappt, nach Telnet. Die letzten Meldungen stehen unter
279+
`/log`, hinter demselben Zugang wie der Notbetriebsknopf, verlinkt von dessen
280+
Seite. Der Ring überlebt keinen Neustart; dass es einen gab, sieht man jetzt ja
281+
am Bootzähler.
282+
283+
**Und die Zeitstempel stimmen wieder.** Die Loguhr wurde beim Booten einmal
284+
gestellt und lief danach frei auf `millis()` weiter — über Monate driftet das,
285+
und die Umstellung im Oktober erreichte sie erst beim nächsten Neustart. Jetzt
286+
liest sie die Systemuhr, die im Hintergrund von SNTP nachgeführt wird. Und wenn
287+
gar keine Zeit da ist, stempeln die Zeilen auf die Laufzeit (`+01:23:45`) statt
288+
auf `1970-01-01` — was nach einem Stromausfall ohne Internet der Normalfall ist
289+
und die Zeilen sonst alle gleich aussehen ließe.
290+
291+
#### Und dann kam die Messung dazwischen
292+
293+
Der interessanteste Teil war nicht das Bauen, sondern der Versuch, den ersten
294+
Befund am Gerät nachzustellen. Es ging um das Karenzfenster aus
295+
[3.6.1](#der-broker-spielt-beim-verbinden-alles-wieder-ein-361): Nach jedem
296+
Verbinden schickt der ioBroker-Adapter den gespeicherten Wert *jedes*
297+
abonnierten Set-Topics hinterher, teils Monate alt, und die Firmware verwirft
298+
diesen Schwall fünf Sekunden lang. Der neue Befund lautete: Das Fenster startet
299+
in `setupMqtt()`, gelesen wird der Schwall aber erst nach `setup()` — und
300+
dazwischen wartet die Zeitsynchronisation bis zu 30 Sekunden auf einen
301+
Zeitserver. Ist das Fenster dann zu, läuft alles durch.
302+
303+
Die im Bericht vorgeschlagene Prüfung war: Zeitserver unerreichbar machen,
304+
neu starten, mitschneiden. **Sie liefert grün — und prüft dabei nichts.** Zwei
305+
unabhängige Gründe, beide erst am Gerät sichtbar geworden:
306+
307+
* Die **Systemuhr überlebt einen Software-Neustart**. Nach `/reboot` und nach
308+
jedem OTA findet die Zeitsynchronisation sofort eine plausible Uhrzeit und
309+
wartet gar nicht erst. Gemessen: Neustart 10:29:16, erste Logzeile 10:29:26 —
310+
mit korrekter Uhrzeit, bei unroutbarem Zeitserver. Der Befund ist also
311+
ausschließlich ein *Kaltstart*-Thema.
312+
* Und selbst der Kaltstart löst ihn nicht aus, solange der Broker mitspielt:
313+
Der ioBroker-MQTT-Adapter **trennt stille Verbindungen nach rund 30 Sekunden**
314+
von selbst. Die Bridge verliert die Verbindung also mitten in der Wartezeit,
315+
verbindet danach neu — und das frische Abonnement armiert das Fenster wieder.
316+
317+
Womit die Aussage im Bericht, der Keepalive rette hier nicht, schlicht falsch
318+
war. Der Befund selbst ist trotzdem echt: Mit einer auf 10 Sekunden verkürzten
319+
NTP-Frist endet der Start unterhalb der Trennschwelle, die Verbindung bleibt
320+
stehen, das Fenster ist wirklich abgelaufen — und dann läuft der Schwall
321+
vollständig durch. Fünfzehn ausgeführte Set-Kommandos im Log, die beiden
322+
Kurvenpunkte mittendrin. Dieselbe Lage mit 3.20.0: null ausgeführt, alle
323+
verworfen.
324+
325+
Der Fix bleibt damit richtig, aber seine Begründung ist eine andere geworden.
326+
Er schützt nicht vor etwas, das hier ständig passiert — er macht die Firmware
327+
unabhängig davon, ob ein Broker stille Verbindungen aufräumt. Ein
328+
ioBroker-Update, ein anderer Broker, ein Zeitserver, der schon nach zwölf
329+
Sekunden aufgibt: Jedes davon nimmt diese unfreiwillige Rettung wieder weg.
330+
331+
Nachgestellt wurde das alles auf einem **Backup-Board als Leihgabe** — eigener
332+
MQTT-Präfix, keine Wärmepumpe dran, und der Hydraulik-Schalter auf einen
333+
Platzhalter umgebogen, damit ein Testlauf nicht die echte Hydraulik des Hauses
334+
umlegt. Vier Prüfpunkte, mehrere Kaltstarts von Hand, danach das Board wieder
335+
in den Auslieferungszustand. Zwei Fehler in der neuen Firmware sind dabei noch
336+
aufgefallen und behoben worden, darunter Logzeilen, die vor dem Setzen der
337+
Zeitzone entstehen und deshalb zwei Stunden zurücksprangen — im Ringpuffer, der
338+
für die Nachschau nach einem Störfall gebaut ist, ausgerechnet.
339+
340+
#### Was es gekostet hat
341+
342+
RAM 57 528 → 61 608 Byte, davon 4 096 der Ringpuffer; der Rest hebt sich mit
343+
dem Wegfall der Zeitbibliothek auf. Flash 1 211 105 → 1 213 049 Byte. Die
344+
Abnahme auf beiden Stufen war zeilengleich bis auf laufende Messwerte, kein
345+
Sollwert hat sich bewegt.
346+
347+
Und die ersten Zahlen aus dem Dauerbetrieb, seit es sie zu sehen gibt: freier
348+
Heap-Tiefpunkt 188 916 (Stufe 1) und 220 676 Byte (Stufe 2), größter Block
349+
172 020 bzw. 176 116, Rest-Stack 4 184 bzw. 4 036 von 8 192. Der Unterschied
350+
zwischen den Stufen ist der volle Dekodierpfad — das Testboard ohne Wärmepumpe
351+
lag rund 25 KB höher. Genau deshalb arbeitet der Wächter auf der ioBroker-Seite
352+
mit Grenzen, die sich pro Gerät selbst eichen, statt mit festen Zahlen aus
353+
einer Labormessung.
354+
205355
### Der Notbetrieb nimmt den Heizstab zurück (3.18.0)
206356
207357
Seit dem 2026-08-30 benutzt die Kaskadensteuerung SET39 `ForceHeater` im

0 commit comments

Comments
 (0)