diff --git a/Bat_EV_SDL/module.php b/Bat_EV_SDL/module.php index f935da2..284dd4c 100644 --- a/Bat_EV_SDL/module.php +++ b/Bat_EV_SDL/module.php @@ -15,9 +15,7 @@ class Bat_EV_SDL extends IPSModule $this->RegisterVariableBoolean("State", "Aktiv", "~Switch", 1); $this->EnableAction("State"); - // Prozentwerte nach deiner Skizze: - // SDL_% = verfügbare SDL-Energie / benötigte SDL-Energie - // EV_% = verfügbare EV-Energie / max. EV-Energie (SumCap - SumSDLreq) + // Prozentwerte $this->RegisterVariableFloat("SDL_Pos", "SDL Energie verfügbar (%)", "", 10); $this->RegisterVariableFloat("SoC_EV", "EV Energie verfügbar (%)", "", 11); @@ -33,7 +31,7 @@ class Bat_EV_SDL extends IPSModule // Debug $this->RegisterVariableString("CalcJSON", "Berechnung (JSON)", "", 99); - // Timer (Prefix: GEF) + // Timer $this->RegisterTimer("UpdateTimer", 0, 'GEF_Update($_IPS["TARGET"]);'); } @@ -66,41 +64,50 @@ class Bat_EV_SDL extends IPSModule } $batteries = json_decode($this->ReadPropertyString("Batteries"), true); - if (!is_array($batteries)) { - $batteries = []; - } + if (!is_array($batteries)) $batteries = []; $sdlTotalW = (float)$this->ReadPropertyInteger("SDL_Leistung"); // W if ($sdlTotalW < 0) $sdlTotalW = 0; - // Zeitscheibe in Stunden (0.5h hattest du vorher – jetzt 1h fix) + // 30 Minuten Fenster $hours = 0.5; - // Summe Batterie-Maxleistungen + // Summe Batterie-Leistung $sumBatPowerW = 0.0; foreach ($batteries as $b) { $p = (float)($b["powerbat"] ?? 0); if ($p > 0) $sumBatPowerW += $p; } - if ($sumBatPowerW <= 0) { - // Keine Batterie-Leistung konfiguriert -> nichts zu tun - $this->SetValueFloat("SDL_Pos", 0.0); - $this->SetValueFloat("SoC_EV", 0.0); - $this->SetValueFloat("P_SDL_max", 0.0); - $this->SetValueFloat("P_SDL_laden", 0.0); - $this->SetValueFloat("P_SDL_entladen", 0.0); - $this->SetValueFloat("P_EV_max", 0.0); - $this->SetValueFloat("P_EV_laden", 0.0); - $this->SetValueFloat("P_EV_entladen", 0.0); - $this->SetValueString("CalcJSON", json_encode(["error" => "sumBatPowerW=0"], JSON_PRETTY_PRINT)); + + // Wenn keine Leistung vorhanden → alles 0 + if ($sumBatPowerW <= 0.0) { + $this->WriteAllZero("sumBatPowerW=0"); return; } - // Begrenzung: SDL kann nicht mehr ziehen als gesamt verfügbar ist - if ($sdlTotalW > $sumBatPowerW) { - $sdlTotalW = $sumBatPowerW; - } + // SDL kann nicht größer als Gesamtleistung + if ($sdlTotalW > $sumBatPowerW) $sdlTotalW = $sumBatPowerW; + // Totale + $totalCapKWh = 0.0; + $totalStoredKWh = 0.0; + + $sumSdlPowerW = 0.0; + $sumEvPowerW = 0.0; + + $sdlNeedTotalKWh = ($sdlTotalW / 1000.0) * $hours; + + $sumSdlAvailKWh = 0.0; // SDL-Energie, die wirklich geliefert werden kann + $sumEvKWh = 0.0; // Restenergie nach SDL-Reservierung + + // Power Limits + $pSdlChargeW = 0.0; + $pEvChargeW = 0.0; + + $pSdlDischargeW = 0.0; + $pEvDischargeW = 0.0; + + // Debug JSON $calc = [ "inputs" => [ "SDL_Leistung_W" => $sdlTotalW, @@ -110,73 +117,57 @@ class Bat_EV_SDL extends IPSModule "batteries" => [] ]; - // Totale - $totalStoredKWh = 0.0; - - $sumSdlPowerW = 0.0; - $sumEvPowerW = 0.0; - - $sumSdlEnergyAvailKWh = 0.0; // verfügbare SDL-Energie innerhalb der Zeitscheibe - $sumEvEnergyKWh = 0.0; // Energie nach SDL-Reservierung (EV „Restenergie“) - - $pSdlChargeW = 0.0; - $pSdlDischargeW = 0.0; - $pEvChargeW = 0.0; - $pEvDischargeW = 0.0; - foreach ($batteries as $idx => $b) { - $pBatW = (float)($b["powerbat"] ?? 0); - $capKWh = (float)($b["capazity"] ?? 0); + $pBatW = max(0.0, (float)($b["powerbat"] ?? 0)); + $capKWh = max(0.0, (float)($b["capazity"] ?? 0)); $socPct = (float)($b["soc"] ?? 0); - if ($pBatW < 0) $pBatW = 0; - if ($capKWh < 0) $capKWh = 0; if ($socPct < 0) $socPct = 0; if ($socPct > 100) $socPct = 100; + $totalCapKWh += $capKWh; + // Energie im Akku $storedKWh = $capKWh * ($socPct / 100.0); $totalStoredKWh += $storedKWh; - // Anteilige SDL-Leistung (nach Leistungsklasse verteilt) + // SDL-Leistung anteilig verteilen $sdlShareW = 0.0; if ($pBatW > 0 && $sdlTotalW > 0) { $sdlShareW = $sdlTotalW * ($pBatW / $sumBatPowerW); } - - // Physikalisch: nicht über Batterie-Max + // nicht über Batterie-Max if ($sdlShareW > $pBatW) $sdlShareW = $pBatW; - // EV-Leistung ist der Rest $evShareW = max(0.0, $pBatW - $sdlShareW); - // Energie-Anforderung für SDL innerhalb der Zeitscheibe - // (ohne 0,5h: 1h => kWh = kW * 1h) + // Energiebedarf in diesem Zeitfenster $sdlNeedKWh = ($sdlShareW / 1000.0) * $hours; - // Verfügbare SDL-Energie kann nicht größer sein als gespeicherte Energie + // Wirklich verfügbare SDL-Energie $sdlAvailKWh = min($storedKWh, $sdlNeedKWh); - // EV-Energie ist der Rest im Tank nach SDL-Reservierung - $evEnergyKWh = max(0.0, $storedKWh - $sdlAvailKWh); + // EV Energie ist Rest + $evKWh = max(0.0, $storedKWh - $sdlAvailKWh); - // Totale sammeln + // Sammeln $sumSdlPowerW += $sdlShareW; $sumEvPowerW += $evShareW; - $sumSdlEnergyAvailKWh += $sdlAvailKWh; - $sumEvEnergyKWh += $evEnergyKWh; + $sumSdlAvailKWh += $sdlAvailKWh; + $sumEvKWh += $evKWh; - // Lade-/Entladegrenzen (einfach & robust) - // Laden: geht immer bis zur Leistungsgrenze + // Laden (so wie du es willst) $pSdlChargeW += $sdlShareW; $pEvChargeW += $evShareW; - // Entladen: nur wenn überhaupt Energie drin ist - if ($storedKWh > 0.0001) { - $pSdlDischargeW += $sdlShareW; - $pEvDischargeW += $evShareW; - } + // Entladen: energiebegrenzt für das Zeitfenster! + // max Leistung aus Energie über 0,5h: P = E/h *1000 + $maxWFromSdlEnergy = ($hours > 0) ? ($sdlAvailKWh / $hours) * 1000.0 : 0.0; + $maxWFromEvEnergy = ($hours > 0) ? ($evKWh / $hours) * 1000.0 : 0.0; + + $pSdlDischargeW += min($sdlShareW, $maxWFromSdlEnergy); + $pEvDischargeW += min($evShareW, $maxWFromEvEnergy); $calc["batteries"][] = [ "idx" => $idx, @@ -191,27 +182,30 @@ class Bat_EV_SDL extends IPSModule "SDL_need_kWh" => round($sdlNeedKWh, 4), "SDL_avail_kWh" => round($sdlAvailKWh, 4), - "EV_energy_kWh" => round($evEnergyKWh, 4), + "EV_kWh" => round($evKWh, 4), + + "SDL_discharge_max_W" => round(min($sdlShareW, $maxWFromSdlEnergy), 2), + "EV_discharge_max_W" => round(min($evShareW, $maxWFromEvEnergy), 2), ]; } - // SDL% = verfügbare SDL-Energie / benötigte SDL-Energie - $sdlNeedTotalKWh = ($sdlTotalW / 1000.0) * $hours; + // SDL% = verfügbare SDL Energie / benötigte SDL Energie $sdlPosPct = 0.0; if ($sdlNeedTotalKWh > 0.000001) { - $sdlPosPct = ($sumSdlEnergyAvailKWh / $sdlNeedTotalKWh) * 100.0; + $sdlPosPct = ($sumSdlAvailKWh / $sdlNeedTotalKWh) * 100.0; } - // EV%: wie viel Energie bleibt nach SDL-Deckung, bezogen auf "max EV Energie" - // Max EV Energie = totalStoredKWh - (SDL_needTotalKWh - SDL_availTotalKWh) - // (also: was theoretisch nach dem Versuch SDL zu liefern übrig bleiben kann) - $evMaxKWh = $totalStoredKWh - max(0.0, $sdlNeedTotalKWh - $sumSdlEnergyAvailKWh); + // EV%: + // Deine Skizze ist faktisch: EV verfügbar / EV max + // EV max interpretieren wir sinnvoll als: SumCap - SDL_need + // (wenn SDL_need größer ist, wird EV max 0) + $evMaxKWh = max(0.0, $totalCapKWh - $sdlNeedTotalKWh); $evPosPct = 0.0; if ($evMaxKWh > 0.000001) { - $evPosPct = ($sumEvEnergyKWh / $evMaxKWh) * 100.0; + $evPosPct = ($sumEvKWh / $evMaxKWh) * 100.0; } - // Variablen setzen + // Werte setzen $this->SetIdentValue("SDL_Pos", round($sdlPosPct, 3)); $this->SetIdentValue("SoC_EV", round($evPosPct, 3)); @@ -224,10 +218,11 @@ class Bat_EV_SDL extends IPSModule $this->SetIdentValue("P_EV_entladen", round($pEvDischargeW, 2)); $calc["totals"] = [ + "totalCap_kWh" => round($totalCapKWh, 4), "totalStored_kWh" => round($totalStoredKWh, 4), "SDL_needTotal_kWh" => round($sdlNeedTotalKWh, 4), - "SDL_availTotal_kWh" => round($sumSdlEnergyAvailKWh, 4), - "EV_energyTotal_kWh" => round($sumEvEnergyKWh, 4), + "SDL_availTotal_kWh" => round($sumSdlAvailKWh, 4), + "EV_total_kWh" => round($sumEvKWh, 4), "SDL_Pos_pct" => round($sdlPosPct, 3), "EV_Pos_pct" => round($evPosPct, 3), @@ -239,24 +234,32 @@ class Bat_EV_SDL extends IPSModule "P_EV_discharge_W" => round($pEvDischargeW, 2), ]; - $this->SetValueString("CalcJSON", json_encode($calc, JSON_PRETTY_PRINT)); + $this->SetIdentValue("CalcJSON", json_encode($calc, JSON_PRETTY_PRINT)); } + + private function WriteAllZero(string $reason): void + { + $this->SetIdentValue("SDL_Pos", 0.0); + $this->SetIdentValue("SoC_EV", 0.0); + + $this->SetIdentValue("P_SDL_max", 0.0); + $this->SetIdentValue("P_SDL_laden", 0.0); + $this->SetIdentValue("P_SDL_entladen", 0.0); + + $this->SetIdentValue("P_EV_max", 0.0); + $this->SetIdentValue("P_EV_laden", 0.0); + $this->SetIdentValue("P_EV_entladen", 0.0); + + $this->SetIdentValue("CalcJSON", json_encode(["error" => $reason], JSON_PRETTY_PRINT)); + } + private function SetIdentValue(string $ident, $value): void { - $id = $this->GetIDForIdent($ident); - if ($id <= 0) return; + $id = @$this->GetIDForIdent($ident); + if ($id <= 0) { + $this->SendDebug(__FUNCTION__, "Ident nicht gefunden: $ident", 0); + return; + } SetValue($id, $value); } - - private function ReadSocPercent(int $varId): float - { - if ($varId <= 0 || !IPS_VariableExists($varId)) return 0.0; - $v = GetValue($varId); - if (!is_numeric($v)) return 0.0; - - $f = (float)$v; - if ($f >= 0.0 && $f <= 1.0) $f *= 100.0; - - return max(0.0, min(100.0, $f)); - } }