sphings-dev.de

Marstek Offline Endpoint 2.0: Weboberfläche, entschlüsselte Messwerte und MQTT für Home Assistant

Bis Version 1 beantwortete der Marstek Offline Endpoint den Telemetrie-Upload der Batterie, damit die Firmware ihren Netzwerkchip nicht mehr alle halbe Stunde zurücksetzt, und schrieb Logs. Mit Version 2.0 hat er eine Weboberfläche, und der Upload wird nicht mehr nur mitgeschrieben, sondern Wert für Wert entschlüsselt. Die rund siebzig Felder, die ein Venus bei jedem Upload an die Marstek-Cloud schickt, stehen damit lokal zur Verfügung: mit Namen, Einheiten, Kurven und auf Wunsch als Entitäten in Home Assistant.

Übersichtsseite der Endpoint-Weboberfläche mit den erkannten Batterien und der Verfügbarkeit des Endpoints, links hell, rechts dunkel
Die Übersicht in hellem und dunklem Modus. Beispieldaten, die Oberfläche läuft hier auf Englisch; sie gibt es auch auf Deutsch.

Was „alle Daten“ hier heißt

Der Upload ist ein einziger sprintf in der Control-Firmware: Jedes Argument ist eine Kopie einer benannten Live-Variable des Geräts. Daraus ergibt sich für fast jeden Schlüssel, was er bedeutet. Nachgeprüft wurde das an einem Venus D mit Control-Firmware 151 gegen die Modbus-Werte in Home Assistant, außerdem im Code von zehn Control-Firmwares (VNSA, VNSD, VNSE3). Wo der Firmware-Code die Bedeutung liefert, aber keine Einheit, bleibt die Einheit leer und der Rohwert wird gezeigt. Ein Schlüssel, hd, ist noch ungeklärt.

Eine Grenze gehört dazu: Das gilt für den HTTPS-Upload. Die MQTT-Sitzung des Geräts zu AWS IoT bedient der Endpoint nicht und kann es nicht, weil das Gerät das Broker-Zertifikat gegen eine CA im Flash prüft und ein eigenes Client-Zertifikat vorlegt. Der MQTT-Broker darf deshalb weiterhin nicht umgeleitet werden.

Einrichtung und Anmeldung

Die Oberfläche steckt im selben Container und läuft auf Port 8443. Beim ersten Start steht im Container-Log ein einmaliger Einrichtungscode, nur dort, nie in der Oberfläche:

docker logs marstek-offline-endpoint 2>&1 | grep SETUP

Danach https://<Host>:8443/ öffnen, den Code eingeben und ein Passwort mit mindestens 8 Zeichen wählen. Das Zertifikat ist selbstsigniert, der Browser warnt einmal. Es gibt genau ein Konto. Wer das Passwort vergisst, löscht auth.json im Datenordner; beim nächsten Aufruf fragt die Seite wieder nach einem Einrichtungscode.

Die Anmeldung folgt den OWASP-Empfehlungen: scrypt für das Passwort, ein Sitzungs-Cookie mit HttpOnly, SameSite=Strict und Secure, ein CSRF-Token bei jeder Änderung, 30 Minuten Leerlauf bei höchstens 12 Stunden Sitzungsdauer und eine Sperre nach fünf Fehlversuchen je Adresse in 15 Minuten. Die Einstellungen zeigen die letzte Anmeldung und die Fehlversuche seitdem.

Geräte, Verlauf und Erreichbarkeit

Geräte werden erkannt, nicht eingerichtet: Die Upload-URL trägt die Geräte-ID, die Zeitanfrage die Firmware-Stände, der Upload-Inhalt die Seriennummer. Die Übersicht zeigt je Batterie Adresse, Firmware, letzten Upload, den üblichen Rhythmus, die Dauer des TLS-Handshakes, TLS-Fehler und ob der Ping beantwortet wird. Auf der Geräteseite lässt sich ein Gerät umbenennen, entfernen oder in ein anderes zusammenführen, etwa nach einem Platinentausch mit neuer ID.

