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

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

Community Forum

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

NEWS

  • Neues YouTube-Video: Visualisierung im Devices-Adapter
    BluefoxB
    Bluefox
    14
    1
    2.3k

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

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

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

Geplant Angeheftet Gesperrt Verschoben JavaScript
334 Beiträge 19 Kommentatoren 29.1k Aufrufe 18 Beobachtet
  • Älteste zuerst
  • Neuste zuerst
  • Meiste Stimmen
Antworten
  • In einem neuen Thema antworten
Anmelden zum Antworten
Dieses Thema wurde gelöscht. Nur Nutzer mit entsprechenden Rechten können es sehen.
  • maxclaudiM maxclaudi

    NEU
    Zendure-Geräte Webserver: Steuerung über das zenSDK (HTTP)
    2026.05.02_00.39h für das ioBroker-Forum; Update 24.05.26 00:15h globale Typsicherung
    In memory of Daisy 02.05.24 – miss you.

    Viel Spaß!


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

    update 19.05.26 16:50h BatteryTypen

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

    1 Antwort Letzte Antwort
    0
    • maxclaudiM maxclaudi

      Zendure-Geräte Webserver: Steuerung über das zenSDK (HTTP)
      2026.05.02_00.39h; update 24.05.26; 00:15h globale Typsicherung für das ioBroker-Forum

      In memory of Daisy 02.05.24 – miss you.

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

      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

      Weitere Hinweise

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

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

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

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

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

      • control (inkl. automaticKonfig)

      • packData (unter Cxxxxx finden sich die jeweiligen Batteriedaten)

      • properties


      update 21.05.26 22:05h auto_in_out_Limit

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

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

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

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

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

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

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

      Verhalten bei Null (0 W):

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

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

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

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

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

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


      Update 24.05.26 00:15h globale Typsicherung

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

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

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

      update 21.05.26 22:05h auto_in_out_Limit

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


      Update 24.05.26 00:15h globale Typsicherung

      Viel Spaß!

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

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

        Moin allerseits,

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

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

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

        Besten Dank und weiter frohes Werkeln

        maxclaudiM 1 Antwort Letzte Antwort
        0
        • T T-147

          Moin allerseits,

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

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

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

          Besten Dank und weiter frohes Werkeln

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

          @T-147 sagte:

          Moin allerseits,

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

          Besten Dank und weiter frohes Werkeln

          Hallo und herzlich willkommen!

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

          T 1 Antwort Letzte Antwort
          0
          • maxclaudiM maxclaudi

            @T-147 sagte:

            Moin allerseits,

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

            Besten Dank und weiter frohes Werkeln

            Hallo und herzlich willkommen!

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            1 Antwort Letzte Antwort
            0
            • M Offline
              M Offline
              MP_Trixi
              schrieb am zuletzt editiert von MP_Trixi
              #295

              @maxclaudi
              Hi, ich bin jetzt auch stolzer Besitzer eines Solarflow 800 Pro 2 (auch wenn es bis dahin eine kleine Odysse war :)). Vorab habe ich mich natürlich informiert, welches Gerät mit ioBroker zusammen arbeiten kann und bin auf den Adapter gestossen (hier mal ein Danke dafür, tolle Arbeit die ihr alle leistet). Installiert ist die Version 4.0.6
              Nun möchte ich mein Gerät natürlich auch steuern (die lesenden Werte werden aktualisiert, ein paar bleiben auf (null) stehen, denke mal die gibt es für das Modell nicht). Bevor ich ein Skript bastle wollte ich die Werte mal von Hand anpassen. Plan wäre das über "control->setOutputLimit" zu machen. Wenn ich das aber versuche bekomme ich den Fehler:

              zendure-solarflow.0 2026-06-10 15:16:02.253 warn Operation mode (autoModel) is not set to '0', we can't set the output limit!
              

              nach Adapterstart war nur zendure-solarflow.0.xyz.abc.control.acMode auf "AC output mode (2)" gesetzt. ...autoModel stand auf (null). Wenn ich das auf Nothing (0) setzen will kommt:

              zendure-solarflow.0 2026-06-10 15:18:07.137 warn [setAutoModel] Can't set autoModel to a value other than 0 when using zenSDK!
              

              Es steht dann "Nothing (0)" als unbestätigter Wert drin.

              Setze ich umgekehrt in der Zendure App die Ausgangsleistung auf einen anderen Wert, kommt dieser im Adapter an (sowohl bei outpuLimit als auch bei control.setOutputLimit).

              Was mache ich falsch? Danke für die Hilfe.

              maxclaudiM 1 Antwort Letzte Antwort
              0
              • M MP_Trixi

                @maxclaudi
                Hi, ich bin jetzt auch stolzer Besitzer eines Solarflow 800 Pro 2 (auch wenn es bis dahin eine kleine Odysse war :)). Vorab habe ich mich natürlich informiert, welches Gerät mit ioBroker zusammen arbeiten kann und bin auf den Adapter gestossen (hier mal ein Danke dafür, tolle Arbeit die ihr alle leistet). Installiert ist die Version 4.0.6
                Nun möchte ich mein Gerät natürlich auch steuern (die lesenden Werte werden aktualisiert, ein paar bleiben auf (null) stehen, denke mal die gibt es für das Modell nicht). Bevor ich ein Skript bastle wollte ich die Werte mal von Hand anpassen. Plan wäre das über "control->setOutputLimit" zu machen. Wenn ich das aber versuche bekomme ich den Fehler:

                zendure-solarflow.0 2026-06-10 15:16:02.253 warn Operation mode (autoModel) is not set to '0', we can't set the output limit!
                

                nach Adapterstart war nur zendure-solarflow.0.xyz.abc.control.acMode auf "AC output mode (2)" gesetzt. ...autoModel stand auf (null). Wenn ich das auf Nothing (0) setzen will kommt:

                zendure-solarflow.0 2026-06-10 15:18:07.137 warn [setAutoModel] Can't set autoModel to a value other than 0 when using zenSDK!
                

                Es steht dann "Nothing (0)" als unbestätigter Wert drin.

                Setze ich umgekehrt in der Zendure App die Ausgangsleistung auf einen anderen Wert, kommt dieser im Adapter an (sowohl bei outpuLimit als auch bei control.setOutputLimit).

                Was mache ich falsch? Danke für die Hilfe.

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

                @mp_trixi

                Glückwunsch zum neuen Gerät.

                In diesem Thread geht es um eine vollständig adapterlose Steuerung der Zendure-Geräte.
                Die Regelung läuft hier direkt über den Webserver des Zendure-Gerätes mittels zenSDK – ganz ohne komplizierte Umwege.

                Wenn du diesen alternativen Weg ausprobieren möchtest, findest du die gesamte Anleitung und Beschreibung direkt hier:
                Zendure-Geräte Webserver: Steuerung über das zenSDK (HTTP)

                Edit/PS:
                Cloud und App funktionieren dabei ganz normal herkömmlich weiter, so wie von Zendure vorgesehen.


                Falls du jedoch lieber den klassischen solar-flow-Adapter weiternutzen möchtest und dazu Fragen oder Probleme hast, erstelle deine Anfrage bitte im entsprechenden Support-Thread des Adapters.

                Dort kann dir bei spezifischen Adapter-Fehlern besser geholfen werden:
                Test Adapter Zendure Solarflow

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

                1 Antwort Letzte Antwort
                0
                • M Offline
                  M Offline
                  MP_Trixi
                  schrieb am zuletzt editiert von MP_Trixi
                  #297

                  @maxclaudi
                  Ah, tut mir Leid - dachte ich wäre hier richtig. Danke für die schnelle Antwort. Das wie Skript muss ich mir mal bei Gelegenheit genauer anschauen.

                  maxclaudiM 1 Antwort Letzte Antwort
                  0
                  • M MP_Trixi

                    @maxclaudi
                    Ah, tut mir Leid - dachte ich wäre hier richtig. Danke für die schnelle Antwort. Das wie Skript muss ich mir mal bei Gelegenheit genauer anschauen.

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

                    @MP_Trixi
                    Alles gut, kein Thema und gern geschehen ;-)
                    Lass dir ruhig Zeit beim Anschauen – wenn du später den adapterlosen Weg ausprobieren möchtest und Fragen dazu hast, gerne.

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

                    maxclaudiM 1 Antwort Letzte Antwort
                    0
                    • maxclaudiM maxclaudi

                      @MP_Trixi
                      Alles gut, kein Thema und gern geschehen ;-)
                      Lass dir ruhig Zeit beim Anschauen – wenn du später den adapterlosen Weg ausprobieren möchtest und Fragen dazu hast, gerne.

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

                      12.06.2026 WICHTIGES UPDATE IN EIGENER SACHE

                      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.

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

                      1 Antwort Letzte Antwort
                      1
                      • mcm1957M Online
                        mcm1957M Online
                        mcm1957
                        schrieb am zuletzt editiert von
                        #300

                        „Erkenntnis aus einer KI ohne Quelle“

                        Bist du denn sicher dass das nicht eh stimmt? Hast du mit dem Author der betreffenden Veröffentlichung mal Kontakt aufgenommen?

                        Eventuell hat ja wirklich eine AI deine Arbeit "geklaut". So ganz unwahrscheinlich ist es nicht, dass auf ein passende Frage dein Code mehr oder weniger unverändert angeboten wird. Und die meisten AI Augaben beinhalten keine Quellen. Die zitierte Angabe „Erkenntnis aus einer KI ohne Quelle“ deutet für mich eigentlich nicht darauf hin, dass sich da wer persönlich deine Arbeit unter den Nagel reißen will.

                        Zusatzfrage: Hast du ein Copyright / Lizenz o.ä. direkt in deinem Script stehen? Den Querbezug zwischen diesem Topic und einem Skript ist ev. auch für eine AI nicht leicht herzustellen.

                        Entwicklung u Betreuung: envertech-pv, hoymiles-ms, ns-client, pid, snmp Adapter;
                        Support Repositoryverwaltung.

                        Wer 'nen Kaffee spendieren will: https://paypal.me

                        LESEN - gute Forenbeitrage

                        maxclaudiM 1 Antwort Letzte Antwort
                        1
                        • mcm1957M mcm1957

                          „Erkenntnis aus einer KI ohne Quelle“

                          Bist du denn sicher dass das nicht eh stimmt? Hast du mit dem Author der betreffenden Veröffentlichung mal Kontakt aufgenommen?

                          Eventuell hat ja wirklich eine AI deine Arbeit "geklaut". So ganz unwahrscheinlich ist es nicht, dass auf ein passende Frage dein Code mehr oder weniger unverändert angeboten wird. Und die meisten AI Augaben beinhalten keine Quellen. Die zitierte Angabe „Erkenntnis aus einer KI ohne Quelle“ deutet für mich eigentlich nicht darauf hin, dass sich da wer persönlich deine Arbeit unter den Nagel reißen will.

                          Zusatzfrage: Hast du ein Copyright / Lizenz o.ä. direkt in deinem Script stehen? Den Querbezug zwischen diesem Topic und einem Skript ist ev. auch für eine AI nicht leicht herzustellen.

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

                          @mcm1957 sagte:

                          „Erkenntnis aus einer KI ohne Quelle“

                          Bist du denn sicher dass das nicht eh stimmt? Hast du mit dem Author der betreffenden Veröffentlichung mal Kontakt aufgenommen?

                          Eventuell hat ja wirklich eine AI deine Arbeit "geklaut". So ganz unwahrscheinlich ist es nicht, dass auf ein passende Frage dein Code mehr oder weniger unverändert angeboten wird. Und die meisten AI Augaben beinhalten keine Quellen. Die zitierte Angabe „Erkenntnis aus einer KI ohne Quelle“ deutet für mich eigentlich nicht darauf hin, dass sich da wer persönlich deine Arbeit unter den Nagel reißen will.

                          Die betreffenden Personen wissen sehr genau, was sie tun.

                          Zusatzfrage: Hast du ein Copyright / Lizenz o.ä. direkt in deinem Script stehen? Den Querbezug zwischen diesem Topic und einem Skript ist ev. auch für eine AI nicht leicht herzustellen.

                          Zur Zusatzfrage:

                          Ja, dieser Hinweis steht von Anfang an gut sichtbar als Textblock sowohl in der Anleitung, im ersten Post als auch direkt im Quelltext des Skripts unter „Ein Wort in eigener Sache“:

                          • im Script
                          • im ersten Post

                          Es geht mir hierbei überhaupt nicht um irgendwelche weiteren Schritte oder endlose Foren-Diskussionen, sondern schlicht und einfach um den grundlegendsten Anstand unter Entwicklern innerhalb einer Open-Source-Community.

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

                          1 Antwort Letzte Antwort
                          0
                          • R Offline
                            R Offline
                            Rico Sander
                            schrieb am zuletzt editiert von
                            #302

                            @maxclaudi

                            Du schreibst:

                            Cloud und App funktionieren dabei ganz normal herkömmlich weiter, so wie von Zendure vorgesehen.

                            Verständnisproblem:
                            Das heißt, SF800 Pro benötigt weiterhin einen Internetzugang und kontaktiert etwas alle 3min das Zuhause?

                            Ich habe mal spaßenshalber den Internetzugang gesperrt, aber dann ist das Gerät offline (laut App) und Dein HTTP-Script wirft Fehler ("Bitte IP des Gerätes überprüfen").
                            Dies im Zusamnmenhang mit dem Hinweis von @nograx betreffs SSL bei neuer Firmware.

                            Ich interpretiere das so, dass das Gerät weiterhin die Daten in die Cloud schickt und die App diese dort abholt (oder halt andersherum). Also doch nicht ganz lokaler Betrieb?

                            Kannst Du mir bitte auf die Sprünge helfen?

                            Andere Frage:
                            Das Steuerungsscript (ehemals für den zendure-solarflow-Adapter) hat selbst ein Interval (Standard 5000ms). Vermutlich verlängert sich dadurch die Reaktionszeit im Zusammenhang mit dem HTTP-Script. Könnte ich im Steuerungsscript das Interval weit heruntersetzen?

                            BTW:
                            Darüber hinaus klappt das mit dem HTTP-Script hervorragend, allerdings wird ein kleiner, vermutlich unbedeutender Fehler/Warnung im Script signalisiert:

                             return `${d.toLocaleTimeString("de-DE", **timeOptions**)}, ${d.toLocaleDateString("de-DE", **dateOptions**)}`;
                            
                            No overload matches this call.
                              Overload 1 of 3, '(locales?: string | string[], options?: DateTimeFormatOptions): string', gave the following error.
                                Argument of type '{ timeZone: string; hour: string; minute: string; second: string; hour12: boolean; }' is not assignable to parameter of type 'DateTimeFormatOptions'.
                            

                            Muss ich da etwas ändern?

                            Danke für Nachhilfe.

                            Wenns nicht geht, wie man will
                            - muss mans tun, wie man kann.
                            maxclaudiM 1 Antwort Letzte Antwort
                            0
                            • R Rico Sander

                              @maxclaudi

                              Du schreibst:

                              Cloud und App funktionieren dabei ganz normal herkömmlich weiter, so wie von Zendure vorgesehen.

                              Verständnisproblem:
                              Das heißt, SF800 Pro benötigt weiterhin einen Internetzugang und kontaktiert etwas alle 3min das Zuhause?

                              Ich habe mal spaßenshalber den Internetzugang gesperrt, aber dann ist das Gerät offline (laut App) und Dein HTTP-Script wirft Fehler ("Bitte IP des Gerätes überprüfen").
                              Dies im Zusamnmenhang mit dem Hinweis von @nograx betreffs SSL bei neuer Firmware.

                              Ich interpretiere das so, dass das Gerät weiterhin die Daten in die Cloud schickt und die App diese dort abholt (oder halt andersherum). Also doch nicht ganz lokaler Betrieb?

                              Kannst Du mir bitte auf die Sprünge helfen?

                              Andere Frage:
                              Das Steuerungsscript (ehemals für den zendure-solarflow-Adapter) hat selbst ein Interval (Standard 5000ms). Vermutlich verlängert sich dadurch die Reaktionszeit im Zusammenhang mit dem HTTP-Script. Könnte ich im Steuerungsscript das Interval weit heruntersetzen?

                              BTW:
                              Darüber hinaus klappt das mit dem HTTP-Script hervorragend, allerdings wird ein kleiner, vermutlich unbedeutender Fehler/Warnung im Script signalisiert:

                               return `${d.toLocaleTimeString("de-DE", **timeOptions**)}, ${d.toLocaleDateString("de-DE", **dateOptions**)}`;
                              
                              No overload matches this call.
                                Overload 1 of 3, '(locales?: string | string[], options?: DateTimeFormatOptions): string', gave the following error.
                                  Argument of type '{ timeZone: string; hour: string; minute: string; second: string; hour12: boolean; }' is not assignable to parameter of type 'DateTimeFormatOptions'.
                              

                              Muss ich da etwas ändern?

                              Danke für Nachhilfe.

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

                              @Rico-Sander

                              hallo erst mal :-)

                              zum Thema Cloud / lokaler Betrieb:

                              Das HTTP-Script selbst benötigt keinen Internetzugang.
                              Die Kommunikation erfolgt direkt zwischen ioBroker und dem Zendure-Gerät über dessen lokale IP-Adresse.

                              Die App hingegen arbeitet weiterhin über die Zendure-Cloud.
                              Wenn Du dem Gerät den Internetzugang sperrst, erscheint es deshalb in der App als offline, obwohl es im lokalen Netzwerk möglicherweise weiterhin erreichbar ist.

                              Mein Script greift ausschließlich auf die lokale HTTP-Schnittstelle zu und nutzt die Cloud nicht.

                              Wenn bei gesperrtem Internetzugang zusätzlich die Meldung "Keine Verbindung möglich.
                              Zendure-Geräte IP prüfen!" erscheint, bedeutet das lediglich, dass das Script keine gültige Antwort mehr vom lokalen HTTP-Endpunkt erhalten hat.
                              Dass die Fehlermeldung kam, bedeutet, dass vier Verbindungsversuche in Folge fehlgeschlagen sind.
                              Das Script läuft weiter und sobald wieder eine Verbindung zustande kommt, verschwindet die Fehlermeldung.

                              Mein aktueller Kenntnisstand:

                              • Für die Aktivierung der lokalen API verlangt Zendure einmalig den HEMS-Schritt.
                              • Für den anschließenden Betrieb meines Scripts nutze ich kein HEMS.
                              • Bei mir läuft die lokale Steuerung seit langer Zeit problemlos ohne HEMS.
                              • Aufgrund der vielen Nutzer und der hohen Anzahl an Thread-Aufrufen gehe ich davon aus, dass die grundsätzliche Funktionsweise des Scripts korrekt ist.

                              Ob bei aktivem HEMS die lokale HTTP-Schnittstelle deaktiviert oder anderweitig eingeschränkt wird, kann ich Dir nicht sicher sagen.
                              Die bisherigen Rückmeldungen deuten jedoch darauf hin, dass HEMS und rein lokale Steuerung nicht immer problemlos zusammenarbeiten.
                              Bei mir ist HEMS deaktiviert.

                              Da mein Script ausschließlich lokal per HTTP kommuniziert, wäre das zunächst nicht zu erwarten.
                              Warum das Gerät in diesem Fall nicht mehr auf die lokale HTTP-Abfrage antwortet, kann ich aktuell nicht sicher beurteilen.
                              Es wurde allerdings schon mehrfach berichtet, dass einige neuere Geräte ohne Cloud-Verbindung nicht vollständig lokal nutzbar sind.
                              Ob das hier die Ursache ist, kann ich nicht beurteilen.
                              Hier ist Zendure mit der Firmware gefragt.

                              Zum Intervall des Steuerungsscripts:
                              Mein Script selbst arbeitet standardmäßig mit einem GET-Intervall von 5000 ms. Die aktuelle Gerätekonfiguration wird also alle 5 Sekunden abgefragt.
                              Zusätzlich werden Schreibvorgänge gesammelt und zeitnah übertragen.

                              Das Intervall von meinem Script kann grundsätzlich verkleinert werden, ich empfehle das aber nicht.
                              Die HTTP-Schnittstelle liefert keine Messwerte im Millisekundenbereich und die zusätzliche Last auf Gerät und Netzwerk steigt unnötig an.

                              Für Regelungen ist meist ohnehin das eigentliche Steuerungsscript der begrenzende Faktor.
                              Wenn dieses ebenfalls alle 5 Sekunden läuft, ergibt sich im Normalfall eine Reaktionszeit von wenigen Sekunden, was für Energiemanagement-Aufgaben üblicherweise ausreichend ist.
                              Ob Dein eigentliches Steuerungsscript 5 Sekunden benötigt, kann ich nicht beurteilen.
                              Beispiel: Mein Hauptscript zur Steuerung verwendet z. B. als Haupt-Trigger den Stromzählerwert.
                              Der Stromzählerwert wird ca alle 3 bis 5 sek. aktualisiert und damit getriggert.
                              Es hat sonst keine Verzögerungen.

                              Zur TypeScript-Warnung:

                              Du musst nichts ändern.
                              Die Meldung stammt vom TypeScript-Checker des Editors und hat keine Auswirkung auf die Funktion.
                              Falls Du die Warnung loswerden möchtest, kannst Du timeOptions und dateOptions explizit als Intl.DateTimeFormatOptions typisieren.
                              Für den Betrieb des Scripts ist das aber nicht erforderlich.

                              Der betreffende Code ist normales JavaScript und funktioniert korrekt.

                              Je nach Version des JavaScript-Adapters, Node.js oder der verwendeten TypeScript-Definitionen kann bei den Objekten timeOptions und dateOptions eine Typwarnung angezeigt werden.

                              Da das Script bei Dir korrekt läuft, musst Du daran nichts ändern. Es handelt sich lediglich um eine Editor-Warnung und nicht um einen Laufzeitfehler.

                              Ich würde zunächst prüfen:

                              • JavaScript-Adapter aktuell?
                              • Node.js aktuell?
                              • Verschwindet die Warnung nach einem Adapter-Update?

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

                              paul53P 1 Antwort Letzte Antwort
                              0
                              • R Offline
                                R Offline
                                Rico Sander
                                schrieb am zuletzt editiert von
                                #304

                                @maxclaudi

                                Guten Morgen @maxclaudi ,

                                und vielen Dank für die ausführliche Beantwortung meiner Fragen. Damit klärt sich einiges im BioRAM 😳 .

                                Mal von hinten beginnend:

                                Die Meldung stammt vom TypeScript-Checker des Editors und hat keine Auswirkung auf die Funktion.

                                Ah, ok. Und ja, Probleme damit gab es nicht. Mir ist es lediglich aufgefallen und ich war mir nicht sicher, ob das für Dich wichjtig sein könnte. - Erledigt.

                                Ob Dein eigentliches Steuerungsscript 5 Sekunden benötigt, kann ich nicht beurteilen.

                                Ja, natürlich, aber Dein Beispiel (Trigger ist Stromzähler) allein hilft mir schon weiter, weil es bei mir ähnlich läuft. Dann werde ich mal schauen, ob ich das Interval beim eigentlichen Steuerungsscript verkleinern oder ganz rausnehmen kann. Sonst wird mir das Ganze bei meiner bescheidenen Anlage zu träge.

                                Zum Internetzugang:

                                Die Kommunikation erfolgt direkt zwischen ioBroker und dem Zendure-Gerät über dessen lokale IP-Adresse.
                                Die App hingegen arbeitet weiterhin über die Zendure-Cloud.

                                Ok, dann habe ich das zumindest richtig verstanden - Danke.

                                Das Script läuft weiter und sobald wieder eine Verbindung zustande kommt, verschwindet die Fehlermeldung.

                                Das muss ich nochmal überprüfen. Mir ist so, als wenn das dann permanent im Log stand.
                                Da melde ich mich ggfl. noch einmal...

                                Mein aktueller Kenntnisstand:
                                Für die Aktivierung der lokalen API verlangt Zendure einmalig den HEMS-Schritt.
                                Für den anschließenden Betrieb meines Scripts nutze ich kein HEMS.
                                Bei mir läuft die lokale Steuerung seit langer Zeit problemlos ohne HEMS.

                                Ok, ich habe ja seit ich das Gerät habe, mehrfach HEMS ein- und ausgeschaltet, womit die lokale API eigentlich aktiv sein sollte. Vermutlich würde sonst Dein Script wohl auch nicht funktionieren.
                                Auch ich nutze kein HEMS - weder vorher mit dem zendure-Adapter, noch mit Deinem Script.
                                Und ja, die Steuerung läuft auch hier problemlos - besten Dank für Deine Arbeit.

                                Da ich mit Zendure in regem und angenehmen Austausch stehe, kam heute die Frage von dort, ob ich schon auf HEMS 2.0 aktualisiert habe. Natürlich erst einmal nicht.

                                So, nun an die Arbeit....
                                Bis dahin zunächst und nochmals Danke. Ich melde mich dann nochmal.

                                Wenns nicht geht, wie man will
                                - muss mans tun, wie man kann.
                                maxclaudiM 1 Antwort Letzte Antwort
                                0
                                • R Rico Sander

                                  @maxclaudi

                                  Guten Morgen @maxclaudi ,

                                  und vielen Dank für die ausführliche Beantwortung meiner Fragen. Damit klärt sich einiges im BioRAM 😳 .

                                  Mal von hinten beginnend:

                                  Die Meldung stammt vom TypeScript-Checker des Editors und hat keine Auswirkung auf die Funktion.

                                  Ah, ok. Und ja, Probleme damit gab es nicht. Mir ist es lediglich aufgefallen und ich war mir nicht sicher, ob das für Dich wichjtig sein könnte. - Erledigt.

                                  Ob Dein eigentliches Steuerungsscript 5 Sekunden benötigt, kann ich nicht beurteilen.

                                  Ja, natürlich, aber Dein Beispiel (Trigger ist Stromzähler) allein hilft mir schon weiter, weil es bei mir ähnlich läuft. Dann werde ich mal schauen, ob ich das Interval beim eigentlichen Steuerungsscript verkleinern oder ganz rausnehmen kann. Sonst wird mir das Ganze bei meiner bescheidenen Anlage zu träge.

                                  Zum Internetzugang:

                                  Die Kommunikation erfolgt direkt zwischen ioBroker und dem Zendure-Gerät über dessen lokale IP-Adresse.
                                  Die App hingegen arbeitet weiterhin über die Zendure-Cloud.

                                  Ok, dann habe ich das zumindest richtig verstanden - Danke.

                                  Das Script läuft weiter und sobald wieder eine Verbindung zustande kommt, verschwindet die Fehlermeldung.

                                  Das muss ich nochmal überprüfen. Mir ist so, als wenn das dann permanent im Log stand.
                                  Da melde ich mich ggfl. noch einmal...

                                  Mein aktueller Kenntnisstand:
                                  Für die Aktivierung der lokalen API verlangt Zendure einmalig den HEMS-Schritt.
                                  Für den anschließenden Betrieb meines Scripts nutze ich kein HEMS.
                                  Bei mir läuft die lokale Steuerung seit langer Zeit problemlos ohne HEMS.

                                  Ok, ich habe ja seit ich das Gerät habe, mehrfach HEMS ein- und ausgeschaltet, womit die lokale API eigentlich aktiv sein sollte. Vermutlich würde sonst Dein Script wohl auch nicht funktionieren.
                                  Auch ich nutze kein HEMS - weder vorher mit dem zendure-Adapter, noch mit Deinem Script.
                                  Und ja, die Steuerung läuft auch hier problemlos - besten Dank für Deine Arbeit.

                                  Da ich mit Zendure in regem und angenehmen Austausch stehe, kam heute die Frage von dort, ob ich schon auf HEMS 2.0 aktualisiert habe. Natürlich erst einmal nicht.

                                  So, nun an die Arbeit....
                                  Bis dahin zunächst und nochmals Danke. Ich melde mich dann nochmal.

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

                                  @Rico-Sander
                                  Thema Trigger:
                                  ich weiß nicht, ob es für Dich oder andere interessant ist und weiterhilft.
                                  Hier mal eine Info, wie das mit den Triggern realisiert werden kann.

                                  Bei einem Objekt mit:

                                  mehreren Zendure-Geräten

                                  • 1 Hauptgerät mit 4 PV-Modulen an den MC4-Eingängen und 3x AB2000S-Batterien
                                  • 1 weiteres Zendure-Gerät mit 2x AB2000X-Batterien, an dessen OffGrid-Steckdose zusätzlich 2 Hoymiles-Wechselrichter angeschlossen sind
                                  • mehreren weiteren PV-Modulen mit separaten Hoymiles-Wechselrichtern, die direkt ins Hausnetz einspeisen

                                  bestehen die Regelungen aus mehreren Scripts, die zusammenarbeiten.

                                  Das Haupt-Script mit DPL-Regelung für das Zendure-Hauptgerät verwendet als Haupt-Trigger den aktuellen Stromzählerwert.

                                  Der aktuelle Zählerwert wird aus einem Datenpunkt gelesen:

                                  trigger_easy_shelly

                                  Für diesen Datenpunkt gibt es ein separates Script.

                                  Der Zählerwert wird redundant über zwei Quellen erfasst:

                                  • IR-Lesekopf direkt am easyMeter-Zähler des Energieversorgers
                                  • Shelly Pro 3EM als Fallback

                                  Standardmäßig wird der Wert des IR-Lesekopfs verwendet.
                                  Sollte dieser innerhalb einer definierten Zeit keine Daten mehr liefern, wird automatisch auf den Wert des Shelly umgeschaltet.
                                  Gleichzeitig wird eine Nachricht auf das Smartphone gesendet.

                                  Sollten von beiden Quellen keine Daten mehr kommen, liegt entweder ein Stromausfall oder ein größeres Problem vor.

                                  Dadurch hat man einen sehr zuverlässigen Trigger auf den tatsächlichen Netzbezug bzw. die Einspeisung.


                                  Das zweite Zendure-Gerät dient hauptsächlich zur Versorgung einer nächtlichen Grundlast.

                                  Die Leistung wird dort nur in wenigen Stufen angepasst, abhängig von Netzbezug, verfügbarer Leistung der DPL-Regelung vom anderen Haupt-Script, SoC und Zellspannungen aller Batterien.

                                  Für dieses Script wird bewusst kein Trigger auf den Stromzählerwert verwendet.

                                  Der Trigger ist stattdessen der vom Gerät gelieferte Zendure-Timestamp.

                                  Der Hintergrund:

                                  Eine Änderung des Timestamps bedeutet:

                                  • Polling erfolgreich
                                  • Zendure erreichbar
                                  • aktuelle Daten empfangen
                                  • die empfangene Geräteantwort wurde vollständig verarbeitet und ausgewertet
                                  • die Auswertung erfolgt unmittelbar nach dem Empfang der Geräteantwort und nicht zeitversetzt über eine separate Intervall-Abfrage

                                  Das ist für die Regelung wichtiger als ein einzelner aktueller Zählerwert, da dieser ohnehin zusätzlich im Script abgefragt wird.

                                  Wird kein neuer Timestamp empfangen, wurde auch keine neue Geräteantwort erfolgreich verarbeitet.
                                  In diesem Fall wird sofort eine Benachrichtigung versendet.

                                  Einen solchen Ausfall habe ich testweise simuliert. Die Benachrichtigung wurde unmittelbar ausgelöst.
                                  Im laufenden Betrieb ist dieser Fall bisher noch nicht aufgetreten.

                                  Das Script aus diesem Thread zur Steuerung eines Zendure-Geräts pollt im festen Intervall und verarbeitet die JSON-Antwort.
                                  Anschließend werden nur die Werte aktualisiert, die sich tatsächlich geändert haben.
                                  Unveränderte Datenpunkte werden bewusst nicht neu geschrieben.

                                  Eine Änderung des Timestamps bedeutet daher nicht, dass sich Werte geändert haben, sondern dass eine neue Geräteantwort erfolgreich empfangen, verarbeitet und ausgewertet wurde.

                                  Dadurch ist sichergestellt, dass die Regelung auf Basis eines vollständig verarbeiteten Datensatzes arbeitet.
                                  Auch wenn einzelne Datenpunkte unverändert bleiben und deshalb nicht neu geschrieben werden, entsprechen sie weiterhin dem aktuellen Gerätezustand.

                                  Deshalb ist für mich hier ein Trigger auf den Datenpunkt timestamp ideal.

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

                                  1 Antwort Letzte Antwort
                                  0
                                  • R Offline
                                    R Offline
                                    Rico Sander
                                    schrieb am zuletzt editiert von
                                    #306

                                    Danke für die Einblicke in ein ziemlich ausgewachsenes System - auch wenn es für mich nicht zutrifft. Interessant sind solche Infos allemal.

                                    Wenns nicht geht, wie man will
                                    - muss mans tun, wie man kann.
                                    1 Antwort Letzte Antwort
                                    0
                                    • maxclaudiM maxclaudi

                                      @Rico-Sander

                                      hallo erst mal :-)

                                      zum Thema Cloud / lokaler Betrieb:

                                      Das HTTP-Script selbst benötigt keinen Internetzugang.
                                      Die Kommunikation erfolgt direkt zwischen ioBroker und dem Zendure-Gerät über dessen lokale IP-Adresse.

                                      Die App hingegen arbeitet weiterhin über die Zendure-Cloud.
                                      Wenn Du dem Gerät den Internetzugang sperrst, erscheint es deshalb in der App als offline, obwohl es im lokalen Netzwerk möglicherweise weiterhin erreichbar ist.

                                      Mein Script greift ausschließlich auf die lokale HTTP-Schnittstelle zu und nutzt die Cloud nicht.

                                      Wenn bei gesperrtem Internetzugang zusätzlich die Meldung "Keine Verbindung möglich.
                                      Zendure-Geräte IP prüfen!" erscheint, bedeutet das lediglich, dass das Script keine gültige Antwort mehr vom lokalen HTTP-Endpunkt erhalten hat.
                                      Dass die Fehlermeldung kam, bedeutet, dass vier Verbindungsversuche in Folge fehlgeschlagen sind.
                                      Das Script läuft weiter und sobald wieder eine Verbindung zustande kommt, verschwindet die Fehlermeldung.

                                      Mein aktueller Kenntnisstand:

                                      • Für die Aktivierung der lokalen API verlangt Zendure einmalig den HEMS-Schritt.
                                      • Für den anschließenden Betrieb meines Scripts nutze ich kein HEMS.
                                      • Bei mir läuft die lokale Steuerung seit langer Zeit problemlos ohne HEMS.
                                      • Aufgrund der vielen Nutzer und der hohen Anzahl an Thread-Aufrufen gehe ich davon aus, dass die grundsätzliche Funktionsweise des Scripts korrekt ist.

                                      Ob bei aktivem HEMS die lokale HTTP-Schnittstelle deaktiviert oder anderweitig eingeschränkt wird, kann ich Dir nicht sicher sagen.
                                      Die bisherigen Rückmeldungen deuten jedoch darauf hin, dass HEMS und rein lokale Steuerung nicht immer problemlos zusammenarbeiten.
                                      Bei mir ist HEMS deaktiviert.

                                      Da mein Script ausschließlich lokal per HTTP kommuniziert, wäre das zunächst nicht zu erwarten.
                                      Warum das Gerät in diesem Fall nicht mehr auf die lokale HTTP-Abfrage antwortet, kann ich aktuell nicht sicher beurteilen.
                                      Es wurde allerdings schon mehrfach berichtet, dass einige neuere Geräte ohne Cloud-Verbindung nicht vollständig lokal nutzbar sind.
                                      Ob das hier die Ursache ist, kann ich nicht beurteilen.
                                      Hier ist Zendure mit der Firmware gefragt.

                                      Zum Intervall des Steuerungsscripts:
                                      Mein Script selbst arbeitet standardmäßig mit einem GET-Intervall von 5000 ms. Die aktuelle Gerätekonfiguration wird also alle 5 Sekunden abgefragt.
                                      Zusätzlich werden Schreibvorgänge gesammelt und zeitnah übertragen.

                                      Das Intervall von meinem Script kann grundsätzlich verkleinert werden, ich empfehle das aber nicht.
                                      Die HTTP-Schnittstelle liefert keine Messwerte im Millisekundenbereich und die zusätzliche Last auf Gerät und Netzwerk steigt unnötig an.

                                      Für Regelungen ist meist ohnehin das eigentliche Steuerungsscript der begrenzende Faktor.
                                      Wenn dieses ebenfalls alle 5 Sekunden läuft, ergibt sich im Normalfall eine Reaktionszeit von wenigen Sekunden, was für Energiemanagement-Aufgaben üblicherweise ausreichend ist.
                                      Ob Dein eigentliches Steuerungsscript 5 Sekunden benötigt, kann ich nicht beurteilen.
                                      Beispiel: Mein Hauptscript zur Steuerung verwendet z. B. als Haupt-Trigger den Stromzählerwert.
                                      Der Stromzählerwert wird ca alle 3 bis 5 sek. aktualisiert und damit getriggert.
                                      Es hat sonst keine Verzögerungen.

                                      Zur TypeScript-Warnung:

                                      Du musst nichts ändern.
                                      Die Meldung stammt vom TypeScript-Checker des Editors und hat keine Auswirkung auf die Funktion.
                                      Falls Du die Warnung loswerden möchtest, kannst Du timeOptions und dateOptions explizit als Intl.DateTimeFormatOptions typisieren.
                                      Für den Betrieb des Scripts ist das aber nicht erforderlich.

                                      Der betreffende Code ist normales JavaScript und funktioniert korrekt.

                                      Je nach Version des JavaScript-Adapters, Node.js oder der verwendeten TypeScript-Definitionen kann bei den Objekten timeOptions und dateOptions eine Typwarnung angezeigt werden.

                                      Da das Script bei Dir korrekt läuft, musst Du daran nichts ändern. Es handelt sich lediglich um eine Editor-Warnung und nicht um einen Laufzeitfehler.

                                      Ich würde zunächst prüfen:

                                      • JavaScript-Adapter aktuell?
                                      • Node.js aktuell?
                                      • Verschwindet die Warnung nach einem Adapter-Update?
                                      paul53P Offline
                                      paul53P Offline
                                      paul53
                                      schrieb am zuletzt editiert von paul53
                                      #307

                                      @maxclaudi [sagte]: Die aktuelle Gerätekonfiguration wird also alle 5 Sekunden abgefragt.

                                      Das habe ich beibehalten.
                                      Was gelegentlich passiert: httpGet() liefert offenbar selbst einen Error-Log:

                                      SF800_Error.JPG

                                      Kann man das nicht unterdrücken?

                                      Gerät ist ein "SF 800 Pro 2", das ich gestern in Betrieb genommen habe. Internetzugang ist im Router gesperrt.

                                      Bitte verzichtet auf Chat-Nachrichten, denn die Handhabung ist grauenhaft !
                                      Produktiv: Asus PN 42 / N100 / 8 GB / 500 GB

                                      maxclaudiM 2 Antworten Letzte Antwort
                                      0
                                      • R Offline
                                        R Offline
                                        Rico Sander
                                        schrieb am zuletzt editiert von
                                        #308

                                        Hallo @maxclaudi ,

                                        ich reiche nochmal die angekündigten Infos nach.

                                        SF800Pro in der Fritte Internet gesperrt, Zendure App zwangsbeendet und via pihole zwei weitere Ziele gesperrt (mqtteu.zen-iot.com, app.zendure.tech).

                                        Script läuft prinzipiell, wirft aber jede Menge Fehler aus (Auszug aus Log):

                                        2026-06-16 17:53:48.644 - error: javascript.0 (463079) script.js.common.Projekte.Zendure-HTTP: httpGet(url=http://192.168.178.56/properties/report, error=timeout of 2500ms exceeded)
                                        2026-06-16 17:53:48.644 - warn: javascript.0 (463079) script.js.common.Projekte.Zendure-HTTP: GET Fehler (3): timeout of 2500ms exceeded
                                        2026-06-16 17:53:49.196 - info: admin.0 (450218) ==> Connected system.user.admin from ::ffff:192.168.178.2
                                        2026-06-16 17:53:49.351 - info: javascript.0 (463079) script.js.common.Projekte.Zendure-HTTP: Verbindung wieder OK
                                        2026-06-16 17:53:55.176 - error: javascript.0 (463079) script.js.common.Projekte.Zendure-HTTP: httpGet(url=http://192.168.178.56/properties/report, error=socket hang up)
                                        2026-06-16 17:53:55.176 - warn: javascript.0 (463079) script.js.common.Projekte.Zendure-HTTP: GET Fehler (1): socket hang up
                                        2026-06-16 17:53:58.198 - error: javascript.0 (463079) script.js.common.Projekte.Zendure-HTTP: httpGet(url=http://192.168.178.56/properties/report, error=socket hang up)
                                        2026-06-16 17:53:58.199 - warn: javascript.0 (463079) script.js.common.Projekte.Zendure-HTTP: GET Fehler (2): socket hang up
                                        2026-06-16 17:54:01.216 - error: javascript.0 (463079) script.js.common.Projekte.Zendure-HTTP: httpGet(url=http://192.168.178.56/properties/report, error=socket hang up)
                                        2026-06-16 17:54:01.217 - warn: javascript.0 (463079) script.js.common.Projekte.Zendure-HTTP: GET Fehler (3): socket hang up
                                        2026-06-16 17:54:04.198 - error: javascript.0 (463079) script.js.common.Projekte.Zendure-HTTP: httpGet(url=http://192.168.178.56/properties/report, error=socket hang up)
                                        2026-06-16 17:54:04.198 - error: javascript.0 (463079) script.js.common.Projekte.Zendure-HTTP: Keine Verbindung möglich. Zendure-Geräte IP prüfen!
                                        2026-06-16 17:54:07.219 - error: javascript.0 (463079) script.js.common.Projekte.Zendure-HTTP: httpGet(url=http://192.168.178.56/properties/report, error=socket hang up)
                                        2026-06-16 17:54:10.200 - error: javascript.0 (463079) script.js.common.Projekte.Zendure-HTTP: httpGet(url=http://192.168.178.56/properties/report, error=socket hang up)
                                        2026-06-16 17:54:13.220 - error: javascript.0 (463079) script.js.common.Projekte.Zendure-HTTP: httpGet(url=http://192.168.178.56/properties/report, error=socket hang up)
                                        2026-06-16 17:54:16.204 - error: javascript.0 (463079) script.js.common.Projekte.Zendure-HTTP: httpGet(url=http://192.168.178.56/properties/report, error=socket hang up)
                                        2026-06-16 17:54:19.779 - error: javascript.0 (463079) script.js.common.Projekte.Zendure-HTTP: httpGet(url=http://192.168.178.56/properties/report, error=socket hang up)
                                        

                                        Die Freigabe der beiden Domains brachten keine Änderungen. Daran liegt es also nicht.

                                        Da es sich nicht zwingend um sensible Daten handelt,werde ich den Internetzugang wieder freigeben und die App aktivieren.

                                        Ob sich Handlungsbedarf ergibt kann ich nicht beurteilen. Insofern siehe das bitte nicht als Kritik am Script an, sondern lediglich als Rückmeldung.

                                        Wenns nicht geht, wie man will
                                        - muss mans tun, wie man kann.
                                        1 Antwort Letzte Antwort
                                        0
                                        • paul53P paul53

                                          @maxclaudi [sagte]: Die aktuelle Gerätekonfiguration wird also alle 5 Sekunden abgefragt.

                                          Das habe ich beibehalten.
                                          Was gelegentlich passiert: httpGet() liefert offenbar selbst einen Error-Log:

                                          SF800_Error.JPG

                                          Kann man das nicht unterdrücken?

                                          Gerät ist ein "SF 800 Pro 2", das ich gestern in Betrieb genommen habe. Internetzugang ist im Router gesperrt.

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

                                          @paul53 und @Rico-Sander

                                          Das Problem liegt nicht am Script, sondern an der Firmware
                                          Die Geräte unterstützen zwar die lokale Steuerung über zenSDK, aber die Firmware erwartet weiterhin regelmäßige Verbindungen zu Zendure-Cloud-Diensten (MQTT/HTTPS) zur Synchronisation und Geräteverwaltung.

                                          Verliert das Gerät die Cloud-Verbindung, dann versucht das Gerät immer häufiger sich mit der Cloud zu verbinden.
                                          Das passiert so oft und häufig bis der Zendure-ESP intern überlastet ist und eine Steuerung gar nicht mehr möglich sein kann.

                                          Wird der Internetzugang komplett gesperrt, versucht das Gerät die Cloud-Verbindungen immer wieder neu aufzubauen.
                                          Laut Aussage eines Zendure-Entwicklers erfolgt dies derzeit mit einem recht aggressiven Wiederholungsintervall.
                                          Dadurch werden CPU- und Netzwerkressourcen des Geräts überlastet, was dann zu Timeouts, "socket hang up" und nicht erreichbaren lokalen Schnittstellen führt.
                                          Bis am Ende keine Steuerung mehr möglich ist.

                                          Genau dieses Verhalten sieht man in euren Logs.

                                          Deshalb würde ich den Internetzugang für das Gerät aktuell (noch) nicht dauerhaft sperren.
                                          Die Steuerung erfolgt trotzdem lokal, die Cloud wird nur für die von der Firmware erwarteten Synchronisationsvorgänge genutzt.

                                          Zendure hat das Verhalten bereits im Februar 2026 bestätigt und angekündigt, an einer besseren Lösung für den Local-Only-Betrieb zu arbeiten.
                                          Aber bisher wurde das leider - Stand heute - meines Wissens nach nicht umgesetzt.


                                          Quelle Bestätigung des Zendure Entwicklers:
                                          Local only use creates multiple external network requests leading to overload of SolarFlow 800 Pro

                                          Zendure Entwickler dav1dBoy sagte:

                                          What you are observing is indeed related to the device’s current cloud interaction mechanism.
                                          At the moment, the firmware still relies on periodic communication with Zendure cloud services (e.g. MQTT / HTTPS endpoints) for status synchronization, device management, and consistency checks. When outgoing traffic is blocked by a firewall, the connection attempts fail immediately and the device enters a retry loop with a relatively aggressive retry interval.

                                          You are absolutely correct that retrying at such a high frequency (e.g. multiple attempts per second) is not ideal behavior, especially in constrained embedded environments. In this blocked-network scenario, the repeated connection attempts can temporarily consume CPU time and network resources, which may affect the responsiveness of local interfaces such as the Home Assistant integration.

                                          To be clear:
                                          • This behavior is not intended to overload the device, and
                                          • It does not indicate a fault in Home Assistant or your local setup.

                                          At the same time, we agree with your assessment:
                                          a more robust retry strategy (e.g. exponential backoff, longer retry intervals, or adaptive retry timing when the network is unavailable) would be healthier and more resilient.

                                          Regarding your expectation of fully local operation:
                                          we hear this feedback very clearly. We are actively working towards a more complete local-only runtime model, where core device control logic can operate independently of continuous cloud connectivity. This is an ongoing effort and requires careful changes across firmware architecture, but it is very much aligned with the direction we are moving in.

                                          For now, please note that current firmware versions still expect periodic interaction with Zendure servers, and completely blocking internet access may lead to degraded responsiveness as you observed.

                                          We really appreciate you taking the time to analyze this so thoroughly and to report it constructively. Feedback like this directly influences how we prioritize robustness improvements and local-control capabilities.

                                          Thank you for your patience — and thank you for pushing us to make the zenSDK better.


                                          @paul53 sagte:
                                          Was gelegentlich passiert: httpGet() liefert offenbar selbst einen Error-Log:

                                          SF800_Error.JPG

                                          Ja. Das ist aktuell bewusst so gewählt, damit Zustandsänderungen möglichst zeitnah erkannt werden.
                                          Die HTTP-Abfrage selbst erzeugt normalerweise keine Probleme.
                                          Die von euch gezeigten Fehler entstehen erst dann, wenn das Gerät durch die fehlende Cloud-Anbindung zeitweise nicht mehr sauber auf lokale HTTP-Anfragen reagiert.

                                          @paul53 sagte:
                                          Kann man das nicht unterdrücken?

                                          Ja, grundsätzlich schon.
                                          Die Log-Ausgaben stammen nicht von der Zendure-Firmware, sondern vom Script und dienen lediglich dazu, Verbindungsprobleme sichtbar zu machen.
                                          Wer die Ursache kennt und bewusst mit einem dauerhaft geblockten Internetzugang arbeitet, kann die entsprechenden log()-Ausgaben natürlich auskommentieren oder auf ein anderes Log-Level ändern.
                                          An Alle:
                                          Das beseitigt allerdings nur die Meldungen im Log.
                                          Die eigentliche Ursache – die aggressiven Cloud-Reconnect-Versuche der Firmware – bleibt unverändert bestehen.

                                          Allgemein würde ich noch den rssi Wert überprüfen.
                                          Wenn die WLAN-Verbindung zum Gerät nicht gut genug ist, kommt es eher bzw. häufiger zu timeouts.
                                          Sollte rssi gut genug sein (z. B. <= -60) und es dennoch zu GET - Fehler und Timeouts kommen, liegt es mit hoher Wahrscheinlicheit an dem gesperrten Internet.

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

                                          paul53P 1 Antwort Letzte Antwort
                                          2

                                          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

                                          594

                                          Online

                                          32.9k

                                          Benutzer

                                          83.2k

                                          Themen

                                          1.3m

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

                                          • Du hast noch kein Konto? Registrieren

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