Weiter zum Inhalt
  • Home
  • Aktuell
  • Tags
  • 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

  • Neuer ioBroker-Blog online: Monatsrückblick März/April 2026
    BluefoxB
    Bluefox
    8
    1
    1.8k

  • Verwendung von KI bitte immer deutlich kennzeichnen
    HomoranH
    Homoran
    11
    1
    743

  • Monatsrückblick Januar/Februar 2026 ist online!
    BluefoxB
    Bluefox
    18
    1
    1.2k

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

Geplant Angeheftet Gesperrt Verschoben JavaScript
290 Beiträge 14 Kommentatoren 24.0k Aufrufe 13 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.
  • maxclaudiM maxclaudi

    @Daniel-8 sagte:
    Hatte heute ziemliche viele Probleme. Sehr viele Meldungen bis hin das ich sogar die Ip Adresse prüfen sollte. Hatte aber auch festgestellt, das ich einen sehr schlechten Rssi Wert von -75 hatte.
    Habe dann mal mein Wlan ausgemacht und wieder an und hatte dann gleich eine bessere Datenrate und auch einen veränderten Rssi von -67 in der Fritzbox.

    Das lag oder liegt eindeutig an Deiner WLAN-Verbindung.
    Ein RSSI von -75 dBm ist für eine stabile HTTP-Kommunikation grenzwertig.
    Dass es nach dem WLAN-Reset auf -67 dBm gesprungen ist, zeigt, dass der Solarflow zuvor keine saubere Verbindung hatte.

    Was mir aber jetzt auf fiel, das der Rssi Wert in den Objekten durch das Script nicht aktualisiert wurde. Da stand immer noch das Datum vom 7.5.2026 dran.

    Das ist kein Fehler, sondern beabsichtigt.
    Das Script ist auf maximale Effizienz optimiert:

    • es fragt im Intervall das JSON mit allen Daten ab.
    • Eintreffende Daten werden mit den alten Werten verglichen.
    • Nur wenn sich ein Wert (z. B. der RSSI) zum vorherigen Stand unterscheidet, wird er aktualisiert.
    • Bleibt der Wert identisch, wird er nicht neu geschrieben. Das schont die Ressourcen deines ioBroker-Systems.

    Dann habe ich das Script neu gestartet und da hat sich auch der Rssi Wert verändert.

    Korrekt. Bei einem Script-Neustart gibt es noch keine Vergleichswerte im Speicher.
    Daher werden beim Start einmalig alle Datenpunkte initial geschrieben.

    Warum heute so Viele meldungen anstanden kann ich mir noch nicht erklären, da ich eigentlich nichts verändert habe.

    Wenn 4x hintereinander kein HTTP GET erfolgreich war
    (z. B. wegen Deiner schlechten WLAN-Verbindung), schreibt mein Script zur Diagnose in das LOG:

    log("Keine Verbindung möglich. Zendure-Geräte IP prüfen!", "error")
    

    Das dient nur der Info und weist auf Handlungsbedarf hin.
    Das Script selbst läuft weiter und funktioniert auch weiterhin.
    Sollte ein GET wieder funktionieren, arbeitet es trotz der vorherigen Meldungen normal weiter.

    Dass mit dieser Version mehr LOG-Meldungen kommen, ist zur Fehlersuche im Feld beabsichtigt.
    In einer künftigen Version werde ich evtl. eine Option einbauen, mit der bestimmte oder alle LOGs abgeschaltet werden können.

    Bitte versuche, Deine WLAN-Verbindung dauerhaft zu stabilisieren.
    Bei einem RSSI von -75 ist der Webserver des Geräts meistens nicht mehr schnell genug erreichbar, was dann zwangsläufig zu Timeouts führt.

    D Offline
    D Offline
    Daniel 8
    schrieb am zuletzt editiert von
    #281

    @maxclaudi sagte:

    Was mir aber jetzt auf fiel, das der Rssi Wert in den Objekten durch das Script nicht aktualisiert wurde. Da stand immer noch das Datum vom 7.5.2026 dran.

    Das ist kein Fehler, sondern beabsichtigt.
    Das Script ist auf maximale Effizienz optimiert:

    es fragt im Intervall das JSON mit allen Daten ab.
    Eintreffende Daten werden mit den alten Werten verglichen.
    Nur wenn sich ein Wert (z. B. der RSSI) zum vorherigen Stand unterscheidet, wird er aktualisiert.
    Bleibt der Wert identisch, wird er nicht neu geschrieben. Das schont die Ressourcen deines ioBroker-Systems.

    Ich habe ja 5 Minuten gewartet und der Wert blieb auf dem alten mit -75 obwohl er laut fritzbox auf -67 war. Nach dem ich dann das Script manuell neu startete stimmte der wert mit der Fritzbox überein.

    Solarflow 800 Pro mit 1,3 Kwp / Iobroker / Homematic / Shellys / Mediola / Intertechno

    1 Antwort Letzte Antwort
    0
    • D Offline
      D Offline
      Daniel 8
      schrieb am zuletzt editiert von
      #282

      Gerade geschaut. Der Wert steht in der Fritzbox auf -69. In der Systemvariable immer noch auf -67. Erst nach Neustart des Scripts wurde der Wert aktualisiert.

      Solarflow 800 Pro mit 1,3 Kwp / Iobroker / Homematic / Shellys / Mediola / Intertechno

      maxclaudiM 1 Antwort Letzte Antwort
      1
      • D Daniel 8

        Gerade geschaut. Der Wert steht in der Fritzbox auf -69. In der Systemvariable immer noch auf -67. Erst nach Neustart des Scripts wurde der Wert aktualisiert.

        maxclaudiM Offline
        maxclaudiM Offline
        maxclaudi
        schrieb am zuletzt editiert von
        #283

        @Daniel-8 sagte:

        Gerade geschaut. Der Wert steht in der Fritzbox auf -69. In der Systemvariable immer noch auf -67. Erst nach Neustart des Scripts wurde der Wert aktualisiert.

        Richtig erkannt, Dankeschön.
        Hatte bewusst den stetigen Vergleich speziell nur auf rssi (für mich) deaktiviert.
        Wurde mit dem update wieder aktiviert.

        HINWEIS:
        Beim update sind im CONFIG:
        timeout: 1500ms, weil es hier seit > 1 Woche stabil läuft.

        // Timout Handler HTTP GET /POST
        // Wichtig:
        // getTimeoutMs < getIntervalMs
        // postTimeoutMs < getIntervalMs
        // Bei instabiler Verbindung ggf. erhöhen (z. B. 3000–5000 ms).
        const getTimeoutMs = 1500;  // Empfehlung: 2000 ms.
        const postTimeoutMs = 1500; // Empfehlung: 2000 ms.
        

        @Daniel-8
        bitte verwende bei Deinem WLAN-Problem lieber 2000ms oder mehr.

        update 11.05.26 11:00h konfig getTimeoutMs / rssi.

        Ich schreibe meistens sehr direkt – bitte nicht falsch verstehen, es ist nie böse gemeint. Das ist einfach mein Stil und niemals abwertend gemeint.

        1 Antwort Letzte Antwort
        0
        • D Offline
          D Offline
          Daniel 8
          schrieb am zuletzt editiert von
          #284

          @maxclaudi

          Wir haben zu danken, das du so etwas auf die Beine stellst.

          Ja den timeout werde ich erhöhen.

          Ich kann leider nur mit solchen Kleinigkeiten helfen.

          Solarflow 800 Pro mit 1,3 Kwp / Iobroker / Homematic / Shellys / Mediola / Intertechno

          maxclaudiM 1 Antwort Letzte Antwort
          0
          • D Daniel 8

            @maxclaudi

            Wir haben zu danken, das du so etwas auf die Beine stellst.

            Ja den timeout werde ich erhöhen.

            Ich kann leider nur mit solchen Kleinigkeiten helfen.

            maxclaudiM Offline
            maxclaudiM Offline
            maxclaudi
            schrieb am zuletzt editiert von
            #285

            @Daniel-8

            Ohne vor allem Dich, anfangs noch @Michi-0 und auch die Rückmeldungen von @Mabbi, wäre das Script nie (so früh) entstanden.

            Ein großes Dankeschön an alle, die mit ihren Feedbacks dazu beigetragen haben und vielleicht auch noch beitragen werden.

            Ein ganz großes Dankeschön speziell an Dich, @Daniel-8.
            Deine Rückmeldungen sind keine „Kleinigkeiten“ – sie sind für alle hilfreich.

            Mehr Augen sehen einfach mehr als nur meine zwei.

            Dankeschön!

            Ich schreibe meistens sehr direkt – bitte nicht falsch verstehen, es ist nie böse gemeint. Das ist einfach mein Stil und niemals abwertend gemeint.

            1 Antwort Letzte Antwort
            1
            • D Offline
              D Offline
              Daniel 8
              schrieb am zuletzt editiert von
              #286

              rssi wird jetzt regelmäßig aktualisiert. Danke

              Solarflow 800 Pro mit 1,3 Kwp / Iobroker / Homematic / Shellys / Mediola / Intertechno

              1 Antwort Letzte Antwort
              1
              • maxclaudiM maxclaudi

                Zendure-Geräte Webserver: Steuerung über das zenSDK (HTTP)
                2026.05.02_00.39h; update 19.05.26 16:50h BatteryType.
                für das ioBroker-Forum
                In memory of Daisy 02.05.24 – miss you.

                Dieses Skript ermöglicht die lokale Steuerung eines Zendure-Geräts.
                Es funktioniert sofort und benötigt keinerlei Keys oder sonstige Authentifizierungen.
                Es ist mit APP und Cloud nutzbar.
                Oder auch nur lokal, wenn für Gerät(e) der Internetzugang gesperrt wird.
                Dann ist jedoch die Verwendung der App per WLAN nicht mehr möglich.
                Außerdem sollte eine Möglichkeit geschaffen werden, einen Zeitserver zu erreichen.
                Also nur die Zendure spezifischen URL sperren.
                Näher möchte ich hier nicht darauf eingehen.

                Voraussetzungen:

                • Hardware: Zendure-Geräte (Generation 2025/2026) mit integriertem Webserver.

                • Aktivierung: Vor der ersten Nutzung muss HEMS einmalig aktiviert, kurz abgewartet und anschließend wieder deaktiviert werden.

                • Netzwerk: Die IP-Adresse des Zendure-Geräts muss bekannt sein. Empfehle dringend, dem Gerät im Router oder Access Point eine dauerhafte, feste IP zuzuweisen.

                Wichtiger Hinweis zum Flash-Speicher:
                Ich bin kein Freund von automatischen Befehlsketten, die für den Nutzer nicht nachvollziehbar sind – vor allem, wenn sie zu unnötigen Schreibvorgängen im Flash-Speicher führen.

                Nach aktuellem Stand ist sicher: Wenn smartMode: 1 gesetzt ist, werden zumindest die Werte für outputLimit und inputLimit lediglich in den RAM geschrieben.

                Ein Modewechsel (z. B. acMode, Energiepläne, Automodi, MQTT-Konfiguration) wird hingegen fast immer in den Flash-Speicher geschrieben.
                Dabei wird oft nicht nur der einzelne Wert, sondern die gesamte Konfiguration dauerhaft gespeichert.


                Neue Datenpunkte unter "control"
                Im aktuellen Skript gibt es zusätzliche Datenpunkte sowie den Ordner automaticKonfig mit zwei Schaltern (Boolean):

                1. auto_inputLimitMode (Watt)
                  Hier kann ein Wert in Watt für das Ladelimit (inputLimit) gesetzt werden.
                  Das Skript prüft daraufhin automatisch:
                • Ist acMode: 1 und outputLimit: 0 W gesetzt?

                • Falls nicht, wird automatisch acMode: 1 und outputLimit: 0 gesetzt, bevor mit dem gewählten Wert vom Netz geladen wird.

                • Ist der Schalter input_output_LimitMode_smartMode_RAM aktiviert, wird vor dem Senden zusätzlich geprüft, ob smartMode: 1 aktiv ist.
                  Wenn nicht, wird dieser automatisch mitgesendet.

                • Prinzip: Es wird nur gesendet, was zwingend nötig ist (Vorab-Prüfung).

                1. auto_outputLimitMode (Watt)
                  Dies ist das Gegenstück zum auto_inputLimitMode für die Entladesteuerung.

                2. smartModeWatcher (Schalter)
                  Wenn dieser Slider aktiviert ist (true), wird bei jedem empfangenen Report automatisch geprüft, ob smartMode: 1 gesetzt ist.
                  Sollte der Wert auf 0 stehen, setzt das Skript ihn automatisch wieder auf 1. Dieser Slider kann manuell oder per Skript (de-)aktiviert werden.

                Weitere Hinweise

                • inverseMaxPower: Dieser Wert sollte nicht zur laufenden Regelung verwendet werden. Er definiert die Obergrenze, die der Wechselrichter maximal ausgeben darf, und wird sehr wahrscheinlich in den Flash geschrieben.

                • chargeMaxLimit:
                  Mit Vorsicht zu genießen. Diesen Wert am besten nicht für regelmäßige Limit-Anpassungen verwenden.

                • Verbraucher-Geräte:
                  Vorsicht bei Consumer-Produkten (auch Zendure); hier wird oft bei jeder Parameteränderung die komplette Konfiguration in den Flash geschrieben.

                • mDNS: Funktioniert zwar, hat sich in Tests jedoch als unzuverlässig erwiesen. Eine feste IP-Adresse ist die bessere Wahl.

                • Struktur-Update: Die Verzeichnisse befinden sich nun direkt im Hauptordner:

                • control (inkl. automaticKonfig)

                • packData (unter Cxxxxx finden sich die jeweiligen Batteriedaten)

                • properties

                maxclaudiM Offline
                maxclaudiM Offline
                maxclaudi
                schrieb am zuletzt editiert von
                #287

                Technischer Hinweis zur Kaskadierung von Speichersystemen:
                In der Praxis taucht immer wieder der Wunsch auf, zwei Speichersysteme (z. B. großer Dach-PV-Akku + Zendure) nacheinander zu schalten.
                Exemplarisch für dieses Denkmodell steht diese aktuelle Anfrage im Forum:

                ... Idee war aber eigentlich, dass ich in der App durch den EcoTracker, DANN ERST ins Haus einspeise, wenn der Stromzähler über 300Watt anzeigt.
                Dann ist nämlich die grosse Batterie leer und schaltet sich ab.
                Quelle

                Bevor hier jemand auch so eine Idee umsetzen möchte:

                LiFePO4-Zellen degenerieren durch Alterung im Standby (vor allem bei extrem hohen oder niedrigen SoC-Ständen) schneller als durch eine gleichmäßige, schonende Nutzung im optimalen mittleren Fenster.

                Ein System künstlich „warten“ zu lassen, schadet der Hardware mehr, als es gut tut.

                Wer zwei Systeme besitzt, sollte diese zeitgleich und unabhängig voneinander auf die Grundlast bzw. den aktuellen Hausverbrauch regeln lassen (echter Parallelbetrieb).

                Jede künstliche Blockade („Warte bis System A leer ist“) widerspricht der Effizienz und der Physik einer PV-Regelung.

                Eine evtl. Option wäre z. B., siehe HIER und HIER

                Ich schreibe meistens sehr direkt – bitte nicht falsch verstehen, es ist nie böse gemeint. Das ist einfach mein Stil und niemals abwertend gemeint.

                haselchenH 1 Antwort Letzte Antwort
                0
                • maxclaudiM maxclaudi

                  Technischer Hinweis zur Kaskadierung von Speichersystemen:
                  In der Praxis taucht immer wieder der Wunsch auf, zwei Speichersysteme (z. B. großer Dach-PV-Akku + Zendure) nacheinander zu schalten.
                  Exemplarisch für dieses Denkmodell steht diese aktuelle Anfrage im Forum:

                  ... Idee war aber eigentlich, dass ich in der App durch den EcoTracker, DANN ERST ins Haus einspeise, wenn der Stromzähler über 300Watt anzeigt.
                  Dann ist nämlich die grosse Batterie leer und schaltet sich ab.
                  Quelle

                  Bevor hier jemand auch so eine Idee umsetzen möchte:

                  LiFePO4-Zellen degenerieren durch Alterung im Standby (vor allem bei extrem hohen oder niedrigen SoC-Ständen) schneller als durch eine gleichmäßige, schonende Nutzung im optimalen mittleren Fenster.

                  Ein System künstlich „warten“ zu lassen, schadet der Hardware mehr, als es gut tut.

                  Wer zwei Systeme besitzt, sollte diese zeitgleich und unabhängig voneinander auf die Grundlast bzw. den aktuellen Hausverbrauch regeln lassen (echter Parallelbetrieb).

                  Jede künstliche Blockade („Warte bis System A leer ist“) widerspricht der Effizienz und der Physik einer PV-Regelung.

                  Eine evtl. Option wäre z. B., siehe HIER und HIER

                  haselchenH Offline
                  haselchenH Offline
                  haselchen
                  Most Active
                  schrieb am zuletzt editiert von
                  #288

                  @maxclaudi

                  Jeder weiss vermutlich , dass ich gemeint bin 😉
                  WR, Batterie und PV Wissen ist bei mir nur aus Lesen in den entsprechenden Foren und Erfahrung aus einem BKW vorhanden.
                  In der ganzen Materie wirst Du tiefer drinstecken.
                  Beim ganzen PV Thema habe ich gelernt, es gibt kein richtig oder falsch.
                  Dafür sind die Gegebenheiten in jedem Einzelfall zu speziell.
                  Beim Deye ist alles , für meine Belange, perfekt eingestellt.
                  Eventuell habe ich mich mißverständlich ausgedrückt.
                  Mit "leer" meine ich 30%. Das ist die Grenze für "Notstrom". Und in den Winter/Frühjahrsmonaten ein ganz normaler Wert.
                  Im Sommer wird die Batterie über Nacht nie auf 30% fallen.
                  Mein Szenario soll in den besagten Winter/Frühjahrsmonaten greifen.
                  Das Abschalten des Deye ist auch, entgegen Deiner Aussage, wenn ich das so richtig verstanden habe, unschädlich. In Absprache mit Solarteur und Hersteller.
                  Der zieht nämlich abends/nachts um die 150Watt für seine "Arbeit".
                  Um die Batterie vom Zendure mach ich mir keine Sorgen.
                  Die habe ich zu Testzwecken gekauft.
                  Um Gedankengänge auszuprobieren.
                  Viele Schreibvorgänge muss der Speicher gar nicht aushalten.
                  Eigentlich nur 2.
                  Wenn über 300 Watt , Batterie anschalten -> wenn bei 10% -> Batterie aus und dann Standby bis Sonne scheint.
                  Ich wurschtel mich nochmal durch alle Skripte, Informationen und Datenpunkte und sage einen grossen Dank an
                  Dich , @felli und @nograx für den Gehirnschmalz, den ihr für mein Anliegen aufgebracht habt!

                  Synology DS218+ & 2 x Fujitsu Esprimo (VM/Container) + FritzBox7590 + 2 AVM 3000 Repeater & Homematic & HUE & Osram & Xiaomi, NPM 10.9.7, Nodejs 22.22.2 ,JS Controller 7.0.7 ,Admin 7.8.24

                  maxclaudiM 1 Antwort Letzte Antwort
                  0
                  • haselchenH haselchen

                    @maxclaudi

                    Jeder weiss vermutlich , dass ich gemeint bin 😉
                    WR, Batterie und PV Wissen ist bei mir nur aus Lesen in den entsprechenden Foren und Erfahrung aus einem BKW vorhanden.
                    In der ganzen Materie wirst Du tiefer drinstecken.
                    Beim ganzen PV Thema habe ich gelernt, es gibt kein richtig oder falsch.
                    Dafür sind die Gegebenheiten in jedem Einzelfall zu speziell.
                    Beim Deye ist alles , für meine Belange, perfekt eingestellt.
                    Eventuell habe ich mich mißverständlich ausgedrückt.
                    Mit "leer" meine ich 30%. Das ist die Grenze für "Notstrom". Und in den Winter/Frühjahrsmonaten ein ganz normaler Wert.
                    Im Sommer wird die Batterie über Nacht nie auf 30% fallen.
                    Mein Szenario soll in den besagten Winter/Frühjahrsmonaten greifen.
                    Das Abschalten des Deye ist auch, entgegen Deiner Aussage, wenn ich das so richtig verstanden habe, unschädlich. In Absprache mit Solarteur und Hersteller.
                    Der zieht nämlich abends/nachts um die 150Watt für seine "Arbeit".
                    Um die Batterie vom Zendure mach ich mir keine Sorgen.
                    Die habe ich zu Testzwecken gekauft.
                    Um Gedankengänge auszuprobieren.
                    Viele Schreibvorgänge muss der Speicher gar nicht aushalten.
                    Eigentlich nur 2.
                    Wenn über 300 Watt , Batterie anschalten -> wenn bei 10% -> Batterie aus und dann Standby bis Sonne scheint.
                    Ich wurschtel mich nochmal durch alle Skripte, Informationen und Datenpunkte und sage einen grossen Dank an
                    Dich , @felli und @nograx für den Gehirnschmalz, den ihr für mein Anliegen aufgebracht habt!

                    maxclaudiM Offline
                    maxclaudiM Offline
                    maxclaudi
                    schrieb am zuletzt editiert von
                    #289

                    @haselchen
                    Alles gut, kein Ding! 👍

                    Der Hinweis war auch überhaupt nicht als persönlicher Angriff gemeint.
                    Zu Test- und Bastelzwecken kann man natürlich alles ausprobieren – genau dafür ist ein Hobby ja da.

                    Mir ging es mit dem Post rein um einen Hinweis für stille Mitleser, die so ein Setup nicht
                    als Hobby, sondern für den produktiven Regelbetrieb planen und sich der elektrochemischen und regelungstechnischen Hintergründe (Stichwort: Alterung im Standby) nicht bewusst sind.

                    Wenn der große Dach-Akku im Winter auf 30% gehalten wird, um als Notstrompuffer zu dienen, ist das völlig legitim.
                    Aber genau hier schnappt die Falle zu: Das Zendure-System steht während dieser gesamten Zeit (bis der Hauptakku auf 30% sinkt) ungenutzt bei 100% oder einem hohen SoC-Wert im Standby.
                    Das ist exakt das, was vermieden werden sollte.
                    Das Zendure-System altert im Winter primär durch das Herumstehen, nicht durch die Nutzung.

                    Viel Erfolg beim Tüfteln und Skript-Wurschteln!

                    Ich schreibe meistens sehr direkt – bitte nicht falsch verstehen, es ist nie böse gemeint. Das ist einfach mein Stil und niemals abwertend gemeint.

                    1 Antwort Letzte Antwort
                    1
                    • maxclaudiM maxclaudi

                      NEU
                      Zendure-Geräte Webserver: Steuerung über das zenSDK (HTTP)
                      2026.05.02_00.39h für das ioBroker-Forum; update 19.05.26 16:50h BatteryType.
                      In memory of Daisy 02.05.24 – miss you.

                      Viel Spaß!


                      // ioBroker JavaScript: Zendure zenSDK Adapter-Ersatz für ein Zendure-Gerät.
                      // Für alle Geräte ab 2025/2026, die zenSDK unterstützen, wie z. B.:
                      // SF800, SF800 PLUS, 800Pro (2), 1600AC, SF2400AC(+) usw.
                      // (c) maxclaudi 2026.05.02_00.39h für das ioBroker-Forum; update 19.05.26 16:50h BatteryType.
                      // In memory of Daisy 02.05.24 – miss you.
                      // 
                      // EIN WORT IN EIGENER SACHE:
                      // Dieses Skript basiert auf vielen Stunden Hardware-Tests und Analysen (besonders zum Flash-Schutz).
                      // In der Vergangenheit wurden meine Erkenntnisse oft ohne Erwähnung in andere Projekte übernommen.
                      // Ich teile diesen Code gerne. Wer diese Logik in öffentliche Adapter integriert, ist herzlich 
                      // eingeladen – ich bitte jedoch um die Fairness, die Quelle zu nennen. 
                      // Das ist der "Lohn" für meine Zeit und Forschung.
                      //----------------------------------------------------------------------------------------------------
                      // Konfiguration
                      //
                      // Hinweis bei mehreren Zendure-Geräten:
                      //   -> Für jedes Gerät ein eigenes Skript mit individueller Konfiguration verwenden!
                      //   -> IP-Adresse anpassen.
                      //   -> maxInputLimit / maxOutputLimit (abhängig vom Gerätetyp) einstellen.
                      //   -> Intervall: getIntervalMs = 5000 ms (Standard). 
                      //      Bei Verbindungsproblemen oder WLAN-Lags wird ein Wert von mindestens 8000 ms empfohlen.
                      //
                      // Empfehlung zur Geräteanzahl:
                      //   • Bis zu 3 Geräte: völlig unkritisch.
                      //   • 4 Geräte: problemlos möglich.
                      //   • Mehr als 4 Geräte: nicht empfohlen. Aber abhängig von Systemleistung und Intervall prüfen.
                      //
                      // Das offizielle MQTT ist mit einer Aktualisierungsrate von bis zu 90 Sek. zu langsam.
                      // Ich empfehle die Nutzung des zenSDK.
                      // Hinweis: Das (De-)Aktivieren von MQTT sowie die MQTT-Verbindungsüberwachung werden nicht unterstützt.
                      //
                      // Damit neue Datenpunkte automatisch aus dem JSON erstellt werden können:
                      // -> Instanzen -> JavaScript-Adapter -> Allgemeine Einstellungen -> "Kommando 'setObject' erlauben" aktivieren!
                      //
                      // Bei kurzem Abfrageintervall:
                      // -> Instanzen -> JavaScript-Adapter -> Allgemeine Einstellungen -> 
                      //    "Maximale setState-Anfragen pro Minute pro Skript" auf 5000 erhöhen.
                      //    (Testweise reicht ein geringerer Wert wie 2000; das Skript wurde optimiert).
                      //
                      // Datenpunkte im Ordner "control" werden automatisch aktualisiert.
                      //----------------------------------------------------------------------------------------------------
                      //----------------------------------------------------------------------------------------------------
                      // CONFIG
                      //----------------------------------------------------------------------------------------------------
                      
                      // IP Zendure Gerät
                      //Beispiel: const IP = "192.168.40.20";
                      const IP = "192.168.40.20";  // IP des Zendure Geräts
                      // Bitte sicherstellen:
                      // richtige IP verwendet?
                      // Im Router dem Zendure-Gerät eine feste, dauerhafte IP zuweisen!
                      
                      //maximum inputLimit -> Maximal mögliche (unterstützte) Ladeleistung des Zendure-Geräts 
                      //Beispiele: SF800 800W / SF800 PRO: 1000W / 1600AC: 1600 / SF2400AC: 2400W
                      const maxInputLimit = 1600;
                      
                      //maximum outputLimit -> Maximal möglich (unterstützte) Entladeleistung des Zendure-Geräts
                      //Beispiele: SF800 800W / SF800 PRO: 800W / 1600AC: 1600 / SF2400AC: 2400W
                      const maxOutputLimit = 1600;
                      
                      // Haupt-Verzeichnis für das Zendure-Gerät
                      // Der Name für das Hauptverzeichnis kann frei gewählt werden.
                      // Muss aber bei mehreren Skripten und Zendure-Geräte unterschiedlich sein.
                      // Beispiel für "1600plus_01":
                      // const folderZendureApi = '0_userdata.0.zendure.' + "1600ACplus_01";
                      const folderZendureApi = '0_userdata.0.zendure.' + "zenSDK_01";
                      
                      // Timout Handler HTTP GET /POST
                      // Wichtig:
                      // getTimeoutMs < getIntervalMs
                      // postTimeoutMs < getIntervalMs
                      // Bei instabiler Verbindung ggf. erhöhen (z. B. 3000–5000 ms).
                      const getTimeoutMs = 1500;  // Empfehlung: 2000 ms.
                      const postTimeoutMs = 1500; // Empfehlung: 2000 ms.
                      
                      // GET intervall
                      // Empfehlung: >= 5000 ms.
                      // Kleinere Werte möglich, aber m. M. nach sinnfrei und nicht empfohlen (Last / Stabilität).
                      const getIntervalMs = 5000; // Intervall für GET 5000ms. Bei Problemen erhöhen. NICHT < 5000!
                      
                      //----------------------------------------------------------------------------------------------------
                      // END CONFIG
                      //----------------------------------------------------------------------------------------------------
                      
                      let rxNewRam = "";
                      let rxOldRam = "";
                      let rxDiffRam = "";
                      let txRam = "";
                      
                      const dpSmartRAM = folderZendureApi + ".control.automaticKonfig.input_output_LimitMode_smartMode_RAM";
                      const dpSmartWatcher = folderZendureApi + ".control.automaticKonfig.smartModeWatcher";
                      
                      // helper
                      function getRxNew() { return rxNewRam; }
                      function setRxNew(val) {
                          rxNewRam = val;
                      }
                      
                      function getRxOld() { return rxOldRam; }
                      function setRxOld(val) {
                          rxOldRam = val;
                      }
                      
                      function getRxDiff() { return rxDiffRam; }
                      function setRxDiff(val) {
                          rxDiffRam = val;
                      }
                      
                      function setTx(val) {
                          txRam = val;
                      }
                      
                      const postCooldownMs = 2000;
                      const batchWindowMs = 300;
                      
                      let postActive = false;
                      let postQueue = {};
                      let batchTimer = null;
                      let getTimer = null;
                      
                      let retryCount = 0; 
                      const maxRetries = 2; 
                      let getErrorCount = 0; 
                      let SN = '';
                      
                      // Struktur sicherstellen
                      setTimeout(() => {
                          ensureStructure();
                      }, 100);
                      
                      function ensureStructure() {
                          const folders = [
                              folderZendureApi,
                              folderZendureApi + ".properties",
                              folderZendureApi + ".packData",
                              folderZendureApi + ".control",
                              folderZendureApi + ".control.automaticKonfig"
                          ];
                          folders.forEach(path => {
                              if (!existsObject(path)) {
                                  setObject(path, {
                                      type: "folder",
                                      common: { name: path.split(".").pop() },
                                      native: {}
                                  });
                              }
                          });
                          if (!existsState(dpSmartRAM)) {
                              createState(dpSmartRAM, false, { name: "Input/Output Limit Mode SmartMode RAM", type: "boolean", role: "switch", read: true, write: true });
                          }
                          if (!existsState(dpSmartWatcher)) {
                              createState(dpSmartWatcher, false, { name: "Smart Mode Watcher", type: "boolean", role: "switch", read: true, write: true });
                          }
                      	if (existsState(dpSmartRAM)) setState(dpSmartRAM, false, true);
                      	if (existsState(dpSmartWatcher)) setState(dpSmartWatcher, false, true);
                      }
                      
                      // Control
                      const schemaControl = {
                      	acMode: {
                              name: "set acMode; 1: input charge, 2: output discharge",
                              role: "level",
                              type: "number",
                              write: true,
                              min: 1,
                              max: 2,
                              apiKey: "acMode",
                          },
                      	
                      	chargeMaxLimit: {
                              name: "set Charge Max Limit",
                              unit: "W",
                              role: "level",
                              type: "number",
                              write: true,
                              min: 0,
                              max: maxInputLimit,
                              apiKey: "chargeMaxLimit",
                          },
                      	
                      	gridOffMode: {
                              name: "set gridOffMode; 0: Normal mode, 1: Economic mode, 2: OFF",
                              role: "level",
                              type: "number",
                              write: true,
                              min: 0,
                              max: 2,
                              apiKey: "gridOffMode",
                          },
                      	
                      	gridReverse: {
                              name: "set gridReverse; 0: Disabled, 1: Allowed reverse flow, 2: Forbidden reverse flow",
                              role: "level",
                              type: "number",
                              write: true,
                              min: 0,
                              max: 2,
                              apiKey: "gridReverse",
                          },
                      	
                      	gridStandard: {
                              name: "set gridStandard; 0: Germany 1: France 2: Austria 3: Switzerland 4: Netherlands 5: Spain 6: Belgium 7: Greece 8: Denmark 9: Italy",
                              role: "level",
                              type: "number",
                              write: true,
                              min: 0,
                              max: 9,
                              apiKey: "gridStandard",
                          },
                      	
                      	inputLimit: {
                              name: "set inputLimit",
                              unit: "W",
                              role: "level",
                              type: "number",
                              write: true,
                              min: 0,
                              max: maxInputLimit,
                              apiKey: "inputLimit",
                          },
                      	
                      	inverseMaxPower: {
                              name: "set inverseMaxPower; Max inverter output Power Limit",
                              unit: "W",
                              role: "level",
                              type: "number",
                              write: true,
                              min: 200,
                              max: maxOutputLimit,
                      		step: 100,
                              apiKey: "inverseMaxPower",
                          },
                      	
                      	minSoc: {
                              name: "set minSoc; minimum SOC 0-50",
                              unit: "%",
                              role: "level",
                              type: "number",
                              write: true,
                              min: 0,
                              max: 50,
                              apiKey: "minSoc",
                              transformWrite: (val) => val * 10
                          },
                      	
                      	outputLimit: {
                              name: "set outputLimit",
                              unit: "W",
                              role: "level",
                              type: "number",
                              write: true,
                              min: 0,
                              max: maxOutputLimit,
                              apiKey: "outputLimit",
                          },
                      	
                      	smartMode: {
                              name: "set smartMode; parameter are written to: 1: RAM / 0: Flash",
                              role: "level",
                              type: "number",
                              write: true,
                              min: 0,
                              max: 1,
                              apiKey: "smartMode",
                          },
                      	
                      	socSet: {
                              name: "set socSet; SOC Target 70%-100%",
                              unit: "%",
                              role: "level",
                              type: "number",
                              write: true,
                              min: 70,
                              max: 100,
                              apiKey: "socSet",
                              transformWrite: (val) => val * 10
                          },
                      	
                      	lampSwitch: {
                              name: "set lamp switch; 0: lamp off / 1: lamp on",
                              role: "level",
                              type: "number",
                              write: true,
                              min: 0,
                              max: 1,
                              apiKey: "lampSwitch",
                          }
                      };
                      
                      createControlStates();
                       
                      function createControlStates() {
                          const base = folderZendureApi + ".control";
                          Object.keys(schemaControl).forEach(key => {
                              const def = schemaControl[key];
                              const dp = `${base}.${key}`;
                              if (!existsState(dp)) {
                                  createState(dp, 0, {
                                      name: def.name, type: def.type, role: def.role, unit: def.unit,
                                      read: true, write: true, min: def.min, max: def.max
                                  });
                              }
                          });
                      		
                      	const extraStates = [
                      		{ id: `${base}.auto_inputLimitMode`, name: "Set InputLimit (Automatic)", unit: "W", min: 0, max: maxInputLimit },
                      		{ id: `${base}.auto_outputLimitMode`, name: "Set OutputLimit (Automatic)", unit: "W", min: 0, max: maxOutputLimit }
                      	];
                       
                      	extraStates.forEach(def => {
                      		if (!existsState(def.id)) {
                      			createState(def.id, 0, {
                      				name: def.name, type: "number", role: "level", unit: def.unit,
                      				read: true, write: true, min: def.min, max: def.max
                      			});
                      		}
                      	});
                      }
                      
                      on({ id: new RegExp(`^${folderZendureApi}\\.control\\.`), change: "ne" }, obj => {
                          if (obj.state.ack) return; 
                          const key = obj.id.split('.').pop();
                      	
                          if (key === "auto_inputLimitMode") {
                              handleInputLimitMode(obj.state.val);
                              setState(obj.id, obj.state.val, true);
                              return;
                          }
                       
                          if (key === "auto_outputLimitMode") {
                              handleOutputLimitMode(obj.state.val);
                              setState(obj.id, obj.state.val, true);
                              return;
                          }
                      	
                          const def = schemaControl[key];
                          if (!def) return;
                       
                          let val = obj.state.val;
                          if (def.min !== undefined && val < def.min) val = def.min;
                          if (def.max !== undefined && val > def.max) val = def.max;
                          if (def.transformWrite) val = def.transformWrite(val);
                       
                          sendToDevice(def.apiKey, val);
                          setState(obj.id, obj.state.val, true);
                      });
                      
                      function sendToDevice(apiKey, value) {
                      	if (!SN || SN === "null" || SN === "") return;
                          const payload = { sn: SN, properties: {} };
                          payload.properties[apiKey] = value;
                          setTx(JSON.stringify(payload));
                          queuePost({ [apiKey]: value });
                      }
                      
                      function queuePost(obj) {
                          Object.assign(postQueue, obj);
                          if (batchTimer) return;
                          batchTimer = setTimeout(() => {
                              batchTimer = null;
                              executePost();
                          }, batchWindowMs);
                      }
                      
                      function executePost() {
                          if (postActive) return;
                          if (Object.keys(postQueue).length === 0) return;
                          postActive = true;
                          stopGetLoop();
                          const payload = { sn: SN, properties: { ...postQueue } };
                          postQueue = {};
                          const json = JSON.stringify(payload);
                          setTx(json); 
                          httpPost(`http://${IP}/properties/write`, json, { timeout: postTimeoutMs, responseType: "text" }, (err, response) => {
                              let success = false;
                              if (!err && response && response.statusCode === 200) {
                                  try {
                                      const data = JSON.parse(response.data);
                                      success = data.success === true && data.code === 200;
                                  } catch (e) { success = false; }
                              }
                              if (!success) {
                                  retryCount++;
                                  if (retryCount <= maxRetries) {
                                      log(`POST Retry ${retryCount}/${maxRetries}`, "warn");
                                      setTimeout(() => {
                                          postQueue = { ...payload.properties };
                                          postActive = false;
                                          executePost();
                                      }, postCooldownMs);
                                      return;
                                  }
                                  log("POST endgültig fehlgeschlagen", "error");
                                  retryCount = 0;
                                  finishPost(false);
                                  return;
                              }
                              retryCount = 0;
                              finishPost(true);
                          });
                      }
                      
                      function finishPost(success) {
                          if (!success) { syncControlFromProperties(); }
                      	setTimeout(() => { postActive = false; startGetLoop(); }, postCooldownMs);
                      	}
                      
                      function syncControlFromProperties() {
                          const baseCtrl = folderZendureApi + ".control";
                          const baseProp = folderZendureApi + ".properties";
                       
                          Object.keys(schemaControl).forEach(ctrlKey => {
                              const def = schemaControl[ctrlKey];
                              const apiKey = def.apiKey || ctrlKey;
                       
                              const propState = getState(`${baseProp}.${apiKey}`);
                              if (!propState || propState.val === undefined) return;
                       
                              const ctrlId = `${baseCtrl}.${ctrlKey}`;
                      		const ctrlState = getState(ctrlId);
                      		const ctrlVal = ctrlState ? ctrlState.val : undefined;
                              const propVal = propState.val;
                       
                              if (ctrlVal !== propVal) {
                                  setState(ctrlId, propVal, true);
                              }
                          });
                      
                          const inputProp = getState(`${baseProp}.inputLimit`)?.val;
                          if (inputProp !== undefined) {
                              const id = `${baseCtrl}.auto_inputLimitMode`;
                              if (getState(id)?.val !== inputProp) {
                                  setState(id, inputProp, true);
                              }
                          }
                      
                          const outputProp = getState(`${baseProp}.outputLimit`)?.val;
                          if (outputProp !== undefined) {
                              const id = `${baseCtrl}.auto_outputLimitMode`;
                              if (getState(id)?.val !== outputProp) {
                                  setState(id, outputProp, true);
                              }
                          }
                      }
                      
                      function startGetLoop() {
                      	if (getTimer) {
                          clearInterval(getTimer);
                      	}
                          getTimer = setInterval(() => {
                              if (postActive) return;
                              const url = `http://${IP}/properties/report`;
                              httpGet(url, { timeout: getTimeoutMs, responseType: 'text' }, (err, response) => {
                                  if (err || !response || response.statusCode !== 200) {
                                      getErrorCount++;
                                      if (getErrorCount <= 3) log(`GET Fehler (${getErrorCount}): ${err || response?.statusCode}`, "warn");
                                      if (getErrorCount === 4) log("Keine Verbindung möglich. Zendure-Geräte IP prüfen!", "error");
                                      return;
                                  }
                                  if (getErrorCount > 0) log("Verbindung wieder OK", "info");
                                  getErrorCount = 0;
                                  if (!response.data) return;
                                  setRxNew(response.data);
                                  handleRxNewUpdate(response.data);
                              });
                          }, getIntervalMs);
                      }
                      
                      function stopGetLoop() { if (getTimer) { clearInterval(getTimer); getTimer = null; } }
                       
                      function handleInputLimitMode(val) {
                          const currentAcMode = getState(folderZendureApi + ".properties.acMode")?.val;
                          const currentOutput = getState(folderZendureApi + ".properties.outputLimit")?.val;
                      	const currentSmartMode = getState(folderZendureApi + ".properties.smartMode")?.val;
                          const smartRAMActive = getState(dpSmartRAM)?.val;
                       
                          const payload = {};
                          if (currentAcMode !== 1) payload.acMode = 1;
                          if (currentOutput !== 0) payload.outputLimit = 0;
                      	if (smartRAMActive === true && currentSmartMode !== 1) payload.smartMode = 1;
                          
                          payload.inputLimit = val;
                          sendBatch(payload);
                      }
                      
                      function handleOutputLimitMode(val) {
                          const currentAcMode = getState(folderZendureApi + ".properties.acMode")?.val;
                          const currentInput = getState(folderZendureApi + ".properties.inputLimit")?.val;
                      	const currentSmartMode = getState(folderZendureApi + ".properties.smartMode")?.val;
                          const smartRAMActive = getState(dpSmartRAM)?.val;
                       
                          const payload = {};
                          if (currentAcMode !== 2) payload.acMode = 2;
                          if (currentInput !== 0) payload.inputLimit = 0;
                      	if (smartRAMActive === true && currentSmartMode !== 1) payload.smartMode = 1;
                       
                          payload.outputLimit = val;
                          sendBatch(payload);
                      }
                      
                      function sendBatch(properties) {
                          if (!SN || SN === "null" || SN === "") return;
                          const payload = { sn: SN, properties: properties };
                          setTx(JSON.stringify(payload)); 
                      	queuePost(properties); 
                      }
                      
                      const schema = {
                          properties: {
                              BatVolt: {
                                  name: "Battery Voltage",
                                  unit: "V",
                                  role: "value.voltage",
                                  type: "number"
                              },
                      		Fanmode: {
                                  name: "Fanmode - 0: Fan off, 1: Fan on. / Unauthorized modification is not recommended.",
                                  role: "value",
                                  type: "number"
                              },	
                      		Fanspeed: {
                                  name: "Fanspeed - 0: Auto, 1: 1st gear, 2: 2nd gear / Unauthorized modification is not recommended.",
                                  role: "value",
                                  type: "number"
                              },
                      		acMode: {
                                  name: "acMode - 1: input charge / 2: output discharge",
                                  role: "value",
                                  type: "number"
                              },
                      		acStatus: {
                                  name: "AC state 0-2",
                                  role: "value",
                                  type: "number"
                              },
                      		batCalTime: {
                                  name: "batCalTime - Battery Calibration Time. Unauthorized modifications are not recommended",
                      			unit: "min",
                                  role: "value",
                                  type: "number"
                              },
                      		chargeMaxLimit: {
                                  name: "chargeMaxLimit - Max charge power",
                      			unit: "W",
                                  role: "value.power",
                                  type: "number"
                              },
                      		dataReady: {
                                  name: "Data ready flag - 0: Data not ready, 1: Data ready",
                                  role: "value",
                                  type: "number"
                              },
                      		dcStatus: {
                                  name: "dcStatus - DC State 0-2",
                                  role: "value",
                                  type: "number"
                              },
                      		electricLevel: {
                                  name: "electricLevel - Average SOC of all Batterys",
                                  unit: "%",
                                  role: "value.battery",
                                  type: "number"
                              },
                      		gridInputPower: {
                                  name: "gridInputPower - Grid Input Power to Battery",
                      			unit: "W",
                                  role: "value.power",
                                  type: "number"
                              },
                      		gridOffMode: {
                                  name: "gridOffMode - 0: Normal mode, 1: Economic mode, 2: OFF",
                                  role: "value",
                                  type: "number"
                              },
                      		gridOffPower: {
                                  name: "gridOffPower - Off-grid power",
                      			unit: "W",
                                  role: "value.power",
                                  type: "number"
                              },
                      		gridReverse: {
                                  name: "gridReverse - 0: Disabled, 1: Allowed reverse flow, 2: Forbidden reverse flow",
                                  role: "value",
                                  type: "number"
                              },
                      		gridStandard: {
                                  name: "gridStandard - 0: Germany 1: France 2: Austria 3: Switzerland 4: Netherlands 5: Spain 6: Belgium 7: Greece 8: Denmark 9: Italy",
                                  role: "value",
                                  type: "number"
                              },
                      		gridState: {
                                  name: "gridState - Grid connection state, 0: Not connected, 1: Connected",
                                  role: "value",
                                  type: "number"
                              },
                      		heatState: {
                                  name: "heatState - Battery Heat State, 0: Not heating, 1: heating",
                                  role: "value",
                                  type: "number"
                              },
                      		hyperTmp: {
                                  name: "Temperature",
                                  unit: "°C",
                                  role: "value.temperature",
                                  type: "number"
                              },
                      		inputLimit: {
                                  name: "inputLimit - AC charging power limit to Battery",
                      			unit: "W",
                                  role: "value.power",
                                  type: "number"
                              },
                      		inverseMaxPower: {
                                  name: "inverseMaxPower - Max inverter output Power Limit",
                      			unit: "W",
                                  role: "value.power",
                                  type: "number"
                              },
                      		lampSwitch: {
                                  name: "lampSwitch - Lamp state, 0: lamp off / 1: lamp on",
                                  role: "value",
                                  type: "number"
                              },
                      		minSoc: {
                                  name: "minSoc - Minimum SOC 0%-50%",
                                  unit: "%",
                                  role: "value",
                                  type: "number"
                              },
                      		outputHomePower: {
                                  name: "outputHomePower - Output to home",
                      			unit: "W",
                                  role: "value.power",
                                  type: "number"
                              },
                      		outputLimit: {
                                  name: "outputLimit - Output power limit",
                      			unit: "W",
                                  role: "value.power",
                                  type: "number"
                              },
                      		outputPackPower: {
                                  name: "outputPackPower - Battery charge power",
                      			unit: "W",
                                  role: "value.power",
                                  type: "number"
                              },
                      		packInputPower: {
                                  name: "packInputPower - Battery discharge power",
                      			unit: "W",
                                  role: "value.power",
                                  type: "number"
                              },
                      		packState: {
                                  name: "packState - Battery State, 0: Standby, 1: Charging, 2: Discharging",
                                  role: "value",
                                  type: "number"
                              },
                      		pass: {
                                  name: "pass - 0: Bypass Automatic, 1: Bypass OFF, 2: Bypass ON",
                                  role: "value",
                                  type: "number"
                              },
                      		pvStatus: {
                                  name: "pvStatus - PV State producing, 0: Stopped, 1: Running",
                                  role: "value",
                                  type: "number"
                              },
                      		remainOutTime: {
                                  name: "remainOutTime - Estimated discharge time in minutes, if not predictable: 59940",
                      			unit: "min",
                                  role: "value",
                                  type: "number"
                              },
                      		reverseState: {
                                  name: "reverseState - Reverse flow, 0: No, 1: Reverse flow",
                                  role: "value",
                                  type: "number"
                              },
                      		smartMode: {
                                  name: "smartMode - 1: parameter are written to RAM / 0: parameter are written to flash",
                                  role: "value",
                                  type: "number"
                              },
                      		socLimit: {
                                  name: "socLimit - 0: normal, 1: Charge limit reached, 2: Discharge limit reached",
                                  role: "value",
                                  type: "number"
                              },		
                              socSet: {
                                  name: "socSet - SOC Target 70%-100%",
                                  unit: "%",
                                  role: "value",
                                  type: "number"
                              },
                      		socStatus: {
                                  name: "socStatus - SOC calibration Info, 0: No Calibrating / 1: Calibrating",
                                  role: "value",
                                  type: "number"
                              },
                      		solarInputPower: {
                                  name: "solarInputPower - Total Solar Input Power",
                      			unit: "W",
                                  role: "value.power",
                                  type: "number"
                              },
                      		solarPower1: {
                                  name: "solarPower1 - Solar line 1 input power",
                      			unit: "W",
                                  role: "value.power",
                                  type: "number"
                              },
                      		solarPower2: {
                                  name: "solarPower2 - Solar line 2 input power",
                      			unit: "W",
                                  role: "value.power",
                                  type: "number"
                              },
                      		solarPower3: {
                                  name: "solarPower3 - Solar line 3 input power",
                      			unit: "W",
                                  role: "value.power",
                                  type: "number"
                              },
                      		solarPower4: {
                                  name: "solarPower4 - Solar line 4 input power",
                      			unit: "W",
                                  role: "value.power",
                                  type: "number"
                              },
                      		solarPower5: {
                                  name: "solarPower5 - Solar line 5 input power",
                      			unit: "W",
                                  role: "value.power",
                                  type: "number"
                              },
                      		solarPower6: {
                                  name: "solarPower6 - Solar line 6 input power",
                      			unit: "W",
                                  role: "value.power",
                                  type: "number"
                              }
                          },
                          packData: {
                              batcur: {
                                  name: "Battery Current flow - negativ: discharge / positiv: charge",
                                  unit: "A",
                                  role: "value.current",
                                  type: "number"
                              },
                      		maxTemp: {
                                  name: "Battery - max. Stored temperature value",
                                  unit: "°C",
                                  role: "value.temperature",
                                  type: "number"
                              },
                      		maxVol: {
                                  name: "maxVol - Max cell voltage",
                                  unit: "V",
                                  role: "value.voltage",
                                  type: "number"
                              },
                      		minVol: {
                                  name: "minVol - Min cell voltage",
                                  unit: "V",
                                  role: "value.voltage",
                                  type: "number"
                              },
                      		packType: {
                                  name: "Pack Type",
                                  role: "indicator",
                                  type: "number"
                              },
                      		power: {
                                  name: "Battery pack power",
                                  unit: "W",
                                  role: "value.power",
                                  type: "number"
                              },
                      		sn: {
                                  name: "Battery pack serial number",
                                  role: "indicator",
                                  type: "string"
                              },
                      		socLevel: {
                                  name: "socLevel - State of charge in Percent",
                      			unit: "%",
                                  role: "value.battery",
                                  type: "number"
                              },
                      		state: {
                      			name: "Battery state, 0: Standby, 1: Charging, 2: Discharging",
                                  role: "indicator",
                                  type: "number"
                              },
                      		totalVol: {
                      			name: "Battery Total Voltage",
                                  unit: "V",
                                  role: "value.voltage",
                                  type: "number"
                              }
                          }
                      };
                      
                      function formatTime(unix) {
                          if (unix === undefined || unix === null) return "";
                          const d = new Date(unix * 1000);
                          const timeOptions = { timeZone: "Europe/Berlin", hour: "2-digit", minute: "2-digit", second: "2-digit", hour12: false };
                          const dateOptions = { timeZone: "Europe/Berlin", day: "2-digit", month: "2-digit", year: "2-digit" };
                          return `${d.toLocaleTimeString("de-DE", timeOptions)}, ${d.toLocaleDateString("de-DE", dateOptions)}`;
                      }
                      
                      function getBatteryType(sn, model) {
                          let batType = "";
                          if (!sn || typeof sn !== "string") return "unknown";
                          if (sn.startsWith("A")) {
                              batType = "AB1000";
                          } 
                          else if (sn.startsWith("B")) {
                              batType = "AB1000S";
                          } 
                          else if (sn.startsWith("C")) {
                              const sub = sn.length > 3 ? sn[3] : "";
                              if (sub === "F") batType = "AB2000S";
                              else if (sub === "E") batType = "AB2000X";
                              else if (sub === "A") batType = "AB2000X internal";
                              else batType = "AB2000";
                          } 
                          else if (sn.startsWith("F")) {
                              batType = "AB3000X";
                          }
                          else if (sn.startsWith("J")) {
                              const sub = sn.length > 3 ? sn[3] : "";
                              if (sub === "A") batType = "Internal 2,4kWh";
                              else batType = "unknown internal";
                          }
                          else if (sn.startsWith("G")) {
                              const sub = sn.length > 3 ? sn[3] : "";
                              if (sub === "A") batType = "AB3000L";
                              else batType = "unknown AB3000?";
                          }
                          if (model && typeof model === "string" && model.trim()) {
                              batType = model.trim();
                          }
                          return batType || "unknown";
                      }
                      
                      function writeMainKeys(json) {
                          const keys = ["timestamp", "messageId", "sn", "version", "product"];
                          keys.forEach(key => {
                              if (json[key] === undefined) return;
                      		if (key === "sn" && SN === '') { SN = json[key]; }
                              const dp = `${folderZendureApi}.${key}`;
                              if (existsState(dp)) {
                                  if (getState(dp).val !== json[key]) { setState(dp, json[key], true); }
                              } else {
                                  createState(dp, json[key], { name: key, type: typeof json[key], role: "info", read: true, write: false });
                              }
                          });
                      }
                      
                      function writeProperties(obj) {
                          if (!obj) return;
                          const base = folderZendureApi + ".properties";
                          Object.keys(obj).forEach(key => {
                              const dp = `${base}.${key}`;
                              const val = obj[key];
                              const meta = schema.properties?.[key] || {};
                              if (existsState(dp)) {
                                  if (getState(dp).val !== val) { setState(dp, val, true); }
                              } else {
                                  createState(dp, val, { name: meta.name || key, type: meta.type || typeof val, role: meta.role || "value", unit: meta.unit, read: true, write: false });
                              }
                          });
                      }
                      
                      function writePackData(arr) {
                          if (!Array.isArray(arr)) return;
                          const base = folderZendureApi + ".packData";
                          arr.forEach(pack => {
                              if (!pack.sn) return;
                              const folder = `${base}.${pack.sn}`;
                              if (!existsObject(folder)) {
                                  setObject(folder, { type: "folder", common: { name: pack.sn }, native: {} });
                              }
                              Object.keys(pack).forEach(key => {
                                  const dp = `${folder}.${key}`;
                                  const val = pack[key];
                      			const meta = schema.packData?.[key] || {};
                                  if (existsState(dp)) {
                                      if (getState(dp).val !== val) { setState(dp, val, true); }
                                  } else {
                                      createState(dp, val, { name: meta.name || key, type: meta.type || typeof val, role: meta.role || "value", unit: meta.unit, read: true, write: false });
                                  }
                              });
                          });
                      }
                      
                      function buildDiff(oldJson, newJson) {
                          if (!oldJson || !newJson) return null;
                          const diff = {};
                          const mainKeys = ["timestamp", "messageId", "sn", "version", "product"];
                      	mainKeys.forEach(key => {
                              if (newJson[key] !== undefined && newJson[key] !== oldJson[key]) { diff[key] = newJson[key]; }
                          });
                          if (newJson.properties) {
                              const propDiff = {};
                              Object.keys(newJson.properties).forEach(key => {
                                  const newVal = newJson.properties[key];
                                  const oldVal = oldJson.properties ? oldJson.properties[key] : undefined;
                                  if (newVal !== oldVal) { propDiff[key] = newVal; }
                              });
                              if (Object.keys(propDiff).length > 0) { diff.properties = propDiff; }
                          }
                          if (Array.isArray(newJson.packData)) {
                              const packDiff = [];
                              const oldMap = {};
                              if (Array.isArray(oldJson.packData)) {
                                  oldJson.packData.forEach(p => { if (p.sn) oldMap[p.sn] = p; });
                              }
                              newJson.packData.forEach(newPack => {
                                  if (!newPack.sn) return;
                                  const oldPack = oldMap[newPack.sn];
                                  const singleDiff = { sn: newPack.sn };
                                  let changed = false;
                                  Object.keys(newPack).forEach(key => {
                                      if (key === "sn") return;
                                      const newVal = newPack[key];
                                      const oldVal = oldPack ? oldPack[key] : undefined;
                                      if (newVal !== oldVal) { singleDiff[key] = newVal; changed = true; }
                                  });
                                  if (changed) { packDiff.push(singleDiff); }
                              });
                              if (packDiff.length > 0) { diff.packData = packDiff; }
                          }
                          return Object.keys(diff).length === 0 ? null : diff;
                      }
                      
                      function handleRxNewUpdate(val) {
                          if (!val) return;
                          let newJson;
                          try { newJson = JSON.parse(val); } catch (e) { log("rxNew parse error: " + e, "warn"); return; }
                          const smartModeWatcher = getState(dpSmartWatcher)?.val;
                          if (smartModeWatcher === true && newJson.properties && newJson.properties.smartMode === 0) {
                              //log("SmartModeWatcher: smartMode ist 0! Erzwinge RAM-Modus (1)...", "info");
                              queuePost({ smartMode: 1 });
                          }
                          const oldStr = getRxOld(); 
                          if (!oldStr || oldStr === "null" || oldStr === "") {
                      		let full = transformJson(JSON.parse(JSON.stringify(newJson)));
                      		writeMainKeys(full);
                      		if (full.properties) {
                      			writeProperties(full.properties);
                      			const base = folderZendureApi + ".control";
                                  Object.keys(schemaControl).forEach(ctrlKey => {
                                      const def = schemaControl[ctrlKey];
                                      const apiKey = def.apiKey || ctrlKey;
                                      if (full.properties[apiKey] !== undefined) { setState(`${base}.${ctrlKey}`, full.properties[apiKey], true); }
                                  });
                                  if (full.properties.inputLimit !== undefined) { setState(`${base}.auto_inputLimitMode`, full.properties.inputLimit, true); }
                                  if (full.properties.outputLimit !== undefined) { setState(`${base}.auto_outputLimitMode`, full.properties.outputLimit, true); }	
                      		} 
                      		if (full.packData) { writePackData(full.packData); }
                      		setRxOld(val); setRxDiff(""); return;
                      	}
                          let oldJson;
                          try { oldJson = JSON.parse(oldStr); } catch (e) { setRxOld(val); return; }
                      	const isNewer = (newJson.timestamp && oldJson.timestamp && newJson.timestamp > oldJson.timestamp) || (!oldJson.timestamp && newJson.timestamp);
                          if (!isNewer) return;
                          const diff = buildDiff(oldJson, newJson);
                          if (diff && Object.keys(diff).length > 0) {
                              const diffStr = JSON.stringify(diff);
                              setRxDiff(diffStr); handleRxDiffUpdate(diffStr); 
                          } else { setRxDiff(""); }
                          setRxOld(val);
                      }
                      
                      function handleRxDiffUpdate(val) {
                          if (!val) return;
                          let diff;
                          try { diff = JSON.parse(val); } catch (e) { log("rxDiff parse error: " + e, "warn"); return; }
                          diff = transformJson(diff);
                      	if (diff.properties) { syncControlFromDiff(diff); }							   
                          writeMainKeys(diff);
                          if (diff.properties) { writeProperties(diff.properties); }
                          if (diff.packData) { writePackData(diff.packData); }
                      }
                      
                      function syncControlFromDiff(diff) {
                          if (!diff || !diff.properties) return;
                          const base = folderZendureApi + ".control";
                      	if (diff.properties.inputLimit !== undefined) { setState(`${base}.auto_inputLimitMode`, diff.properties.inputLimit, true); }
                      	if (diff.properties.outputLimit !== undefined) { setState(`${base}.auto_outputLimitMode`, diff.properties.outputLimit, true); }	
                      	Object.keys(schemaControl).forEach(ctrlKey => {
                      		const def = schemaControl[ctrlKey];
                      		const apiKey = def.apiKey || ctrlKey;
                      		if (diff.properties[apiKey] !== undefined) { setState(`${base}.${ctrlKey}`, diff.properties[apiKey], true); }
                      	}); 
                      }
                      
                      function createOrSet(id, val, common) {
                          if (existsState(id)) {
                              if (getState(id).val !== val) { setState(id, val, true); }
                          } else { createState(id, val, { read: true, write: false, ...common }); }
                      }
                      
                      function transformJson(obj) {
                          if (!obj) return obj;
                          if (obj.timestamp !== undefined && obj.timestamp !== null) {
                              const formatted = formatTime(obj.timestamp);
                              createOrSet(`${folderZendureApi}.timeUpdateTimestamp`, formatted, { type: "string", role: "text", name: "TimeUpdate (timestamp)" });
                          }
                          if (obj.properties) {
                              Object.keys(obj.properties).forEach(key => {
                                  let val = obj.properties[key];
                                  switch (key) {
                      				case "BatVolt": val = val / 100; break;
                      				case "acCouplingState": {
                      					let states = [];
                      					if (val & (1 << 0)) states.push("AC-coupled input present");
                      					if (val & (1 << 1)) states.push("AC input present flag");
                      					if (val & (1 << 2)) states.push("AC-coupled overload");
                      					if (val & (1 << 3)) states.push("Excess AC input power");
                      					const statusText = states.length > 0 ? states.join(", ") : "Normal / No Flags";
                      					createOrSet(`${folderZendureApi}.properties.acCouplingState_String`, statusText, { type: "string", role: "text", name: "AC Coupling State (Text)" });
                      				} break;
                      				case "hyperTmp": val = (val - 2731) / 10.0; break;
                                      case "minSoc":
                      				case "socSet": val = val / 10; break;
                                      case "ts":
                      					if (val !== undefined && val !== null) {
                      						const formatted = formatTime(val);
                      						createOrSet(`${folderZendureApi}.properties.timeUpdateTs`, formatted, { type: "string", role: "text", name: "TimeUpdate (ts)" });
                      					}
                      					break;
                                  }
                                  obj.properties[key] = val;
                              });
                          }
                          if (Array.isArray(obj.packData)) {
                      		obj.packData.forEach(pack => {
                      			if (pack.sn) {
                      				const batType = getBatteryType(pack.sn, pack.model);
                      				createOrSet(`${folderZendureApi}.packData.${pack.sn}.model`, batType, { type: "string", role: "text", name: "Battery Model" });
                      			}
                      		}); 	
                              obj.packData.forEach(pack => {
                                  Object.keys(pack).forEach(key => {
                                      let val = pack[key];
                                      switch (key) {
                      					case "batcur":
                      						if (val > 32767) { val = val - 65536; }
                      						val = val / 10; break;
                                          case "maxTemp": val = (val - 2731) / 10; break;
                      					case "maxVol":
                      					case "minVol":
                      					case "totalVol": val = val / 100; break;
                      					case "softVersion":
                                              const major = Math.floor(val / 4096);
                                              const minor = Math.floor((val % 4096) / 256);
                                              const patch = val % 256;
                                              val = `V${major}.${minor}.${patch}`; break;  
                                      }
                                      pack[key] = val;
                                  });
                              });
                          }
                          return obj;
                      }
                      
                      startGetLoop();
                      
                      
                      maxclaudiM Offline
                      maxclaudiM Offline
                      maxclaudi
                      schrieb zuletzt editiert von
                      #290

                      update 19.05.26 16:50h BatteryTypen

                      Ich schreibe meistens sehr direkt – bitte nicht falsch verstehen, es ist nie böse gemeint. Das ist einfach mein Stil und niemals abwertend gemeint.

                      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

                      582

                      Online

                      32.9k

                      Benutzer

                      83.0k

                      Themen

                      1.3m

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

                      • Du hast noch kein Konto? Registrieren

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