Geräteseite einer Batterie mit Zeitleisten für Endpoint, Uploads, Zeitanfragen, Ping, TLS-Fehler und der Dauer des TLS-Handshakes über sieben Tage
Verlauf einer Batterie über sieben Tage: grün wie erwartet, orange weniger, rot nichts. Die gestrichelte Linie markiert einen Firmware-Wechsel.

Die Grafik zeigt von einer Stunde bis zu allem Gespeicherten, was angekommen ist und was erwartet wurde, dazu Ping-Aussetzer ab 2,5 s, TLS-Fehler und die Handshake-Dauer jeder Verbindung. Der Ping läuft im Container, ein ping je Gerät, und schreibt nur Wechsel; er lässt sich je Gerät oder mit PING=0 ganz abschalten.

Damit „die Batterie war weg“ von „mein Endpoint war weg“ unterscheidbar bleibt, schreibt der Endpoint sein eigenes Leben mit: einen Herzschlag alle 30 s, an dem er beim nächsten Start erkennt, ob der letzte Lauf sauber endete, und alle 5 s einen Ping an das Gateway des Netzes, in dem die Batterien hängen. Drei Fehlversuche in Folge gelten als „kein Netz“. Zeiten, in denen der Endpoint aus war oder kein Netz hatte, sind in der Grafik schraffiert, denn eine Lücke dort geht nicht aufs Gerät. Die Aufzeichnung beginnt, sobald die Funktion zum ersten Mal läuft.

Messwerte

Jeder Upload wird entschlüsselt, und die Werte, die sich geändert haben, landen in einer SQLite-Datei (measurements.db im Datenordner): eine Zeile je Wert, mit Zeit, Gerät und Rohtext. Ein gleich bleibender Wert fügt nichts hinzu, eine ruhige Batterie kostet also wenig. Listen wie die Zellspannungen werden zu einem Schlüssel je Position (vc[2], vc[3] …). Geräte-ID und Seriennummer werden nicht als Werte gespeichert.

Seite Messwerte mit dem neuesten Wert jedes Schlüssels in Karten wie Firmware, BMS und Solar/MPPT, links hell, rechts dunkel
Messwerte eines Venus D, nach Kategorien gruppiert. Unter jedem Namen steht der Schlüssel und woher die Bedeutung stammt.

Die Seite zeigt den neuesten Wert jedes Schlüssels je Gerät, gruppiert nach Firmware, Leistung, Energie, Batterie, BMS, Temperatur, Netz und Zähler, Solar/MPPT, Status, Fehler, Verbindung und Gerät. Ein Tipp auf den Namen öffnet die Kurve über 24 Stunden bis zu allem Gespeicherten, Firmware-Wechsel sind gestrichelt eingezeichnet. Eine eigene Karte führt den Firmware-Verlauf von Steuerung, BMS, Wechselrichter und MPPT.

Darunter sind Werte, die per Modbus gar nicht zu lesen sind: MPPT-Leistung und -Spannung, die vier PV-Eingänge, die CT-Leistung je Phase, Netzbezug und -einspeisung des Zählers, die Zellindizes oder die Fehlerworte der Packs. Das Kennzeichen an jedem Schlüssel sagt, woher die Bedeutung stammt: gegen Home Assistant geprüft oder aus dem Firmware-Code.

Namen, Einheiten und Skalierung ändern

Jeder Schlüssel hat einen Namen, eine Einheit und eine Skalierung, die sich mit „Ändern“ anpassen lassen. Sie gelten beim Lesen; die gespeicherten Rohwerte werden nie angefasst, eine spätere Korrektur macht also alle alten Werte auf einmal richtig. Der Filter „Nur bestätigte Schlüssel“ ist standardmäßig an und blendet alles aus, dessen Bedeutung noch nicht belegt ist.

Modell und Firmware

Ein anderes Modell oder eine andere Firmware kann andere Schlüssel senden oder einen Schlüssel anders meinen. Die Datenbank hält deshalb Gerätemodell und Control-Firmware bei jedem Wert fest. Das Modell stammt aus der Zeitanfrage (VNSD ist ein Venus D, VNSA ein Venus A, VNSE3 ein Venus E 3.0). Eine Bedeutung lässt sich für alles, für eine Firmware, für ein Modell oder für ein Modell mit einer Firmware festlegen; die genaueste Regel gewinnt. Beim Venus E 3.0 etwa enthält vc die Spannung von 16 Zellen des Packs 1 und nicht die Min/Max-Statistik von sechs Packs, dafür gibt es eine eigene Regel. Einige Schlüssel senden nur bestimmte Versionen: p_sta und p_pow erst ab Control-Firmware 151, pv, sk, mv, me, ma und fu nicht der VNSE3.

