Files
Symcon_Belevo_Energiemanage…/Bat_EV_SDL/module.php
2026-01-22 14:50:51 +01:00

352 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)", "", 10);
$this->RegisterVariableFloat("kWh_EV", "Energie EV (kWh)", "", 11);
$this->RegisterVariableFloat("SoC_SDL", "SoC SDL (%)", "", 12);
$this->RegisterVariableFloat("SoC_EV", "SoC EV (%)", "", 13);
$this->RegisterVariableFloat("SDL_max", "P SDL max (W)", "", 20);
$this->RegisterVariableFloat("EV_max", "P EV max (W)", "", 21);
$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
// 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;
}
// Optional: 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 für Energiesicht
$sumSDLPowerW = 0.0;
$sumEVPowerW = 0.0;
$sumSDLcapKWh = 0.0; // SDL-Topf Kapazität (Reserveenergie 30 min)
$sumEVcapKWh = 0.0; // EV-Topf Kapazität (Rest)
$sumSDLkWh = 0.0; // aktueller Füllstand SDL
$sumEVkWh = 0.0; // aktueller Füllstand EV
// Neue Leistungs-Maxwerte (wie dein Beispiel)
$P_EV_laden_W = 0.0;
$P_EV_entladen_W = 0.0;
$P_SDL_laden_W = 0.0;
$P_SDL_entladen_W = 0.0;
$eps = 0.0001;
$calc = [];
// 2) Pro Batterie rechnen
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;
}
// SDL Anteil Leistung (W) proportional
$pSDL_W_raw = ($sdlPowerW * $pBatW) / $sumBatPowerW;
// Physikalisch kappen: pro Batterie nicht mehr als pBatW
$pSDL_W = min($pSDL_W_raw, (float)$pBatW);
if ($pSDL_W < 0) $pSDL_W = 0.0;
// EV Anteil Leistung (W) ist Rest
$pEV_W = max(0.0, $pBatW - $pSDL_W);
// SDL Reserveenergie für 30 Minuten (kWh)
$eSDLcap_kWh = ($pSDL_W * $reserveH) / 1000.0;
// EV-Topf ist Rest der Kapazität
$eEVcap_kWh = max(0.0, $capKWh - $eSDLcap_kWh);
// SoC lesen (0..100 oder 0..1 -> wird umgerechnet)
$socPct = $this->ReadSocPercent($socVar);
// Gesamtenergie in der Batterie (kWh)
$eTotal_kWh = ($socPct / 100.0) * $capKWh;
// Aufteilung Energie: SDL zuerst füllen
$eSDL_kWh = min($eTotal_kWh, $eSDLcap_kWh);
$eEV_kWh = max(0.0, $eTotal_kWh - $eSDL_kWh);
// EV kann maximal eEVcap aufnehmen (sollte i.d.R. eh passen)
if ($eEV_kWh > $eEVcap_kWh) {
$eEV_kWh = $eEVcap_kWh;
}
// Summieren Energiesicht
$sumSDLPowerW += $pSDL_W;
$sumEVPowerW += $pEV_W;
$sumSDLcapKWh += $eSDLcap_kWh;
$sumEVcapKWh += $eEVcap_kWh;
$sumSDLkWh += $eSDL_kWh;
$sumEVkWh += $eEV_kWh;
// 3) Maxwerte Leistung (dein gewünschtes Verhalten)
// EV laden: nur wenn im EV-Topf noch Platz ist
if (($eEVcap_kWh - $eEV_kWh) > $eps) {
$P_EV_laden_W += $pEV_W;
}
// EV entladen: nur wenn im EV-Topf Energie drin ist
if ($eEV_kWh > $eps) {
$P_EV_entladen_W += $pEV_W;
}
// SDL laden: nur wenn im SDL-Topf noch Platz ist
if (($eSDLcap_kWh - $eSDL_kWh) > $eps) {
$P_SDL_laden_W += $pSDL_W;
}
// SDL entladen: HART -> nur wenn SDL-Topf voll ist (30min Garantie)
if (($eSDL_kWh + $eps) >= $eSDLcap_kWh) {
$P_SDL_entladen_W += $pSDL_W;
}
// Detail pro Batterie
$calc[] = [
"typ" => $typ,
"pBat_W" => $pBatW,
"cap_kWh" => round($capKWh, 3),
"soc_pct" => round($socPct, 2),
"eTotal_kWh" => round($eTotal_kWh, 3),
"pSDL_W" => round($pSDL_W, 0),
"eSDLcap_kWh" => round($eSDLcap_kWh, 3),
"eSDL_kWh" => round($eSDL_kWh, 3),
"eEVcap_kWh" => round($eEVcap_kWh, 3),
"eEV_kWh" => round($eEV_kWh, 3),
"pEV_W" => round($pEV_W, 0),
// hilfreiche Debug-Felder für deine Maxwerte-Logik
"canEVcharge" => (($eEVcap_kWh - $eEV_kWh) > $eps),
"canEVdischarge" => ($eEV_kWh > $eps),
"canSDLcharge" => (($eSDLcap_kWh - $eSDL_kWh) > $eps),
"canSDLdischarge_30min" => (($eSDL_kWh + $eps) >= $eSDLcap_kWh)
];
}
// 4) Gesamt-SoC für SDL/EV (bezogen auf jeweilige Topf-Kapazität)
$socSDL = ($sumSDLcapKWh > 0.0) ? ($sumSDLkWh / $sumSDLcapKWh) * 100.0 : 0.0;
$socEV = ($sumEVcapKWh > 0.0) ? ($sumEVkWh / $sumEVcapKWh) * 100.0 : 0.0;
// 5) Setzen der Ergebnisvariablen (Energie + SoC)
SetValue($this->GetIDForIdent("kWh_SDL"), round($sumSDLkWh, 3));
SetValue($this->GetIDForIdent("kWh_EV"), round($sumEVkWh, 3));
SetValue($this->GetIDForIdent("SoC_SDL"), round($socSDL, 2));
SetValue($this->GetIDForIdent("SoC_EV"), round($socEV, 2));
// "statische" Power-Grenzen (ohne Füllstand) bleiben nützlich
SetValue($this->GetIDForIdent("P_SDL_max"), round($sumSDLPowerW, 0));
SetValue($this->GetIDForIdent("P_EV_max"), round($sumEVPowerW, 0));
// 6) Neue Power-Maxwerte (abhängig von Füllstand/Platz)
SetValue($this->GetIDForIdent("P_EV_laden"), round($P_EV_laden_W, 0));
SetValue($this->GetIDForIdent("P_EV_entladen"), round($P_EV_entladen_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));
// 7) Debug / Buffer
$out = [
"SDL_W" => $sdlPowerW,
"Reserve_h" => $reserveH,
"SumBatPower_W" => $sumBatPowerW,
"SumSDLcap_kWh" => round($sumSDLcapKWh, 3),
"SumEVcap_kWh" => round($sumEVcapKWh, 3),
"SumSDL_kWh" => round($sumSDLkWh, 3),
"SumEV_kWh" => round($sumEVkWh, 3),
"SoC_SDL_pct" => round($socSDL, 2),
"SoC_EV_pct" => round($socEV, 2),
"P_SDL_max_W" => round($sumSDLPowerW, 0),
"P_EV_max_W" => round($sumEVPowerW, 0),
"P_EV_laden_W" => round($P_EV_laden_W, 0),
"P_EV_entladen_W" => round($P_EV_entladen_W, 0),
"P_SDL_laden_W" => round($P_SDL_laden_W, 0),
"P_SDL_entladen_W" => round($P_SDL_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", "{}");
}
}