From 60b900fd36957aaaf4f78d3e90e17c7683957b88 Mon Sep 17 00:00:00 2001 From: "belevo\\mh" Date: Thu, 21 May 2026 13:58:36 +0200 Subject: [PATCH] no message --- Bat_EV_SDL _V1/README.md | 67 -- Bat_EV_SDL _V1/form.json | 112 ---- Bat_EV_SDL _V1/module.json | 12 - Bat_EV_SDL _V1/module.php | 881 -------------------------- Bat_EV_SDL_V3_Beta/README.md | 67 -- Bat_EV_SDL_V3_Beta/form.json | 117 ---- Bat_EV_SDL_V3_Beta/module.json | 12 - Bat_EV_SDL_V3_Beta/module.php | 1077 -------------------------------- 8 files changed, 2345 deletions(-) delete mode 100644 Bat_EV_SDL _V1/README.md delete mode 100644 Bat_EV_SDL _V1/form.json delete mode 100644 Bat_EV_SDL _V1/module.json delete mode 100644 Bat_EV_SDL _V1/module.php delete mode 100644 Bat_EV_SDL_V3_Beta/README.md delete mode 100644 Bat_EV_SDL_V3_Beta/form.json delete mode 100644 Bat_EV_SDL_V3_Beta/module.json delete mode 100644 Bat_EV_SDL_V3_Beta/module.php diff --git a/Bat_EV_SDL _V1/README.md b/Bat_EV_SDL _V1/README.md deleted file mode 100644 index b059e3a..0000000 --- a/Bat_EV_SDL _V1/README.md +++ /dev/null @@ -1,67 +0,0 @@ -# Manager_1 -Beschreibung des Moduls. - -### Inhaltsverzeichnis - -1. [Funktionsumfang](#1-funktionsumfang) -2. [Voraussetzungen](#2-voraussetzungen) -3. [Software-Installation](#3-software-installation) -4. [Einrichten der Instanzen in IP-Symcon](#4-einrichten-der-instanzen-in-ip-symcon) -5. [Statusvariablen und Profile](#5-statusvariablen-und-profile) -6. [WebFront](#6-webfront) -7. [PHP-Befehlsreferenz](#7-php-befehlsreferenz) - -### 1. Funktionsumfang - -* - -### 2. Voraussetzungen - -- IP-Symcon ab Version 7.1 - -### 3. Software-Installation - -* Über den Module Store das 'Manager_1'-Modul installieren. -* Alternativ über das Module Control folgende URL hinzufügen - -### 4. Einrichten der Instanzen in IP-Symcon - - Unter 'Instanz hinzufügen' kann das 'Manager_1'-Modul mithilfe des Schnellfilters gefunden werden. - - Weitere Informationen zum Hinzufügen von Instanzen in der [Dokumentation der Instanzen](https://www.symcon.de/service/dokumentation/konzepte/instanzen/#Instanz_hinzufügen) - -__Konfigurationsseite__: - -Name | Beschreibung --------- | ------------------ - | - | - -### 5. Statusvariablen und Profile - -Die Statusvariablen/Kategorien werden automatisch angelegt. Das Löschen einzelner kann zu Fehlfunktionen führen. - -#### Statusvariablen - -Name | Typ | Beschreibung ------- | ------- | ------------ - | | - | | - -#### Profile - -Name | Typ ------- | ------- - | - | - -### 6. WebFront - -Die Funktionalität, die das Modul im WebFront bietet. - -### 7. PHP-Befehlsreferenz - -`boolean GEF_BeispielFunktion(integer $InstanzID);` -Erklärung der Funktion. - -Beispiel: -`GEF_BeispielFunktion(12345);` \ No newline at end of file diff --git a/Bat_EV_SDL _V1/form.json b/Bat_EV_SDL _V1/form.json deleted file mode 100644 index 9a50854..0000000 --- a/Bat_EV_SDL _V1/form.json +++ /dev/null @@ -1,112 +0,0 @@ -{ - "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" - }, - { - "type":"List", - "name":"Batteries", - "caption":"Batterien mit Typ, Leistung und kapazität", - "add":true, - "delete":true, - "columns":[ - { - "caption":"Typ", - "name":"typ", - "width":"100px", - "add":"Stufe", - "edit":{ - "type":"ValidationTextBox" - } - }, - { - "caption":"Leistung in W", - "name":"powerbat", - "width":"150px", - "add":5000, - "edit":{ - "type":"NumberSpinner" - } - }, - { - "caption":"Kapazität in kWh", - "name":"capazity", - "width":"200px", - "add":60, - "edit":{ - "type":"NumberSpinner" - } - }, - { - "caption":"SoC", - "name":"soc", - "width":"200px", - "add":0, - "edit":{ - "type":"SelectVariable" - } - }, - { - "caption":"Batterieleistung Laden", - "name":"powerbat_laden", - "width":"200px", - "add":0, - "edit":{ - "type":"SelectVariable" - } - }, - { - "caption":"Batterieleistung Entladen", - "name":"powerbat_entladen", - "width":"200px", - "add":0, - "edit":{ - "type":"SelectVariable" - } - } - , - { - "caption": "Register Laden/Entladen Modus", - "name": "register_ladenentladen_modus", - "width": "260px", - "add": 0, - "edit": { - "type": "SelectVariable" - } - } - , - { - "caption": "Aktuelle Batterieleistung", - "name": "register_bat_power", - "width": "260px", - "add": 0, - "edit": { - "type": "SelectVariable" - } - } - - ] - }, - { - "type": "NumberSpinner", - "name": "SDL_Leistung_Laden", - "caption": "SDL_Leistung_Laden", - "suffix": "W" - }, - { - "type": "NumberSpinner", - "name": "SDL_Leistung_Entladen", - "caption": "SDL_Leistung_Entladen", - "suffix": "W" - }, - { - "type": "NumberSpinner", - "name": "UpdateInterval", - "caption": "Neuberechnung alle", - "suffix": "Sekunden", - "minimum": 0, - "maximum": 86400 - } - ] -} diff --git a/Bat_EV_SDL _V1/module.json b/Bat_EV_SDL _V1/module.json deleted file mode 100644 index e1cdf98..0000000 --- a/Bat_EV_SDL _V1/module.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "id": "{843EB84A-5180-6B47-A72E-9CDBE59DD9D3}", - "name": "Bat_EV_SDL_V1", - "type": 3, - "vendor": "Belevo AG", - "aliases": ["DemoAlias"], - "parentRequirements": [], - "childRequirements": [], - "implemented": [], - "prefix": "GEF", - "url": "" -} \ No newline at end of file diff --git a/Bat_EV_SDL _V1/module.php b/Bat_EV_SDL _V1/module.php deleted file mode 100644 index b66bdcd..0000000 --- a/Bat_EV_SDL _V1/module.php +++ /dev/null @@ -1,881 +0,0 @@ -RegisterPropertyString("Batteries", "[]"); - //$this->RegisterPropertyInteger("$upKWh + $underKWh)", 0); - $this->RegisterPropertyInteger("SDL_Leistung_Laden", 0); - $this->RegisterPropertyInteger("SDL_Leistung_Entladen", 0); // W - $this->RegisterPropertyInteger("UpdateInterval", 5); // Minuten - - // Status - $this->RegisterVariableBoolean("State", "Aktiv", "~Switch", 1); - $this->EnableAction("State"); - - // Prozentwerte - $this->RegisterVariableFloat("SDL_Pos", "SDL Energie verfügbar (%)", "", 10); - $this->RegisterVariableFloat("SoC_EV", "EV Energie verfügbar (%)", "", 11); - - // Variablen - $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); - $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_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: wichtig -> Prefix muss passen - $this->RegisterTimer("UpdateTimer", 0, 'GEF_Update($_IPS["TARGET"]);'); - } - - public function ApplyChanges() - { - parent::ApplyChanges(); - - $intervalSec = (int)$this->ReadPropertyInteger("UpdateInterval"); - $this->SetTimerInterval("UpdateTimer", ($intervalSec > 0) ? $intervalSec * 1000 : 0); - - // Cache neu bauen (force) - $this->BuildBatteryCache(true); - - $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() - { - - - // Reentranzschutz – verhindert parallele Update()-Läufe - $semKey = 'BatEVSDL_Update_' . $this->InstanceID; - - if (!IPS_SemaphoreEnter($semKey, 5000)) { - // Überspringen, wenn bereits ein Lauf aktiv ist - $this->SendDebug("Update", "SKIP (Semaphore locked)", 0); - return; - } - - - - try { - - if (!GetValue($this->GetIDForIdent("State"))) { - return; - } - - // Cache nur neu bauen, wenn nötig - $this->BuildBatteryCache(false); - - $cache = json_decode($this->GetBufferSafe("BatCacheJSON"), true); - if (!is_array($cache) || empty($cache["bats"])) { - $this->WriteAllZero("cache empty/invalid"); - return; - } - - $calc = [ - "inputs" => $cache["inputs"] ?? [], - "batteries" => [], - "total" => [] - ]; - - // Summen - $sdlDisKW_ges = 0.0; - $evDisKW_ges = 0.0; - $sdlChKW_ges = 0.0; - $evChKW_ges = 0.0; - - $real_kWh_ev_ges = 0.0; - $real_kWh_sdl_ges = 0.0; - - $SDL_kWh_ges = 0.0; - $EV_kWh_ges = 0.0; - $totalCapKWh = 0.0; - - foreach ($cache["bats"] as $i => $c) { - - $capKWh = (float)($c["capKWh"] ?? 0.0); - if ($capKWh <= 0.0) { - continue; - } - - // dynamisch: SoC lesen - $socVarId = (int)($c["socVarId"] ?? 0); - $socPct = $this->ReadSocPercent($socVarId); - - $real_kWh = $capKWh / 100.0 * $socPct; - - // vorkalkuliert: - $typ = (string)($c["typ"] ?? ("Bat " . ($i + 1))); - $underKWh = (float)($c["underKWh"] ?? 0.0); - $upKWh = (float)($c["upKWh"] ?? 0.0); - $SDL_kWh = (float)($c["SDL_kWh_total"] ?? 0.0); - $EV_kWh = (float)($c["EV_kWh_total"] ?? 0.0); - - $sdlShareKW_laden = (float)($c["sdlShareKW_laden"] ?? 0.0); - $sdlShareKW_entladen = (float)($c["sdlShareKW_entladen"] ?? 0.0); - $evShareKW_laden = (float)($c["evShareKW_laden"] ?? 0.0); - $evShareKW_entladen = (float)($c["evShareKW_entladen"] ?? 0.0); - - - - // Defaults - $EV_SOC = 0.0; - $SDL_SOC = 0.0; - - $sdlDisKW = 0.0; $evDisKW = 0.0; - $sdlChKW = 0.0; $evChKW = 0.0; - - $real_kWh_ev = 0.0; - $real_kWh_sdl = 0.0; - - // --- Deine 3 Fälle --- - if ($underKWh <= $real_kWh && $upKWh >= $real_kWh) { - - - $denSDL = ($capKWh - $upKWh + $underKWh); - if ($denSDL > 0.0) { - $SDL_SOC = 100.0 * $underKWh / $denSDL; - } else { - $SDL_SOC = 0.0; - } - $SDL_SOC = is_finite($SDL_SOC) ? max(0.0, min(100.0, $SDL_SOC)) : 0.0; - - - if ($EV_kWh > 0.0) { - $EV_SOC = 100.0 * ($real_kWh - $underKWh) / $EV_kWh; - } else { - $EV_SOC = 0.0; // definierter Fallback - } - $EV_SOC = is_finite($EV_SOC) ? max(0.0, min(100.0, $EV_SOC)) : 0.0; - - - - - $sdlDisKW = $sdlShareKW_entladen; - $evDisKW = $evShareKW_entladen; - $sdlChKW = $sdlShareKW_laden; - $evChKW = $evShareKW_laden; - - $real_kWh_ev = $real_kWh - $underKWh; - $real_kWh_sdl = $underKWh; - - } elseif ($upKWh < $real_kWh) { - - $EV_SOC = 100.0; - - $den1 = ($capKWh - $real_kWh + $underKWh); - $den2 = (2.0 * $underKWh); - - if ($den1 > 0.0 && $den2 > 0.0) { - $SDL_SOC = min(100.0, ($den1 / $den2) * 100.0); - } else { - $SDL_SOC = 0.0; - } - $SDL_SOC = is_finite($SDL_SOC) ? max(0.0, min(100.0, $SDL_SOC)) : 0.0; - - $sdlDisKW = $sdlShareKW_entladen; - $evDisKW = $evShareKW_entladen; - $sdlChKW = $sdlShareKW_laden; - $evChKW = 0.0; - - $real_kWh_ev = $capKWh - ($capKWh - $upKWh + $underKWh); - $real_kWh_sdl = ($capKWh - $upKWh + $underKWh) - ($capKWh - $real_kWh); - - } elseif ($underKWh > $real_kWh) { - - $EV_SOC = 0.0; - - $den = $upKWh + $underKWh; - $SDL_SOC = ($den > 0.0) ? ($real_kWh * 100.0 / $den) : 0.0; - $SDL_SOC = is_finite($SDL_SOC) ? max(0.0, min(100.0, $SDL_SOC)) : 0.0; - - $sdlDisKW = $sdlShareKW_entladen; - $evDisKW = 0.0; - $sdlChKW = $sdlShareKW_laden; - $evChKW = $evShareKW_laden; - - $real_kWh_ev = 0.0; - $real_kWh_sdl = $real_kWh; - } - - // Null/Full - if ($real_kWh <= 0.0) { - $sdlDisKW = 0.0; - $real_kWh_ev = 0.0; - $real_kWh_sdl = 0.0; - } elseif ($real_kWh >= $capKWh) { - $sdlChKW = 0.0; - //$real_kWh_ev = $capKWh - ($upKWh + $underKWh); - //$real_kWh_sdl = $upKWh + $underKWh; - - $real_kWh_ev = $EV_kWh; - $real_kWh_sdl = $SDL_kWh; - } - - $real_kWh_ev = max(0.0, $real_kWh_ev); - $real_kWh_sdl = max(0.0, $real_kWh_sdl); - - // Summen - $totalCapKWh += $capKWh; - - $sdlDisKW_ges += $sdlDisKW; - $evDisKW_ges += $evDisKW; - $sdlChKW_ges += $sdlChKW; - $evChKW_ges += $evChKW; - - $real_kWh_ev_ges += $real_kWh_ev; - $real_kWh_sdl_ges += $real_kWh_sdl; - - $SDL_kWh_ges += $SDL_kWh; - $EV_kWh_ges += $EV_kWh; - - $calc["batteries"][] = [ - "idx" => $c["idx"] ?? $i, - "typ" => $typ, - "SoC_varId" => $socVarId, - "SoC_pct" => round($socPct, 3), - "Effektive kWh" => round($real_kWh, 3), - - "EV_SOC" => round($EV_SOC, 3), - "SDL_SOC" => round($SDL_SOC, 3), - - "EV_kWh" => round($real_kWh_ev, 3), - "SDL_kWh" => round($real_kWh_sdl, 3), - - "under_grenze_kWh" => round($underKWh, 3), - "up_grenze_kWh" => round($upKWh, 3), - - "SDL_Charge_kW" => round($sdlChKW, 3), - "SDL_Discharge_kW" => round($sdlDisKW, 3), - "EV_Charge_kW" => round($evChKW, 3), - "EV_Discharge_kW" => round($evDisKW, 3), - ]; - } - - $evPosPct = ($EV_kWh_ges > 0.0) ? ($real_kWh_ev_ges / $EV_kWh_ges * 100.0) : 0.0; - $sdlPosPct = ($SDL_kWh_ges > 0.0) ? ($real_kWh_sdl_ges / $SDL_kWh_ges * 100.0) : 0.0; - - $this->SetIdentValue("SDL_Pos", round($sdlPosPct, 3)); - $this->SetIdentValue("SoC_EV", round($evPosPct, 3)); - - $this->SetIdentValue("P_SDL_laden", round($sdlChKW_ges * 1000.0, 0)); - $this->SetIdentValue("P_SDL_entladen", round($sdlDisKW_ges * 1000.0, 0)); - $this->SetIdentValue("P_EV_laden", round($evChKW_ges * 1000.0, 0)); - $this->SetIdentValue("P_EV_entladen", round($evDisKW_ges * 1000.0, 0)); - - $calc["total"] = [ - "SDL_SoC_pct" => round($sdlPosPct, 3), - "EV_SoC_pct" => round($evPosPct, 3), - - "SDL_kWh_total" => round($SDL_kWh_ges, 3), - "EV_kWh_total" => round($EV_kWh_ges, 3), - - "SDL_Charge_kW" => round($sdlChKW_ges, 3), - "SDL_Discharge_kW" => round($sdlDisKW_ges, 3), - "EV_Charge_kW" => round($evChKW_ges, 3), - "EV_Discharge_kW" => round($evDisKW_ges, 3), - - "totalCap_kWh" => round($totalCapKWh, 3) - ]; - - $this->SetIdentValue("CalcJSON", json_encode($calc, JSON_PRETTY_PRINT)); - $this->ApplySetpoints(); - - - } catch (Throwable $e) { - $this->SendDebug("Update ERROR", $e->getMessage() . " @ " . $e->getFile() . ":" . $e->getLine(), 0); - $this->WriteAllZero("Exception: " . $e->getMessage()); - } - finally { - IPS_SemaphoreLeave($semKey); // ✅ Tür immer wieder aufschließen - } - } - - private function BuildBatteryCache(bool $force): void - { - $batteriesRaw = $this->ReadPropertyString("Batteries"); - //$sdlTotalW = max(0, (int)$this->ReadPropertyInteger("SDL_Leistung")); - $sdlTotalW_laden = max(0, (int)$this->ReadPropertyInteger("SDL_Leistung_Laden")); - $sdlTotalW_entladen = max(0, (int)$this->ReadPropertyInteger("SDL_Leistung_Entladen")); - $hours = self::HOURS; - - $hash = md5(json_encode([ - "Batteries" => $batteriesRaw, - "SDL_W_Laden" => $sdlTotalW_laden, - "SDL_W_Entladen" => $sdlTotalW_entladen, - "hours" => $hours - ])); - - $oldHash = $this->GetBufferSafe("BatCacheHash"); - if (!$force && $oldHash === $hash) { - return; - } - - $batteries = json_decode($batteriesRaw, true); - if (!is_array($batteries)) $batteries = []; - - $sumBatPowerW = 0.0; - foreach ($batteries as $b) { - $p = (float)($b["powerbat"] ?? 0); - if ($p > 0) $sumBatPowerW += $p; - } - - $cache = [ - "inputs" => [ - "SDL_Leistung_W_laden" => $sdlTotalW_laden, - "SDL_Leistung_W_entladen" => $sdlTotalW_entladen, - "SumBatPower_W" => round($sumBatPowerW, 0), - "hours" => $hours - ], - "bats" => [] - ]; - - if ($sumBatPowerW <= 0.0) { - $this->SetBuffer("BatCacheHash", $hash); - $this->SetBuffer("BatCacheJSON", json_encode($cache)); - $this->SendDebug("Cache", "sumBatPowerW=0 -> empty cache", 0); - return; - } - - $sumBatPowerkW = $sumBatPowerW / 1000.0; - //$sdlTotalkW = $sdlTotalW / 1000.0; - - $sdlTotalkW_laden = $sdlTotalW_laden / 1000.0; - $sdlTotalkW_entladen = $sdlTotalW_entladen / 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) continue; - - $socVarId = (int)($b["soc"] ?? 0); - $typ = (string)($b["typ"] ?? ("Bat " . ($idx + 1))); - - - // Mit laden und entladen unterschiedlich - //---------------------------------------------------- - $sdlShareKW_laden = ($sumBatPowerkW > 0.0) ? ($sdlTotalkW_laden / $sumBatPowerkW * $pBatkW) : 0.0; - $evShareKW_laden = $pBatkW - $sdlShareKW_laden; - $upKWh = $capKWh - $sdlShareKW_laden * 0.5; - - $sdlShareKW_entladen = ($sumBatPowerkW > 0.0) ? ($sdlTotalkW_entladen / $sumBatPowerkW * $pBatkW) : 0.0; - $evShareKW_entladen = $pBatkW - $sdlShareKW_entladen; - $underKWh = $sdlShareKW_entladen * 0.5; - - $SDL_kWh = $underKWh + ($capKWh -$upKWh); - $EV_kWh = $capKWh - $SDL_kWh; - //---------------------------------------------------- - - $cache["bats"][] = [ - "idx" => $idx, - "typ" => $typ, - - "socVarId" => $socVarId, - "capKWh" => $capKWh, - - "pBatW" => $pBatW, - "sdlShareKW_laden" => $sdlShareKW_laden, - "sdlShareKW_entladen" => $sdlShareKW_entladen, - "evShareKW_laden" => $evShareKW_laden, - "evShareKW_entladen" => $evShareKW_entladen, - - "underKWh" => $underKWh, - "upKWh" => $upKWh, - - "SDL_kWh_total" => $SDL_kWh, - "EV_kWh_total" => $EV_kWh - ]; - } - - $this->SetBuffer("BatCacheHash", $hash); - $this->SetBuffer("BatCacheJSON", json_encode($cache)); - - $this->SendDebug("Cache", "Battery cache rebuilt (" . count($cache["bats"]) . " bats)", 0); - } - - private function GetBufferSafe(string $name): string - { - $v = $this->GetBuffer($name); - return is_string($v) ? $v : ""; - } - - 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.0); - $this->SetIdentValue("P_EV_entladen", 0.0); - - $this->SetIdentValue("CalcJSON", json_encode(["error" => $reason], JSON_PRETTY_PRINT)); - } - - private function SetIdentValue(string $ident, $value): void - { - $id = @$this->GetIDForIdent($ident); - if ($id <= 0) { - $this->SendDebug(__FUNCTION__, "Ident nicht gefunden: $ident", 0); - return; - } - SetValue($id, $value); - } - - private function ReadSocPercent(int $varId): float - { - // Falls jemand statt Variable direkt 0..100 einträgt - 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; - } - - $soc = (float)$v; - if ($soc < 0.0) $soc = 0.0; - if ($soc > 100.0) $soc = 100.0; - - return $soc; - } - - public function ApplySetpoints(): void - { - $pEvW = (float) GetValue($this->GetIDForIdent("Nennleistung_Soll_EV")); - $pSdlW = (float) GetValue($this->GetIDForIdent("Nennleistung_Soll_SDL")); - - //Diverenz zwischen EV und SDl berechnen - - - // Methode für priorisierung - $distribution = $this->CalculateBatteryDistribution($pEvW,$pSdlW); - - - $this->WriteBatteryPowerSetpoints($distribution); - } - - - - -private function CalculateBatteryDistribution(float $pEvW, float $pSdlW): array -{ - $calcJsonId = $this->GetIDForIdent("CalcJSON"); - // Fallback, falls Variable leer ist oder nicht existiert - if (!IPS_VariableExists($calcJsonId)) { - return []; - } - $rawJson = (string)GetValue($calcJsonId); - if (empty($rawJson)) { - return []; - } - $calc = json_decode($rawJson, true); - $batteries = $calc['batteries'] ?? []; - - // --------------------------------------------------------- - // Hilfsfunktion: Verteilungslogik (Closure) - // --------------------------------------------------------- - $distributePower = function(float $targetPower, string $mode) use ($batteries): array { - // Initialisierung des Ergebnis-Arrays (Key = idx, Value = zugewiesene Watt) - $result = []; - foreach ($batteries as $bat) { - $result[$bat['idx']] = 0.0; - } - - if (abs($targetPower) < 0.01) { - return $result; // Nichts zu tun - } - - $isCharge = ($targetPower > 0); - $absPower = abs($targetPower); - // Relevante Keys basierend auf Modus (EV oder SDL) und Richtung (Laden/Entladen) - $socKey = ($mode === 'EV') ? 'EV_SOC' : 'SDL_SOC'; - // Achtung: JSON Limits sind in kW, wir rechnen in Watt -> * 1000 - $limitKey = $isCharge ? $mode . '_Charge_kW' : $mode . '_Discharge_kW'; - - // 1. Batterien vorbereiten und gruppieren nach gerundetem SoC - $groups = []; - foreach ($batteries as $bat) { - $soc = (int)round($bat[$socKey]); // Auf ganze Zahl runden - $maxW = ((float)$bat[$limitKey]) * 1000.0; // kW in Watt umrechnen - $groups[$soc][] = [ - 'idx' => $bat['idx'], - 'maxW' => $maxW - ]; - } - - // 2. Sortieren der Gruppen - // Laden: Wenig SoC zuerst (ASC) -> leere füllen - // Entladen: Viel SoC zuerst (DESC) -> volle leeren - if ($isCharge) { - ksort($groups); - } else { - krsort($groups); - } - - // 3. Verteilung - $remainingNeeded = $absPower; - - foreach ($groups as $soc => $groupBatteries) { - if ($remainingNeeded <= 0.01) break; - - // Gesamte verfügbare Leistung in dieser SoC-Gruppe ermitteln - $groupTotalCapacity = 0.0; - foreach ($groupBatteries as $gb) { - $groupTotalCapacity += $gb['maxW']; - } - - // Wie viel können wir dieser Gruppe zuteilen? - // Entweder alles was die Gruppe kann, oder den Restbedarf - $powerForThisGroup = min($remainingNeeded, $groupTotalCapacity); - - // Proportionale Aufteilung innerhalb der Gruppe - // Falls Gruppe Kapazität 0 hat (Defekt/Voll), verhindern wir DivByZero - if ($groupTotalCapacity > 0) { - $ratio = $powerForThisGroup / $groupTotalCapacity; - foreach ($groupBatteries as $gb) { - $assigned = $gb['maxW'] * $ratio; - $result[$gb['idx']] = $assigned; - } - } - - $remainingNeeded -= $powerForThisGroup; - } - - // Wenn wir entladen, müssen die Werte negativ sein - if (!$isCharge) { - foreach ($result as $idx => $val) { - $result[$idx] = -$val; - } - } - - return $result; - }; - - // --------------------------------------------------------- - // Hauptablauf - // --------------------------------------------------------- - - // 1. Berechnung für EV und SDL getrennt durchführen - $evDistribution = $distributePower($pEvW, 'EV'); - $sdlDistribution = $distributePower($pSdlW, 'SDL'); - - // 2. Ergebnisse zusammenführen und Output formatieren - $finalOutput = []; - - foreach ($batteries as $bat) { - $idx = $bat['idx']; - // Summe der beiden Anforderungen (kann sich gegenseitig aufheben) - $valEv = $evDistribution[$idx] ?? 0.0; - $valSdl = $sdlDistribution[$idx] ?? 0.0; - $totalW = $valEv + $valSdl; - - // Aufteilen in Charge / Discharge für das Return-Format - $chargeW = 0.0; - $dischargeW = 0.0; - - if ($totalW > 0) { - $chargeW = abs($totalW); - } else { - $dischargeW = abs($totalW); - } - - // JSON Objekt erstellen - $finalOutput[] = [ - "idx" => $idx, - "typ" => (string)$bat['typ'], // Typ als String beibehalten - "chargeW" => round($chargeW, 0), // Optional: runden für sauberes JSON - "dischargeW" => round($dischargeW, 0) - ]; - } - - $batteriesRaw = json_decode($this->ReadPropertyString("Batteries")); - - $totalPower_ist = 0; - - foreach ($batteriesRaw as $bat) { - $totalPower_ist += (-1)*GetValue($bat->register_bat_power); - } - - // ---------------------------- - // ALT (nicht mehr gebraucht) -> AUSKOMMENTIERT - // ---------------------------- - /* - $sumReq = (abs($pEvW) + abs($pSdlW)); - $sumReqRel = ($pEvW + $pSdlW); - - if($sumReq==0){ - - $this->SetValue("Aktuelle_Leistung_EV", $totalPower_ist / 2); - $this->SetValue("Aktuelle_Leistung_SDL", $totalPower_ist / 2); - - }else{ - - if($pEvW>=0){ - $this->SetValue("Aktuelle_Leistung_EV",((1+($totalPower_ist-$sumReqRel) / $sumReq)) * $pEvW); - }else{ - $this->SetValue("Aktuelle_Leistung_EV",((1-($totalPower_ist-$sumReqRel) / $sumReq)) * $pEvW); - } - - if($pSdlW>=0){ - $this->SetValue("Aktuelle_Leistung_SDL",((1+($totalPower_ist-$sumReqRel) / $sumReq)) * $pSdlW); - }else{ - $this->SetValue("Aktuelle_Leistung_SDL",((1-($totalPower_ist-$sumReqRel) / $sumReq)) * $pSdlW); - } - } - */ - - // ---------------------------- - // NEU (genau deine gewünschte Logik) - // aktuelle - EV_SOLL = aktuelle SDL - // aktuelle - SDL_SOLL = aktuelle EV - // ---------------------------- - - - - - /* - $eps = 0.01; - - $aktEV = 0.0; - $aktSDL = 0.0; - - // Optional: Wenn einer 0 ist -> aktiver Kanal bekommt totalPower_ist - if (abs($pEvW) < $eps && abs($pSdlW) < $eps) { - - $aktEV = 0.0; - $aktSDL = 0.0; - - } elseif (abs($pEvW) < $eps) { - - $aktEV = 0.0; - $aktSDL = $totalPower_ist; - - } elseif (abs($pSdlW) < $eps) { - - $aktEV = $totalPower_ist; - $aktSDL = 0.0; - - } else { - - // Beide aktiv - $sameDirection = (($pEvW > 0 && $pSdlW > 0) || ($pEvW < 0 && $pSdlW < 0)); - - if ($sameDirection) { - // Regel A: gleiche Richtung -> Ist proportional nach |Soll| aufteilen - $sumAbs = abs($pEvW) + abs($pSdlW); - - if ($sumAbs < $eps) { - // Fallback - $aktEV = $totalPower_ist / 2.0; - $aktSDL = $totalPower_ist / 2.0; - } else { - $aktEV = $totalPower_ist * (abs($pEvW) / $sumAbs); - $aktSDL = $totalPower_ist - $aktEV; // Summe bleibt exakt totalPower_ist - } - - } else { - // Regel B: gegensätzliche Richtung -> Sollwerte anzeigen - $aktEV = $pEvW; - $aktSDL = $pSdlW; - } - } - - $this->SetValue("Aktuelle_Leistung_EV", (float)$aktEV); - $this->SetValue("Aktuelle_Leistung_SDL", (float)$aktSDL); - */ - - $eps = 0.01; - - $sumSoll = (float)($pEvW + $pSdlW); - - if (abs($sumSoll) > $eps) { - // Normalfall: wir skalieren so, dass EV+SDL genau totalPower_ist ergibt - $factor = (float)($totalPower_ist / $sumSoll); - - $aktEV = (float)($pEvW * $factor); - $aktSDL = (float)($pSdlW * $factor); - - } else { - // Sonderfall: Soll hebt sich nahezu auf (sumSoll ~ 0) - // -> wenn Ist ebenfalls ~0: Soll anzeigen - if (abs($totalPower_ist) < 50.0) { // 50W Toleranz kannst du anpassen - $aktEV = (float)$pEvW; - $aktSDL = (float)$pSdlW; - } else { - // Ist ist nicht 0 obwohl Sollsumme 0 -> Abweichung zuweisen (z.B. SDL) - $aktEV = (float)$pEvW; - $aktSDL = (float)($totalPower_ist - $aktEV); - // Alternative: Abweichung EV geben: - // $aktSDL = (float)$pSdlW; - // $aktEV = (float)($totalPower_ist - $aktSDL); - } - } - - $this->SetValue("Aktuelle_Leistung_EV", $aktEV); - $this->SetValue("Aktuelle_Leistung_SDL", $aktSDL); - - - - return $finalOutput; -} - - - private function WriteBatteryPowerSetpoints(array $distribution): void - { - IPS_LogMessage( - __FUNCTION__, - "distribution=" . json_encode($distribution, JSON_PRETTY_PRINT) - ); - - $batteriesCfg = json_decode($this->ReadPropertyString("Batteries"), true); - if (!is_array($batteriesCfg) || empty($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 " . ($idx + 1)))); - - // ✅ immer positiv - $chargeW = max(0.0, (float)($d["chargeW"] ?? 0.0)); - $dischargeW = max(0.0, (float)($d["dischargeW"] ?? 0.0)); - - // nie gleichzeitig - if ($chargeW > 0.0 && $dischargeW > 0.0) { - $this->SendDebug("WriteBatteryPowerSetpoints", "WARN: both >0 for $typ (idx=$idx) -> set 0", 0); - $chargeW = 0.0; - $dischargeW = 0.0; - } - - $this->WriteByVendorRegistersSingleMode($typ, $cfg, $chargeW, $dischargeW); - - $this->SendDebug("Setpoints", "$typ (idx=$idx) charge={$chargeW}W discharge={$dischargeW}W", 0); - } - } - -private function WriteByVendorRegistersSingleMode(string $typ, array $cfg, float $chargeW, float $dischargeW): void -{ - $t = mb_strtolower($typ); - - // Leistungs-Variablen - $varPowerCharge = (int)($cfg["powerbat_laden"] ?? 0); - $varPowerDisch = (int)($cfg["powerbat_entladen"] ?? 0); - - // ✅ EIN Modus-Register - $varMode = (int)($cfg["register_ladenentladen_modus"] ?? 0); - - // sichere Writer - $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)); // ✅ niemals negativ - } - }; - - // Moduscodes je Typ - $modeCharge = 1; $modeDisch = 2; // Default - if (strpos($t, "goodwe") !== false) { - $modeCharge = 11; $modeDisch = 12; - } elseif (strpos($t, "solaredge") !== false) { - $modeCharge = 3; $modeDisch = 4; - } - - // ========================= - // Laden - // ========================= - if ($chargeW > 0.0) { - - // Modus setzen (ein Register) - $setInt($varMode, $modeCharge); - - // GoodWe: nur powerbat_laden - if (strpos($t, "goodwe") !== false) { - $setW($varPowerCharge, (int)$chargeW); - $setW($varPowerDisch, (int)0); - return; - } - - // SolarEdge + Default: zwei Leistungsregister - $setW($varPowerCharge, $chargeW); - $setW($varPowerDisch, 0.0); - return; - } - - // ========================= - // Entladen - // ========================= - if ($dischargeW > 0.0) { - - // Modus setzen (ein Register) - $setInt($varMode, $modeDisch); - - // GoodWe: Entladen nutzt trotzdem powerbat_laden (immer positiv) - if (strpos($t, "goodwe") !== false) { - $setW($varPowerCharge, (int)$dischargeW); - $setW($varPowerDisch, (int)0); - return; - } - - // SolarEdge + Default: Entladen in powerbat_entladen - $setW($varPowerDisch, $dischargeW); - $setW($varPowerCharge, 0.0); - return; - } - - // ========================= - // Stop / Neutral - // ========================= - $setW($varPowerCharge, 0.0); - $setW($varPowerDisch, 0.0); - // optional: Modus nicht anfassen oder auf 0 setzen: - // $setInt($varMode, 0); -} - - -} -?> diff --git a/Bat_EV_SDL_V3_Beta/README.md b/Bat_EV_SDL_V3_Beta/README.md deleted file mode 100644 index b059e3a..0000000 --- a/Bat_EV_SDL_V3_Beta/README.md +++ /dev/null @@ -1,67 +0,0 @@ -# Manager_1 -Beschreibung des Moduls. - -### Inhaltsverzeichnis - -1. [Funktionsumfang](#1-funktionsumfang) -2. [Voraussetzungen](#2-voraussetzungen) -3. [Software-Installation](#3-software-installation) -4. [Einrichten der Instanzen in IP-Symcon](#4-einrichten-der-instanzen-in-ip-symcon) -5. [Statusvariablen und Profile](#5-statusvariablen-und-profile) -6. [WebFront](#6-webfront) -7. [PHP-Befehlsreferenz](#7-php-befehlsreferenz) - -### 1. Funktionsumfang - -* - -### 2. Voraussetzungen - -- IP-Symcon ab Version 7.1 - -### 3. Software-Installation - -* Über den Module Store das 'Manager_1'-Modul installieren. -* Alternativ über das Module Control folgende URL hinzufügen - -### 4. Einrichten der Instanzen in IP-Symcon - - Unter 'Instanz hinzufügen' kann das 'Manager_1'-Modul mithilfe des Schnellfilters gefunden werden. - - Weitere Informationen zum Hinzufügen von Instanzen in der [Dokumentation der Instanzen](https://www.symcon.de/service/dokumentation/konzepte/instanzen/#Instanz_hinzufügen) - -__Konfigurationsseite__: - -Name | Beschreibung --------- | ------------------ - | - | - -### 5. Statusvariablen und Profile - -Die Statusvariablen/Kategorien werden automatisch angelegt. Das Löschen einzelner kann zu Fehlfunktionen führen. - -#### Statusvariablen - -Name | Typ | Beschreibung ------- | ------- | ------------ - | | - | | - -#### Profile - -Name | Typ ------- | ------- - | - | - -### 6. WebFront - -Die Funktionalität, die das Modul im WebFront bietet. - -### 7. PHP-Befehlsreferenz - -`boolean GEF_BeispielFunktion(integer $InstanzID);` -Erklärung der Funktion. - -Beispiel: -`GEF_BeispielFunktion(12345);` \ No newline at end of file diff --git a/Bat_EV_SDL_V3_Beta/form.json b/Bat_EV_SDL_V3_Beta/form.json deleted file mode 100644 index 7b134c9..0000000 --- a/Bat_EV_SDL_V3_Beta/form.json +++ /dev/null @@ -1,117 +0,0 @@ -{ - "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" - }, - { - "type":"List", - "name":"Batteries", - "caption":"Batterien mit Typ, Leistung und kapazität", - "add":true, - "delete":true, - "columns":[ - { - "caption":"Typ", - "name":"typ", - "width":"100px", - "add":"Stufe", - "edit":{ - "type":"ValidationTextBox" - } - }, - { - "caption":"Leistung in W", - "name":"powerbat", - "width":"150px", - "add":5000, - "edit":{ - "type":"NumberSpinner" - } - }, - { - "caption":"Kapazität in kWh", - "name":"capazity", - "width":"200px", - "add":60, - "edit":{ - "type":"NumberSpinner" - } - }, - { - "caption":"SoC", - "name":"soc", - "width":"200px", - "add":0, - "edit":{ - "type":"SelectVariable" - } - }, - { - "caption":"Batterieleistung Laden", - "name":"powerbat_laden", - "width":"200px", - "add":0, - "edit":{ - "type":"SelectVariable" - } - }, - { - "caption":"Batterieleistung Entladen", - "name":"powerbat_entladen", - "width":"200px", - "add":0, - "edit":{ - "type":"SelectVariable" - } - } - , - { - "caption": "Register Laden/Entladen Modus", - "name": "register_ladenentladen_modus", - "width": "260px", - "add": 0, - "edit": { - "type": "SelectVariable" - } - } - , - { - "caption": "Aktuelle Batterieleistung", - "name": "register_bat_power", - "width": "260px", - "add": 0, - "edit": { - "type": "SelectVariable" - } - } - - ] - }, - { - "type": "NumberSpinner", - "name": "SDL_Leistung_Laden", - "caption": "SDL_Leistung_Laden", - "suffix": "W" - }, - { - "type": "NumberSpinner", - "name": "SDL_Leistung_Entladen", - "caption": "SDL_Leistung_Entladen", - "suffix": "W" - }, - { - "type": "NumberSpinner", - "name": "UpdateInterval", - "caption": "Neuberechnung alle", - "suffix": "Sekunden", - "minimum": 0, - "maximum": 86400 - }, - { - "type": "CheckBox", - "name": "FilterAktiv", - "caption": "Aktuelle Leistung filtern" - } - ] -} diff --git a/Bat_EV_SDL_V3_Beta/module.json b/Bat_EV_SDL_V3_Beta/module.json deleted file mode 100644 index eb54ec3..0000000 --- a/Bat_EV_SDL_V3_Beta/module.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "id": "{9C3DCC91-FA9E-72E5-7FA6-7B483D1D114D}", - "name": "Bat_EV_SDL_V3_Beta", - "type": 3, - "vendor": "Belevo AG", - "aliases": [], - "parentRequirements": [], - "childRequirements": [], - "implemented": [], - "prefix": "GEF", - "url": "" -} \ No newline at end of file diff --git a/Bat_EV_SDL_V3_Beta/module.php b/Bat_EV_SDL_V3_Beta/module.php deleted file mode 100644 index 2c7c4e3..0000000 --- a/Bat_EV_SDL_V3_Beta/module.php +++ /dev/null @@ -1,1077 +0,0 @@ -RegisterPropertyString("Batteries", "[]"); - //$this->RegisterPropertyInteger("$upKWh + $underKWh)", 0); - $this->RegisterPropertyInteger("SDL_Leistung_Laden", 0); - $this->RegisterPropertyInteger("SDL_Leistung_Entladen", 0); // W - $this->RegisterPropertyInteger("UpdateInterval", 5); // Minuten - $this->RegisterPropertyBoolean("FilterAktiv", true); - // Status - $this->RegisterVariableBoolean("State", "Aktiv", "~Switch", 1); - $this->EnableAction("State"); - // Prozentwerte - $this->RegisterVariableFloat("SDL_Pos", "SDL Energie verfügbar (%)", "", 10); - $this->RegisterVariableFloat("SoC_EV", "EV Energie verfügbar (%)", "", 11); - // Variablen - $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); - $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 (Start)", "~Switch", 36); - $this->EnableAction("SDL_Reset"); - - // Debug - $this->RegisterVariableString("CalcJSON", "Berechnung (JSON)", "", 99); - // Timer: wichtig -> Prefix muss passen - $this->RegisterTimer("UpdateTimer", 0, 'GEF_Update($_IPS["TARGET"]);'); - } - public function ApplyChanges() - { - parent::ApplyChanges(); - $intervalSec = (int)$this->ReadPropertyInteger("UpdateInterval"); - $this->SetTimerInterval("UpdateTimer", ($intervalSec > 0) ? $intervalSec * 1000 : 0); - // Cache neu bauen (force) - $this->BuildBatteryCache(true); - - $this->Update(); - } - - public function RequestAction($Ident, $Value) - { - if ($Ident === "State") { - SetValue($this->GetIDForIdent("State"), (bool)$Value); - if ((bool)$Value) { - $this->Update(); - } - return; - } - - if ($Ident === "SDL_Reset") { - if ((bool)$Value) { - $this->ResetSDLToStartPoint(); // setzt Buffer auf Startzustand - SetValue($this->GetIDForIdent("SDL_Reset"), false); // Schalter automatisch zurück - $this->Update(); // optional: sofort Anzeige aktualisieren - } else { - SetValue($this->GetIDForIdent("SDL_Reset"), false); - } - return; - } - - throw new Exception("Invalid Ident: " . $Ident); - } - -public function Update() -{ - $semKey = 'BatEVSDL_Update_' . $this->InstanceID; - - if (!IPS_SemaphoreEnter($semKey, 5000)) { - $this->SendDebug("Update", "SKIP (Semaphore locked)", 0); - return; - } - - try { - if (!GetValue($this->GetIDForIdent("State"))) { - return; - } - - // Cache nur neu bauen, wenn nötig - $this->BuildBatteryCache(false); - - $cache = json_decode($this->GetBufferSafe("BatCacheJSON"), true); - if (!is_array($cache) || empty($cache["bats"])) { - $this->WriteAllZero("cache empty/invalid"); - return; - } - - // Zeitbasis - $now = microtime(true); - - $lastTs = (float)$this->GetBufferSafe("Int_LastTs"); - $Esdl_kWh = (float)$this->GetBufferSafe("Int_E_SDL_kWh"); - - if ($lastTs <= 0.0) { - $lastTs = $now; - } - - $dtSec = $now - $lastTs; - if ($dtSec < 0.0) $dtSec = 0.0; - if ($dtSec > 10.0) $dtSec = 10.0; - $dtH = $dtSec / 3600.0; - - // ============================ - // WICHTIG: Integrator nutzt NUR aktuelle SDL-Leistung - // ============================ - $pSdlIstW_raw = (float)GetValue($this->GetIDForIdent("Aktuelle_Leistung_SDL")); - $epsW = 1.0; - $sdlActive = (abs($pSdlIstW_raw) > $epsW); - - $calc = [ - "inputs" => $cache["inputs"] ?? [], - "batteries" => [], - "total" => [] - ]; - - // Summen - $sdlDisKW_ges = 0.0; - $evDisKW_ges = 0.0; - $sdlChKW_ges = 0.0; - $evChKW_ges = 0.0; - - $real_kWh_ev_ges = 0.0; - $real_kWh_sdl_ges = 0.0; - - $SDL_kWh_ges = 0.0; - $EV_kWh_ges = 0.0; - $totalCapKWh = 0.0; - - // CFG-Summen (Startpunkt / Anzeige) - $SDL_kWh_ges_cfg = 0.0; - $SDL_start_kWh_cfg = 0.0; - - foreach ($cache["bats"] as $bc) { - $SDL_kWh_ges_cfg += (float)($bc["SDL_kWh_total"] ?? 0.0); - $SDL_start_kWh_cfg += (float)($bc["underKWh"] ?? 0.0); - } - - $startPct = ($SDL_kWh_ges_cfg > 0.0) - ? ($SDL_start_kWh_cfg / $SDL_kWh_ges_cfg * 100.0) - : 0.0; - $startPct = max(0.0, min(100.0, $startPct)); - $this->SetIdentValue("SDL_Start_Pos", round($startPct, 3)); - - // Init SDL-Konto einmalig auf Startpunkt (untere Reserve) - if ($this->GetBufferSafe("Int_Init_SDL") !== "1") { - $Esdl_kWh = max(0.0, min($SDL_kWh_ges_cfg, $SDL_start_kWh_cfg)); - $this->SetBuffer("Int_Init_SDL", "1"); - } - - // Virtueller Stand in % - $sdlVirtPct = ($SDL_kWh_ges_cfg > 0.0) - ? ($Esdl_kWh / $SDL_kWh_ges_cfg * 100.0) - : 0.0; - $sdlVirtPct = max(0.0, min(100.0, $sdlVirtPct)); - - $sdlIsEmpty = ($sdlVirtPct <= 0.1); - $sdlIsFull = ($sdlVirtPct >= 99.9); - - // Flags für Grenz-Anker: - // (nur wenn SDL-Leistung tatsächlich Richtung Grenze fährt) - $sdlHitUpperBySDL = false; - $sdlHitLowerBySDL = false; - - // ============================ - // Battery Loop - // ============================ - foreach ($cache["bats"] as $i => $c) { - - $capKWh = (float)($c["capKWh"] ?? 0.0); - if ($capKWh <= 0.0) { - continue; - } - - // SoC lesen - $socVarId = (int)($c["socVarId"] ?? 0); - $socPct = $this->ReadSocPercent($socVarId); - $real_kWh = $capKWh / 100.0 * $socPct; - - // vorkalkuliert - $typ = (string)($c["typ"] ?? ("Bat " . ($i + 1))); - $underKWh = (float)($c["underKWh"] ?? 0.0); - $upKWh = (float)($c["upKWh"] ?? 0.0); - - - - // EV-Fenster (dynamisch erweitert bei SDL virt leer/voll) - $evUnderKWh = $underKWh; - $evUpKWh = $upKWh; - - if ($sdlIsEmpty) { - $evUnderKWh = 0.0; - } - if ($sdlIsFull) { - $evUpKWh = $capKWh; - } - - $evWindowKWh = max(0.0, $evUpKWh - $evUnderKWh); - - // Fensterkapazitäten / Limits aus Cache - $SDL_kWh = (float)($c["SDL_kWh_total"] ?? 0.0); // SDL-Fenster total (kWh) - $EV_kWh = (float)($c["EV_kWh_total"] ?? 0.0); - - $sdlShareKW_laden = (float)($c["sdlShareKW_laden"] ?? 0.0); - $sdlShareKW_entladen = (float)($c["sdlShareKW_entladen"] ?? 0.0); - $evShareKW_laden = (float)($c["evShareKW_laden"] ?? 0.0); - $evShareKW_entladen = (float)($c["evShareKW_entladen"] ?? 0.0); - - // Defaults - $EV_SOC = 0.0; - $SDL_SOC = 0.0; - - $sdlDisKW = 0.0; $evDisKW = 0.0; - $sdlChKW = 0.0; $evChKW = 0.0; - - $real_kWh_ev = 0.0; - $real_kWh_sdl = 0.0; - - // --- Fälle --- - if ($underKWh <= $real_kWh && $real_kWh <= $upKWh) { - - // EV_SOC im Fenster (0..100) - if ($evWindowKWh > 0.0) { - $EV_SOC = 100.0 * ($real_kWh - $evUnderKWh) / $evWindowKWh; - } else { - $EV_SOC = 0.0; - } - $EV_SOC = is_finite($EV_SOC) ? max(0.0, min(100.0, $EV_SOC)) : 0.0; - - // Leistungen - $sdlDisKW = $sdlShareKW_entladen; - $evDisKW = $evShareKW_entladen; - $sdlChKW = $sdlShareKW_laden; - $evChKW = $evShareKW_laden; - - // Energieaufteilung (EV im Fenster, Rest SDL) - $real_kWh_ev = max(0.0, min($evWindowKWh, $real_kWh - $evUnderKWh)); - $real_kWh_sdl = $real_kWh - $real_kWh_ev; - - } elseif ($real_kWh > $evUpKWh) { - - // oberhalb EV-Fenster - $EV_SOC = 100.0; - - $sdlDisKW = $sdlShareKW_entladen; - $evDisKW = $evShareKW_entladen; - $sdlChKW = $sdlShareKW_laden; - $evChKW = 0.0; // EV darf oben nicht laden - - $real_kWh_ev = $evWindowKWh; - $real_kWh_sdl = max(0.0, $real_kWh - $real_kWh_ev); - - } elseif ($real_kWh < $evUnderKWh) { - - // unterhalb EV-Fenster - $EV_SOC = 0.0; - - $sdlDisKW = $sdlShareKW_entladen; - $evDisKW = 0.0; // EV darf unten nicht entladen - $sdlChKW = $sdlShareKW_laden; - $evChKW = $evShareKW_laden; - - $real_kWh_ev = 0.0; - $real_kWh_sdl = $real_kWh; - - } else { - // Fallback - if ($evWindowKWh > 0.0) { - $EV_SOC = 100.0 * ($real_kWh - $evUnderKWh) / $evWindowKWh; - } - $EV_SOC = is_finite($EV_SOC) ? max(0.0, min(100.0, $EV_SOC)) : 0.0; - - $sdlDisKW = $sdlShareKW_entladen; - $evDisKW = $evShareKW_entladen; - $sdlChKW = $sdlShareKW_laden; - $evChKW = $evShareKW_laden; - - $real_kWh_ev = max(0.0, min($evWindowKWh, $real_kWh - $evUnderKWh)); - $real_kWh_sdl = $real_kWh - $real_kWh_ev; - } - - // Null/Full Schutz - if ($real_kWh <= 0.0) { - $sdlDisKW = 0.0; - $real_kWh_ev = 0.0; - $real_kWh_sdl = 0.0; - } elseif ($real_kWh >= $capKWh) { - $sdlChKW = 0.0; - $real_kWh_ev = $EV_kWh; - $real_kWh_sdl = $SDL_kWh; - } - - $real_kWh_ev = max(0.0, $real_kWh_ev); - $real_kWh_sdl = max(0.0, $real_kWh_sdl); - - // ========================================================== - // FIX #1: SDL_SOC als echter SDL-Füllstand (passt zur Int-Logik) - // ========================================================== - $SDL_SOC = ($SDL_kWh > 0.0) ? (100.0 * $real_kWh_sdl / $SDL_kWh) : 0.0; - $SDL_SOC = is_finite($SDL_SOC) ? max(0.0, min(100.0, $SDL_SOC)) : 0.0; - - // Summen - $totalCapKWh += $capKWh; - - $sdlDisKW_ges += $sdlDisKW; - $evDisKW_ges += $evDisKW; - $sdlChKW_ges += $sdlChKW; - $evChKW_ges += $evChKW; - - $real_kWh_ev_ges += $real_kWh_ev; - $real_kWh_sdl_ges += $real_kWh_sdl; - - $SDL_kWh_ges += $SDL_kWh; - $EV_kWh_ges += $EV_kWh; - - // JSON pro Batterie - $calc["batteries"][] = [ - "idx" => $c["idx"] ?? $i, - "typ" => $typ, - "SoC_varId" => $socVarId, - "SoC_pct" => round($socPct, 3), - "Effektive kWh" => round($real_kWh, 3), - - "EV_SOC" => round($EV_SOC, 3), - "SDL_SOC" => round($SDL_SOC, 3), - - "EV_kWh" => round($real_kWh_ev, 3), - "SDL_kWh" => round($real_kWh_sdl, 3), - - "under_grenze_kWh" => round($underKWh, 3), - "up_grenze_kWh" => round($upKWh, 3), - - "SDL_Charge_kW" => round($sdlChKW, 3), - "SDL_Discharge_kW" => round($sdlDisKW, 3), - "EV_Charge_kW" => round($evChKW, 3), - "EV_Discharge_kW" => round($evDisKW, 3), - ]; - } - - // EV Anzeige - $evPosPct = ($EV_kWh_ges > 0.0) ? ($real_kWh_ev_ges / $EV_kWh_ges * 100.0) : 0.0; - $evPosPct = max(0.0, min(100.0, $evPosPct)); - $this->SetIdentValue("SoC_EV", round($evPosPct, 3)); - - // Maximalleistungen - $maxSDL_ch = $sdlChKW_ges * 1000.0; - $maxSDL_dis = $sdlDisKW_ges * 1000.0; - - // CacheHash Change Reset - $cacheHash = (string)$this->GetBufferSafe("BatCacheHash"); - $intHash = (string)$this->GetBufferSafe("Int_CFG_HASH"); - - if ($intHash === "") { - $this->SetBuffer("Int_CFG_HASH", $cacheHash); - $intHash = $cacheHash; - } - - - - // ========================================================== - // FIX #2 (dein Wunsch): Integration NUR mit aktueller SDL-Leistung - // ========================================================== - $pSdlIstW = $pSdlIstW_raw; - - // clamp Istleistung auf erlaubte SDL-Leistung (robust) - $pSdlIstW = max(-$maxSDL_dis, min($maxSDL_ch, $pSdlIstW)); - - if (abs($pSdlIstW) > $epsW) { - $Esdl_kWh += (($pSdlIstW / 1000.0) * $dtH); - } - - // Clamp SDL-Konto - $Esdl_kWh = ($SDL_kWh_ges > 0.0) ? max(0.0, min($SDL_kWh_ges, $Esdl_kWh)) : 0.0; - - // SDL Anzeige - $sdlPosPct = ($SDL_kWh_ges > 0.0) ? ($Esdl_kWh / $SDL_kWh_ges * 100.0) : 0.0; - $sdlPosPct = max(0.0, min(100.0, $sdlPosPct)); - $this->SetIdentValue("SDL_Pos", round($sdlPosPct, 3)); - - // Maximalleistungen anzeigen - $this->SetIdentValue("P_SDL_laden", round($sdlChKW_ges * 1000.0, 0)); - $this->SetIdentValue("P_SDL_entladen", round($sdlDisKW_ges * 1000.0, 0)); - $this->SetIdentValue("P_EV_laden", (int)round($evChKW_ges * 1000.0, 0)); - $this->SetIdentValue("P_EV_entladen", (int)round($evDisKW_ges * 1000.0, 0)); - - // Total JSON (mit Debug für IST) - $calc["total"] = [ - "SDL_SoC_pct" => round($sdlPosPct, 3), - "EV_SoC_pct" => round($evPosPct, 3), - - "SDL_kWh_total" => round($SDL_kWh_ges, 3), - "EV_kWh_total" => round($EV_kWh_ges, 3), - - "SDL_Charge_kW" => round($sdlChKW_ges, 3), - "SDL_Discharge_kW" => round($sdlDisKW_ges, 3), - "EV_Charge_kW" => round($evChKW_ges, 3), - "EV_Discharge_kW" => round($evDisKW_ges, 3), - - "totalCap_kWh" => round($totalCapKWh, 3), - - "SDL_Start_pct" => round($startPct, 3), - "SDL_IsEmpty" => $sdlIsEmpty, - "SDL_IsFull" => $sdlIsFull, - "SDL_HitUpperBySDL" => $sdlHitUpperBySDL, - "SDL_HitLowerBySDL" => $sdlHitLowerBySDL, - - // Debug: - "SDL_Ist_W_raw" => round($pSdlIstW_raw, 0), - "SDL_Ist_W_used" => round($pSdlIstW, 0), - "dtSec" => round($dtSec, 3) - ]; - - $this->SetIdentValue("CalcJSON", json_encode($calc, JSON_PRETTY_PRINT)); - - // Setpoints wie gehabt - $this->ApplySetpoints(); - - // Integrator State speichern - $this->SetBuffer("Int_LastTs", (string)$now); - $this->SetBuffer("Int_E_SDL_kWh", (string)$Esdl_kWh); - - } catch (Throwable $e) { - $this->SendDebug("Update ERROR", $e->getMessage() . " @ " . $e->getFile() . ":" . $e->getLine(), 0); - $this->WriteAllZero("Exception: " . $e->getMessage()); - } finally { - IPS_SemaphoreLeave($semKey); - } -} - - - - - - private function BuildBatteryCache(bool $force): void - { - $batteriesRaw = $this->ReadPropertyString("Batteries"); - //$sdlTotalW = max(0, (int)$this->ReadPropertyInteger("SDL_Leistung")); - $sdlTotalW_laden = max(0, (int)$this->ReadPropertyInteger("SDL_Leistung_Laden")); - $sdlTotalW_entladen = max(0, (int)$this->ReadPropertyInteger("SDL_Leistung_Entladen")); - $hours = self::HOURS; - $hash = md5(json_encode([ - "Batteries" => $batteriesRaw, - "SDL_W_Laden" => $sdlTotalW_laden, - "SDL_W_Entladen" => $sdlTotalW_entladen, - "hours" => $hours - ])); - $oldHash = $this->GetBufferSafe("BatCacheHash"); - if (!$force && $oldHash === $hash) { - return; - } - $batteries = json_decode($batteriesRaw, true); - if (!is_array($batteries)) $batteries = []; - $sumBatPowerW = 0.0; - foreach ($batteries as $b) { - $p = (float)($b["powerbat"] ?? 0); - if ($p > 0) $sumBatPowerW += $p; - } - $cache = [ - "inputs" => [ - "SDL_Leistung_W_laden" => $sdlTotalW_laden, - "SDL_Leistung_W_entladen" => $sdlTotalW_entladen, - "SumBatPower_W" => round($sumBatPowerW, 0), - "hours" => $hours - ], - "bats" => [] - ]; - if ($sumBatPowerW <= 0.0) { - $this->SetBuffer("BatCacheHash", $hash); - $this->SetBuffer("BatCacheJSON", json_encode($cache)); - $this->SendDebug("Cache", "sumBatPowerW=0 -> empty cache", 0); - return; - } - $sumBatPowerkW = $sumBatPowerW / 1000.0; - //$sdlTotalkW = $sdlTotalW / 1000.0; - $sdlTotalkW_laden = $sdlTotalW_laden / 1000.0; - $sdlTotalkW_entladen = $sdlTotalW_entladen / 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) continue; - $socVarId = (int)($b["soc"] ?? 0); - $typ = (string)($b["typ"] ?? ("Bat " . ($idx + 1))); - - // Mit laden und entladen unterschiedlich - //---------------------------------------------------- - $sdlShareKW_laden = ($sumBatPowerkW > 0.0) ? ($sdlTotalkW_laden / $sumBatPowerkW * $pBatkW) : 0.0; - $evShareKW_laden = $pBatkW - $sdlShareKW_laden; - $upKWh = $capKWh - $sdlShareKW_laden * 0.5; - $sdlShareKW_entladen = ($sumBatPowerkW > 0.0) ? ($sdlTotalkW_entladen / $sumBatPowerkW * $pBatkW) : 0.0; - $evShareKW_entladen = $pBatkW - $sdlShareKW_entladen; - $underKWh = $sdlShareKW_entladen * 0.5; - $SDL_kWh = $underKWh + ($capKWh -$upKWh); - $EV_kWh = $capKWh - $SDL_kWh; - //---------------------------------------------------- - $cache["bats"][] = [ - "idx" => $idx, - "typ" => $typ, - "socVarId" => $socVarId, - "capKWh" => $capKWh, - "pBatW" => $pBatW, - "sdlShareKW_laden" => $sdlShareKW_laden, - "sdlShareKW_entladen" => $sdlShareKW_entladen, - "evShareKW_laden" => $evShareKW_laden, - "evShareKW_entladen" => $evShareKW_entladen, - "underKWh" => $underKWh, - "upKWh" => $upKWh, - "SDL_kWh_total" => $SDL_kWh, - "EV_kWh_total" => $EV_kWh - ]; - } - $this->SetBuffer("BatCacheHash", $hash); - $this->SetBuffer("BatCacheJSON", json_encode($cache)); - $this->SendDebug("Cache", "Battery cache rebuilt (" . count($cache["bats"]) . " bats)", 0); - } - - - - - private function GetBufferSafe(string $name): string - { - $v = $this->GetBuffer($name); - return is_string($v) ? $v : ""; - } - 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)); - } - private function SetIdentValue(string $ident, $value): void - { - $id = @$this->GetIDForIdent($ident); - if ($id <= 0) { - $this->SendDebug(__FUNCTION__, "Ident nicht gefunden: $ident", 0); - return; - } - SetValue($id, $value); - } - private function ReadSocPercent(int $varId): float - { - // Falls jemand statt Variable direkt 0..100 einträgt - 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; - } - $soc = (float)$v; - if ($soc < 0.0) $soc = 0.0; - if ($soc > 100.0) $soc = 100.0; - return $soc; - } - public function ApplySetpoints(): void - { - $pEvW = (float) GetValue($this->GetIDForIdent("Nennleistung_Soll_EV")); - $pSdlW = (float) GetValue($this->GetIDForIdent("Nennleistung_Soll_SDL")); - //Diverenz zwischen EV und SDl berechnen - - // Methode für priorisierung - $distribution = $this->CalculateBatteryDistribution($pEvW,$pSdlW); - - $this->WriteBatteryPowerSetpoints($distribution); - } - - - -private function CalculateBatteryDistribution(float $pEvW, float $pSdlW): array -{ - $calcJsonId = $this->GetIDForIdent("CalcJSON"); - // Fallback, falls Variable leer ist oder nicht existiert - if (!IPS_VariableExists($calcJsonId)) { - return []; - } - $rawJson = (string)GetValue($calcJsonId); - if (empty($rawJson)) { - return []; - } - $calc = json_decode($rawJson, true); - $batteries = $calc['batteries'] ?? []; - // --------------------------------------------------------- - // Hilfsfunktion: Verteilungslogik (Closure) - // --------------------------------------------------------- - $distributePower = function(float $targetPower, string $mode) use ($batteries): array { - // Initialisierung des Ergebnis-Arrays (Key = idx, Value = zugewiesene Watt) - $result = []; - foreach ($batteries as $bat) { - $result[$bat['idx']] = 0.0; - } - if (abs($targetPower) < 0.01) { - return $result; // Nichts zu tun - } - $isCharge = ($targetPower > 0); - $absPower = abs($targetPower); - // Relevante Keys basierend auf Modus (EV oder SDL) und Richtung (Laden/Entladen) - $socKey = ($mode === 'EV') ? 'EV_SOC' : 'SDL_SOC'; - // Achtung: JSON Limits sind in kW, wir rechnen in Watt -> * 1000 - $limitKey = $isCharge ? $mode . '_Charge_kW' : $mode . '_Discharge_kW'; - // 1. Batterien vorbereiten und gruppieren nach gerundetem SoC - $groups = []; - foreach ($batteries as $bat) { - $soc = (int)round($bat[$socKey]); // Auf ganze Zahl runden - $maxW = ((float)$bat[$limitKey]) * 1000.0; // kW in Watt umrechnen - $groups[$soc][] = [ - 'idx' => $bat['idx'], - 'maxW' => $maxW - ]; - } - // 2. Sortieren der Gruppen - // Laden: Wenig SoC zuerst (ASC) -> leere füllen - // Entladen: Viel SoC zuerst (DESC) -> volle leeren - if ($isCharge) { - ksort($groups); - } else { - krsort($groups); - } - // 3. Verteilung - $remainingNeeded = $absPower; - foreach ($groups as $soc => $groupBatteries) { - if ($remainingNeeded <= 0.01) break; - // Gesamte verfügbare Leistung in dieser SoC-Gruppe ermitteln - $groupTotalCapacity = 0.0; - foreach ($groupBatteries as $gb) { - $groupTotalCapacity += $gb['maxW']; - } - // Wie viel können wir dieser Gruppe zuteilen? - // Entweder alles was die Gruppe kann, oder den Restbedarf - $powerForThisGroup = min($remainingNeeded, $groupTotalCapacity); - // Proportionale Aufteilung innerhalb der Gruppe - // Falls Gruppe Kapazität 0 hat (Defekt/Voll), verhindern wir DivByZero - if ($groupTotalCapacity > 0) { - $ratio = $powerForThisGroup / $groupTotalCapacity; - foreach ($groupBatteries as $gb) { - $assigned = $gb['maxW'] * $ratio; - $result[$gb['idx']] = $assigned; - } - } - $remainingNeeded -= $powerForThisGroup; - } - // Wenn wir entladen, müssen die Werte negativ sein - if (!$isCharge) { - foreach ($result as $idx => $val) { - $result[$idx] = -$val; - } - } - return $result; - }; - // --------------------------------------------------------- - // Hauptablauf - // --------------------------------------------------------- - // 1. Berechnung für EV und SDL getrennt durchführen - $evDistribution = $distributePower($pEvW, 'EV'); - $sdlDistribution = $distributePower($pSdlW, 'SDL'); - // 2. Ergebnisse zusammenführen und Output formatieren - $finalOutput = []; - foreach ($batteries as $bat) { - $idx = $bat['idx']; - // Summe der beiden Anforderungen (kann sich gegenseitig aufheben) - $valEv = $evDistribution[$idx] ?? 0.0; - $valSdl = $sdlDistribution[$idx] ?? 0.0; - $totalW = $valEv + $valSdl; - // Aufteilen in Charge / Discharge für das Return-Format - $chargeW = 0.0; - $dischargeW = 0.0; - if ($totalW > 0) { - $chargeW = abs($totalW); - } else { - $dischargeW = abs($totalW); - } - // JSON Objekt erstellen - $finalOutput[] = [ - "idx" => $idx, - "typ" => (string)$bat['typ'], // Typ als String beibehalten - "chargeW" => round($chargeW, 0), // Optional: runden für sauberes JSON - "dischargeW" => round($dischargeW, 0) - ]; - } - $batteriesRaw = json_decode($this->ReadPropertyString("Batteries")); - $totalPower_ist = 0; - foreach ($batteriesRaw as $bat) { - $totalPower_ist += (-1)*GetValue($bat->register_bat_power); - } - // ---------------------------- - // ALT (nicht mehr gebraucht) -> AUSKOMMENTIERT - // ---------------------------- - /* - $sumReq = (abs($pEvW) + abs($pSdlW)); - $sumReqRel = ($pEvW + $pSdlW); - if($sumReq==0){ - $this->SetValue("Aktuelle_Leistung_EV", $totalPower_ist / 2); - $this->SetValue("Aktuelle_Leistung_SDL", $totalPower_ist / 2); - }else{ - if($pEvW>=0){ - $this->SetValue("Aktuelle_Leistung_EV",((1+($totalPower_ist-$sumReqRel) / $sumReq)) * $pEvW); - }else{ - $this->SetValue("Aktuelle_Leistung_EV",((1-($totalPower_ist-$sumReqRel) / $sumReq)) * $pEvW); - } - if($pSdlW>=0){ - $this->SetValue("Aktuelle_Leistung_SDL",((1+($totalPower_ist-$sumReqRel) / $sumReq)) * $pSdlW); - }else{ - $this->SetValue("Aktuelle_Leistung_SDL",((1-($totalPower_ist-$sumReqRel) / $sumReq)) * $pSdlW); - } - } - */ - // ---------------------------- - // NEU (genau deine gewünschte Logik) - // aktuelle - EV_SOLL = aktuelle SDL - // aktuelle - SDL_SOLL = aktuelle EV - // ---------------------------- - - - - /* - $eps = 0.01; - $aktEV = 0.0; - $aktSDL = 0.0; - // Optional: Wenn einer 0 ist -> aktiver Kanal bekommt totalPower_ist - if (abs($pEvW) < $eps && abs($pSdlW) < $eps) { - $aktEV = 0.0; - $aktSDL = 0.0; - } elseif (abs($pEvW) < $eps) { - $aktEV = 0.0; - $aktSDL = $totalPower_ist; - } elseif (abs($pSdlW) < $eps) { - $aktEV = $totalPower_ist; - $aktSDL = 0.0; - } else { - // Beide aktiv - $sameDirection = (($pEvW > 0 && $pSdlW > 0) || ($pEvW < 0 && $pSdlW < 0)); - if ($sameDirection) { - // Regel A: gleiche Richtung -> Ist proportional nach |Soll| aufteilen - $sumAbs = abs($pEvW) + abs($pSdlW); - if ($sumAbs < $eps) { - // Fallback - $aktEV = $totalPower_ist / 2.0; - $aktSDL = $totalPower_ist / 2.0; - } else { - $aktEV = $totalPower_ist * (abs($pEvW) / $sumAbs); - $aktSDL = $totalPower_ist - $aktEV; // Summe bleibt exakt totalPower_ist - } - } else { - // Regel B: gegensätzliche Richtung -> Sollwerte anzeigen - $aktEV = $pEvW; - $aktSDL = $pSdlW; - } - } - $this->SetValue("Aktuelle_Leistung_EV", (float)$aktEV); - $this->SetValue("Aktuelle_Leistung_SDL", (float)$aktSDL); - */ - $eps = 0.01; - $sumSoll = (float)($pEvW + $pSdlW); - if (abs($sumSoll) > $eps) { - // Normalfall: wir skalieren so, dass EV+SDL genau totalPower_ist ergibt - $factor = (float)($totalPower_ist / $sumSoll); - $aktEV = (float)($pEvW * $factor); - $aktSDL = (float)($pSdlW * $factor); - } else { - // Sonderfall: Soll hebt sich nahezu auf (sumSoll ~ 0) - // -> wenn Ist ebenfalls ~0: Soll anzeigen - if (abs($totalPower_ist) < 50.0) { // 50W Toleranz kannst du anpassen - $aktEV = (float)$pEvW; - $aktSDL = (float)$pSdlW; - } else { - // Ist ist nicht 0 obwohl Sollsumme 0 -> Abweichung zuweisen (z.B. SDL) - $aktEV = (float)$pEvW; - $aktSDL = (float)($totalPower_ist - $aktEV); - // Alternative: Abweichung EV geben: - // $aktSDL = (float)$pSdlW; - // $aktEV = (float)($totalPower_ist - $aktSDL); - } - } - - $filterCurrent = function(string $ch, float $raw, float $target, float $tolW, int $needHits): float { - - // Letzter akzeptierter Wert - $lastVal = (float)$this->GetBufferSafe("CUR_{$ch}_VAL"); - // Letztes Ziel (Soll), auf das wir "warten" - $pending = (float)$this->GetBufferSafe("CUR_{$ch}_PEND"); - // Counter wie oft raw im Fenster war - $hits = (int)$this->GetBufferSafe("CUR_{$ch}_HITS"); - - // Init falls leer - if ($this->GetBufferSafe("CUR_{$ch}_INIT") !== "1") { - $lastVal = $raw; // Start mit aktuellem Rohwert - $pending = $target; // erstes Ziel - $hits = 0; - $this->SetBuffer("CUR_{$ch}_INIT", "1"); - $this->SetBuffer("CUR_{$ch}_VAL", (string)$lastVal); - $this->SetBuffer("CUR_{$ch}_PEND", (string)$pending); - $this->SetBuffer("CUR_{$ch}_HITS", (string)$hits); - } - - // Wenn Soll (target) sich ändert -> neuen Pending setzen und Hits reset - // (kleine Toleranz, damit Fließkomma nicht nervt) - if (abs($target - $pending) > 0.5) { - $pending = $target; - $hits = 0; - $this->SetBuffer("CUR_{$ch}_PEND", (string)$pending); - $this->SetBuffer("CUR_{$ch}_HITS", (string)$hits); - // lastVal bleibt stehen bis raw "passt" - } - - // Sonderfall: target ~ 0 -> sofort auf 0 ziehen (optional) - if (abs($pending) < 0.5) { - $lastVal = 0.0; - $hits = 0; - $this->SetBuffer("CUR_{$ch}_VAL", (string)$lastVal); - $this->SetBuffer("CUR_{$ch}_HITS", (string)$hits); - return $lastVal; - } - - // Optional: Vorzeichen passend zum Ziel erzwingen (hilft bei “-2000, -3000, +3000, +4900”) - // Wenn Ziel positiv ist, ignoriere negative raw komplett (und umgekehrt) - if (($pending > 0 && $raw < 0) || ($pending < 0 && $raw > 0)) { - $hits = 0; - $this->SetBuffer("CUR_{$ch}_HITS", (string)$hits); - return $lastVal; - } - - // Prüfen ob raw im Fenster um pending liegt - if (abs($raw - $pending) <= $tolW) { - $hits++; - $this->SetBuffer("CUR_{$ch}_HITS", (string)$hits); - - // Erst nach N Treffern übernehmen (Debounce) - if ($hits >= $needHits) { - $lastVal = $raw; // oder: $pending, wenn du exakt das Ziel schreiben willst - $hits = 0; - $this->SetBuffer("CUR_{$ch}_VAL", (string)$lastVal); - $this->SetBuffer("CUR_{$ch}_HITS", (string)$hits); - } - } else { - // außerhalb -> Hits zurücksetzen - if ($hits !== 0) { - $hits = 0; - $this->SetBuffer("CUR_{$ch}_HITS", (string)$hits); - } - } - - return $lastVal; - }; - - - - - - $rawEV = (float)$aktEV; - $rawSDL = (float)$aktSDL; - - // Ziel = Sollwerte (das ist das, was du in deiner Beschreibung meinst) - $targetEV = (float)$pEvW; - $targetSDL = (float)$pSdlW; - - // Parameter: Toleranzband + Anzahl Treffer - // Beispiel: 300W Band, 2 Treffer (bei 2s Update => ~4s stabil) - - $tolPct = 0.10; // 10% - $needHits = 1; // 2 Treffer hintereinander (oder 1 für sofort) - - $tolW_EV = abs($targetEV) * $tolPct; - $tolW_SDL = abs($targetSDL) * $tolPct; - - $filterAktiv = $this->ReadPropertyBoolean("FilterAktiv"); - - if ($filterAktiv) { - $fEV = $filterCurrent("EV", $rawEV, $targetEV, $tolW_EV, $needHits); - $fSDL = $filterCurrent("SDL", $rawSDL, $targetSDL, $tolW_SDL, $needHits); - } else { - // Filter deaktiviert: Rohwerte direkt übernehmen - $fEV = $rawEV; - $fSDL = $rawSDL; - - // Optional: alte Filter-Buffer zurücksetzen - $this->SetBuffer("CUR_EV_INIT", ""); - $this->SetBuffer("CUR_EV_VAL", ""); - $this->SetBuffer("CUR_EV_PEND", ""); - $this->SetBuffer("CUR_EV_HITS", ""); - - $this->SetBuffer("CUR_SDL_INIT", ""); - $this->SetBuffer("CUR_SDL_VAL", ""); - $this->SetBuffer("CUR_SDL_PEND", ""); - $this->SetBuffer("CUR_SDL_HITS", ""); - } - - $this->SetIdentValue("Aktuelle_Leistung_EV", (float)$fEV); - $this->SetIdentValue("Aktuelle_Leistung_SDL", (float)$fSDL); - - - - - return $finalOutput; -} - - private function WriteBatteryPowerSetpoints(array $distribution): void - { - IPS_LogMessage( - __FUNCTION__, - "distribution=" . json_encode($distribution, JSON_PRETTY_PRINT) - ); - $batteriesCfg = json_decode($this->ReadPropertyString("Batteries"), true); - if (!is_array($batteriesCfg) || empty($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 " . ($idx + 1)))); - // ✅ immer positiv - $chargeW = max(0.0, (float)($d["chargeW"] ?? 0.0)); - $dischargeW = max(0.0, (float)($d["dischargeW"] ?? 0.0)); - // nie gleichzeitig - if ($chargeW > 0.0 && $dischargeW > 0.0) { - $this->SendDebug("WriteBatteryPowerSetpoints", "WARN: both >0 for $typ (idx=$idx) -> set 0", 0); - $chargeW = 0.0; - $dischargeW = 0.0; - } - $this->WriteByVendorRegistersSingleMode($typ, $cfg, $chargeW, $dischargeW); - $this->SendDebug("Setpoints", "$typ (idx=$idx) charge={$chargeW}W discharge={$dischargeW}W", 0); - } - } -private function WriteByVendorRegistersSingleMode(string $typ, array $cfg, float $chargeW, float $dischargeW): void -{ - $t = mb_strtolower($typ); - // Leistungs-Variablen - $varPowerCharge = (int)($cfg["powerbat_laden"] ?? 0); - $varPowerDisch = (int)($cfg["powerbat_entladen"] ?? 0); - // ✅ EIN Modus-Register - $varMode = (int)($cfg["register_ladenentladen_modus"] ?? 0); - // sichere Writer - $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)); // ✅ niemals negativ - } - }; - // Moduscodes je Typ - $modeCharge = 1; $modeDisch = 2; // Default - if (strpos($t, "goodwe") !== false) { - $modeCharge = 11; $modeDisch = 12; - } elseif (strpos($t, "solaredge") !== false) { - $modeCharge = 3; $modeDisch = 4; - } - // ========================= - // Laden - // ========================= - if ($chargeW > 0.0) { - // Modus setzen (ein Register) - $setInt($varMode, $modeCharge); - // GoodWe: nur powerbat_laden - if (strpos($t, "goodwe") !== false) { - $setW($varPowerCharge, (int)$chargeW); - $setW($varPowerDisch, (int)0); - return; - } - // SolarEdge + Default: zwei Leistungsregister - $setW($varPowerCharge, $chargeW); - $setW($varPowerDisch, 0.0); - return; - } - // ========================= - // Entladen - // ========================= - if ($dischargeW > 0.0) { - // Modus setzen (ein Register) - $setInt($varMode, $modeDisch); - // GoodWe: Entladen nutzt trotzdem powerbat_laden (immer positiv) - if (strpos($t, "goodwe") !== false) { - $setW($varPowerCharge, (int)$dischargeW); - $setW($varPowerDisch, (int)0); - return; - } - // SolarEdge + Default: Entladen in powerbat_entladen - $setW($varPowerDisch, $dischargeW); - $setW($varPowerCharge, 0.0); - return; - } - // ========================= - // Stop / Neutral - // ========================= - $setW($varPowerCharge, 0.0); - $setW($varPowerDisch, 0.0); - // optional: Modus nicht anfassen oder auf 0 setzen: - // $setInt($varMode, 0); -} - - -private function ResetSDLToStartPoint(): void -{ - // Cache sicherstellen (damit underKWh/SDL_kWh_total aktuell sind) - $this->BuildBatteryCache(true); - - $cache = json_decode($this->GetBufferSafe("BatCacheJSON"), true); - if (!is_array($cache) || empty($cache["bats"])) { - $this->SendDebug("SDL_Reset", "Cache leer/invalid", 0); - return; - } - - $sdlKWhTotal = 0.0; - $startKWh = 0.0; - - foreach ($cache["bats"] as $bat) { - $sdlKWhTotal += (float)($bat["SDL_kWh_total"] ?? 0.0); - $startKWh += (float)($bat["underKWh"] ?? 0.0); - } - - if ($sdlKWhTotal <= 0.0) { - $this->SendDebug("SDL_Reset", "SDL_kWh_total = 0", 0); - return; - } - - // Startzustand = Summe underKWh (bei symmetrisch => exakt 50%) - $startKWh = max(0.0, min($sdlKWhTotal, $startKWh)); - - $this->SetBuffer("Int_E_SDL_kWh", (string)$startKWh); - $this->SetBuffer("Int_Init_SDL", "1"); - $this->SetBuffer("Int_LastTs", (string)microtime(true)); - - // Hash merken, damit dein "ApplyChanges-Resync" nicht direkt wieder reinfunkt - $cacheHash = (string)$this->GetBufferSafe("BatCacheHash"); - if ($cacheHash !== "") { - $this->SetBuffer("Int_CFG_HASH", $cacheHash); - } - - // Optional: direkt Debug ausgeben - $startPct = ($sdlKWhTotal > 0.0) ? ($startKWh / $sdlKWhTotal * 100.0) : 0.0; - $this->SendDebug("SDL_Reset", "StartKWh=" . round($startKWh,3) . " (" . round($startPct,3) . "%)", 0); -} - - - -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) { - // Key muss in deiner Batteries-Konfig existieren: - $varId = (int)($b["register_bat_power"] ?? 0); - if ($varId > 0 && IPS_VariableExists($varId)) { - $raw = (float)GetValue($varId); - - // Vorzeichen wie bei dir: totalPower_ist += (-1)*GetValue(...) - $sum += (-1.0) * $raw; - } - } - return $sum; -} - -} -?>