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. [Statusvariablen & 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. Statusvariablen & 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 @@ + + +
+ + + + +