Auf dieser Seite

An sonnigen Tagen hat mein 5-kW-Wechselrichter von Huawei den Großteil seines Stroms für ein paar Cent eingespeist, während die Vaillant-Wärmepumpe das Haus am Abend mit Netzbezug geheizt hat. Die Lösung ist in der Theorie einfach: Wenn es PV-Überschuss gibt, soll die Wärmepumpe ihn als Wärme speichern, in den Räumen und im Warmwasserspeicher. In diesem Beitrag zeige ich, wie ich das mit einem ESP32 für 5 € umgesetzt habe, was ich gemessen habe und welche Fallen mich am meisten Zeit gekostet haben.

Getestet vs. modellabhängig. Alles mit ✅ läuft bei mir zu Hause: SUN2000-5KTL-M1 mit SDongle, aroTHERM split VWL 75/5 AS + uniTOWER VWL 78/5 IS, VRC 700, Firmware auf einem Lolin S3 Mini. Alles mit ⚠️ hängt von deinem Modell, deiner Firmware oder deiner Installation ab: Schau in deine Handbücher, bevor du es übernimmst.

Was du baust

  • Ein ESP32 liest den Wechselrichter alle paar Sekunden über Modbus TCP aus (✅).
  • Alle 10 Minuten entscheidet er, ob es genug stabilen Überschuss gibt, um die Wärmepumpe mit Solarstrom zu betreiben.
  • Wenn ja, hebt er die Raum-Solltemperatur für begrenzte Zeit um ein paar Grad an (ein Vaillant-Quick-Veto, ✅) und optional die Warmwasser-Solltemperatur.
  • Verschwindet die Sonne, beginnt Netzbezug oder ist der Raum warm genug, hört er auf. Die von dir gesetzten Grenzen überschreitet er nie.
  • Ein kleines Web-Dashboard auf dem ESP32 zeigt alles an, ganz ohne eigene Cloud.
SystemübersichtHuawei SUN2000+ optionaler Smart MeterHuawei SDongleModbus TCP :502, unit 1ESP32-S3Regeln + Web-DashboardWLAN, FC 03 lesenmyVAILLANT cloudHTTPS (inoffizielle API)VRC 700 + aroTHERMeBUS (2-Draht, Kleinspannung)VR 920/921 GatewayHandy / Browser (LAN)
Datenfluss. Durchgezogene Linien: lokales Netzwerk. Gestrichelt: Internet. Am SDongle kann immer nur ein Modbus-Client gleichzeitig verbunden sein.

Hardware

BauteilWas ich verwendeHinweise
MikrocontrollerLolin S3 Mini (ESP32-S3, 4 MB Flash, 2 MB PSRAM)✅ Jeder ESP32 mit WLAN funktioniert; das PSRAM des S3 hilft bei großen JSON-Antworten
StromversorgungUSB-C-Handyladegerät, 5 V ≥ 1 A✅ Nur ein zertifiziertes Netzteil; das Board braucht etwa 0,5 W
Verbindung zum WechselrichterHuawei SDongle (WLAN/LAN)✅ Modbus TCP auf Port 502; keine Verkabelung zum ESP32
Verbindung zur WärmepumpemyVAILLANT-Gateway (VR 920/921) oder ein eBUS-Adapter⚠️ Siehe die drei Varianten unten
Optionale RelaisRelaismodul mit Optokoppler und 3,3-V-Logikeingang⚠️ Nur für Verbraucher oder einen SG-Ready-Eingang, den dein Handbuch dokumentiert
VerkabelungUSB-C-Netzteil 5 Vzertifiziert, ≥ 1 ALolin S3 Mini (ESP32-S3)USB-C ← Strom + erstes FlashenWi-Fi ← SDongle, cloud, LANGPIO (3,3 V) → Relaismodul INGND → Relaismodul GNDGPIO47 = RGB-Status-LEDRelaismodulOptokoppler, 3,3-V-Logik(optional, für SG Ready)Wärmepumpen-Klemmennur Fachbetrieb, laut AnleitungeBUS-Adapter (optional)2 Drähte zum eBUS, WLAN zum LAN
Alles, was der ESP32 berührt, ist Kleinspannung. Modbus läuft über WLAN; der optionale eBUS-Adapter ist ein separates, galvanisch getrenntes Gerät. Netzspannung bleibt Sache des Elektrikers.