Für jedes Modell und jede Firmware außer dem geprüften Venus D mit 151 gelten die eingebauten Bedeutungen dem Namen nach weiter, aber als nicht geprüft, und die Seite sagt das. Wie man eine neue Control-Firmware gegen die Schlüssel prüft, steht im Repository (docs/NEW-FIRMWARE.de.md); zwei Skripte in tools/ vergleichen den Upload-Code mehrerer dekompilierter Versionen und die Einheiten gegen die Modbus-Registerkarte.

Aufbewahrung und Größe

  • Aufbewahrung wie bei den Logs (Standard 30 Tage); der neueste Wert jedes Schlüssels bleibt immer.
  • Abschalten unter „Einstellungen › Messwerte-Datenbank“; das Gespeicherte bleibt dann erhalten.
  • „Aus den Logs neu aufbauen“ füllt die Datenbank aus den Anfrage-Logs neu, eigene Namen bleiben. Beim ersten Start passiert das von selbst, die Datenbank beginnt also mit dem Verlauf, den du schon hast.
  • Rund 19 MB für 12 Tage bei drei Batterien, weil sich Leistung und Spannung bei fast jedem Upload ändern. Langsame Werte kosten fast nichts.
  • Die Datenbank braucht Node 22 (node:sqlite); das Container-Image bringt es mit.

Benachrichtigungen

Seite Benachrichtigungen mit Hauptschalter, Regeln und je einer Karte für Telegram, Pushover, Home Assistant und Webhook
Hauptschalter, Regeln und Kanäle. Solange der Hauptschalter aus ist, senden die Kanäle nichts.

Gesendet wird erst, wenn du es einschaltest und einen Kanal einträgst. Kanäle sind Telegram, Pushover, Home Assistant notify und ein Webhook (für ntfy, Gotify, Slack, Discord oder ein eigenes Skript). Die Regeln:

  • Eine Batterie hat das Dreifache ihres üblichen Abstands nicht hochgeladen, mindestens 15 Minuten – an.
  • Eine Batterie antwortet seit 60 Sekunden nicht auf Ping – an.
  • Der Endpoint ist nach einem unsauberen Stopp gestartet oder hat kein Netz – an.
  • Der Endpoint wurde normal neu gestartet – aus.
  • Eine Firmware-Version hat gewechselt – aus.
  • Eine neue Container- oder Web-Version ist erschienen – aus.
  • Der Vorfall ist vorbei – an.

Es kommt eine Nachricht je Vorfall und eine, wenn er vorbei ist. Ein Neustart des Endpoints beginnt die Upload-Zählung neu und wird nie als fehlender Upload gemeldet. Nicht zustellbare Nachrichten werden jede Minute bis zu 24 Stunden lang erneut versucht; höchstens 30 pro Stunde verlassen den Container. Eine Nachricht nennt das Gerät mit dem Namen, den du vergeben hast, und enthält nie eine Geräte-ID, eine Adresse oder einen Messwert. Tokens liegen in secrets.json im Datenordner, werden nie wieder angezeigt und sind nie Teil eines Support-Pakets. Die Telegram-Einrichtung ist in docs/TELEGRAM.de.md Schritt für Schritt beschrieben.

MQTT für Home Assistant

MQTT-Seite mit Broker-Verbindung, Topics, der Auswahl der Messwert-Kategorien und der Liste dessen, was gesendet wird
Broker, Topics und Kategorien. Rechts steht, was immer gesendet wird und was nur auf Wunsch.

