803 lines
26 KiB
PHP
803 lines
26 KiB
PHP
<?php
|
|
|
|
class Bat_EV_SDL_V4 extends IPSModule
|
|
{
|
|
public function Create()
|
|
{
|
|
parent::Create();
|
|
|
|
$this->RegisterPropertyString("Batteries", "[]");
|
|
$this->RegisterPropertyInteger("SDL_Leistung_Laden", 0);
|
|
$this->RegisterPropertyInteger("SDL_Leistung_Entladen", 0);
|
|
$this->RegisterPropertyInteger("UpdateIntervalMs", 1000);
|
|
$this->RegisterPropertyFloat("ReserveHours", 0.5);
|
|
$this->RegisterPropertyBoolean("FilterAktiv", false);
|
|
|
|
$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);
|
|
|
|
$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("SDL_Start_Pos", "SDL Start SoC (%)", "", 35);
|
|
|
|
$this->RegisterVariableBoolean("SDL_Reset", "SDL Konto Reset", "~Switch", 36);
|
|
$this->EnableAction("SDL_Reset");
|
|
|
|
$this->RegisterVariableBoolean("EV_Reset", "EV Konto Reset", "~Switch", 37);
|
|
$this->EnableAction("EV_Reset");
|
|
|
|
$this->RegisterVariableString("CalcJSON", "Berechnung JSON", "", 99);
|
|
|
|
$this->RegisterTimer("UpdateTimer", 0, 'BEVSDL4_Update($_IPS["TARGET"]);');
|
|
}
|
|
|
|
public function ApplyChanges()
|
|
{
|
|
parent::ApplyChanges();
|
|
|
|
$intervalMs = (int)$this->ReadPropertyInteger("UpdateIntervalMs");
|
|
$this->SetTimerInterval("UpdateTimer", ($intervalMs > 0) ? $intervalMs : 0);
|
|
|
|
$this->BuildStaticBatteryConfig();
|
|
|
|
$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));
|
|
}
|
|
}
|
|
|
|
?>
|