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

  • Neues YouTube-Video: Visualisierung im Devices-Adapter
    BluefoxB
    Bluefox
    13
    1
    1.1k

  • Neuer ioBroker-Blog online: MonatsrĂŒckblick MĂ€rz/April 2026
    BluefoxB
    Bluefox
    8
    1
    2.1k

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

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

Geplant Angeheftet Gesperrt Verschoben JavaScript
294 BeitrÀge 15 Kommentatoren 26.6k Aufrufe 15 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

    @maxclaudi sagte:

    @Daniel-8 sagte:

    Habe das neue Script mal getestet. Habe gefĂŒhlt mehr Timeouts. Kann aber auch zufall sein.

    javascript.0
    2026-05-09 18:09:57.999	warn	script.js.common.Garten.Balkonkraftwerke.ZenSDK_Zendure_neue_Struktur: GET Fehler (1): timeout of 2000ms exceeded
    
    javascript.0
    2026-05-09 18:09:57.998	error	script.js.common.Garten.Balkonkraftwerke.ZenSDK_Zendure_neue_Struktur: httpGet(url=http://192.168.xxx.xxx/properties/report, error=timeout of 2000ms exceeded)
    

    Die beiden Meldungen sind technisch identisch und zeigen nur einen einzigen Fehlversuch an.
    Da das GerÀt nicht innerhalb von 2 Sekunden geantwortet hat, meldet ioBroker einen Fehler und mein Skript fÀngt diesen als Warnung ab, um stabil weiterzulaufen.
    Das ist bei WLAN-Verbindungen normal und kein Grund zur Sorge, solange danach sofort wieder Daten kommen.

    Der Webserver vom Zendure-GerĂ€t ist recht schmalbrĂŒstig,
    das wissen wir ja.

    Wenn das WLAN mal kurz hakt, lÀuft der 2000ms-Timer ab.
    Mein Skript akzeptiert das und funktioniert weiterhin beim nÀchsten Durchgang.

    Wichtig: bitte nicht 2 Versionen meines Scripts gleichzeitig auf 1 Zendure-GerÀt-Webserver laufen lassen.

    Ich habe nur eins am laufen.

    Danke fĂŒr Deine Antwort, bitte einfach mal 48h intensiv nutzen und beobachten.

    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.
    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. Dann habe ich das Script neu gestartet und da hat sich auch der Rssi Wert verÀndert.
    Warum heute so Viele meldungen anstanden kann ich mir noch nicht erklÀren, da ich eigentlich nichts verÀndert habe.

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

    @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.

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

    D 1 Antwort Letzte Antwort
    0
    • 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.

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

          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!

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

              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 24.05.26; 00:15h globale Typsicherung 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.

                  3. auto_in_out_Limit
                    update 21.05.26 22:05h auto_in_out_Limit
                    Beschreibung siehe weiter unten

                  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


                  update 21.05.26 22:05h auto_in_out_Limit

                  Obwohl ich kein Fan der Adapter-Funktion
                  setDeviceAutomationInOutLimit bin, ist die Nachfrage nach einem einfachen, komfortablen Umschaltbefehl anscheinend groß.

                  Deshalb habe ich einen neuen Datenpunkt zum Steuern hinzugefĂŒgt:
                  auto_in_out_Limit

                  Er bietet denselben Komfort, arbeitet aber absolut transparent und mit sicheren VorabprĂŒfungen.

                  Negative Werte (z. B. -100 W fĂŒr das Laden der Batterie):

                  • acMode wird geprĂŒft: Falls acMode: 2 (Entladen), wird es auf acMode: 1 (Laden) geĂ€ndert.
                  • inputLimit wird auf 100 W gesetzt.
                  • outputLimit wird geprĂŒft: Wenn es ungleich 0 ist, wird es auf 0 gesetzt.
                  • Wenn der Schalter "input_output_LimitMode_smartMode_RAM" aktiv ist (unbedingt empfohlen!), wird smartMode geprĂŒft und bei Bedarf von 0 auf 1 gesetzt.
                  • Erst nach diesen PrĂŒfungen wird der Befehl gesendet.

                  Positive Werte (z. B. 100 W fĂŒr das Entladen / Einspeisen):

                  • acMode wird geprĂŒft: Falls acMode: 1 (Laden), wird es auf acMode: 2 (Entladen) geĂ€ndert.
                  • outputLimit wird auf 100 W gesetzt.
                  • inputLimit wird geprĂŒft: Wenn es ungleich 0 ist, wird es auf 0 gesetzt.
                  • Wenn der Schalter "input_output_LimitMode_smartMode_RAM" aktiv ist, wird smartMode geprĂŒft und bei Bedarf von 0 auf 1 gesetzt.
                  • Erst nach diesen PrĂŒfungen wird der Befehl gesendet.

                  Verhalten bei Null (0 W):

                  • Beide Limits (inputLimit und outputLimit) werden sauber auf 0 gesetzt.
                  • Wichtig: Es gibt im Hintergrund keine geheimen Timeouts, die den smartMode nach ein paar Sekunden blind auf 0 reißen! Das System bleibt im sicheren RAM-Modus.

                  Wichtiger Hinweis zum Flash-Speicher:
                  Denkt daran, dass ein Wechsel des acMode (Laden <-> Entladen) vermutlich immer direkt in den Flash-Speicher geschrieben wird – wie alle Modus-Wechsel. Das passiert mit sehr hoher Wahrscheinlichkeit auch dann, wenn smartMode: 1 gesetzt ist. Zendure hat diese Frage bis heute (21.05.2026) nicht zweifelsfrei beantwortet.

                  Der Unterschied zum originalen Adapter:
                  Im Gegensatz zur Funktion des ioBroker-Adapters, die im Hintergrund unkontrollierte Befehlsketten auslöst, Cloud-Schnittstellen zweckentfremdet und mit inoffiziellen Hacks arbeitet, nutzt auto_in_out_Limit ausschließlich offizielle, von Zendure fĂŒr die lokale Steuerung freigegebene Befehle.

                  smartMode ist fĂŒr die aktive, hochfrequente Regelung da – und nicht dafĂŒr, bei jedem kurzen Nulldurchgang im Sekundentakt auf smartMode: 0 gesetzt zu werden. PV-Systeme sind dafĂŒr gebaut, aktiv zu regeln, und nicht, um permanent kĂŒnstlich in den Standby gezwungen zu werden.

                  Am Ende ĂŒberlasse ich die Verantwortung den Nutzern. Jeder sollte frei in seiner Entscheidung sein und genau nachvollziehen können, was auf seiner Hardware passiert. Bei mir gibt es keine versteckten Timer – ihr entscheidet selbst, wie euer smartMode geschaltet wird.

                  Es wird nur verwendet, was auch verwendet werden darf. Das ist der Grund, warum dieses Skript ohne Probleme einfach funktioniert.


                  Update 24.05.26 00:15h globale Typsicherung

                  • Globale Eingangs-Validierung:
                    Alle Steuerbefehle werden automatisch gegen Typenfehler abgesichert.
                    Krumme Zahlen (Floats wie 53.8) werden vor dem Senden abgefangen.

                  • PrĂ€zisere Datenpunkte:
                    Beschreibungen mehrerer Datenpunkte verstÀndlicher aktualisiert.

                  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

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

                  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!

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

                      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 24.05.26 00:15h globale Typsicherung
                        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 24.05.26 00:15h globale Typsicherung.
                        // 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,
                        		def: 1,
                                min: 1,
                                max: 2,
                                apiKey: "acMode",
                            },
                        
                        	chargeMaxLimit: {
                                name: "set Charge Max Limit",
                                unit: "W",
                                role: "level",
                                type: "number",
                                write: true,
                        		def: maxInputLimit,
                                min: 0,
                                max: maxInputLimit,
                                apiKey: "chargeMaxLimit",
                            },
                        
                        	gridOffMode: {
                                name: "set gridOffMode; 0: Normal mode, 1: Economic mode, 2: OFF",
                                role: "level",
                                type: "number",
                                write: true,
                        		def: 2,
                                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,
                        		def: 0,
                                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,
                        		def: 0,
                                min: 0,
                                max: 9,
                                apiKey: "gridStandard",
                            },
                        
                        	inputLimit: {
                                name: "set inputLimit",
                                unit: "W",
                                role: "level",
                                type: "number",
                                write: true,
                        		def: 0,
                                min: 0,
                                max: maxInputLimit,
                                apiKey: "inputLimit",
                            },
                        
                        	inverseMaxPower: {
                                name: "set inverseMaxPower; Max inverter output Power Limit",
                                unit: "W",
                                role: "level",
                                type: "number",
                                write: true,
                        		def: maxOutputLimit,
                                min: 200,
                                max: maxOutputLimit,
                        		step: 100,
                                apiKey: "inverseMaxPower",
                            },
                        
                        	minSoc: {
                                name: "set minSoc; minimum SOC 0-50",
                                unit: "%",
                                role: "level",
                                type: "number",
                                write: true,
                        		def: 10,
                                min: 0,
                                max: 50,
                                apiKey: "minSoc",
                                transformWrite: (val) => val * 10
                            },
                        
                        	outputLimit: {
                                name: "set outputLimit",
                                unit: "W",
                                role: "level",
                                type: "number",
                                write: true,
                        		def: 0,
                                min: 0,
                                max: maxOutputLimit,
                                apiKey: "outputLimit",
                            },
                        
                        	smartMode: {
                                name: "set smartMode; parameter are written to: 1: RAM / 0: Flash",
                                role: "level",
                                type: "number",
                                write: true,
                        		def: 0,
                                min: 0,
                                max: 1,
                                apiKey: "smartMode",
                            },
                        
                        	socSet: {
                                name: "set socSet; SOC Target 70%-100%",
                                unit: "%",
                                role: "level",
                                type: "number",
                                write: true,
                        		def: 100,
                                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,
                        		def: 1,
                                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, def: def.def, 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 },
                        		{ id: `${base}.auto_in_out_Limit`, name: "Set In/Out-Limit-Automatic: Negative: Charging; Positive: Discharging", unit: "W", min: (maxInputLimit * -1), 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") {
                                let val = Math.round(Number(obj.state.val)) || 0;
                                if (val < 0) val = 0;
                                if (val > maxInputLimit) val = maxInputLimit;
                                handleInputLimitMode(val);
                                setState(obj.id, val, true);
                                return;
                            }
                            if (key === "auto_outputLimitMode") {
                                let val = Math.round(Number(obj.state.val)) || 0;
                                if (val < 0) val = 0;
                                if (val > maxOutputLimit) val = maxOutputLimit;
                                handleOutputLimitMode(val);
                                setState(obj.id, val, true);
                                return;
                            }
                        	if (key === "auto_in_out_Limit") {
                        		let val = Math.round(Number(obj.state.val)) || 0;
                        		if (val > maxOutputLimit) val = maxOutputLimit;
                        		if ((-val) > maxInputLimit) val = -maxInputLimit;
                        		handleAuto_in_out_Limit(val);
                        		setState(obj.id, val, true);
                        		return;
                        	}
                            const def = schemaControl[key];
                            if (!def) return;
                            let val = obj.state.val;
                            if (def.type === "number") {
                                val = Math.round(Number(val)) || 0;
                            }
                            if (def.min !== undefined && val < def.min) val = def.min;
                            if (def.max !== undefined && val > def.max) val = def.max;
                        	let sendVal = val;
                        	if (def.transformWrite) { sendVal = def.transformWrite(val); }
                            sendToDevice(def.apiKey, sendVal);
                            setState(obj.id, 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);
                                }
                            }
                        	const acModeProp = getState(`${baseProp}.acMode`)?.val;
                            if (acModeProp !== undefined && outputProp !== undefined && inputProp !== undefined) {
                                const id = `${baseCtrl}.auto_in_out_Limit`;
                        		if (getState(id)?.val !== outputProp && acModeProp === 2) {
                                    setState(id, outputProp, true);
                                }
                        		if (getState(id)?.val !== inputProp * -1 && acModeProp === 1) {
                                    setState(id, inputProp * -1, 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 handleAuto_in_out_Limit(val) {
                            const currentAcMode = getState(folderZendureApi + ".properties.acMode")?.val;
                            const currentInput = getState(folderZendureApi + ".properties.inputLimit")?.val;
                        	const currentOutput = getState(folderZendureApi + ".properties.outputLimit")?.val;
                        	const currentSmartMode = getState(folderZendureApi + ".properties.smartMode")?.val;
                            const smartRAMActive = getState(dpSmartRAM)?.val;
                            const payload = {};
                        	if (val === 0) {
                        		if (currentInput !== 0) payload.inputLimit = 0;
                        		if (currentOutput !== 0) payload.outputLimit = 0;	
                        	}
                        	if (val > 0) {
                        		if (currentInput !== 0) payload.inputLimit = 0;
                        		if (currentAcMode !== 2) payload.acMode = 2;
                        		payload.outputLimit = val;	
                        	}
                        	if (val < 0) {
                        		if (currentOutput !== 0) payload.outputLimit = 0;
                        		if (currentAcMode !== 1) payload.acMode = 1;
                        		payload.inputLimit = val * -1;	
                        	}
                        	if (smartRAMActive === true && currentSmartMode !== 1) payload.smartMode = 1;
                            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: "BatVolt - Average Battery Voltage of all Batteries",
                                    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: idle, 1: AC Flow out, 2: AC Flow in",
                                    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: "DC State; 0: idle, 1: DC Flow out, 2: DC Flow in",
                                    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: writes to RAM / 0: writes 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.properties.acMode === 2 && full.properties.outputLimit !== undefined) { setState(`${base}.auto_in_out_Limit`, full.properties.outputLimit, true); }
                        			if (full.properties.acMode === 1 && full.properties.inputLimit !== undefined) { setState(`${base}.auto_in_out_Limit`, full.properties.inputLimit * -1, 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";
                            const acMode = diff.properties.acMode !== undefined ? diff.properties.acMode : getState(folderZendureApi + ".properties.acMode")?.val;
                            if (acMode === 2 && diff.properties.outputLimit !== undefined) { setState(`${base}.auto_in_out_Limit`, diff.properties.outputLimit, true); }
                            if (acMode === 1 && diff.properties.inputLimit !== undefined) { setState(`${base}.auto_in_out_Limit`, diff.properties.inputLimit * -1, true); }
                        	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 am zuletzt editiert von
                        #290

                        update 19.05.26 16:50h BatteryTypen

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

                        1 Antwort Letzte Antwort
                        0
                        • maxclaudiM maxclaudi

                          Zendure-GerĂ€te Webserver: Steuerung ĂŒber das zenSDK (HTTP)
                          2026.05.02_00.39h; update 24.05.26; 00:15h globale Typsicherung 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.

                          3. auto_in_out_Limit
                            update 21.05.26 22:05h auto_in_out_Limit
                            Beschreibung siehe weiter unten

                          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


                          update 21.05.26 22:05h auto_in_out_Limit

                          Obwohl ich kein Fan der Adapter-Funktion
                          setDeviceAutomationInOutLimit bin, ist die Nachfrage nach einem einfachen, komfortablen Umschaltbefehl anscheinend groß.

                          Deshalb habe ich einen neuen Datenpunkt zum Steuern hinzugefĂŒgt:
                          auto_in_out_Limit

                          Er bietet denselben Komfort, arbeitet aber absolut transparent und mit sicheren VorabprĂŒfungen.

                          Negative Werte (z. B. -100 W fĂŒr das Laden der Batterie):

                          • acMode wird geprĂŒft: Falls acMode: 2 (Entladen), wird es auf acMode: 1 (Laden) geĂ€ndert.
                          • inputLimit wird auf 100 W gesetzt.
                          • outputLimit wird geprĂŒft: Wenn es ungleich 0 ist, wird es auf 0 gesetzt.
                          • Wenn der Schalter "input_output_LimitMode_smartMode_RAM" aktiv ist (unbedingt empfohlen!), wird smartMode geprĂŒft und bei Bedarf von 0 auf 1 gesetzt.
                          • Erst nach diesen PrĂŒfungen wird der Befehl gesendet.

                          Positive Werte (z. B. 100 W fĂŒr das Entladen / Einspeisen):

                          • acMode wird geprĂŒft: Falls acMode: 1 (Laden), wird es auf acMode: 2 (Entladen) geĂ€ndert.
                          • outputLimit wird auf 100 W gesetzt.
                          • inputLimit wird geprĂŒft: Wenn es ungleich 0 ist, wird es auf 0 gesetzt.
                          • Wenn der Schalter "input_output_LimitMode_smartMode_RAM" aktiv ist, wird smartMode geprĂŒft und bei Bedarf von 0 auf 1 gesetzt.
                          • Erst nach diesen PrĂŒfungen wird der Befehl gesendet.

                          Verhalten bei Null (0 W):

                          • Beide Limits (inputLimit und outputLimit) werden sauber auf 0 gesetzt.
                          • Wichtig: Es gibt im Hintergrund keine geheimen Timeouts, die den smartMode nach ein paar Sekunden blind auf 0 reißen! Das System bleibt im sicheren RAM-Modus.

                          Wichtiger Hinweis zum Flash-Speicher:
                          Denkt daran, dass ein Wechsel des acMode (Laden <-> Entladen) vermutlich immer direkt in den Flash-Speicher geschrieben wird – wie alle Modus-Wechsel. Das passiert mit sehr hoher Wahrscheinlichkeit auch dann, wenn smartMode: 1 gesetzt ist. Zendure hat diese Frage bis heute (21.05.2026) nicht zweifelsfrei beantwortet.

                          Der Unterschied zum originalen Adapter:
                          Im Gegensatz zur Funktion des ioBroker-Adapters, die im Hintergrund unkontrollierte Befehlsketten auslöst, Cloud-Schnittstellen zweckentfremdet und mit inoffiziellen Hacks arbeitet, nutzt auto_in_out_Limit ausschließlich offizielle, von Zendure fĂŒr die lokale Steuerung freigegebene Befehle.

                          smartMode ist fĂŒr die aktive, hochfrequente Regelung da – und nicht dafĂŒr, bei jedem kurzen Nulldurchgang im Sekundentakt auf smartMode: 0 gesetzt zu werden. PV-Systeme sind dafĂŒr gebaut, aktiv zu regeln, und nicht, um permanent kĂŒnstlich in den Standby gezwungen zu werden.

                          Am Ende ĂŒberlasse ich die Verantwortung den Nutzern. Jeder sollte frei in seiner Entscheidung sein und genau nachvollziehen können, was auf seiner Hardware passiert. Bei mir gibt es keine versteckten Timer – ihr entscheidet selbst, wie euer smartMode geschaltet wird.

                          Es wird nur verwendet, was auch verwendet werden darf. Das ist der Grund, warum dieses Skript ohne Probleme einfach funktioniert.


                          Update 24.05.26 00:15h globale Typsicherung

                          • Globale Eingangs-Validierung:
                            Alle Steuerbefehle werden automatisch gegen Typenfehler abgesichert.
                            Krumme Zahlen (Floats wie 53.8) werden vor dem Senden abgefangen.

                          • PrĂ€zisere Datenpunkte:
                            Beschreibungen mehrerer Datenpunkte verstÀndlicher aktualisiert.

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

                          update 21.05.26 22:05h auto_in_out_Limit

                          Bitte "Wichtiger Hinweis zum Flash-Speicher" beachten: Beschreibung


                          Update 24.05.26 00:15h globale Typsicherung

                          Viel Spaß!

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

                          1 Antwort Letzte Antwort
                          1
                          • T Offline
                            T Offline
                            T-147
                            schrieb am zuletzt editiert von
                            #292

                            Moin allerseits,

                            erstmal großen Respekt, es ist bewundernswert, wie hier an dem Thema gearbeitet und weiterentwickelt wird. Absolut 👍 dafĂŒr.

                            Jetzt zu dem Grund, warum ich hier schreibe:
                            Hab letztes Jahr mein BKW auf 4 Paneele erweitert und in einem Jahr gesehen, wie viel davon ungenutzt verpuffen. Also hab ich mir jetzt auch nen Solarflow 800 Pro mit Zusatzakku bestellt.
                            Weil der 800 Pro kaum noch verfĂŒgbar war, hab ich direkt den 800 Pro 2 genommen und stellte jetzt fest, dass ich dann ja eher dieses Script hier verwenden muss, weil der Zendure-Adapter eh noch nicht mit dem Pro 2 zusammenarbeitet.

                            Jetzt endlich zu meiner VerstÀndnisfrage:
                            Um das Laden/Entladen zu steuern, reicht nach meinem VerstĂ€ndnis dieses Script nicht, sondern ich benötige noch ein zusĂ€tzliches Script, was die Datenpunkte von meinem BitShake am ZĂ€hler nimmt und die hier angelegten Datenpunkte vom SolarFlow damit "fĂŒttert", um damit eine "Nulleinspeisung" zu realisieren.
                            Sehe ich das so richtig?
                            Wenn ja, wie genau realisiert ihr das?
                            Sollte das zu weit ab vom eigentlichen Thema fĂŒhren, mach ich dafĂŒr gern einen eigenen Thread auf.

                            Besten Dank und weiter frohes Werkeln

                            maxclaudiM 1 Antwort Letzte Antwort
                            0
                            • T T-147

                              Moin allerseits,

                              erstmal großen Respekt, es ist bewundernswert, wie hier an dem Thema gearbeitet und weiterentwickelt wird. Absolut 👍 dafĂŒr.

                              Jetzt zu dem Grund, warum ich hier schreibe:
                              Hab letztes Jahr mein BKW auf 4 Paneele erweitert und in einem Jahr gesehen, wie viel davon ungenutzt verpuffen. Also hab ich mir jetzt auch nen Solarflow 800 Pro mit Zusatzakku bestellt.
                              Weil der 800 Pro kaum noch verfĂŒgbar war, hab ich direkt den 800 Pro 2 genommen und stellte jetzt fest, dass ich dann ja eher dieses Script hier verwenden muss, weil der Zendure-Adapter eh noch nicht mit dem Pro 2 zusammenarbeitet.

                              Jetzt endlich zu meiner VerstÀndnisfrage:
                              Um das Laden/Entladen zu steuern, reicht nach meinem VerstĂ€ndnis dieses Script nicht, sondern ich benötige noch ein zusĂ€tzliches Script, was die Datenpunkte von meinem BitShake am ZĂ€hler nimmt und die hier angelegten Datenpunkte vom SolarFlow damit "fĂŒttert", um damit eine "Nulleinspeisung" zu realisieren.
                              Sehe ich das so richtig?
                              Wenn ja, wie genau realisiert ihr das?
                              Sollte das zu weit ab vom eigentlichen Thema fĂŒhren, mach ich dafĂŒr gern einen eigenen Thread auf.

                              Besten Dank und weiter frohes Werkeln

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

                              @T-147 sagte:

                              Moin allerseits,

                              erstmal großen Respekt, es ist bewundernswert, wie hier an dem Thema gearbeitet und weiterentwickelt wird. Absolut 👍 dafĂŒr.
                              ...
                              Jetzt endlich zu meiner VerstÀndnisfrage:
                              Um das Laden/Entladen zu steuern, reicht nach meinem VerstĂ€ndnis dieses Script nicht, sondern ich benötige noch ein zusĂ€tzliches Script, was die Datenpunkte von meinem BitShake am ZĂ€hler nimmt und die hier angelegten Datenpunkte vom SolarFlow damit "fĂŒttert", um damit eine "Nulleinspeisung" zu realisieren.
                              Sehe ich das so richtig?
                              Wenn ja, wie genau realisiert ihr das?
                              Sollte das zu weit ab vom eigentlichen Thema fĂŒhren, mach ich dafĂŒr gern einen eigenen Thread auf.

                              Besten Dank und weiter frohes Werkeln

                              Hallo und herzlich willkommen!

                              Erstmal vielen Dank fĂŒr dein großes Lob, das motiviert ungemein!

                              GlĂŒckwunsch auch zum neuen Solarflow 800 Pro 2, da hast du feine Hardware am Start.

                              Um deine Frage direkt und kurz zu beantworten:
                              Ja, du siehst das absolut richtig!

                              Warum das so ist und warum dieser modulare Aufbau fĂŒr dich (und alle anderen) eigentlich die beste Nachricht ĂŒberhaupt ist, erklĂ€re ich dir gerne:

                              Ein Skript als "Adapter"
                              Das Skript fungiert im Grunde wie ein lokaler ioBroker-Adapter.
                              Es ist ein reines Steuerungs-Skript.
                              Seine einzige, aber dafĂŒr extrem wichtige Aufgabe ist es, die Kommunikation mit der lokalen API der Zendure-GerĂ€te fehlerfrei, schnell und zuverlĂ€ssig abzuwickeln.

                              Von allein regelt das Skript erst einmal gar nichts.
                              Es stellt dir "nur" die sauberen Datenpunkte zur VerfĂŒgung und leitet deine Befehle an das GerĂ€t weiter.

                              Warum keine eingebaute Nulleinspeisung? Keine Bevormundung und StabilitÀt.
                              Es wĂ€re möglich, hier eine starre Logik fĂŒr z. B. eine Nulleinspeisung mit einzubauen.
                              Aber genau das möchte ich bewusst nicht tun – und zwar aus guten GrĂŒnden:

                              - Volle FlexibilitÀt:
                              Jeder nutzt andere ZĂ€hler (BitShake, Shelly 3EM, VolkszĂ€hler, Tibber, etc.) und hat individuelle WĂŒnsche oder Ziele.
                              WĂŒrde man eine feste Logik einbauen, mĂŒssten alle exakt dasselbe Setup nutzen.

                              - Deine eigene Strategie:
                              Der eine möchte eine klassische, schnelle Nulleinspeisung.
                              Der nÀchste will trÀge regeln, um den Akku zu schonen.
                              Ein Dritter möchte Wetterprognosen einbeziehen oder ab einer bestimmten Akku-Ladung priorisiert einspeisen.

                              - Sicherheit vor Code-Fehlern:
                              Wenn in einer komplexen All-in-One-Regelstrategie ein Fehler steckt, stĂŒrzt dir unter UmstĂ€nden die gesamte Kommunikation zum Zendure-GerĂ€t ab.
                              Durch die Trennung bleibt die Steuerung immer stabil.
                              Du wirst nicht durch vorgefertigten Code (wie bei einigen anderen Adapter-Lösungen) bevormundet, sondern behĂ€ltst die absolute Kontrolle ĂŒber deine Hardware.

                              Wie wird es realisiert?
                              Du erstellst zum Steuern/Regeln ein eigenes separates Blockly- oder JavaScript-Skript.
                              Dieses liest die aktuellen Verbrauchswerte deines BitShake-Auslesers, berechnet den benötigten Sollwert fĂŒr den Solarflow und fĂŒttert damit die Datenpunkte, die dieses Steuerungs-Skript hier erzeugt.

                              Wenn Du möchtest, kannst du sehr gerne genau hier im Thread bleiben!
                              Du musst definitiv keinen neuen Thread aufmachen.

                              @Alle
                              Hier im Thread kann und darf sehr gerne alles besprochen werden, was mit dieser Skriptsteuerung zu tun hat.
                              Dazu gehören logischerweise auch Eure Regelstrategien und Blocklys!
                              Es hilft der ganzen Community, wenn man sich hier austauscht.
                              So bleibt alles an einem Ort auffindbar.

                              Solltet ihr extra Threads fĂŒr Eure eigenen Skripte aufgemacht haben (die mein Skript zur Steuerung verwenden), dann könnt ihr sie hier natĂŒrlich auch gerne verlinken.
                              Ich verlinke ohnehin bei Updates und im Eingangspost immer direkt auf das aktuelle Skript, damit die Basisversion fĂŒr jeden sofort auffindbar bleibt.

                              Wer möchte, kann also gerne seine Logiken zeigen und sich zur Umsetzung austauschen.
                              Gemeinsam könnt ihr hier das Beste herausholen!

                              Viel Erfolg, Spaß beim Einrichten und ganz viel Sonne!

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

                              T 1 Antwort Letzte Antwort
                              0
                              • maxclaudiM maxclaudi

                                @T-147 sagte:

                                Moin allerseits,

                                erstmal großen Respekt, es ist bewundernswert, wie hier an dem Thema gearbeitet und weiterentwickelt wird. Absolut 👍 dafĂŒr.
                                ...
                                Jetzt endlich zu meiner VerstÀndnisfrage:
                                Um das Laden/Entladen zu steuern, reicht nach meinem VerstĂ€ndnis dieses Script nicht, sondern ich benötige noch ein zusĂ€tzliches Script, was die Datenpunkte von meinem BitShake am ZĂ€hler nimmt und die hier angelegten Datenpunkte vom SolarFlow damit "fĂŒttert", um damit eine "Nulleinspeisung" zu realisieren.
                                Sehe ich das so richtig?
                                Wenn ja, wie genau realisiert ihr das?
                                Sollte das zu weit ab vom eigentlichen Thema fĂŒhren, mach ich dafĂŒr gern einen eigenen Thread auf.

                                Besten Dank und weiter frohes Werkeln

                                Hallo und herzlich willkommen!

                                Erstmal vielen Dank fĂŒr dein großes Lob, das motiviert ungemein!

                                GlĂŒckwunsch auch zum neuen Solarflow 800 Pro 2, da hast du feine Hardware am Start.

                                Um deine Frage direkt und kurz zu beantworten:
                                Ja, du siehst das absolut richtig!

                                Warum das so ist und warum dieser modulare Aufbau fĂŒr dich (und alle anderen) eigentlich die beste Nachricht ĂŒberhaupt ist, erklĂ€re ich dir gerne:

                                Ein Skript als "Adapter"
                                Das Skript fungiert im Grunde wie ein lokaler ioBroker-Adapter.
                                Es ist ein reines Steuerungs-Skript.
                                Seine einzige, aber dafĂŒr extrem wichtige Aufgabe ist es, die Kommunikation mit der lokalen API der Zendure-GerĂ€te fehlerfrei, schnell und zuverlĂ€ssig abzuwickeln.

                                Von allein regelt das Skript erst einmal gar nichts.
                                Es stellt dir "nur" die sauberen Datenpunkte zur VerfĂŒgung und leitet deine Befehle an das GerĂ€t weiter.

                                Warum keine eingebaute Nulleinspeisung? Keine Bevormundung und StabilitÀt.
                                Es wĂ€re möglich, hier eine starre Logik fĂŒr z. B. eine Nulleinspeisung mit einzubauen.
                                Aber genau das möchte ich bewusst nicht tun – und zwar aus guten GrĂŒnden:

                                - Volle FlexibilitÀt:
                                Jeder nutzt andere ZĂ€hler (BitShake, Shelly 3EM, VolkszĂ€hler, Tibber, etc.) und hat individuelle WĂŒnsche oder Ziele.
                                WĂŒrde man eine feste Logik einbauen, mĂŒssten alle exakt dasselbe Setup nutzen.

                                - Deine eigene Strategie:
                                Der eine möchte eine klassische, schnelle Nulleinspeisung.
                                Der nÀchste will trÀge regeln, um den Akku zu schonen.
                                Ein Dritter möchte Wetterprognosen einbeziehen oder ab einer bestimmten Akku-Ladung priorisiert einspeisen.

                                - Sicherheit vor Code-Fehlern:
                                Wenn in einer komplexen All-in-One-Regelstrategie ein Fehler steckt, stĂŒrzt dir unter UmstĂ€nden die gesamte Kommunikation zum Zendure-GerĂ€t ab.
                                Durch die Trennung bleibt die Steuerung immer stabil.
                                Du wirst nicht durch vorgefertigten Code (wie bei einigen anderen Adapter-Lösungen) bevormundet, sondern behĂ€ltst die absolute Kontrolle ĂŒber deine Hardware.

                                Wie wird es realisiert?
                                Du erstellst zum Steuern/Regeln ein eigenes separates Blockly- oder JavaScript-Skript.
                                Dieses liest die aktuellen Verbrauchswerte deines BitShake-Auslesers, berechnet den benötigten Sollwert fĂŒr den Solarflow und fĂŒttert damit die Datenpunkte, die dieses Steuerungs-Skript hier erzeugt.

                                Wenn Du möchtest, kannst du sehr gerne genau hier im Thread bleiben!
                                Du musst definitiv keinen neuen Thread aufmachen.

                                @Alle
                                Hier im Thread kann und darf sehr gerne alles besprochen werden, was mit dieser Skriptsteuerung zu tun hat.
                                Dazu gehören logischerweise auch Eure Regelstrategien und Blocklys!
                                Es hilft der ganzen Community, wenn man sich hier austauscht.
                                So bleibt alles an einem Ort auffindbar.

                                Solltet ihr extra Threads fĂŒr Eure eigenen Skripte aufgemacht haben (die mein Skript zur Steuerung verwenden), dann könnt ihr sie hier natĂŒrlich auch gerne verlinken.
                                Ich verlinke ohnehin bei Updates und im Eingangspost immer direkt auf das aktuelle Skript, damit die Basisversion fĂŒr jeden sofort auffindbar bleibt.

                                Wer möchte, kann also gerne seine Logiken zeigen und sich zur Umsetzung austauschen.
                                Gemeinsam könnt ihr hier das Beste herausholen!

                                Viel Erfolg, Spaß beim Einrichten und ganz viel Sonne!

                                T Offline
                                T Offline
                                T-147
                                schrieb am zuletzt editiert von
                                #294

                                @maxclaudi
                                Vielen Dank fĂŒr die ausfĂŒhrliche Info, dann hab ich es tatsĂ€chlich richtig verstanden.

                                Klassische Nulleinsppeisungs-Script fĂŒr Wechselrichter hab ich schon öfter gesehen und mir auch mal ein sehr umfangreiches Script bereitsgestellt, was ich im Netz (glaube sogar hier im Forum) gefunden habe.
                                Aber aktuell entsteht massig Wirrwarr in meinem Kopf, wenn ich ĂŒberlege, wie ich das anpassen muss, um damit auch noch das Laden/Entladen Wechselspiel zu realisieren. đŸ€”
                                Ich hoffe dada kommt Licht ins Dunkel, wenn der 800 Pro2 erstmal mit Deinem Script angebunden ist und ich sehe, wie die Datenpunkte zusammenspielen.

                                Bin sehr gespannt und freu mich auf's MittĂŒfteln.

                                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

                                272

                                Online

                                32.9k

                                Benutzer

                                83.1k

                                Themen

                                1.3m

                                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
                                • Tags
                                • Ungelesen 0
                                • Kategorien
                                • Unreplied
                                • Beliebt
                                • GitHub
                                • Docu
                                • Hilfe