Der Endpoint kann Werte an deinen MQTT-Broker übertragen; Home Assistant findet sie per MQTT-Discovery (Präfix homeassistant), ohne YAML. Der MQTT-Client steckt im Endpoint (MQTT 3.1.1, einfach oder mit TLS, Benutzer und Passwort). Immer gesendet werden Gesundheitswerte: je Gerät letzter Upload, Upload-Abstand, erreichbar, Upload überfällig, TLS-Handshake-Dauer, TLS-Fehler und die vier Firmware-Versionen; für den Endpoint Version, läuft seit, Netz, Verfügbarkeit und Ausfälle über sieben Tage.

Die Messwerte sind eine Option, standardmäßig aus. Dann entsteht eine Entität je Schlüssel, benannt wie auf der Seite Messwerte; eine Umbenennung dort kommt auch in Home Assistant an. Zur Wahl stehen:

  • nur die Gesundheitswerte,
  • Messwerte ergänzen, die Home Assistant per Modbus nicht bekommt (empfohlen: lässt Schlüssel weg, die sich Modbus-Werten zuordnen lassen),
  • alle Messwerte ergänzen, jeweils nach Kategorien schaltbar,
  • nur die Werte, die du auf der Seite Messwerte per Kästchen auswählst.

Das Dropdown nennt in Klammern, wie viele Entitäten je Gerät eine Wahl erzeugt. Zahlen tragen Einheit, Geräteklasse und Zustandsklasse, Hex-Felder kommen als 0x..-Text. Adresse und Geräteuhr werden nie übertragen, die Topics tragen einen kurzen Hash statt der Geräte-ID, und ein Verfügbarkeits-Topic mit Last Will setzt die Entitäten auf „nicht verfügbar“, wenn der Endpoint weg ist. Schaltest du eine Kategorie oder die Option ab, verschwinden die Entitäten wieder aus Home Assistant. Es wird nur gemeldet: Nichts kommt aus Home Assistant, nichts steuert eine Batterie, und Ladezustand und Leistung der Batterie selbst werden nicht gesendet, weil Home Assistant sie schon per Modbus liest, etwa über die Marstek Modbus Suite.

Support-Paket

Für eine Fehlermeldung erzeugt die Seite ein .tar.gz für alle Geräte oder eines, von 24 Stunden bis zu allem Gespeicherten: Version und Einstellungen, die letzten Konsolenzeilen, je Gerät eine Tabelle mit Uploads, Zeitanfragen, Ping-Aussetzern und TLS-Ereignissen sowie die Ausfälle des Endpoints. Optional liegen die Messwerte als measurements.csv bei. Standardmäßig ist das Paket unkenntlich gemacht: Geräte-IDs, Seriennummern, IP-Adressen und Namen werden zu device-1, mac-1, ip-1, Anfrage-Inhalte fehlen. Tokens und E-Mail-Adressen werden immer entfernt, auch bei „Rohdaten einschließen“. Die Datei sollte man trotzdem durchsehen, bevor man sie anhängt.

Zwei Prozesse und Web-Updates

Der Container führt zwei Prozesse aus. Der Hauptprozess beantwortet die Batterien auf den Ports 443 und 80, schreibt die Logs und führt das Protokoll über das Leben des Endpoints. Der Web-Prozess betreibt Oberfläche, Messwerte-Datenbank, Benachrichtigungen, MQTT-Client und Ping. Ein Neustart des Web-Prozesses dauert ein bis zwei Sekunden, die Anmeldung bleibt bestehen, und der Teil, der die Batterien beantwortet, merkt nichts davon.

Dadurch lässt sich die Weboberfläche für sich aktualisieren. Die Update-Karte auf der Seite Einstellungen unterscheidet Container-Releases (vX.Y.Z, ein neues Image) von Web-Releases (web-vX.Y.Z). Ein Web-Update installiert sich nur auf Knopfdruck: Der Endpoint lädt Archiv, Manifest und Signatur von github.com, prüft die Ed25519-Signatur gegen den im Image eingebauten öffentlichen Schlüssel und die SHA-256 des Archivs, liest das Archiv nur mit einem Leser, der ausschließlich die aufgelisteten Dateinamen akzeptiert, und startet den Web-Prozess neu. Das Manifest nennt Mindestversionen für Container und die Verständigung zwischen den Prozessen; passt ein Release nicht, lehnt die Seite es ab. Startet die neue Version dreimal binnen einer Minute nicht, läuft die Seite wieder mit den Dateien aus dem Image.

