Für alle mit den neuen Geräten:
- SolarFlow 800
- SolarFlow 800 Pro
- SolarFlow 2400 AC
Immer wieder taucht die Frage auf z.B. (z. B. Michi 0 ), wie man den SmartMode:1 setzen kann.
Bisher wurde das noch nirgends wirklich anwenderfreundlich beantwortet.
Hier nun die Lösung:
Mit diesem Script können nicht nur smartMode:1 und die MQTT-Verbindung (de-)aktiviert werden, sondern auch einiges mehr.
Update 15.10.2025 21:21h– Neues Script
ioBroker JavaScript: Zendure SolarFlow 800 Pro (SF2400AC) via zenSDK HTTP
Kein Hack, dafür wurde die offizielle zenSDK verwendet.
plattformunabhängig - Linux, Windows
Jetzt können auch Commands gesetzt werden, wie in einem Adapter. 
Muss man nicht nutzen, kann man aber zusätzlich einsetzen – z. B. falls MQTT mal Probleme macht oder wenn weitere set-Befehle dazukommen sollen.
Hinweis:
Damit neue Datenpunkte automatisch aus dem JSON erstellt werden:
→ Instanzen → JavaScript-Adapter → Allgemeine Einstellungen → „Enable command setObject“ aktivieren!
So viel wie möglich wurde ohne Gerät getestet und simuliert.
Extra ohne Schleifen und strukturiert geschrieben, damit jeder das Script nachvollziehen kann.
Intervalle
- intervalGet sollte nicht < 30 s gesetzt werden – das bringt keinen Mehrwert.
- intervalMqtt = 300 s ist völlig ausreichend, um zu prüfen, ob MQTT verbunden ist.
Eigene Daten anpassen
Zendure Gerät:
const IP = "192.168.177.103"; // IP des Zendure Geräts
const SN = "EXXXXXXXXXXXXX0"; // Seriennummer
Intervalle für Abfragen (in Sekunden):
const intervalGet = 60; // SmartMode (default: 60 s, nicht < 30)
const intervalMqtt = 300; // MQTT-Status (default: 300 s, > SmartMode)
MQTT-Broker Konfiguration:
const mqttBrokerIp = "192.168.177.200"; // IP MQTT Broker (auch in App eingestellt)
const mqttPort = 1883; // Port MQTT Broker (auch in App eingestellt)
const mqttUsername = "Daisy"; // MQTT Username
const mqttPassword = "coco"; // MQTT Passwort
Maximale Input-/Output-Limits:
// Maximum Input-Limit
const maxInputLimit = 800; // SF800Pro
//const maxInputLimit = 2400; // SF2400AC
// Maximum Output-Limit
const maxOutputLimit = 800; // SF800Pro
//const maxOutputLimit = 2400; // SF2400AC
Hinweise
-
wichtig: wenn MQTT über dieses Script eingeschaltet wird, hat es vermutlich Vorrang!
Es werden ggf. andere MQTT-Einstellungen in der App durch das Script überschrieben.
-
Wer meine Arbeit nützlich findet und sich bedanken möchte:
Ein Klick auf den Pfeil nach oben (unten am Post, neben Zitieren) reicht völlig. Das ist für mich mehr als genug. 
-
Dieser erste Post wird bei Änderungen editiert, damit die Scripte immer direkt hier auffindbar bleiben.
Viel Spaß! 
//
// ioBroker JavaScript: Zendure SolarFlow 800 Pro (SF2400AC) via zenSDK HTTP
// by maxclaudi 2025.10.15 21:21h for ioBroker Forum
//------------------------------------------------------------------------------------
// konfiguration
//
// Damit neue Datenpunkte automatisch aus dem JSON erstellt werden:
// -> Instanzen -> javascript-Adapter -> Allgemeine Einstellungen -> Enable command"setObject“ aktivieren!
//
// Intervalle für Abfragen (Sekunden)
const intervalGet = 60; // sek SmartMode (default: 60 sek, not <30)
const intervalMqtt = 300; // sek MQTT-Status (default: 300 sek, > SmartMode)
// IP und Seriennummer Zendure Gerät
const IP = "192.168.40.111"; // IP des Zendure Geräts
const SN = "EXXXXXXXXXXXXX0"; // Seriennummer
// MQTT Broker Konfiguration
const mqttBrokerIp = "192.168.40.200"; // IP MQTT Broker und in App eingestellt
const mqttPort = 1883; // PORT MQTT Broker und in App eingestellt
const mqttUsername = "Daisy"; // MQTT Username
const mqttPassword = "coco"; // MQTT Passwort
//maximum inputLimit SF800PRO: 800W / SF2400AC: 2400W
const maxInputLimit = 1000; //SF800Pro
//const maxInputLimit = 2400; //SF2400AC
//maximum outputLimit SF800PRO: 800W / SF2400AC: 2400W
const maxOutputLimit = 800; //SF800Pro
//const maxOutputLimit = 2400; //SF2400AC
//------------------------------------------------------------------------------------
//------------------------------------------------------------------------------------
// Datenpunkte
const folderZendureApi = '0_userdata.0.zendureApi';
//------------------------------------------------------------------------------------
const dpSmartModeInfo = folderZendureApi + ".zendureSmartMode.smartModeInfo";
const dpSetSmartMode = folderZendureApi + ".zendureSmartMode.setSmartMode";
const dpSetSmartModeResult = folderZendureApi + ".zendureSmartMode.setResult";
const dpTimestamp = folderZendureApi + ".zendureSmartMode.timestamp";
const dpMqttConnectInfo = folderZendureApi + ".zendureMqttState.mqttConnectInfo";
const dpSetMqttConnect = folderZendureApi + ".zendureMqttState.setMqttConnect";
const dpSetMqttConnectResult = folderZendureApi + ".zendureMqttState.setMqttConnectResult";
const dpMqttTimestamp = folderZendureApi + ".zendureMqttState.mqttTimestamp";
//set dp
const dpSetAcMode = folderZendureApi + ".control.acMode.setAcMode";
const dpSetAcModeResult = folderZendureApi + ".control.acMode.setAcModeResult";
const dpSetAcModeResultTS = folderZendureApi + ".control.acMode.setAcModeResultTs";
const dpSetInputLimit = folderZendureApi + ".control.inputLimit.setInputLimit";
const dpSetInputLimitResult = folderZendureApi + ".control.inputLimit.setInputLimitResult";
const dpSetInputLimitResultTS = folderZendureApi + ".control.inputLimit.setInputLimitResultTs";
const dpSetOutputLimit = folderZendureApi + ".control.outputLimit.setOutputLimit";
const dpSetOutputLimitResult = folderZendureApi + ".control.outputLimit.setOutputLimitResult";
const dpSetOutputLimitResultTS = folderZendureApi + ".control.outputLimit.setOutputLimitResultTs";
const dpSetSocSet = folderZendureApi + ".control.socSet.setSocSet";
const dpSetSocSetResult = folderZendureApi + ".control.socSet.setSocSetResult";
const dpSetSocSetResultTS = folderZendureApi + ".control.socSet.setSocSetResultTs";
const dpSetMinSoc = folderZendureApi + ".control.minSoc.setMinSoc";
const dpSetMinSocResult = folderZendureApi + ".control.minSoc.setMinSocResult";
const dpSetMinSocResultTS = folderZendureApi + ".control.minSoc.setMinSocResultTs";
const dpSetGridReverse = folderZendureApi + ".control.gridReverse.setGridReverse";
const dpSetGridReverseResult = folderZendureApi + ".control.gridReverse.setGridReverseResult";
const dpSetGridReverseResultTS = folderZendureApi + ".control.gridReverse.setGridReverseResultTs";
const dpSetGridStandard = folderZendureApi + ".control.gridStandard.setGridStandard";
const dpSetGridStandardResult = folderZendureApi + ".control.gridStandard.setGridStandardResult";
const dpSetGridStandardResultTS = folderZendureApi + ".control.gridStandard.setGridStandardResultTs";
const dpSetInverseMaxPower = folderZendureApi + ".control.inverseMaxPower.setInverseMaxPower";
const dpSetInverseMaxPowerResult = folderZendureApi + ".control.inverseMaxPower.setInverseMaxPowerResult";
const dpSetInverseMaxPowerResultTS = folderZendureApi + ".control.inverseMaxPower.setInverseMaxPowerResultTs";
//------------------------------------------------------------------------------------
const http = require("http"); // Node.js http Standardmodul
//------------------------------------------------------------------------------------
// Mapping: Payload key, optional transformation, result and timestamp dp
const dpMap = {
[dpSetAcMode]: { key: "acMode", result: dpSetAcModeResult, resultTS: dpSetAcModeResultTS, minNormal: 1, maxNormal: 2 },
[dpSetInputLimit]: { key: "inputLimit", result: dpSetInputLimitResult, resultTS: dpSetInputLimitResultTS, minNormal: 0, maxNormal: maxInputLimit },
[dpSetOutputLimit]: { key: "outputLimit", result: dpSetOutputLimitResult, resultTS: dpSetOutputLimitResultTS, minNormal: 0, maxNormal: maxOutputLimit },
[dpSetSocSet]: { key: "socSet", transform: v => v*10, result: dpSetSocSetResult, resultTS: dpSetSocSetResultTS, minNormal: 70, maxNormal: 100 },
[dpSetMinSoc]: { key: "minSoc", transform: v => v*10, result: dpSetMinSocResult, resultTS: dpSetMinSocResultTS, minNormal: 5, maxNormal: 50 },
[dpSetGridReverse]: { key: "gridReverse", result: dpSetGridReverseResult, resultTS: dpSetGridReverseResultTS, minNormal: 0, maxNormal: 2 },
[dpSetGridStandard]: { key: "gridStandard", result: dpSetGridStandardResult, resultTS: dpSetGridStandardResultTS, minNormal: 0, maxNormal: 2 },
[dpSetInverseMaxPower]: { key: "inverseMaxPower", result: dpSetInverseMaxPowerResult, resultTS: dpSetInverseMaxPowerResultTS, minNormal: 600, maxNormal: maxOutputLimit }
};
// Queue HTTP
const curlQueue = [];
let curlRunning = false;
function runQueue() {
if (curlRunning || curlQueue.length === 0) return;
const task = curlQueue.shift();
curlRunning = true;
task.fn(() => {
curlRunning = false;
runQueue();
});
}
//dp have to exist - get
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// dp create
createState(dpSmartModeInfo, 0, {
name: "SmartMode Info",
type: "number",
role: "state",
read: true,
write: false,
min: 0,
max: 1
}, async () => {
await delay(1);
getReport();
});
createState(dpSetSmartMode, -1, {
name: "SmartMode Set",
type: "number",
role: "state",
read: true,
write: true,
min: -1,
max: 1
}, () => {});
createState(dpSetSmartModeResult, "", {
name: "SmartMode Set Result",
type: "string",
role: "info",
read: true,
write: false
}, () => {});
createState(dpTimestamp, "", {
name: "Timestamp",
type: "string",
role: "info",
read: true,
write: false
}, async () => {
await delay(1);
});
createState(dpMqttConnectInfo, 0, {
name: "MQTT Connect Info",
type: "number",
role: "state",
read: true,
write: false,
min: 0,
max: 1
}, async () => {
await delay(1);
getMqttStatus();
});
createState(dpSetMqttConnect, -1, {
name: "MQTT Connect Set",
type: "number",
role: "state",
read: true,
write: true,
min: -1,
max: 1
}, () => {});
createState(dpSetMqttConnectResult, "", {
name: "MQTT Connect Result",
type: "string",
role: "info",
read: true,
write: false
}, () => {});
createState(dpMqttTimestamp, "", {
name: "MQTT Timestamp",
type: "string",
role: "info",
read: true,
write: false
}, async () => { await delay(1); });
// set > result ts dp
// acMode
createState(dpSetAcMode, -1, {
name: "set acMode, 1: charging 2: discharging",
desc: "1: Input, 2: Output",
type: "number",
role: "state",
read: true,
write: true,
min: -1,
max: 2
}, () => {});
createState(dpSetAcModeResult, "", {
name: "AcMode Set Result",
type: "string",
role: "info",
read: true,
write: false
}, () => {});
createState(dpSetAcModeResultTS, "", {
name: "AcMode Set Result Timestamp",
type: "string",
role: "info",
read: true,
write: false
}, () => {});
// inputLimit
createState(dpSetInputLimit, -1, {
name: "set inputLimit",
desc: "AC charging power limit",
type: "number",
role: "state",
read: true,
write: true,
unit: 'W',
min: -1,
max: maxInputLimit //SF800Pro: 1000W, SF2400AC: 2400W
}, () => {});
createState(dpSetInputLimitResult, "", {
name: "InputLimit Set Result",
type: "string",
role: "info",
read: true,
write: false
}, () => {});
createState(dpSetInputLimitResultTS, "", {
name: "InputLimit Set Result Timestamp",
type: "string",
role: "info",
read: true,
write: false
}, () => {});
// outputLimit
createState(dpSetOutputLimit, -1, {
name: "set outputLimit",
desc: "Output power limit",
type: "number",
role: "state",
read: true,
write: true,
unit: 'W',
min: -1,
max: maxOutputLimit //SF800Pro: 800W, SF2400AC: 2400W
}, () => {});
createState(dpSetOutputLimitResult, "", {
name: "OutputLimit Set Result",
type: "string",
role: "info",
read: true,
write: false
}, () => {});
createState(dpSetOutputLimitResultTS, "", {
name: "OutputLimit Set Result Timestamp",
type: "string",
role: "info",
read: true,
write: false
}, () => {});
// socSet
createState(dpSetSocSet, -1, {
name: "set socSet 70-100%",
desc: "Stop charging at battery percentage",
type: "number",
role: "state",
read: true,
write: true,
unit: '%',
min: -1,
max: 100
}, () => {});
createState(dpSetSocSetResult, "", {
name: "SocSet Set Result",
type: "string",
role: "info",
read: true,
write: false
}, () => {});
createState(dpSetSocSetResultTS, "", {
name: "SocSet Set Result Timestamp",
type: "string",
role: "info",
read: true,
write: false
}, () => {});
// minSoc
createState(dpSetMinSoc, -1, {
name: "set minSoc 0-50%",
desc: "Stop discharging at battery percentage",
type: "number",
role: "state",
read: true,
write: true,
unit: '%',
min: -1,
max: 50
}, () => {});
createState(dpSetMinSocResult, "", {
name: "MinSoc Set Result",
type: "string",
role: "info",
read: true,
write: false
}, () => {});
createState(dpSetMinSocResultTS, "", {
name: "MinSoc Set Result Timestamp",
type: "string",
role: "info",
read: true,
write: false
}, () => {});
// gridReverse
createState(dpSetGridReverse, -1, {
name: "setGridReverse, 0: off, 1: rev. 2: no rev",
desc: "0: Disabled, 1: Allowed reverse flow, 2: Forbidden reverse flow",
type: "number",
role: "state",
read: true,
write: true,
min: -1,
max: 2
}, () => {});
createState(dpSetGridReverseResult, "", {
name: "GridReverse Set Result",
type: "string",
role: "info",
read: true,
write: false
}, () => {});
createState(dpSetGridReverseResultTS, "", {
name: "GridReverse Set Result Timestamp",
type: "string",
role: "info",
read: true,
write: false
}, () => {});
// gridStandard
createState(dpSetGridStandard, -1, {
name: "setGridStandard, 0: Germany / 2: France",
desc: "Grid connection standard 0: Germany 1: France 2: Austria",
type: "number",
role: "state",
read: true,
write: true,
min: -1,
max: 2
}, () => {});
createState(dpSetGridStandardResult, "", {
name: "GridStandard Set Result",
type: "string",
role: "info",
read: true,
write: false
}, () => {});
createState(dpSetGridStandardResultTS, "", {
name: "GridStandard Set Result Timestamp",
type: "string",
role: "info",
read: true,
write: false
}, () => {});
// inverseMaxPower
createState(dpSetInverseMaxPower, -1, {
name: "set inverseMaxPower",
desc: "Maximum output power limit",
type: "number",
role: "state",
read: true,
write: true,
unit: 'W',
min: -1,
max: maxOutputLimit
}, () => {});
createState(dpSetInverseMaxPowerResult, "", {
name: "inverseMaxPower Set Result",
type: "string",
role: "info",
read: true,
write: false
}, () => {});
createState(dpSetInverseMaxPowerResultTS, "", {
name: "inverseMaxPower Set Result Timestamp",
type: "string",
role: "info",
read: true,
write: false
}, () => {});
// intervals start
setInterval(getReport, intervalGet*1000);
setInterval(getMqttStatus, intervalMqtt*1000);
// trigger smartMode
on({id: dpSetSmartMode, ack:false}, obj => {
const val = parseInt(obj.state.val,10);
if(val===0||val===1) setSmartMode(val);
setState(dpSetSmartMode, -1, { ack: true });
});
// trigger MQTT connect
on({id: dpSetMqttConnect, ack:false}, obj => {
const val = parseInt(obj.state.val,10);
if(val===0||val===1) setMqttConnect(val);
setState(dpSetMqttConnect, -1, { ack: true });
});
// trigger dp set
Object.keys(dpMap).forEach(id => {
on({ id: id, ack: false }, obj => {
const val = obj.state.val;
const { key, transform, result, resultTS, minNormal, maxNormal } = dpMap[id];
// Prüfen, ob Wert im gültigen Bereich liegt
if (val >= minNormal && val <= maxNormal) {
const valueToSend = transform ? transform(val) : val;
setControlDP(id, valueToSend); // POST senden
} else {
log(`Value ${val} for ${id} is not allowed`, 'warn');
}
setState(id, -1, { ack: true });
});
});
// time format
function formatTime(ts) {
// ts unix sek
const d = new Date(ts * 1000);
const pad = n => n.toString().padStart(2, "0");
return `${pad(d.getDate())}.${pad(d.getMonth()+1)}.${d.getFullYear().toString().slice(-2)} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
}
// helper HTTP-error
function handleHttpError(context, err, options) {
const transient = [
"EHOSTUNREACH", "ECONNRESET", "ECONNREFUSED", "ETIMEDOUT",
"socket hang up", "ENETUNREACH", "EAI_AGAIN", "ENOTFOUND"
];
const msg = err && err.message ? err.message : String(err);
const isTransient = transient.some(code => msg.includes(code));
//info_error
//log(`${context}: ${isTransient ? "war vorübergehend im Intervall nicht erfolgreich:" : "Warnung"} (${msg})`, isTransient ? "info" : "warn");
log(`${context}: ${isTransient ? "war vorübergehend im Intervall nicht erfolgreich:" : "Warnung"} (${msg})`, "info");
// set Timestamp
if (options && options.timestamp) {
setState(options.timestamp, formatTime(Math.floor(Date.now() / 1000)), true);
}
}
// smartMode GET report
function getReport() {
curlQueue.push({
fn: async done => { //async
const options = {
hostname: IP,
port: 80,
path: "/properties/report",
method: "GET",
timeout: 5000
};
const req = http.request(options, res => {
let data = "";
res.on("data", chunk => data += chunk);
res.on("end", async () => { // async
try {
const json = JSON.parse(data);
if (json.properties && typeof json.properties.smartMode !== "undefined") {
setState(dpSmartModeInfo, json.properties.smartMode, true);
setState(dpTimestamp, formatTime(Math.floor(Date.now() / 1000)), true);
}
//JSON
await processJson(json);
} catch (e) {
log("GET JSON Parse Fehler: " + e, "info"); //no valid JSON
setState(dpTimestamp, formatTime(Math.floor(Date.now() / 1000)), true);
}
done();
});
});
req.on("error", err => {
handleHttpError("HTTP GET", err, { timestamp: dpTimestamp });
done();
});
req.end();
}
});
runQueue();
}
// smartMode POST
function setSmartMode(val) {
curlQueue.push({
fn: done => {
const payload = JSON.stringify({ sn: SN, properties: { smartMode: val } });
const options = {
hostname: IP,
port: 80,
path: "/properties/write",
method: "POST",
headers: {
"Content-Type": "application/json",
"Content-Length": Buffer.byteLength(payload)
},
timeout: 5000
};
const req = http.request(options, res => {
let data = "";
res.on("data", chunk => data += chunk);
res.on("end", () => {
let ok = false;
if (data) {
try {
const json = JSON.parse(data);
ok = json.success === true;
} catch (e) {
ok = false; // no valid JSON
}
}
setState(dpSetSmartModeResult, ok ? `ok set ${val}` : `error set ${val}`, true);
setState(dpTimestamp, formatTime(Math.floor(Date.now() / 1000)), true);
done();
});
});
req.on("error", err => {
handleHttpError("HTTP POST", err, { timestamp: dpTimestamp });
setState(dpSetSmartModeResult, `error set ${val}`, true);
setState(dpTimestamp, formatTime(Math.floor(Date.now() / 1000)), true);
done();
});
req.write(payload);
req.end();
}
});
runQueue();
}
// MQTT state GET
function getMqttStatus() {
curlQueue.push({
fn: done => {
const options = {
hostname: IP,
port: 80,
path: "/rpc?method=HA.Mqtt.GetStatus",
method: "GET",
timeout: 5000
};
const req = http.request(options, res => {
let data = "";
res.on("data", chunk => data += chunk);
res.on("end", () => {
try {
const json = JSON.parse(data);
const state = json.connected ? 1 : 0;
setState(dpMqttConnectInfo, state, true);
setState(dpMqttTimestamp, formatTime(Math.floor(Date.now() / 1000)), true);
} catch (e) {
log(`MQTT JSON Parse Fehler: ${e}`, "info"); //no valid JSON
setState(dpMqttTimestamp, formatTime(Math.floor(Date.now() / 1000)), true);
}
done();
});
});
req.on("error", err => {
handleHttpError("HTTP MQTT GET", err, { timestamp: dpMqttTimestamp });
done();
});
req.end();
}
});
runQueue();
}
// MQTT POST
function setMqttConnect(val) {
curlQueue.push({
fn: done => {
const enable = !!val;
const payload = JSON.stringify({
sn: SN,
method: "HA.Mqtt.SetConfig",
params: {
config: {
enable: enable,
server: mqttBrokerIp,
port: mqttPort,
protocol: "mqtt",
username: mqttUsername,
password: mqttPassword
}
}
});
const options = {
hostname: IP,
port: 80,
path: "/rpc",
method: "POST",
headers: {
"Content-Type": "application/json",
"Content-Length": Buffer.byteLength(payload)
},
timeout: 5000
};
const req = http.request(options, res => {
let data = "";
res.on("data", chunk => data += chunk);
res.on("end", () => {
let ok = false;
// HTTP 200 = Verbindung ok, Request angekommen
if (res.statusCode === 200) {
if (data) {
try {
const json = JSON.parse(data);
ok = json.success === true;
} catch (e) {
ok = false; // no valid JSON
}
}
}
setState(dpSetMqttConnectResult, ok ? `ok set ${val}` : `error set ${val}`, true);
setState(dpMqttTimestamp, formatTime(Math.floor(Date.now() / 1000)), true);
done();
});
});
req.on("error", err => {
handleHttpError("HTTP MQTT POST", err, { timestamp: dpMqttTimestamp });
setState(dpSetMqttConnectResult, `error set ${val}`, true);
setState(dpMqttTimestamp, formatTime(Math.floor(Date.now() / 1000)), true);
done();
});
req.write(payload);
req.end();
}
});
runQueue();
}
// Generic POST function with result and timestamp handling
function setControlDP(id, val) {
if (!dpMap[id]) return;
const { key, transform, result, resultTS } = dpMap[id];
const value = transform ? transform(val) : val;
curlQueue.push({
fn: done => {
const payload = JSON.stringify({ sn: SN, properties: { [key]: value } });
const options = {
hostname: IP,
port: 80,
path: "/properties/write",
method: "POST",
headers: {
"Content-Type": "application/json",
"Content-Length": Buffer.byteLength(payload)
},
timeout: 5000
};
const req = http.request(options, res => {
let data = "";
res.on("data", chunk => data += chunk);
res.on("end", () => {
let ok = false;
// HTTP 200 = Connection established
if (res.statusCode === 200) {
if (data) {
try {
const json = JSON.parse(data);
ok = json.success === true;
} catch (e) {
ok = false; // no valid JSON
}
}
}
// Write success or failure to data points
if (result) {
const valueToSend = transform ? transform(val) : val; // Wert, der gesendet wurde
setState(result, ok ? `ok set ${valueToSend}` : `error set ${valueToSend}`, true);
}
if (resultTS) setState(resultTS, formatTime(Math.floor(Date.now() / 1000)), true);
done();
});
});
req.on("error", err => {
handleHttpError("HTTP POST " + key, err, { timestamp: resultTS });
if (result) {
const valueToSend = transform ? transform(val) : val; // Wert, der gesendet wurde
setState(result, `error set ${valueToSend}`, true);
}
if (resultTS) setState(resultTS, formatTime(Math.floor(Date.now() / 1000)), true);
done();
});
req.write(payload);
req.end();
}
});
runQueue();
}
//------------------------------------------------------------------------------------
// JSON
//------------------------------------------------------------------------------------
async function processJson(obj) {
try {
if (!obj || !obj.product) return;
const product = obj.product;
const basePath = `${folderZendureApi}.${product}`;
// create product folder if not exist
if (!existsObject(basePath)) {
setObject(basePath, {
type: 'folder',
common: { name: product },
native: {},
});
}
// create main keys
const mainKeys = ['timestamp', 'messageId', 'sn', 'version', 'product'];
for (const key of mainKeys) {
if (obj[key] !== undefined) {
const statePath = `${basePath}.${key}`;
if (existsState(statePath)) {
setState(statePath, obj[key], true);
} else {
createState(statePath, obj[key], {
name: key,
type: typeof obj[key],
role: 'info',
read: true,
write: false,
});
}
}
}
// properties
if (obj.properties) {
await iter(`${basePath}.properties`, obj.properties);
}
// PackData
if (obj.packData && Array.isArray(obj.packData)) {
await iter(`${basePath}`, { packData: obj.packData, timestamp: obj.timestamp });
}
} catch (e) {
log(`Error processing JSON: ${e}`, 'info');
}
}
//------------------------------------------------------------------------------------
// Battery
//------------------------------------------------------------------------------------
function getBatteryType(sn, model) {
let batType = '';
if (sn?.startsWith('A')) batType = 'AB1000';
else if (sn?.startsWith('B')) batType = 'AB1000S';
else if (sn?.startsWith('C')) batType = sn[3] === 'F' ? 'AB2000S' : sn[3] === 'E' ? 'AB2000X' : 'AB2000';
else if (sn?.startsWith('F')) batType = 'AB3000X';
if (model?.trim()) batType = model.trim();
return batType || 'unknown';
}
//------------------------------------------------------------------------------------
// helper Iteration
//------------------------------------------------------------------------------------
async function iter(id, obj) {
try {
if (!obj || typeof obj !== "object") {
//log("iter: Ungültiges oder leeres Objekt übersprungen", "info");
return;
}
for (let i in obj) {
if (i === 'packData' && Array.isArray(obj[i])) {
const ts = obj.timestamp ? obj.timestamp * 1000 : Date.now();
for (const pack of obj[i]) {
if (!pack.sn) continue;
const sn = pack.sn;
const path = `${id}.packData.${sn}`;
if (!existsObject(path)) {
setObject(path, {
type: 'folder',
common: { name: sn },
native: {},
});
}
for (let [key, val] of Object.entries(pack)) {
const statePath = `${path}.${key}`;
switch (key) {
case 'batcur':
val = (val << 16 >> 16) / 10;
if (existsState(statePath)) {
setState(statePath, { val, ts, ack: true });
} else {
createState(statePath, val, {
name: 'Battery Current',
type: 'number',
desc: 'battery current',
role: 'value',
read: true,
write: false,
unit: 'A',
});
}
break;
case 'heatState':
if (existsState(statePath)) {
setState(statePath, { val, ts, ack: true });
} else {
createState(statePath, val, {
name: 'Heat State, 0: Not heating, 1: heating',
type: 'number',
role: 'value',
read: true,
write: false,
});
}
break;
case 'maxTemp':
val = (val - 2731) / 10;
if (existsState(statePath)) {
setState(statePath, { val, ts, ack: true });
} else {
createState(statePath, val, {
name: 'Highest Akku Temperature',
type: 'number',
desc: 'maximum temperature',
role: 'value',
read: true,
write: false,
unit: '°C',
});
}
break;
case 'maxVol':
val = val / 100;
if (existsState(statePath)) {
setState(statePath, { val, ts, ack: true });
} else {
createState(statePath, val, {
name: 'Highest Cell Voltage',
type: 'number',
desc: 'highest cell voltage',
role: 'value',
read: true,
write: false,
unit: 'V',
});
}
break;
case 'minVol':
val = val / 100;
if (existsState(statePath)) {
setState(statePath, { val, ts, ack: true });
} else {
createState(statePath, val, {
name: 'Lowest Cell Voltage',
type: 'number',
desc: 'lowest cell voltage',
role: 'value',
read: true,
write: false,
unit: 'V',
});
}
break;
case 'packType':
if (existsState(statePath)) {
setState(statePath, { val, ts, ack: true });
} else {
createState(statePath, val, {
name: 'packType',
type: 'number',
role: 'value',
read: true,
write: false,
});
}
break;
case 'power':
if (existsState(statePath)) {
setState(statePath, { val, ts, ack: true });
} else {
createState(statePath, val, {
name: 'Battery Power',
type: 'number',
role: 'value',
read: true,
write: false,
unit: 'W',
});
}
break;
case 'sn':
const batType = getBatteryType(val, pack.model);
// Modelltyp als Datenpunkt setzen/erstellen
const modelPath = `${path}.model`;
if (existsState(modelPath)) {
setState(modelPath, { val: batType, ts, ack: true });
} else {
createState(modelPath, batType, {
name: 'Battery Model',
type: 'string',
role: 'text',
read: true,
write: false,
});
}
break;
case 'socLevel':
if (existsState(statePath)) {
setState(statePath, { val, ts, ack: true });
} else {
createState(statePath, val, {
name: 'Battery SoC Level',
type: 'number',
role: 'value',
read: true,
write: false,
unit: '%',
});
}
break;
case 'softVersion':
if (existsState(statePath)) {
setState(statePath, { val, ts, ack: true });
} else {
createState(statePath, val, {
name: 'Battery Software Version',
type: 'number',
role: 'value',
read: true,
write: false,
});
}
break;
case 'state':
if (existsState(statePath)) {
setState(statePath, { val, ts, ack: true });
} else {
createState(statePath, val, {
name: 'Battery, 0: Standby, 1: Charging, 2: Discharging',
type: 'number',
role: 'value',
read: true,
write: false,
});
}
break;
case 'totalVol':
val = val / 100;
if (existsState(statePath)) {
setState(statePath, { val, ts, ack: true });
} else {
createState(statePath, val, {
name: 'Total Voltage',
type: 'number',
desc: 'total voltage',
role: 'value',
read: true,
write: false,
unit: 'V',
});
}
break;
default:
if (existsState(statePath)) {
setState(statePath, { val, ts, ack: true });
} else {
createState(statePath, val, {
name: key,
type: typeof val,
role: 'value',
read: true,
write: false,
});
}
break;
}
}
}
} else {
const ts = Date.now();
const statePath = `${id}.${i}`;
let val = obj[i];
switch (i) {
case 'BatVolt':
val = val / 100;
if (existsState(statePath)) {
setState(statePath, { val, ts, ack: true });
} else {
createState(statePath, val, {
name: 'Battery Voltage',
type: 'number',
desc: 'battery voltage',
role: 'value',
read: true,
write: false,
unit: 'V',
});
}
break;
case 'chargeMaxLimit':
val = val / 10;
if (existsState(statePath)) {
setState(statePath, { val, ts, ack: true });
} else {
createState(statePath, val, {
name: 'maximum Charge Limit',
type: 'number',
desc: 'Battery Charge Limit Maximum',
role: 'value',
read: true,
write: false,
unit: '%',
});
}
break;
case 'acMode':
if (existsState(statePath)) {
setState(statePath, { val, ts, ack: true });
} else {
createState(statePath, val, {
name: 'acMode, 1: input mode / 2: output mode',
type: 'number',
desc: '1: charging / 2: discharging',
role: 'value',
read: true,
write: false,
});
}
break;
case 'dataReady':
if (existsState(statePath)) {
setState(statePath, { val, ts, ack: true });
} else {
createState(statePath, val, {
name: 'Data Ready, 0: Not ready, 1: Ready',
type: 'number',
role: 'value',
read: true,
write: false,
});
}
break;
case 'dcStatus':
if (existsState(statePath)) {
setState(statePath, { val, ts, ack: true });
} else {
createState(statePath, val, {
name: 'dcStatus, 0: Stopped, 1: Battery input, 2: Battery output',
type: 'number',
role: 'value',
read: true,
write: false,
});
}
break;
case 'electricLevel':
if (existsState(statePath)) {
setState(statePath, { val, ts, ack: true });
} else {
createState(statePath, val, {
name: 'Total Battery Charge Level',
type: 'number',
desc: 'SoC',
role: 'value',
read: true,
write: false,
unit: '%',
});
}
break;
case 'gridInputPower':
if (existsState(statePath)) {
setState(statePath, { val, ts, ack: true });
} else {
createState(statePath, val, {
name: 'Grid Input Power to Battery',
type: 'number',
role: 'value',
read: true,
write: false,
unit: 'W',
});
}
break;
case 'gridReverse':
if (existsState(statePath)) {
setState(statePath, { val, ts, ack: true });
} else {
createState(statePath, val, {
name: '0: Disabled, 1: Allowed reverse flow, 2: Forbidden reverse flow',
type: 'number',
role: 'value',
read: true,
write: false,
});
}
break;
case 'gridStandard':
if (existsState(statePath)) {
setState(statePath, { val, ts, ack: true });
} else {
createState(statePath, val, {
name: 'Grid connection standard 0: Germany 1: France 2: Austria',
type: 'number',
role: 'value',
read: true,
write: false,
});
}
break;
case 'gridState':
if (existsState(statePath)) {
setState(statePath, { val, ts, ack: true });
} else {
createState(statePath, val, {
name: 'GridState, 0: Not connected, 1: Connected',
type: 'number',
role: 'value',
read: true,
write: false,
});
}
break;
case 'heatState':
if (existsState(statePath)) {
setState(statePath, { val, ts, ack: true });
} else {
createState(statePath, val, {
name: 'Heat State, 0: Not heating, 1: heating',
type: 'number',
role: 'value',
read: true,
write: false,
});
}
break;
case 'hyperTmp':
val = (val - 2731) / 10.0;
if (existsState(statePath)) {
setState(statePath, { val, ts, ack: true });
} else {
createState(statePath, val, {
name: 'Enclosure Temperature',
type: 'number',
role: 'value',
read: true,
write: false,
unit: '°C',
});
}
break;
case 'inputLimit':
if (existsState(statePath)) {
setState(statePath, { val, ts, ack: true });
} else {
createState(statePath, val, {
name: 'AC charging power limit to Battery',
type: 'number',
role: 'value',
read: true,
write: false,
unit: 'W',
});
}
break;
case 'inverseMaxPower':
if (existsState(statePath)) {
setState(statePath, { val, ts, ack: true });
} else {
createState(statePath, val, {
name: 'Device Maximum Output Power Limit',
type: 'number',
role: 'value',
read: true,
write: false,
unit: 'W',
});
}
break;
case 'minSoc':
val = val / 10;
if (existsState(statePath)) {
setState(statePath, { val, ts, ack: true });
} else {
createState(statePath, val, {
name: 'min SoC',
type: 'number',
desc: 'minimum Battery SoCset',
role: 'value',
read: true,
write: false,
unit: '%',
});
}
break;
case 'outputHomePower':
if (existsState(statePath)) {
setState(statePath, { val, ts, ack: true });
} else {
createState(statePath, val, {
name: 'outputHomePower',
type: 'number',
role: 'value',
read: true,
write: false,
unit: 'W',
});
}
break;
case 'outputLimit':
if (existsState(statePath)) {
setState(statePath, { val, ts, ack: true });
} else {
createState(statePath, val, {
name: 'Output power limit',
type: 'number',
role: 'value',
read: true,
write: false,
unit: 'W',
});
}
break;
case 'outputPackPower':
if (existsState(statePath)) {
setState(statePath, { val, ts, ack: true });
} else {
createState(statePath, val, {
name: 'Output power to battery pack (charging)',
type: 'number',
role: 'value',
read: true,
write: false,
unit: 'W',
});
}
break;
case 'packInputPower':
if (existsState(statePath)) {
setState(statePath, { val, ts, ack: true });
} else {
createState(statePath, val, {
name: 'Battery pack input power (discharging)',
type: 'number',
role: 'value',
read: true,
write: false,
unit: 'W',
});
}
break;
case 'packNum':
if (existsState(statePath)) {
setState(statePath, { val, ts, ack: true });
} else {
createState(statePath, val, {
name: 'Number of battery packs',
type: 'number',
role: 'value',
read: true,
write: false,
});
}
break;
case 'packState':
if (existsState(statePath)) {
setState(statePath, { val, ts, ack: true });
} else {
createState(statePath, val, {
name: 'Battery State, 0: Standby, 1: Charging, 2: Discharging',
type: 'number',
role: 'value',
read: true,
write: false,
});
}
break;
case 'pass':
if (existsState(statePath)) {
setState(statePath, { val, ts, ack: true });
} else {
createState(statePath, val, {
name: 'Bypass, 0: No, 1: Yes',
type: 'number',
role: 'value',
read: true,
write: false,
});
}
break;
case 'pvStatus':
if (existsState(statePath)) {
setState(statePath, { val, ts, ack: true });
} else {
createState(statePath, val, {
name: 'PV State producing, 0: Stopped, 1: Running',
type: 'number',
role: 'value',
read: true,
write: false,
});
}
break;
case 'remainOutTime':
if (existsState(statePath)) {
setState(statePath, { val, ts, ack: true });
} else {
createState(statePath, val, {
name: 'Estimated discharge time in minutes, if not predictable: 59940',
type: 'number',
role: 'value',
read: true,
write: false,
unit: 'min',
});
}
break;
case 'reverseState':
if (existsState(statePath)) {
setState(statePath, { val, ts, ack: true });
} else {
createState(statePath, val, {
name: 'Reverse flow, 0: No, 1: Reverse flow',
type: 'number',
role: 'value',
read: true,
write: false,
});
}
break;
case 'rssi':
if (existsState(statePath)) {
setState(statePath, { val, ts, ack: true });
} else {
createState(statePath, val, {
name: 'Received Signal Strength Indicator',
type: 'number',
role: 'value',
read: true,
write: false,
unit: 'dBm',
});
}
break;
case 'smartMode':
if (existsState(statePath)) {
setState(statePath, { val, ts, ack: true });
} else {
createState(statePath, val, {
name: '1: parameter written to RAM / 0: parameter is written to flash.',
type: 'number',
role: 'value',
read: true,
write: false,
});
}
break;
case 'socLimit':
if (existsState(statePath)) {
setState(statePath, { val, ts, ack: true });
} else {
createState(statePath, val, {
name: 'socLimit-Info 0: normal, 1: Charge limit reached, 2: Discharge limit reached',
type: 'number',
role: 'value',
read: true,
write: false,
});
}
break;
case 'socSet':
val = val / 10;
if (existsState(statePath)) {
setState(statePath, { val, ts, ack: true });
} else {
createState(statePath, val, {
name: 'max SoC',
type: 'number',
desc: 'maximum Battery SoCset',
role: 'value',
read: true,
write: false,
unit: '%',
});
}
break;
case 'socStatus':
if (existsState(statePath)) {
setState(statePath, { val, ts, ack: true });
} else {
createState(statePath, val, {
name: 'Info of Calibrating, 0: No / 1: Calibrating',
type: 'number',
role: 'value',
read: true,
write: false,
});
}
break;
case 'solarInputPower':
if (existsState(statePath)) {
setState(statePath, { val, ts, ack: true });
} else {
createState(statePath, val, {
name: 'Total Solar Input Power',
type: 'number',
role: 'value',
read: true,
write: false,
unit: 'W',
});
}
break;
case 'solarPower1':
if (existsState(statePath)) {
setState(statePath, { val, ts, ack: true });
} else {
createState(statePath, val, {
name: 'Solar line 1 input power',
type: 'number',
role: 'value',
read: true,
write: false,
unit: 'W',
});
}
break;
case 'solarPower2':
if (existsState(statePath)) {
setState(statePath, { val, ts, ack: true });
} else {
createState(statePath, val, {
name: 'Solar line 2 input power',
type: 'number',
role: 'value',
read: true,
write: false,
unit: 'W',
});
}
break;
case 'solarPower3':
if (existsState(statePath)) {
setState(statePath, { val, ts, ack: true });
} else {
createState(statePath, val, {
name: 'Solar line 3 input power',
type: 'number',
role: 'value',
read: true,
write: false,
unit: 'W',
});
}
break;
case 'solarPower4':
if (existsState(statePath)) {
setState(statePath, { val, ts, ack: true });
} else {
createState(statePath, val, {
name: 'Solar line 4 input power',
type: 'number',
role: 'value',
read: true,
write: false,
unit: 'W',
});
}
break;
case 'solarPower5':
if (existsState(statePath)) {
setState(statePath, { val, ts, ack: true });
} else {
createState(statePath, val, {
name: 'Solar line 5 input power',
type: 'number',
role: 'value',
read: true,
write: false,
unit: 'W',
});
}
break;
case 'solarPower6':
if (existsState(statePath)) {
setState(statePath, { val, ts, ack: true });
} else {
createState(statePath, val, {
name: 'Solar line 6 input power',
type: 'number',
role: 'value',
read: true,
write: false,
unit: 'W',
});
}
break;
case 'timestamp':
const timestampVal = obj[i];
const timestampFormatted = formatTime(timestampVal);
if (existsState(statePath)) setState(statePath, timestampVal, true);
else createState(statePath, timestampVal, {
name: 'unix timestamp (timestamp)',
type: 'number',
desc: 'unix timestamp in Seconds since Jan 01 1970 (UTC)',
role: 'value',
read: true,
write: false,
});
if (existsState(id + '.timeUpdateTimestamp')) setState(id + '.timeUpdateTimestamp', timestampFormatted, true);
else createState(id + '.timeUpdateTimestamp', timestampFormatted, {
name: 'TimeUpdate (timestamp)',
type: 'string',
desc: 'unix timestamp in readable Format (timestamp)',
read: true,
write: false,
});
break;
case 'ts':
const tsVal = obj[i];
const tsFormatted = formatTime(tsVal);
if (existsState(statePath)) setState(statePath, tsVal, true);
else createState(statePath, tsVal, {
name: 'unix timestamp (ts)',
type: 'number',
desc: 'unix timestamp in Seconds since Jan 01 1970 (UTC)',
role: 'value',
read: true,
write: false,
});
if (existsState(id + '.timeUpdateTs')) setState(id + '.timeUpdateTs', tsFormatted, true);
else createState(id + '.timeUpdateTs', tsFormatted, {
name: 'TimeUpdate (ts)',
type: 'string',
desc: 'unix timestamp in readable Format (ts)',
read: true,
write: false,
});
break;
default:
if (existsState(statePath)) {
setState(statePath, { val, ack: true });
} else {
createState(statePath, val, {
name: i,
type: typeof val,
role: 'value',
read: true,
write: false,
});
}
break;
}
}
}
} catch (e) {
log(`Error in iter: ${e}`, 'info');
}
}
Historisch:
Frühere Versionen der Scripte sind hier (im Spoiler) zu finden.
Die kompletten Scripte sind zu groß für diesen Post, daher habe ich sie dort ausgelagert.