Worauf hab ich mich da nur eingelassen…

Bevor die folgenden Zeilen jemand in den falschen Hals bekommt, möchte ich ausdrücklich darauf hinweisen das ich mir das Gerät bewusst und im vollem Besitz meiner Geistigen Kräfte gekauft habe. Auch möchte ich keinesfalls jemandem einen Vorwurf machen, da ich die von mir benötigten Funktionen vor dem Kauf abgewogen habe. Zugegebener Weise nicht in jeglicher Hinsicht korrekt. Das hat mich in den letzten Wochen stark frustriert was sich in diesem Artikel niederschlägt. Wer mich kennt weiß allerdings dass das letzte was ich tue Aufgeben ist.
Hier hatte ich bereits erwähnt das mir Zendure empfohlen wurde und wie ich geplant hatte diese in meine HomeAutomation einzubinden. Zunächst aber erst mal ein paar Infos um die Zendure Landschaft zu verstehen:
- Zendure ist der Hersteller
- Zendure SolarFlow ist die Produktkategorie
- SolarFlow 1600 AC+ Ist in meinem Fall das Produkt
- Hub nennt Zendure seine Steuerungskomponente im Gerät
- Zendure Cloud nennt Zendure ihren Hauseigenen MQTT Broker
Nach anfänglicher Euphorie beim Testen meines Proxys mit dem Laptop kamen mir dann doch Bedenken weil ich immer wildere Dinge über die Absicherung des Zendure SolarFlow Hubs und dessen MQTT-Clients gelesen habe. Das war glücklicherweise Grundlos. Als die Kiste kam, habe ich diese über die App in Betrieb genommen und mit dem WLAN des S3-Proxies verbunden. Katsching, sofort hatte ich die SolarFlow auf dem Broker. Super Start dachte ich noch. Pustekuchen. Beim Rumspielen mit den Sollwerten musste ich feststellen, das die Istwerte sich in der App innerhalb 4-10 Sekunden ändern, auf dem MQTT allerdings 16 – 60 Sekunden benötigen. Eine Regeltechnische Katastrophe. Zu diesem Zeitpunkt dachte ich das es eventuell am ESP32 liegt. Also musste eine Alternative her. In der neuesten Firmware kann man nun Praktischerweise einen eigenen MQTT Broker einstellen.

Prima, man öffnet sich den Kunden.
Und wieder war mein Gedanke völlig abwegig. Dieser funktioniert nämlich nur dann wenn der intern Voreingestellte mit der Cloud verbunden ist. Keine Verbindung zur Cloud, keine Nachrichten auf dem privaten Brocker. Ok, zum Testen die Kiste ins Internet gelassen, und siehe da …

