diff --git a/Bat_EV_SDL_V3_Beta/module.php b/Bat_EV_SDL_V3_Beta/module.php index 3e53739..24d7f8d 100644 --- a/Bat_EV_SDL_V3_Beta/module.php +++ b/Bat_EV_SDL_V3_Beta/module.php @@ -76,13 +76,35 @@ class Bat_EV_SDL_V3_Beta extends IPSModule return; } + // ===================== + // Zeitbasis (ms-genau) + // ===================== + $now = microtime(true); + + $lastTs = (float)$this->GetBufferSafe("Int_LastTs"); // Float! + $Esdl_kWh = (float)$this->GetBufferSafe("Int_E_SDL_kWh"); // SDL-Konto in kWh (Integrator) + + if ($lastTs <= 0.0) { + $lastTs = $now; + $Esdl_kWh = 0.0; + } + + $dtSec = $now - $lastTs; + if ($dtSec < 0.0) $dtSec = 0.0; + // optional: falls IPS mal hängt, keine riesigen Sprünge integrieren + if ($dtSec > 10.0) $dtSec = 10.0; + + $dtH = $dtSec / 3600.0; + $calc = [ "inputs" => $cache["inputs"] ?? [], "batteries" => [], "total" => [] ]; + // ===================== // Summen + // ===================== $sdlDisKW_ges = 0.0; $evDisKW_ges = 0.0; $sdlChKW_ges = 0.0; @@ -102,13 +124,13 @@ class Bat_EV_SDL_V3_Beta extends IPSModule continue; } - // dynamisch: SoC lesen + // SoC lesen $socVarId = (int)($c["socVarId"] ?? 0); $socPct = $this->ReadSocPercent($socVarId); $real_kWh = $capKWh / 100.0 * $socPct; - // vorkalkuliert: + // vorkalkuliert $typ = (string)($c["typ"] ?? ("Bat " . ($i + 1))); $underKWh = (float)($c["underKWh"] ?? 0.0); $upKWh = (float)($c["upKWh"] ?? 0.0); @@ -130,24 +152,10 @@ class Bat_EV_SDL_V3_Beta extends IPSModule $real_kWh_ev = 0.0; $real_kWh_sdl = 0.0; - // ============================ - // NEU: SDL_SOC als Fenster-Position (abhängig von under/up) - // -> unter under: 0% - // -> über up: 100% - // -> dazwischen linear 0..100 - // ============================ - $win = ($upKWh - $underKWh); - 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; + // --- 3 Fälle --- + if ($underKWh <= $real_kWh && $real_kWh <= $upKWh) { - // --- Deine 3 Fälle --- - if ($underKWh <= $real_kWh && $upKWh >= $real_kWh) { - - // EV_SOC im Fenster dynamisch + // EV_SOC im Fenster dynamisch (0..100) if ($EV_kWh > 0.0) { $EV_SOC = 100.0 * ($real_kWh - $underKWh) / $EV_kWh; } else { @@ -155,6 +163,16 @@ class Bat_EV_SDL_V3_Beta extends IPSModule } $EV_SOC = is_finite($EV_SOC) ? max(0.0, min(100.0, $EV_SOC)) : 0.0; + // SDL_SOC im Fenster (wie du es früher hattest – hier konstant, wenn du willst) + // Wenn du hier lieber dynamisch willst, sag kurz Bescheid. + $denSDL = ($capKWh - $upKWh + $underKWh); + if ($denSDL > 0.0) { + $SDL_SOC = 100.0 * $underKWh / $denSDL; + } 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; @@ -163,26 +181,41 @@ class Bat_EV_SDL_V3_Beta extends IPSModule $real_kWh_ev = $real_kWh - $underKWh; $real_kWh_sdl = $underKWh; - } elseif ($upKWh < $real_kWh) { + } elseif ($real_kWh > $upKWh) { - // obere Grenze => EV = 100% + // Obere Grenze: EV = 100% $EV_SOC = 100.0; + // SDL_SOC (deine bestehende obere Formel) + $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; + $evChKW = 0.0; // EV darf oben nicht laden $real_kWh_ev = $capKWh - ($capKWh - $upKWh + $underKWh); $real_kWh_sdl = ($capKWh - $upKWh + $underKWh) - ($capKWh - $real_kWh); - } elseif ($underKWh > $real_kWh) { + } else { // $real_kWh < $underKWh - // untere Grenze => EV = 0% + // Untere Grenze: EV = 0% $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; + $sdlDisKW = $sdlShareKW_entladen; - $evDisKW = 0.0; + $evDisKW = 0.0; // EV darf unten nicht entladen $sdlChKW = $sdlShareKW_laden; $evChKW = $evShareKW_laden; @@ -190,10 +223,10 @@ class Bat_EV_SDL_V3_Beta extends IPSModule $real_kWh_sdl = $real_kWh; } - // Null/Full (wie gehabt) + // Null/Full Schutz if ($real_kWh <= 0.0) { $sdlDisKW = 0.0; - $real_kWh_ev = 0.0; + $real_kWh_ev = 0.0; $real_kWh_sdl = 0.0; } elseif ($real_kWh >= $capKWh) { $sdlChKW = 0.0; @@ -241,31 +274,65 @@ class Bat_EV_SDL_V3_Beta extends IPSModule ]; } - // Physikalische Gesamt-Positionen (wie in deinem ursprünglichen Code) - $evPosPct = ($EV_kWh_ges > 0.0) ? ($real_kWh_ev_ges / $EV_kWh_ges * 100.0) : 0.0; - $sdlPosPct = ($SDL_kWh_ges > 0.0) ? ($real_kWh_sdl_ges / $SDL_kWh_ges * 100.0) : 0.0; + // ============================ + // 1) EV-SoC "normal" (physikalisch) ohne Integrator + // ============================ + $evPosPct = ($EV_kWh_ges > 0.0) ? ($real_kWh_ev_ges / $EV_kWh_ges * 100.0) : 0.0; + $evPosPct = max(0.0, min(100.0, $evPosPct)); + $this->SetIdentValue("SoC_EV", round($evPosPct, 3)); + // ============================ + // 2) SDL-SoC integriert mit Nennleistung_Soll_SDL (virtuell) + // ============================ + $pSdlSollW = (float)GetValue($this->GetIDForIdent("Nennleistung_Soll_SDL")); + $epsW = 1.0; + $sdlActive = (abs($pSdlSollW) > $epsW); + + // Soll auf erlaubte SDL-Leistung clampen (aus Summen kW->W) + $maxSDL_ch = $sdlChKW_ges * 1000.0; + $maxSDL_dis = $sdlDisKW_ges * 1000.0; + $pSdlSollW = max(-$maxSDL_dis, min($maxSDL_ch, $pSdlSollW)); + + // Init SDL-Konto einmalig auf physikalische Referenz + if ($this->GetBufferSafe("Int_Init_SDL") !== "1") { + $Esdl_kWh = $real_kWh_sdl_ges; + $this->SetBuffer("Int_Init_SDL", "1"); + } + + // Wenn SDL inaktiv: hart auf Referenz (damit nichts driftet) + if (!$sdlActive) { + $Esdl_kWh = $real_kWh_sdl_ges; + } else { + // Integration: (+) laden -> steigt, (-) entladen -> fällt + $Esdl_kWh += (($pSdlSollW / 1000.0) * $dtH); + } + + // Clamp SDL-Konto + $Esdl_kWh = ($SDL_kWh_ges > 0.0) ? max(0.0, min($SDL_kWh_ges, $Esdl_kWh)) : 0.0; + + $sdlPosPct = ($SDL_kWh_ges > 0.0) ? ($Esdl_kWh / $SDL_kWh_ges * 100.0) : 0.0; + $sdlPosPct = max(0.0, min(100.0, $sdlPosPct)); $this->SetIdentValue("SDL_Pos", round($sdlPosPct, 3)); - $this->SetIdentValue("SoC_EV", round($evPosPct, 3)); + // ============================ + // Maximalleistungen anzeigen + // ============================ $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)); + $this->SetIdentValue("P_EV_laden", (int)round($evChKW_ges * 1000.0, 0)); + $this->SetIdentValue("P_EV_entladen", (int)round($evDisKW_ges * 1000.0, 0)); + // Total JSON $calc["total"] = [ - "SDL_SoC_pct" => round($sdlPosPct, 3), - "EV_SoC_pct" => round($evPosPct, 3), - + "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) + "totalCap_kWh" => round($totalCapKWh, 3) ]; $this->SetIdentValue("CalcJSON", json_encode($calc, JSON_PRETTY_PRINT)); @@ -273,6 +340,12 @@ class Bat_EV_SDL_V3_Beta extends IPSModule // Setpoints wie gehabt $this->ApplySetpoints(); + // ============================ + // Integrator State speichern (nur SDL) + // ============================ + $this->SetBuffer("Int_LastTs", (string)$now); + $this->SetBuffer("Int_E_SDL_kWh", (string)$Esdl_kWh); + } catch (Throwable $e) { $this->SendDebug("Update ERROR", $e->getMessage() . " @ " . $e->getFile() . ":" . $e->getLine(), 0); $this->WriteAllZero("Exception: " . $e->getMessage());