Anschließen

  1. Versorge den ESP32 über USB-C. Das erste Flashen läuft über USB, alle späteren Updates über WLAN (ArduinoOTA).
  2. Kein Kabel zum Wechselrichter. Der SDongle ist schon in deinem WLAN; der ESP32 spricht über das Netzwerk mit ihm.
  3. Relais (optional): Relais-IN an einen freien GPIO, GND an GND, Relais-VCC an das, was das Modul vorgibt. Meide beim S3 Mini die Strapping-Pins (0, 3, 45, 46), USB (19, 20) und die RGB-LED (47).
  4. Alles mit Netzspannung oder an den Klemmen der Wärmepumpe gehört in die Hände eines Elektrikers. Der ESP32 hängt nie direkt am Netz und nie an einer Vaillant-Klemme, außer die Installationsanleitung nennt genau diese Klemme als potenzialfreien Eingang für genau diesen Zweck.

Teil 1: den Huawei-Wechselrichter auslesen

Modbus TCP oder RTU?

WegWieStatus
Modbus TCP über SDongleWLAN/LAN, Port 502, Unit-ID 1✅ Das verwende ich
Modbus RTU über RS485COM-Port des Wechselrichters, braucht einen RS485-Transceiver (z. B. MAX3485-Modul)⚠️ Pinbelegung und Terminierung sind modellabhängig; der SDongle ist dann eventuell nicht gleichzeitig nutzbar

Um Modbus TCP zu aktivieren, stellt ein Installateur-Konto in der FusionSolar-App oder im Webportal Modbus TCP am Dongle auf "Enable (Unrestricted)" (⚠️ der Menüname ändert sich je nach App-Version). Danach antwortet Port 502 im lokalen Netzwerk.

Die Falle, die mich einen Tag gekostet hat: Der SDongle akzeptiert nur einen Modbus-TCP-Client gleichzeitig. Hängt Home Assistant, eine Handy-App oder ein zweiter ESP dran, laufen deine Abfragen in einen Timeout. Wähle einen Leser aus und versorge alles andere von dort aus mit Daten.

Zwei weitere Eigenheiten: Warte nach dem Verbinden etwa eine Sekunde bis zur ersten Anfrage und lies in Blöcken von höchstens 64 Registern mit einer kleinen Pause zwischen den Anfragen.

Die wichtigen Register (✅ getestet am SUN2000-5KTL-M1)

Alle sind Holding-Register, gelesen mit Funktionscode 03. 32-Bit-Werte sind Big-Endian (High-Word zuerst). Die Liste ist mit wlcrs/huawei-solar-lib abgeglichen.

RegisterTypGainBedeutung
32064I321PV-Eingangsleistung (W)
32080I321AC-Wirkleistung (W), der "Solar"-Wert, den ich verwende
32114U32100Ertrag heute (kWh)
32106U32100Gesamtertrag (kWh)
37100U161Status des Stromzählers (1 = online), nur mit Huawei Smart Meter
37113I321Netzleistung am Zähler (W): positiv = Einspeisung, negativ = Netzbezug
37119 / 37121I32100Eingespeiste / bezogene Energie (kWh)

Den Hausverbrauch gibt es nicht als Register. Mit Zähler: consumption = active power − grid power. Ohne Zähler (mein Fall) schätzt die Firmware grid = house base load + heat pump + car − solar, wobei die Grundlast des Hauses eine Einstellung ist (bei mir 300 W).

Firmware: eine Anfrage, dekodiert

Ich verwende eModbus, das Anfragen in eine Warteschlange stellt und dich asynchron zurückruft:

