Marstek Venus E lokal steuern: Modbus TCP, UDP-API und warum man jeden Schreibvorgang prüfen sollte

· 6 Min. Lesezeit · Auch auf English

Der Marstek Venus E ist ein günstiger Plug-in-Hausakku mit 5,12 kWh. Er regelt sich selbst: Ein Smart Meter am Netzanschluss – bei mir ein Shelly Pro 3EM – meldet ihm, ob gerade Strom bezogen oder eingespeist wird. Der Akku lädt oder entlädt dann so, dass am Zähler möglichst null steht.

Das funktioniert gut, solange der Akku der einzige „intelligente“ Verbraucher ist. Sobald eine Wallbox dazukommt, wird es kompliziert. Mein Energiemanagement JG Energy greift deshalb lokal in die Regelung ein – nicht mit eigener Leistungsregelung, sondern mit zwei einfachen Grenzwerten.

Das Problem: Der Akku lädt das Auto

Der Akku sieht nur den Netzzähler. Startet die Wallbox mit 11 kW, sieht er plötzlich Netzbezug – und entlädt mit voller Leistung, um ihn auszugleichen. Der Hausakku lädt dann also das Auto.

Ob das gewollt ist, hängt von der Situation ab. In JG Energy gibt es dafür auf der Wallbox-Seite die Einstellung „Hausbatterie beim Autoladen“ mit zwei Möglichkeiten:

Die Einstellung gilt für das Überschussladen. Lädt das Auto im Schnellmodus mit voller Leistung, verhält sich der Akku ganz normal. Während einer laufenden Ladung lässt sie sich nicht umschalten, damit die Register nicht mitten im Vorgang wechseln.

Zwei Schnittstellen: UDP zum Lesen, Modbus zum Schreiben

Der Venus E 3.0 bietet im lokalen Netz zwei Wege. Die UDP-Schnittstelle heißt „Local API“ und muss in der Marstek-App aktiviert sein. Modbus TCP war bei mir ohne weitere Einstellung auf Port 502 erreichbar.

Lesen über die UDP-API (Port 30000). Das ist ein einfaches JSON-RPC über UDP:

{ "id": 1, "method": "ES.GetStatus", "params": { "id": 0 } }

Die Antwort enthält unter anderem bat_soc (Ladestand), bat_cap (Kapazität), ongrid_power (aktuelle Leistung, positiv = Entladen) sowie Zählerstände für geladene und abgegebene Energie. JG Energy fragt das alle 2 Sekunden ab.

Schreiben über Modbus TCP (Port 502, Unit-ID 1). Für die Steuerung reichen mir zwei Holding-Register:

RegisterInhaltWerte
44002maximale Ladeleistung0–2500 W
44003maximale Entladeleistung0–2500 W
35000interne Temperatur (lesen)int16, × 0,1 °C

Damit ergeben sich drei Zustände:

Zustand4400244003
normal25002500
nur laden („Bleibt aus“, Auto lädt)25000
komplett aus00

Achtung: Die Generation 3 verwendet andere Registeradressen als ältere Venus-Modelle. Anleitungen für den Venus E der ersten Generationen passen nicht.

Warum nicht alles über Modbus?

Ursprünglich wollte ich komplett auf Modbus umsteigen und UDP abschalten. Ein Messvergleich hat mich davon abgebracht:

Deshalb die Aufteilung: UDP für das schnelle, häufige Lesen, Modbus nur für die seltenen Schreibvorgänge und einmal pro Minute für die Temperatur. So bleibt die Modbus-Schnittstelle frei, wenn es darauf ankommt.

Falle 1: Nur eine einzige TCP-Verbindung

Der Marstek erlaubt genau eine Modbus-TCP-Verbindung gleichzeitig. Fragt eine zweite Funktion an, während die erste noch verbunden ist, gibt es ECONNREFUSED.

