RegisterPropertyString("Batteries", "[]"); $this->RegisterPropertyInteger("SDL_Leistung_Laden", 0); $this->RegisterPropertyInteger("SDL_Leistung_Entladen", 0); $this->RegisterPropertyInteger("UpdateIntervalMs", 1000); $this->RegisterPropertyFloat("ReserveHours", 0.5); $this->RegisterPropertyBoolean("FilterAktiv", false); $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); $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("SDL_Start_Pos", "SDL Start SoC (%)", "", 35); $this->RegisterVariableBoolean("SDL_Reset", "SDL Konto Reset", "~Switch", 36); $this->EnableAction("SDL_Reset"); $this->RegisterVariableBoolean("EV_Reset", "EV Konto Reset", "~Switch", 37); $this->EnableAction("EV_Reset"); $this->RegisterVariableString("CalcJSON", "Berechnung JSON", "", 99); $this->RegisterTimer("UpdateTimer", 0, 'GEF_Update($_IPS["TARGET"]);'); } public function ApplyChanges() { parent::ApplyChanges(); $intervalMs = (int)$this->ReadPropertyInteger("UpdateIntervalMs"); $this->SetTimerInterval("UpdateTimer", ($intervalMs > 0) ? $intervalMs : 0); $this->BuildStaticBatteryConfig(); $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)); } } ?>