#include <WiFi.h>
#include "ModbusClientTCP.h"
#include "secrets.h"  // WIFI_SSID, WIFI_PASSWORD
 
WiFiClient wifiClient;
ModbusClientTCP modbus(wifiClient);
const uint32_t TOKEN_AC = 1;
 
int32_t readI32(ModbusMessage& msg, uint16_t offsetRegisters) {
  uint16_t high, low;
  // Byte 3 is the first data byte after unit ID, function code and byte count.
  msg.get(3 + offsetRegisters * 2, high);
  msg.get(5 + offsetRegisters * 2, low);
  return (int32_t)(((uint32_t)high << 16) | low);
}
 
void onData(ModbusMessage msg, uint32_t token) {
  if (token == TOKEN_AC) {
    int32_t pvInputW = readI32(msg, 0);          // 32064
    int32_t activePowerW = readI32(msg, 16);     // 32080
    Serial.printf("PV %ld W, AC %ld W\n", (long)pvInputW, (long)activePowerW);
  }
}
 
void onError(Error error, uint32_t token) {
  Serial.printf("Modbus error %02X on request %u\n", (int)error, (unsigned)token);
}
 
void setup() {
  Serial.begin(115200);
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
  while (WiFi.status() != WL_CONNECTED) {
    delay(200);
  }
  modbus.onDataHandler(&onData);
  modbus.onErrorHandler(&onError);
  modbus.setTimeout(5000, 200);                  // response timeout, gap between requests (ms)
  modbus.begin();
  modbus.setTarget(IPAddress(192, 168, 1, 11), 502);
}
 
void loop() {
  static uint32_t last = 0;
  if (millis() - last > 5000 && modbus.pendingRequests() == 0) {
    last = millis();
    modbus.addRequest(TOKEN_AC, 1, READ_HOLD_REGISTER, 32064, 18);  // 32064..32081
  }
}

Teil 2: die Vaillant-Wärmepumpe überwachen und steuern

Es gibt drei Zugänge. Sie unterscheiden sich stark in Aufwand und Risiko:

WegWas du bekommstLokal?Status
myVAILLANT-Cloud (über das VR 920/921-Gateway, das du schon hast)Raum- und Warmwassertemperaturen, Betriebsarten, Energie, Quick-Veto, Warmwasser-SolltemperaturNein✅ Das verwende ich, inoffizielle API
eBUS-Adapter + ebusdAlles auf dem Bus: Vorlauf-/Rücklauftemperatur, Verdichterdaten, Sollwerte; Sollwerte schreibenJa⚠️ Bei mir noch nicht im Einsatz; Nachrichtendefinitionen hängen von der Reglerfirmware ab
SG Ready / Energiemanager-EingangZwei potenzialfreie Kontakte: "Solarüberschuss, mehr heizen"Ja⚠️ Nur wenn das Handbuch deines Geräts den Eingang dokumentiert; Verkabelung durch einen Installateur

Der Cloud-Weg (den ich nutze)

Das Gateway hat keine lokale API; die myVAILLANT-App spricht mit der Vaillant-Cloud, und der ESP32 macht das auch. Die Open-Source-Bibliothek myPyllant dokumentiert die API; die Teile, die ich brauche, habe ich nach C++ portiert. Drei Dinge sind wichtig:

  • Die Anmeldung läuft über OAuth mit PKCE, einmalig von dir im Browser. Versuch nicht, die Login-Seite zu automatisieren.
  • Die API hat ein Rate Limit. Auf 403 "Out of call volume quota" reagierst du, indem du genau so lange wartest, wie in der Meldung steht. Meine Firmware fragt den Status alle 5 Minuten ab, solange die Sonne scheint, sonst alle 15 Minuten: etwa 400 Aufrufe pro Tag.
  • Die Cloud hinkt hinterher. Nach einer Änderung kommt einige Minuten lang noch der alte Wert zurück. Vertrau ~10 Minuten lang deiner eigenen Änderung, statt auf veraltete Daten zu reagieren.

