fix(verbraucher): clear discarded power setpoints
Tests / test (push) Failing after 51s

This commit is contained in:
dh
2026-10-06 13:45:54 +00:00
parent ebec355c96
commit 5437f3f058
18 changed files with 296 additions and 38 deletions
+7 -6
View File
@@ -153,6 +153,7 @@ class Batterie extends IPSModule implements VerbraucherSchnittstelle
parent::ApplyChanges(); parent::ApplyChanges();
$this->aktualisiereVariablen(); $this->aktualisiereVariablen();
$this->bereinigeUngueltigenSollwert();
$this->registriereMesswertmeldungen(); $this->registriereMesswertmeldungen();
$this->WriteAttributeBoolean('RegisterInitialisiert', false); $this->WriteAttributeBoolean('RegisterInitialisiert', false);
@@ -219,7 +220,7 @@ class Batterie extends IPSModule implements VerbraucherSchnittstelle
case 'Aktiv': case 'Aktiv':
$this->SetValue('Aktiv', (bool) $wert); $this->SetValue('Aktiv', (bool) $wert);
if (!(bool) $wert) { if (!(bool) $wert) {
$this->setzeZustand('SollwertGueltig', false); $this->verwerfeSollwert();
$this->SetTimerInterval('VorgabeTimeout', 0); $this->SetTimerInterval('VorgabeTimeout', 0);
} }
$this->aktualisiere(true); $this->aktualisiere(true);
@@ -235,7 +236,7 @@ class Batterie extends IPSModule implements VerbraucherSchnittstelle
case 'VorgabeTimeout': case 'VorgabeTimeout':
$this->SetTimerInterval('VorgabeTimeout', 0); $this->SetTimerInterval('VorgabeTimeout', 0);
$this->setzeZustand('SollwertGueltig', false); $this->verwerfeSollwert();
$this->aktualisiere(true); $this->aktualisiere(true);
return; return;
@@ -249,7 +250,7 @@ class Batterie extends IPSModule implements VerbraucherSchnittstelle
return; return;
case 'SichererZustand': case 'SichererZustand':
$this->setzeZustand('SollwertGueltig', false); $this->verwerfeSollwert();
$this->schreibeRegister(0, true); $this->schreibeRegister(0, true);
$this->aktualisiere(true); $this->aktualisiere(true);
return; return;
@@ -313,7 +314,7 @@ class Batterie extends IPSModule implements VerbraucherSchnittstelle
(int) $this->leseZustand('Sollleistung') (int) $this->leseZustand('Sollleistung')
) )
) { ) {
$this->setzeZustand('SollwertGueltig', false); $this->verwerfeSollwert();
$this->aktualisiere(false); $this->aktualisiere(false);
} }
$this->planeMeldung(); $this->planeMeldung();
@@ -419,7 +420,7 @@ class Batterie extends IPSModule implements VerbraucherSchnittstelle
(int) $this->leseZustand('Sollleistung') (int) $this->leseZustand('Sollleistung')
) )
) { ) {
$this->setzeZustand('SollwertGueltig', false); $this->verwerfeSollwert();
} }
$this->pruefeVorgabeTimeout($jetzt); $this->pruefeVorgabeTimeout($jetzt);
@@ -596,7 +597,7 @@ class Batterie extends IPSModule implements VerbraucherSchnittstelle
if ($letzteVorgabe <= 0 if ($letzteVorgabe <= 0
|| $jetzt - $letzteVorgabe >= $this->ReadPropertyInteger('VorgabeTimeout') || $jetzt - $letzteVorgabe >= $this->ReadPropertyInteger('VorgabeTimeout')
) { ) {
$this->setzeZustand('SollwertGueltig', false); $this->verwerfeSollwert();
$this->SetTimerInterval('VorgabeTimeout', 0); $this->SetTimerInterval('VorgabeTimeout', 0);
} }
} }
+7
View File
@@ -4,6 +4,13 @@ Alle wesentlichen Aenderungen an Enelix EMS werden in dieser Datei dokumentiert.
## Unreleased ## Unreleased
- Verbraucher: Beim Verwerfen einer Manager-Vorgabe wird auch die gespeicherte
und sichtbare Sollleistung auf 0 W gesetzt. Gilt fuer Batterie, beide
Ladestationen, Pufferspeicher, Verbraucher 1-Stufig, Waermepumpe und
Warmwassererwaermer. Bereits ungueltige Altwerte werden beim Anwenden der
Instanzkonfiguration bereinigt. Gueltige Vorgaben, lokale Schutzprogramme,
Mindestlaufzeiten und der Nachrichtenvertrag bleiben erhalten.
- Manager: SDL-/Regelenergie als separate Istleistungsquelle mit optionalem SOC, - Manager: SDL-/Regelenergie als separate Istleistungsquelle mit optionalem SOC,
Leistungsfaktor und getrennten Anzeigeoptionen fuer Energiefluss und Diagramme. Leistungsfaktor und getrennten Anzeigeoptionen fuer Energiefluss und Diagramme.
SDL wird unabhaengig von der Anzeige aus Hauslast und neuer Hausenergie SDL wird unabhaengig von der Anzeige aus Hauslast und neuer Hausenergie
+6 -5
View File
@@ -144,6 +144,7 @@ class LadestationGateway extends IPSModule implements VerbraucherSchnittstelle
$this->initialisiereLokaleEinstellungen(); $this->initialisiereLokaleEinstellungen();
$this->aktualisiereVariablen(); $this->aktualisiereVariablen();
$this->bereinigeUngueltigenSollwert();
$this->WriteAttributeBoolean('GatewayVerbunden', false); $this->WriteAttributeBoolean('GatewayVerbunden', false);
$this->WriteAttributeInteger('LetzterGesetzterStrom', -1); $this->WriteAttributeInteger('LetzterGesetzterStrom', -1);
$this->WriteAttributeBoolean('LadestartFuerVerbindungGesendet', false); $this->WriteAttributeBoolean('LadestartFuerVerbindungGesendet', false);
@@ -179,7 +180,7 @@ class LadestationGateway extends IPSModule implements VerbraucherSchnittstelle
$this->starteRegeluebergang(); $this->starteRegeluebergang();
} else { } else {
$this->beendeRegeluebergang(); $this->beendeRegeluebergang();
$this->setzeZustand('SollwertGueltig', false); $this->verwerfeSollwert();
} }
$this->aktualisiere(true); $this->aktualisiere(true);
return; return;
@@ -192,7 +193,7 @@ class LadestationGateway extends IPSModule implements VerbraucherSchnittstelle
$this->starteRegeluebergang(); $this->starteRegeluebergang();
} else { } else {
$this->beendeRegeluebergang(); $this->beendeRegeluebergang();
$this->setzeZustand('SollwertGueltig', false); $this->verwerfeSollwert();
} }
$this->aktualisiere(true); $this->aktualisiere(true);
return; return;
@@ -215,7 +216,7 @@ class LadestationGateway extends IPSModule implements VerbraucherSchnittstelle
case 'VorgabeTimeout': case 'VorgabeTimeout':
$this->SetTimerInterval('VorgabeTimeout', 0); $this->SetTimerInterval('VorgabeTimeout', 0);
$this->setzeZustand('SollwertGueltig', false); $this->verwerfeSollwert();
$this->aktualisiere(true); $this->aktualisiere(true);
return; return;
@@ -322,7 +323,7 @@ class LadestationGateway extends IPSModule implements VerbraucherSchnittstelle
true true
) )
) { ) {
$this->setzeZustand('SollwertGueltig', false); $this->verwerfeSollwert();
$this->aktualisiere(false); $this->aktualisiere(false);
} }
$this->SetTimerInterval('RueckmeldungVerzoegert', 100); $this->SetTimerInterval('RueckmeldungVerzoegert', 100);
@@ -559,7 +560,7 @@ class LadestationGateway extends IPSModule implements VerbraucherSchnittstelle
if (in_array($sollleistung, $angebot, true)) { if (in_array($sollleistung, $angebot, true)) {
return $sollleistung; return $sollleistung;
} }
$this->setzeZustand('SollwertGueltig', false); $this->verwerfeSollwert();
} }
if ( if (
+6 -5
View File
@@ -139,6 +139,7 @@ class LadestationStandAlone extends IPSModule implements VerbraucherSchnittstell
$this->initialisiereLokaleEinstellungen(); $this->initialisiereLokaleEinstellungen();
$this->aktualisiereVariablen(); $this->aktualisiereVariablen();
$this->bereinigeUngueltigenSollwert();
// Ein persistiertes Ladeende nach Neueinrichtung oder Modulupdate einmal neu pruefen. // Ein persistiertes Ladeende nach Neueinrichtung oder Modulupdate einmal neu pruefen.
$this->setzeZustand('FahrzeugGeladen', false); $this->setzeZustand('FahrzeugGeladen', false);
@@ -190,7 +191,7 @@ class LadestationStandAlone extends IPSModule implements VerbraucherSchnittstell
$this->starteRegeluebergang(); $this->starteRegeluebergang();
} else { } else {
$this->beendeRegeluebergang(); $this->beendeRegeluebergang();
$this->setzeZustand('SollwertGueltig', false); $this->verwerfeSollwert();
} }
$this->aktualisiere(true); $this->aktualisiere(true);
return; return;
@@ -203,7 +204,7 @@ class LadestationStandAlone extends IPSModule implements VerbraucherSchnittstell
$this->starteRegeluebergang(); $this->starteRegeluebergang();
} else { } else {
$this->beendeRegeluebergang(); $this->beendeRegeluebergang();
$this->setzeZustand('SollwertGueltig', false); $this->verwerfeSollwert();
} }
$this->aktualisiere(true); $this->aktualisiere(true);
return; return;
@@ -224,7 +225,7 @@ class LadestationStandAlone extends IPSModule implements VerbraucherSchnittstell
case 'VorgabeTimeout': case 'VorgabeTimeout':
$this->SetTimerInterval('VorgabeTimeout', 0); $this->SetTimerInterval('VorgabeTimeout', 0);
$this->setzeZustand('SollwertGueltig', false); $this->verwerfeSollwert();
$this->aktualisiere(true); $this->aktualisiere(true);
return; return;
@@ -275,7 +276,7 @@ class LadestationStandAlone extends IPSModule implements VerbraucherSchnittstell
if ((bool) $this->leseZustand('SollwertGueltig') if ((bool) $this->leseZustand('SollwertGueltig')
&& !in_array((int) $this->leseZustand('Sollleistung'), $this->leseLeistungsangebot(), true) && !in_array((int) $this->leseZustand('Sollleistung'), $this->leseLeistungsangebot(), true)
) { ) {
$this->setzeZustand('SollwertGueltig', false); $this->verwerfeSollwert();
$this->aktualisiere(false, false); $this->aktualisiere(false, false);
} }
$this->SetTimerInterval('RueckmeldungVerzoegert', 100); $this->SetTimerInterval('RueckmeldungVerzoegert', 100);
@@ -560,7 +561,7 @@ class LadestationStandAlone extends IPSModule implements VerbraucherSchnittstell
if (in_array($sollleistung, $angebot, true)) { if (in_array($sollleistung, $angebot, true)) {
return $sollleistung; return $sollleistung;
} }
$this->setzeZustand('SollwertGueltig', false); $this->verwerfeSollwert();
$ersatz = 0; $ersatz = 0;
foreach ($angebot as $leistungswert) { foreach ($angebot as $leistungswert) {
if ($leistungswert > $sollleistung) { if ($leistungswert > $sollleistung) {
+5 -4
View File
@@ -112,6 +112,7 @@ class VerbraucherPufferspeicher extends IPSModule implements VerbraucherSchnitts
parent::ApplyChanges(); parent::ApplyChanges();
$this->aktualisiereVariablen(); $this->aktualisiereVariablen();
$this->bereinigeUngueltigenSollwert();
try { try {
$this->pruefeKonfiguration(); $this->pruefeKonfiguration();
} catch (Throwable $fehler) { } catch (Throwable $fehler) {
@@ -150,7 +151,7 @@ class VerbraucherPufferspeicher extends IPSModule implements VerbraucherSchnitts
case 'Aktiv': case 'Aktiv':
$this->SetValue('Aktiv', (bool) $wert); $this->SetValue('Aktiv', (bool) $wert);
if (!(bool) $wert) { if (!(bool) $wert) {
$this->setzeZustand('SollwertGueltig', false); $this->verwerfeSollwert();
} }
$this->regelzyklus(true); $this->regelzyklus(true);
return; return;
@@ -208,7 +209,7 @@ class VerbraucherPufferspeicher extends IPSModule implements VerbraucherSchnitts
if ((bool) $this->leseZustand('SollwertGueltig') if ((bool) $this->leseZustand('SollwertGueltig')
&& !in_array((int) $this->leseZustand('Sollleistung'), $angebot, true) && !in_array((int) $this->leseZustand('Sollleistung'), $angebot, true)
) { ) {
$this->setzeZustand('SollwertGueltig', false); $this->verwerfeSollwert();
$this->regelzyklus(false); $this->regelzyklus(false);
} }
$this->SetTimerInterval('RueckmeldungVerzoegert', 100); $this->SetTimerInterval('RueckmeldungVerzoegert', 100);
@@ -290,7 +291,7 @@ class VerbraucherPufferspeicher extends IPSModule implements VerbraucherSchnitts
if ($letzteVorgabe <= 0 if ($letzteVorgabe <= 0
|| $jetzt - $letzteVorgabe > $this->ReadPropertyInteger('VorgabeTimeout') || $jetzt - $letzteVorgabe > $this->ReadPropertyInteger('VorgabeTimeout')
) { ) {
$this->setzeZustand('SollwertGueltig', false); $this->verwerfeSollwert();
} }
$ziel = 0; $ziel = 0;
@@ -301,7 +302,7 @@ class VerbraucherPufferspeicher extends IPSModule implements VerbraucherSchnitts
} elseif ((bool) $this->leseZustand('SollwertGueltig')) { } elseif ((bool) $this->leseZustand('SollwertGueltig')) {
$ziel = (int) $this->leseZustand('Sollleistung'); $ziel = (int) $this->leseZustand('Sollleistung');
if (!in_array($ziel, $angebot, true)) { if (!in_array($ziel, $angebot, true)) {
$this->setzeZustand('SollwertGueltig', false); $this->verwerfeSollwert();
$ziel = 0; $ziel = 0;
} }
} }
+6 -5
View File
@@ -118,6 +118,7 @@ class VerbraucherEinStufig extends IPSModule implements VerbraucherSchnittstelle
parent::ApplyChanges(); parent::ApplyChanges();
$this->aktualisiereVariablen(); $this->aktualisiereVariablen();
$this->bereinigeUngueltigenSollwert();
$this->registriereMeldungen(); $this->registriereMeldungen();
try { try {
@@ -158,7 +159,7 @@ class VerbraucherEinStufig extends IPSModule implements VerbraucherSchnittstelle
case 'Aktiv': case 'Aktiv':
$this->SetValue('Aktiv', (bool) $wert); $this->SetValue('Aktiv', (bool) $wert);
if (!(bool) $wert) { if (!(bool) $wert) {
$this->setzeZustand('SollwertGueltig', false); $this->verwerfeSollwert();
$this->SetTimerInterval('VorgabeTimeout', 0); $this->SetTimerInterval('VorgabeTimeout', 0);
} }
$this->aktualisiere(true); $this->aktualisiere(true);
@@ -214,7 +215,7 @@ class VerbraucherEinStufig extends IPSModule implements VerbraucherSchnittstelle
if ((bool) $this->leseZustand('SollwertGueltig') if ((bool) $this->leseZustand('SollwertGueltig')
&& !in_array((int) $this->leseZustand('Sollleistung'), $this->leseLeistungsangebot(), true) && !in_array((int) $this->leseZustand('Sollleistung'), $this->leseLeistungsangebot(), true)
) { ) {
$this->setzeZustand('SollwertGueltig', false); $this->verwerfeSollwert();
} }
$this->planeMeldung(); $this->planeMeldung();
return; return;
@@ -254,7 +255,7 @@ class VerbraucherEinStufig extends IPSModule implements VerbraucherSchnittstelle
true true
) )
) { ) {
$this->setzeZustand('SollwertGueltig', false); $this->verwerfeSollwert();
} }
$aktiv = (bool) $this->GetValue('Aktiv'); $aktiv = (bool) $this->GetValue('Aktiv');
@@ -474,7 +475,7 @@ class VerbraucherEinStufig extends IPSModule implements VerbraucherSchnittstelle
if ($letzteVorgabe <= 0 if ($letzteVorgabe <= 0
|| $jetzt - $letzteVorgabe >= $this->ReadPropertyInteger('VorgabeTimeout') || $jetzt - $letzteVorgabe >= $this->ReadPropertyInteger('VorgabeTimeout')
) { ) {
$this->setzeZustand('SollwertGueltig', false); $this->verwerfeSollwert();
$this->SetTimerInterval('VorgabeTimeout', 0); $this->SetTimerInterval('VorgabeTimeout', 0);
} }
} }
@@ -528,7 +529,7 @@ class VerbraucherEinStufig extends IPSModule implements VerbraucherSchnittstelle
$this->WriteAttributeString('LaufzeitTag', date('Y-m-d', $jetzt)); $this->WriteAttributeString('LaufzeitTag', date('Y-m-d', $jetzt));
$this->WriteAttributeInteger('LaufzeitStandZeit', $jetzt); $this->WriteAttributeInteger('LaufzeitStandZeit', $jetzt);
$this->setzeZustand('Tageslaufzeit', 0); $this->setzeZustand('Tageslaufzeit', 0);
$this->setzeZustand('SollwertGueltig', false); $this->verwerfeSollwert();
$this->setzeZustand('Rueckmeldefehler', false); $this->setzeZustand('Rueckmeldefehler', false);
$this->WriteAttributeBoolean('Initialisiert', true); $this->WriteAttributeBoolean('Initialisiert', true);
} }
+5 -4
View File
@@ -132,6 +132,7 @@ class VerbraucherWaermepumpe extends IPSModule implements VerbraucherSchnittstel
parent::ApplyChanges(); parent::ApplyChanges();
$this->aktualisiereVariablen(); $this->aktualisiereVariablen();
$this->bereinigeUngueltigenSollwert();
$this->registriereMeldungen(); $this->registriereMeldungen();
try { try {
$this->pruefeKonfiguration(); $this->pruefeKonfiguration();
@@ -172,7 +173,7 @@ class VerbraucherWaermepumpe extends IPSModule implements VerbraucherSchnittstel
case 'Aktiv': case 'Aktiv':
$this->SetValue('Aktiv', (bool) $wert); $this->SetValue('Aktiv', (bool) $wert);
if (!(bool) $wert) { if (!(bool) $wert) {
$this->setzeZustand('SollwertGueltig', false); $this->verwerfeSollwert();
$this->SetTimerInterval('VorgabeTimeout', 0); $this->SetTimerInterval('VorgabeTimeout', 0);
} }
$this->aktualisiere(true); $this->aktualisiere(true);
@@ -230,7 +231,7 @@ class VerbraucherWaermepumpe extends IPSModule implements VerbraucherSchnittstel
if ((bool) $this->leseZustand('SollwertGueltig') if ((bool) $this->leseZustand('SollwertGueltig')
&& !in_array((int) $this->leseZustand('Sollleistung'), $this->leseLeistungsangebot(), true) && !in_array((int) $this->leseZustand('Sollleistung'), $this->leseLeistungsangebot(), true)
) { ) {
$this->setzeZustand('SollwertGueltig', false); $this->verwerfeSollwert();
} }
$this->planeMeldung(); $this->planeMeldung();
return; return;
@@ -286,7 +287,7 @@ class VerbraucherWaermepumpe extends IPSModule implements VerbraucherSchnittstel
$this->WriteAttributeInteger('Laufbeginn', $laeuft ? $jetzt : 0); $this->WriteAttributeInteger('Laufbeginn', $laeuft ? $jetzt : 0);
$this->schalte(WaermepumpenRegler::STEUERZUSTAND_NORMAL, $jetzt); $this->schalte(WaermepumpenRegler::STEUERZUSTAND_NORMAL, $jetzt);
$steuerzustand = WaermepumpenRegler::STEUERZUSTAND_NORMAL; $steuerzustand = WaermepumpenRegler::STEUERZUSTAND_NORMAL;
$this->setzeZustand('SollwertGueltig', false); $this->verwerfeSollwert();
} }
$this->aktualisiereSperrerholung($laeuft, $jetzt); $this->aktualisiereSperrerholung($laeuft, $jetzt);
@@ -828,7 +829,7 @@ class VerbraucherWaermepumpe extends IPSModule implements VerbraucherSchnittstel
$ablauf = $this->ReadAttributeInteger('LetzteVorgabeZeit') $ablauf = $this->ReadAttributeInteger('LetzteVorgabeZeit')
+ $this->ReadPropertyInteger('VorgabeTimeout'); + $this->ReadPropertyInteger('VorgabeTimeout');
if ($this->ReadAttributeInteger('LetzteVorgabeZeit') <= 0 || $jetzt >= $ablauf) { if ($this->ReadAttributeInteger('LetzteVorgabeZeit') <= 0 || $jetzt >= $ablauf) {
$this->setzeZustand('SollwertGueltig', false); $this->verwerfeSollwert();
$this->SetTimerInterval('VorgabeTimeout', 0); $this->SetTimerInterval('VorgabeTimeout', 0);
} }
} }
+4 -3
View File
@@ -122,6 +122,7 @@ class VerbraucherWarmwassererwaermer extends IPSModule implements VerbraucherSch
$this->initialisiereTemperaturen(); $this->initialisiereTemperaturen();
$this->aktualisiereVariablen(); $this->aktualisiereVariablen();
$this->bereinigeUngueltigenSollwert();
try { try {
$this->pruefeKonfiguration(); $this->pruefeKonfiguration();
@@ -219,7 +220,7 @@ class VerbraucherWarmwassererwaermer extends IPSModule implements VerbraucherSch
if ((bool) $this->leseZustand('SollwertGueltig') if ((bool) $this->leseZustand('SollwertGueltig')
&& !in_array((int) $this->leseZustand('Sollleistung'), $angebot, true) && !in_array((int) $this->leseZustand('Sollleistung'), $angebot, true)
) { ) {
$this->setzeZustand('SollwertGueltig', false); $this->verwerfeSollwert();
$this->regelzyklus(false); $this->regelzyklus(false);
} }
$this->SetTimerInterval('RueckmeldungVerzoegert', 100); $this->SetTimerInterval('RueckmeldungVerzoegert', 100);
@@ -264,7 +265,7 @@ class VerbraucherWarmwassererwaermer extends IPSModule implements VerbraucherSch
if ($letzteVorgabe <= 0 if ($letzteVorgabe <= 0
|| $jetzt - $letzteVorgabe > $this->ReadPropertyInteger('VorgabeTimeout') || $jetzt - $letzteVorgabe > $this->ReadPropertyInteger('VorgabeTimeout')
) { ) {
$this->setzeZustand('SollwertGueltig', false); $this->verwerfeSollwert();
} }
$ziel = 0; $ziel = 0;
@@ -276,7 +277,7 @@ class VerbraucherWarmwassererwaermer extends IPSModule implements VerbraucherSch
} elseif ((bool) $this->leseZustand('SollwertGueltig')) { } elseif ((bool) $this->leseZustand('SollwertGueltig')) {
$ziel = (int) $this->leseZustand('Sollleistung'); $ziel = (int) $this->leseZustand('Sollleistung');
if (!in_array($ziel, $angebot, true)) { if (!in_array($ziel, $angebot, true)) {
$this->setzeZustand('SollwertGueltig', false); $this->verwerfeSollwert();
$ziel = 0; $ziel = 0;
} }
} }
+23
View File
@@ -41,6 +41,29 @@ berechnet sein Leistungsangebot neu und meldet es zurueck. Eine vorhandene
Sollleistung wird dabei nur verworfen, wenn sie im neuen Angebot nicht mehr Sollleistung wird dabei nur verworfen, wenn sie im neuen Angebot nicht mehr
zulaessig ist. zulaessig ist.
### Verworfene Sollwerte und Anzeige
Beim Verwerfen einer gueltigen Vorgabe setzen alle Verbrauchermodule sowohl
`SollwertGueltig=false` als auch den gespeicherten und gegebenenfalls sichtbaren
Wert `Sollleistung=0`. An den Manager geht weiterhin `Sollleistung_W=null`;
die Anzeige 0 W ist keine neue gueltige Manager-Vorgabe und kein Nachweis einer
physisch ausgeschalteten Last.
Lokale Schutzprogramme, Mindestlaufzeiten und bestehende Lade-Uebergaenge
bleiben unveraendert. Berechnet ein Modul danach eine lokale Sollleistung,
darf es diese weiterhin anzeigen. Bei Ladestationen bleibt waehrend eines
Regeluebergangs insbesondere der letzte Geraetebefehl erhalten, auch wenn die
alte Manager-Vorgabe bereits verworfen und ihre Anzeige auf 0 gesetzt wurde.
Wiederholte Verwerfungen einer bereits ungueltigen Manager-Vorgabe lassen eine
inzwischen neu berechnete lokale Schutzleistung unveraendert.
Nach einem Modulupdate bereinigt `ApplyChanges` bereits gespeicherte ungueltige
Altwerte. Gueltige Vorgaben einschliesslich 0 W und negativer Batterieleistung
bleiben erhalten. Keine neuen Properties, Variablen-IDs oder Vertragsversion;
es ist keine manuelle Konfigurationsmigration erforderlich. Das Update ist
kontrolliert anzuwenden, da die regulaere Instanzinitialisierung wie bisher
Geraeteaktionen ausloesen kann.
## Verbraucher an Manager ## Verbraucher an Manager
```json ```json
+19 -1
View File
@@ -5,13 +5,31 @@ declare(strict_types=1);
namespace Belevo\EnelixEMS; namespace Belevo\EnelixEMS;
/** /**
* Registriert die gemeinsamen Properties und Variablen aller EMS-Verbraucher. * Gemeinsame Registrierung und Sollwert-Bereinigung aller EMS-Verbraucher.
* *
* Das Trait ist noch keine vollstaendige Steuerungsimplementierung. Die * Das Trait ist noch keine vollstaendige Steuerungsimplementierung. Die
* konkreten Module ergaenzen Geraeteanbindung, Leistungsangebot und Zustand. * konkreten Module ergaenzen Geraeteanbindung, Leistungsangebot und Zustand.
*/ */
trait VerbraucherBasisTrait trait VerbraucherBasisTrait
{ {
protected function verwerfeSollwert(): void
{
// Lokale Ersatzleistungen nicht bei jedem weiteren Timeout kurz auf 0 setzen.
if (!(bool) $this->leseZustand('SollwertGueltig')) {
return;
}
$this->setzeZustand('SollwertGueltig', false);
$this->setzeZustand('Sollleistung', 0);
}
protected function bereinigeUngueltigenSollwert(): void
{
// Auch vor dem Update gespeicherte, bereits ungueltige Vorgaben bereinigen.
if (!(bool) $this->leseZustand('SollwertGueltig')) {
$this->setzeZustand('Sollleistung', 0);
}
}
protected function registriereVerbraucherBasis(): void protected function registriereVerbraucherBasis(): void
{ {
$this->RegisterPropertyInteger('PrioritaetPV', 0); $this->RegisterPropertyInteger('PrioritaetPV', 0);
+6
View File
@@ -133,6 +133,7 @@ PHP
'GoodWe-Entladen verwendet negativen Managerwert und Modus 12', 'GoodWe-Entladen verwendet negativen Managerwert und Modus 12',
static function (TestContext $test) use ( static function (TestContext $test) use (
$managerdatenSenden, $managerdatenSenden,
$batterieID,
$modusID, $modusID,
$leistungID $leistungID
): void { ): void {
@@ -141,6 +142,10 @@ PHP
static fn (): bool => (float) GetValue($leistungID) === 843.0 static fn (): bool => (float) GetValue($leistungID) === 843.0
&& (int) GetValue($modusID) === 12 && (int) GetValue($modusID) === 12
); );
$test->assertSame(-843, GetValue($test->objectByIdent('Sollleistung', $batterieID)));
IPS_RequestAction($batterieID, 'VorgabeTimeout', 0);
$test->assertSame(false, GetValue($test->objectByIdent('SollwertGueltig', $batterieID)));
$test->assertSame(0, GetValue($test->objectByIdent('Sollleistung', $batterieID)));
} }
); );
@@ -197,6 +202,7 @@ PHP
$leistungID $leistungID
): void { ): void {
$test->configureInstance($batterieID, ['Batteriemanagement' => 1]); $test->configureInstance($batterieID, ['Batteriemanagement' => 1]);
$test->assertSame(0, GetValue($test->objectByIdent('Sollleistung', $batterieID)));
$test->waitUntil( $test->waitUntil(
static fn (): bool => (float) GetValue($leistungID) === 0.0 static fn (): bool => (float) GetValue($leistungID) === 0.0
&& (int) GetValue($managementID) === 1 && (int) GetValue($managementID) === 1
+23 -3
View File
@@ -186,7 +186,7 @@ return static function (TestContext $test): void {
$test->runCase('Solarumschaltung wartet auf die neue Manager-Vorgabe', static function ( $test->runCase('Solarumschaltung wartet auf die neue Manager-Vorgabe', static function (
TestContext $test TestContext $test
) use ($station): void { ) use ($station, $sendeBeobachtung): void {
$managerId = $test->createInstance( $managerId = $test->createInstance(
'{6F771B18-59D4-4C8A-B951-3B2FE9F6A2C4}', '{6F771B18-59D4-4C8A-B951-3B2FE9F6A2C4}',
'Hilfsmanager Easee-Uebergang', 'Hilfsmanager Easee-Uebergang',
@@ -221,10 +221,11 @@ return static function (TestContext $test): void {
(string) GetValue($test->objectByIdent('LetzterGeraetebefehl', $station)) (string) GetValue($test->objectByIdent('LetzterGeraetebefehl', $station))
); );
$test->assertSame( $test->assertSame(
4104, 0,
GetValue($test->objectByIdent('Sollleistung', $station)), GetValue($test->objectByIdent('Sollleistung', $station)),
'Die Sollleistung muss bis zur neuen Manager-Vorgabe stabil bleiben.' 'Die verworfene Manager-Vorgabe darf nicht als aktuelle Sollleistung stehen bleiben.'
); );
$test->assertSame(false, GetValue($test->objectByIdent('SollwertGueltig', $station)));
$sendeSollwert(10944); $sendeSollwert(10944);
$test->assertTrue(str_contains( $test->assertTrue(str_contains(
@@ -234,6 +235,25 @@ return static function (TestContext $test): void {
IPS_RequestAction($station, 'Solarladen', true); IPS_RequestAction($station, 'Solarladen', true);
$sendeSollwert(0); $sendeSollwert(0);
$test->assertSame(true, GetValue($test->objectByIdent('SollwertGueltig', $station)));
$test->assertSame(0, GetValue($test->objectByIdent('Sollleistung', $station)));
$sendeSollwert(10944);
$sendeBeobachtung('EH123456', 109, 1);
$test->assertSame('[0]', GetValue($test->objectByIdent('LeistungsangebotDiagnose', $station)));
$test->assertSame(false, GetValue($test->objectByIdent('SollwertGueltig', $station)));
$test->assertSame(0, GetValue($test->objectByIdent('Sollleistung', $station)));
$test->assertTrue(str_contains(
(string) GetValue($test->objectByIdent('LetzterGeraetebefehl', $station)),
'"Ampere":0'
));
$sendeBeobachtung('EH123456', 110, 30);
$sendeBeobachtung('EH123456', 109, 3);
$sendeSollwert(10944);
IPS_RequestAction($station, 'VorgabeTimeout', 0);
$test->assertSame(false, GetValue($test->objectByIdent('SollwertGueltig', $station)));
$test->assertSame(0, GetValue($test->objectByIdent('Sollleistung', $station)));
}); });
$test->runCase('SignalR-Abbruch erreicht die Ladestation', static function ( $test->runCase('SignalR-Abbruch erreicht die Ladestation', static function (
+26 -2
View File
@@ -387,10 +387,11 @@ return static function (TestContext $test): void {
'Die Umschaltung darf vor der neuen Manager-Vorgabe keinen Zwischenwert setzen.' 'Die Umschaltung darf vor der neuen Manager-Vorgabe keinen Zwischenwert setzen.'
); );
$test->assertSame( $test->assertSame(
4104, 0,
GetValue($test->objectByIdent('Sollleistung', $stationId)), GetValue($test->objectByIdent('Sollleistung', $stationId)),
'Die Sollleistung muss bis zur neuen Manager-Vorgabe stabil bleiben.' 'Die verworfene Manager-Vorgabe darf nicht als aktuelle Sollleistung stehen bleiben.'
); );
$test->assertSame(false, GetValue($test->objectByIdent('SollwertGueltig', $stationId)));
$angebot = (string) GetValue($test->objectByIdent('LeistungsangebotDiagnose', $stationId)); $angebot = (string) GetValue($test->objectByIdent('LeistungsangebotDiagnose', $stationId));
$test->assertSame('[10944]', $angebot, 'PV ohne Solarladen muss nur das Maximum anbieten.'); $test->assertSame('[10944]', $angebot, 'PV ohne Solarladen muss nur das Maximum anbieten.');
$abgewiesen = false; $abgewiesen = false;
@@ -502,6 +503,29 @@ return static function (TestContext $test): void {
); );
}); });
$test->runCase('Abziehen und Vorgabe-Timeout hinterlassen keine alte Sollleistung', static function (
TestContext $test
) use ($stationen, $goEAntwort, $anforderungSenden): void {
$stationId = $stationen['go-e alt'];
IPS_SetProperty($stationId, 'Testantwort', $goEAntwort(2, 10944, [160, 160, 160]));
IPS_ApplyChanges($stationId);
IPS_RequestAction($stationId, 'Aktiv', true);
IPS_RequestAction($stationId, 'Solarladen', true);
$anforderungSenden($stationId, 10944);
$test->assertSame(10944, GetValue($test->objectByIdent('Sollleistung', $stationId)));
IPS_RequestAction($stationId, 'VorgabeTimeout', 0);
$test->assertSame(false, GetValue($test->objectByIdent('SollwertGueltig', $stationId)));
$test->assertSame(0, GetValue($test->objectByIdent('Sollleistung', $stationId)));
$anforderungSenden($stationId, 10944);
IPS_SetProperty($stationId, 'Testantwort', $goEAntwort(1, 0));
IPS_ApplyChanges($stationId);
$test->assertSame('[0]', GetValue($test->objectByIdent('LeistungsangebotDiagnose', $stationId)));
$test->assertSame(false, GetValue($test->objectByIdent('SollwertGueltig', $stationId)));
$test->assertSame(0, GetValue($test->objectByIdent('Sollleistung', $stationId)));
});
$test->runCase('Leistungswerte und Fahrzeugstatus folgen ihrer Sichtbarkeitsgruppe', static function ( $test->runCase('Leistungswerte und Fahrzeugstatus folgen ihrer Sichtbarkeitsgruppe', static function (
TestContext $test TestContext $test
) use ($stationen): void { ) use ($stationen): void {
+1
View File
@@ -105,6 +105,7 @@ return static function (TestContext $test): void {
$test->assertSame(false, GetValue($test->objectByIdent('Heizbedarf', $verbraucherId))); $test->assertSame(false, GetValue($test->objectByIdent('Heizbedarf', $verbraucherId)));
SetValue($pufferId, 55.0); SetValue($pufferId, 55.0);
$test->assertSame(0, GetValue($test->objectByIdent('Sollleistung', $verbraucherId)));
IPS_RequestAction($verbraucherId, 'Melden', true); IPS_RequestAction($verbraucherId, 'Melden', true);
$test->assertSame([false, false], $kontaktzustand()); $test->assertSame([false, false], $kontaktzustand());
}); });
@@ -225,4 +225,18 @@ return static function (TestContext $test): void {
) === false); ) === false);
$test->assertInstanceStatus($verbraucherId); $test->assertInstanceStatus($verbraucherId);
}); });
$test->runCase('Deaktivierung verwirft eine positive Vorgabe vollstaendig', static function (
TestContext $test
) use ($verbraucherId, $anforderungSenden): void {
IPS_SetProperty($verbraucherId, 'Mindestlaufzeit', 0);
IPS_SetProperty($verbraucherId, 'Mindesteinschaltdauer', 0);
IPS_SetProperty($verbraucherId, 'Mindestausschaltdauer', 0);
IPS_ApplyChanges($verbraucherId);
$anforderungSenden(1200);
$test->assertSame(1200, GetValue($test->objectByIdent('Sollleistung', $verbraucherId)));
IPS_RequestAction($verbraucherId, 'Aktiv', false);
$test->assertSame(false, GetValue($test->objectByIdent('SollwertGueltig', $verbraucherId)));
$test->assertSame(0, GetValue($test->objectByIdent('Sollleistung', $verbraucherId)));
});
}; };
+2
View File
@@ -121,6 +121,8 @@ return static function (TestContext $test): void {
TestContext $test TestContext $test
) use ($waermepumpe, $kontakt1, $kontakt2): void { ) use ($waermepumpe, $kontakt1, $kontakt2): void {
IPS_RequestAction($waermepumpe, 'Aktiv', false); IPS_RequestAction($waermepumpe, 'Aktiv', false);
$test->assertSame(false, GetValue($test->objectByIdent('SollwertGueltig', $waermepumpe)));
$test->assertSame(0, GetValue($test->objectByIdent('Sollleistung', $waermepumpe)));
IPS_RequestAction($waermepumpe, 'Aktiv', true); IPS_RequestAction($waermepumpe, 'Aktiv', true);
$test->waitUntil( $test->waitUntil(
static fn (): bool => GetValue($kontakt1) === false static fn (): bool => GetValue($kontakt1) === false
@@ -110,6 +110,33 @@ return static function (TestContext $test): void {
$temperaturSetzen(52.0); $temperaturSetzen(52.0);
}); });
$test->runCase('Abgelaufene Vorgabe wird auch in der Anzeige verworfen', static function (
TestContext $test
) use ($verbraucherId, $managerId, $kontakte): void {
IPS_SetProperty($verbraucherId, 'VorgabeTimeout', 1);
IPS_ApplyChanges($verbraucherId);
IPS_RequestAction($verbraucherId, 'Aktiv', true);
IPS_RequestAction($verbraucherId, 'ManagerdatenEmpfangen', json_encode([
'Kopf' => [
'Version' => '4.0',
'AbsenderID' => $managerId,
'EmpfaengerID' => $verbraucherId,
'Zeitpunkt' => time(),
],
'Betriebsart' => 'PV',
'Sollleistung_W' => 1000,
], JSON_THROW_ON_ERROR));
$test->assertSame(1000, GetValue($test->objectByIdent('Sollleistung', $verbraucherId)));
IPS_Sleep(2100);
IPS_RequestAction($verbraucherId, 'Melden', true);
$test->assertSame(false, GetValue($test->objectByIdent('SollwertGueltig', $verbraucherId)));
$test->assertSame(0, GetValue($test->objectByIdent('Sollleistung', $verbraucherId)));
$test->waitUntil(static fn (): bool => GetValue($kontakte[0]) === false);
IPS_RequestAction($verbraucherId, 'Aktiv', false);
IPS_SetProperty($verbraucherId, 'VorgabeTimeout', 120);
IPS_ApplyChanges($verbraucherId);
});
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
+109
View File
@@ -9,6 +9,86 @@ use PHPUnit\Framework\TestCase;
final class VerbraucherBasisTraitTest extends TestCase final class VerbraucherBasisTraitTest extends TestCase
{ {
/** @dataProvider sollwerte */
public function testVerwerfenLoeschtGespeicherteUndSichtbareVorgabe(int $leistung, bool $sichtbar): void
{
$modul = new VerbraucherBasisDummy();
$modul->zustand = ['Sollleistung' => $leistung, 'SollwertGueltig' => true];
$modul->sichtbareWerte = $sichtbar ? $modul->zustand : [];
$modul->verwerfen();
$modul->verwerfen();
self::assertSame(['Sollleistung' => 0, 'SollwertGueltig' => false], $modul->zustand);
self::assertSame($sichtbar ? $modul->zustand : [], $modul->sichtbareWerte);
}
/** @dataProvider sollwerte */
public function testUpdateBereinigtAlteUngueltigeVorgaben(int $leistung, bool $sichtbar): void
{
$modul = new VerbraucherBasisDummy();
$modul->zustand = ['Sollleistung' => $leistung, 'SollwertGueltig' => false];
$modul->sichtbareWerte = $sichtbar ? $modul->zustand : [];
$modul->bereinigen();
self::assertSame(['Sollleistung' => 0, 'SollwertGueltig' => false], $modul->zustand);
self::assertSame($sichtbar ? $modul->zustand : [], $modul->sichtbareWerte);
}
/** @dataProvider sollwerte */
public function testUpdateErhaeltGueltigeVorgabenAuchNullUndEntladen(int $leistung, bool $sichtbar): void
{
$modul = new VerbraucherBasisDummy();
$modul->zustand = ['Sollleistung' => $leistung, 'SollwertGueltig' => true];
$modul->sichtbareWerte = $sichtbar ? $modul->zustand : [];
$vorher = $modul->zustand;
$modul->bereinigen();
self::assertSame($vorher, $modul->zustand);
self::assertSame($sichtbar ? $vorher : [], $modul->sichtbareWerte);
}
public static function sollwerte(): array
{
return [[10944, true], [10944, false], [-843, true], [-843, false], [0, true], [0, false]];
}
public function testWiederholtesVerwerfenLaesstNeuBerechneteLokaleSchutzleistungStehen(): void
{
$modul = new VerbraucherBasisDummy();
$modul->zustand = ['Sollleistung' => 3000, 'SollwertGueltig' => false];
$modul->sichtbareWerte = $modul->zustand;
$modul->verwerfen();
self::assertSame(['Sollleistung' => 3000, 'SollwertGueltig' => false], $modul->zustand);
self::assertSame($modul->zustand, $modul->sichtbareWerte);
}
public function testAlleVerbraucherVerwerfenSollwerteUeberDieGemeinsameFunktion(): void
{
$anzahl = 0;
foreach (glob(__DIR__ . '/../*/module.php') as $datei) {
$quelle = (string) file_get_contents($datei);
if (!str_contains($quelle, 'implements VerbraucherSchnittstelle')) {
continue;
}
++$anzahl;
self::assertStringContainsString('use VerbraucherBasisTrait;', $quelle, $datei);
self::assertStringContainsString('$this->verwerfeSollwert();', $quelle, $datei);
self::assertStringNotContainsString("setzeZustand('SollwertGueltig', false)", $quelle, $datei);
self::assertStringNotContainsString("WriteAttributeBoolean('ZustandSollwertGueltig', false)", $quelle, $datei);
self::assertStringContainsString(
'$this->aktualisiereVariablen();' . "\n " . '$this->bereinigeUngueltigenSollwert();',
$quelle,
$datei
);
}
self::assertSame(7, $anzahl);
}
public function testGemeinsamePropertiesUndVariablenWerdenRegistriert(): void public function testGemeinsamePropertiesUndVariablenWerdenRegistriert(): void
{ {
$modul = new VerbraucherBasisDummy(); $modul = new VerbraucherBasisDummy();
@@ -50,6 +130,35 @@ final class VerbraucherBasisDummy
{ {
use VerbraucherBasisTrait; use VerbraucherBasisTrait;
/** @var array<string, int|bool> */
public array $zustand = [];
/** @var array<string, int|bool> */
public array $sichtbareWerte = [];
public function verwerfen(): void
{
$this->verwerfeSollwert();
}
public function bereinigen(): void
{
$this->bereinigeUngueltigenSollwert();
}
private function setzeZustand(string $ident, $wert): void
{
$this->zustand[$ident] = $wert;
if (array_key_exists($ident, $this->sichtbareWerte)) {
$this->sichtbareWerte[$ident] = $wert;
}
}
private function leseZustand(string $ident)
{
return $this->zustand[$ident];
}
/** @var array<string, mixed> */ /** @var array<string, mixed> */
public array $properties = []; public array $properties = [];