feat(warmwasser): Temperaturgrenzen konfigurierbar machen
Tests / test (push) Successful in 1m10s

This commit is contained in:
dh
2026-09-29 12:11:43 +00:00
parent fe966516b5
commit a74f14aa19
6 changed files with 105 additions and 16 deletions
+1
View File
@@ -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. - 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. - 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. - 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. - Vollstaendige PHP-, Struktur- und Symcon-Modultests fuer alle Module.
### Hinweise ### Hinweise
+14
View File
@@ -130,6 +130,20 @@
"minimum": 0.1, "minimum": 0.1,
"digits": 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", "type": "List",
"name": "Zeitplan", "name": "Zeitplan",
+31 -11
View File
@@ -56,6 +56,8 @@ class VerbraucherWarmwassererwaermer extends IPSModule implements VerbraucherSch
$this->RegisterPropertyString('Zeitplan', '[]'); $this->RegisterPropertyString('Zeitplan', '[]');
$this->RegisterPropertyInteger('LastwechselSperrzeit', 5); $this->RegisterPropertyInteger('LastwechselSperrzeit', 5);
$this->RegisterPropertyInteger('TemperaturMaxAlter', 30); $this->RegisterPropertyInteger('TemperaturMaxAlter', 30);
$this->RegisterPropertyFloat('TemperaturUntergrenze', 0.0);
$this->RegisterPropertyFloat('TemperaturObergrenze', 100.0);
$this->RegisterPropertyFloat('Hysterese', 5.0); $this->RegisterPropertyFloat('Hysterese', 5.0);
$this->RegisterPropertyBoolean('LegionellenfunktionAktiv', true); $this->RegisterPropertyBoolean('LegionellenfunktionAktiv', true);
$this->RegisterPropertyInteger('LegionellenMinimalintervallTage', 4); $this->RegisterPropertyInteger('LegionellenMinimalintervallTage', 4);
@@ -537,6 +539,10 @@ class VerbraucherWarmwassererwaermer extends IPSModule implements VerbraucherSch
if (!$temperaturGueltig) { if (!$temperaturGueltig) {
$stoerungen[] = 'Boilertemperatur fehlt oder ist veraltet.'; $stoerungen[] = 'Boilertemperatur fehlt oder ist veraltet.';
} }
$untertemperatur = $this->istUntertemperatur($temperaturGueltig);
if ($untertemperatur) {
$stoerungen[] = 'Untertemperatur erkannt.';
}
$uebertemperatur = $this->istUebertemperatur($temperaturGueltig); $uebertemperatur = $this->istUebertemperatur($temperaturGueltig);
if ($uebertemperatur) { if ($uebertemperatur) {
$stoerungen[] = 'Uebertemperatur erkannt.'; $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 private function istUebertemperatur(bool $temperaturGueltig): bool
{ {
if (!$temperaturGueltig) { return $temperaturGueltig
return false; && (float) $this->GetValue('Boilertemperatur')
} > $this->ReadPropertyFloat('TemperaturObergrenze');
$grenze = (float) $this->GetValue('Maximaltemperatur');
if ($this->ReadPropertyBoolean('LegionellenfunktionAktiv')) {
$grenze = max($grenze, $this->ReadAttributeFloat('ZustandLegionellentemperatur'));
}
return (float) $this->GetValue('Boilertemperatur')
> $grenze + $this->ReadPropertyFloat('Hysterese');
} }
private function sendeVerbraucherdaten(): void private function sendeVerbraucherdaten(): void
@@ -592,6 +597,7 @@ class VerbraucherWarmwassererwaermer extends IPSModule implements VerbraucherSch
{ {
$temperaturGueltig = (bool) $this->leseZustand('TemperaturGueltig'); $temperaturGueltig = (bool) $this->leseZustand('TemperaturGueltig');
$schaltfehler = $this->ReadAttributeString('Schaltfehler'); $schaltfehler = $this->ReadAttributeString('Schaltfehler');
$untertemperatur = $this->istUntertemperatur($temperaturGueltig);
$uebertemperatur = $this->istUebertemperatur($temperaturGueltig); $uebertemperatur = $this->istUebertemperatur($temperaturGueltig);
return [ return [
@@ -655,6 +661,13 @@ class VerbraucherWarmwassererwaermer extends IPSModule implements VerbraucherSch
'Einheit' => '', 'Einheit' => '',
'Text' => $temperaturGueltig ? '' : 'Boilertemperatur fehlt oder ist veraltet.', 'Text' => $temperaturGueltig ? '' : 'Boilertemperatur fehlt oder ist veraltet.',
], ],
[
'Kennung' => 'Untertemperatur',
'Art' => 'Stoerung',
'Wert' => $untertemperatur,
'Einheit' => '',
'Text' => $untertemperatur ? 'Untertemperatur erkannt.' : '',
],
[ [
'Kennung' => 'Uebertemperatur', 'Kennung' => 'Uebertemperatur',
'Art' => 'Stoerung', 'Art' => 'Stoerung',
@@ -745,6 +758,13 @@ class VerbraucherWarmwassererwaermer extends IPSModule implements VerbraucherSch
if ($this->ReadPropertyFloat('Hysterese') <= 0) { if ($this->ReadPropertyFloat('Hysterese') <= 0) {
throw new InvalidArgumentException('Hysterese muss groesser als 0 sein.'); 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'); $minimum = (float) $this->GetValue('Mindesttemperatur');
$maximum = (float) $this->GetValue('Maximaltemperatur'); $maximum = (float) $this->GetValue('Maximaltemperatur');
$legionellen = (float) $this->ReadAttributeFloat('ZustandLegionellentemperatur'); $legionellen = (float) $this->ReadAttributeFloat('ZustandLegionellentemperatur');
+5 -3
View File
@@ -13,9 +13,11 @@
| `Interval` | entfaellt | Die Regelung wird ereignisbasiert ausgeloest. | | `Interval` | entfaellt | Die Regelung wird ereignisbasiert ausgeloest. |
| `IdleCounterMax` | `LastwechselSperrzeit` | Zykluszaehler wird durch eine Sperrzeit in Sekunden ersetzt; Standard `5`. | | `IdleCounterMax` | `LastwechselSperrzeit` | Zykluszaehler wird durch eine Sperrzeit in Sekunden ersetzt; Standard `5`. |
Die neuen Properties `TemperaturMaxAlter`, `Hysterese`, die Parameter der Die neuen Properties `TemperaturMaxAlter`, `TemperaturUntergrenze`,
Legionellenfunktion und `DiagnosevariablenAnzeigen` ersetzen feste Werte und `TemperaturObergrenze`, `Hysterese`, die Parameter der Legionellenfunktion und
nicht pruefbare Annahmen des Altmoduls. `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 ## Variablen
@@ -31,6 +31,8 @@ return static function (TestContext $test): void {
'LastwechselSperrzeit' => 1, 'LastwechselSperrzeit' => 1,
'Hysterese' => 5.0, 'Hysterese' => 5.0,
'TemperaturMaxAlter' => 120, 'TemperaturMaxAlter' => 120,
'TemperaturUntergrenze' => 0.0,
'TemperaturObergrenze' => 100.0,
'LegionellenfunktionAktiv' => true, 'LegionellenfunktionAktiv' => true,
'LegionellenMinimalintervallTage' => 4, 'LegionellenMinimalintervallTage' => 4,
'LegionellenMaximalintervallTage' => 7, 'LegionellenMaximalintervallTage' => 7,
@@ -90,6 +92,24 @@ return static function (TestContext $test): void {
IPS_RequestAction($managerId, 'Regeln', true); 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 === '') { if ($lizenzcode === '') {
$test->runCase('Manager bleibt ohne Lizenz sicher gesperrt', static function ( $test->runCase('Manager bleibt ohne Lizenz sicher gesperrt', static function (
TestContext $test TestContext $test
+34 -2
View File
@@ -29,14 +29,14 @@ final class WarmwassererwaermerModulstrukturTest extends TestCase
self::assertMatchesRegularExpression('/^\{[0-9A-F-]{36}\}$/', $metadaten['id']); 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'); $basis = (string) file_get_contents(__DIR__ . '/../libs/VerbraucherBasisTrait.php');
$modul = (string) file_get_contents(self::MODULPFAD . 'module.php'); $modul = (string) file_get_contents(self::MODULPFAD . 'module.php');
preg_match_all('/RegisterProperty(?:Integer|Float|Boolean|String)\(\'([^\']+)\'/', $basis . $modul, $treffer); preg_match_all('/RegisterProperty(?:Integer|Float|Boolean|String)\(\'([^\']+)\'/', $basis . $modul, $treffer);
$properties = array_values(array_unique($treffer[1])); $properties = array_values(array_unique($treffer[1]));
self::assertCount(19, $properties); self::assertCount(21, $properties);
$formular = (string) file_get_contents(self::MODULPFAD . 'form.json'); $formular = (string) file_get_contents(self::MODULPFAD . 'form.json');
foreach ($properties as $property) { foreach ($properties as $property) {
@@ -136,6 +136,38 @@ final class WarmwassererwaermerModulstrukturTest extends TestCase
self::assertStringContainsString('Mindesttemperatur < Maximaltemperatur', $inhalt); 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 public function testSchaltfolgeIstBreakBeforeMake(): void
{ {
$inhalt = (string) file_get_contents(self::MODULPFAD . 'module.php'); $inhalt = (string) file_get_contents(self::MODULPFAD . 'module.php');