// ===================== KONFIGURATION =====================
const hyper1 = {
name: 'Hyper1',
inputLimitDP: 'zendure-solarflow.0.gDa3tb.UP5B874L.control.setInputLimit',
inputDP: 'zendure-solarflow.0.gDa3tb.UP5B874L.gridInputPower',
outputLimitDP: 'zendure-solarflow.0.gDa3tb.UP5B874L.control.setOutputLimit',
outputDP: 'zendure-solarflow.0.gDa3tb.UP5B874L.outputHomePower',
acModeDP: 'zendure-solarflow.0.gDa3tb.UP5B874L.control.acMode',
pvLeistungDP: 'zendure-solarflow.0.gDa3tb.UP5B874L.solarInputPower',
socDP: 'zendure-solarflow.0.gDa3tb.UP5B874L.electricLevel',
};
const hyper2 = {
name: 'Hyper2',
inputLimitDP: 'zendure-solarflow.0.gDa3tb.4jkWG0V5.control.setInputLimit',
inputDP: 'zendure-solarflow.0.gDa3tb.4jkWG0V5.gridInputPower',
outputLimitDP: 'zendure-solarflow.0.gDa3tb.4jkWG0V5.control.setOutputLimit',
outputDP: 'zendure-solarflow.0.gDa3tb.4jkWG0V5.outputHomePower',
acModeDP: 'zendure-solarflow.0.gDa3tb.4jkWG0V5.control.acMode',
socDP: 'zendure-solarflow.0.gDa3tb.4jkWG0V5.electricLevel',
};
const logHTMLDP = '0_userdata.0.Eigene_Variablen.PV.Solarflow.SolarflowLog'; // Datenpunkt fuer Logausgabe
const hausverbrauchDP = 'mqtt.0.openWB.global.WHouseConsumption'; // Hausverbrauch
const evuLeistungDP = 'shelly.1.shellypro3em#fce8c0db1e70#1.EM0.TotalActivePower'; // EVU Datenpunkt
const hausverbrauchBisherDP = 'mqtt.0.openWB.global.TaeglicherHausverbrauchKwh'; // Taeglicher bisheriger Hausverbrauch
const pv1LeistungDP = 'mqtt.0.openWB.pv.1.W'; // Leistung externe PV-Anlage
const preisLadenModusDP = '0_userdata.0.Eigene_Variablen.PV.Solarflow.PreisLadenModus'; // Preisladenmodus: 0 = aus, 1 = wenn noetig (PV reicht nicht), 2 = immer
const stromGuenstigDP = 'tibberlink.0.Homes.8f8a4278-17b9-48fb-b725-b60bd1485ac6.Eigene_Werte.load_batterie'; // Akku's ueber Preis laden sinnvoll ?
const stromTeuerDP = 'tibberlink.0.Homes.8f8a4278-17b9-48fb-b725-b60bd1485ac6.Eigene_Werte.expensive_single_hour'; // Akku's entladen sinnvoll ?
const speicherPrioGrenzeSocDP = '0_userdata.0.Eigene_Variablen.PV.Solarflow.Grenze_Prio_SOC'; // Ladegrenze Hyper
const pvForecastRestDP = 'pvforecast.1.summary.energy.nowUntilEndOfDay'; // Rest PV (Hyper und externe Anlage) bis Tagesende
// Parameter fuer eAuto laden
const ladenVorrangDP = '0_userdata.0.Eigene_Variablen.PV.Laden_Vorrang'; // Vorrang Laden 0 = Akkus, 1 = eAuto
const ladeleistungReserveDP = '0_userdata.0.Eigene_Variablen.openWB.Ladeleistung_Reserve'; // Reserve fuer Akku laden wenn eAuto laden aktiv ist
const autoMaxSOCDP = '0_userdata.0.Eigene_Variablen.Auto.SoC_Set'; // Soll SOC eAuto
const cargeModeDP = '0_userdata.0.Eigene_Variablen.openWB.ChargeMode'; // Lademodus Aus = 3, Sofort = 0, Min+PC = 1, PV = 2
const autoSocDP = 'mqtt.0.openWB.lp.1.%Soc'; // Ist SOC eAuto
const openWBLadepunkt1DP = 'mqtt.0.openWB.lp.1.W'; // Ladeleistung eAuto
const autoPlugstatDP = 'mqtt.0.openWB.lp.1.boolPlugStat' // eAuto angesteckt ?
let autoLadeStartZeit = null;
const socMin = 10; // Minimaler SOC Akku
const socMax = 100; // Maximaler SOC Akku
const ladenStartSchwelle = 100; // Schwelle ab der das Laden ueber externe PV startet
const entladenStartSchwelle = 50; // Schwelle ab der das Entladen der Akku's startet
const netzPufferWatt_l = 50; // Puffer beim Laden
const netzPufferWatt_el = 20; // Puffer bein Entladen
const maxLeistung = xxx; // Max Leistung pro Hyper
const maxLadeleistungGesamt = maxLeistung * 2;
const kapazitaetKWh1 = x.y; // Kapazitaet Hyper 1 in kWh
const kapazitaetKWh2 = x.y; // Kapazitaet Hyper 2 in kWh
let lastSetLadeleistung = {h1: 0, h2: 0};
let lastSetEntladeleistung = {h1: 0, h2: 0};
// Globale Variablen fuer Modus-Hysterese
let lastMode = 0; // 0=stop,1=laden,2=entladen
let lastSwitchTime = 0; // Timestamp in ms
let entladeStartZeit = null;
const entladeVerzoegerung = 15 * 1000; // 15 Sekunden // Timeout zwischen laden und entladen
// Logging-Funktion VIS
const maxDurchlaeufeImLog = 2;
let logDurchlaeufe = [];
let lastLogLadeleistung = null;
// ========================== Ab hier keine Konfiguration mehr notwendig ===============================
function visLog(meldung, typ = 'info', amAnfang = false) {
const zeit = new Date().toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit', second: '2-digit' });
const farben = {
info: '', ok: '#28a745', warn: '#d39e00', error: '#dc3545',
laden: '#007f3f', entladen: '#0057b7', stop: '#6c757d',
mode: '#17a2b8', header: '#6c757d', debug: '#999'
};
const style = farben[typ] ? ` style="color:${farben[typ]}"` : '';
const eintrag = `
${zeit} | ${meldung} |
`;
if (logDurchlaeufe.length === 0) logDurchlaeufe.unshift([]);
if (amAnfang) logDurchlaeufe[0].unshift(eintrag);
else logDurchlaeufe[0].push(eintrag);
while (logDurchlaeufe.length > maxDurchlaeufeImLog) logDurchlaeufe.pop();
let html = '';
for (let i = 0; i < logDurchlaeufe.length; i++) {
html += logDurchlaeufe[i].join('');
if (i < logDurchlaeufe.length - 1) {
html += `──────────────────────────── |
`;
}
}
setState(logHTMLDP, ``);
log(meldung);
}
// Hilfsfunktionen
function getStateVal(id, def = 0) {
const s = getState(id);
return (s && typeof s.val !== 'undefined') ? s.val : def;
}
function getSOC(h) {
return getStateVal(h.socDP);
}
function getKWh(soc, kapazitaet) {
return (soc / 100) * kapazitaet;
}
function isPreisGuenstig() {
return getStateVal(stromGuenstigDP, false) === true;
}
function isPreisTeuer() {
return getStateVal(stromTeuerDP, false) === true;
}
function verteileLeistungNachSOC(gesamt, soc1, soc2, modus = 'laden') {
const maxProHyper = maxLeistung;
let l1 = 0, l2 = 0;
if (gesamt <= 0) return [0, 0];
if (modus === 'laden') {
if (soc1 < 100 || soc2 < 100) {
const sumSOC = soc1 + soc2 || 1;
l1 = Math.round(gesamt * (100 - soc1) / (200 - sumSOC));
l2 = Math.round(gesamt * (100 - soc2) / (200 - sumSOC));
}
} else if (modus === 'entladen') {
if (soc1 <= socMin) l2 = gesamt;
else if (soc2 <= socMin) l1 = gesamt;
else {
l1 = Math.round(gesamt / 2);
l2 = gesamt - l1;
}
}
if (l1 > maxProHyper) l1 = maxProHyper;
if (l2 > maxProHyper) l2 = maxProHyper;
return [l1, l2];
}
function setLademodus(h, leistung) {
setState(h.acModeDP, 1);
setState(h.inputLimitDP, leistung);
}
function setEntlademodus(h, leistung) {
setState(h.acModeDP, 2);
setState(h.outputLimitDP, leistung);
}
function stopHyper(h) {
setState(h.outputLimitDP, 0);
setState(h.inputLimitDP, 0);
}
function pruefeObSonnenuntergangErreicht() {
const jetzt = new Date();
const sunsetUhrzeit = getState('0_userdata.0.Eigene_Variablen.Zeiten.Sonnenuntergang_minus_60').val; // z.B. "20:29"
if (!sunsetUhrzeit || typeof sunsetUhrzeit !== 'string' || !sunsetUhrzeit.match(/^\d{2}:\d{2}$/)) {
log("⚠️ Sonnenuntergangszeit ungueltig: " + sunsetUhrzeit, 'warn');
return;
}
// Aktuelles Datum + Sonnenuntergangszeit kombinieren
const [stunden, minuten] = sunsetUhrzeit.split(':').map(Number);
const sunset = new Date(jetzt.getFullYear(), jetzt.getMonth(), jetzt.getDate(), stunden, minuten, 0);
if (!amountHoursHeuteBerechnet && jetzt >= sunset) {
amountHoursHeuteBerechnet = true;
const soc1 = getState(hyper1.socDP).val;
const soc2 = getState(hyper2.socDP).val;
const hausverbrauchProStunde = 0.6; // fixer Nachtverbrauch
passeAmountHoursAn(soc1, soc2, hausverbrauchProStunde);
}
}
function passeAmountHoursAn(soc1, soc2, verbrauchProStunde = 0.6) {
const kap1 = getKWh(soc1, kapazitaetKWh1);
const kap2 = getKWh(soc2, kapazitaetKWh2);
const gesamtKWh = kap1 + kap2;
// Zeiten holen
const sunsetStr = getStateVal('0_userdata.0.Eigene_Variablen.Zeiten.Sonnenuntergang_minus_60', '');
const sunriseStr = getStateVal('0_userdata.0.Eigene_Variablen.Zeiten.Sonnenaufgang_plus_60', '');
if (!sunsetStr.match(/^\d{2}:\d{2}$/) || !sunriseStr.match(/^\d{2}:\d{2}$/)) {
log(`❌ Fehler: Sonnenzeiten ungueltig – Sunset: ${sunsetStr}, Sunrise: ${sunriseStr}`, 'warn');
return;
}
const now = new Date();
const [sunsetH, sunsetM] = sunsetStr.split(':').map(Number);
const [sunriseH, sunriseM] = sunriseStr.split(':').map(Number);
const sunset = new Date(now.getFullYear(), now.getMonth(), now.getDate(), sunsetH, sunsetM);
const sunrise = new Date(now.getFullYear(), now.getMonth(), now.getDate() + 1, sunriseH, sunriseM);
const nachtDauerStunden = (sunrise - sunset) / (1000 * 60 * 60);
const abdeckbareStunden = gesamtKWh / verbrauchProStunde;
const fehlendeStunden = Math.max(0, nachtDauerStunden - abdeckbareStunden);
// AmountHours linear zwischen 2 (voller Akku) und 12 (Akku leer)
let amountHours;
if (fehlendeStunden <= 0) {
amountHours = 4;
} else if (fehlendeStunden >= nachtDauerStunden) {
amountHours = 12;
} else {
const skala = (fehlendeStunden / nachtDauerStunden); // 0...1
amountHours = 4 + skala * 10; // linear zwischen 2 und 12
}
amountHours = Math.round(amountHours);
setState('tibberlink.0.Homes.8f8a4278-17b9-48fb-b725-b60bd1485ac6.Calculations.2.AmountHours', amountHours);
visLog(`🌙 Nacht: ${nachtDauerStunden.toFixed(1)}h | Akku reicht fuer ~${abdeckbareStunden.toFixed(1)}h → fehlend: ${fehlendeStunden.toFixed(1)}h → AmountHours: ${amountHours}`, 'info');
}
// ===================== HAUPTSTEUERUNG =====================
function steuerung(h1, h2) {
logDurchlaeufe.unshift([]);
const jetztTs = Date.now();
const jetzt = new Date();
// ================= PV-Leistung erfassen =================
let pvLeistung1 = -1 * getStateVal(pv1LeistungDP, 0); // Solaredge
let pvLeistung2 = getStateVal(hyper1.pvLeistungDP, 0); // Hyper1
const pvLeistungRaw = pvLeistung1 + pvLeistung2;
// ================= SOC & Verbrauch =================
const soc1 = getSOC(h1);
const soc2 = getSOC(h2);
const avgSOC = (soc1 + soc2)/2;
const speicherMinSOC = Math.min(soc1, soc2);
const aktuelleEinspeisung = getStateVal(evuLeistungDP, 0);
const ladepunkt1Leistung = getStateVal(openWBLadepunkt1DP, 0);
const hausverbrauch = getStateVal(hausverbrauchDP, 0);
const hausverbrauch_lp1 = hausverbrauch + ladepunkt1Leistung;
const hausverbrauchBisher = getStateVal(hausverbrauchBisherDP, 0);
// ================= eAuto Parameter =================
const ladenVorrang = getStateVal(ladenVorrangDP, false);
const speicherPrioGrenze = getStateVal(speicherPrioGrenzeSocDP, 80);
const autoPlugstat = getStateVal(autoPlugstatDP, 0);
const chargeMode = getStateVal(cargeModeDP, 0);
// ================= Lade-Prioritaet =================
const nettoEinspeisung = aktuelleEinspeisung + ladepunkt1Leistung;
if (ladenVorrang === 0) { // Speicher vor Auto
if (speicherMinSOC < speicherPrioGrenze) {
if (nettoEinspeisung <= -1500 && autoLadeStartZeit === null) {
autoLadeStartZeit = Date.now();
visLog(`⏳ Auto-Ladestart verzoegert – Speicher < Grenze (${speicherMinSOC}% < ${speicherPrioGrenze}%)`, 'info');
}
} else {
autoLadeStartZeit = null; // Reset
visLog(`🔋 SOC ${speicherMinSOC}% ≥ Grenze ${speicherPrioGrenze}% → Auto-Laden frei`, 'ok');
}
}
if (ladenVorrang === 1 && autoPlugstat === 1 && chargeMode != 0) {
// ✅ Pruefen ob Auto-SOC schon am Max ist
let autoSOC = getStateVal(autoSocDP);
let autoMaxSOC = getStateVal(autoMaxSOCDP, 100);
if (autoSOC >= autoMaxSOC) {
setState(ladenVorrangDP, false);
visLog(`🚗 Auto-SOC ${autoSOC}% erreicht Ziel ${autoMaxSOC}% → Vorrang beendet`, 'warn');
return; // Abbrechen, damit Speicher normal weiterlaeuft
}
// Reserve aus Datenpunkt
let reserveDP = getStateVal(ladeleistungReserveDP, 0);
let reserve = 0;
if (aktuelleEinspeisung < -60) {
// Einspeisung vorhanden → nur so viel wie real eingespeist wird
reserve = Math.min(reserveDP, -aktuelleEinspeisung);
} else {
// kein ueberschuss
reserve = 0;
visLog("⚡ Netzbezug oder PV-Reserve < 60 W → Speicher-Reserve auf 0 gesetzt", "warn");
}
// Auf beide Hyper nach SOC verteilen
let [l1, l2] = verteileLeistungNachSOC(reserve, soc1, soc2, 'laden');
if (soc1 >= 100) stopHyper(h1); else setLademodus(h1, l1);
if (soc2 >= 100) stopHyper(h2); else setLademodus(h2, l2);
visLog(`🚗 Auto hat Vorrang – Speicher-Reserve: ${Math.round(reserve)} W ` + `(EVU ${Math.round(aktuelleEinspeisung)} W, Limit ${reserveDP} W) → ` + `H1 ${l1}W | H2 ${l2}W`,'warn');
return; // restliche Logik ueberspringen
}
// ================= Forecast & Ziel =================
const pvRest = getStateVal(pvForecastRestDP, 0) / 1000; // Rest-PV in kWh
const zielSOC = socMax;
const preisGuenstig = isPreisGuenstig();
const preisTeuer = isPreisTeuer();
const stundeJetzt = jetzt.getHours() + jetzt.getMinutes() / 60;
const verbrauchProStunde = stundeJetzt > 1
? hausverbrauchBisher / stundeJetzt
: hausverbrauchBisher; // Durchschnitt bis jetzt
const gesamtKWh = getKWh(soc1, kapazitaetKWh1) + getKWh(soc2, kapazitaetKWh2);
const fehlendeKapazitaet_kWh = Math.max(0, ((zielSOC / 100) * (kapazitaetKWh1 + kapazitaetKWh2)) - gesamtKWh);
// ================= Prognose Restverbrauch realistischer =================
const sunsetStr = getStateVal('0_userdata.0.Eigene_Variablen.Zeiten.Sonnenuntergang_minus_60', '20:00');
let prognoseRestverbrauch = 0;
if (sunsetStr.match(/^\d{2}:\d{2}$/)) {
const [sunsetH, sunsetM] = sunsetStr.split(':').map(Number);
const sunset = new Date(jetzt.getFullYear(), jetzt.getMonth(), jetzt.getDate(), sunsetH, sunsetM);
const stundenBisSunset = Math.max(0, (sunset - jetzt) / (1000 * 60 * 60));
prognoseRestverbrauch = verbrauchProStunde * stundenBisSunset;
} else {
visLog(`⚠️ Sonnenuntergangszeit ungueltig: ${sunsetStr}`, 'warn');
}
visLog(`🔮 Prognose Restverbrauch: ${prognoseRestverbrauch.toFixed(2)} kWh bis Sunset (${sunsetStr})`, 'info');
// Jetzt erst PV-Rest abzueglich Prognose berechnen
const pvRestEffektiv = Math.max(0, pvRest - prognoseRestverbrauch);
// ================= Energie aus Akkus =================
const hyper1Leistung = getStateVal(hyper1.inputDP, 0) - getStateVal(hyper1.outputDP, 0);
const hyper2Leistung = getStateVal(hyper2.inputDP, 0) - getStateVal(hyper2.outputDP, 0);
const leistungAusAkkus = aktuelleEinspeisung - hyper1Leistung - hyper2Leistung;
let preisLadenModus = getStateVal(preisLadenModusDP, 1);
// Lesbarer Text fuer den PreisLaden-Modus
let preisLadenText = '';
switch (preisLadenModus) {
case 0: preisLadenText = 'aus'; break;
case 1: preisLadenText = 'wenn noetig'; break;
case 2: preisLadenText = 'immer'; break;
default: preisLadenText = 'unbekannt'; break;
}
visLog('⚙️ Steuerung gestartet', 'header', true);
visLog(`🏠 Haus: ${hausverbrauch_lp1}W | ☀️ PV: ${pvLeistungRaw}W`);
visLog(`🔋 SOC Ø: ${avgSOC.toFixed(1)}% | Ziel-SOC: ${zielSOC}% | Energie: ${gesamtKWh.toFixed(2)}kWh`);
visLog(`💰 Preis: ${preisTeuer ? 'teuer' : preisGuenstig ? 'guenstig' : 'neutral'} | PreisLaden: ${preisLadenText}`);
visLog(`📈 Fehlende Kapazitaet: ${fehlendeKapazitaet_kWh.toFixed(2)} kWh | 🔮 PV Rest effektiv: ${pvRestEffektiv.toFixed(2)} kWh`);
// ================= Laden/Entladen bestimmen =================
let ladenErlaubt = false;
// PV-ueberschuss-Erkennung
if (leistungAusAkkus < -ladenStartSchwelle && speicherMinSOC < zielSOC) {
ladenErlaubt = true;
visLog('☀️ PV-ueberschuss → Laden erlaubt', 'ok');
}
// Preis-Laden Logik (0 = aus, 1 = wenn noetig, 2 = immer)
if (!ladenErlaubt && preisGuenstig) {
const sonnenstandSpaet = stundeJetzt >= 17;
const prognoseReichtNichtMehr = fehlendeKapazitaet_kWh > pvRestEffektiv;
const socZiemlichLeer = avgSOC < 90;
if ((preisLadenModus === 1 && prognoseReichtNichtMehr) || (preisLadenModus === 2 && (prognoseReichtNichtMehr || (sonnenstandSpaet && socZiemlichLeer))))
{
ladenErlaubt = true;
visLog(`💶 Preis-Laden aktiviert (Modus ${preisLadenModus} | noetig: ${prognoseReichtNichtMehr ? 'ja' : 'nein'})`, 'laden');
}
}
// ========== Verzoegerte Entladefreigabe ==========
let entladenErlaubt = false;
const entladeBedingung = (ladepunkt1Leistung <= 500 && leistungAusAkkus > entladenStartSchwelle);
if (entladeBedingung) {
if (entladeStartZeit === null) {
entladeStartZeit = jetztTs;
visLog(`⏳ Entlade-Bedingung erkannt – Warte ${entladeVerzoegerung/1000}s`, 'info');
} else if (jetztTs - entladeStartZeit >= entladeVerzoegerung) {
entladenErlaubt = true;
}
} else {
entladeStartZeit = null;
}
if (entladenErlaubt) {
if (preisTeuer) entladenErlaubt = true;
else if (preisGuenstig) entladenErlaubt = avgSOC > 80;
else entladenErlaubt = avgSOC > 10;
}
// ================= Ziel-Modus =================
const aktuellerModus = getStateVal(h1.acModeDP, 0);
let zielModus = 0;
if (ladenErlaubt) zielModus = 1;
else if (entladenErlaubt) zielModus = 2;
if (zielModus === 1 && soc1 >= 100 && soc2 >= 100) zielModus = aktuellerModus;
if (zielModus === 2 && soc1 <= socMin && soc2 <= socMin) zielModus = aktuellerModus;
if (zielModus !== aktuellerModus) {
if (zielModus === 1 || zielModus === 2) {
lastMode = zielModus;
lastSwitchTime = jetztTs;
visLog(`🔄 Moduswechsel → ${zielModus === 1 ? 'Laden' : 'Entladen'}`, 'mode');
} else {
visLog(`⏹ Moduswechsel auf 0 (Stopp) → AC-Modus bleibt`, 'stop');
}
}
// ================= Aktion je Modus =================
if (zielModus === 1) {
// --- Laden ---
let ueberschuss = Math.max(0, -leistungAusAkkus - netzPufferWatt_l);
let ladeLimit = ueberschuss;
if (ladenVorrang && ladepunkt1Leistung > 0 && chargeMode === 2) {
ladeLimit = Math.min(getStateVal(ladeleistungReserveDP, maxLadeleistungGesamt), ladeLimit);
visLog('🔌 Lade-Vorrang aktiv & Auto laedt → Ladeleistung begrenzt', 'warn');
}
let [l1, l2] = verteileLeistungNachSOC(Math.min(ladeLimit, maxLadeleistungGesamt), soc1, soc2, 'laden');
// --- Preis-Laden Mindestleistung ---
if (preisGuenstig) {
if (preisLadenModus === 2) {
[l1, l2] = [maxLeistung, maxLeistung];
visLog('💶 Immer-Preis-Laden aktiv (guenstig) → beide Hyper ${maxLeistung}W', 'laden');
} else if (preisLadenModus === 1 && fehlendeKapazitaet_kWh > pvRestEffektiv) {
[l1, l2] = [maxLeistung, maxLeistung];
visLog('💶 Preisguenstig & PV reicht nicht → Mindestladung ${maxLeistung}W/Hyper', 'laden');
}
}
if (soc1 >= 100) stopHyper(h1); else setLademodus(h1, l1);
if (soc2 >= 100) stopHyper(h2); else setLademodus(h2, l2);
visLog(`⚡ Ladeleistung gesamt: ${Math.round(l1+l2)}W`, 'laden');
} else if (zielModus === 2) {
// --- Entladen ---
const maxEntladeLeistung = Math.max(0, leistungAusAkkus + netzPufferWatt_el); // nur bis Hausverbrauch + Puffer
const entladeGesamt = Math.min(maxEntladeLeistung, maxLadeleistungGesamt);
// Auf Hyper1/2 verteilen
const [e1, e2] = verteileLeistungNachSOC(entladeGesamt, soc1, soc2, 'entladen');
if ((e1 || e2) && (soc1 >= socMin || soc2 >= socMin)) {
setEntlademodus(h1, e1);
setEntlademodus(h2, e2);
visLog(`⚡ Entladen (kein Netz) → ${h1.name} ${e1}W | ${h2.name} ${Math.round(e2)}W`, 'entladen');
} else {
stopHyper(h1); stopHyper(h2);
visLog('⚠️ Keine Entladeleistung → gestoppt', 'warn');
}
} else {
// --- Stopp ---
stopHyper(h1); stopHyper(h2);
visLog('⏹ Speicher STOP', 'stop');
}
}
// Trigger
//on({ id: evuLeistungDP, change: "ne" }, () => steuerung(hyper1, hyper2)); // Bei aenderung EVU Leistung
schedule('*/10 * * * * *', () => steuerung(hyper1, hyper2)); // Alle 10 Sekunden
//schedule('*/5 * * * *', () => steuerung(hyper1, hyper2)); // Alle 5 Minuten
// ========================== AmountHours einmal taeglich zum Sonnenuntergang berechnen ==========================
let amountHoursHeuteBerechnet = false;
// Trigger bei aenderung der Sonnenuntergangszeit
on({id: '0_userdata.0.Eigene_Variablen.Zeiten.Sonnenuntergang_minus_60', change: 'any'}, () => {
amountHoursHeuteBerechnet = false; // Reset bei neuem Sonnenuntergang
});
// Pruefen alle 5 Minuten, ob Sonnenuntergang erreicht ist
schedule("*/5 * * * *", () => {
pruefeObSonnenuntergangErreicht();
});