Marstek Venus A – Erfahrungen & Modbus-Tools

Praxisnotizen zur Ansteuerung eines Marstek Venus A Heimspeichers per Modbus TCP, gesammelt beim Aufbau einer preisgesteuerten Lade-/Entladestrategie.

Werbung
Den hier beschriebenen Akku (Marstek Venus A) gibt es bei Amazon
Affiliate-Link zum Amazon-Partnerprogramm – als Amazon-Partner verdienen wir an qualifizierten Verkäufen, für dich entstehen dabei keine Mehrkosten.
Bei Amazon ansehen

Kernerkenntnis: Lokale API vs. Modbus

Die WLAN-App selbst läuft einwandfrei – aber die lokale API des Akkus (UDP, Port 30000, muss extra eingeschaltet werden) läuft nicht zuverlässig. Sobald diese API aktiv ist, kommt es zu unregelmäßigen Aussetzern und Fehlverhalten – z. B. schaltet der Akku von selbst die Backup-Steckdose aus, schaltet die API selbständig wieder aus oder schaltet die LEDs von selbst an – der Akku lief damit keine zwei Tage am Stück stabil. Lässt man die API komplett deaktiviert und steuert den Akku stattdessen ausschließlich per Modbus TCP über ein LAN-Kabel an, läuft er sehr stabil (Stand dieses Tests: erst wenige Wochen, aber bislang ohne Aussetzer – getestet mit Firmware-Version 149).

Im Internet liest man häufig von genau solchen Problemen (Aussetzern, spontanen Fehlfunktionen, Geräten, die irgendwann gar nicht mehr reagieren) – aber so gut wie nie, wodurch sie eigentlich verursacht werden. Unsere Vermutung, basierend auf den eigenen Erfahrungen: die API-Zugriffe selbst.

Modbus TCP ist bei diesem Gerät nur über die kabelgebundene Ethernet-Schnittstelle erreichbar, nicht über WLAN. Der Akku hat in der Praxis zwei unterschiedliche IPs, je nachdem welches Interface gerade aktiv ist – bei Verbindungsproblemen lohnt es sich, zuerst zu prüfen, ob eine zweite (LAN-)IP existiert.

Kurzer Vorgriff: Auch die Modbus-TCP-Umsetzung des Akkus ist genau genommen nicht standardkonform – handelsübliche Modbus-Software kommt damit nicht klar, weshalb wir eigene Python-Scripts geschrieben haben. Details dazu weiter unten.

Warum uns das wichtig ist – eine kurze Geschichte: Der erste Akku wurde über die API gesteuert. Nach etwa drei Wochen mit ständigen Problemen ging er irgendwann gar nicht mehr an: Man konnte ihn einschalten, die grüne Power-LED leuchtete, aber die LEDs auf der Frontleiste blieben aus. Auch per App war er nicht mehr erreichbar. Erst nach langem Nachhaken und vielen Diskussionen mit dem Support wurde das Gerät getauscht. Auch hier vermuten wir im Nachhinein die API als Ursache – gesichert ist das nicht, aber es passt genau ins Bild der Aussetzer und Fehlfunktionen von oben.

Das Ersatzgerät wird von Anfang an ausschließlich per Modbus angesteuert (API deaktiviert) – und läuft seitdem problemlos.

Zwei Zugriffswege im Überblick

Modbus TCP (Port 502) empfohlen

Nur über LAN/Ethernet-Kabel erreichbar. Lesen (FC3/FC4) und Schreiben (FC6) einzelner Register. In unserer Erfahrung deutlich stabiler als die App-gesteuerten Wege – funktioniert aber nur mit selbst geschriebenen Scripts, da die Modbus-Implementierung des Akkus nicht standardkonform ist und fertige Modbus-Software daran scheitert (Details).

Lokale JSON-RPC API (UDP, Port 30000) instabil

Funktioniert über WLAN. Offizielle „Local API“ mit Methoden wie ES.GetStatus/ES.SetMode. Das ist genau die API, die den Akku instabil laufen lässt (siehe Kernerkenntnis oben) – nicht als primären Steuerweg nutzen.

Warum eigene Scripts statt fertiger Modbus-Software?

Die Modbus-TCP-Implementierung des Akkus weicht vom Standard ab. Mit keinem der getesteten fertigen Modbus-Programme kam eine funktionierende Verbindung zustande. Erst eigene, minimalistische Python-Scripts (modbus.py/modbus_w.py) haben zuverlässig funktioniert.

