From 5437f3f058affa74f5efe203fb4988e3eb31c04a Mon Sep 17 00:00:00 2001 From: dh_Agent Date: Tue, 6 Oct 2026 13:45:54 +0000 Subject: [PATCH] fix(verbraucher): clear discarded power setpoints --- Batterie/module.php | 13 ++- CHANGELOG.md | 7 ++ LadestationGateway/module.php | 11 +- LadestationStandAlone/module.php | 11 +- Pufferspeicher/module.php | 9 +- VerbraucherEinStufig/module.php | 11 +- Waermepumpe/module.php | 9 +- Warmwassererwaermer/module.php | 7 +- docs/Schnittstelle.md | 23 ++++ libs/VerbraucherBasisTrait.php | 20 +++- tests/Symcon/modules/Batterie.php | 6 + tests/Symcon/modules/LadestationGateway.php | 26 ++++- .../Symcon/modules/LadestationStandAlone.php | 28 ++++- tests/Symcon/modules/Pufferspeicher.php | 1 + tests/Symcon/modules/VerbraucherEinStufig.php | 14 +++ tests/Symcon/modules/Waermepumpe.php | 2 + tests/Symcon/modules/Warmwassererwaermer.php | 27 +++++ tests/VerbraucherBasisTraitTest.php | 109 ++++++++++++++++++ 18 files changed, 296 insertions(+), 38 deletions(-) diff --git a/Batterie/module.php b/Batterie/module.php index 97c7a90..c7445bc 100644 --- a/Batterie/module.php +++ b/Batterie/module.php @@ -153,6 +153,7 @@ class Batterie extends IPSModule implements VerbraucherSchnittstelle parent::ApplyChanges(); $this->aktualisiereVariablen(); + $this->bereinigeUngueltigenSollwert(); $this->registriereMesswertmeldungen(); $this->WriteAttributeBoolean('RegisterInitialisiert', false); @@ -219,7 +220,7 @@ class Batterie extends IPSModule implements VerbraucherSchnittstelle case 'Aktiv': $this->SetValue('Aktiv', (bool) $wert); if (!(bool) $wert) { - $this->setzeZustand('SollwertGueltig', false); + $this->verwerfeSollwert(); $this->SetTimerInterval('VorgabeTimeout', 0); } $this->aktualisiere(true); @@ -235,7 +236,7 @@ class Batterie extends IPSModule implements VerbraucherSchnittstelle case 'VorgabeTimeout': $this->SetTimerInterval('VorgabeTimeout', 0); - $this->setzeZustand('SollwertGueltig', false); + $this->verwerfeSollwert(); $this->aktualisiere(true); return; @@ -249,7 +250,7 @@ class Batterie extends IPSModule implements VerbraucherSchnittstelle return; case 'SichererZustand': - $this->setzeZustand('SollwertGueltig', false); + $this->verwerfeSollwert(); $this->schreibeRegister(0, true); $this->aktualisiere(true); return; @@ -313,7 +314,7 @@ class Batterie extends IPSModule implements VerbraucherSchnittstelle (int) $this->leseZustand('Sollleistung') ) ) { - $this->setzeZustand('SollwertGueltig', false); + $this->verwerfeSollwert(); $this->aktualisiere(false); } $this->planeMeldung(); @@ -419,7 +420,7 @@ class Batterie extends IPSModule implements VerbraucherSchnittstelle (int) $this->leseZustand('Sollleistung') ) ) { - $this->setzeZustand('SollwertGueltig', false); + $this->verwerfeSollwert(); } $this->pruefeVorgabeTimeout($jetzt); @@ -596,7 +597,7 @@ class Batterie extends IPSModule implements VerbraucherSchnittstelle if ($letzteVorgabe <= 0 || $jetzt - $letzteVorgabe >= $this->ReadPropertyInteger('VorgabeTimeout') ) { - $this->setzeZustand('SollwertGueltig', false); + $this->verwerfeSollwert(); $this->SetTimerInterval('VorgabeTimeout', 0); } } diff --git a/CHANGELOG.md b/CHANGELOG.md index 18e3383..6b61f23 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,13 @@ Alle wesentlichen Aenderungen an Enelix EMS werden in dieser Datei dokumentiert. ## 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, Leistungsfaktor und getrennten Anzeigeoptionen fuer Energiefluss und Diagramme. SDL wird unabhaengig von der Anzeige aus Hauslast und neuer Hausenergie diff --git a/LadestationGateway/module.php b/LadestationGateway/module.php index e10942f..68686d9 100644 --- a/LadestationGateway/module.php +++ b/LadestationGateway/module.php @@ -144,6 +144,7 @@ class LadestationGateway extends IPSModule implements VerbraucherSchnittstelle $this->initialisiereLokaleEinstellungen(); $this->aktualisiereVariablen(); + $this->bereinigeUngueltigenSollwert(); $this->WriteAttributeBoolean('GatewayVerbunden', false); $this->WriteAttributeInteger('LetzterGesetzterStrom', -1); $this->WriteAttributeBoolean('LadestartFuerVerbindungGesendet', false); @@ -179,7 +180,7 @@ class LadestationGateway extends IPSModule implements VerbraucherSchnittstelle $this->starteRegeluebergang(); } else { $this->beendeRegeluebergang(); - $this->setzeZustand('SollwertGueltig', false); + $this->verwerfeSollwert(); } $this->aktualisiere(true); return; @@ -192,7 +193,7 @@ class LadestationGateway extends IPSModule implements VerbraucherSchnittstelle $this->starteRegeluebergang(); } else { $this->beendeRegeluebergang(); - $this->setzeZustand('SollwertGueltig', false); + $this->verwerfeSollwert(); } $this->aktualisiere(true); return; @@ -215,7 +216,7 @@ class LadestationGateway extends IPSModule implements VerbraucherSchnittstelle case 'VorgabeTimeout': $this->SetTimerInterval('VorgabeTimeout', 0); - $this->setzeZustand('SollwertGueltig', false); + $this->verwerfeSollwert(); $this->aktualisiere(true); return; @@ -322,7 +323,7 @@ class LadestationGateway extends IPSModule implements VerbraucherSchnittstelle true ) ) { - $this->setzeZustand('SollwertGueltig', false); + $this->verwerfeSollwert(); $this->aktualisiere(false); } $this->SetTimerInterval('RueckmeldungVerzoegert', 100); @@ -559,7 +560,7 @@ class LadestationGateway extends IPSModule implements VerbraucherSchnittstelle if (in_array($sollleistung, $angebot, true)) { return $sollleistung; } - $this->setzeZustand('SollwertGueltig', false); + $this->verwerfeSollwert(); } if ( diff --git a/LadestationStandAlone/module.php b/LadestationStandAlone/module.php index 5e37852..0608f75 100644 --- a/LadestationStandAlone/module.php +++ b/LadestationStandAlone/module.php @@ -139,6 +139,7 @@ class LadestationStandAlone extends IPSModule implements VerbraucherSchnittstell $this->initialisiereLokaleEinstellungen(); $this->aktualisiereVariablen(); + $this->bereinigeUngueltigenSollwert(); // Ein persistiertes Ladeende nach Neueinrichtung oder Modulupdate einmal neu pruefen. $this->setzeZustand('FahrzeugGeladen', false); @@ -190,7 +191,7 @@ class LadestationStandAlone extends IPSModule implements VerbraucherSchnittstell $this->starteRegeluebergang(); } else { $this->beendeRegeluebergang(); - $this->setzeZustand('SollwertGueltig', false); + $this->verwerfeSollwert(); } $this->aktualisiere(true); return; @@ -203,7 +204,7 @@ class LadestationStandAlone extends IPSModule implements VerbraucherSchnittstell $this->starteRegeluebergang(); } else { $this->beendeRegeluebergang(); - $this->setzeZustand('SollwertGueltig', false); + $this->verwerfeSollwert(); } $this->aktualisiere(true); return; @@ -224,7 +225,7 @@ class LadestationStandAlone extends IPSModule implements VerbraucherSchnittstell case 'VorgabeTimeout': $this->SetTimerInterval('VorgabeTimeout', 0); - $this->setzeZustand('SollwertGueltig', false); + $this->verwerfeSollwert(); $this->aktualisiere(true); return; @@ -275,7 +276,7 @@ class LadestationStandAlone extends IPSModule implements VerbraucherSchnittstell if ((bool) $this->leseZustand('SollwertGueltig') && !in_array((int) $this->leseZustand('Sollleistung'), $this->leseLeistungsangebot(), true) ) { - $this->setzeZustand('SollwertGueltig', false); + $this->verwerfeSollwert(); $this->aktualisiere(false, false); } $this->SetTimerInterval('RueckmeldungVerzoegert', 100); @@ -560,7 +561,7 @@ class LadestationStandAlone extends IPSModule implements VerbraucherSchnittstell if (in_array($sollleistung, $angebot, true)) { return $sollleistung; } - $this->setzeZustand('SollwertGueltig', false); + $this->verwerfeSollwert(); $ersatz = 0; foreach ($angebot as $leistungswert) { if ($leistungswert > $sollleistung) { diff --git a/Pufferspeicher/module.php b/Pufferspeicher/module.php index 548e09e..60d20e8 100644 --- a/Pufferspeicher/module.php +++ b/Pufferspeicher/module.php @@ -112,6 +112,7 @@ class VerbraucherPufferspeicher extends IPSModule implements VerbraucherSchnitts parent::ApplyChanges(); $this->aktualisiereVariablen(); + $this->bereinigeUngueltigenSollwert(); try { $this->pruefeKonfiguration(); } catch (Throwable $fehler) { @@ -150,7 +151,7 @@ class VerbraucherPufferspeicher extends IPSModule implements VerbraucherSchnitts case 'Aktiv': $this->SetValue('Aktiv', (bool) $wert); if (!(bool) $wert) { - $this->setzeZustand('SollwertGueltig', false); + $this->verwerfeSollwert(); } $this->regelzyklus(true); return; @@ -208,7 +209,7 @@ class VerbraucherPufferspeicher extends IPSModule implements VerbraucherSchnitts if ((bool) $this->leseZustand('SollwertGueltig') && !in_array((int) $this->leseZustand('Sollleistung'), $angebot, true) ) { - $this->setzeZustand('SollwertGueltig', false); + $this->verwerfeSollwert(); $this->regelzyklus(false); } $this->SetTimerInterval('RueckmeldungVerzoegert', 100); @@ -290,7 +291,7 @@ class VerbraucherPufferspeicher extends IPSModule implements VerbraucherSchnitts if ($letzteVorgabe <= 0 || $jetzt - $letzteVorgabe > $this->ReadPropertyInteger('VorgabeTimeout') ) { - $this->setzeZustand('SollwertGueltig', false); + $this->verwerfeSollwert(); } $ziel = 0; @@ -301,7 +302,7 @@ class VerbraucherPufferspeicher extends IPSModule implements VerbraucherSchnitts } elseif ((bool) $this->leseZustand('SollwertGueltig')) { $ziel = (int) $this->leseZustand('Sollleistung'); if (!in_array($ziel, $angebot, true)) { - $this->setzeZustand('SollwertGueltig', false); + $this->verwerfeSollwert(); $ziel = 0; } } diff --git a/VerbraucherEinStufig/module.php b/VerbraucherEinStufig/module.php index 72193ca..4c493bb 100644 --- a/VerbraucherEinStufig/module.php +++ b/VerbraucherEinStufig/module.php @@ -118,6 +118,7 @@ class VerbraucherEinStufig extends IPSModule implements VerbraucherSchnittstelle parent::ApplyChanges(); $this->aktualisiereVariablen(); + $this->bereinigeUngueltigenSollwert(); $this->registriereMeldungen(); try { @@ -158,7 +159,7 @@ class VerbraucherEinStufig extends IPSModule implements VerbraucherSchnittstelle case 'Aktiv': $this->SetValue('Aktiv', (bool) $wert); if (!(bool) $wert) { - $this->setzeZustand('SollwertGueltig', false); + $this->verwerfeSollwert(); $this->SetTimerInterval('VorgabeTimeout', 0); } $this->aktualisiere(true); @@ -214,7 +215,7 @@ class VerbraucherEinStufig extends IPSModule implements VerbraucherSchnittstelle if ((bool) $this->leseZustand('SollwertGueltig') && !in_array((int) $this->leseZustand('Sollleistung'), $this->leseLeistungsangebot(), true) ) { - $this->setzeZustand('SollwertGueltig', false); + $this->verwerfeSollwert(); } $this->planeMeldung(); return; @@ -254,7 +255,7 @@ class VerbraucherEinStufig extends IPSModule implements VerbraucherSchnittstelle true ) ) { - $this->setzeZustand('SollwertGueltig', false); + $this->verwerfeSollwert(); } $aktiv = (bool) $this->GetValue('Aktiv'); @@ -474,7 +475,7 @@ class VerbraucherEinStufig extends IPSModule implements VerbraucherSchnittstelle if ($letzteVorgabe <= 0 || $jetzt - $letzteVorgabe >= $this->ReadPropertyInteger('VorgabeTimeout') ) { - $this->setzeZustand('SollwertGueltig', false); + $this->verwerfeSollwert(); $this->SetTimerInterval('VorgabeTimeout', 0); } } @@ -528,7 +529,7 @@ class VerbraucherEinStufig extends IPSModule implements VerbraucherSchnittstelle $this->WriteAttributeString('LaufzeitTag', date('Y-m-d', $jetzt)); $this->WriteAttributeInteger('LaufzeitStandZeit', $jetzt); $this->setzeZustand('Tageslaufzeit', 0); - $this->setzeZustand('SollwertGueltig', false); + $this->verwerfeSollwert(); $this->setzeZustand('Rueckmeldefehler', false); $this->WriteAttributeBoolean('Initialisiert', true); } diff --git a/Waermepumpe/module.php b/Waermepumpe/module.php index 8263efa..4e40a34 100644 --- a/Waermepumpe/module.php +++ b/Waermepumpe/module.php @@ -132,6 +132,7 @@ class VerbraucherWaermepumpe extends IPSModule implements VerbraucherSchnittstel parent::ApplyChanges(); $this->aktualisiereVariablen(); + $this->bereinigeUngueltigenSollwert(); $this->registriereMeldungen(); try { $this->pruefeKonfiguration(); @@ -172,7 +173,7 @@ class VerbraucherWaermepumpe extends IPSModule implements VerbraucherSchnittstel case 'Aktiv': $this->SetValue('Aktiv', (bool) $wert); if (!(bool) $wert) { - $this->setzeZustand('SollwertGueltig', false); + $this->verwerfeSollwert(); $this->SetTimerInterval('VorgabeTimeout', 0); } $this->aktualisiere(true); @@ -230,7 +231,7 @@ class VerbraucherWaermepumpe extends IPSModule implements VerbraucherSchnittstel if ((bool) $this->leseZustand('SollwertGueltig') && !in_array((int) $this->leseZustand('Sollleistung'), $this->leseLeistungsangebot(), true) ) { - $this->setzeZustand('SollwertGueltig', false); + $this->verwerfeSollwert(); } $this->planeMeldung(); return; @@ -286,7 +287,7 @@ class VerbraucherWaermepumpe extends IPSModule implements VerbraucherSchnittstel $this->WriteAttributeInteger('Laufbeginn', $laeuft ? $jetzt : 0); $this->schalte(WaermepumpenRegler::STEUERZUSTAND_NORMAL, $jetzt); $steuerzustand = WaermepumpenRegler::STEUERZUSTAND_NORMAL; - $this->setzeZustand('SollwertGueltig', false); + $this->verwerfeSollwert(); } $this->aktualisiereSperrerholung($laeuft, $jetzt); @@ -828,7 +829,7 @@ class VerbraucherWaermepumpe extends IPSModule implements VerbraucherSchnittstel $ablauf = $this->ReadAttributeInteger('LetzteVorgabeZeit') + $this->ReadPropertyInteger('VorgabeTimeout'); if ($this->ReadAttributeInteger('LetzteVorgabeZeit') <= 0 || $jetzt >= $ablauf) { - $this->setzeZustand('SollwertGueltig', false); + $this->verwerfeSollwert(); $this->SetTimerInterval('VorgabeTimeout', 0); } } diff --git a/Warmwassererwaermer/module.php b/Warmwassererwaermer/module.php index 5ad6f7a..6b14382 100644 --- a/Warmwassererwaermer/module.php +++ b/Warmwassererwaermer/module.php @@ -122,6 +122,7 @@ class VerbraucherWarmwassererwaermer extends IPSModule implements VerbraucherSch $this->initialisiereTemperaturen(); $this->aktualisiereVariablen(); + $this->bereinigeUngueltigenSollwert(); try { $this->pruefeKonfiguration(); @@ -219,7 +220,7 @@ class VerbraucherWarmwassererwaermer extends IPSModule implements VerbraucherSch if ((bool) $this->leseZustand('SollwertGueltig') && !in_array((int) $this->leseZustand('Sollleistung'), $angebot, true) ) { - $this->setzeZustand('SollwertGueltig', false); + $this->verwerfeSollwert(); $this->regelzyklus(false); } $this->SetTimerInterval('RueckmeldungVerzoegert', 100); @@ -264,7 +265,7 @@ class VerbraucherWarmwassererwaermer extends IPSModule implements VerbraucherSch if ($letzteVorgabe <= 0 || $jetzt - $letzteVorgabe > $this->ReadPropertyInteger('VorgabeTimeout') ) { - $this->setzeZustand('SollwertGueltig', false); + $this->verwerfeSollwert(); } $ziel = 0; @@ -276,7 +277,7 @@ class VerbraucherWarmwassererwaermer extends IPSModule implements VerbraucherSch } elseif ((bool) $this->leseZustand('SollwertGueltig')) { $ziel = (int) $this->leseZustand('Sollleistung'); if (!in_array($ziel, $angebot, true)) { - $this->setzeZustand('SollwertGueltig', false); + $this->verwerfeSollwert(); $ziel = 0; } } diff --git a/docs/Schnittstelle.md b/docs/Schnittstelle.md index 9fda990..beb87c5 100644 --- a/docs/Schnittstelle.md +++ b/docs/Schnittstelle.md @@ -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 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 ```json diff --git a/libs/VerbraucherBasisTrait.php b/libs/VerbraucherBasisTrait.php index 083d324..63c0ffa 100644 --- a/libs/VerbraucherBasisTrait.php +++ b/libs/VerbraucherBasisTrait.php @@ -5,13 +5,31 @@ declare(strict_types=1); 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 * konkreten Module ergaenzen Geraeteanbindung, Leistungsangebot und Zustand. */ 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 { $this->RegisterPropertyInteger('PrioritaetPV', 0); diff --git a/tests/Symcon/modules/Batterie.php b/tests/Symcon/modules/Batterie.php index 1b8bd4a..5277610 100644 --- a/tests/Symcon/modules/Batterie.php +++ b/tests/Symcon/modules/Batterie.php @@ -133,6 +133,7 @@ PHP 'GoodWe-Entladen verwendet negativen Managerwert und Modus 12', static function (TestContext $test) use ( $managerdatenSenden, + $batterieID, $modusID, $leistungID ): void { @@ -141,6 +142,10 @@ PHP static fn (): bool => (float) GetValue($leistungID) === 843.0 && (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 ): void { $test->configureInstance($batterieID, ['Batteriemanagement' => 1]); + $test->assertSame(0, GetValue($test->objectByIdent('Sollleistung', $batterieID))); $test->waitUntil( static fn (): bool => (float) GetValue($leistungID) === 0.0 && (int) GetValue($managementID) === 1 diff --git a/tests/Symcon/modules/LadestationGateway.php b/tests/Symcon/modules/LadestationGateway.php index 679a3a3..a9d22ef 100644 --- a/tests/Symcon/modules/LadestationGateway.php +++ b/tests/Symcon/modules/LadestationGateway.php @@ -186,7 +186,7 @@ return static function (TestContext $test): void { $test->runCase('Solarumschaltung wartet auf die neue Manager-Vorgabe', static function ( TestContext $test - ) use ($station): void { + ) use ($station, $sendeBeobachtung): void { $managerId = $test->createInstance( '{6F771B18-59D4-4C8A-B951-3B2FE9F6A2C4}', 'Hilfsmanager Easee-Uebergang', @@ -221,10 +221,11 @@ return static function (TestContext $test): void { (string) GetValue($test->objectByIdent('LetzterGeraetebefehl', $station)) ); $test->assertSame( - 4104, + 0, 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); $test->assertTrue(str_contains( @@ -234,6 +235,25 @@ return static function (TestContext $test): void { IPS_RequestAction($station, 'Solarladen', true); $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 ( diff --git a/tests/Symcon/modules/LadestationStandAlone.php b/tests/Symcon/modules/LadestationStandAlone.php index bfd8615..395af5d 100644 --- a/tests/Symcon/modules/LadestationStandAlone.php +++ b/tests/Symcon/modules/LadestationStandAlone.php @@ -387,10 +387,11 @@ return static function (TestContext $test): void { 'Die Umschaltung darf vor der neuen Manager-Vorgabe keinen Zwischenwert setzen.' ); $test->assertSame( - 4104, + 0, 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)); $test->assertSame('[10944]', $angebot, 'PV ohne Solarladen muss nur das Maximum anbieten.'); $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 ( TestContext $test ) use ($stationen): void { diff --git a/tests/Symcon/modules/Pufferspeicher.php b/tests/Symcon/modules/Pufferspeicher.php index 1ba5540..f33059b 100644 --- a/tests/Symcon/modules/Pufferspeicher.php +++ b/tests/Symcon/modules/Pufferspeicher.php @@ -105,6 +105,7 @@ return static function (TestContext $test): void { $test->assertSame(false, GetValue($test->objectByIdent('Heizbedarf', $verbraucherId))); SetValue($pufferId, 55.0); + $test->assertSame(0, GetValue($test->objectByIdent('Sollleistung', $verbraucherId))); IPS_RequestAction($verbraucherId, 'Melden', true); $test->assertSame([false, false], $kontaktzustand()); }); diff --git a/tests/Symcon/modules/VerbraucherEinStufig.php b/tests/Symcon/modules/VerbraucherEinStufig.php index 071140f..20ff316 100644 --- a/tests/Symcon/modules/VerbraucherEinStufig.php +++ b/tests/Symcon/modules/VerbraucherEinStufig.php @@ -225,4 +225,18 @@ return static function (TestContext $test): void { ) === false); $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))); + }); }; diff --git a/tests/Symcon/modules/Waermepumpe.php b/tests/Symcon/modules/Waermepumpe.php index 243064e..9e78c1d 100644 --- a/tests/Symcon/modules/Waermepumpe.php +++ b/tests/Symcon/modules/Waermepumpe.php @@ -121,6 +121,8 @@ return static function (TestContext $test): void { TestContext $test ) use ($waermepumpe, $kontakt1, $kontakt2): void { 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); $test->waitUntil( static fn (): bool => GetValue($kontakt1) === false diff --git a/tests/Symcon/modules/Warmwassererwaermer.php b/tests/Symcon/modules/Warmwassererwaermer.php index e12602d..a08a681 100644 --- a/tests/Symcon/modules/Warmwassererwaermer.php +++ b/tests/Symcon/modules/Warmwassererwaermer.php @@ -110,6 +110,33 @@ return static function (TestContext $test): void { $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 === '') { $test->runCase('Manager bleibt ohne Lizenz sicher gesperrt', static function ( TestContext $test diff --git a/tests/VerbraucherBasisTraitTest.php b/tests/VerbraucherBasisTraitTest.php index 116f505..bfcde28 100644 --- a/tests/VerbraucherBasisTraitTest.php +++ b/tests/VerbraucherBasisTraitTest.php @@ -9,6 +9,86 @@ use PHPUnit\Framework\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 { $modul = new VerbraucherBasisDummy(); @@ -50,6 +130,35 @@ final class VerbraucherBasisDummy { use VerbraucherBasisTrait; + /** @var array */ + public array $zustand = []; + + /** @var array */ + 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 */ public array $properties = [];