From 4535be0f2ca62d147c5720c7073780c8cde75bab Mon Sep 17 00:00:00 2001 From: "belevo\\mh" Date: Thu, 22 Jan 2026 14:11:53 +0100 Subject: [PATCH] no message --- Bat_EV_SDL/README.md | 67 ++++++++++ Bat_EV_SDL/fom.json | 56 ++++++++ Bat_EV_SDL/module.json | 12 ++ Bat_EV_SDL/module.php | 285 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 420 insertions(+) create mode 100644 Bat_EV_SDL/README.md create mode 100644 Bat_EV_SDL/fom.json create mode 100644 Bat_EV_SDL/module.json create mode 100644 Bat_EV_SDL/module.php diff --git a/Bat_EV_SDL/README.md b/Bat_EV_SDL/README.md new file mode 100644 index 0000000..b059e3a --- /dev/null +++ b/Bat_EV_SDL/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/fom.json b/Bat_EV_SDL/fom.json new file mode 100644 index 0000000..6828511 --- /dev/null +++ b/Bat_EV_SDL/fom.json @@ -0,0 +1,56 @@ + { + "elements": [ + { + "type":"List", + "name":"Batteries", + "caption":"Batterien mit Typ, Leistung und kapazität", + "add":true, + "delete":true, + "columns":[ + { + "caption":"Typ", + "name":"typ", + "width":"200px", + "add":"Stufe", + "edit":{ + "type":"ValidationTextBox" + } + }, + { + "caption":"Leistung in W", + "name":"powerbat", + "width":"300px", + "add":5000, + "edit":{ + "type":"NumberSpinner" + } + }, + { + "caption":"Kapazität in kWh", + "name":"capazity", + "width":"400px", + "add":60, + "edit":{ + "type":"NumberSpinner" + } + }, + { + "caption":"SoC", + "name":"soc", + "width":"400px", + "add":0, + "edit":{ + "type":"SelectVariable" + } + } + + ] + }, + { + "type": "NumberSpinner", + "name": "SDL_Leistung", + "caption": "SDL_Leistung", + "suffix": "W" + } + ] +} diff --git a/Bat_EV_SDL/module.json b/Bat_EV_SDL/module.json new file mode 100644 index 0000000..6683bc7 --- /dev/null +++ b/Bat_EV_SDL/module.json @@ -0,0 +1,12 @@ +{ + "id": "{5C7A4F8D-B141-1E3A-0D7B-BB187550B220}", + "name": "Bat_EV_SDL", + "type": 3, + "vendor": "Belevo AG", + "aliases": [], + "parentRequirements": [], + "childRequirements": [], + "implemented": [], + "prefix": "GEF", + "url": "" +} \ No newline at end of file diff --git a/Bat_EV_SDL/module.php b/Bat_EV_SDL/module.php new file mode 100644 index 0000000..5cd6ac6 --- /dev/null +++ b/Bat_EV_SDL/module.php @@ -0,0 +1,285 @@ +RegisterPropertyString("Batteries", "[]"); // List JSON + $this->RegisterPropertyInteger("SDL_Leistung", 0); // W + + // ---- Status / Steuerung ---- + $this->RegisterVariableBoolean("State", "Aktiv", "~Switch", 1); + $this->EnableAction("State"); + + // ---- Ergebnisse (Gesamt) ---- + $this->RegisterVariableFloat("kWh_SDL", "Energie SDL (kWh)", "", 10); + $this->RegisterVariableFloat("kWh_EV", "Energie EV (kWh)", "", 11); + + $this->RegisterVariableFloat("SoC_SDL", "SoC SDL (%)", "", 12); + $this->RegisterVariableFloat("SoC_EV", "SoC EV (%)", "", 13); + + $this->RegisterVariableFloat("P_SDL_max", "P SDL max (W)", "", 20); + $this->RegisterVariableFloat("P_EV_max", "P EV max (W)", "", 21); + + // Optional: Voller JSON-Dump zum Debuggen/Weiterverarbeiten + $this->RegisterVariableString("CalcJSON", "Berechnung (JSON)", "", 99); + + // ---- Timer ---- + $this->RegisterTimer("UpdateTimer", 0, 'MyModule_Update($_IPS["TARGET"]);'); + } + + /* =========================== + * 2) ApplyChanges (Konsole speichern) + * =========================== */ + public function ApplyChanges() + { + parent::ApplyChanges(); + + // z.B. alle 10s neu rechnen (0 = aus) + $this->SetTimerInterval("UpdateTimer", 10000); + + // Sofort nach Speichern einmal rechnen + $this->Update(); + } + + /* =========================== + * 3) RequestAction (WebFront Klicks) + * =========================== */ + public function RequestAction($Ident, $Value) + { + switch ($Ident) { + case "State": + SetValue($this->GetIDForIdent("State"), (bool)$Value); + if ((bool)$Value) { + $this->Update(); + } + break; + + default: + throw new Exception("Invalid Ident: " . $Ident); + } + } + + /* =========================== + * 4) Hauptlogik + * =========================== */ + public function Update() + { + if (!GetValue($this->GetIDForIdent("State"))) { + return; + } + + $batteries = json_decode($this->ReadPropertyString("Batteries"), true); + if (!is_array($batteries) || count($batteries) === 0) { + $this->SendDebug("Update", "Keine Batterien konfiguriert.", 0); + $this->WriteEmptyResults(); + return; + } + + $sdlPowerW = (int)$this->ReadPropertyInteger("SDL_Leistung"); + $reserveH = 0.5; // FIX: SDL muss immer 30 Minuten laden/entladen können + + // 1) Summe Max-Leistung aller Batterien + $sumBatPowerW = 0; + foreach ($batteries as $b) { + $p = (int)($b["powerbat"] ?? 0); + if ($p > 0) { + $sumBatPowerW += $p; + } + } + + if ($sumBatPowerW <= 0) { + $this->SendDebug("Update", "Summe powerbat ist 0 – bitte Leistungen setzen.", 0); + $this->WriteEmptyResults(); + return; + } + + // Optional: SDL nicht größer als physikalisch möglich + if ($sdlPowerW > $sumBatPowerW) { + $this->SendDebug("Update", "SDL_Leistung ($sdlPowerW W) > SummeBatPower ($sumBatPowerW W) -> begrenze.", 0); + $sdlPowerW = $sumBatPowerW; + } + if ($sdlPowerW < 0) $sdlPowerW = 0; + + // Summen + $sumSDLPowerW = 0.0; + $sumEVPowerW = 0.0; + + $sumSDLcapKWh = 0.0; // SDL-Topf Kapazität (Reserveenergie) + $sumEVcapKWh = 0.0; // EV-Topf Kapazität (Rest) + + $sumSDLkWh = 0.0; // aktueller Füllstand SDL + $sumEVkWh = 0.0; // aktueller Füllstand EV + + $calc = []; + + // 2) Pro Batterie rechnen + foreach ($batteries as $idx => $b) { + + $typ = (string)($b["typ"] ?? ("Bat#" . ($idx + 1))); + $pBatW = (int)($b["powerbat"] ?? 0); + $capKWh = (float)($b["capazity"] ?? 0); + $socVar = (int)($b["soc"] ?? 0); + + if ($pBatW <= 0 || $capKWh <= 0) { + $calc[] = [ + "typ" => $typ, + "skip" => "powerbat<=0 oder capacity<=0" + ]; + continue; + } + + // SDL Anteil Leistung (W) proportional + $pSDL_W_raw = ($sdlPowerW * $pBatW) / $sumBatPowerW; + + // Physikalisch kappen: pro Batterie nicht mehr als pBatW + $pSDL_W = min($pSDL_W_raw, (float)$pBatW); + if ($pSDL_W < 0) $pSDL_W = 0.0; + + // SDL Reserveenergie für 30 Minuten (kWh) + $eSDLcap_kWh = ($pSDL_W * $reserveH) / 1000.0; + + // EV-Topf ist Rest der Kapazität + $eEVcap_kWh = max(0.0, $capKWh - $eSDLcap_kWh); + + // SoC lesen (0..100 oder 0..1 -> wird umgerechnet) + $socPct = $this->ReadSocPercent($socVar); + + // Gesamtenergie in der Batterie (kWh) + $eTotal_kWh = ($socPct / 100.0) * $capKWh; + + // Aufteilung Energie: SDL zuerst füllen (damit SDL "sicher" ist) + $eSDL_kWh = min($eTotal_kWh, $eSDLcap_kWh); + $eEV_kWh = max(0.0, $eTotal_kWh - $eSDL_kWh); + + // EV kann maximal eEVcap aufnehmen (sollte i.d.R. eh passen) + if ($eEV_kWh > $eEVcap_kWh) { + $eEV_kWh = $eEVcap_kWh; + } + + // Power-Limits + $pEV_W = max(0.0, $pBatW - $pSDL_W); + + // Summieren + $sumSDLPowerW += $pSDL_W; + $sumEVPowerW += $pEV_W; + + $sumSDLcapKWh += $eSDLcap_kWh; + $sumEVcapKWh += $eEVcap_kWh; + + $sumSDLkWh += $eSDL_kWh; + $sumEVkWh += $eEV_kWh; + + // Detail pro Batterie + $calc[] = [ + "typ" => $typ, + + "pBat_W" => $pBatW, + "cap_kWh" => round($capKWh, 3), + "soc_pct" => round($socPct, 2), + "eTotal_kWh" => round($eTotal_kWh, 3), + + "pSDL_W" => round($pSDL_W, 0), + "eSDLcap_kWh" => round($eSDLcap_kWh, 3), + "eSDL_kWh" => round($eSDL_kWh, 3), + + "eEVcap_kWh" => round($eEVcap_kWh, 3), + "eEV_kWh" => round($eEV_kWh, 3), + + "pEV_W" => round($pEV_W, 0) + ]; + } + + // 3) Gesamt-SoC für SDL/EV (bezogen auf jeweilige Topf-Kapazität) + $socSDL = ($sumSDLcapKWh > 0.0) ? ($sumSDLkWh / $sumSDLcapKWh) * 100.0 : 0.0; + $socEV = ($sumEVcapKWh > 0.0) ? ($sumEVkWh / $sumEVcapKWh) * 100.0 : 0.0; + + // 4) Setzen der Ergebnisvariablen + SetValue($this->GetIDForIdent("kWh_SDL"), round($sumSDLkWh, 3)); + SetValue($this->GetIDForIdent("kWh_EV"), round($sumEVkWh, 3)); + + SetValue($this->GetIDForIdent("SoC_SDL"), round($socSDL, 2)); + SetValue($this->GetIDForIdent("SoC_EV"), round($socEV, 2)); + + SetValue($this->GetIDForIdent("P_SDL_max"), round($sumSDLPowerW, 0)); + SetValue($this->GetIDForIdent("P_EV_max"), round($sumEVPowerW, 0)); + + // 5) Debug / Buffer + $out = [ + "SDL_W" => $sdlPowerW, + "Reserve_h" => $reserveH, + "SumBatPower_W" => $sumBatPowerW, + + "SumSDLcap_kWh" => round($sumSDLcapKWh, 3), + "SumEVcap_kWh" => round($sumEVcapKWh, 3), + + "SumSDL_kWh" => round($sumSDLkWh, 3), + "SumEV_kWh" => round($sumEVkWh, 3), + + "SoC_SDL_pct" => round($socSDL, 2), + "SoC_EV_pct" => round($socEV, 2), + + "P_SDL_max_W" => round($sumSDLPowerW, 0), + "P_EV_max_W" => round($sumEVPowerW, 0), + + "batteries" => $calc + ]; + + $json = json_encode($out, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT); + SetValue($this->GetIDForIdent("CalcJSON"), $json); + $this->SetBuffer("BatteryCalc", $json); + + $this->SendDebug("Update", $json, 0); + } + + /* =========================== + * 5) Helper + * =========================== */ + + /** + * SoC in Prozent (0..100). + * Akzeptiert 0..1 (wird *100) oder 0..100. + */ + private function ReadSocPercent(int $varId): float + { + if ($varId <= 0 || !IPS_VariableExists($varId)) { + return 0.0; + } + + $v = GetValue($varId); + if (!is_numeric($v)) { + return 0.0; + } + + $f = (float)$v; + + // 0..1 -> Prozent + if ($f >= 0.0 && $f <= 1.0) { + $f *= 100.0; + } + + // Clamp + if ($f < 0.0) $f = 0.0; + if ($f > 100.0) $f = 100.0; + + return $f; + } + + private function WriteEmptyResults() + { + SetValue($this->GetIDForIdent("kWh_SDL"), 0.0); + SetValue($this->GetIDForIdent("kWh_EV"), 0.0); + SetValue($this->GetIDForIdent("SoC_SDL"), 0.0); + SetValue($this->GetIDForIdent("SoC_EV"), 0.0); + SetValue($this->GetIDForIdent("P_SDL_max"), 0.0); + SetValue($this->GetIDForIdent("P_EV_max"), 0.0); + SetValue($this->GetIDForIdent("CalcJSON"), "{}"); + $this->SetBuffer("BatteryCalc", "{}"); + } +}