Telegram, Pushover, Home Assistant notify, Webhook und MQTT wurden gegen echte Dienste getestet. Das Installieren eines Web-Updates aus einem echten Release konnte bisher nur gegen Mock-Server geprüft werden.

Einstellungen

Einstellungsseite mit Updates, Messwerte-Datenbank, Aufbewahrung, Support-Paket, Speicherbelegung und Konfiguration
Einstellungen mit Update-Prüfung, Aufbewahrung, Support-Paket und Speicherbelegung.

Die Update-Prüfung fragt einmal täglich die öffentlichen Versionshinweise bei api.github.com ab; nur die Anfrage verlässt das Netz, über deine Geräte wird nichts gesendet. Wer keine ausgehenden Anfragen will, schaltet sie ab. Dazu kommen die Aufbewahrung der Logs, die Speicherbelegung des Datenordners nach Art mit freiem Platz des Datenträgers, die Passwortverwaltung und eine Übersicht, wo was liegt und welche Einstellungen aus der Umgebung kommen. Die Oberfläche folgt dem Bildschirm (Burger-Menü, kompakte Grafik zum Antippen), kennt Hell, Dunkel und System und läuft auf Deutsch und Englisch.

Hinter einem Reverse Proxy

Alles zum Listener wird mit Umgebungsvariablen gesetzt, mit Absicht: Ein falscher Wert in der Oberfläche könnte aussperren. WEB_PORT (Standard 8443, 0 schaltet die Oberfläche ab), WEB_TLS, BASE_PATH, TRUST_PROXY für die Adressen, deren X-Forwarded-*-Header geglaubt werden, sowie PING und ENDPOINT_GATEWAY. Die Oberfläche listet Geräte und Adressen und gehört nicht ins Internet. Belegt ein Proxy auf dem Host schon Port 443, braucht der Endpoint eine eigene Adresse, meist über ein macvlan-Netz; Docker lässt den Host dann nicht an den Container, deshalb bekommt er ein zweites Netz, das der Proxy teilt. Compose-Beispiel und Traefik-Route stehen in docs/WEB-INTERFACE.de.md.

Korrektur in 2.0.1

Hinter einem Reverse Proxy, der TLS beendet, bleibt die Verbindung zum Endpoint offen und wird für mehrere Anfragen genutzt. Auf so einer Verbindung ließ die zweite Anfrage den Prozess abstürzen. Antworten gehen jetzt über den eigenen Socket der Anfrage, und der 25-s-Timer zum Schließen startet mit jeder Antwort neu. Batterien, die direkt mit dem Endpoint verbunden sind, waren nicht betroffen. tools/test-keepalive.cjs schickt mehrere Anfragen über eine Verbindung und prüft, dass alle beantwortet werden.

Aktualisieren

Neues Image (v2.0.1) ziehen und bei den Ports der Compose-Datei die Zeile - "8443:8443" ergänzen. Datenordner und Zertifikate bleiben unverändert; beim ersten Start steht der Einrichtungscode im Log. Nach außen ist nichts offen, solange der Port nicht abgebildet wird. Container-Updates kommen nicht über den Web-Update-Knopf, sondern nur als neues Image.

ports:
  - "443:443"
  - "80:80"
  - "8443:8443"

Tags

Zum Projekt

  • Marstek Offline Endpoint

    Selbst gehosteter Container, der den Telemetrie-Upload eines Marstek Venus lokal beantwortet und entschlüsselt. Stoppt die 30-Minuten-Netzwerk-Resets und liefert alle Cloud-Messwerte lokal, mit Weboberfläche, Benachrichtigungen und MQTT für Home Assistant.

    DockerVersion 2.0.1github.com/sphings79/Marstek-offline-endpoint

Unterstützen

Diese Tools entstehen in meiner Freizeit und bleiben kostenlos, offen und cloud-frei. Wenn dir eins davon einen Nachmittag gespart hat, freue ich mich über einen Kaffee oder ein Stück Pizza.

Kaffee oder Pizza

Weitere Beiträge