diff --git a/Bat_EV_SDL_V3_Beta/module.php b/Bat_EV_SDL_V3_Beta/module.php index b0665f4..289f4f5 100644 --- a/Bat_EV_SDL_V3_Beta/module.php +++ b/Bat_EV_SDL_V3_Beta/module.php @@ -66,8 +66,9 @@ class Bat_EV_SDL_V3_Beta extends IPSModule throw new Exception("Invalid Ident: " . $Ident); } -public function Update() + public function Update() { + // Reentranzschutz – verhindert parallele Update()-Läufe $semKey = 'BatEVSDL_Update_' . $this->InstanceID; if (!IPS_SemaphoreEnter($semKey, 5000)) { @@ -81,6 +82,7 @@ public function Update() return; } + // Cache nur neu bauen, wenn nötig $this->BuildBatteryCache(false); $cache = json_decode($this->GetBufferSafe("BatCacheJSON"), true); @@ -89,25 +91,30 @@ public function Update() return; } - // ----------------------------- - // Zeit + Konten laden - // ----------------------------- - $now = microtime(true); + // ===================== + // Integrator: State laden + // ===================== + // Optional für 2s besser: microtime(true) statt time() verwenden + $now = time(); - $lastTs = (float)$this->GetBufferSafe("Int_LastTs"); - $Eev = (float)$this->GetBufferSafe("Int_E_EV_kWh"); - $Esdl = (float)$this->GetBufferSafe("Int_E_SDL_kWh"); + $lastTs = (int)$this->GetBufferSafe("Int_LastTs"); + $lastPEV = (float)$this->GetBufferSafe("Int_LastP_EV_W"); // W (+ laden, - entladen) + $lastPSDL = (float)$this->GetBufferSafe("Int_LastP_SDL_W"); // W (+ laden, - entladen) - if ($lastTs <= 0.0) { + $Eev = (float)$this->GetBufferSafe("Int_E_EV_kWh"); // kWh-Konto EV + $Esdl = (float)$this->GetBufferSafe("Int_E_SDL_kWh"); // kWh-Konto SDL + + if ($lastTs <= 0) { $lastTs = $now; - $Eev = 0.0; + $lastPEV = 0.0; + $lastPSDL = 0.0; + $Eev = 0.0; $Esdl = 0.0; } - $dtSec = $now - $lastTs; - if ($dtSec < 0.0) $dtSec = 0.0; - if ($dtSec > 3600) $dtSec = 3600.0; - $dtH = $dtSec / 3600.0; + $dtH = ($now - $lastTs) / 3600.0; // Stunden + if ($dtH < 0) $dtH = 0.0; + if ($dtH > 1.0) $dtH = 1.0; // Sicherheitskappe $calc = [ "inputs" => $cache["inputs"] ?? [], @@ -115,9 +122,9 @@ public function Update() "total" => [] ]; - // ----------------------------- + // ===================== // Summen - // ----------------------------- + // ===================== $sdlDisKW_ges = 0.0; $evDisKW_ges = 0.0; $sdlChKW_ges = 0.0; @@ -130,27 +137,40 @@ public function Update() $EV_kWh_ges = 0.0; $totalCapKWh = 0.0; + // Für 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; + if ($capKWh <= 0.0) { + continue; + } + // dynamisch: 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; @@ -160,15 +180,25 @@ public function Update() $real_kWh_ev = 0.0; $real_kWh_sdl = 0.0; - // 3 Fälle - if ($underKWh <= $real_kWh && $real_kWh <= $upKWh) { + // --- 3 Fälle --- + if ($underKWh <= $real_kWh && $upKWh >= $real_kWh) { + // 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 = max(0.0, min(100.0, $SDL_SOC)); + if ($win > 0.0) { + $SDL_SOC = 100.0 * ($real_kWh - $underKWh) / $win; + } else { + $SDL_SOC = 0.0; + } + $SDL_SOC = is_finite($SDL_SOC) ? max(0.0, min(100.0, $SDL_SOC)) : 0.0; - $EV_SOC = ($EV_kWh > 0.0) ? (100.0 * ($real_kWh - $underKWh) / $EV_kWh) : 0.0; - $EV_SOC = max(0.0, min(100.0, $EV_SOC)); + // EV_SOC im Fenster dynamisch + if ($EV_kWh > 0.0) { + $EV_SOC = 100.0 * ($real_kWh - $underKWh) / $EV_kWh; + } else { + $EV_SOC = 0.0; + } + $EV_SOC = is_finite($EV_SOC) ? max(0.0, min(100.0, $EV_SOC)) : 0.0; $sdlDisKW = $sdlShareKW_entladen; $evDisKW = $evShareKW_entladen; @@ -178,24 +208,38 @@ public function Update() $real_kWh_ev = $real_kWh - $underKWh; $real_kWh_sdl = $underKWh; - } elseif ($real_kWh > $upKWh) { + } elseif ($upKWh < $real_kWh) { $EV_SOC = 100.0; + $den1 = ($capKWh - $real_kWh + $underKWh); + $den2 = (2.0 * $underKWh); + + if ($den1 > 0.0 && $den2 > 0.0) { + $SDL_SOC = min(100.0, ($den1 / $den2) * 100.0); + } else { + $SDL_SOC = 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; - $evChKW = 0.0; // EV darf oben nicht laden + $evChKW = 0.0; $real_kWh_ev = $capKWh - ($capKWh - $upKWh + $underKWh); $real_kWh_sdl = ($capKWh - $upKWh + $underKWh) - ($capKWh - $real_kWh); - } else { + } elseif ($underKWh > $real_kWh) { $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 + $evDisKW = 0.0; $sdlChKW = $sdlShareKW_laden; $evChKW = $evShareKW_laden; @@ -203,6 +247,17 @@ public function Update() $real_kWh_sdl = $real_kWh; } + // Null/Full + 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); @@ -233,6 +288,9 @@ 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), @@ -240,66 +298,153 @@ public function Update() ]; } - // Maximalleistungen setzen (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)); + // ===================== + // Nach foreach: Durchschnitts-SoC + Reset-Logik + // ===================== + $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 je nach SoC-Auflösung - // Setpoints berechnen/schreiben (setzt auch "Aktuelle_Leistung_*") - $this->ApplySetpoints(); + $inWindow = (abs($socAvg - $resetTarget) <= $resetTol); - // ----------------------------- - // Dynamisch integrieren mit "Aktueller Leistung" (Ist-basiert) - // ----------------------------- + $armedStr = $this->GetBufferSafe("Int_ResetArmed"); + $armed = ($armedStr === "") ? true : ($armedStr === "1"); + + $doReset = false; + if ($armed && $inWindow) { + $doReset = true; + $armed = false; + } + if (!$inWindow) { + $armed = true; + } + $this->SetBuffer("Int_ResetArmed", $armed ? "1" : "0"); + + // ===================== + // EV-Zone (oben/unten) erkennen und EV-Konto beim Zonenwechsel hart setzen + // Ziel: Wenn obere Grenze erreicht -> SoC_EV = 100% (Eev = EV_total) + // Wenn untere Grenze erreicht -> SoC_EV = 0% (Eev = 0) + // ===================== + $epsKWh = 0.001; + + // ZoneNow aus EV-Referenz ableiten: + // -1 = EV leer (untere Zone), 0 = Fenster, +1 = EV voll (obere Zone) + $zoneNow = 0; + if ($real_kWh_ev_ges <= $epsKWh) { + $zoneNow = -1; + } elseif ($EV_kWh_ges > 0.0 && $real_kWh_ev_ges >= ($EV_kWh_ges - $epsKWh)) { + $zoneNow = 1; + } + + // Vorherige Zone aus Buffer + $zonePrevStr = $this->GetBufferSafe("EV_Zone"); + $zonePrev = ($zonePrevStr === "") ? $zoneNow : (int)$zonePrevStr; + + // Nur beim Zonenwechsel hart setzen (Edge-Trigger) + if ($zoneNow !== $zonePrev) { + if ($zoneNow === 1) { + // Übergang in obere Zone -> EV "voll" + $Eev = $EV_kWh_ges; + } elseif ($zoneNow === -1) { + // Übergang in untere Zone -> EV "leer" + $Eev = 0.0; + } + } + + // Zone speichern + $this->SetBuffer("EV_Zone", (string)$zoneNow); + + // =================================================== + // Integrator: NUR integrieren wenn Soll != 0W + // Angleichen ist raus: wir setzen bei Soll=0 hart auf Referenz + // =================================================== $pEvSoll = (float)GetValue($this->GetIDForIdent("Nennleistung_Soll_EV")); $pSdlSoll = (float)GetValue($this->GetIDForIdent("Nennleistung_Soll_SDL")); - $epsW = 1.0; - $evActive = (abs($pEvSoll) > $epsW); - $sdlActive = (abs($pSdlSoll) > $epsW); + $eps = 1.0; + $evActive = (abs($pEvSoll) > $eps); + $sdlActive = (abs($pSdlSoll) > $eps); - $PEV = (float)GetValue($this->GetIDForIdent("Aktuelle_Leistung_EV")); - $PSDL = (float)GetValue($this->GetIDForIdent("Aktuelle_Leistung_SDL")); + // Initialisierung beim ersten Mal + if ($this->GetBufferSafe("Int_Init") !== "1") { + $Eev = $real_kWh_ev_ges; + $Esdl = $real_kWh_sdl_ges; + $this->SetBuffer("Int_Init", "1"); + } - // Clamp auf lokale Maxima (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; - - $PEV = max(-$maxEV_dis, min($maxEV_ch, $PEV)); - $PSDL = max(-$maxSDL_dis, min($maxSDL_ch, $PSDL)); + // Reset bei SoC~50%: hart auf Referenz (du akzeptierst Sprung) + if ($doReset) { + $Eev = $real_kWh_ev_ges; + $Esdl = $real_kWh_sdl_ges; + } + // Integration nur wenn aktiv, sonst hart auf Referenz if ($evActive) { - $Eev += (($PEV / 1000.0) * $dtH); + $Eev += (($lastPEV / 1000.0) * $dtH); } else { $Eev = $real_kWh_ev_ges; } if ($sdlActive) { - $Esdl += (($PSDL / 1000.0) * $dtH); + $Esdl += (($lastPSDL / 1000.0) * $dtH); } else { $Esdl = $real_kWh_sdl_ges; } - // Clamp Energiekonten + // Clamp: Grenzen strikt einhalten (SDL reserviert!) $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 + // Prozentwerte aus den Konten $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; - $this->SetIdentValue("SoC_EV", round($evPosPct, 3)); + // Output setzen $this->SetIdentValue("SDL_Pos", round($sdlPosPct, 3)); + $this->SetIdentValue("SoC_EV", round($evPosPct, 3)); - // Integrator speichern - $this->SetBuffer("Int_LastTs", (string)$now); - $this->SetBuffer("Int_E_EV_kWh", (string)$Eev); - $this->SetBuffer("Int_E_SDL_kWh", (string)$Esdl); + // Maximalleistungen setzen (wie gehabt) + $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)); + + $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), + "doReset" => $doReset ? 1 : 0 + ]; + + $this->SetIdentValue("CalcJSON", json_encode($calc, JSON_PRETTY_PRINT)); + + // Setpoints schreiben + $this->ApplySetpoints(); + + // ================================== + // Integrator: neue LastP für nächstes Intervall speichern + // ================================== + $aktEV = (float)GetValue($this->GetIDForIdent("Aktuelle_Leistung_EV")); + $aktSDL = (float)GetValue($this->GetIDForIdent("Aktuelle_Leistung_SDL")); + + $this->SetBuffer("Int_LastP_EV_W", (string)$aktEV); + $this->SetBuffer("Int_LastP_SDL_W", (string)$aktSDL); + $this->SetBuffer("Int_LastTs", (string)$now); + + $this->SetBuffer("Int_E_EV_kWh", (string)$Eev); + $this->SetBuffer("Int_E_SDL_kWh", (string)$Esdl); } catch (Throwable $e) { $this->SendDebug("Update ERROR", $e->getMessage() . " @ " . $e->getFile() . ":" . $e->getLine(), 0); @@ -309,8 +454,6 @@ public function Update() } } - - private function BuildBatteryCache(bool $force): void { $batteriesRaw = $this->ReadPropertyString("Batteries"); @@ -416,9 +559,6 @@ public function Update() $this->SendDebug("Cache", "Battery cache rebuilt (" . count($cache["bats"]) . " bats)", 0); } - - - private function GetBufferSafe(string $name): string { $v = $this->GetBuffer($name); @@ -493,75 +633,88 @@ 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 + // Hilfsfunktion: Verteilungslogik (Closure) // --------------------------------------------------------- $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; + return $result; // Nichts zu tun } $isCharge = ($targetPower > 0); $absPower = abs($targetPower); - - $socKey = ($mode === 'EV') ? 'EV_SOC' : 'SDL_SOC'; + // 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 $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]); - $maxW = ((float)$bat[$limitKey]) * 1000.0; // kW -> W + $soc = (int)round($bat[$socKey]); // Auf ganze Zahl runden + $maxW = ((float)$bat[$limitKey]) * 1000.0; // kW in Watt umrechnen $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) { - $result[$gb['idx']] = $gb['maxW'] * $ratio; + $assigned = $gb['maxW'] * $ratio; + $result[$gb['idx']] = $assigned; } } $remainingNeeded -= $powerForThisGroup; } + // Wenn wir entladen, müssen die Werte negativ sein if (!$isCharge) { foreach ($result as $idx => $val) { $result[$idx] = -$val; @@ -572,116 +725,167 @@ private function CalculateBatteryDistribution(float $pEvW, float $pSdlW): array }; // --------------------------------------------------------- - // 1) Verteilung EV/SDL getrennt + // Hauptablauf // --------------------------------------------------------- - $evDistribution = $distributePower($pEvW, 'EV'); + + // 1. Berechnung für EV und SDL getrennt durchführen + $evDistribution = $distributePower($pEvW, 'EV'); $sdlDistribution = $distributePower($pSdlW, 'SDL'); - // --------------------------------------------------------- - // 2) Zusammenführen pro Batterie (Netto-Setpoint) - // --------------------------------------------------------- + // 2. Ergebnisse zusammenführen und Output formatieren $finalOutput = []; + foreach ($batteries as $bat) { $idx = $bat['idx']; - - $valEv = $evDistribution[$idx] ?? 0.0; + // Summe der beiden Anforderungen (kann sich gegenseitig aufheben) + $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 = $totalW; - } elseif ($totalW < 0) { - $dischargeW = -$totalW; + $chargeW = abs($totalW); + } else { + $dischargeW = abs($totalW); } + // JSON Objekt erstellen $finalOutput[] = [ - "idx" => $idx, - "typ" => (string)$bat['typ'], - "chargeW" => round($chargeW, 0), + "idx" => $idx, + "typ" => (string)$bat['typ'], // Typ als String beibehalten + "chargeW" => round($chargeW, 0), // Optional: runden für sauberes JSON "dischargeW" => round($dischargeW, 0) ]; } - // --------------------------------------------------------- - // 3) Ist-Batterieleistung lesen (Summe) - // --------------------------------------------------------- $batteriesRaw = json_decode($this->ReadPropertyString("Batteries")); - $totalPower_ist = 0.0; - 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); - } + $totalPower_ist = 0; + + foreach ($batteriesRaw as $bat) { + $totalPower_ist += (-1)*GetValue($bat->register_bat_power); } - // --------------------------------------------------------- - // 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) - // --------------------------------------------------------- + // ---------------------------- + // ALT (nicht mehr gebraucht) -> AUSKOMMENTIERT + // ---------------------------- + /* + $sumReq = (abs($pEvW) + abs($pSdlW)); + $sumReqRel = ($pEvW + $pSdlW); + + if($sumReq==0){ + + $this->SetValue("Aktuelle_Leistung_EV", $totalPower_ist / 2); + $this->SetValue("Aktuelle_Leistung_SDL", $totalPower_ist / 2); + + }else{ + + if($pEvW>=0){ + $this->SetValue("Aktuelle_Leistung_EV",((1+($totalPower_ist-$sumReqRel) / $sumReq)) * $pEvW); + }else{ + $this->SetValue("Aktuelle_Leistung_EV",((1-($totalPower_ist-$sumReqRel) / $sumReq)) * $pEvW); + } + + if($pSdlW>=0){ + $this->SetValue("Aktuelle_Leistung_SDL",((1+($totalPower_ist-$sumReqRel) / $sumReq)) * $pSdlW); + }else{ + $this->SetValue("Aktuelle_Leistung_SDL",((1-($totalPower_ist-$sumReqRel) / $sumReq)) * $pSdlW); + } + } + */ + + // ---------------------------- + // NEU (genau deine gewünschte Logik) + // aktuelle - EV_SOLL = aktuelle SDL + // aktuelle - SDL_SOLL = aktuelle EV + // ---------------------------- + + + + + /* $eps = 0.01; $aktEV = 0.0; $aktSDL = 0.0; - $evOn = (abs($pEvW) > $eps); - $sdlOn = (abs($pSdlW) > $eps); + // Optional: Wenn einer 0 ist -> aktiver Kanal bekommt totalPower_ist + if (abs($pEvW) < $eps && abs($pSdlW) < $eps) { - if (!$evOn && !$sdlOn) { $aktEV = 0.0; $aktSDL = 0.0; - } elseif (!$evOn && $sdlOn) { - // nur SDL aktiv -> bekommt Istleistung + } elseif (abs($pEvW) < $eps) { + $aktEV = 0.0; $aktSDL = $totalPower_ist; - } elseif ($evOn && !$sdlOn) { - // nur EV aktiv -> bekommt Istleistung + } elseif (abs($pSdlW) < $eps) { + $aktEV = $totalPower_ist; $aktSDL = 0.0; } else { - // beide aktiv - $sameDirection = - (($pEvW > 0 && $pSdlW > 0) || ($pEvW < 0 && $pSdlW < 0)); + + // Beide aktiv + $sameDirection = (($pEvW > 0 && $pSdlW > 0) || ($pEvW < 0 && $pSdlW < 0)); if ($sameDirection) { - // Ist proportional nach |Soll| + // Regel A: gleiche Richtung -> Ist proportional nach |Soll| aufteilen $sumAbs = abs($pEvW) + abs($pSdlW); + if ($sumAbs < $eps) { + // Fallback $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); - } + $aktSDL = $totalPower_ist - $aktEV; // Summe bleibt exakt totalPower_ist } } else { - // gegensätzliche Richtung: Sollwerte anzeigen (stabil, keine Explosion) + // Regel B: gegensätzliche Richtung -> Sollwerte anzeigen $aktEV = $pEvW; $aktSDL = $pSdlW; } } - // Stabil setzen (kein $this->SetValue!) - $this->SetIdentValue("Aktuelle_Leistung_EV", (float)$aktEV); - $this->SetIdentValue("Aktuelle_Leistung_SDL", (float)$aktSDL); + $this->SetValue("Aktuelle_Leistung_EV", (float)$aktEV); + $this->SetValue("Aktuelle_Leistung_SDL", (float)$aktSDL); + */ + + $eps = 0.01; + + $sumSoll = (float)($pEvW + $pSdlW); + + if (abs($sumSoll) > $eps) { + // Normalfall: wir skalieren so, dass EV+SDL genau totalPower_ist ergibt + $factor = (float)($totalPower_ist / $sumSoll); + + $aktEV = (float)($pEvW * $factor); + $aktSDL = (float)($pSdlW * $factor); + + } else { + // Sonderfall: Soll hebt sich nahezu auf (sumSoll ~ 0) + // -> wenn Ist ebenfalls ~0: Soll anzeigen + if (abs($totalPower_ist) < 50.0) { // 50W Toleranz kannst du anpassen + $aktEV = (float)$pEvW; + $aktSDL = (float)$pSdlW; + } else { + // Ist ist nicht 0 obwohl Sollsumme 0 -> Abweichung zuweisen (z.B. SDL) + $aktEV = (float)$pEvW; + $aktSDL = (float)($totalPower_ist - $aktEV); + // Alternative: Abweichung EV geben: + // $aktSDL = (float)$pSdlW; + // $aktEV = (float)($totalPower_ist - $aktSDL); + } + } + + $this->SetValue("Aktuelle_Leistung_EV", $aktEV); + $this->SetValue("Aktuelle_Leistung_SDL", $aktSDL); @@ -689,7 +893,6 @@ private function CalculateBatteryDistribution(float $pEvW, float $pSdlW): array } - private function WriteBatteryPowerSetpoints(array $distribution): void { IPS_LogMessage(