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

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

Community Forum

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

NEWS

  • Neuer Installer und neuer Fixer: übersichtliche Oberfläche und `iob docker`
    BluefoxB
    Bluefox
    7
    1
    588

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

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

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

Geplant Angeheftet Gesperrt Verschoben JavaScript
687 Beiträge 24 Kommentatoren 117.6k Aufrufe 22 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
    D
    Daniel 8
    schrieb am zuletzt editiert von
    #594

    @maxclaudi

    kann ich denn das DPL Blockly auch für meinen Solarflolw 800 Pro verwenden? Du hast das ja auf deine AC geräte ausgelegt oder? Ich brauche ja kein Laden aus dem Netz, weil ich ja nur über PV auflade.

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

    maxclaudiM 1 Antwort Letzte Antwort
    0
    • maxclaudiM maxclaudi

      @T-147 sagte:
      Steuerung lief damit gestern auch super, bis die Batterie heute Nacht bei 24% aufgehört hat zu entladen, weil die Entladesperre gegriffen hat (laut Log). Ich verstehe nur noch nicht, warum so hoch? BatteryLockMinCellVoltage steht auf 3.2V.

      Da hat die Entladesperre bei 3,2 V absolut richtig gegriffen. Nachdem die Batterien nicht mehr entladen wurden, haben sie sich im Ruhezustand (unbelasteter Zustand) etwas erholt, wodurch die Zellspannung wieder leicht angestiegen ist.
      Warum das in Prozent (SoC) so hoch wirkt? Die Entladesperre greift, sobald die schwächste Zelle einer einzelnen Batterie den Mindestwert (Datenpunkt minVol) erreicht – und zwar über alle vorhandenen Batterien hinweg.
      Wenn keine Last mehr anliegt, kommen die Zellen in eine Ruhephase, die Spannung steigt an, aber der SoC-Wert bleibt auf dem geschätzten Stand stehen.
      Der SoC ist ohnehin nur ein errechneter/geschätzter Wert.

      Wenn die Batterien nicht im kalten Außenbereich stehen, kannst du die Entladesperre testweise etwas niedriger ansetzen, z. B. auf 3,16 V. Zu niedrig würde ich allerdings nicht gehen, um die Zellen zu schonen. Taste dich da einfach heran.

      Mir ist jetzt nur aufgefallen, dass die minVol bei mir im ioBroker mit einem Komma geschrieben wird und im Blockly mit einem Punkt als Trennzeichen. Das muss so sein oder kommt der damit evtl durcheinander?

      Im Blockly-Code bitte zwingend den Punkt als Dezimaltrenner verwenden. Der Punkt ist in JavaScript Pflicht. Selbst wenn ioBroker ein Komma akzeptieren würde, bitte trotzdem den Punkt als Dezimaltrenner verwenden.

      Achso, aktuell liegt minVol bei 3,2V und der SoC bei 15%, während das System ein wenig durch die Morgensonne geladen wird. Gestern lag der minVol bei 15% SoC bei 3,24V.
      Woher kommen diese Unterschiede bei fast identischen Bedingungen gestern und heute?

      Wie oben schon kurz angerissen: Die Spannung (Volt) ist ein harter, exakter Messwert.
      Der SoC ist ein vom BMS mathematisch geschätzter und abgeleiteter Wert.
      Diese Abweichungen sind völlig normal.
      Je nachdem, wie hoch die Last kurz vor dem Abschalten war, bricht die Spannung unter Last unterschiedlich stark ein.
      Bei hoher Last werden die 3,2 V Zellspannung viel früher (also bei einem höheren SoC) erreicht als bei einer sehr schwachen, langsamen Entladung.

      T
      T
      T-147
      schrieb am zuletzt editiert von
      #595

      @maxclaudi sagte:

      @T-147 sagte:
      Steuerung lief damit gestern auch super, bis die Batterie heute Nacht bei 24% aufgehört hat zu entladen, weil die Entladesperre gegriffen hat (laut Log). Ich verstehe nur noch nicht, warum so hoch? BatteryLockMinCellVoltage steht auf 3.2V.

      Da hat die Entladesperre bei 3,2 V absolut richtig gegriffen. Nachdem die Batterien nicht mehr entladen wurden, haben sie sich im Ruhezustand (unbelasteter Zustand) etwas erholt, wodurch die Zellspannung wieder leicht angestiegen ist.
      Warum das in Prozent (SoC) so hoch wirkt? Die Entladesperre greift, sobald die schwächste Zelle einer einzelnen Batterie den Mindestwert (Datenpunkt minVol) erreicht – und zwar über alle vorhandenen Batterien hinweg.
      Wenn keine Last mehr anliegt, kommen die Zellen in eine Ruhephase, die Spannung steigt an, aber der SoC-Wert bleibt auf dem geschätzten Stand stehen.
      Der SoC ist ohnehin nur ein errechneter/geschätzter Wert.

      Vielen Dank für die Erklärung. Ich bin zwar elektrotechnisch und was Elektronik angeht relativ Fit, aber für solche Details reicht es dann bisher nicht (hatte auch nie wirklich Kontakt mit Batterie- und Akku-Technik)

      Wenn die Batterien nicht im kalten Außenbereich stehen, kannst du die Entladesperre testweise etwas niedriger ansetzen, z. B. auf 3,16 V. Zu niedrig würde ich allerdings nicht gehen, um die Zellen zu schonen. Taste dich da einfach heran.

      Mein SF800 steht auf dem Dachboden. Unbeheizt, aber im letzten Winter war er nie unter 14°. Dann taste ich mich da mal Stückweise heran. Jetzt im Herbst wird die Batterie eh nicht mehr ganz voll, da stört das experimentieren mit den Settings ja nicht so.

      Im Blockly-Code bitte zwingend den Punkt als Dezimaltrenner verwenden. Der Punkt ist in JavaScript Pflicht. Selbst wenn ioBroker ein Komma akzeptieren würde, bitte trotzdem den Punkt als Dezimaltrenner verwenden.

      👍

      Ich hab dann gestern mal angefangen, dass alle Werte parallel in der InfluxDB landen: Lade- und Entladewerte, SoC, minVol von beiden Akkus und natürllich die Wirkleistung am Zähler.

      Folgendes ist jezt heute Nacht passiert:

      Line  819: 2026-10-08 01:59:50.891 - info: javascript.0 (276) script.js.Energieversorgung.Zendure.SF800_Pro_2_-_Nulleinspeisung_2026_09: Es gibt nichts zu tun, theoretisches positives newLimit: 180; oldLimit: 180
      Line  820: 2026-10-08 02:00:00.879 - info: javascript.0 (276) script.js.Energieversorgung.Zendure.SF800_Pro_2_-_Nulleinspeisung_2026_09: newLimit positiv: 230 ist > oldLimit: 180 Es wird mehr entladen als zuvor.
      Line  821: 2026-10-08 02:00:00.879 - info: javascript.0 (276) script.js.Energieversorgung.Zendure.SF800_Pro_2_-_Nulleinspeisung_2026_09: auto_in_out_Limit ist positiv oder 0: 230
      Line  823: 2026-10-08 02:00:20.892 - info: javascript.0 (276) script.js.Energieversorgung.Zendure.SF800_Pro_2_-_Nulleinspeisung_2026_09: newLimit positiv: 250 ist > oldLimit: 230 Es wird mehr entladen als zuvor.
      Line  824: 2026-10-08 02:00:20.893 - info: javascript.0 (276) script.js.Energieversorgung.Zendure.SF800_Pro_2_-_Nulleinspeisung_2026_09: auto_in_out_Limit ist positiv oder 0: 250
      Line  827: 2026-10-08 02:00:40.925 - info: javascript.0 (276) script.js.Energieversorgung.Zendure.SF800_Pro_2_-_Nulleinspeisung_2026_09: newLimit positiv: 240 ist < oldLimit: 250 Es wird weniger entladen als zuvor.
      Line  828: 2026-10-08 02:00:40.925 - info: javascript.0 (276) script.js.Energieversorgung.Zendure.SF800_Pro_2_-_Nulleinspeisung_2026_09: auto_in_out_Limit ist positiv oder 0: 240
      Line  830: 2026-10-08 02:01:00.912 - info: javascript.0 (276) script.js.Energieversorgung.Zendure.SF800_Pro_2_-_Nulleinspeisung_2026_09: Es gibt nichts zu tun, theoretisches positives newLimit: 240; oldLimit: 240
      Line  832: 2026-10-08 02:01:10.962 - info: javascript.0 (276) script.js.Energieversorgung.Zendure.SF800_Pro_2_-_Nulleinspeisung_2026_09: Es gibt nichts zu tun, theoretisches positives newLimit: 240; oldLimit: 240
      Line  834: 2026-10-08 02:01:20.909 - info: javascript.0 (276) script.js.Energieversorgung.Zendure.SF800_Pro_2_-_Nulleinspeisung_2026_09: Es gibt nichts zu tun, theoretisches positives newLimit: 240; oldLimit: 240
      Line  836: 2026-10-08 02:01:40.864 - info: javascript.0 (276) script.js.Energieversorgung.Zendure.SF800_Pro_2_-_Nulleinspeisung_2026_09: Es gibt nichts zu tun, theoretisches positives newLimit: 240; oldLimit: 240
      Line  837: 2026-10-08 02:01:50.878 - info: javascript.0 (276) script.js.Energieversorgung.Zendure.SF800_Pro_2_-_Nulleinspeisung_2026_09: Es gibt nichts zu tun, theoretisches positives newLimit: 240; oldLimit: 240
      Line  838: 2026-10-08 02:02:00.853 - info: javascript.0 (276) script.js.Energieversorgung.Zendure.SF800_Pro_2_-_Nulleinspeisung_2026_09: newLimit positiv: 230 ist < oldLimit: 240 Es wird weniger entladen als zuvor.
      Line  839: 2026-10-08 02:02:00.854 - info: javascript.0 (276) script.js.Energieversorgung.Zendure.SF800_Pro_2_-_Nulleinspeisung_2026_09: auto_in_out_Limit ist positiv oder 0: 230
      Line  841: 2026-10-08 02:02:20.894 - info: javascript.0 (276) script.js.Energieversorgung.Zendure.SF800_Pro_2_-_Nulleinspeisung_2026_09: Es gibt nichts zu tun, theoretisches positives newLimit: 230; oldLimit: 230
      Line  842: 2026-10-08 02:02:30.902 - info: javascript.0 (276) script.js.Energieversorgung.Zendure.SF800_Pro_2_-_Nulleinspeisung_2026_09: newLimit positiv: 240 ist > oldLimit: 230 Es wird mehr entladen als zuvor.
      Line  843: 2026-10-08 02:02:30.903 - info: javascript.0 (276) script.js.Energieversorgung.Zendure.SF800_Pro_2_-_Nulleinspeisung_2026_09: auto_in_out_Limit ist positiv oder 0: 240
      Line  845: 2026-10-08 02:02:50.854 - info: javascript.0 (276) script.js.Energieversorgung.Zendure.SF800_Pro_2_-
      ...
      Line  870: 2026-10-08 02:06:30.883 - info: javascript.0 (276) script.js.Energieversorgung.Zendure.SF800_Pro_2_-_Nulleinspeisung_2026_09: Es gibt nichts zu tun, theoretisches positives newLimit: 240; oldLimit: 240
      Line  872: 2026-10-08 02:06:40.879 - info: javascript.0 (276) script.js.Energieversorgung.Zendure.SF800_Pro_2_-_Nulleinspeisung_2026_09: Es gibt nichts zu tun, theoretisches positives newLimit: 240; oldLimit: 240
      **Line  873: 2026-10-08 02:06:50.916 - info: javascript.0 (276) script.js.Energieversorgung.Zendure.SF800_Pro_2_-_Nulleinspeisung_2026_09: auto_in_out_Limit 0: 0Entladen gesperrt (Batterieschutz aktiv / MinSoC erreicht)
      Line 1525: 2026-10-08 05:06:41.039 - info: javascript.0 (276) script.js.Energieversorgung.Zendure.SF800_Pro_2_-_Nulleinspeisung_2026_09: auto_in_out_Limit ist positiv, hoher Leistungsabruf: 800
      Line 1957: 2026-10-08 07:17:21.231 - info: javascript.0 (276) script.js.Energieversorgung.Zendure.SF800_Pro_2_-_Nulleinspeisung_2026_09: auto_in_out_Limit 0: 0Entladen gesperrt (Batterieschutz aktiv / MinSoC erreicht)**
      
      


      Um 2:06 Uhr hat die Entladesperre gegriffen, weil minVol bei einem Akku bei 3,19V war. Soweit alles richtig gemacht.
      Aber der SF800 ist dann auf dem letzten Entladewert von 240W stehen geblieben und hat fleißig weiter entladen, bis um der minSoc von 10% (vermutlich aus der App) gegriffen hat. Das war um 4:33 Uhr, da war der minVol schon bei 2,84V. In den folgenden 4 Minuten ist der SoC auf 5% gefallen (ja, ich weiß, ist eh nur nen Schätzwert), während der minVol sich etwas "erholt" hat und stetig wieder anstieg.
      Um 5:48 Uhr fing der SF800 an, mit 1000W zu laden, wohl wegen des niedrigen Batteriestandes. Um 5:06 Uhr wechselte der SF800 von 1000W laden zu 800W entladen (offensichtlich erkennt er sein eigenes Aufladen als hohen Leistungsabruf vom Netz). Das Entladen mit vollen 800W dauerte etwa 7 Minuten, wobei über 600W ins Stromnetz gingen. Dann war der SoC nach dem kurzen Aufladen wieder auf 10% und es kehrte Ruhe ein.
      Aktuell lädt der SF800 gemächlich in die Batterie und seit 7:17 Uhr keinerlei Einträge mehr im Log.
      Update: 10:31 Uhr ist das System in den Bypass gewechselt.

      Läuft also alles noch ein wenig unrund, zumindest bei mir mit dem neueren Blockly.
      Ich beobachte das mal weiter und schaue, ob ich ein Schema oder die Ursache finde.

      maxclaudiM 1 Antwort Letzte Antwort
      0
      • D Daniel 8

        @maxclaudi

        kann ich denn das DPL Blockly auch für meinen Solarflolw 800 Pro verwenden? Du hast das ja auf deine AC geräte ausgelegt oder? Ich brauche ja kein Laden aus dem Netz, weil ich ja nur über PV auflade.

        maxclaudiM
        maxclaudiM
        maxclaudi
        schrieb am zuletzt editiert von
        #596

        @Daniel-8 sagte:

        @maxclaudi

        kann ich denn das DPL Blockly auch für meinen Solarflolw 800 Pro verwenden? Du hast das ja auf deine AC geräte ausgelegt oder? Ich brauche ja kein Laden aus dem Netz, weil ich ja nur über PV auflade.

        Ja.
        Noch einmal ein Hinweis dazu:

        Es ist und soll nur eine Vorlage als Basis sein, die man selbst bearbeiten und auf sein Setup anpassen sollte.
        Ich habe es nicht auf reine AC-Geräte ausgelegt, aber auf Geräte, die über AC laden können. Berücksichtigt ist dabei, dass wenn das Gridmeter negative Werte meldet, dann Richtung 0 die Batterie(n) geladen wird/werden.
        Z. B. falls ein autarker, weiterer Wechselrichter mit PV-Modulen vorhanden ist und einspeist.
        Das Blockly sollte nicht blind übernommen werden.
        Es ist kein „Rundum-sorglos-Paket zum blind Kopieren“, sondern eine Vorlage, die Eigeninitiative erfordert.
        Fehler können immer vorhanden sein oder es funktioniert für das eigene Setup / den eigenen Verwendungszweck nicht direkt aus dem Stand heraus fehlerfrei und perfekt.


        08.10.2026 14:26h
        Weitere Erkenntnis und wahrscheinlich weltweit der erste Hinweis zum LOG-ARRAY:
        Das params-Array im Log-Topic umfasst zwar stets exakt 200 Werte, es handelt sich dabei jedoch nicht um ein statisches Layout!
        Die Struktur ist dynamisch und verschiebt sich nach hinten, je nachdem, wie viele Batterien (packNum) im System aktiv sind.
        Es werden im vorderen Block keine leeren Null-Plätze für optionale Akkus reserviert, sondern staucht oder dehnt die Batterie-Datenfelder sequentiell.
        Das bedeutet: ein starrer Zugriff auf feste Indizes (z. B. für PV-Leistung oder AC-Laden) funktioniert nur so lange, wie sich die Anzahl der Akkus nicht ändert.
        Für ein universelles Skript muss die Verschiebung anhand des Akku-Zählers auf Index [9] dynamisch berechnet werden!

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

        neue Zendure Geräte Cloudless: zURLtool V2.0

        D 1 Antwort Letzte Antwort
        1
        • T T-147

          @maxclaudi sagte:

          @T-147 sagte:
          Steuerung lief damit gestern auch super, bis die Batterie heute Nacht bei 24% aufgehört hat zu entladen, weil die Entladesperre gegriffen hat (laut Log). Ich verstehe nur noch nicht, warum so hoch? BatteryLockMinCellVoltage steht auf 3.2V.

          Da hat die Entladesperre bei 3,2 V absolut richtig gegriffen. Nachdem die Batterien nicht mehr entladen wurden, haben sie sich im Ruhezustand (unbelasteter Zustand) etwas erholt, wodurch die Zellspannung wieder leicht angestiegen ist.
          Warum das in Prozent (SoC) so hoch wirkt? Die Entladesperre greift, sobald die schwächste Zelle einer einzelnen Batterie den Mindestwert (Datenpunkt minVol) erreicht – und zwar über alle vorhandenen Batterien hinweg.
          Wenn keine Last mehr anliegt, kommen die Zellen in eine Ruhephase, die Spannung steigt an, aber der SoC-Wert bleibt auf dem geschätzten Stand stehen.
          Der SoC ist ohnehin nur ein errechneter/geschätzter Wert.

          Vielen Dank für die Erklärung. Ich bin zwar elektrotechnisch und was Elektronik angeht relativ Fit, aber für solche Details reicht es dann bisher nicht (hatte auch nie wirklich Kontakt mit Batterie- und Akku-Technik)

          Wenn die Batterien nicht im kalten Außenbereich stehen, kannst du die Entladesperre testweise etwas niedriger ansetzen, z. B. auf 3,16 V. Zu niedrig würde ich allerdings nicht gehen, um die Zellen zu schonen. Taste dich da einfach heran.

          Mein SF800 steht auf dem Dachboden. Unbeheizt, aber im letzten Winter war er nie unter 14°. Dann taste ich mich da mal Stückweise heran. Jetzt im Herbst wird die Batterie eh nicht mehr ganz voll, da stört das experimentieren mit den Settings ja nicht so.

          Im Blockly-Code bitte zwingend den Punkt als Dezimaltrenner verwenden. Der Punkt ist in JavaScript Pflicht. Selbst wenn ioBroker ein Komma akzeptieren würde, bitte trotzdem den Punkt als Dezimaltrenner verwenden.

          👍

          Ich hab dann gestern mal angefangen, dass alle Werte parallel in der InfluxDB landen: Lade- und Entladewerte, SoC, minVol von beiden Akkus und natürllich die Wirkleistung am Zähler.

          Folgendes ist jezt heute Nacht passiert:

          Line  819: 2026-10-08 01:59:50.891 - info: javascript.0 (276) script.js.Energieversorgung.Zendure.SF800_Pro_2_-_Nulleinspeisung_2026_09: Es gibt nichts zu tun, theoretisches positives newLimit: 180; oldLimit: 180
          Line  820: 2026-10-08 02:00:00.879 - info: javascript.0 (276) script.js.Energieversorgung.Zendure.SF800_Pro_2_-_Nulleinspeisung_2026_09: newLimit positiv: 230 ist > oldLimit: 180 Es wird mehr entladen als zuvor.
          Line  821: 2026-10-08 02:00:00.879 - info: javascript.0 (276) script.js.Energieversorgung.Zendure.SF800_Pro_2_-_Nulleinspeisung_2026_09: auto_in_out_Limit ist positiv oder 0: 230
          Line  823: 2026-10-08 02:00:20.892 - info: javascript.0 (276) script.js.Energieversorgung.Zendure.SF800_Pro_2_-_Nulleinspeisung_2026_09: newLimit positiv: 250 ist > oldLimit: 230 Es wird mehr entladen als zuvor.
          Line  824: 2026-10-08 02:00:20.893 - info: javascript.0 (276) script.js.Energieversorgung.Zendure.SF800_Pro_2_-_Nulleinspeisung_2026_09: auto_in_out_Limit ist positiv oder 0: 250
          Line  827: 2026-10-08 02:00:40.925 - info: javascript.0 (276) script.js.Energieversorgung.Zendure.SF800_Pro_2_-_Nulleinspeisung_2026_09: newLimit positiv: 240 ist < oldLimit: 250 Es wird weniger entladen als zuvor.
          Line  828: 2026-10-08 02:00:40.925 - info: javascript.0 (276) script.js.Energieversorgung.Zendure.SF800_Pro_2_-_Nulleinspeisung_2026_09: auto_in_out_Limit ist positiv oder 0: 240
          Line  830: 2026-10-08 02:01:00.912 - info: javascript.0 (276) script.js.Energieversorgung.Zendure.SF800_Pro_2_-_Nulleinspeisung_2026_09: Es gibt nichts zu tun, theoretisches positives newLimit: 240; oldLimit: 240
          Line  832: 2026-10-08 02:01:10.962 - info: javascript.0 (276) script.js.Energieversorgung.Zendure.SF800_Pro_2_-_Nulleinspeisung_2026_09: Es gibt nichts zu tun, theoretisches positives newLimit: 240; oldLimit: 240
          Line  834: 2026-10-08 02:01:20.909 - info: javascript.0 (276) script.js.Energieversorgung.Zendure.SF800_Pro_2_-_Nulleinspeisung_2026_09: Es gibt nichts zu tun, theoretisches positives newLimit: 240; oldLimit: 240
          Line  836: 2026-10-08 02:01:40.864 - info: javascript.0 (276) script.js.Energieversorgung.Zendure.SF800_Pro_2_-_Nulleinspeisung_2026_09: Es gibt nichts zu tun, theoretisches positives newLimit: 240; oldLimit: 240
          Line  837: 2026-10-08 02:01:50.878 - info: javascript.0 (276) script.js.Energieversorgung.Zendure.SF800_Pro_2_-_Nulleinspeisung_2026_09: Es gibt nichts zu tun, theoretisches positives newLimit: 240; oldLimit: 240
          Line  838: 2026-10-08 02:02:00.853 - info: javascript.0 (276) script.js.Energieversorgung.Zendure.SF800_Pro_2_-_Nulleinspeisung_2026_09: newLimit positiv: 230 ist < oldLimit: 240 Es wird weniger entladen als zuvor.
          Line  839: 2026-10-08 02:02:00.854 - info: javascript.0 (276) script.js.Energieversorgung.Zendure.SF800_Pro_2_-_Nulleinspeisung_2026_09: auto_in_out_Limit ist positiv oder 0: 230
          Line  841: 2026-10-08 02:02:20.894 - info: javascript.0 (276) script.js.Energieversorgung.Zendure.SF800_Pro_2_-_Nulleinspeisung_2026_09: Es gibt nichts zu tun, theoretisches positives newLimit: 230; oldLimit: 230
          Line  842: 2026-10-08 02:02:30.902 - info: javascript.0 (276) script.js.Energieversorgung.Zendure.SF800_Pro_2_-_Nulleinspeisung_2026_09: newLimit positiv: 240 ist > oldLimit: 230 Es wird mehr entladen als zuvor.
          Line  843: 2026-10-08 02:02:30.903 - info: javascript.0 (276) script.js.Energieversorgung.Zendure.SF800_Pro_2_-_Nulleinspeisung_2026_09: auto_in_out_Limit ist positiv oder 0: 240
          Line  845: 2026-10-08 02:02:50.854 - info: javascript.0 (276) script.js.Energieversorgung.Zendure.SF800_Pro_2_-
          ...
          Line  870: 2026-10-08 02:06:30.883 - info: javascript.0 (276) script.js.Energieversorgung.Zendure.SF800_Pro_2_-_Nulleinspeisung_2026_09: Es gibt nichts zu tun, theoretisches positives newLimit: 240; oldLimit: 240
          Line  872: 2026-10-08 02:06:40.879 - info: javascript.0 (276) script.js.Energieversorgung.Zendure.SF800_Pro_2_-_Nulleinspeisung_2026_09: Es gibt nichts zu tun, theoretisches positives newLimit: 240; oldLimit: 240
          **Line  873: 2026-10-08 02:06:50.916 - info: javascript.0 (276) script.js.Energieversorgung.Zendure.SF800_Pro_2_-_Nulleinspeisung_2026_09: auto_in_out_Limit 0: 0Entladen gesperrt (Batterieschutz aktiv / MinSoC erreicht)
          Line 1525: 2026-10-08 05:06:41.039 - info: javascript.0 (276) script.js.Energieversorgung.Zendure.SF800_Pro_2_-_Nulleinspeisung_2026_09: auto_in_out_Limit ist positiv, hoher Leistungsabruf: 800
          Line 1957: 2026-10-08 07:17:21.231 - info: javascript.0 (276) script.js.Energieversorgung.Zendure.SF800_Pro_2_-_Nulleinspeisung_2026_09: auto_in_out_Limit 0: 0Entladen gesperrt (Batterieschutz aktiv / MinSoC erreicht)**
          
          


          Um 2:06 Uhr hat die Entladesperre gegriffen, weil minVol bei einem Akku bei 3,19V war. Soweit alles richtig gemacht.
          Aber der SF800 ist dann auf dem letzten Entladewert von 240W stehen geblieben und hat fleißig weiter entladen, bis um der minSoc von 10% (vermutlich aus der App) gegriffen hat. Das war um 4:33 Uhr, da war der minVol schon bei 2,84V. In den folgenden 4 Minuten ist der SoC auf 5% gefallen (ja, ich weiß, ist eh nur nen Schätzwert), während der minVol sich etwas "erholt" hat und stetig wieder anstieg.
          Um 5:48 Uhr fing der SF800 an, mit 1000W zu laden, wohl wegen des niedrigen Batteriestandes. Um 5:06 Uhr wechselte der SF800 von 1000W laden zu 800W entladen (offensichtlich erkennt er sein eigenes Aufladen als hohen Leistungsabruf vom Netz). Das Entladen mit vollen 800W dauerte etwa 7 Minuten, wobei über 600W ins Stromnetz gingen. Dann war der SoC nach dem kurzen Aufladen wieder auf 10% und es kehrte Ruhe ein.
          Aktuell lädt der SF800 gemächlich in die Batterie und seit 7:17 Uhr keinerlei Einträge mehr im Log.
          Update: 10:31 Uhr ist das System in den Bypass gewechselt.

          Läuft also alles noch ein wenig unrund, zumindest bei mir mit dem neueren Blockly.
          Ich beobachte das mal weiter und schaue, ob ich ein Schema oder die Ursache finde.

          maxclaudiM
          maxclaudiM
          maxclaudi
          schrieb am zuletzt editiert von
          #597

          @T-147
          genau für solche Rückmeldungen ist die Vorlage da!
          Danke für das genaue Log.
          Du hast hier einen extremen Ausnahmefall aufgedeckt, den das Skript in dieser Basis-Version so noch nicht abfangen konnte.

          Was ist passiert?
          Deine Batterie ist in den hardwareseitigen Notlademodus gegangen und hat sich mit 1000W aus dem Netz gerettet.
          Das Skript hat diesen plötzlichen Netzbezug über das Gridmeter registriert und dachte wegen des criticalMode, im Haus sei ein riesiger Verbraucher angesprungen.
          Daraufhin hat es die Entladung wieder freigegeben und gegen die eigene Notladung des Hubs angekämpft.
          Ein klassischer Logik-Teufelskreis.

          Der criticalMode und der normale Regelkreis müssen sofort blockiert werden, wenn der Batterieschutz aktiv ist.
          Ich schaue mir das noch genauer an, damit der batteryLock immer die oberste Priorität hat und nicht mehr vom eigenen Laden ausgetrickst werden kann.

          Möchtest Du bitte dieses Blockly testen:

          updateBlockly-Test.xml

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

          neue Zendure Geräte Cloudless: zURLtool V2.0

          T 2 Antworten Letzte Antwort
          0
          • maxclaudiM maxclaudi

            @Daniel-8 sagte:

            @maxclaudi

            kann ich denn das DPL Blockly auch für meinen Solarflolw 800 Pro verwenden? Du hast das ja auf deine AC geräte ausgelegt oder? Ich brauche ja kein Laden aus dem Netz, weil ich ja nur über PV auflade.

            Ja.
            Noch einmal ein Hinweis dazu:

            Es ist und soll nur eine Vorlage als Basis sein, die man selbst bearbeiten und auf sein Setup anpassen sollte.
            Ich habe es nicht auf reine AC-Geräte ausgelegt, aber auf Geräte, die über AC laden können. Berücksichtigt ist dabei, dass wenn das Gridmeter negative Werte meldet, dann Richtung 0 die Batterie(n) geladen wird/werden.
            Z. B. falls ein autarker, weiterer Wechselrichter mit PV-Modulen vorhanden ist und einspeist.
            Das Blockly sollte nicht blind übernommen werden.
            Es ist kein „Rundum-sorglos-Paket zum blind Kopieren“, sondern eine Vorlage, die Eigeninitiative erfordert.
            Fehler können immer vorhanden sein oder es funktioniert für das eigene Setup / den eigenen Verwendungszweck nicht direkt aus dem Stand heraus fehlerfrei und perfekt.


            08.10.2026 14:26h
            Weitere Erkenntnis und wahrscheinlich weltweit der erste Hinweis zum LOG-ARRAY:
            Das params-Array im Log-Topic umfasst zwar stets exakt 200 Werte, es handelt sich dabei jedoch nicht um ein statisches Layout!
            Die Struktur ist dynamisch und verschiebt sich nach hinten, je nachdem, wie viele Batterien (packNum) im System aktiv sind.
            Es werden im vorderen Block keine leeren Null-Plätze für optionale Akkus reserviert, sondern staucht oder dehnt die Batterie-Datenfelder sequentiell.
            Das bedeutet: ein starrer Zugriff auf feste Indizes (z. B. für PV-Leistung oder AC-Laden) funktioniert nur so lange, wie sich die Anzahl der Akkus nicht ändert.
            Für ein universelles Skript muss die Verschiebung anhand des Akku-Zählers auf Index [9] dynamisch berechnet werden!

            D
            D
            Daniel 8
            schrieb am zuletzt editiert von
            #598

            @maxclaudi sagte:

            Das Blockly sollte nicht blind übernommen werden.
            Es ist kein „Rundum-sorglos-Paket zum blind Kopieren“, sondern eine Vorlage, die Eigeninitiative erfordert.

            Das hatte ich auch nicht vor es blind zu übernehmen. Wenn ich mal ne lange ruhige Minute habe schaue ich es mir genauer an.

            Meine Steuerung läuft ja aktuell. Vielleicht nicht so perfekt aufgebaut wie dein blockly aber es tut.

            Danke für deine ganze Mühe

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

            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
              update axios 21.06.26 00:10h. Danke paul53 für die Idee und das Mitwirken.

              Update 22.06.26 20:00h zenSDK API 3
              API 2 funktioniert weiterhin.
              Bitte Beschreibung im Script beachten.
              update 25.06.26 10.30h control FAN

              update 06.10.26 02:00h neu set batCalTime, fix acMode/auto_in_out_Limit - Übernehmer von Logiken etc. könnten sich auch mal "wenigstens" bedanken!

              update 09.10.26 11:30h fix acMode/auto_in_out_Limit, Standardwert Intervall
              Danke Danitz und petman für Hinweise.

              Wer bisher das Script bereits nutzt: Bitte vor Ausführung des Scripts den Datenpunkt:

              control.acMode
              

              löschen.


              update 16.10.26 21:00h
              batCalTime zusätzliche Type-Prüfung; fallback 01:00h / control.socCompSwitch ohne Funktion, entfernt

              Wer bisher das Script bereits nutzt: Bitte vor Ausführung des Scripts den Datenpunkt:

              control.socCompSwitch
              

              löschen.


              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.
              // update axios 21.06.26 00:10h. Danke paul53 für die Idee und das Mitwirken.																			  
              // update 22.06.26 20:00h API3 und API2 Unterstützung; update 25.06.26 10.30h control FAN
              // update 06.10.26 02:00h neu set batCalTime, fix acMode/auto_in_out_Limit - Übernehmer von Logiken etc. könnten sich auch mal "wenigstens" bedanken!
              // update 09.10.26 11:30h acMode/auto_in_out_Limit Standardwert Intervall Danke Danitz und petman für die Hinweise.
              // update 16.10.26 21:00h batCalTime zusätzliche Type-Prüfung; fallback 01:00h / control.socCompSwitch ohne Funktion, entfernt
              // 
              // 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
              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 = 2400;
               
              //maximum outputLimit -> Maximal möglich (unterstützte) Entladeleistung des Zendure-Geräts
              //Beispiele: SF800 800W / SF800 PRO: 800W / 1600AC: 1600 / SF2400AC: 2400W
              const maxOutputLimit = 2400;
               
              // Haupt-Verzeichnis für das Zendure-Gerät
              // Der Name für das Hauptverzeichnis kann frei gewählt werden.
              // Bitte kein "+", keine Sonderzeichen und keine Umlaute etc. verwenden!
              // Muss aber bei mehreren Skripten und Zendure-Geräte unterschiedlich sein.
              // Beispiel für "2400ACproZENsdk":
              //const folderZendureApi = '0_userdata.0.zendure.' + "2400ACproZENsdk";
              const folderZendureApi = '0_userdata.0.zendure.' + "2400ACproZENsdk";
               
              // 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 kleiner 4000 m. M. nach sinnfrei und nicht empfohlen (Last / Stabilität).
              // HINWEIS: Original Firmwarecode verifiziert, dass z.B. bei MQTT Standard alle 5000ms ein report published wird.
              // wenn noch schneller abgefragt wird, kann es vorkommen, dass alte/identische Werte (hardwarebedingt) empfangen werden.
              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 axios = require('axios'); 
              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);
              }
               
              function hhmmToMinutes(str) {
                  const m = /^(\d{1,2}):(\d{2})$/.exec(String(str).trim());
                  if (!m) return 60;
                  const total = (parseInt(m[1], 10) * 60) + parseInt(m[2], 10);
                  return (total > 1439) ? 60 : total;
              }
              function minutesToHhmm(min) {
                  const n = Math.round(Number(min));
                  if (!Number.isFinite(n) || n < 0 || n > 1439) return String(min);
                  return String(Math.floor(n / 60)).padStart(2, "0") + ":" + String(n % 60).padStart(2, "0");
              }
               
              // Control
              const schemaControl = {
              	acMode: {
                      name: "set acMode; 1: input charge, 2: output discharge",
                      role: "level",
                      type: "number",
                      write: true,
              		def: 2,
                      min: 1,
                      max: 2,
                      apiKey: "acMode",
                  },
              	batCalTime: {
                      name: "set Calibration-Start-Time: 00:00 to 23:59 - on error: 01:00h (HH:MM)",
                      role: "text",
              		unit: "h",
                      type: "string",
                      write: true,
              		def: "01:00",
              		pattern: /^([01][0-9]|2[0-3]):[0-5][0-9]$/,
                      apiKey: "batCalTime",
              		transformWrite: (val) => hhmmToMinutes(val),
              		transformRead: (val) => minutesToHhmm(val),
                  },
              	chargeMaxLimit: {
                      name: "set Charge Max Limit",
                      unit: "W",
                      role: "level",
                      type: "number",
                      write: true,
              		def: maxInputLimit,
                      min: 0,
                      max: maxInputLimit,
                      apiKey: "chargeMaxLimit",
                  },	
              	Fanmode: {
              		name: "set Fanmode; 0: Fan OFF, 1: Fan ON",
                      role: "level",
                      type: "number",
                      write: true,
              		def: 1,
                      min: 0,
                      max: 1,
                      apiKey: "Fanmode",
                  },
              	Fanspeed: {
              		name: "set Fanspeed; 0: Automatic, 1: 1st gear, 2: 2nd gear",
                      role: "level",
                      type: "number",
                      write: true,
              		def: 0,
                      min: 0,
                      max: 2,
                      apiKey: "Fanspeed",
                  },
              	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, def.type === "string" ? def.def : 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.type === "string" && def.pattern) {
                      val = String(val).trim();
                      if (!def.pattern.test(val)) {
                          log(`${key}: Fehlerhafte Eingabe "${val}"; Standard ${def.def} wird verwendet.`, "info");
                          val = def.def;
                      }
                  }
                  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); }
                  if (key === "batCalTime" && getState(folderZendureApi + ".properties.batCalTime")?.val === sendVal) {
                      setState(obj.id, val, true); // Gerät hat den Wert schon -> nichts senden
                      return;
                  }
                  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 = {};
                  setTx(JSON.stringify(payload));
                  axios
                  .post(`http://${IP}/properties/write`, payload, { 
                      timeout: postTimeoutMs, 
                      responseType: "json" 
                  })
                  .then((response) => {
                      const data = response.data;
                      if (data && data.success === true && data.code === 200) {
                          retryCount = 0;
                          finishPost(true);
                      } else {
                          throw new Error("Zendure success/code invalid"); 
                      }
                  })
                  .catch((err) => {
                      retryCount++;
                      if (retryCount <= maxRetries) {
                          log(`POST Retry ${retryCount}/${maxRetries} (${err.message || 'Zendure Error'})`, "warn");
                          setTimeout(() => {
                              postQueue = { ...payload.properties };
                              postActive = false;
                              executePost();
                          }, postCooldownMs);
                          return;
                      }
                      log("POST endgültig fehlgeschlagen", "error");
                      retryCount = 0;
                      finishPost(false);
                  });
              }
               
              function finishPost(success) {
                  if (!success) { syncControlFromProperties(); }
              	setTimeout(() => { postActive = false; startGetLoop(); }, postCooldownMs);
              	}
               
              function syncAutoInOutLimit(diffProps) {
                  const prop = folderZendureApi + ".properties.";
                  const pick = (k) => (diffProps && diffProps[k] !== undefined) ? diffProps[k] : getState(prop + k)?.val;
                  const acMode = pick("acMode");
                  const inp = pick("inputLimit");
                  const out = pick("outputLimit");
                  let val;
                  if (acMode === 2 && out !== undefined) val = out;
                  else if (acMode === 1 && inp !== undefined) val = -inp;
                  else if (acMode === 0) val = 0;
                  else return;
                  val = val || 0; // -0 vermeiden
                  const id = folderZendureApi + ".control.auto_in_out_Limit";
                  if (getState(id)?.val !== val) setState(id, val, true);
              }
               
              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 = def.transformRead ? def.transformRead(propState.val) : 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);
                      }
                  }
              	syncAutoInOutLimit(null);
              }
               
              function startGetLoop() {
                  if (getTimer) { clearInterval(getTimer); }
                  getTimer = setInterval(() => {
                      if (postActive) return;
                      const url = `http://${IP}/properties/report`;
                      axios
                      .get(url, { timeout: getTimeoutMs, responseType: 'text' })
                      .then(function (response) {
                          if (getErrorCount > 0) log("Verbindung wieder OK", "info");
                          getErrorCount = 0;
                          if (!response.data) return;
                          setRxNew(response.data);
                          handleRxNewUpdate(response.data);
                      })
                      .catch(function (err) {
                          const errorMsg = err.response ? err.response.status : err.message;
                          getErrorCount++;
                          if (getErrorCount <= 3) log(`GET Fehler (${getErrorCount}): ${errorMsg}`, "info");
                          if (getErrorCount === 4) log("Keine Verbindung möglich. Zendure-Geräte IP prüfen!", "error");
                      });
                  }, getIntervalMs);
              }
               
              function stopGetLoop() { if (getTimer) { clearInterval(getTimer); getTimer = null; } }
               
              function handleInputLimitMode(val) {
                  const currentOutput = Number(getState(folderZendureApi + ".properties.outputLimit")?.val);
                  const currentSmartMode = getState(folderZendureApi + ".properties.smartMode")?.val;
                  const smartRAMActive = getState(dpSmartRAM)?.val;
                  const payload = {};
              
                  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 currentInput = Number(getState(folderZendureApi + ".properties.inputLimit")?.val);
                  const currentSmartMode = getState(folderZendureApi + ".properties.smartMode")?.val;
                  const smartRAMActive = getState(dpSmartRAM)?.val;
                  const payload = {};
               
                  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 currentInput = Number(getState(folderZendureApi + ".properties.inputLimit")?.val);
                  const currentOutput = Number(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;
                  } else if (val > 0) {
                      payload.acMode = 2;
                      if (currentInput !== 0) payload.inputLimit = 0;
                      payload.outputLimit = val;
                  } else if (val < 0) {
                      payload.acMode = 1;
                      if (currentOutput !== 0) payload.outputLimit = 0;
                      payload.inputLimit = Math.abs(val);
                  }
                  if (smartRAMActive === true && currentSmartMode !== 1) payload.smartMode = 1;
                  if (Object.keys(payload).length > 0) 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",
                          role: "value",
                          type: "number"
                      },	
              		Fanspeed: {
                          name: "Fanspeed - 0: Auto, 1: 1st gear, 2: 2nd gear",
                          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 Start-Time",
              			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 (Zendure-Device)",
                          unit: "°C",
                          role: "value.temperature",
                          type: "number"
                      },
              		IOTState: {
                          name: "Network 0: disconnected, 1: is being configured, 2: Connection successful",
                          role: "value",
                          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"
                      },
              		packNum: {
                          name: "packNum - Number of batteries",
                          role: "value",
                          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, 3: unknown",
                          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"
                      },
              		rssi: {
                          name: "rssi - Received Signal Strength Indicator",
              			unit: "dBm",
                          role: "value",
                          type: "number"
                      },				  
              		smartMode: {
                          name: "smartMode - 1: writes to RAM / 0: writes to Flash",
                          role: "value",
                          type: "number"
                      },
              		socCompSwitch: { //api:3
                          name: "socCompSwitch; Unauthorized modifications are not recommended",
                          role: "value",
                          type: "number"
                      },					
              		socLimit: {
                          name: "socLimit - 0: normal, 1: Charge limit reached, 2: Discharge limit reached, 3: unknown",
                          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 = "AB2000L";
                      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}`;
              		const dpName = (key === "version") ? "zenSDK API Version" : key;
                      if (existsState(dp)) {
                          if (getState(dp).val !== json[key]) { setState(dp, json[key], true); }
                      } else {
                          createState(dp, json[key], { name: dpName, 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}`, def.transformRead ? def.transformRead(full.properties[apiKey]) : 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); }
              			syncAutoInOutLimit(full.properties);
              		} 
              		if (full.packData) { writePackData(full.packData); }
              		setRxOld(val); setRxDiff(""); return;
              	}
                  let oldJson;
                  try { oldJson = JSON.parse(oldStr); } catch (e) { setRxOld(val); return; }
              	const isNewer = (newJson.timestamp && oldJson.timestamp && newJson.timestamp > oldJson.timestamp) || (!oldJson.timestamp && newJson.timestamp);
                  if (!isNewer) return;
                  const diff = buildDiff(oldJson, newJson);
                  if (diff && Object.keys(diff).length > 0) {
                      const diffStr = JSON.stringify(diff);
                      setRxDiff(diffStr); handleRxDiffUpdate(diffStr); 
                  } else { setRxDiff(""); }
                  setRxOld(val);
              }
               
              function handleRxDiffUpdate(val) {
                  if (!val) return;
                  let diff;
                  try { diff = JSON.parse(val); } catch (e) { log("rxDiff parse error: " + e, "warn"); return; }
                  diff = transformJson(diff);
              	if (diff.properties) { syncControlFromDiff(diff); }							   
                  writeMainKeys(diff);
                  if (diff.properties) { writeProperties(diff.properties); }
                  if (diff.packData) { writePackData(diff.packData); }
              }
               
              function syncControlFromDiff(diff) {
              	if (!diff || !diff.properties) return;
                  const base = folderZendureApi + ".control";
                  if (diff.properties.acMode !== undefined || diff.properties.inputLimit !== undefined || diff.properties.outputLimit !== undefined) { syncAutoInOutLimit(diff.properties); }
              	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}`, def.transformRead ? def.transformRead(diff.properties[apiKey]) : 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 "batCalTime":
              					createOrSet(`${folderZendureApi}.properties.batCalTime_h`, minutesToHhmm(val), { type: "string", role: "text", name: "batCalTime_h - Battery Calibration Time (HH:MM)", unit: "h" });
              					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 "socLimit": {
              					if (val >= 16) {
              					val = val & 0x03;
              					}
              				} 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();
              
              
              P
              P
              petman
              schrieb am zuletzt editiert von
              #599

              @maxclaudi sagte:

              // GET intervall
              // Empfehlung: >= 5000 ms.
              // Kleinere Werte möglich, aber m. M. nach sinnfrei und nicht empfohlen (Last / Stabilität).
              const getIntervalMs = 4000; // Intervall für GET 5000ms. Bei Problemen erhöhen. NICHT < 5000!

              Hallo maxclaudi,
              in deinem überarbeiteten Skript steht bei const getIntervalMs = 4000 und im Komentar dahinter, dass man nichrt unter 5000 gehen soll.

              Ich bewundere deinen Enthusiasmus bei der Analyse der Firmware.

              peter

              NUC mit Proxmox,VM's IOB, InfluxDB, Grafana, PiHole;
              Hitchi Lesekopf, jomjol AI-Device für Gas und Wasser;
              BKW, 4x485Wp, SF800Pro2, AB2000, AB2000L
              Insel: 2x430Wp, SF1200, HM800T

              maxclaudiM 1 Antwort Letzte Antwort
              1
              • maxclaudiM maxclaudi

                @T-147
                genau für solche Rückmeldungen ist die Vorlage da!
                Danke für das genaue Log.
                Du hast hier einen extremen Ausnahmefall aufgedeckt, den das Skript in dieser Basis-Version so noch nicht abfangen konnte.

                Was ist passiert?
                Deine Batterie ist in den hardwareseitigen Notlademodus gegangen und hat sich mit 1000W aus dem Netz gerettet.
                Das Skript hat diesen plötzlichen Netzbezug über das Gridmeter registriert und dachte wegen des criticalMode, im Haus sei ein riesiger Verbraucher angesprungen.
                Daraufhin hat es die Entladung wieder freigegeben und gegen die eigene Notladung des Hubs angekämpft.
                Ein klassischer Logik-Teufelskreis.

                Der criticalMode und der normale Regelkreis müssen sofort blockiert werden, wenn der Batterieschutz aktiv ist.
                Ich schaue mir das noch genauer an, damit der batteryLock immer die oberste Priorität hat und nicht mehr vom eigenen Laden ausgetrickst werden kann.

                Möchtest Du bitte dieses Blockly testen:

                updateBlockly-Test.xml

                T
                T
                T-147
                schrieb am zuletzt editiert von
                #600

                @maxclaudi sagte:
                Möchtest Du bitte dieses Blockly testen:

                updateBlockly-Test.xml

                Wird gemacht, kein Problem.

                Ich hatte zusätzlich noch die Vermutung, dass der Batterieschutz zwar aktiviert wird, aber die Regelung dann auf dem aktuellen Wert stehen bleibt (in meinem aktuellen Fall 240W).
                Ich habe deswegen dem Blockly unter Entladeschutz noch zwei kleine Teile hinzugefügt, die einmal den minSoc als zusätzlichen Parameter für den Entladeschutz hinzufügen und einmal nach Aktivieren des Ladeschutzes das In/Out-Limit auf 0 setzt, damit das Einspeisen beendet wird:
                grafik.jpeg
                Maybe ist das mit dem minSoc auch doppelt gemoppelt, aber er sollte den Entladeschutz aktivieren, je nachdem was eher eintritt.

                Ist noch nicht getestet, morgen kann ich da vielleicht schon mehr zu sagen.

                1 Antwort Letzte Antwort
                1
                • P petman

                  @maxclaudi sagte:

                  // GET intervall
                  // Empfehlung: >= 5000 ms.
                  // Kleinere Werte möglich, aber m. M. nach sinnfrei und nicht empfohlen (Last / Stabilität).
                  const getIntervalMs = 4000; // Intervall für GET 5000ms. Bei Problemen erhöhen. NICHT < 5000!

                  Hallo maxclaudi,
                  in deinem überarbeiteten Skript steht bei const getIntervalMs = 4000 und im Komentar dahinter, dass man nichrt unter 5000 gehen soll.

                  Ich bewundere deinen Enthusiasmus bei der Analyse der Firmware.

                  peter

                  maxclaudiM
                  maxclaudiM
                  maxclaudi
                  schrieb am zuletzt editiert von maxclaudi
                  #601

                  @petman sagte:
                  Hallo maxclaudi,

                  Hallo Peter, bald kommt schon wieder das große Fest :-)

                  in deinem überarbeiteten Skript steht bei const getIntervalMs = 4000 und im Komentar dahinter, dass man nichrt unter 5000 gehen soll.

                  Du kennst das doch: Sicher ist sicher ;-)

                  Bei mir läuft es mit 4000 (und selbst mit 3500) absolut stabil.
                  Aber nicht jeder hat ein optimales WLAN oder einen eigenen Access Point direkt neben den Geräten. Zudem funken meine in einem komplett eigenen, isolierten Netz.
                  Das muss man im Zweifel einfach selbst ausprobieren.
                  Bei mir funktionieren die 3500 ms einwandfrei – wahrscheinlich auch, weil in dem isolierten Netz absolut kein anderer HTTP(S)-Traffic dazwischenfunken kann.

                  Ich bewundere deinen Enthusiasmus bei der Analyse der Firmware.

                  Ich sage dazu nur: Kühlschrank und Festplatte ;-)

                  Ich liebe es einfach, wenn Kollegen einen Enthusiasmus und eine gesunde Portion Pragmatismus teilen und verstehen! :-)

                  Viele Grüße


                  edit /PS 09.10.2026 12:20h

                  HINWEIS:
                  Der originale Firmware-Code verifiziert, dass (z. B. bei MQTT im Standard) alle 5000 ms automatisch ein Report published wird.

                  Wenn mittels GET via zenSDK noch schneller abgefragt wird, kann es hardwarebedingt vorkommen, dass man alte bzw. identische Werte empfängt. Es ist m. M. n. daher sinnfrei, schneller als alle 4000–5000 ms abzufragen.

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

                  neue Zendure Geräte Cloudless: zURLtool V2.0

                  maxclaudiM 1 Antwort Letzte Antwort
                  1
                  • D
                    D
                    Danitz
                    schrieb am zuletzt editiert von
                    #602

                    Hallo @maxclaudi,

                    erstmal danke für deine Arbeit und für das Bereitstellen des Skriptes. Ich habe seit ein paar Tagen einen Solarflow 4000 Mix AC+ und versuche eine Regelung zu realisieren.

                    Bei dem "offiziellen" Adapter stoße ich aber auf ein Problem, das auch bei deinem Skript existiert. Bei letzterem kann ich es aber selber anpassen. Warum wird acMode auf 0 gesetzt, wenn auto_in_out_Limit null ist? Der Wert 0 ist laut zenSDK Doku gar nicht vorgesehen oder übersehe ich etwas? Mein Speicher wechselt bei acMode == 0 nicht in den Standby sondern speist mit 200W ein. Belasse ich acMode bei 1 oder 2 passt es.

                    Ach und vielleicht möchtest du noch einen Hinweis bei folderZendureApi ergänzen, das man auf Sonderzeichen versichten sollte. '0_userdata.0.zendure.' + "solarFlow4000MixAC+" schmeckte dem Skript natürlich nicht☺

                    maxclaudiM 1 Antwort Letzte Antwort
                    1
                    • maxclaudiM maxclaudi

                      @T-147
                      genau für solche Rückmeldungen ist die Vorlage da!
                      Danke für das genaue Log.
                      Du hast hier einen extremen Ausnahmefall aufgedeckt, den das Skript in dieser Basis-Version so noch nicht abfangen konnte.

                      Was ist passiert?
                      Deine Batterie ist in den hardwareseitigen Notlademodus gegangen und hat sich mit 1000W aus dem Netz gerettet.
                      Das Skript hat diesen plötzlichen Netzbezug über das Gridmeter registriert und dachte wegen des criticalMode, im Haus sei ein riesiger Verbraucher angesprungen.
                      Daraufhin hat es die Entladung wieder freigegeben und gegen die eigene Notladung des Hubs angekämpft.
                      Ein klassischer Logik-Teufelskreis.

                      Der criticalMode und der normale Regelkreis müssen sofort blockiert werden, wenn der Batterieschutz aktiv ist.
                      Ich schaue mir das noch genauer an, damit der batteryLock immer die oberste Priorität hat und nicht mehr vom eigenen Laden ausgetrickst werden kann.

                      Möchtest Du bitte dieses Blockly testen:

                      updateBlockly-Test.xml

                      T
                      T
                      T-147
                      schrieb am zuletzt editiert von
                      #603

                      @maxclaudi

                      Das Blockly zum Testen ist auf meine Datenpunkte angepasst (gefühlt 3000 Stück...) und läuft ab jetzt. Hab sonst erstmal keinerlei Änderungen daran vorgenommen.

                      Gestern Abend konnte ich nochmal beobachten, wie das Blockly den Entladeschutz aktiviert hat, aber trotzdem bis 10% SoC weiter entladen wurde. Mein kleiner Zusatz hat offensichtlich keinerlei Wirkung gezeigt.
                      Im Anschluss sackte der SoC über einen längeren Zeitraum bis auf 6% runter, minVol stand in der Zeit ziemlich konstan bei 3,15V.
                      Habe dann dann nach einiger Zeit manuell das Aufladen gestartet und ca 30min Laden lassen. minVol ging kurzfristig auf 3,25V hoch und hielt sich dann anschließen auf 3,21V.

                      Aber wie gesagt, ab jetzt läuft das Script zum Testen und ich melde mich, sobald ich Erkenntnisse dazu habe.

                      Beste Grüße

                      maxclaudiM 1 Antwort Letzte Antwort
                      1
                      • D Danitz

                        Hallo @maxclaudi,

                        erstmal danke für deine Arbeit und für das Bereitstellen des Skriptes. Ich habe seit ein paar Tagen einen Solarflow 4000 Mix AC+ und versuche eine Regelung zu realisieren.

                        Bei dem "offiziellen" Adapter stoße ich aber auf ein Problem, das auch bei deinem Skript existiert. Bei letzterem kann ich es aber selber anpassen. Warum wird acMode auf 0 gesetzt, wenn auto_in_out_Limit null ist? Der Wert 0 ist laut zenSDK Doku gar nicht vorgesehen oder übersehe ich etwas? Mein Speicher wechselt bei acMode == 0 nicht in den Standby sondern speist mit 200W ein. Belasse ich acMode bei 1 oder 2 passt es.

                        Ach und vielleicht möchtest du noch einen Hinweis bei folderZendureApi ergänzen, das man auf Sonderzeichen versichten sollte. '0_userdata.0.zendure.' + "solarFlow4000MixAC+" schmeckte dem Skript natürlich nicht☺

                        maxclaudiM
                        maxclaudiM
                        maxclaudi
                        schrieb am zuletzt editiert von maxclaudi
                        #604

                        @Danitz sagte:

                        Hallo @maxclaudi,

                        erstmal danke für deine Arbeit und für das Bereitstellen des Skriptes.

                        Danke auch für das Vertrauen und die Nutzung :-)

                        Zum Bedanken:
                        tut gut, aber ist in einem post nicht nötig.
                        Dazu lieber den voting-Button benutzen:
                        image.jpeg

                        Für das Steuerungs-Script vorzugsweise ganz oben das erste Post (vom Thread) oder auch im Beitrag des Scripts selbst.

                        Ich habe seit ein paar Tagen einen Solarflow 4000 Mix AC+ und versuche eine Regelung zu realisieren.

                        Warum wird acMode auf 0 gesetzt, wenn auto_in_out_Limit null ist? Der Wert 0 ist laut zenSDK Doku gar nicht vorgesehen

                        Du hast absolut Recht. Wurde gefixt. Danke.

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

                        neue Zendure Geräte Cloudless: zURLtool V2.0

                        1 Antwort Letzte Antwort
                        0
                        • T T-147

                          @maxclaudi

                          Das Blockly zum Testen ist auf meine Datenpunkte angepasst (gefühlt 3000 Stück...) und läuft ab jetzt. Hab sonst erstmal keinerlei Änderungen daran vorgenommen.

                          Gestern Abend konnte ich nochmal beobachten, wie das Blockly den Entladeschutz aktiviert hat, aber trotzdem bis 10% SoC weiter entladen wurde. Mein kleiner Zusatz hat offensichtlich keinerlei Wirkung gezeigt.
                          Im Anschluss sackte der SoC über einen längeren Zeitraum bis auf 6% runter, minVol stand in der Zeit ziemlich konstan bei 3,15V.
                          Habe dann dann nach einiger Zeit manuell das Aufladen gestartet und ca 30min Laden lassen. minVol ging kurzfristig auf 3,25V hoch und hielt sich dann anschließen auf 3,21V.

                          Aber wie gesagt, ab jetzt läuft das Script zum Testen und ich melde mich, sobald ich Erkenntnisse dazu habe.

                          Beste Grüße

                          maxclaudiM
                          maxclaudiM
                          maxclaudi
                          schrieb am zuletzt editiert von maxclaudi
                          #605

                          @T-147 sagte:

                          @maxclaudi

                          Das Blockly zum Testen ist auf meine Datenpunkte angepasst (gefühlt 3000 Stück...) und läuft ab jetzt. Hab sonst erstmal keinerlei Änderungen daran vorgenommen.

                          Reines JS ist einfach besser als Blockly.
                          Wenn alle Datenpunkte umgestellt sind, kannst Du die Ansicht auf JS umstellen und dann (auch ohne letzte Zeile) in einen Editor kopieren (z.B. Notepad++).
                          Nach 1600 suchen.
                          Wenn was gefunden wird siehst Du dank der vielen Kommentare auch gleich wo was vergessen wurde.

                          Gestern Abend konnte ich nochmal beobachten, wie das Blockly den Entladeschutz aktiviert hat, aber trotzdem bis 10% SoC weiter entladen wurde. Mein kleiner Zusatz hat offensichtlich keinerlei Wirkung gezeigt.
                          Im Anschluss sackte der SoC über einen längeren Zeitraum bis auf 6% runter, minVol stand in der Zeit ziemlich konstan bei 3,15V.
                          Habe dann dann nach einiger Zeit manuell das Aufladen gestartet und ca 30min Laden lassen. minVol ging kurzfristig auf 3,25V hoch und hielt sich dann anschließen auf 3,21V.

                          das war noch das alte Blockly, nicht war?

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

                          neue Zendure Geräte Cloudless: zURLtool V2.0

                          T 1 Antwort Letzte Antwort
                          0
                          • maxclaudiM maxclaudi

                            @petman sagte:
                            Hallo maxclaudi,

                            Hallo Peter, bald kommt schon wieder das große Fest :-)

                            in deinem überarbeiteten Skript steht bei const getIntervalMs = 4000 und im Komentar dahinter, dass man nichrt unter 5000 gehen soll.

                            Du kennst das doch: Sicher ist sicher ;-)

                            Bei mir läuft es mit 4000 (und selbst mit 3500) absolut stabil.
                            Aber nicht jeder hat ein optimales WLAN oder einen eigenen Access Point direkt neben den Geräten. Zudem funken meine in einem komplett eigenen, isolierten Netz.
                            Das muss man im Zweifel einfach selbst ausprobieren.
                            Bei mir funktionieren die 3500 ms einwandfrei – wahrscheinlich auch, weil in dem isolierten Netz absolut kein anderer HTTP(S)-Traffic dazwischenfunken kann.

                            Ich bewundere deinen Enthusiasmus bei der Analyse der Firmware.

                            Ich sage dazu nur: Kühlschrank und Festplatte ;-)

                            Ich liebe es einfach, wenn Kollegen einen Enthusiasmus und eine gesunde Portion Pragmatismus teilen und verstehen! :-)

                            Viele Grüße


                            edit /PS 09.10.2026 12:20h

                            HINWEIS:
                            Der originale Firmware-Code verifiziert, dass (z. B. bei MQTT im Standard) alle 5000 ms automatisch ein Report published wird.

                            Wenn mittels GET via zenSDK noch schneller abgefragt wird, kann es hardwarebedingt vorkommen, dass man alte bzw. identische Werte empfängt. Es ist m. M. n. daher sinnfrei, schneller als alle 4000–5000 ms abzufragen.

                            maxclaudiM
                            maxclaudiM
                            maxclaudi
                            schrieb am zuletzt editiert von maxclaudi
                            #606

                            HINWEIS:
                            Der originale Firmware-Code verifiziert, dass (z. B. bei MQTT im Standard) alle 5000 ms automatisch ein Report published wird.

                            Wenn mittels GET via zenSDK noch schneller abgefragt wird, kann es hardwarebedingt vorkommen, dass man alte bzw. identische Werte empfängt. Es ist m. M. n. daher sinnfrei, schneller als alle 4000–5000 ms abzufragen.

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

                            neue Zendure Geräte Cloudless: zURLtool V2.0

                            maxclaudiM 1 Antwort Letzte Antwort
                            0
                            • maxclaudiM maxclaudi

                              @T-147 sagte:

                              @maxclaudi

                              Das Blockly zum Testen ist auf meine Datenpunkte angepasst (gefühlt 3000 Stück...) und läuft ab jetzt. Hab sonst erstmal keinerlei Änderungen daran vorgenommen.

                              Reines JS ist einfach besser als Blockly.
                              Wenn alle Datenpunkte umgestellt sind, kannst Du die Ansicht auf JS umstellen und dann (auch ohne letzte Zeile) in einen Editor kopieren (z.B. Notepad++).
                              Nach 1600 suchen.
                              Wenn was gefunden wird siehst Du dank der vielen Kommentare auch gleich wo was vergessen wurde.

                              Gestern Abend konnte ich nochmal beobachten, wie das Blockly den Entladeschutz aktiviert hat, aber trotzdem bis 10% SoC weiter entladen wurde. Mein kleiner Zusatz hat offensichtlich keinerlei Wirkung gezeigt.
                              Im Anschluss sackte der SoC über einen längeren Zeitraum bis auf 6% runter, minVol stand in der Zeit ziemlich konstan bei 3,15V.
                              Habe dann dann nach einiger Zeit manuell das Aufladen gestartet und ca 30min Laden lassen. minVol ging kurzfristig auf 3,25V hoch und hielt sich dann anschließen auf 3,21V.

                              das war noch das alte Blockly, nicht war?

                              T
                              T
                              T-147
                              schrieb am zuletzt editiert von T-147
                              #607

                              @maxclaudi sagte:>
                              Reines JS ist einfach besser als Blockly.
                              Wenn alle Datenpunkte umgestellt sind, kannst Du die Ansicht auf JS umstellen und dann (auch ohne letzte Zeile) in einen Editor kopieren (z.B. Notepad++).
                              Nach 1600 suchen.
                              Wenn was gefunden wird siehst Du dank der vielen Kommentare auch gleich wo was vergessen wurde.

                              Probier ich beim nächsten Mal aus. Mit der XML direkt hab ich das nicht hinbekommen.

                              das war noch das alte Blockly, nicht war?

                              Ja, in der Tat.
                              Ich kann aber zumindest schon sagen, dass das Problem bei deinem Test-Blockly weiterhin besteht.
                              Um etwa 12:00 Uhr wurde die Entladesperre automatisch entfernt und ordnungsgemäß das Entladen geregelt. Um etwa 12:23 Uhr sprint ein großer Verbaucher an (ca 2kW) und der SF800 geht kurz darauf auf Entladen, allerdings nur etwa 400W. Der Verbraucher ist dann wieder aus und das Entladen geht weiter, sodass der Stromzähler -180W zeigt. Nach etwa 20 min hab ich mal das Blockly neu gestartet, hat aber auch nicht geholfen. Blockly aus und altes wieder an hat auch nicht geholfen, ich musste dann manuell (habs leider in der App gemacht und nicht im ZenSDK) Laden/Entladen auf 0 gestellt, dann fing der SF800 wieder an aus dem PV zu laden und nichts weiter zu machen.
                              Im Log ist davon nichts zu sehen, letzter Eintrag ist 12:20 Uhr und dann bis zum Neustart des Blockly nichts, obwohl um 12:23 Uhr auch die Entladesperre mit minVol 3,19V gegriffen haben müsste (oder zeigt das Test-Blockly diese Meldung nicht?)

                              javascript.0	2026-10-09 12:42:15.557	info	script.js.Energieversorgung.Zendure.SF800_Pro_2_-_Nulleinspeisung_2026_09: registered 2 subscriptions, 0 schedules, 0 messages, 0 logs and 0 file subscriptions
                              javascript.0	2026-10-09 12:42:15.543	info	script.js.Energieversorgung.Zendure.SF800_Pro_2_-_Nulleinspeisung_2026_09: start JavaScript (Blockly)
                              javascript.0	2026-10-09 12:42:09.903	info	script.js.Energieversorgung.Zendure.Test_v__Maxclaudi: Stopping script
                              javascript.0	2026-10-09 12:41:28.025	info	script.js.Energieversorgung.Zendure.Test_v__Maxclaudi: registered 2 subscriptions, 0 schedules, 0 messages, 0 logs and 0 file subscriptions
                              javascript.0	2026-10-09 12:41:27.985	info	script.js.Energieversorgung.Zendure.Test_v__Maxclaudi: start JavaScript (Blockly)
                              javascript.0	2026-10-09 12:41:22.772	info	script.js.Energieversorgung.Zendure.Test_v__Maxclaudi: Stopping script
                              javascript.0	2026-10-09 12:32:57.360	info	script.js.Energieversorgung.Zendure.Test_v__Maxclaudi: registered 2 subscriptions, 0 schedules, 0 messages, 0 logs and 0 file subscriptions
                              javascript.0	2026-10-09 12:32:57.290	info	script.js.Energieversorgung.Zendure.Test_v__Maxclaudi: start JavaScript (Blockly)
                              javascript.0	2026-10-09 12:32:57.086	info	script.js.Energieversorgung.Zendure.Test_v__Maxclaudi: Stopping script
                              javascript.0	2026-10-09 12:20:28.408	info	script.js.Energieversorgung.Zendure.Test_v__Maxclaudi: auto_in_out_Limit ist positiv oder 0: 400
                              javascript.0	2026-10-09 12:20:28.407	info	script.js.Energieversorgung.Zendure.Test_v__Maxclaudi: newLimit positiv: 400 ist > oldLimit: 190 Es wird mehr entladen als zuvor.
                              javascript.0	2026-10-09 12:20:18.359	info	script.js.Energieversorgung.Zendure.Test_v__Maxclaudi: auto_in_out_Limit ist positiv oder 0: 190
                              javascript.0	2026-10-09 12:20:18.358	info	script.js.Energieversorgung.Zendure.Test_v__Maxclaudi: newLimit positiv: 190 ist > oldLimit: 170 Es wird mehr entladen als zuvor.
                              javascript.0	2026-10-09 12:19:58.594	info	script.js.Energieversorgung.Zendure.Test_v__Maxclaudi: Es gibt nichts zu tun, theoretisches positives newLimit: 170; oldLimit: 170
                              javascript.0	2026-10-09 12:19:51.338	info	script.js.Energieversorgung.Zendure.Test_v__Maxclaudi: Es gibt nichts zu tun, theoretisches positives newLimit: 170; oldLimit: 170
                              javascript.0	2026-10-09 12:19:50.682	info	script.js.Energieversorgung.Zendure.Test_v__Maxclaudi: Es gibt nichts zu tun, theoretisches positives newLimit: 170; oldLimit: 170
                              

                              Nachtrag:
                              Auch beim Aktivieren des Batterieschutzes wird das Laden/Entladen nicht auf 0 gesetzt und der SF800 entlädt stur auf bisherigem Wert weiter, bis der min SoC aus der App greift.
                              Auch hier muss ich manuell eingreifen. Diesmal hab ich es über das ZenSDK-Script gemacht und auto_in_out_limit auf 0 gesetzt, hat aber erstmal keine Wirkung gezeigt. Ein -1 brachte dann Erfolg. Das würde auch erklären, warum mein Blockly-Zusatz und auf 0 setzen von gestern nicht funktioniert hat.

                              maxclaudiM 1 Antwort Letzte Antwort
                              0
                              • T T-147

                                @maxclaudi sagte:>
                                Reines JS ist einfach besser als Blockly.
                                Wenn alle Datenpunkte umgestellt sind, kannst Du die Ansicht auf JS umstellen und dann (auch ohne letzte Zeile) in einen Editor kopieren (z.B. Notepad++).
                                Nach 1600 suchen.
                                Wenn was gefunden wird siehst Du dank der vielen Kommentare auch gleich wo was vergessen wurde.

                                Probier ich beim nächsten Mal aus. Mit der XML direkt hab ich das nicht hinbekommen.

                                das war noch das alte Blockly, nicht war?

                                Ja, in der Tat.
                                Ich kann aber zumindest schon sagen, dass das Problem bei deinem Test-Blockly weiterhin besteht.
                                Um etwa 12:00 Uhr wurde die Entladesperre automatisch entfernt und ordnungsgemäß das Entladen geregelt. Um etwa 12:23 Uhr sprint ein großer Verbaucher an (ca 2kW) und der SF800 geht kurz darauf auf Entladen, allerdings nur etwa 400W. Der Verbraucher ist dann wieder aus und das Entladen geht weiter, sodass der Stromzähler -180W zeigt. Nach etwa 20 min hab ich mal das Blockly neu gestartet, hat aber auch nicht geholfen. Blockly aus und altes wieder an hat auch nicht geholfen, ich musste dann manuell (habs leider in der App gemacht und nicht im ZenSDK) Laden/Entladen auf 0 gestellt, dann fing der SF800 wieder an aus dem PV zu laden und nichts weiter zu machen.
                                Im Log ist davon nichts zu sehen, letzter Eintrag ist 12:20 Uhr und dann bis zum Neustart des Blockly nichts, obwohl um 12:23 Uhr auch die Entladesperre mit minVol 3,19V gegriffen haben müsste (oder zeigt das Test-Blockly diese Meldung nicht?)

                                javascript.0	2026-10-09 12:42:15.557	info	script.js.Energieversorgung.Zendure.SF800_Pro_2_-_Nulleinspeisung_2026_09: registered 2 subscriptions, 0 schedules, 0 messages, 0 logs and 0 file subscriptions
                                javascript.0	2026-10-09 12:42:15.543	info	script.js.Energieversorgung.Zendure.SF800_Pro_2_-_Nulleinspeisung_2026_09: start JavaScript (Blockly)
                                javascript.0	2026-10-09 12:42:09.903	info	script.js.Energieversorgung.Zendure.Test_v__Maxclaudi: Stopping script
                                javascript.0	2026-10-09 12:41:28.025	info	script.js.Energieversorgung.Zendure.Test_v__Maxclaudi: registered 2 subscriptions, 0 schedules, 0 messages, 0 logs and 0 file subscriptions
                                javascript.0	2026-10-09 12:41:27.985	info	script.js.Energieversorgung.Zendure.Test_v__Maxclaudi: start JavaScript (Blockly)
                                javascript.0	2026-10-09 12:41:22.772	info	script.js.Energieversorgung.Zendure.Test_v__Maxclaudi: Stopping script
                                javascript.0	2026-10-09 12:32:57.360	info	script.js.Energieversorgung.Zendure.Test_v__Maxclaudi: registered 2 subscriptions, 0 schedules, 0 messages, 0 logs and 0 file subscriptions
                                javascript.0	2026-10-09 12:32:57.290	info	script.js.Energieversorgung.Zendure.Test_v__Maxclaudi: start JavaScript (Blockly)
                                javascript.0	2026-10-09 12:32:57.086	info	script.js.Energieversorgung.Zendure.Test_v__Maxclaudi: Stopping script
                                javascript.0	2026-10-09 12:20:28.408	info	script.js.Energieversorgung.Zendure.Test_v__Maxclaudi: auto_in_out_Limit ist positiv oder 0: 400
                                javascript.0	2026-10-09 12:20:28.407	info	script.js.Energieversorgung.Zendure.Test_v__Maxclaudi: newLimit positiv: 400 ist > oldLimit: 190 Es wird mehr entladen als zuvor.
                                javascript.0	2026-10-09 12:20:18.359	info	script.js.Energieversorgung.Zendure.Test_v__Maxclaudi: auto_in_out_Limit ist positiv oder 0: 190
                                javascript.0	2026-10-09 12:20:18.358	info	script.js.Energieversorgung.Zendure.Test_v__Maxclaudi: newLimit positiv: 190 ist > oldLimit: 170 Es wird mehr entladen als zuvor.
                                javascript.0	2026-10-09 12:19:58.594	info	script.js.Energieversorgung.Zendure.Test_v__Maxclaudi: Es gibt nichts zu tun, theoretisches positives newLimit: 170; oldLimit: 170
                                javascript.0	2026-10-09 12:19:51.338	info	script.js.Energieversorgung.Zendure.Test_v__Maxclaudi: Es gibt nichts zu tun, theoretisches positives newLimit: 170; oldLimit: 170
                                javascript.0	2026-10-09 12:19:50.682	info	script.js.Energieversorgung.Zendure.Test_v__Maxclaudi: Es gibt nichts zu tun, theoretisches positives newLimit: 170; oldLimit: 170
                                

                                Nachtrag:
                                Auch beim Aktivieren des Batterieschutzes wird das Laden/Entladen nicht auf 0 gesetzt und der SF800 entlädt stur auf bisherigem Wert weiter, bis der min SoC aus der App greift.
                                Auch hier muss ich manuell eingreifen. Diesmal hab ich es über das ZenSDK-Script gemacht und auto_in_out_limit auf 0 gesetzt, hat aber erstmal keine Wirkung gezeigt. Ein -1 brachte dann Erfolg. Das würde auch erklären, warum mein Blockly-Zusatz und auf 0 setzen von gestern nicht funktioniert hat.

                                maxclaudiM
                                maxclaudiM
                                maxclaudi
                                schrieb am zuletzt editiert von maxclaudi
                                #608

                                @T-147 sagte:
                                Auch hier muss ich manuell eingreifen. Diesmal hab ich es über das ZenSDK-Script gemacht und auto_in_out_limit auf 0 gesetzt, hat aber erstmal keine Wirkung gezeigt. Ein -1 brachte dann Erfolg. Das würde auch erklären, warum mein Blockly-Zusatz und auf 0 setzen von gestern nicht funktioniert hat.

                                Bitte das aktuelle Steuerungsscript benutzen und "vor Aktivieren" den Datenpunkt ....control.acMode löschen.

                                Das andere schaue ich mir demnächst an, sobald ich Zeit habe
                                edit/ps - vor 16/17 Uhr leider nicht, sorry

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

                                neue Zendure Geräte Cloudless: zURLtool V2.0

                                T 1 Antwort Letzte Antwort
                                0
                                • maxclaudiM maxclaudi

                                  @T-147 sagte:
                                  Auch hier muss ich manuell eingreifen. Diesmal hab ich es über das ZenSDK-Script gemacht und auto_in_out_limit auf 0 gesetzt, hat aber erstmal keine Wirkung gezeigt. Ein -1 brachte dann Erfolg. Das würde auch erklären, warum mein Blockly-Zusatz und auf 0 setzen von gestern nicht funktioniert hat.

                                  Bitte das aktuelle Steuerungsscript benutzen und "vor Aktivieren" den Datenpunkt ....control.acMode löschen.

                                  Das andere schaue ich mir demnächst an, sobald ich Zeit habe
                                  edit/ps - vor 16/17 Uhr leider nicht, sorry

                                  T
                                  T
                                  T-147
                                  schrieb am zuletzt editiert von T-147
                                  #609

                                  @maxclaudi

                                  Alles gut, mach dir keinen Stress. Werd am WE eh nicht viel Zeit dafür haben. Hab im Blockly den Bereich mit den Großverbrauchern jetzt erstmal deaktiviert.

                                  ZenSDK-Script grad neu geladen und vor dem Starten den Datenpunkt ...control.acMode gelöscht. 👍
                                  Ich teste weiter.

                                  maxclaudiM 1 Antwort Letzte Antwort
                                  0
                                  • T T-147

                                    @maxclaudi

                                    Alles gut, mach dir keinen Stress. Werd am WE eh nicht viel Zeit dafür haben. Hab im Blockly den Bereich mit den Großverbrauchern jetzt erstmal deaktiviert.

                                    ZenSDK-Script grad neu geladen und vor dem Starten den Datenpunkt ...control.acMode gelöscht. 👍
                                    Ich teste weiter.

                                    maxclaudiM
                                    maxclaudiM
                                    maxclaudi
                                    schrieb am zuletzt editiert von maxclaudi
                                    #610

                                    @T-147 sagte:

                                    @maxclaudi

                                    Alles gut, mach dir keinen Stress. Werd am WE eh nicht viel Zeit dafür haben. Hab im Blockly den Bereich mit den Großverbrauchern jetzt erstmal deaktiviert.

                                    ZenSDK-Script grad neu geladen und vor dem Starten den Datenpunkt ...control.acMode gelöscht. 👍
                                    Ich teste weiter.

                                    ok, Bitte habe Nachsicht mit mir, dass ich nicht die Zeit finde, das alles in Blockly zusammenzuklicken.

                                    Ich habe das Ganze nun – auf die Schnelle – in reinem JavaScript umgesetzt. Das ist für mich wesentlich einfacher zu lesen, und eventuelle Fehler sind schneller zu finden und zu beheben.

                                    Ich habe das JS extra für Einsteiger geschrieben und sehr einfach gehalten, damit es leicht nachvollziehbar ist.

                                    Oben im Skript muss nur noch die Konfiguration eingetragen werden, ohne dass man (wie im Blockly) mühsam unzählige Datenpunkte manuell anpassen muss.

                                    Bitte einfach mal testen.
                                    Ich denke, es ist ausreichend kommentiert...

                                    Das Skript ist bei mir nur kurzfristig auf Lauffähigkeit getestet, sollte aber einwandfrei funktionieren.

                                    // ZENDURE einfache NULLEINSPEISUNG - ioBroker JavaScript-Adapter
                                    // Wahlweise 1 bis 6 Batterien. Keine Schleifen, einfach zu lesen für Einsteiger
                                    // 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 09.10.2026_18:00h für das ioBroker-Forum
                                    //
                                    // EIN WORT IN EIGENER SACHE:.
                                    // In der Vergangenheit wurden meine Erkenntnisse oft ohne Erwähnung in andere Projekte übernommen.
                                    // Ich teile diesen Code gerne. 
                                    // Wer diese(n) Code/Logik in öffentliche Adapter integriert oder anderweitig - auch Auszugsweise verwenden möchte, ist herzlich 
                                    // eingeladen – ich bitte jedoch um die Fairness, die Quelle zu nennen. 
                                    // Das ist der "Lohn" für meine Zeit und Forschung.
                                    // ======================================================================================
                                    
                                    // 1. KONFIGURATION: HIER PFADE UND DATENPUNKTE und Anzahl der Batterien ANPASSEN
                                    
                                    
                                    // Trigger-Datenpunkt (Stromzähler / Smart Meter, Netzbezug in Watt)
                                    const DP_GRID_METER_TRIGGER = '0_userdata.0.Datenpunkte.zendureSolarFlow.user.ZaehlerTrigger_easy_shelly';
                                    
                                    // Haupt-Verzeichnis des Zendure-Datenpunkte-Scripts (muss identisch sein)
                                    const folderZendureApi = '0_userdata.0.zendure.' + "2400ACproZENsdk";
                                    
                                    // Wie viele Batterien hast du? Mögliche Werte: 1, 2, 3, 4, 5 oder 6
                                    const vorhandeneBatAnz = 2;
                                    
                                    // Seriennummern der Batterien.
                                    // Nur die Seriennummer eintragen (ohne ".minVol"), nicht benötigte Zeilen einfach so lassen.
                                    const akku1sn = "Cxxxxxxxxxxxxx1"; // z.B.: const akku1sn = "JO2AEP1P0605193";
                                    const akku2sn = "Cxxxxxxxxxxxxx2";
                                    const akku3sn = "Cxxxxxxxxxxxxx3";
                                    const akku4sn = "Cxxxxxxxxxxxxx4";
                                    const akku5sn = "Cxxxxxxxxxxxxx5";
                                    const akku6sn = "Cxxxxxxxxxxxxx6";
                                    
                                    
                                    // 2. REGEL-PARAMETER (die Standardwerte müssten für die meisten Zendure-Geräte passen)
                                    //---------------------------------------------------------------------------------------
                                    let intervalSec_outputLimit = 15; // Entladen frühestens nach X Sekunden. Standard: 15 s
                                    let intervalSec_inputLimit  = 20; // Laden frühestens nach X Sekunden. Standard: 20 s
                                    let chargingStep            = 50; // Schritte AC-Laden (z.B. 50 W, 100 W). Standard: 50 W
                                    let dischargeStep           = 10; // Schritte Entladen, wird aufgerundet. Standard: 10 W
                                    let wattChangeThreshold     = 30; // Hysterese: Änderungen erst ab X Watt Abweichung. Standard: 30 W
                                    let safetyReserve           = 20; // Sicherheitsreserve gegen Netzbezug beim Laden. Standard: 20 W
                                    
                                    let criticalGridPower        = 600;  // Ab wie viel Watt Netzbezug gilt es als hoher Leistungsabruf. Standard: 600 W
                                    let criticalOutputLimit      = 1600; // Maximale Entladeleistung im Critical-Mode (nicht größer als dein Gerät: 800, 1000, 2400 W)
                                    let intervalSec_criticalMode = 45;   // So lange wird die Spitze gehalten (taktende Großverbraucher: Herd, Klima, Durchlauferhitzer)
                                    
                                    let intervalSec_change_acModeAllowed = 45; // Relais-Schutz: Sperrzeit nach einem Moduswechsel in Sekunden, bitte nicht zu kurz
                                    
                                    let batteryLockMinCellVoltage   = 3.17; // Entladesperre bei dieser Zellspannung: 3.16 bis 3.20 V
                                    let batteryUnlockMinCellVoltage = 3.25; // Freigabe nach der Sperre ab dieser Zellspannung. Standard: 3.25 V
                                    let batteryRecoveryTimeSec      = 300;  // Beruhigungszeit der Zellen in Sekunden. Standard: 300 s (5 min)
                                    
                                    
                                    
                                    //---------------------------------------------------------------------------------------
                                    // AB HIER NICHTS VERÄNDERN!
                                    //---------------------------------------------------------------------------------------
                                    
                                    // Anzahl Batterien absichern (nur 1 bis 6 erlaubt)
                                    let batAnz = Math.round(vorhandeneBatAnz);
                                    if (!(batAnz >= 1)) batAnz = 1;
                                    if (batAnz > 6) batAnz = 6;
                                    
                                    // Basis-Pfade zu den Zendure-Eigenschaften
                                    const BASE_PATH_PROPERTIES = folderZendureApi + '.properties.';
                                    const BASE_PATH_CONTROL    = folderZendureApi + '.control.';
                                    const BASE_PATH_PACKDATA   = folderZendureApi + '.packData.';
                                    
                                    // Zellspannungs-Datenpunkt einer Batterie zusammensetzen
                                    // (ein versehentlich mit eingetragenes ".minVol" wird automatisch entfernt)
                                    function packDp(sn) {
                                        return BASE_PATH_PACKDATA + String(sn).replace('.minVol', '') + '.minVol';
                                    }
                                    
                                    const DP_MIN_VOL_PACK1 = packDp(akku1sn);
                                    const DP_MIN_VOL_PACK2 = packDp(akku2sn);
                                    const DP_MIN_VOL_PACK3 = packDp(akku3sn);
                                    const DP_MIN_VOL_PACK4 = packDp(akku4sn);
                                    const DP_MIN_VOL_PACK5 = packDp(akku5sn);
                                    const DP_MIN_VOL_PACK6 = packDp(akku6sn);
                                    
                                    // Spezifische Datenpunkte (automatisch zusammengesetzt)
                                    const DP_AUTO_IN_OUT_LIMIT = BASE_PATH_CONTROL + 'auto_in_out_Limit';
                                    const DP_SMART_MODE        = BASE_PATH_CONTROL + 'smartMode';
                                    
                                    const DP_INVERSE_MAX_POWER = BASE_PATH_PROPERTIES + 'inverseMaxPower';
                                    const DP_CHARGE_MAX_LIMIT  = BASE_PATH_PROPERTIES + 'chargeMaxLimit';
                                    const DP_GRID_INPUT_POWER  = BASE_PATH_PROPERTIES + 'gridInputPower';
                                    const DP_OUTPUT_HOME_POWER = BASE_PATH_PROPERTIES + 'outputHomePower';
                                    const DP_INPUT_LIMIT       = BASE_PATH_PROPERTIES + 'inputLimit';
                                    const DP_OUTPUT_LIMIT      = BASE_PATH_PROPERTIES + 'outputLimit';
                                    const DP_AC_MODE           = BASE_PATH_PROPERTIES + 'acMode';
                                    const DP_PASS              = BASE_PATH_PROPERTIES + 'pass';
                                    const DP_SOC_LIMIT         = BASE_PATH_PROPERTIES + 'socLimit';
                                    const DP_PACK_STATE        = BASE_PATH_PROPERTIES + 'packState';
                                    const DP_ELECTRIC_LEVEL    = BASE_PATH_PROPERTIES + 'electricLevel';
                                    const DP_MIN_SOC           = BASE_PATH_PROPERTIES + 'minSoc';
                                    const DP_SOC_SET           = BASE_PATH_PROPERTIES + 'socSet';
                                    
                                    // Merker-Variablen
                                    let dplAktiv = true;
                                    let lastSec_outputLimit_auto_in_out_Limit = 0;
                                    let lastSec_inputLimit_auto_in_out_Limit = 0;
                                    let lastSec_criticalMode = 0;
                                    let lastSec_change_acModeAllowed = 0;
                                    let batteryRecoveryStartSec = 0;
                                    
                                    let batteryLock = false;
                                    let batteryRecoveryPending = 0;
                                    
                                    // aktuelle Sekunden
                                    function getNowSec() {
                                        return Math.floor(Date.now() / 1000);
                                    }
                                    
                                    // sicherer getState mit Fallback, verhindert Abstürze bei fehlenden Datenpunkten
                                    function getSafeVal(dpId, fallback) {
                                        if (fallback === undefined) fallback = 0;
                                        let state = getState(dpId);
                                        if (state && state.val !== undefined && state.val !== null) {
                                            return state.val;
                                        }
                                        return fallback;
                                    }
                                    
                                    // konfiguration prüfen und ggf. auf Standardwerte zurücksetzen
                                    if (chargingStep < 10 || chargingStep > 100) chargingStep = 50;
                                    if (dischargeStep < 10 || dischargeStep > 60) dischargeStep = 10;
                                    if (wattChangeThreshold < 6 || wattChangeThreshold > 60) wattChangeThreshold = 30;
                                    if (safetyReserve < 0 || safetyReserve > 60) safetyReserve = 20;
                                    
                                    // Hardware-Maxima aus den Geräte-Eigenschaften lesen (mit Absturzsicherung)
                                    let maxHardwareOutput = getSafeVal(DP_INVERSE_MAX_POWER, 800);
                                    let maxHardwareCharge = getSafeVal(DP_CHARGE_MAX_LIMIT, 800);
                                    
                                    let max_outputLimit = maxHardwareOutput;
                                    let max_inputLimit  = maxHardwareCharge;
                                    if (criticalOutputLimit > maxHardwareOutput) criticalOutputLimit = maxHardwareOutput;
                                    
                                    if (batteryLockMinCellVoltage < 3.0 || batteryLockMinCellVoltage > 3.4 || batteryLockMinCellVoltage >= batteryUnlockMinCellVoltage) {
                                        batteryLockMinCellVoltage = 3.16;
                                    }
                                    if (batteryUnlockMinCellVoltage < 3.2 || batteryUnlockMinCellVoltage > 3.4 || batteryUnlockMinCellVoltage <= batteryLockMinCellVoltage) {
                                        batteryUnlockMinCellVoltage = 3.25;
                                    }
                                    if (batteryRecoveryTimeSec < 60 || batteryRecoveryTimeSec > 1800) batteryRecoveryTimeSec = 300;
                                    
                                    console.info('Nulleinspeisung gestartet. Batterien: ' + batAnz);
                                    
                                    // RELAIS-SCHUTZ: Zeitpunkt des letzten Moduswechsels (Laden <-> Entladen) merken
                                    on({ id: DP_AC_MODE, change: 'ne' }, function () {
                                        lastSec_change_acModeAllowed = getNowSec();
                                    });
                                    
                                    // MAIN TRIGGER: reagiert auf den Stromzähler
                                    on({ id: DP_GRID_METER_TRIGGER, change: 'ne' }, function (obj) {
                                        let gridMeterValue = Math.ceil(obj.state.val || 0);
                                        let nowSec = getNowSec();
                                    
                                        // 1. ZELLSPANNUNGEN UND GERÄTE-WERTE AUSLESEN
                                        // Es wird die niedrigste Zellspannung aller vorhandenen Batterien verwendet.
                                        let minCell = getSafeVal(DP_MIN_VOL_PACK1, 3.3);
                                        if (batAnz >= 2) minCell = Math.min(minCell, getSafeVal(DP_MIN_VOL_PACK2, 3.3));
                                        if (batAnz >= 3) minCell = Math.min(minCell, getSafeVal(DP_MIN_VOL_PACK3, 3.3));
                                        if (batAnz >= 4) minCell = Math.min(minCell, getSafeVal(DP_MIN_VOL_PACK4, 3.3));
                                        if (batAnz >= 5) minCell = Math.min(minCell, getSafeVal(DP_MIN_VOL_PACK5, 3.3));
                                        if (batAnz >= 6) minCell = Math.min(minCell, getSafeVal(DP_MIN_VOL_PACK6, 3.3));
                                    
                                        let socLimitState = getSafeVal(DP_SOC_LIMIT, 0);
                                        let packState     = getSafeVal(DP_PACK_STATE, 0);
                                        let currentSoc    = getSafeVal(DP_ELECTRIC_LEVEL, 0);
                                        let minSocLimit   = getSafeVal(DP_MIN_SOC, 10);
                                        let maxSocLimit   = getSafeVal(DP_SOC_SET, 100);
                                    
                                        // 2. BATTERIE-ENTLADESCHUTZ
                                        if (socLimitState === 2 || (minCell <= batteryLockMinCellVoltage && packState !== 1)) {
                                            if (!batteryLock) {
                                                batteryLock = true;
                                                console.info('Zellschutz AKTIVIERT. Mindestspannung unterschritten oder SoC-Limit erreicht.');
                                            }
                                        }
                                    
                                        // Freigabe nach der Sperre (erst nach Beruhigungszeit)
                                        if (minCell >= batteryUnlockMinCellVoltage && batteryLock && batteryRecoveryPending === 0 && socLimitState !== 2) {
                                            batteryRecoveryPending = 1;
                                            batteryRecoveryStartSec = nowSec;
                                        }
                                    
                                        if (batteryRecoveryPending === 1 && socLimitState !== 2 && (nowSec - batteryRecoveryStartSec >= batteryRecoveryTimeSec)) {
                                            if (minCell < batteryUnlockMinCellVoltage) {
                                                batteryRecoveryPending = 0; // Zellen wieder eingebrochen, Abbruch
                                            } else {
                                                batteryLock = false;
                                                batteryRecoveryPending = 0;
                                                console.info('Zellschutz ERFOLGREICH DEAKTIVIERT. Batterien wieder stabil.');
                                            }
                                        }
                                    
                                        // true = Entladen ist gesperrt
                                        let protectionActive = (batteryLock || socLimitState === 2);
                                    
                                    	// 3. aktuellen Hausverbrauch (ohne Bat) berechnen
                                        // positiv = Verbrauch / Netzbezug, negativ = Überschuss
                                        let netPowerWithoutBattery = (gridMeterValue - getSafeVal(DP_GRID_INPUT_POWER)) + getSafeVal(DP_OUTPUT_HOME_POWER);
                                    
                                    	// 4. hoher Leistungsbedarf (criticalMode)
                                        // Darf NUR laufen, wenn KEIN Batterieschutz greift
                                        if (!protectionActive && gridMeterValue >= criticalGridPower && dplAktiv && (nowSec - lastSec_criticalMode > intervalSec_criticalMode)) {
                                            // criticalMode starten
                                            dplAktiv = false;
                                            let critLimit = Math.min(Math.ceil(Math.max(netPowerWithoutBattery, 0)), criticalOutputLimit);
                                    
                                            setState(DP_AUTO_IN_OUT_LIMIT, critLimit);
                                            console.info('Critical Mode AN: auto_in_out_Limit gesetzt auf: ' + critLimit + ' W');
                                    
                                            lastSec_criticalMode = nowSec;
                                            lastSec_outputLimit_auto_in_out_Limit = nowSec;
                                    
                                        } else if (!dplAktiv && (protectionActive || (nowSec - lastSec_criticalMode > intervalSec_criticalMode))) {
                                            // criticalMode läuft: verlängern oder beenden
                                            if (gridMeterValue >= criticalGridPower && !protectionActive) {
                                                let critLimit = Math.min(Math.ceil(Math.max(netPowerWithoutBattery, 0)), criticalOutputLimit);
                                                if (critLimit !== getSafeVal(DP_OUTPUT_LIMIT)) {
                                                    setState(DP_AUTO_IN_OUT_LIMIT, critLimit);
                                                    console.info('Critical Mode VERLÄNGERT: auto_in_out_Limit angepasst auf: ' + critLimit + ' W');
                                                }
                                                lastSec_criticalMode = nowSec;
                                                lastSec_outputLimit_auto_in_out_Limit = nowSec;
                                            } else {
                                                // Lastspitze vorbei ODER Batterieschutz, dann criticalMode beenden, normale Regelung wieder freigeben
                                                lastSec_outputLimit_auto_in_out_Limit = 0;
                                                lastSec_inputLimit_auto_in_out_Limit = 0;
                                                dplAktiv = true;
                                                lastSec_criticalMode = nowSec;
                                                console.info('Critical Mode AUS: Zurück zur normalen Regelung.');
                                            }
                                        }
                                    
                                    	// 5. normale Regelung Nulleinspeisung & Laderegelung
                                        if (dplAktiv) {
                                            // Sicherstellen, dass Zendure im SmartMode (1) läuft
                                            if (getSafeVal(DP_SMART_MODE) === 0) {
                                                setState(DP_SMART_MODE, 1);
                                            }
                                    
                                            let newLimit = 0;
                                            let currentAcMode = getSafeVal(DP_AC_MODE);
                                            let acModeTimeOk = (nowSec - lastSec_change_acModeAllowed >= intervalSec_change_acModeAllowed);
                                    
                                            // Aktuell gesetzten Wert ermitteln (Laden negativ, Entladen positiv)
                                            let currentActiveLimit = 0;
                                            if (getSafeVal(DP_INPUT_LIMIT) > 0) {
                                                currentActiveLimit = getSafeVal(DP_INPUT_LIMIT) * -1;
                                            } else if (getSafeVal(DP_OUTPUT_LIMIT) > 0) {
                                                currentActiveLimit = getSafeVal(DP_OUTPUT_LIMIT);
                                            }
                                    
                                            // Hysterese: bei kleiner Abweichung wird nichts geändert
                                            let difference = Math.abs(netPowerWithoutBattery - currentActiveLimit);
                                            let hysteresisHold = (difference < wattChangeThreshold);
                                            if (hysteresisHold) {
                                                newLimit = currentActiveLimit;
                                            }
                                    
                                            // FALL A: Überschuss vorhanden, dann Ladeleistung berechnen (Wert wird negativ)
                                            if (!hysteresisHold && netPowerWithoutBattery <= -100 - safetyReserve) {
                                                let safeSurplus = netPowerWithoutBattery + safetyReserve;
                                                let steppedLimit = Math.ceil(safeSurplus / chargingStep) * chargingStep;
                                    
                                                newLimit = steppedLimit > -100 ? 0 : steppedLimit;
                                    
                                                if (newLimit <= (max_inputLimit * -1)) {
                                                    newLimit = max_inputLimit * -1;
                                                }
                                            }
                                    
                                            // FALL D: NOTABSCHALTUNG bei Batterieschutz oder leerem Akku
                                            if ((protectionActive || currentSoc <= minSocLimit) && getSafeVal(DP_OUTPUT_LIMIT) > 0) {
                                                setState(DP_AUTO_IN_OUT_LIMIT, 0);
                                                console.info('auto_in_out_Limit 0: Entladen gesperrt (Batterieschutz aktiv / MinSoC erreicht)');
                                                lastSec_outputLimit_auto_in_out_Limit = nowSec;
                                    
                                            // FALL B: Netzbezug vorhanden, dann aus Akku entladen (Wert ist positiv)
                                            } else if (!protectionActive && currentSoc > minSocLimit && netPowerWithoutBattery > 0 && !hysteresisHold && (nowSec - lastSec_outputLimit_auto_in_out_Limit >= intervalSec_outputLimit)) {
                                                let targetDemand = Math.ceil(netPowerWithoutBattery);
                                    
                                                if (targetDemand <= 30) {
                                                    newLimit = 30;
                                                } else if (targetDemand <= 60) {
                                                    newLimit = 60;
                                                } else if (targetDemand <= 90) {
                                                    newLimit = 90;
                                                } else if (targetDemand <= 100) {
                                                    newLimit = 100;
                                                } else {
                                                    newLimit = Math.ceil(targetDemand / dischargeStep) * dischargeStep;
                                                }
                                    
                                                if (newLimit >= max_outputLimit) {
                                                    newLimit = max_outputLimit;
                                                }
                                    
                                                // Senden... Relais-Schutz: Wechsel vom Lade- in Entlade-Modus nur nach der Sperrzeit
                                                if (newLimit !== getSafeVal(DP_OUTPUT_LIMIT)) {
                                                    if (currentAcMode !== 1 || acModeTimeOk) {
                                                        setState(DP_AUTO_IN_OUT_LIMIT, newLimit);
                                                        console.info('Entladen: auto_in_out_Limit gesetzt auf ' + newLimit + ' W');
                                                        lastSec_outputLimit_auto_in_out_Limit = nowSec;
                                                    }
                                                }
                                    
                                            // FALL C: Überschuss oder kein Verbrauch, dann Laden bzw. Entladen beenden
                                            } else if (netPowerWithoutBattery <= 0 && (nowSec - lastSec_inputLimit_auto_in_out_Limit >= intervalSec_inputLimit)) {
                                                let currentPassMode = getSafeVal(DP_PASS);
                                    
                                                // Ladegrenze erreicht oder Gerät im Pass-Modus -> nicht laden
                                                let chargeBlocked = (socLimitState === 1 || currentSoc >= maxSocLimit || currentPassMode === 2);
                                                if (chargeBlocked && newLimit < 0) {
                                                    newLimit = 0;
                                                }
                                    
                                                if (newLimit !== currentActiveLimit) {
                                                    // Relais-Schutz: Wechsel vom Entlade- ins Lade-Modus nur nach der Sperrzeit (Stoppen ist immer erlaubt)
                                                    if (newLimit === 0 || currentAcMode !== 2 || acModeTimeOk) {
                                                        setState(DP_AUTO_IN_OUT_LIMIT, newLimit);
                                                        console.info('auto_in_out_Limit gesetzt auf ' + newLimit + ' W (negativ = Laden, 0 = Aus)');
                                                        lastSec_inputLimit_auto_in_out_Limit = nowSec;
                                                        if (newLimit === 0) {
                                                            lastSec_outputLimit_auto_in_out_Limit = nowSec;
                                                        }
                                                    }
                                                }
                                            }
                                        }
                                    });
                                    
                                    

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

                                    neue Zendure Geräte Cloudless: zURLtool V2.0

                                    T 1 Antwort Letzte Antwort
                                    1
                                    • maxclaudiM maxclaudi

                                      HINWEIS:
                                      Der originale Firmware-Code verifiziert, dass (z. B. bei MQTT im Standard) alle 5000 ms automatisch ein Report published wird.

                                      Wenn mittels GET via zenSDK noch schneller abgefragt wird, kann es hardwarebedingt vorkommen, dass man alte bzw. identische Werte empfängt. Es ist m. M. n. daher sinnfrei, schneller als alle 4000–5000 ms abzufragen.

                                      maxclaudiM
                                      maxclaudiM
                                      maxclaudi
                                      schrieb am zuletzt editiert von maxclaudi
                                      #611

                                      10.10.2026; 16:28h Memo:
                                      Ab Firmware > V2.0.2 (bzw. V2.0.4) wird eine weitere, spezifische proprietäre Endstelle/Adresse hinzugefügt.

                                      Bisher nur:

                                      apiUrl: https://app.zendure.tech/as
                                      

                                      Nun zusätzlich:

                                      ocppUrl: wss://mq.zen-iot.com:33033/ocpp
                                      

                                      Hintergrund: OCPP (Open Charge Point Protocol) ist der weltweite Standard zur Steuerung von E-Auto-Ladestationen (Wallboxen). Die Aktivierung dieses Endpunkts signalisiert die firmwareseitige Vorbereitung oder bereits vorhandene Funktion auf integriertes EV-Charging (z. B. herstellereigene Wallbox oder Lademanagement).

                                      Für "Cloudless"-Setups: Wer sein System rein lokal betreibt, sollte diese WebSocket-Adresse im Router/DNS blockieren, um unbemerktes "Nachhausetelefonieren" zu verhindern.


                                      edit 11.10.2026; 08:20h
                                      2400AC Pro; Firmware: 2.0.4
                                      seit 11.10.2026; 05:51:15h mit mosquitto als Broker über Port: 1883 ohne TLS verbunden (Bridge-Modus Cloud).

                                      2400ACpro-1883b.png

                                      edit: Memo


                                      Bei Verbindung Port:1883 wurde automatisch gesetzt:

                                      gridOffMode: 2
                                      gridReverse: 2
                                      inverseMaxPower: 800
                                      outputLimit:200
                                      gridStandard: 0
                                      
                                      

                                      Setzen der Werte sofort durchgeführt.
                                      Topic: iot/<prod.>/<dev.>/properties/write

                                      {
                                          "properties": {
                                          "gridStandard": 0,
                                          "inverseMaxPower": 2400,
                                          "chargeLimit": 2400,
                                          "smartMode": 1,
                                          "outputLimit": 0,
                                          "inputLimit": 0,
                                          "gridReverse": 1
                                        }
                                      }
                                      

                                      toDo_247 - edit 21:40h Analyse abgeschlossen.
                                      Abzweigung 31 analysieren.
                                      edit 11.10.2026; 12:48h
                                      Firmware / apk HTTPS verifiziert.
                                      Treffer; Werte in App werden unabhängig von Abzweigung 31 aktualisiert.

                                      edit 11.10.2026; 12:04h
                                      2400AC Pro; seit 11.10.2026; 05:51:15h mit mosquitto als Broker über Port: 1883 ohne TLS verbunden (Bridge-Modus Cloud).
                                      App: Parallele Steuerung in der Bridge per App funktioniert einwandfrei.


                                      edit 11.10.2026; 13:15h report/getALL stabil.


                                      Auszug Log (Report-Intervall: 3000ms):

                                      [getAll] -> iot/2Qe7C9/xXXXxXXX/properties/read
                                      2026-10-11 10:26:45:027
                                      [report] -> /2Qe7C9/xXXXxXXX/properties/report
                                      2026-10-11 10:26:45:046
                                      
                                      [getAll] -> iot/2Qe7C9/xXXXxXXX/properties/read
                                      2026-10-11 10:26:48:026
                                      [report] -> /2Qe7C9/xXXXxXXX/properties/report
                                      2026-10-11 10:26:48:086
                                      
                                      [getAll] -> iot/2Qe7C9/xXXXxXXX/properties/read
                                      2026-10-11 10:26:51:026
                                      [report] -> /2Qe7C9/xXXXxXXX/properties/report
                                      2026-10-11 10:26:51:069
                                      
                                      [getAll] -> iot/2Qe7C9/xXXXxXXX/properties/read
                                      2026-10-11 10:26:54:026
                                      [report] -> /2Qe7C9/xXXXxXXX/properties/report
                                      2026-10-11 10:26:54:046
                                      
                                      [getAll] -> iot/2Qe7C9/xXXXxXXX/properties/read
                                      2026-10-11 10:26:57:026
                                      [report] -> /2Qe7C9/xXXXxXXX/properties/report
                                      2026-10-11 10:26:57:087
                                      
                                      [getAll] -> iot/2Qe7C9/xXXXxXXX/properties/read
                                      2026-10-11 10:27:00:026
                                      [report] -> /2Qe7C9/xXXXxXXX/properties/report
                                      2026-10-11 10:27:00:084
                                      
                                      [getAll] -> iot/2Qe7C9/xXXXxXXX/properties/read
                                      2026-10-11 10:27:03:028
                                      [report] -> /2Qe7C9/xXXXxXXX/properties/report
                                      2026-10-11 10:27:03:056
                                      
                                      [getAll] -> iot/2Qe7C9/xXXXxXXX/properties/read
                                      2026-10-11 10:27:06:026
                                      [report] -> /2Qe7C9/xXXXxXXX/properties/report
                                      2026-10-11 10:27:06:046
                                      
                                      [getAll] -> iot/2Qe7C9/xXXXxXXX/properties/read
                                      2026-10-11 10:27:09:025
                                      [report] -> /2Qe7C9/xXXXxXXX/properties/report
                                      2026-10-11 10:27:09:093
                                      
                                      [getAll] -> iot/2Qe7C9/xXXXxXXX/properties/read
                                      2026-10-11 10:27:12:026
                                      [report] -> /2Qe7C9/xXXXxXXX/properties/report
                                      2026-10-11 10:27:12:067
                                      
                                      

                                      Ergebnis:
                                      3 sek. Report absolut stabil, lückenlos und ohne Lag möglich.
                                      Man sieht wunderbar, dass der Broker immer exakt zur Sekunde :45, :48, :51 etc. anfragt und die vollständigen Antworten Millisekunden später da sind.


                                      Edit 11.10.2026, 21:40 Uhr Analyse abgeschlossen


                                      Analysiert, getestet und verifiziert:
                                      mqttInit und WiFi liegen im Zweig_43 <Datenschutz> und können auch aktiviert werden.

                                      Ergebnis:
                                      WiFi und MQTT können aktiviert werden (toDo_247/Analyse abgeschlossen).
                                      Mehrmals erfolgreich getestet. Das Gerät verbindet sich über Port 1883. Die Aktivierung ist mit zURLtool oder der App V7.0 mit einem Trick möglich.
                                      Weltweit das erste Mal öffentlich dokumentiert.

                                      Hinweis: Eine detaillierte Anleitung zum Verbinden über Port 1883 wird von mir nicht öffentlich publiziert. Entsprechende Anfragen bitte ausschließlich per PN oder E-Mail – ich werde im Einzelfall abwägen.

                                      Damit sind alle entscheidenden Analysen und das Log-Mapping (bis auf wenige Ausnahmen) für mich abgeschlossen.

                                      edit: 12.10.2026; 12:40h Mythos widerlegt: BT und WiFi simultan möglich


                                      verifiziert:
                                      Entgegen der bisherigen Annahme ist eine simultane, dauerhafte Parallelschaltung von MQTT (Port 1883) und Bluetooth möglich.

                                      simultanBT-MQTT-forum.png

                                      Damit ist der weitverbreitete Mythos, dass BT und WLAN nicht parallel laufen können, widerlegt.
                                      Interessantes Detail zur Funktionsweise:
                                      Im Gegensatz zu MQTT (Port 1883) sendet die BT-Verbindung sofort nach einmaligem Anstoßen durch ein getAll automatisch alle 5000ms einen Report über einen Timer (wie im standardmäßigen MQTT Port 8883 Modus).

                                      Ein reiner Bluetooth-Betrieb ist somit als dauerhafte, lokale Alternative zu MQTT möglich. Damit stehen nun drei Verbindungswege zur Auswahl: MQTT 8883, MQTT 1883 und reines BT. Zieht man das zenSDK separat in Betracht, sind es vier Verbindungswege.

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

                                      neue Zendure Geräte Cloudless: zURLtool V2.0

                                      1 Antwort Letzte Antwort
                                      0
                                      • maxclaudiM maxclaudi

                                        @T-147 sagte:

                                        @maxclaudi

                                        Alles gut, mach dir keinen Stress. Werd am WE eh nicht viel Zeit dafür haben. Hab im Blockly den Bereich mit den Großverbrauchern jetzt erstmal deaktiviert.

                                        ZenSDK-Script grad neu geladen und vor dem Starten den Datenpunkt ...control.acMode gelöscht. 👍
                                        Ich teste weiter.

                                        ok, Bitte habe Nachsicht mit mir, dass ich nicht die Zeit finde, das alles in Blockly zusammenzuklicken.

                                        Ich habe das Ganze nun – auf die Schnelle – in reinem JavaScript umgesetzt. Das ist für mich wesentlich einfacher zu lesen, und eventuelle Fehler sind schneller zu finden und zu beheben.

                                        Ich habe das JS extra für Einsteiger geschrieben und sehr einfach gehalten, damit es leicht nachvollziehbar ist.

                                        Oben im Skript muss nur noch die Konfiguration eingetragen werden, ohne dass man (wie im Blockly) mühsam unzählige Datenpunkte manuell anpassen muss.

                                        Bitte einfach mal testen.
                                        Ich denke, es ist ausreichend kommentiert...

                                        Das Skript ist bei mir nur kurzfristig auf Lauffähigkeit getestet, sollte aber einwandfrei funktionieren.

                                        // ZENDURE einfache NULLEINSPEISUNG - ioBroker JavaScript-Adapter
                                        // Wahlweise 1 bis 6 Batterien. Keine Schleifen, einfach zu lesen für Einsteiger
                                        // 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 09.10.2026_18:00h für das ioBroker-Forum
                                        //
                                        // EIN WORT IN EIGENER SACHE:.
                                        // In der Vergangenheit wurden meine Erkenntnisse oft ohne Erwähnung in andere Projekte übernommen.
                                        // Ich teile diesen Code gerne. 
                                        // Wer diese(n) Code/Logik in öffentliche Adapter integriert oder anderweitig - auch Auszugsweise verwenden möchte, ist herzlich 
                                        // eingeladen – ich bitte jedoch um die Fairness, die Quelle zu nennen. 
                                        // Das ist der "Lohn" für meine Zeit und Forschung.
                                        // ======================================================================================
                                        
                                        // 1. KONFIGURATION: HIER PFADE UND DATENPUNKTE und Anzahl der Batterien ANPASSEN
                                        
                                        
                                        // Trigger-Datenpunkt (Stromzähler / Smart Meter, Netzbezug in Watt)
                                        const DP_GRID_METER_TRIGGER = '0_userdata.0.Datenpunkte.zendureSolarFlow.user.ZaehlerTrigger_easy_shelly';
                                        
                                        // Haupt-Verzeichnis des Zendure-Datenpunkte-Scripts (muss identisch sein)
                                        const folderZendureApi = '0_userdata.0.zendure.' + "2400ACproZENsdk";
                                        
                                        // Wie viele Batterien hast du? Mögliche Werte: 1, 2, 3, 4, 5 oder 6
                                        const vorhandeneBatAnz = 2;
                                        
                                        // Seriennummern der Batterien.
                                        // Nur die Seriennummer eintragen (ohne ".minVol"), nicht benötigte Zeilen einfach so lassen.
                                        const akku1sn = "Cxxxxxxxxxxxxx1"; // z.B.: const akku1sn = "JO2AEP1P0605193";
                                        const akku2sn = "Cxxxxxxxxxxxxx2";
                                        const akku3sn = "Cxxxxxxxxxxxxx3";
                                        const akku4sn = "Cxxxxxxxxxxxxx4";
                                        const akku5sn = "Cxxxxxxxxxxxxx5";
                                        const akku6sn = "Cxxxxxxxxxxxxx6";
                                        
                                        
                                        // 2. REGEL-PARAMETER (die Standardwerte müssten für die meisten Zendure-Geräte passen)
                                        //---------------------------------------------------------------------------------------
                                        let intervalSec_outputLimit = 15; // Entladen frühestens nach X Sekunden. Standard: 15 s
                                        let intervalSec_inputLimit  = 20; // Laden frühestens nach X Sekunden. Standard: 20 s
                                        let chargingStep            = 50; // Schritte AC-Laden (z.B. 50 W, 100 W). Standard: 50 W
                                        let dischargeStep           = 10; // Schritte Entladen, wird aufgerundet. Standard: 10 W
                                        let wattChangeThreshold     = 30; // Hysterese: Änderungen erst ab X Watt Abweichung. Standard: 30 W
                                        let safetyReserve           = 20; // Sicherheitsreserve gegen Netzbezug beim Laden. Standard: 20 W
                                        
                                        let criticalGridPower        = 600;  // Ab wie viel Watt Netzbezug gilt es als hoher Leistungsabruf. Standard: 600 W
                                        let criticalOutputLimit      = 1600; // Maximale Entladeleistung im Critical-Mode (nicht größer als dein Gerät: 800, 1000, 2400 W)
                                        let intervalSec_criticalMode = 45;   // So lange wird die Spitze gehalten (taktende Großverbraucher: Herd, Klima, Durchlauferhitzer)
                                        
                                        let intervalSec_change_acModeAllowed = 45; // Relais-Schutz: Sperrzeit nach einem Moduswechsel in Sekunden, bitte nicht zu kurz
                                        
                                        let batteryLockMinCellVoltage   = 3.17; // Entladesperre bei dieser Zellspannung: 3.16 bis 3.20 V
                                        let batteryUnlockMinCellVoltage = 3.25; // Freigabe nach der Sperre ab dieser Zellspannung. Standard: 3.25 V
                                        let batteryRecoveryTimeSec      = 300;  // Beruhigungszeit der Zellen in Sekunden. Standard: 300 s (5 min)
                                        
                                        
                                        
                                        //---------------------------------------------------------------------------------------
                                        // AB HIER NICHTS VERÄNDERN!
                                        //---------------------------------------------------------------------------------------
                                        
                                        // Anzahl Batterien absichern (nur 1 bis 6 erlaubt)
                                        let batAnz = Math.round(vorhandeneBatAnz);
                                        if (!(batAnz >= 1)) batAnz = 1;
                                        if (batAnz > 6) batAnz = 6;
                                        
                                        // Basis-Pfade zu den Zendure-Eigenschaften
                                        const BASE_PATH_PROPERTIES = folderZendureApi + '.properties.';
                                        const BASE_PATH_CONTROL    = folderZendureApi + '.control.';
                                        const BASE_PATH_PACKDATA   = folderZendureApi + '.packData.';
                                        
                                        // Zellspannungs-Datenpunkt einer Batterie zusammensetzen
                                        // (ein versehentlich mit eingetragenes ".minVol" wird automatisch entfernt)
                                        function packDp(sn) {
                                            return BASE_PATH_PACKDATA + String(sn).replace('.minVol', '') + '.minVol';
                                        }
                                        
                                        const DP_MIN_VOL_PACK1 = packDp(akku1sn);
                                        const DP_MIN_VOL_PACK2 = packDp(akku2sn);
                                        const DP_MIN_VOL_PACK3 = packDp(akku3sn);
                                        const DP_MIN_VOL_PACK4 = packDp(akku4sn);
                                        const DP_MIN_VOL_PACK5 = packDp(akku5sn);
                                        const DP_MIN_VOL_PACK6 = packDp(akku6sn);
                                        
                                        // Spezifische Datenpunkte (automatisch zusammengesetzt)
                                        const DP_AUTO_IN_OUT_LIMIT = BASE_PATH_CONTROL + 'auto_in_out_Limit';
                                        const DP_SMART_MODE        = BASE_PATH_CONTROL + 'smartMode';
                                        
                                        const DP_INVERSE_MAX_POWER = BASE_PATH_PROPERTIES + 'inverseMaxPower';
                                        const DP_CHARGE_MAX_LIMIT  = BASE_PATH_PROPERTIES + 'chargeMaxLimit';
                                        const DP_GRID_INPUT_POWER  = BASE_PATH_PROPERTIES + 'gridInputPower';
                                        const DP_OUTPUT_HOME_POWER = BASE_PATH_PROPERTIES + 'outputHomePower';
                                        const DP_INPUT_LIMIT       = BASE_PATH_PROPERTIES + 'inputLimit';
                                        const DP_OUTPUT_LIMIT      = BASE_PATH_PROPERTIES + 'outputLimit';
                                        const DP_AC_MODE           = BASE_PATH_PROPERTIES + 'acMode';
                                        const DP_PASS              = BASE_PATH_PROPERTIES + 'pass';
                                        const DP_SOC_LIMIT         = BASE_PATH_PROPERTIES + 'socLimit';
                                        const DP_PACK_STATE        = BASE_PATH_PROPERTIES + 'packState';
                                        const DP_ELECTRIC_LEVEL    = BASE_PATH_PROPERTIES + 'electricLevel';
                                        const DP_MIN_SOC           = BASE_PATH_PROPERTIES + 'minSoc';
                                        const DP_SOC_SET           = BASE_PATH_PROPERTIES + 'socSet';
                                        
                                        // Merker-Variablen
                                        let dplAktiv = true;
                                        let lastSec_outputLimit_auto_in_out_Limit = 0;
                                        let lastSec_inputLimit_auto_in_out_Limit = 0;
                                        let lastSec_criticalMode = 0;
                                        let lastSec_change_acModeAllowed = 0;
                                        let batteryRecoveryStartSec = 0;
                                        
                                        let batteryLock = false;
                                        let batteryRecoveryPending = 0;
                                        
                                        // aktuelle Sekunden
                                        function getNowSec() {
                                            return Math.floor(Date.now() / 1000);
                                        }
                                        
                                        // sicherer getState mit Fallback, verhindert Abstürze bei fehlenden Datenpunkten
                                        function getSafeVal(dpId, fallback) {
                                            if (fallback === undefined) fallback = 0;
                                            let state = getState(dpId);
                                            if (state && state.val !== undefined && state.val !== null) {
                                                return state.val;
                                            }
                                            return fallback;
                                        }
                                        
                                        // konfiguration prüfen und ggf. auf Standardwerte zurücksetzen
                                        if (chargingStep < 10 || chargingStep > 100) chargingStep = 50;
                                        if (dischargeStep < 10 || dischargeStep > 60) dischargeStep = 10;
                                        if (wattChangeThreshold < 6 || wattChangeThreshold > 60) wattChangeThreshold = 30;
                                        if (safetyReserve < 0 || safetyReserve > 60) safetyReserve = 20;
                                        
                                        // Hardware-Maxima aus den Geräte-Eigenschaften lesen (mit Absturzsicherung)
                                        let maxHardwareOutput = getSafeVal(DP_INVERSE_MAX_POWER, 800);
                                        let maxHardwareCharge = getSafeVal(DP_CHARGE_MAX_LIMIT, 800);
                                        
                                        let max_outputLimit = maxHardwareOutput;
                                        let max_inputLimit  = maxHardwareCharge;
                                        if (criticalOutputLimit > maxHardwareOutput) criticalOutputLimit = maxHardwareOutput;
                                        
                                        if (batteryLockMinCellVoltage < 3.0 || batteryLockMinCellVoltage > 3.4 || batteryLockMinCellVoltage >= batteryUnlockMinCellVoltage) {
                                            batteryLockMinCellVoltage = 3.16;
                                        }
                                        if (batteryUnlockMinCellVoltage < 3.2 || batteryUnlockMinCellVoltage > 3.4 || batteryUnlockMinCellVoltage <= batteryLockMinCellVoltage) {
                                            batteryUnlockMinCellVoltage = 3.25;
                                        }
                                        if (batteryRecoveryTimeSec < 60 || batteryRecoveryTimeSec > 1800) batteryRecoveryTimeSec = 300;
                                        
                                        console.info('Nulleinspeisung gestartet. Batterien: ' + batAnz);
                                        
                                        // RELAIS-SCHUTZ: Zeitpunkt des letzten Moduswechsels (Laden <-> Entladen) merken
                                        on({ id: DP_AC_MODE, change: 'ne' }, function () {
                                            lastSec_change_acModeAllowed = getNowSec();
                                        });
                                        
                                        // MAIN TRIGGER: reagiert auf den Stromzähler
                                        on({ id: DP_GRID_METER_TRIGGER, change: 'ne' }, function (obj) {
                                            let gridMeterValue = Math.ceil(obj.state.val || 0);
                                            let nowSec = getNowSec();
                                        
                                            // 1. ZELLSPANNUNGEN UND GERÄTE-WERTE AUSLESEN
                                            // Es wird die niedrigste Zellspannung aller vorhandenen Batterien verwendet.
                                            let minCell = getSafeVal(DP_MIN_VOL_PACK1, 3.3);
                                            if (batAnz >= 2) minCell = Math.min(minCell, getSafeVal(DP_MIN_VOL_PACK2, 3.3));
                                            if (batAnz >= 3) minCell = Math.min(minCell, getSafeVal(DP_MIN_VOL_PACK3, 3.3));
                                            if (batAnz >= 4) minCell = Math.min(minCell, getSafeVal(DP_MIN_VOL_PACK4, 3.3));
                                            if (batAnz >= 5) minCell = Math.min(minCell, getSafeVal(DP_MIN_VOL_PACK5, 3.3));
                                            if (batAnz >= 6) minCell = Math.min(minCell, getSafeVal(DP_MIN_VOL_PACK6, 3.3));
                                        
                                            let socLimitState = getSafeVal(DP_SOC_LIMIT, 0);
                                            let packState     = getSafeVal(DP_PACK_STATE, 0);
                                            let currentSoc    = getSafeVal(DP_ELECTRIC_LEVEL, 0);
                                            let minSocLimit   = getSafeVal(DP_MIN_SOC, 10);
                                            let maxSocLimit   = getSafeVal(DP_SOC_SET, 100);
                                        
                                            // 2. BATTERIE-ENTLADESCHUTZ
                                            if (socLimitState === 2 || (minCell <= batteryLockMinCellVoltage && packState !== 1)) {
                                                if (!batteryLock) {
                                                    batteryLock = true;
                                                    console.info('Zellschutz AKTIVIERT. Mindestspannung unterschritten oder SoC-Limit erreicht.');
                                                }
                                            }
                                        
                                            // Freigabe nach der Sperre (erst nach Beruhigungszeit)
                                            if (minCell >= batteryUnlockMinCellVoltage && batteryLock && batteryRecoveryPending === 0 && socLimitState !== 2) {
                                                batteryRecoveryPending = 1;
                                                batteryRecoveryStartSec = nowSec;
                                            }
                                        
                                            if (batteryRecoveryPending === 1 && socLimitState !== 2 && (nowSec - batteryRecoveryStartSec >= batteryRecoveryTimeSec)) {
                                                if (minCell < batteryUnlockMinCellVoltage) {
                                                    batteryRecoveryPending = 0; // Zellen wieder eingebrochen, Abbruch
                                                } else {
                                                    batteryLock = false;
                                                    batteryRecoveryPending = 0;
                                                    console.info('Zellschutz ERFOLGREICH DEAKTIVIERT. Batterien wieder stabil.');
                                                }
                                            }
                                        
                                            // true = Entladen ist gesperrt
                                            let protectionActive = (batteryLock || socLimitState === 2);
                                        
                                        	// 3. aktuellen Hausverbrauch (ohne Bat) berechnen
                                            // positiv = Verbrauch / Netzbezug, negativ = Überschuss
                                            let netPowerWithoutBattery = (gridMeterValue - getSafeVal(DP_GRID_INPUT_POWER)) + getSafeVal(DP_OUTPUT_HOME_POWER);
                                        
                                        	// 4. hoher Leistungsbedarf (criticalMode)
                                            // Darf NUR laufen, wenn KEIN Batterieschutz greift
                                            if (!protectionActive && gridMeterValue >= criticalGridPower && dplAktiv && (nowSec - lastSec_criticalMode > intervalSec_criticalMode)) {
                                                // criticalMode starten
                                                dplAktiv = false;
                                                let critLimit = Math.min(Math.ceil(Math.max(netPowerWithoutBattery, 0)), criticalOutputLimit);
                                        
                                                setState(DP_AUTO_IN_OUT_LIMIT, critLimit);
                                                console.info('Critical Mode AN: auto_in_out_Limit gesetzt auf: ' + critLimit + ' W');
                                        
                                                lastSec_criticalMode = nowSec;
                                                lastSec_outputLimit_auto_in_out_Limit = nowSec;
                                        
                                            } else if (!dplAktiv && (protectionActive || (nowSec - lastSec_criticalMode > intervalSec_criticalMode))) {
                                                // criticalMode läuft: verlängern oder beenden
                                                if (gridMeterValue >= criticalGridPower && !protectionActive) {
                                                    let critLimit = Math.min(Math.ceil(Math.max(netPowerWithoutBattery, 0)), criticalOutputLimit);
                                                    if (critLimit !== getSafeVal(DP_OUTPUT_LIMIT)) {
                                                        setState(DP_AUTO_IN_OUT_LIMIT, critLimit);
                                                        console.info('Critical Mode VERLÄNGERT: auto_in_out_Limit angepasst auf: ' + critLimit + ' W');
                                                    }
                                                    lastSec_criticalMode = nowSec;
                                                    lastSec_outputLimit_auto_in_out_Limit = nowSec;
                                                } else {
                                                    // Lastspitze vorbei ODER Batterieschutz, dann criticalMode beenden, normale Regelung wieder freigeben
                                                    lastSec_outputLimit_auto_in_out_Limit = 0;
                                                    lastSec_inputLimit_auto_in_out_Limit = 0;
                                                    dplAktiv = true;
                                                    lastSec_criticalMode = nowSec;
                                                    console.info('Critical Mode AUS: Zurück zur normalen Regelung.');
                                                }
                                            }
                                        
                                        	// 5. normale Regelung Nulleinspeisung & Laderegelung
                                            if (dplAktiv) {
                                                // Sicherstellen, dass Zendure im SmartMode (1) läuft
                                                if (getSafeVal(DP_SMART_MODE) === 0) {
                                                    setState(DP_SMART_MODE, 1);
                                                }
                                        
                                                let newLimit = 0;
                                                let currentAcMode = getSafeVal(DP_AC_MODE);
                                                let acModeTimeOk = (nowSec - lastSec_change_acModeAllowed >= intervalSec_change_acModeAllowed);
                                        
                                                // Aktuell gesetzten Wert ermitteln (Laden negativ, Entladen positiv)
                                                let currentActiveLimit = 0;
                                                if (getSafeVal(DP_INPUT_LIMIT) > 0) {
                                                    currentActiveLimit = getSafeVal(DP_INPUT_LIMIT) * -1;
                                                } else if (getSafeVal(DP_OUTPUT_LIMIT) > 0) {
                                                    currentActiveLimit = getSafeVal(DP_OUTPUT_LIMIT);
                                                }
                                        
                                                // Hysterese: bei kleiner Abweichung wird nichts geändert
                                                let difference = Math.abs(netPowerWithoutBattery - currentActiveLimit);
                                                let hysteresisHold = (difference < wattChangeThreshold);
                                                if (hysteresisHold) {
                                                    newLimit = currentActiveLimit;
                                                }
                                        
                                                // FALL A: Überschuss vorhanden, dann Ladeleistung berechnen (Wert wird negativ)
                                                if (!hysteresisHold && netPowerWithoutBattery <= -100 - safetyReserve) {
                                                    let safeSurplus = netPowerWithoutBattery + safetyReserve;
                                                    let steppedLimit = Math.ceil(safeSurplus / chargingStep) * chargingStep;
                                        
                                                    newLimit = steppedLimit > -100 ? 0 : steppedLimit;
                                        
                                                    if (newLimit <= (max_inputLimit * -1)) {
                                                        newLimit = max_inputLimit * -1;
                                                    }
                                                }
                                        
                                                // FALL D: NOTABSCHALTUNG bei Batterieschutz oder leerem Akku
                                                if ((protectionActive || currentSoc <= minSocLimit) && getSafeVal(DP_OUTPUT_LIMIT) > 0) {
                                                    setState(DP_AUTO_IN_OUT_LIMIT, 0);
                                                    console.info('auto_in_out_Limit 0: Entladen gesperrt (Batterieschutz aktiv / MinSoC erreicht)');
                                                    lastSec_outputLimit_auto_in_out_Limit = nowSec;
                                        
                                                // FALL B: Netzbezug vorhanden, dann aus Akku entladen (Wert ist positiv)
                                                } else if (!protectionActive && currentSoc > minSocLimit && netPowerWithoutBattery > 0 && !hysteresisHold && (nowSec - lastSec_outputLimit_auto_in_out_Limit >= intervalSec_outputLimit)) {
                                                    let targetDemand = Math.ceil(netPowerWithoutBattery);
                                        
                                                    if (targetDemand <= 30) {
                                                        newLimit = 30;
                                                    } else if (targetDemand <= 60) {
                                                        newLimit = 60;
                                                    } else if (targetDemand <= 90) {
                                                        newLimit = 90;
                                                    } else if (targetDemand <= 100) {
                                                        newLimit = 100;
                                                    } else {
                                                        newLimit = Math.ceil(targetDemand / dischargeStep) * dischargeStep;
                                                    }
                                        
                                                    if (newLimit >= max_outputLimit) {
                                                        newLimit = max_outputLimit;
                                                    }
                                        
                                                    // Senden... Relais-Schutz: Wechsel vom Lade- in Entlade-Modus nur nach der Sperrzeit
                                                    if (newLimit !== getSafeVal(DP_OUTPUT_LIMIT)) {
                                                        if (currentAcMode !== 1 || acModeTimeOk) {
                                                            setState(DP_AUTO_IN_OUT_LIMIT, newLimit);
                                                            console.info('Entladen: auto_in_out_Limit gesetzt auf ' + newLimit + ' W');
                                                            lastSec_outputLimit_auto_in_out_Limit = nowSec;
                                                        }
                                                    }
                                        
                                                // FALL C: Überschuss oder kein Verbrauch, dann Laden bzw. Entladen beenden
                                                } else if (netPowerWithoutBattery <= 0 && (nowSec - lastSec_inputLimit_auto_in_out_Limit >= intervalSec_inputLimit)) {
                                                    let currentPassMode = getSafeVal(DP_PASS);
                                        
                                                    // Ladegrenze erreicht oder Gerät im Pass-Modus -> nicht laden
                                                    let chargeBlocked = (socLimitState === 1 || currentSoc >= maxSocLimit || currentPassMode === 2);
                                                    if (chargeBlocked && newLimit < 0) {
                                                        newLimit = 0;
                                                    }
                                        
                                                    if (newLimit !== currentActiveLimit) {
                                                        // Relais-Schutz: Wechsel vom Entlade- ins Lade-Modus nur nach der Sperrzeit (Stoppen ist immer erlaubt)
                                                        if (newLimit === 0 || currentAcMode !== 2 || acModeTimeOk) {
                                                            setState(DP_AUTO_IN_OUT_LIMIT, newLimit);
                                                            console.info('auto_in_out_Limit gesetzt auf ' + newLimit + ' W (negativ = Laden, 0 = Aus)');
                                                            lastSec_inputLimit_auto_in_out_Limit = nowSec;
                                                            if (newLimit === 0) {
                                                                lastSec_outputLimit_auto_in_out_Limit = nowSec;
                                                            }
                                                        }
                                                    }
                                                }
                                            }
                                        });
                                        
                                        
                                        T
                                        T
                                        T-147
                                        schrieb am zuletzt editiert von
                                        #612

                                        @maxclaudi

                                        Danke Dir, probier ich die Tage aus.

                                        Hatte nach dem Firmware-Update auf 2.0.2 gestern den Fehlercode 33, der ja offensichtlich ein Bug zu sein scheint. Hab aber noch keine neue Firmware verfügbar bei mir.
                                        Das mit dem Absinken deutlich unter 10% soll auch mit der Firmware zu tun haben, hab ich gestern gelesen, aber 100% sicher ist das natürlich nicht. Evtl schreib ich mal den Support an, ob ich vorgezogen werden kann mit dem Update.
                                        Warte aktuell darauf, dass der Wechselrichter startet, aber die Sonne tut sich grad noch schwer...

                                        maxclaudiM 1 Antwort Letzte Antwort
                                        0
                                        • T T-147

                                          @maxclaudi

                                          Danke Dir, probier ich die Tage aus.

                                          Hatte nach dem Firmware-Update auf 2.0.2 gestern den Fehlercode 33, der ja offensichtlich ein Bug zu sein scheint. Hab aber noch keine neue Firmware verfügbar bei mir.
                                          Das mit dem Absinken deutlich unter 10% soll auch mit der Firmware zu tun haben, hab ich gestern gelesen, aber 100% sicher ist das natürlich nicht. Evtl schreib ich mal den Support an, ob ich vorgezogen werden kann mit dem Update.
                                          Warte aktuell darauf, dass der Wechselrichter startet, aber die Sonne tut sich grad noch schwer...

                                          maxclaudiM
                                          maxclaudiM
                                          maxclaudi
                                          schrieb am zuletzt editiert von maxclaudi
                                          #613

                                          @T-147
                                          Neben einem reinen Software-Bug können es auch kurzzeitige Spannungsschwankungen oder Kriechströme an den MC4-Steckern der PV-Module sein. Ich würde an deiner Stelle die Steckverbindungen mal kontrollieren – nicht dass sich dort Feuchtigkeit eingeschlichen hat oder Kontaktprobleme vorhanden sind.
                                          Sollte es sich beim Pro tatsächlich um einen Bug handeln: Nach meinen Analysen ist die V2.0.2 (bzw. die darin enthaltene ESP-Steuerungs-Firmware) – wenn auch vielleicht nicht absolut fehlerfrei – m. M. n. immer noch die stabilste Version.

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

                                          neue Zendure Geräte Cloudless: zURLtool V2.0

                                          1 Antwort Letzte Antwort
                                          0

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

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

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

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


                                          Support us

                                          ioBroker
                                          Community Adapters
                                          Donate

                                          458

                                          Online

                                          33.1k

                                          Benutzer

                                          84.2k

                                          Themen

                                          1.4m

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

                                          • Du hast noch kein Konto? Registrieren

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