From 6f335c1fc879f38758efa07050daf14967a68681 Mon Sep 17 00:00:00 2001 From: "belevo\\mh" Date: Tue, 12 May 2026 13:30:31 +0200 Subject: [PATCH] no message --- Bat_EV_SDL_V4/form.json | 104 ++++++++++++++++++++++-------------- Bat_EV_SDL_V4/module.php | 112 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 175 insertions(+), 41 deletions(-) diff --git a/Bat_EV_SDL_V4/form.json b/Bat_EV_SDL_V4/form.json index 499fa6c..5afa226 100644 --- a/Bat_EV_SDL_V4/form.json +++ b/Bat_EV_SDL_V4/form.json @@ -1,9 +1,9 @@ { "elements": [ - { - "type": "Label", - "caption": "Aufgepasst: Bei Goodwe nur Ladenvariabel auswählen und entladen Dummy Variabel.\nGoodwe braucht nur eine Leistungssoll Variabel. Entlade NICHT auf gleiche Variabel setzen wie Laden\nGoodwe: Laden=11, Entladen=12,\nSolaredge: Laden=3, Entladen=4\nDefault: Laden=1, Entladen=2" - }, + { + "type": "Label", + "caption": "Aufgepasst: Bei Goodwe nur Ladenvariabel auswählen und entladen Dummy Variabel.\nGoodwe braucht nur eine Leistungssoll Variabel. Entlade NICHT auf gleiche Variabel setzen wie Laden\nGoodwe: Laden=11, Entladen=12,\nSolaredge: Laden=3, Entladen=4\nDefault: Laden=1, Entladen=2" + }, { "type": "List", "name": "Batteries", @@ -95,17 +95,17 @@ } }, { - "caption": "Min. SoC", - "name": "minPhysicalSocPct", - "width": "100px", - "suffix": " %", - "add": 5, - "edit": { + "caption": "Min. SoC", + "name": "minPhysicalSocPct", + "width": "100px", + "suffix": " %", + "add": 5, + "edit": { "type": "NumberSpinner", "minimum": 0, "maximum": 30, "digits": 1 - } + } } ], "values": [] @@ -131,13 +131,13 @@ "digits": 0 }, { - "type": "NumberSpinner", - "name": "ReserveHours", - "caption": "SDL Reservezeit", - "suffix": " h", - "minimum": 0, - "maximum": 24, - "digits": 2 + "type": "NumberSpinner", + "name": "ReserveHours", + "caption": "SDL Reservezeit", + "suffix": " h", + "minimum": 0, + "maximum": 24, + "digits": 2 }, { "type": "NumberSpinner", @@ -167,42 +167,66 @@ "type": "NumberSpinner", "name": "UpdateInterval", "caption": "Update Intervall", - "suffix": "S", + "suffix": " s", "minimum": 1, + "digits": 0 + }, + { + "type": "Label", + "caption": "EV-SoC Nachberechnung:\n\nAlle X Stunden wird geprüft, ob der virtuelle SDL-SoC bei 50% liegt.\nWenn ja, wird der EV-SoC anhand der physischen Batterie-SoCs neu berechnet.\nDie EV-Toleranz verhindert kleine unnötige Korrekturen.\n\n0 Stunden = automatische EV-Neuberechnung deaktiviert." + }, + { + "type": "NumberSpinner", + "name": "EV_Recalc_IntervalHours", + "caption": "EV SoC neu berechnen alle", + "suffix": " h", + "minimum": 0, + "maximum": 168, + "digits": 2 + }, + { + "type": "NumberSpinner", + "name": "EV_Recalc_TolerancePct", + "caption": "EV Recalc Toleranz", + "suffix": " %", + "minimum": 0, + "maximum": 100, "digits": 2 }, - { "type": "CheckBox", "name": "FilterAktiv", "caption": "Filter für aktuelle EV/SDL Leistung aktiv. Dient für die Visualisierung, um Leistungssprünge zu vermeiden." }, { - "type": "Label", - "caption": "Filter für aktuelle Leistung:\n\nToleranz (%): Wie stark der Istwert vom Soll abweichen darf\nRampe (W/s): Wie schnell die Leistung verändert wird (Trägheit)\nTreffer: Wie oft ein Wert passen muss, bevor er übernommen wird\n\n→ Höhere Rampe = schneller, aber sprunghafter\n→ Niedrigere Rampe = ruhiger, aber träger" + "type": "Label", + "caption": "Filter für aktuelle Leistung:\n\nToleranz (%): Wie stark der Istwert vom Soll abweichen darf\nRampe (W/s): Wie schnell die Leistung verändert wird (Trägheit)\nTreffer: Wie oft ein Wert passen muss, bevor er übernommen wird\n\n→ Höhere Rampe = schneller, aber sprunghafter\n→ Niedrigere Rampe = ruhiger, aber träger" }, { - "type": "NumberSpinner", - "name": "FilterTolerancePct", - "caption": "Filter Toleranz (%)", - "minimum": 0, - "maximum": 100, - "digits": 1 + "type": "NumberSpinner", + "name": "FilterTolerancePct", + "caption": "Filter Toleranz", + "suffix": " %", + "minimum": 0, + "maximum": 100, + "digits": 1 }, { - "type": "NumberSpinner", - "name": "FilterRampWPerSec", - "caption": "Filter Rampe (W/s)", - "minimum": 100, - "maximum": 20000, - "digits": 0 + "type": "NumberSpinner", + "name": "FilterRampWPerSec", + "caption": "Filter Rampe", + "suffix": " W/s", + "minimum": 100, + "maximum": 20000, + "digits": 0 }, { - "type": "NumberSpinner", - "name": "FilterHits", - "caption": "Filter Treffer bis Übernahme", - "minimum": 1, - "maximum": 10 + "type": "NumberSpinner", + "name": "FilterHits", + "caption": "Filter Treffer bis Übernahme", + "minimum": 1, + "maximum": 10, + "digits": 0 } ] } @@ -219,4 +243,4 @@ "onClick": "GEF_Update($id);" } ] -} +} \ No newline at end of file diff --git a/Bat_EV_SDL_V4/module.php b/Bat_EV_SDL_V4/module.php index b39a86f..533e0ab 100644 --- a/Bat_EV_SDL_V4/module.php +++ b/Bat_EV_SDL_V4/module.php @@ -18,6 +18,8 @@ class Bat_EV_SDL_V4 extends IPSModule $this->RegisterPropertyFloat("FilterTolerancePct", 15.0); // % $this->RegisterPropertyFloat("FilterRampWPerSec", 2000.0); // W/s $this->RegisterPropertyInteger("FilterHits", 1); + $this->RegisterPropertyFloat("EV_Recalc_IntervalHours", 6.0); + $this->RegisterPropertyFloat("EV_Recalc_TolerancePct", 2.0); // Status $this->RegisterVariableBoolean("State", "Aktiv", "~Switch", 1); @@ -424,7 +426,8 @@ class Bat_EV_SDL_V4 extends IPSModule $eSDL = ($sdlTotal > 0.0) ? $sdlTotal * $lastSdlPct / 100.0 : 0.0; $eEV = ($evTotal > 0.0) ? $evTotal * $lastEvPct / 100.0 : 0.0; - + + $this->MaybeRecalculateEVFromPhysical($plan, $eSDL, $eEV, $sdlTotal, $evTotal); $this->SetBuffer("Int_E_SDL_kWh", (string)$eSDL); $this->SetBuffer("Int_E_EV_kWh", (string)$eEV); $this->SetBuffer("Int_LastTs", (string)$now); @@ -916,6 +919,113 @@ class Bat_EV_SDL_V4 extends IPSModule } + private function MaybeRecalculateEVFromPhysical(array $plan, float $eSDL, float &$eEV, float $sdlTotal, float $evTotal): void + { + if ($evTotal <= 0.0 || $sdlTotal <= 0.0) { + return; + } + + $intervalHours = max(0.0, (float)$this->ReadPropertyFloat("EV_Recalc_IntervalHours")); + + if ($intervalHours <= 0.0) { + return; + } + + $now = microtime(true); + + $lastCheckTs = (float)$this->GetBufferSafe("EV_Recalc_LastCheckTs"); + + // Nur alle X Stunden prüfen + if ( + $lastCheckTs > 0.0 && + ($now - $lastCheckTs) < ($intervalHours * 3600.0) + ) { + return; + } + + $this->SetBuffer("EV_Recalc_LastCheckTs", (string)$now); + + // SDL muss bei 50% sein + $sdlPct = ($eSDL / $sdlTotal) * 100.0; + + if (abs($sdlPct - 50.0) > 0.001) { + $this->SendDebug( + "EV_Recalc", + "Skip: SDL nicht bei 50%, aktuell=" . round($sdlPct, 3) . "%", + 0 + ); + return; + } + + $newEVkWh = 0.0; + + foreach (($plan["bats"] ?? []) as $bat) { + + $capKWh = (float)($bat["capKWh"] ?? 0.0); + $socVarId = (int)($bat["socVarId"] ?? 0); + + $realSocPct = $this->ReadSocPercent($socVarId); + $realKWh = $capKWh * $realSocPct / 100.0; + + $underKWh = (float)($bat["underKWh"] ?? 0.0); + + // Maximales EV Fenster dieser Batterie + $evBatTotal = (float)($bat["EV_kWh_total"] ?? 0.0); + + // EV Anteil relativ zur unteren Grenze + $evPart = $realKWh - $underKWh; + + // Begrenzung: + // unter underKWh => 0 + // über upKWh => max EV Fenster + $evPart = max(0.0, min($evBatTotal, $evPart)); + + $newEVkWh += $evPart; + } + + // Global begrenzen + $newEVkWh = max(0.0, min($evTotal, $newEVkWh)); + + $newEVpct = ($evTotal > 0.0) + ? ($newEVkWh / $evTotal * 100.0) + : 0.0; + + $oldEVpct = ($evTotal > 0.0) + ? ($eEV / $evTotal * 100.0) + : 0.0; + + $tolPct = max(0.0, (float)$this->ReadPropertyFloat("EV_Recalc_TolerancePct")); + + // Nur übernehmen wenn Differenz gross genug + if (abs($newEVpct - $oldEVpct) <= $tolPct) { + + $this->SendDebug( + "EV_Recalc", + "Skip: Differenz innerhalb Toleranz. Alt=" . + round($oldEVpct, 3) . + "% Neu=" . + round($newEVpct, 3) . + "%", + 0 + ); + + return; + } + + $eEV = $newEVkWh; + + $this->SendDebug( + "EV_Recalc", + "EV neu berechnet. Alt=" . + round($oldEVpct, 3) . + "% Neu=" . + round($newEVpct, 3) . + "%", + 0 + ); + } + + private function RefreshDynamicPlanValues(array $plan): array { foreach ($plan["bats"] as &$bat) {