Files
Symcon_Belevo_Energiemanage…/Bat_EV_SDL/module.php
2026-01-22 16:24:36 +01:00

339 lines
12 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
class Bat_EV_SDL extends IPSModule
{
/* ===========================
* 1) Setup
* =========================== */
public function Create()
{
parent::Create();
// ---- Properties (kommen aus form.json) ----
$this->RegisterPropertyString("Batteries", "[]"); // List JSON
$this->RegisterPropertyInteger("SDL_Leistung", 0); // W
$this->RegisterPropertyInteger("UpdateInterval", 5);
// ---- Status / Steuerung ----
$this->RegisterVariableBoolean("State", "Aktiv", "~Switch", 1);
$this->EnableAction("State");
// ---- Ergebnisse (Gesamt) ----
$this->RegisterVariableFloat("kWh_SDL", "Energie SDL (kWh)", "", 20);
$this->RegisterVariableFloat("kWh_EV", "Energie EV (kWh)", "", 21);
$this->RegisterVariableFloat("SoC_SDL", "SoC SDL (%)", "", 12);
$this->RegisterVariableFloat("SoC_EV", "SoC EV (%)", "", 13);
$this->RegisterVariableFloat("P_SDL_max", "SDL max (W)", "", 10);
$this->RegisterVariableFloat("P_EV_max", "EV max (W)", "", 11);
$this->RegisterVariableFloat("P_EV_laden", "P EV laden max (W)", "", 22);
$this->RegisterVariableFloat("P_EV_entladen", "P EV entladen max (W)", "", 23);
$this->RegisterVariableFloat("P_SDL_laden", "P SDL laden max (W)", "", 24);
$this->RegisterVariableFloat("P_SDL_entladen", "P SDL entladen max (W)", "", 25);
// Optional: Voller JSON-Dump zum Debuggen/Weiterverarbeiten
$this->RegisterVariableString("CalcJSON", "Berechnung (JSON)", "", 99);
$this->RegisterTimer("UpdateTimer",0,'GEF_Update($_IPS["TARGET"]);');
}
/* ===========================
* 2) ApplyChanges (Konsole speichern)
* =========================== */
public function ApplyChanges()
{
parent::ApplyChanges();
$intervalMin = (int)$this->ReadPropertyInteger("UpdateInterval");
if ($intervalMin > 0) {
// Minuten → Millisekunden
$this->SetTimerInterval("UpdateTimer", $intervalMin * 60 * 1000);
$this->SendDebug("ApplyChanges", "Timer aktiv: {$intervalMin} min", 0);
} else {
// Timer aus
$this->SetTimerInterval("UpdateTimer", 0);
$this->SendDebug("ApplyChanges", "Timer deaktiviert", 0);
}
// Direkt einmal rechnen
$this->Update();
}
/* ===========================
* 3) RequestAction (WebFront Klicks)
* =========================== */
public function RequestAction($Ident, $Value)
{
switch ($Ident) {
case "State":
SetValue($this->GetIDForIdent("State"), (bool)$Value);
if ((bool)$Value) {
$this->Update();
}
break;
default:
throw new Exception("Invalid Ident: " . $Ident);
}
}
/* ===========================
* 4) Hauptlogik
* =========================== */
public function Update()
{
if (!GetValue($this->GetIDForIdent("State"))) {
return;
}
$batteries = json_decode($this->ReadPropertyString("Batteries"), true);
if (!is_array($batteries) || count($batteries) === 0) {
$this->SendDebug("Update", "Keine Batterien konfiguriert.", 0);
$this->WriteEmptyResults();
return;
}
$sdlPowerW = (int)$this->ReadPropertyInteger("SDL_Leistung");
if ($sdlPowerW < 0) $sdlPowerW = 0;
$reserveH = 0.5; // FIX: 30 Minuten
$eps = 0.0001;
// 1) Summe Max-Leistung aller Batterien
$sumBatPowerW = 0;
foreach ($batteries as $b) {
$p = (int)($b["powerbat"] ?? 0);
if ($p > 0) $sumBatPowerW += $p;
}
if ($sumBatPowerW <= 0) {
$this->SendDebug("Update", "Summe powerbat ist 0 bitte Leistungen setzen.", 0);
$this->WriteEmptyResults();
return;
}
// SDL nicht größer als physikalisch möglich
if ($sdlPowerW > $sumBatPowerW) {
$this->SendDebug("Update", "SDL_Leistung ($sdlPowerW W) > SummeBatPower ($sumBatPowerW W) -> begrenze.", 0);
$sdlPowerW = $sumBatPowerW;
}
// Summen (Leistung)
$P_SDL_max_W = 0.0;
$P_EV_max_W = 0.0;
$P_SDL_laden_W = 0.0;
$P_SDL_entladen_W = 0.0;
$P_EV_laden_W = 0.0;
$P_EV_entladen_W = 0.0;
// Summen (Energie/Fenster)
$sumSDL_req_kWh = 0.0; // Summe Ereq (Reservebedarf 30 min)
$sumEVcap_kWh = 0.0; // Summe EV-Fensterkapazität = Sum(Cap - 2*Ereq)
$sumEV_kWh = 0.0; // Summe EV-Energie im Fenster
$sumSDL_ok_count = 0; // wie viele Batterien erfüllen beide Richtungen
$sumBat_count = 0;
$calc = [];
foreach ($batteries as $idx => $b) {
$typ = (string)($b["typ"] ?? ("Bat#" . ($idx + 1)));
$pBatW = (int)($b["powerbat"] ?? 0);
$capKWh = (float)($b["capazity"] ?? 0);
$socVar = (int)($b["soc"] ?? 0);
if ($pBatW <= 0 || $capKWh <= 0) {
$calc[] = ["typ" => $typ, "skip" => "powerbat<=0 oder capacity<=0"];
continue;
}
$sumBat_count++;
// SDL Anteil Leistung proportional
$pSDL_W_raw = ($sdlPowerW * $pBatW) / $sumBatPowerW;
$pSDL_W = min($pSDL_W_raw, (float)$pBatW);
if ($pSDL_W < 0) $pSDL_W = 0.0;
// EV Anteil Leistung ist Rest
$pEV_W = max(0.0, $pBatW - $pSDL_W);
$P_SDL_max_W += $pSDL_W;
$P_EV_max_W += $pEV_W;
// Reservebedarf für 30 Minuten (kWh)
$Ereq_kWh = ($pSDL_W * $reserveH) / 1000.0;
$sumSDL_req_kWh += $Ereq_kWh;
// Aktuelle Energie aus SoC
$socPct = $this->ReadSocPercent($socVar);
$E_kWh = ($socPct / 100.0) * $capKWh;
// Reserviertes Fenster für SDL
$Emin = $Ereq_kWh;
$Emax = $capKWh - $Ereq_kWh;
// EV-Fensterkapazität (kann 0 werden wenn Cap < 2*Ereq)
$EVcap = max(0.0, $Emax - $Emin);
$sumEVcap_kWh += $EVcap;
// EV-Energie innerhalb Fenster: clamp(E - Emin, 0..EVcap)
$EV_kWh = 0.0;
if ($EVcap > $eps) {
$EV_kWh = min(max($E_kWh - $Emin, 0.0), $EVcap);
}
$sumEV_kWh += $EV_kWh;
// SDL kann 30min ENTladen?
$canSDLdischarge = ($E_kWh + $eps) >= $Emin;
// SDL kann 30min LADEN?
$canSDLcharge = (($capKWh - $E_kWh) + $eps) >= $Ereq_kWh; // equivalent E_kWh <= Emax
// EV kann ENTladen? (nur wenn über Emin)
$canEVdischarge = ($E_kWh - $Emin) > $eps;
// EV kann LADEN? (nur wenn unter Emax)
$canEVcharge = ($Emax - $E_kWh) > $eps;
// Leistungsmaxima (dein Wunsch)
if ($canSDLdischarge) $P_SDL_entladen_W += $pSDL_W;
if ($canSDLcharge) $P_SDL_laden_W += $pSDL_W;
if ($canEVdischarge) $P_EV_entladen_W += $pEV_W;
if ($canEVcharge) $P_EV_laden_W += $pEV_W;
// SDL "OK" wenn beide Richtungen 30min gehen
$sdlOK = ($canSDLdischarge && $canSDLcharge);
if ($sdlOK) $sumSDL_ok_count++;
// Debug / Detail
$calc[] = [
"typ" => $typ,
"pBat_W" => $pBatW,
"cap_kWh" => round($capKWh, 3),
"soc_pct" => round($socPct, 2),
"E_kWh" => round($E_kWh, 3),
"pSDL_W" => round($pSDL_W, 0),
"Ereq_kWh" => round($Ereq_kWh, 3),
"Emin_kWh" => round($Emin, 3),
"Emax_kWh" => round($Emax, 3),
"pEV_W" => round($pEV_W, 0),
"EVcap_kWh" => round($EVcap, 3),
"EV_kWh" => round($EV_kWh, 3),
"canSDLcharge_30min" => $canSDLcharge,
"canSDLdischarge_30min" => $canSDLdischarge,
"canEVcharge" => $canEVcharge,
"canEVdischarge" => $canEVdischarge,
"SDL_OK_both_directions" => $sdlOK
];
}
// SoC_EV = EV-Energie / EV-Fensterkapazität
$socEV = ($sumEVcap_kWh > $eps) ? ($sumEV_kWh / $sumEVcap_kWh) * 100.0 : 0.0;
// SoC_SDL als "OK-Quote" (wie viele Batterien schaffen beide Richtungen)
// Wenn du stattdessen "Leistungs-gewichtete" Quote willst, sag Bescheid.
$socSDL = ($sumBat_count > 0) ? ($sumSDL_ok_count / $sumBat_count) * 100.0 : 0.0;
// Setzen der Ergebnisvariablen
SetValue($this->GetIDForIdent("kWh_SDL"), round($sumSDL_req_kWh, 3));
SetValue($this->GetIDForIdent("kWh_EV"), round($sumEV_kWh, 3));
SetValue($this->GetIDForIdent("SoC_SDL"), round($socSDL, 2));
SetValue($this->GetIDForIdent("SoC_EV"), round($socEV, 2));
SetValue($this->GetIDForIdent("P_SDL_max"), round($P_SDL_max_W, 0));
SetValue($this->GetIDForIdent("P_EV_max"), round($P_EV_max_W, 0));
SetValue($this->GetIDForIdent("P_SDL_laden"), round($P_SDL_laden_W, 0));
SetValue($this->GetIDForIdent("P_SDL_entladen"), round($P_SDL_entladen_W, 0));
SetValue($this->GetIDForIdent("P_EV_laden"), round($P_EV_laden_W, 0));
SetValue($this->GetIDForIdent("P_EV_entladen"), round($P_EV_entladen_W, 0));
// JSON Debug / Buffer
$out = [
"SDL_W" => $sdlPowerW,
"Reserve_h" => $reserveH,
"SumBatPower_W" => $sumBatPowerW,
"SumSDL_req_kWh" => round($sumSDL_req_kWh, 3),
"SumEVcap_kWh" => round($sumEVcap_kWh, 3),
"SumEV_kWh" => round($sumEV_kWh, 3),
"SoC_SDL_pct" => round($socSDL, 2),
"SoC_EV_pct" => round($socEV, 2),
"P_SDL_max_W" => round($P_SDL_max_W, 0),
"P_EV_max_W" => round($P_EV_max_W, 0),
"P_SDL_laden_W" => round($P_SDL_laden_W, 0),
"P_SDL_entladen_W" => round($P_SDL_entladen_W, 0),
"P_EV_laden_W" => round($P_EV_laden_W, 0),
"P_EV_entladen_W" => round($P_EV_entladen_W, 0),
"batteries" => $calc
];
$json = json_encode($out, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
SetValue($this->GetIDForIdent("CalcJSON"), $json);
$this->SetBuffer("BatteryCalc", $json);
$this->SendDebug("Update", $json, 0);
}
/* ===========================
* 5) Helper
* =========================== */
/**
* SoC in Prozent (0..100).
* Akzeptiert 0..1 (wird *100) oder 0..100.
*/
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;
// 0..1 -> Prozent
if ($f >= 0.0 && $f <= 1.0) {
$f *= 100.0;
}
// Clamp
if ($f < 0.0) $f = 0.0;
if ($f > 100.0) $f = 100.0;
return $f;
}
private function WriteEmptyResults()
{
SetValue($this->GetIDForIdent("kWh_SDL"), 0.0);
SetValue($this->GetIDForIdent("kWh_EV"), 0.0);
SetValue($this->GetIDForIdent("SoC_SDL"), 0.0);
SetValue($this->GetIDForIdent("SoC_EV"), 0.0);
SetValue($this->GetIDForIdent("P_SDL_max"), 0.0);
SetValue($this->GetIDForIdent("P_EV_max"), 0.0);
SetValue($this->GetIDForIdent("CalcJSON"), "{}");
$this->SetBuffer("BatteryCalc", "{}");
}
}