Enttäuschend. Die selbe Latenz ohne eventuell limitierende Bridge zwischendrin. Daran lag es also nicht. Allerdings kommt auf diesem Weg noch Unmengen an Datenmüll hinzu. Zunächst die Daten des Hubs, der Batterien, und der Off-Grid Steckdose, was den selben Daten im Cloud MQTT entspricht. Das wäre OK, aber zusätzlich Strukturen für Home-Assistant, IO-Broker und was weiß ich noch alles. Und nichts davon konfigurierbar.
So geht’s jedenfalls auch nicht.
Nach mühseligen Tagen auf der Suche nach Lösungen bei der mir Gemini und ChatGPT keinen Millimeter geholfen hat, habe ich in einem Forum in dem es um etwas ganz anderes ging das Schlagwort „ZenSDK“ entdeckt. Das ganze Meisterwerk ist, wie soll es denn anders sein, nicht öffentlich Dokumentiert. An dieser Stelle konnte mir Gemini dann weiterhelfen. Denn die GET und POST Anfragen enthalten die selbe JSON Struktur wie bei MQTT. Das ist zwar auch nicht öffentlich Dokumentiert, aber hier hat Reinhard Brandstätter schon reichlich Vorarbeit geleistet. Auf GitHub zu finden unter reinhard-brandstaedter.
In Powershell kurz GET und POST getestet
PowerShell:
Invoke-WebRequest -Method Get -Uri "http://192.168.4.2/properties/report"
Und siehe da, die Istwert Änderung wird sofort angezeigt sobald diese in der App ersichtlich ist wenn nicht sogar schneller. Die SolarFlow also wieder vom Internet getrennt, aber was ist das? Keine Cloud heißt kein API (Application Programming Interface). An dieser Stelle greife ich etwas vor, denn der Hub verbindet sich nicht nur mit dem Zendure MQTT Broker sondern auch mit deren NTP-Server. Und ohne die Zeit schaltet die SolarFlow die Ausgänge nicht frei. Die haben doch nicht mehr alle Latten am Zaun.
Es muss die Frage gestattet sein: Wie steure ich meine „Off-Grid Steckdose“ wenn der Strom weg ist und der Router dadurch keine Internetverbindung mehr herstellen kann. Ach ja richtig, ich kann sie ja vorher schon einschalten wenn ich weiß das gleich ein Stromausfall kommt. Auf die 20 Watt Inverter Leistung im Leerlauf ist ja geschissen.
Also die Solarflow wieder mit dem S3-Proxy verbunden und damit mit meinem Broker. Da geht es dann wieder. Aber nun hatte ich den ganzen MQTT-Müll wieder auf meinem Broker. Der Plan: Erstens den Proxy umbauen, und dann den Zendure Controller bauen.
Die neue Bridge (Proxy)
Zunächst einmal sei gesagt das Zendure anders als vielmals erwähnt oder behauptet, die Pakete nicht über Port 8883, also „MQTT over TLS“ verschickt, sondern unverschlüsselt über Port 1883 was noch ein Grund mehr für mich ist, die SolarFlow nicht ins Internet zu lassen. Dazu kommt, dass Zendure die Pakete mit QoS0 (Quality of Service Level 0) sendet, also „fire and forget“ was in dem Fall gut für mich ist. Denn so muss der Client ja gar nicht wirklich mit einem Brocker verbunden sein. Er muss nur denken das er es ist. Das Ganze nennt sich Mocking. Man erstellt einen TCP Socket auf Port 1883. Der MQTT Client verbindet sich dorthin, ist verbunden und ballert seine Pakete ins Nirvana.
So habe ich dann meine Bridge zum Testen umgebaut. Das weiterleiten von Port 1883 und 8883 habe ich entfernt. Dafür kam das oben erwähnte TCP Socket in den Code. Wer mal damit spielen will:
Python:
import socket
# TCP Server auf Port 1883 starten
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(('0.0.0.0', 1883))
server.listen(1)
print("Dummy-Broker wartet auf Clients...")
while True:
client_socket, addr = server.accept()
print(f"Verbindung von {addr}")
# 1. CONNECT Paket vom Client empfangen (wird hier ignoriert)
connect_packet = client_socket.recv(1024)
# 2. Das magische CONNACK zurücksenden
client_socket.send(b'\x20\x02\x00\x00')
print("CONNACK gesendet. Client ist drin!")
# 3. Jetzt kommen die QoS 0 Daten reingeflattert
while True:
data = client_socket.recv(1024)
if not data:
break
print(f"Rohdaten empfangen: {data}")
Da die DNS Anfragen an mqtt-eu.zen-iot.de eh schon an die 192.168.4.1 umgeleitet waren, verbindet sich der Hub nun mit dem digitalen Bermudadreieck. Nun Funktioniert technisch alles so wie ich es will. Ich kann die SolarFlow mit wenig Latenz ansprechen, sie ist nicht mit dem Internet verbunden und müllt mir nicht meinen MQTT Brocker voll.
Der Controller (Knoten im Hirn)
Das schlimmste wäre geschafft, dachte ich da noch. Welch wunderbarer Traum.
Ich bekomme meine Stromzählerwerte über einen Hitchi Sensor der mir diese über Tasmota an den Broker schickt. Wenn ich nun eine Glühbirne einschalte, dauert es ca 3-6 Sekunden bis die Änderung am Stromzähler abzulesen ist. Für meine Stromkosten ist das nicht relevant aber es ist eine Latenz die bei der Regelung berücksichtigt werden muss. Dazu kommen konstante 3 Sekunden, bis die Änderung im Broker angekommen ist. Zum Zeitpunkt meiner Stellwertermittlung liegt das in der Vergangenheit. Nun setze ich den Stellwert und es dauert je nach Regelabweichung und Situation 1-16 Sekunden bis der Zielwert in der SolarFlow erreicht ist. Ist die SolarFlow in Regelung kann man von maximal 8 Sekunden ausgehen. Sowas steuert man nicht mehr über einem PID-Regler.