Was den Unterschied macht, lässt sich direkt am Code der Scripts ablesen – sie sprechen bewusst „unsauberes" Modbus TCP:

  • Es wird kein fertiges Modbus-Framework verwendet, sondern ein rohes TCP-Socket. Der 7-Byte-Header (Transaction ID, Protocol ID, Length, Unit ID) wird von Hand zusammengebaut.
  • Die Antwort wird nicht anhand der Längenangabe im Header geparst, sondern anhand fester Byte-Positionen (Function-Code-Byte, Datenbytes) – die Length-Angabe im Header wird schlicht ignoriert, statt als Prüfkriterium zu dienen.
  • Ein Code-Kommentar in modbus.py dokumentiert explizit einen Firmware-Bug: Bei Fehler-/Exception-Antworten schickt der Akku eine falsche Längenangabe im Header (4 statt der korrekten 3) – die Scripts werten die Antwort trotzdem aus, statt sie deswegen zu verwerfen.
  • Die Registernummern (z. B. 42010, 30001) werden unverändert als Protokolladresse übertragen, ohne die in der Modbus-Welt verbreitete Offset-Konvention (z. B. „40001 abziehen") anzuwenden.

Genau das war der Punkt, an dem fertige Modbus-Programme gescheitert sind – sie halten sich strikt an die Spezifikation und geben bei diesen Abweichungen auf. Die eigenen Scripts sind bewusst genau an diesen Stellen toleranter gebaut, und funktionieren dadurch zuverlässig.

Transparenzhinweis: Beide Scripts sind KI-generiert (mit Unterstützung von Claude entstanden). Bei uns laufen sie im Test einwandfrei und liefern zuverlässig korrekte Werte – trotzdem: selbst prüfen, bevor man sich blind darauf verlässt, insbesondere bei schreibenden Zugriffen (Force Mode, Leistung setzen).

Werbung
Selbst mit Claude coden?
Schenke jemandem eine kostenlose Woche Claude Code. Schließt die Person ein Abo ab, erhältst du 10 € Nutzungsguthaben – Referral-Link, es gelten die Nutzungsbedingungen von Anthropic.
Claude Code verschenken

Wichtige Register

Schreibbare Steuerregister

RegisterFunktionWerte
42000RS485-Kontrolle aktivieren21930 (0x55AA) = ein, 21947 (0x55BB) = aus
42010Force Mode0 = Stop/Idle, 1 = Laden, 2 = Entladen
42020Ladeleistung0–2500 W
42021Entladeleistung0–2500 W
43000Work Mode0 = Manual, 1 = Anti-Feed, 2 = Trade Mode
44002Max Charge Power800–2500 W
44003Max Discharge Power800–2500 W

Wichtige Leseregister

RegisterBedeutungSkalierung
30001Batterieleistung (W, signed): + = Laden, − = Entladenroh (1:1)
30006AC-Leistungroh (1:1), W
30037–30040PV/MPPT-Leistung 1–4×0.1, W
30100Batteriespannung×0.01
30101Batteriestrom (signed)×0.1
32104SOC (%)roh (1:1)
32105„Battery Total Energy" = Nennkapazität (nicht der aktuelle Ladezustand!)roh in Wh
33000Kumulierte Akku-Entladung (32-Bit, +33001)×0.01 kWh
33002Kumulierte Akku-Ladung (32-Bit, +33003)×0.01 kWh

Es gibt kein eigenes Register für die aktuell gespeicherte Energie (Wh) – sie lässt sich nur als SOC% × Nennkapazität berechnen. Ebenso existiert kein Register für kumulierte PV-Gesamterzeugung, nur die Momentanleistung je MPPT-String.

Vorsicht bei den Vendor-Labels: Die offizielle Doku bezeichnet 33000/33002 als „Total Charging/Discharging Energy“ – bei unserem Gerät war das aber genau andersherum belegt als beschriftet. Vor dem Verlassen auf diese Register lohnt sich ein empirischer Abgleich (Register-Wert vs. beobachtetes Lade-/Entladeverhalten).

Bekannte Fallstricke

Work Mode kann Steuerbefehle stillschweigend blockieren. Steht Register 43000 nicht auf 0 (Manual) – z. B. auf 1 (Anti-Feed), Werkseinstellung bei neuen Geräten – werden Schreibbefehle auf 42010/42020/42021 vom Gerät ignoriert. Der Modbus-Write selbst meldet trotzdem Erfolg (Echo kommt zurück), nur der Akku reagiert nicht.

42000 und 43000 setzen sich beim Schreiben gegenseitig zurück (bestätigter, deterministischer Effekt, kein Timing-Problem). Schreiben von 43000 wirft 42000 zuverlässig wieder auf „aus“ (21947) zurück – und umgekehrt. Das zuletzt geschriebene Register „gewinnt“, das andere kippt garantiert um, ohne dass der Write das meldet.

Funktionierende Reihenfolge nach einem Neustart – 42000 muss der allerletzte Write sein:

43000 = 0       # Work Mode Manual (falls gewünscht)
42000 = 21930   # RS485-Kontrolle – MUSS zuletzt vor 42010/42020 stehen
42010 = 1/2     # Force Mode Laden/Entladen
42020/42021     # Leistung setzen

42010/42020/42021 setzen 42000 nicht zurück – sie können also gefahrlos danach folgen.

Diese Prozedur muss nach jedem Neustart des Akkus erneut durchlaufen werden. Ist das Gerät aber einmal im richtigen Modus (42000 aktiv), scheint es Schreibbefehle bis zum nächsten Neustart zuverlässig anzunehmen – ohne dass man die Register zwischendurch nochmal anfassen muss.

Nicht zu häufig abfragen. Wird der Akku zu dicht getaktet mit Modbus-Anfragen „bombardiert“, kommt es zu fehlerhaften bzw. unvollständigen Rückmeldungen. Ein Poll-Intervall von einigen Sekunden (z. B. alle 3–5 s ein Register) ist unproblematisch – mehr sollte man dem Gerät nicht zumuten. Zwischen mehreren Register-Reads hintereinander lohnt sich zusätzlich eine kurze Pause (in unserem Dauerbetrieb bewährt: 3 s).

Sich nicht darauf verlassen, dass jede einzelne Abfrage klappt. Auch bei moderater Abfragefrequenz schlägt gelegentlich ein Read oder Write fehl (Timeout, leere/unvollständige Antwort). Eigener Code sollte das einplanen – also Fehler abfangen, ggf. mit Retry, und nicht bei einem einzelnen Fehlschlag den letzten bekannten Wert verwerfen oder einen Steuerbefehl als endgültig gescheitert werten.

Scripts zum Download

Zwei schlanke Python-Scripts, raw-socket-basiert (keine externen Modbus-Bibliotheken nötig), zum Lesen und Schreiben einzelner Register per Modbus TCP.

modbus.py
Liest ein Register aus (FC3 Holding, Fallback FC4 Input). Aufruf: python3 modbus.py <IP> <REGISTER> [slave_id]
Download
modbus_w.py
Schreibt ein Register (FC6 Write Single Register). Aufruf: python3 modbus_w.py <IP> <REGISTER> <VALUE> [slave_id]
Download

Beide Scripts verbinden sich per TCP auf Port 502 direkt mit der LAN-IP des Akkus – dafür muss der Akku per Ethernet-Kabel im gleichen Netz hängen.

Nutzung auf eigene Verantwortung. Insbesondere modbus_w.py schreibt direkt in Steuerregister des Akkus (Force Mode, Lade-/Entladeleistung). Bei falscher Anwendung – z. B. Schreiben unerwarteter Werte in unbekannte Register – ist eine Fehlfunktion oder Beschädigung des Geräts nicht auszuschließen. Wir übernehmen keine Haftung für Schäden am Akku, angeschlossenen Geräten oder sonstige Folgeschäden durch die Nutzung dieser Scripts. Vor Schreibzugriffen immer erst mit Lesebefehlen (modbus.py) vertraut machen und Werte im Zweifel gegen die Register-Tabellen oben prüfen.

Voraussetzungen & Verwendung

Abhängigkeiten

Beide Scripts nutzen ausschließlich die Python-Standardbibliothek (socket, struct, sys) – es müssen keine zusätzlichen Pakete per pip installiert werden. Es genügt eine vorhandene Python-3-Installation.

Linux

Auf den meisten Distributionen ist Python 3 bereits vorinstalliert. Falls nicht:

# Debian/Ubuntu
sudo apt install python3

# Fedora
sudo dnf install python3

# Arch
sudo pacman -S python

Prüfen mit python3 --version. Danach direkt aus dem Download-Ordner aufrufen, z. B.:

python3 modbus.py 192.168.1.50 32104
# → liest SOC (%) vom Akku unter dieser IP

python3 modbus_w.py 192.168.1.50 42010 2
# → setzt Force Mode auf "Entladen"

Windows

Die Scripts sind unverändert unter Windows lauffähig – sie verwenden nur plattformunabhängige Standardbibliotheks-Funktionen (TCP-Socket, Byte-Packing), keine Linux-spezifischen Aufrufe.

Vorgehen:

  1. Python 3 installieren von python.org/downloads (beim Setup „Add python.exe to PATH“ ankreuzen).
  2. modbus.py und modbus_w.py in einen beliebigen Ordner herunterladen.
  3. Eingabeaufforderung (cmd) oder PowerShell in diesem Ordner öffnen (z. B. Shift+Rechtsklick im Explorer → „PowerShell-Fenster hier öffnen“).
  4. Aufruf mit python statt python3 (unter Windows heißt das Kommando meist schlicht python, teils auch py):
python modbus.py 192.168.1.50 32104
python modbus_w.py 192.168.1.50 42010 2

Falls Windows das Kommando nicht findet: py -3 modbus.py 192.168.1.50 32104 probieren (der „py“-Launcher wird bei der Standard-Installation mitinstalliert).

Netzwerk-Voraussetzung (beide Systeme)

Ausgabe verstehen

modbus.py gibt Rohwert (dezimal + hex) und – für signed Register wie 30001 – zusätzlich den vorzeichenbehafteten Wert aus:

Register : 30001  [Holding FC3]  Slave: 1
Roh      : 65326  (0xFF2E)
Signed   : -210

modbus_w.py bestätigt den geschriebenen Wert per Geräte-Echo:

Register : 42010  Wert: 2  Slave: 1
OK — Register 42010 = 2