Weiter zum Inhalt
  • Home
  • Aktuell
  • 0 Ungelesen 0
  • Kategorien
  • Unreplied
  • Beliebt
  • GitHub
  • Docu
  • Hilfe
Skins
  • Hell
  • Brite
  • Cerulean
  • Cosmo
  • Flatly
  • Journal
  • Litera
  • Lumen
  • Lux
  • Materia
  • Minty
  • Morph
  • Pulse
  • Sandstone
  • Simplex
  • Sketchy
  • Spacelab
  • United
  • Yeti
  • Zephyr
  • Dunkel
  • Cyborg
  • Darkly
  • Quartz
  • Slate
  • Solar
  • Superhero
  • Vapor

  • Standard: (Kein Skin)
  • Kein Skin
Einklappen
ioBroker Logo

Community Forum

donate donate
  1. ioBroker Community Home
  2. Deutsch
  3. Skripten / Logik
  4. JavaScript
  5. Zendure zenSDK Lokal API, SmartMode, SolarFlow AC 800 Pro 2

NEWS

  • Monatsrückblick Oktober 2026 ist online!
    BluefoxB
    Bluefox
    10
    1
    258

  • Mitmachen: Sprich „ioBroker“ ein – für ein lokales Aktivierungswort
    BluefoxB
    Bluefox
    6
    1
    464

  • Neuer Blogbeitrag: KI in ioBroker, vom Helfer im Admin bis zum eigenen Smart Assistant
    BluefoxB
    Bluefox
    4
    1
    509

Zendure zenSDK Lokal API, SmartMode, SolarFlow AC 800 Pro 2

Geplant Angeheftet Gesperrt Verschoben JavaScript
694 Beiträge 25 Kommentatoren 126.4k Aufrufe 23 Beobachtet
  • Älteste zuerst
  • Neuste zuerst
  • Meiste Stimmen
Antworten
  • In einem neuen Thema antworten
