diff --git a/Bat_EV_SDL_V3_Beta/module.php b/Bat_EV_SDL_V3_Beta/module.php index 70016a7..dcf2ec1 100644 --- a/Bat_EV_SDL_V3_Beta/module.php +++ b/Bat_EV_SDL_V3_Beta/module.php @@ -92,23 +92,31 @@ public function Update() } // ===================== - // Integrator: Zeit / Konten laden (microtime für 2s stabiler) + // Integrator: Zeit / Konten laden (microtime ist bei 2s besser) // ===================== $now = microtime(true); $lastTs = (float)$this->GetBufferSafe("Int_LastTs"); - $Eev = (float)$this->GetBufferSafe("Int_E_EV_kWh"); // kWh-Konto EV - $Esdl = (float)$this->GetBufferSafe("Int_E_SDL_kWh"); // kWh-Konto SDL + $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; } - $dtH = ($now - $lastTs) / 3600.0; // Stunden - if ($dtH < 0.0) $dtH = 0.0; - if ($dtH > 1.0) $dtH = 1.0; // Sicherheitskappe + $dtSec = $now - $lastTs; + if ($dtSec < 0.0) $dtSec = 0.0; + if ($dtSec > 3600.0) $dtSec = 3600.0; // Kappe 1h + + $dtH = $dtSec / 3600.0; $calc = [ "inputs" => $cache["inputs"] ?? [], @@ -131,7 +139,7 @@ public function Update() $EV_kWh_ges = 0.0; $totalCapKWh = 0.0; - // Reset bei SoC≈50% (global): kapazitätsgewichteter Durchschnitt + // Reset bei SoC=50% (mehrere Batterien): kapazitätsgewichteter Durchschnitt $socCapSum = 0.0; $socWeighted = 0.0; @@ -172,7 +180,7 @@ public function Update() $real_kWh_ev = 0.0; $real_kWh_sdl = 0.0; - // --- 3 Fälle (physikalische Zuordnung + EV/SDL Limits) --- + // --- 3 Fälle --- if ($underKWh <= $real_kWh && $real_kWh <= $upKWh) { // SDL_SOC im Fenster dynamisch (0..100) @@ -199,7 +207,6 @@ public function Update() // Oberhalb: EV voll, EV darf NICHT laden $EV_SOC = 100.0; - // SDL_SOC (deine bestehende obere Formel) $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; @@ -210,7 +217,6 @@ public function Update() $sdlChKW = $sdlShareKW_laden; $evChKW = 0.0; // ✅ EV darf oben nicht laden - // Referenz-kWh $real_kWh_ev = $capKWh - ($capKWh - $upKWh + $underKWh); $real_kWh_sdl = ($capKWh - $upKWh + $underKWh) - ($capKWh - $real_kWh); @@ -219,7 +225,6 @@ public function Update() // Unterhalb: EV leer, EV darf NICHT entladen $EV_SOC = 0.0; - // SDL_SOC (deine bestehende untere Formel) $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; @@ -285,12 +290,20 @@ public function Update() } // ===================== - // Nach foreach: Reset bei SoC ~ 50% (einmalig) + // Maximalleistungen setzen (Anzeige/Debug) – jetzt sofort, weil wir sie gleich brauchen + // ===================== + $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; $resetTarget = 50.0; - $resetTol = 0.5; // bei 2s Update: 0.2..0.5 je nach SoC-Auflösung + $resetTol = 0.5; // bei 2s Update: 0.2..0.5 $inWindow = (abs($socAvg - $resetTarget) <= $resetTol); @@ -317,7 +330,7 @@ public function Update() } // ===================== - // EV-Zone Edge Reset (oben/unten) -> SoC_EV = 100% / 0% + // EV-Zone Edge Reset (oben/unten) // ===================== $epsKWh = 0.001; $zoneNow = 0; @@ -331,16 +344,13 @@ public function Update() $zonePrev = ($zonePrevStr === "") ? $zoneNow : (int)$zonePrevStr; if ($zoneNow !== $zonePrev) { - if ($zoneNow === 1) { - $Eev = $EV_kWh_ges; // EV voll -> 100% - } elseif ($zoneNow === -1) { - $Eev = 0.0; // EV leer -> 0% - } + if ($zoneNow === 1) $Eev = $EV_kWh_ges; // 100% + if ($zoneNow === -1) $Eev = 0.0; // 0% } $this->SetBuffer("EV_Zone", (string)$zoneNow); // ===================== - // Reset bei SoC≈50% (du akzeptierst Sprung) + // 50%-Reset (du akzeptierst Sprung) // ===================== if ($doReset50) { $Eev = $real_kWh_ev_ges; @@ -348,7 +358,7 @@ public function Update() } // ===================== - // Variante A: Integration mit SOLL (stabil) + Clamp an lokale Maxima + // Integrator soll mit "aktuellen" Werten laufen – aber bereinigt! // ===================== $pEvSoll = (float)GetValue($this->GetIDForIdent("Nennleistung_Soll_EV")); $pSdlSoll = (float)GetValue($this->GetIDForIdent("Nennleistung_Soll_SDL")); @@ -357,34 +367,56 @@ public function Update() $evActive = (abs($pEvSoll) > $epsW); $sdlActive = (abs($pSdlSoll) > $epsW); - // Maxima in W direkt aus lokalen Summen (kW -> W) + // 1) Rohwerte: "Aktuelle Leistung" aus Variablen lesen + $rawEV = (float)GetValue($this->GetIDForIdent("Aktuelle_Leistung_EV")); + $rawSDL = (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; - // Soll clampen - $PevW = max(-$maxEV_dis, min($maxEV_ch, $pEvSoll)); - $PsdlW = max(-$maxSDL_dis, min($maxSDL_ch, $pSdlSoll)); + $rawEV = max(-$maxEV_dis, min($maxEV_ch, $rawEV)); + $rawSDL = max(-$maxSDL_dis, min($maxSDL_ch, $rawSDL)); - // EV-Zonenregeln auch für Anzeige/Integrator erzwingen - if ($zoneNow === 1 && $PevW > 0.0) $PevW = 0.0; // oben: EV darf nicht laden - if ($zoneNow === -1 && $PevW < 0.0) $PevW = 0.0; // unten: EV darf nicht entladen + // 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 - // Integration nur wenn aktiv, sonst hart auf Referenz + // 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 += (($PevW / 1000.0) * $dtH); + $Eev += (($PEV / 1000.0) * $dtH); } else { $Eev = $real_kWh_ev_ges; + $PEV = 0.0; } if ($sdlActive) { - $Esdl += (($PsdlW / 1000.0) * $dtH); + $Esdl += (($PSDL / 1000.0) * $dtH); } else { $Esdl = $real_kWh_sdl_ges; + $PSDL = 0.0; } - // Clamp: Grenzen strikt einhalten + // 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; @@ -396,12 +428,7 @@ public function Update() $this->SetIdentValue("SoC_EV", round($evPosPct, 3)); $this->SetIdentValue("SDL_Pos", round($sdlPosPct, 3)); - // Maximalleistungen (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)); - + // JSON total $calc["total"] = [ "SDL_SoC_pct" => round($sdlPosPct, 3), "EV_SoC_pct" => round($evPosPct, 3), @@ -416,13 +443,17 @@ public function Update() "totalCap_kWh" => round($totalCapKWh, 3), "socAvg" => round($socAvg, 3), - "doReset50" => $doReset50 ? 1 : 0, - "evZone" => $zoneNow + + "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 (deine bestehende Logik) + // Setpoints schreiben $this->ApplySetpoints(); // ===================== @@ -432,6 +463,10 @@ public function Update() $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());