// ===================== KONFIGURATION ===================== const hyper1 = { name: 'Hyper1', inputLimitDP: 'zendure-solarflow.0.aaaaaa.bbbbbbbb.control.setInputLimit', outputLimitDP: 'zendure-solarflow.0.aaaaaa.bbbbbbbb.control.setOutputLimit', acModeDP: 'zendure-solarflow.0.aaaaaa.bbbbbbbb.control.acMode', socDP: 'zendure-solarflow.0.aaaaaa.bbbbbbbb.electricLevel', }; const hyper2 = { name: 'Hyper2', inputLimitDP: 'zendure-solarflow.0.cccccc.dddddddd.control.setInputLimit', outputLimitDP: 'zendure-solarflow.0.cccccc.dddddddd.control.setOutputLimit', acModeDP: 'zendure-solarflow.0.cccccc.dddddddd.control.acMode', socDP: 'zendure-solarflow.0.cccccc.dddddddd.electricLevel', }; const logHTMLDP = '0_userdata.0.Eigene_Variablen.PV.Solarflow.SolarflowLog'; const hausverbrauchDP = 'mqtt.0.openWB.global.WHouseConsumption'; const evuLeistungDP = 'shelly.1.shellypro3em#ggggggggggg#1.EM0.TotalActivePower'; const hausverbrauchBisherDP = 'mqtt.0.openWB.global.DailyYieldHausverbrauchKwh'; const pv1LeistungDP = 'mqtt.0.openWB.pv.1.W'; const pv2LeistungDP = 'zendure-solarflow.0.aaaaaa.bbbbbbbb.solarInputPower'; const priceLoadingDP = '0_userdata.0.Eigene_Variablen.PV.Solarflow.PriceLoading'; const preisLadenModusDP = '0_userdata.0.Eigene_Variablen.PV.Solarflow.PreisLadenModus'; // 0=konservativ, 1=aggressiv const stromGuenstigDP = 'tibberlink.0.Homes.8f8a4278-17b9-48fb-ffff-eeeeeeeeeeee.Eigene_Werte.load_batterie'; const stromTeuerDP = 'tibberlink.0.Homes.8f8a4278-17b9-48fb-ffff-eeeeeeeeeeee.Eigene_Werte.expensive_single_hour'; const pvForecastTodayDP = 'pvforecast.1.summary.energy.today'; const pvForecastRestDP = 'pvforecast.1.summary.energy.nowUntilEndOfDay'; const socMin = 10; const socMax = 100; const ladenStartSchwelle = 100; const ladenStoppSchwelle = 30; const entladenStartSchwelle = 40; const entladenStoppSchwelle = 20; const netzPufferWatt = 30; const maxLeistung = xxx; // Anpassen const maxLadeleistungGesamt = maxLeistung * 2; const kapazitaetKWh1 = x.y; // Anpassen const kapazitaetKWh2 = x.y; // Anpassen // Parameter für eAuto laden const ampereSetDP = 'go-e.0.ampere'; // Wallbox-Stromstärke setzen const allowChargingDP = 'go-e.0.allow_charging'; // Laden zulassen const ladenVorrangDP = '0_userdata.0.Eigene_Variablen.PV.Laden_Vorrang'; const ladeleistungReserveDP = '0_userdata.0.Eigene_Variablen.openWB.Ladeleistung_Reserve'; const openWBLadepunkt1DP = 'mqtt.0.openWB.lp.1.W'; const autoMaxSOCDP = 80; // Ziel-SOC des E-Autos const autoSocDP = 'mqtt.0.openWB.lp.1.%Soc'; // Beispiel-Datenpunkt für Auto-SOC const autoPrioGrenzeSocDP = '0_userdata.0.Eigene_Variablen.PV.Solarflow.Grenze_Prio_SOC'; const cargeModeDP = '0_userdata.0.Eigene_Variablen.openWB.ChargeMode'; const ladepunktDP = '0_userdata.0.Eigene_Variablen.openWB.ChargePointEnabled'; const autoPlugstatDP = 'mqtt.0.openWB.lp.1.boolPlugStat' const minAmp = 6; // Mindeststromstärke const maxAmp = 16; // Maximale Stromstärke const ampereWatt = 690; // 1 A entspricht 690 W bei 3-phasig (oder 230 für 1-phasig) const rampUpBuffer = 150; // zusätzlich benötigte Watt je Schritt (zur Sicherheit) const minEinspeisungW = 1400; // Mindestwert für Ladefreigabe const rampUpDelay = 1 * 30 * 1000; // 30 Sekunden Mindestverzögerung zwischen Ramp-Ups //Später z.B. zwischen Sommer-/Winterlogik unterscheiden (z.B. aggressiveres Entladen im Winter) //const monat = (new Date()).getMonth() + 1; // Januar = 1 //const istWinter = monat <= 2 || monat >= 11; const maxDurchlaeufeImLog = 2; let logDurchlaeufe = []; // Logging-Funktion VIS 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' }; 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 limit = 2; const maxProHyper = 1200; const pv2BonusProzent = 2; // === Preis-Laden aktiv === if (gesamt <= 0) return [0, 0]; let effektiverSOC1 = soc1; let effektiverSOC2 = soc2; if (modus === 'laden') { effektiverSOC1 += pv2BonusProzent; } let g1 = 0.5, g2 = 0.5; const diff = effektiverSOC1 - effektiverSOC2; if (modus === 'laden') { if (diff > limit) { g1 = 0.3; g2 = 0.7; } else if (diff < -limit) { g1 = 0.7; g2 = 0.3; } } else if (modus === 'entladen') { if (diff > limit) { g1 = 0.7; g2 = 0.3; } else if (diff < -limit) { g1 = 0.3; g2 = 0.7; } } let leistung1 = Math.min(Math.round(gesamt * g1), maxProHyper); let leistung2 = Math.min(Math.round(gesamt * g2), maxProHyper); // Korrektur falls Summe nicht mit Gesamtleistung übereinstimmt let korrektur = Math.round(gesamt) - (leistung1 + leistung2); if (korrektur !== 0) { if (leistung1 > leistung2) leistung1 += korrektur; else leistung2 += korrektur; } return [leistung1, leistung2]; } function setLademodus(h, leistung) { setState(h.acModeDP, 1); setState(h.inputLimitDP, Math.round(Math.max(0, leistung))); } function setEntlademodus(h, leistung) { setState(h.acModeDP, 2); setState(h.outputLimitDP, Math.round(Math.max(0, leistung))); } function stopHyper(h) { setState(h.outputLimitDP,0); setState(h.inputLimitDP,0); } // Prüft, ob ein Moduswechsel zwischen Laden und Entladen aktuell erlaubt ist function delayWechselErlaubt(altModus, neuModus, letzterWechselZeitpunkt, delaySekunden) { if ((altModus === 1 && neuModus === 2) || (altModus === 2 && neuModus === 1)) { const vergangen = (Date.now() - letzterWechselZeitpunkt) / 1000; return vergangen >= delaySekunden; } return true; } 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 ungültig: " + 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 ungültig – 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-ffff-eeeeeeeeeeee.Calculations.2.AmountHours', amountHours); visLog(`🌙 Nacht: ${nachtDauerStunden.toFixed(1)}h | Akku reicht für ~${abdeckbareStunden.toFixed(1)}h → fehlend: ${fehlendeStunden.toFixed(1)}h → AmountHours: ${amountHours}`, 'info'); } // ===================== HAUPTSTEUERUNG ===================== // Globale Variablen für Modus-Hysterese let lastMode = 0; // 0=stop,1=laden,2=entladen let lastSwitchTime = 0; // Timestamp in ms const modusDelaySek = 20; // Verzögerung zwischen Laden und Entladen 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(pv2LeistungDP, 0); // Hyper 2 // SOC und Verbrauch const soc1 = getSOC(h1); const soc2 = getSOC(h2); const avgSOC = (soc1 + soc2) / 2; const autoSOC = getStateVal(autoSocDP, 100); // aktueller Auto-SOC const speicherMinSOC = Math.min(soc1, soc2); // niedrigster Speicher-SOC const aktuelleEinspeisung = getState(evuLeistungDP).val; // Positiv = Netzbezug, negativ = Einspeisung const ladepunkt1Leistung = getStateVal(openWBLadepunkt1DP, 0); const hausverbrauch = getStateVal(hausverbrauchDP, 0); const hausverbrauch_lp1 = getStateVal(hausverbrauchDP, 0) + ladepunkt1Leistung; const hausverbrauchBisher = getStateVal(hausverbrauchBisherDP, 0); const chargeMode = getStateVal(cargeModeDP, 0); // eAuto Parameter const ladenVorrang = getStateVal(ladenVorrangDP, false); const autoPrioGrenze = getStateVal(autoPrioGrenzeSocDP, 80); const autoPlugstat = getStateVal(autoPlugstatDP, 0); let lastAmpere = getState(ampereSetDP).val || minAmp; let lastRampUpTime = Date.now(); // Zustand: Speicher hat Vorrang // Eingangsgrößen const nettoEinspeisung = aktuelleEinspeisung + ladepunkt1Leistung; // Berücksichtigt den Autoverbrauch if (ladenVorrang === 0) { if (speicherMinSOC < autoPrioGrenze) { //setState(ladepunktDP, false); //visLog(`🔋 SOC ${speicherMinSOC}% < Grenze ${autoPrioGrenze}%, Auto-Laden deaktiviert`, 'warn'); if (nettoEinspeisung > -1500) { //Auto darf nicht laden //setState(ladepunktDP, false); //visLog(`🔋 SOC ${speicherMinSOC}% < Grenze ${autoPrioGrenze}%, Netto-Einspeisung (${nettoEinspeisung} W) < 1400 W → Auto-Laden deaktiviert`, 'warn'); } else { //Auto darf laden //setState(ladepunktDP, true); //visLog(`🔋 SOC ${speicherMinSOC}% < Grenze, aber Netto-Einspeisung (${nettoEinspeisung} W) > 1400 W → Auto-Laden erlaubt`, 'ok'); } } else { // SOC hoch genug → Auto darf sowieso laden setState(ladepunktDP, true); visLog(`🔋 SOC ${speicherMinSOC}% ≥ Grenze ${autoPrioGrenze}% → Auto-Laden aktiviert`, 'ok'); } } // Zustand: Auto hat Vorrang if (ladenVorrang === 1 && autoPlugstat === 1 && chargeMode != 0) { const nettoEinspeisung = aktuelleEinspeisung + ladepunkt1Leistung; const moeglicheLadeleistung = pvLeistung1 - hausverbrauch_lp1; //visLog(`Mögliche Ladelesitung ${moeglicheLadeleistung}`, 'warn'); //if (nettoEinspeisung <= -minEinspeisungW && autoSOC < autoMaxSOCDP) { if (moeglicheLadeleistung >= minEinspeisungW && autoSOC < autoMaxSOCDP) { //Mindestbedingung erfüllt → Ladefreigabe //setState(ladepunktDP, true); setLademodus(h1, 0); setLademodus(h2, 0); setState(allowChargingDP, 1); let availableWatt = Math.abs(nettoEinspeisung); let targetAmp = Math.floor(availableWatt / ampereWatt); // Begrenzen auf zulässige Ampere if (targetAmp > maxAmp) targetAmp = maxAmp; if (targetAmp < minAmp) targetAmp = minAmp; // RAMP-UP: nur erhöhen wenn genug Leistung + Zeit seit letzter Änderung if (targetAmp > lastAmpere) { const neededWatt = (targetAmp * ampereWatt) + rampUpBuffer; if (availableWatt >= neededWatt && Date.now() - lastRampUpTime > rampUpDelay) { setState(ampereSetDP, targetAmp); visLog(`🔼 Ramp-Up: Erhöhe auf ${targetAmp} A bei ${availableWatt} W Überschuss`, 'ok'); lastAmpere = targetAmp; lastRampUpTime = Date.now(); } else { visLog(`🔼 Ramp-Up: Noch nicht genug Reserve/Delay für ${targetAmp} A`, 'info'); } } // Runterregeln sofort wenn nötig else if (targetAmp < lastAmpere) { setState(ampereSetDP, targetAmp); visLog(`🔽 Reduziere Stromstärke auf ${targetAmp} A (nur ${availableWatt} W verfügbar)`, 'warn'); lastAmpere = targetAmp; lastRampUpTime = Date.now(); // optional auch hier setzen } else { visLog(`⏸ Strom bleibt bei ${lastAmpere} A`, 'debug'); } } else { // Zu wenig Einspeisung → Deaktivieren //setState(ladepunktDP, false); setState(allowChargingDP, 0); visLog(`⚠️ Einspeisung zu gering (${Math.round(nettoEinspeisung)} W) → eAuto Laden deaktiviert`, 'warn'); setState(ampereSetDP, minAmp); // evtl. zurücksetzen lastAmpere = minAmp; } } /*if (ladenVorrang === 1 && autoPlugstat === 1) { * if (autoSOC < autoMaxSOCDP) { * visLog(`🚗 Auto-SOC (${autoSOC} %) < Ziel (${autoMaxSOCDP} %) → Speicher wird nicht geladen`, 'warn'); * setLademodus(h1, 0); * setLademodus(h2, 0); * setState(ladepunktDP, true); // Auto darf laden * setState(cargeModeDP, 1); // Modus Min+PV * return; * } else { * visLog(`🚗 Auto voll (SOC ≥ ${autoMaxSOCDP} %) → Speicher darf geladen werden`, 'ok'); * setState(cargeModeDP, 2) * setState(ladepunktDP, false); // Auto laden beenden * } }*/ // PV-Leistung berechnen, abhängig vom SOC von Hyper1 let pvLeistungGesamt = (soc1 >= 100) ? (pvLeistung1 + pvLeistung2) : pvLeistung1; // Optional: Plausibilitätsprüfung if (pvLeistung1 < 0 || pvLeistung2 < 0) { visLog(`⚠️ PV-Leistung negativ: PV1=${pvLeistung1}W, PV2=${pvLeistung2}W`, 'warn'); pvLeistung1 = Math.max(0, pvLeistung1); pvLeistung2 = Math.max(0, pvLeistung2); // Nochmal prüfen, ob SOC1 voll ist – um richtige Addition zu machen pvLeistungGesamt = (soc1 >= 100) ? (pvLeistung1 + pvLeistung2) : pvLeistung1; } if (pvLeistungGesamt === 0) { visLog('⚠️ PV-Leistung = 0W – evtl. Messproblem?', 'warn'); } // Die Leistung, die direkt zur Deckung des Hausverbrauchs beiträgt: const pvLeistungRaw = pvLeistungGesamt; // Forecast & Ziel const pvRest = getStateVal(pvForecastRestDP, 0) / 1000; const zielSOC = socMax; // Preisstatus const preisErlaubt = getStateVal(priceLoadingDP, false) === true; const preisGuenstig = isPreisGuenstig(); const preisTeuer = isPreisTeuer(); // Verbrauchsprognose const stundeJetzt = jetzt.getHours() + jetzt.getMinutes()/60 + jetzt.getSeconds()/3600; const verbrauchProStunde = stundeJetzt > 1 ? hausverbrauchBisher / stundeJetzt : hausverbrauchBisher; const gesamtKWh = getKWh(soc1, kapazitaetKWh1) + getKWh(soc2, kapazitaetKWh2); const fehlendeKapazitaet_kWh = Math.max(0, ((zielSOC/100)*(kapazitaetKWh1+kapazitaetKWh2)) - gesamtKWh); const prognoseRestverbrauch = Math.max(0, verbrauchProStunde*24 - hausverbrauchBisher); const pvRestEffektiv = Math.max(0, pvRest - prognoseRestverbrauch); // Energie aus Akkus //const leistungAusAkkus = hausverbrauch_lp1 - pvLeistungRaw; const evuLeistung = getStateVal(evuLeistungDP, 0); // positiv = Netzbezug, negativ = Einspeisung // Aktuelle Lade-/Entladeleistung der Hyper const hyper1Leistung = getStateVal(hyper1.inputLimitDP, 0) - getStateVal(hyper1.outputLimitDP, 0); // positiv = Laden, negativ = Entladen const hyper2Leistung = getStateVal(hyper2.inputLimitDP, 0) - getStateVal(hyper2.outputLimitDP, 0); // Netto-Leistung Richtung Netz inkl. Akkus const leistungAusAkkus = evuLeistung - hyper1Leistung - hyper2Leistung; // Auto-Laden blockiert Entladen //const autoLaedt = ladepunkt1Leistung > 0; //const hyperUnterGrenze = soc1 < autoPrioGrenze || soc2 < autoPrioGrenze; 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?'günstig':'neutral'} | PreisLaden: ${preisErlaubt}`); visLog(`📈 Fehlende Kapazität: ${fehlendeKapazitaet_kWh.toFixed(2)} kWh | 🔮 PV Rest effektiv: ${pvRestEffektiv.toFixed(2)} kWh`); //visLog(`🔌 EVU: ${Math.round(evuLeistung)}W | Hyper1: ${Math.round(hyper1Leistung)}W | Hyper2: ${Math.round(hyper2Leistung)}W → Aus Akkus: ${Math.round(leistungAusAkkus)}W`); // Laden erlaubt? let ladenErlaubt = false; const minSOC = Math.min(soc1, soc2); if (leistungAusAkkus < -ladenStartSchwelle && minSOC < zielSOC) { ladenErlaubt = true; visLog('☀️ PV-Überschuss → Laden erlaubt', 'ok'); } // Preis-Lademodus auslesen let preisLadenModus = getStateVal(preisLadenModusDP, 0); // 0=konservativ, 1=aggressiv // NEU: Wenn Auto lädt und Preis günstig ist, dann immer aggressiver Lademodus if (ladepunkt1Leistung > 0 && preisGuenstig) { preisLadenModus = 1; visLog('🚗 Auto lädt & Preis günstig → Aggressiver Lademodus aktiviert', 'warn'); } if (!ladenErlaubt && preisGuenstig && preisErlaubt) { const stundeJetzt = jetzt.getHours() + jetzt.getMinutes()/60; const sonnenstandSpät = stundeJetzt >= 17; const prognoseReichtNichtMehr = fehlendeKapazitaet_kWh > pvRestEffektiv * 0.5; const socZiemlichLeer = avgSOC < 90; if ( preisLadenModus === 1 || (preisLadenModus === 0 && ( prognoseReichtNichtMehr || (sonnenstandSpät && socZiemlichLeer) )) ) { ladenErlaubt = true; const art = preisLadenModus === 1 ? 'aggressiv (immer bei günstigem Preis)' : sonnenstandSpät && socZiemlichLeer ? 'konservativ (spät & SOC niedrig)' : 'konservativ (PV reicht nicht)'; visLog(`💶 Preis-Laden erlaubt (${art})`, 'laden'); } } // Entladen erlaubt? let entladenErlaubt = false; if (ladepunkt1Leistung <= 500 && leistungAusAkkus > entladenStartSchwelle) { entladenErlaubt = true; if (preisTeuer) entladenErlaubt = true; else if (preisGuenstig) entladenErlaubt = avgSOC > 80; else entladenErlaubt = avgSOC > 30; } // Ziel-Modus festlegen const aktuellerModus = getStateVal(h1.acModeDP, 0); let zielModus = 0; if (ladenErlaubt) zielModus = 1; else if (entladenErlaubt) zielModus = 2; // Moduswechsel verzögert bei Laden <-> Entladen if (!delayWechselErlaubt(aktuellerModus, zielModus, lastSwitchTime, modusDelaySek)) { const rest = modusDelaySek - ((jetztTs - lastSwitchTime) / 1000); visLog(`⏳ Moduswechsel ${aktuellerModus}->${zielModus} blockiert (${rest.toFixed(0)}s Verzögerung)`, 'warn'); zielModus = aktuellerModus; } // Moduswechsel durchführen if (zielModus !== aktuellerModus) { if (zielModus === 1 || zielModus === 2) { setState(h1.acModeDP, zielModus); lastMode = zielModus; lastSwitchTime = jetztTs; visLog(`🔄 Moduswechsel → ${zielModus === 1 ? 'Laden' : 'Entladen'}`, 'mode'); } else { visLog(`⏹ Moduswechsel auf 0 (Stopp) → AC-Modus bleibt auf ${aktuellerModus}`, 'stop'); } } // Aktion je Modus if (zielModus === 1) { // Laden let ueberschuss = Math.max(0, -leistungAusAkkus - netzPufferWatt); let ladeLimit = preisGuenstig && preisErlaubt ? maxLadeleistungGesamt : ueberschuss; if (getStateVal(ladenVorrangDP, false) && ladepunkt1Leistung > 0 && chargeMode === 2) { ladeLimit = Math.min(getStateVal(ladeleistungReserveDP, maxLadeleistungGesamt), ladeLimit); visLog('🔌 Lade-Vorrang aktiv & Auto lädt → Ladeleistung begrenzt auf Reserve', 'warn'); } const ladeleistung = Math.min(ladeLimit, maxLadeleistungGesamt); let [l1, l2] = verteileLeistungNachSOC(ladeleistung, soc1, soc2, 'laden'); // === Preis-Laden aktiv === if (preisErlaubt && preisGuenstig) { visLog(`💰 Preis-Laden aktiv → Hyper1 & Hyper2 erhalten je 1200 W`, 'laden'); let l1 = 1200; let l2 = 1200; } const l1_begrenzt = Math.min(l1, 1200); const l2_begrenzt = Math.min(l2, 1200); //setLademodus(h1, l1_begrenzt); if (soc1 >= 100) { visLog(`🔋 ${h1.name} ist voll (100 %) → auf Ausgang (AC-Modus) schalten`, 'entladen'); setState(h1.acModeDP, 2); // AC-Ausgang aktivieren setState(h1.inputLimitDP, 0); // Kein Laden mehr setState(h1.outputLimitDP, 0); // Kein Entladen } else { setLademodus(h1, l1_begrenzt); } setLademodus(h2, l2_begrenzt); if ((l1_begrenzt > 0 || l2_begrenzt > 0) && (soc1 < 100 || soc2 < 100) && (!preisErlaubt || !preisGuenstig)) { visLog(`⚡ Mögl. Ladeleistung gesamt: ${Math.round(l1_begrenzt+l2_begrenzt)}W -> ${h1.name}: ${Math.round(l1_begrenzt)}W | ${h2.name}: ${Math.round(l2_begrenzt)}W`, 'laden'); } //visLog(`⚡ Mögl. Ladeleistung gesamt: ${Math.round(ladeLimit)}W`); } else if (zielModus === 2) { // Entladen const entladeGesamt = Math.max(0, Math.min(leistungAusAkkus, maxLadeleistungGesamt)); const [e1,e2] = verteileLeistungNachSOC(entladeGesamt, soc1, soc2, 'entladen'); if (e1||e2 && (soc1 >= 10 || soc2 >= 10)) { setEntlademodus(h1, e1); setEntlademodus(h2, e2); visLog(`⚡ Entladen: ${h1.name} ${e1}W | ${h2.name} ${e2}W`, 'entladen'); } else { stopHyper(h1); stopHyper(h2); visLog('⚠️ Keine Entladeleistung → gestoppt', 'warn'); } } else { // Stopp stopHyper(h1); stopHyper(h2); visLog('⏹ Speicher STOP', 'stop'); } } function getStateVal(id, def = 0) { try { const s = getState(id); return (s && typeof s.val !== 'undefined') ? s.val : def; } catch(e) { log(`❌ Fehler beim Lesen von ${id}: ${e}`, 'error'); return def; } } // Trigger //on({ id: hausverbrauchDP, change: "ne" }, () => steuerung(hyper1, hyper2)); on({ id: evuLeistungDP, change: "ne" }, () => steuerung(hyper1, hyper2)); schedule('*/5 * * * *', () => steuerung(hyper1, hyper2)); // ========================== AmountHours einmal täglich zum Sonnenuntergang berechnen ========================== let amountHoursHeuteBerechnet = false; // Trigger bei Änderung der Sonnenuntergangszeit on({id: '0_userdata.0.Eigene_Variablen.Zeiten.Sonnenuntergang_minus_60', change: 'any'}, () => { amountHoursHeuteBerechnet = false; // Reset bei neuem Sonnenuntergang visLog('🔄 AmountHours zurückgesetzt – neuer Sonnenuntergangswert erkannt', 'info'); }); // Prüfen alle 5 Minuten, ob Sonnenuntergang erreicht ist schedule("*/5 * * * *", () => { pruefeObSonnenuntergangErreicht(); });