Anmelden zum Antworten
Dieses Thema wurde gelöscht. Nur Nutzer mit entsprechenden Rechten können es sehen.
  • D Daniel 8

    Ja die 49 kommen daher das er halt über Nacht was verliert. Die Akkus werden dann in Standby geschickt. Da ist er aber noch erreichbar. Das ein bisschen mehr drinnen ist wie angezeigt habe ich auch schon festgestellt, wenn er mal auf 100 Prozent lädt und viel weniger rein geht als soll.

    Aber das tiefe abschalten ist erst nach ca 13 Stunden der letzten BefehlEingabe.

    Das ist für mich kein Problem und wollte es nur mitteilen. Ich bin so zufrieden wenn er bei 50% soc aufhört zum einspeisen.

    Mir ist das ganze ja nur aufgefallen weil dann von deinem Script die Meldung IP prüfen kam. Dann schaute ich ob er vom wlan getrennt war. Aber sobald sonne kommt läuft ja wieder alles. Deswegen unternehme ich da auch nichts.

    maxclaudiM
    maxclaudiM
    maxclaudi
    schrieb am zuletzt editiert von
    #685

    @Daniel-8
    Alles klar, dann passt das ja perfekt für dein Setup :-)
    Es ist auf jeden Fall gut, dass das für alle Mitleser hier im Thread dokumentiert ist.

    Sobald mit der Sonne morgens die MCU erwacht, läuft das Skript ja wie gewohnt weiter.

    Ein zusätzliches Isolieren der Batterie(n) wäre zu empfehlen, wenn schon im Oktober nur 8°C erreicht werden.

    Zendure zenSDK Lokal API, SmartMode, SolarFlow AC 800 Pro 2

    neue Zendure Geräte Cloudless: zURLtool V2.0

    maxclaudiM 1 Antwort Letzte Antwort
    1
    • maxclaudiM maxclaudi

      zURLtool_V20 (c) maxclaudi 2026 - Version 2.0 cdh

      Neu in zURLtool V 2.0 dch
      • Sprachauswahl: Deutsch und Englisch sind auswählbar und während der Laufzeit umschaltbar.

      • Reset-Funktion: Geräte können zurückgesetzt werden (Hinweis: Zum vollständigen Abmelden - z.B. bei Verkauf - Gerät auch in der App entfernen!).

      • WLAN-Handling: Das manuelle Setzen des WiFi-Mode entfällt. Ein automatischer Scan zeigt alle vom Zendure-Gerät erreichbaren Netze an. Bei zur config.ini abweichendem, manuell ausgewählten WLAN muss auch das Passwort in der GUI angepasst werden.

      • Offline-Modus: Die Option OFFLINE = 1 in der config.ini überspringt den WLAN-Scan, ideal für Vorbereitungen oder Konfigurationen ohne aktiven Router.

      • Token & User-ID: Gerätespezifischer Token wird in mqtt_creds.txt gesichert. Für die korrekte userId (bzw. consumerId) empfiehlt sich der Einsatz des Zusatztools zAppEmulationDeviceInfo

      • Ein Backup mit den verwendeten Daten in der config.ini wird zusätzlich gespeichert. Beispiel: bak_config_26.10.26_14.51.35.ini

      • Hinweis zu TLS / Port 8883: Das Tool ist ein reiner Konfigurator per Bluetooth und generiert keine TLS-Zertifikate oder Keys.

      00.png

      01-de.png

      Beschreibung im Spoiler.


      zURLtool V 2.0 dch – Lokaler MQTT-Broker für Zendure 2026

      Hallo zusammen, das Update zURLtool V 2.0 dch de/gb ist nun fertig!


      Das Programm ist in seiner Art einzigartig, neu und durch monatelanges Reverse-Engineering, Analysen und intensives Testen entstanden.
      Es handelt sich um ein reines Windows-GUI-Programm, das ganz einfach per Doppelklick gestartet wird.

      Es verwendet keinen Code von anderen bekannten Projekten, die teils fehlerhaften und unvollständigen Code verwenden, mit dem Konfigurationen neuer Geräte nicht möglich sind.

      Technische Voraussetzungen & Basis:

      Vielen Dank an Henrik Blidh für die hervorragende BT-Bibliothek.

      Das Programm nutzt zur Kommunikation ausschließlich die offiziellen Windows-BT-Treiber ab Windows 10/11 und die integrierte Bluetooth-Bibliothek bleak:

      ==================================================

      LICENSE NOTICE FOR THE UTILIZED BLUETOOTH LIBRARY
      This program uses "bleak" for Bluetooth functionality.
      https://github.com/hbldh/bleak
      Copyright (c) 2020, Henrik Blidh
      Permission is hereby granted, free of charge, to any person obtaining a copy
      of this software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND.

      ==================================================

      Die Bibliothek wurde fest in das Programm eingebunden.

      Weitere Bibliotheken oder Frameworks müssen auf eurem PC nicht installiert werden.

      • Betriebssystem: Windows (10) 11 (64-Bit)
      • Hardware: Interner Bluetooth-Chip oder ein standardmäßiger USB-Bluetooth-Stick am Rechner.
      • Kompatibilität: Getestet mit Zendure-Geräten bis zur 2400er-Serie (sowohl ältere als auch neueste Generationen / Firmware-Stände).

      Inhalt des Programmpakets (zURLtoolV20.zip):
      Das ZIP-Archiv enthält genau drei Dateien, die zwingend im selben Ordner liegen bleiben müssen:
      • zURLtool_V20.exe (Größe: ca. 15.5 MB / 15555 KB)
      • config.ini (Konfigurationsdatei für eure Zugangsdaten)
      • ReadMe_zURLtoolV20.txt (Ausführliche Anleitung in Deutsch & Englisch)

      Was genau macht das zURLtool?
      Das Tool schreibt im Zendure-Gerät die Konfiguration der Broker-URL um.

      • Es wird NICHT in die Firmware eingegriffen und es wird nichts modifiziert. Die originale Firmware von Zendure bleibt zu 100 % unangetastet.

      • Es werden lediglich die standardmäßigen Bluetooth-Befehle verwendet, die die Firmware von Haus aus für die App bereitstellt.

      • Das Skript verhält sich für das Gerät exakt wie die originale App, greift auf die Konfigurationsvariablen im Speicher zu und überschreibt dort die Wi-Fi-Daten und den Eintrag für die Cloud-URL mit der IP eures eigenen lokalen Brokers.

      Es funktioniert quasi wie eine zusätzliche Länderauswahl, bei der man sich einfach mit einem anderen Server verbindet.
      Das bietet die Firmware von Haus aus an, da die Geräte sonst permanent fest an ganz bestimmte AWS-Server gebunden wären.
      Zurück zur Cloud ist auch möglich. Beispiele sind in der config.ini enthalten.

      Die Geräte verbinden sich und können beschrieben werden ohne hartes Reset und ohne hartes Trennen von allen Stromquellen.

      Freigabe vereinfacht.
      Wer schon eine Seriennummer und Freischalt-Code für zURLtool V1.0 dch hat, kann diese verwenden.
      Seriennummer und Freischalt-Code bleiben gültig, eine erneute Anfrage für zURLtool V2.0 ist nicht erforderlich.

      Der Freischalt-Ablauf:
      Damit das Programm funktioniert und um anonyme Massen-Weiterreichungen zu unterbinden, benötigt das Programm eine Seriennummer und einen Freigabe-Code.

      1. Sende mir deinen (Foren-) Namen per privater Nachricht (PN) im Forum oder per E-Mail an: ztool.maxclaudi AT gmail . com
      2. Du erhältst von mir eine persönliche "Seriennummer" und den dazu passenden "Freigabe-Code"
      3. Sichere dir die Seriennummer und den Freigabe-Code unbedingt in einer Textdatei als Backup! Falls du die Datei mal verlierst, hast du deine Seriennummer und deinen Freigabe-Code als Backup griffbereit.
      4. Starte die 'zURLtool_GUI.exe'.

      Trage beide Werte exakt so, wie du sie erhalten hast, in die dafür vorgesehenen Felder im Startfenster ein und klicke auf "Akzeptieren & Starten".
      Nach einmaliger Eingabe startet zURLtool zukünftig vollautomatisch in die Hauptoberfläche.

      Als Notfall hast du zusätzlich Seriennummer und Freigabecode als Backup.
      Vor dem ersten Start muss bzw. kann die config.ini in einem einfachen Text-Editor mit euren echten Daten befüllt werden.
      Auswählbare Beispiele und Erklärungen zu den WLAN-Modi sind direkt darin enthalten.

      Wenn keine config.ini vorhanden ist oder nicht benutzt wird, müssen die Daten in die GUI eingegeben werden.

      Warum ist das Programm Closed Source?
      Zu oft wurden meine Skripte, Codes oder Teile daraus in der Vergangenheit verwendet und/oder modifiziert – ohne Nennung meines Namens, ohne Danksagung / Anerkennung und unter Entfernung meiner originalen Hinweise.
      Entweder wurde (leicht abgeändert) einfach "übernommen" oder u. a. als eigener Erfolg ausgegeben oder kommentiert mit "ich habe einen Weg gefunden".
      Das ist ungerecht.

      Monatelange harte Arbeit sollte zumindest anerkannt werden.

      Sollte das Programm oder der Code in irgendeiner Form gehackt zur Verfügung gestellt oder verwendet werden, dann war dies mein letzter offizieller Beitrag in Form von Code für die Community.
      Das gilt auch wenn das Programm inkl. Seriennummer und Freigabe-Code mehrfach geteilt wird.

      Viel Spaß mit dem zURLtool V2.0 dch!


      01-eng.png

      02-eng.png

      04-eng.png

      05-eng.png


      Bitte ReadMe_zURLtoolV20.txt beachten, enthält die deutsche und englische Beschreibung.

      HINWEIS FÜR BESTEHENDE NUTZER:
      Wer bereits für die Version 1.0 eine Seriennummer und einen Freischalt-Code erhalten hat, kann diese Daten direkt weiterverwenden!
      Sie sind auch für die Version 2.0 gültig.
      Bitte in diesem Fall keine erneute Anfrage.

      RECHTLICHER HINWEIS ZUR FREIGABE:
      Eine Weitergabe von Seriennummer und Freigabe-Code
      sowie deren Mehrfachnutzung durch verschiedene Personen ist ausdrücklich UNZULÄSSIG.


      Please note ReadMe_zURLtoolV20.txt: contains the German and English description.

      NOTE FOR EXISTING USERS:
      If you have already received a serial number and activation code for version 1.0, you can reuse them directly!
      They are valid for version 2.0 as well. Please do not submit a new request in this case.

      LEGAL NOTICE ON ACTIVATION:
      Sharing the serial number and activation code, as well as their multiple use by different individuals, is strictly PROHIBITED.

      maxclaudiM
      maxclaudiM
      maxclaudi
      schrieb am zuletzt editiert von
      #686

      zURLtool goes to ESP32!

      Zuerst wollte ich eine Android-App schreiben.
      Außerdem war im Hinterstübchen immer noch die Idee für ein eigenständiges BT-MQTT-Gateway vorhanden.

      Dann dachte ich an Nebelkerzenwerfer mit Verschwörungstheorien, was mein Windows-Programm angeblich alles auf dem PC anstellen könnte.
      Open-Source ist für mich in diesem Umfeld leider nicht mehr möglich, weil ich es nicht einsehe, dass meine Erkenntnisse und mein Code ungefragt übernommen werden, ohne dass auch nur eine einzige Namensnennung oder ein Danke erfolgt.

      Heute Mittag kam mir dann die Idee, eine zURLtool-Firmware für einen ESP32 mit integriertem Webserver zu schreiben.
      In einer Schublade lag noch ein passendes ESP32-Board mit 4MB Flash.

      Also ab ans Proggen...

      Ich habe eine einfache Prototyp-Benutzeroberfläche als Website erstellt (natürlich auch Smartphone-tauglich) und den Code für den ESP32 umgesetzt.

      Die .bin-Datei wurde erstellt, kompiliert, geflasht und ... NICHTS.

      Es wurde kein Access Point zur Verfügung gestellt und der Chip blieb stumm.

      Dann kam mir der Gedanke an diese günstigen ESP32-Entwicklungsboards und deren Empfindlichkeit gegenüber Spannungsspitzen. Fehlende oder zu kleine Blockkondensatoren (C)?

      In der Tat, das war das Problem! Ich habe zwei noch vorhandene Elkos auf die Schnelle hinten auf das Board gelötet: einen 220µF-Kondensator an die 3V3-Schiene und einen 10µF-Kondensator an die 5V-Leitung.

      ESP32_02.jpeg.jpeg

      ESP32_01.jpeg

      Dazu noch ein gutes 5V DC 2.4A Mean Well Hutschienen-Netzteil über ein altes Kabel mit dem Micro-USB-Stecker verbunden und ...

      ESP32_03.jpeg

      ESP32_04.jpeg

      läuft! :-)

      Es wird also wahrscheinlich demnächst eine zURLtool-Firmware für den ESP32 geben.

      Damit ist für das Umschreiben der Geräte-Konfiguration kein Notebook mit Bluetooth, keine EXE und keine Zusatzsoftware mehr nötig.

      Das eigene Heimnetzwerk, ein Smartphone oder ein beliebiger Rechner reichen dann völlig aus – zusammen mit einem ESP32, zwei Kondensatoren und einem 5V-Netzteil.

      endlich schlafen :-)

      Allen eine erholsame und gute Nacht ;-)

      Zendure zenSDK Lokal API, SmartMode, SolarFlow AC 800 Pro 2

      neue Zendure Geräte Cloudless: zURLtool V2.0

      1 Antwort Letzte Antwort
      1
      • maxclaudiM maxclaudi

        @Daniel-8
        Alles klar, dann passt das ja perfekt für dein Setup :-)
        Es ist auf jeden Fall gut, dass das für alle Mitleser hier im Thread dokumentiert ist.

        Sobald mit der Sonne morgens die MCU erwacht, läuft das Skript ja wie gewohnt weiter.

        Ein zusätzliches Isolieren der Batterie(n) wäre zu empfehlen, wenn schon im Oktober nur 8°C erreicht werden.

        maxclaudiM
        maxclaudiM
        maxclaudi
        schrieb am zuletzt editiert von maxclaudi
        #687

        03.11.2026
        (Testgeräte HUB2000 / Hyper 2000 / 1600AC+ / 2400 ACpro)

        Technische Beobachtung in aktuellen Zendure-Firmware-Versionen: 

        Es fällt auf, dass das Verhalten bei (Cloud-)MQTT-Publishes angepasst wurde, was das Echtzeit-Streaming von Werten betrifft. Alle Beobachtungen beziehen sich hierbei auf die properties/report-Meldungen: 

        • Früher: Sobald sich Werte von PV-Modulen oder Batterien änderten, wurden diese sofort per MQTT (in Echtzeit) im Report gemeldet.

        • Jetzt: Unabhängig von tatsächlichen Wertänderungen wird die properties/report-Meldung nun starr in einem festen Intervall von 5000 ms publiziert. Werte scheinen dort gesammelt übertragen zu werden.

        • Aktive Abfrage / externe Befehle: Werden explizite Anfragen oder Befehle an das Gerät gerichtet, erfolgt die Antwort-Veröffentlichung und Änderung – wie bisher – sofort.

        • HEMS-Ausnahme: Ist HEMS „in der App aktiviert“ und die Steuerung läuft über die Cloud, scheint es Ausnahmen von dieser Begrenzung zu geben. Zum Teil läuft es scheinbar wie zuvor, damit vermutlich schneller geregelt werden kann (Wertänderungen werden schneller gesendet als das Intervall).

        edit 05.11.2026 10:15h
        Ob bei deaktiviertem HEMS bei extrem drastischen Wertsprüngen zwischendurch doch ein Sofort-Publish vom Gerät getriggert wird, weiß ich (noch) nicht.
        Ich kann es nur hoffen und werde das Verhalten weiter beobachten und versuchen, es in zwei Firmware-Versionen zu analysieren.
         

        • bei wechselhafter PV-Leistung werden die Werte schneller via MQTT veröffentlicht (Sofort-Publish). Das Gerät reagiert demnach dynamisch auf starke Leistungsschwankungen, auch ohne aktives HEMS.

        • zusätzlich bisher unbekannter Schalter (enable/false) in der Firmware identifiziert. Der Status dieses Schalters lässt sich direkt über ein MQTT-Publish steuern.
          edit-Ende

        Die Anpassung dient vermutlich der Reduzierung des Traffics der Geräte zur Cloud und der Entlastung des ESP32. 

        Die aktuellen Firmware-Versionen von HUB2000, ace1500 und die letzte Firmware-Version vom Hyper < 2 (vor TLS) sind von dieser Änderung nach meinem Kenntnisstand und den vorhandenen Test-Geräten nicht betroffen.

        Zendure zenSDK Lokal API, SmartMode, SolarFlow AC 800 Pro 2

        neue Zendure Geräte Cloudless: zURLtool V2.0

        maxclaudiM 1 Antwort Letzte Antwort
        0
        • maxclaudiM maxclaudi

          03.11.2026
          (Testgeräte HUB2000 / Hyper 2000 / 1600AC+ / 2400 ACpro)

          Technische Beobachtung in aktuellen Zendure-Firmware-Versionen: 

          Es fällt auf, dass das Verhalten bei (Cloud-)MQTT-Publishes angepasst wurde, was das Echtzeit-Streaming von Werten betrifft. Alle Beobachtungen beziehen sich hierbei auf die properties/report-Meldungen: 

          • Früher: Sobald sich Werte von PV-Modulen oder Batterien änderten, wurden diese sofort per MQTT (in Echtzeit) im Report gemeldet.

          • Jetzt: Unabhängig von tatsächlichen Wertänderungen wird die properties/report-Meldung nun starr in einem festen Intervall von 5000 ms publiziert. Werte scheinen dort gesammelt übertragen zu werden.

          • Aktive Abfrage / externe Befehle: Werden explizite Anfragen oder Befehle an das Gerät gerichtet, erfolgt die Antwort-Veröffentlichung und Änderung – wie bisher – sofort.

          • HEMS-Ausnahme: Ist HEMS „in der App aktiviert“ und die Steuerung läuft über die Cloud, scheint es Ausnahmen von dieser Begrenzung zu geben. Zum Teil läuft es scheinbar wie zuvor, damit vermutlich schneller geregelt werden kann (Wertänderungen werden schneller gesendet als das Intervall).

          edit 05.11.2026 10:15h
          Ob bei deaktiviertem HEMS bei extrem drastischen Wertsprüngen zwischendurch doch ein Sofort-Publish vom Gerät getriggert wird, weiß ich (noch) nicht.
          Ich kann es nur hoffen und werde das Verhalten weiter beobachten und versuchen, es in zwei Firmware-Versionen zu analysieren.
           

          • bei wechselhafter PV-Leistung werden die Werte schneller via MQTT veröffentlicht (Sofort-Publish). Das Gerät reagiert demnach dynamisch auf starke Leistungsschwankungen, auch ohne aktives HEMS.

          • zusätzlich bisher unbekannter Schalter (enable/false) in der Firmware identifiziert. Der Status dieses Schalters lässt sich direkt über ein MQTT-Publish steuern.
            edit-Ende

          Die Anpassung dient vermutlich der Reduzierung des Traffics der Geräte zur Cloud und der Entlastung des ESP32. 

          Die aktuellen Firmware-Versionen von HUB2000, ace1500 und die letzte Firmware-Version vom Hyper < 2 (vor TLS) sind von dieser Änderung nach meinem Kenntnisstand und den vorhandenen Test-Geräten nicht betroffen.

          maxclaudiM
          maxclaudiM
          maxclaudi
          schrieb am zuletzt editiert von maxclaudi
          #688

          Heute hatte ich wieder einen langen Tag.

          Ein Zendure-Gerät und dafür extra einen Broker aufsetzen?

          Jemand wollte sein Zendure-Gerät (mit TLS) cloudfrei betreiben und direkt mit dem ioBroker-MQTT-Adapter verbinden.

          Bedingungen:

          • Zendure-Gerät (TLS)soll sich mit MQTT-Adapter verbinden
          • MQTT und zenSDK Steurung sollen möglich sein
          • Cloudfrei
          • auf keinen Fall extra einen eigenen Broker wie Mosquitto aufsetzen
          • Setup sollte simpel, leicht änderbar und direkt einsatzbereit sein
          • nichts kompliziertes

          Dachte: nicht realisierbar.
          Wo fängt man da an?

          Nach einigem Grübeln habe ich mir angeschaut was vorhanden ist und was genutzt werden kann und angefangen zu proggen.

          Viele Stunden später....

          Ergebnis: eine echte eierlegende Wollmilchsau:
          TLS-Entschlüsselung und Plain-Brücke für ioBroker in einem.

          Das absolut Sensationelle daran:
          Der ESP32 verhält sich wie ein Mosquitto-Broker (inklusive Echos und RAM-geschütztem Retain-Speicher) und das mit nur 4MB Flash.
          Konnte dafür keine 3rd Party Libs verwenden.
          Alle verfügbaren Libs hatten entweder Fehler, waren aufgebläht oder die Funktionen wurden nicht unterstützt.

          Also wieder ans Eingemachte....
          Der Code baut nur auf den nackten Bordmitteln des ESP-IDF-Frameworks (bzw. Arduino-Core) auf.
          Schlanker und effizienter geht es kaum.

          00f-kompiliert.png

          Ich dachte selbst erst nicht, dass es klappt ohne PSRAM und mit nur 4MB Flash, aber das Ergebnis läuft stabil und sicher.

          Funktionierendes Multi-User-System:

          • 2x TLS-Clients (Port 8883): für bis zu zwei Zendure-Geräte.

          • 3x Plain-Clients (Port 1883): ohne TLS, z. B. für den ioBroker-Adapter, MQTT-Explorer und MQTTX.

          Ein separater MQTT-Broker wird damit komplett überflüssig.
          Die Konfiguration (WLAN und TLS-Zugangsdaten) erfolgt einfach einmalig seriell über USB am Rechner, z.B. mit Arduino-Serial-Monitor oder PuTTY.

          Man kann im laufenden Betrieb:
          • "setup" eingeben und schnell Daten ändern.
          • "status" eingeben und man erhält Infos (siehe Bilder)

          01f-setup-ESP-seriell-MQTTbroker.png

          02f-setup-ESP-seriell-MQTTbroker.png

          04f-setup-ESP-seriell-MQTTbroker.png

          05f-laufenderBetrieb-ESP-seriell-MQTTbroker.png

          Also wurde nur kurz zURLtool benutzt und nun übernimmt ein alter Standard ESP32 mit 4MB flash (noch mit Micro-USB) den Broker.
          Bild im Spoiler

          Darf ich vorstellen :-) ... ein vollwertiger TLS-Broker für Zendure Geräte:

          image.jpeg

          .
          Einrichtung von Zendure-Gerät und ESP32-Broker komplett in ca. 10min.

          Wahnsinn.... Ziel erreicht!

          Wieder so spät, hoffe Popcorn ist noch vorhanden ;-)

          und wieder Allen eine erholsame und gute Nacht ;-)

          Zendure zenSDK Lokal API, SmartMode, SolarFlow AC 800 Pro 2

          neue Zendure Geräte Cloudless: zURLtool V2.0

          maxclaudiM 1 Antwort Letzte Antwort
          1
          • maxclaudiM maxclaudi

            Heute hatte ich wieder einen langen Tag.

            Ein Zendure-Gerät und dafür extra einen Broker aufsetzen?

            Jemand wollte sein Zendure-Gerät (mit TLS) cloudfrei betreiben und direkt mit dem ioBroker-MQTT-Adapter verbinden.

            Bedingungen:

            • Zendure-Gerät (TLS)soll sich mit MQTT-Adapter verbinden
            • MQTT und zenSDK Steurung sollen möglich sein
            • Cloudfrei
            • auf keinen Fall extra einen eigenen Broker wie Mosquitto aufsetzen
            • Setup sollte simpel, leicht änderbar und direkt einsatzbereit sein
            • nichts kompliziertes

            Dachte: nicht realisierbar.
            Wo fängt man da an?

            Nach einigem Grübeln habe ich mir angeschaut was vorhanden ist und was genutzt werden kann und angefangen zu proggen.

            Viele Stunden später....

            Ergebnis: eine echte eierlegende Wollmilchsau:
            TLS-Entschlüsselung und Plain-Brücke für ioBroker in einem.

            Das absolut Sensationelle daran:
            Der ESP32 verhält sich wie ein Mosquitto-Broker (inklusive Echos und RAM-geschütztem Retain-Speicher) und das mit nur 4MB Flash.
            Konnte dafür keine 3rd Party Libs verwenden.
            Alle verfügbaren Libs hatten entweder Fehler, waren aufgebläht oder die Funktionen wurden nicht unterstützt.

            Also wieder ans Eingemachte....
            Der Code baut nur auf den nackten Bordmitteln des ESP-IDF-Frameworks (bzw. Arduino-Core) auf.
            Schlanker und effizienter geht es kaum.

            00f-kompiliert.png

            Ich dachte selbst erst nicht, dass es klappt ohne PSRAM und mit nur 4MB Flash, aber das Ergebnis läuft stabil und sicher.

            Funktionierendes Multi-User-System:

            • 2x TLS-Clients (Port 8883): für bis zu zwei Zendure-Geräte.

            • 3x Plain-Clients (Port 1883): ohne TLS, z. B. für den ioBroker-Adapter, MQTT-Explorer und MQTTX.

            Ein separater MQTT-Broker wird damit komplett überflüssig.
            Die Konfiguration (WLAN und TLS-Zugangsdaten) erfolgt einfach einmalig seriell über USB am Rechner, z.B. mit Arduino-Serial-Monitor oder PuTTY.

            Man kann im laufenden Betrieb:
            • "setup" eingeben und schnell Daten ändern.
            • "status" eingeben und man erhält Infos (siehe Bilder)

            01f-setup-ESP-seriell-MQTTbroker.png

            02f-setup-ESP-seriell-MQTTbroker.png

            04f-setup-ESP-seriell-MQTTbroker.png

            05f-laufenderBetrieb-ESP-seriell-MQTTbroker.png

            Also wurde nur kurz zURLtool benutzt und nun übernimmt ein alter Standard ESP32 mit 4MB flash (noch mit Micro-USB) den Broker.
            Bild im Spoiler

            Darf ich vorstellen :-) ... ein vollwertiger TLS-Broker für Zendure Geräte:

            image.jpeg

            .
            Einrichtung von Zendure-Gerät und ESP32-Broker komplett in ca. 10min.

            Wahnsinn.... Ziel erreicht!

            Wieder so spät, hoffe Popcorn ist noch vorhanden ;-)

            und wieder Allen eine erholsame und gute Nacht ;-)

            maxclaudiM
            maxclaudiM
            maxclaudi
            schrieb am zuletzt editiert von maxclaudi
            #689

            Wünsche allen ein schönes Wochenende :-)

            Vielleicht ist jemandem langweilig oder möchte wissen, was ich momentan teste.
            Roman zum Wochenende und vielleicht Interesse an einem Projekt?

            ja? Dann spoiler lesen ;-) ...ansonsten nicht weiter beachten.

            ESP32 WROOM 4MB FLASH als lokaler TLS-Broker läuft "bis jetzt" unauffällig, schnell, stabil und zuverlässig.
            Nach TLS-Verbindung mit dem zenGerät scheint er sich beinahe zu langweilen.
            Auch wenn man ihn per Skript mit unnötigen und häufigen Anfragen zu fluten versucht...
            ist er nicht aus der Ruhe zu bringen und arbeitet weiter.
            Mit 1 TLS-Client und 3 Clients über 1883 (iobroker-Adapter, MQTTX und MQTT-Explorer) funktioniert es gut.
            Bleibt weiterhin im Test-Run.

            Das hat mir keine Ruhe gelassen.
            Vielleicht geht noch ein wenig mehr... los ging's.


            Beta-Status erreicht und die eierlegende Wollmilchsau ist seit Stunden im Testbetrieb.
            Bin gespannt, ob das stabil und dauerhaft läuft.

            Hardware:
            ESP32 WROOM D0WDQ6 Revision v1.0 4MB FLASH
            altes Board aus der Schublade, siehe Bild.

            image.jpeg

            Wofür? Was ist das?

            Der ESP32 ist:
            • TLS-Client zu einer anderen DNS/CLOUD als Bridge
            • ein TLS-Broker Port: 8883 für 1 TLS-Client zenGerät
            • ein Broker Port: 1883 für maximal 3 zusätzliche Clients mit jeweils User + Passwort
            Z. B. für iobroker-MQTT Broker/Client-Adapter v8.1.2, MQTTX und MQTT-Explorer


            Was wird benötigt?

            • 1 Zendure-Gerät (z. B. 800 pro plus / 1600 AC+ / 2400AC pro plus)
            • iobroker Adapter MQTT Broker/Client Adapter v8.1.2 als Client
            • ESP32 mit 4MB FLASH
            • irgendein Kunststoffgehäuse für den ESP32
            • gutes Netzteil für den ESP32 mit stabilen 5V mit "reinem" geglätteten DC-Ausgang und lieber zu viel A als zu wenig.
            • Schalter (Schliesser) oder Brücke.
            • zURLtool oder AdGuard oder Pi-hole, um die DNS des zenGeräts auf die IP des ESP32 umzukonfigurieren.
            • WLAN-Router (Fritzbox etc.) oder WLAN-AP mit DHCP


            Wie soll das funktionieren?

            Siehe Bilder.

            001.png

            003.png

            Nachdem der ESP geflasht ist, öffnet man eine serielle Konsole (Visual Studio Code PlatformIO / PuTTY oder Arduino serieller Monitor etc.).

            004.png

            • Ist der Chip nicht konfiguriert, landet man automatisch im Setup.
            • Ist der Chip bereits konfiguriert, hat man beim Starten 3 Sek. Zeit, um ins Setup zu kommen.
            Z. B. nach RESET durch die Reset-Taste.

            005.png

            Dann kommen die Abfragen.
            Betätigt man nur die Enter-Taste, wird nichts geändert.
            Ansonsten gibt man entweder ein, was geändert werden soll, und betätigt danach die Enter-Taste,
            oder man gibt nur das Minuszeichen ein und drückt die Enter-Taste, um den aktuellen Wert zu löschen.

            006.png

            Zur Konfiguration kann man sich zuvor alle Daten in einem Textfile vorbereiten
            und dann per Copy-Paste einfach jeweils einfügen.
            Es ist ein reines zenGateway (Bridge) und nur für 1 zenGerät.

            Erforderliche Daten:
            Es muss mindestens ProductKey und DeviceID eines Geräts eingegeben werden.

            Der Reihe nach wird abgefragt:
            WLAN-SSID
            WLAN-Passwort
            CLOUD-DNS
            ProductKey
            DeviceID

            und danach für den Port: 1883 jeweils gewünschte User + Passwort

            006.png

            Am Schluss wird noch der LOG-LEVEL abgefragt.
            Sollte das System absolut stabil laufen, dann 0 setzen.

            Das war es, mehr ist nicht nötig :-)

            Danach verbindet sich der ESP mit dem Heim-WLAN und zeigt seine bezogene, aktuelle IP an.

            Die angezeigte IP im Internetrouter (z. B. Fritzbox) dauerhaft festlegen.

            Dann kann man den ESP trennen, an ein besseres Netzteil anschließen und am besten in der Nähe des WLAN-Routers platzieren.

            Wenn ein Schalter (Schließer) zwischen GPIO: 17 und GND angeschlossen wird, kann man umschalten.
            Ist kein Schalter angeschlossen, dann ist der ESP im Bridge-Betrieb zur Cloud.
            Ist ein Schalter (Schließer) an GPIO:17 und GND angeschlossen und eingeschaltet ODER eine Brücke von GPIO:17 zu GND vorhanden, dann wird die Cloud-Verbindung getrennt und das zenGerät ist rein lokal mit dem ESP32 als Broker verbunden mit lokalen Client(s).

            Man kann also zwischen Cloud oder lokal physisch umschalten.


            Aktuelle Analyse auch als Memo für mich:

            Zum Status:
            Unnötige Cloud-Echos werden ausgewertet und gefiltert.
            Dass die ms größer werden, ist gut und hat seinen Grund.

            Der errno: 104, "Connection reset by peer":
            [STATUS] IP 192.168.50.153 | Clients 4 | Switch-Cloud: AKTIV | Cloud-TLS VERBUNDEN | Gerät aktiv: JA | Heap frei 129276 | Min 102428 | Max-Block 69620 | Cloud-Echo 1563
            [4297115][E][WiFiClient.cpp:429] write(): fail on fd 50, errno: 104, "Connection reset by peer"
            [STATUS] IP 192.168.50.153 | Clients 4 | Switch-Cloud: AKTIV | Cloud-TLS VERBUNDEN | Gerät aktiv: JA | Heap frei 128944 | Min 100704 | Max-Block 57332 | Cloud-Echo 2395
            war bewusst erzwungen.

            Schließen ohne DISCONNECT vom MQTT-Explorer und gleich wieder Öffnen + CONNECT.
            Ein Test, wie sich der ESP mit HEAP verhält.

            007.png

            Alles ok, sieht nicht übel aus.
            Werte, auf die es ankommt, sind stabil.

            Heap frei: Er liegt über die ganze Zeit zwischen etwa 129 und 133 KB.
            Er sinkt nicht, sondern pendelt.

            Min: Er ist von 102 KB auf 99,7 KB gefallen, also um etwa 3 KB in mehreren Stunden.
            Min kann nur sinken, und kleine Stufen kommen von einmaligen Spitzen, zum Beispiel beim Wiederaufbau einer Verbindung.
            Problem wäre, wenn er mit jedem Reconnect um ein paar KB weiterfällt und am Ende unter etwa 50 KB landet.

            Max-Block: Der Abfall von 69 bis 73 KB auf 57 KB passt zeitlich zum Test mit der Fehlermeldung write(): fail on fd 50, errno: 104, "Connection reset by peer".
            Verbindung wurde dabei zurückgesetzt.
            ESP und Gegenstelle haben danach neu aufgebaut.
            Die neuen Puffer liegen an anderen Stellen im Speicher.
            Das ist normale Fragmentierung.
            57 KB sind aber weit mehr als 16 KB, die ein neuer TLS-Puffer braucht :-)


            ToDo:
            Vorerst nur laufen lassen und beobachten:

            • Heap sollte um 130 KB frei bleiben.
            • Max-Block sollte über etwa 40 KB bleiben.
            • Min darf nicht bei jedem Reconnect weiter fallen.

            Zum Schluss noch ein Bildausschnitt vom Notebook, auf dem ioBroker, MQTTX und MQTT Explorer gleichzeitig mit dem ESP-Broker verbunden sind, während der 1600AC+ Daten an die Cloud und an alle Clients liefert.

            008.png

            Falls jemand sich bedanken möchte für den Roman, bitte nur voten ;-)
            Ein extra Post ist dafür nicht nötig.

            Wer Fragen oder Interesse an dem Projekt hat, bitte schön :-)

            Liebe Grüße maxclaudi

            Zendure zenSDK Lokal API, SmartMode, SolarFlow AC 800 Pro 2

            neue Zendure Geräte Cloudless: zURLtool V2.0

            maxclaudiM 1 Antwort Letzte Antwort
            2
            • maxclaudiM maxclaudi

              Wünsche allen ein schönes Wochenende :-)

              Vielleicht ist jemandem langweilig oder möchte wissen, was ich momentan teste.
              Roman zum Wochenende und vielleicht Interesse an einem Projekt?

              ja? Dann spoiler lesen ;-) ...ansonsten nicht weiter beachten.

              ESP32 WROOM 4MB FLASH als lokaler TLS-Broker läuft "bis jetzt" unauffällig, schnell, stabil und zuverlässig.
              Nach TLS-Verbindung mit dem zenGerät scheint er sich beinahe zu langweilen.
              Auch wenn man ihn per Skript mit unnötigen und häufigen Anfragen zu fluten versucht...
              ist er nicht aus der Ruhe zu bringen und arbeitet weiter.
              Mit 1 TLS-Client und 3 Clients über 1883 (iobroker-Adapter, MQTTX und MQTT-Explorer) funktioniert es gut.
              Bleibt weiterhin im Test-Run.

              Das hat mir keine Ruhe gelassen.
              Vielleicht geht noch ein wenig mehr... los ging's.


              Beta-Status erreicht und die eierlegende Wollmilchsau ist seit Stunden im Testbetrieb.
              Bin gespannt, ob das stabil und dauerhaft läuft.

              Hardware:
              ESP32 WROOM D0WDQ6 Revision v1.0 4MB FLASH
              altes Board aus der Schublade, siehe Bild.

              image.jpeg

              Wofür? Was ist das?

              Der ESP32 ist:
              • TLS-Client zu einer anderen DNS/CLOUD als Bridge
              • ein TLS-Broker Port: 8883 für 1 TLS-Client zenGerät
              • ein Broker Port: 1883 für maximal 3 zusätzliche Clients mit jeweils User + Passwort
              Z. B. für iobroker-MQTT Broker/Client-Adapter v8.1.2, MQTTX und MQTT-Explorer


              Was wird benötigt?

              • 1 Zendure-Gerät (z. B. 800 pro plus / 1600 AC+ / 2400AC pro plus)
              • iobroker Adapter MQTT Broker/Client Adapter v8.1.2 als Client
              • ESP32 mit 4MB FLASH
              • irgendein Kunststoffgehäuse für den ESP32
              • gutes Netzteil für den ESP32 mit stabilen 5V mit "reinem" geglätteten DC-Ausgang und lieber zu viel A als zu wenig.
              • Schalter (Schliesser) oder Brücke.
              • zURLtool oder AdGuard oder Pi-hole, um die DNS des zenGeräts auf die IP des ESP32 umzukonfigurieren.
              • WLAN-Router (Fritzbox etc.) oder WLAN-AP mit DHCP


              Wie soll das funktionieren?

              Siehe Bilder.

              001.png

              003.png

              Nachdem der ESP geflasht ist, öffnet man eine serielle Konsole (Visual Studio Code PlatformIO / PuTTY oder Arduino serieller Monitor etc.).

              004.png

              • Ist der Chip nicht konfiguriert, landet man automatisch im Setup.
              • Ist der Chip bereits konfiguriert, hat man beim Starten 3 Sek. Zeit, um ins Setup zu kommen.
              Z. B. nach RESET durch die Reset-Taste.

              005.png

              Dann kommen die Abfragen.
              Betätigt man nur die Enter-Taste, wird nichts geändert.
              Ansonsten gibt man entweder ein, was geändert werden soll, und betätigt danach die Enter-Taste,
              oder man gibt nur das Minuszeichen ein und drückt die Enter-Taste, um den aktuellen Wert zu löschen.

              006.png

              Zur Konfiguration kann man sich zuvor alle Daten in einem Textfile vorbereiten
              und dann per Copy-Paste einfach jeweils einfügen.
              Es ist ein reines zenGateway (Bridge) und nur für 1 zenGerät.

              Erforderliche Daten:
              Es muss mindestens ProductKey und DeviceID eines Geräts eingegeben werden.

              Der Reihe nach wird abgefragt:
              WLAN-SSID
              WLAN-Passwort
              CLOUD-DNS
              ProductKey
              DeviceID

              und danach für den Port: 1883 jeweils gewünschte User + Passwort

              006.png

              Am Schluss wird noch der LOG-LEVEL abgefragt.
              Sollte das System absolut stabil laufen, dann 0 setzen.

              Das war es, mehr ist nicht nötig :-)

              Danach verbindet sich der ESP mit dem Heim-WLAN und zeigt seine bezogene, aktuelle IP an.

              Die angezeigte IP im Internetrouter (z. B. Fritzbox) dauerhaft festlegen.

              Dann kann man den ESP trennen, an ein besseres Netzteil anschließen und am besten in der Nähe des WLAN-Routers platzieren.

              Wenn ein Schalter (Schließer) zwischen GPIO: 17 und GND angeschlossen wird, kann man umschalten.
              Ist kein Schalter angeschlossen, dann ist der ESP im Bridge-Betrieb zur Cloud.
              Ist ein Schalter (Schließer) an GPIO:17 und GND angeschlossen und eingeschaltet ODER eine Brücke von GPIO:17 zu GND vorhanden, dann wird die Cloud-Verbindung getrennt und das zenGerät ist rein lokal mit dem ESP32 als Broker verbunden mit lokalen Client(s).

              Man kann also zwischen Cloud oder lokal physisch umschalten.


              Aktuelle Analyse auch als Memo für mich:

              Zum Status:
              Unnötige Cloud-Echos werden ausgewertet und gefiltert.
              Dass die ms größer werden, ist gut und hat seinen Grund.

              Der errno: 104, "Connection reset by peer":
              [STATUS] IP 192.168.50.153 | Clients 4 | Switch-Cloud: AKTIV | Cloud-TLS VERBUNDEN | Gerät aktiv: JA | Heap frei 129276 | Min 102428 | Max-Block 69620 | Cloud-Echo 1563
              [4297115][E][WiFiClient.cpp:429] write(): fail on fd 50, errno: 104, "Connection reset by peer"
              [STATUS] IP 192.168.50.153 | Clients 4 | Switch-Cloud: AKTIV | Cloud-TLS VERBUNDEN | Gerät aktiv: JA | Heap frei 128944 | Min 100704 | Max-Block 57332 | Cloud-Echo 2395
              war bewusst erzwungen.

              Schließen ohne DISCONNECT vom MQTT-Explorer und gleich wieder Öffnen + CONNECT.
              Ein Test, wie sich der ESP mit HEAP verhält.

              007.png

              Alles ok, sieht nicht übel aus.
              Werte, auf die es ankommt, sind stabil.

              Heap frei: Er liegt über die ganze Zeit zwischen etwa 129 und 133 KB.
              Er sinkt nicht, sondern pendelt.

              Min: Er ist von 102 KB auf 99,7 KB gefallen, also um etwa 3 KB in mehreren Stunden.
              Min kann nur sinken, und kleine Stufen kommen von einmaligen Spitzen, zum Beispiel beim Wiederaufbau einer Verbindung.
              Problem wäre, wenn er mit jedem Reconnect um ein paar KB weiterfällt und am Ende unter etwa 50 KB landet.

              Max-Block: Der Abfall von 69 bis 73 KB auf 57 KB passt zeitlich zum Test mit der Fehlermeldung write(): fail on fd 50, errno: 104, "Connection reset by peer".
              Verbindung wurde dabei zurückgesetzt.
              ESP und Gegenstelle haben danach neu aufgebaut.
              Die neuen Puffer liegen an anderen Stellen im Speicher.
              Das ist normale Fragmentierung.
              57 KB sind aber weit mehr als 16 KB, die ein neuer TLS-Puffer braucht :-)


              ToDo:
              Vorerst nur laufen lassen und beobachten:

              • Heap sollte um 130 KB frei bleiben.
              • Max-Block sollte über etwa 40 KB bleiben.
              • Min darf nicht bei jedem Reconnect weiter fallen.

              Zum Schluss noch ein Bildausschnitt vom Notebook, auf dem ioBroker, MQTTX und MQTT Explorer gleichzeitig mit dem ESP-Broker verbunden sind, während der 1600AC+ Daten an die Cloud und an alle Clients liefert.

              008.png

              Falls jemand sich bedanken möchte für den Roman, bitte nur voten ;-)
              Ein extra Post ist dafür nicht nötig.

              Wer Fragen oder Interesse an dem Projekt hat, bitte schön :-)

              Liebe Grüße maxclaudi

              maxclaudiM
              maxclaudiM
              maxclaudi
              schrieb am zuletzt editiert von maxclaudi
              #690

              ...manchmal möchte man mehr, und nun ist es gelungen und gut. :-)

              NEU: nur noch APP und ESP32-Board WT32-ETH01 werden benötigt.

              Ein zenGerät lokal über MQTT verbinden und in Echtzeit steuern:
              • ohne extra Broker
              • wahlweise lokal isoliert oder innerhalb von Sekunden umgeschaltet ins Internet zur Cloud mit einfachem Befehl.
              • wahlweise zenSDK oder echtes, schnelles MQTT (nicht das lokale HA-MQTT!)
              • Nur ESP32 WT32-ETH01 und zenApp zur einmaligen Einrichtung werden benötigt.

                                  +-----+
                                  |ESP32| <- LAN -> |Verbindung aus- einschaltbar | <- PORT 8883 mit TLS -> Router (z. B. FritBox) <- Internet-> TLS-Cloud
              zenGerät <- WLAN -> |WT32-| <- LAN -> PORT 1883 ohne TLS lokal  z. B. iobroker MQTT-Adapter als Client lokale Steuerung
                                  |ETH01| <- LAN -> PORT 1883 ohne TLS lokal Reserve z.B. gelegentlich MQTT-Explorer
              		    |W/LAN| <- LAN -> PORT 1883 ohne TLS lokal Reserve z. B. gelegentlich MQTTX
              		    +-----+
              

              Final und läuft seit heute, 07.11.2026 21:00 Uhr, im ununterbrochenen Dauertest.

              Wer Interesse hat, liest den Spoiler, sonst einfach ignorieren.

              edit/ps 08.11.2026 16:30h

              Steuerung isoliert lokal oder mit Cloud.
              Einfach umschaltbar in Sekunden.
              Alles ohne externen Broker, ohne AdGuard, ohne Pi-Hole etc. und ohne Router-Tricks. Internetsperre nicht nötig.

              Was wird benötigt?

              • zenGerät
              • MQTT-Client z.B. ioBroker MQTT Broker/Client v8.1.2 - Adapter
              • ESP32-Board WT32-ETH01 (Empfehlung: mit >= 100µF Elko).
              • Smartphone mit zenApp
              • LAN in der Nähe des zenGeräts

              Habe noch einige PCB von einem anderen Projekt

              01-eth01.png


              Ersteinrichtung ESP32 kinderleicht - einmalig über die serielle Konsole.

              Eingegeben werden folgende Daten:

              • CLOUD DNS
              • productKey
              • userID
              • die ( maximal 3) Client - Konten PORT 1883 ohne TLS
              • SSID-WLAN-AP: für ESP32 WT32-ETH01
              • WLAN-Password-AP: für ESP32 WT32-ETH01
              • LOG-Level (bei Normal-Betrieb: 0)

              02-setup.png

              Alles Weitere wird vollautomatisch erledigt.


              Einrichtung des zenGeräts:

              • Smartphone BT einschalten
              • zenAPP öffnen.
              • In der App Netzwerk einrichten:
                Netzwerk auswählen: SSID-WLAN-AP vom ESP32 WT32-ETH01
                Passwort: vom WLAN-Password-AP eingeben.

              verbinden und drin. "einfach drin" - kinderleicht :-)

              03-AP-ZenAPP.png

              WLAN-AP setzt automatisch: 192.168.4.1 und lässt nur 1 Gerät zu.

              Durch die Platzierung direkt neben dem 1600AC+ hat das Gerät eine super Verbindung bei -35 dBm ohne paketbasierte Latenzen:

              04-eth32-iobroker.png


              Highlights & Funktion

              1. Dedizierter LAN- & AP-Betrieb
              ESP32 bezieht seine Daten und - wenn man möchte - Internetverbindung stabil über LAN.
              WLAN-Teil wird vom Heimnetz entkoppelt und arbeitet isoliert als WIFI_AP, fest limitiert auf 1 Verbindung für das zenGerät.

              2. Integrierter Mini-DNS-Server
              Ein integrierter DNS-S. leitet auf den ESP-AP mit IP 192.168.4.1 um.
              Gerät verbindet sich mit dem lokalen TLS-Broker des ESP32, während Bridge den Namen über das Heimnetz auflöst.

              3. Integrierte Whitelist für den Bridge-Tunnel
              Sicherheitstechnisch ein Meilenstein:
              Das Gateway besitzt eine integrierte Whitelist.
              Lokale 1883 Clients (ioBroker, MQQTX, MQTT-Explorer etc.) dürfen unverschlüsselt auf Port 1883 Daten austauschen, aber der ESP32 lässt nur die echten zen-topics des Geräts in das Inet.
              Fremde Daten oder lokaler Skript-Traffic werden gefiltert und blockiert, auch wenn die Bridge aktiviert ist.

              4. Statischer Heap-Schutz gegen Fragmentierung
              Speicherarchitektur wurde zur Vermeidung von Fragmentierung im 24/7 auf statische char-Arrays umgestellt.
              Die Funktion parst MQTT-Pakete ohne jede Heap-Allokation direkt in feste Speicherbereiche.
              Heap bleibt bis jetzt konstant.

              5. Software-Bridge-Steuerung über MQTT
              Die Cloud-Bridge wird softwareseitig über ein lokales MQTT-Topic:

              /bridgeMode
              

              gesteuert. Mit

              {"bridgeSwitch":0}
              

              wird die Cloud-Verbindung abgeschaltet und sauber getrennt, während der lokale TLS-Server für den Hub unterbrechungsfrei weiterläuft.

              {"bridgeSwitch":1}
              

              verbindet bei Bedarf wieder mit Cloud. Z. B. falls man doch die App unterwegs benötigt.

              Durch den statischen Stream-Parser und die Reduzierung der Stack-Puffer ist das System für den dauerhaften Betrieb ausgelegt.

              toDo
              Testlauf und nur noch reine Beobachtung.


              Wer vielleich noch einen WT32-ETH01 hat... ;-)

              Falls jemand sich bedanken möchte, bitte nur voten.
              Ein extra Post ist dafür nicht nötig.

              Wer Fragen oder Interesse an dem Projekt hat, bitte schön :-)


              edit/ps 08.11.2026 16:30h

              Ein User bat um meine Hilfe, das Projekt umzusetzen.
              Er hat zwei zenGeräte (SF2400 AC, 1x SF 800 Pro 2) und zwei ESP32-WT32-ETH01.

              Knackpunkt: In der Nähe gibt es kein LAN-Anschluss und das WLAN ist dort nicht gut – meinte er.

              Nach kurzer Unterhaltung und dem Eruieren, was vor Ort gegeben und vorhanden ist, war die Lösung schnell gefunden und umgesetzt.

              Ein alter TP-Link-Router, den er noch herumliegen hatte, war schnell als WLAN-Brücke (Wireless Client Mode) konfiguriert.
              Dieser ist nun per WLAN mit dem Heimnetz (Fritzbox) verbunden.

              Das Signal ist um Welten besser als das von den zenGeräten mit ihren ESP-Chips.
              Vorher lag RSSI im Schnitt bei -79 dBm.
              Der Router mit seinen externen Antennen erreicht nun -58 bis -64 dBm (wird zumindest so angezeigt).
              Der Router hat vier LAN-Buchsen, an die die zwei ESPs angeschlossen wurden.
              Die beiden ESPs sind ca. 30 cm voneinander getrennt und werden gemeinsam über ein 5V-3A-Netzteil versorgt.
              Weil es ein Noname-Netzteil ist, wurde bei beiden ESPs zwischen 5V und GND jeweils ein Elko mit 220 µF angeschlossen.
              Zudem wurde zwischen dem EN-Pin und GND jeweils noch ein Elko mit 100 µF angelötet.

              Ergebnis:
              Es funktioniert besser als zuvor – allein schon, weil die Bridge eine deutlich bessere Verbindung zum WLAN hat als die zenGeräte.
              Die zwei ESP32-WT32-ETH01 befinden sich nun weniger als einen Meter von den zenGeräten entfernt und haben damit natürlich ebenfalls eine Top-Verbindung.

              Läuft! :-)

              Wer noch Fragen hat, bitte schön.
              Wünsche Allen einen wunderschönen Sonntag.

              edit/Ende

              Was für ein Meilenstein in meiner persönlichen zenHistory und weltweit.
              Was für ein gelungenes Wochenende.

              Wünsche Allen eine schöne, gute Nacht :-)

              Zendure zenSDK Lokal API, SmartMode, SolarFlow AC 800 Pro 2

              neue Zendure Geräte Cloudless: zURLtool V2.0

              maxclaudiM 1 Antwort Letzte Antwort
              2
              • maxclaudiM maxclaudi

                ...manchmal möchte man mehr, und nun ist es gelungen und gut. :-)

                NEU: nur noch APP und ESP32-Board WT32-ETH01 werden benötigt.

                Ein zenGerät lokal über MQTT verbinden und in Echtzeit steuern:
                • ohne extra Broker
                • wahlweise lokal isoliert oder innerhalb von Sekunden umgeschaltet ins Internet zur Cloud mit einfachem Befehl.
                • wahlweise zenSDK oder echtes, schnelles MQTT (nicht das lokale HA-MQTT!)
                • Nur ESP32 WT32-ETH01 und zenApp zur einmaligen Einrichtung werden benötigt.

                                    +-----+
                                    |ESP32| <- LAN -> |Verbindung aus- einschaltbar | <- PORT 8883 mit TLS -> Router (z. B. FritBox) <- Internet-> TLS-Cloud
                zenGerät <- WLAN -> |WT32-| <- LAN -> PORT 1883 ohne TLS lokal  z. B. iobroker MQTT-Adapter als Client lokale Steuerung
                                    |ETH01| <- LAN -> PORT 1883 ohne TLS lokal Reserve z.B. gelegentlich MQTT-Explorer
                		    |W/LAN| <- LAN -> PORT 1883 ohne TLS lokal Reserve z. B. gelegentlich MQTTX
                		    +-----+
                

                Final und läuft seit heute, 07.11.2026 21:00 Uhr, im ununterbrochenen Dauertest.

                Wer Interesse hat, liest den Spoiler, sonst einfach ignorieren.

                edit/ps 08.11.2026 16:30h

                Steuerung isoliert lokal oder mit Cloud.
                Einfach umschaltbar in Sekunden.
                Alles ohne externen Broker, ohne AdGuard, ohne Pi-Hole etc. und ohne Router-Tricks. Internetsperre nicht nötig.

                Was wird benötigt?

                • zenGerät
                • MQTT-Client z.B. ioBroker MQTT Broker/Client v8.1.2 - Adapter
                • ESP32-Board WT32-ETH01 (Empfehlung: mit >= 100µF Elko).
                • Smartphone mit zenApp
                • LAN in der Nähe des zenGeräts

                Habe noch einige PCB von einem anderen Projekt

                01-eth01.png


                Ersteinrichtung ESP32 kinderleicht - einmalig über die serielle Konsole.

                Eingegeben werden folgende Daten:

                • CLOUD DNS
                • productKey
                • userID
                • die ( maximal 3) Client - Konten PORT 1883 ohne TLS
                • SSID-WLAN-AP: für ESP32 WT32-ETH01
                • WLAN-Password-AP: für ESP32 WT32-ETH01
                • LOG-Level (bei Normal-Betrieb: 0)

                02-setup.png

                Alles Weitere wird vollautomatisch erledigt.


                Einrichtung des zenGeräts:

                • Smartphone BT einschalten
                • zenAPP öffnen.
                • In der App Netzwerk einrichten:
                  Netzwerk auswählen: SSID-WLAN-AP vom ESP32 WT32-ETH01
                  Passwort: vom WLAN-Password-AP eingeben.

                verbinden und drin. "einfach drin" - kinderleicht :-)

                03-AP-ZenAPP.png

                WLAN-AP setzt automatisch: 192.168.4.1 und lässt nur 1 Gerät zu.

                Durch die Platzierung direkt neben dem 1600AC+ hat das Gerät eine super Verbindung bei -35 dBm ohne paketbasierte Latenzen:

                04-eth32-iobroker.png


                Highlights & Funktion

                1. Dedizierter LAN- & AP-Betrieb
                ESP32 bezieht seine Daten und - wenn man möchte - Internetverbindung stabil über LAN.
                WLAN-Teil wird vom Heimnetz entkoppelt und arbeitet isoliert als WIFI_AP, fest limitiert auf 1 Verbindung für das zenGerät.

                2. Integrierter Mini-DNS-Server
                Ein integrierter DNS-S. leitet auf den ESP-AP mit IP 192.168.4.1 um.
                Gerät verbindet sich mit dem lokalen TLS-Broker des ESP32, während Bridge den Namen über das Heimnetz auflöst.

                3. Integrierte Whitelist für den Bridge-Tunnel
                Sicherheitstechnisch ein Meilenstein:
                Das Gateway besitzt eine integrierte Whitelist.
                Lokale 1883 Clients (ioBroker, MQQTX, MQTT-Explorer etc.) dürfen unverschlüsselt auf Port 1883 Daten austauschen, aber der ESP32 lässt nur die echten zen-topics des Geräts in das Inet.
                Fremde Daten oder lokaler Skript-Traffic werden gefiltert und blockiert, auch wenn die Bridge aktiviert ist.

                4. Statischer Heap-Schutz gegen Fragmentierung
                Speicherarchitektur wurde zur Vermeidung von Fragmentierung im 24/7 auf statische char-Arrays umgestellt.
                Die Funktion parst MQTT-Pakete ohne jede Heap-Allokation direkt in feste Speicherbereiche.
                Heap bleibt bis jetzt konstant.

                5. Software-Bridge-Steuerung über MQTT
                Die Cloud-Bridge wird softwareseitig über ein lokales MQTT-Topic:

                /bridgeMode
                

                gesteuert. Mit

                {"bridgeSwitch":0}
                

                wird die Cloud-Verbindung abgeschaltet und sauber getrennt, während der lokale TLS-Server für den Hub unterbrechungsfrei weiterläuft.

                {"bridgeSwitch":1}
                

                verbindet bei Bedarf wieder mit Cloud. Z. B. falls man doch die App unterwegs benötigt.

                Durch den statischen Stream-Parser und die Reduzierung der Stack-Puffer ist das System für den dauerhaften Betrieb ausgelegt.

                toDo
                Testlauf und nur noch reine Beobachtung.


                Wer vielleich noch einen WT32-ETH01 hat... ;-)

                Falls jemand sich bedanken möchte, bitte nur voten.
                Ein extra Post ist dafür nicht nötig.

                Wer Fragen oder Interesse an dem Projekt hat, bitte schön :-)


                edit/ps 08.11.2026 16:30h

                Ein User bat um meine Hilfe, das Projekt umzusetzen.
                Er hat zwei zenGeräte (SF2400 AC, 1x SF 800 Pro 2) und zwei ESP32-WT32-ETH01.

                Knackpunkt: In der Nähe gibt es kein LAN-Anschluss und das WLAN ist dort nicht gut – meinte er.

                Nach kurzer Unterhaltung und dem Eruieren, was vor Ort gegeben und vorhanden ist, war die Lösung schnell gefunden und umgesetzt.

                Ein alter TP-Link-Router, den er noch herumliegen hatte, war schnell als WLAN-Brücke (Wireless Client Mode) konfiguriert.
                Dieser ist nun per WLAN mit dem Heimnetz (Fritzbox) verbunden.

                Das Signal ist um Welten besser als das von den zenGeräten mit ihren ESP-Chips.
                Vorher lag RSSI im Schnitt bei -79 dBm.
                Der Router mit seinen externen Antennen erreicht nun -58 bis -64 dBm (wird zumindest so angezeigt).
                Der Router hat vier LAN-Buchsen, an die die zwei ESPs angeschlossen wurden.
                Die beiden ESPs sind ca. 30 cm voneinander getrennt und werden gemeinsam über ein 5V-3A-Netzteil versorgt.
                Weil es ein Noname-Netzteil ist, wurde bei beiden ESPs zwischen 5V und GND jeweils ein Elko mit 220 µF angeschlossen.
                Zudem wurde zwischen dem EN-Pin und GND jeweils noch ein Elko mit 100 µF angelötet.

                Ergebnis:
                Es funktioniert besser als zuvor – allein schon, weil die Bridge eine deutlich bessere Verbindung zum WLAN hat als die zenGeräte.
                Die zwei ESP32-WT32-ETH01 befinden sich nun weniger als einen Meter von den zenGeräten entfernt und haben damit natürlich ebenfalls eine Top-Verbindung.

                Läuft! :-)

                Wer noch Fragen hat, bitte schön.
                Wünsche Allen einen wunderschönen Sonntag.

                edit/Ende

                Was für ein Meilenstein in meiner persönlichen zenHistory und weltweit.
                Was für ein gelungenes Wochenende.

                Wünsche Allen eine schöne, gute Nacht :-)

                maxclaudiM
                maxclaudiM
                maxclaudi
                schrieb zuletzt editiert von
                #691

                Tipp
                gleich noch einmal separat, weil mein Tipp einem User sehr geholfen hat:
                Wenn jemand WLAN-Probleme mit dem zenGerät hat und das Handy daneben eine gute WLAN-Verbindung – wichtig: im 2,4-GHz-Netz – hat, dann könnte eine WLAN-Bridge direkt neben dem zenGerät Wunder bewirken.

                Zendure zenSDK Lokal API, SmartMode, SolarFlow AC 800 Pro 2

                neue Zendure Geräte Cloudless: zURLtool V2.0

                1 Antwort Letzte Antwort
                0
                • maxclaudiM maxclaudi

                  aktuell: zenSDK-Steuerungs-Script update 16.10.26 21:00h


                  In memory of Daisy 02.05.24 – miss you.


                  Das Projekt wurde komplett neu überarbeitet und auf maximale Effizienz optimiert.

                  Zendure-Geräte Webserver: Lokale Steuerung über das zenSDK (HTTP)
                  (In memory of Daisy 02.05.24 – miss you.)

                  Für alle Zendure Geräte mit Webserver (2025/2026):

                  • SolarFlow 800
                  • SolarFlow 800 Plus
                  • SolarFlow 800 Pro
                  • SolarFlow 800 Pro 2
                  • SolarFlow 1600 AC+
                  • SolarFlow 2400 AC
                  • Solarflow 2400 AC+
                  • SolarFlow 2400 Pro
                  • SolarFlow-Mix-Serie

                  • Kein Hack: Nutzung der offiziellen zenSDK-Schnittstelle

                  • Hardware-Schutz: Fokus auf RAM-Steuerung zur Schonung des Flash-Speichers (Flash-Schutz ist mir wichtig.)

                  • Plattformunabhängig: Läuft unter Linux und Windows.

                  • Funktion: Das Skript dient als Adapter-Ersatz für jeweils ein Zendure-Gerät.
                    Es funktioniert sofort und wahlweise komplett lokal oder parallel zu Cloud und App (bei deaktiviertem HEMS).
                    Außer der IP-Adresse des Zendure-Geräts sind keine weiteren Keys oder Authentifizierungen notwendig.

                  Ein Wort in eigener Sache - Urheberrecht:


                  Ich habe im Header des Skripts Hinweise zu meiner Motivation und zur Urheberschaft hinterlassen. Da in dieses Projekt extrem viel Eigenforschung (auch Thema Flash-Schutz) geflossen ist, bitte ich darum, die Urheberschaft zu respektieren. Wer diese Logik in öffentliche Projekte oder Adapter übernimmt, sollte so fair sein, die Quelle namentlich zu nennen. Dies ist der „Lohn“ für meine Arbeit.


                  12.06.2026 WICHTIGES UPDATE IN EIGENER SACHE Urheberrecht

                  Ich teile mein Wissen, meine (API-) Analysen und mathematischen Berechnungen gerne, um der Community zu helfen. Open Source basiert jedoch auf Fairness, Respekt und Transparenz.

                  Leider muss ich immer wieder feststellen, dass aus meinen Skripten und Logiken eins zu eins kopiert wird. Wenn diese Arbeit dann andernorts ohne ein Wort des Dankes als „Eigenentwicklung“ oder gar als „Erkenntnis aus einer KI ohne Quelle“ verkauft wird, ist für mich eine rote Linie überschritten.

                  Deshalb hier ein letztes Mal mein klares Statement für die Zukunft:

                  Ich lade jeden herzlich ein, meine Logiken in öffentliche Projekte oder als hilfreiche Antwort in anderen Threads zu integrieren – aber nur mit einer sauberen Quellenangabe/Verlinkung auf meine Posts, Threads, das jeweilige Skript oder unter Nennung meines Namens „maxclaudi“.

                  Sollte ich noch einmal feststellen, dass meine Quelltexte oder mathematischen Formeln anonym abgestaubt und die Spuren meiner Arbeit verwischt werden, ziehe ich die Konsequenzen:

                  Ich werde sämtliche Veröffentlichungen, Updates und den Support für meine Skripte in diesem Forum augenblicklich und dauerhaft einstellen.

                  Wer die Vorteile von Open Source nutzen möchte, sollte auch die Fairness besitzen, den Urheber zu nennen.


                  Zum aktuellen zenSDK-Steuerungs-Skript: HIER KLICKEN

                  • Wer meine Arbeit nützlich findet und sich bedanken möchte:
                    Ein Klick auf den Pfeil nach oben (unten am Post, neben Zitieren) reicht völlig.


                  update 26.10.2026; 18:19h

                  zURLtool_V20 (c) maxclaudi 2026 - Version 2.0 cdh

                  Neu in zURLtool V 2.0 dch
                  • Sprachauswahl: Deutsch und Englisch sind auswählbar und während der Laufzeit umschaltbar.

                  • Reset-Funktion: Geräte können zurückgesetzt werden (Hinweis: Zum vollständigen Abmelden - z.B. bei Verkauf - Gerät auch in der App entfernen!).

                  • WLAN-Handling: Das manuelle Setzen des WiFi-Mode entfällt. Ein automatischer Scan zeigt alle vom Zendure-Gerät erreichbaren Netze an. Bei zur config.ini abweichendem, manuell ausgewählten WLAN muss auch das Passwort in der GUI angepasst werden.

                  • Offline-Modus: Die Option OFFLINE = 1 in der config.ini überspringt den WLAN-Scan, ideal für Vorbereitungen oder Konfigurationen ohne aktiven Router.

                  • Token & User-ID: Gerätespezifischer Token wird in mqtt_creds.txt gesichert. Für die korrekte userId (bzw. consumerId) empfiehlt sich der Einsatz des Zusatztools zAppEmulationDeviceInfo

                  • Hinweis zu TLS / Port 8883: Das Tool ist ein reiner Konfigurator per Bluetooth und generiert keine TLS-Zertifikate oder Keys.

                  00.png

                  01-de.png

                  zu zURLtool_V20 (c) maxclaudi 2026 - Version 2.0 cdh: HIER KLICKEN

                  Beschreibung im Spoiler.


                  zURLtool V 2.0 dch – Lokaler MQTT-Broker für Zendure 2026

                  Hallo zusammen, das Update zURLtool V 2.0 dch de/gb ist nun fertig!


                  Das Programm ist in seiner Art einzigartig, neu und durch monatelanges Reverse-Engineering, Analysen und intensives Testen entstanden.
                  Es handelt sich um ein reines Windows-GUI-Programm, das ganz einfach per Doppelklick gestartet wird.

                  Es verwendet keinen Code von anderen bekannten Projekten, die teils fehlerhaften und unvollständigen Code verwenden, mit dem Konfigurationen neuer Geräte nicht möglich sind.

                  Technische Voraussetzungen & Basis:

                  Vielen Dank an Henrik Blidh für die hervorragende BT-Bibliothek.

                  Das Programm nutzt zur Kommunikation ausschließlich die offiziellen Windows-BT-Treiber ab Windows 10/11 und die integrierte Bluetooth-Bibliothek bleak:

                  ==================================================

                  LICENSE NOTICE FOR THE UTILIZED BLUETOOTH LIBRARY
                  This program uses "bleak" for Bluetooth functionality.
                  https://github.com/hbldh/bleak
                  Copyright (c) 2020, Henrik Blidh
                  Permission is hereby granted, free of charge, to any person obtaining a copy
                  of this software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND.

                  ==================================================

                  Die Bibliothek wurde fest in das Programm eingebunden.

                  Weitere Bibliotheken oder Frameworks müssen auf eurem PC nicht installiert werden.

                  • Betriebssystem: Windows (10) 11 (64-Bit)
                  • Hardware: Interner Bluetooth-Chip oder ein standardmäßiger USB-Bluetooth-Stick am Rechner.
                  • Kompatibilität: Getestet mit Zendure-Geräten bis zur 2400er-Serie (sowohl ältere als auch neueste Generationen / Firmware-Stände).

                  Inhalt des Programmpakets (zURLtoolV20.zip):
                  Das ZIP-Archiv enthält genau drei Dateien, die zwingend im selben Ordner liegen bleiben müssen:
                  • zURLtool_V20.exe (Größe: ca. 15.5 MB / 15555 KB)
                  • config.ini (Konfigurationsdatei für eure Zugangsdaten)
                  • ReadMe_zURLtoolV20.txt (Ausführliche Anleitung in Deutsch & Englisch)

                  Was genau macht das zURLtool?
                  Das Tool schreibt im Zendure-Gerät die Konfiguration der Broker-URL um.

                  • Es wird NICHT in die Firmware eingegriffen und es wird nichts modifiziert. Die originale Firmware von Zendure bleibt zu 100 % unangetastet.

                  • Es werden lediglich die standardmäßigen Bluetooth-Befehle verwendet, die die Firmware von Haus aus für die App bereitstellt.

                  • Das Skript verhält sich für das Gerät exakt wie die originale App, greift auf die Konfigurationsvariablen im Speicher zu und überschreibt dort die Wi-Fi-Daten und den Eintrag für die Cloud-URL mit der IP eures eigenen lokalen Brokers.

                  Es funktioniert quasi wie eine zusätzliche Länderauswahl, bei der man sich einfach mit einem anderen Server verbindet.
                  Das bietet die Firmware von Haus aus an, da die Geräte sonst permanent fest an ganz bestimmte AWS-Server gebunden wären.
                  Zurück zur Cloud ist auch möglich. Beispiele sind in der config.ini enthalten.

                  Die Geräte verbinden sich und können beschrieben werden ohne hartes Reset und ohne hartes Trennen von allen Stromquellen.

                  Freigabe vereinfacht.
                  Wer schon eine Seriennummer und Freischalt-Code für zURLtool V1.0 dch hat, kann diese verwenden.
                  Seriennummer und Freischalt-Code bleiben gültig, eine erneute Anfrage für zURLtool V2.0 ist nicht erforderlich.

                  Der Freischalt-Ablauf:
                  Damit das Programm funktioniert und um anonyme Massen-Weiterreichungen zu unterbinden, benötigt das Programm eine Seriennummer und einen Freigabe-Code.

                  1. Sende mir deinen (Foren-) Namen per privater Nachricht (PN) im Forum oder per E-Mail an: ztool.maxclaudi AT gmail . com
                  2. Du erhältst von mir eine persönliche "Seriennummer" und den dazu passenden "Freigabe-Code"
                  3. Sichere dir die Seriennummer und den Freigabe-Code unbedingt in einer Textdatei als Backup! Falls du die Datei mal verlierst, hast du deine Seriennummer und deinen Freigabe-Code als Backup griffbereit.
                  4. Starte die 'zURLtool_GUI.exe'.

                  Trage beide Werte exakt so, wie du sie erhalten hast, in die dafür vorgesehenen Felder im Startfenster ein und klicke auf "Akzeptieren & Starten".
                  Nach einmaliger Eingabe startet zURLtool zukünftig vollautomatisch in die Hauptoberfläche.

                  Als Notfall hast du zusätzlich Seriennummer und Freigabecode als Backup.
                  Vor dem ersten Start muss bzw. kann die config.ini in einem einfachen Text-Editor mit euren echten Daten befüllt werden.
                  Auswählbare Beispiele und Erklärungen zu den WLAN-Modi sind direkt darin enthalten.

                  Wenn keine config.ini vorhanden ist oder nicht benutzt wird, müssen die Daten in die GUI eingegeben werden.

                  Warum ist das Programm Closed Source?
                  Zu oft wurden meine Skripte, Codes oder Teile daraus in der Vergangenheit verwendet und/oder modifiziert – ohne Nennung meines Namens, ohne Danksagung / Anerkennung und unter Entfernung meiner originalen Hinweise.
                  Entweder wurde (leicht abgeändert) einfach "übernommen" oder u. a. als eigener Erfolg ausgegeben oder kommentiert mit "ich habe einen Weg gefunden".
                  Das ist ungerecht.

                  Monatelange harte Arbeit sollte zumindest anerkannt werden.

                  Sollte das Programm oder der Code in irgendeiner Form gehackt zur Verfügung gestellt oder verwendet werden, dann war dies mein letzter offizieller Beitrag in Form von Code für die Community.
                  Das gilt auch wenn das Programm inkl. Seriennummer und Freigabe-Code mehrfach geteilt wird.

                  Viel Spaß mit dem zURLtool V2.0 dch!


                  01-eng.png

                  02-eng.png

                  04-eng.png

                  05-eng.png


                  Bitte ReadMe_zURLtoolV20.txt beachten, enthält die deutsche und englische Beschreibung.

                  HINWEIS FÜR BESTEHENDE NUTZER:
                  Wer bereits für die Version 1.0 eine Seriennummer und einen Freischalt-Code erhalten hat, kann diese Daten direkt weiterverwenden!
                  Sie sind auch für die Version 2.0 gültig.
                  Bitte in diesem Fall keine erneute Anfrage.

                  RECHTLICHER HINWEIS ZUR FREIGABE:
                  Eine Weitergabe von Seriennummer und Freigabe-Code
                  sowie deren Mehrfachnutzung durch verschiedene Personen ist ausdrücklich UNZULÄSSIG.


                  Please note ReadMe_zURLtoolV20.txt: contains the German and English description.

                  NOTE FOR EXISTING USERS:
                  If you have already received a serial number and activation code for version 1.0, you can reuse them directly!
                  They are valid for version 2.0 as well. Please do not submit a new request in this case.

                  LEGAL NOTICE ON ACTIVATION:
                  Sharing the serial number and activation code, as well as their multiple use by different individuals, is strictly PROHIBITED.



                  historisch: zURLtool V 1.0 dch

                  16.08.2026, NEU
                  zusätzliches Windows-Programm:

                  zURLtool V 1.0 dch – Lokaler MQTT-Broker für Zendure 2026

                  Es handelt sich um ein reines Windows-GUI-Programm, das ganz einfach per Doppelklick gestartet wird.

                  Was genau macht das zURLtool?
                  Das Tool konfiguriert im Zendure-Gerät die DNS-Adresse (URL) des Ziel-Brokers mit einer selbst gewählten DNS oder IP – beispielsweise für einen eigenen, lokalen MQTT-Broker anstelle der Cloud. 

                  Es funktioniert einwandfrei mit allen Zendure-Geräten der neueren Generation (2025/2026) bis zum 2400 AC pro. 
                  Die SolarFlow-Mix-Serie wurde mangels Testgeräten nicht getestet.

                  • Es wird NICHT in die Firmware eingegriffen und es wird nichts modifiziert. Die originale Firmware von Zendure bleibt zu 100 % unangetastet.

                  • Es werden lediglich die standardmäßigen Bluetooth-Befehle verwendet, die die Firmware von Haus aus für die App bereitstellt.

                  • Das Skript verhält sich für das Gerät exakt wie die originale App, greift auf die Konfigurations-Variablen im Speicher zu und überschreibt dort die Wi-Fi-Daten und den Eintrag für die Cloud-URL mit der IP eures eigenen lokalen Brokers.

                  Es funktioniert quasi wie eine zusätzliche Länderauswahl, bei der man sich einfach mit einem anderen Server verbindet.
                  Das bietet die Firmware von Haus aus an, da die Geräte sonst permanent fest an ganz bestimmte AWS-Server gebunden wären.

                  Zurück zur Cloud ist auch möglich. Beispiele sind in der config.ini enthalten.

                  Die Geräte verbinden sich und können beschrieben werden ohne hartes Reset und ohne hartes Trennen von allen Stromquellen.

                  • Betriebssystem: Windows 11 (64-Bit)
                  • Hardware: Interner Bluetooth-Chip oder ein standardmäßiger USB-Bluetooth-Stick am Rechner.
                  • Kompatibilität: Getestet mit Zendure-Geräten bis zur 2400er-Serie wie z. B. 2400 AC Pro (sowohl ältere als auch neueste Generationen / Firmware-Stände).

                  Das Programm ist in seiner Art einzigartig, neu und durch monatelanges Reverse-Engineering, Analysen und intensives Testen entstanden.
                  Es handelt sich um ein reines Windows-GUI-Programm, das ganz einfach per Doppelklick gestartet wird.
                  Keine (zusätzlich(en)) Installation(en) über Setup etc. erfoderlich.
                  Es verwendet keinen Code von anderen bekannten Projekten (wie dem Solarflow Bluetooth Manager oder dem zendure-cloud-disconnector).

                  Zur Beschreibung und Download: zURLtool


                  Null-Einspeisung JS-Script Basis-Vorlage vom 14. Okt. 2026; 10:04h
                  einfach gehalten und für Anfänger leicht nachvollziehbar.
                  Funktioniert mit Zendure-Geräte die 1 bis 6 Batterien verwenden (konfigurierbar) POST #632




                  HINWEIS ZU SUPPORT- & SOFTWARE-ANFRAGEN (PM / E-MAIL)
                  bitte lesen: HIER KLICKEN



                  HISTORISCH:

                  Immer wieder taucht die Frage auf (z. B. Michi 0 ), wie man den SmartMode:1 setzen kann.
                  Bisher wurde das noch nirgends wirklich anwenderfreundlich beantwortet.

                  Hier nun die Lösung:

                  Mit diesem Script können nicht nur smartMode:1 und die MQTT-Verbindung (de-)aktiviert werden, sondern auch einiges mehr.


                  Update 2026.01.13_16.05h
                  ioBroker JavaScript: Zendure SolarFlow 800 Pro (SF2400AC) via zenSDK HTTP
                  Kein Hack, dafür wurde die offizielle zenSDK verwendet.
                  plattformunabhängig - Linux, Windows

                  Jetzt können auch Commands gesetzt werden, wie in einem Adapter. 🙂

                  Muss man nicht nutzen, kann man aber zusätzlich einsetzen – z. B. falls MQTT mal Probleme macht oder wenn weitere set-Befehle dazukommen sollen.

                  Hinweis:
                  Damit neue Datenpunkte automatisch aus dem JSON erstellt werden:
                  → Instanzen → JavaScript-Adapter → Allgemeine Einstellungen → „Enable command setObject“ aktivieren!

                  So viel wie möglich wurde ohne Gerät getestet und simuliert.
                  Extra ohne Schleifen und strukturiert geschrieben, damit jeder das Script nachvollziehen kann.

                  Intervalle

                  • intervalGet sollte nicht < 30 s gesetzt werden – das bringt keinen Mehrwert.
                  • intervalMqtt = 300 s ist völlig ausreichend, um zu prüfen, ob MQTT verbunden ist.

                  Eigene Daten anpassen
                  Zendure Gerät:

                  const IP = "192.168.177.103";   // IP des Zendure Geräts
                  const SN = "EXXXXXXXXXXXXX0";   // Seriennummer
                  

                  MQTT-Verbindungs-Status im Intervall abfragen? ja: true / nein: false

                  // MQTT Status zyklisch abfragen? ja: true / nein: false
                  const mqttStateAsking = true;   // true = zyklische MQTT-Abfrage aktiv, false = deaktiviert
                  

                  Intervalle für Abfragen (in Sekunden):

                  const intervalGet = 60;   // SmartMode (default: 60 s, nicht < 30)
                  const intervalMqtt = 300; // MQTT-Status (default: 300 s, > SmartMode)
                  

                  MQTT-Broker Konfiguration:

                  const mqttBrokerIp = "192.168.177.200"; // IP MQTT Broker (auch in App eingestellt)
                  const mqttPort = 1883;                  // Port MQTT Broker (auch in App eingestellt)           
                  const mqttUsername = "Daisy";           // MQTT Username
                  const mqttPassword = "coco";            // MQTT Passwort
                  
                  

                  Maximale Input-/Output-Limits:

                  // Maximum Input-Limit
                  const maxInputLimit = 1000;   // SF800Pro
                  //const maxInputLimit = 2400; // SF2400AC
                  
                  // Maximum Output-Limit
                  const maxOutputLimit = 800;   // SF800Pro
                  //const maxOutputLimit = 2400; // SF2400AC
                  
                  

                  Hinweise

                  • wichtig: wenn MQTT über dieses Script eingeschaltet wird, hat es vermutlich Vorrang!
                    Es werden ggf. andere MQTT-Einstellungen in der App durch das Script überschrieben.

                  • Wer meine Arbeit nützlich findet und sich bedanken möchte:
                    Ein Klick auf den Pfeil nach oben (unten am Post, neben Zitieren) reicht völlig. Das ist für mich mehr als genug. 🙂

                  • Dieser erste Post wird bei Änderungen editiert, damit das aktuelle Script immer direkt hier auffindbar ist.

                  • Beschreibung / Anleitung
                    Hinweise zur Benutzung, der aktuellen Version des Scripts und zusätzliche Tipps, siehe:
                    Post im Thread

                  Viel Spaß! ☀


                  //
                  // ioBroker JavaScript: Zendure SolarFlow 800 Pro (SF2400AC) via zenSDK HTTP
                  // by maxclaudi 2026.01.13_16.05h for ioBroker Forum
                  //------------------------------------------------------------------------------------
                  // konfiguration
                  //
                  // Hinweis bei mehreren Zendure-Geräten:
                  // - Für jedes Gerät ein eigenes Script mit individueller Konfiguration verwenden!
                  //   -> IP-Adresse
                  //   -> Seriennummer (SN)
                  //   -> MQTT-Daten (Broker, Port, Benutzer, Passwort)
                  //   -> maxInputLimit / maxOutputLimit (abhängig vom Gerätetyp)
                  // - Intervalle (Standard): intervalGet = 60s, intervalMqtt = 300s – können gleich bleiben
                  //
                  // Empfehlung:
                  //   • 3 Geräte: völlig unkritisch
                  //   • 4 Geräte: problemlos möglich
                  //   • mehr als 4: nicht empfohlen
                  //
                  //
                  // Damit neue Datenpunkte automatisch aus dem JSON erstellt werden:
                  // -> Instanzen -> javascript-Adapter -> Allgemeine Einstellungen -> Enable command"setObject“ aktivieren!
                  //
                  //------------------------------------------------------------------------------------
                  // MQTT Status zyklisch abfragen? ja: true / nein: false
                  const mqttStateAsking = true;   // true = zyklische MQTT-Abfrage aktiv, false = deaktiviert
                  
                  // Intervalle für Abfragen (Sekunden)
                  const intervalGet = 60;                 // sek SmartMode   (default:  60 sek, not <30)
                  const intervalMqtt = 300;               // sek MQTT-Status (default: 300 sek, > SmartMode)
                   
                  // IP und Seriennummer Zendure Gerät
                  const IP = "192.168.40.111";            // IP des Zendure Geräts
                  const SN = "EXXXXXXXXXXXXX0";           // Seriennummer
                   
                  // MQTT Broker Konfiguration
                  const mqttBrokerIp = "192.168.40.200";  // IP MQTT Broker und in App eingestellt 
                  const mqttPort = 1883;                  // PORT MQTT Broker und in App eingestellt           
                  const mqttUsername = "Daisy";           // MQTT Username
                  const mqttPassword = "coco";            // MQTT Passwort
                   
                  //maximum inputLimit SF800PRO: 800W / SF2400AC: 2400W
                  const maxInputLimit = 1000; //SF800Pro
                  //const maxInputLimit = 2400; //SF2400AC
                   
                  //maximum outputLimit SF800PRO: 800W / SF2400AC: 2400W
                  const maxOutputLimit = 800; //SF800Pro
                  //const maxOutputLimit = 2400; //SF2400AC
                  //------------------------------------------------------------------------------------ 
                  //------------------------------------------------------------------------------------
                  // Datenpunkte
                  const folderZendureApi = '0_userdata.0.zendure.' + SN;
                  //------------------------------------------------------------------------------------
                  const dpSmartModeInfo              = folderZendureApi + ".zendureSmartMode.smartModeInfo";
                  const dpSetSmartMode               = folderZendureApi + ".zendureSmartMode.setSmartMode";
                  const dpSetSmartModeResult         = folderZendureApi + ".zendureSmartMode.setResult";
                  const dpTimestamp                  = folderZendureApi + ".zendureSmartMode.timestamp";
                   
                  const dpMqttConnectInfo            = folderZendureApi + ".zendureMqttState.mqttConnectInfo";
                  const dpSetMqttConnect             = folderZendureApi + ".zendureMqttState.setMqttConnect";
                  const dpSetMqttConnectResult       = folderZendureApi + ".zendureMqttState.setMqttConnectResult";
                  const dpMqttTimestamp              = folderZendureApi + ".zendureMqttState.mqttTimestamp";
                   
                  //set dp
                  const dpSetAcMode                  = folderZendureApi + ".control.acMode.setAcMode";
                  const dpSetAcModeResult            = folderZendureApi + ".control.acMode.setAcModeResult";
                  const dpSetAcModeResultTS          = folderZendureApi + ".control.acMode.setAcModeResultTs";
                   
                  const dpSetInputLimit              = folderZendureApi + ".control.inputLimit.setInputLimit";
                  const dpSetInputLimitResult        = folderZendureApi + ".control.inputLimit.setInputLimitResult";
                  const dpSetInputLimitResultTS      = folderZendureApi + ".control.inputLimit.setInputLimitResultTs";
                   
                  const dpSetOutputLimit             = folderZendureApi + ".control.outputLimit.setOutputLimit";
                  const dpSetOutputLimitResult       = folderZendureApi + ".control.outputLimit.setOutputLimitResult";
                  const dpSetOutputLimitResultTS     = folderZendureApi + ".control.outputLimit.setOutputLimitResultTs";
                   
                  const dpSetSocSet                  = folderZendureApi + ".control.socSet.setSocSet";
                  const dpSetSocSetResult            = folderZendureApi + ".control.socSet.setSocSetResult";
                  const dpSetSocSetResultTS          = folderZendureApi + ".control.socSet.setSocSetResultTs";
                   
                  const dpSetMinSoc                  = folderZendureApi + ".control.minSoc.setMinSoc";
                  const dpSetMinSocResult            = folderZendureApi + ".control.minSoc.setMinSocResult";
                  const dpSetMinSocResultTS          = folderZendureApi + ".control.minSoc.setMinSocResultTs";
                   
                  const dpSetGridReverse             = folderZendureApi + ".control.gridReverse.setGridReverse";
                  const dpSetGridReverseResult       = folderZendureApi + ".control.gridReverse.setGridReverseResult";
                  const dpSetGridReverseResultTS     = folderZendureApi + ".control.gridReverse.setGridReverseResultTs";
                   
                  const dpSetGridStandard            = folderZendureApi + ".control.gridStandard.setGridStandard";
                  const dpSetGridStandardResult      = folderZendureApi + ".control.gridStandard.setGridStandardResult";
                  const dpSetGridStandardResultTS    = folderZendureApi + ".control.gridStandard.setGridStandardResultTs";
                   
                  const dpSetInverseMaxPower         = folderZendureApi + ".control.inverseMaxPower.setInverseMaxPower";
                  const dpSetInverseMaxPowerResult   = folderZendureApi + ".control.inverseMaxPower.setInverseMaxPowerResult";
                  const dpSetInverseMaxPowerResultTS = folderZendureApi + ".control.inverseMaxPower.setInverseMaxPowerResultTs";
                   
                  const dpSetChargeMaxLimit         = folderZendureApi + ".control.chargeMaxLimit.setChargeMaxLimit";
                  const dpSetChargeMaxLimitResult   = folderZendureApi + ".control.chargeMaxLimit.setChargeMaxLimitResult";
                  const dpSetChargeMaxLimitTS       = folderZendureApi + ".control.chargeMaxLimit.setChargeMaxLimitResultTs"; 
                  //------------------------------------------------------------------------------------
                  const http = require("http");           // Node.js http Standardmodul
                  //------------------------------------------------------------------------------------
                   
                  // Mapping: Payload key, optional transformation, result and timestamp dp
                  const dpMap = {
                      [dpSetAcMode]:          { key: "acMode", result: dpSetAcModeResult, resultTS: dpSetAcModeResultTS, minNormal: 1, maxNormal: 2 },
                      [dpSetInputLimit]:      { key: "inputLimit", result: dpSetInputLimitResult, resultTS: dpSetInputLimitResultTS, minNormal: 0, maxNormal: maxInputLimit },
                      [dpSetOutputLimit]:     { key: "outputLimit", result: dpSetOutputLimitResult, resultTS: dpSetOutputLimitResultTS, minNormal: 0, maxNormal: maxOutputLimit },
                      [dpSetSocSet]:          { key: "socSet", transform: v => v*10, result: dpSetSocSetResult, resultTS: dpSetSocSetResultTS, minNormal: 70, maxNormal: 100 },
                      [dpSetMinSoc]:          { key: "minSoc", transform: v => v*10, result: dpSetMinSocResult, resultTS: dpSetMinSocResultTS, minNormal: 5, maxNormal: 50 },
                      [dpSetGridReverse]:     { key: "gridReverse", result: dpSetGridReverseResult, resultTS: dpSetGridReverseResultTS, minNormal: 0, maxNormal: 2 },
                      [dpSetGridStandard]:    { key: "gridStandard", result: dpSetGridStandardResult, resultTS: dpSetGridStandardResultTS, minNormal: 0, maxNormal: 2 },
                      [dpSetInverseMaxPower]: { key: "inverseMaxPower", result: dpSetInverseMaxPowerResult, resultTS: dpSetInverseMaxPowerResultTS, minNormal: 600, maxNormal: maxOutputLimit },
                  	[dpSetChargeMaxLimit]:  { key: "chargeMaxLimit", result: dpSetChargeMaxLimitResult, resultTS: dpSetChargeMaxLimitTS, minNormal: 0, maxNormal: maxInputLimit }
                  };
                   
                  // Queue HTTP
                  const curlQueue = [];
                  let curlRunning = false;
                   
                  function runQueue() {
                      if (curlRunning || curlQueue.length === 0) return;
                      const task = curlQueue.shift();
                      curlRunning = true;
                      task.fn(() => {
                          curlRunning = false;
                          runQueue();
                      });
                  }
                   
                  //dp have to exist - get
                  function delay(ms) {
                      return new Promise(resolve => setTimeout(resolve, ms));
                  }
                   
                  // dp create
                  createState(dpSmartModeInfo, 0, {
                          name: "SmartMode Info",
                          type: "number",
                          role: "state",
                          read: true,
                          write: false,
                          min: 0,
                          max: 1
                          }, async () => {
                             await delay(1);
                             getReport();
                  });
                   
                  createState(dpSetSmartMode, -1, {
                         name: "SmartMode Set",
                  	   type: "number",
                  	   role: "state",
                  	   read: true,
                  	   write: true,
                  	   min: -1,
                  	   max: 1
                  }, () => {});
                   
                  createState(dpSetSmartModeResult, "", {
                         name: "SmartMode Set Result",
                  	   type: "string",
                  	   role: "info",
                  	   read: true,
                  	   write: false
                  }, () => {});
                   
                  createState(dpTimestamp, "", {
                         name: "Timestamp",
                  	   type: "string",
                  	   role: "info",
                  	   read: true,
                  	   write: false
                         }, async () => {
                             await delay(1);
                  });
                  
                  createState(dpMqttConnectInfo, 0, {
                      name: "MQTT Connect Info",
                      type: "number", 
                      role: "state",
                      read: true,
                      write: false,
                      min: 0,
                      max: 1
                  }, async () => {
                      await delay(1);
                      if (mqttStateAsking) {
                          getMqttStatus();
                      }
                  });
                   
                  createState(dpSetMqttConnect, -1, {
                         name: "MQTT Connect Set",
                  	   type: "number",
                  	   role: "state",
                  	   read: true,
                  	   write: true,
                  	   min: -1,
                  	   max: 1
                  }, () => {});
                   
                  createState(dpSetMqttConnectResult, "", {
                         name: "MQTT Connect Result",
                  	   type: "string",
                  	   role: "info",
                  	   read: true,
                  	   write: false
                  }, () => {});
                   
                  createState(dpMqttTimestamp, "", {
                         name: "MQTT Timestamp",
                  	   type: "string",
                  	   role: "info",
                  	   read: true,
                  	   write: false
                  }, async () => { await delay(1); });
                   
                  // set > result ts dp
                  // acMode
                  createState(dpSetAcMode, -1, {
                         name: "set acMode, 1: charging 2: discharging",
                  	   desc: "1: Input, 2: Output",
                  	   type: "number",
                  	   role: "state",
                  	   read: true,
                  	   write: true,
                  	   min: -1,
                  	   max: 2
                  }, () => {});
                   
                  createState(dpSetAcModeResult, "", {
                      name: "AcMode Set Result",
                      type: "string",
                      role: "info",
                      read: true,
                      write: false
                  }, () => {});
                   
                  createState(dpSetAcModeResultTS, "", {
                      name: "AcMode Set Result Timestamp",
                      type: "string",
                      role: "info",
                      read: true,
                      write: false
                  }, () => {});
                   
                  // inputLimit
                  createState(dpSetInputLimit, -1, {
                         name: "set inputLimit",
                  	   desc: "AC charging power limit",
                  	   type: "number",
                  	   role: "state",
                  	   read: true,
                  	   write: true,
                  	   unit: 'W',
                  	   min: -1,
                  	   max: maxInputLimit //SF800Pro: 1000W, SF2400AC: 2400W
                  }, () => {});
                   
                  createState(dpSetInputLimitResult, "", {
                      name: "InputLimit Set Result",
                      type: "string",
                      role: "info",
                      read: true,
                      write: false
                  }, () => {});
                   
                  createState(dpSetInputLimitResultTS, "", {
                      name: "InputLimit Set Result Timestamp",
                      type: "string",
                      role: "info",
                      read: true,
                      write: false
                  }, () => {});
                   
                  // outputLimit
                  createState(dpSetOutputLimit, -1, {
                         name: "set outputLimit",
                  	   desc: "Output power limit",
                  	   type: "number",
                  	   role: "state",
                  	   read: true,
                  	   write: true,
                  	   unit: 'W',
                  	   min: -1,
                  	   max: maxOutputLimit //SF800Pro: 800W, SF2400AC: 2400W
                  }, () => {});
                   
                  createState(dpSetOutputLimitResult, "", {
                      name: "OutputLimit Set Result",
                      type: "string",
                      role: "info",
                      read: true,
                      write: false
                  }, () => {});
                   
                  createState(dpSetOutputLimitResultTS, "", {
                      name: "OutputLimit Set Result Timestamp",
                      type: "string",
                      role: "info",
                      read: true,
                      write: false
                  }, () => {});
                   
                  // socSet
                  createState(dpSetSocSet, -1, {
                         name: "set socSet 70-100%",
                  	   desc: "Stop charging at battery percentage",
                  	   type: "number",
                  	   role: "state",
                  	   read: true,
                  	   write: true,
                  	   unit: '%',
                  	   min: -1,
                  	   max: 100
                  }, () => {});
                   
                  createState(dpSetSocSetResult, "", {
                      name: "SocSet Set Result",
                      type: "string",
                      role: "info",
                      read: true,
                      write: false
                  }, () => {});
                   
                  createState(dpSetSocSetResultTS, "", {
                      name: "SocSet Set Result Timestamp",
                      type: "string",
                      role: "info",
                      read: true,
                      write: false
                  }, () => {});
                   
                  // minSoc
                  createState(dpSetMinSoc, -1, {
                         name: "set minSoc 0-50%",
                  	   desc: "Stop discharging at battery percentage",
                  	   type: "number",
                  	   role: "state",
                  	   read: true,
                  	   write: true,
                  	   unit: '%',
                  	   min: -1,
                  	   max: 50
                  }, () => {});
                   
                  createState(dpSetMinSocResult, "", {
                      name: "MinSoc Set Result",
                      type: "string",
                      role: "info",
                      read: true,
                      write: false
                  }, () => {});
                   
                  createState(dpSetMinSocResultTS, "", {
                      name: "MinSoc Set Result Timestamp",
                      type: "string",
                      role: "info",
                      read: true,
                      write: false
                  }, () => {});
                   
                  // gridReverse
                  createState(dpSetGridReverse, -1, {
                         name: "setGridReverse, 0: off, 1: rev. 2: no rev",
                  	   desc: "0: Disabled, 1: Allowed reverse flow, 2: Forbidden reverse flow",
                  	   type: "number",
                  	   role: "state",
                  	   read: true,
                  	   write: true,
                  	   min: -1,
                  	   max: 2
                  }, () => {});
                   
                  createState(dpSetGridReverseResult, "", {
                      name: "GridReverse Set Result",
                      type: "string",
                      role: "info",
                      read: true,
                      write: false
                  }, () => {});
                   
                  createState(dpSetGridReverseResultTS, "", {
                      name: "GridReverse Set Result Timestamp",
                      type: "string",
                      role: "info",
                      read: true,
                      write: false
                  }, () => {});
                   
                  // gridStandard
                  createState(dpSetGridStandard, -1, {
                         name: "setGridStandard, 0: Germany / 2: France",
                  	   desc: "Grid connection standard 0: Germany 1: France 2: Austria",
                  	   type: "number",
                  	   role: "state",
                  	   read: true,
                  	   write: true,
                  	   min: -1,
                  	   max: 2
                  }, () => {});
                   
                  createState(dpSetGridStandardResult, "", {
                      name: "GridStandard Set Result",
                      type: "string",
                      role: "info",
                      read: true,
                      write: false
                  }, () => {});
                   
                  createState(dpSetGridStandardResultTS, "", {
                      name: "GridStandard Set Result Timestamp",
                      type: "string",
                      role: "info",
                      read: true,
                      write: false
                  }, () => {});
                   
                  // inverseMaxPower
                  createState(dpSetInverseMaxPower, -1, {
                         name: "set inverseMaxPower",
                  	   desc: "Maximum output power limit",
                  	   type: "number",
                  	   role: "state",
                  	   read: true,
                  	   write: true,
                  	   unit: 'W',
                  	   min: -1,
                  	   max: maxOutputLimit
                  }, () => {});
                   
                  createState(dpSetInverseMaxPowerResult, "", {
                      name: "inverseMaxPower Set Result",
                      type: "string",
                      role: "info",
                      read: true,
                      write: false
                  }, () => {});
                   
                  createState(dpSetInverseMaxPowerResultTS, "", {
                      name: "inverseMaxPower Set Result Timestamp",
                      type: "string",
                      role: "info",
                      read: true,
                      write: false
                  }, () => {});
                   
                  // chargeMaxLimit
                  createState(dpSetChargeMaxLimit, -1, {
                         name: "set chargeMaxLimit",
                  	   desc: "Maximum permissible charging power",
                  	   type: "number",
                  	   role: "state",
                  	   read: true,
                  	   write: true,
                  	   unit: 'W',
                  	   min: -1,
                  	   max: maxInputLimit
                  }, () => {});
                   
                  createState(dpSetChargeMaxLimitResult, "", {
                      name: "chargeMaxLimit Set Result",
                      type: "string",
                      role: "info",
                      read: true,
                      write: false
                  }, () => {});
                   
                  createState(dpSetChargeMaxLimitTS, "", {
                      name: "chargeMaxLimit Set Result Timestamp",
                      type: "string",
                      role: "info",
                      read: true,
                      write: false
                  }, () => {});
                   
                  //dpSet -1 
                  const dpSetList = [
                      dpSetSmartMode,
                      dpSetMqttConnect,
                      dpSetAcMode,
                      dpSetInputLimit,
                      dpSetOutputLimit,
                      dpSetSocSet,
                      dpSetMinSoc,
                      dpSetGridReverse,
                      dpSetGridStandard,
                      dpSetInverseMaxPower,
                  	dpSetChargeMaxLimit
                  ];
                   
                  //reset -1
                  function resetAllDpSet() {
                      dpSetList.forEach(dp => {
                          setState(dp, -1, true);
                      });
                  }
                   
                  setTimeout(() => resetAllDpSet(), 10);
                   
                  // intervals start																											  
                  setInterval(getReport, intervalGet*1000);
                  if (mqttStateAsking) {
                      setInterval(getMqttStatus, intervalMqtt * 1000);
                  }
                   
                  // trigger smartMode
                  on({id: dpSetSmartMode, ack:false}, obj => {
                      const val = parseInt(obj.state.val,10);
                      if(val===0||val===1) setSmartMode(val);
                  });
                   
                  // trigger MQTT connect
                  on({id: dpSetMqttConnect, ack:false}, obj => {
                      const val = parseInt(obj.state.val,10);
                      if(val===0||val===1) setMqttConnect(val);
                  });
                   
                  // trigger dp set
                  Object.keys(dpMap).forEach(id => {
                      on({ id: id, ack: false }, obj => {
                          const val = obj.state.val;
                          const { minNormal, maxNormal } = dpMap[id];
                   
                          // check values
                          if (val >= minNormal && val <= maxNormal) {
                  																 
                              setControlDP(id, val);  // Transformation
                          } else {
                              log(`Value ${val} for ${id} is not allowed`, 'warn');
                          }
                      });
                  });
                   
                  // time format
                  function formatTime(ts) {
                  	// ts unix sek							
                      const d = new Date(ts * 1000);
                      const pad = n => n.toString().padStart(2, "0");
                      return `${pad(d.getDate())}.${pad(d.getMonth()+1)}.${d.getFullYear().toString().slice(-2)} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
                  }
                   
                  // helper HTTP-error
                  function handleHttpError(context, err, options) {
                      const transient = [
                          "EHOSTUNREACH", "ECONNRESET", "ECONNREFUSED", "ETIMEDOUT",
                          "socket hang up", "ENETUNREACH", "EAI_AGAIN", "ENOTFOUND"
                      ];
                   
                      const msg = err && err.message ? err.message : String(err);
                      const isTransient = transient.some(code => msg.includes(code));
                   
                      //info_error
                  	//log(`${context}: ${isTransient ? "war vorübergehend im Intervall nicht erfolgreich:" : "Warnung"} (${msg})`, isTransient ? "info" : "warn");
                      log(`${context}: ${isTransient ? "war vorübergehend im Intervall nicht erfolgreich:" : "Warnung"} (${msg})`, "info");
                   
                      // set Timestamp
                      if (options && options.timestamp) {
                          setState(options.timestamp, formatTime(Math.floor(Date.now() / 1000)), true);
                      }
                  }
                   
                  // smartMode GET report
                  function getReport() {
                  	resetAllDpSet();
                      curlQueue.push({
                          fn: async done => {      //async
                              const options = {
                                  hostname: IP,
                                  port: 80,
                                  path: "/properties/report",
                                  method: "GET",
                                  timeout: 5000
                              };
                              const req = http.request(options, res => {
                                  let data = "";
                                  res.on("data", chunk => data += chunk);
                                  res.on("end", async () => {     // async
                                      try {
                                          const json = JSON.parse(data);
                                          if (json.properties && typeof json.properties.smartMode !== "undefined") {
                                              setState(dpSmartModeInfo, json.properties.smartMode, true);
                                              setState(dpTimestamp, formatTime(Math.floor(Date.now() / 1000)), true);
                                          }
                                          //JSON
                                          await processJson(json);
                                      } catch (e) {
                                          log("GET JSON Parse Fehler: " + e, "info"); //no valid JSON
                                          setState(dpTimestamp, formatTime(Math.floor(Date.now() / 1000)), true);
                                      }
                                      done();
                                  });
                              });
                              req.on("error", err => { 
                                  handleHttpError("HTTP GET", err, { timestamp: dpTimestamp }); 
                                  done(); 
                              });
                              req.end();
                          }
                      });
                      runQueue();
                  }
                   
                  // smartMode POST
                  function setSmartMode(val) {
                      curlQueue.push({
                          fn: done => {
                              const payload = JSON.stringify({ sn: SN, properties: { smartMode: val } });
                              const options = {
                                  hostname: IP,
                                  port: 80,
                                  path: "/properties/write",
                                  method: "POST",
                                  headers: {
                                      "Content-Type": "application/json",
                                      "Content-Length": Buffer.byteLength(payload)
                                  },
                                  timeout: 5000
                              };
                   
                              const req = http.request(options, res => {
                                  let data = "";
                                  res.on("data", chunk => data += chunk);
                   
                                  res.on("end", () => {
                                      let ok = false;
                   
                                      if (data) {
                                          try {
                                              const json = JSON.parse(data);
                                              ok = json.success === true;
                                          } catch (e) {
                                              ok = false; // no valid JSON
                                          }
                                      }
                  					setState(dpSetSmartModeResult, ok ? `ok set ${val}` : `error set ${val}`, true);
                                      setState(dpTimestamp, formatTime(Math.floor(Date.now() / 1000)), true);
                  					setState(dpSetSmartMode, -1, true);
                                      done();
                                  });
                              });
                   
                              req.on("error", err => {
                                  handleHttpError("HTTP POST", err, { timestamp: dpTimestamp });
                                  setState(dpSetSmartModeResult, `error set ${val}`, true);
                                  setState(dpTimestamp, formatTime(Math.floor(Date.now() / 1000)), true);
                  				setState(dpSetSmartMode, -1, true);
                                  done();
                              });
                   
                              req.write(payload);
                              req.end();
                          }
                      });
                   
                      runQueue();
                  }
                   
                  // MQTT state GET
                  function getMqttStatus() {
                      curlQueue.push({
                          fn: done => {
                              const options = {
                                  hostname: IP,
                                  port: 80,
                                  path: "/rpc?method=HA.Mqtt.GetStatus",
                                  method: "GET",
                                  timeout: 5000
                              };
                              const req = http.request(options, res => {
                                  let data = "";
                                  res.on("data", chunk => data += chunk);
                                  res.on("end", () => {
                                      try {
                                          const json = JSON.parse(data);
                                          const state = json.connected ? 1 : 0;
                                          setState(dpMqttConnectInfo, state, true);
                                          setState(dpMqttTimestamp, formatTime(Math.floor(Date.now() / 1000)), true);
                                      } catch (e) {
                                          log(`MQTT JSON Parse Fehler: ${e}`, "info"); //no valid JSON
                                          setState(dpMqttTimestamp, formatTime(Math.floor(Date.now() / 1000)), true);
                                      }
                                      done();
                                  });
                              });
                              req.on("error", err => { 
                                  handleHttpError("HTTP MQTT GET", err, { timestamp: dpMqttTimestamp }); 
                                  done(); 
                              });
                              req.end();
                          }
                      });
                      runQueue();
                  }
                   
                  // MQTT POST
                  function setMqttConnect(val) {
                      curlQueue.push({
                          fn: done => {
                              const enable = !!val;
                              const payload = JSON.stringify({
                                  sn: SN,
                                  method: "HA.Mqtt.SetConfig",
                                  params: {
                                      config: {
                                          enable: enable,
                                          server: mqttBrokerIp,
                                          port: mqttPort,
                                          protocol: "mqtt",
                                          username: mqttUsername,
                                          password: mqttPassword
                                      }
                                  }
                              });
                   
                              const options = {
                                  hostname: IP,
                                  port: 80,
                                  path: "/rpc",
                                  method: "POST",
                                  headers: {
                                      "Content-Type": "application/json",
                                      "Content-Length": Buffer.byteLength(payload)
                                  },
                                  timeout: 5000
                              };
                   
                              const req = http.request(options, res => {
                                  let data = "";
                                  res.on("data", chunk => data += chunk);
                   
                                  res.on("end", () => {
                                      let ok = false;
                   
                                      // HTTP 200 =  ok, Request succes
                                      if (res.statusCode === 200) {
                                          if (data) {
                                              try {
                                                  const json = JSON.parse(data);
                                                  ok = json.success === true;
                                              } catch (e) {
                                                  ok = false; // no valid JSON
                                              }
                                          }
                                      }
                  					setState(dpSetMqttConnectResult, ok ? `ok set ${val}` : `error set ${val}`, true);
                                      setState(dpMqttTimestamp, formatTime(Math.floor(Date.now() / 1000)), true);
                  					setState(dpSetMqttConnect, -1, true);
                                      done();
                                  });
                              });
                   
                              req.on("error", err => {
                                  handleHttpError("HTTP MQTT POST", err, { timestamp: dpMqttTimestamp });
                                  setState(dpSetMqttConnectResult, `error set ${val}`, true);
                                  setState(dpMqttTimestamp, formatTime(Math.floor(Date.now() / 1000)), true);
                  				setState(dpSetMqttConnect, -1, true);
                                  done();
                              });
                   
                              req.write(payload);
                              req.end();
                          }
                      });
                   
                      runQueue();
                  }
                   
                  // Generic POST function with result and timestamp handling
                  function setControlDP(id, val) {
                      if (!dpMap[id]) return;
                      const { key, transform, result, resultTS } = dpMap[id];
                      const value = transform ? transform(val) : val;
                   
                      curlQueue.push({
                          fn: done => {
                              const payload = JSON.stringify({ sn: SN, properties: { [key]: value } });
                              const options = {
                                  hostname: IP,
                                  port: 80,
                                  path: "/properties/write",
                                  method: "POST",
                                  headers: {
                                      "Content-Type": "application/json",
                                      "Content-Length": Buffer.byteLength(payload)
                                  },
                                  timeout: 5000
                              };
                   
                              const req = http.request(options, res => {
                                  let data = "";
                                  res.on("data", chunk => data += chunk);
                   
                                  res.on("end", () => {
                                      let ok = false;
                   
                                      // HTTP 200 = Connection established
                                      if (res.statusCode === 200) {
                                          if (data) {
                                              try {
                                                  const json = JSON.parse(data);
                                                  ok = json.success === true;
                                              } catch (e) {
                                                  ok = false; // no valid JSON
                                              }
                                          }
                                      }
                   
                                      // Write success or failure to data points
                                      if (result) {
                                          const valueToSend = val;  // Wert, der gesendet wurde
                  						setState(result, ok ? `ok set ${valueToSend}` : `error set ${valueToSend}`, true);
                                      }
                                      if (resultTS) setState(resultTS, formatTime(Math.floor(Date.now() / 1000)), true);
                  					setState(id, -1, true);
                                      done();
                                  });
                              });
                              
                  			req.on("error", err => {
                                  handleHttpError("HTTP POST " + key, err, { timestamp: resultTS });
                                  if (result) {
                                      const valueToSend = val;  // Wert, der gesendet wurde
                  					setState(result, `error set ${valueToSend}`, true);
                                  }
                                  if (resultTS) setState(resultTS, formatTime(Math.floor(Date.now() / 1000)), true);
                  				setState(id, -1, true);
                                  done();
                              });
                              req.write(payload);
                              req.end();
                          }
                      });
                      runQueue();
                  }
                   
                  //------------------------------------------------------------------------------------
                  // JSON
                  //------------------------------------------------------------------------------------
                  async function processJson(obj) {
                      try {
                          if (!obj || !obj.product) return;
                   
                          const product = obj.product;
                          const basePath = `${folderZendureApi}.${product}`;
                   
                          // create product folder if not exist
                          if (!existsObject(basePath)) {
                              setObject(basePath, {
                                  type: 'folder',
                                  common: { name: product },
                                  native: {},
                              });
                          }
                   
                          // create main keys
                          const mainKeys = ['timestamp', 'messageId', 'sn', 'version', 'product'];
                          for (const key of mainKeys) {
                              if (obj[key] !== undefined) {
                                  const statePath = `${basePath}.${key}`;
                                  if (existsState(statePath)) {
                                      setState(statePath, obj[key], true);
                                  } else {
                                      createState(statePath, obj[key], {
                                          name: key,
                                          type: typeof obj[key],
                                          role: 'info',
                                          read: true,
                                          write: false,
                                      });
                                  }
                              }
                          }
                   
                          // properties
                          if (obj.properties) {
                              await iter(`${basePath}.properties`, obj.properties);
                          }
                   
                          // PackData
                          if (obj.packData && Array.isArray(obj.packData)) {
                              await iter(`${basePath}`, { packData: obj.packData, timestamp: obj.timestamp });
                          }
                      } catch (e) {
                          log(`Error processing JSON: ${e}`, 'info');
                      }
                  }
                   
                  //------------------------------------------------------------------------------------
                  // Battery
                  //------------------------------------------------------------------------------------
                  function getBatteryType(sn, model) {
                    let batType = '';
                    if (sn?.startsWith('A')) batType = 'AB1000';
                    else if (sn?.startsWith('B')) batType = 'AB1000S';
                    else if (sn?.startsWith('C')) batType = sn[3] === 'F' ? 'AB2000S' : sn[3] === 'E' ? 'AB2000X' : 'AB2000';
                    else if (sn?.startsWith('F')) batType = 'AB3000X';
                    if (model?.trim()) batType = model.trim();
                    return batType || 'unknown';
                  } 
                   
                  //------------------------------------------------------------------------------------
                  // helper Iteration
                  //------------------------------------------------------------------------------------
                  async function iter(id, obj) {
                      try {
                  		if (!obj || typeof obj !== "object") {
                             //log("iter: Ungültiges oder leeres Objekt übersprungen", "info");
                             return;
                             }
                  		for (let i in obj) {
                              if (i === 'packData' && Array.isArray(obj[i])) {
                                  const ts = obj.timestamp ? obj.timestamp * 1000 : Date.now();
                                  for (const pack of obj[i]) {
                                      if (!pack.sn) continue;
                                      const sn = pack.sn;
                                      const path = `${id}.packData.${sn}`;
                   
                                      if (!existsObject(path)) {
                                          setObject(path, {
                                              type: 'folder',
                                              common: { name: sn },
                                              native: {},
                                          });
                                      }
                   
                                      for (let [key, val] of Object.entries(pack)) {
                                          const statePath = `${path}.${key}`;
                                          switch (key) {
                                              case 'batcur':
                                                  val = (val << 16 >> 16) / 10;
                                                  if (existsState(statePath)) {
                                                      setState(statePath, val, true);;
                                                  } else {
                                                      createState(statePath, val, {
                                                          name: 'Battery Current',
                                                          type: 'number',
                                                          desc: 'battery current',
                                                          role: 'value',
                                                          read: true,
                                                          write: false,
                                                          unit: 'A',
                                                      });
                                                  }
                                                  break;
                  							case 'heatState':
                                                  if (existsState(statePath)) {
                                                      setState(statePath, val, true);
                                                  } else {
                                                      createState(statePath, val, {
                                                          name: 'Heat State, 0: Not heating, 1: heating',
                                                          type: 'number',
                                                          role: 'value',
                                                          read: true,
                                                          write: false,
                                                      });
                                                  }
                                                  break;		
                  							case 'maxTemp':
                                                  val = (val - 2731) / 10;
                                                  if (existsState(statePath)) {
                                                      setState(statePath, val, true);
                                                  } else {
                                                      createState(statePath, val, {
                                                          name: 'Highest Akku Temperature',
                                                          type: 'number',
                                                          desc: 'maximum temperature',
                                                          role: 'value',
                                                          read: true,
                                                          write: false,
                                                          unit: '°C',
                                                      });
                                                  }
                                                  break;
                                              case 'maxVol':
                                                  val = val / 100;
                                                  if (existsState(statePath)) {
                                                      setState(statePath, val, true);
                                                  } else {
                                                      createState(statePath, val, {
                                                          name: 'Highest Cell Voltage',
                                                          type: 'number',
                                                          desc: 'highest cell voltage',
                                                          role: 'value',
                                                          read: true,
                                                          write: false,
                                                          unit: 'V',
                                                      });
                                                  }
                                                  break;
                                              case 'minVol':
                                                  val = val / 100;
                                                  if (existsState(statePath)) {
                                                      setState(statePath, val, true);
                                                  } else {
                                                      createState(statePath, val, {
                                                          name: 'Lowest Cell Voltage',
                                                          type: 'number',
                                                          desc: 'lowest cell voltage',
                                                          role: 'value',
                                                          read: true,
                                                          write: false,
                                                          unit: 'V',
                                                      });
                                                  }
                                                  break;
                  							case 'packType':
                                                  if (existsState(statePath)) {
                                                      setState(statePath, val, true);
                                                  } else {
                                                      createState(statePath, val, {
                                                          name: 'packType',
                                                          type: 'number',
                                                          role: 'value',
                                                          read: true,
                                                          write: false,
                                                      });
                                                  }
                                                  break;
                  							case 'power':
                                                  if (existsState(statePath)) {
                                                      setState(statePath, val, true);
                                                  } else {
                                                      createState(statePath, val, {
                                                          name: 'Battery Power',
                                                          type: 'number',
                                                          role: 'value',
                                                          read: true,
                                                          write: false,
                                                          unit: 'W',
                                                      });
                                                  }
                  								break;
                  							case 'sn':
                                              const batType = getBatteryType(val, pack.model);
                                              // Modelltyp als Datenpunkt setzen/erstellen
                                              const modelPath = `${path}.model`;
                                              if (existsState(modelPath)) {
                  								setState(modelPath, batType, true);
                                                  } else {
                                                      createState(modelPath, batType, {
                                                      name: 'Battery Model',
                                                      type: 'string',
                                                      role: 'text',
                                                      read: true,
                                                      write: false,
                                                      });
                                                     }
                                                  break;
                  							case 'socLevel':
                                                  if (existsState(statePath)) {
                                                      setState(statePath, val, true);
                                                  } else {
                                                      createState(statePath, val, {
                                                          name: 'Battery SoC Level',
                                                          type: 'number',
                                                          role: 'value',
                                                          read: true,
                                                          write: false,
                                                          unit: '%',
                                                      });
                                                  }
                  								break;
                  							case 'softVersion':
                                                  if (existsState(statePath)) {
                                                      setState(statePath, val, true);
                                                  } else {
                                                      createState(statePath, val, {
                                                          name: 'Battery Software Version',
                                                          type: 'number',
                                                          role: 'value',
                                                          read: true,
                                                          write: false,
                                                      });
                                                  }
                  								break;
                  							case 'state':
                                                  if (existsState(statePath)) {
                                                      setState(statePath, val, true);
                                                  } else {
                                                      createState(statePath, val, {
                                                          name: 'Battery, 0: Standby, 1: Charging, 2: Discharging',
                                                          type: 'number',
                                                          role: 'value',
                                                          read: true,
                                                          write: false,
                                                      });
                                                  }
                  								break;	
                                              case 'totalVol':
                                                  val = val / 100;
                                                  if (existsState(statePath)) {
                                                      setState(statePath, val, true);
                                                  } else {
                                                      createState(statePath, val, {
                                                          name: 'Total Voltage',
                                                          type: 'number',
                                                          desc: 'total voltage',
                                                          role: 'value',
                                                          read: true,
                                                          write: false,
                                                          unit: 'V',
                                                      });
                                                  }
                                                  break;
                                              default:
                                                  if (existsState(statePath)) {
                                                      setState(statePath, val, true);
                                                  } else {
                                                      createState(statePath, val, {
                                                          name: key,
                                                          type: typeof val,
                                                          role: 'value',
                                                          read: true,
                                                          write: false,
                                                      });
                                                  }
                                                  break;
                                          }
                                      }
                                  }
                              } else {
                                  const ts = Date.now();
                                  const statePath = `${id}.${i}`;
                                  let val = obj[i];
                   
                                  switch (i) {
                                      case 'BatVolt':
                                          val = val / 100;
                                          if (existsState(statePath)) {
                                              setState(statePath, val, true);
                                          } else {
                                              createState(statePath, val, {
                                                  name: 'Battery Voltage',
                                                  type: 'number',
                                                  desc: 'battery voltage',
                                                  role: 'value',
                                                  read: true,
                                                  write: false,
                                                  unit: 'V',
                                              });
                                          }
                                          break;
                  					case 'chargeMaxLimit':
                                          if (existsState(statePath)) {
                                              setState(statePath, val, true);
                                          } else {
                                              createState(statePath, val, {
                                                  name: 'maximum Charge Limit',
                                                  type: 'number',
                                                  desc: 'Maximum permissible charging power',
                                                  role: 'value',
                                                  read: true,
                                                  write: false,
                                                  unit: 'W',
                                              });
                                          }
                                          break;
                                      case 'acMode':
                                          if (existsState(statePath)) {
                                              setState(statePath, val, true);
                                          } else {
                                              createState(statePath, val, {
                                                  name: 'acMode, 1: input mode / 2: output mode',
                                                  type: 'number',
                                                  desc: '1: charging / 2: discharging',
                                                  role: 'value',
                                                  read: true,
                                                  write: false,
                                              });
                                          }
                                          break;
                  					case 'dataReady':
                                          if (existsState(statePath)) {
                                              setState(statePath, val, true);
                                          } else {
                                              createState(statePath, val, {
                                                  name: 'Data Ready, 0: Not ready, 1: Ready',
                                                  type: 'number',
                                                  role: 'value',
                                                  read: true,
                                                  write: false,
                                              });
                                          }
                                          break;	
                  					case 'dcStatus':
                                          if (existsState(statePath)) {
                                              setState(statePath, val, true);
                                          } else {
                                              createState(statePath, val, {
                                                  name: 'dcStatus, 0: Stopped, 1: Battery input, 2: Battery output',
                                                  type: 'number',
                                                  role: 'value',
                                                  read: true,
                                                  write: false,
                                              });
                                          }
                                          break;						
                  					case 'electricLevel':
                                          if (existsState(statePath)) {
                                              setState(statePath, val, true);
                                          } else {
                                              createState(statePath, val, {
                                                  name: 'Total Battery Charge Level',
                                                  type: 'number',
                                                  desc: 'SoC',
                                                  role: 'value',
                                                  read: true,
                                                  write: false,
                                                  unit: '%',
                                              });
                                          }
                                          break;
                                      case 'gridInputPower':
                                          if (existsState(statePath)) {
                                              setState(statePath, val, true);
                                          } else {
                                              createState(statePath, val, {
                                                  name: 'Grid Input Power to Battery',
                                                  type: 'number',
                                                  role: 'value',
                                                  read: true,
                                                  write: false,
                                                  unit: 'W',
                                              });
                                          }
                                          break;
                                      case 'gridReverse':
                                          if (existsState(statePath)) {
                                              setState(statePath, val, true);
                                          } else {
                                              createState(statePath, val, {
                                                  name: '0: Disabled, 1: Allowed reverse flow, 2: Forbidden reverse flow',
                                                  type: 'number',
                                                  role: 'value',
                                                  read: true,
                                                  write: false,
                                              });
                                          }
                                          break;
                                      case 'gridStandard':
                                          if (existsState(statePath)) {
                                              setState(statePath, val, true);
                                          } else {
                                              createState(statePath, val, {
                                                  name: 'Grid connection standard 0: Germany 1: France 2: Austria',
                                                  type: 'number',
                                                  role: 'value',
                                                  read: true,
                                                  write: false,
                                              });
                                          }
                                          break;
                                      case 'gridState':
                                          if (existsState(statePath)) {
                                              setState(statePath, val, true);
                                          } else {
                                              createState(statePath, val, {
                                                  name: 'GridState, 0: Not connected, 1: Connected',
                                                  type: 'number',
                                                  role: 'value',
                                                  read: true,
                                                  write: false,
                                              });
                                          }
                                          break;									
                                      case 'heatState':
                                          if (existsState(statePath)) {
                                              setState(statePath, val, true);
                                          } else {
                                              createState(statePath, val, {
                                                  name: 'Heat State, 0: Not heating, 1: heating',
                                                  type: 'number',
                                                  role: 'value',
                                                  read: true,
                                                  write: false,
                                              });
                                          }
                                          break;		
                  					case 'hyperTmp':
                                          val = (val - 2731) / 10.0;
                                          if (existsState(statePath)) {
                                              setState(statePath, val, true);
                                                  } else {
                                                      createState(statePath, val, {
                                                      name: 'Enclosure Temperature',
                                                      type: 'number',
                                                      role: 'value',
                                                      read: true,
                                                      write: false,
                                                      unit: '°C',
                                                      });
                                                  }
                                          break;
                  					case 'inputLimit':
                                          if (existsState(statePath)) {
                                              setState(statePath, val, true);
                                                  } else {
                                                      createState(statePath, val, {
                                                      name: 'AC charging power limit to Battery',
                                                      type: 'number',
                                                      role: 'value',
                                                      read: true,
                                                      write: false,
                                                      unit: 'W',
                                                      });
                                                  }
                                          break;
                  					case 'inverseMaxPower': 
                                          if (existsState(statePath)) {
                                              setState(statePath, val, true);
                                                  } else {
                                                      createState(statePath, val, {
                                                      name: 'Device Maximum Output Power Limit',
                                                      type: 'number',
                                                      role: 'value',
                                                      read: true,
                                                      write: false,
                                                      unit: 'W',
                                                      });
                                                  }
                                          break;
                  					case 'minSoc':
                                          val = val / 10;
                                          if (existsState(statePath)) {
                                              setState(statePath, val, true);
                                                  } else {
                                                      createState(statePath, val, {
                                                      name: 'min SoC',
                                                      type: 'number',
                                                      desc: 'minimum Battery SoCset',
                                                      role: 'value',
                                                      read: true,
                                                      write: false,
                                                      unit: '%',
                                                      });
                                                  }
                                          break;
                  					case 'outputHomePower':
                                          if (existsState(statePath)) {
                                              setState(statePath, val, true);
                                                  } else {
                  									createState(statePath, val, {
                                                      name: 'outputHomePower',
                                                      type: 'number',
                                                      role: 'value',
                                                      read: true,
                                                      write: false,
                  							        unit: 'W',
                                                      });
                  								}
                                          break;
                  					case 'outputLimit':
                                          if (existsState(statePath)) {
                                              setState(statePath, val, true);
                                                  } else {
                  									createState(statePath, val, {
                                                      name: 'Output power limit',
                                                      type: 'number',
                                                      role: 'value',
                                                      read: true,
                                                      write: false,
                  							        unit: 'W',
                                                      });
                  								}
                                          break;
                  					case 'outputPackPower':
                                          if (existsState(statePath)) {
                                              setState(statePath, val, true);
                                                  } else {
                  									createState(statePath, val, {
                                                      name: 'Output power to battery pack (charging)',
                                                      type: 'number',
                                                      role: 'value',
                                                      read: true,
                                                      write: false,
                  							        unit: 'W',
                                                      });
                  								}
                                          break;
                  					case 'packInputPower':
                                          if (existsState(statePath)) {
                                              setState(statePath, val, true);
                                                  } else {
                  									createState(statePath, val, {
                                                      name: 'Battery pack input power (discharging)',
                                                      type: 'number',
                                                      role: 'value',
                                                      read: true,
                                                      write: false,
                  							        unit: 'W',
                                                      });
                  								}
                                          break;
                  					case 'packNum':
                                          if (existsState(statePath)) {
                                              setState(statePath, val, true);
                                                  } else {
                  									createState(statePath, val, {
                                                      name: 'Number of battery packs',
                                                      type: 'number',
                                                      role: 'value',
                                                      read: true,
                                                      write: false,
                                                      });
                  								}
                                          break;
                  					case 'packState':
                                          if (existsState(statePath)) {
                                              setState(statePath, val, true);
                                                  } else {
                  									createState(statePath, val, {
                                                      name: 'Battery State, 0: Standby, 1: Charging, 2: Discharging',
                                                      type: 'number',
                                                      role: 'value',
                                                      read: true,
                                                      write: false,
                                                      });
                  								}
                                          break;
                  					case 'pass':
                                          if (existsState(statePath)) {
                                              setState(statePath, val, true);
                                                  } else {
                  									createState(statePath, val, {
                                                      name: 'Bypass, 0: No, 1: Yes',
                                                      type: 'number',
                                                      role: 'value',
                                                      read: true,
                                                      write: false,
                                                      });
                  								}
                                          break;
                  					case 'pvStatus':
                                          if (existsState(statePath)) {
                                              setState(statePath, val, true);
                                                  } else {
                  									createState(statePath, val, {
                                                      name: 'PV State producing, 0: Stopped, 1: Running',
                                                      type: 'number',
                                                      role: 'value',
                                                      read: true,
                                                      write: false,
                                                      });
                  								}
                                          break;	
                  					case 'remainOutTime':
                                          if (existsState(statePath)) {
                                              setState(statePath, val, true);
                                                  } else {
                  									createState(statePath, val, {
                                                      name: 'Estimated discharge time in minutes, if not predictable: 59940',
                                                      type: 'number',
                                                      role: 'value',
                                                      read: true,
                                                      write: false,
                  									unit: 'min',
                                                      });
                  								}
                                          break;		
                  					case 'reverseState':
                                          if (existsState(statePath)) {
                                              setState(statePath, val, true);
                                                  } else {
                  									createState(statePath, val, {
                                                      name: 'Reverse flow, 0: No, 1: Reverse flow',
                                                      type: 'number',
                                                      role: 'value',
                                                      read: true,
                                                      write: false,
                                                      });
                  								}
                                          break;			
                  					case 'rssi':
                                          if (existsState(statePath)) {
                                              setState(statePath, val, true);
                                                  } else {
                  									createState(statePath, val, {
                                                      name: 'Received Signal Strength Indicator',
                                                      type: 'number',
                                                      role: 'value',
                                                      read: true,
                                                      write: false,
                  									unit: 'dBm',
                                                      });
                  								}
                                          break;
                  					case 'smartMode':
                                          if (existsState(statePath)) {
                                              setState(statePath, val, true);
                                                  } else {
                  									createState(statePath, val, {
                                                      name: '1: parameter written to RAM / 0: parameter is written to flash.',
                                                      type: 'number',
                                                      role: 'value',
                                                      read: true,
                                                      write: false,
                                                      });
                  								}
                                          break;		
                  					case 'socLimit':
                                          if (existsState(statePath)) {
                                              setState(statePath, val, true);
                                                  } else {
                                                      createState(statePath, val, {
                                                      name: 'socLimit-Info 0: normal, 1: Charge limit reached, 2: Discharge limit reached',
                                                      type: 'number',
                                                      role: 'value',
                                                      read: true,
                                                      write: false,
                                                      });
                                                  }
                                          break;
                  					case 'socSet':
                                          val = val / 10;
                                          if (existsState(statePath)) {
                                              setState(statePath, val, true);
                                                  } else {
                                                      createState(statePath, val, {
                                                      name: 'max SoC',
                                                      type: 'number',
                                                      desc: 'maximum Battery SoCset',
                                                      role: 'value',
                                                      read: true,
                                                      write: false,
                                                      unit: '%',
                                                      });
                                                  }
                                          break;
                  					case 'socStatus':
                                          if (existsState(statePath)) {
                                              setState(statePath, val, true);
                                                  } else {
                                                      createState(statePath, val, {
                                                      name: 'Info of Calibrating, 0: No / 1: Calibrating',
                                                      type: 'number',
                                                      role: 'value',
                                                      read: true,
                                                      write: false,
                                                      });
                                                  }
                                          break;
                  					case 'solarInputPower':
                                          if (existsState(statePath)) {
                                              setState(statePath, val, true);
                                                  } else {
                                                      createState(statePath, val, {
                                                      name: 'Total Solar Input Power',
                                                      type: 'number',
                                                      role: 'value',
                                                      read: true,
                                                      write: false,
                  									unit: 'W',
                                                      });
                                                  }
                                          break;
                                      case 'solarPower1':
                                          if (existsState(statePath)) {
                                              setState(statePath, val, true);
                                                  } else {
                                                      createState(statePath, val, {
                                                      name: 'Solar line 1 input power',
                                                      type: 'number',
                                                      role: 'value',
                                                      read: true,
                                                      write: false,
                  									unit: 'W',
                                                      });
                                                  }
                                          break;
                                      case 'solarPower2':
                                          if (existsState(statePath)) {
                                              setState(statePath, val, true);
                                                  } else {
                                                      createState(statePath, val, {
                                                      name: 'Solar line 2 input power',
                                                      type: 'number',
                                                      role: 'value',
                                                      read: true,
                                                      write: false,
                  									unit: 'W',
                                                      });
                                                  }
                                          break;
                                      case 'solarPower3':
                                          if (existsState(statePath)) {
                                              setState(statePath, val, true);
                                                  } else {
                                                      createState(statePath, val, {
                                                      name: 'Solar line 3 input power',
                                                      type: 'number',
                                                      role: 'value',
                                                      read: true,
                                                      write: false,
                  									unit: 'W',
                                                      });
                                                  }
                                          break;
                                      case 'solarPower4':
                                          if (existsState(statePath)) {
                                              setState(statePath, val, true);
                                                  } else {
                                                      createState(statePath, val, {
                                                      name: 'Solar line 4 input power',
                                                      type: 'number',
                                                      role: 'value',
                                                      read: true,
                                                      write: false,
                  									unit: 'W',
                                                      });
                                                  }
                                          break;
                                      case 'solarPower5':
                                          if (existsState(statePath)) {
                                              setState(statePath, val, true);
                                                  } else {
                                                      createState(statePath, val, {
                                                      name: 'Solar line 5 input power',
                                                      type: 'number',
                                                      role: 'value',
                                                      read: true,
                                                      write: false,
                  									unit: 'W',
                                                      });
                                                  }
                                          break;
                                      case 'solarPower6':
                                          if (existsState(statePath)) {
                                              setState(statePath, val, true);
                                                  } else {
                                                      createState(statePath, val, {
                                                      name: 'Solar line 6 input power',
                                                      type: 'number',
                                                      role: 'value',
                                                      read: true,
                                                      write: false,
                  									unit: 'W',
                                                      });
                                                  }
                                          break;
                                      case 'timestamp':
                                          const timestampVal = obj[i];
                                          const timestampFormatted = formatTime(timestampVal);
                                          if (existsState(statePath)) setState(statePath, timestampVal, true);
                                          else createState(statePath, timestampVal, {
                                              name: 'unix timestamp (timestamp)',
                                              type: 'number',
                                              desc: 'unix timestamp in Seconds since Jan 01 1970 (UTC)',
                                              role: 'value',
                                              read: true,
                                              write: false,
                                          });
                                          if (existsState(id + '.timeUpdateTimestamp')) setState(id + '.timeUpdateTimestamp', timestampFormatted, true);
                                          else createState(id + '.timeUpdateTimestamp', timestampFormatted, {
                                              name: 'TimeUpdate (timestamp)',
                                              type: 'string',
                                              desc: 'unix timestamp in readable Format (timestamp)',
                                              read: true,
                                              write: false,
                                          });
                                          break;
                                      case 'ts':
                                          const tsVal = obj[i];
                                          const tsFormatted = formatTime(tsVal);
                                          if (existsState(statePath)) setState(statePath, tsVal, true);
                                          else createState(statePath, tsVal, {
                                              name: 'unix timestamp (ts)',
                                              type: 'number',
                                              desc: 'unix timestamp in Seconds since Jan 01 1970 (UTC)',
                                              role: 'value',
                                              read: true,
                                              write: false,
                                          });
                                          if (existsState(id + '.timeUpdateTs')) setState(id + '.timeUpdateTs', tsFormatted, true);
                                          else createState(id + '.timeUpdateTs', tsFormatted, {
                                              name: 'TimeUpdate (ts)',
                                              type: 'string',
                                              desc: 'unix timestamp in readable Format (ts)',
                                              read: true,
                                              write: false,
                                          });
                                          break;			
                                      default:
                                          if (existsState(statePath)) {
                                              setState(statePath, val, true);
                                          } else {
                                              createState(statePath, val, {
                                                  name: i,
                                                  type: typeof val,
                                                  role: 'value',
                                                  read: true,
                                                  write: false,
                                              });
                                          }
                                          break;
                                  }
                              }
                          }
                      } catch (e) {
                          log(`Error in iter: ${e}`, 'info');
                      }
                  }
                   
                  

                  Historisch:
                  Frühere Versionen der Scripte sind hier (im Spoiler) zu finden.
                  Die kompletten Scripte sind zu groß für diesen Post, daher habe ich sie dort ausgelagert.

                  maxclaudiM
                  maxclaudiM
                  maxclaudi
                  schrieb zuletzt editiert von maxclaudi
                  #692

                  HINWEIS ZU SUPPORT- & SOFTWARE-ANFRAGEN (PM / E-MAIL)
                  In diese Projekte sind monatelange Forschung und Reverse Engineering geflossen.
                  Es gelten bei mir klare Spielregeln für den Support und Software per privater Nachricht (PN) oder E-Mail:

                  • Keine Antwort auf KI-Generierung: Offensichtlich mit KI generierte Support- oder Software-Anfragen per E-Mail oder PN werden nicht beantwortet.

                  • Kein Support für anonyme "0-Beitrag-Accounts": Wer keine nachweisbare Foren-Historie (hier im ioBroker-Forum, in anderen bekannten Foren oder auf GitHub etc.) besitzt und komplett anonym bleiben möchte, erhält von mir privat keinen Support.
                    Wenn ein neuer Account ohne einen einzigen Beitrag eine PM sendet, kann darin aber gerne offen gelegt werden, in welchem anderen Forum der User mit welchem Namen aktiv ist.

                  • Der offizielle Weg steht jedem offen: Jede(r) kann sich hier im Forum anmelden und offiziell im Thread seine Fragen stellen. Wer sich aktiv beteiligt, zeigt nach einiger Zeit von ganz alleine, ob er/sie vertrauenswürdig ist.

                  edit/PS:
                  Ausnahme – Reguläre Freischaltung zURLtool:
                  Anfragen zur offiziellen Freischaltung (Seriennummer / Freischalt-Code für die Windows-Versionen des zURLtools) über seriöse E-Mail-Adressen sind von dieser Support-Regel natürlich ausgenommen. Das kann jede(r) wie bisher ganz normal beantragen und bekommen!

                  Wer Vorteile von Open Source und/oder Community-Arbeit nutzen möchte, kann auch so fair sein und etwas transparent auftreten.

                  Private „Hintertür-Anfragen“ zur Code-Abgreifung versuche ich zu vermeiden.

                  Ich helfe gerne aber sorry, das muss leider sein.

                  Liebe Grüße
                  maxclaudi

                  Zendure zenSDK Lokal API, SmartMode, SolarFlow AC 800 Pro 2

                  neue Zendure Geräte Cloudless: zURLtool V2.0

                  1 Antwort Letzte Antwort
                  0
                  • Kosta DachK
                    Kosta DachK
                    Kosta Dach
                    schrieb zuletzt editiert von
                    #693

                    Hi Maxclaudi, ich habe dir eben über meinen anderen Account @Kocta geschrieben, leider kann ich mich dort nicht mehr anmelden. Angeblich habe ich keine E-Mail hinterlegt und kann jetzt nichts machen :(.
                    Kannst du mir nochmal die Info zusenden.
                    Sorry für die Unannehmlichkeiten
                    Login fehlgeschlagen
                    Entschuldigung, dieses Konto wurde bis 9.11.2026, 15:11:56 (Reason: fehlende Mail Adresse) gesperrt.

                    maxclaudiM 1 Antwort Letzte Antwort
                    0
                    • Kosta DachK Kosta Dach

                      Hi Maxclaudi, ich habe dir eben über meinen anderen Account @Kocta geschrieben, leider kann ich mich dort nicht mehr anmelden. Angeblich habe ich keine E-Mail hinterlegt und kann jetzt nichts machen :(.
                      Kannst du mir nochmal die Info zusenden.
                      Sorry für die Unannehmlichkeiten
                      Login fehlgeschlagen
                      Entschuldigung, dieses Konto wurde bis 9.11.2026, 15:11:56 (Reason: fehlende Mail Adresse) gesperrt.

                      maxclaudiM
                      maxclaudiM
                      maxclaudi
                      schrieb zuletzt editiert von
                      #694

                      kein Problem.

                      Zendure zenSDK Lokal API, SmartMode, SolarFlow AC 800 Pro 2

                      neue Zendure Geräte Cloudless: zURLtool V2.0

                      1 Antwort Letzte Antwort
                      0

                      Hey! Du scheinst an dieser Unterhaltung interessiert zu sein, hast aber noch kein Konto.

                      Hast du es satt, bei jedem Besuch durch die gleichen Beiträge zu scrollen? Wenn du dich für ein Konto anmeldest, kommst du immer genau dorthin zurück, wo du zuvor warst, und kannst dich über neue Antworten benachrichtigen lassen (entweder per E-Mail oder Push-Benachrichtigung). Du kannst auch Lesezeichen speichern und Beiträge positiv bewerten, um anderen Community-Mitgliedern deine Wertschätzung zu zeigen.

                      Mit deinem Input könnte dieser Beitrag noch besser werden 💗

                      Registrieren Anmelden
                      Antworten
                      • In einem neuen Thema antworten
                      Anmelden zum Antworten
                      • Älteste zuerst
                      • Neuste zuerst
                      • Meiste Stimmen


                      Support us

                      ioBroker
                      Community Adapters
                      Donate

                      501

                      Online

                      33.1k

                      Benutzer

                      84.2k

                      Themen

                      1.4m

                      Beiträge
                      Community
                      Impressum | Datenschutz-Bestimmungen | Nutzungsbedingungen | Einwilligungseinstellungen
                      ioBroker Community 2014-2026
                      logo
                      • Anmelden

                      • Du hast noch kein Konto? Registrieren

                      • Anmelden oder registrieren, um zu suchen
                      • Erster Beitrag
                        Letzter Beitrag
                      0
                      • Home
                      • Aktuell
                      • Ungelesen 0
                      • Kategorien
                      • Unreplied
                      • Beliebt
                      • GitHub
                      • Docu
                      • Hilfe