diff --git a/Bat_EV_SDL_V4/form.json b/Bat_EV_SDL_V4/form.json index aef7d56..cec16fd 100644 --- a/Bat_EV_SDL_V4/form.json +++ b/Bat_EV_SDL_V4/form.json @@ -2,70 +2,86 @@ "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" + "caption": "Aufgepasst: Bei Goodwe nur Ladenvariable auswählen und Entladen Dummy-Variable. Goodwe braucht nur eine Leistungssoll-Variable. Entlade NICHT auf gleiche Variable setzen wie Laden.\nGoodwe: Laden=11, Entladen=12\nSolarEdge: Laden=3, Entladen=4\nDefault: Laden=1, Entladen=2" }, { "type": "List", "name": "Batteries", - "caption": "Batterien", + "caption": "Batterien mit Typ, Leistung und Kapazität", "add": true, "delete": true, "columns": [ { "caption": "Typ", "name": "typ", - "width": "100px", - "add": "Goodwe", - "edit": { "type": "ValidationTextBox" } + "width": "120px", + "add": "Batterie", + "edit": { + "type": "ValidationTextBox" + } }, { - "caption": "Leistung in W", + "caption": "Leistung (W)", "name": "powerbat", - "width": "150px", + "width": "120px", "add": 5000, - "edit": { "type": "NumberSpinner" } + "edit": { + "type": "NumberSpinner" + } }, { - "caption": "Kapazität in kWh", + "caption": "Kapazität (kWh)", "name": "capazity", - "width": "180px", + "width": "140px", "add": 60, - "edit": { "type": "NumberSpinner" } + "edit": { + "type": "NumberSpinner" + } }, { "caption": "SoC", "name": "soc", - "width": "200px", + "width": "160px", "add": 0, - "edit": { "type": "SelectVariable" } + "edit": { + "type": "SelectVariable" + } }, { - "caption": "Batterieleistung Laden", + "caption": "Power Laden", "name": "powerbat_laden", - "width": "200px", + "width": "160px", "add": 0, - "edit": { "type": "SelectVariable" } + "edit": { + "type": "SelectVariable" + } }, { - "caption": "Batterieleistung Entladen", + "caption": "Power Entladen", "name": "powerbat_entladen", - "width": "200px", + "width": "160px", "add": 0, - "edit": { "type": "SelectVariable" } + "edit": { + "type": "SelectVariable" + } }, { - "caption": "Register Laden/Entladen Modus", + "caption": "Modus Register", "name": "register_ladenentladen_modus", - "width": "260px", + "width": "180px", "add": 0, - "edit": { "type": "SelectVariable" } + "edit": { + "type": "SelectVariable" + } }, { - "caption": "Aktuelle Batterieleistung", + "caption": "Ist-Leistung", "name": "register_bat_power", - "width": "260px", + "width": "180px", "add": 0, - "edit": { "type": "SelectVariable" } + "edit": { + "type": "SelectVariable" + } } ] }, @@ -73,15 +89,13 @@ "type": "NumberSpinner", "name": "SDL_Leistung_Laden", "caption": "SDL Leistung Laden", - "suffix": "W", - "minimum": 0 + "suffix": "W" }, { "type": "NumberSpinner", "name": "SDL_Leistung_Entladen", "caption": "SDL Leistung Entladen", - "suffix": "W", - "minimum": 0 + "suffix": "W" }, { "type": "NumberSpinner", @@ -94,10 +108,10 @@ }, { "type": "NumberSpinner", - "name": "UpdateIntervalMs", - "caption": "Neuberechnung alle", - "suffix": "S", - "minimum": 1, + "name": "UpdateInterval", + "caption": "Update Intervall", + "suffix": "Sekunden", + "minimum": 0, "maximum": 86400 }, { diff --git a/Bat_EV_SDL_V4/module.php b/Bat_EV_SDL_V4/module.php index 81fa561..ad3550e 100644 --- a/Bat_EV_SDL_V4/module.php +++ b/Bat_EV_SDL_V4/module.php @@ -6,39 +6,44 @@ class Bat_EV_SDL_V4 extends IPSModule { parent::Create(); + // Properties $this->RegisterPropertyString("Batteries", "[]"); - $this->RegisterPropertyInteger("SDL_Leistung_Laden", 0); - $this->RegisterPropertyInteger("SDL_Leistung_Entladen", 0); - $this->RegisterPropertyInteger("UpdateInterval", 2); - $this->RegisterPropertyFloat("ReserveHours", 0.5); - $this->RegisterPropertyBoolean("FilterAktiv", false); + $this->RegisterPropertyInteger("SDL_Leistung_Laden", 0); // W + $this->RegisterPropertyInteger("SDL_Leistung_Entladen", 0); // W + $this->RegisterPropertyFloat("ReserveHours", 0.5); // h, vorher fix 0.5h + $this->RegisterPropertyInteger("UpdateInterval", 5); // Sekunden + $this->RegisterPropertyBoolean("FilterAktiv", true); + // Status $this->RegisterVariableBoolean("State", "Aktiv", "~Switch", 1); $this->EnableAction("State"); - $this->RegisterVariableFloat("Nennleistung_Soll_EV", "Nennleistung Soll EV", "", 2); - $this->RegisterVariableFloat("Nennleistung_Soll_SDL", "Nennleistung Soll SDL", "", 3); - $this->RegisterVariableFloat("Aktuelle_Leistung_SDL", "Aktuelle Leistung SDL", "", 4); - $this->RegisterVariableFloat("Aktuelle_Leistung_EV", "Aktuelle Leistung EV", "", 5); - + // Prozentwerte / virtuelle Konten $this->RegisterVariableFloat("SDL_Pos", "SDL Energie verfügbar (%)", "", 10); - $this->RegisterVariableFloat("SoC_EV", "EV Energie verfügbar (%)", "", 11); - - $this->RegisterVariableFloat("P_SDL_laden", "P SDL laden max (W)", "", 21); - $this->RegisterVariableFloat("P_SDL_entladen", "P SDL entladen max (W)", "", 22); - $this->RegisterVariableInteger("P_EV_laden", "P EV laden max (W)", "", 31); - $this->RegisterVariableInteger("P_EV_entladen", "P EV entladen max (W)", "", 32); - + $this->RegisterVariableFloat("SoC_EV", "EV Energie verfügbar (%)", "", 11); $this->RegisterVariableFloat("SDL_Start_Pos", "SDL Start SoC (%)", "", 35); + $this->RegisterVariableFloat("EV_Start_Pos", "EV Start SoC (%)", "", 36); - $this->RegisterVariableBoolean("SDL_Reset", "SDL Konto Reset", "~Switch", 36); + // Soll-/Istwerte + $this->RegisterVariableFloat("Nennleistung_Soll_EV", "Nennleistung Soll EV", "", 2); + $this->RegisterVariableFloat("Nennleistung_Soll_SDL", "Nennleistung Soll SDL", "", 3); + $this->RegisterVariableFloat("Aktuelle_Leistung_EV", "Aktuelle Leistung EV", "", 5); + $this->RegisterVariableFloat("Aktuelle_Leistung_SDL", "Aktuelle Leistung SDL", "", 4); + + // Maximalleistungen + $this->RegisterVariableFloat("P_SDL_laden", "P SDL laden max (W)", "", 21); + $this->RegisterVariableFloat("P_SDL_entladen", "P SDL entladen max (W)", "", 22); + $this->RegisterVariableInteger("P_EV_laden", "P EV laden max (W)", "", 31); + $this->RegisterVariableInteger("P_EV_entladen", "P EV entladen max (W)", "", 32); + + // Reset + $this->RegisterVariableBoolean("SDL_Reset", "SDL/EV Konto Reset (Start)", "~Switch", 37); $this->EnableAction("SDL_Reset"); - $this->RegisterVariableBoolean("EV_Reset", "EV Konto Reset", "~Switch", 37); - $this->EnableAction("EV_Reset"); - - $this->RegisterVariableString("CalcJSON", "Berechnung JSON", "", 99); + // Debug + $this->RegisterVariableString("CalcJSON", "Berechnung (JSON)", "", 99); + // Timer $this->RegisterTimer("UpdateTimer", 0, 'GEF_Update($_IPS["TARGET"]);'); } @@ -49,755 +54,13 @@ class Bat_EV_SDL_V4 extends IPSModule $intervalSec = (int)$this->ReadPropertyInteger("UpdateInterval"); $this->SetTimerInterval("UpdateTimer", ($intervalSec > 0) ? $intervalSec * 1000 : 0); - $this->BuildStaticBatteryConfig(); + // Grenz-/Fensterberechnung nur hier hart neu aufbauen + $this->BuildBatteryCache(true); + + // Wenn Konfig neu ist: Integratoren sauber auf Startpunkte setzen + $this->ResetVirtualAccountsToStartPoint(false); $this->Update(); } - public function RequestAction($Ident, $Value) - { - switch ($Ident) { - case "State": - SetValue($this->GetIDForIdent("State"), (bool)$Value); - if ((bool)$Value) { - $this->Update(); - } - return; - - case "SDL_Reset": - if ((bool)$Value) { - $this->ResetSDLToStartPoint(); - SetValue($this->GetIDForIdent("SDL_Reset"), false); - $this->Update(); - } - return; - - case "EV_Reset": - if ((bool)$Value) { - $this->ResetEVToRealPoint(); - SetValue($this->GetIDForIdent("EV_Reset"), false); - $this->Update(); - } - return; - } - - throw new Exception("Invalid Ident: " . $Ident); - } - - public function Update() - { - $semKey = "BEVSDL4_Update_" . $this->InstanceID; - - if (!IPS_SemaphoreEnter($semKey, 5000)) { - $this->SendDebug("Update", "SKIP Semaphore locked", 0); - return; - } - - try { - if (!GetValue($this->GetIDForIdent("State"))) { - return; - } - - $cfg = json_decode($this->GetBufferSafe("StaticBatteryConfig"), true); - if (!is_array($cfg) || empty($cfg["bats"])) { - $this->WriteAllZero("StaticBatteryConfig leer"); - return; - } - - $nowMs = (int)round(microtime(true) * 1000); - $lastMs = (int)$this->GetBufferSafe("Int_LastMs"); - - if ($lastMs <= 0) { - $lastMs = $nowMs; - } - - $dtMs = max(0, $nowMs - $lastMs); - $dtMs = min($dtMs, 60000); - $dtH = $dtMs / 3600000.0; - - $runtime = $this->CalculateRuntimeState($cfg); - - $sdlTotalKWh = (float)$runtime["total"]["SDL_kWh_total"]; - $evTotalKWh = (float)$runtime["total"]["EV_kWh_total"]; - - $EsdlKWh = (float)$this->GetBufferSafe("Int_E_SDL_kWh"); - $EevKWh = (float)$this->GetBufferSafe("Int_E_EV_kWh"); - - if ($this->GetBufferSafe("Int_Init_SDL") !== "1") { - $EsdlKWh = (float)$runtime["total"]["SDL_Start_kWh"]; - $this->SetBuffer("Int_Init_SDL", "1"); - } - - if ($this->GetBufferSafe("Int_Init_EV") !== "1") { - $EevKWh = (float)$runtime["total"]["Real_EV_kWh"]; - $this->SetBuffer("Int_Init_EV", "1"); - } - - $pSdlIstWRaw = (float)GetValue($this->GetIDForIdent("Aktuelle_Leistung_SDL")); - $pEvIstWRaw = (float)GetValue($this->GetIDForIdent("Aktuelle_Leistung_EV")); - - $maxSdlChargeW = (float)$runtime["total"]["SDL_Charge_kW"] * 1000.0; - $maxSdlDisW = (float)$runtime["total"]["SDL_Discharge_kW"] * 1000.0; - $maxEvChargeW = (float)$runtime["total"]["EV_Charge_kW"] * 1000.0; - $maxEvDisW = (float)$runtime["total"]["EV_Discharge_kW"] * 1000.0; - - $pSdlIstW = max(-$maxSdlDisW, min($maxSdlChargeW, $pSdlIstWRaw)); - $pEvIstW = max(-$maxEvDisW, min($maxEvChargeW, $pEvIstWRaw)); - - if (abs($pSdlIstW) > 1.0) { - $EsdlKWh += ($pSdlIstW / 1000.0) * $dtH; - } - - if (abs($pEvIstW) > 1.0) { - $EevKWh += ($pEvIstW / 1000.0) * $dtH; - } - - $EsdlKWh = ($sdlTotalKWh > 0) ? max(0.0, min($sdlTotalKWh, $EsdlKWh)) : 0.0; - $EevKWh = ($evTotalKWh > 0) ? max(0.0, min($evTotalKWh, $EevKWh)) : 0.0; - - $sdlPct = ($sdlTotalKWh > 0) ? ($EsdlKWh / $sdlTotalKWh * 100.0) : 0.0; - $evPct = ($evTotalKWh > 0) ? ($EevKWh / $evTotalKWh * 100.0) : 0.0; - - $sdlPct = max(0.0, min(100.0, $sdlPct)); - $evPct = max(0.0, min(100.0, $evPct)); - - $this->SetIdentValue("SDL_Pos", round($sdlPct, 3)); - $this->SetIdentValue("SoC_EV", round($evPct, 3)); - - $this->SetIdentValue("P_SDL_laden", round($maxSdlChargeW, 0)); - $this->SetIdentValue("P_SDL_entladen", round($maxSdlDisW, 0)); - $this->SetIdentValue("P_EV_laden", (int)round($maxEvChargeW, 0)); - $this->SetIdentValue("P_EV_entladen", (int)round($maxEvDisW, 0)); - $this->SetIdentValue("SDL_Start_Pos", round((float)$runtime["total"]["SDL_Start_pct"], 3)); - - $runtime["total"]["SDL_SoC_pct"] = round($sdlPct, 3); - $runtime["total"]["EV_SoC_pct"] = round($evPct, 3); - $runtime["total"]["SDL_E_kWh_virtual"] = round($EsdlKWh, 4); - $runtime["total"]["EV_E_kWh_virtual"] = round($EevKWh, 4); - $runtime["total"]["dtMs"] = $dtMs; - $runtime["total"]["SDL_Ist_W_raw"] = round($pSdlIstWRaw, 0); - $runtime["total"]["SDL_Ist_W_used"] = round($pSdlIstW, 0); - $runtime["total"]["EV_Ist_W_raw"] = round($pEvIstWRaw, 0); - $runtime["total"]["EV_Ist_W_used"] = round($pEvIstW, 0); - - $this->SetIdentValue("CalcJSON", json_encode($runtime, JSON_PRETTY_PRINT)); - - $this->ApplySetpoints($runtime); - - $this->SetBuffer("Int_E_SDL_kWh", (string)$EsdlKWh); - $this->SetBuffer("Int_E_EV_kWh", (string)$EevKWh); - $this->SetBuffer("Int_LastMs", (string)$nowMs); - - } catch (Throwable $e) { - $this->SendDebug("Update ERROR", $e->getMessage(), 0); - $this->WriteAllZero("Exception: " . $e->getMessage()); - } finally { - IPS_SemaphoreLeave($semKey); - } - } - - private function BuildStaticBatteryConfig(): void - { - $batteriesRaw = $this->ReadPropertyString("Batteries"); - $batteries = json_decode($batteriesRaw, true); - if (!is_array($batteries)) { - $batteries = []; - } - - $sdlWCharge = max(0, (int)$this->ReadPropertyInteger("SDL_Leistung_Laden")); - $sdlWDis = max(0, (int)$this->ReadPropertyInteger("SDL_Leistung_Entladen")); - $reserveH = max(0.0, (float)$this->ReadPropertyFloat("ReserveHours")); - - $sumPowerW = 0.0; - foreach ($batteries as $b) { - $sumPowerW += max(0.0, (float)($b["powerbat"] ?? 0)); - } - - $cfg = [ - "inputs" => [ - "SDL_Leistung_W_laden" => $sdlWCharge, - "SDL_Leistung_W_entladen" => $sdlWDis, - "ReserveHours" => $reserveH, - "SumBatPower_W" => round($sumPowerW, 0) - ], - "bats" => [] - ]; - - if ($sumPowerW <= 0.0) { - $this->SetBuffer("StaticBatteryConfig", json_encode($cfg)); - return; - } - - $sdlKWCharge = $sdlWCharge / 1000.0; - $sdlKWDis = $sdlWDis / 1000.0; - $sumPowerKW = $sumPowerW / 1000.0; - - foreach ($batteries as $idx => $b) { - $pBatW = max(0.0, (float)($b["powerbat"] ?? 0)); - $pBatKW = $pBatW / 1000.0; - $capKWh = max(0.0, (float)($b["capazity"] ?? 0)); - - if ($capKWh <= 0.0 || $pBatKW <= 0.0) { - continue; - } - - $sdlShareKWCharge = ($sdlKWCharge / $sumPowerKW) * $pBatKW; - $sdlShareKWDis = ($sdlKWDis / $sumPowerKW) * $pBatKW; - - $evShareKWCharge = max(0.0, $pBatKW - $sdlShareKWCharge); - $evShareKWDis = max(0.0, $pBatKW - $sdlShareKWDis); - - $underKWh = $sdlShareKWDis * $reserveH; - $upperReserveKWh = $sdlShareKWCharge * $reserveH; - $upKWh = $capKWh - $upperReserveKWh; - - $underKWh = max(0.0, min($capKWh, $underKWh)); - $upKWh = max(0.0, min($capKWh, $upKWh)); - - if ($upKWh < $underKWh) { - $mid = $capKWh / 2.0; - $underKWh = $mid; - $upKWh = $mid; - } - - $sdlTotalKWh = $underKWh + ($capKWh - $upKWh); - $evTotalKWh = max(0.0, $capKWh - $sdlTotalKWh); - - $cfg["bats"][] = [ - "idx" => $idx, - "typ" => (string)($b["typ"] ?? ("Bat " . ($idx + 1))), - "socVarId" => (int)($b["soc"] ?? 0), - "capKWh" => $capKWh, - "pBatW" => $pBatW, - - "sdlShareKW_laden" => $sdlShareKWCharge, - "sdlShareKW_entladen" => $sdlShareKWDis, - "evShareKW_laden" => $evShareKWCharge, - "evShareKW_entladen" => $evShareKWDis, - - "underKWh" => $underKWh, - "upKWh" => $upKWh, - "upperReserveKWh" => $upperReserveKWh, - - "SDL_kWh_total" => $sdlTotalKWh, - "EV_kWh_total" => $evTotalKWh - ]; - } - - $this->SetBuffer("StaticBatteryConfig", json_encode($cfg)); - $this->SendDebug("BuildStaticBatteryConfig", "Neu berechnet: " . count($cfg["bats"]) . " Batterien", 0); - } - - private function CalculateRuntimeState(array $cfg): array - { - $calc = [ - "inputs" => $cfg["inputs"] ?? [], - "batteries" => [], - "total" => [] - ]; - - $sdlTotal = 0.0; - $evTotal = 0.0; - $sdlStart = 0.0; - $realEvTotal = 0.0; - $realSdlTotal = 0.0; - $sdlChargeKW = 0.0; - $sdlDisKW = 0.0; - $evChargeKW = 0.0; - $evDisKW = 0.0; - $totalCap = 0.0; - - foreach ($cfg["bats"] as $bat) { - $capKWh = (float)$bat["capKWh"]; - $socPct = $this->ReadSocPercent((int)$bat["socVarId"]); - $realKWh = $capKWh * $socPct / 100.0; - - $underKWh = (float)$bat["underKWh"]; - $upKWh = (float)$bat["upKWh"]; - - $sdlKWhTotal = (float)$bat["SDL_kWh_total"]; - $evKWhTotal = (float)$bat["EV_kWh_total"]; - - $realEvKWh = max(0.0, min($evKWhTotal, $realKWh - $underKWh)); - $realSdlKWh = max(0.0, $realKWh - $realEvKWh); - - if ($realKWh <= 0.0) { - $realEvKWh = 0.0; - $realSdlKWh = 0.0; - } - - if ($realKWh >= $capKWh) { - $realEvKWh = $evKWhTotal; - $realSdlKWh = $sdlKWhTotal; - } - - $evSocReal = ($evKWhTotal > 0.0) ? ($realEvKWh / $evKWhTotal * 100.0) : 0.0; - $sdlSocReal = ($sdlKWhTotal > 0.0) ? ($realSdlKWh / $sdlKWhTotal * 100.0) : 0.0; - - $sdlCh = (float)$bat["sdlShareKW_laden"]; - $sdlDis = (float)$bat["sdlShareKW_entladen"]; - $evCh = (float)$bat["evShareKW_laden"]; - $evDis = (float)$bat["evShareKW_entladen"]; - - // EV-Laden physikalisch sperren, wenn obere EV-Grenze erreicht ist - if ($realKWh >= $upKWh) { - $evCh = 0.0; - } - - // EV-Entladen physikalisch sperren, wenn untere EV-Grenze erreicht ist - if ($realKWh <= $underKWh) { - $evDis = 0.0; - } - - // Absolute Batteriegrenzen - if ($realKWh <= 0.0) { - $sdlDis = 0.0; - $evDis = 0.0; - } - - if ($realKWh >= $capKWh) { - $sdlCh = 0.0; - $evCh = 0.0; - } - - $sdlTotal += $sdlKWhTotal; - $evTotal += $evKWhTotal; - $sdlStart += $underKWh; - - $realEvTotal += $realEvKWh; - $realSdlTotal += $realSdlKWh; - - $sdlChargeKW += $sdlCh; - $sdlDisKW += $sdlDis; - $evChargeKW += $evCh; - $evDisKW += $evDis; - - $totalCap += $capKWh; - - $calc["batteries"][] = [ - "idx" => $bat["idx"], - "typ" => $bat["typ"], - "SoC_varId" => $bat["socVarId"], - "SoC_pct" => round($socPct, 3), - "Effektive_kWh" => round($realKWh, 3), - - "under_grenze_kWh" => round($underKWh, 3), - "up_grenze_kWh" => round($upKWh, 3), - - "EV_SOC_REAL" => round(max(0, min(100, $evSocReal)), 3), - "SDL_SOC_REAL" => round(max(0, min(100, $sdlSocReal)), 3), - - "EV_kWh_real" => round($realEvKWh, 3), - "SDL_kWh_real" => round($realSdlKWh, 3), - - "EV_kWh_total" => round($evKWhTotal, 3), - "SDL_kWh_total" => round($sdlKWhTotal, 3), - - "SDL_Charge_kW" => round($sdlCh, 3), - "SDL_Discharge_kW" => round($sdlDis, 3), - "EV_Charge_kW" => round($evCh, 3), - "EV_Discharge_kW" => round($evDis, 3) - ]; - } - - $startPct = ($sdlTotal > 0.0) ? ($sdlStart / $sdlTotal * 100.0) : 0.0; - - $calc["total"] = [ - "SDL_kWh_total" => round($sdlTotal, 4), - "EV_kWh_total" => round($evTotal, 4), - "SDL_Start_kWh" => round($sdlStart, 4), - "SDL_Start_pct" => round($startPct, 3), - - "Real_EV_kWh" => round($realEvTotal, 4), - "Real_SDL_kWh" => round($realSdlTotal, 4), - - "SDL_Charge_kW" => round($sdlChargeKW, 4), - "SDL_Discharge_kW" => round($sdlDisKW, 4), - "EV_Charge_kW" => round($evChargeKW, 4), - "EV_Discharge_kW" => round($evDisKW, 4), - - "totalCap_kWh" => round($totalCap, 4) - ]; - - return $calc; - } - - public function ApplySetpoints(array $runtime = null): void - { - if ($runtime === null) { - $raw = (string)GetValue($this->GetIDForIdent("CalcJSON")); - $runtime = json_decode($raw, true); - } - - if (!is_array($runtime)) { - return; - } - - $pEvW = (float)GetValue($this->GetIDForIdent("Nennleistung_Soll_EV")); - $pSdlW = (float)GetValue($this->GetIDForIdent("Nennleistung_Soll_SDL")); - - $distribution = $this->CalculateBatteryDistribution($runtime, $pEvW, $pSdlW); - $this->WriteBatteryPowerSetpoints($distribution); - } - - private function CalculateBatteryDistribution(array $runtime, float $pEvW, float $pSdlW): array - { - $batteries = $runtime["batteries"] ?? []; - if (empty($batteries)) { - return []; - } - - $distributePower = function (float $targetPower, string $mode) use ($batteries): array { - $result = []; - - foreach ($batteries as $bat) { - $result[(int)$bat["idx"]] = 0.0; - } - - if (abs($targetPower) < 0.01) { - return $result; - } - - $isCharge = $targetPower > 0.0; - $needW = abs($targetPower); - - $socKey = ($mode === "EV") ? "EV_SOC_REAL" : "SDL_SOC_REAL"; - $limitKey = $isCharge ? $mode . "_Charge_kW" : $mode . "_Discharge_kW"; - - $groups = []; - - foreach ($batteries as $bat) { - $soc = (int)round((float)$bat[$socKey]); - $maxW = max(0.0, (float)$bat[$limitKey] * 1000.0); - - if ($maxW <= 0.0) { - continue; - } - - $groups[$soc][] = [ - "idx" => (int)$bat["idx"], - "maxW" => $maxW - ]; - } - - if ($isCharge) { - ksort($groups); - } else { - krsort($groups); - } - - foreach ($groups as $group) { - if ($needW <= 0.01) { - break; - } - - $groupMax = 0.0; - foreach ($group as $g) { - $groupMax += $g["maxW"]; - } - - if ($groupMax <= 0.0) { - continue; - } - - $useW = min($needW, $groupMax); - $ratio = $useW / $groupMax; - - foreach ($group as $g) { - $result[$g["idx"]] += $g["maxW"] * $ratio; - } - - $needW -= $useW; - } - - if (!$isCharge) { - foreach ($result as $idx => $w) { - $result[$idx] = -$w; - } - } - - return $result; - }; - - $ev = $distributePower($pEvW, "EV"); - $sdl = $distributePower($pSdlW, "SDL"); - - $out = []; - - foreach ($batteries as $bat) { - $idx = (int)$bat["idx"]; - - $totalW = ($ev[$idx] ?? 0.0) + ($sdl[$idx] ?? 0.0); - - $out[] = [ - "idx" => $idx, - "typ" => (string)$bat["typ"], - "chargeW" => $totalW > 0 ? round($totalW, 0) : 0, - "dischargeW" => $totalW < 0 ? round(abs($totalW), 0) : 0 - ]; - } - - $this->UpdateActualPowerValues($pEvW, $pSdlW); - - return $out; - } - - private function UpdateActualPowerValues(float $pEvW, float $pSdlW): void - { - $totalPowerIst = $this->GetTotalBatteryPowerIstW(); - - $eps = 0.01; - $sumSoll = $pEvW + $pSdlW; - - if (abs($sumSoll) > $eps) { - $factor = $totalPowerIst / $sumSoll; - $rawEV = $pEvW * $factor; - $rawSDL = $pSdlW * $factor; - } else { - if (abs($totalPowerIst) < 50.0) { - $rawEV = $pEvW; - $rawSDL = $pSdlW; - } else { - $rawEV = $pEvW; - $rawSDL = $totalPowerIst - $rawEV; - } - } - - if ($this->ReadPropertyBoolean("FilterAktiv")) { - $rawEV = $this->FilterCurrent("EV", $rawEV, $pEvW); - $rawSDL = $this->FilterCurrent("SDL", $rawSDL, $pSdlW); - } else { - $this->ClearCurrentFilterBuffers(); - } - - $this->SetIdentValue("Aktuelle_Leistung_EV", $rawEV); - $this->SetIdentValue("Aktuelle_Leistung_SDL", $rawSDL); - } - - private function FilterCurrent(string $channel, float $raw, float $target): float - { - $tolW = max(300.0, abs($target) * 0.10); - $last = (float)$this->GetBufferSafe("CUR_{$channel}_VAL"); - - if ($this->GetBufferSafe("CUR_{$channel}_INIT") !== "1") { - $this->SetBuffer("CUR_{$channel}_INIT", "1"); - $this->SetBuffer("CUR_{$channel}_VAL", (string)$raw); - return $raw; - } - - if (abs($target) < 0.5) { - $this->SetBuffer("CUR_{$channel}_VAL", "0"); - return 0.0; - } - - $sameDirection = (($target > 0 && $raw > 0) || ($target < 0 && $raw < 0)); - $notCrazyHigh = abs($raw) <= abs($target) + $tolW; - - if ($sameDirection && $notCrazyHigh) { - $this->SetBuffer("CUR_{$channel}_VAL", (string)$raw); - return $raw; - } - - return $last; - } - - private function WriteBatteryPowerSetpoints(array $distribution): void - { - $batteriesCfg = json_decode($this->ReadPropertyString("Batteries"), true); - if (!is_array($batteriesCfg)) { - return; - } - - foreach ($distribution as $d) { - $idx = (int)($d["idx"] ?? -1); - if ($idx < 0 || !isset($batteriesCfg[$idx])) { - continue; - } - - $cfg = $batteriesCfg[$idx]; - $typ = (string)($d["typ"] ?? ($cfg["typ"] ?? "Bat")); - - $chargeW = max(0.0, (float)($d["chargeW"] ?? 0)); - $dischargeW = max(0.0, (float)($d["dischargeW"] ?? 0)); - - if ($chargeW > 0.0 && $dischargeW > 0.0) { - $chargeW = 0.0; - $dischargeW = 0.0; - } - - $this->WriteByVendorRegistersSingleMode($typ, $cfg, $chargeW, $dischargeW); - } - } - - private function WriteByVendorRegistersSingleMode(string $typ, array $cfg, float $chargeW, float $dischargeW): void - { - $t = mb_strtolower($typ); - - $varPowerCharge = (int)($cfg["powerbat_laden"] ?? 0); - $varPowerDisch = (int)($cfg["powerbat_entladen"] ?? 0); - $varMode = (int)($cfg["register_ladenentladen_modus"] ?? 0); - - $setInt = function (int $varId, int $value): void { - if ($varId > 0 && IPS_VariableExists($varId)) { - RequestAction($varId, $value); - } - }; - - $setW = function (int $varId, float $w): void { - if ($varId > 0 && IPS_VariableExists($varId)) { - RequestAction($varId, (int)round(max(0.0, $w), 0)); - } - }; - - $modeCharge = 1; - $modeDisch = 2; - - if (strpos($t, "goodwe") !== false) { - $modeCharge = 11; - $modeDisch = 12; - } elseif (strpos($t, "solaredge") !== false) { - $modeCharge = 3; - $modeDisch = 4; - } - - if ($chargeW > 0.0) { - $setInt($varMode, $modeCharge); - - if (strpos($t, "goodwe") !== false) { - $setW($varPowerCharge, $chargeW); - $setW($varPowerDisch, 0); - return; - } - - $setW($varPowerCharge, $chargeW); - $setW($varPowerDisch, 0); - return; - } - - if ($dischargeW > 0.0) { - $setInt($varMode, $modeDisch); - - if (strpos($t, "goodwe") !== false) { - $setW($varPowerCharge, $dischargeW); - $setW($varPowerDisch, 0); - return; - } - - $setW($varPowerDisch, $dischargeW); - $setW($varPowerCharge, 0); - return; - } - - $setW($varPowerCharge, 0); - $setW($varPowerDisch, 0); - } - - private function ResetSDLToStartPoint(): void - { - $cfg = json_decode($this->GetBufferSafe("StaticBatteryConfig"), true); - if (!is_array($cfg) || empty($cfg["bats"])) { - return; - } - - $total = 0.0; - $start = 0.0; - - foreach ($cfg["bats"] as $bat) { - $total += (float)$bat["SDL_kWh_total"]; - $start += (float)$bat["underKWh"]; - } - - $start = ($total > 0.0) ? max(0.0, min($total, $start)) : 0.0; - - $this->SetBuffer("Int_E_SDL_kWh", (string)$start); - $this->SetBuffer("Int_Init_SDL", "1"); - $this->SetBuffer("Int_LastMs", (string)round(microtime(true) * 1000)); - } - - private function ResetEVToRealPoint(): void - { - $cfg = json_decode($this->GetBufferSafe("StaticBatteryConfig"), true); - if (!is_array($cfg) || empty($cfg["bats"])) { - return; - } - - $runtime = $this->CalculateRuntimeState($cfg); - $realEv = (float)$runtime["total"]["Real_EV_kWh"]; - - $this->SetBuffer("Int_E_EV_kWh", (string)$realEv); - $this->SetBuffer("Int_Init_EV", "1"); - $this->SetBuffer("Int_LastMs", (string)round(microtime(true) * 1000)); - } - - private function GetTotalBatteryPowerIstW(): float - { - $cfg = json_decode($this->ReadPropertyString("Batteries"), true); - if (!is_array($cfg)) { - return 0.0; - } - - $sum = 0.0; - - foreach ($cfg as $b) { - $varId = (int)($b["register_bat_power"] ?? 0); - if ($varId > 0 && IPS_VariableExists($varId)) { - $sum += -1.0 * (float)GetValue($varId); - } - } - - return $sum; - } - - private function ReadSocPercent(int $varId): float - { - if ($varId >= 0 && $varId <= 100 && !IPS_VariableExists($varId)) { - return (float)$varId; - } - - if ($varId <= 0 || !IPS_VariableExists($varId)) { - return 0.0; - } - - $v = GetValue($varId); - if (!is_numeric($v)) { - return 0.0; - } - - return max(0.0, min(100.0, (float)$v)); - } - - private function SetIdentValue(string $ident, $value): void - { - $id = @$this->GetIDForIdent($ident); - if ($id > 0) { - SetValue($id, $value); - } - } - - private function GetBufferSafe(string $name): string - { - $v = $this->GetBuffer($name); - return is_string($v) ? $v : ""; - } - - private function ClearCurrentFilterBuffers(): void - { - foreach (["EV", "SDL"] as $ch) { - $this->SetBuffer("CUR_{$ch}_INIT", ""); - $this->SetBuffer("CUR_{$ch}_VAL", ""); - } - } - - private function WriteAllZero(string $reason): void - { - $this->SetIdentValue("SDL_Pos", 0.0); - $this->SetIdentValue("SoC_EV", 0.0); - $this->SetIdentValue("P_SDL_laden", 0.0); - $this->SetIdentValue("P_SDL_entladen", 0.0); - $this->SetIdentValue("P_EV_laden", 0); - $this->SetIdentValue("P_EV_entladen", 0); - $this->SetIdentValue("CalcJSON", json_encode(["error" => $reason], JSON_PRETTY_PRINT)); - } -} - ?> \ No newline at end of file