Also hab ich die KI befragt und als Antwort kam: Du brauchst einen Smith-Prädiktor.
Das kam mir irgendwie bekannt vor, allerdings hatte ich vermutlich das letzte mal in der Ausbildung davon gehört. Aber warum einfach wenn’s auch kompliziert geht. Immerhin ist es in diesem Fall dann gleich ein Smith-Prädiktor mit zwei zeitlich variablen Messgrößen. Uiuiuiuiui, ich weiß gar nicht wie ich das beschreiben soll. In den Worten von Gemini:
Ein erweiterter Smith-Prädiktor für zwei zeitlich variable Messgrößen nutzt ein mathematisches Prozessmodell, um die jeweiligen, sich dynamisch verändernden Totzeiten beider Eingangsgrößen parallel zu simulieren und rechnerisch zu kompensieren. Dadurch kann der Regler auf Basis von verzögerungsfreien Vorhersagen agieren, noch bevor die echten, variablen Effekte an den Messstellen messbar sind. Auftretende Modellabweichungen werden über eine kontinuierliche Differenzbildung mit den realen Zählerwerten korrigiert, was den Regelkreis selbst bei schwankenden Latenzen stabil hält.
Gecheckt? Ok, dann kann’s weiter gehen. An diesem Punkt angekommen, wundert es mich nicht das der Akkudoktor (Andreas Schmitz) bei seinen DIY Regelungen immer nur 10 Watt hoch oder runter regelt.
Das will ich so nicht haben. Man stelle sich vor: 600 Watt PV-Überschuss. Man lädt den Akku mit 600 Watt. Es kommt ne Wolke und es dauert in 10er Schritten multipliziert mit der Latenz erst mal 1½ Minuten bis der Sollwert auf 0 ist. Die Wolke ist weg und es dauert wieder 1½ Minuten bis die Regelung wieder bei 600 Watt ist.
Nun ist es in letzter Zeit sehr bequem geworden zu Programmieren. Man schreib die passenden Prompts zusammen, übergibt es dem LLM, prüft den erhaltenen Code und ändert die ein oder andere Stelle. So habe ich es auch diesmal gemacht. Das ging leider in die Hose. Nicht nur einmal. Die Regelung hat nicht das gemacht was sie sollte. Ich konnte dem LLM einfach nicht beibringen, das der Leistungswert des Stromzählers nicht der Absolutwert im Gesamtsystem ist. Hatte ich Gemini davon überzeugt die richtige Sollwertberechnung aus Beispielsweise Einspeiseleistung und Zählerwert zu berechnen, hat er es bei den Regelbedingungen wieder rausgeworfen. Mehrere Threads führten zu Todesspiralen. Die KI hat sich jedes mal aufs neue verrannt.
Also musste ich es wie früher selbst Programmieren was nunja doch recht langwierig ist, wenn man’s inzwischen anders kennt. Angefangen mit dem Interface! Danach eine „State Machine“ und am Schluss die Sollwertberechnungen je Zustand. Es war total Irre. Wann ist der Messwert so und wann so. Rein und raus, plus ist minus und eins ist NULL. Da weiß man nicht mehr ob man Männlein oder Weiblein ist. Aber das ist nicht das schlimmste. Man denkt es funktioniert alles, und dann kommt die Zendure Logik ins Spiel. Man könnte ja Meinen, wenn man einen Input Wert setzt, der die Leistung angibt mit der der Akku geladen wird, und einen Output Wert, der angibt wieviel Leistung eingespeist werden soll, das sich das gegenseitig verriegelt, zumal man mit dem Wert acMode:1 das Laden aktiviert und mit acMode:2 das Einspeisen. Dann müsste die Regelung funktionieren! Aber NEIN. Der Stromzähler springt wie ein Hoppelhase durch die Gegend und man fragt sich warum? Warum nur? Wo ist der Fehler? Doch man findet keinen Fehler! Und irgendwann schaut man in die App und sieht: Die Kiste lädt den Akku und speist gleichzeitig ins Haus ein, über das gleiche Kabel. Was ein Blödsinn. Dann findet man raus, das man erst Output:0 und Input:0 schicken muss, bevor man die Richtung umkehrt. Wenn man Output:0 und Input:300 hinschickt und gleichzeitig acMode:1 weil man mit 300 Watt laden will, überschreibt der Hub den Output nicht mit 0. Dabei funktioniert das perfekt in die andere Richtung. Und das ist auch noch Absicht.
Ich muss mich verbessern. Denen fehlen keine Latten, die haben gar keine Latten mehr am Zaun.

