Surfer1264(2nd) Es gibt extra für HA eine spezifische HA Bibliothek, die NodeRed exakt und perfekt an den HAEventbus andockt .
Gibt es ebenfalls im ioBroker, da läuft NodeRed als Adapter also "Integration" und man greift direkt auf die Datenpunkte aus ioBroker zu und kann auch direkt dahin schreiben, ebenfalls mit einem separaten iobroker Node-Set.
Das mit dem Javascript läuft super, ich sprenge mal den Kontext hier LOL Viel Spaß damit 😉
Sorry diese Software ist zu dämlich den Code komplett in ein Codefenster zu packen ... Naja ich hoffe Ihr hasst mich nicht dafür lösche es auch gerne 😃
`/************************************************************
- ZENDURE LADE-/ENTLADELOGIK – ioBroker JavaScript
- ----------------------------------------------------------
- Funktionen:
- ✅ Dynamisches Laden am Tag mit Netzbezugsgrenze
- ✅ Sticky-Charging: oben bleiben solange Netzbezug <= Ziel
- ✅ Rückregelung nur so weit wie nötig (keine 0W-Abstürze)
- ✅ Nacht-Entladen
- ✅ Notladen bei minVol <= 3.0V (mit Hysterese bis 3.3V)
- ✅ Entladestopp bei minVol <= minVol_Schwelle
- ✅ Akku-LEER-Flag verhindert Flattern bei Spannungserholung
- ✅ SOC=100% → Tag-Entladen
- ✅ SmartMode immer aktiv halten
- ✅ minVol-Schwelle über Schlechtwetter-Flag
************************************************************/
const DEBUG = false;
// ----------------------------------------------------------
// ✅ KONFIGURATION
// ----------------------------------------------------------
const NETZBEZUG_ZIEL = 100;
const MAX_LADELEISTUNG = 900;
const LADESTUFEN = [100, 200, 300, 400, 500, 600, 700, 800, 900];
const ENTLADELEISTUNG_NACHT = 1000;
const ENTLADELEISTUNG_TAG = 1000;
const SUNRISE_OFFSET_MIN = 0;
const SUNSET_OFFSET_MIN = 0;
// ----------------------------------------------------------
// ✅ DATENPUNKTE
// ----------------------------------------------------------
const dp = {
hausPower: 'sonoff.0.Lesekopf.MT691_Power_curr',
soc: 'zendure-solarflow.0.73bkTV.xxx.electricLevel',
// echter Messwert – TABU!
minVol: '0_userdata.0.Zendure_Werte.minVol',
// neuer Schwellwert
minVol_Schwelle: '0_userdata.0.Zendure_Werte.minVol_Schwelle',
acMode: 'zendure-solarflow.0.73bkTV.xxx.acMode',
inputLimit: 'zendure-solarflow.0.73bkTV.xxx.inputLimit',
outputLimit: 'zendure-solarflow.0.73bkTV.xxx.outputLimit',
smartMode: 'zendure-solarflow.0.73bkTV.xxx.smartMode',
schlechtWetter: '0_userdata.0.PV-Daten.Schlecht_Wetter',
setAcMode: 'zendure-solarflow.0.73bkTV.xxx.control.acMode',
setInputLimit: 'zendure-solarflow.0.73bkTV.xxx.control.setInputLimit',
setOutputLimit: 'zendure-solarflow.0.73bkTV.xxx.control.setOutputLimit',
setSmartMode: 'zendure-solarflow.0.73bkTV.xxx.control.smartMode',
sunrise: 'shuttercontrol.0.info.Sunrise',
sunset: 'shuttercontrol.0.info.Sunset',
};
// Status-DP für AkkuLeer und AkkuVoll Tag
const dpAkkuLeer = '0_userdata.0.Zendure_Status.AkkuLeer';
const dpAkkuVollTag = '0_userdata.0.Zendure_Status.AkkuVoll_Tag';
let akkuVollTag = false;
// Beim Start gespeicherten Zustand einlesen
if (existsState(dpAkkuVollTag)) {
akkuVollTag = getState(dpAkkuVollTag).val === true;
}
// ----------------------------------------------------------
// ✅ Hilfsfunktionen
// ----------------------------------------------------------
function setIfChanged(id, value) {
const old = getState(id).val;
if (old !== value) {
setState(id, value);
if (DEBUG) log(🔄 ${id} geändert: ${old} → ${value});
}
}
function toMinutes(str) {
const [h, m] = str.split(':').map(Number);
return h * 60 + m;
}
function nowMinutes() {
const d = new Date();
return d.getHours() * 60 + d.getMinutes();
}
function nearestStep(value) {
let best = 0;
for (let s of LADESTUFEN) {
if (value >= s) best = s;
}
return best;
}
// ----------------------------------------------------------
// ✅ Sticky Charging
// ----------------------------------------------------------
let lastLadeleistung = 0;
// ----------------------------------------------------------
// ✅ Notladen-Hysterese
// ----------------------------------------------------------
let notladenAktiv = false;
// ----------------------------------------------------------
// ✅ Akku-Leer-Flag
// ----------------------------------------------------------
let akkuLeer = false;
// Beim Start den gespeicherten Zustand einlesen
if (existsState(dpAkkuLeer)) {
akkuLeer = getState(dpAkkuLeer).val === true;
}
// ----------------------------------------------------------
// ✅ Schlechtwetter setzt NUR den Schwellwert
// ----------------------------------------------------------
function updateMinVol() {
const schlecht = getState(dp.schlechtWetter).val;
const newMinVol = schlecht ? 3.2 : 3.1;
setIfChanged(dp.minVol_Schwelle, newMinVol);
if (DEBUG) {
log(schlecht ? '🌧️ Schlechtwetter → minVol_Schwelle = 3.2V' : '☀️ Normalbetrieb → minVol_Schwelle = 3.1V');
}
}
updateMinVol();
on({ id: dp.schlechtWetter, change: 'ne' }, () => updateMinVol());
// ----------------------------------------------------------
// ✅ HAUPTLOGIK
// ----------------------------------------------------------
schedule('0 * * * * *', async () => {
const hausPower = getState(dp.hausPower).val;
const soc = getState(dp.soc).val;
const minVol = getState(dp.minVol).val; // echter Messwert
const minVol_S = getState(dp.minVol_Schwelle).val; // Schwellwert
const smartMode = getState(dp.smartMode).val;
const sunrise = getState(dp.sunrise).val;
const sunset = getState(dp.sunset).val;
const now = nowMinutes();
const sunriseMin = toMinutes(sunrise) + SUNRISE_OFFSET_MIN;
const sunsetMin = toMinutes(sunset) + SUNSET_OFFSET_MIN;
// ------------------------------------------------------
// ✅ SmartMode aktiv halten
// ------------------------------------------------------
if (smartMode !== true) {
if (DEBUG) log('⚙️ SmartMode war AUS – wird aktiviert');
setIfChanged(dp.setSmartMode, true);
}
// ------------------------------------------------------
// ✅ BLOCK A - NOTLADEN – mit Hysterese
// ------------------------------------------------------
if (minVol <= 3.0) {
if (!notladenAktiv && DEBUG) log('⚠️ NOTLADEN gestartet – minVol <= 3.0V');
notladenAktiv = true;
}
if (notladenAktiv && minVol >= 3.3) {
if (DEBUG) log('✅ NOTLADEN beendet – minVol >= 3.3V');
notladenAktiv = false;
}
if (notladenAktiv) {
lastLadeleistung = 900;
setIfChanged(dp.setAcMode, 1);
setIfChanged(dp.setInputLimit, 900);
setIfChanged(dp.setOutputLimit, 0);
return;
}
// ------------------------------------------------------
// ✅ BLOCK B - ENTLADESTOPP – Akku leer Flag
// ------------------------------------------------------
// Akku wird leer, wenn minVol unter die Schwelle fällt
if (minVol <= minVol_S) {
if (!akkuLeer && DEBUG) log(`⛔ Akku LEER – minVol <= Schwelle (${minVol_S}V)`);
akkuLeer = true;
setIfChanged(dpAkkuLeer, true);
}
// Rückkehr-Hysterese: 0.1V über der aktuellen Schwelle
if (akkuLeer && minVol >= minVol_S + 0.1) {
if (DEBUG) log(`✅ Akku wieder OK – minVol >= ${minVol_S + 0.1}V`);
akkuLeer = false;
setIfChanged(dpAkkuLeer, false);
}
// Wenn Akku leer ist → Entladen verhindern, aber Laden am Tag erlauben
if (akkuLeer) {
// TAG → Laden erlaubt
if (now >= sunriseMin && now <= sunsetMin) {
if (DEBUG) log('☀️ Akku leer, aber TAG → Laden erlaubt');
// Tag-Logik darf weiterlaufen → KEIN return
} else {
// NACHT → Entladen komplett blockieren
if (DEBUG) log('🌙 Akku leer → Nacht-Entladen blockiert');
lastLadeleistung = 0;
setIfChanged(dp.setAcMode, 1);
setIfChanged(dp.setInputLimit, 0);
setIfChanged(dp.setOutputLimit, 0);
return;
}
}
// ------------------------------------------------------
// ✅ BLOCK C - TAG-ENTLADEN – Sticky wenn SOC einmal 100% erreicht
// ------------------------------------------------------
if (now >= sunriseMin && now <= sunsetMin) {
// Wenn SOC 100% erreicht → Sticky-Flag setzen
if (soc >= 100) {
if (!akkuVollTag && DEBUG) log('🔋 SOC = 100% → AkkuVoll_Tag gesetzt');
akkuVollTag = true;
setIfChanged(dpAkkuVollTag, true);
}
// Wenn Flag aktiv → immer Tag-Entladen
if (akkuVollTag) {
if (DEBUG) log('🔋 AkkuVoll_Tag aktiv → Tag-Entladen erzwungen');
lastLadeleistung = 0;
setIfChanged(dp.setAcMode, 2);
setIfChanged(dp.setInputLimit, 0);
setIfChanged(dp.setOutputLimit, ENTLADELEISTUNG_TAG);
return;
}
}
// ------------------------------------------------------
// ✅ BLOCK D - TAG – dynamisches Laden
// ------------------------------------------------------
if (now >= sunriseMin && now <= sunsetMin) {
const netzbezug = hausPower;
const ueberschuss = -hausPower + NETZBEZUG_ZIEL;
let ladeWunsch = nearestStep(ueberschuss);
if (ladeWunsch < 0) ladeWunsch = 0;
if (ladeWunsch > MAX_LADELEISTUNG) ladeWunsch = MAX_LADELEISTUNG;
// ✅ HOCHREGELN immer erlaubt
if (ladeWunsch > lastLadeleistung) {
lastLadeleistung = ladeWunsch;
}
// ✅ RUNTERREGELN nur wenn Netzbezug > Ziel
if (ladeWunsch < lastLadeleistung) {
if (netzbezug > NETZBEZUG_ZIEL) {
const differenz = netzbezug - NETZBEZUG_ZIEL;
let neueLeistung = lastLadeleistung - differenz;
neueLeistung = nearestStep(neueLeistung);
if (neueLeistung < 0) neueLeistung = 0;
lastLadeleistung = neueLeistung;
ladeWunsch = neueLeistung;
} else {
ladeWunsch = lastLadeleistung;
}
} else {
lastLadeleistung = ladeWunsch;
}
if (DEBUG) {
log(`☀️ TAGMODUS – Überschuss: ${ueberschuss}W → Ladeleistung: ${ladeWunsch}W`);
}
setIfChanged(dp.setAcMode, 1);
setIfChanged(dp.setInputLimit, ladeWunsch);
setIfChanged(dp.setOutputLimit, 0);
return;
}
// ------------------------------------------------------
// ✅ BLOCK E - NACHT BEGINNT → AkkuVoll_Tag zurücksetzen
// ------------------------------------------------------
if (now > sunsetMin && akkuVollTag) {
if (DEBUG) log('🌙 Sonnenuntergang → AkkuVoll_Tag zurückgesetzt');
akkuVollTag = false;
setIfChanged(dpAkkuVollTag, false);
}
// ------------------------------------------------------
// ✅ BLOCK F - NACHT – Entladen
// ------------------------------------------------------
if (DEBUG) log('🌙 NACHTMODUS – Entladen');
lastLadeleistung = 0;
setIfChanged(dp.setAcMode, 2);
setIfChanged(dp.setInputLimit, 0);
setIfChanged(dp.setOutputLimit, ENTLADELEISTUNG_NACHT);
});`