Skip to content

Commit a9b2582

Browse files
Merge doku/deploy-3.9.0: v3.9.0 auf beiden Stufen ausgerollt
2 parents 0051049 + 89d0b75 commit a9b2582

3 files changed

Lines changed: 121 additions & 2 deletions

File tree

‎Massnahmenplan-Codedurchsicht-2026-08-18.md‎

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ Vorgeschlagene Bündelung:
2626
| Version | Inhalt | Art | Stand |
2727
| --- | --- | --- | --- |
2828
| 3.8.1 | Maßnahme 1 + K1 (Zugangswege, kein Protokoll-/Topic-Einfluss) | Fix | **erledigt 2026-08-18** — umgesetzt, am Gerät abgenommen, auf beiden Stufen ausgerollt |
29-
| 3.8.2 | Maßnahme 2 (retained `Online`) + K3 (Publish-Wiederholung) | Fix | **erledigt 2026-08-19** — gebaut und getestet, Abnahme am Gerät steht aus |
30-
| 3.9.0 | Maßnahme 3 (Weg B + `nullptr`-Abschluss) + Testausbau | Umbau | **erledigt 2026-08-19** — gebaut und getestet, Abnahme am Gerät steht aus |
29+
| 3.8.2 | Maßnahme 2 (retained `Online`) + K3 (Publish-Wiederholung) | Fix | **erledigt 2026-08-19** — in 3.9.0 enthalten, nicht einzeln ausgerollt |
30+
| 3.9.0 | Maßnahme 3 (Weg B + `nullptr`-Abschluss) + Testausbau | Umbau | **erledigt 2026-08-19** — am Gerät abgenommen, auf beiden Stufen ausgerollt |
3131
| — | K2 (Beobachtungsauftrag), K4 (bekannte Grenze) | Entscheid | **entschieden 2026-08-19** — keine Codeänderung |
3232
| — | CI-Trigger nur noch `main` | Aufräumen | **erledigt 2026-08-19** |
3333

@@ -311,6 +311,45 @@ Repository sind Actions-Minuten frei; der Gewinn ist weniger Lauf-Rauschen.
311311

312312
---
313313

314+
## Rollout 3.9.0 — erledigt (2026-08-19)
315+
316+
**Direkt auf 3.9.0, ohne 3.8.2 als Zwischenschritt.** Ein eigener 3.8.2-Rollout
317+
hätte keinen zusätzlichen Nachweis gebracht: Das Retain-Verhalten zeigt der
318+
ioBroker-Adapter nicht, und die Publish-Wiederholung wird erst bei einer
319+
MQTT-Unterbrechung sichtbar. Die `/tablerefresh`-Abnahme deckt genau das ab,
320+
was 3.9.0 an der Anzeige ändert. 3.8.2 ist trotzdem als Binärstand abgelegt,
321+
damit ein Rückfall um eine Stufe möglich bleibt.
322+
323+
| Stufe | Gerät | OTA | Ergebnis |
324+
| --- | --- | --- | --- |
325+
| 1 | 192.168.2.120 | 09:28 | 90 Zeilen **identisch**, keine einzige Abweichung |
326+
| 2 | 192.168.2.122 | 09:29 | 90 Zeilen, **eine** Abweichung: `Eva_Outlet_Temp` 21 → 22 °C (laufender Messwert) |
327+
328+
Beide Geräte meldeten sich rund sieben Sekunden nach dem Upload mit 3.9.0
329+
zurück, `info/LWT` stand unmittelbar danach wieder auf `Online`, die
330+
State-Topics liefen im 5-Sekunden-Takt weiter. Stufe 2 lief während des OTA im
331+
Warmwasserbetrieb (`Heatpump_State` On, `Operating_Mode_State` DHW) und lief
332+
danach unverändert weiter.
333+
334+
Die 22 von Maßnahme 3 betroffenen Zeilen wurden zusätzlich einzeln angesehen —
335+
alle zeigen ihren erwarteten Klartext (`Off`, `Disabled`, `Blocked`, `Direct`,
336+
`DHW` …), keine leere Zelle und kein `unknown`. Letzteres ist der Normalfall:
337+
`unknown` erscheint nur bei einem Rohwert, den die Wärmepumpe bisher nie
338+
geliefert hat — genau dem Wert, der vorher zum Absturz geführt hätte.
339+
340+
Stabilitätskontrolle rund vier Minuten nach dem Flash: beide Geräte weiter auf
341+
3.9.0, `info/LWT` unverändert seit dem Neustart (kein zwischenzeitlicher
342+
Reconnect, also kein Watchdog-Zyklus), und das 5-Minuten-Vollupdate hat auf
343+
Stufe 1 um 09:33:20 alle State-Topics erneut geschrieben. Dass die
344+
Zeitstempel dazwischen stehen bleiben, ist kein Befund, sondern das erwartete
345+
Verhalten: Publiziert wird nur bei Wertänderung oder mit dem Vollupdate.
346+
347+
Für die Abnahme entstand [`test/tablesnap.py`](test/tablesnap.py) (holt
348+
`/tablerefresh` und gibt `TOP<n>|Name|Wert|Klartext` aus), damit der Vergleich
349+
ein `diff` ist statt Zeilenvergleich per Auge.
350+
351+
---
352+
314353
## Ausdrücklich geprüft und in Ordnung
315354

316355
Damit die nächste Durchsicht nicht von vorn anfängt — diese Punkte wurden

