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
    651

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

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

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

Geplant Angeheftet Gesperrt Verschoben JavaScript
688 Beiträge 24 Kommentatoren 117.9k 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.
  • T T-147

    @maxclaudi

    Das neue Java-Script lief gestern ohne erkennbare Probleme und hat die Batteriesperre sauber gesetzt und auch dann aufgehört einzuspeisen.

    Was mich nur grad ein wenig stört ist die Tatsache, dass das Einspeisen schon bei 24% SoC beendet wurde, weil die minVol von 3,16V erreicht wurde. So weit über dem MinSoC find ich schon viel. Macht jetzt im Herbst/Winter nicht so viel aus, aber im Sommer würde da schon einiges verschenkt.
    Ich weiß, SoC ist (deutlich) ungenauer, aber so viel Unterschied ist schon krass.

    Aber das ist ja kein Problem Deiner Skripte, sondern ein generelles Elektronik-/Akku-Thema.

    Das mit dem CriticalOutputLimit muss ich über das Wochenende nochmal beobachten. Heute Nachmittag kommt das Kind von der Klassenfahrt zurück, dann steigt der Stromverbauch wieder deutlich 😁

    maxclaudiM
    maxclaudiM
    maxclaudi
    schrieb am zuletzt editiert von
    #630

    @T-147 sagte:
    Das neue Java-Script lief gestern ohne erkennbare Probleme und hat die Batteriesperre sauber gesetzt und auch dann aufgehört einzuspeisen.

    Was mich nur grad ein wenig stört ist die Tatsache, dass das Einspeisen schon bei 24% SoC beendet wurde, weil die minVol von 3,16V erreicht wurde. So weit über dem MinSoC find ich schon viel.

    Schön zu hören, dass das Skript an sich erst einmal sauber läuft!

    Zu dem frühen Stopp bei 24% SoC: Das ist bei LFP-Zellen (wie den 15S-Akkus von Zendure) unter Last völlig normal.

    Die LFP-Spannungskurve ist extrem flach, und unter aktiver Entladung bricht die Spannung durch den Innenwiderstand der Zellen künstlich ein (sogenannter Voltage Sag).
    Eine Zelle, die laut BMS-Schätzung noch bei 24% steht, kann unter Last also kurzfristig unter die 3,16 V rutschen.
    Sobald die Last weg ist, erholt sich die Spannung wieder.
    Zudem neigt der angezeigte SoC zum "Driften", wenn länger nicht voll- und tiefer entladen wurde.

    Als reiner Erfahrungswert (ausdrücklich KEINE allgemeine Empfehlung!):
    Meine Batterien stehen im unbeheizten Innenbereich (nie unter 10°C).
    Bei meinem Setup fahre ich im Normalbetrieb hiermit sehr gut:

    let batteryLockMinCellVoltage   = 3.14; // Entladesperre normal (entspricht unter Last ca. 8-12% realem Rest-SoC)
    
    

    Wenn es draußen richtig kalt wird (0°C oder Minusgrade), passe ich es an, da Kälte den Spannungsabfall unter Last noch verstärkt:

    let batteryLockMinCellVoltage   = 3.16; // Entladesperre bei Kälte
    
    

    Allgemein empfehlen möchte ich das aber nicht, da jedes Setup anders reagiert. Bei mir hat das System dieses Jahr bisher nur zweimal komplett abgeschaltet. Die normalen Ladevorgänge finden meistens statt, bevor es so weit runtergeht.

    Um die SoC-Berechnung des BMS wieder zu kalibrieren, lasse ich die Akkus 1x monatlich komplett auf 100% aufladen – und zusätzlich immer dann, wenn die minVol doch mal häufiger erreicht wurden bzw. erreicht werden sollten.

    Vielleicht hilft Dir das ein wenig weiter.

    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:
      Das neue Java-Script lief gestern ohne erkennbare Probleme und hat die Batteriesperre sauber gesetzt und auch dann aufgehört einzuspeisen.

      Was mich nur grad ein wenig stört ist die Tatsache, dass das Einspeisen schon bei 24% SoC beendet wurde, weil die minVol von 3,16V erreicht wurde. So weit über dem MinSoC find ich schon viel.

      Schön zu hören, dass das Skript an sich erst einmal sauber läuft!

      Zu dem frühen Stopp bei 24% SoC: Das ist bei LFP-Zellen (wie den 15S-Akkus von Zendure) unter Last völlig normal.

      Die LFP-Spannungskurve ist extrem flach, und unter aktiver Entladung bricht die Spannung durch den Innenwiderstand der Zellen künstlich ein (sogenannter Voltage Sag).
      Eine Zelle, die laut BMS-Schätzung noch bei 24% steht, kann unter Last also kurzfristig unter die 3,16 V rutschen.
      Sobald die Last weg ist, erholt sich die Spannung wieder.
      Zudem neigt der angezeigte SoC zum "Driften", wenn länger nicht voll- und tiefer entladen wurde.

      Als reiner Erfahrungswert (ausdrücklich KEINE allgemeine Empfehlung!):
      Meine Batterien stehen im unbeheizten Innenbereich (nie unter 10°C).
      Bei meinem Setup fahre ich im Normalbetrieb hiermit sehr gut:

      let batteryLockMinCellVoltage   = 3.14; // Entladesperre normal (entspricht unter Last ca. 8-12% realem Rest-SoC)
      
      

      Wenn es draußen richtig kalt wird (0°C oder Minusgrade), passe ich es an, da Kälte den Spannungsabfall unter Last noch verstärkt:

      let batteryLockMinCellVoltage   = 3.16; // Entladesperre bei Kälte
      
      

      Allgemein empfehlen möchte ich das aber nicht, da jedes Setup anders reagiert. Bei mir hat das System dieses Jahr bisher nur zweimal komplett abgeschaltet. Die normalen Ladevorgänge finden meistens statt, bevor es so weit runtergeht.

      Um die SoC-Berechnung des BMS wieder zu kalibrieren, lasse ich die Akkus 1x monatlich komplett auf 100% aufladen – und zusätzlich immer dann, wenn die minVol doch mal häufiger erreicht wurden bzw. erreicht werden sollten.

      Vielleicht hilft Dir das ein wenig weiter.

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

      @maxclaudi sagte:
      Zu dem frühen Stopp bei 24% SoC: Das ist bei LFP-Zellen (wie den 15S-Akkus von Zendure) unter Last völlig normal.

      Die LFP-Spannungskurve ist extrem flach, und unter aktiver Entladung bricht die Spannung durch den Innenwiderstand der Zellen künstlich ein (sogenannter Voltage Sag).
      Eine Zelle, die laut BMS-Schätzung noch bei 24% steht, kann unter Last also kurzfristig unter die 3,16 V rutschen.
      Sobald die Last weg ist, erholt sich die Spannung wieder.
      Zudem neigt der angezeigte SoC zum "Driften", wenn länger nicht voll- und tiefer entladen wurde.

      Genau das meinte ich mit "Elektronik-/Akku-Thema" 😊

      Als reiner Erfahrungswert (ausdrücklich KEINE allgemeine Empfehlung!):
      Meine Batterien stehen im unbeheizten Innenbereich (nie unter 10°C).
      Bei meinem Setup fahre ich im Normalbetrieb hiermit sehr gut:

      let batteryLockMinCellVoltage   = 3.14; // Entladesperre normal (entspricht unter Last ca. 8-12% realem Rest-SoC)
      
      

      Wenn es draußen richtig kalt wird (0°C oder Minusgrade), passe ich es an, da Kälte den Spannungsabfall unter Last noch verstärkt:

      let batteryLockMinCellVoltage   = 3.16; // Entladesperre bei Kälte
      
      

      Ich habe hier ähnliche Bedingungen: ungeheizter Dachboden, auch im Winter nie unter 10° gewesen. Ich werde mich mal ganz langsam rantasten.

      Um die SoC-Berechnung des BMS wieder zu kalibrieren, lasse ich die Akkus 1x monatlich komplett auf 100% aufladen – und zusätzlich immer dann, wenn die minVol doch mal häufiger erreicht wurden bzw. erreicht werden sollten.

      Vielleicht hilft Dir das ein wenig weiter.

      Wie machst du die Kalibrierung? Schaltest du die DPL-Regelung einfach solange ab, machst du das über ein separates Script als CronJob oder sowas? Einfach manuell auf volle Last Aufladen stellen geht ja nicht, dann schaltet die Regelung das direkt wieder ab.
      Oder setzt Zendure beim Kalibrieren irgendwo ein Flag, was man für eine automatische Steuerung nutzen könnte, oder sowas?
      Hab gestern auf nem Screenshot der App was von Zwangskalibrierung gelesen und dass die Steuerung derzeit blockiert ist und nach der Kalibrierung wieder automatisch freigegeben wird.

      Und nur nochmal zur Info:
      Mein SF800 Pro 2 arbeitet mit der neuen FW 2.0.5 bisher ohne Auffälligkeiten, auch mit den ZenSDK- und DPL-Scripten.

      maxclaudiM 3 Antworten Letzte Antwort
      0
      • T T-147

        @maxclaudi sagte:
        Zu dem frühen Stopp bei 24% SoC: Das ist bei LFP-Zellen (wie den 15S-Akkus von Zendure) unter Last völlig normal.

        Die LFP-Spannungskurve ist extrem flach, und unter aktiver Entladung bricht die Spannung durch den Innenwiderstand der Zellen künstlich ein (sogenannter Voltage Sag).
        Eine Zelle, die laut BMS-Schätzung noch bei 24% steht, kann unter Last also kurzfristig unter die 3,16 V rutschen.
        Sobald die Last weg ist, erholt sich die Spannung wieder.
        Zudem neigt der angezeigte SoC zum "Driften", wenn länger nicht voll- und tiefer entladen wurde.

        Genau das meinte ich mit "Elektronik-/Akku-Thema" 😊

        Als reiner Erfahrungswert (ausdrücklich KEINE allgemeine Empfehlung!):
        Meine Batterien stehen im unbeheizten Innenbereich (nie unter 10°C).
        Bei meinem Setup fahre ich im Normalbetrieb hiermit sehr gut:

        let batteryLockMinCellVoltage   = 3.14; // Entladesperre normal (entspricht unter Last ca. 8-12% realem Rest-SoC)
        
        

        Wenn es draußen richtig kalt wird (0°C oder Minusgrade), passe ich es an, da Kälte den Spannungsabfall unter Last noch verstärkt:

        let batteryLockMinCellVoltage   = 3.16; // Entladesperre bei Kälte
        
        

        Ich habe hier ähnliche Bedingungen: ungeheizter Dachboden, auch im Winter nie unter 10° gewesen. Ich werde mich mal ganz langsam rantasten.

        Um die SoC-Berechnung des BMS wieder zu kalibrieren, lasse ich die Akkus 1x monatlich komplett auf 100% aufladen – und zusätzlich immer dann, wenn die minVol doch mal häufiger erreicht wurden bzw. erreicht werden sollten.

        Vielleicht hilft Dir das ein wenig weiter.

        Wie machst du die Kalibrierung? Schaltest du die DPL-Regelung einfach solange ab, machst du das über ein separates Script als CronJob oder sowas? Einfach manuell auf volle Last Aufladen stellen geht ja nicht, dann schaltet die Regelung das direkt wieder ab.
        Oder setzt Zendure beim Kalibrieren irgendwo ein Flag, was man für eine automatische Steuerung nutzen könnte, oder sowas?
        Hab gestern auf nem Screenshot der App was von Zwangskalibrierung gelesen und dass die Steuerung derzeit blockiert ist und nach der Kalibrierung wieder automatisch freigegeben wird.

        Und nur nochmal zur Info:
        Mein SF800 Pro 2 arbeitet mit der neuen FW 2.0.5 bisher ohne Auffälligkeiten, auch mit den ZenSDK- und DPL-Scripten.

        maxclaudiM
        maxclaudiM
        maxclaudi
        schrieb am zuletzt editiert von maxclaudi
        #632

        @T-147 sagte:

        @maxclaudi sagte:
        Zu dem frühen Stopp bei 24% SoC: Das ist bei LFP-Zellen (wie den 15S-Akkus von Zendure) unter Last völlig normal.

        Genau das meinte ich mit "Elektronik-/Akku-Thema" 😊

        Unter plötzlicher großer Last oder im criticalMode kann die Spannung auch nur kurzzeitig einbrechen.
        Um das sauber abzufangen, habe ich die Logik im Skript erweitert.

        Neu bzw. Änderungen zum bisherigen JS-Script Post #610:

        • Frühes Abschalten durch kurze Lastspitzen wird verhindert:
          Das Skript wurde um eine gefahrlose Pufferzeit erweitert.
          Wenn die schwächste Zelle die batteryLockMinCellVoltage (z. B. 3,14 V) erreicht, passiert erst einmal nichts.
          Erst wenn die Spannung nach Ablauf der Pufferzeit immer noch dauerhaft bei oder unter dem Grenzwert liegt, schaltet das Skript die Entladung ab.
          Das überbrückt das typische Taktungsverhalten von Großverbrauchern (wie dem Herd) perfekt.
          Da eine echte Tiefentladung bei LFP-Zellen erst etwas unter 2,50 V beginnt, besteht bei 3,14 V für wenige Minuten absolut kein Risiko für die Hardware.

        • Einfache und verständliche Umsetzung: Die Verzögerung wurde auch diesmal sehr einfach gelöst – komplett ohne komplizierte Schleifen, Timeouts oder Ähnliches.
          Es bleibt für Einsteiger leicht verständlich und ist im Code ausreichend kommentiert.

        • Keine neuen Parameter nötig:
          Die Logik nutzt einfach die bereits vorhandene Variable batteryRecoveryStartSec, um sich den Beginn des Spannungseinbruchs zu merken.
          Für die Dauer der Pufferzeit (z. B. 5 Minuten) wird auch der Wert aus der bestehenden Konfigurationsvariable batteryRecoveryTimeSec verwendet.

        Vielleicht ist das Skript in dieser Form noch hilfreicher für Dich bzw. Euch! ;-)

        // 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 16.10.2026_12:30h 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.
        // ======================================================================================
         
        //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), bitte kein "+", keine Sonderzeichen und keine Umlaute verwendnen!
        const folderZendureApi = '0_userdata.0.zendure.' + "2400ACproZENsdk";
         
        // Wie viele Batterien hast du? Mögliche Werte: 1, 2, 3, 4, 5 oder 6
        const vorhandeneBatAnz = 1;
         
        // 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";
         
         
        // 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.16; // 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/Verzögerungszeit der Zellen in Sekunden. Standard: 300 s (5 min)
        
        //---------------------------------------------------------------------------------------
        // AB HIER NICHTS VERÄNDERN!
        //---------------------------------------------------------------------------------------
         
        // Anzahl Batterien sichern (nur 1 bis 6 erlaubt)
        let batAnz = Math.round(vorhandeneBatAnz);
        if (!(batAnz >= 1)) batAnz = 1;
        if (batAnz > 6) batAnz = 6;
         
        // Basis-Pfade zu Zendure-Eigenschaften
        const BASE_PATH_PROPERTIES = folderZendureApi + '.properties.';
        const BASE_PATH_CONTROL    = folderZendureApi + '.control.';
        const BASE_PATH_PACKDATA   = folderZendureApi + '.packData.';
         
        // Zellspannungs-Datenpunkt einer Batterie zusetzen
        // (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;
         
        // NEU: Zeitstempel für Beginn des Spannungseinbruchs (0 = alles ok)
        let batteryVoltageDropStartSec = 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();
         
            // ZELLSPANNUNGEN UND GERÄTE-WERTE AUSLESEN
            // Es wird die niedrigste Zellspannung aller vorhandenen Batterien verwendet; Fallback 3.30V
            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);
        
            // BATTERIE-ENTLADESCHUTZ (Erweitert um verzögerten Zellschutz unter Last)
            if (socLimitState === 2) {
                // Sofortiger Stopp bei hartem SoC-Limit der App
                if (!batteryLock) {
                    batteryLock = true;
                    console.info('Zellschutz AKTIVIERT. SoC-Limit der App erreicht.');
                }
            } else if (minCell <= batteryLockMinCellVoltage && packState !== 1) {
                // Spannung unterschritten, aber Gerät lädt nicht aktiv
                if (batteryVoltageDropStartSec === 0) {
                    // Der Spannungseinbruch fängt gerade erst an -> Zeitpunkt merken
                    batteryVoltageDropStartSec = nowSec;
                    console.info('Mindestspannung unterschritten (' + minCell + ' V). Pufferzeit von ' + batteryRecoveryTimeSec + ' Sekunden läuft...');
                } else if (nowSec - batteryVoltageDropStartSec >= batteryRecoveryTimeSec) {
                    // Die Pufferzeit (z.B. 5 Min) ist abgelaufen und die Spannung ist immer noch zu niedrig
                    if (!batteryLock) {
                        batteryLock = true;
                        console.info('Zellschutz AKTIVIERT. Mindestspannung nach Ablauf der Pufferzeit dauerhaft unterschritten.');
                    }
                }
            } else {
                // Spannung ist wieder über dem Grenzwert -> Timer zurücksetzen
                batteryVoltageDropStartSec = 0;
            }
         
            // 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;
                    batteryVoltageDropStartSec = 0; // Komplett zurücksetzen
                    console.info('Zellschutz ERFOLGREICH DEAKTIVIERT. Batterien wieder stabil.');
                }
            }
         
            // true = Entladen ist gesperrt
            let protectionActive = (batteryLock || socLimitState === 2);
         
        	// aktuellen Hausverbrauch (ohne Bat) berechnen
            // positiv = Verbrauch / Netzbezug, negativ = Überschuss
            let netPowerWithoutBattery = (gridMeterValue - getSafeVal(DP_GRID_INPUT_POWER)) + getSafeVal(DP_OUTPUT_HOME_POWER);
         
        	// 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.');
                }
            }
         
        	// normale Regelung Nulleinspeisung & Laderegelung
            if (dplAktiv) {
                // Sichern, 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

        1 Antwort Letzte Antwort
        1
        • T T-147

          @maxclaudi sagte:
          Zu dem frühen Stopp bei 24% SoC: Das ist bei LFP-Zellen (wie den 15S-Akkus von Zendure) unter Last völlig normal.

          Die LFP-Spannungskurve ist extrem flach, und unter aktiver Entladung bricht die Spannung durch den Innenwiderstand der Zellen künstlich ein (sogenannter Voltage Sag).
          Eine Zelle, die laut BMS-Schätzung noch bei 24% steht, kann unter Last also kurzfristig unter die 3,16 V rutschen.
          Sobald die Last weg ist, erholt sich die Spannung wieder.
          Zudem neigt der angezeigte SoC zum "Driften", wenn länger nicht voll- und tiefer entladen wurde.

          Genau das meinte ich mit "Elektronik-/Akku-Thema" 😊

          Als reiner Erfahrungswert (ausdrücklich KEINE allgemeine Empfehlung!):
          Meine Batterien stehen im unbeheizten Innenbereich (nie unter 10°C).
          Bei meinem Setup fahre ich im Normalbetrieb hiermit sehr gut:

          let batteryLockMinCellVoltage   = 3.14; // Entladesperre normal (entspricht unter Last ca. 8-12% realem Rest-SoC)
          
          

          Wenn es draußen richtig kalt wird (0°C oder Minusgrade), passe ich es an, da Kälte den Spannungsabfall unter Last noch verstärkt:

          let batteryLockMinCellVoltage   = 3.16; // Entladesperre bei Kälte
          
          

          Ich habe hier ähnliche Bedingungen: ungeheizter Dachboden, auch im Winter nie unter 10° gewesen. Ich werde mich mal ganz langsam rantasten.

          Um die SoC-Berechnung des BMS wieder zu kalibrieren, lasse ich die Akkus 1x monatlich komplett auf 100% aufladen – und zusätzlich immer dann, wenn die minVol doch mal häufiger erreicht wurden bzw. erreicht werden sollten.

          Vielleicht hilft Dir das ein wenig weiter.

          Wie machst du die Kalibrierung? Schaltest du die DPL-Regelung einfach solange ab, machst du das über ein separates Script als CronJob oder sowas? Einfach manuell auf volle Last Aufladen stellen geht ja nicht, dann schaltet die Regelung das direkt wieder ab.
          Oder setzt Zendure beim Kalibrieren irgendwo ein Flag, was man für eine automatische Steuerung nutzen könnte, oder sowas?
          Hab gestern auf nem Screenshot der App was von Zwangskalibrierung gelesen und dass die Steuerung derzeit blockiert ist und nach der Kalibrierung wieder automatisch freigegeben wird.

          Und nur nochmal zur Info:
          Mein SF800 Pro 2 arbeitet mit der neuen FW 2.0.5 bisher ohne Auffälligkeiten, auch mit den ZenSDK- und DPL-Scripten.

          maxclaudiM
          maxclaudiM
          maxclaudi
          schrieb am zuletzt editiert von maxclaudi
          #633

          @T-147 sagte:
          Wie machst du die Kalibrierung? Schaltest du die DPL-Regelung einfach solange ab, machst du das über ein separates Script als CronJob oder sowas? Einfach manuell auf volle Last Aufladen stellen geht ja nicht, dann schaltet die Regelung das direkt wieder ab.

          Richtig: Wenn die normale Regelung aktiv ist, funkt sie natürlich dazwischen und regelt die AC-Ladeleistung sofort wieder weg.
          Das lässt sich im Skript elegant über einen eigenen Software-Schalter lösen.

          Ich mache das über eine einfache Erweiterung des Skripts durch einen zusätzlichen Boolean-Datenpunkt (z. B. 0_userdata.0...batteryCalibrationActive).

          Das Prinzip ist denkbar einfach:

          • Wenn true (Wert 1): Das Skript läuft im ganz normalen Alltagsmodus (Nulleinspeisung/CriticalMode)

          • Wenn false (Wert 0): Die komplette normale Regelung und der Entladeschutz werden vollständig deaktiviert. Stattdessen wird eine separate Ladefunktion aufgerufen.

          Wie läuft meine "sanfte" Kalibrierungs-Funktion ab?

          1. Sanftes Laden per AC: Die Batterie wird zu einer bestimmten Zeit (vorzugsweise nachts bei günstigem Strom oder geringer Netzlast) materialschonend aufgeladen.

          2. Drosselung ab 80 %: Sobald ein SoC von 80 % erreicht wird, drosselt die Funktion die AC-Ladeleistung automatisch herunter, um den Zellen im oberen Bereich Zeit zum Ausgleichen (Balancing) zu geben, und lädt sie so behutsam auf echte 100 % voll.

          3. Die wichtige Ruhephase: Ist die Batterie voll, schaltet das Laden ab und es folgt eine programmierte Ruhephase (bei mir von 20 Minuten).
            In dieser Zeit können sich die Zellchemie und die Spannungen setzen.

          4. Nachladen & Freigabe: Nach den 20 Minuten prüft die Funktion noch einmal die Spannungen.
            Ist eine Zelle wieder leicht abgesackt, wird kurz nachgeladen. Ist der Akku final stabil, setzt die Funktion den Boolean-Datenpunkt automatisch wieder auf true (1).
            Ab diesem Moment übernimmt sofort wieder die normale Nulleinspeisung.

          Hinweis: Ich triggere diesen Datenpunkt ganz bewusst nur manuell, wenn ich merke, dass es Zeit für die Kalibrierung ist oder der monatliche Turnus ansteht.
          Vollautomatisch möchte ich das nicht laufen lassen.
          Die Funktion sorgt dann selbstständig dafür, dass das Ganze sauber in der Nacht durchgezogen wird.
          Die zeitliche Steuerung (wann es nachts losgeht) kann sich dann ja jeder so anpassen, wie es zum eigenen Setup passt.

          Hab gestern auf nem Screenshot der App was von Zwangskalibrierung gelesen und dass die Steuerung derzeit blockiert ist und nach der Kalibrierung wieder automatisch freigegeben wird.

          Das würde doch dazu passen:

          Was mich nur grad ein wenig stört ist die Tatsache, dass das Einspeisen schon bei 24% SoC beendet wurde, weil die minVol von 3,16V erreicht wurde.

          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

            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: 21.06.26 00:10h axios
            update 22.06.26 20:00h zenSDK API 3, API 2 funktioniert weiterhin;
            update 25.06.26 10.30h control FAN

            In memory of Daisy 02.05.24 – miss you.

            Dieses Skript ermöglicht die lokale Steuerung eines Zendure-Geräts.
            Es funktioniert sofort und benötigt keinerlei Keys oder sonstige Authentifizierungen.
            edit: 09.10.2026 14:45h
            Es ist lokal i. V. mit APP und Cloud nutzbar.
            Oder auch nur lokal, wenn Firmware >= 2.x.x verwendet wird mit api3 und für Gerät(e) der Internetzugang gesperrt wird.
            Dann ist jedoch die Verwendung der App per WLAN nicht mehr möglich.
            Allerdings driftet dann die Zeit des Geräts weil sie nicht mehr "automatisch" synchronisiert wird.
            Näher möchte ich hier nicht darauf eingehen.

            Das nachfolgende edit vom 17.06.26 mit dem Hinweis ist weitherhin gültig für Geräte mit Firmware < 2.x.x:

            edit 17.06.26:
            Hinweis:
            Aus technischen Gründen sollte sowohl dem Gerät als auch der App weiterhin der Zugang ins Internet möglich sein.
            Das ist (noch) Firmware bedingt. Begründung
            Danke @Rico-Sander

            Voraussetzungen:

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

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

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

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

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

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


            fdb87689-7727-4c2d-9900-1dd942465061-image.jpeg

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

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

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

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

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

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

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

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

            4. batCalTime
              Beschreibung "set batCalTime" siehe weiter unten

            Weitere Hinweise

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

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

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

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

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

            • control (inkl. automaticKonfig)

            • packData (unter Cxxxxx finden sich die jeweiligen Batteriedaten)

            • properties


            update 21.05.26 22:05h auto_in_out_Limit

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

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

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

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

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

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

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

            Verhalten bei Null (0 W):

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

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

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

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

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

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


            Update 24.05.26 00:15h globale Typsicherung

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

            • Präzisere Datenpunkte:
              Beschreibungen mehrerer Datenpunkte verständlicher aktualisiert


            Update: 21.06.26 00:10h axios
            Danke paul53 für die Idee und das Mitwirken.


            Update 22.06.26 20:00h zenSDK API 3
            API 2 funktioniert weiterhin.


            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!


            set batCalTime Beschreibug

            Wenn die Firmware eine Kalibrierung des Systems für nötig erachtet, kann hier die gewünschte Uhrzeit gesetzt werden, wann die Kalibrierung beginnen soll.

            Standard (oft): 01:00 Uhr.

            Im Datenpunkt control.batCalTime muss die Syntax exakt im 24-Stunden-Format eingehalten werden – also mit führender Null vor 10 Uhr!
            type: "string" !
            Beispiel:

            batCalTime: 09:28
            
            • Syntaxfehler:
              Bei einer ungültigen Eingabe wird ein Log-Eintrag erzeugt und der Datenpunkt automatisch auf 01:00 zurückgesetzt.

            • Wert schon im Gerät:
              Steht im Gerät bereits derselbe Wert, wird nichts gesendet. Das gilt auch für den Fall „Fehler → 60“, wenn dort schon 60 steht. Der Control-Datenpunkt wird in diesem Fall nur bestätigt (ack: true).


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

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

            maxclaudiM
            maxclaudiM
            maxclaudi
            schrieb am zuletzt editiert von maxclaudi
            #634

            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.

            Alternativ gesamtes Verzeichnis mit allen Datenpunkten (inkl. dp properties etc.) löschen, für zusätzliche Beschreibungen.

            Zum aktuellen Skript: HIER KLICKEN

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

            neue Zendure Geräte Cloudless: zURLtool V2.0

            maxclaudiM 1 Antwort Letzte Antwort
            0
            • T T-147

              @maxclaudi sagte:
              Zu dem frühen Stopp bei 24% SoC: Das ist bei LFP-Zellen (wie den 15S-Akkus von Zendure) unter Last völlig normal.

              Die LFP-Spannungskurve ist extrem flach, und unter aktiver Entladung bricht die Spannung durch den Innenwiderstand der Zellen künstlich ein (sogenannter Voltage Sag).
              Eine Zelle, die laut BMS-Schätzung noch bei 24% steht, kann unter Last also kurzfristig unter die 3,16 V rutschen.
              Sobald die Last weg ist, erholt sich die Spannung wieder.
              Zudem neigt der angezeigte SoC zum "Driften", wenn länger nicht voll- und tiefer entladen wurde.

              Genau das meinte ich mit "Elektronik-/Akku-Thema" 😊

              Als reiner Erfahrungswert (ausdrücklich KEINE allgemeine Empfehlung!):
              Meine Batterien stehen im unbeheizten Innenbereich (nie unter 10°C).
              Bei meinem Setup fahre ich im Normalbetrieb hiermit sehr gut:

              let batteryLockMinCellVoltage   = 3.14; // Entladesperre normal (entspricht unter Last ca. 8-12% realem Rest-SoC)
              
              

              Wenn es draußen richtig kalt wird (0°C oder Minusgrade), passe ich es an, da Kälte den Spannungsabfall unter Last noch verstärkt:

              let batteryLockMinCellVoltage   = 3.16; // Entladesperre bei Kälte
              
              

              Ich habe hier ähnliche Bedingungen: ungeheizter Dachboden, auch im Winter nie unter 10° gewesen. Ich werde mich mal ganz langsam rantasten.

              Um die SoC-Berechnung des BMS wieder zu kalibrieren, lasse ich die Akkus 1x monatlich komplett auf 100% aufladen – und zusätzlich immer dann, wenn die minVol doch mal häufiger erreicht wurden bzw. erreicht werden sollten.

              Vielleicht hilft Dir das ein wenig weiter.

              Wie machst du die Kalibrierung? Schaltest du die DPL-Regelung einfach solange ab, machst du das über ein separates Script als CronJob oder sowas? Einfach manuell auf volle Last Aufladen stellen geht ja nicht, dann schaltet die Regelung das direkt wieder ab.
              Oder setzt Zendure beim Kalibrieren irgendwo ein Flag, was man für eine automatische Steuerung nutzen könnte, oder sowas?
              Hab gestern auf nem Screenshot der App was von Zwangskalibrierung gelesen und dass die Steuerung derzeit blockiert ist und nach der Kalibrierung wieder automatisch freigegeben wird.

              Und nur nochmal zur Info:
              Mein SF800 Pro 2 arbeitet mit der neuen FW 2.0.5 bisher ohne Auffälligkeiten, auch mit den ZenSDK- und DPL-Scripten.

              maxclaudiM
              maxclaudiM
              maxclaudi
              schrieb am zuletzt editiert von maxclaudi
              #635

              @T-147 sagte:

              Oder setzt Zendure beim Kalibrieren irgendwo ein Flag, was man für eine automatische Steuerung nutzen könnte, oder sowas?

              Zu deiner Frage mit den Flags von Zendure:
              Ja, die gibt es. Man kann dazu zwei spezifische Datenpunkte (Keys) verwenden. Ich habe das bisher allerdings noch nicht im Live-Betrieb getestet und weiß nicht, wie gut die Firmware das handhabt oder ob die Cloud auch eine Rolle spielt.

              1. Kalibrierungs-Flag (socStatus)
                Zendure bietet ein Info-Flag, das den aktuellen Zustand ausgibt:
              socStatus - SOC calibration Info, 0: No Calibrating / 1: Calibrating
              

              Wenn die Kalibrierung aktiv angelaufen ist, steht der Wert auf 1. Solange sie nur geplant ist und auf den Start wartet, bleibt der Status vermutlich auf 0.

              1. Die Startzeit festlegen (control.batCalTime)
                Wenn das System eine Kalibrierung als notwendig erachtet, kann man den Startzeitpunkt beeinflussen.
              batCalTime
              

              Du kannst die Startzeit im gewohnten Uhrzeitformat von 00:00 bis 23:59 eingeben. Mein Skript fängt Fehleingaben ab: Wenn ein ungültiger Wert eingegeben wird, setzt das Skript automatisch 01:00 Uhr als Fallback.
              Wichtig bei der Eingabe: Uhrzeiten vor 10:00 Uhr müssen mit einer führenden Null eingetragen werden, also zum Beispiel:

              04:00
              

              Erlaubte Werte: 00:00 bis 23:59

              Könnte man im Regelungsscript einbauen, habe ich aber bisher im zenSDK-Steuerungs-Script nicht getestet. Weder mit noch ohne Cloud.

              PS: Falls noch jemand Popcorn übrig hat: Ich hoffe, meine kleinen Monologe hier im Thread bieten zumindest guten Lesestoff für den Drink am Abend! ;-)

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

              neue Zendure Geräte Cloudless: zURLtool V2.0

              M 1 Antwort Letzte Antwort
              0
              • maxclaudiM maxclaudi

                @T-147 sagte:

                Oder setzt Zendure beim Kalibrieren irgendwo ein Flag, was man für eine automatische Steuerung nutzen könnte, oder sowas?

                Zu deiner Frage mit den Flags von Zendure:
                Ja, die gibt es. Man kann dazu zwei spezifische Datenpunkte (Keys) verwenden. Ich habe das bisher allerdings noch nicht im Live-Betrieb getestet und weiß nicht, wie gut die Firmware das handhabt oder ob die Cloud auch eine Rolle spielt.

                1. Kalibrierungs-Flag (socStatus)
                  Zendure bietet ein Info-Flag, das den aktuellen Zustand ausgibt:
                socStatus - SOC calibration Info, 0: No Calibrating / 1: Calibrating
                

                Wenn die Kalibrierung aktiv angelaufen ist, steht der Wert auf 1. Solange sie nur geplant ist und auf den Start wartet, bleibt der Status vermutlich auf 0.

                1. Die Startzeit festlegen (control.batCalTime)
                  Wenn das System eine Kalibrierung als notwendig erachtet, kann man den Startzeitpunkt beeinflussen.
                batCalTime
                

                Du kannst die Startzeit im gewohnten Uhrzeitformat von 00:00 bis 23:59 eingeben. Mein Skript fängt Fehleingaben ab: Wenn ein ungültiger Wert eingegeben wird, setzt das Skript automatisch 01:00 Uhr als Fallback.
                Wichtig bei der Eingabe: Uhrzeiten vor 10:00 Uhr müssen mit einer führenden Null eingetragen werden, also zum Beispiel:

                04:00
                

                Erlaubte Werte: 00:00 bis 23:59

                Könnte man im Regelungsscript einbauen, habe ich aber bisher im zenSDK-Steuerungs-Script nicht getestet. Weder mit noch ohne Cloud.

                PS: Falls noch jemand Popcorn übrig hat: Ich hoffe, meine kleinen Monologe hier im Thread bieten zumindest guten Lesestoff für den Drink am Abend! ;-)

                M
                M
                MP_Trixi
                schrieb am zuletzt editiert von MP_Trixi
                #636

                @maxclaudi
                Hi. Wie oben geschrieben habe ich sowohl die FW als auch das Script auf Stand gebracht. Dazu noch die Max AC Ladeleistung auf 1000W gesetzt. Zwei Dinge sind mir aufgefallen:
                wenn ich in 0_userdata.0.zendure.SolarFlow800Pro2.control.auto_in_out_Limit 0 schreibe war es so, dass nichts von AC geladen, nichts eingespeist aber von den Modulen die Batterien geladen wurden (glaube ich zumindest, kann auch sein dass es sich vorher so verhalten hat weil z.B. meine max. AC Ladeleistung auf 0 war). Jetzt sieht es komisch aus. In der App hat er „keinen Modus“, also weder Netzeingang noch Netzausgang. Die 0 scheint er aber zu registrieren aber faktisch arbeitet er mit dem letzten Modus und eingestellten Wert weiter.
                image.jpeg

                maxclaudiM 1 Antwort Letzte Antwort
                0
                • M MP_Trixi

                  @maxclaudi
                  Hi. Wie oben geschrieben habe ich sowohl die FW als auch das Script auf Stand gebracht. Dazu noch die Max AC Ladeleistung auf 1000W gesetzt. Zwei Dinge sind mir aufgefallen:
                  wenn ich in 0_userdata.0.zendure.SolarFlow800Pro2.control.auto_in_out_Limit 0 schreibe war es so, dass nichts von AC geladen, nichts eingespeist aber von den Modulen die Batterien geladen wurden (glaube ich zumindest, kann auch sein dass es sich vorher so verhalten hat weil z.B. meine max. AC Ladeleistung auf 0 war). Jetzt sieht es komisch aus. In der App hat er „keinen Modus“, also weder Netzeingang noch Netzausgang. Die 0 scheint er aber zu registrieren aber faktisch arbeitet er mit dem letzten Modus und eingestellten Wert weiter.
                  image.jpeg

                  maxclaudiM
                  maxclaudiM
                  maxclaudi
                  schrieb am zuletzt editiert von
                  #637

                  @MP_Trixi

                  Bitte stoppe das zenSDK-Steuerungsscript.
                  Lösche danach am besten den kompletten Pfad mit allen Datenpunkten.
                  Ersetze das bisherige zenSDK-Steuerungs-JS-Script mit der aktuellen Version und passe die Konfig an:

                  maxclaudi sagte:

                  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.

                  Alternativ gesamtes Verzeichnis mit allen Datenpunkten (inkl. dp properties etc.) löschen, für zusätzliche Beschreibungen.

                  Zum aktuellen Skript: HIER KLICKEN

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

                  neue Zendure Geräte Cloudless: zURLtool V2.0

                  M 1 Antwort Letzte Antwort
                  1
                  • maxclaudiM maxclaudi

                    @MP_Trixi

                    Bitte stoppe das zenSDK-Steuerungsscript.
                    Lösche danach am besten den kompletten Pfad mit allen Datenpunkten.
                    Ersetze das bisherige zenSDK-Steuerungs-JS-Script mit der aktuellen Version und passe die Konfig an:

                    maxclaudi sagte:

                    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.

                    Alternativ gesamtes Verzeichnis mit allen Datenpunkten (inkl. dp properties etc.) löschen, für zusätzliche Beschreibungen.

                    Zum aktuellen Skript: HIER KLICKEN

                    M
                    M
                    MP_Trixi
                    schrieb am zuletzt editiert von
                    #638

                    @maxclaudi
                    Ok. Ich habe einige aliase gesetzt. Sind die danach wieder da oder reicht es wie in deinen Post oben geschrieben nur control.socCompSwitch zu löschen?

                    maxclaudiM 1 Antwort Letzte Antwort
                    0
                    • M MP_Trixi

                      @maxclaudi
                      Ok. Ich habe einige aliase gesetzt. Sind die danach wieder da oder reicht es wie in deinen Post oben geschrieben nur control.socCompSwitch zu löschen?

                      maxclaudiM
                      maxclaudiM
                      maxclaudi
                      schrieb am zuletzt editiert von
                      #639

                      @MP_Trixi
                      Es reicht auch nur control.socCompSwitch zu löschen.

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

                      neue Zendure Geräte Cloudless: zURLtool V2.0

                      M 1 Antwort Letzte Antwort
                      0
                      • maxclaudiM maxclaudi

                        @MP_Trixi
                        Es reicht auch nur control.socCompSwitch zu löschen.

                        M
                        M
                        MP_Trixi
                        schrieb am zuletzt editiert von MP_Trixi
                        #640

                        @maxclaudi
                        danke für deinen Input - habe ich gemacht (altes Script gestoppt, control.socComSwitch gelöscht, neues Script gestartet). Ich werde testen

                        maxclaudiM 1 Antwort Letzte Antwort
                        0
                        • M MP_Trixi

                          @maxclaudi
                          danke für deinen Input - habe ich gemacht (altes Script gestoppt, control.socComSwitch gelöscht, neues Script gestartet). Ich werde testen

                          maxclaudiM
                          maxclaudiM
                          maxclaudi
                          schrieb am zuletzt editiert von maxclaudi
                          #641

                          @MP_Trixi
                          Kann es sein, dass im Moment keine oder zu wenig Leistung kommt?
                          Ist bei mir so.
                          Es gibt eigenlich unter properties nur:

                          properties.outputPackPower
                          properties.packInputPower
                          

                          Zur Kontrolle kannst Du das Script zur Nulleinspeisung stoppen.
                          Setze dann mal im dp:

                          control.auto_outputLimitMode
                          

                          100 oder -100

                          gib dem Befehl 10 sek Zeit.
                          Danach ändert sich auch der Wert in
                          properties.packInputPower
                          oder
                          properties.outputPackPower

                          Wenn das funktioniert ist zu wenig PV-Leistung vorhanden. Müsstest Du auch in properties.solarInputPower feststellen.

                          Für die PV-Leistung oder Sonne kann ich nichts ;-)

                          PS: Durch das Edit von Dir sieht mein Post nun super aus :-)

                          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:
                            Ich hab hier den SF800 Pro2, der fängt morgens schon ab 10W Gesamtleistung (über alle 4 Panels/MPPT) an zu laden. In meinem Fall und Setuo scheint sich die deutlich niedrigere Start-Spannung von Zendure im Vergleich zu meinem HMS-1000 auszuzahlen.

                            Meiner Meinung nach sitzt du da leider einem kleinen Mess- bzw. Anzeigefehler auf, was die realen physikalischen Abläufe angeht.

                            Die SolarFlow 800er-Pro-Serie hat die Steuerelektronik, die 4 MPPT-Regler und den Wechselrichter komplett in einem Gehäuse integriert.
                            Dieses Gesamtsystem (inklusive ESP32-Chip, WLAN, Bluetooth und Batterie-Management-System) hat einen permanenten, spürbaren Eigenverbrauch für den reinen Betrieb.

                            Wenn dir die App am Morgen anzeigt, dass mit "10 W" geladen wird, bedeutet das rein physikalisch folgendes:
                            Deine PV-Module müssen zu diesem Zeitpunkt bereits den kompletten internen Grundverbrauch des Geräts (für CPU, WLAN, BMS-Aktivierung etc.) gedeckt haben. Erst die Leistung, die darüber hinausgeht, bleibt netto übrig und wandert als die von dir gesehenen 10 W in die Batterie.

                            Man darf hier die reine Startspannung der MPPT-Tracker (die beim Pro 2 erfreulicherweise schon ab 14 V liegt) nicht mit der notwendigen Mindestleistung für den Systembetrieb verwechseln.
                            Ohne dass die PV-Leistung die interne Grundlast des Speichers vorab komplett kompensiert, kann das Gerät weder einspeisen noch laden.
                            Die Solarmodule müssen real also schon deutlich mehr als 10 W liefern, damit das System überhaupt aus dem Standby erwacht.

                            maxclaudiM
                            maxclaudiM
                            maxclaudi
                            schrieb am zuletzt editiert von
                            #642

                            maxclaudi sagte:

                            Die SolarFlow 800er-Pro-Serie hat die Steuerelektronik, die 4 MPPT-Regler und den Wechselrichter komplett in einem Gehäuse integriert.
                            Dieses Gesamtsystem (inklusive ESP32-Chip, WLAN, Bluetooth und Batterie-Management-System) hat einen permanenten, spürbaren Eigenverbrauch für den reinen Betrieb.

                            Wenn dir die App am Morgen anzeigt, dass mit "10 W" geladen wird, bedeutet das rein physikalisch folgendes:
                            Deine PV-Module müssen zu diesem Zeitpunkt bereits den kompletten internen Grundverbrauch des Geräts (für CPU, WLAN, BMS-Aktivierung etc.) gedeckt haben. Erst die Leistung, die darüber hinausgeht, bleibt netto übrig und wandert als die von dir gesehenen 10 W in die Batterie.

                            Man darf hier die reine Startspannung der MPPT-Tracker (die beim Pro 2 erfreulicherweise schon ab 14 V liegt) nicht mit der notwendigen Mindestleistung für den Systembetrieb verwechseln.
                            Ohne dass die PV-Leistung die interne Grundlast des Speichers vorab komplett kompensiert, kann das Gerät weder einspeisen noch laden.
                            Die Solarmodule müssen real also schon deutlich mehr als 10 W liefern, damit das System überhaupt aus dem Standby erwacht.

                            Heute ist bei mir so ein Tag - Regen - trüb.
                            2400ACpro werden PV-Leistung: 0 angezeigt obwohl 3 Module (420W, 200W, 200W) per DC MC4 angeschlossen sind.
                            Im Gegensatz Hoymiles:
                            Jedes einzelne Modul an einem Hoymiles speist mit 9 bis 23W ein.

                            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

                              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.

                              Alternativ gesamtes Verzeichnis mit allen Datenpunkten (inkl. dp properties etc.) löschen, für zusätzliche Beschreibungen.

                              Zum aktuellen Skript: HIER KLICKEN

                              maxclaudiM
                              maxclaudiM
                              maxclaudi
                              schrieb am zuletzt editiert von
                              #643

                              Update: Das zenSDK-Skript ist aktuell.
                              Wichtigste Änderung bleibt das auto_in_out_Limit (Danke @danitz!).
                              Da anscheinend noch ein paar ältere Versionen genutzt werden, obwohl es im letzten Skript schon drin war: Bitte updaten!

                              maxclaudi sagte:

                              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.

                              Alternativ gesamtes Verzeichnis mit allen Datenpunkten (inkl. dp properties etc.) löschen, für zusätzliche Beschreibungen.

                              Zum aktuellen Skript: HIER KLICKEN

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

                              neue Zendure Geräte Cloudless: zURLtool V2.0

                              1 Antwort Letzte Antwort
                              1
                              • maxclaudiM maxclaudi

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


                                In memory of Daisy 02.05.24 – miss you.


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

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

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

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

                                • Kein Hack: Nutzung der offiziellen zenSDK-Schnittstelle

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

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

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

                                Ein Wort in eigener Sache - Urheberrecht:


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


                                12.06.2026 WICHTIGES UPDATE IN EIGENER SACHE Urheberrecht

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

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

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

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

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

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

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


                                Zum aktuellen zenSDK-Steuerungs-Skript: HIER KLICKEN

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


                                update 26.10.2026; 18:19h

                                zURLtool_V20 (c) maxclaudi 2026 - Version 2.0 cdh

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

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

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

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

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

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

                                00.png

                                01-de.png

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

                                Beschreibung im Spoiler.


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

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


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

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

                                Technische Voraussetzungen & Basis:

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

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

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

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

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

                                Die Bibliothek wurde fest in das Programm eingebunden.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

                                Monatelange harte Arbeit sollte zumindest anerkannt werden.

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

                                Viel Spaß mit dem zURLtool V2.0 dch!


                                01-eng.png

                                02-eng.png

                                04-eng.png

                                05-eng.png


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

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

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


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

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

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



                                historisch: zURLtool V 1.0 dch

                                16.08.2026, NEU
                                zusätzliches Windows-Programm:

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

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

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

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

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

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

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

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

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

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

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

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

                                Zur Beschreibung und Download: zURLtool


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




                                HISTORISCH:

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

                                Hier nun die Lösung:

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


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

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

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

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

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

                                Intervalle

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

                                Eigene Daten anpassen
                                Zendure Gerät:

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

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

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

                                Intervalle für Abfragen (in Sekunden):

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

                                MQTT-Broker Konfiguration:

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

                                Maximale Input-/Output-Limits:

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

                                Hinweise

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

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

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

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

                                Viel Spaß! ☀


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

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

                                maxclaudiM
                                maxclaudiM
                                maxclaudi
                                schrieb am zuletzt editiert von maxclaudi
                                #644

                                20.10.2026; 15:18h Fortsetzung von:

                                15.10.2026; 16:00 Uhr: Weiterer Bericht zum Betrieb über MQTT-Port 1883 und zenSDK POST #628

                                Da es dazu weltweit quasi null Infos gibt, dachte ich, ich teile meine bisherigen Erkenntnisse mal auszugsweise als kleinen Bericht mit euch.

                                Die Fortsetzung ist vermutlich gleichzeitig der Abschluss.

                                Wer möchte, kann den Bericht im Spoiler lesen;
                                für alle anderen ist der Beitrag dann kürzer und kann ignoriert werden. :-)

                                Ich habe meine Analysen abgeschlossen und teile ein paar Ergebnisse mit euch.

                                TLS Verbindung Port: 8883
                                Das wichtigste Ergebnis vorab:
                                Der herkömmliche, vorgesehene "Normal-" Betrieb über TLS ist absolut stabil möglich.

                                Es gibt keine Verbindungsabbrüche und keine Reconnects mehr – und das gilt auch für den Mosquitto-Bridge-Betrieb, sofern die eigene lokale IP im Zendure-Gerät (mithilfe meines zURLtool) konfiguriert ist.
                                Alles läuft extrem schnell und stabil.
                                Für mich gibt es dadurch keinen Grund mehr, einen anderen Weg einzuschlagen - vor allem nicht mit zusätzlichen Optionen (mehr dazu folgt weiter unten).
                                Durch die Stabilität und Schnelligkeit bei der jetzigen, regulären Betriebsart (TLS-Port 8883) kann ich keinen Mehrwert mehr über eine alternative Verbindung feststellen.
                                Zuvor war der ESP anscheinend auch teils durch die ständige Verarbeitung der Disconnects und Reconnects überlastet.
                                Jetzt ist alles super.

                                Die ständigen Disconnects/Reconnects der 8883-TLS-Verbindungen - seltsamerweise vom Client zum mosquitto-Broker und vermehrt im Bridge-Betrieb - waren für mich ein Kernproblem.
                                Laut Log passierte das ja quasi ununterbrochen im Minutentakt, teilweise sogar mehrfach pro Minute, obwohl man es subjektiv nicht richtig bemerkte.
                                Dachte immer, dass der ESP32-C überlastet wäre - jedoch explizit durch was?
                                Nur weil TLS verwendet wird und mit 1883 gab es keine Probleme.
                                Konnte doch nicht sein...
                                Nach tagelanger Analyse habe ich des Rätsels Lösung gefunden.
                                Im Nachhinein betrachtet fast trivial – aber mein Reverse Engineering hat sich hier definitiv gelohnt.
                                Die zuvor beobachteten ständigen Disconnects/Reconnects und OpenSSL-Fehler, die den ESP unbemerkt auslasteten, sind damit behoben.

                                Bisher dachte ich, die kursierenden Informationen der Open-Source-Community zu Bluetooth, MQTT & Co. seien korrekt oder nur minimal fehlerbehaftet, weil sie sich an vielen Stellen fast eins zu eins gleichen.
                                Dem ist aber nicht so.
                                Scheinbar wurde hier oft nur voneinander abgeschrieben.


                                Wenn man weiß, wie es geht, eröffnen sich ein paar sehr interessante Optionen:

                                • HTTP-Server komplett abschalten (localAPIenable true/false):
                                  Damit lässt sich der HTTP-Port bzw. der interne Webserver gezielt ein- oder ausschalten.
                                  Wenn er deaktiviert ist, kann das zenSDK natürlich leider nicht genutzt werden.
                                  Den reglementierten, „offiziellen lokalen HA-MQTT“ möchte man aber sowieso eher nicht nutzen.
                                  Der Vorteil:
                                  Ist der Server aus, wird der ESP32-C zusätzlich entlastet.

                                • Freie SoC-Grenzen:
                                  Es ist möglich, minSoc (5–98 %) und socSet (6–100 %) völlig frei einzustellen.
                                  Theoretisch beide von 0–100 %, jedoch muss socSet immer größer sein als minSoc!
                                  Damit lassen sich beispielsweise der Bypass (pass:0 / pass:2) und die Wirkung von socLimit indirekt steuern.
                                  Wenn der Webserver eingeschaltet bleibt, funktioniert das auch für zenSDK.
                                  Außer es wird in einer künftigen Firmware wieder geändert.
                                  Ich hoffe sehr, dass der Hersteller nichts ändert!
                                  Die Probleme mit den Verschlimmbesserungen beim SoC-Thema sollte endlich ein Ende finden.
                                  Hier gibt es bisher überhaupt kein Problem.
                                  Hoffentlich hört das Reglementieren auf..
                                  Aktuell getestet mit einigen Geräten <= V2.0.2 und V2.0.4 beim SF2400ACpro.


                                Cloud- & Bridge-Modus:

                                Bisher läuft alles tadellos über den Mosquitto-Broker im Bridgemodus zur Cloud.
                                Das Ganze ist absolut flexibel:

                                • Bei Bedarf muss man nur eine einzige Zeile auskommentieren und ist innerhalb von Sekunden entweder komplett lokal oder wieder mit der Cloud verbunden.

                                • Das Gleiche gilt für den Bridge-Mode: Man kann sich mit der Cloud verbinden – wahlweise mit oder ohne Schreibrechte für die Cloud.

                                • Bei deaktiviertem HEMS hat die Cloud von sich aus (bis jetzt) ausschließlich die Uhrzeit aktualisiert. Gut so.


                                HEMS- & NVS-Verschleiß (Flash):

                                Ich nutze HEMS selbst zwar nicht, habe es aber nun genauer auf dem Schirm.
                                So wie es „für mich aktuell“ aussieht, werden bei der Nutzung von hemsEp sehr wohl manche Parameter direkt in den NVS geschrieben.
                                Zwar nicht permanent kontinuierlich, aber bestimmte Keys landen dort.
                                Das betrifft nicht nur den Hyper, sondern auch mindestens 1600ACplus und SF2400ACpro, vermutlich alle Geräte.
                                Wer glaubt, dass alles ausschließlich im RAM landet, soll das gerne glauben.

                                Wie geht es weiter?
                                Für meinen Teil muss ich nun noch die Dokumentationen vervollständigen und ordnen.
                                Werde noch einmal die Geräte – pro forma erneut, frisch – aufsetzen und im reinen, produktiven Testbetrieb beobachten und loggen.
                                Evtl. widme ich mich nun noch etwas dem Thema hemsEp NVS/RAM – hat aber keine Priorität für mich, weil ich es nicht nutzen möchte.
                                Weil wir gerade beim Thema NVS sind: smartMode: 1 lässt sich für mich schwerer nachverfolgen als hemsEp.
                                Manches kann ich bis jetzt nicht eindeutig nachvollziehen, deshalb äußere ich mich auch nicht dazu.

                                Das war es dann.

                                Wer sich bedanken möchte, weiß ja, wo der Voting-Button ist. Ein extra Post dafür muss nicht sein. ;-)

                                Wie bei einem langweiligen Film: Endlich Ende! :-)

                                edit 21.10.26; 10:35h
                                Keine Tester mehr gesucht.
                                Tester und Nuzer denen ich mit mosquitto broker half, bitte per PM oder email melden. Danke.

                                Testers and users whom I have assisted while they were working with the Mosquitto broker: please get in touch with me via pm or email. Thanks.

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

                                neue Zendure Geräte Cloudless: zURLtool V2.0

                                D maxclaudiM 2 Antworten Letzte Antwort
                                3
                                • maxclaudiM maxclaudi

                                  20.10.2026; 15:18h Fortsetzung von:

                                  15.10.2026; 16:00 Uhr: Weiterer Bericht zum Betrieb über MQTT-Port 1883 und zenSDK POST #628

                                  Da es dazu weltweit quasi null Infos gibt, dachte ich, ich teile meine bisherigen Erkenntnisse mal auszugsweise als kleinen Bericht mit euch.

                                  Die Fortsetzung ist vermutlich gleichzeitig der Abschluss.

                                  Wer möchte, kann den Bericht im Spoiler lesen;
                                  für alle anderen ist der Beitrag dann kürzer und kann ignoriert werden. :-)

                                  Ich habe meine Analysen abgeschlossen und teile ein paar Ergebnisse mit euch.

                                  TLS Verbindung Port: 8883
                                  Das wichtigste Ergebnis vorab:
                                  Der herkömmliche, vorgesehene "Normal-" Betrieb über TLS ist absolut stabil möglich.

                                  Es gibt keine Verbindungsabbrüche und keine Reconnects mehr – und das gilt auch für den Mosquitto-Bridge-Betrieb, sofern die eigene lokale IP im Zendure-Gerät (mithilfe meines zURLtool) konfiguriert ist.
                                  Alles läuft extrem schnell und stabil.
                                  Für mich gibt es dadurch keinen Grund mehr, einen anderen Weg einzuschlagen - vor allem nicht mit zusätzlichen Optionen (mehr dazu folgt weiter unten).
                                  Durch die Stabilität und Schnelligkeit bei der jetzigen, regulären Betriebsart (TLS-Port 8883) kann ich keinen Mehrwert mehr über eine alternative Verbindung feststellen.
                                  Zuvor war der ESP anscheinend auch teils durch die ständige Verarbeitung der Disconnects und Reconnects überlastet.
                                  Jetzt ist alles super.

                                  Die ständigen Disconnects/Reconnects der 8883-TLS-Verbindungen - seltsamerweise vom Client zum mosquitto-Broker und vermehrt im Bridge-Betrieb - waren für mich ein Kernproblem.
                                  Laut Log passierte das ja quasi ununterbrochen im Minutentakt, teilweise sogar mehrfach pro Minute, obwohl man es subjektiv nicht richtig bemerkte.
                                  Dachte immer, dass der ESP32-C überlastet wäre - jedoch explizit durch was?
                                  Nur weil TLS verwendet wird und mit 1883 gab es keine Probleme.
                                  Konnte doch nicht sein...
                                  Nach tagelanger Analyse habe ich des Rätsels Lösung gefunden.
                                  Im Nachhinein betrachtet fast trivial – aber mein Reverse Engineering hat sich hier definitiv gelohnt.
                                  Die zuvor beobachteten ständigen Disconnects/Reconnects und OpenSSL-Fehler, die den ESP unbemerkt auslasteten, sind damit behoben.

                                  Bisher dachte ich, die kursierenden Informationen der Open-Source-Community zu Bluetooth, MQTT & Co. seien korrekt oder nur minimal fehlerbehaftet, weil sie sich an vielen Stellen fast eins zu eins gleichen.
                                  Dem ist aber nicht so.
                                  Scheinbar wurde hier oft nur voneinander abgeschrieben.


                                  Wenn man weiß, wie es geht, eröffnen sich ein paar sehr interessante Optionen:

                                  • HTTP-Server komplett abschalten (localAPIenable true/false):
                                    Damit lässt sich der HTTP-Port bzw. der interne Webserver gezielt ein- oder ausschalten.
                                    Wenn er deaktiviert ist, kann das zenSDK natürlich leider nicht genutzt werden.
                                    Den reglementierten, „offiziellen lokalen HA-MQTT“ möchte man aber sowieso eher nicht nutzen.
                                    Der Vorteil:
                                    Ist der Server aus, wird der ESP32-C zusätzlich entlastet.

                                  • Freie SoC-Grenzen:
                                    Es ist möglich, minSoc (5–98 %) und socSet (6–100 %) völlig frei einzustellen.
                                    Theoretisch beide von 0–100 %, jedoch muss socSet immer größer sein als minSoc!
                                    Damit lassen sich beispielsweise der Bypass (pass:0 / pass:2) und die Wirkung von socLimit indirekt steuern.
                                    Wenn der Webserver eingeschaltet bleibt, funktioniert das auch für zenSDK.
                                    Außer es wird in einer künftigen Firmware wieder geändert.
                                    Ich hoffe sehr, dass der Hersteller nichts ändert!
                                    Die Probleme mit den Verschlimmbesserungen beim SoC-Thema sollte endlich ein Ende finden.
                                    Hier gibt es bisher überhaupt kein Problem.
                                    Hoffentlich hört das Reglementieren auf..
                                    Aktuell getestet mit einigen Geräten <= V2.0.2 und V2.0.4 beim SF2400ACpro.


                                  Cloud- & Bridge-Modus:

                                  Bisher läuft alles tadellos über den Mosquitto-Broker im Bridgemodus zur Cloud.
                                  Das Ganze ist absolut flexibel:

                                  • Bei Bedarf muss man nur eine einzige Zeile auskommentieren und ist innerhalb von Sekunden entweder komplett lokal oder wieder mit der Cloud verbunden.

                                  • Das Gleiche gilt für den Bridge-Mode: Man kann sich mit der Cloud verbinden – wahlweise mit oder ohne Schreibrechte für die Cloud.

                                  • Bei deaktiviertem HEMS hat die Cloud von sich aus (bis jetzt) ausschließlich die Uhrzeit aktualisiert. Gut so.


                                  HEMS- & NVS-Verschleiß (Flash):

                                  Ich nutze HEMS selbst zwar nicht, habe es aber nun genauer auf dem Schirm.
                                  So wie es „für mich aktuell“ aussieht, werden bei der Nutzung von hemsEp sehr wohl manche Parameter direkt in den NVS geschrieben.
                                  Zwar nicht permanent kontinuierlich, aber bestimmte Keys landen dort.
                                  Das betrifft nicht nur den Hyper, sondern auch mindestens 1600ACplus und SF2400ACpro, vermutlich alle Geräte.
                                  Wer glaubt, dass alles ausschließlich im RAM landet, soll das gerne glauben.

                                  Wie geht es weiter?
                                  Für meinen Teil muss ich nun noch die Dokumentationen vervollständigen und ordnen.
                                  Werde noch einmal die Geräte – pro forma erneut, frisch – aufsetzen und im reinen, produktiven Testbetrieb beobachten und loggen.
                                  Evtl. widme ich mich nun noch etwas dem Thema hemsEp NVS/RAM – hat aber keine Priorität für mich, weil ich es nicht nutzen möchte.
                                  Weil wir gerade beim Thema NVS sind: smartMode: 1 lässt sich für mich schwerer nachverfolgen als hemsEp.
                                  Manches kann ich bis jetzt nicht eindeutig nachvollziehen, deshalb äußere ich mich auch nicht dazu.

                                  Das war es dann.

                                  Wer sich bedanken möchte, weiß ja, wo der Voting-Button ist. Ein extra Post dafür muss nicht sein. ;-)

                                  Wie bei einem langweiligen Film: Endlich Ende! :-)

                                  edit 21.10.26; 10:35h
                                  Keine Tester mehr gesucht.
                                  Tester und Nuzer denen ich mit mosquitto broker half, bitte per PM oder email melden. Danke.

                                  Testers and users whom I have assisted while they were working with the Mosquitto broker: please get in touch with me via pm or email. Thanks.

                                  D
                                  D
                                  Daniel 8
                                  schrieb am zuletzt editiert von
                                  #645

                                  @maxclaudi sagte:

                                  TLS Verbindung Port: 8883
                                  Das wichtigste Ergebnis vorab:
                                  Der herkömmliche, vorgesehene "Normal-" Betrieb über TLS ist absolut stabil möglich.

                                  Kann man den Port über die app Option einfach so einstellen?

                                  Das heißt mit dem läuft alles schneller und über mqtt. Dann bräuchte man Zensdk gar Nicht mehr.

                                  Irgendwie übersteigt das ganze hier jetzt mein können.

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

                                  maxclaudiM 1 Antwort Letzte Antwort
                                  1
                                  • D Daniel 8

                                    @maxclaudi sagte:

                                    TLS Verbindung Port: 8883
                                    Das wichtigste Ergebnis vorab:
                                    Der herkömmliche, vorgesehene "Normal-" Betrieb über TLS ist absolut stabil möglich.

                                    Kann man den Port über die app Option einfach so einstellen?

                                    Das heißt mit dem läuft alles schneller und über mqtt. Dann bräuchte man Zensdk gar Nicht mehr.

                                    Irgendwie übersteigt das ganze hier jetzt mein können.

                                    maxclaudiM
                                    maxclaudiM
                                    maxclaudi
                                    schrieb am zuletzt editiert von
                                    #646

                                    @Daniel-8 sagte:

                                    @maxclaudi sagte:

                                    TLS Verbindung Port: 8883
                                    Das wichtigste Ergebnis vorab:
                                    Der herkömmliche, vorgesehene "Normal-" Betrieb über TLS ist absolut stabil möglich.

                                    Kann man den Port über die app Option einfach so einstellen?

                                    Um es verständlicher zu erklären: Es geht hier um zwei unterschiedliche MQTT-Verbindungen.

                                    Port 1883 ohne TLS:
                                    Man kann den Port 1883 ohne TLS am Gerät und in der App (gewusst wie) freischalten und aktivieren.
                                    Ab dann ist das Gerät über Port 1883 mit einer DNS-Adresse verbunden.
                                    Diese DNS-Adresse muss per AdGuard, Pi-hole oder über einen eigenen DNS-Server auf den lokalen Broker umgebogen werden, wenn man lokal über die Datenpunkte steuern möchte.
                                    Aber bei dieser Verbindung sind zu viele - ich nenne es mal - Möglichkeiten vorhanden und sollte m. M. n. nur kurzzeitig verwendet werden, näher gehe ich darauf nicht ein.


                                    Port 8883 mit TLS:
                                    Das ist der Standard.
                                    So verbindet sich jedes Gerät.
                                    Das Gerät ist über Port 8883 mit einer DNS-Adresse verbunden.
                                    Diese DNS-Adresse kann ebenfalls per AdGuard, Pi-hole oder DNS-Server auf die IP des lokalen Brokers umgebogen werden, wenn man lokal über die Datenpunkte steuern möchte.

                                    Alternativ:
                                    Man konfiguriert die Variable für DNS/IP auf dem Gerät mit dem zURLtool um auf den lokalen Broker.
                                    Auf dem lokalen Broker (z.B. Mosquitto) kann zusätzlich eine Bridge eingerichtet werden, damit das Gerät mit der Cloud verbunden bleibt.
                                    Es ist in wenigen Sekunden erledigt, diese Bridge wahlweise abzuschalten oder der Cloud nur Lesezugriffe oder vollen Zugriff zu erlauben.

                                    Mit einem lokalen Broker über MQTT ist die Steuerung zuverlässig, stabil und schneller als über die Cloud oder die lokale API (also auch zenSDK).

                                    zenSDK macht nach meinen Analysen und Tests nur einen kleinen Unterschied, solange kein erhöhter HTTP(S)-Traffic im jeweiligen IP-Bereich bzw. Netzwerk vorliegt.
                                    Wenn diese Voraussetzung gegeben ist und ein Abfrageintervall (Get) von etwa 4000 ms stabil möglich ist, kann auch das zenSDK genutzt werden.
                                    Es ist dann nur unwesentlich langsamer als MQTT.

                                    Der Grund dafür ist hardwarebedingt.
                                    Auch wenn MQTT durch den schnelleren, automatischen Bericht-Versand (Report Publish) vom Gerät und das schnellere Publizieren neuer Werte klar im Vorteil ist, benötigt das Gerät hardwarebedingt Zeit.
                                    Eine Limit-Umschaltung von inputLimit oder outputLimit braucht beispielsweise Zeit, um den Zielwert vollständig umzusetzen.

                                    Einfacher Vergleich:
                                    Du kannst bei einem Auto mit Automatikgetriebe und Turbolader zwar das Gaspedal oder die Bremse voll durchtreten, aber es dauert dennoch eine gewisse Zeit, bis die Höchstgeschwindigkeit oder der Stillstand erreicht sind.

                                    Alle Klarheiten beseitigt? ;-)


                                    Das heißt mit dem läuft alles schneller und über mqtt. Dann bräuchte man Zensdk gar Nicht mehr.

                                    Richtig.
                                    zenSDK ist, wie erklärt, unter den passenden Voraussetzungen langsamer als der lokale Broker über MQTT.
                                    Eigentlich ist MQTT vorzuziehen, da hier nicht auf neue Werte gepollt werden muss, sondern diese sofort automatisch publiziert werden.
                                    Zudem kann die lokale API abgeschaltet werden (gewusst wie) und der ESP32-C wird dadurch weiter entlastet.

                                    22.10.2026:
                                    ESP32-C, das ist übrigens die Standard-CPU, die verwendet wird und noch nirgendwo offiziell weltweit dokumentiert wurde.

                                    Irgendwie übersteigt das ganze hier jetzt mein können.

                                    Jetzt nicht mehr ;-)

                                    Hinweis:
                                    Ich überlege gerade ein MQTT-Steurungs-Script anzupassen, damit es als Ersatz für das zenSDK-Steuerungs-Script verwendet werden kann.
                                    Damit bleiben alle Datenpunkte identisch, wenn man es gleich konfiguriert wie das zenSDK-Steuerungs-Script.
                                    Bei identischer Benennung und Konfiguration, muss man darauf achten dass nur 1 Steuerungs-Script aktiv ist.

                                    Bei unterschiedlicher Benennung kann zenSDK und MQTT gleichzeitig, simultan genutzt werden oder auch nur eins von beiden als vorrangiges Steuerungsscripts.

                                    MQTT liefer natürlich einiges mehr an Infos.

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

                                    neue Zendure Geräte Cloudless: zURLtool V2.0

                                    P 1 Antwort Letzte Antwort
                                    2
                                    • D
                                      D
                                      Daniel 8
                                      schrieb am zuletzt editiert von
                                      #647

                                      Vielen Dank für die ausführlichen Antworten.

                                      Dann belasse ich es mal bei der Steuerung über zenSDK.

                                      Ich Kämpfe seit 3 Tagen mit einem Problem wo ich nicht weiß woher das kommt.
                                      Immer um 6.57 Uhr bringt das Script die Meldung Das ich meine IP Adresse überprüfen soll.

                                      Und heute hat irgendwie mein Steuerungsscript komplett verrückt gespielt und mal alles ins Netz eingespeist. Das lief bis jetzt ohne Probleme schon eine Weile. Wobei ich heute auch das Firmware Update auf 2
                                      0.5 gemacht habe.

                                      War leider arbeiten und konnte nicht schauen warum dies heute so passiert ist.

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

                                      maxclaudiM 1 Antwort Letzte Antwort
                                      0
                                      • D Daniel 8

                                        Vielen Dank für die ausführlichen Antworten.

                                        Dann belasse ich es mal bei der Steuerung über zenSDK.

                                        Ich Kämpfe seit 3 Tagen mit einem Problem wo ich nicht weiß woher das kommt.
                                        Immer um 6.57 Uhr bringt das Script die Meldung Das ich meine IP Adresse überprüfen soll.

                                        Und heute hat irgendwie mein Steuerungsscript komplett verrückt gespielt und mal alles ins Netz eingespeist. Das lief bis jetzt ohne Probleme schon eine Weile. Wobei ich heute auch das Firmware Update auf 2
                                        0.5 gemacht habe.

                                        War leider arbeiten und konnte nicht schauen warum dies heute so passiert ist.

                                        maxclaudiM
                                        maxclaudiM
                                        maxclaudi
                                        schrieb am zuletzt editiert von
                                        #648

                                        @Daniel-8 sagte:
                                        Immer um 6.57 Uhr bringt das Script die Meldung Das ich meine IP Adresse überprüfen soll.

                                        Wenn das täglich zur gleichen Uhrzeit vorkommt:
                                        Prüfe mal deinen WLAN-Router oder WLAN-AP, ob dort ein täglicher Neustart konfiguriert ist.

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

                                        neue Zendure Geräte Cloudless: zURLtool V2.0

                                        D 1 Antwort Letzte Antwort
                                        0
                                        • maxclaudiM maxclaudi

                                          @Daniel-8 sagte:
                                          Immer um 6.57 Uhr bringt das Script die Meldung Das ich meine IP Adresse überprüfen soll.

                                          Wenn das täglich zur gleichen Uhrzeit vorkommt:
                                          Prüfe mal deinen WLAN-Router oder WLAN-AP, ob dort ein täglicher Neustart konfiguriert ist.

                                          D
                                          D
                                          Daniel 8
                                          schrieb am zuletzt editiert von Daniel 8
                                          #649

                                          @maxclaudi sagte:

                                          @Daniel-8 sagte:
                                          Immer um 6.57 Uhr bringt das Script die Meldung Das ich meine IP Adresse überprüfen soll.

                                          Wenn das täglich zur gleichen Uhrzeit vorkommt:
                                          Prüfe mal deinen WLAN-Router oder WLAN-AP, ob dort ein täglicher Neustart konfiguriert ist.

                                          Ist nicht immer ganz genau zur gleichen Zeit. So ca. Plus minus 6 Minuten.

                                          Nein habe nichts programmiert. Ich werde mal beobachten. Jetzt ist ja zumindest der Speicher mal neu gestartet mit der neuen Firmware.
                                          Und so werde ich schritt für schritt testen. Habe ja auch nichts verändert. Deswegen verstehe ich es nicht warum es jetzt so ist.

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

                                          maxclaudiM 1 Antwort Letzte Antwort
                                          -1

                                          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

                                          219

                                          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