diff --git a/CHANGELOG.md b/CHANGELOG.md index 38b7ecb..13743d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ Alle wesentlichen Aenderungen an Enelix EMS werden in dieser Datei dokumentiert. - Wartende Easee-Ladestationen erhalten bei positiver Stromvorgabe einmal pro Fahrzeugverbindung einen Startbefehl; Freigabe- und Blockiergruende werden diagnostiziert. - Fehlende oder veraltete Verbraucher blockieren die Verteilung an aktuelle Verbraucher nicht mehr. - Die Stoerungsueberwachung fordert einen fehlenden Geraetezugang innerhalb ihres Retry-Zyklus automatisch neu an. +- Der Warmwassererwaermer verwendet konfigurierbare Temperatur-Stoergrenzen von standardmaessig 0 bis 100 Grad C. - Vollstaendige PHP-, Struktur- und Symcon-Modultests fuer alle Module. ### Hinweise diff --git a/Warmwassererwaermer/form.json b/Warmwassererwaermer/form.json index 13fb5cf..d029f25 100644 --- a/Warmwassererwaermer/form.json +++ b/Warmwassererwaermer/form.json @@ -130,6 +130,20 @@ "minimum": 0.1, "digits": 1 }, + { + "type": "NumberSpinner", + "name": "TemperaturUntergrenze", + "caption": "Untertemperaturgrenze", + "suffix": " °C", + "digits": 1 + }, + { + "type": "NumberSpinner", + "name": "TemperaturObergrenze", + "caption": "Übertemperaturgrenze", + "suffix": " °C", + "digits": 1 + }, { "type": "List", "name": "Zeitplan", diff --git a/Warmwassererwaermer/module.php b/Warmwassererwaermer/module.php index 9a2b040..5ad6f7a 100644 --- a/Warmwassererwaermer/module.php +++ b/Warmwassererwaermer/module.php @@ -56,6 +56,8 @@ class VerbraucherWarmwassererwaermer extends IPSModule implements VerbraucherSch $this->RegisterPropertyString('Zeitplan', '[]'); $this->RegisterPropertyInteger('LastwechselSperrzeit', 5); $this->RegisterPropertyInteger('TemperaturMaxAlter', 30); + $this->RegisterPropertyFloat('TemperaturUntergrenze', 0.0); + $this->RegisterPropertyFloat('TemperaturObergrenze', 100.0); $this->RegisterPropertyFloat('Hysterese', 5.0); $this->RegisterPropertyBoolean('LegionellenfunktionAktiv', true); $this->RegisterPropertyInteger('LegionellenMinimalintervallTage', 4); @@ -537,6 +539,10 @@ class VerbraucherWarmwassererwaermer extends IPSModule implements VerbraucherSch if (!$temperaturGueltig) { $stoerungen[] = 'Boilertemperatur fehlt oder ist veraltet.'; } + $untertemperatur = $this->istUntertemperatur($temperaturGueltig); + if ($untertemperatur) { + $stoerungen[] = 'Untertemperatur erkannt.'; + } $uebertemperatur = $this->istUebertemperatur($temperaturGueltig); if ($uebertemperatur) { $stoerungen[] = 'Uebertemperatur erkannt.'; @@ -555,19 +561,18 @@ class VerbraucherWarmwassererwaermer extends IPSModule implements VerbraucherSch } } + private function istUntertemperatur(bool $temperaturGueltig): bool + { + return $temperaturGueltig + && (float) $this->GetValue('Boilertemperatur') + < $this->ReadPropertyFloat('TemperaturUntergrenze'); + } + private function istUebertemperatur(bool $temperaturGueltig): bool { - if (!$temperaturGueltig) { - return false; - } - - $grenze = (float) $this->GetValue('Maximaltemperatur'); - if ($this->ReadPropertyBoolean('LegionellenfunktionAktiv')) { - $grenze = max($grenze, $this->ReadAttributeFloat('ZustandLegionellentemperatur')); - } - - return (float) $this->GetValue('Boilertemperatur') - > $grenze + $this->ReadPropertyFloat('Hysterese'); + return $temperaturGueltig + && (float) $this->GetValue('Boilertemperatur') + > $this->ReadPropertyFloat('TemperaturObergrenze'); } private function sendeVerbraucherdaten(): void @@ -592,6 +597,7 @@ class VerbraucherWarmwassererwaermer extends IPSModule implements VerbraucherSch { $temperaturGueltig = (bool) $this->leseZustand('TemperaturGueltig'); $schaltfehler = $this->ReadAttributeString('Schaltfehler'); + $untertemperatur = $this->istUntertemperatur($temperaturGueltig); $uebertemperatur = $this->istUebertemperatur($temperaturGueltig); return [ @@ -655,6 +661,13 @@ class VerbraucherWarmwassererwaermer extends IPSModule implements VerbraucherSch 'Einheit' => '', 'Text' => $temperaturGueltig ? '' : 'Boilertemperatur fehlt oder ist veraltet.', ], + [ + 'Kennung' => 'Untertemperatur', + 'Art' => 'Stoerung', + 'Wert' => $untertemperatur, + 'Einheit' => '', + 'Text' => $untertemperatur ? 'Untertemperatur erkannt.' : '', + ], [ 'Kennung' => 'Uebertemperatur', 'Art' => 'Stoerung', @@ -745,6 +758,13 @@ class VerbraucherWarmwassererwaermer extends IPSModule implements VerbraucherSch if ($this->ReadPropertyFloat('Hysterese') <= 0) { throw new InvalidArgumentException('Hysterese muss groesser als 0 sein.'); } + if ($this->ReadPropertyFloat('TemperaturUntergrenze') + >= $this->ReadPropertyFloat('TemperaturObergrenze') + ) { + throw new InvalidArgumentException( + 'Die Temperaturuntergrenze muss kleiner als die Temperaturobergrenze sein.' + ); + } $minimum = (float) $this->GetValue('Mindesttemperatur'); $maximum = (float) $this->GetValue('Maximaltemperatur'); $legionellen = (float) $this->ReadAttributeFloat('ZustandLegionellentemperatur'); diff --git a/docs/migration/Boiler-x-Stufig.md b/docs/migration/Boiler-x-Stufig.md index ac75462..02525e4 100644 --- a/docs/migration/Boiler-x-Stufig.md +++ b/docs/migration/Boiler-x-Stufig.md @@ -13,9 +13,11 @@ | `Interval` | entfaellt | Die Regelung wird ereignisbasiert ausgeloest. | | `IdleCounterMax` | `LastwechselSperrzeit` | Zykluszaehler wird durch eine Sperrzeit in Sekunden ersetzt; Standard `5`. | -Die neuen Properties `TemperaturMaxAlter`, `Hysterese`, die Parameter der -Legionellenfunktion und `DiagnosevariablenAnzeigen` ersetzen feste Werte und -nicht pruefbare Annahmen des Altmoduls. +Die neuen Properties `TemperaturMaxAlter`, `TemperaturUntergrenze`, +`TemperaturObergrenze`, `Hysterese`, die Parameter der Legionellenfunktion und +`DiagnosevariablenAnzeigen` ersetzen feste Werte und nicht pruefbare Annahmen +des Altmoduls. Die Stoergrenzen liegen standardmaessig bei 0 und 100 Grad C und +sind von den Regel-Sollwerten `Mindesttemperatur` und `Maximaltemperatur` getrennt. ## Variablen diff --git a/tests/Symcon/modules/Warmwassererwaermer.php b/tests/Symcon/modules/Warmwassererwaermer.php index d9a42f0..e12602d 100644 --- a/tests/Symcon/modules/Warmwassererwaermer.php +++ b/tests/Symcon/modules/Warmwassererwaermer.php @@ -31,6 +31,8 @@ return static function (TestContext $test): void { 'LastwechselSperrzeit' => 1, 'Hysterese' => 5.0, 'TemperaturMaxAlter' => 120, + 'TemperaturUntergrenze' => 0.0, + 'TemperaturObergrenze' => 100.0, 'LegionellenfunktionAktiv' => true, 'LegionellenMinimalintervallTage' => 4, 'LegionellenMaximalintervallTage' => 7, @@ -90,6 +92,24 @@ return static function (TestContext $test): void { IPS_RequestAction($managerId, 'Regeln', true); }; + $test->runCase('Temperaturgrenzen melden Unter- und Uebertemperatur', static function ( + TestContext $test + ) use ($verbraucherId, $temperaturSetzen): void { + $stoertextId = $test->objectByIdent('Stoertext', $verbraucherId); + + $temperaturSetzen(100.0); + $test->assertTrue(!str_contains((string) GetValue($stoertextId), 'Uebertemperatur')); + $temperaturSetzen(100.1); + $test->assertTrue(str_contains((string) GetValue($stoertextId), 'Uebertemperatur')); + + $temperaturSetzen(0.0); + $test->assertTrue(!str_contains((string) GetValue($stoertextId), 'Untertemperatur')); + $temperaturSetzen(-0.1); + $test->assertTrue(str_contains((string) GetValue($stoertextId), 'Untertemperatur')); + + $temperaturSetzen(52.0); + }); + if ($lizenzcode === '') { $test->runCase('Manager bleibt ohne Lizenz sicher gesperrt', static function ( TestContext $test diff --git a/tests/WarmwassererwaermerModulstrukturTest.php b/tests/WarmwassererwaermerModulstrukturTest.php index 7996f38..752cde7 100644 --- a/tests/WarmwassererwaermerModulstrukturTest.php +++ b/tests/WarmwassererwaermerModulstrukturTest.php @@ -29,14 +29,14 @@ final class WarmwassererwaermerModulstrukturTest extends TestCase self::assertMatchesRegularExpression('/^\{[0-9A-F-]{36}\}$/', $metadaten['id']); } - public function testAlle19PropertiesSindRegistriertUndImFormularDokumentiert(): void + public function testAlle21PropertiesSindRegistriertUndImFormularDokumentiert(): void { $basis = (string) file_get_contents(__DIR__ . '/../libs/VerbraucherBasisTrait.php'); $modul = (string) file_get_contents(self::MODULPFAD . 'module.php'); preg_match_all('/RegisterProperty(?:Integer|Float|Boolean|String)\(\'([^\']+)\'/', $basis . $modul, $treffer); $properties = array_values(array_unique($treffer[1])); - self::assertCount(19, $properties); + self::assertCount(21, $properties); $formular = (string) file_get_contents(self::MODULPFAD . 'form.json'); foreach ($properties as $property) { @@ -136,6 +136,38 @@ final class WarmwassererwaermerModulstrukturTest extends TestCase self::assertStringContainsString('Mindesttemperatur < Maximaltemperatur', $inhalt); } + public function testTemperaturstoergrenzenSindKonfigurierbarUndVonSollwertenGetrennt(): void + { + $inhalt = (string) file_get_contents(self::MODULPFAD . 'module.php'); + self::assertStringContainsString( + "RegisterPropertyFloat('TemperaturUntergrenze', 0.0)", + $inhalt + ); + self::assertStringContainsString( + "RegisterPropertyFloat('TemperaturObergrenze', 100.0)", + $inhalt + ); + self::assertStringContainsString("ReadPropertyFloat('TemperaturUntergrenze')", $inhalt); + self::assertStringContainsString("ReadPropertyFloat('TemperaturObergrenze')", $inhalt); + self::assertStringContainsString("'Kennung' => 'Untertemperatur'", $inhalt); + self::assertStringContainsString("'Kennung' => 'Uebertemperatur'", $inhalt); + + $formular = json_decode( + (string) file_get_contents(self::MODULPFAD . 'form.json'), + true, + 512, + JSON_THROW_ON_ERROR + ); + $erweitert = array_values(array_filter( + $formular['elements'], + static fn (array $element): bool => + $element['caption'] === 'Erweiterte Speichereinstellungen' + )); + self::assertCount(1, $erweitert); + self::assertContains('TemperaturUntergrenze', array_column($erweitert[0]['items'], 'name')); + self::assertContains('TemperaturObergrenze', array_column($erweitert[0]['items'], 'name')); + } + public function testSchaltfolgeIstBreakBeforeMake(): void { $inhalt = (string) file_get_contents(self::MODULPFAD . 'module.php');