Drei Stromzähler im Haus sollen per Tasmota-Lesekopf ihren Stand in Home Assistant melden. Zwei tun das. Der dritte liefert mit dem dokumentierten Script exakt 0 — und stürzt mit dem Script, das auf dem Nachbargerät seit Wochen stabil läuft, reproduzierbar ab. Kein Log, keine Fehlermeldung, nur ein Gerät, das in einer Bootloop hängt.
TL;DR:
- Der Zähler sendet SML-Binary, nicht OBIS-ASCII. Erkennungsmerkmal im Rohstrom:
1b 1b 1b 1b … 1b 1b 1b 1aplus CRC, dazwischen4c 4f 47(„LOG"). - Das
@schließt das Match-Pattern ab und ist nicht optional. Ein Komma vor dem@wird Teil des Hex-Patterns → kein Match → Wert0. Ein fehlendes@lässt den Parser über den Puffer laufen → OOB →Exception→ Bootloop. - Der Web-OTA (
POST /u2) meldet bei einer 1-MB-ESP8266 „Success", wendet das Image aber nicht an — das übernimmt erstebootbeim Reboot, und der kann das Image verwerfen.
Das Problem
Setup: Ein Tasmota-Gerät auf einem ESP8266 mit 1 MB Flash liest einen Logarex-Zähler (LK13BE) über die optische Schnittstelle aus. Auf ha-leia landen die Werte per MQTT in Home Assistant. Das Topic und die Entities existieren bereits, das Gerät ist im Netz erreichbar — nur die Werte bleiben auf null.
Der naheliegende erste Schritt: das Script vom funktionierenden Nachbarzähler herüberkopieren. Der Nachbar nutzt einen Handshake-Block und ein Pattern der Form (1. Ergebnis: Das Gerät kommt hoch, bleibt ein paar Sekunden erreichbar, und crasht dann mit einer Exception — dreimal in Folge, auch wenn das Script nur einen einzigen Wert definiert.
17:04:12 SRC: SML
17:04:12 Exception
17:04:12 ...
17:04:12 Restart
17:04:14 WIF: Connecting to AP1 deathstar.lan ...
Zwischendurch ein zweiter Befund: Das Gerät läuft plötzlich auf einer Stock-Firmware (15.6.0), nicht mehr auf dem Custom-Build. /s10 (der Script-Editor) antwortet mit 404, Script ist „Unknown" — die Script- und SML-Unterstützung ist komplett weg. Der OtaUrl-Eintrag zeigte auf ota.tasmota.com — der Verdacht liegt nahe, dass ein Auto-Update den Custom-Build überschrieben hat. Abschließend belegt ist das nicht — es blieb bei der Hypothese.
Die Diagnose
Der Weg zur Wahrheit ist nicht das Script, sondern der Rohdatenstrom auf der Leitung. Ein Blick auf die empfangenen Bytes zeigt sofort, warum ASCII-Patterns hier scheitern müssen:
1b 1b 1b 1b 01 01 01 01 76 05 ... 4c 4f 47 ... 1b 1b 1b 1a <CRC>
Das ist SML-Binary (Smart Message Language): eine Rahmenstruktur mit 1b-Startsequenz, 1b 1b 1b 1a als Ende und einer angehängten CRC. Das 4c 4f 47 mitten im Frame ist der Klartext „LOG". Der Nachbarzähler dagegen sendet echte OBIS-ASCII-Zeilen wie 1.8.0(0012934.985*kWh), die ein OBIS-Pattern (Typ o) parst. Zwei Geräte, zwei völlig verschiedene Protokolle — das Herüberkopieren des Scripts war von Anfang an der falsche Reflex.
Die Pattern-Forensik
Der zweite Teil der Diagnose ist die Script-Syntax selbst. Für SML muss das Pattern die rohen OBIS-Bytes als Hex beschreiben, und das @ schließt den Match ab. Vergleicht man die Varianten, ergibt sich ein sauberes Bild:
Bad (Komma vor dem @):
1,77070100010800ff,@1,Gesamtbezug,kWh,Total_in,3
Das Komma direkt vor dem @ wird als Teil des Hex-Patterns gelesen. Der Parser sucht dann nach einem Byte, das nie im Frame vorkommt → kein Match → Wert 0.
Bad (kein @ am Ende):
1,77070100010800ff,Gesamtbezug,kWh,Total_in,3
Ohne das abschließende @ hat der Pattern keine Endmarke. Der Parser läuft über den Puffer hinaus (OOB) und wirft eine Exception — genau die Bootloop von oben.
Good:
1,77070100010800ff@1000,Gesamtbezug,kWh,Total_in,3
Hex-Pattern, direkt gefolgt vom @ und der Skala 1000. Der Rohwert des Zählers ist Wh, die Skala rechnet ihn auf kWh herunter.
Die OTA-Wand
Bevor das Script überhaupt ankommen konnte, musste erst die Firmware wieder stimmen. Und hier kam die dritte Falle. Ein manueller Upload über das Web-UI (POST /u2, multipart) gegen die Stock-15.6.0:
Uploading ...
Success
„Success" — und trotzdem nach dem Reboot weiter 15.6.0, /s10 bleibt 404. Der Upload schreibt die komprimierte Datei nur in den freien Slot und setzt den eboot-Auftrag; angewendet wird er erst beim Reboot, und dort wurde das Image offenbar verworfen. Die Zahlen erklären, warum es eng ist:
| Image | raw | gzip |
|---|---|---|
Full-Build (tasmota.bin) | 712 KB | 513 KB |
Minimal (tasmota-minimal.bin) | 376 KB | 265 KB |
| offizielles Minimal | 373 KB | — |
freier Flash auf 15.6.0 | — | 352 KB |
Die 352 KB freier Flash sind schon für das Minimal-Raw zu klein — Stufe 1 scheitert mit „Not enough space". Und selbst nach einem Minimal-Flash blieben nur rund 627 KB frei: Der 712-KB-Full-Build wäre danach immer noch nicht OTA-bar. Jede Tasmota-Variante mit Web und MQTT liegt über 373 KB. Auf einem ESP8266 gibt es — anders als beim ESP32 — keine zweite Partition und kein Safeboot als Ausweg.
Die Ursache
Das Perfide: Das @ ist kein Skalen-Trenner, sondern das Pattern-Ende. Die alte Doku-Notiz („(1, @ optional") war schlicht invertiert. Beide Fehlerbilder, die man tagelang für zwei verschiedene Probleme hält, kommen aus derselben Zeile:
- Komma vor
@→ das Pattern matcht nie → stille0. @fehlt ganz → kein Endanker → Out-of-Bounds →Exception→ Bootloop.
Der erste Verdacht — ein Bug im Custom-14.5.0-Build — war falsch. Dieselbe (1-Variante (ohne @) crasht auf jeder Firmware; das Gerät verhält sich exakt wie der Custom-Build, den man ihm vorher nachgesagt hatte. Und die zweite Illusion ist das Wort „Success" beim Web-OTA: Es bedeutet nur Update.end(true) — die Datei ist geschrieben, der eboot-Befehl gesetzt. Ob das neue Image beim Reboot tatsächlich angewendet wird, steht auf einem ganz anderen Blatt. Nur auf der seriellen Konsole (UART 74880 8N1) sieht man den eigentlichen Grund im @cp:-Code — :0 bedeutet null geschriebene Bytes, :5 ein gzip-Header, :6 ein inflate-Fehler.
Die Lösung
Firmware neu bauen (Tasmota 15.1.0, USE_SCRIPT + USE_SML_M, 1 MB-safe), dabei die Toolchain pinnen — der Build scheitert sonst an einem scons-Mismatch:
pip install "platformio==6.1.19" # NICHT 6.2.0 -> scons 4.8.1-Mismatch (SCons.Tool.FortranCommon)
Im user_config_override.h:
#undef USE_RULES
#define USE_SCRIPT
#ifndef FIRMWARE_MINIMAL
#define USE_SML_M
#endif
Dann nicht per Web-Upload, sondern per URL-OTA mit Auto-Minimal. Tasmota 15.6 staged die volle .gz und schiebt bei Platzmangel selbst ein Minimal-Image dazwischen — vorausgesetzt, es liegt mit gleichem Basisnamen plus Suffix -minimal neben der Zieldatei:
/OtaUrl http://192.168.66.10:9999/tasmota.bin.gz
/Upgrade 1
Im Verzeichnis auf ha-leia müssen also beide Dateien liegen:
python3 -m http.server 9999
# /tmp/fw/tasmota.bin.gz (Full, 513 KB)
# /tmp/fw/tasmota-minimal.bin.gz (Auto-Zwischenschritt, 265 KB)
Wichtig: Upgrade 1, nicht Upgrade 15.1.0 — letzteres fällt intern an einer NewerVersion-Prüfung durch. Der HTTP-Server muss vom IoT-VLAN aus erreichbar sein (HTTP, kein HTTPS). Danach OtaUrl wieder auf leer setzen, sonst holt sich das Gerät beim nächsten Update erneut eine Stock-Firmware.
Das finale, verifizierte Script (Typ s, passiv — kein Polling, der Zähler sendet von selbst):
>D
>B
=>sensor53 r
>M 1
+1,3,s,0,9600,AS1440
1,77070100010800ff@1000,Gesamtbezug,kWh,Total_in,3
1,77070100020800ff@1000,Gesamtlieferung,kWh,Total_out,3
#
Verifiziert: Total_in 502.722 und Total_out 2050.09 kWh, die Einspeisung zählt live mit. Beide Entities sind in Home Assistant da.
Good (nach dem Fix):
17:41:02 SRC: SML
17:41:02 Total_in: 502.722 kWh
17:41:02 Total_out: 2050.090 kWh
17:41:03 MQT: tele/tasmota_A1B2C3/SENSOR = {"SML":{...}}
Die Deploy-Falle beim Script
Ein Script-Text wird erst beim Reboot frisch in den RAM geladen. Script 1 allein lädt nichts nach — danach meldet der Sensor53-Treiber no meter section found!. Der belastbare Ablauf ist:
/SaveData 0
POST /ta (t1=<script>, c1=on, save=1)
/SaveData 1
/Restart 1
Achtung: Script 1 setzt das Script auf ON, man kann den Zustand damit nicht gefahrlos „lesen". Nach einem versehentlichen Enable sofort wieder Backlog Script 0;SaveData 1 absetzen. Und ein erneutes POST /ta bei bereits aktivem Script crasht das Gerät.
Zwei begleitende Details: Der Zähler sendet nur 1.8.0 (Bezug) und 2.8.0 (Einspeisung), keine Leistungs-OBIS — power_in/power_out existieren also nicht, Momentanleistung muss man in HA per derivative aus den kWh-Ständen rechnen. Und weil die Telemetrie sonst nur alle fünf Minuten kommt, gehört TelePeriod von 300 auf 30 s herunter, sonst bleibt das Derivat träge bei null.
Lessons Learned
- Erst den Rohdatenstrom ansehen, dann das Pattern schreiben.
1b 1b 1b … 1aist SML-Binary — ein OBIS-ASCII-Pattern kann darauf nie passen. Das Kopieren eines funktionierenden Scripts von einem anderen Zähler ist kein Fix, sondern eine Hypothese. - Das
@schließt das Match ab und ist Pflicht. Ein Komma direkt davor macht sich zum Teil des Hex-Patterns (stille Nullen); ein fehlendes@lässt den Parser über den Puffer laufen (Exception/Bootloop). Beide Symptome haben dieselbe Ursache. - „Success" ist kein Beweis, dass ein Update greift. Der Web-OTA schreibt nur und setzt den
eboot-Auftrag; die Anwendung passiert beim Reboot und kann stillschweigend verworfen werden. Verlässlich war nur der URL-OTA-Weg mit automatischem Minimal-Zwischenschritt. - Auf 1 MB ESP8266 rechnet man vorher. Full-Build 712 KB, Minimal 376 KB, frei nur 352 KB — ohne Rechnung läuft man in „Not enough space" oder in einen 627-KB-Deckel, an dem jedes Web-plus-MQTT-Image scheitert. ESP32 hat Safeboot-Partitionen, ESP8266 nicht.
OtaUrlneutral halten. Zeigt das Feld auf die Stock-Quelle, kann der nächste Update-Zyklus eine Stock-Firmware nachziehen. Nach jedem Custom-Flash leeren — der genaue Auslöser des Firmware-Wechsels blieb hier unbelegt.- MQTT-Broker und HA lehnen Benutzernamen mit Umlaut ab. Eine ASCII-Variante verbindet zuverlässig, die Umlaut-Variante nie — das gilt still und wird gern für ein Netz-/Passwortproblem gehalten.
- Deploy heißt Reboot.
SaveData 0 → POST /ta → SaveData 1 → Restart 1, in dieser Reihenfolge.Script 1lädt keinen neuen Text in den RAM.
Checkliste
- Vor dem Pattern-Schreiben die Rohbytes prüfen:
1b 1b 1b 1b+1b 1b 1b 1a→ SML-Binary (Typs), ASCII1.8.0(...)→ OBIS (Typo) - Im Pattern das
@direkt hinter das Hex-Pattern setzen — kein Komma davor, kein@weglassen - Bei SML die Skala mitgeben (
@1000, Rohwert Wh → kWh) - Script-Deploy immer mit Reboot abschließen, nicht nur
Script 1 - Vor jedem OTA die Größenrechnung machen (full vs. free vs. Minimal) — bei 1 MB ESP8266 zuerst Minimal, dann Ziel
- URL-OTA statt Web-Upload nutzen und beide Dateien ablegen:
<name>.bin.gzund<name>-minimal.bin.gz -
Upgrade 1statt einer Versionsnummer verwenden - Nach erfolgreichem Flash
OtaUrlauf leer setzen (verhindert ungewolltes Auto-Update zurück auf Stock) -
TelePeriodauf 30 s stellen, sonst bleibt die perderivativeberechnete Momentanleistung bei null - Für Momentanleistung die
derivative-Sensoren aufround: 3(bei kW) setzen —round: 0rundet 0,12 kW auf 0 W