Zum Steuern verwende ich das Quick-Veto: eine vorübergehende Raum-Solltemperatur mit Dauer, die der Regler nach Ablauf von selbst wieder entfernt. Das macht es ausfallsicher: Stirbt der ESP32 mitten im Boost, kehrt die Heizung von allein zum Normalbetrieb zurück. Beim VRC 700 ist der Endpoint PATCH …/zone/{index}/heating/quick-veto, und er braucht sowohl desiredRoomTemperatureSetpoint als auch duration (nur die Dauer liefert HTTP 400 ✅).

Der lokale Weg: eBUS

VRC 700, Wärmepumpe und Gateway teilen sich den eBUS, einen zweiadrigen Niederspannungsbus. Ein eigener Adapter (zum Beispiel der ebusd-Adapter, auf dem ein ESP32 sitzt) hängt am Bus, und ebusd dekodiert ihn mit den Nachrichtendateien aus ebusd-configuration. Schließ den eBUS nie direkt an Pins des ESP32 an: Der Bus läuft mit deutlich mehr als 3,3 V und braucht eine passende Schnittstellenschaltung.

Sobald ebusd läuft, finde heraus, was dein Regler bereitstellt, statt Namen zu raten:

ebusctl find            # lists all messages ebusd knows for your devices
ebusctl read <name>     # read one value, using a name from the list above

ebusd kann alles an MQTT veröffentlichen, das deine Automatisierung (oder der ESP32) abonniert. ⚠️ Fang nur lesend an; schreib Sollwerte erst, wenn du die genaue Nachricht auf deiner Reglerfirmware bestätigt hast.

Teil 3: die Automatisierung, sicher umgesetzt

Die Entscheidung, alle 10 Minuten

spare = solar − house base load − car (if charging)           # 1 s samples, 10-min average
level = highest level whose start is reached
        and where spare − learned heat pump draw ≥ margin
start only if   spare ≥ level 1 start for ≥ 80 % of the window   # steady sun, not one sunny minute
          and   ≥ 30 min since the last boost ended                # protects the compressor
stop if         grid import > 30 Wh in the window                  # checked every second
           or   no level fits any more
target          = day temperature + level offset (+1 … +4.5 °C), never above the cap (24.5 °C)

Was ich auf die harte Tour gelernt habe:

  • Lern den tatsächlichen Verbrauch der Wärmepumpe, aber nur, während sie den Raum heizt. Eine Warmwasserbereitung zieht ~1,8 kW; als das in den "Verbrauch auf Stufe 1" (tatsächlich ~0,8 kW) eingeflossen ist, startete gar kein Boost mehr.
  • Zähl Warmwasserbereitungen nicht als gescheiterte Boosts. Der Regler startet sie nach seinem eigenen Zeitplan; wenn du deinen Raum-Boost beendest, stoppt das sie nicht.
  • Starte nach Stabilität, nicht nach Durchschnittswerten. Eine einzelne Sonnenlücke zwischen Wolken sollte keinen Verdichter starten.

Warmwasser: verschieben statt dagegen ankämpfen

Warmwasser ist die am leichtesten verschiebbare Last, weil der Speicher die Wärme stundenlang hält. Der robusteste Schritt kostet keine Zeile Code: Erlaube im VRC 700 (oder in der App) Warmwasser nur von 11:00 bis 15:00. Zusätzlich kann der ESP32 bei Überschuss die Warmwasser-Solltemperatur anheben und danach wiederherstellen (er merkt sich den ursprünglichen Wert im Flash, sodass er auch nach einem Neustart wiederhergestellt wird).

Sicherheitsregeln in der Firmware

  • Jede Änderung ist vorübergehend (Quick-Veto mit Dauer) oder wird gemerkt und wiederhergestellt (Warmwasser-Solltemperatur, Heizbetriebsart), auch nach einem Stromausfall.
  • Harte Obergrenzen: maximale Raum-Solltemperatur, maximale Warmwasser-Solltemperatur, maximale Pausenzeit (18 h).
  • Fehlen Daten oder sind sie veraltet, endet der Boost. Keine Daten heißt nie "mehr heizen".
  • Die eigenen Schutzfunktionen der Wärmepumpe (Frostschutz, Legionellenschutz, Mindestlaufzeiten) bleiben unangetastet.

