From 4544832391c1bf84fb93fdcb27127cd6364cc40f Mon Sep 17 00:00:00 2001 From: "belevo\\mh" Date: Tue, 21 Apr 2026 07:49:28 +0200 Subject: [PATCH] no message --- Bat_EV_SDL_V3_Beta/module.php | 447 +++++++++------------------------- 1 file changed, 114 insertions(+), 333 deletions(-) diff --git a/Bat_EV_SDL_V3_Beta/module.php b/Bat_EV_SDL_V3_Beta/module.php index bfa332b..fe63969 100644 --- a/Bat_EV_SDL_V3_Beta/module.php +++ b/Bat_EV_SDL_V3_Beta/module.php @@ -68,7 +68,6 @@ class Bat_EV_SDL_V3_Beta extends IPSModule public function Update() { - // Reentranzschutz – verhindert parallele Update()-Läufe $semKey = 'BatEVSDL_Update_' . $this->InstanceID; if (!IPS_SemaphoreEnter($semKey, 5000)) { @@ -82,7 +81,6 @@ public function Update() return; } - // Cache nur neu bauen, wenn nötig $this->BuildBatteryCache(false); $cache = json_decode($this->GetBufferSafe("BatCacheJSON"), true); @@ -91,31 +89,24 @@ public function Update() return; } - // ===================== - // Integrator: Zeit / Konten laden (microtime ist bei 2s besser) - // ===================== + // ----------------------------- + // Zeit + Konten laden + // ----------------------------- $now = microtime(true); $lastTs = (float)$this->GetBufferSafe("Int_LastTs"); $Eev = (float)$this->GetBufferSafe("Int_E_EV_kWh"); $Esdl = (float)$this->GetBufferSafe("Int_E_SDL_kWh"); - // Letzte gefilterte Leistungen (damit wir Slew-Rate begrenzen können) - $prevPEV = (float)$this->GetBufferSafe("Int_P_EV_W"); - $prevPSDL = (float)$this->GetBufferSafe("Int_P_SDL_W"); - if ($lastTs <= 0.0) { $lastTs = $now; $Eev = 0.0; $Esdl = 0.0; - $prevPEV = 0.0; - $prevPSDL = 0.0; } $dtSec = $now - $lastTs; - if ($dtSec < 0.0) $dtSec = 0.0; - if ($dtSec > 3600.0) $dtSec = 3600.0; // Kappe 1h - + if ($dtSec < 0) $dtSec = 0.0; + if ($dtSec > 3600) $dtSec = 3600.0; $dtH = $dtSec / 3600.0; $calc = [ @@ -124,9 +115,9 @@ public function Update() "total" => [] ]; - // ===================== + // ----------------------------- // Summen - // ===================== + // ----------------------------- $sdlDisKW_ges = 0.0; $evDisKW_ges = 0.0; $sdlChKW_ges = 0.0; @@ -139,38 +130,27 @@ public function Update() $EV_kWh_ges = 0.0; $totalCapKWh = 0.0; - // Reset bei SoC=50% (mehrere Batterien): kapazitätsgewichteter Durchschnitt - $socCapSum = 0.0; - $socWeighted = 0.0; - foreach ($cache["bats"] as $i => $c) { $capKWh = (float)($c["capKWh"] ?? 0.0); if ($capKWh <= 0.0) continue; - // SoC lesen $socVarId = (int)($c["socVarId"] ?? 0); $socPct = $this->ReadSocPercent($socVarId); $real_kWh = $capKWh / 100.0 * $socPct; - // SoC für Reset mitteln (gewichtet) - $socWeighted += $socPct * $capKWh; - $socCapSum += $capKWh; - - // vorkalkuliert: $typ = (string)($c["typ"] ?? ("Bat " . ($i + 1))); $underKWh = (float)($c["underKWh"] ?? 0.0); $upKWh = (float)($c["upKWh"] ?? 0.0); $SDL_kWh = (float)($c["SDL_kWh_total"] ?? 0.0); $EV_kWh = (float)($c["EV_kWh_total"] ?? 0.0); - $sdlShareKW_laden = (float)($c["sdlShareKW_laden"] ?? 0.0); - $sdlShareKW_entladen = (float)($c["sdlShareKW_entladen"] ?? 0.0); - $evShareKW_laden = (float)($c["evShareKW_laden"] ?? 0.0); - $evShareKW_entladen = (float)($c["evShareKW_entladen"] ?? 0.0); + $sdlShareKW_laden = (float)($c["sdlShareKW_laden"] ?? 0.0); + $sdlShareKW_entladen = (float)($c["sdlShareKW_entladen"] ?? 0.0); + $evShareKW_laden = (float)($c["evShareKW_laden"] ?? 0.0); + $evShareKW_entladen = (float)($c["evShareKW_entladen"] ?? 0.0); - // Defaults $EV_SOC = 0.0; $SDL_SOC = 0.0; @@ -180,38 +160,27 @@ public function Update() $real_kWh_ev = 0.0; $real_kWh_sdl = 0.0; - // --- 3 Fälle --- if ($underKWh <= $real_kWh && $real_kWh <= $upKWh) { - // SDL_SOC im Fenster dynamisch (0..100) $win = ($upKWh - $underKWh); $SDL_SOC = ($win > 0.0) ? (100.0 * ($real_kWh - $underKWh) / $win) : 0.0; - $SDL_SOC = is_finite($SDL_SOC) ? max(0.0, min(100.0, $SDL_SOC)) : 0.0; + $SDL_SOC = max(0.0, min(100.0, $SDL_SOC)); - // EV_SOC im Fenster dynamisch $EV_SOC = ($EV_kWh > 0.0) ? (100.0 * ($real_kWh - $underKWh) / $EV_kWh) : 0.0; - $EV_SOC = is_finite($EV_SOC) ? max(0.0, min(100.0, $EV_SOC)) : 0.0; + $EV_SOC = max(0.0, min(100.0, $EV_SOC)); - // Limits $sdlDisKW = $sdlShareKW_entladen; $evDisKW = $evShareKW_entladen; $sdlChKW = $sdlShareKW_laden; $evChKW = $evShareKW_laden; - // Referenz-kWh $real_kWh_ev = $real_kWh - $underKWh; $real_kWh_sdl = $underKWh; } elseif ($real_kWh > $upKWh) { - // Oberhalb: EV voll, EV darf NICHT laden $EV_SOC = 100.0; - $den1 = ($capKWh - $real_kWh + $underKWh); - $den2 = (2.0 * $underKWh); - $SDL_SOC = ($den1 > 0.0 && $den2 > 0.0) ? min(100.0, ($den1 / $den2) * 100.0) : 0.0; - $SDL_SOC = is_finite($SDL_SOC) ? max(0.0, min(100.0, $SDL_SOC)) : 0.0; - $sdlDisKW = $sdlShareKW_entladen; $evDisKW = $evShareKW_entladen; $sdlChKW = $sdlShareKW_laden; @@ -220,15 +189,10 @@ public function Update() $real_kWh_ev = $capKWh - ($capKWh - $upKWh + $underKWh); $real_kWh_sdl = ($capKWh - $upKWh + $underKWh) - ($capKWh - $real_kWh); - } else { // $real_kWh < $underKWh + } else { - // Unterhalb: EV leer, EV darf NICHT entladen $EV_SOC = 0.0; - $den = $upKWh + $underKWh; - $SDL_SOC = ($den > 0.0) ? ($real_kWh * 100.0 / $den) : 0.0; - $SDL_SOC = is_finite($SDL_SOC) ? max(0.0, min(100.0, $SDL_SOC)) : 0.0; - $sdlDisKW = $sdlShareKW_entladen; $evDisKW = 0.0; // ✅ EV darf unten nicht entladen $sdlChKW = $sdlShareKW_laden; @@ -238,21 +202,9 @@ public function Update() $real_kWh_sdl = $real_kWh; } - // Null/Full Schutz - if ($real_kWh <= 0.0) { - $sdlDisKW = 0.0; - $real_kWh_ev = 0.0; - $real_kWh_sdl = 0.0; - } elseif ($real_kWh >= $capKWh) { - $sdlChKW = 0.0; - $real_kWh_ev = $EV_kWh; - $real_kWh_sdl = $SDL_kWh; - } - $real_kWh_ev = max(0.0, $real_kWh_ev); $real_kWh_sdl = max(0.0, $real_kWh_sdl); - // Summen bilden $totalCapKWh += $capKWh; $sdlDisKW_ges += $sdlDisKW; @@ -279,9 +231,6 @@ public function Update() "EV_kWh" => round($real_kWh_ev, 3), "SDL_kWh" => round($real_kWh_sdl, 3), - "under_grenze_kWh" => round($underKWh, 3), - "up_grenze_kWh" => round($upKWh, 3), - "SDL_Charge_kW" => round($sdlChKW, 3), "SDL_Discharge_kW" => round($sdlDisKW, 3), "EV_Charge_kW" => round($evChKW, 3), @@ -289,77 +238,22 @@ public function Update() ]; } - // ===================== - // Maximalleistungen setzen (Anzeige/Debug) – jetzt sofort, weil wir sie gleich brauchen - // ===================== + // Maximalleistungen setzen (für Anzeige/Debug) $this->SetIdentValue("P_SDL_laden", round($sdlChKW_ges * 1000.0, 0)); $this->SetIdentValue("P_SDL_entladen", round($sdlDisKW_ges * 1000.0, 0)); $this->SetIdentValue("P_EV_laden", round($evChKW_ges * 1000.0, 0)); $this->SetIdentValue("P_EV_entladen", round($evDisKW_ges * 1000.0, 0)); - // ===================== - // Reset bei SoC ~ 50% (einmalig) - // ===================== - $socAvg = ($socCapSum > 0.0) ? ($socWeighted / $socCapSum) : 0.0; + $this->SetIdentValue("CalcJSON", json_encode($calc, JSON_PRETTY_PRINT)); - $resetTarget = 50.0; - $resetTol = 0.5; // bei 2s Update: 0.2..0.5 + // ------------------------------------------------- + // Setpoints berechnen/schreiben (setzt auch "Aktuelle_Leistung_*") + // ------------------------------------------------- + $this->ApplySetpoints(); - $inWindow = (abs($socAvg - $resetTarget) <= $resetTol); - - $armedStr = $this->GetBufferSafe("Int_ResetArmed"); - $armed = ($armedStr === "") ? true : ($armedStr === "1"); - - $doReset50 = false; - if ($armed && $inWindow) { - $doReset50 = true; - $armed = false; - } - if (!$inWindow) { - $armed = true; - } - $this->SetBuffer("Int_ResetArmed", $armed ? "1" : "0"); - - // ===================== - // Integrator Init (damit Zonenreset nicht überschrieben wird) - // ===================== - if ($this->GetBufferSafe("Int_Init") !== "1") { - $Eev = $real_kWh_ev_ges; - $Esdl = $real_kWh_sdl_ges; - $this->SetBuffer("Int_Init", "1"); - } - - // ===================== - // EV-Zone Edge Reset (oben/unten) - // ===================== - $epsKWh = 0.001; - $zoneNow = 0; - if ($real_kWh_ev_ges <= $epsKWh) { - $zoneNow = -1; // EV leer - } elseif ($EV_kWh_ges > 0.0 && $real_kWh_ev_ges >= ($EV_kWh_ges - $epsKWh)) { - $zoneNow = 1; // EV voll - } - - $zonePrevStr = $this->GetBufferSafe("EV_Zone"); - $zonePrev = ($zonePrevStr === "") ? $zoneNow : (int)$zonePrevStr; - - if ($zoneNow !== $zonePrev) { - if ($zoneNow === 1) $Eev = $EV_kWh_ges; // 100% - if ($zoneNow === -1) $Eev = 0.0; // 0% - } - $this->SetBuffer("EV_Zone", (string)$zoneNow); - - // ===================== - // 50%-Reset (du akzeptierst Sprung) - // ===================== - if ($doReset50) { - $Eev = $real_kWh_ev_ges; - $Esdl = $real_kWh_sdl_ges; - } - - // ===================== - // Integrator soll mit "aktuellen" Werten laufen – aber bereinigt! - // ===================== + // ------------------------------------------------- + // Jetzt dynamisch integrieren mit "Aktueller Leistung" (Ist-basiert) + // ------------------------------------------------- $pEvSoll = (float)GetValue($this->GetIDForIdent("Nennleistung_Soll_EV")); $pSdlSoll = (float)GetValue($this->GetIDForIdent("Nennleistung_Soll_SDL")); @@ -367,113 +261,11 @@ public function Update() $evActive = (abs($pEvSoll) > $epsW); $sdlActive = (abs($pSdlSoll) > $epsW); - // 1) Rohwerte: "Aktuelle Leistung" aus Variablen lesen - $rawEV = (float)GetValue($this->GetIDForIdent("Aktuelle_Leistung_EV")); - $rawSDL = (float)GetValue($this->GetIDForIdent("Aktuelle_Leistung_SDL")); + $PEV = (float)GetValue($this->GetIDForIdent("Aktuelle_Leistung_EV")); + $PSDL = (float)GetValue($this->GetIDForIdent("Aktuelle_Leistung_SDL")); - // 2) Hard-Clamp an physikalische Maxima (lokale Summen, kW->W) - $maxEV_ch = $evChKW_ges * 1000.0; - $maxEV_dis = $evDisKW_ges * 1000.0; - $maxSDL_ch = $sdlChKW_ges * 1000.0; - $maxSDL_dis = $sdlDisKW_ges * 1000.0; + // Clamp auf lokale Maxima (kW->W) - $rawEV = max(-$maxEV_dis, min($maxEV_ch, $rawEV)); - $rawSDL = max(-$maxSDL_dis, min($maxSDL_ch, $rawSDL)); - - // 3) EV-Zonenregeln auch auf aktuelle Leistung anwenden - if ($zoneNow === 1 && $rawEV > 0.0) $rawEV = 0.0; // oben: EV darf nicht laden - if ($zoneNow === -1 && $rawEV < 0.0) $rawEV = 0.0; // unten: EV darf nicht entladen - - // 4) Slew-Rate: Sprünge begrenzen (W pro Sekunde) - // Passe diesen Wert an dein System an: - $maxSlewWps = 10000.0; // 10 kW pro Sekunde - $maxDeltaW = $maxSlewWps * max(0.001, $dtSec); - - $PEV = $rawEV; - $PSDL = $rawSDL; - - if (is_finite($prevPEV) && abs($PEV - $prevPEV) > $maxDeltaW) { - $this->SendDebug("SpikeFilter", "EV spike ignored: new=$PEV prev=$prevPEV", 0); - $PEV = $prevPEV; - } - if (is_finite($prevPSDL) && abs($PSDL - $prevPSDL) > $maxDeltaW) { - $this->SendDebug("SpikeFilter", "SDL spike ignored: new=$PSDL prev=$prevPSDL", 0); - $PSDL = $prevPSDL; - } - - // 5) Wenn Kanal inaktiv: Energie hart auf Referenz (wie du es wolltest) - if ($evActive) { - $Eev += (($PEV / 1000.0) * $dtH); - } else { - $Eev = $real_kWh_ev_ges; - $PEV = 0.0; - } - - if ($sdlActive) { - $Esdl += (($PSDL / 1000.0) * $dtH); - } else { - $Esdl = $real_kWh_sdl_ges; - $PSDL = 0.0; - } - - // Clamp Energiekonten - $Eev = ($EV_kWh_ges > 0.0) ? max(0.0, min($EV_kWh_ges, $Eev)) : 0.0; - $Esdl = ($SDL_kWh_ges > 0.0) ? max(0.0, min($SDL_kWh_ges, $Esdl)) : 0.0; - - // Prozentwerte - $evPosPct = ($EV_kWh_ges > 0.0) ? ($Eev / $EV_kWh_ges * 100.0) : 0.0; - $sdlPosPct = ($SDL_kWh_ges > 0.0) ? ($Esdl / $SDL_kWh_ges * 100.0) : 0.0; - - // Output setzen - $this->SetIdentValue("SoC_EV", round($evPosPct, 3)); - $this->SetIdentValue("SDL_Pos", round($sdlPosPct, 3)); - - // JSON total - $calc["total"] = [ - "SDL_SoC_pct" => round($sdlPosPct, 3), - "EV_SoC_pct" => round($evPosPct, 3), - - "SDL_kWh_total" => round($SDL_kWh_ges, 3), - "EV_kWh_total" => round($EV_kWh_ges, 3), - - "SDL_Charge_kW" => round($sdlChKW_ges, 3), - "SDL_Discharge_kW" => round($sdlDisKW_ges, 3), - "EV_Charge_kW" => round($evChKW_ges, 3), - "EV_Discharge_kW" => round($evDisKW_ges, 3), - - "totalCap_kWh" => round($totalCapKWh, 3), - "socAvg" => round($socAvg, 3), - - "doReset50" => $doReset50 ? 1 : 0, - "evZone" => $zoneNow, - - "PEV_used_W" => round($PEV, 0), - "PSDL_used_W" => round($PSDL, 0) - ]; - - $this->SetIdentValue("CalcJSON", json_encode($calc, JSON_PRETTY_PRINT)); - - // Setpoints schreiben - $this->ApplySetpoints(); - - // ===================== - // Integrator State speichern - // ===================== - $this->SetBuffer("Int_LastTs", (string)$now); - $this->SetBuffer("Int_E_EV_kWh", (string)$Eev); - $this->SetBuffer("Int_E_SDL_kWh", (string)$Esdl); - - // letzte gefilterte Leistung speichern - $this->SetBuffer("Int_P_EV_W", (string)$PEV); - $this->SetBuffer("Int_P_SDL_W", (string)$PSDL); - - } catch (Throwable $e) { - $this->SendDebug("Update ERROR", $e->getMessage() . " @ " . $e->getFile() . ":" . $e->getLine(), 0); - $this->WriteAllZero("Exception: " . $e->getMessage()); - } finally { - IPS_SemaphoreLeave($semKey); - } -} private function BuildBatteryCache(bool $force): void @@ -655,88 +447,75 @@ public function Update() private function CalculateBatteryDistribution(float $pEvW, float $pSdlW): array { $calcJsonId = $this->GetIDForIdent("CalcJSON"); - // Fallback, falls Variable leer ist oder nicht existiert if (!IPS_VariableExists($calcJsonId)) { return []; } + $rawJson = (string)GetValue($calcJsonId); if (empty($rawJson)) { return []; } + $calc = json_decode($rawJson, true); $batteries = $calc['batteries'] ?? []; // --------------------------------------------------------- - // Hilfsfunktion: Verteilungslogik (Closure) + // Hilfsfunktion: Verteilungslogik // --------------------------------------------------------- $distributePower = function(float $targetPower, string $mode) use ($batteries): array { - // Initialisierung des Ergebnis-Arrays (Key = idx, Value = zugewiesene Watt) $result = []; foreach ($batteries as $bat) { $result[$bat['idx']] = 0.0; } if (abs($targetPower) < 0.01) { - return $result; // Nichts zu tun + return $result; } $isCharge = ($targetPower > 0); $absPower = abs($targetPower); - // Relevante Keys basierend auf Modus (EV oder SDL) und Richtung (Laden/Entladen) - $socKey = ($mode === 'EV') ? 'EV_SOC' : 'SDL_SOC'; - // Achtung: JSON Limits sind in kW, wir rechnen in Watt -> * 1000 + + $socKey = ($mode === 'EV') ? 'EV_SOC' : 'SDL_SOC'; $limitKey = $isCharge ? $mode . '_Charge_kW' : $mode . '_Discharge_kW'; - // 1. Batterien vorbereiten und gruppieren nach gerundetem SoC $groups = []; foreach ($batteries as $bat) { - $soc = (int)round($bat[$socKey]); // Auf ganze Zahl runden - $maxW = ((float)$bat[$limitKey]) * 1000.0; // kW in Watt umrechnen + $soc = (int)round($bat[$socKey]); + $maxW = ((float)$bat[$limitKey]) * 1000.0; // kW -> W $groups[$soc][] = [ - 'idx' => $bat['idx'], + 'idx' => $bat['idx'], 'maxW' => $maxW ]; } - // 2. Sortieren der Gruppen - // Laden: Wenig SoC zuerst (ASC) -> leere füllen - // Entladen: Viel SoC zuerst (DESC) -> volle leeren if ($isCharge) { ksort($groups); } else { krsort($groups); } - // 3. Verteilung $remainingNeeded = $absPower; foreach ($groups as $soc => $groupBatteries) { if ($remainingNeeded <= 0.01) break; - // Gesamte verfügbare Leistung in dieser SoC-Gruppe ermitteln $groupTotalCapacity = 0.0; foreach ($groupBatteries as $gb) { $groupTotalCapacity += $gb['maxW']; } - // Wie viel können wir dieser Gruppe zuteilen? - // Entweder alles was die Gruppe kann, oder den Restbedarf $powerForThisGroup = min($remainingNeeded, $groupTotalCapacity); - // Proportionale Aufteilung innerhalb der Gruppe - // Falls Gruppe Kapazität 0 hat (Defekt/Voll), verhindern wir DivByZero if ($groupTotalCapacity > 0) { $ratio = $powerForThisGroup / $groupTotalCapacity; foreach ($groupBatteries as $gb) { - $assigned = $gb['maxW'] * $ratio; - $result[$gb['idx']] = $assigned; + $result[$gb['idx']] = $gb['maxW'] * $ratio; } } $remainingNeeded -= $powerForThisGroup; } - // Wenn wir entladen, müssen die Werte negativ sein if (!$isCharge) { foreach ($result as $idx => $val) { $result[$idx] = -$val; @@ -747,120 +526,122 @@ private function CalculateBatteryDistribution(float $pEvW, float $pSdlW): array }; // --------------------------------------------------------- - // Hauptablauf + // 1) Verteilung EV/SDL getrennt // --------------------------------------------------------- - - // 1. Berechnung für EV und SDL getrennt durchführen - $evDistribution = $distributePower($pEvW, 'EV'); + $evDistribution = $distributePower($pEvW, 'EV'); $sdlDistribution = $distributePower($pSdlW, 'SDL'); - // 2. Ergebnisse zusammenführen und Output formatieren + // --------------------------------------------------------- + // 2) Zusammenführen pro Batterie (Netto-Setpoint) + // --------------------------------------------------------- $finalOutput = []; - foreach ($batteries as $bat) { $idx = $bat['idx']; - // Summe der beiden Anforderungen (kann sich gegenseitig aufheben) - $valEv = $evDistribution[$idx] ?? 0.0; + + $valEv = $evDistribution[$idx] ?? 0.0; $valSdl = $sdlDistribution[$idx] ?? 0.0; + $totalW = $valEv + $valSdl; - // Aufteilen in Charge / Discharge für das Return-Format $chargeW = 0.0; $dischargeW = 0.0; if ($totalW > 0) { - $chargeW = abs($totalW); - } else { - $dischargeW = abs($totalW); + $chargeW = $totalW; + } elseif ($totalW < 0) { + $dischargeW = -$totalW; } - // JSON Objekt erstellen $finalOutput[] = [ - "idx" => $idx, - "typ" => (string)$bat['typ'], // Typ als String beibehalten - "chargeW" => round($chargeW, 0), // Optional: runden für sauberes JSON + "idx" => $idx, + "typ" => (string)$bat['typ'], + "chargeW" => round($chargeW, 0), "dischargeW" => round($dischargeW, 0) ]; } + // --------------------------------------------------------- + // 3) Ist-Batterieleistung lesen (Summe) + // --------------------------------------------------------- $batteriesRaw = json_decode($this->ReadPropertyString("Batteries")); + $totalPower_ist = 0.0; - $totalPower_ist = 0; - - foreach ($batteriesRaw as $bat) { - $totalPower_ist += (-1)*GetValue($bat->register_bat_power); + if (is_array($batteriesRaw)) { + foreach ($batteriesRaw as $bat) { + // ACHTUNG: Vorzeichen-Konvention! + // Du hattest (-1)*GetValue(...) drin. Behalte das, wenn dein Register so liefert. + $totalPower_ist += (-1) * (float)GetValue($bat->register_bat_power); + } } - // ===================================================== - // Stabilisierung "Aktuelle Leistung": Sample & Hold - // -> ändert sich nur wenn Sollwerte sich ändern - // ===================================================== + // --------------------------------------------------------- + // 4) "Aktuelle Leistung EV/SDL" stabil aus Istleistung ableiten + // - gleiche Richtung: proportional nach |Soll| + // - nur einer aktiv: der bekommt die Istleistung + // - gegensätzliche Richtung: Sollwerte anzeigen (keine Skalierung) + // --------------------------------------------------------- $eps = 0.01; - // Toleranz: wann gilt Soll als "gleich geblieben" (W) - $tolW = 0.5; + $aktEV = 0.0; + $aktSDL = 0.0; - // Letzte Sollwerte und Haltefaktor aus Buffer - $prevEvSoll = (float)$this->GetBufferSafe("Hold_EvSoll"); - $prevSdlSoll = (float)$this->GetBufferSafe("Hold_SdlSoll"); - $factorHold = (float)$this->GetBufferSafe("Hold_Factor"); + $evOn = (abs($pEvW) > $eps); + $sdlOn = (abs($pSdlW) > $eps); - // Initialwerte falls Buffer leer - if ($this->GetBufferSafe("Hold_Init") !== "1") { - $prevEvSoll = $pEvW; - $prevSdlSoll = $pSdlW; - $factorHold = 1.0; - $this->SetBuffer("Hold_Init", "1"); - } - - // Prüfen ob Sollwerte sich geändert haben - $changed = - (abs($pEvW - $prevEvSoll) > $tolW) || - (abs($pSdlW - $prevSdlSoll) > $tolW); - - // Beim Sollwechsel: neuen Faktor bestimmen und einfrieren - if ($changed) { - $sumSoll = (float)($pEvW + $pSdlW); - - if (abs($sumSoll) > $eps) { - $factorHold = (float)($totalPower_ist / $sumSoll); - } else { - // Soll hebt sich auf / fast 0: keine Skalierung - $factorHold = 1.0; - } - - // Faktor begrenzen (Schutz gegen Unsinn; ggf. anpassen) - $factorHold = max(-2.0, min(2.0, $factorHold)); - - // Sollwerte merken - $prevEvSoll = $pEvW; - $prevSdlSoll = $pSdlW; - - $this->SetBuffer("Hold_EvSoll", (string)$prevEvSoll); - $this->SetBuffer("Hold_SdlSoll", (string)$prevSdlSoll); - $this->SetBuffer("Hold_Factor", (string)$factorHold); - } - - // "Aktuelle" Leistungen aus gehaltenem Faktor berechnen - $aktEV = (float)($pEvW * $factorHold); - $aktSDL = (float)($pSdlW * $factorHold); - - // Optional: wenn beide Soll = 0 -> beide aktuelle = 0 - if (abs($pEvW) <= $eps && abs($pSdlW) <= $eps) { - $aktEV = 0.0; + if (!$evOn && !$sdlOn) { + $aktEV = 0.0; $aktSDL = 0.0; + + } elseif (!$evOn && $sdlOn) { + // nur SDL aktiv -> bekommt Istleistung + $aktEV = 0.0; + $aktSDL = $totalPower_ist; + + } elseif ($evOn && !$sdlOn) { + // nur EV aktiv -> bekommt Istleistung + $aktEV = $totalPower_ist; + $aktSDL = 0.0; + + } else { + // beide aktiv + $sameDirection = + (($pEvW > 0 && $pSdlW > 0) || ($pEvW < 0 && $pSdlW < 0)); + + if ($sameDirection) { + // Ist proportional nach |Soll| + $sumAbs = abs($pEvW) + abs($pSdlW); + if ($sumAbs < $eps) { + $aktEV = $totalPower_ist / 2.0; + $aktSDL = $totalPower_ist / 2.0; + } else { + $aktEV = $totalPower_ist * (abs($pEvW) / $sumAbs); + $aktSDL = $totalPower_ist - $aktEV; // exakt Summe = totalPower_ist + // Vorzeichen an Istleistung anpassen + if ($totalPower_ist < 0) { + $aktEV = -abs($aktEV); + $aktSDL = -abs($aktSDL); + } else { + $aktEV = abs($aktEV); + $aktSDL = abs($aktSDL); + } + } + + } else { + // gegensätzliche Richtung: Sollwerte anzeigen (stabil, keine Explosion) + $aktEV = $pEvW; + $aktSDL = $pSdlW; + } } - // Diese beiden Variablen stabil setzen (kein $this->SetValue!) + // Stabil setzen (kein $this->SetValue!) $this->SetIdentValue("Aktuelle_Leistung_EV", (float)$aktEV); $this->SetIdentValue("Aktuelle_Leistung_SDL", (float)$aktSDL); - - return $finalOutput; } + private function WriteBatteryPowerSetpoints(array $distribution): void { IPS_LogMessage(