From ec3ee01e52a297fd81f0b0572de71d6f902c53e5 Mon Sep 17 00:00:00 2001 From: "belevo\\mh" Date: Thu, 2 Oct 2025 13:48:09 +0200 Subject: [PATCH] no message --- Boiler_x_Stufig/README.md | 67 ++++++ Boiler_x_Stufig/form.json | 84 ++++++++ Boiler_x_Stufig/module.json | 12 ++ Boiler_x_Stufig/module.php | 396 ++++++++++++++++++++++++++++++++++++ 4 files changed, 559 insertions(+) create mode 100644 Boiler_x_Stufig/README.md create mode 100644 Boiler_x_Stufig/form.json create mode 100644 Boiler_x_Stufig/module.json create mode 100644 Boiler_x_Stufig/module.php diff --git a/Boiler_x_Stufig/README.md b/Boiler_x_Stufig/README.md new file mode 100644 index 0000000..b059e3a --- /dev/null +++ b/Boiler_x_Stufig/README.md @@ -0,0 +1,67 @@ +# Manager_1 +Beschreibung des Moduls. + +### Inhaltsverzeichnis + +1. [Funktionsumfang](#1-funktionsumfang) +2. [Voraussetzungen](#2-voraussetzungen) +3. [Software-Installation](#3-software-installation) +4. [Einrichten der Instanzen in IP-Symcon](#4-einrichten-der-instanzen-in-ip-symcon) +5. [Statusvariablen und Profile](#5-statusvariablen-und-profile) +6. [WebFront](#6-webfront) +7. [PHP-Befehlsreferenz](#7-php-befehlsreferenz) + +### 1. Funktionsumfang + +* + +### 2. Voraussetzungen + +- IP-Symcon ab Version 7.1 + +### 3. Software-Installation + +* Über den Module Store das 'Manager_1'-Modul installieren. +* Alternativ über das Module Control folgende URL hinzufügen + +### 4. Einrichten der Instanzen in IP-Symcon + + Unter 'Instanz hinzufügen' kann das 'Manager_1'-Modul mithilfe des Schnellfilters gefunden werden. + - Weitere Informationen zum Hinzufügen von Instanzen in der [Dokumentation der Instanzen](https://www.symcon.de/service/dokumentation/konzepte/instanzen/#Instanz_hinzufügen) + +__Konfigurationsseite__: + +Name | Beschreibung +-------- | ------------------ + | + | + +### 5. Statusvariablen und Profile + +Die Statusvariablen/Kategorien werden automatisch angelegt. Das Löschen einzelner kann zu Fehlfunktionen führen. + +#### Statusvariablen + +Name | Typ | Beschreibung +------ | ------- | ------------ + | | + | | + +#### Profile + +Name | Typ +------ | ------- + | + | + +### 6. WebFront + +Die Funktionalität, die das Modul im WebFront bietet. + +### 7. PHP-Befehlsreferenz + +`boolean GEF_BeispielFunktion(integer $InstanzID);` +Erklärung der Funktion. + +Beispiel: +`GEF_BeispielFunktion(12345);` \ No newline at end of file diff --git a/Boiler_x_Stufig/form.json b/Boiler_x_Stufig/form.json new file mode 100644 index 0000000..04fe1a3 --- /dev/null +++ b/Boiler_x_Stufig/form.json @@ -0,0 +1,84 @@ +{ + "elements": [ + { + "type": "Label", + "caption": "Konfiguration der nötigen Schaltkontakte und Nennleistungen" + }, + { + "type":"Select", + "name":"Boilertemperatur_glätten", + "caption":"Boilertemperatur glätten", + "options":[ + { + "caption":"Ja", + "value":true + }, + { + "caption":"Nein", + "value":false + } + ] + }, + { + "type": "NumberSpinner", + "name": "ZeitKonstante", + "caption": "Zeit Konstante", + "suffix": "" + }, + { + "type": "NumberSpinner", + "name": "Interval", + "caption": "Intervall Neuberechnung der Werte Erst für spätere Versionen, aktuell auf 5 lassen!", + "suffix": "Sekunden" + }, + { + "type": "NumberSpinner", + "name": "IdleCounterMax", + "caption": "Zyklen zwischen zwei Leistungsänderungen (Multipliziert sich mit Interval)", + "suffix": "" + }, + { + "type": "NumberSpinner", + "name": "Boilervolumen", + "caption": "Boilervolumen", + "suffix": "Liter" + }, + { + "type": "SelectVariable", + "name": "Boilerfuehler_PT1", + "caption": "Variable für Boilerfühler PT1", + "test": true + }, + { + "type": "List", + "name": "Zeitplan", + "caption": "Zeitplan für Solltemperaturen", + "columns": [ + { + "caption": "Uhrzeit", + "name": "Uhrzeit", + "width": "150px", + "add": "00:00", + "edit": { + "type": "ValidationTextBox" + } + }, + { + "caption": "Solltemperatur", + "name": "Solltemperatur", + "width": "150px", + "add": 0, + "edit": { + "type": "NumberSpinner" + } + } + ], + "add": true, + "delete": true, + "sort": { + "column": "Uhrzeit", + "direction": "ascending" + } + } + ] +} \ No newline at end of file diff --git a/Boiler_x_Stufig/module.json b/Boiler_x_Stufig/module.json new file mode 100644 index 0000000..ab11405 --- /dev/null +++ b/Boiler_x_Stufig/module.json @@ -0,0 +1,12 @@ +{ + "id": "{72B9606B-D5E6-6033-F2E4-0B2C5533F726}", + "name": "Boiler_x_Stufig", + "type": 3, + "vendor": "Belevo AG", + "aliases": [], + "parentRequirements": [], + "childRequirements": [], + "implemented": [], + "prefix": "GEF", + "url": "" +} \ No newline at end of file diff --git a/Boiler_x_Stufig/module.php b/Boiler_x_Stufig/module.php new file mode 100644 index 0000000..18ed327 --- /dev/null +++ b/Boiler_x_Stufig/module.php @@ -0,0 +1,396 @@ +RegisterPropertyString("LeistungsStufen", json_encode([])); // Bezeichnung der Liste + $this->RegisterPropertyInteger("ZeitKonstante", 120); + $this->RegisterPropertyInteger("Boilerfuehler_PT1", 0); + $this->RegisterPropertyBoolean("Boilertemperatur_glätten", false); + $this->RegisterPropertyInteger("Boilervolumen", 300); + $this->RegisterPropertyString("Zeitplan", ""); + $this->RegisterPropertyInteger("Interval", 5); // Recheninterval + + + // Boiler spezifische Variablen + + $this->RegisterVariableInteger("Mindesttemperatur","Mindesttemperatur","",45); + $this->RegisterVariableInteger("Maximaltemperatur","Maximaltemperatur","",60); + $this->RegisterVariableInteger("Legionellentemperatur","Legionellentemperatur","",65); + $this->RegisterVariableInteger("LegioCounter", "LegioCounter", "", 0); + $this->RegisterVariableInteger("Boilertemperatur", "Boilertemperatur", "", 0); + + + // Variabeln für Kommunkation mit Manager + $this->RegisterVariableInteger("Sperre_Prio", "Sperre_Prio"); + $this->RegisterVariableInteger("PV_Prio", "PV_Prio"); + $this->RegisterVariableBoolean("Idle", "Idle", "", 0); + $this->RegisterVariableInteger("Aktuelle_Leistung", "Aktuelle_Leistung", "", 0); + $this->RegisterVariableFloat("Bezogene_Energie", "Bezogene_Energie", "", 0); + $this->RegisterVariableString("PowerSteps", "PowerSteps"); + $this->RegisterVariableInteger("Power", "Power", '', 0); + $this->RegisterVariableBoolean("Is_Peak_Shaving", "Is_Peak_Shaving", "", true); + $this->RegisterVariableInteger("Leistung_Delta", "Leistung_Delta", "", 0); + + // Hilfsvariabeln für Idle zustand + $this->RegisterPropertyInteger("IdleCounterMax", 2); + $this->RegisterVariableInteger("IdleCounter", "IdleCounter", "", 0); + $this->SetValue("IdleCounter", 0); + + // Initialisiere Idle + $this->SetValue("Idle", true); + + $this->RegisterTimer("Timer_Do_UserCalc_Boiler",$this->ReadPropertyInteger("Interval")*1000,"IPS_RequestAction(" .$this->InstanceID .', "Do_UserCalc", "");'); + + } + + public function ApplyChanges() + { + parent::ApplyChanges(); + $this->SetTimerInterval("Timer_Do_UserCalc_Boiler",$this->ReadPropertyInteger("Interval")*1000); + + $this->LadeUndSortiereLeistungen(); + + } + + public function RequestAction($Ident, $Value) + { + switch ($Ident) { + + case "SetAktuelle_Leistung": + $this->SetValue("Power", (int)$Value); + break; + + case "GetCurrentData": + $this->SetValue("Is_Peak_Shaving", (bool)$Value); + break; + + case "Do_UserCalc": + + $this->SetAktuelle_Leistung($this->GetValue("Power")); + $this->GetCurrentData($this->GetValue("Is_Peak_Shaving")); + break; + + default: + throw new Exception("Invalid Ident"); + } + } + + private function LadeUndSortiereLeistungen() + { + // Array initialisieren, immer mit 0 + $this->leistungArray = [0]; + + // JSON aus der Property auslesen + $json = $this->ReadPropertyString("LeistungsStufen"); + $leistungsStufen = json_decode($json, true); + + if (is_array($leistungsStufen)) { + foreach ($leistungsStufen as $stufe) { + $this->leistungArray[] = $stufe['Leistung'] ?? 0; + } + } + + // Array sortieren (Bubble Sort) + $len = count($this->leistungArray); + for ($i = 0; $i < $len - 1; $i++) { + for ($j = 0; $j < $len - $i - 1; $j++) { + if ($this->leistungArray[$j] > $this->leistungArray[$j + 1]) { + $temp = $this->leistungArray[$j]; + $this->leistungArray[$j] = $this->leistungArray[$j + 1]; + $this->leistungArray[$j + 1] = $temp; + } + } + } + + } + + public function getNextTimeAndTemperature($zeitplan) { + $arr = json_decode($zeitplan, true); + if (empty($arr)) { + return null; + } + $currentTime = new DateTime(); + $nextEntry = null; + $minDiff = PHP_INT_MAX; + + foreach ($arr as $entry) { + $entryTime = DateTime::createFromFormat('H:i', $entry['Uhrzeit']); + if ($entryTime < $currentTime) { + $entryTime->modify('+1 day'); + } + $diff = $currentTime->diff($entryTime)->format('%r%a') * 24 * 60 + $currentTime->diff($entryTime)->format('%r%h') * 60 + $currentTime->diff($entryTime)->format('%r%i'); + if ($diff < $minDiff) { + $minDiff = $diff; + $nextEntry = $entry; + } + } + + return $nextEntry; + } + + public function calculateRemainingTime($nextTime) { + $currentTime = new DateTime(); + $nextDateTime = DateTime::createFromFormat('H:i', $nextTime); + if ($nextDateTime < $currentTime) { + $nextDateTime->modify('+1 day'); + } + $interval = $currentTime->diff($nextDateTime); + return $interval->h + ($interval->i / 60); + } + + public function calculateRequiredHeat($boilervolumen, $tempDiff) { + // Annahme: spezifische Wärmekapazität von Wasser = 4.186 J/g°C + // 1 Liter Wasser = 1000 Gramm + $specificHeatCapacity = 4.186; // J/g°C + $waterMass = $boilervolumen * 1000; // Gramm + return $specificHeatCapacity * $waterMass * $tempDiff; // Joules + } + + public function canBoilerReachTemperature($boilervolumen, $boilerTemper, $nextTemp, $remainingTime, $vollleistung) { + $tempDiff = $nextTemp - $boilerTemper; + $requiredHeat = $this->calculateRequiredHeat($boilervolumen, $tempDiff); + $availableHeat = $vollleistung * $remainingTime * 3600; // Leistung in Watt * Zeit in Sekunden + return $availableHeat >= $requiredHeat; + } + + // Methode zum Setzen des aktuellen Stromverbrauchs + public function SetAktuelle_Leistung(int $power) + { + // Lade sicherheitshalber das aktuelle LeistungArray + $this->LadeUndSortiereLeistungen(); + + // Schleife über alle Leistungsstufen + foreach ($this->leistungArray as $leistung) { + $kontaktID = $this->GetKontaktIDZuLeistung($leistung); + + // Prüfen, ob Variable existiert und gültige ID + if ($kontaktID > 0 && IPS_VariableExists($kontaktID)) { + // Setze TRUE für die aktuelle Leistungsstufe, FALSE für alle anderen + SetValue($kontaktID, ($leistung === $power)); + } else { + if ($kontaktID > 0) { + IPS_LogMessage("ERROR", "KontaktID $kontaktID existiert nicht oder ist ungültig!"); + } + } + } + + // Prüfe auf Änderung der Power im Vergleich zur letzten Einstellung + $lastPower = GetValue($this->GetIDForIdent("Aktuelle_Leistung")); + if ($power != $lastPower) { + $this->SetValue("Idle", false); + $this->SetValue( + "IdleCounter", + $this->ReadPropertyInteger("IdleCounterMax") + ); + } + + // Setze die neue Aktuelle_Leistung + $this->SetValue("Aktuelle_Leistung", $power); + $this->SetValue("Bezogene_Energie", ($this->GetValue("Bezogene_Energie") + ($this->GetValue("Aktuelle_Leistung")*($this->ReadPropertyInteger("Interval")/3600)))); + // IdleCounter verarbeiten + $this->ProcessIdleCounter(); + } + + private function GetKontaktIDZuLeistung(int $leistung): int + { + $json = $this->ReadPropertyString("LeistungsStufen"); + $leistungsStufen = json_decode($json, true); + + if (is_array($leistungsStufen)) { + foreach ($leistungsStufen as $stufe) { + if (($stufe['Leistung'] ?? 0) == $leistung) { + return $stufe['Schaltkontakt_Stufe'] ?? 0; + } + } + } + return 0; + } + + // Methode zum Abrufen der aktuellen Daten + public function GetCurrentData(bool $Peak) + { + $LegioCounter = $this->GetValue("LegioCounter"); + $this->LadeUndSortiereLeistungen(); + + $boilertemperatur_glätten = $this->ReadPropertyBoolean("Boilertemperatur_glätten"); + + if ($boilertemperatur_glätten) { + // Wenn Glättung aktiviert ist, führe das Glätten durch + $boilerFuehlerPT1ID = $this->ReadPropertyInteger("Boilerfuehler_PT1"); + + if (IPS_VariableExists($boilerFuehlerPT1ID)) { + $boilerPT1 = GetValue($boilerFuehlerPT1ID); + } else { + $boilerPT1 = 0.0; // Standardwert + } + + $boilerTempID = $this->GetIDForIdent("Boilertemperatur"); + if (IPS_VariableExists($boilerTempID)) { + $boilerTemp = $this->GetValue("Boilertemperatur"); + } else { + $boilerTemp = 0.0; // Standardwert + } + + // PT + $time_constant= $this->ReadPropertyInteger("ZeitKonstante"); + $delta_t = 5; // Zeitdifferenz zwischen den Messungen (30 Sekunden) + $alpha = $delta_t / ($time_constant + $delta_t); + $newBoilerTemp = $boilerTemp + $alpha * ($boilerPT1 - $boilerTemp); + $this->SetValue("Boilertemperatur", $newBoilerTemp); + } else { + // Wenn Glättung nicht aktiviert ist, setze die Boilertemperatur direkt auf den Wert des Boilerfühlers + $boilerFuehlerPT1ID = $this->ReadPropertyInteger("Boilerfuehler_PT1"); + + if (IPS_VariableExists($boilerFuehlerPT1ID)) { + $boilerPT1 = GetValue($boilerFuehlerPT1ID); + } else { + $boilerPT1 = 0.0; // Standardwert + } + + // Setze Boilertemperatur direkt auf den Wert des Boilerfühlers + $this->SetValue("Boilertemperatur", $boilerPT1); + } + + + $boilerTemp = $this->GetValue("Boilertemperatur"); + $minTemp = $this->GetValue("Mindesttemperatur"); + $maxTemp = $this->GetValue("Maximaltemperatur"); + $LegioTemp = $this->GetValue("Legionellentemperatur"); + + + $nextEntry = $this->getNextTimeAndTemperature($this->ReadPropertyString("Zeitplan")); + if ($nextEntry !== null) { + $remainingTime = $this->calculateRemainingTime($nextEntry['Uhrzeit']); + $nextTemp = $nextEntry['Solltemperatur']; + + if (!$this->canBoilerReachTemperature($this->ReadPropertyInteger("Boilervolumen"), $boilerTemp, $nextTemp, $remainingTime, $vollLeistung)) { + $minTemp = $nextTemp; + } + } + + + + + if ($boilerTemp > $LegioTemp) { + $LegioCounter = 0; + } else { + $LegioCounter = $LegioCounter + 1; + } + if ($LegioCounter > 69120) { + $maxTemp = $LegioTemp; + } + if ($LegioCounter > 120960 && $this->ist_nachts()) { + $minTemp = $LegioTemp; + } + if ($LegioCounter > 138240) { // Timeout für Legio wenn temperatur nicht erreicht werden kann, setze legionellenfunktion zurück + $LegioCounter = 0; + } + + $this->SetValue("LegioCounter", $LegioCounter); + + + if ($Peak) { + if ($boilerTemp < $minTemp) { + $this->SetValue("PowerSteps", json_encode($this->leistungArray)); + } elseif ( + $boilerTemp < $minTemp + 5 && + $this->IstEineStufeAktiv() + ) { + $this->SetValue("PowerSteps", json_encode($this->leistungArray)); + } else { + $this->SetValue("PowerSteps", json_encode([0])); + } + } else { + if ($boilerTemp < $minTemp) { + $this->SetValue("PowerSteps", json_encode([max($this->leistungArray)])); + + } elseif ( + $boilerTemp < $minTemp + 5 && + $this->IstEineStufeAktiv() + ) { + $this->SetValue("PowerSteps", json_encode([max($this->leistungArray)])); + + } elseif ($boilerTemp < $maxTemp - 5) { + $this->SetValue("PowerSteps", json_encode($this->leistungArray)); + } elseif ( $boilerTemp < $maxTemp && $this->IstEineStufeAktiv() + ) { + $this->SetValue("PowerSteps", json_encode($this->leistungArray)); + } else { + $this->SetValue("PowerSteps", json_encode([0])); + } + } + } + + private function IstEineStufeAktiv(): bool + { + $json = $this->ReadPropertyString("LeistungsStufen"); + $leistungsStufen = json_decode($json, true); + + if (is_array($leistungsStufen)) { + foreach ($leistungsStufen as $stufe) { + $kontaktID = $stufe['Schaltkontakt_Stufe'] ?? 0; + if ($kontaktID > 0 && IPS_VariableExists($kontaktID)) { + if (GetValue($kontaktID) === true) { + return true; // mindestens eine Stufe ist aktiv + } + } + } + } + return false; // keine aktiv + } + + private function ProcessIdleCounter() + { + // IdleCounter auslesen und verarbeiten + $idleCounter = $this->GetValue("IdleCounter"); + if ($idleCounter > 0) { + $this->SetValue("Idle", false); + $this->SetValue("IdleCounter", $idleCounter - 1); + } else { + $this->SetValue("Idle", true); + } + } + + private function CheckIdle($power) + { + $lastpower = GetValue("Aktuelle_Leistung"); + if ($lastpower != GetValue("Aktuelle_Leistung")) { + $this->SetValue("Idle", false); + $this->SetValue( + "IdleCounter", + $this->ReadPropertyInteger("IdleCounterMax") + ); + } + // IdleCounter auslesen und verarbeiten + $idleCounter = $this->GetValue("IdleCounter"); + if ($idleCounter > 0) { + $this->SetValue("Idle", false); + $this->SetValue("IdleCounter", $idleCounter - 1); + } else { + $this->SetValue("Idle", true); + } + } + + private function ist_nachts() + { + date_default_timezone_set("Europe/Berlin"); // Setze hier deine Zeitzone + + $aktuelle_zeit = strtotime(date("H:i")); // Aktuelle Zeit in Stunden und Minuten umwandeln + $start_nacht = strtotime("22:00"); // Startzeit der Nacht (22 Uhr) + $ende_nacht = strtotime("07:00"); // Endzeit der Nacht (7 Uhr) + + if ($aktuelle_zeit >= $start_nacht || $aktuelle_zeit < $ende_nacht) { + return true; + } else { + return false; + } + } +} + +?>