From 6e9835d8529b627fcfc017bd2dc238093b21fcf8 Mon Sep 17 00:00:00 2001 From: "belevo\\mh" Date: Tue, 27 Jan 2026 07:42:49 +0100 Subject: [PATCH] no message --- Bat_EV_SDL _V2/README.md | 67 ++++++ Bat_EV_SDL _V2/form.json | 82 +++++++ Bat_EV_SDL _V2/module.json | 12 ++ Bat_EV_SDL _V2/module.php | 431 +++++++++++++++++++++++++++++++++++++ Bat_EV_SDL/module.php | 2 + 5 files changed, 594 insertions(+) create mode 100644 Bat_EV_SDL _V2/README.md create mode 100644 Bat_EV_SDL _V2/form.json create mode 100644 Bat_EV_SDL _V2/module.json create mode 100644 Bat_EV_SDL _V2/module.php diff --git a/Bat_EV_SDL _V2/README.md b/Bat_EV_SDL _V2/README.md new file mode 100644 index 0000000..b059e3a --- /dev/null +++ b/Bat_EV_SDL _V2/README.md @@ -0,0 +1,67 @@ +# 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 _V2/form.json b/Bat_EV_SDL _V2/form.json new file mode 100644 index 0000000..414e7b6 --- /dev/null +++ b/Bat_EV_SDL _V2/form.json @@ -0,0 +1,82 @@ +{ + "elements": [ + { + "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":"200px", + "add":5000, + "edit":{ + "type":"NumberSpinner" + } + }, + { + "caption":"Kapazität in kWh", + "name":"capazity", + "width":"100px", + "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" + } + } + + ] + }, + { + "type": "NumberSpinner", + "name": "SDL_Leistung", + "caption": "SDL_Leistung", + "suffix": "W" + }, + { + "type": "NumberSpinner", + "name": "UpdateInterval", + "caption": "Neuberechnung alle", + "suffix": "Minuten", + "minimum": 0, + "maximum": 1440 + } + ] +} diff --git a/Bat_EV_SDL _V2/module.json b/Bat_EV_SDL _V2/module.json new file mode 100644 index 0000000..a583f6c --- /dev/null +++ b/Bat_EV_SDL _V2/module.json @@ -0,0 +1,12 @@ +{ + "id": "{843EB84A-5180-6B47-A72E-9CDBE59DD9D3}", + "name": "Bat_EV_SDL _V2", + "type": 3, + "vendor": "Belevo AG", + "aliases": [], + "parentRequirements": [], + "childRequirements": [], + "implemented": [], + "prefix": "GEF", + "url": "" +} \ No newline at end of file diff --git a/Bat_EV_SDL _V2/module.php b/Bat_EV_SDL _V2/module.php new file mode 100644 index 0000000..5eba5bf --- /dev/null +++ b/Bat_EV_SDL _V2/module.php @@ -0,0 +1,431 @@ +RegisterPropertyString("Batteries", "[]"); + $this->RegisterPropertyInteger("SDL_Leistung", 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); + + // Leistungsgrenzen + $this->RegisterVariableFloat("Nennleistung_Soll_EV", "Nennleistung Soll EV", "", 2); + $this->RegisterVariableFloat("Nennleistung_Soll_SDL", "Nennleistung Soll SDL", "", 3); + $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); + + // Cache Buffer + $this->RegisterBuffer("BatCacheHash", ""); + $this->RegisterBuffer("BatCacheJSON", "[]"); + + // Timer + $this->RegisterTimer("UpdateTimer", 0, 'GEF_Update($_IPS["TARGET"]);'); + } + + public function ApplyChanges() + { + parent::ApplyChanges(); + + $intervalMin = (int)$this->ReadPropertyInteger("UpdateInterval"); + $this->SetTimerInterval("UpdateTimer", ($intervalMin > 0) ? $intervalMin * 60 * 1000 : 0); + + // Cache neu bauen wenn Properties geändert wurden + $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() + { + try { + + if (!GetValue($this->GetIDForIdent("State"))) { + return; + } + + // Cache nur neu bauen, wenn nötig + $this->BuildBatteryCache(false); + + $cache = json_decode($this->GetBuffer("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 = (float)($c["sdlShareKW"] ?? 0.0); + $evShareKW = (float)($c["evShareKW"] ?? 0.0); + + // Defaults pro Batterie + $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) { + + $SDL_SOC = 50.0; + $EV_SOC = ($capKWh > 0.0) ? (($real_kWh - $underKWh) * 100.0 / $capKWh) : 0.0; + + $sdlDisKW = $sdlShareKW; + $evDisKW = $evShareKW; + $sdlChKW = $sdlShareKW; + $evChKW = $evShareKW; + + $real_kWh_ev = $real_kWh - $underKWh; + $real_kWh_sdl = $underKWh; + + } elseif ($upKWh < $real_kWh) { + + $EV_SOC = 100.0; + + $den = ($capKWh - $real_kWh + $underKWh); + $SDL_SOC = ($den > 0.0 && $underKWh > 0.0) ? ($den / (2.0 * $underKWh) * 100.0) : 0.0; + + $sdlDisKW = $sdlShareKW; + $evDisKW = $evShareKW; + $sdlChKW = $sdlShareKW; + $evChKW = 0.0; + + $real_kWh_ev = $capKWh - 2.0 * $underKWh; + $real_kWh_sdl = (2.0 * $underKWh) - ($capKWh - $real_kWh); + + } elseif ($underKWh > $real_kWh) { + + $EV_SOC = 0.0; + + $den = 2.0 * $underKWh; + $SDL_SOC = ($den > 0.0) ? ($real_kWh * 100.0 / $den) : 0.0; + + $sdlDisKW = $sdlShareKW; + $evDisKW = 0.0; + $sdlChKW = $sdlShareKW; + $evChKW = $evShareKW; + + $real_kWh_ev = 0.0; + $real_kWh_sdl = $real_kWh; + } + + // Null/Full Abdeckung (wie vorher) + 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 - 2.0 * $underKWh; + $real_kWh_sdl = 2.0 * $underKWh; + } + + // Negative verhindern + $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; + + // Debug 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), + + "SDL_Power_kW" => round($sdlShareKW, 3), + "EV_Power_kW" => round($evShareKW, 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), + ]; + } + + // Prozent gesamt + $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; + + // Werte setzen + $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)); + + } catch (Throwable $e) { + $this->SendDebug("Update ERROR", $e->getMessage() . " @ " . $e->getFile() . ":" . $e->getLine(), 0); + $this->WriteAllZero("Exception: " . $e->getMessage()); + } + } + + /** + * Baut Cache nur neu, wenn sich Inputs geändert haben. + * Cache enthält alles, was NICHT vom SoC abhängt. + */ + private function BuildBatteryCache(bool $force): void + { + $batteriesRaw = $this->ReadPropertyString("Batteries"); + $sdlTotalW = max(0, (int)$this->ReadPropertyInteger("SDL_Leistung")); + $hours = self::HOURS; + + $hash = md5(json_encode([ + "Batteries" => $batteriesRaw, + "SDL_W" => $sdlTotalW, + "hours" => $hours + ])); + + $oldHash = $this->GetBuffer("BatCacheHash"); + if (!$force && $oldHash === $hash) { + return; // nix zu tun + } + + $batteries = json_decode($batteriesRaw, true); + if (!is_array($batteries)) { + $batteries = []; + } + + // Summe Batterie-Maxleistungen + $sumBatPowerW = 0.0; + foreach ($batteries as $b) { + $p = (float)($b["powerbat"] ?? 0); + if ($p > 0) $sumBatPowerW += $p; + } + + // Cache Grundstruktur + $cache = [ + "inputs" => [ + "SDL_Leistung_W" => $sdlTotalW, + "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; + + 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))); + + // Anteil SDL/EV über Leistungsanteil (wie vorher) + $sdlShareKW = ($sumBatPowerkW > 0.0) ? ($sdlTotalkW / $sumBatPowerkW * $pBatkW) : 0.0; + $evShareKW = $pBatkW - $sdlShareKW; + + // untere Grenze + $underKWh = $sdlShareKW * $hours; + $underKWh = max(0.0, min($underKWh, $capKWh / 2.0)); + + $upKWh = $capKWh - $underKWh; + $SDL_kWh = 2.0 * $underKWh; + $EV_kWh = max(0.0, $capKWh - $SDL_kWh); + + $cache["bats"][] = [ + "idx" => $idx, + "typ" => $typ, + + "socVarId" => $socVarId, + "capKWh" => $capKWh, + + "pBatW" => $pBatW, + "sdlShareKW" => $sdlShareKW, + "evShareKW" => $evShareKW, + + "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); + } + + // ---------------- Helpers ---------------- + + 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; + } +} + +?> diff --git a/Bat_EV_SDL/module.php b/Bat_EV_SDL/module.php index 788fe5a..f94c6d6 100644 --- a/Bat_EV_SDL/module.php +++ b/Bat_EV_SDL/module.php @@ -20,6 +20,8 @@ class Bat_EV_SDL extends IPSModule $this->RegisterVariableFloat("SoC_EV", "EV Energie verfügbar (%)", "", 11); // Leistungsgrenzen + $this->RegisterVariableFloat("Nennleistung_Soll_EV", "Nennleistung Soll EV", "", 2); + $this->RegisterVariableFloat("Nennleistung_Soll_SDL", "Nennleistung Soll EV", "", 3); $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);