diff --git a/Bat_EV_SDL _V2/form.json b/Bat_EV_SDL _V2/form.json index 66b7f7c..119f2b9 100644 --- a/Bat_EV_SDL _V2/form.json +++ b/Bat_EV_SDL _V2/form.json @@ -67,22 +67,13 @@ } , { - "caption":"Register Laden Modus", - "name":"register_laden_modus", - "width":"200px", - "add":0, - "edit":{ - "type":"SelectVariable" - } - }, - { - "caption":"Register Entladen Modus", - "name":"register_entladen_modus", - "width":"200px", - "add":0, - "edit":{ - "type":"SelectVariable" - } + "caption": "Register Laden/Entladen Modus", + "name": "register_ladenentladen_modus", + "width": "260px", + "add": 0, + "edit": { + "type": "SelectVariable" + } } ] diff --git a/Bat_EV_SDL _V2/module.php b/Bat_EV_SDL _V2/module.php index 250c13d..d80e5ac 100644 --- a/Bat_EV_SDL _V2/module.php +++ b/Bat_EV_SDL _V2/module.php @@ -456,54 +456,142 @@ class Bat_EV_SDL_V2 extends IPSModule - private function CalculateBatteryDistribution(float $pEvW, float $pSdlW): array +private function CalculateBatteryDistribution(float $pEvW, float $pSdlW): array { - - // Holt das berechnete Json aus, also das Debug json, wo in der Konsole angezeigt wird $calcJsonId = $this->GetIDForIdent("CalcJSON"); - $calc = json_decode((string)GetValue($calcJsonId), true); - - if (!is_array($calc) || empty($calc["batteries"])) { + // Fallback, falls Variable leer ist oder nicht existiert + if (!IPS_VariableExists($calcJsonId)) { return []; } - - foreach ($calc["batteries"] as $bat) { - - $idx = (int)$bat["idx"]; - $typ = (string)$bat["typ"]; - - $evSoc = (float)$bat["EV_SOC"]; - $sdlSoc = (float)$bat["SDL_SOC"]; - - $sdlChargeKW = (float)$bat["SDL_Charge_kW"]; - $sdlDischargeKW = (float)$bat["SDL_Discharge_kW"]; - $evChargeKW = (float)$bat["EV_Charge_kW"]; - $evDischargeKW = (float)$bat["EV_Discharge_kW"]; + $rawJson = (string)GetValue($calcJsonId); + if (empty($rawJson)) { + return []; } - - - - /* - // Return sieht so aus: - return :[ - "idx" => 0, - "typ":"1", - "chargeW" => 0.0, - "dischargeW" => 3000.0 - ], - [ - "idx" => 1, - "typ":"2", - "chargeW" => 0.0, - "dischargeW" => 1000.0 + $calc = json_decode($rawJson, true); + $batteries = $calc['batteries'] ?? []; + + // --------------------------------------------------------- + // 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; // Nichts zu tun + } + + $isCharge = ($targetPower > 0); + $absPower = abs($targetPower); + // 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]); // Auf ganze Zahl runden + $maxW = ((float)$bat[$limitKey]) * 1000.0; // kW in Watt umrechnen + $groups[$soc][] = [ + '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) { + $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; + } + } + + return $result; + }; + + // --------------------------------------------------------- + // Hauptablauf + // --------------------------------------------------------- + + // 1. Berechnung für EV und SDL getrennt durchführen + $evDistribution = $distributePower($pEvW, 'EV'); + $sdlDistribution = $distributePower($pSdlW, 'SDL'); + + // 2. Ergebnisse zusammenführen und Output formatieren + $finalOutput = []; + + foreach ($batteries as $bat) { + $idx = $bat['idx']; + // 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 = abs($totalW); + } else { + $dischargeW = abs($totalW); + } + + // JSON Objekt erstellen + $finalOutput[] = [ + "idx" => $idx, + "typ" => (string)$bat['typ'], // Typ als String beibehalten + "chargeW" => round($chargeW, 0), // Optional: runden für sauberes JSON + "dischargeW" => round($dischargeW, 0) ]; - */ - + } + + return $finalOutput; } private function WriteBatteryPowerSetpoints(array $distribution): void { - // Batteries-Config laden (enthält register_laden / register_entladen) $batteriesCfg = json_decode($this->ReadPropertyString("Batteries"), true); if (!is_array($batteriesCfg) || empty($batteriesCfg)) { return; @@ -511,7 +599,6 @@ class Bat_EV_SDL_V2 extends IPSModule foreach ($distribution as $d) { - // --- Batterie bestimmen --- $idx = (int)($d["idx"] ?? -1); if ($idx < 0 || !isset($batteriesCfg[$idx])) { continue; @@ -519,105 +606,107 @@ class Bat_EV_SDL_V2 extends IPSModule $cfg = $batteriesCfg[$idx]; - // typ aus distribution bevorzugen, sonst aus Config $typ = (string)($d["typ"] ?? ($cfg["typ"] ?? ("Bat " . ($idx + 1)))); - // --- Sollwerte (Watt) --- + // ✅ immer positiv $chargeW = max(0.0, (float)($d["chargeW"] ?? 0.0)); $dischargeW = max(0.0, (float)($d["dischargeW"] ?? 0.0)); - // Sicherheitsnetz: nie gleichzeitig laden & entladen + // nie gleichzeitig if ($chargeW > 0.0 && $dischargeW > 0.0) { - $this->SendDebug( - "WriteBatteryPowerSetpoints", - "WARN: charge & discharge gleichzeitig > 0 bei $typ (idx=$idx) -> setze beides 0", - 0 - ); + $this->SendDebug("WriteBatteryPowerSetpoints", "WARN: both >0 for $typ (idx=$idx) -> set 0", 0); $chargeW = 0.0; $dischargeW = 0.0; } - // --- Vendor-/Default-Handling über Register schreiben --- - $this->WriteByVendorRegisters($typ, $cfg, $chargeW, $dischargeW); + $this->WriteByVendorRegistersSingleMode($typ, $cfg, $chargeW, $dischargeW); - // Optionales Debug - $this->SendDebug( - "Setpoints", - "$typ (idx=$idx): charge={$chargeW}W discharge={$dischargeW}W", - 0 - ); + $this->SendDebug("Setpoints", "$typ (idx=$idx) charge={$chargeW}W discharge={$dischargeW}W", 0); } } - private function WriteByVendorRegisters(string $typ, array $cfg, float $chargeW, float $dischargeW): void - { - $typLower = mb_strtolower($typ); +private function WriteByVendorRegistersSingleMode(string $typ, array $cfg, float $chargeW, float $dischargeW): void +{ + $t = mb_strtolower($typ); - // Register aus Formular - $regCharge = (int)($cfg["register_laden"] ?? 0); - $regDisch = (int)($cfg["register_entladen"] ?? 0); + // Leistungs-Variablen + $varPowerCharge = (int)($cfg["powerbat_laden"] ?? 0); + $varPowerDisch = (int)($cfg["powerbat_entladen"] ?? 0); - // ----------------------------- - // GOODWE → ein Register, signed - // ----------------------------- - if (strpos($typLower, "goodwe") !== false) { + // ✅ EIN Modus-Register + $varMode = (int)($cfg["register_ladenentladen_modus"] ?? 0); - if ($regCharge > 0 && IPS_VariableExists($regCharge)) { - $signedW = 0.0; - - if ($chargeW > 0.0) { - $signedW = $chargeW; // Laden → + - } elseif ($dischargeW > 0.0) { - $signedW = -1.0 * $dischargeW; // Entladen → - - } - - SetValue($regCharge, (int)round($signedW, 0)); - } - - // optional: zweites Register auf 0 halten - if ($regDisch > 0 && IPS_VariableExists($regDisch)) { - SetValue($regDisch, 0); - } - - return; + // sichere Writer + $setInt = function(int $varId, int $value): void { + if ($varId > 0 && IPS_VariableExists($varId)) { + SetValue($varId, $value); } - - // -------------------------------- - // SOLAREDGE → getrennte Register - // -------------------------------- - if (strpos($typLower, "solaredge") !== false) { - - if ($regCharge > 0 && IPS_VariableExists($regCharge)) { - SetValue($regCharge, (int)round($chargeW, 0)); - } - - if ($regDisch > 0 && IPS_VariableExists($regDisch)) { - SetValue($regDisch, (int)round($dischargeW, 0)); - } - - return; + }; + $setW = function(int $varId, float $w): void { + if ($varId > 0 && IPS_VariableExists($varId)) { + SetValue($varId, (int)round(max(0.0, $w), 0)); // ✅ niemals negativ } + }; - // -------------------------------- - // DEFAULT → getrennt, fest: 1 / 2 - // -------------------------------- - // Laden = Register 1, Entladen = Register 2 - - if (IPS_VariableExists(1)) { - SetValue(1, (int)round($chargeW, 0)); - } - - if (IPS_VariableExists(2)) { - SetValue(2, (int)round($dischargeW, 0)); - } - - $this->SendDebug( - "WriteByVendorRegisters", - "DEFAULT ($typ): charge={$chargeW}W -> Reg1, discharge={$dischargeW}W -> Reg2", - 0 - ); + // Moduscodes je Typ + $modeCharge = 1; $modeDisch = 2; // Default + if (strpos($t, "goodwe") !== false) { + $modeCharge = 11; $modeDisch = 12; + } elseif (strpos($t, "solaredge") !== false) { + $modeCharge = 3; $modeDisch = 4; } + // ========================= + // Laden + // ========================= + if ($chargeW > 0.0) { + + // Modus setzen (ein Register) + $setInt($varMode, $modeCharge); + + // GoodWe: nur powerbat_laden + if (strpos($t, "goodwe") !== false) { + $setW($varPowerCharge, $chargeW); + $setW($varPowerDisch, 0.0); + return; + } + + // SolarEdge + Default: zwei Leistungsregister + $setW($varPowerCharge, $chargeW); + $setW($varPowerDisch, 0.0); + return; + } + + // ========================= + // Entladen + // ========================= + if ($dischargeW > 0.0) { + + // Modus setzen (ein Register) + $setInt($varMode, $modeDisch); + + // GoodWe: Entladen nutzt trotzdem powerbat_laden (immer positiv) + if (strpos($t, "goodwe") !== false) { + $setW($varPowerCharge, $dischargeW); + $setW($varPowerDisch, 0.0); + return; + } + + // SolarEdge + Default: Entladen in powerbat_entladen + $setW($varPowerDisch, $dischargeW); + $setW($varPowerCharge, 0.0); + return; + } + + // ========================= + // Stop / Neutral + // ========================= + $setW($varPowerCharge, 0.0); + $setW($varPowerDisch, 0.0); + // optional: Modus nicht anfassen oder auf 0 setzen: + // $setInt($varMode, 0); +} + } ?>