Wassererwaermer-Konfiguration vereinfachen
Tests / test (push) Successful in 46s

This commit is contained in:
dh
2026-09-17 08:29:49 +00:00
parent 9869d99641
commit 4514ccb557
8 changed files with 312 additions and 277 deletions
+47 -99
View File
@@ -1,9 +1,5 @@
{
"elements": [
{
"type": "Label",
"caption": "Mehrstufiger Warmwassererwaermer nach Enelix-2-Vertrag 3.0"
},
{
"type": "ExpansionPanel",
"caption": "Manager und Zeitverhalten",
@@ -12,13 +8,13 @@
{
"type": "NumberSpinner",
"name": "PrioritaetPV",
"caption": "Prioritaet PV",
"caption": "Priorität PV",
"minimum": 0
},
{
"type": "NumberSpinner",
"name": "PrioritaetPeak",
"caption": "Prioritaet Peak",
"caption": "Priorität Peak",
"minimum": 0
},
{
@@ -52,7 +48,7 @@
},
{
"type": "ExpansionPanel",
"caption": "Leistungsstufen",
"caption": "Speichereinstellungen",
"expanded": true,
"items": [
{
@@ -94,38 +90,37 @@
}
}
]
},
{
"type": "SelectVariable",
"name": "Boilerfuehler_PT1",
"caption": "Boilerfühler"
},
{
"type": "CheckBox",
"name": "LegionellenfunktionAktiv",
"caption": "Legionellenschaltung aktiv"
},
{
"type": "NumberSpinner",
"name": "LegionellenMinimalintervallTage",
"caption": "Frühestens nach",
"suffix": " Tagen",
"minimum": 1
},
{
"type": "NumberSpinner",
"name": "LegionellenMaximalintervallTage",
"caption": "Spätestens nach",
"suffix": " Tagen",
"minimum": 1
}
]
},
{
"type": "ExpansionPanel",
"caption": "Temperatur und Speicher",
"expanded": true,
"caption": "Erweiterte Speichereinstellungen",
"items": [
{
"type": "SelectVariable",
"name": "Boilerfuehler_PT1",
"caption": "Boilerfuehler"
},
{
"type": "NumberSpinner",
"name": "TemperaturMaxAlter",
"caption": "Maximales Messwertalter",
"suffix": " s",
"minimum": 1
},
{
"type": "CheckBox",
"name": "BoilertemperaturGlaetten",
"caption": "Boilertemperatur mit PT1 glaetten"
},
{
"type": "NumberSpinner",
"name": "ZeitKonstante",
"caption": "PT1-Zeitkonstante",
"suffix": " s",
"minimum": 1
},
{
"type": "NumberSpinner",
"name": "Boilervolumen",
@@ -141,21 +136,10 @@
"minimum": 0.1,
"digits": 1
},
{
"type": "CheckBox",
"name": "EinstellungenInVisu",
"caption": "Temperatursollwerte in der Visualisierung anzeigen und bedienen"
}
]
},
{
"type": "ExpansionPanel",
"caption": "Zeitplan",
"items": [
{
"type": "List",
"name": "Zeitplan",
"caption": "Naechste Solltemperatur vorausberechnen",
"caption": "Zeitplan für Solltemperaturen",
"add": true,
"delete": true,
"sortable": true,
@@ -179,7 +163,7 @@
"minimum": 0,
"maximum": 100,
"digits": 1,
"suffix": " C"
"suffix": " °C"
}
}
]
@@ -188,60 +172,36 @@
},
{
"type": "ExpansionPanel",
"caption": "Legionellenfunktion",
"caption": "Erweiterte sonstige Einstellungen",
"items": [
{
"type": "NumberSpinner",
"name": "TemperaturMaxAlter",
"caption": "Maximales Messwertalter",
"suffix": " s",
"minimum": 1
},
{
"type": "CheckBox",
"name": "LegionellenfunktionAktiv",
"caption": "Legionellenfunktion aktiv"
"name": "BoilertemperaturGlaetten",
"caption": "Boilertemperatur mit PT1 glätten"
},
{
"type": "NumberSpinner",
"name": "LegionellenIntervallTage",
"caption": "Intervall",
"suffix": " Tage",
"minimum": 1
},
{
"type": "NumberSpinner",
"name": "LegionellenHaltezeit",
"caption": "Haltezeit auf Legionellentemperatur",
"suffix": " s",
"minimum": 0
},
{
"type": "NumberSpinner",
"name": "LegionellenMaximaldauer",
"caption": "Maximaldauer",
"name": "ZeitKonstante",
"caption": "PT1-Zeitkonstante",
"suffix": " s",
"minimum": 1
},
{
"type": "NumberSpinner",
"name": "NachtstartStunde",
"caption": "Nachtbeginn",
"suffix": " Uhr",
"minimum": 0,
"maximum": 23
"type": "CheckBox",
"name": "EinstellungenInVisu",
"caption": "Temperatursollwerte in der Visualisierung anzeigen und bedienen"
},
{
"type": "NumberSpinner",
"name": "NachtendeStunde",
"caption": "Nachtende",
"suffix": " Uhr",
"minimum": 0,
"maximum": 23
}
]
},
{
"type": "ExpansionPanel",
"caption": "Diagnose",
"items": [
{
"type": "CheckBox",
"name": "DiagnosevariablenAnzeigen",
"caption": "Diagnosevariablen anzeigen"
"caption": "Diagnosevariablen anlegen"
},
{
"type": "CheckBox",
@@ -251,18 +211,6 @@
]
}
],
"actions": [
{
"type": "Button",
"caption": "Regelung jetzt ausfuehren",
"onClick": "IPS_RequestAction($id, 'Regelzyklus', false);"
},
{
"type": "Button",
"caption": "Aktuellen Status einmalig ins Debug-Log schreiben",
"onClick": "IPS_RequestAction($id, 'LogStatus', true);"
}
],
"status": [
{
"code": 102,
@@ -277,7 +225,7 @@
{
"code": 202,
"icon": "error",
"caption": "Konfiguration ungueltig"
"caption": "Konfiguration ungültig"
},
{
"code": 203,
+154 -153
View File
@@ -55,11 +55,8 @@ class VerbraucherWarmwassererwaermer extends IPSModule implements VerbraucherSch
$this->RegisterPropertyInteger('TemperaturMaxAlter', 30);
$this->RegisterPropertyFloat('Hysterese', 5.0);
$this->RegisterPropertyBoolean('LegionellenfunktionAktiv', true);
$this->RegisterPropertyInteger('LegionellenIntervallTage', 7);
$this->RegisterPropertyInteger('LegionellenHaltezeit', 0);
$this->RegisterPropertyInteger('LegionellenMaximaldauer', 86400);
$this->RegisterPropertyInteger('NachtstartStunde', 22);
$this->RegisterPropertyInteger('NachtendeStunde', 7);
$this->RegisterPropertyInteger('LegionellenMinimalintervallTage', 4);
$this->RegisterPropertyInteger('LegionellenMaximalintervallTage', 7);
$this->RegisterPropertyBoolean('DiagnosevariablenAnzeigen', false);
$this->RegisterVariableFloat('Boilertemperatur', 'Boilertemperatur', '~Temperature', 100);
@@ -73,31 +70,25 @@ class VerbraucherWarmwassererwaermer extends IPSModule implements VerbraucherSch
$this->EnableAction('Maximaltemperatur');
$this->EnableAction('Legionellentemperatur');
$this->RegisterVariableBoolean('TemperaturGueltig', 'Temperatur gueltig', '~Switch', 200);
$this->RegisterVariableBoolean('NachladenAktiv', 'Nachladen aktiv', '~Switch', 210);
$this->RegisterVariableBoolean(
'LegionellenbetriebAktiv',
'Legionellenbetrieb aktiv',
'~Switch',
220
);
$this->RegisterVariableInteger('LegioCounter', 'Zeit seit Legionellenabschluss', '', 230);
$this->RegisterVariableBoolean('Idle', 'Idle', '~Switch', 240);
$this->RegisterVariableInteger('IdleCounter', 'Idle Counter', '', 250);
$this->RegisterAttributeBoolean('InitialwerteGesetzt', false);
$this->RegisterAttributeInteger('RegistrierteFuehlerID', 0);
$this->RegisterAttributeInteger('LetzteBerechnung', 0);
$this->RegisterAttributeInteger('LetzteVorgabeZeit', 0);
$this->RegisterAttributeInteger('LetzteManagerID', 0);
$this->RegisterAttributeInteger('LetzterLegionellenabschluss', 0);
$this->RegisterAttributeInteger('LegionellenStart', 0);
$this->RegisterAttributeInteger('LegionellenSeitZiel', 0);
$this->RegisterAttributeFloat('Glaettungswert', 0.0);
$this->RegisterAttributeBoolean('GlaettungInitialisiert', false);
$this->RegisterAttributeString('Leistungsangebot', '[]');
$this->RegisterAttributeString('Schaltfehler', '');
$this->RegisterAttributeString('Legionellenfehler', '');
$this->RegisterAttributeBoolean('ZustandSollwertGueltig', false);
$this->RegisterAttributeBoolean('ZustandVerfuegbar', false);
$this->RegisterAttributeBoolean('ZustandAenderungMoeglich', false);
$this->RegisterAttributeBoolean('ZustandTemperaturGueltig', false);
$this->RegisterAttributeBoolean('ZustandNachladenAktiv', false);
$this->RegisterAttributeBoolean('ZustandLegionellenbetriebAktiv', false);
$this->RegisterAttributeInteger('ZustandLegioCounter', 0);
$this->RegisterAttributeBoolean('ZustandIdle', true);
$this->RegisterAttributeInteger('ZustandIdleCounter', 0);
$this->RegisterTimer(
'Regelzyklus',
@@ -121,7 +112,7 @@ class VerbraucherWarmwassererwaermer extends IPSModule implements VerbraucherSch
parent::ApplyChanges();
$this->initialisiereTemperaturen();
$this->aktualisiereSichtbarkeit();
$this->aktualisiereVariablen();
try {
$this->pruefeKonfiguration();
@@ -129,8 +120,8 @@ class VerbraucherWarmwassererwaermer extends IPSModule implements VerbraucherSch
$this->SetTimerInterval('Regelzyklus', 0);
$this->SetTimerInterval('Meldezyklus', 0);
$this->SetTimerInterval('RueckmeldungVerzoegert', 0);
$this->SetValue('Verfuegbar', false);
$this->SetValue('AenderungMoeglich', false);
$this->setzeZustand('Verfuegbar', false);
$this->setzeZustand('AenderungMoeglich', false);
$this->SetStatus(self::STATUS_KONFIGURATION_UNGUELTIG);
$this->protokolliere('Konfiguration', $fehler->getMessage());
return;
@@ -191,9 +182,6 @@ class VerbraucherWarmwassererwaermer extends IPSModule implements VerbraucherSch
$this->ManagerdatenEmpfangen($daten);
return;
case 'LogStatus':
$this->protokolliereStatus(true);
return;
}
throw new InvalidArgumentException('Unbekannte Aktion: ' . $ident);
@@ -222,7 +210,7 @@ class VerbraucherWarmwassererwaermer extends IPSModule implements VerbraucherSch
$this->WriteAttributeInteger('LetzteManagerID', $managerID);
$this->WriteAttributeInteger('LetzteVorgabeZeit', time());
$this->SetValue('Sollleistung', $sollleistung);
$this->SetValue('SollwertGueltig', true);
$this->setzeZustand('SollwertGueltig', true);
$this->regelzyklus(false);
// Verhindert eine synchrone Manager-Verbraucher-Endlosschleife.
@@ -240,24 +228,24 @@ class VerbraucherWarmwassererwaermer extends IPSModule implements VerbraucherSch
[$angebot, $aenderungMoeglich, $verfuegbar, $zwangsleistung] =
$this->bestimmeLeistungsangebot($temperaturGueltig, $jetzt);
$this->WriteAttributeString('Leistungsangebot', json_encode($angebot, JSON_THROW_ON_ERROR));
$this->SetValue('Verfuegbar', $verfuegbar);
$this->SetValue('AenderungMoeglich', $aenderungMoeglich);
$this->setzeZustand('Verfuegbar', $verfuegbar);
$this->setzeZustand('AenderungMoeglich', $aenderungMoeglich);
$letzteVorgabe = $this->ReadAttributeInteger('LetzteVorgabeZeit');
if ($letzteVorgabe <= 0
|| $jetzt - $letzteVorgabe > $this->ReadPropertyInteger('VorgabeTimeout')
) {
$this->SetValue('SollwertGueltig', false);
$this->setzeZustand('SollwertGueltig', false);
}
$ziel = 0;
if ($verfuegbar) {
if ($zwangsleistung !== null) {
$ziel = $zwangsleistung;
} elseif ((bool) $this->GetValue('SollwertGueltig')) {
} elseif ((bool) $this->leseZustand('SollwertGueltig')) {
$ziel = (int) $this->GetValue('Sollleistung');
if (!in_array($ziel, $angebot, true)) {
$this->SetValue('SollwertGueltig', false);
$this->setzeZustand('SollwertGueltig', false);
$ziel = 0;
}
}
@@ -271,8 +259,8 @@ class VerbraucherWarmwassererwaermer extends IPSModule implements VerbraucherSch
}
} catch (Throwable $fehler) {
$this->WriteAttributeString('Schaltfehler', $fehler->getMessage());
$this->SetValue('Verfuegbar', false);
$this->SetValue('AenderungMoeglich', false);
$this->setzeZustand('Verfuegbar', false);
$this->setzeZustand('AenderungMoeglich', false);
$this->SetValue('Stoerung', true);
$this->SetValue('Stoertext', $fehler->getMessage());
$this->SetStatus(self::STATUS_SCHALTFEHLER);
@@ -284,18 +272,18 @@ class VerbraucherWarmwassererwaermer extends IPSModule implements VerbraucherSch
{
$fuehlerID = $this->ReadPropertyInteger('Boilerfuehler_PT1');
if ($fuehlerID <= 0 || !IPS_VariableExists($fuehlerID)) {
$this->SetValue('TemperaturGueltig', false);
$this->setzeZustand('TemperaturGueltig', false);
return false;
}
$variable = IPS_GetVariable($fuehlerID);
if (time() - (int) $variable['VariableUpdated'] > $this->ReadPropertyInteger('TemperaturMaxAlter')) {
$this->SetValue('TemperaturGueltig', false);
$this->setzeZustand('TemperaturGueltig', false);
return false;
}
$rohwert = GetValue($fuehlerID);
if (!is_int($rohwert) && !is_float($rohwert)) {
$this->SetValue('TemperaturGueltig', false);
$this->setzeZustand('TemperaturGueltig', false);
return false;
}
@@ -318,7 +306,7 @@ class VerbraucherWarmwassererwaermer extends IPSModule implements VerbraucherSch
}
$this->SetValue('Boilertemperatur', $temperatur);
$this->SetValue('TemperaturGueltig', true);
$this->setzeZustand('TemperaturGueltig', true);
return true;
}
@@ -330,7 +318,7 @@ class VerbraucherWarmwassererwaermer extends IPSModule implements VerbraucherSch
{
$stufen = WarmwasserRegler::dekodiereLeistungsstufen($this->ReadPropertyString('LeistungsStufen'));
if (!(bool) $this->GetValue('Aktiv') || !$temperaturGueltig || $stufen === []) {
$this->SetValue('NachladenAktiv', false);
$this->setzeZustand('NachladenAktiv', false);
return [[], false, false, 0];
}
@@ -339,16 +327,25 @@ class VerbraucherWarmwassererwaermer extends IPSModule implements VerbraucherSch
$temperatur = (float) $this->GetValue('Boilertemperatur');
$minimum = (float) $this->GetValue('Mindesttemperatur');
$maximum = (float) $this->GetValue('Maximaltemperatur');
[$minimum, $maximum] = WarmwasserRegler::legionellenTemperaturgrenzen(
$this->ReadPropertyBoolean('LegionellenfunktionAktiv'),
max(0, $jetzt - $this->ReadAttributeInteger('LetzterLegionellenabschluss')),
$this->ReadPropertyInteger('LegionellenMinimalintervallTage'),
$this->ReadPropertyInteger('LegionellenMaximalintervallTage'),
$minimum,
$maximum,
(float) $this->GetValue('Legionellentemperatur')
);
$hysterese = $this->ReadPropertyFloat('Hysterese');
$aktuelleLeistung = (int) round((float) $this->GetValue('Istleistung'));
$nachladen = (bool) $this->GetValue('NachladenAktiv');
$nachladen = (bool) $this->leseZustand('NachladenAktiv');
if ($temperatur < $minimum) {
$nachladen = true;
} elseif ($temperatur >= $minimum + $hysterese) {
$nachladen = false;
}
$this->SetValue('NachladenAktiv', $nachladen);
$this->setzeZustand('NachladenAktiv', $nachladen);
$zeitplanErfordertHeizen = false;
$naechster = WarmwasserRegler::naechsterZeitplaneintrag(
@@ -365,7 +362,7 @@ class VerbraucherWarmwassererwaermer extends IPSModule implements VerbraucherSch
);
}
if ((bool) $this->GetValue('LegionellenbetriebAktiv') || $nachladen || $zeitplanErfordertHeizen) {
if ($nachladen || $zeitplanErfordertHeizen) {
return [[$maximalleistung], false, true, $maximalleistung];
}
if ($temperatur >= $maximum) {
@@ -380,54 +377,21 @@ class VerbraucherWarmwassererwaermer extends IPSModule implements VerbraucherSch
private function aktualisiereLegionellenstatus(bool $temperaturGueltig, int $jetzt): void
{
$aktiviert = $this->ReadPropertyBoolean('LegionellenfunktionAktiv');
$letzterAbschluss = $this->ReadAttributeInteger('LetzterLegionellenabschluss');
$this->SetValue('LegioCounter', max(0, $jetzt - $letzterAbschluss));
$sekundenSeitAbschluss = max(0, $jetzt - $letzterAbschluss);
if (!$this->ReadPropertyBoolean('LegionellenfunktionAktiv')) {
$this->SetValue('LegionellenbetriebAktiv', false);
$this->WriteAttributeInteger('LegionellenStart', 0);
$this->WriteAttributeInteger('LegionellenSeitZiel', 0);
$this->WriteAttributeString('Legionellenfehler', '');
return;
}
$aktiv = (bool) $this->GetValue('LegionellenbetriebAktiv');
$faellig = $jetzt - $letzterAbschluss >= $this->ReadPropertyInteger('LegionellenIntervallTage') * 86400;
if (!$aktiv && $faellig && $this->istNachtstunde((int) date('G', $jetzt))) {
$aktiv = true;
$this->WriteAttributeInteger('LegionellenStart', $jetzt);
$this->WriteAttributeInteger('LegionellenSeitZiel', 0);
}
if ($aktiv && $jetzt - $this->ReadAttributeInteger('LegionellenStart')
> $this->ReadPropertyInteger('LegionellenMaximaldauer')
) {
$aktiv = false;
$this->WriteAttributeInteger('LetzterLegionellenabschluss', $jetzt);
$this->WriteAttributeString(
'Legionellenfehler',
'Legionellenprogramm innerhalb der Maximaldauer nicht abgeschlossen.'
);
}
if ($aktiv && $temperaturGueltig
if ($aktiviert && $temperaturGueltig
&& (float) $this->GetValue('Boilertemperatur') >= (float) $this->GetValue('Legionellentemperatur')
) {
$seitZiel = $this->ReadAttributeInteger('LegionellenSeitZiel');
if ($seitZiel <= 0) {
$seitZiel = $jetzt;
$this->WriteAttributeInteger('LegionellenSeitZiel', $seitZiel);
}
if ($jetzt - $seitZiel >= $this->ReadPropertyInteger('LegionellenHaltezeit')) {
$aktiv = false;
$this->WriteAttributeInteger('LetzterLegionellenabschluss', $jetzt);
$this->WriteAttributeInteger('LegionellenStart', 0);
$this->WriteAttributeInteger('LegionellenSeitZiel', 0);
$this->WriteAttributeString('Legionellenfehler', '');
}
$this->WriteAttributeInteger('LetzterLegionellenabschluss', $jetzt);
$sekundenSeitAbschluss = 0;
}
$this->SetValue('LegionellenbetriebAktiv', $aktiv);
$fruehestensFaellig = $aktiviert
&& $sekundenSeitAbschluss >= $this->ReadPropertyInteger('LegionellenMinimalintervallTage') * 86400;
$this->setzeZustand('LegioCounter', $sekundenSeitAbschluss);
$this->setzeZustand('LegionellenbetriebAktiv', $fruehestensFaellig);
}
private function schalteLeistung(int $zielLeistung): void
@@ -468,7 +432,7 @@ class VerbraucherWarmwassererwaermer extends IPSModule implements VerbraucherSch
$this->WriteAttributeString('Schaltfehler', '');
$this->SetValue('Istleistung', (float) $zielLeistung);
$this->SetValue('Leistungsquelle', Nachrichtenvertrag::LEISTUNGSQUELLE_BERECHNET);
$this->setzeZustand('Leistungsquelle', Nachrichtenvertrag::LEISTUNGSQUELLE_BERECHNET);
$this->SetValue('AktiveStufe', $zielStufe);
$this->verarbeiteIdle(true);
$this->protokolliere('Leistungsstufe', ['Leistung_W' => $zielLeistung, 'Stufe' => $zielStufe]);
@@ -494,14 +458,14 @@ class VerbraucherWarmwassererwaermer extends IPSModule implements VerbraucherSch
private function verarbeiteIdle(bool $geaendert): void
{
$counter = (int) $this->GetValue('IdleCounter');
$counter = (int) $this->leseZustand('IdleCounter');
if ($geaendert) {
$counter = $this->ReadPropertyInteger('IdleCounterMax');
} elseif ($counter > 0) {
--$counter;
}
$this->SetValue('IdleCounter', $counter);
$this->SetValue('Idle', $counter === 0);
$this->setzeZustand('IdleCounter', $counter);
$this->setzeZustand('Idle', $counter === 0);
}
private function aktualisiereEnergie(int $jetzt): void
@@ -537,10 +501,6 @@ class VerbraucherWarmwassererwaermer extends IPSModule implements VerbraucherSch
if ($this->ReadAttributeString('Schaltfehler') !== '') {
$stoerungen[] = $this->ReadAttributeString('Schaltfehler');
}
if ($this->ReadAttributeString('Legionellenfehler') !== '') {
$stoerungen[] = $this->ReadAttributeString('Legionellenfehler');
}
$this->SetValue('Stoerung', $stoerungen !== []);
$this->SetValue('Stoertext', implode("\n", array_values(array_unique($stoerungen))));
if ($this->ReadAttributeString('Schaltfehler') !== '') {
@@ -572,9 +532,8 @@ class VerbraucherWarmwassererwaermer extends IPSModule implements VerbraucherSch
/** @return array<string, mixed> */
private function baueVerbraucherdaten(int $managerID): array
{
$temperaturGueltig = (bool) $this->GetValue('TemperaturGueltig');
$temperaturGueltig = (bool) $this->leseZustand('TemperaturGueltig');
$schaltfehler = $this->ReadAttributeString('Schaltfehler');
$legionellenfehler = $this->ReadAttributeString('Legionellenfehler');
$uebertemperatur = $temperaturGueltig
&& (float) $this->GetValue('Boilertemperatur')
> max(
@@ -592,15 +551,15 @@ class VerbraucherWarmwassererwaermer extends IPSModule implements VerbraucherSch
'PrioritaetPV' => $this->ReadPropertyInteger('PrioritaetPV'),
'PrioritaetPeak' => $this->ReadPropertyInteger('PrioritaetPeak'),
'Leistungswerte_W' => $this->leseLeistungsangebot(),
'AenderungMoeglich' => (bool) $this->GetValue('AenderungMoeglich'),
'Verfuegbar' => (bool) $this->GetValue('Verfuegbar'),
'AenderungMoeglich' => (bool) $this->leseZustand('AenderungMoeglich'),
'Verfuegbar' => (bool) $this->leseZustand('Verfuegbar'),
'Istleistung_W' => (float) $this->GetValue('Istleistung'),
'Leistungsquelle' => Nachrichtenvertrag::LEISTUNGSQUELLE_BERECHNET,
'Zustand' => [
[
'Kennung' => 'Sollleistung_W',
'Art' => 'Sollwert',
'Wert' => (bool) $this->GetValue('SollwertGueltig')
'Wert' => (bool) $this->leseZustand('SollwertGueltig')
? (int) $this->GetValue('Sollleistung')
: null,
'Einheit' => 'W',
@@ -626,13 +585,13 @@ class VerbraucherWarmwassererwaermer extends IPSModule implements VerbraucherSch
[
'Kennung' => 'NachladenAktiv',
'Art' => 'Status',
'Wert' => (bool) $this->GetValue('NachladenAktiv'),
'Wert' => (bool) $this->leseZustand('NachladenAktiv'),
'Einheit' => '',
],
[
'Kennung' => 'LegionellenbetriebAktiv',
'Art' => 'Status',
'Wert' => (bool) $this->GetValue('LegionellenbetriebAktiv'),
'Wert' => (bool) $this->leseZustand('LegionellenbetriebAktiv'),
'Einheit' => '',
],
[
@@ -656,13 +615,6 @@ class VerbraucherWarmwassererwaermer extends IPSModule implements VerbraucherSch
'Einheit' => '',
'Text' => $schaltfehler,
],
[
'Kennung' => 'Legionellenfehler',
'Art' => 'Stoerung',
'Wert' => $legionellenfehler !== '',
'Einheit' => '',
'Text' => $legionellenfehler,
],
],
];
}
@@ -709,25 +661,23 @@ class VerbraucherWarmwassererwaermer extends IPSModule implements VerbraucherSch
}
foreach ([
'Meldeintervall', 'VorgabeTimeout', 'Interval', 'IdleCounterMax', 'TemperaturMaxAlter',
'ZeitKonstante', 'Boilervolumen', 'LegionellenIntervallTage', 'LegionellenMaximaldauer',
'ZeitKonstante', 'Boilervolumen', 'LegionellenMinimalintervallTage',
'LegionellenMaximalintervallTage',
] as $property) {
if ($this->ReadPropertyInteger($property) <= 0) {
throw new InvalidArgumentException($property . ' muss groesser als 0 sein.');
}
}
if ($this->ReadPropertyInteger('LegionellenHaltezeit') < 0) {
throw new InvalidArgumentException('LegionellenHaltezeit darf nicht negativ sein.');
if ($this->ReadPropertyInteger('LegionellenMinimalintervallTage')
> $this->ReadPropertyInteger('LegionellenMaximalintervallTage')
) {
throw new InvalidArgumentException(
'Das Legionellen-Minimalintervall darf nicht groesser als das Maximalintervall sein.'
);
}
if ($this->ReadPropertyFloat('Hysterese') <= 0) {
throw new InvalidArgumentException('Hysterese muss groesser als 0 sein.');
}
foreach (['NachtstartStunde', 'NachtendeStunde'] as $property) {
$stunde = $this->ReadPropertyInteger($property);
if ($stunde < 0 || $stunde > 23) {
throw new InvalidArgumentException($property . ' muss zwischen 0 und 23 liegen.');
}
}
$minimum = (float) $this->GetValue('Mindesttemperatur');
$maximum = (float) $this->GetValue('Maximaltemperatur');
$legionellen = (float) $this->GetValue('Legionellentemperatur');
@@ -786,8 +736,8 @@ class VerbraucherWarmwassererwaermer extends IPSModule implements VerbraucherSch
$this->SetValue('Mindesttemperatur', 45.0);
$this->SetValue('Maximaltemperatur', 60.0);
$this->SetValue('Legionellentemperatur', 65.0);
$this->SetValue('Leistungsquelle', Nachrichtenvertrag::LEISTUNGSQUELLE_BERECHNET);
$this->SetValue('Idle', true);
$this->setzeZustand('Leistungsquelle', Nachrichtenvertrag::LEISTUNGSQUELLE_BERECHNET);
$this->setzeZustand('Idle', true);
$this->WriteAttributeBoolean('InitialwerteGesetzt', true);
}
@@ -823,11 +773,33 @@ class VerbraucherWarmwassererwaermer extends IPSModule implements VerbraucherSch
$this->regelzyklus(true);
}
private function aktualisiereSichtbarkeit(): void
private function aktualisiereVariablen(): void
{
$diagnose = $this->ReadPropertyBoolean('DiagnosevariablenAnzeigen');
foreach (self::DIAGNOSE_VARIABLEN as $ident) {
IPS_SetHidden($this->GetIDForIdent($ident), !$diagnose);
if ($this->ReadPropertyBoolean('DiagnosevariablenAnzeigen')) {
$this->registriereVerbraucherDiagnose();
$this->RegisterVariableBoolean('TemperaturGueltig', 'Temperatur gueltig', '~Switch', 200);
$this->RegisterVariableBoolean('NachladenAktiv', 'Nachladen aktiv', '~Switch', 210);
$this->RegisterVariableBoolean(
'LegionellenbetriebAktiv',
'Legionellenbetrieb aktiv',
'~Switch',
220
);
$this->RegisterVariableInteger('LegioCounter', 'Zeit seit Legionellenabschluss', '', 230);
$this->RegisterVariableBoolean('Idle', 'Idle', '~Switch', 240);
$this->RegisterVariableInteger('IdleCounter', 'Idle Counter', '', 250);
$this->setzeZustand('Leistungsquelle', Nachrichtenvertrag::LEISTUNGSQUELLE_BERECHNET);
foreach (array_slice(self::DIAGNOSE_VARIABLEN, 1) as $ident) {
$this->setzeZustand($ident, $this->leseZustand($ident));
}
} else {
foreach (self::DIAGNOSE_VARIABLEN as $ident) {
$variablenID = @IPS_GetObjectIDByIdent($ident, $this->InstanceID);
if (is_int($variablenID) && $variablenID > 0 && IPS_VariableExists($variablenID)) {
$this->UnregisterVariable($ident);
}
}
}
$einstellungen = $this->ReadPropertyBoolean('EinstellungenInVisu');
@@ -841,6 +813,66 @@ class VerbraucherWarmwassererwaermer extends IPSModule implements VerbraucherSch
}
}
/** @param bool|int $wert */
private function setzeZustand(string $ident, $wert): void
{
$booleanAttribute = [
'SollwertGueltig' => 'ZustandSollwertGueltig',
'Verfuegbar' => 'ZustandVerfuegbar',
'AenderungMoeglich' => 'ZustandAenderungMoeglich',
'TemperaturGueltig' => 'ZustandTemperaturGueltig',
'NachladenAktiv' => 'ZustandNachladenAktiv',
'LegionellenbetriebAktiv' => 'ZustandLegionellenbetriebAktiv',
'Idle' => 'ZustandIdle',
];
$integerAttribute = [
'LegioCounter' => 'ZustandLegioCounter',
'IdleCounter' => 'ZustandIdleCounter',
];
if (isset($booleanAttribute[$ident])) {
$wert = (bool) $wert;
$this->WriteAttributeBoolean($booleanAttribute[$ident], $wert);
} elseif (isset($integerAttribute[$ident])) {
$wert = (int) $wert;
$this->WriteAttributeInteger($integerAttribute[$ident], $wert);
} elseif ($ident !== 'Leistungsquelle') {
throw new LogicException('Unbekannter interner Zustand: ' . $ident);
}
$variablenID = @IPS_GetObjectIDByIdent($ident, $this->InstanceID);
if (is_int($variablenID) && $variablenID > 0 && IPS_VariableExists($variablenID)) {
SetValue($variablenID, $wert);
}
}
/** @return bool|int */
private function leseZustand(string $ident)
{
$booleanAttribute = [
'SollwertGueltig' => 'ZustandSollwertGueltig',
'Verfuegbar' => 'ZustandVerfuegbar',
'AenderungMoeglich' => 'ZustandAenderungMoeglich',
'TemperaturGueltig' => 'ZustandTemperaturGueltig',
'NachladenAktiv' => 'ZustandNachladenAktiv',
'LegionellenbetriebAktiv' => 'ZustandLegionellenbetriebAktiv',
'Idle' => 'ZustandIdle',
];
$integerAttribute = [
'LegioCounter' => 'ZustandLegioCounter',
'IdleCounter' => 'ZustandIdleCounter',
];
if (isset($booleanAttribute[$ident])) {
return $this->ReadAttributeBoolean($booleanAttribute[$ident]);
}
if (isset($integerAttribute[$ident])) {
return $this->ReadAttributeInteger($integerAttribute[$ident]);
}
throw new LogicException('Unbekannter interner Zustand: ' . $ident);
}
/** @return list<int> */
private function leseLeistungsangebot(): array
{
@@ -852,37 +884,6 @@ class VerbraucherWarmwassererwaermer extends IPSModule implements VerbraucherSch
return array_values(array_map('intval', $angebot));
}
private function istNachtstunde(int $stunde): bool
{
$start = $this->ReadPropertyInteger('NachtstartStunde');
$ende = $this->ReadPropertyInteger('NachtendeStunde');
if ($start === $ende) {
return true;
}
if ($start < $ende) {
return $stunde >= $start && $stunde < $ende;
}
return $stunde >= $start || $stunde < $ende;
}
private function protokolliereStatus(bool $erzwingen): void
{
if (!$erzwingen && !$this->ReadPropertyBoolean('LoggingEin')) {
return;
}
$this->SendDebug('Status', json_encode([
'Aktiv' => (bool) $this->GetValue('Aktiv'),
'Boilertemperatur_C' => (float) $this->GetValue('Boilertemperatur'),
'TemperaturGueltig' => (bool) $this->GetValue('TemperaturGueltig'),
'Leistungsangebot_W' => $this->leseLeistungsangebot(),
'Sollleistung_W' => (int) $this->GetValue('Sollleistung'),
'Istleistung_W' => (float) $this->GetValue('Istleistung'),
'AktiveStufe' => (int) $this->GetValue('AktiveStufe'),
'Stoertext' => (string) $this->GetValue('Stoertext'),
], JSON_THROW_ON_ERROR), 0);
}
/** @param mixed $daten */
private function protokolliere(string $bezeichnung, $daten): void
{
+10 -13
View File
@@ -25,7 +25,7 @@ Legionellenprogramm koennen die lokale Warmwassersicherheit priorisieren.
## Properties
Zu den sechs gemeinsamen Verbraucher-Properties aus
[`Schnittstelle.md`](../../Schnittstelle.md) kommen 17 Modul-Properties hinzu.
[`Schnittstelle.md`](../../Schnittstelle.md) kommen 14 Modul-Properties hinzu.
| Ident | Typ | Standard | Beschreibung |
| --- | --- | ---: | --- |
@@ -39,16 +39,12 @@ Zu den sechs gemeinsamen Verbraucher-Properties aus
| `IdleCounterMax` | Integer | `2` | Zyklen bis zum Idle-Status. |
| `TemperaturMaxAlter` | Integer | `30` | Maximal zulaessiges Messwertalter in Sekunden. |
| `Hysterese` | Float | `5.0` | Temperaturhysterese in K. |
| `LegionellenfunktionAktiv` | Boolean | `true` | Periodische Aufheizung aktiv. |
| `LegionellenIntervallTage` | Integer | `7` | Intervall seit erfolgreichem Abschluss. |
| `LegionellenHaltezeit` | Integer | `0` | Haltezeit auf Zieltemperatur in Sekunden. |
| `LegionellenMaximaldauer` | Integer | `86400` | Abbruchzeit in Sekunden. |
| `NachtstartStunde` | Integer | `22` | Fruehester Programmstart. |
| `NachtendeStunde` | Integer | `7` | Ende des Nachtfensters. |
| `DiagnosevariablenAnzeigen` | Boolean | `false` | Blendet zehn Diagnosevariablen ein. |
| `LegionellenfunktionAktiv` | Boolean | `true` | Zweistufige periodische Aufheizung aktiv. |
| `LegionellenMinimalintervallTage` | Integer | `4` | Ab diesem Intervall wird die Legionellentemperatur als Maximaltemperatur angeboten. |
| `LegionellenMaximalintervallTage` | Integer | `7` | Ab diesem Intervall wird die Legionellentemperatur zwingend nachgeladen. |
| `DiagnosevariablenAnzeigen` | Boolean | `false` | Legt zehn Diagnosevariablen bei Bedarf an. |
`LoggingEin` aktiviert laufende Debug-Ausgaben. Der Formular-Knopf schreibt
unabhaengig davon genau einen aktuellen Statusdatensatz ins Debug-Log.
`LoggingEin` aktiviert laufende Debug-Ausgaben.
## Variablen
@@ -87,9 +83,10 @@ verzoegert, damit keine synchrone Manager-Verbraucher-Endlosschleife entsteht.
## Tests
`WarmwasserReglerTest.php` prueft Leistungsstufen, PT1, Energieberechnung,
Zeitplan und thermische Prognose. `WarmwassererwaermerModulstrukturTest.php`
prueft Metadaten, Formular, 23 Properties, Sichtbarkeit, Schnittstellenvertrag
und Break-before-make-Schaltung.
Zeitplan, thermische Prognose und die zweistufigen Legionellengrenzen.
`WarmwassererwaermerModulstrukturTest.php` prueft Metadaten, Formular,
20 Properties, bedarfsgesteuerte Diagnosevariablen, Schnittstellenvertrag und
Break-before-make-Schaltung.
## Migration
+8 -4
View File
@@ -24,12 +24,16 @@ trait VerbraucherBasisTrait
$this->RegisterVariableBoolean('Aktiv', 'Aktiv', '~Switch', 10);
$this->EnableAction('Aktiv');
$this->RegisterVariableFloat('Istleistung', 'Istleistung', '', 20);
$this->RegisterVariableInteger('Leistungsquelle', 'Leistungsquelle', '', 30);
$this->RegisterVariableInteger('Sollleistung', 'Sollleistung', '', 40);
$this->RegisterVariableBoolean('SollwertGueltig', 'Sollwert gueltig', '~Switch', 50);
$this->RegisterVariableBoolean('Verfuegbar', 'Verfuegbar', '~Switch', 60);
$this->RegisterVariableBoolean('AenderungMoeglich', 'Aenderung moeglich', '~Switch', 70);
$this->RegisterVariableBoolean('Stoerung', 'Stoerung', '', 80);
$this->RegisterVariableString('Stoertext', 'Stoertext', '', 90);
}
protected function registriereVerbraucherDiagnose(): void
{
$this->RegisterVariableInteger('Leistungsquelle', 'Leistungsquelle', '', 30);
$this->RegisterVariableBoolean('SollwertGueltig', 'Sollwert gueltig', '~Switch', 50);
$this->RegisterVariableBoolean('Verfuegbar', 'Verfuegbar', '~Switch', 60);
$this->RegisterVariableBoolean('AenderungMoeglich', 'Aenderung moeglich', '~Switch', 70);
}
}
+26
View File
@@ -138,6 +138,32 @@ final class WarmwasserRegler
return $verfuegbareWaermeJ >= $erforderlicheWaermeJ;
}
/**
* @return array{0: float, 1: float}
*/
public static function legionellenTemperaturgrenzen(
bool $aktiv,
int $sekundenSeitAbschluss,
int $minimalintervallTage,
int $maximalintervallTage,
float $mindesttemperatur,
float $maximaltemperatur,
float $legionellentemperatur
): array {
if (!$aktiv) {
return [$mindesttemperatur, $maximaltemperatur];
}
if ($sekundenSeitAbschluss >= $minimalintervallTage * 86400) {
$maximaltemperatur = $legionellentemperatur;
}
if ($sekundenSeitAbschluss >= $maximalintervallTage * 86400) {
$mindesttemperatur = $legionellentemperatur;
}
return [$mindesttemperatur, $maximaltemperatur];
}
/**
* @return array{Zeitpunkt: int, Solltemperatur: float}|null
*/
+21 -4
View File
@@ -29,17 +29,29 @@ final class VerbraucherBasisTraitTest extends TestCase
[
'Aktiv',
'Istleistung',
'Leistungsquelle',
'Sollleistung',
'SollwertGueltig',
'Verfuegbar',
'AenderungMoeglich',
'Stoerung',
'Stoertext',
],
array_keys($modul->variablen)
);
self::assertSame(['Aktiv'], $modul->aktionen);
$modul->registriereDiagnose();
self::assertSame(
[
'Aktiv',
'Istleistung',
'Sollleistung',
'Stoerung',
'Stoertext',
'Leistungsquelle',
'SollwertGueltig',
'Verfuegbar',
'AenderungMoeglich',
],
array_keys($modul->variablen)
);
}
}
@@ -61,6 +73,11 @@ final class VerbraucherBasisDummy
$this->registriereVerbraucherBasis();
}
public function registriereDiagnose(): void
{
$this->registriereVerbraucherDiagnose();
}
public function RegisterPropertyInteger(string $ident, int $standard): void
{
$this->properties[$ident] = $standard;
+20
View File
@@ -79,4 +79,24 @@ final class WarmwasserReglerTest extends TestCase
);
self::assertSame(55.0, $eintrag['Solltemperatur']);
}
public function testLegionellenschaltungHebtTemperaturgrenzenZweistufigAn(): void
{
self::assertSame(
[45.0, 60.0],
WarmwasserRegler::legionellenTemperaturgrenzen(true, 3 * 86400, 4, 7, 45.0, 60.0, 65.0)
);
self::assertSame(
[45.0, 65.0],
WarmwasserRegler::legionellenTemperaturgrenzen(true, 4 * 86400, 4, 7, 45.0, 60.0, 65.0)
);
self::assertSame(
[65.0, 65.0],
WarmwasserRegler::legionellenTemperaturgrenzen(true, 7 * 86400, 4, 7, 45.0, 60.0, 65.0)
);
self::assertSame(
[45.0, 60.0],
WarmwasserRegler::legionellenTemperaturgrenzen(false, 8 * 86400, 4, 7, 45.0, 60.0, 65.0)
);
}
}
+26 -4
View File
@@ -29,14 +29,14 @@ final class WarmwassererwaermerModulstrukturTest extends TestCase
self::assertMatchesRegularExpression('/^\{[0-9A-F-]{36}\}$/', $metadaten['id']);
}
public function testAlle23PropertiesSindRegistriertUndImFormularDokumentiert(): void
public function testAlle20PropertiesSindRegistriertUndImFormularDokumentiert(): 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(23, $properties);
self::assertCount(20, $properties);
$formular = (string) file_get_contents(self::MODULPFAD . 'form.json');
foreach ($properties as $property) {
@@ -55,7 +55,7 @@ final class WarmwassererwaermerModulstrukturTest extends TestCase
self::assertStringNotContainsString("RegisterPropertyInteger('ManagerID'", $inhalt);
}
public function testZehnDiagnosevariablenSindOptionalUndLoggingIstGetrennt(): void
public function testZehnDiagnosevariablenWerdenBedarfsgesteuertAngelegtUndGeloescht(): void
{
$inhalt = (string) file_get_contents(self::MODULPFAD . 'module.php');
preg_match('/private const DIAGNOSE_VARIABLEN = \[(.*?)\];/s', $inhalt, $treffer);
@@ -64,8 +64,30 @@ final class WarmwassererwaermerModulstrukturTest extends TestCase
self::assertCount(10, $variablen[1]);
self::assertStringContainsString("ReadPropertyBoolean('DiagnosevariablenAnzeigen')", $inhalt);
self::assertStringContainsString('registriereVerbraucherDiagnose', $inhalt);
self::assertStringContainsString('UnregisterVariable($ident)', $inhalt);
self::assertStringContainsString("ReadPropertyBoolean('LoggingEin')", $inhalt);
self::assertStringContainsString("case 'LogStatus':", $inhalt);
self::assertStringNotContainsString("case 'LogStatus':", $inhalt);
}
public function testFormularIstWieGewuenschtGegliedertUndHatKeineAktionsknoepfe(): void
{
$formular = json_decode(
(string) file_get_contents(self::MODULPFAD . 'form.json'),
true,
512,
JSON_THROW_ON_ERROR
);
self::assertSame(
[
'Manager und Zeitverhalten',
'Speichereinstellungen',
'Erweiterte Speichereinstellungen',
'Erweiterte sonstige Einstellungen',
],
array_column($formular['elements'], 'caption')
);
self::assertArrayNotHasKey('actions', $formular);
}
public function testTemperatursollwerteSindGeprueftBedienbar(): void