In einem größeren Programm passiert das schneller, als man denkt: Bei mir liefen Temperaturabfrage, Entladesperre der Wallbox und Diagnose-Endpunkte unabhängig voneinander. Gelegentlich ging dabei ein Schreibvorgang verloren.

Die Lösung ist eine Warteschlange pro Gerät: Alle Zugriffe auf den Marstek laufen nacheinander. Dazu kommen zwei Details:

Und noch eine Kleinigkeit in modbus-serial: Den Timeout mit setTimeout() vor connectTCP() setzen. Sonst kann schon der Verbindungsaufbau unbegrenzt hängen.

Falle 2: Bestätigt heißt nicht ausgeführt

Die zweite Eigenheit war tückischer: Der Marstek bestätigt Schreibvorgänge gelegentlich, ohne sie auszuführen. Die Modbus-Antwort ist positiv, im Register steht aber weiter der alte Wert.

Besonders unangenehm ist das bei Register 44003. Geht das Freigeben nach einer Ladung verloren, bleibt die Entladung gesperrt. Der Akku lädt dann nur noch, entlädt aber nie – und das fällt erst auf, wenn man sich wundert, warum abends Netzstrom fließt, obwohl der Akku voll ist.

Deshalb schreibt JG Energy jetzt und liest danach in derselben Verbindung zurück. Stimmt der Wert nicht, wird einmal neu geschrieben. Bleibt die Abweichung, meldet die Funktion einen Fehler:

const ModbusRTU = require('modbus-serial');

async function schreibenUndPruefen(ip, paare) {   // paare: [[44003, 0], ...]
  const client = new ModbusRTU();
  client.setTimeout(3000);                         // vor connectTCP!
  await client.connectTCP(ip, { port: 502 });
  client.setID(1);
  try {
    for (let versuch = 1; versuch <= 2; versuch++) {
      for (const [reg, wert] of paare) await client.writeRegister(reg, wert);
      const falsch = [];
      for (const [reg, wert] of paare) {
        const r = await client.readHoldingRegisters(reg, 1);
        if (r.data[0] !== wert) falsch.push(reg);
      }
      if (!falsch.length) return true;
    }
    return false;
  } finally {
    await new Promise(ok => client.destroy(ok));
  }
}

Falle 3: Wenn der Akku trotzdem nichts tut

Selbst mit Prüfung kann es passieren, dass der Akku nicht entlädt, obwohl er sollte. Deshalb läuft zusätzlich ein kleiner Wächter:

Dann liest der Wächter die Register direkt aus dem Gerät und schreibt den Befund ins Log: Steht 44003 auf 0, ist die Sperre im Gerät hängen geblieben. Erlauben die Register das Entladen, begrenzt der Akku selbst – etwa wegen der Entladegrenze in der App. Damit ist schon beim ersten Blick ins Log klar, wo man suchen muss.

Der Akku als Brücke

Steht die Einstellung auf „Hilft mit“, geht JG Energy noch einen Schritt weiter. Fällt beim Überschussladen die PV-Leistung kurz unter die Mindestleistung der Wallbox (6 A, also 1,4 bzw. 4,1 kW), müsste die Ladung pausieren. Dann darf der Akku gezielt die Lücke füllen – aber nur:

Das Ergebnis: deutlich weniger Ein-Aus-Zyklen an der Wallbox.

Fazit

Zwei Register reichen, um den Marstek sinnvoll mit einer Wallbox zusammenarbeiten zu lassen. Die eigentliche Arbeit steckt in der Zuverlässigkeit: eine Verbindung nach der anderen, jeden Schreibvorgang zurücklesen und im Betrieb überwachen, ob das Gerät tut, was es soll.

Eine Lücke bleibt bei „Bleibt aus“: Fällt der Server aus, während die Entladung gesperrt ist, bleibt sie gesperrt, bis er wieder läuft. Anders als die Wallbox kennt der Marstek keine Sperre mit Ablaufzeit. Bei mir ist das ein seltener Fall, den ich bewusst in Kauf nehme.

Links