@Daniel-8 und @mabbi und Alle
habe nun endlich ein neues Gerät zum testen erhalten.
Dabei fiel mir gleich in meinem bisherigen Script die unübersichtliche Struktur auf.
Auch beim Start gab es Warnungen, wenn erstmals neue Verzeichnisse angelegt wurden.
Hat mir niemand geschrieben.
Korrigiert und optimiert.
Das Script ist ein vollwertiger Adpater-Ersatz.
Das -1 setzen wurde entfernt.
Beim setzen eines Control-State wird der Wert gesendet und gleich mit dem bestätigten Wert aktualisiert.
Intervall von 8 s (für die State-Abfrage/Report) läuft mit diesem Script problemlos (Queue Sicherung), wenn Netzwerkverbindung Zendure <> WLAN-Router ok ist.
MQTT (lokal) ist per Slider (boolean default deaktiviert).
Wenn deaktiviert, ist die MQTT-Script-Funktion komplett ausgeschaltet:
MQTT-Intervall wird nicht ausgeführt
dadurch keine MQTT-Statusabfrage
kein Ein-/Auschalten von lokalem MQTT möglich
MQTT- Datenfelder im Kofig-Block sind dann bedeutungslos.
Keine Fehler bei Start. Fehlende Verzeichnisse und Datenpunkte werden automatisch erstellt.
Sollten neue Datenpunkte hinzukommen (durch Frimwareupdate) werden sie als Datenpunkte automatisch erstellt.
Neue Struktur.
Haupt-Verzeichnis kann im Konfigblock leicht auf einen sinnvollen, eigenen Namen geändert werden.
Hinweis: wenn man auf
+ SN;
dort verzichtet, dann ist der Verzeichnis-Name auch ohne Seriennummer des Geräts zur Unterscheidung.
Die Seriennummer muss hier nicht verwendet werden, der Verzeichnis-Name muss bei mehreren Geräten aber unterschiedlich sein.
Script ist erprobt. Einige keys, die mir bekannt sind, wurden unter control nicht aufgenommen, weil sie Geräte-spezifisch sind, Gefahren darstellen und/oder (noch?) nicht offiziell unterstützt werden.... wie auch immer.
Bin etwas entäuscht von zenSDK.
Manche keys werden von dem Zendure-Gerät-Webserver nicht oder unregelmäßig aktualisiert.
Neue Struktur des Scripts:
[image: 1774891156463-script.png]
minTimeBreakForSetDpSec = 5;
Ist einstellbar. 5 sek. haben sich bei mir bewährt.
Nach dem setzen eines Befehls müssen mindestens x sek. vergangen sein um einen neuen zu senden.
Es sind mehrere Sicherungen eingebaut.
Kommentare im Konfig-Bereich bitte lesen.
Script ist ein Adapter-Ersatz.
In meinen Augen der ehrlichste.
Es werden keine Befehle und keine Befehlsketten gesendet, von denen man nichts weiß.
Volle Verantwortung bei dem user.
Auch smartMode wird nicht automatisch geschaltet.
// ioBroker JavaScript: Zendure zenSDK Adapter-Ersatz
// für alle Geräte ab 2025 die zenSDK unterstützen wie
// SF800, SF800 PLUS, 800Pro, 1600AC, SF2400AC usw.
// by maxclaudi 2026.03.30_19.10h for ioBroker Forum
//------------------------------------------------------------------------------------
// konfiguration
//
// Hinweis bei mehreren Zendure-Geräten:
// - Für jedes Gerät ein eigenes Script mit individueller Konfiguration verwenden!
// -> IP-Adresse
// -> Seriennummer (SN)
// -> MQTT-Daten (Broker, Port, Benutzer, Passwort)
// -> maxInputLimit / maxOutputLimit (abhängig vom Gerätetyp)
// - Intervalle (Standard): intervalGet = 10s, intervalMqtt = 300s – können gleich bleiben
//
// Empfehlung:
// • 3 Geräte: völlig unkritisch
// • 4 Geräte: problemlos möglich
// • mehr als 4: nicht empfohlen
//
// MQTT-Abfrage ist per default false=disabled. Lokales, offizielles MQTT ist mit bis zu 90sek Aktualisierungsrate zu langsam.
// Empfehle zenSDK zu verwenden oder DNS auf lokalen Betrieb umzubiegen.
// Wer offizielles, lokale MQTT dennoch benutzen möchte und überwachen möchte kann den Datenpunkt mit dem Slider einschalten.
// Satndardist bei script-Start MQTT-Abfrage ausgeschaltet und wird dann vom script komplett deaktiviert.
// Auch kein Intervall und keine Aktualisierung erfolgt dann.
//
// Damit neue Datenpunkte automatisch aus dem JSON erstellt werden:
// -> Instanzen -> javascript-Adapter -> Allgemeine Einstellungen -> Enable command"setObject“ aktivieren!
//
// Datenpunkte bei set werden automatisch aktualisiert und nicht auf -1 zurück gesetzt.
//------------------------------------------------------------------------------------
//------------------------------------------------------------------------------------
// CONFIG
//------------------------------------------------------------------------------------
// Intervalle für Abfragen (Sekunden)
const intervalGet = 8; // sek Intervall für GET report (default: 8, nicht <8) intervalGet MUSS größer sein als timeoutHttp !!
const intervalMqtt = 300; // sek MQTT-Status (default: 300 sek, > intervalGet) wird nur ausgeführt wenn Abfrage über SetMqttEnable eingeschaltet wird.
// IP und Seriennummer Zendure Gerät
const IP = "192.168.40.20"; // IP des Zendure Geräts
const SN = "Exxxxxxxxxxx007"; // 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 SF800 800W / SF800 PRO: 1000W / 1600AC: 1600 / SF2400AC: 2400W
const maxInputLimit = 1000;
//maximum outputLimit SF800 800W / SF800 PRO: 800W / 1600AC: 1600 / SF2400AC: 2400W
const maxOutputLimit = 800;
// Pause (Wert in Sekunden) die vergangen sein muss bevor ein neuer Befehl gesendet werden kann.
const minTimeBreakForSetDpSec = 5; // 5 s hat sich bei mir bewährt, minimal 2s!
// Haupt-Verzeichnis für das Zendure-Gerät
// hier kann der Name für das Hauptverzeichnis für das Gerät verändert werden.
// Standard wird die Seriennummer verwendet. Es kann aber durch einen beliebigen Namen ersetzt werden.
// Beispiel:
//const folderZendureApi = '0_userdata.0.zendure.' + "SolarFlow_800_1_Dachboden_links";
const folderZendureApi = '0_userdata.0.zendure.' + SN;
//
// Timout Handler HTTP GET /POST
// timeoutHttp MUSS kleiner sein als intervalGet!! Besser nicht ändern. Standard: 2000ms
const timeoutHttp = 2000; // Timeout in ms für alle HTTP-Anfragen (Zendure)
//------------------------------------------------------------------------------------
// End of config
//------------------------------------------------------------------------------------
const mqttStateAskingDefault = false; // nicht verändern! Kann bei laufendem script ein- oder ausgeschaltet werden
let lastMqttSet = 0;
let mqttInterval = null;
//------------------------------------------------------------------------------------
// dp
//------------------------------------------------------------------------------------
const dpSetSmartMode = folderZendureApi + ".control.setSmartMode";
const dpSetAcMode = folderZendureApi + ".control.setAcMode";
const dpSetInputLimit = folderZendureApi + ".control.setInputLimit";
const dpSetOutputLimit = folderZendureApi + ".control.setOutputLimit";
const dpSetSocSet = folderZendureApi + ".control.setSocSet";
const dpSetMinSoc = folderZendureApi + ".control.setMinSoc";
const dpSetGridReverse = folderZendureApi + ".control.setGridReverse";
const dpSetGridStandard = folderZendureApi + ".control.setGridStandard";
const dpSetInverseMaxPower = folderZendureApi + ".control.setInverseMaxPower";
const dpSetChargeMaxLimit = folderZendureApi + ".control.setChargeMaxLimit";
const dpSetGridOffMode = folderZendureApi + ".control.setGridOffMode";
const dpSetMqttEnable = folderZendureApi + ".control.localMqtt.EnableScriptControlLocalMqtt";
const dpSetMqttConnect = folderZendureApi + ".control.localMqtt.SetConnectlocalMqtt";
const dpMqttConnectInfo = folderZendureApi + ".control.localMqtt.InfoLocalMqttConnected";
const dpTimestamp = folderZendureApi + ".timestamp";
//------------------------------------------------------------------------------------
// mapping
//------------------------------------------------------------------------------------
const dpMap = {
[dpSetSmartMode]: { key: "smartMode", min: 0, max: 1 },
[dpSetAcMode]: { key: "acMode", min: 1, max: 2 },
[dpSetInputLimit]: { key: "inputLimit", min: 0, max: maxInputLimit },
[dpSetOutputLimit]: { key: "outputLimit", min: 0, max: maxOutputLimit },
[dpSetSocSet]: { key: "socSet", transform: v => v*10, min: 70, max: 100 },
[dpSetMinSoc]: { key: "minSoc", transform: v => v*10, min: 5, max: 50 },
[dpSetGridReverse]: { key: "gridReverse", min: 0, max: 2 },
[dpSetGridStandard]: { key: "gridStandard", min: 0, max: 2 },
[dpSetInverseMaxPower]: { key: "inverseMaxPower", min: 600, max: maxOutputLimit },
[dpSetChargeMaxLimit]: { key: "chargeMaxLimit", min: 0, max: maxInputLimit },
[dpSetGridOffMode]: { key: "gridOffMode", min: 0, max: 2 }
};
//------------------------------------------------------------------------------------
// QUEUE
//------------------------------------------------------------------------------------
const curlQueue = [];
let curlRunning = false;
function runQueue() {
if (curlRunning || curlQueue.length === 0) return;
const task = curlQueue.shift();
curlRunning = true;
task.fn(() => {
curlRunning = false;
runQueue();
});
}
//------------------------------------------------------------------------------------
// helper
//------------------------------------------------------------------------------------
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
function formatTime(ts) {
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())}`;
}
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);
}
}
//------------------------------------------------------------------------------------
// CREATE STATES
//------------------------------------------------------------------------------------
const dataPoints = [
{ id: dpSetSmartMode, val: 0, common: { name: "Smart Mode 0:FLASH 1:RAM",desc: "Flash write behavior", type: "number", role: "state", min: 0, max: 1 } },
{ id: dpSetAcMode, val: 1, common: { name: "AC Mode 1:charge mode 2:discharge mode", type: "number", role: "state", min: 1, max: 2 } },
{ id: dpSetInputLimit, val: 0, common: { name: "Input Limit AC charge limit", type: "number", role: "state", min: 0, max: maxInputLimit, unit: 'W' } },
{ id: dpSetOutputLimit, val: 0, common: { name: "Output Limit", type: "number", role: "state", min: 0, max: maxOutputLimit, unit: 'W' } },
{ id: dpSetSocSet, val: 70, common: { name: "SOC Set (Target SOC 70%-100%)", type: "number", role: "state", min: 70, max: 100, unit: '%' } },
{ id: dpSetMinSoc, val: 5, common: { name: "Min SOC (Minimum SOC 5%-50%)", type: "number", role: "state", min: 5, max: 50, unit: '%' } },
{ id: dpSetGridReverse, val: 0, common: { name: "Reverse flow control (0/1/2)", type: "number", role: "state", min: 0, max: 2 } },
{ id: dpSetGridStandard, val: 0, common: { name: "Grid Standard (0: Germany 1: France 2: Austria...)", desc: "(3: Switzerland 4: Netherlands 5: Spain 6: Belgium 7: Greece 8: Denmark 9: Italy)", type: "number", role: "state", min: 0, max: 9 } },
{ id: dpSetInverseMaxPower, val: 600, common: { name: "Max inverter output Power", type: "number", role: "state", min: 600, max: maxOutputLimit, unit: 'W' } },
{ id: dpSetChargeMaxLimit, val: 0, common: { name: "Charge Max Limit", type: "number", role: "state", min: 0, max: maxInputLimit, unit: 'W' } },
{ id: dpSetGridOffMode, val: 0, common: { name: "Grid Off Mode 0: Standard Mode 1: Economic Mode 2: Closure", type: "number", role: "state", min: 0, max: 2 } },
{ id: dpSetMqttEnable, val: mqttStateAskingDefault, common: { name: "Enable Script-Control of Local Mqtt: Off ON", type: "boolean", role: "state", read: true, write: true } },
{ id: dpSetMqttConnect, val: 0, common: { name: "Set Connect localMqtt -> only if EnableScriptControlLocalMqtt: on", type: "number", role: "indicator", read: true, write: true } },
{ id: dpMqttConnectInfo, val: 0, common: { name: "Info Local Mqtt Connected 0:off 1:on -> only if EnableScriptControlLocalMqtt: on", type: "number", role: "indicator", read: true, write: false } },
{ id: dpTimestamp, val: "", common: { name: "Timestamp", type: "string", role: "state", read: true, write: false } }
];
async function startScript() {
console.log("Erstelle Datenpunkte...");
for (const dp of dataPoints) {
await createStateAsync(dp.id, dp.val, dp.common);
}
setState(dpSetMqttEnable, mqttStateAskingDefault, true);
console.log("Datenpunkte bereit. Starte Abfragen...");
setInterval(getReport, intervalGet * 1000);
getReport();
}
startScript();
//------------------------------------------------------------------------------------
// TRIGGERS (SET)
//------------------------------------------------------------------------------------
Object.keys(dpMap).forEach(id => {
let lastSetTime = 0;
on({ id: id, ack: false }, obj => {
const val = obj.state.val;
const { min, max } = dpMap[id];
if (val >= min && val <= max) {
const now = Date.now();
if (now - lastSetTime >= minTimeBreakForSetDpSec * 1000) {
lastSetTime = now;
setControlDP(id, val);
}
}
});
});
// MQTT POST nur wenn enable=true + rate limit
on({ id: dpSetMqttConnect, ack: false }, obj => {
if (!getState(dpSetMqttEnable).val) return;
const now = Date.now();
if (now - lastMqttSet < minTimeBreakForSetDpSec * 1000) return;
lastMqttSet = now;
const val = parseInt(obj.state.val, 10);
if (val === 0 || val === 1) setMqttConnect(val);
});
//------------------------------------------------------------------------------------
// HTTP
//------------------------------------------------------------------------------------
const http = require("http");
function setControlDP(id, val) {
const { key, transform } = dpMap[id];
const value = transform ? transform(val) : val;
curlQueue.push({
fn: done => {
const payload = JSON.stringify({ sn: SN, properties: { [key]: value } });
const req = http.request({
hostname: IP,
port: 80,
path: "/properties/write",
method: "POST",
headers: {
"Content-Type": "application/json",
"Content-Length": Buffer.byteLength(payload)
}
}, res => {
res.on("data", () => {});
res.on("end", () => {
// nach Schreiben sofort anfordern
curlQueue.unshift({ fn: d => getReportInternal(d) });
done(); // freigeben
});
});
// Timeout
req.setTimeout(timeoutHttp, () => {
req.destroy(); // Verbindung hart abbrechen
handleHttpError("POST Timeout", "Gerät antwortet nicht", { timestamp: dpTimestamp });
done(); // WICHTIG: Queue freigeben, damit nächste Befehle folgen können
});
// Fehler
req.on("error", err => {
handleHttpError("POST Error", err, { timestamp: dpTimestamp });
done();
});
req.write(payload);
req.end();
}
});
runQueue();
}
function getReport() {
curlQueue.push({ fn: done => getReportInternal(done) });
runQueue();
}
function getReportInternal(done) {
const req = http.request({
hostname: IP,
port: 80,
path: "/properties/report",
method: "GET"
}, res => {
let data = "";
res.on("data", chunk => data += chunk);
res.on("end", async () => {
try {
const json = JSON.parse(data);
if (json.properties) {
Object.keys(dpMap).forEach(dp => {
const { key } = dpMap[dp];
if (json.properties[key] !== undefined) {
setState(dp, json.properties[key], true);
}
});
}
setState(dpTimestamp, formatTime(Math.floor(Date.now()/1000)), true);
await processJson(json);
} catch (e) {
log("GET parse error: " + e, "info");
}
done(); // WICHTIG: Queue freigeben nach Erfolg
});
});
// Fehler
req.on("error", err => {
handleHttpError("GET Error", err, { timestamp: dpTimestamp });
done(); // WICHTIG: Queue freigeben bei Fehler
});
// Timeout
req.setTimeout(timeoutHttp, () => {
req.destroy(); // Verbindung abbrechen
handleHttpError("GET Timeout", "Gerät antwortet nicht", { timestamp: dpTimestamp });
done(); // WICHTIG: Queue freigeben bei Zeitüberschreitung
});
req.end();
}
//------------------------------------------------------------------------------------
// MQTT
//------------------------------------------------------------------------------------
// Enable-Schalter nur Steuerung, kein POST
on({ id: dpSetMqttEnable, ack: false }, obj => {
const enable = !!obj.state.val;
setState(dpSetMqttEnable, enable, true);
if (enable) {
// sofort GET
curlQueue.push({ fn: d => getMqttStatusInternal(d) });
runQueue();
// Intervall start
if (!mqttInterval) {
mqttInterval = setInterval(() => {
curlQueue.push({ fn: d => getMqttStatusInternal(d) });
runQueue();
}, intervalMqtt * 1000);
}
} else {
if (mqttInterval) {
clearInterval(mqttInterval);
mqttInterval = null;
}
}
});
function getMqttStatusInternal(done) {
const req = http.request({
hostname: IP,
port: 80,
path: "/rpc?method=HA.Mqtt.GetStatus",
method: "GET"
}, res => {
let data = "";
res.on("data", chunk => data += chunk);
res.on("end", () => {
try {
const json = JSON.parse(data);
const state = json.connected ? 1 : 0;
// readonly
setState(dpMqttConnectInfo, state, true);
// sync setDP (OHNE trigger)
setState(dpSetMqttConnect, state, true);
} catch (e) {
log("MQTT parse error: " + e, "info");
}
done();
});
});
req.on("error", err => { handleHttpError("MQTT GET", err, { timestamp: dpTimestamp }); done(); });
req.end();
}
function setMqttConnect(enable) {
const payload = JSON.stringify({
sn: SN,
method: "HA.Mqtt.SetConfig",
params: {
config: {
enable: !!enable,
server: mqttBrokerIp,
port: mqttPort,
protocol: "mqtt",
username: mqttUsername,
password: mqttPassword
}
}
});
curlQueue.push({
fn: done => {
const req = http.request({
hostname: IP,
port: 80,
path: "/rpc",
method: "POST",
headers: { "Content-Type": "application/json", "Content-Length": Buffer.byteLength(payload) }
}, res => {
res.on("data", () => {});
res.on("end", () => done());
});
req.on("error", err => { handleHttpError("MQTT POST", err, { timestamp: dpTimestamp }); done(); });
req.write(payload);
req.end();
}
});
runQueue();
}
//------------------------------------------------------------------------------------
// JSON
//------------------------------------------------------------------------------------
async function processJson(obj) {
try {
if (!obj || !obj.product) return;
const product = obj.product;
const basePath = `${folderZendureApi}.${product}`;
// create product folder if not exist
if (!existsObject(basePath)) {
setObject(basePath, {
type: 'folder',
common: { name: product },
native: {},
});
}
// create main keys
const mainKeys = ['timestamp', 'messageId', 'sn', 'version', 'product'];
for (const key of mainKeys) {
if (obj[key] !== undefined) {
const statePath = `${basePath}.${key}`;
if (existsState(statePath)) {
setState(statePath, obj[key], true);
} else {
createState(statePath, obj[key], {
name: key,
type: typeof obj[key],
role: 'info',
read: true,
write: false,
});
}
}
}
// properties
if (obj.properties) {
await iter(`${basePath}.properties`, obj.properties);
}
// PackData
if (obj.packData && Array.isArray(obj.packData)) {
await iter(`${basePath}`, { packData: obj.packData, timestamp: obj.timestamp });
}
} catch (e) {
log(`Error processing JSON: ${e}`, 'info');
}
}
//------------------------------------------------------------------------------------
// Battery
//------------------------------------------------------------------------------------
function getBatteryType(sn, model) {
let batType = '';
if (sn?.startsWith('A')) batType = 'AB1000';
else if (sn?.startsWith('B')) batType = 'AB1000S';
else if (sn?.startsWith('C')) batType = sn[3] === 'F' ? 'AB2000S' : sn[3] === 'E' ? 'AB2000X' : 'AB2000';
else if (sn?.startsWith('F')) batType = 'AB3000X';
if (model?.trim()) batType = model.trim();
return batType || 'unknown';
}
//------------------------------------------------------------------------------------
// helper Iteration
//------------------------------------------------------------------------------------
async function iter(id, obj) {
try {
if (!obj || typeof obj !== "object") {
//log("iter: Ungültiges oder leeres Objekt übersprungen", "info");
return;
}
for (let i in obj) {
if (i === 'packData' && Array.isArray(obj[i])) {
const ts = obj.timestamp ? obj.timestamp * 1000 : Date.now();
for (const pack of obj[i]) {
if (!pack.sn) continue;
const sn = pack.sn;
const path = `${id}.packData.${sn}`;
if (!existsObject(path)) {
setObject(path, {
type: 'folder',
common: { name: sn },
native: {},
});
}
for (let [key, val] of Object.entries(pack)) {
const statePath = `${path}.${key}`;
switch (key) {
case 'batcur':
val = (val << 16 >> 16) / 10;
if (existsState(statePath)) {
setState(statePath, val, true);;
} else {
createState(statePath, val, {
name: 'Battery Current',
type: 'number',
desc: 'battery current',
role: 'value',
read: true,
write: false,
unit: 'A',
});
}
break;
case 'heatState':
if (existsState(statePath)) {
setState(statePath, val, true);
} else {
createState(statePath, val, {
name: 'Heat State, 0: Not heating, 1: heating',
type: 'number',
role: 'value',
read: true,
write: false,
});
}
break;
case 'maxTemp':
val = (val - 2731) / 10;
if (existsState(statePath)) {
setState(statePath, val, true);
} else {
createState(statePath, val, {
name: 'Highest Akku Temperature',
type: 'number',
desc: 'maximum temperature',
role: 'value',
read: true,
write: false,
unit: '°C',
});
}
break;
case 'maxVol':
val = val / 100;
if (existsState(statePath)) {
setState(statePath, val, true);
} else {
createState(statePath, val, {
name: 'Highest Cell Voltage',
type: 'number',
desc: 'highest cell voltage',
role: 'value',
read: true,
write: false,
unit: 'V',
});
}
break;
case 'minVol':
val = val / 100;
if (existsState(statePath)) {
setState(statePath, val, true);
} else {
createState(statePath, val, {
name: 'Lowest Cell Voltage',
type: 'number',
desc: 'lowest cell voltage',
role: 'value',
read: true,
write: false,
unit: 'V',
});
}
break;
case 'packType':
if (existsState(statePath)) {
setState(statePath, val, true);
} else {
createState(statePath, val, {
name: 'packType',
type: 'number',
role: 'value',
read: true,
write: false,
});
}
break;
case 'power':
if (existsState(statePath)) {
setState(statePath, val, true);
} else {
createState(statePath, val, {
name: 'Battery Power',
type: 'number',
role: 'value',
read: true,
write: false,
unit: 'W',
});
}
break;
case 'sn':
const batType = getBatteryType(val, pack.model);
// Modelltyp als Datenpunkt setzen/erstellen
const modelPath = `${path}.model`;
if (existsState(modelPath)) {
setState(modelPath, batType, true);
} else {
createState(modelPath, batType, {
name: 'Battery Model',
type: 'string',
role: 'text',
read: true,
write: false,
});
}
break;
case 'socLevel':
if (existsState(statePath)) {
setState(statePath, val, true);
} else {
createState(statePath, val, {
name: 'Battery SoC Level',
type: 'number',
role: 'value',
read: true,
write: false,
unit: '%',
});
}
break;
case 'softVersion':
if (existsState(statePath)) {
setState(statePath, val, true);
} else {
createState(statePath, val, {
name: 'Battery Software Version',
type: 'number',
role: 'value',
read: true,
write: false,
});
}
break;
case 'state':
if (existsState(statePath)) {
setState(statePath, val, true);
} else {
createState(statePath, val, {
name: 'Battery, 0: Standby, 1: Charging, 2: Discharging',
type: 'number',
role: 'value',
read: true,
write: false,
});
}
break;
case 'totalVol':
val = val / 100;
if (existsState(statePath)) {
setState(statePath, val, true);
} else {
createState(statePath, val, {
name: 'Total Voltage',
type: 'number',
desc: 'total voltage',
role: 'value',
read: true,
write: false,
unit: 'V',
});
}
break;
default:
if (existsState(statePath)) {
setState(statePath, val, true);
} else {
createState(statePath, val, {
name: key,
type: typeof val,
role: 'value',
read: true,
write: false,
});
}
break;
}
}
}
} else {
const ts = Date.now();
const statePath = `${id}.${i}`;
let val = obj[i];
switch (i) {
case 'BatVolt':
val = val / 100;
if (existsState(statePath)) {
setState(statePath, val, true);
} else {
createState(statePath, val, {
name: 'Battery Voltage',
type: 'number',
desc: 'battery voltage',
role: 'value',
read: true,
write: false,
unit: 'V',
});
}
break;
case 'chargeMaxLimit':
if (existsState(statePath)) {
setState(statePath, val, true);
} else {
createState(statePath, val, {
name: 'maximum Charge Power Limit',
type: 'number',
desc: 'Maximum permissible charging power',
role: 'value',
read: true,
write: false,
unit: 'W',
});
}
break;
case 'acMode':
if (existsState(statePath)) {
setState(statePath, val, true);
} else {
createState(statePath, val, {
name: 'acMode, 1: input mode / 2: output mode',
type: 'number',
desc: '1: charging / 2: discharging',
role: 'value',
read: true,
write: false,
});
}
break;
case 'dataReady':
if (existsState(statePath)) {
setState(statePath, val, true);
} else {
createState(statePath, val, {
name: 'Data Ready, 0: Not ready, 1: Ready',
type: 'number',
role: 'value',
read: true,
write: false,
});
}
break;
case 'dcStatus':
if (existsState(statePath)) {
setState(statePath, val, true);
} else {
createState(statePath, val, {
name: 'dcStatus, 0: Stopped, 1: Battery input, 2: Battery output',
type: 'number',
role: 'value',
read: true,
write: false,
});
}
break;
case 'electricLevel':
if (existsState(statePath)) {
setState(statePath, val, true);
} else {
createState(statePath, val, {
name: 'Total Battery Charge Level',
type: 'number',
desc: 'SoC',
role: 'value',
read: true,
write: false,
unit: '%',
});
}
break;
case 'FMVolt':
if (existsState(statePath)) {
setState(statePath, val, true);
} else {
createState(statePath, val, {
name: 'Voltage activation value',
type: 'number',
desc: 'SoC',
role: 'value',
read: true,
write: false,
unit: 'V',
});
}
break;
case 'gridInputPower':
if (existsState(statePath)) {
setState(statePath, val, true);
} else {
createState(statePath, val, {
name: 'Grid Input Power to Battery',
type: 'number',
role: 'value',
read: true,
write: false,
unit: 'W',
});
}
break;
case 'gridReverse':
if (existsState(statePath)) {
setState(statePath, val, true);
} else {
createState(statePath, val, {
name: '0: Disabled, 1: Allowed reverse flow, 2: Forbidden reverse flow',
type: 'number',
role: 'value',
read: true,
write: false,
});
}
break;
case 'gridStandard':
if (existsState(statePath)) {
setState(statePath, val, true);
} else {
createState(statePath, val, {
name: 'Grid connection standard 0: Germany 1: France 2: Austria',
type: 'number',
role: 'value',
read: true,
write: false,
});
}
break;
case 'gridState':
if (existsState(statePath)) {
setState(statePath, val, true);
} else {
createState(statePath, val, {
name: 'Grid connection state, 0: Not connected, 1: Connected',
type: 'number',
role: 'value',
read: true,
write: false,
});
}
break;
case 'heatState':
if (existsState(statePath)) {
setState(statePath, val, true);
} else {
createState(statePath, val, {
name: 'Heat State, 0: Not heating, 1: heating',
type: 'number',
role: 'value',
read: true,
write: false,
});
}
break;
case 'hyperTmp':
val = (val - 2731) / 10.0;
if (existsState(statePath)) {
setState(statePath, val, true);
} else {
createState(statePath, val, {
name: 'Enclosure Temperature',
type: 'number',
role: 'value',
read: true,
write: false,
unit: '°C',
});
}
break;
case 'inputLimit':
if (existsState(statePath)) {
setState(statePath, val, true);
} else {
createState(statePath, val, {
name: 'AC charging power limit to Battery',
type: 'number',
role: 'value',
read: true,
write: false,
unit: 'W',
});
}
break;
case 'inverseMaxPower':
if (existsState(statePath)) {
setState(statePath, val, true);
} else {
createState(statePath, val, {
name: 'Max inverter output Power Limit',
type: 'number',
role: 'value',
read: true,
write: false,
unit: 'W',
});
}
break;
case 'minSoc':
val = val / 10;
if (existsState(statePath)) {
setState(statePath, val, true);
} else {
createState(statePath, val, {
name: 'min SoC',
type: 'number',
desc: 'minimum Battery SoCset',
role: 'value',
read: true,
write: false,
unit: '%',
});
}
break;
case 'outputHomePower':
if (existsState(statePath)) {
setState(statePath, val, true);
} else {
createState(statePath, val, {
name: 'outputHomePower',
type: 'number',
role: 'value',
read: true,
write: false,
unit: 'W',
});
}
break;
case 'outputLimit':
if (existsState(statePath)) {
setState(statePath, val, true);
} else {
createState(statePath, val, {
name: 'Output power limit',
type: 'number',
role: 'value',
read: true,
write: false,
unit: 'W',
});
}
break;
case 'outputPackPower':
if (existsState(statePath)) {
setState(statePath, val, true);
} else {
createState(statePath, val, {
name: 'Output power to battery pack (charging)',
type: 'number',
role: 'value',
read: true,
write: false,
unit: 'W',
});
}
break;
case 'packInputPower':
if (existsState(statePath)) {
setState(statePath, val, true);
} else {
createState(statePath, val, {
name: 'Battery pack input power (discharging)',
type: 'number',
role: 'value',
read: true,
write: false,
unit: 'W',
});
}
break;
case 'packNum':
if (existsState(statePath)) {
setState(statePath, val, true);
} else {
createState(statePath, val, {
name: 'Number of battery packs',
type: 'number',
role: 'value',
read: true,
write: false,
});
}
break;
case 'packState':
if (existsState(statePath)) {
setState(statePath, val, true);
} else {
createState(statePath, val, {
name: 'Battery State, 0: Standby, 1: Charging, 2: Discharging',
type: 'number',
role: 'value',
read: true,
write: false,
});
}
break;
case 'pass':
if (existsState(statePath)) {
setState(statePath, val, true);
} else {
createState(statePath, val, {
name: 'Bypass, 0: No, 1: Yes',
type: 'number',
role: 'value',
read: true,
write: false,
});
}
break;
case 'pvStatus':
if (existsState(statePath)) {
setState(statePath, val, true);
} else {
createState(statePath, val, {
name: 'PV State producing, 0: Stopped, 1: Running',
type: 'number',
role: 'value',
read: true,
write: false,
});
}
break;
case 'acStatus':
if (existsState(statePath)) {
setState(statePath, val, true);
} else {
createState(statePath, val, {
name: 'AC state 0-2',
type: 'number',
role: 'value',
read: true,
write: false,
});
}
break;
case 'remainOutTime':
if (existsState(statePath)) {
setState(statePath, val, true);
} else {
createState(statePath, val, {
name: 'Estimated discharge time in minutes, if not predictable: 59940',
type: 'number',
role: 'value',
read: true,
write: false,
unit: 'min',
});
}
break;
case 'reverseState':
if (existsState(statePath)) {
setState(statePath, val, true);
} else {
createState(statePath, val, {
name: 'Reverse flow, 0: No, 1: Reverse flow',
type: 'number',
role: 'value',
read: true,
write: false,
});
}
break;
case 'rssi':
if (existsState(statePath)) {
setState(statePath, val, true);
} else {
createState(statePath, val, {
name: 'Received Signal Strength Indicator',
type: 'number',
role: 'value',
read: true,
write: false,
unit: 'dBm',
});
}
break;
case 'gridOffPower':
if (existsState(statePath)) {
setState(statePath, val, true);
} else {
createState(statePath, val, {
name: 'Off-grid power',
type: 'number',
role: 'value',
read: true,
write: false,
unit: 'W',
});
}
break;
case 'lampSwitch':
if (existsState(statePath)) {
setState(statePath, val, true);
} else {
createState(statePath, val, {
name: 'Lamp state 0:off 1:on',
type: 'number',
role: 'value',
read: true,
write: false,
});
}
break;
case 'gridOffMode':
if (existsState(statePath)) {
setState(statePath, val, true);
} else {
createState(statePath, val, {
name: 'Off-grid mode',
type: 'number',
role: 'value',
read: true,
write: false,
});
}
break;
case 'IOTState':
if (existsState(statePath)) {
setState(statePath, val, true);
} else {
createState(statePath, val, {
name: 'IoT connection',
type: 'number',
role: 'value',
read: true,
write: false,
});
}
break;
case 'fanSwitch':
if (existsState(statePath)) {
setState(statePath, val, true);
} else {
createState(statePath, val, {
name: 'Fan state 0:off 1:on',
type: 'number',
role: 'value',
read: true,
write: false,
});
}
break;
case 'fanSpeed':
if (existsState(statePath)) {
setState(statePath, val, true);
} else {
createState(statePath, val, {
name: 'Fan level',
type: 'number',
role: 'value',
read: true,
write: false,
});
}
break;
case 'faultLevel':
if (existsState(statePath)) {
setState(statePath, val, true);
} else {
createState(statePath, val, {
name: 'Fault severity',
type: 'number',
role: 'value',
read: true,
write: false,
});
}
break;
case 'bindstate':
if (existsState(statePath)) {
setState(statePath, val, true);
} else {
createState(statePath, val, {
name: 'Bind state',
type: 'number',
role: 'value',
read: true,
write: false,
});
}
break;
case 'VoltWakeup':
if (existsState(statePath)) {
setState(statePath, val, true);
} else {
createState(statePath, val, {
name: 'Voltage wake-up',
type: 'number',
role: 'value',
read: true,
write: false,
});
}
break;
case 'OldMode':
if (existsState(statePath)) {
setState(statePath, val, true);
} else {
createState(statePath, val, {
name: 'Legacy mode',
type: 'number',
role: 'value',
read: true,
write: false,
});
}
break;
case 'OTAState':
if (existsState(statePath)) {
setState(statePath, val, true);
} else {
createState(statePath, val, {
name: 'OTA state',
type: 'number',
role: 'value',
read: true,
write: false,
});
}
break;
case 'LCNState':
if (existsState(statePath)) {
setState(statePath, val, true);
} else {
createState(statePath, val, {
name: 'LCN state',
type: 'number',
role: 'value',
read: true,
write: false,
});
}
break;
case 'factoryModeState':
if (existsState(statePath)) {
setState(statePath, val, true);
} else {
createState(statePath, val, {
name: 'Phase switch',
type: 'number',
role: 'value',
read: true,
write: false,
});
}
break;
case 'phaseSwitch':
if (existsState(statePath)) {
setState(statePath, val, true);
} else {
createState(statePath, val, {
name: 'Factory mode',
type: 'number',
role: 'value',
read: true,
write: false,
});
}
break;
case 'is_error':
if (existsState(statePath)) {
setState(statePath, val, true);
} else {
createState(statePath, val, {
name: 'Error flag 0: no Error 1: Error',
type: 'number',
role: 'value',
read: true,
write: false,
});
}
break;
case 'smartMode':
if (existsState(statePath)) {
setState(statePath, val, true);
} else {
createState(statePath, val, {
name: '1: parameter written to RAM / 0: parameter is written to flash.',
type: 'number',
role: 'value',
read: true,
write: false,
});
}
break;
case 'socLimit':
if (existsState(statePath)) {
setState(statePath, val, true);
} else {
createState(statePath, val, {
name: 'socLimit-Info 0: normal, 1: Charge limit reached, 2: Discharge limit reached',
type: 'number',
role: 'value',
read: true,
write: false,
});
}
break;
case 'socSet':
val = val / 10;
if (existsState(statePath)) {
setState(statePath, val, true);
} else {
createState(statePath, val, {
name: 'max SoC',
type: 'number',
desc: 'maximum Battery SoCset',
role: 'value',
read: true,
write: false,
unit: '%',
});
}
break;
case 'socStatus':
if (existsState(statePath)) {
setState(statePath, val, true);
} else {
createState(statePath, val, {
name: 'Info of Calibrating, 0: No / 1: Calibrating',
type: 'number',
role: 'value',
read: true,
write: false,
});
}
break;
case 'solarInputPower':
if (existsState(statePath)) {
setState(statePath, val, true);
} else {
createState(statePath, val, {
name: 'Total Solar Input Power',
type: 'number',
role: 'value',
read: true,
write: false,
unit: 'W',
});
}
break;
case 'solarPower1':
if (existsState(statePath)) {
setState(statePath, val, true);
} else {
createState(statePath, val, {
name: 'Solar line 1 input power',
type: 'number',
role: 'value',
read: true,
write: false,
unit: 'W',
});
}
break;
case 'solarPower2':
if (existsState(statePath)) {
setState(statePath, val, true);
} else {
createState(statePath, val, {
name: 'Solar line 2 input power',
type: 'number',
role: 'value',
read: true,
write: false,
unit: 'W',
});
}
break;
case 'solarPower3':
if (existsState(statePath)) {
setState(statePath, val, true);
} else {
createState(statePath, val, {
name: 'Solar line 3 input power',
type: 'number',
role: 'value',
read: true,
write: false,
unit: 'W',
});
}
break;
case 'solarPower4':
if (existsState(statePath)) {
setState(statePath, val, true);
} else {
createState(statePath, val, {
name: 'Solar line 4 input power',
type: 'number',
role: 'value',
read: true,
write: false,
unit: 'W',
});
}
break;
case 'solarPower5':
if (existsState(statePath)) {
setState(statePath, val, true);
} else {
createState(statePath, val, {
name: 'Solar line 5 input power',
type: 'number',
role: 'value',
read: true,
write: false,
unit: 'W',
});
}
break;
case 'solarPower6':
if (existsState(statePath)) {
setState(statePath, val, true);
} else {
createState(statePath, val, {
name: 'Solar line 6 input power',
type: 'number',
role: 'value',
read: true,
write: false,
unit: 'W',
});
}
break;
case 'timeZone':
if (existsState(statePath)) {
setState(statePath, val, true);
} else {
createState(statePath, val, {
name: 'Timezone',
type: 'number',
role: 'value',
read: true,
write: false,
});
}
break;
case 'tsZone':
if (existsState(statePath)) {
setState(statePath, val, true);
} else {
createState(statePath, val, {
name: 'Timezone offset',
type: 'number',
role: 'value',
read: true,
write: false,
});
}
break;
case 'acCouplingState':
if (existsState(statePath)) {
setState(statePath, val, true);
} else {
createState(statePath, val, {
name: 'AC Coupling State',
type: 'number',
role: 'value',
read: true,
write: false
});
}
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";
const stringPath = statePath + "_String"; // acCouplingState_String
if (existsState(stringPath)) {
setState(stringPath, statusText, true);
} else {
createState(stringPath, statusText, { name: 'AC Coupling State Info', type: 'string', role: 'text', read: true, write: false });
}
break;
case 'dryNodeState':
if (existsState(statePath)) {
setState(statePath, val, true);
} else {
createState(statePath, val, {
name: 'Dry contact status 1: Connected 0: Connected(May be reversed depending on actual wiring)',
type: 'number',
role: 'value',
read: true,
write: false,
});
}
break;
case 'timestamp':
const timestampVal = obj[i];
const timestampFormatted = formatTime(timestampVal);
if (existsState(statePath)) setState(statePath, timestampVal, true);
else createState(statePath, timestampVal, {
name: 'System timestamp',
type: 'number',
desc: 'unix timestamp in Seconds since Jan 01 1970 (UTC)',
role: 'value',
read: true,
write: false,
});
if (existsState(id + '.timeUpdateTimestamp')) setState(id + '.timeUpdateTimestamp', timestampFormatted, true);
else createState(id + '.timeUpdateTimestamp', timestampFormatted, {
name: 'TimeUpdate (timestamp)',
type: 'string',
desc: 'unix timestamp in readable Format (timestamp)',
read: true,
write: false,
});
break;
case 'ts':
const tsVal = obj[i];
const tsFormatted = formatTime(tsVal);
if (existsState(statePath)) setState(statePath, tsVal, true);
else createState(statePath, tsVal, {
name: 'Unix timestamp (ts)',
type: 'number',
desc: 'unix timestamp in Seconds since Jan 01 1970 (UTC)',
role: 'value',
read: true,
write: false,
});
if (existsState(id + '.timeUpdateTs')) setState(id + '.timeUpdateTs', tsFormatted, true);
else createState(id + '.timeUpdateTs', tsFormatted, {
name: 'TimeUpdate (ts)',
type: 'string',
desc: 'unix timestamp in readable Format (ts)',
read: true,
write: false,
});
break;
default:
if (existsState(statePath)) {
setState(statePath, val, true);
} else {
createState(statePath, val, {
name: i,
type: typeof val,
role: 'value',
read: true,
write: false,
});
}
break;
}
}
}
} catch (e) {
log(`Error in iter: ${e}`, 'info');
}
}
Euch allen viel Sonne und weiterhin viel Freude.