diff --git a/Ansteuerung_Askoheat/form.json b/Ansteuerung_Askoheat/form.json index 9097e63..7c4b6b8 100644 --- a/Ansteuerung_Askoheat/form.json +++ b/Ansteuerung_Askoheat/form.json @@ -25,13 +25,7 @@ { "type": "SelectVariable", "name": "Variable_Leistung", - "caption": "Variable mit der Nennleistung", - "test": true - }, - { - "type": "SelectVariable", - "name": "Variable_Temperatur", - "caption": "Variable mit der Solltemperatur Boiler", + "caption": "Variable mit der Heizstufe", "test": true }, { diff --git a/Ansteuerung_Askoheat/module.php b/Ansteuerung_Askoheat/module.php index 38b4a5d..68deb5a 100644 --- a/Ansteuerung_Askoheat/module.php +++ b/Ansteuerung_Askoheat/module.php @@ -12,10 +12,9 @@ class Ansteuerung_Askoheat extends IPSModule $this->RegisterPropertyBoolean("Boilertemperatur_glätten", false); $this->RegisterPropertyInteger("Boilervolumen", 300); $this->RegisterPropertyString("Zeitplan", ""); - $this->RegisterPropertyInteger("BoilerLeistung", 3500); // Standardwert für Volllast + $this->RegisterPropertyInteger("BoilerLeistung", 4400); // Standardwert für Volllast $this->RegisterPropertyInteger("Interval", 5); // Recheninterval $this->RegisterPropertyInteger("Variable_Leistung", 0); // Recheninterval - $this->RegisterPropertyInteger("Variable_Temperatur", 0); // Recheninterval $this->RegisterPropertyInteger("Variable_Temperatur_Ist", 0); // Recheninterval // Variabeln für Kommunkation mit Manager @@ -132,7 +131,15 @@ class Ansteuerung_Askoheat extends IPSModule // Methode zum Setzen des aktuellen Stromverbrauchs public function SetAktuelle_Leistung(int $power) { - RequestAction($this->ReadPropertyInteger("Variable_Leistung"), $power); + if($power>0){ + + RequestAction($this->ReadPropertyInteger("Variable_Leistung"), round($power / round(($this->ReadPropertyInteger("BoilerLeistung")/7)))); + + }else{ + + RequestAction($this->ReadPropertyInteger("Variable_Leistung"), 0); + + } // Prüfe auf Änderung der Power im Vergleich zur letzten Einstellung $lastPower = GetValue($this->GetIDForIdent("Aktuelle_Leistung")); @@ -156,7 +163,7 @@ class Ansteuerung_Askoheat extends IPSModule public function Calc_Seven_Steps(int $power) { $steps = []; - $increment = $power / 7; + $increment = round($power / 7); for ($i = 0; $i <= 7; $i++) { $steps[] = $i * $increment; diff --git a/HauptManager/README.md b/HauptManager/README.md index b059e3a..99f5ee7 100644 --- a/HauptManager/README.md +++ b/HauptManager/README.md @@ -1,67 +1,95 @@ -# Manager_1 -Beschreibung des Moduls. +# Belevo EMS Hauptmanager -### Inhaltsverzeichnis +Das **Belevo EMS Hauptmanager**-Modul fasst mehrere untergeordnete **Belevo EMS Manager**-Instanzen zusammen und verteilt die globale verfügbare Leistung direkt auf die einzelnen Verbraucher-Instanzen – nicht nur auf die Manager. Jeder Unter-Manager meldet seine Verbraucher-Daten als JSON, der Hauptmanager berechnet die finalen Zuweisungen und liefert für jeden Unter-Manager ein JSON mit den fertigen Verbraucher-Leistungen zurück. -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 +## Inhaltsverzeichnis -* +1. [Beschreibung](#beschreibung) +2. [Voraussetzungen](#voraussetzungen) +3. [Installation](#installation) +4. [Instanz anlegen & Konfiguration](#instanz-anlegen--konfiguration) +5. [Statusvariablen](#statusvariablen) +6. [Funktionsweise](#funktionsweise) + 1. [Mode-Entscheid](#1-mode-entscheid) + 2. [Datenaggregation](#2-datenaggregation) + 3. [Globale Soll-Ist-Differenz](#3-globale-soll-ist-differenz) + 4. [Verbraucher-Priorisierung](#4-verbraucher-priorisierung) + 5. [Leistungszuweisung](#5-leistungszuweisung) + 6. [Rückgabe an Unter-Manager](#6-rückgabe-an-unter-manager) +7. [Beispiel-Workflow](#beispiel-workflow) +8. [Mapping auf Code-Komponenten](#mapping-auf-code-komponenten) +9. [Zukünftige Erweiterungen](#zukünftige-erweiterungen) +10. [Support](#support) -### 2. Voraussetzungen +--- -- IP-Symcon ab Version 7.1 +## Beschreibung -### 3. Software-Installation +Der Hauptmanager koordiniert beliebig viele Unter-Manager (z. B. Hausanschluss, ZEV-Cluster, LEG-Center). Jeder Unter-Manager sammelt in seinem Zyklus die Daten aller angeschlossenen Verbraucher (JSON-Array mit Feldern wie `deviceID`, `priority`, `requestedLevels`, `currentDrawn`, `receivedTotal` usw.) und stellt dieses JSON in seiner `DatenZurueck`-Variable bereit. Der Hauptmanager -* Über den Module Store das 'Manager_1'-Modul installieren. -* Alternativ über das Module Control folgende URL hinzufügen +1. liest alle `DatenZurueck` JSONs der Unter-Manager ein, +2. entscheidet global über Solarlade- oder Peak-Shaving-Mode, +3. berechnet eine einzige **globale** Soll–Ist-Differenz, +4. führt Priorisierung und Fair-Round-Robin über **alle** Verbraucher durch, +5. verteilt die verfügbare Leistung in Stufen (je Verbraucher aus `PowerSteps`), +6. erzeugt für jeden Unter-Manager ein JSON mit den finalen `assignedLevel`-Werten pro `deviceID`, +7. schreibt diese JSONs in die jeweilige `DatenHoch`-Variable. -### 4. Einrichten der Instanzen in IP-Symcon +Die Unter-Manager übernehmen das JSON und senden die fertigen `assignedLevel`-Werte an ihre lokalen Verbraucher-Instanzen. - 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__: +## Voraussetzungen -Name | Beschreibung --------- | ------------------ - | - | +- IP-Symcon **≥ 8.0** +- Bereits installierte **Belevo EMS Manager**-Instanzen +- Zugriff auf Git-Repository: +https://git.belevo.ch/dh/Symcon_Belevo_Energiemanagement_testing.git -### 5. Statusvariablen und Profile +yaml +Kopieren +Bearbeiten -Die Statusvariablen/Kategorien werden automatisch angelegt. Das Löschen einzelner kann zu Fehlfunktionen führen. +--- -#### Statusvariablen +## Installation -Name | Typ | Beschreibung ------- | ------- | ------------ - | | - | | +1. In IP-Symcon **Module Control** öffnen +2. **Hinzufügen → Git-Repository** +3. URL eintragen (s. o.) +4. Modul **„Belevo EMS Hauptmanager“** installieren +5. IP-Symcon neu starten -#### Profile +--- -Name | Typ ------- | ------- - | - | +## Instanz anlegen & Konfiguration -### 6. WebFront +1. Rechtsklick auf **Instanzen** → **Instanz hinzufügen** +2. Filter: **Belevo** +3. **„Belevo EMS Hauptmanager“** auswählen und Instanz erstellen -Die Funktionalität, die das Modul im WebFront bietet. +### Properties -### 7. PHP-Befehlsreferenz +| Name | Typ | Beschreibung | +|------------------------|----------------|------------------------------------------------------------------------------| +| **HauptmanagerAktiv** | Boolean | Schaltet globale Verteilungslogik ein/aus | +| **Interval** | Integer (s) | Zyklusintervall für Verteilung und Mode-Entscheid | +| **Sollleistung_Max** | Float (W) | Max. Gesamtleistung, die verteilt werden darf | +| **Ueberschussleistung**| Float (W) | Untergrenze für Solarlade-Mode (z. B. 0 W) | +| **Manager_Liste** | InstanceList | Liste aller untergeordneten Belevo EMS Manager-Instanzen | +| **DatenZurueck** | SelectVariable | Variable-ID (Integer) in jeder Unter-Manager-Instanz, aus der JSON gelesen wird | +| **DatenHoch** | SelectVariable | Variable-ID in jeder Unter-Manager-Instanz, in die JSON geschrieben wird | -`boolean GEF_BeispielFunktion(integer $InstanzID);` -Erklärung der Funktion. +--- -Beispiel: -`GEF_BeispielFunktion(12345);` \ No newline at end of file +## Statusvariablen + +| Ident | Typ | Profil | Beschreibung | +|--------------------------|-----------|--------------|-----------------------------------------------------| +| **LetzteBerechnung** | DateTime | — | Zeitstempel der letzten Verteilung | +| **Globale_Differenz** | Float | ~Watt~~W~ | Zuletzt berechnete Soll–Ist-Differenz global (W) | +| **Anzahl_Manager** | Integer | — | Anzahl aktuell verbundener Unter-Manager | + +--- \ No newline at end of file diff --git a/HauptManager/module.php b/HauptManager/module.php index 8e5c20c..bd7ef55 100644 --- a/HauptManager/module.php +++ b/HauptManager/module.php @@ -110,12 +110,12 @@ class HauptManager extends IPSModule } // Addiere die aktuell bereits verwendete Leistung auf, um sie bei der verteilung zu berücksichtigen - if(in_array(0, $user["PowerSteps"], true)){ + //if(in_array(0, $user["PowerSteps"], true)){ // Addiere die aktuell bereits verwendete Leistung auf, um sie bei der verteilung zu berücksichtigen $totalAktuelle_Leistung += ($user["Aktuelle_Leistung"]- $user["Leistung_Delta"]); - } + //} } // Berücksichtigung der bereits verteilten Leistungen (nachher kann dafür wieder bei 0 begonnen werden zu verteilen) @@ -206,9 +206,11 @@ class HauptManager extends IPSModule if (empty($samePriorityUsers)) { continue; } + $withZero = []; - $withoutZero = []; - // Verbraucher die nicht 0 Annhemen können, bekommen einfach den tiefsten wert + $withoutZeroHigh = []; + $withoutZeroLow = []; + /* Alter Verteilalgor. zu testzwecken auskommentiert, wird dann gelöscht wenns funktioniert. foreach ($samePriorityUsers as $entry) { if (min($entry["PowerSteps"]) <= 0) { $withZero[] = $entry; @@ -217,18 +219,55 @@ class HauptManager extends IPSModule } } - // Verbraucher die nicht 0 annhemen können erhalten nun den minimalwert if (!empty($withoutZero)) { foreach ($withoutZero as $entry) { - $resultArray[] = [ - 'user' => $user['InstanceID'], - 'Writeback' => $user['Writeback'], - 'Set_Leistung' => min($entry["PowerSteps"]) - ]; + $instanceID = $entry["InstanceID"]; + $minPowerStep = min($entry["PowerSteps"]); + + IPS_RequestAction($instanceID,"SetAktuelle_Leistung",$minPowerStep); //$remainingPower -= $entry["Aktuelle_Leistung"]; } } + */ + + /* Neuer Block */ + foreach ($samePriorityUsers as $entry) { + $withZero[] = $entry; + + if (min($entry["PowerSteps"]) > 0) { + + $withoutZeroHigh[] = $entry; + } + if (max($entry["PowerSteps"]) < 0) { + + $withoutZeroLow[] = $entry; + } + } + + // Verbraucher die nicht 0 annhemen können erhalten nun den minimalwert + if (!empty($withoutZeroHigh)) { + foreach ($withoutZeroHigh as $entry) { + $instanceID = $entry["InstanceID"]; + $minPowerStep = min($entry["PowerSteps"]); + + $remainingPower -= $minPowerStep; + } + } + // Verbraucher die nicht 0 annhemen können erhalten nun den minimalwert + if (!empty($withoutZeroLow)) { + foreach ($withoutZeroLow as $entry) { + $instanceID = $entry["InstanceID"]; + $minPowerStep = max($entry["PowerSteps"]); + + $remainingPower += $minPowerStep; + } + } + + + /* Neuer Block Ende */ + + // Nun die verteilen, die 0 erhalten können. $samePriorityUsers = $withZero; @@ -299,22 +338,36 @@ class HauptManager extends IPSModule // Prüfen, dass jeder User mindestens seinen minimalwert an Leistung bekommt foreach ($userEnergyProv as $userInstanceID => $leistung) { - $minimalleitsung = min( - array_column( - array_filter($allSteps, function ($entry) use ( - $userInstanceID - ) { - return $entry["user"] == $userInstanceID; - }), - "step" - ) - ); + $positiveValues = array_filter( + array_column( + $samePriorityUsers, + 'PowerSteps', + 'InstanceID' + )[$userInstanceID], + function ($l) { + return $l >= 0; + } + ); + // 2. Falls keine Werte ≥ 0 vorhanden sind, auf 0 zurückfallen + $fallbackMinimum = empty($positiveValues) + ? max( array_column( + $samePriorityUsers, + 'PowerSteps', + 'InstanceID' + )[$userInstanceID]) + : min($positiveValues); - IPS_LogMessage("Manager", $userInstanceID); - IPS_LogMessage("Manager", $minimalleitsung); - IPS_LogMessage("Manager", $remainingPower); - // Jedem user den höheren der beiden werte aus minimalwert oder vergebenem zuteilen - $leistung = max($leistung["Set_Leistung"], $minimalleitsung); + // 3. minimalleistung = dieser Fallback + $minimalleistung = $fallbackMinimum; + + // 4. den höheren Wert wählen und für IPS negativieren + //$schreibleistung = max($leistung, $minimalleistung); + + if (abs($leistung["Set_Leistung"]) > abs($minimalleistung)) { + $leistung = $leistung["Set_Leistung"]; + } else { + $leistung = $minimalleistung; + } // Methode SetAktuelle_Leistung für jeden Verbraucher mit der entsprechenden Energie aufrufen @@ -387,22 +440,37 @@ class HauptManager extends IPSModule // Prüfen, dass jeder User mindestens seinen minimalwert an Leistung bekommt foreach ($userEnergyProv as $userInstanceID => $leistung) { - $minimalleitsung = min( - array_column( - array_filter($allSteps, function ($entry) use ( - $userInstanceID - ) { - return $entry["user"] == $userInstanceID; - }), - "step" - ) - ); + $positiveValues = array_filter( + array_column( + $samePriorityUsers, + 'PowerSteps', + 'InstanceID' + )[$userInstanceID], + function ($l) { + return $l <= 0; + } + ); + // 2. Falls keine Werte ≥ 0 vorhanden sind, auf 0 zurückfallen + $fallbackMinimum = empty($positiveValues) + ? min( array_column( + $samePriorityUsers, + 'PowerSteps', + 'InstanceID' + )[$userInstanceID]) + : max($positiveValues); - IPS_LogMessage("Manager", $userInstanceID); - IPS_LogMessage("Manager", $minimalleitsung); - IPS_LogMessage("Manager", $remainingPower); + // 3. minimalleistung = dieser Fallback + $minimalleistung = $fallbackMinimum; + + // 4. den höheren Wert wählen und für IPS negativieren + //$schreibleistung = max($leistung, $minimalleistung); + + if (abs($leistung["Set_Leistung"]) > abs($minimalleistung)) { + $leistung = $leistung["Set_Leistung"]; + } else { + $leistung = $minimalleistung; + } // Jedem user den höheren der beiden werte aus minimalwert oder vergebenem zuteilen - $leistung = max($leistung["Set_Leistung"], $minimalleitsung)*-1; // Methode SetAktuelle_Leistung für jeden Verbraucher mit der entsprechenden Energie aufrufen diff --git a/Ladestation_v2/form.json b/Ladestation_v2/form.json index 4244213..4faa18c 100644 --- a/Ladestation_v2/form.json +++ b/Ladestation_v2/form.json @@ -24,6 +24,10 @@ { "caption": "Easee - Nur Solarladen", "value": 5 + }, + { + "caption": "Easee", + "value": 6 } ] }, @@ -48,35 +52,48 @@ { "type": "NumberSpinner", "name": "Zeit_Zwischen_Zustandswechseln", - "caption": "Mindestlaufzeit des Verbrauchers bei Lastschaltung (Für Easee Solarladen zwingend 1 Minute einstellen)", + "caption": "Mindestlaufzeit des Verbrauchers bei Lastschaltung ", + "suffix": "" + }, + { + "type": "NumberSpinner", + "name": "Ein_Zeit", + "caption": "Mindestlaufzeit Ladestation wenn Ein", + "suffix": "" + }, + { + "type": "NumberSpinner", + "name": "Aus_Zeit", + "caption": "Mindestlaufzeit Ladestation wenn Aus", "suffix": "" }, { "type": "ValidationTextBox", "name": "IP_Adresse", "caption": "IP-Adresse Ladestation" - }, - { - "type": "ValidationTextBox", - "name": "Geräte-ID Smart-Me / ECarUp / Easee", - "caption": "ID" }, { "type": "ValidationTextBox", - "name": "Seriennummer Smart-Me / ECarUp / Easee", - "caption": "Seriennummer" + "caption": "Geräte-ID Smart-Me / ECarUp / Easee", + "name": "ID" }, { "type": "ValidationTextBox", - "name": "Username / Benutzernahme Solarladen", - "caption": "Username" + "caption": "Seriennummer Smart-Me / ECarUp / Easee", + "name": "Seriennummer" + + }, + { + "type": "ValidationTextBox", + "caption": "Username / Benutzernahme Solarladen", + "name": "Username" }, { "type": "PasswordTextBox", - "name": "Password -> Bei Anwendung mit Pico-Stationen", - "caption": "Passwort" + "caption": "Password -> Bei Anwendung mit Pico-Stationen", + "name": "Password" }, { "type": "SelectVariable", diff --git a/Ladestation_v2/module.php b/Ladestation_v2/module.php index a39b19b..11a0054 100644 --- a/Ladestation_v2/module.php +++ b/Ladestation_v2/module.php @@ -17,6 +17,8 @@ class Ladestation_v2 extends IPSModule $this->RegisterPropertyInteger("Interval", 5); // Recheninterval $this->RegisterPropertyInteger("Max_Current_abs", 32); // Recheninterval $this->RegisterPropertyInteger("Zeit_Zwischen_Zustandswechseln", 1); + $this->RegisterPropertyInteger("Ein_Zeit", 0); // Recheninterval + $this->RegisterPropertyInteger("Aus_Zeit", 0); // Recheninterval $this->RegisterPropertyInteger("Token_Easee", 0); // Recheninterval $this->RegisterPropertyInteger("Token_ECarUp", 0); // Recheninterval @@ -41,8 +43,18 @@ class Ladestation_v2 extends IPSModule IPS_SetHidden($this->GetIDForIdent("Peak"), true); $this->RegisterVariableBoolean("IsTimerActive", "IsTimerActive", "", false); + IPS_SetHidden($this->GetIDForIdent("IsTimerActive"), true); + $this->RegisterTimer("ZustandswechselTimer",0,"IPS_RequestAction(" .$this->InstanceID .', "ResetTimer", "");'); + $this->RegisterVariableBoolean("IsTimerActive_Null_Timer", "IsTimerActive_Null_Timer", "", false); + IPS_SetHidden($this->GetIDForIdent("IsTimerActive_Null_Timer"), true); + + $this->RegisterTimer("Null_Timer",0,"IPS_RequestAction(" .$this->InstanceID .', "ResetNullTimer", "");'); + + $this->RegisterVariableString("Letzer_User", "Letzter registrierter Benutzer"); + IPS_SetHidden($this->GetIDForIdent("Letzer_User"), true); + // Variabeln für Kommunkation mit Manager @@ -75,6 +87,9 @@ class Ladestation_v2 extends IPSModule $this->RegisterVariableInteger("Leistung_Delta", "Leistung_Delta", "", 0); IPS_SetHidden($this->GetIDForIdent("Leistung_Delta"), true); + $this->RegisterVariableString("Token_Intern", "Internes Token Easee"); + IPS_SetHidden($this->GetIDForIdent("Token_Intern"), true); + // Hilfsvariabeln für Idle zustand $this->RegisterPropertyInteger("IdleCounterMax", 2); $this->RegisterVariableInteger("IdleCounter", "IdleCounter", "", 0); @@ -85,6 +100,7 @@ class Ladestation_v2 extends IPSModule $this->SetValue("Idle", true); $this->RegisterTimer("Timer_Do_UserCalc_EVC",$this->ReadPropertyInteger("Interval")*1000,"IPS_RequestAction(" .$this->InstanceID .', "Do_UserCalc", "");'); + $this->RegisterTimer("Timer_Refresh_Token",0,"IPS_RequestAction(" .$this->InstanceID .', "Refresh_Token", "");'); } @@ -93,6 +109,14 @@ class Ladestation_v2 extends IPSModule parent::ApplyChanges(); $this->SetTimerInterval("Timer_Do_UserCalc_EVC",$this->ReadPropertyInteger("Interval")*1000); + // erstelle einen Timer der das Request token aktualisiert wenn die station dies braucht. + if($this->ReadPropertyInteger("Ladestation")==6){ + + $this->Refresh_Token(); + $this->SetTimerInterval("Timer_Refresh_Token",1800000); + + } + // Zusätzliche Anpassungen nach Bedarf } @@ -142,6 +166,14 @@ class Ladestation_v2 extends IPSModule case "ResetTimer": $this->ResetTimer(); + break; + + case "ResetNullTimer": + $this->ResetNullTimer(); + break; + + case "Refresh_Token": + $this->Refresh_Token(); break; default: @@ -158,6 +190,8 @@ class Ladestation_v2 extends IPSModule $this->SetValue("Car_detected", true); if($this->GetValue("Max_Current")<6){ $this->SetValue("Car_is_full", true); + $this->ResetTimer(); + $this->ResetNullTimer(); } @@ -165,7 +199,8 @@ class Ladestation_v2 extends IPSModule $this->SetValue("Car_detected", false); $this->SetValue("Leistung_Delta", 0); $this->SetValue("Car_is_full", false); - $this->SetValue("IsTimerActive", false); + $this->ResetTimer(); + $this->ResetNullTimer(); $this->SetValue("Is_1_ph", false); $this->SetValue("Aktuelle_Leistung", 0); $this->SetValue("IdleCounter", 0); @@ -231,6 +266,21 @@ class Ladestation_v2 extends IPSModule } } + + // Methode zum Setzen der PowerSteps und Timer starten + public function SetTimerNullMindestlast($time) + { + if($time>0){ + $this->SetTimerInterval("Null_Timer", $time * 60000); // Timer in Millisekunden + // Timer-Status auf true setzen + $this->SetValue("IsTimerActive_Null_Timer", true); + }else{ + $this->SetValue("IsTimerActive_Null_Timer", false); + + } + } + + // Methode zum Zurücksetzen von PowerSteps nach Ablauf des Timers public function ResetTimer() { @@ -241,6 +291,17 @@ class Ladestation_v2 extends IPSModule $this->SetValue("IsTimerActive", false); } + // Methode zum Zurücksetzen von PowerSteps nach Ablauf des Timers + public function ResetNullTimer() + { + // Timer stoppen + $this->SetTimerInterval("Null_Timer", 0); + + // Timer-Status auf false setzen + $this->SetValue("IsTimerActive_Null_Timer", false); + } + + public function Get_Car_Status(int $carType) { @@ -345,31 +406,101 @@ class Ladestation_v2 extends IPSModule ]); $response = curl_exec($ch); - curl_close($ch); - - if ($response === false) { - IPS_LogMessage("Ladestation", "Fehler beim Abrufen der eCarUp API-Daten"); - return; - } - $data = json_decode($response, true); - if (json_last_error() !== JSON_ERROR_NONE) { - IPS_LogMessage("Ladestation", "Fehler beim Dekodieren der JSON-Antwort"); - return; - } - - if (isset($data['driverIdentifier']) && ($data['driverIdentifier']==$this->ReadPropertyInteger("Username"))) { - $this->SetValue("Ladeleistung_Effektiv", $this->GetValue("Power")); - $this->SetValue("Fahrzeugstatus", 2); - $car_on_station = true; - } else { - $car_on_station = false; - $this->SetValue("Fahrzeugstatus", 1); - $this->SetValue("Ladeleistung_Effektiv", 0); + curl_close($ch); + + if (json_last_error() === JSON_ERROR_NONE && isset($data["driverIdentifier"])) { + + $this->SetValue("Letzer_User", $data["driverIdentifier"]); + + if($this->ReadPropertyString("Username") === $data["driverIdentifier"]){ + $this->SetValue("Solarladen", true); + }else{ + $this->SetValue("Solarladen", false); + } } + + //Aktueller Zustand Ladestation abfragen (Leistung, auto eingesteckt?) + $ch2 = curl_init(); + + $url2 = "https://api.easee.com/api/chargers/".$this->ReadPropertyString("Seriennummer")."/state"; + curl_setopt_array($ch2, [ + CURLOPT_ENCODING => "", + CURLOPT_MAXREDIRS => 10, + CURLOPT_TIMEOUT => 30, + CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1, + CURLOPT_CUSTOMREQUEST => "GET", + CURLOPT_HTTPHEADER => [ + "Authorization: Bearer ".GetValue($this->ReadPropertyInteger("Token_Easee"))."", + "content-type: application/*+json" + ], + ]); + + curl_setopt($ch2, CURLOPT_URL, $url2); + curl_setopt($ch2, CURLOPT_RETURNTRANSFER, true); + $response_easee = curl_exec($ch2); + curl_close($ch2); + + $easee_data = json_decode($response_easee, true); + + + if (json_last_error() === JSON_ERROR_NONE && isset($easee_data["chargerOpMode"])) { + + if ($easee_data["chargerOpMode"] != 1 && (($easee_data["chargerOpMode"] != 6)||($this->GetValue("Car_detected")==true)) && $easee_data["chargerOpMode"] != 7) { + $car_on_station = true; + } + else{ + $car_on_station = false; + } + $this->SetValue("Ladeleistung_Effektiv", round($easee_data["totalPower"]*1000)); + $this->SetValue("Fahrzeugstatus", $easee_data["chargerOpMode"]); + } + break; - + + case 6: + + + + //Aktueller Zustand Ladestation abfragen (Leistung, auto eingesteckt?) + $ch2 = curl_init(); + + $url2 = "https://api.easee.com/api/chargers/".$this->ReadPropertyString("Seriennummer")."/state"; + curl_setopt_array($ch2, [ + CURLOPT_ENCODING => "", + CURLOPT_MAXREDIRS => 10, + CURLOPT_TIMEOUT => 30, + CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1, + CURLOPT_CUSTOMREQUEST => "GET", + CURLOPT_HTTPHEADER => [ + "Authorization: Bearer ".$this->GetBuffer("Token_Intern")."", + "content-type: application/*+json" + ], + ]); + + curl_setopt($ch2, CURLOPT_URL, $url2); + curl_setopt($ch2, CURLOPT_RETURNTRANSFER, true); + $response_easee = curl_exec($ch2); + curl_close($ch2); + + $easee_data = json_decode($response_easee, true); + + + if (json_last_error() === JSON_ERROR_NONE && isset($easee_data["chargerOpMode"])) { + + if ($easee_data["chargerOpMode"] != 1 && (($easee_data["chargerOpMode"] != 6)||($this->GetValue("Car_detected")==true)) && $easee_data["chargerOpMode"] != 7) { + $car_on_station = true; + } + else{ + $car_on_station = false; + } + $this->SetValue("Ladeleistung_Effektiv", round($easee_data["totalPower"]*1000)); + $this->SetValue("Fahrzeugstatus", $easee_data["chargerOpMode"]); + } + + break; + default: $this->SetValue("Car_detected", false); $this->SetValue("Fahrzeugstatus", -1); @@ -384,10 +515,10 @@ class Ladestation_v2 extends IPSModule { $maxCurrent = 32; if($is_1_ph){ - $maxCurrent = 1.5 + ($this->GetValue("Ladeleistung_Effektiv") / 230); + $maxCurrent = 2.5 + ($this->GetValue("Ladeleistung_Effektiv") / 230); } else{ - $maxCurrent = 1.5 + ($this->GetValue("Ladeleistung_Effektiv") / (1.71*400)); + $maxCurrent = 2.5 + ($this->GetValue("Ladeleistung_Effektiv") / (1.71*400)); } if($maxCurrent>$this->ReadPropertyInteger("Max_Current_abs")){ $maxCurrent = $this->ReadPropertyInteger("Max_Current_abs"); @@ -406,10 +537,24 @@ class Ladestation_v2 extends IPSModule return $current; } - public function Get_Array_From_Current(bool $is_1_ph, float $current) + public function Get_Array_From_Current(bool $is_1_ph, float $current, int $power, bool $timer) { $resultArray = []; - $resultArray[] = 0; + + if ($timer) { + // Timer an + if ($power === 0) { + // power == 0: nur eine 0 zurückgeben + $resultArray[] = 0; + return $resultArray; + } + // power > 0: keine 0 am Anfang, Schleife normal durchlaufen + } else { + // Timer aus: wie bisher, 0 am Anfang + $resultArray[] = 0; + } + + // Schleife wie gehabt for ($i = 6; $i <= $current; $i++) { if ($is_1_ph) { $resultArray[] = $i * 230; @@ -417,12 +562,13 @@ class Ladestation_v2 extends IPSModule $resultArray[] = $i * 400 * 1.71; } } - + return $resultArray; } + public function SetAktuelle_Leistung(int $power) { // Hier eine Leistungsänderung detektieren für Idle und interne Mindestlaufzeiten @@ -431,6 +577,14 @@ class Ladestation_v2 extends IPSModule $this->SetTimerOn(); } + if (($this->GetValue("Aktuelle_Leistung") == 0) && ($power>0)) { + $this->SetTimerNullMindestlast($this->ReadPropertyInteger("Ein_Zeit")); + } + elseif(($this->GetValue("Aktuelle_Leistung") > 0) && ($power==0)){ + $this->SetTimerNullMindestlast($this->ReadPropertyInteger("Aus_Zeit")); + + } + $internalPower = GetValue($this->GetIDForIdent("Aktuelle_Leistung")); // Aktuelle Leistungsvorgabe setzen @@ -444,7 +598,7 @@ class Ladestation_v2 extends IPSModule if ($power != $internalPower) { // Setze die interne Leistungsvorgabe - // Idle für 4 Zyklen auf false setzen + // Idle für x Zyklen auf false setzen SetValue($this->GetIDForIdent("Idle"), false); SetValue( $this->GetIDForIdent("IdleCounter"), @@ -511,13 +665,13 @@ class Ladestation_v2 extends IPSModule if (!$ladebereit) { $powerSteps = [0]; } elseif (!$Peak && !$solarladen) { - $powerSteps = [max($this->Get_Array_From_Current($this->GetValue("Is_1_ph"),$this->GetValue("Max_Current")))]; + $powerSteps = [max($this->Get_Array_From_Current($this->GetValue("Is_1_ph"),$this->GetValue("Max_Current"), $this->GetValue("Aktuelle_Leistung"), $this->GetValue("IsTimerActive_Null_Timer")))]; } elseif (!$Peak && $solarladen) { - $powerSteps = $this->Get_Array_From_Current($this->GetValue("Is_1_ph"),$this->GetValue("Max_Current")); + $powerSteps = $this->Get_Array_From_Current($this->GetValue("Is_1_ph"),$this->GetValue("Max_Current"), $this->GetValue("Aktuelle_Leistung"), $this->GetValue("IsTimerActive_Null_Timer")); } elseif ($solarladen && $Peak) { $powerSteps = [0]; } else { - $powerSteps = $this->Get_Array_From_Current($this->GetValue("Is_1_ph"),$this->GetValue("Max_Current")); + $powerSteps = $this->Get_Array_From_Current($this->GetValue("Is_1_ph"),$this->GetValue("Max_Current"), $this->GetValue("Aktuelle_Leistung"), $this->GetValue("IsTimerActive_Null_Timer")); } // PowerSteps in der RegisterVariable speichern @@ -528,7 +682,7 @@ class Ladestation_v2 extends IPSModule if($this->GetValue("Aktuelle_Leistung")>(1.11*$this->GetValue("Ladeleistung_Effektiv"))){ $this->SetValue("Pending_Counter", $counter+1); - }elseif(max($this->Get_Array_From_Current($this->GetValue("Is_1_ph"),$this->GetValue("Max_Current")))<(1.11*$this->GetValue("Ladeleistung_Effektiv"))){ // diesen elseif ggf wieder rauslöschen + }elseif(max($this->Get_Array_From_Current($this->GetValue("Is_1_ph"),$this->GetValue("Max_Current"), $this->GetValue("Aktuelle_Leistung"), $this->GetValue("IsTimerActive_Null_Timer")))<(1.11*$this->GetValue("Ladeleistung_Effektiv"))){ // diesen elseif ggf wieder rauslöschen $this->SetValue("Pending_Counter", $counter+1); } @@ -544,9 +698,6 @@ class Ladestation_v2 extends IPSModule $this->Calc_Max_Current($this->GetValue("Is_1_ph")); } - - - return $powerSteps; } @@ -571,6 +722,9 @@ class Ladestation_v2 extends IPSModule case 5: // Keine base Url nötig break; + case 6: + // Keine base Url nötig + break; } $ch = curl_init(); @@ -595,6 +749,9 @@ class Ladestation_v2 extends IPSModule // Nichts zu tun für Dummy station return; case 5: + if($this->ReadPropertyString("Username") != $this->GetValue("Letzer_User")){ + return; + } $url = "https://api.easee.com/api/chargers/".$this->ReadPropertyString("Seriennummer")."/commands/set_dynamic_charger_current"; curl_setopt_array($ch, [ CURLOPT_ENCODING => "", @@ -602,13 +759,31 @@ class Ladestation_v2 extends IPSModule CURLOPT_TIMEOUT => 30, CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1, CURLOPT_CUSTOMREQUEST => "POST", - CURLOPT_POSTFIELDS => "{\"amps\":". $value .",\"minutes\":1}", + CURLOPT_POSTFIELDS => "{\"amps\":". $value .",\"minutes\":0}", CURLOPT_HTTPHEADER => [ "Authorization: Bearer ".GetValue($this->ReadPropertyInteger("Token_Easee"))."", "content-type: application/*+json" ], ]); - + break; + + case 6: + + $url = "https://api.easee.com/api/chargers/".$this->ReadPropertyString("Seriennummer")."/commands/set_dynamic_charger_current"; + curl_setopt_array($ch, [ + CURLOPT_ENCODING => "", + CURLOPT_MAXREDIRS => 10, + CURLOPT_TIMEOUT => 30, + CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1, + CURLOPT_CUSTOMREQUEST => "POST", + CURLOPT_POSTFIELDS => "{\"amps\":". $value .",\"minutes\":0}", + CURLOPT_HTTPHEADER => [ + "Authorization: Bearer ".$this->GetBuffer("Token_Intern")."", + "content-type: application/*+json" + ], + ]); + break; + default: return "Invalid station type."; } @@ -637,6 +812,9 @@ class Ladestation_v2 extends IPSModule // Nichts zu tun für Dummy station return; case 5: + if($this->ReadPropertyString("Username") != $this->GetValue("Letzer_User")){ + return; + } $url2 = "https://api.easee.com/api/chargers/".$this->ReadPropertyString("Seriennummer")."/commands/set_dynamic_charger_current"; curl_setopt_array($ch, [ CURLOPT_ENCODING => "", @@ -644,13 +822,31 @@ class Ladestation_v2 extends IPSModule CURLOPT_TIMEOUT => 30, CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1, CURLOPT_CUSTOMREQUEST => "POST", - CURLOPT_POSTFIELDS => "{\"amps\":". $value .",\"minutes\":1}", + CURLOPT_POSTFIELDS => "{\"amps\":". $value .",\"minutes\":0}", CURLOPT_HTTPHEADER => [ "Authorization: Bearer ".GetValue($this->ReadPropertyInteger("Token_Easee"))."", "content-type: application/*+json" ], ]); break; + + case 6: + + $url2 = "https://api.easee.com/api/chargers/".$this->ReadPropertyString("Seriennummer")."/commands/set_dynamic_charger_current"; + curl_setopt_array($ch, [ + CURLOPT_ENCODING => "", + CURLOPT_MAXREDIRS => 10, + CURLOPT_TIMEOUT => 30, + CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1, + CURLOPT_CUSTOMREQUEST => "POST", + CURLOPT_POSTFIELDS => "{\"amps\":". $value .",\"minutes\":0}", + CURLOPT_HTTPHEADER => [ + "Authorization: Bearer ".$this->GetBuffer("Token_Intern")."", + "content-type: application/*+json" + ], + ]); + break; + default: return "Invalid station type."; } @@ -678,6 +874,43 @@ class Ladestation_v2 extends IPSModule } } + public function Refresh_Token(){ + + $payload = json_encode([ + 'userName' => $this->ReadPropertyString("Username"), + 'password' => $this->ReadPropertyString("Password"), + ]); + + // cURL-Handle initialisieren + $ch = curl_init('https://api.easee.com/api/accounts/login'); + + // cURL-Optionen setzen + curl_setopt_array($ch, [ + CURLOPT_RETURNTRANSFER => true, // Antwort als String zurückgeben + CURLOPT_POST => true, // POST-Methode verwenden + CURLOPT_HTTPHEADER => [ + 'Accept: application/json', + 'Content-Type: application/json', + ], + CURLOPT_POSTFIELDS => $payload, // JSON-Body übergeben + ]); + + // Anfrage ausführen und Antwort speichern + $response = curl_exec($ch); + + // Auf Fehler prüfen + if ($response === false) { + echo 'cURL-Fehler: ' . curl_error($ch); + curl_close($ch); + exit; + } + + // cURL-Handle schließen + curl_close($ch); + $this->SetBuffer("Token_Intern", (json_decode($response, true)['accessToken'])); + + } + public function CheckIdle($power) { $lastpower = GetValue($this->GetIDForIdent("Aktuelle_Leistung")); diff --git a/Manager/README.md b/Manager/README.md index b059e3a..e6e5f34 100644 --- a/Manager/README.md +++ b/Manager/README.md @@ -1,67 +1,125 @@ -# Manager_1 -Beschreibung des Moduls. +# Belevo EMS Manager -### Inhaltsverzeichnis +Dieses Modul ist das Herzstück des Belevo Energiemanagement-Systems. Es steuert in festgelegten Intervallen die Leistungsverteilung auf Ihre Verbraucher-Instanzen, trifft den Solarlade- vs. Peak-Shaving-Entscheid und verteilt entsprechend die Leistungen an die Verbraucher. -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 +## Inhaltsverzeichnis -* +1. [Funktionsumfang](#1-funktionsumfang) +2. [Voraussetzungen](#2-voraussetzungen) +3. [Installation](#3-installation) +4. [Instanz anlegen & Konfiguration](#4-instanz-anlegen--konfiguration) +5. [Status­variablen & Profile](#5-statusvariablen--profile) +6. [WebFront / Bedienung](#6-webfront--bedienung) +7. [Mapping auf Code-Komponenten](#7-mapping-auf-code-komponenten) +8. [Zukünftige Erweiterungen](#8-zukünftige-erweiterungen) -### 2. Voraussetzungen +--- -- IP-Symcon ab Version 7.1 +## 1. Funktionsumfang -### 3. Software-Installation +- **Zyklische Steuerung** + In konfigurierbaren Intervallen (`Interval`) wird die Methode `DistributeEnergy()` ausgelöst. +- **Mode-Entscheid** + Basierend auf der aktuellen Netz-Sollleistung (`Sollleistung_Max`) und der PV-Überschuss-Grenze (`Ueberschussleistung`) wählt der Manager automatisch zwischen + - **Solarlade-Mode** (`Is_Peak_Shaving = false`) + - **Peak-Shaving-Mode** (`Is_Peak_Shaving = true`) +- **Leistungskonten** + Jeder Verbraucher meldet über seine Instanzvariablen (`Power`, `Bezogene_Energie`, `PowerSteps`, `PV_Prio` / `Sperre_Prio`) seinen Bedarf bzw. Reduktions- oder Einspeise-Potential. +- **Priorisierung & Fairness** + 1. Sortierung der Verbraucher nach Priorität (`PV_Prio` im Solarmode, `Sperre_Prio` im Shaving-Mode) + 2. Bei Gleichpriorität abwechselnde Zuteilung nach bisher bezogener Energie (`Bezogene_Energie`) +- **Differenz-Verteilung** + Die verbleibende Soll–Ist-Differenz D wird in Stufen (je Verbraucher aus `PowerSteps`) abgearbeitet, bis D ≃ 0. +- **Externe Kommunikation** + Über die Properties `DatenHoch` und `DatenZuruck` (Variable-IDs) können mehrere Manager zu einem Übergeordneten Hauptmanager zusammengefasst werden (V-ZEV, LEG). -* Ü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 +## 2. Voraussetzungen - 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) +- IP-Symcon **≥ 8.0** +- Modul-URL: +https://git.belevo.ch/dh/Symcon_Belevo_Energiemanagement_testing.git -__Konfigurationsseite__: +yaml +Kopieren +Bearbeiten +- Einmalige manuelle Anmeldung aller Verbraucher in `Verbraucher_Liste` -Name | Beschreibung --------- | ------------------ - | - | +--- -### 5. Statusvariablen und Profile +## 3. Installation -Die Statusvariablen/Kategorien werden automatisch angelegt. Das Löschen einzelner kann zu Fehlfunktionen führen. +1. In IP-Symcon **Module Control** öffnen +2. **Hinzufügen → Git-Repository** +3. URL eingeben (oben) und **Installieren** +4. IP-Symcon neu starten -#### Statusvariablen +--- -Name | Typ | Beschreibung ------- | ------- | ------------ - | | - | | +## 4. Instanz anlegen & Konfiguration -#### Profile +### 4.1 Instanz anlegen -Name | Typ ------- | ------- - | - | +- Rechtsklick **Instanzen** → **Instanz hinzufügen** +- Filter: **Belevo** +- Auswahl: **Manager** -### 6. WebFront +### 4.2 Properties -Die Funktionalität, die das Modul im WebFront bietet. +| Name | Typ | Beschreibung | +|--------------------------|-----------------|----------------------------------------------------------------------------| +| **Peakleistung** | NumberSpinner | Sollwert-Vorgabe für Peak-Shaving (Watt) | +| **Ueberschussleistung** | NumberSpinner | Sollwert-Vorgabe für Solarladen (Watt) | +| **Netzbezug** | SelectVariable | Variable mit dem aktuellen Netz-Ist-Wert (Watt) | +| **HauptmanagerAktiv** | CheckBox | Schaltet die Ünergeordnete Manager-Logik global ein/aus | +| **ManagerID** | NumberSpinner | Eindeutige ID für externe Manager‐Kommunikation | +| **DatenHoch** | SelectVariable | Variable, in die Manager-Statistiken oder Statusdaten geschrieben werden | +| **DatenZuruck** | SelectVariable | Variable, aus der Verbraucherdaten importiert werden | +| **Interval** | NumberSpinner | Intervall für Neuberechnung der Werte (Sekunden) | +| **Verbraucher_Liste** | List | Auswahl aller Verbraucher-Instanzen, die gesteuert werden sollen | -### 7. PHP-Befehlsreferenz +--- -`boolean GEF_BeispielFunktion(integer $InstanzID);` -Erklärung der Funktion. +## 5. Status­variablen & Profile -Beispiel: -`GEF_BeispielFunktion(12345);` \ No newline at end of file +| Ident | Typ | Profil | Beschreibung | +|-------------------------|------------|----------------|-------------------------------------------------------------------| +| **Is_Peak_Shaving** | Boolean | — | Modusanzeige (false = Solarladen, true = Peak-Shaving) | +| **LetzteBerechnung** | DateTime | — | Zeitpunkt der letzten Zyklus-Ausführung | +| **Aktuelle_Differenz** | Float | ~Watt~~W~ | Zuletzt berechnete Soll–Ist-Differenz | + +--- + +## 6. WebFront / Bedienung + +Im WebFront können Sie +- den **aktuellen Modus** (`Is_Peak_Shaving`) beobachten + +--- + +## 7. Mapping auf Code-Komponenten + +| Komponente | Modul-Datei | Funktion | +|---------------------------|-----------------------------|------------------------------------------------------------| +| **Timer-Registrierung** | `Manager/module.php` | `ApplyChanges()` → `SetTimerInterval('Interval', …)` | +| **Zyklischer Aufruf** | `Manager/module.php` | `ManageTimer()` ruft `DistributeEnergy()` auf | +| **Mode-Entscheidlogik** | `Manager/module.php` | Berechnung: +```php +$isPeak = $ist > (($peak + $solar) / 2); +$this->SetValue('Is_Peak_Shaving', $isPeak); +``` | +| **Datenzugriff** | `Manager/module.php` | `$this->ReadPropertyInteger('Peakleistung')` usw. | +| **Verbraucher-Schleife** | `Manager/module.php` | `$this->ReadPropertyArray('Verbraucher_Liste')` | +| **Leistungszuteilung** | `Manager/module.php` | `DistributeEnergy()` → Round-Robin über `PowerSteps` | +| **Externe Schnittstelle** | `Manager/module.php` | Verwendung von `DatenHoch` / `DatenZuruck` Variablen-IDs | + +--- + +## 8. Zukünftige Erweiterungen + +- **Automatische Registrierung** neuer Verbraucher (ohne manuelles Eintragen) + +--- \ No newline at end of file diff --git a/Manager/module.php b/Manager/module.php index 8422eff..b58c241 100644 --- a/Manager/module.php +++ b/Manager/module.php @@ -169,12 +169,12 @@ class Manager extends IPSModule IPS_LogMessage("Manager", "nciht idle"); } - if(in_array(0, $powerSteps, true)){ + //if(in_array(0, $powerSteps, true)){ // Addiere die aktuell bereits verwendete Leistung auf, um sie bei der verteilung zu berücksichtigen $totalAktuelle_Leistung += ($Aktuelle_Leistung-$delta); - } + // } } // Berücksichtigung der bereits verteilten Leistungen (nachher kann dafür wieder bei 0 begonnen werden zu verteilen) @@ -238,8 +238,15 @@ class Manager extends IPSModule continue; } $withZero = []; - $withoutZero = []; + $withoutZeroHigh = []; + $withoutZeroLow = []; + // Verbraucher die nicht 0 Annhemen können, bekommen einfach den tiefsten wert + + + + + /* Alter Verteilalgor. zu testzwecken auskommentiert, wird dann gelöscht wenns funktioniert. foreach ($samePriorityUsers as $entry) { if (min($entry["PowerSteps"]) <= 0) { $withZero[] = $entry; @@ -247,6 +254,7 @@ class Manager extends IPSModule $withoutZero[] = $entry; } } + // Verbraucher die nicht 0 annhemen können erhalten nun den minimalwert if (!empty($withoutZero)) { foreach ($withoutZero as $entry) { @@ -257,6 +265,45 @@ class Manager extends IPSModule //$remainingPower -= $entry["Aktuelle_Leistung"]; } } + */ + + /* Neuer Block */ + foreach ($samePriorityUsers as $entry) { + $withZero[] = $entry; + + if (min($entry["PowerSteps"]) > 0) { + + $withoutZeroHigh[] = $entry; + } + if (max($entry["PowerSteps"]) < 0) { + + $withoutZeroLow[] = $entry; + } + } + + // Verbraucher die nicht 0 annhemen können erhalten nun den minimalwert + if (!empty($withoutZeroHigh)) { + foreach ($withoutZeroHigh as $entry) { + $instanceID = $entry["InstanceID"]; + $minPowerStep = min($entry["PowerSteps"]); + + $remainingPower -= $minPowerStep; + } + } + // Verbraucher die nicht 0 annhemen können erhalten nun den minimalwert + if (!empty($withoutZeroLow)) { + foreach ($withoutZeroLow as $entry) { + $instanceID = $entry["InstanceID"]; + $minPowerStep = max($entry["PowerSteps"]); + + $remainingPower += $minPowerStep; + } + } + + + /* Neuer Block Ende */ + + // Nun die verteilen, die 0 erhalten können. @@ -301,35 +348,51 @@ class Manager extends IPSModule } - //else - - - // mache invertierte verteilung - - // Prüfen, dass jeder User mindestens seinen minimalwert an Leistung bekommt foreach ($userEnergyProv as $userInstanceID => $leistung) { - $minimalleitsung = min( + // 1. Innerhalb der Schleife: alle nicht-negativen Leistungen sammeln + $positiveValues = array_filter( array_column( - array_filter($allSteps, function ($entry) use ( - $userInstanceID - ) { - return $entry["user"] == $userInstanceID; - }), - "step" - ) + $samePriorityUsers, + 'PowerSteps', + 'InstanceID' + )[$userInstanceID], + function ($l) { + return $l >= 0; + } ); + // 2. Falls keine Werte ≥ 0 vorhanden sind, auf 0 zurückfallen + $fallbackMinimum = empty($positiveValues) + ? max( array_column( + $samePriorityUsers, + 'PowerSteps', + 'InstanceID' + )[$userInstanceID]) + : min($positiveValues); - // Jedem user den höheren der beiden werte aus minimalwert oder vergebenem zuteilen - $leistung = max($leistung, $minimalleitsung); + // 3. minimalleistung = dieser Fallback + $minimalleistung = $fallbackMinimum; + // 4. den höheren Wert wählen und für IPS negativieren + //$schreibleistung = max($leistung, $minimalleistung); - - // Methode SetAktuelle_Leistung für jeden Verbraucher mit der entsprechenden Energie aufrufen + if (abs($leistung) > abs($minimalleistung)) { + $schreibleistung = $leistung; + } else { + $schreibleistung = $minimalleistung; + } + + // 5. Aktion ausführen if (IPS_InstanceExists($userInstanceID)) { - IPS_RequestAction($userInstanceID,"SetAktuelle_Leistung",$leistung); - IPS_LogMessage("Manager", "aufgerufen setleistung if". $leistung); - + IPS_RequestAction( + $userInstanceID, + "SetAktuelle_Leistung", + $schreibleistung + ); + IPS_LogMessage( + "Manager", + "aufgerufen setleistung (Berechnung in foreach): " . $schreibleistung + ); } } } @@ -341,7 +404,7 @@ class Manager extends IPSModule if($step<=0){ $allSteps[] = [ "user" => $user["InstanceID"], - "Leistung_Delta" => $user["Leistung_Delta"], + //"Leistung_Delta" => $user["Leistung_Delta"], "step" => -1*$step, ];} } @@ -352,7 +415,6 @@ class Manager extends IPSModule return $a["step"] <=> $b["step"]; }); - $remainingPower = $remainingPower *-1; // Iteriere durch alle Schritte foreach ($allSteps as $entry) { @@ -371,28 +433,50 @@ class Manager extends IPSModule // Prüfen, dass jeder User mindestens seinen minimalwert an Leistung bekommt - foreach ($userEnergyProv as $userInstanceID => $leistung) { - $minimalleitsung = min( + foreach ($userEnergyProv as $userInstanceID => $leistung) { + // 1. Innerhalb der Schleife: alle nicht-negativen Leistungen sammeln + $positiveValues = array_filter( array_column( - array_filter($allSteps, function ($entry) use ( - $userInstanceID - ) { - return $entry["user"] == $userInstanceID; - }), - "step" - ) + $samePriorityUsers, + 'PowerSteps', + 'InstanceID' + )[$userInstanceID], + function ($l) { + return $l <= 0; + } ); + // 2. Falls keine Werte ≥ 0 vorhanden sind, auf 0 zurückfallen + $fallbackMinimum = empty($positiveValues) + ? min( array_column( + $samePriorityUsers, + 'PowerSteps', + 'InstanceID' + )[$userInstanceID]) + : max($positiveValues); - // Jedem user den höheren der beiden werte aus minimalwert oder vergebenem zuteilen - $schreibleistung = max($leistung, $minimalleitsung)*-1; + // 3. minimalleistung = dieser Fallback + $minimalleistung = $fallbackMinimum; + // 4. den höheren Wert wählen und für IPS negativieren + //$schreibleistung = max($leistung, $minimalleistung); - - // Methode SetAktuelle_Leistung für jeden Verbraucher mit der entsprechenden Energie aufrufen + if (abs($leistung) > abs($minimalleistung)) { + $schreibleistung = $leistung*-1; + } else { + $schreibleistung = $minimalleistung; + } + + // 5. Aktion ausführen if (IPS_InstanceExists($userInstanceID)) { - IPS_RequestAction($userInstanceID,"SetAktuelle_Leistung",$schreibleistung); - IPS_LogMessage("Manager", "aufgerufen setleistung else ".$schreibleistung); - + IPS_RequestAction( + $userInstanceID, + "SetAktuelle_Leistung", + $schreibleistung + ); + IPS_LogMessage( + "Manager", + "aufgerufen setleistung (Berechnung in foreach): " . $schreibleistung + ); } } } diff --git a/PV_Visu/form.json b/PV_Visu/form.json new file mode 100644 index 0000000..1acde8f --- /dev/null +++ b/PV_Visu/form.json @@ -0,0 +1,9 @@ +{ + "elements": [ + { "type": "SelectVariable", "name": "VarProduction", "caption": "Produktion (kWh)" }, + { "type": "SelectVariable", "name": "VarConsumption", "caption": "Verbrauch (kWh)" }, + { "type": "SelectVariable", "name": "VarFeedIn", "caption": "Einspeisung (kWh)" }, + { "type": "SelectVariable", "name": "VarGrid", "caption": "Bezug Netz (kWh)" } + ], + "actions": [] +} diff --git a/PV_Visu/module.html b/PV_Visu/module.html new file mode 100644 index 0000000..4d2975c --- /dev/null +++ b/PV_Visu/module.html @@ -0,0 +1,78 @@ + + + + + + + +
+
+
Produktion (Eigenverbrauch / Einspeisung)
+
+
+
+
+
+
+
+
Verbrauch (PV / Netz)
+
+
+
+
+
+
+
+ + + diff --git a/PV_Visu/module.json b/PV_Visu/module.json new file mode 100644 index 0000000..dfbdcba --- /dev/null +++ b/PV_Visu/module.json @@ -0,0 +1,12 @@ +{ + "id": "{DDE89CBE-4411-5FF4-4931-14204E05CAD0}", + "name": "PV_Visu", + "type": 3, + "vendor": "Belevo AG", + "aliases": [], + "parentRequirements": [], + "childRequirements": [], + "implemented": [], + "prefix": "", + "url": "" +} diff --git a/PV_Visu/module.php b/PV_Visu/module.php new file mode 100644 index 0000000..475b3be --- /dev/null +++ b/PV_Visu/module.php @@ -0,0 +1,99 @@ +RegisterPropertyInteger('VarProduction', 0); + $this->RegisterPropertyInteger('VarConsumption', 0); + $this->RegisterPropertyInteger('VarFeedIn', 0); + $this->RegisterPropertyInteger('VarGrid', 0); + + $this->RegisterVariableString('JSONData', 'Visualisierungsdaten', '', 0); + IPS_SetHidden($this->GetIDForIdent('JSONData'), true); + + $this->SetVisualizationType(1); // HTML SDK Tile + } + + public function ApplyChanges() + { + parent::ApplyChanges(); + + foreach (['VarProduction', 'VarConsumption', 'VarFeedIn', 'VarGrid'] as $prop) { + $vid = $this->ReadPropertyInteger($prop); + if ($vid > 0) { + $this->RegisterMessage($vid, VM_UPDATE); + } + } + + $this->UpdateData(); // Initial + } + + public function MessageSink($TimeStamp, $SenderID, $Message, $Data) + { + if ($Message === VM_UPDATE) { + $this->UpdateData(); + } + } + + public function GetVisualizationTile() + { + $initialData = ''; + $html = file_get_contents(__DIR__ . '/module.html'); + return $html . $initialData; + } + + public function RequestAction($Ident, $Value) + { + if ($Ident === 'update') { + return $this->UpdateData(); // Rückgabe für Visualisierung + } + throw new \Exception("Unknown Ident: $Ident"); + } + + public function UpdateData() + { + $start = strtotime('today 00:00'); + $end = time(); + + $prod = $this->GetDailyTotal($this->ReadPropertyInteger('VarProduction'), $start, $end); + $cons = $this->GetDailyTotal($this->ReadPropertyInteger('VarConsumption'), $start, $end); + $feed = $this->GetDailyTotal($this->ReadPropertyInteger('VarFeedIn'), $start, $end); + $grid = $this->GetDailyTotal($this->ReadPropertyInteger('VarGrid'), $start, $end); + + $prodCons = $prod > 0 ? (($cons - $grid) / $prod) * 100 : 0; + $prodFeed = $prod > 0 ? 100 - $prodCons : 0; + $consPV = $cons > 0 ? min($prod, ($cons - $grid)) / $cons * 100 : 0; + $consGrid = $cons > 0 ? 100 - $consPV : 0; + + $data = [ + 'prodCons' => round($prodCons, 1), + 'prodFeed' => round($prodFeed, 1), + 'consPV' => round($consPV, 1), + 'consGrid' => round($consGrid, 1), + 'value' => [ + 'prod' => round($prod, 2), + 'cons' => round($cons, 2), + 'feed' => round($feed, 2), + 'grid' => round($grid, 2), + ], + ]; + + $json = json_encode($data); + SetValueString($this->GetIDForIdent('JSONData'), $json); + return $data; + } + + private function GetDailyTotal(int $varID, int $start, int $end) + { + if ($varID <= 0) return 0.0; + + $archiveID = @IPS_GetInstanceListByModuleID('{43192F0B-135B-4CE7-A0A7-1475603F3060}')[0]; + if (!$archiveID) return 0.0; + + $values = @AC_GetAggregatedValues($archiveID, $varID, 1, $start, $end, 1); + return isset($values[0]['Avg']) ? (float)$values[0]['Avg'] : 0.0; + } +} \ No newline at end of file diff --git a/PV_Visu/readme.md b/PV_Visu/readme.md new file mode 100644 index 0000000..bc8dd0b --- /dev/null +++ b/PV_Visu/readme.md @@ -0,0 +1,59 @@ +# PV_Visu + +Visualisierung des Eigenverbrauchs: Tages-Quoten für PV-Produktion vs. Einspeisung und Verbrauch vs. Netz-Bezug. + +## Inhaltsverzeichnis + +1. [Funktionsumfang](#funktionsumfang) +2. [Voraussetzungen](#voraussetzungen) +3. [Installation](#installation) +4. [Instanz einrichten](#instanz-einrichten) +5. [WebFront](#webfront) +6. [PHP-Befehlsreferenz](#php-befehlsreferenz) + +## Funktionsumfang + +- Anzeige von Tages-Quoten (%) +- Produktion: Eigenverbrauch vs. Einspeisung +- Verbrauch: PV-Anteil vs. Netz-Anteil +- Zwei Balkendiagramme +- Absolute Tages-Summen (kWh) + +## Voraussetzungen + +- IP-Symcon ≥ 7.1 +- Archiv-Modul aktiviert +- Vier kWh-Zähler-Variablen + +## Installation + +1. **Module Store** → Suche nach „PV_Visu“ und installieren +2. **Alternativ**: Unter Module → Repositories folgende URL hinzufügen: + ``` + https://github.com/DeinRepo/PV_Visu.git + ``` + und Modul neu einlesen. + +## Instanz einrichten + +- **Instanz hinzufügen** → Filter: „PV_Visu“ +- Variablen zuweisen: + + | Property | Beschreibung | + | -------------- | -------------------------- | + | VarProduction | PV-Produktionszähler (kWh) | + | VarConsumption | Gesamtverbrauch (kWh) | + | VarFeedIn | Einspeisung (kWh) | + | VarGrid | Netz-Bezug (kWh) | + +## WebFront + +- **Tile-Typ:** PV_Visu +- Balken 1 (Grün): Produktion +- Balken 2 (Orange/Rot): Verbrauch + +## PHP-Befehlsreferenz + +```php +IPS_RequestAction($InstanceID, 'update', true); +``` diff --git a/README.md b/README.md index 6d0656d..c4ad849 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,209 @@ -# Energiemanager_Symconmodule_Demo +# Symcon Belevo Energiemanagement Modul -Module zum Managen und Optimieren von Solarstrom und Lastmanagement innerhalb eines Symconservers \ No newline at end of file +Dieses Modul implementiert das neuartige Energiemanagementsystem der Belevo AG in IP-Symcon. +Es verteilt elektrische Leistung auf mehrere Verbraucher (z. B. Elektroauto-Ladestationen, Boiler, Speicher) dynamisch nach Priorität und Fairness. Unterstützt werden: + +- **Einzel­haus-Anschlüsse** +- **ZEV/V-ZEV-Verbundsysteme** +- **Lasten­management-Gemeinschaften (LEG)** + +--- + +## Besonderheiten & Vorteile + +- **Vollständige Skalierbarkeit** von Einfamilienhaus über ZEV/V-ZEV bis LEG +- **Standardisierte Schnittstelle** für Manager ↔ Verbraucher (kein ständiges Nachkonfigurieren) +- **Fairness** bei gleicher Priorität durch Round-Robin-Verteilung der Leistungsschritte +- **Bidirektionaler Betrieb** (z. B. Batteriespeicher, bidirektionale Ladestationen) +- **Dynamische Tarif­gestaltung** über Prioritätsklassen +- **Automatisches Peak-Shaving** ohne teure Netzspitzen +- **Einfaches Hinzufügen/Entfernen** von Verbrauchern (aktuell manuell, später automatisch möglich) + +--- + +## Voraussetzungen + +- IP-Symcon **≥ 8.0** +- Zugriff auf das Git-Repository + +--- + +## Installation + +1. In IP-Symcon **Module Control** öffnen +2. **Hinzufügen → Git-Repository** +3. URL eintragen: https://git.belevo.ch/dh/Symcon_Belevo_Energiemanagement_testing.git +4. Modul installieren und IP-Symcon neu starten + +--- + +## 1. Manager-Instanz anlegen + +1. Rechtsklick auf **Instanzen** → **Instanz hinzufügen** +2. Filter: **Belevo** +3. **„Manager“** auswählen und Instanz erstellen + +### Manager-Eigenschaften + +| Property | Typ | Beschreibung | +|------------------------------------|-----------------|------------------------------------------------------------------------------| +| **Zeit_Zwischen_Zustandswechseln** | Integer (Sek.) | Dauer zwischen zwei Zyklen (Default: 5 s) | +| **Interval** | Integer (Sek.) | Haupt-Timerintervall zur Neuberechnung | +| **Sollleistung_Max** | Float (Watt) | Maximale erlaubte Gesamtleistung am Netzanschluss | +| **Ueberschussleistung** | Float (Watt) | Untergrenze für Solarlade-Mode (z. B. 0 W) | +| **HauptmanagerAktiv** | Boolean | Schaltet die gesamte Manager-Logik ein/aus | +| **Verbraucher_Liste** | InstanceList | Liste aller Verbraucher-Instanzen, die der Manager steuern soll | + +### Manager-Statusvariablen + +| Ident | Typ | Beschreibung | +|-------------------------|-----------|------------------------------------------------------------------------------| +| **Is_Peak_Shaving** | Boolean | `true` = Peak-Shaving-Mode; `false` = Solarlade-Mode | +| **LetzteBerechnung** | DateTime | Zeitstempel der letzten Ausführung | +| **Aktuelle_Differenz** | Float (W) | Zuletzt berechnete Soll–Ist-Differenz | + +--- + +## 2. Verbraucher-Instanzen anlegen + +Für jedes zu steuernde Gerät: + +1. Rechtsklick auf **Instanzen** → **Instanz hinzufügen** +2. Filter: **Belevo** +3. **„Belevo EMS Verbraucher“** auswählen und Instanz erstellen + +### Verbraucher-Eigenschaften + +| Property | Typ | Beschreibung | +|-----------------|------------|----------------------------------------------------------------------------| +| **PV_Prio** | Integer | Priorität im Solarlade-Mode (niedriger = höhere Priorität) | +| **Sperre_Prio** | Integer | Priorität im Peak-Shaving-Mode (niedriger = höhere Priorität) | +| **PowerSteps** | String | JSON-Array möglicher Leistungsstufen in Watt, z. B. `[0,1000,2000]` | + +### Automatisch angelegte Variablen + +| Ident | Typ | Profil | Beschreibung | +|----------------------|----------|--------------|-----------------------------------------------------------------------| +| **Power** | Float | ~Watt~~W~ | Aktuell angeforderte Leistung (W) | +| **Aktuelle_Leistung**| Float | ~Watt~~W~ | Vom Manager zugewiesene Leistung im aktuellen Zyklus (W) | +| **Bezogene_Energie** | Float | Wh | Kumulierte Energieabnahme seit Zyklusstart (Wh) | +| **Leistung_Delta** | Float | ~Watt~~W~ | Differenz zwischen `Power` und `Aktuelle_Leistung` (W) | +| **Idle** | Boolean | — | `true`, wenn Verbraucher gerade keine Änderung benötigt | + +--- + +## 3. Funktionsweise + +### 3.1 Mode-Entscheid + +- In jedem Zyklus liest der Manager den **aktuellen Ist-Netzanschlusswert** (Summe aller `Aktuelle_Leistung`). +- Er berechnet den **Schwellwert** als +Threshold = (Sollleistung_Max + Ueberschussleistung) / 2 + +markdown +Kopieren +Bearbeiten +- Ist der Ist-Wert **> Threshold**, setzt er **Peak-Shaving** (`Is_Peak_Shaving = true`), sonst **Solarladen** (`false`). +- Diese Boolean teilt er allen Verbrauchern mit, damit sie ihre `PowerSteps`-Arrays entsprechend vorbereiten. + +### 3.2 Soll–Ist-Berechnung + +- Differenz **D** = `Sollleistung_Max` − Ist-Wert +- **D > 0**: Bedarf → Solarlade-Zuteilung +- **D < 0**: Überschuss → Peak-Shaving-Reduktion + +### 3.3 Leistungszuweisung + +- Für jeden Verbraucher wählt der Manager die **höchste** Stufe aus `PowerSteps`, die die verbleibende Differenz \|D\| **nicht überschreitet**. +- Er durchläuft die Verbraucher nacheinander (Round-Robin), bis \|D\| annähernd erschöpft ist. + +**Beispiel** +- Verbraucher A: `[0,1000,2000]` Prio 1 5 kWh aktuell 2000 W +- Verbraucher B: `[0,500,1500,2500]` Prio 2 0 kWh aktuell 500 W +- Verbraucher C: `[0,500,1500,4000]` Prio 2 10 kWh aktuell 0 W +- Verbraucher D: `[0,500,1200,2500]` Prio 2 15 kWh aktuell 0 W +- Verbraucher E: `[0,200,1500]` Prio 3 15 kWh aktuell 0 W + +--- + +#### Sortierte Stufen-Arrays + +**Prio 1** +[A0], [A1000], [A2000] + +**Prio 2** +[B0], [C0], [D0], +[B500], [C500], [D500], +[D1200], [B1500], [C1500], +[B2500], [D2500], [C4000] + + +**Prio 3** +[E0], [E200], [E1500] + + +--- + +#### Verteilungsschritte + +1. **Delta berechnen** + - Netzbezug = –5000 W + - Sollwert = 0 W + → Differenz D = 5000 W + +2. **Bereits verteilte Leistungen aufsummieren** + - A = 2000 W + - B = 500 W + → GesamtVerteilbareLeistung = 5000 + 2000 + 500 = **7500 W** + +3. **Prio 1** +7500 − 0 W (A0) = 7500 +7500 − 1000 W (A1000) = 6500 +(6500 + 1000 W vorher) − 2000 W (A2000) = 5500 + +→ A erhält **2000 W** +→ Rest: 5500 W + +4. **Prio 2** +5500 − 0 W (B0) = 5500 +5500 − 0 W (C0) = 5500 +5500 − 0 W (D0) = 5500 +5500 − 500 W (B500) = 5000 +5000 − 500 W (C500) = 4500 +4500 − 500 W (D500) = 4000 +(4000 + 500 W vorher) − 1200 W (D1200) = 3300 +(3300 + 500 W vorher) − 1500 W (B1500) = 2300 +(2300 + 500 W vorher) − 1500 W (C1500) = 1300 +(1300 + 1500 W vorher) − 2500 W (B2500) = 300 +(300 + 1500 W vorher) − 2500 W (C2500) → negativ → nicht möglich + +→ B erhält **2500 W**, C **1500 W**, D **1200 W** +→ Rest: 300 W + +5. **Prio 3** +300 − 0 W (E0) = 300 +300 − 200 W (E200) = 100 + +→ E erhält **200 W** +→ Verteilung beendet (Rest 100 W ungenutzt) + +--- + +Durch dieses Verfahren – Auf­addierung der bereits verteilten Leistungen, Sortierung nach Priorität und Fairness sowie sukzessive Abarbeitung der Stufen-Arrays – wird die **Gesamtleistung optimal und gerecht** auf alle Verbraucher verteilt. + + + + +--- + +## 4. Registrierung der Verbraucher + +- **Aktuell müssen alle Verbraucher manuell** in `Verbraucher_Liste` eingetragen werden. +- **Zukünftige Versionen** werden eine automatische Geräteerkennung unterstützen, so dass neue Verbraucher ohne Konfigurationsaufwand hinzukommen können. + +--- + +## 5. Support & Entwicklung + +- **Issues & Feature-Requests** bitte im Git-Repository eröffnen. +- **Entwickler**: Belevo AG diff --git a/Symcon_Publish_to_Shelly_MQTT/README.md b/Symcon_Publish_to_Shelly_MQTT/README.md new file mode 100644 index 0000000..e69de29 diff --git a/Symcon_Publish_to_Shelly_MQTT/form.json b/Symcon_Publish_to_Shelly_MQTT/form.json new file mode 100644 index 0000000..6e4e50d --- /dev/null +++ b/Symcon_Publish_to_Shelly_MQTT/form.json @@ -0,0 +1,61 @@ +{ + "elements": [ + { + "type": "ValidationTextBox", + "name": "broker_address", + "caption": "Broker-Adresse" + }, + { + "type": "NumberSpinner", + "name": "broker_port", + "caption": "Broker-Port" + }, + { + "type": "ValidationTextBox", + "name": "username", + "caption": "Benutzername" + }, + { + "type": "PasswordTextBox", + "name": "password", + "caption": "Passwort" + }, + { + "type": "ValidationTextBox", + "name": "Topic", + "caption": "MQTT Topic" + }, + { + "type": "ValidationTextBox", + "name": "client_id", + "caption": "Klient Id" + }, + { + "type": "ValidationTextBox", + "name": "mqtt_instance_id", + "caption": "MQTT Id" + }, + { + "type": "NumberSpinner", + "name": "msg_id", + "caption": "Message ID" + }, + { + "type": "ValidationTextBox", + "name": "src", + "caption": "Source" + }, + { + "type": "ValidationTextBox", + "name": "method", + "caption": "Method" + }, + { + "type": "SelectVariable", + "name": "switch_bool", + "caption": "Variable mit dem zu regelnden Shelly Kontakt", + "validVariableTypes": [0] + } + + ] +} diff --git a/Symcon_Publish_to_Shelly_MQTT/module.json b/Symcon_Publish_to_Shelly_MQTT/module.json new file mode 100644 index 0000000..e8676c4 --- /dev/null +++ b/Symcon_Publish_to_Shelly_MQTT/module.json @@ -0,0 +1,12 @@ +{ + "id": "{F6020D23-AA8C-34C5-D92F-9034BEFBBCD8}", + "name": "Symcon_Publish_to_Shelly_MQTT", + "type": 3, + "vendor": "Belevo AG", + "aliases": [], + "parentRequirements": [], + "childRequirements": [], + "implemented": [], + "prefix": "GEF", + "url": "" +} \ No newline at end of file diff --git a/Symcon_Publish_to_Shelly_MQTT/module.php b/Symcon_Publish_to_Shelly_MQTT/module.php new file mode 100644 index 0000000..a4f2b73 --- /dev/null +++ b/Symcon_Publish_to_Shelly_MQTT/module.php @@ -0,0 +1,90 @@ +RegisterPropertyString("broker_address", ""); + $this->RegisterPropertyInteger("broker_port", 1883); + $this->RegisterPropertyString("username", ""); + $this->RegisterPropertyString("password", ""); + $this->RegisterPropertyString("Topic", ""); + $this->RegisterPropertyString("client_id", ""); + $this->RegisterPropertyInteger("mqtt_instance_id", 0); + // Nachricht-Payload-Parameter als Properties + $this->RegisterPropertyInteger("msg_id", 100); + $this->RegisterPropertyString("src", "user1"); + $this->RegisterPropertyString("method", "Switch.Set"); + $this->RegisterPropertyInteger("switch_bool", 0); // ID der Bool-Variable + + + $this->RegisterTimer("Timer_Influx",5000,"IPS_RequestAction(" . $this->InstanceID . ', "GetAction", "");'); + } + + public function ApplyChanges() + { + parent::ApplyChanges(); + + } + public function RequestAction($Ident, $Value) + { + IPS_LogMessage("ShellySwitchSender", "RequestAction gestartet"); + switch ($Ident) { + case "GetAction": + $this->GetAction(); + break; + default: + throw new Exception("Invalid action"); + } + } + + + public function GetAction() + { + IPS_LogMessage("ShellySwitchSender", "GetAction gestartet"); + + $mqttInstanceID = $this->ReadPropertyInteger("mqtt_instance_id"); + $topic = $this->ReadPropertyString("Topic"); + + $msg_id = $this->ReadPropertyInteger("msg_id"); + $src = $this->ReadPropertyString("src"); + $method = $this->ReadPropertyString("method"); + $boolVarID = $this->ReadPropertyInteger("switch_bool"); + + if (!IPS_VariableExists($boolVarID)) { + IPS_LogMessage("ShellySwitchSender", "FEHLER: Bool-Variable mit ID $boolVarID existiert nicht."); + return; + } + + $onValue = GetValueBoolean($boolVarID); + + $payload = [ + "id" => 0, + "src" => $src, + "method" => $method, + "params" => [ + "id" => $msg_id, + "on" => $onValue + ] + ]; + + $jsonPayload = json_encode($payload); + + IPS_LogMessage("ShellySwitchSender", "MQTT Payload: $jsonPayload"); + + if (!IPS_InstanceExists($mqttInstanceID)) { + IPS_LogMessage("ShellySwitchSender", "FEHLER: MQTT-Instanz-ID $mqttInstanceID existiert nicht."); + return; + } + + // ✅ RICHTIG: Direkt senden über MQTTClient_SendMessage + MQTTClient_SendMessage($mqttInstanceID, $topic, $jsonPayload, 0, false); + IPS_LogMessage("ShellySwitchSender", "Nachricht erfolgreich gesendet"); + } + + + +}