‎test/README.md‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ bewusst unveraendert - dort warnt die Firmware nur.
3838
| `decode_vergleich.py` | Dekodierpfad zweier Codestaende gegeneinander laufen lassen, auf dem Mac | nein |
3939
| `frame_diff.py` | Rohtelegramme eines Mitschnitts ueber alle 203 Bytes vergleichen, angereichert aus `ProtocolByteDecrypt.md` | nein |
4040
| `retained_loeschen.py` | Retained Messages entfallener state-Topics vom Broker raeumen (Anzeige, `--loeschen` fuer echt) | Broker |
41+
| `tablesnap.py` | Momentaufnahme der Topic-Tabelle ueber `/tablerefresh`, zeilenweise diffbar - fuer die Abnahme nach dem Flashen | Produktivgeraet (nur lesend) |
4142
| `heisha_probe.py` | gemeinsame Helfer (Telnet, Hexlog-Parser) | - |
4243
| `mqtt_pub.py` | minimaler MQTT-Publisher ohne Abhaengigkeiten | - |
4344
| `stubs/` | Arduino-Ersatzheader, gemeinsam genutzt von `byte110_test.cpp` und `decode_vergleich.py` | - |
@@ -197,6 +198,33 @@ einbindenden Datei sucht: aus `src/` heraus gewinnt der echte Header und zieht
197198
LittleFS, WiFi und den Rest der Arduino-Welt nach. `decode_hosttest.sh` kopiert
198199
die Uebersetzungseinheit deshalb neben die Ersatzheader aus `stubs/`.
199200

201+
## Abnahme nach dem Flashen (tablesnap.py, 3.9.0)
202+
203+
Die Projektkonvention nach jedem OTA: Baseline der Topic-Tabelle vor dem Flash
204+
ziehen, nach dem Flash noch einmal, und beide zeilenweise halten. Bis 3.9.0 ging
205+
das per Augenschein ueber die Weboberflaeche - bei 90 Zeilen je Stufe ist das
206+
muehsam und uebersieht leicht etwas.
207+
208+
`tablesnap.py` holt `/tablerefresh` und gibt je Zeile `TOP<n>|Name|Wert|Klartext`
209+
aus. Damit ist die Abnahme ein `diff`:
210+
211+
```
212+
./tablesnap.py 192.168.2.120 > vorher.txt
213+
pio run -e heishamon_esp32_h1_ota -t upload
214+
./tablesnap.py 192.168.2.120 > nachher.txt
215+
diff vorher.txt nachher.txt
216+
```
217+
218+
Was uebrig bleibt, ist entweder ein laufender Messwert oder ein Befund. Reines
219+
GET, kein Eingriff ins Geraet. Beim Rollout von 3.9.0 am 2026-08-19 blieb Stufe 1
220+
vollstaendig ohne Abweichung, Stufe 2 mit einer einzigen (`Eva_Outlet_Temp`
221+
21 -> 22 Grad).
222+
223+
Zwei Hinweise: Nach dem Neustart braucht das Geraet ein paar Abfragezyklen, bis
224+
die Tabelle wieder gefuellt ist - erst schnappen, wenn keine Zeile mehr leer
225+
oder `unused` ist. Und die Baseline gehoert unmittelbar vor den Flash gezogen,
226+
sonst wandern in der Zwischenzeit Messwerte und der Diff wird unuebersichtlich.
227+
200228
## Verhaeltnis zu `pio test`
201229

202230
Das sind eigenstaendige Diagnosewerkzeuge, keine Unity-Testsuites - `pio test`

‎test/tablesnap.py‎

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
#!/usr/bin/env python3
2+
"""Momentaufnahme der Topic-Tabelle eines HeishaMon-Geraets ueber /tablerefresh.
3+
4+
Zweck: die Abnahme nach dem Flashen zu einem Zeilenvergleich machen. Vor dem OTA
5+
einmal laufen lassen, nach dem OTA ein zweites Mal, dann 'diff' der beiden
6+
Dateien - was uebrig bleibt, sind entweder laufende Messwerte oder ein Befund.
7+
8+
./tablesnap.py 192.168.2.120 > vorher.txt
9+
./tablesnap.py 192.168.2.120 > nachher.txt
10+
diff vorher.txt nachher.txt
11+
12+
Ausgabe je Zeile: TOP<n>|<Name>|<Wert>|<Klartext bzw. Einheit>
13+
Nur Standardbibliothek, kein Geraeteeingriff (reines GET).
14+
"""
15+
import html
16+
import re
17+
import sys
18+
import urllib.request
19+
20+
ZELLEN = re.compile(r"<td>(.*?)</td>", re.S)
21+
22+
23+
def schnappschuss(ip: str, timeout: float = 10.0) -> list[str]:
24+
with urllib.request.urlopen(f"http://{ip}/tablerefresh", timeout=timeout) as antwort:
25+
seite = antwort.read().decode("utf-8", "replace")
26+
zeilen = []
27+
for roh in seite.split("<tr>"):
28+
felder = [html.unescape(z).strip() for z in ZELLEN.findall(roh)]
29+
if len(felder) >= 4:
30+
zeilen.append("|".join(felder[:4]))
31+
return zeilen
32+
33+
34+
def main() -> int:
35+
if len(sys.argv) != 2:
36+
print(__doc__, file=sys.stderr)
37+
return 2
38+
try:
39+
zeilen = schnappschuss(sys.argv[1])
40+
except OSError as fehler:
41+
print(f"FEHLER: {sys.argv[1]} nicht erreichbar ({fehler})", file=sys.stderr)
42+
return 1
43+
if not zeilen:
44+
print("FEHLER: keine Tabellenzeilen gefunden", file=sys.stderr)
45+
return 1
46+
print("\n".join(zeilen))
47+
print(f"# {len(zeilen)} Zeilen von {sys.argv[1]}", file=sys.stderr)
48+
return 0
49+
50+
51+
if __name__ == "__main__":
52+
sys.exit(main())

0 commit comments

Comments
 (0)