Mein Regler heizt einen Raum mit 22 °C. Warum?

Ist die Raumaufschaltung auf "Kein" gestellt (engl. "None"), folgt der VRC 700 nur der Außentemperatur und ignoriert den Raum. Zwei Lösungen: Stell die Raumaufschaltung im Installateurmenü auf "Aktiv" oder "Thermostat" (⚠️ nur wenn der Regler in einem Wohnraum hängt), oder, wie meine Firmware, pausiere die Heizung, solange der Raum über seiner Solltemperatur liegt und keine Sonne scheint, und setze sie fort, sobald der Raum wieder bei der Solltemperatur ist.

Einrichtung, Schritt für Schritt

  1. Aktiviere in FusionSolar (Installateur-Konto) Modbus TCP am Dongle; notier dir seine IP.
  2. Flashe den ESP32 einmal per USB mit WLAN-Zugangsdaten und der Dongle-IP; prüf im seriellen Log, ob Messwerte kommen.
  3. Öffne das Dashboard und melde dich bei myVAILLANT an (oder richte die Firmware auf ebusd/MQTT aus).
  4. Stell Grundlast des Hauses, Boost-Stufen, Obergrenzen und die Toleranz für Netzbezug ein; starte im Modus Auto mit vorsichtigen Werten.
  5. Beobachte einen sonnigen Tag im Verlaufsdiagramm: Solarleistung, Verbrauch der Wärmepumpe, geschätzter Netzbezug und Boost-Phasen.
  6. Feintuning: Senk den Start von Stufe 1 auf den tatsächlichen Mindestverbrauch der Wärmepumpe (~0,7–0,9 kW bei meinem Gerät).

Fehlerbehebung

SymptomUrsache und Lösung
timeout reading 30000, keine Daten vom WechselrichterEin anderer Modbus-Client belegt den SDongle. Trenn ihn; immer nur ein Leser gleichzeitig
Erste Anfrage nach dem Verbinden schlägt fehlNach dem TCP-Verbindungsaufbau ~1 s warten, bevor du liest
Manche Blöcke liefern ExceptionsNicht jedes Modell hat jedes Register (Zähler, Batterie). Markier sie als nicht unterstützt und versuch es nur selten erneut
myVAILLANT 403 Out of call volume quotaZu viele Aufrufe. Warte die angegebene Zeit und frag danach seltener ab
HTTP 400 beim Verlängern eines Quick-Vetos (VRC 700)Schick die Solltemperatur zusammen mit der Dauer
Boost startet nie, obwohl die Sonne scheintPrüf den gelernten Verbrauch pro Stufe; setz ihn zurück, falls eine Warmwasserbereitung ihn aufgebläht hat
Wärmepumpe taktet starkVerlange stabilen Überschuss und eine Ruhezeit zwischen Boosts

Open-Source-Projekte, die du kennen solltest

Fazit

Einen Huawei-Wechselrichter auszulesen ist einfach, sobald du die Ein-Client-Regel und die Registertabelle kennst. Eine Vaillant-Wärmepumpe sicher zu steuern ist die schwierigere Hälfte: Setz auf vorübergehende Änderungen, die von selbst ablaufen, halte die Rate Limits der Cloud ein (oder geh mit eBUS lokal) und bau Geduld in deine Regeln ein (stabile Sonne, Ruhezeiten), damit der Verdichter nicht gestresst wird. Das Ergebnis bei mir zu Hause: An sonnigen Tagen läuft die Wärmepumpe mit Solarstrom, das Haus speichert die Wärme, und am Abend pausiert die Heizung, statt den Strom wieder aus dem Netz zu kaufen.