Files
Symcon_Belevo_Energiemanage…/Bat_EV_SDL/module.php
2026-01-22 17:53:33 +01:00

263 lines
9.5 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
{
public function Create()
{
parent::Create();
// Properties
$this->RegisterPropertyString("Batteries", "[]");
$this->RegisterPropertyInteger("SDL_Leistung", 0); // W
$this->RegisterPropertyInteger("UpdateInterval", 5); // Minuten
// Status
$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)
$this->RegisterVariableFloat("SDL_Pos", "SDL Energie verfügbar (%)", "", 10);
$this->RegisterVariableFloat("SoC_EV", "EV Energie verfügbar (%)", "", 11);
// Leistungsgrenzen
$this->RegisterVariableFloat("P_SDL_max", "SDL max (W)", "", 20);
$this->RegisterVariableFloat("P_SDL_laden", "P SDL laden max (W)", "", 21);
$this->RegisterVariableFloat("P_SDL_entladen", "P SDL entladen max (W)", "", 22);
$this->RegisterVariableFloat("P_EV_max", "EV max (W)", "", 30);
$this->RegisterVariableFloat("P_EV_laden", "P EV laden max (W)", "", 31);
$this->RegisterVariableFloat("P_EV_entladen", "P EV entladen max (W)", "", 32);
// Debug
$this->RegisterVariableString("CalcJSON", "Berechnung (JSON)", "", 99);
// Timer (Prefix: GEF)
$this->RegisterTimer("UpdateTimer", 0, 'GEF_Update($_IPS["TARGET"]);');
}
public function ApplyChanges()
{
parent::ApplyChanges();
$intervalMin = (int)$this->ReadPropertyInteger("UpdateInterval");
$this->SetTimerInterval("UpdateTimer", ($intervalMin > 0) ? $intervalMin * 60 * 1000 : 0);
$this->Update();
}
public function RequestAction($Ident, $Value)
{
if ($Ident === "State") {
SetValue($this->GetIDForIdent("State"), (bool)$Value);
if ((bool)$Value) {
$this->Update();
}
return;
}
throw new Exception("Invalid Ident: " . $Ident);
}
public function Update()
{
if (!GetValue($this->GetIDForIdent("State"))) {
return;
}
$batteries = json_decode($this->ReadPropertyString("Batteries"), true);
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)
$hours = 0.5;
// Summe Batterie-Maxleistungen
$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));
return;
}
// Begrenzung: SDL kann nicht mehr ziehen als gesamt verfügbar ist
if ($sdlTotalW > $sumBatPowerW) {
$sdlTotalW = $sumBatPowerW;
}
$calc = [
"inputs" => [
"SDL_Leistung_W" => $sdlTotalW,
"SumBatPower_W" => $sumBatPowerW,
"hours" => $hours
],
"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);
$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;
// Energie im Akku
$storedKWh = $capKWh * ($socPct / 100.0);
$totalStoredKWh += $storedKWh;
// Anteilige SDL-Leistung (nach Leistungsklasse verteilt)
$sdlShareW = 0.0;
if ($pBatW > 0 && $sdlTotalW > 0) {
$sdlShareW = $sdlTotalW * ($pBatW / $sumBatPowerW);
}
// Physikalisch: 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)
$sdlNeedKWh = ($sdlShareW / 1000.0) * $hours;
// Verfügbare SDL-Energie kann nicht größer sein als gespeicherte Energie
$sdlAvailKWh = min($storedKWh, $sdlNeedKWh);
// EV-Energie ist der Rest im Tank nach SDL-Reservierung
$evEnergyKWh = max(0.0, $storedKWh - $sdlAvailKWh);
// Totale sammeln
$sumSdlPowerW += $sdlShareW;
$sumEvPowerW += $evShareW;
$sumSdlEnergyAvailKWh += $sdlAvailKWh;
$sumEvEnergyKWh += $evEnergyKWh;
// Lade-/Entladegrenzen (einfach & robust)
// Laden: geht immer bis zur Leistungsgrenze
$pSdlChargeW += $sdlShareW;
$pEvChargeW += $evShareW;
// Entladen: nur wenn überhaupt Energie drin ist
if ($storedKWh > 0.0001) {
$pSdlDischargeW += $sdlShareW;
$pEvDischargeW += $evShareW;
}
$calc["batteries"][] = [
"idx" => $idx,
"typ" => (string)($b["typ"] ?? ("Bat " . ($idx + 1))),
"P_bat_W" => round($pBatW, 2),
"Cap_kWh" => round($capKWh, 4),
"SoC_pct" => round($socPct, 3),
"Stored_kWh" => round($storedKWh, 4),
"SDL_share_W" => round($sdlShareW, 2),
"EV_share_W" => round($evShareW, 2),
"SDL_need_kWh" => round($sdlNeedKWh, 4),
"SDL_avail_kWh" => round($sdlAvailKWh, 4),
"EV_energy_kWh" => round($evEnergyKWh, 4),
];
}
// SDL% = verfügbare SDL-Energie / benötigte SDL-Energie
$sdlNeedTotalKWh = ($sdlTotalW / 1000.0) * $hours;
$sdlPosPct = 0.0;
if ($sdlNeedTotalKWh > 0.000001) {
$sdlPosPct = ($sumSdlEnergyAvailKWh / $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);
$evPosPct = 0.0;
if ($evMaxKWh > 0.000001) {
$evPosPct = ($sumEvEnergyKWh / $evMaxKWh) * 100.0;
}
// Variablen setzen
$this->SetIdentValue("SDL_Pos", round($sdlPosPct, 3));
$this->SetIdentValue("SoC_EV", round($evPosPct, 3));
$this->SetIdentValue("P_SDL_max", round($sumSdlPowerW, 2));
$this->SetIdentValue("P_SDL_laden", round($pSdlChargeW, 2));
$this->SetIdentValue("P_SDL_entladen", round($pSdlDischargeW, 2));
$this->SetIdentValue("P_EV_max", round($sumEvPowerW, 2));
$this->SetIdentValue("P_EV_laden", round($pEvChargeW, 2));
$this->SetIdentValue("P_EV_entladen", round($pEvDischargeW, 2));
$calc["totals"] = [
"totalStored_kWh" => round($totalStoredKWh, 4),
"SDL_needTotal_kWh" => round($sdlNeedTotalKWh, 4),
"SDL_availTotal_kWh" => round($sumSdlEnergyAvailKWh, 4),
"EV_energyTotal_kWh" => round($sumEvEnergyKWh, 4),
"SDL_Pos_pct" => round($sdlPosPct, 3),
"EV_Pos_pct" => round($evPosPct, 3),
"P_SDL_sum_W" => round($sumSdlPowerW, 2),
"P_EV_sum_W" => round($sumEvPowerW, 2),
"P_SDL_charge_W" => round($pSdlChargeW, 2),
"P_SDL_discharge_W" => round($pSdlDischargeW, 2),
"P_EV_charge_W" => round($pEvChargeW, 2),
"P_EV_discharge_W" => round($pEvDischargeW, 2),
];
$this->SetValueString("CalcJSON", json_encode($calc, JSON_PRETTY_PRINT));
}
private function SetIdentValue(string $ident, $value): void
{
$id = $this->GetIDForIdent($ident);
if ($id <= 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));
}
}