no message

This commit is contained in:
belevo\mh
2026-05-04 18:33:24 +02:00
parent 8a557a30c5
commit ebf76fd518
2 changed files with 79 additions and 802 deletions
+47 -33
View File
@@ -2,70 +2,86 @@
"elements": [
{
"type": "Label",
"caption": "Aufgepasst: Bei Goodwe nur Ladenvariabel auswählen und entladen Dummy Variabel.\nGoodwe braucht nur eine Leistungssoll Variabel. Entlade NICHT auf gleiche Variabel setzen wie Laden\nGoodwe: Laden=11, Entladen=12,\nSolaredge: Laden=3, Entladen=4\nDefault: Laden=1, Entladen=2"
"caption": "Aufgepasst: Bei Goodwe nur Ladenvariable auswählen und Entladen Dummy-Variable. Goodwe braucht nur eine Leistungssoll-Variable. Entlade NICHT auf gleiche Variable setzen wie Laden.\nGoodwe: Laden=11, Entladen=12\nSolarEdge: Laden=3, Entladen=4\nDefault: Laden=1, Entladen=2"
},
{
"type": "List",
"name": "Batteries",
"caption": "Batterien",
"caption": "Batterien mit Typ, Leistung und Kapazität",
"add": true,
"delete": true,
"columns": [
{
"caption": "Typ",
"name": "typ",
"width": "100px",
"add": "Goodwe",
"edit": { "type": "ValidationTextBox" }
"width": "120px",
"add": "Batterie",
"edit": {
"type": "ValidationTextBox"
}
},
{
"caption": "Leistung in W",
"caption": "Leistung (W)",
"name": "powerbat",
"width": "150px",
"width": "120px",
"add": 5000,
"edit": { "type": "NumberSpinner" }
"edit": {
"type": "NumberSpinner"
}
},
{
"caption": "Kapazität in kWh",
"caption": "Kapazität (kWh)",
"name": "capazity",
"width": "180px",
"width": "140px",
"add": 60,
"edit": { "type": "NumberSpinner" }
"edit": {
"type": "NumberSpinner"
}
},
{
"caption": "SoC",
"name": "soc",
"width": "200px",
"width": "160px",
"add": 0,
"edit": { "type": "SelectVariable" }
"edit": {
"type": "SelectVariable"
}
},
{
"caption": "Batterieleistung Laden",
"caption": "Power Laden",
"name": "powerbat_laden",
"width": "200px",
"width": "160px",
"add": 0,
"edit": { "type": "SelectVariable" }
"edit": {
"type": "SelectVariable"
}
},
{
"caption": "Batterieleistung Entladen",
"caption": "Power Entladen",
"name": "powerbat_entladen",
"width": "200px",
"width": "160px",
"add": 0,
"edit": { "type": "SelectVariable" }
"edit": {
"type": "SelectVariable"
}
},
{
"caption": "Register Laden/Entladen Modus",
"caption": "Modus Register",
"name": "register_ladenentladen_modus",
"width": "260px",
"width": "180px",
"add": 0,
"edit": { "type": "SelectVariable" }
"edit": {
"type": "SelectVariable"
}
},
{
"caption": "Aktuelle Batterieleistung",
"caption": "Ist-Leistung",
"name": "register_bat_power",
"width": "260px",
"width": "180px",
"add": 0,
"edit": { "type": "SelectVariable" }
"edit": {
"type": "SelectVariable"
}
}
]
},
@@ -73,15 +89,13 @@
"type": "NumberSpinner",
"name": "SDL_Leistung_Laden",
"caption": "SDL Leistung Laden",
"suffix": "W",
"minimum": 0
"suffix": "W"
},
{
"type": "NumberSpinner",
"name": "SDL_Leistung_Entladen",
"caption": "SDL Leistung Entladen",
"suffix": "W",
"minimum": 0
"suffix": "W"
},
{
"type": "NumberSpinner",
@@ -94,10 +108,10 @@
},
{
"type": "NumberSpinner",
"name": "UpdateIntervalMs",
"caption": "Neuberechnung alle",
"suffix": "S",
"minimum": 1,
"name": "UpdateInterval",
"caption": "Update Intervall",
"suffix": "Sekunden",
"minimum": 0,
"maximum": 86400
},
{
+32 -769
View File
@@ -6,39 +6,44 @@ class Bat_EV_SDL_V4 extends IPSModule
{
parent::Create();
// Properties
$this->RegisterPropertyString("Batteries", "[]");
$this->RegisterPropertyInteger("SDL_Leistung_Laden", 0);
$this->RegisterPropertyInteger("SDL_Leistung_Entladen", 0);
$this->RegisterPropertyInteger("UpdateInterval", 2);
$this->RegisterPropertyFloat("ReserveHours", 0.5);
$this->RegisterPropertyBoolean("FilterAktiv", false);
$this->RegisterPropertyInteger("SDL_Leistung_Laden", 0); // W
$this->RegisterPropertyInteger("SDL_Leistung_Entladen", 0); // W
$this->RegisterPropertyFloat("ReserveHours", 0.5); // h, vorher fix 0.5h
$this->RegisterPropertyInteger("UpdateInterval", 5); // Sekunden
$this->RegisterPropertyBoolean("FilterAktiv", true);
// Status
$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);
// Prozentwerte / virtuelle Konten
$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("SoC_EV", "EV Energie verfügbar (%)", "", 11);
$this->RegisterVariableFloat("SDL_Start_Pos", "SDL Start SoC (%)", "", 35);
$this->RegisterVariableFloat("EV_Start_Pos", "EV Start SoC (%)", "", 36);
$this->RegisterVariableBoolean("SDL_Reset", "SDL Konto Reset", "~Switch", 36);
// Soll-/Istwerte
$this->RegisterVariableFloat("Nennleistung_Soll_EV", "Nennleistung Soll EV", "", 2);
$this->RegisterVariableFloat("Nennleistung_Soll_SDL", "Nennleistung Soll SDL", "", 3);
$this->RegisterVariableFloat("Aktuelle_Leistung_EV", "Aktuelle Leistung EV", "", 5);
$this->RegisterVariableFloat("Aktuelle_Leistung_SDL", "Aktuelle Leistung SDL", "", 4);
// Maximalleistungen
$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);
// Reset
$this->RegisterVariableBoolean("SDL_Reset", "SDL/EV Konto Reset (Start)", "~Switch", 37);
$this->EnableAction("SDL_Reset");
$this->RegisterVariableBoolean("EV_Reset", "EV Konto Reset", "~Switch", 37);
$this->EnableAction("EV_Reset");
$this->RegisterVariableString("CalcJSON", "Berechnung JSON", "", 99);
// Debug
$this->RegisterVariableString("CalcJSON", "Berechnung (JSON)", "", 99);
// Timer
$this->RegisterTimer("UpdateTimer", 0, 'GEF_Update($_IPS["TARGET"]);');
}
@@ -49,755 +54,13 @@ class Bat_EV_SDL_V4 extends IPSModule
$intervalSec = (int)$this->ReadPropertyInteger("UpdateInterval");
$this->SetTimerInterval("UpdateTimer", ($intervalSec > 0) ? $intervalSec * 1000 : 0);
$this->BuildStaticBatteryConfig();
// Grenz-/Fensterberechnung nur hier hart neu aufbauen
$this->BuildBatteryCache(true);
// Wenn Konfig neu ist: Integratoren sauber auf Startpunkte setzen
$this->ResetVirtualAccountsToStartPoint(false);
$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));
}
}
?>