Manager-Diagnose und Verbrauchersuche verbessern
Tests / test (push) Failing after 40s

This commit is contained in:
dh
2026-09-17 09:30:26 +00:00
parent 92e8ec59df
commit 29cfd8ceaa
7 changed files with 227 additions and 75 deletions
+24 -12
View File
@@ -1,5 +1,16 @@
{
"elements": [
{
"type": "ExpansionPanel",
"caption": "Lizenzierung",
"items": [
{
"type": "ValidationTextBox",
"name": "LizenzAnschluss",
"caption": "Konfiguration als JSON"
}
]
},
{
"type": "ExpansionPanel",
"caption": "Grundeinstellungen",
@@ -103,6 +114,13 @@
"caption": "Suchbereich",
"visible": true
},
{
"type": "Button",
"name": "VerbraucherAktualisieren",
"caption": "Verbraucher aktualisieren",
"visible": true,
"onClick": "IPS_RequestAction($id, 'FormVerbraucherAktualisieren', json_encode(['SuchbereichID' => $SuchbereichID, 'Zuordnung' => $AutomatischeVerbraucherZuordnung]));"
},
{
"type": "List",
"name": "VerbraucherZuordnung",
@@ -203,10 +221,15 @@
"caption": "Netzleistungsfaktor",
"digits": 4
},
{
"type": "CheckBox",
"name": "DiagnosevariablenAnzeigen",
"caption": "Diagnosevariablen anlegen"
},
{
"type": "CheckBox",
"name": "LoggingEin",
"caption": "Logging ein"
"caption": "Laufendes Debug-Logging aktivieren"
}
]
},
@@ -232,17 +255,6 @@
}
]
},
{
"type": "ExpansionPanel",
"caption": "Lizenzierung",
"items": [
{
"type": "ValidationTextBox",
"name": "LizenzAnschluss",
"caption": "Konfiguration als JSON"
}
]
},
{
"type": "ExpansionPanel",
"caption": "Stoerueberwachung",
+144 -49
View File
@@ -18,11 +18,27 @@ class Manager extends IPSModule implements ManagerSchnittstelle
private const VM_UPDATE = 10603;
private const VERBRAUCHER_MODUL_IDS = [
'{B7C54AF4-AD7D-4FE4-B75D-203693906251}',
'{15879A4E-D0C2-4495-83DE-46E1E462591E}',
];
private const MONATSNAMEN = [
1 => 'Januar', 'Februar', 'Maerz', 'April', 'Mai', 'Juni',
'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember',
];
private const DIAGNOSE_VARIABLEN = [
'NetzleistungGueltig',
'WirksameLastspitzengrenze',
'Verteilbudget',
'VerbraucherAnzahl',
'VerbraucherVerfuegbar',
'Verbraucherstatus',
'Monatsgrenzen',
'Prognosestatus',
'SDLStatus',
'Lizenzstatus',
'Stoerueberwachungsstatus',
'Sammelstoerung',
'Stoertext',
];
public function Create(): void
{
@@ -47,25 +63,13 @@ class Manager extends IPSModule implements ManagerSchnittstelle
$this->RegisterPropertyString('SDLAnschluss', '{}');
$this->RegisterPropertyString('LizenzAnschluss', '{}');
$this->RegisterPropertyString('StoermeldeAnschluss', '{}');
$this->RegisterPropertyBoolean('DiagnosevariablenAnzeigen', false);
$this->RegisterPropertyBoolean('LoggingEin', false);
$this->RegisterVariableBoolean('Aktiv', 'Aktiv', '~Switch', 10);
$this->EnableAction('Aktiv');
$this->RegisterVariableString('Betriebsart', 'Betriebsart', '', 20);
$this->RegisterVariableFloat('Netzleistung', 'Netzleistung', '', 30);
$this->RegisterVariableBoolean('NetzleistungGueltig', 'Netzleistung gueltig', '~Switch', 40);
$this->RegisterVariableFloat('WirksameLastspitzengrenze', 'Wirksame Lastspitzengrenze', '', 50);
$this->RegisterVariableFloat('Verteilbudget', 'Verteilbudget', '', 60);
$this->RegisterVariableInteger('VerbraucherAnzahl', 'Verbraucher Anzahl', '', 70);
$this->RegisterVariableInteger('VerbraucherVerfuegbar', 'Verbraucher verfuegbar', '', 80);
$this->RegisterVariableString('Verbraucherstatus', 'Verbraucherstatus', '', 90);
$this->RegisterVariableString('Monatsgrenzen', 'Monatsgrenzen', '', 100);
$this->RegisterVariableString('Prognosestatus', 'Prognosestatus', '', 110);
$this->RegisterVariableString('SDLStatus', 'SDL-Status', '', 120);
$this->RegisterVariableString('Lizenzstatus', 'Lizenzstatus', '', 130);
$this->RegisterVariableString('Stoerueberwachungsstatus', 'Stoerueberwachungsstatus', '', 140);
$this->RegisterVariableBoolean('Sammelstoerung', 'Sammelstoerung', '~Alert', 150);
$this->RegisterVariableString('Stoertext', 'Stoertext', '', 160);
$this->RegisterAttributeString('VerbraucherCache', '{}');
$this->RegisterAttributeString('LetzteSollwerte', '{}');
@@ -85,6 +89,8 @@ class Manager extends IPSModule implements ManagerSchnittstelle
{
parent::ApplyChanges();
$this->aktualisiereVariablen();
try {
$this->pruefeKonfiguration();
} catch (Throwable $fehler) {
@@ -100,8 +106,7 @@ class Manager extends IPSModule implements ManagerSchnittstelle
$this->SetTimerInterval('Regelzyklus', 0);
$this->SetTimerInterval('VorgabenErneuern', 0);
$this->SetTimerInterval('KeepAlive', $this->ReadPropertyInteger('KeepAlive') * 1000);
$this->SetValue('Monatsgrenzen', $this->ReadPropertyString('Monatsgrenzen'));
$this->aktualisiereSichtbarkeit();
$this->setzeDiagnosewert('Monatsgrenzen', $this->ReadPropertyString('Monatsgrenzen'));
$this->aktualisiereAnschlussstatus();
$this->SetStatus(self::STATUS_AKTIV);
}
@@ -127,6 +132,12 @@ class Manager extends IPSModule implements ManagerSchnittstelle
$automatisch
);
$this->setzeFormularParameter($formular['elements'], 'SuchbereichID', 'visible', $automatisch);
$this->setzeFormularParameter(
$formular['elements'],
'VerbraucherAktualisieren',
'visible',
$automatisch
);
$this->setzeFormularParameter(
$formular['elements'],
'AutomatischeVerbraucherZuordnung',
@@ -193,6 +204,30 @@ class Manager extends IPSModule implements ManagerSchnittstelle
$this->UpdateFormField('VerbraucherZuordnung', 'visible', !$automatisch);
$this->UpdateFormField('AutomatischeVerbraucherZuordnung', 'visible', $automatisch);
$this->UpdateFormField('SuchbereichID', 'visible', $automatisch);
$this->UpdateFormField('VerbraucherAktualisieren', 'visible', $automatisch);
return;
case 'FormVerbraucherAktualisieren':
if (!is_string($wert)) {
throw new InvalidArgumentException('Suchparameter muessen als JSON uebergeben werden.');
}
$suchparameter = json_decode($wert, true, 512, JSON_THROW_ON_ERROR);
if (!is_array($suchparameter) || !is_array($suchparameter['Zuordnung'] ?? null)) {
throw new InvalidArgumentException('Suchparameter sind ungueltig.');
}
$suchbereichID = $suchparameter['SuchbereichID'] ?? 0;
if (!is_int($suchbereichID)) {
throw new InvalidArgumentException('SuchbereichID muss eine Ganzzahl sein.');
}
$werte = $this->automatischeVerbraucherFuerFormular(
$suchparameter['Zuordnung'],
$suchbereichID
);
$this->UpdateFormField(
'AutomatischeVerbraucherZuordnung',
'values',
json_encode($werte, JSON_THROW_ON_ERROR)
);
return;
case 'FormLastspitzenmodus':
@@ -265,12 +300,12 @@ class Manager extends IPSModule implements ManagerSchnittstelle
{
[$netzleistung, $netzleistungGueltig] = $this->leseNetzleistung();
$this->SetValue('Netzleistung', $netzleistung);
$this->SetValue('NetzleistungGueltig', $netzleistungGueltig);
$this->setzeDiagnosewert('NetzleistungGueltig', $netzleistungGueltig);
$aktiv = (bool) $this->GetValue('Aktiv');
if (!$aktiv || !$netzleistungGueltig) {
$this->SetValue('Betriebsart', ManagerRegler::BETRIEBSART_INAKTIV);
$this->SetValue('Verteilbudget', 0.0);
$this->setzeDiagnosewert('Verteilbudget', 0.0);
$this->SetStatus($aktiv ? self::STATUS_MESSWERT_UNGUELTIG : self::STATUS_AKTIV);
return;
}
@@ -305,9 +340,9 @@ class Manager extends IPSModule implements ManagerSchnittstelle
);
$this->SetValue('Betriebsart', $betriebsart);
$this->SetValue('WirksameLastspitzengrenze', $lastspitzengrenze ?? 0.0);
$this->SetValue('Verteilbudget', (float) $ergebnis['Verteilbudget_W']);
$this->SetValue('Verbraucherstatus', json_encode([
$this->setzeDiagnosewert('WirksameLastspitzengrenze', $lastspitzengrenze ?? 0.0);
$this->setzeDiagnosewert('Verteilbudget', (float) $ergebnis['Verteilbudget_W']);
$this->setzeDiagnosewert('Verbraucherstatus', json_encode([
'Betriebsart' => $betriebsart,
'Restabweichung_W' => $ergebnis['Restabweichung_W'],
'Verbraucher' => $verbraucher,
@@ -358,7 +393,7 @@ class Manager extends IPSModule implements ManagerSchnittstelle
private function aktuelleVerbraucher(array &$stoerungen): array
{
$ids = $this->aktiveVerbraucherIDs();
$this->SetValue('VerbraucherAnzahl', count($ids));
$this->setzeDiagnosewert('VerbraucherAnzahl', count($ids));
$cache = $this->leseJsonAttribut('VerbraucherCache');
$timeout = $this->ReadPropertyInteger('VerbraucherTimeout');
$aktuell = [];
@@ -377,7 +412,7 @@ class Manager extends IPSModule implements ManagerSchnittstelle
}
$verfuegbar = array_filter($aktuell, static fn (array $eintrag): bool => $eintrag['Daten']['Verfuegbar']);
$this->SetValue('VerbraucherVerfuegbar', count($verfuegbar));
$this->setzeDiagnosewert('VerbraucherVerfuegbar', count($verfuegbar));
return $aktuell;
}
@@ -411,6 +446,15 @@ class Manager extends IPSModule implements ManagerSchnittstelle
throw new InvalidArgumentException($property . ' muss eine JSON-Liste sein.');
}
return $this->aktiveVerbraucherIDsAusZuordnung($zuordnung);
}
/**
* @param list<array<string, mixed>> $zuordnung
* @return list<int>
*/
private function aktiveVerbraucherIDsAusZuordnung(array $zuordnung): array
{
$ids = [];
foreach ($zuordnung as $eintrag) {
if (!is_array($eintrag)) {
@@ -428,20 +472,29 @@ class Manager extends IPSModule implements ManagerSchnittstelle
return array_values(array_unique($ids));
}
/** @return list<array{Name: string, InstanzID: int, Aktiv: bool}> */
private function automatischeVerbraucherFuerFormular(): array
{
$gespeichert = json_decode(
$this->ReadPropertyString('AutomatischeVerbraucherZuordnung'),
true,
512,
JSON_THROW_ON_ERROR
);
$ausgewaehlt = $gespeichert === []
? $this->leseAktiveVerbraucherIDs('VerbraucherZuordnung')
: $this->leseAktiveVerbraucherIDs('AutomatischeVerbraucherZuordnung');
/**
* @param list<array<string, mixed>>|null $aktuelleZuordnung
* @return list<array{Name: string, InstanzID: int, Aktiv: bool}>
*/
private function automatischeVerbraucherFuerFormular(
?array $aktuelleZuordnung = null,
?int $suchbereichID = null
): array {
if ($aktuelleZuordnung === null) {
$gespeichert = json_decode(
$this->ReadPropertyString('AutomatischeVerbraucherZuordnung'),
true,
512,
JSON_THROW_ON_ERROR
);
$ausgewaehlt = $gespeichert === []
? $this->leseAktiveVerbraucherIDs('VerbraucherZuordnung')
: $this->aktiveVerbraucherIDsAusZuordnung($gespeichert);
} else {
$ausgewaehlt = $this->aktiveVerbraucherIDsAusZuordnung($aktuelleZuordnung);
}
$suchbereichID = $this->ReadPropertyInteger('SuchbereichID');
$suchbereichID ??= $this->ReadPropertyInteger('SuchbereichID');
$werte = [];
foreach (self::VERBRAUCHER_MODUL_IDS as $modulID) {
foreach (IPS_GetInstanceListByModuleID($modulID) as $instanzID) {
@@ -558,8 +611,8 @@ class Manager extends IPSModule implements ManagerSchnittstelle
}
}
$stoerungen = array_values(array_unique($stoerungen));
$this->SetValue('Sammelstoerung', $stoerungen !== []);
$this->SetValue('Stoertext', implode("\n", $stoerungen));
$this->setzeDiagnosewert('Sammelstoerung', $stoerungen !== []);
$this->setzeDiagnosewert('Stoertext', implode("\n", $stoerungen));
}
private function registriereNetzleistungsmeldung(): void
@@ -575,19 +628,61 @@ class Manager extends IPSModule implements ManagerSchnittstelle
$this->WriteAttributeInteger('RegistrierteNetzleistungVariableID', $neu);
}
private function aktualisiereSichtbarkeit(): void
private function aktualisiereVariablen(): void
{
$logging = $this->ReadPropertyBoolean('LoggingEin');
foreach ([
'NetzleistungGueltig', 'WirksameLastspitzengrenze', 'Verteilbudget',
'VerbraucherAnzahl', 'VerbraucherVerfuegbar', 'Verbraucherstatus',
'Prognosestatus', 'SDLStatus', 'Lizenzstatus',
'Stoerueberwachungsstatus', 'Sammelstoerung', 'Stoertext',
] as $ident) {
IPS_SetHidden($this->GetIDForIdent($ident), !$logging);
if ($this->ReadPropertyBoolean('DiagnosevariablenAnzeigen')) {
$this->RegisterVariableBoolean(
'NetzleistungGueltig',
'Netzleistung gueltig',
'~Switch',
40
);
$this->RegisterVariableFloat(
'WirksameLastspitzengrenze',
'Wirksame Lastspitzengrenze',
'',
50
);
$this->RegisterVariableFloat('Verteilbudget', 'Verteilbudget', '', 60);
$this->RegisterVariableInteger('VerbraucherAnzahl', 'Verbraucher Anzahl', '', 70);
$this->RegisterVariableInteger(
'VerbraucherVerfuegbar',
'Verbraucher verfuegbar',
'',
80
);
$this->RegisterVariableString('Verbraucherstatus', 'Verbraucherstatus', '', 90);
$this->RegisterVariableString('Monatsgrenzen', 'Monatsgrenzen', '', 100);
$this->RegisterVariableString('Prognosestatus', 'Prognosestatus', '', 110);
$this->RegisterVariableString('SDLStatus', 'SDL-Status', '', 120);
$this->RegisterVariableString('Lizenzstatus', 'Lizenzstatus', '', 130);
$this->RegisterVariableString(
'Stoerueberwachungsstatus',
'Stoerueberwachungsstatus',
'',
140
);
$this->RegisterVariableBoolean('Sammelstoerung', 'Sammelstoerung', '~Alert', 150);
$this->RegisterVariableString('Stoertext', 'Stoertext', '', 160);
return;
}
foreach (self::DIAGNOSE_VARIABLEN as $ident) {
$variablenID = @IPS_GetObjectIDByIdent($ident, $this->InstanceID);
if (is_int($variablenID) && $variablenID > 0 && IPS_VariableExists($variablenID)) {
$this->UnregisterVariable($ident);
}
}
}
/** @param bool|float|int|string $wert */
private function setzeDiagnosewert(string $ident, $wert): void
{
$variablenID = @IPS_GetObjectIDByIdent($ident, $this->InstanceID);
if (is_int($variablenID) && $variablenID > 0 && IPS_VariableExists($variablenID)) {
$this->SetValue($ident, $wert);
}
IPS_SetHidden($this->GetIDForIdent('Monatsgrenzen'), true);
$this->DisableAction('Monatsgrenzen');
}
private function aktualisiereAnschlussstatus(): void
@@ -600,7 +695,7 @@ class Manager extends IPSModule implements ManagerSchnittstelle
] as $property => $variable) {
$konfiguration = json_decode($this->ReadPropertyString($property), true, 512, JSON_THROW_ON_ERROR);
$aktiv = is_array($konfiguration) && ($konfiguration['Aktiv'] ?? false) === true;
$this->SetValue($variable, $aktiv ? 'Fehler' : 'NichtVerwendet');
$this->setzeDiagnosewert($variable, $aktiv ? 'Fehler' : 'NichtVerwendet');
}
}
+22 -9
View File
@@ -666,18 +666,18 @@ class VerbraucherWarmwassererwaermer extends IPSModule implements VerbraucherSch
$ergebnis = [];
foreach (IPS_GetInstanceListByModuleID(self::MANAGER_MODULE_ID) as $managerID) {
try {
$zuordnung = json_decode(
(string) IPS_GetProperty($managerID, 'VerbraucherZuordnung'),
true,
512,
JSON_THROW_ON_ERROR
);
$automatisch = (bool) IPS_GetProperty($managerID, 'AutomatischeSuche');
$property = $automatisch
? 'AutomatischeVerbraucherZuordnung'
: 'VerbraucherZuordnung';
$zuordnung = $this->leseManagerZuordnung($managerID, $property);
if ($automatisch && $zuordnung === []) {
$zuordnung = $this->leseManagerZuordnung($managerID, 'VerbraucherZuordnung');
}
} catch (Throwable $fehler) {
continue;
}
if (!is_array($zuordnung)) {
continue;
}
foreach ($zuordnung as $eintrag) {
if (!is_array($eintrag) || ($eintrag['Aktiv'] ?? false) !== true) {
continue;
@@ -693,6 +693,19 @@ class VerbraucherWarmwassererwaermer extends IPSModule implements VerbraucherSch
return array_values(array_unique($ergebnis));
}
/** @return array<mixed> */
private function leseManagerZuordnung(int $managerID, string $property): array
{
$zuordnung = json_decode(
(string) IPS_GetProperty($managerID, $property),
true,
512,
JSON_THROW_ON_ERROR
);
return is_array($zuordnung) ? $zuordnung : [];
}
private function pruefeKonfiguration(): void
{
foreach (['PrioritaetPV', 'PrioritaetPeak'] as $property) {
+8 -4
View File
@@ -8,7 +8,7 @@ kommen aus den Verbrauchermeldungen.
## Variablen
Ohne Logging sind nur `Aktiv`, `Betriebsart` und `Netzleistung` sichtbar.
Ohne angelegte Diagnosevariablen existieren nur `Aktiv`, `Betriebsart` und `Netzleistung`.
| Ident | Typ / Zugriff | Beschreibung |
| --- | --- | --- |
@@ -51,13 +51,16 @@ Ohne Logging sind nur `Aktiv`, `Betriebsart` und `Netzleistung` sichtbar.
| `SDLAnschluss` | String/JSON | Optionaler SDL/VGT-Anschluss. |
| `LizenzAnschluss` | String/JSON | Optionaler Lizenzanschluss. |
| `StoermeldeAnschluss` | String/JSON | Optionaler Anschluss zur Störüberwachung. |
| `LoggingEin` | Boolean | `false`; Protokollierung und zusätzliche Variablen anzeigen. |
| `DiagnosevariablenAnzeigen` | Boolean | `false`; zusätzliche Diagnosevariablen anlegen oder entfernen. |
| `LoggingEin` | Boolean | `false`; laufende Meldungen im Debug-Fenster ausgeben. |
Es gibt keine Sollwertquellenauswahl und keine zweite Prioritätseinstellung im
Manager. Im Modus `Aus` bleibt Solarladen aktiv, nur die Peak-Begrenzung entfällt.
Die Monatsgrenzen werden in den Grundeinstellungen über eine Schaltfläche
ein- und ausgeblendet. Die vier optionalen oberen Anschlüsse besitzen jeweils
einen eigenen Bereich.
ein- und ausgeblendet. Die automatische Verbrauchersuche kann dort erneut
ausgeführt werden, ohne andere ungespeicherte Formulareingaben zu verlieren.
Die vier optionalen oberen Anschlüsse besitzen jeweils einen eigenen Bereich;
die Lizenzierung steht an erster Stelle des Formulars.
## Verteilalgorithmus
@@ -84,6 +87,7 @@ wenn sich die beiden berechneten Korrekturen nicht ausreichend unterscheiden.
- Netzleistungsänderungen und Verbrauchermeldungen lösen die Berechnung aus.
- Identische Sollwerte werden nur beim Keep-alive erneut gesendet.
- Diagnosevariablen und laufendes Debug-Logging werden getrennt aktiviert.
- Verbraucherpakete werden zentral geprüft und nur von aktiv zugeordneten
Absendern angenommen.
- Veraltete oder noch fehlende Verbrauchermeldungen werden nicht verteilt und
+2 -1
View File
@@ -87,7 +87,8 @@ Zustandsablage und funktionieren auch ohne angelegte Diagnosevariablen weiter.
Das Modul implementiert `VerbraucherSchnittstelle::ManagerdatenEmpfangen()`.
Der Transport erfolgt wie beim Manager ueber `IPS_RequestAction` mit JSON. Der
Verbraucher besitzt keine Manager-ID-Property. Er ermittelt nur Manager, in
deren `VerbraucherZuordnung` seine Instanz aktiv eingetragen ist.
deren manueller oder automatischer Verbraucherzuordnung seine Instanz aktiv
eingetragen ist.
Die Rueckmeldung erfolgt ereignisbasiert und zusaetzlich alle
`Meldeintervall` Sekunden. Eine Rueckmeldung nach empfangener Vorgabe wird kurz
+25
View File
@@ -48,7 +48,9 @@ final class ManagerModulstrukturTest extends TestCase
'VerbraucherTimeout',
'MesswertMaxAlter',
'Umschaltdifferenz',
'DiagnosevariablenAnzeigen',
'LoggingEin',
'VerbraucherAktualisieren',
] as $property) {
self::assertStringContainsString($property, $json);
}
@@ -62,6 +64,29 @@ final class ManagerModulstrukturTest extends TestCase
) {
self::assertStringContainsString($bereich, $json);
}
self::assertSame('Lizenzierung', $formular['elements'][0]['caption']);
self::assertStringContainsString(
"'SuchbereichID' => \$SuchbereichID",
$json
);
self::assertStringContainsString(
"'Zuordnung' => \$AutomatischeVerbraucherZuordnung",
$json
);
}
public function testManagerTrenntDiagnosevariablenUndDebugLogging(): void
{
$inhalt = (string) file_get_contents(__DIR__ . '/../Manager/module.php');
self::assertStringContainsString(
"RegisterPropertyBoolean('DiagnosevariablenAnzeigen', false)",
$inhalt
);
self::assertStringContainsString("ReadPropertyBoolean('DiagnosevariablenAnzeigen')", $inhalt);
self::assertStringContainsString('UnregisterVariable($ident)', $inhalt);
self::assertStringContainsString("ReadPropertyBoolean('LoggingEin')", $inhalt);
self::assertStringContainsString('SendDebug(', $inhalt);
}
public function testManagerVerwendetDieNeuenZeitdefaults(): void
@@ -65,6 +65,8 @@ final class WarmwassererwaermerModulstrukturTest extends TestCase
self::assertStringContainsString('Nachrichtenvertrag::pruefeVerbraucherdaten', $inhalt);
self::assertStringNotContainsString("RegisterVariableString('PowerSteps'", $inhalt);
self::assertStringNotContainsString("RegisterPropertyInteger('ManagerID'", $inhalt);
self::assertStringContainsString("'AutomatischeVerbraucherZuordnung'", $inhalt);
self::assertStringContainsString("'VerbraucherZuordnung'", $inhalt);
}
public function testSechzehnDiagnosevariablenWerdenBedarfsgesteuertAngelegtUndGeloescht(): void