// ===================== 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, `${html}
`); 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(); });