Wie dem auch sei. Nach etlichen Korrekturen funktioniert der Controller inzwischen astrein. Besser sogar als ich erwartet habe. Er regelt fantastisch aus bis aufs letzte Watt. Und das innerhalb von Sekunden. Nach nun 3 Tagen hat er bis aufs BMS Kalibrieren alle Zustände durch, und die Tests bestanden.
Beim bauen der Firmware habe ich nun auch eine Funktion zum Updaten der Firmware „over the air“ eingebaut, auch mit Blick auf folgende Projekte. Nun kann ich beim Kompilieren unter folgenden Punkten wählen:
- env:usb – Erstes beschicken oder für den Notfall über USB im Testbetrieb
- env_test – Update OTA im Testbetrieb
- env_live – Update OTA im Livebetrieb
In der Programmierumgebung muss ich einmalig einen „devicename“ angeben. Dieser wird als OTA-Hostname des ESP32 hinterlegt. Im Programmcode kann ich dann bei allen Funktionen sagen, was im Testbetrieb passieren soll, und was im Livebetrieb. So kann beispielsweise die Poolsteuerung laufen, während ich eine neue Version ohne die Sensoren teste. Sobald mir das Ergebnis passt schicke ich die neue Firmware als Live-Version auf den ESP32 im Pool wo er mit den Sensoren läuft, ohne das ich den Programmcode anpassen muss.
Achja, eine Ausgabe der wichtigen Zendure SolarFlow Werte landen inzwischen nahezu in Echtzeit auch auf meinem Brocker.

Mir fällt ein Riesen Stein vom Herzen, das dieses Kapitel endlich erfolgreich geschlossen werden kann. Nachdem alles läuft kann ich mir jetzt meine zweite Batterie zur Erweiterung bestellen.
Fazit
Lasst euch von Versprechen über „sich dem Markt öffnende Systeme“ nicht in die irre Führen. Auch wenn Hersteller mit Flexibilität und Zugänglichkeit werben, landen die Daten am Ende doch in der Cloud. Gekapselte Systemlandschaften wie die von Zendure sind für die Erweiterung bestehender Anlagen ungeeignet. Wer verhindern will, dass seine Anlagendaten im Internet landen, sollte einen Bogen um diese geschlossenen Ökosysteme machen.