no message
This commit is contained in:
+189
-26
@@ -1,5 +1,26 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bat_EV_SDL_V4
|
||||||
|
*
|
||||||
|
* Zweck des Moduls:
|
||||||
|
* Dieses IPS-Modul verteilt Lade- und Entlade-Sollwerte auf mehrere Batteriesysteme.
|
||||||
|
* Dabei werden zwei virtuelle Energiekonten geführt:
|
||||||
|
*
|
||||||
|
* - SDL: reservierter Bereich für SDL-Leistung
|
||||||
|
* - EV: restlicher nutzbarer Bereich für EV-Leistung
|
||||||
|
*
|
||||||
|
* Zusätzlich werden reale Batterie-SoC-Werte gelesen, um Lade- und Entladegrenzen
|
||||||
|
* dynamisch zu berücksichtigen.
|
||||||
|
*
|
||||||
|
* Vorzeichenlogik in diesem Modul:
|
||||||
|
* - Positive Leistung = Laden
|
||||||
|
* - Negative Leistung = Entladen
|
||||||
|
*
|
||||||
|
* Achtung:
|
||||||
|
* Das Modul führt aktuell nur globale virtuelle SDL-/EV-Konten.
|
||||||
|
* Die virtuelle Energie wird also nicht separat pro Batterie geführt.
|
||||||
|
*/
|
||||||
class Bat_EV_SDL_V4 extends IPSModule
|
class Bat_EV_SDL_V4 extends IPSModule
|
||||||
{
|
{
|
||||||
public function Create()
|
public function Create()
|
||||||
@@ -67,7 +88,7 @@ class Bat_EV_SDL_V4 extends IPSModule
|
|||||||
"UpdateTimer",
|
"UpdateTimer",
|
||||||
($intervalSec > 0) ? $intervalSec * 1000 : 0
|
($intervalSec > 0) ? $intervalSec * 1000 : 0
|
||||||
);
|
);
|
||||||
|
// Bei Config-Änderung wird der statische Plan neu aufgebaut.
|
||||||
$this->BuildBatteryPlan(true);
|
$this->BuildBatteryPlan(true);
|
||||||
|
|
||||||
// Kein Reset bei ApplyChanges.
|
// Kein Reset bei ApplyChanges.
|
||||||
@@ -102,11 +123,24 @@ class Bat_EV_SDL_V4 extends IPSModule
|
|||||||
|
|
||||||
throw new Exception("Invalid Ident: " . $Ident);
|
throw new Exception("Invalid Ident: " . $Ident);
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
* Update() ist die zentrale zyklische Berechnung.
|
||||||
|
*
|
||||||
|
* Ablauf:
|
||||||
|
* 1. Semaphore sichern, damit keine parallelen Updates laufen.
|
||||||
|
* 2. Prüfen, ob Modul aktiv ist.
|
||||||
|
* 3. Batterieplan laden/aktualisieren.
|
||||||
|
* 4. Sollwerte auf Batterien verteilen.
|
||||||
|
* 5. Aktuelle EV-/SDL-Leistung rückrechnen.
|
||||||
|
* 6. Virtuelle Konten integrieren.
|
||||||
|
* 7. Diagnosewerte schreiben.
|
||||||
|
*/
|
||||||
public function Update()
|
public function Update()
|
||||||
{
|
{
|
||||||
|
|
||||||
$semKey = 'BatEVSDL_Update_' . $this->InstanceID;
|
$semKey = 'BatEVSDL_Update_' . $this->InstanceID;
|
||||||
|
|
||||||
|
// Verhindert, dass ein Timerlauf den vorherigen überholt.
|
||||||
if (!IPS_SemaphoreEnter($semKey, 5000)) {
|
if (!IPS_SemaphoreEnter($semKey, 5000)) {
|
||||||
//$this->SendDebug("Update", "SKIP - Semaphore locked", 0);
|
//$this->SendDebug("Update", "SKIP - Semaphore locked", 0);
|
||||||
return;
|
return;
|
||||||
@@ -116,11 +150,12 @@ class Bat_EV_SDL_V4 extends IPSModule
|
|||||||
if (!GetValue($this->GetIDForIdent("State"))) {
|
if (!GetValue($this->GetIDForIdent("State"))) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// Baut den Plan nur neu, wenn sich relevante Config-Werte geändert haben.
|
||||||
$this->BuildBatteryPlan(false);
|
$this->BuildBatteryPlan(false);
|
||||||
|
|
||||||
$plan = json_decode($this->GetBufferSafe("BatPlanJSON"), true);
|
|
||||||
|
|
||||||
|
$plan = json_decode($this->GetBufferSafe("BatPlanJSON"), true);
|
||||||
|
// Reale SoC-Werte ändern sich laufend, daher werden Leistungsgrenzen aktualisiert.
|
||||||
$plan = $this->RefreshDynamicPlanValues($plan);
|
$plan = $this->RefreshDynamicPlanValues($plan);
|
||||||
|
|
||||||
if (!is_array($plan) || empty($plan["bats"])) {
|
if (!is_array($plan) || empty($plan["bats"])) {
|
||||||
@@ -133,10 +168,10 @@ class Bat_EV_SDL_V4 extends IPSModule
|
|||||||
|
|
||||||
// Danach EV und SDL virtuell integrieren.
|
// Danach EV und SDL virtuell integrieren.
|
||||||
$virt = $this->IntegrateVirtualAccounts($plan);
|
$virt = $this->IntegrateVirtualAccounts($plan);
|
||||||
|
// Virtuelle Prozentwerte anzeigen.
|
||||||
$this->SetIdentValue("SDL_Pos", round($virt["SDL_pct"], 3));
|
$this->SetIdentValue("SDL_Pos", round($virt["SDL_pct"], 3));
|
||||||
$this->SetIdentValue("SoC_EV", round($virt["EV_pct"], 3));
|
$this->SetIdentValue("SoC_EV", round($virt["EV_pct"], 3));
|
||||||
|
// Aktuelle dynamische Maximalleistungen anzeigen.
|
||||||
$tot = $plan["total"] ?? [];
|
$tot = $plan["total"] ?? [];
|
||||||
$this->SetIdentValue("P_SDL_laden", round((float)($tot["SDL_Charge_kW"] ?? 0.0) * 1000.0, 0));
|
$this->SetIdentValue("P_SDL_laden", round((float)($tot["SDL_Charge_kW"] ?? 0.0) * 1000.0, 0));
|
||||||
$this->SetIdentValue("P_SDL_entladen", round((float)($tot["SDL_Discharge_kW"] ?? 0.0) * 1000.0, 0));
|
$this->SetIdentValue("P_SDL_entladen", round((float)($tot["SDL_Discharge_kW"] ?? 0.0) * 1000.0, 0));
|
||||||
@@ -153,6 +188,7 @@ class Bat_EV_SDL_V4 extends IPSModule
|
|||||||
$calcRounded = $this->RoundArray($calc, 3);
|
$calcRounded = $this->RoundArray($calc, 3);
|
||||||
$this->SetIdentValue("CalcJSON", json_encode($calcRounded, JSON_PRETTY_PRINT));
|
$this->SetIdentValue("CalcJSON", json_encode($calcRounded, JSON_PRETTY_PRINT));
|
||||||
|
|
||||||
|
// Reale Gesamtleistung aller Batterien anzeigen.
|
||||||
$totalBat = $this->GetTotalBatteryPowerIstW();
|
$totalBat = $this->GetTotalBatteryPowerIstW();
|
||||||
$this->SetIdentValue("Aktuelle_Leistung_Batterien", round($totalBat, 0));
|
$this->SetIdentValue("Aktuelle_Leistung_Batterien", round($totalBat, 0));
|
||||||
|
|
||||||
@@ -165,8 +201,18 @@ class Bat_EV_SDL_V4 extends IPSModule
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Baut die statischen Batterie-Fenster und Leistungsanteile.
|
* Baut den Batterieplan.
|
||||||
* Diese Methode enthält die komplette Grenzberechnung und läuft nur bei Config-Änderung neu.
|
*
|
||||||
|
* Der Plan enthält pro Batterie:
|
||||||
|
* - Kapazität
|
||||||
|
* - maximale Leistung
|
||||||
|
* - reservierte SDL-Fenster unten/oben
|
||||||
|
* - nutzbares EV-Fenster
|
||||||
|
* - mögliche Lade-/Entladeleistungen
|
||||||
|
*
|
||||||
|
* Warum als Plan?
|
||||||
|
* Die statischen Werte müssen nicht in jedem Timerlauf komplett neu berechnet werden.
|
||||||
|
* Über einen Hash wird erkannt, ob die Konfiguration geändert wurde.
|
||||||
*/
|
*/
|
||||||
private function BuildBatteryPlan(bool $force): void
|
private function BuildBatteryPlan(bool $force): void
|
||||||
{
|
{
|
||||||
@@ -175,8 +221,11 @@ class Bat_EV_SDL_V4 extends IPSModule
|
|||||||
$sdlTotalW_laden = max(0, (int)$this->ReadPropertyInteger("SDL_Leistung_Laden"));
|
$sdlTotalW_laden = max(0, (int)$this->ReadPropertyInteger("SDL_Leistung_Laden"));
|
||||||
$sdlTotalW_entladen = max(0, (int)$this->ReadPropertyInteger("SDL_Leistung_Entladen"));
|
$sdlTotalW_entladen = max(0, (int)$this->ReadPropertyInteger("SDL_Leistung_Entladen"));
|
||||||
|
|
||||||
|
//Reserve Zeit, zb 30 oder 45 min
|
||||||
$reserveH = max(0.0, (float)$this->ReadPropertyFloat("ReserveHours"));
|
$reserveH = max(0.0, (float)$this->ReadPropertyFloat("ReserveHours"));
|
||||||
|
|
||||||
|
// Hash über alle statischen Eingaben.
|
||||||
|
// Wenn dieser gleich bleibt, muss der Plan nicht neu aufgebaut werden.
|
||||||
$hash = md5(json_encode([
|
$hash = md5(json_encode([
|
||||||
"Batteries" => $batteriesRaw,
|
"Batteries" => $batteriesRaw,
|
||||||
"SDL_W_Laden" => $sdlTotalW_laden,
|
"SDL_W_Laden" => $sdlTotalW_laden,
|
||||||
@@ -235,6 +284,9 @@ class Bat_EV_SDL_V4 extends IPSModule
|
|||||||
$pBatW = max(0.0, (float)($b["powerbat"] ?? 0));
|
$pBatW = max(0.0, (float)($b["powerbat"] ?? 0));
|
||||||
$pBatKW = $pBatW / 1000.0;
|
$pBatKW = $pBatW / 1000.0;
|
||||||
$capKWh = max(0.0, (float)($b["capazity"] ?? 0));
|
$capKWh = max(0.0, (float)($b["capazity"] ?? 0));
|
||||||
|
|
||||||
|
// Physischer Mindest-SoC, unter den EV nicht entladen soll.
|
||||||
|
// SDL darf bis zu diesem Mindestwert herunter.
|
||||||
$minPhysicalSocPct = max(0.0, min(100.0, (float)($b["minPhysicalSocPct"] ?? 5.0)));
|
$minPhysicalSocPct = max(0.0, min(100.0, (float)($b["minPhysicalSocPct"] ?? 5.0)));
|
||||||
$minPhysicalKWh = $capKWh * $minPhysicalSocPct / 100.0;
|
$minPhysicalKWh = $capKWh * $minPhysicalSocPct / 100.0;
|
||||||
|
|
||||||
@@ -245,18 +297,24 @@ class Bat_EV_SDL_V4 extends IPSModule
|
|||||||
$typ = (string)($b["typ"] ?? ("Bat " . ($idx + 1)));
|
$typ = (string)($b["typ"] ?? ("Bat " . ($idx + 1)));
|
||||||
$socVarId = (int)($b["soc"] ?? 0);
|
$socVarId = (int)($b["soc"] ?? 0);
|
||||||
|
|
||||||
|
// SDL-Leistung wird proportional zur Batterie-Nennleistung verteilt.
|
||||||
$sdlShareKW_laden = ($sumBatPowerKW > 0.0) ? ($sdlTotalKW_laden / $sumBatPowerKW * $pBatKW) : 0.0;
|
$sdlShareKW_laden = ($sumBatPowerKW > 0.0) ? ($sdlTotalKW_laden / $sumBatPowerKW * $pBatKW) : 0.0;
|
||||||
$sdlShareKW_entladen = ($sumBatPowerKW > 0.0) ? ($sdlTotalKW_entladen / $sumBatPowerKW * $pBatKW) : 0.0;
|
$sdlShareKW_entladen = ($sumBatPowerKW > 0.0) ? ($sdlTotalKW_entladen / $sumBatPowerKW * $pBatKW) : 0.0;
|
||||||
|
|
||||||
|
// EV bekommt den Rest der Batterie-Leistung.
|
||||||
$evShareKW_laden = max(0.0, $pBatKW - $sdlShareKW_laden);
|
$evShareKW_laden = max(0.0, $pBatKW - $sdlShareKW_laden);
|
||||||
$evShareKW_entladen = max(0.0, $pBatKW - $sdlShareKW_entladen);
|
$evShareKW_entladen = max(0.0, $pBatKW - $sdlShareKW_entladen);
|
||||||
|
|
||||||
|
// SDL-Reserve in kWh.
|
||||||
$sdlLowerKWh = $sdlShareKW_entladen * $reserveH;
|
$sdlLowerKWh = $sdlShareKW_entladen * $reserveH;
|
||||||
$sdlUpperKWh = $sdlShareKW_laden * $reserveH;
|
$sdlUpperKWh = $sdlShareKW_laden * $reserveH;
|
||||||
|
|
||||||
|
// EV darf nur zwischen underKWh und upKWh arbeiten.
|
||||||
|
// Darunter liegt SDL-Entlade-Reserve, darüber SDL-Lade-Reserve.
|
||||||
$underKWh = $minPhysicalKWh + $sdlLowerKWh;
|
$underKWh = $minPhysicalKWh + $sdlLowerKWh;
|
||||||
$upKWh = $capKWh - $sdlUpperKWh;
|
$upKWh = $capKWh - $sdlUpperKWh;
|
||||||
|
|
||||||
|
// Grenzen sauber in die Batteriekapazität klemmen.
|
||||||
$underKWh = max($minPhysicalKWh, min($capKWh, $underKWh));
|
$underKWh = max($minPhysicalKWh, min($capKWh, $underKWh));
|
||||||
$upKWh = max($minPhysicalKWh, min($capKWh, $upKWh));
|
$upKWh = max($minPhysicalKWh, min($capKWh, $upKWh));
|
||||||
|
|
||||||
@@ -273,6 +331,7 @@ class Bat_EV_SDL_V4 extends IPSModule
|
|||||||
$realSocPct = $this->ReadSocPercent($socVarId);
|
$realSocPct = $this->ReadSocPercent($socVarId);
|
||||||
$realKWh = $capKWh * $realSocPct / 100.0;
|
$realKWh = $capKWh * $realSocPct / 100.0;
|
||||||
|
|
||||||
|
// Dynamische Freigaben auf Basis des realen SoC.
|
||||||
$canSDLDischarge =
|
$canSDLDischarge =
|
||||||
($realKWh >= $minPhysicalKWh);
|
($realKWh >= $minPhysicalKWh);
|
||||||
|
|
||||||
@@ -340,6 +399,7 @@ class Bat_EV_SDL_V4 extends IPSModule
|
|||||||
$plan["total"]["EV_Discharge_kW"] += $effectiveEVDischargeKW;
|
$plan["total"]["EV_Discharge_kW"] += $effectiveEVDischargeKW;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Plan im Buffer speichern, damit Update() schnell darauf zugreifen kann.
|
||||||
$this->SetBuffer("BatPlanHash", $hash);
|
$this->SetBuffer("BatPlanHash", $hash);
|
||||||
$this->SetBuffer("BatPlanJSON", json_encode($plan));
|
$this->SetBuffer("BatPlanJSON", json_encode($plan));
|
||||||
$this->SetBuffer("Int_CFG_HASH", $hash);
|
$this->SetBuffer("Int_CFG_HASH", $hash);
|
||||||
@@ -350,14 +410,11 @@ class Bat_EV_SDL_V4 extends IPSModule
|
|||||||
//$this->SendDebug("Plan", "Battery plan rebuilt (" . count($plan["bats"]) . " bats)", 0);
|
//$this->SendDebug("Plan", "Battery plan rebuilt (" . count($plan["bats"]) . " bats)", 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function InitVirtualAccountsIfNeeded(): void
|
|
||||||
{
|
|
||||||
if ($this->GetBufferSafe("Int_Init") !== "1") {
|
|
||||||
$this->SetBuffer("Int_LastTs", (string)microtime(true));
|
|
||||||
$this->SetBuffer("Int_Init", "1");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setzt die virtuellen Konten auf die konfigurierten Startwerte.
|
||||||
|
* Wird über den WebFront-Schalter Virtual_Reset ausgelöst.
|
||||||
|
*/
|
||||||
public function ResetVirtualAccounts(): void
|
public function ResetVirtualAccounts(): void
|
||||||
{
|
{
|
||||||
$this->BuildBatteryPlan(false);
|
$this->BuildBatteryPlan(false);
|
||||||
@@ -373,6 +430,7 @@ class Bat_EV_SDL_V4 extends IPSModule
|
|||||||
$sdlStartPct = max(0.0, min(100.0, (float)$this->ReadPropertyFloat("SDL_Start_Pos_Config")));
|
$sdlStartPct = max(0.0, min(100.0, (float)$this->ReadPropertyFloat("SDL_Start_Pos_Config")));
|
||||||
$evStartPct = max(0.0, min(100.0, (float)$this->ReadPropertyFloat("EV_Start_Pos_Config")));
|
$evStartPct = max(0.0, min(100.0, (float)$this->ReadPropertyFloat("EV_Start_Pos_Config")));
|
||||||
|
|
||||||
|
// Prozentwerte in kWh umrechnen und im Buffer speichern.
|
||||||
$this->SetBuffer("Int_E_SDL_kWh", (string)($sdlTotal * $sdlStartPct / 100.0));
|
$this->SetBuffer("Int_E_SDL_kWh", (string)($sdlTotal * $sdlStartPct / 100.0));
|
||||||
$this->SetBuffer("Int_E_EV_kWh", (string)($evTotal * $evStartPct / 100.0));
|
$this->SetBuffer("Int_E_EV_kWh", (string)($evTotal * $evStartPct / 100.0));
|
||||||
$this->SetBuffer("Int_LastTs", (string)microtime(true));
|
$this->SetBuffer("Int_LastTs", (string)microtime(true));
|
||||||
@@ -384,6 +442,13 @@ class Bat_EV_SDL_V4 extends IPSModule
|
|||||||
$this->SendDebug("Virtual_Reset", "SDL=" . round($sdlStartPct, 3) . "%, EV=" . round($evStartPct, 3) . "%", 0);
|
$this->SendDebug("Virtual_Reset", "SDL=" . round($sdlStartPct, 3) . "%, EV=" . round($evStartPct, 3) . "%", 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Integriert die virtuellen SDL-/EV-Konten.
|
||||||
|
*
|
||||||
|
* Formel grob:
|
||||||
|
* Energieänderung kWh = Leistung kW * Zeit h
|
||||||
|
*/
|
||||||
private function IntegrateVirtualAccounts(array $plan): array
|
private function IntegrateVirtualAccounts(array $plan): array
|
||||||
{
|
{
|
||||||
$now = microtime(true);
|
$now = microtime(true);
|
||||||
@@ -472,7 +537,9 @@ class Bat_EV_SDL_V4 extends IPSModule
|
|||||||
"dtSec" => round($dtSec, 3)
|
"dtSec" => round($dtSec, 3)
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Liest EV-/SDL-Sollwerte, verteilt sie auf die Batterien und schreibt die Register.
|
||||||
|
*/
|
||||||
public function ApplySetpoints(?array $plan = null): void
|
public function ApplySetpoints(?array $plan = null): void
|
||||||
{
|
{
|
||||||
if ($plan === null) {
|
if ($plan === null) {
|
||||||
@@ -487,9 +554,23 @@ class Bat_EV_SDL_V4 extends IPSModule
|
|||||||
|
|
||||||
$distribution = $this->CalculateBatteryDistribution($pEvW, $pSdlW, $plan);
|
$distribution = $this->CalculateBatteryDistribution($pEvW, $pSdlW, $plan);
|
||||||
$this->WriteBatteryPowerSetpoints($distribution);
|
$this->WriteBatteryPowerSetpoints($distribution);
|
||||||
|
|
||||||
|
// Nach dem Schreiben wird aus der realen Batterie-Istleistung berechnet,
|
||||||
|
// welcher Anteil EV und welcher SDL zugeordnet wird.
|
||||||
$this->UpdateActualPowerSplit($pEvW, $pSdlW);
|
$this->UpdateActualPowerSplit($pEvW, $pSdlW);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verteilt die gewünschte EV- und SDL-Leistung auf einzelne Batterien.
|
||||||
|
*
|
||||||
|
* Dabei wird geladen bevorzugt bei niedrigem virtuellem SoC,
|
||||||
|
* entladen bevorzugt bei hohem virtuellem SoC.
|
||||||
|
*
|
||||||
|
* Einschränkung:
|
||||||
|
* Da aktuell globaler virtueller SoC verwendet wird, haben alle Batterien denselben
|
||||||
|
* virtuellen SoC. Die Sortierung bringt deshalb nur begrenzt einen Effekt.
|
||||||
|
*/
|
||||||
|
|
||||||
private function CalculateBatteryDistribution(float $pEvW, float $pSdlW, array $plan): array
|
private function CalculateBatteryDistribution(float $pEvW, float $pSdlW, array $plan): array
|
||||||
{
|
{
|
||||||
$batteries = $plan["bats"] ?? [];
|
$batteries = $plan["bats"] ?? [];
|
||||||
@@ -560,6 +641,7 @@ class Bat_EV_SDL_V4 extends IPSModule
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Innerhalb einer Gruppe proportional zur möglichen Leistung verteilen.
|
||||||
$powerForGroup = min($remaining, $groupTotal);
|
$powerForGroup = min($remaining, $groupTotal);
|
||||||
$ratio = $powerForGroup / $groupTotal;
|
$ratio = $powerForGroup / $groupTotal;
|
||||||
|
|
||||||
@@ -598,6 +680,14 @@ class Bat_EV_SDL_V4 extends IPSModule
|
|||||||
return $finalOutput;
|
return $finalOutput;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rechnet die reale Batterie-Gesamtleistung auf EV und SDL zurück.
|
||||||
|
*
|
||||||
|
* Beispiel:
|
||||||
|
* EV-Soll = 3000 W, SDL-Soll = 1000 W, Summe = 4000 W.
|
||||||
|
* Batterie liefert real nur 3600 W.
|
||||||
|
* Dann wird EV = 2700 W, SDL = 900 W angenommen.
|
||||||
|
*/
|
||||||
private function UpdateActualPowerSplit(float $pEvW, float $pSdlW): void
|
private function UpdateActualPowerSplit(float $pEvW, float $pSdlW): void
|
||||||
{
|
{
|
||||||
$totalPowerIst = $this->GetTotalBatteryPowerIstW();
|
$totalPowerIst = $this->GetTotalBatteryPowerIstW();
|
||||||
@@ -610,7 +700,8 @@ class Bat_EV_SDL_V4 extends IPSModule
|
|||||||
$rawEV = $pEvW * $factor;
|
$rawEV = $pEvW * $factor;
|
||||||
$rawSDL = $pSdlW * $factor;
|
$rawSDL = $pSdlW * $factor;
|
||||||
} else {
|
} else {
|
||||||
// KEIN Sprung mehr auf Soll!
|
// Wenn kein Sollwert vorhanden ist, nicht plötzlich auf Soll springen.
|
||||||
|
// Stattdessen letzten gefilterten Wert verwenden.
|
||||||
$rawEV = (float)$this->GetBufferSafe("CUR_EV_VAL");
|
$rawEV = (float)$this->GetBufferSafe("CUR_EV_VAL");
|
||||||
$rawSDL = (float)$this->GetBufferSafe("CUR_SDL_VAL");
|
$rawSDL = (float)$this->GetBufferSafe("CUR_SDL_VAL");
|
||||||
}
|
}
|
||||||
@@ -630,7 +721,15 @@ class Bat_EV_SDL_V4 extends IPSModule
|
|||||||
$this->SetIdentValue("Aktuelle_Leistung_EV", (float)$fEV);
|
$this->SetIdentValue("Aktuelle_Leistung_EV", (float)$fEV);
|
||||||
$this->SetIdentValue("Aktuelle_Leistung_SDL", (float)$fSDL);
|
$this->SetIdentValue("Aktuelle_Leistung_SDL", (float)$fSDL);
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Filtert die rückgerechnete Istleistung.
|
||||||
|
*
|
||||||
|
* Zweck:
|
||||||
|
* - Fehlmessungen abfangen
|
||||||
|
* - träge Batterieantwort glätten
|
||||||
|
* - harte Sprünge vermeiden
|
||||||
|
* - falsche Vorzeichen ignorieren
|
||||||
|
*/
|
||||||
private function FilterCurrent(string $ch, float $raw, float $target, float $tolW, int $needHits): float
|
private function FilterCurrent(string $ch, float $raw, float $target, float $tolW, int $needHits): float
|
||||||
{
|
{
|
||||||
$lastVal = (float)$this->GetBufferSafe("CUR_{$ch}_VAL");
|
$lastVal = (float)$this->GetBufferSafe("CUR_{$ch}_VAL");
|
||||||
@@ -712,7 +811,9 @@ class Bat_EV_SDL_V4 extends IPSModule
|
|||||||
|
|
||||||
return $lastVal;
|
return $lastVal;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Löscht Filterzustände, wenn der Filter deaktiviert wird.
|
||||||
|
*/
|
||||||
private function ClearCurrentFilterBuffers(): void
|
private function ClearCurrentFilterBuffers(): void
|
||||||
{
|
{
|
||||||
foreach (["EV", "SDL"] as $ch) {
|
foreach (["EV", "SDL"] as $ch) {
|
||||||
@@ -724,6 +825,9 @@ class Bat_EV_SDL_V4 extends IPSModule
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Schreibt die berechneten Lade-/Entladeleistungen in die Batterie-Register.
|
||||||
|
*/
|
||||||
private function WriteBatteryPowerSetpoints(array $distribution): void
|
private function WriteBatteryPowerSetpoints(array $distribution): void
|
||||||
{
|
{
|
||||||
//IPS_LogMessage(__FUNCTION__, "distribution=" . json_encode($distribution, JSON_PRETTY_PRINT));
|
//IPS_LogMessage(__FUNCTION__, "distribution=" . json_encode($distribution, JSON_PRETTY_PRINT));
|
||||||
@@ -756,6 +860,12 @@ class Bat_EV_SDL_V4 extends IPSModule
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Schreibt herstellerspezifische Register.
|
||||||
|
*
|
||||||
|
* Je nach Batterie/Wechselrichter sind unterschiedliche Moduswerte nötig.
|
||||||
|
* GoodWe und SolarEdge werden hier speziell behandelt.
|
||||||
|
*/
|
||||||
private function WriteByVendorRegistersSingleMode(string $typ, array $cfg, float $chargeW, float $dischargeW): void
|
private function WriteByVendorRegistersSingleMode(string $typ, array $cfg, float $chargeW, float $dischargeW): void
|
||||||
{
|
{
|
||||||
$t = mb_strtolower($typ);
|
$t = mb_strtolower($typ);
|
||||||
@@ -837,7 +947,12 @@ class Bat_EV_SDL_V4 extends IPSModule
|
|||||||
$setW($varPowerCharge, 0.0);
|
$setW($varPowerCharge, 0.0);
|
||||||
$setW($varPowerDisch, 0.0);
|
$setW($varPowerDisch, 0.0);
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Summiert die reale Batterie-Istleistung.
|
||||||
|
*
|
||||||
|
* Die Multiplikation mit -1 dreht das Vorzeichen.
|
||||||
|
* Offenbar liefern die Batterie-Register ein umgekehrtes Vorzeichen.
|
||||||
|
*/
|
||||||
private function GetTotalBatteryPowerIstW(): float
|
private function GetTotalBatteryPowerIstW(): float
|
||||||
{
|
{
|
||||||
$cfg = json_decode($this->ReadPropertyString("Batteries"), true);
|
$cfg = json_decode($this->ReadPropertyString("Batteries"), true);
|
||||||
@@ -855,6 +970,14 @@ class Bat_EV_SDL_V4 extends IPSModule
|
|||||||
return $sum;
|
return $sum;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Liest einen SoC-Wert.
|
||||||
|
*
|
||||||
|
* Spezialfall:
|
||||||
|
* Wenn in der Config direkt eine Zahl von 0 bis 100 steht und keine IPS-Variable
|
||||||
|
* mit dieser ID existiert, wird diese Zahl als fester SoC interpretiert.
|
||||||
|
*/
|
||||||
|
|
||||||
private function ReadSocPercent(int $varId): float
|
private function ReadSocPercent(int $varId): float
|
||||||
{
|
{
|
||||||
if ($varId >= 0 && $varId <= 100 && !IPS_VariableExists($varId)) {
|
if ($varId >= 0 && $varId <= 100 && !IPS_VariableExists($varId)) {
|
||||||
@@ -872,13 +995,18 @@ class Bat_EV_SDL_V4 extends IPSModule
|
|||||||
|
|
||||||
return max(0.0, min(100.0, (float)$v));
|
return max(0.0, min(100.0, (float)$v));
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Sichere Buffer-Lesefunktion.
|
||||||
|
* Gibt immer einen String zurück, damit Casts kontrolliert funktionieren.
|
||||||
|
*/
|
||||||
private function GetBufferSafe(string $name): string
|
private function GetBufferSafe(string $name): string
|
||||||
{
|
{
|
||||||
$v = $this->GetBuffer($name);
|
$v = $this->GetBuffer($name);
|
||||||
return is_string($v) ? $v : "";
|
return is_string($v) ? $v : "";
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Schreibt bei Fehlern alle relevanten Ausgabewerte auf 0.
|
||||||
|
*/
|
||||||
private function WriteAllZero(string $reason): void
|
private function WriteAllZero(string $reason): void
|
||||||
{
|
{
|
||||||
$this->SetIdentValue("SDL_Pos", 0.0);
|
$this->SetIdentValue("SDL_Pos", 0.0);
|
||||||
@@ -890,7 +1018,10 @@ class Bat_EV_SDL_V4 extends IPSModule
|
|||||||
$this->SetIdentValue("CalcJSON", json_encode(["error" => $reason], JSON_PRETTY_PRINT));
|
$this->SetIdentValue("CalcJSON", json_encode(["error" => $reason], JSON_PRETTY_PRINT));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rundet rekursiv alle Float-Werte in Arrays.
|
||||||
|
* Wird nur für die lesbare JSON-Diagnose verwendet.
|
||||||
|
*/
|
||||||
private function RoundArray($data, int $decimals = 3)
|
private function RoundArray($data, int $decimals = 3)
|
||||||
{
|
{
|
||||||
if (is_array($data)) {
|
if (is_array($data)) {
|
||||||
@@ -906,7 +1037,10 @@ class Bat_EV_SDL_V4 extends IPSModule
|
|||||||
|
|
||||||
return $data;
|
return $data;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Bewegt einen Wert begrenzt in Richtung Zielwert.
|
||||||
|
* Wird im Filter verwendet, damit Werte nicht sprunghaft wechseln.
|
||||||
|
*/
|
||||||
private function MoveTowards(float $current, float $target, float $maxStep): float
|
private function MoveTowards(float $current, float $target, float $maxStep): float
|
||||||
{
|
{
|
||||||
$maxStep = max(0.0, $maxStep);
|
$maxStep = max(0.0, $maxStep);
|
||||||
@@ -918,6 +1052,25 @@ class Bat_EV_SDL_V4 extends IPSModule
|
|||||||
return $current + (($target > $current) ? $maxStep : -$maxStep);
|
return $current + (($target > $current) ? $maxStep : -$maxStep);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Synchronisiert das virtuelle EV-Konto gelegentlich mit dem realen Batterie-SoC.
|
||||||
|
*
|
||||||
|
* Hintergrund:
|
||||||
|
* Das virtuelle EV-Konto wird normalerweise nur über Leistung × Zeit integriert.
|
||||||
|
* Dadurch können mit der Zeit Abweichungen zur realen Batterie entstehen
|
||||||
|
* (z.B. durch Messfehler, Begrenzungen, Verluste oder Neustarts).
|
||||||
|
*
|
||||||
|
* Diese Methode berechnet deshalb den EV-Anteil neu aus den echten Batterie-SoC-Werten.
|
||||||
|
*
|
||||||
|
* Wichtig:
|
||||||
|
* Die Neuberechnung erfolgt nur:
|
||||||
|
* - alle X Stunden
|
||||||
|
* - wenn SDL genau bei 50% steht (Neutralpunkt)
|
||||||
|
* - und nur wenn die Abweichung gross genug ist
|
||||||
|
*
|
||||||
|
* Ziel:
|
||||||
|
* Das virtuelle EV-Konto langfristig wieder an die reale Batterie angleichen.
|
||||||
|
*/
|
||||||
|
|
||||||
private function MaybeRecalculateEVFromPhysical(array $plan, float $eSDL, float &$eEV, float $sdlTotal, float $evTotal): void
|
private function MaybeRecalculateEVFromPhysical(array $plan, float $eSDL, float &$eEV, float $sdlTotal, float $evTotal): void
|
||||||
{
|
{
|
||||||
@@ -1025,7 +1178,14 @@ class Bat_EV_SDL_V4 extends IPSModule
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Aktualisiert die dynamischen Werte im Plan anhand des realen Batterie-SoC.
|
||||||
|
*
|
||||||
|
* Warum nötig?
|
||||||
|
* BuildBatteryPlan() baut hauptsächlich statische Grenzen.
|
||||||
|
* Der echte Batterie-SoC ändert sich aber laufend.
|
||||||
|
* Deshalb werden Lade-/Entladefreigaben hier bei jedem Update erneuert.
|
||||||
|
*/
|
||||||
private function RefreshDynamicPlanValues(array $plan): array
|
private function RefreshDynamicPlanValues(array $plan): array
|
||||||
{
|
{
|
||||||
foreach ($plan["bats"] as &$bat) {
|
foreach ($plan["bats"] as &$bat) {
|
||||||
@@ -1076,7 +1236,10 @@ class Bat_EV_SDL_V4 extends IPSModule
|
|||||||
|
|
||||||
return $plan;
|
return $plan;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Hilfsfunktion zum sicheren Schreiben einer Instanzvariable per Ident.
|
||||||
|
* Wenn die Variable nicht existiert, wird still abgebrochen.
|
||||||
|
*/
|
||||||
private function SetIdentValue(string $ident, $value): void
|
private function SetIdentValue(string $ident, $value): void
|
||||||
{
|
{
|
||||||
$id = @$this->GetIDForIdent($ident);
|
$id = @$this->GetIDForIdent($ident);
|
||||||
|
|||||||
Reference in New Issue
Block a user