From 37addf4242729926e576a435c2252478a07d81dd Mon Sep 17 00:00:00 2001 From: dh_Agent Date: Tue, 6 Oct 2026 08:00:30 +0000 Subject: [PATCH] feat(manager): Energievisualisierung und Synchronisierung erweitern --- Manager/form.json | 61 +- Manager/module.php | 74 ++- libs/ManagerEnergieTrait.php | 673 ++++++++++++++++++++-- tests/ManagerEnergieModulstrukturTest.php | 20 +- tests/Symcon/modules/Manager.php | 147 ++++- 5 files changed, 864 insertions(+), 111 deletions(-) diff --git a/Manager/form.json b/Manager/form.json index e01761d..0c19799 100644 --- a/Manager/form.json +++ b/Manager/form.json @@ -260,10 +260,14 @@ "type": "Label", "caption": "Energiezaehler werden in kWh erwartet; ueber den Faktor koennen beispielsweise Wh (0,001) umgerechnet werden." }, + { + "type": "Label", + "caption": "Die Wechselrichter-Tabelle beschreibt die AC-Wandler der Anlage. PV-Flaechen und Batteriespeicher verweisen darauf, damit gemeinsame Leistungsgrenzen und die elektrische Kopplung eindeutig sind. Bei einem Hybridgeraet verwenden PV und Batterie dieselbe Wechselrichter-ID; bei einem AC-gekoppelten Speicher wird dessen separater Batteriewechselrichter erfasst." + }, { "type": "List", "name": "AnlagenWechselrichter", - "caption": "Wechselrichter", + "caption": "Wechselrichter / AC-Wandler", "rowCount": 4, "add": true, "delete": true, @@ -459,6 +463,10 @@ } ] }, + { + "type": "Label", + "caption": "Der Batterieeintrag beschreibt den Speicher selbst: Kapazitaet, Leistung, Ladezustand und Energiezaehler. Die Wechselrichter-ID ist nur die Verknuepfung zum zugehoerigen AC-Wandler und keine doppelte Batteriekonfiguration." + }, { "type": "List", "name": "AnlagenBatterien", @@ -722,26 +730,61 @@ { "type": "CheckBox", "name": "EnergyPieAnzeigen", - "caption": "Energy Pie unter dem Manager anzeigen", - "onChange": "IPS_RequestAction($id, 'FormEnergyPieAnzeigen', $EnergyPieAnzeigen);" + "caption": "Energy Pie unter dem Manager anzeigen" }, { "type": "CheckBox", "name": "EnergiediagrammeAnzeigen", - "caption": "Leistungs- und Energiediagramm unter dem Manager anzeigen", - "onChange": "IPS_RequestAction($id, 'FormEnergiediagrammeAnzeigen', $EnergiediagrammeAnzeigen);" + "caption": "Leistungs- und Energiediagramm unter dem Manager anzeigen" + }, + { + "type": "RowLayout", + "items": [ + { + "type": "Select", + "name": "DiagrammPVModus", + "caption": "PV in Diagrammen", + "options": [ + {"caption": "Summe", "value": 0}, + {"caption": "Einzelne Anlagen", "value": 1}, + {"caption": "Summe und einzelne Anlagen", "value": 2} + ] + }, + { + "type": "Select", + "name": "DiagrammBatterieModus", + "caption": "Batterien in Diagrammen", + "options": [ + {"caption": "Summe", "value": 0}, + {"caption": "Einzelne Speicher", "value": 1}, + {"caption": "Summe und einzelne Speicher", "value": 2} + ] + } + ] + }, + { + "type": "CheckBox", + "name": "LadestationenSeparatAnzeigen", + "caption": "Ladestationen separat in Energiefluss und Diagrammen anzeigen" + }, + { + "type": "CheckBox", + "name": "VerbraucherSeparatAnzeigen", + "caption": "Uebrige Verbraucher separat in Energiefluss und Diagrammen anzeigen" + }, + { + "type": "Label", + "caption": "Bestehende Energy Pies, Diagramme und Energiefluesse bleiben bei Apply unveraendert. Fuer eine Neuerzeugung den jeweiligen Schalter ausschalten, Apply ausfuehren, wieder einschalten und erneut Apply ausfuehren." }, { "type": "CheckBox", "name": "FunFactsAnzeigen", - "caption": "Energy Facts unter dem Manager anzeigen", - "onChange": "IPS_RequestAction($id, 'FormFunFactsAnzeigen', $FunFactsAnzeigen);" + "caption": "Energy Facts unter dem Manager anzeigen" }, { "type": "CheckBox", "name": "EnergieflussAnzeigen", - "caption": "Energiefluss unter dem Manager anzeigen", - "onChange": "IPS_RequestAction($id, 'FormEnergieflussAnzeigen', $EnergieflussAnzeigen);" + "caption": "Energiefluss unter dem Manager anzeigen" }, { "type": "Select", diff --git a/Manager/module.php b/Manager/module.php index 1090d94..d956413 100644 --- a/Manager/module.php +++ b/Manager/module.php @@ -171,6 +171,10 @@ class Manager extends IPSModule implements ManagerSchnittstelle $this->RegisterPropertyInteger('EnergieLoeschenMonate', 0); $this->RegisterPropertyBoolean('EnergyPieAnzeigen', false); $this->RegisterPropertyBoolean('EnergiediagrammeAnzeigen', false); + $this->RegisterPropertyInteger('DiagrammPVModus', 0); + $this->RegisterPropertyInteger('DiagrammBatterieModus', 0); + $this->RegisterPropertyBoolean('LadestationenSeparatAnzeigen', true); + $this->RegisterPropertyBoolean('VerbraucherSeparatAnzeigen', true); $this->RegisterPropertyBoolean('FunFactsAnzeigen', false); $this->RegisterPropertyBoolean('EnergieflussAnzeigen', false); $this->RegisterPropertyInteger('EnergieflussLeistungseinheit', 0); @@ -203,6 +207,9 @@ class Manager extends IPSModule implements ManagerSchnittstelle $this->RegisterAttributeInteger('MessungLetzteEnergieaufzeichnung', 0); $this->RegisterAttributeString('MessungEnergiePuffer', '{}'); $this->RegisterAttributeString('MessungZaehlerstaende', '{}'); + $this->RegisterAttributeString('EnergiediagrammKonfiguration', '{}'); + $this->RegisterAttributeString('EnergieflussKonfiguration', '{}'); + $this->RegisterAttributeBoolean('EnergieflussNeuErzeugen', false); // Die alten Timer bleiben deaktiviert registriert, damit bestehende Instanzen sauber migrieren. $this->RegisterTimer('Regelzyklus', 0, ''); @@ -238,8 +245,8 @@ class Manager extends IPSModule implements ManagerSchnittstelle try { $this->pruefeKonfiguration(); - $this->konfiguriereEnergiearchiv(); $this->synchronisiereEnergievisualisierungen(); + $this->konfiguriereEnergiearchiv(); } catch (Throwable $fehler) { $this->SetStatus(self::STATUS_KONFIGURATION_UNGUELTIG); $this->SetTimerInterval('Regelzyklus', 0); @@ -616,7 +623,10 @@ class Manager extends IPSModule implements ManagerSchnittstelle ]; $this->WriteAttributeString('VerbraucherCache', json_encode($cache, JSON_THROW_ON_ERROR)); $this->aktualisiereEnergieflusswerte(); - $this->regeln(false); + $this->aktualisiereDiagrammMesswerte(); + if ($this->GetBuffer('VerbraucherSynchronisierungAktiv') !== '1') { + $this->regeln(false); + } } /** @param array $daten */ @@ -1008,13 +1018,20 @@ class Manager extends IPSModule implements ManagerSchnittstelle foreach ($ids as $instanzID) { $eintrag = $cache[(string) $instanzID] ?? null; - $grund = ''; - if (!is_array($eintrag) || !isset($eintrag['EmpfangenAm'], $eintrag['Daten'])) { - $grund = 'Noch keine Meldung'; - } elseif (time() - (int) $eintrag['EmpfangenAm'] > $timeout) { - $grund = 'Meldung ist veraltet'; - } elseif (($eintrag['Daten']['Betriebsart'] ?? null) !== $betriebsart) { - $grund = 'Leistungsangebot gehoert nicht zur Betriebsart ' . $betriebsart; + $grund = $this->verbraucherMeldegrund($eintrag, $betriebsart, $timeout); + $synchronisierungsfehler = ''; + if (in_array($grund, ['Noch keine Meldung', 'Meldung ist veraltet'], true)) { + try { + $this->SetBuffer('VerbraucherSynchronisierungAktiv', '1'); + IPS_RequestAction($instanzID, 'Melden', true); + } catch (Throwable $fehler) { + $synchronisierungsfehler = $fehler->getMessage(); + } finally { + $this->SetBuffer('VerbraucherSynchronisierungAktiv', '0'); + } + $cache = $this->leseJsonAttribut('VerbraucherCache'); + $eintrag = $cache[(string) $instanzID] ?? null; + $grund = $this->verbraucherMeldegrund($eintrag, $betriebsart, $timeout); } if ($grund !== '') { @@ -1025,8 +1042,11 @@ class Manager extends IPSModule implements ManagerSchnittstelle try { $this->sendeManagerdaten($instanzID, $betriebsart, null); } catch (Throwable $fehler) { - $stoerungen[] = $detail['Name'] . ': Betriebsart konnte nicht uebermittelt werden: ' - . $fehler->getMessage(); + $synchronisierungsfehler = $fehler->getMessage(); + } + if ($synchronisierungsfehler !== '') { + $stoerungen[] = $detail['Name'] . ': Vollmeldung konnte nicht angefordert werden: ' + . $synchronisierungsfehler; } continue; } @@ -1039,6 +1059,27 @@ class Manager extends IPSModule implements ManagerSchnittstelle return $aktuell; } + /** @param array|null $eintrag */ + private function verbraucherMeldegrund( + ?array $eintrag, + string $betriebsart, + int $timeout + ): string { + if (!isset($eintrag['EmpfangenAm'], $eintrag['Daten']) + || !is_array($eintrag['Daten']) + ) { + return 'Noch keine Meldung'; + } + if (time() - (int) $eintrag['EmpfangenAm'] > $timeout) { + return 'Meldung ist veraltet'; + } + if (($eintrag['Daten']['Betriebsart'] ?? null) !== $betriebsart) { + return 'Leistungsangebot gehoert nicht zur Betriebsart ' . $betriebsart; + } + + return ''; + } + private function setzeNichtGeregelteVerbraucher(string $grund): void { try { @@ -2120,6 +2161,7 @@ class Manager extends IPSModule implements ManagerSchnittstelle $this->WriteAttributeInteger('MessungLetzteEnergieaufzeichnung', $jetzt); } + $this->aktualisiereDiagrammMesswerte(true); $funFactsID = @IPS_GetObjectIDByIdent('EnergieFunFacts', $this->InstanceID); if (is_int($funFactsID) && $funFactsID > 0 && IPS_VariableExists($funFactsID)) { $this->aktualisiereFunFacts($funFactsID); @@ -2647,6 +2689,16 @@ class Manager extends IPSModule implements ManagerSchnittstelle throw new InvalidArgumentException($property . ' darf nicht negativ sein.'); } } + foreach (['DiagrammPVModus', 'DiagrammBatterieModus'] as $property) { + if (!in_array($this->ReadPropertyInteger($property), [0, 1, 2], true)) { + throw new InvalidArgumentException($property . ' muss 0, 1 oder 2 sein.'); + } + } + if (!in_array($this->ReadPropertyInteger('EnergieflussLeistungseinheit'), [0, 1], true)) { + throw new InvalidArgumentException( + 'EnergieflussLeistungseinheit muss 0 oder 1 sein.' + ); + } $energieLoeschen = $this->ReadPropertyInteger('EnergieLoeschenMonate'); $energieVerdichten = $this->ReadPropertyInteger('EnergieVerdichtenMonate'); if ($energieLoeschen > 0 && $energieLoeschen <= $energieVerdichten) { diff --git a/libs/ManagerEnergieTrait.php b/libs/ManagerEnergieTrait.php index d94b42b..2a8e8ab 100644 --- a/libs/ManagerEnergieTrait.php +++ b/libs/ManagerEnergieTrait.php @@ -55,6 +55,38 @@ trait ManagerEnergieTrait } } + foreach (IPS_GetChildrenIDs($this->InstanceID) as $variableID) { + $objekt = IPS_GetObject($variableID); + $ident = (string) ($objekt['ObjectIdent'] ?? ''); + if ((int) ($objekt['ObjectType'] ?? -1) !== 2 + || !str_starts_with($ident, 'Diagramm') + ) { + continue; + } + $istEnergie = str_contains($ident, 'Energie') + || str_contains($ident, 'BatterieLaden_') + || str_contains($ident, 'BatterieEntladen_'); + AC_SetLoggingStatus($archivID, $variableID, $aktiv); + AC_SetAggregationType($archivID, $variableID, $istEnergie ? 1 : 0); + AC_SetGraphStatus($archivID, $variableID, $aktiv); + $regeln = [-1 => $istEnergie ? $energietyp : $leistungstyp]; + $verdichten = $istEnergie + ? $this->ReadPropertyInteger('EnergieVerdichtenMonate') + : 0; + if ($verdichten > 0) { + $regeln[$verdichten] = 3; + } + $loeschen = $istEnergie + ? $this->ReadPropertyInteger('EnergieLoeschenMonate') + : $this->ReadPropertyInteger('LeistungLoeschenMonate'); + if ($loeschen > 0) { + $regeln[$loeschen] = 7; + } + if ($aktiv && $this->setzeVerdichtung($archivID, $variableID, $regeln)) { + $geaendert[] = $variableID; + } + } + IPS_ApplyChanges($archivID); foreach (array_unique($geaendert) as $variableID) { AC_ReAggregateVariable($archivID, $variableID); @@ -129,6 +161,7 @@ trait ManagerEnergieTrait } $this->entferneDiagrammUnterobjekt('Leistungsdiagramm'); $this->entferneDiagrammUnterobjekt('Energiediagramm'); + $this->WriteAttributeString('EnergiediagrammKonfiguration', '{}'); } private function synchronisiereFunFacts(bool $anzeigen): void @@ -154,17 +187,18 @@ trait ManagerEnergieTrait return; } $instanzID = $this->findeUnterobjekt('Energiefluss'); - if ($instanzID === 0) { - return; + if ($instanzID !== 0) { + $objekt = IPS_GetObject($instanzID); + $modulID = $objekt['ObjectType'] === 1 + ? (string) IPS_GetInstance($instanzID)['ModuleInfo']['ModuleID'] + : ''; + if ($modulID !== self::ENERGY_FLOW_MODULE_ID) { + throw new RuntimeException('Unterobjekt Energiefluss hat einen unpassenden Typ.'); + } + IPS_SetHidden($instanzID, true); } - $objekt = IPS_GetObject($instanzID); - $modulID = $objekt['ObjectType'] === 1 - ? (string) IPS_GetInstance($instanzID)['ModuleInfo']['ModuleID'] - : ''; - if ($modulID !== self::ENERGY_FLOW_MODULE_ID) { - throw new RuntimeException('Unterobjekt Energiefluss hat einen unpassenden Typ.'); - } - IPS_SetHidden($instanzID, true); + $this->WriteAttributeString('EnergieflussKonfiguration', '{}'); + $this->WriteAttributeBoolean('EnergieflussNeuErzeugen', true); } private function entferneInstanzUnterobjekt(string $ident, string $modulID): void @@ -216,6 +250,9 @@ trait ManagerEnergieTrait if ($modulID !== self::ENERGY_PIE_MODULE_ID) { throw new RuntimeException('Unterobjekt EnergyPie hat einen unpassenden Typ.'); } + IPS_SetHidden($instanzID, false); + + return 'Bestehende Energy-Pie-Konfiguration bleibt unveraendert.'; } IPS_SetName($instanzID, 'Energy Pie'); IPS_SetPosition($instanzID, 500); @@ -239,6 +276,7 @@ trait ManagerEnergieTrait if (!in_array(self::ENERGY_FLOW_MODULE_ID, IPS_GetModuleList(), true)) { throw new RuntimeException('Das Symcon-Modul Energy Distribution ist nicht installiert.'); } + $neuErzeugen = $this->ReadAttributeBoolean('EnergieflussNeuErzeugen'); $instanzID = $this->findeUnterobjekt('Energiefluss'); if ($instanzID === 0) { $instanzID = IPS_CreateInstance(self::ENERGY_FLOW_MODULE_ID); @@ -252,7 +290,15 @@ trait ManagerEnergieTrait if ($modulID !== self::ENERGY_FLOW_MODULE_ID) { throw new RuntimeException('Unterobjekt Energiefluss hat einen unpassenden Typ.'); } + if (!$neuErzeugen) { + $this->visualisierungsKonfiguration('EnergieflussKonfiguration'); + IPS_SetHidden($instanzID, false); + $this->aktualisiereEnergieflusswerte(); + + return 'Bestehende Energiefluss-Konfiguration bleibt unveraendert.'; + } } + $this->visualisierungsKonfiguration('EnergieflussKonfiguration'); IPS_SetName($instanzID, 'Energiefluss'); IPS_SetPosition($instanzID, 540); IPS_SetHidden($instanzID, false); @@ -289,7 +335,6 @@ trait ManagerEnergieTrait $this->energieflussKnoten($netzID, 'Stromnetz', 'grid'), $this->energieflussKnoten($hausID, 'Hausverbrauch', 'consumer'), ]; - $hatBatterieverbraucher = false; $position = 640; foreach ($verbraucher as $eintrag) { $istID = $this->energieflussLeistungsvariable( @@ -311,17 +356,13 @@ trait ManagerEnergieTrait true, $sollID ); - $hatBatterieverbraucher = $hatBatterieverbraucher - || $eintrag['Typ'] === 'storage'; $position += 10; } - if (!$hatBatterieverbraucher) { - $knoten[] = $this->energieflussKnoten( - $batterieID, - 'Batteriespeicher', - 'storage' - ); - } + $knoten[] = $this->energieflussKnoten( + $batterieID, + 'Batteriespeicher', + 'storage' + ); IPS_SetProperty($instanzID, 'Calculation', true); IPS_SetProperty( @@ -332,6 +373,7 @@ trait ManagerEnergieTrait IPS_SetProperty($instanzID, 'EnableLimits', false); IPS_SetProperty($instanzID, 'Restrictions', '[]'); IPS_ApplyChanges($instanzID); + $this->WriteAttributeBoolean('EnergieflussNeuErzeugen', false); $this->aktualisiereEnergieflusswerte($verbraucher); return 'Der Energiefluss wurde erstellt oder aktualisiert.'; @@ -360,7 +402,10 @@ trait ManagerEnergieTrait private function energieflussLeistungsprofil(): string { - if ($this->ReadPropertyInteger('EnergieflussLeistungseinheit') !== 1) { + $konfiguration = $this->visualisierungsKonfiguration( + 'EnergieflussKonfiguration' + ); + if ($konfiguration['EnergieflussLeistungseinheit'] !== 1) { return '~Power'; } @@ -388,18 +433,55 @@ trait ManagerEnergieTrait return $variableID; } + /** @return array */ + private function visualisierungsKonfiguration(string $attribut): array + { + $konfiguration = $this->leseJsonAttribut($attribut); + $gueltig = isset( + $konfiguration['DiagrammPVModus'], + $konfiguration['DiagrammBatterieModus'], + $konfiguration['LadestationenSeparatAnzeigen'], + $konfiguration['VerbraucherSeparatAnzeigen'], + $konfiguration['EnergieflussLeistungseinheit'] + ); + if ($gueltig) { + return $konfiguration; + } + + $konfiguration = [ + 'DiagrammPVModus' => $this->ReadPropertyInteger('DiagrammPVModus'), + 'DiagrammBatterieModus' => $this->ReadPropertyInteger('DiagrammBatterieModus'), + 'LadestationenSeparatAnzeigen' => + $this->ReadPropertyBoolean('LadestationenSeparatAnzeigen'), + 'VerbraucherSeparatAnzeigen' => + $this->ReadPropertyBoolean('VerbraucherSeparatAnzeigen'), + 'EnergieflussLeistungseinheit' => + $this->ReadPropertyInteger('EnergieflussLeistungseinheit'), + ]; + $this->WriteAttributeString( + $attribut, + json_encode($konfiguration, JSON_THROW_ON_ERROR) + ); + + return $konfiguration; + } + /** * @return list */ - private function energieflussVerbraucher(): array - { + private function energieflussVerbraucher( + string $konfigurationsAttribut = 'EnergieflussKonfiguration' + ): array { + $konfiguration = $this->visualisierungsKonfiguration($konfigurationsAttribut); $cache = $this->leseJsonAttribut('VerbraucherCache'); + $energien = $this->leseJsonAttribut('VerbraucherEnergie'); $ergebnis = []; foreach ($this->konfigurierteAktiveVerbraucherIDs() as $instanzID) { if (!IPS_InstanceExists($instanzID)) { @@ -416,17 +498,30 @@ trait ManagerEnergieTrait (string) IPS_GetInstance($instanzID)['ModuleInfo']['ModuleID'] ); $katalog = self::VERBRAUCHER_LIZENZEN[$modulID]['Katalog'] ?? ''; + if ($katalog === 'battery') { + continue; + } + if ($katalog === 'ev_charger' + && !$konfiguration['LadestationenSeparatAnzeigen'] + ) { + continue; + } + if ($katalog !== 'ev_charger' + && !$konfiguration['VerbraucherSeparatAnzeigen'] + ) { + continue; + } + $verbraucherEnergie = $energien[(string) $instanzID]['Energie_Wh'] ?? 0.0; $ergebnis[] = [ 'InstanzID' => $instanzID, 'Name' => IPS_GetName($instanzID), - 'Typ' => match ($katalog) { - 'battery' => 'storage', - 'ev_charger' => 'wallbox', - default => 'consumer', - }, + 'Typ' => $katalog === 'ev_charger' ? 'wallbox' : 'consumer', 'Istleistung_W' => $istleistung, 'Sollleistung_W' => $sollleistung, + 'Energie_Wh' => is_numeric($verbraucherEnergie) + ? (float) $verbraucherEnergie + : 0.0, ]; } @@ -443,7 +538,6 @@ trait ManagerEnergieTrait } $verbraucher = $verbraucher ?? $this->energieflussVerbraucher(); $verbraucherleistung = 0.0; - $hatBatterieverbraucher = false; foreach ($verbraucher as $eintrag) { $this->setzeEnergieflussLeistungswert( 'EnergieflussIst_' . $eintrag['InstanzID'], @@ -453,11 +547,7 @@ trait ManagerEnergieTrait 'EnergieflussSoll_' . $eintrag['InstanzID'], (float) $eintrag['Sollleistung_W'] ); - if ($eintrag['Typ'] === 'storage') { - $hatBatterieverbraucher = true; - } else { - $verbraucherleistung += max(0.0, (float) $eintrag['Istleistung_W']); - } + $verbraucherleistung += max(0.0, (float) $eintrag['Istleistung_W']); } try { @@ -472,12 +562,10 @@ trait ManagerEnergieTrait 'EnergieflussHaus', max(0.0, $leistungen['Haus'] - $verbraucherleistung) ); - if (!$hatBatterieverbraucher) { - $this->setzeEnergieflussLeistungswert( - 'EnergieflussBatterie', - $leistungen['Batterie'] - ); - } + $this->setzeEnergieflussLeistungswert( + 'EnergieflussBatterie', + $leistungen['Batterie'] + ); } private function setzeEnergieflussLeistungswert(string $ident, float $watt): void @@ -486,33 +574,487 @@ trait ManagerEnergieTrait if (!is_int($variableID) || $variableID <= 0 || !IPS_VariableExists($variableID)) { return; } - $faktor = $this->ReadPropertyInteger('EnergieflussLeistungseinheit') === 1 - ? 0.001 - : 1.0; + $konfiguration = $this->visualisierungsKonfiguration( + 'EnergieflussKonfiguration' + ); + $faktor = $konfiguration['EnergieflussLeistungseinheit'] === 1 ? 0.001 : 1.0; SetValue($variableID, $watt * $faktor); } private function erstelleEnergiediagramme(): string { - $leistung = [ - $this->chartDatensatz('PVLeistungArchiv', 'PV', '#F4C542', '~Power'), - $this->chartDatensatz('HausverbrauchLeistungArchiv', 'Haus', '#22A06B', '~Power'), - $this->chartDatensatz('NetzleistungArchiv', 'Netz', '#E95D5D', '~Power'), - $this->chartDatensatz('BatterieleistungArchiv', 'Batterie', '#8B5CF6', '~Power'), - ]; - $energie = [ - $this->chartDatensatz('PVEnergie', 'PV', '#F4C542', '~Electricity'), - $this->chartDatensatz('Hausenergie', 'Haus', '#22A06B', '~Electricity'), - $this->chartDatensatz('NetzbezugEnergie', 'Netzbezug', '#E95D5D', '~Electricity'), - $this->chartDatensatz('EinspeisungEnergie', 'Einspeisung', '#2F80ED', '~Electricity'), - $this->chartDatensatz('BatterieLadenEnergie', 'Batterie laden', '#8B5CF6', '~Electricity'), - $this->chartDatensatz('BatterieEntladenEnergie', 'Batterie entladen', '#F59E0B', '~Electricity'), - ]; + $konfiguration = $this->visualisierungsKonfiguration( + 'EnergiediagrammKonfiguration' + ); + $pvAnlagen = $this->diagrammPVAnlagen(); + $batterien = $this->diagrammBatterien(); + $verbraucher = $this->energieflussVerbraucher( + 'EnergiediagrammKonfiguration' + ); + $this->registriereDiagrammVariablen($pvAnlagen, $batterien, $verbraucher); + $this->aktualisiereDiagrammMesswerte(true); + + $leistung = []; + $energie = []; + if ($konfiguration['DiagrammPVModus'] !== 1) { + $leistung[] = $this->chartDatensatz( + 'PVLeistungArchiv', + 'PV gesamt', + '#F4C542', + '~Power' + ); + $energie[] = $this->chartDatensatz( + 'PVEnergie', + 'PV gesamt', + '#F4C542', + '~Electricity', + 'bar' + ); + } + if ($konfiguration['DiagrammPVModus'] !== 0) { + foreach ($pvAnlagen as $index => $anlage) { + $farbe = $this->diagrammFarbe($index); + if ($anlage['LeistungVariableID'] > 0) { + $leistung[] = $this->chartDatensatz( + $anlage['LeistungIdent'], + $anlage['Name'], + $farbe, + '~Power' + ); + } + if ($anlage['EnergieVariableID'] > 0) { + $energie[] = $this->chartDatensatz( + $anlage['EnergieIdent'], + $anlage['Name'], + $farbe, + '~Electricity', + 'bar' + ); + } + } + } + + $hausTitel = $verbraucher === [] ? 'Haus' : 'Haus ohne separat dargestellte Verbraucher'; + $leistung[] = $this->chartDatensatz( + 'DiagrammHausLeistung', + $hausTitel, + '#22A06B', + '~Power' + ); + $leistung[] = $this->chartDatensatz( + 'NetzleistungArchiv', + 'Netz', + '#E95D5D', + '~Power' + ); + $energie[] = $this->chartDatensatz( + 'DiagrammHausEnergie', + $hausTitel, + '#22A06B', + '~Electricity', + 'bar' + ); + $energie[] = $this->chartDatensatz( + 'NetzbezugEnergie', + 'Netzbezug', + '#E95D5D', + '~Electricity', + 'bar' + ); + $energie[] = $this->chartDatensatz( + 'EinspeisungEnergie', + 'Netzeinspeisung', + '#2F80ED', + '~Electricity', + 'bar' + ); + + if ($konfiguration['DiagrammBatterieModus'] !== 1) { + $leistung[] = $this->chartDatensatz( + 'BatterieleistungArchiv', + 'Batterie gesamt', + '#8B5CF6', + '~Power' + ); + $energie[] = $this->chartDatensatz( + 'BatterieLadenEnergie', + 'Batterie laden gesamt', + '#8B5CF6', + '~Electricity', + 'bar' + ); + $energie[] = $this->chartDatensatz( + 'BatterieEntladenEnergie', + 'Batterie entladen gesamt', + '#F59E0B', + '~Electricity', + 'bar' + ); + } + if ($konfiguration['DiagrammBatterieModus'] !== 0) { + foreach ($batterien as $index => $batterie) { + $farbe = $this->diagrammFarbe($index + count($pvAnlagen)); + if ($batterie['LeistungVariableID'] > 0) { + $leistung[] = $this->chartDatensatz( + $batterie['LeistungIdent'], + $batterie['Name'], + $farbe, + '~Power' + ); + } + if ($batterie['LadeenergieVariableID'] > 0) { + $energie[] = $this->chartDatensatz( + $batterie['LadeenergieIdent'], + $batterie['Name'] . ' laden', + $farbe, + '~Electricity', + 'bar' + ); + } + if ($batterie['EntladeenergieVariableID'] > 0) { + $energie[] = $this->chartDatensatz( + $batterie['EntladeenergieIdent'], + $batterie['Name'] . ' entladen', + $farbe, + '~Electricity', + 'bar' + ); + } + } + } + + foreach ($verbraucher as $index => $eintrag) { + $farbe = $this->diagrammFarbe( + $index + count($pvAnlagen) + count($batterien) + ); + $leistung[] = $this->chartDatensatz( + 'DiagrammVerbraucherLeistung_' . $eintrag['InstanzID'], + $eintrag['Name'], + $farbe, + '~Power' + ); + $energie[] = $this->chartDatensatz( + 'DiagrammVerbraucherEnergie_' . $eintrag['InstanzID'], + $eintrag['Name'], + $farbe, + '~Electricity', + 'bar' + ); + } + $this->erstelleDiagramm('Leistungsdiagramm', 'Leistungen', $leistung, 510); $this->erstelleDiagramm('Energiediagramm', 'Energien', $energie, 520); return 'Die Diagramme Leistungen und Energien wurden erstellt oder aktualisiert.'; } + /** @return list> */ + private function diagrammPVAnlagen(): array + { + $verwendet = []; + foreach ($this->leseAnlagenKonfigurationsliste('AnlagenPVFlaechen') as $flaeche) { + $wechselrichterID = trim((string) ($flaeche['WechselrichterID'] ?? '')); + if ($wechselrichterID !== '') { + $verwendet[$wechselrichterID] = true; + } + } + + $ergebnis = []; + foreach ($this->leseAnlagenKonfigurationsliste('AnlagenWechselrichter') as $eintrag) { + $id = trim((string) ($eintrag['ID'] ?? '')); + if ($id === '' || ($verwendet !== [] && !isset($verwendet[$id]))) { + continue; + } + if ($verwendet === [] + && !in_array((string) ($eintrag['Typ'] ?? ''), ['pv', 'hybrid'], true) + ) { + continue; + } + $suffix = substr(hash('sha256', 'pv:' . $id), 0, 12); + $leistungID = (int) ($eintrag['IstleistungVariableID'] ?? 0); + $energieID = (int) ($eintrag['ErzeugungsenergieVariableID'] ?? 0); + $ergebnis[] = [ + 'Name' => trim((string) ($eintrag['Name'] ?? '')) ?: $id, + 'LeistungVariableID' => $this->istNumerischeVariable($leistungID) + ? $leistungID + : 0, + 'Leistungsfaktor' => (float) ($eintrag['Istleistungsfaktor'] ?? 1.0), + 'EnergieVariableID' => $this->istNumerischeVariable($energieID) + ? $energieID + : 0, + 'Energiefaktor' => (float) ($eintrag['Erzeugungsenergiefaktor'] ?? 1.0), + 'LeistungIdent' => 'DiagrammPVLeistung_' . $suffix, + 'EnergieIdent' => 'DiagrammPVEnergie_' . $suffix, + ]; + } + + return $ergebnis; + } + + /** @return list> */ + private function diagrammBatterien(): array + { + $ergebnis = []; + foreach ($this->leseAnlagenKonfigurationsliste('AnlagenBatterien') as $eintrag) { + $id = trim((string) ($eintrag['ID'] ?? '')); + if ($id === '') { + continue; + } + $suffix = substr(hash('sha256', 'batterie:' . $id), 0, 12); + $leistungID = (int) ($eintrag['LeistungVariableID'] ?? 0); + $ladenID = (int) ($eintrag['LadeenergieVariableID'] ?? 0); + $entladenID = (int) ($eintrag['EntladeenergieVariableID'] ?? 0); + $ergebnis[] = [ + 'Name' => trim((string) ($eintrag['Name'] ?? '')) ?: $id, + 'LeistungVariableID' => $this->istNumerischeVariable($leistungID) + ? $leistungID + : 0, + 'Leistungsfaktor' => (float) ($eintrag['Leistungsfaktor'] ?? 1.0), + 'LadeenergieVariableID' => $this->istNumerischeVariable($ladenID) + ? $ladenID + : 0, + 'EntladeenergieVariableID' => $this->istNumerischeVariable($entladenID) + ? $entladenID + : 0, + 'Energiefaktor' => (float) ($eintrag['Energiefaktor'] ?? 1.0), + 'LeistungIdent' => 'DiagrammBatterieLeistung_' . $suffix, + 'LadeenergieIdent' => 'DiagrammBatterieLaden_' . $suffix, + 'EntladeenergieIdent' => 'DiagrammBatterieEntladen_' . $suffix, + ]; + } + + return $ergebnis; + } + + /** + * @param list> $pvAnlagen + * @param list> $batterien + * @param list> $verbraucher + */ + private function registriereDiagrammVariablen( + array $pvAnlagen, + array $batterien, + array $verbraucher + ): void { + $position = 800; + $this->diagrammMessvariable( + 'DiagrammHausLeistung', + 'Diagramm Hausleistung', + '~Power', + $position + ); + $this->diagrammMessvariable( + 'DiagrammHausEnergie', + 'Diagramm Hausenergie', + '~Electricity', + $position + 1 + ); + $position += 10; + + foreach ($pvAnlagen as $anlage) { + if ($anlage['LeistungVariableID'] > 0) { + $this->diagrammMessvariable( + $anlage['LeistungIdent'], + $anlage['Name'] . ' Leistung', + '~Power', + $position + ); + } + if ($anlage['EnergieVariableID'] > 0) { + $this->diagrammMessvariable( + $anlage['EnergieIdent'], + $anlage['Name'] . ' Energie', + '~Electricity', + $position + 1 + ); + } + $position += 10; + } + foreach ($batterien as $batterie) { + if ($batterie['LeistungVariableID'] > 0) { + $this->diagrammMessvariable( + $batterie['LeistungIdent'], + $batterie['Name'] . ' Leistung', + '~Power', + $position + ); + } + if ($batterie['LadeenergieVariableID'] > 0) { + $this->diagrammMessvariable( + $batterie['LadeenergieIdent'], + $batterie['Name'] . ' Ladeenergie', + '~Electricity', + $position + 1 + ); + } + if ($batterie['EntladeenergieVariableID'] > 0) { + $this->diagrammMessvariable( + $batterie['EntladeenergieIdent'], + $batterie['Name'] . ' Entladeenergie', + '~Electricity', + $position + 2 + ); + } + $position += 10; + } + foreach ($verbraucher as $eintrag) { + $this->diagrammMessvariable( + 'DiagrammVerbraucherLeistung_' . $eintrag['InstanzID'], + $eintrag['Name'] . ' Leistung', + '~Power', + $position + ); + $this->diagrammMessvariable( + 'DiagrammVerbraucherEnergie_' . $eintrag['InstanzID'], + $eintrag['Name'] . ' Energie', + '~Electricity', + $position + 1 + ); + $position += 10; + } + } + + private function diagrammMessvariable( + string $ident, + string $name, + string $profil, + int $position + ): int { + $this->RegisterVariableFloat($ident, $name, $profil, $position); + $variableID = $this->GetIDForIdent($ident); + IPS_SetHidden($variableID, true); + + return $variableID; + } + + private function aktualisiereDiagrammMesswerte(bool $energieAktualisieren = false): void + { + if ($this->findeUnterobjekt('Leistungsdiagramm') === 0 + && $this->findeUnterobjekt('Energiediagramm') === 0 + && $this->findeUnterobjekt('DiagrammHausLeistung') === 0 + ) { + return; + } + + try { + $leistungen = $this->leseMessleistungen(); + $pvAnlagen = $this->diagrammPVAnlagen(); + $batterien = $this->diagrammBatterien(); + $verbraucher = $this->energieflussVerbraucher( + 'EnergiediagrammKonfiguration' + ); + } catch (Throwable $fehler) { + $this->protokolliere('Energiediagramme', $fehler->getMessage()); + return; + } + + $separateLeistung = 0.0; + $separateEnergieKWh = 0.0; + foreach ($verbraucher as $eintrag) { + $leistung = max(0.0, (float) $eintrag['Istleistung_W']); + $separateLeistung += $leistung; + $separateEnergieKWh += max(0.0, (float) $eintrag['Energie_Wh']) / 1000.0; + $this->setzeDiagrammMesswert( + 'DiagrammVerbraucherLeistung_' . $eintrag['InstanzID'], + $leistung + ); + if ($energieAktualisieren) { + $this->setzeDiagrammMesswert( + 'DiagrammVerbraucherEnergie_' . $eintrag['InstanzID'], + max(0.0, (float) $eintrag['Energie_Wh']) / 1000.0 + ); + } + } + $this->setzeDiagrammMesswert( + 'DiagrammHausLeistung', + max(0.0, $leistungen['Haus'] - $separateLeistung) + ); + if ($energieAktualisieren) { + $this->setzeDiagrammMesswert( + 'DiagrammHausEnergie', + max( + 0.0, + (float) $this->GetValue('Hausenergie') - $separateEnergieKWh + ) + ); + } + + foreach ($pvAnlagen as $anlage) { + if ($anlage['LeistungVariableID'] > 0) { + $this->setzeDiagrammMesswert( + $anlage['LeistungIdent'], + max( + 0.0, + (float) GetValue($anlage['LeistungVariableID']) + * $anlage['Leistungsfaktor'] + ) + ); + } + if ($energieAktualisieren && $anlage['EnergieVariableID'] > 0) { + $this->setzeDiagrammMesswert( + $anlage['EnergieIdent'], + max( + 0.0, + (float) GetValue($anlage['EnergieVariableID']) + * $anlage['Energiefaktor'] + ) + ); + } + } + foreach ($batterien as $batterie) { + if ($batterie['LeistungVariableID'] > 0) { + $this->setzeDiagrammMesswert( + $batterie['LeistungIdent'], + (float) GetValue($batterie['LeistungVariableID']) + * $batterie['Leistungsfaktor'] + ); + } + if ($energieAktualisieren && $batterie['LadeenergieVariableID'] > 0) { + $this->setzeDiagrammMesswert( + $batterie['LadeenergieIdent'], + max( + 0.0, + (float) GetValue($batterie['LadeenergieVariableID']) + * $batterie['Energiefaktor'] + ) + ); + } + if ($energieAktualisieren && $batterie['EntladeenergieVariableID'] > 0) { + $this->setzeDiagrammMesswert( + $batterie['EntladeenergieIdent'], + max( + 0.0, + (float) GetValue($batterie['EntladeenergieVariableID']) + * $batterie['Energiefaktor'] + ) + ); + } + } + } + + private function setzeDiagrammMesswert(string $ident, float $wert): void + { + $variableID = @IPS_GetObjectIDByIdent($ident, $this->InstanceID); + if (is_int($variableID) && $variableID > 0 && IPS_VariableExists($variableID)) { + SetValue($variableID, $wert); + } + } + + private function diagrammFarbe(int $index): string + { + $farben = [ + '#A85D3C', + '#007C91', + '#6B5B95', + '#2E7D32', + '#C43E1C', + '#3D5A80', + '#8D6E63', + '#00897B', + ]; + + return $farben[$index % count($farben)]; + } + /** @param list> $datensaetze */ private function erstelleDiagramm( string $ident, @@ -521,7 +1063,8 @@ trait ManagerEnergieTrait int $position ): int { $medienID = $this->findeUnterobjekt($ident); - if ($medienID === 0) { + $neu = $medienID === 0; + if ($neu) { $medienID = IPS_CreateMedia(4); IPS_SetParent($medienID, $this->InstanceID); IPS_SetIdent($medienID, $ident); @@ -530,6 +1073,11 @@ trait ManagerEnergieTrait if ($objekt['ObjectType'] !== 5 || (int) IPS_GetMedia($medienID)['MediaType'] !== 4) { throw new RuntimeException('Unterobjekt ' . $ident . ' ist kein Diagramm.'); } + if (!$neu) { + IPS_SetHidden($medienID, false); + + return $medienID; + } IPS_SetName($medienID, $name); IPS_SetPosition($medienID, $position); IPS_SetHidden($medienID, false); @@ -546,16 +1094,17 @@ trait ManagerEnergieTrait string $ident, string $titel, string $farbe, - string $profil + string $profil, + string $typ = 'line' ): array { return [ 'variableID' => $this->GetIDForIdent($ident), - 'fillColor' => 'clear', + 'fillColor' => $typ === 'bar' ? $farbe : 'clear', 'strokeColor' => $farbe, 'timeOffset' => 0, 'visible' => true, 'title' => $titel, - 'type' => 'line', + 'type' => $typ, 'side' => 'left', 'profile' => $profil, ]; diff --git a/tests/ManagerEnergieModulstrukturTest.php b/tests/ManagerEnergieModulstrukturTest.php index 09d71b3..4e7c949 100644 --- a/tests/ManagerEnergieModulstrukturTest.php +++ b/tests/ManagerEnergieModulstrukturTest.php @@ -17,6 +17,11 @@ final class ManagerEnergieModulstrukturTest extends TestCase foreach ([ "RegisterPropertyBoolean('EnergyPieAnzeigen', false)", "RegisterPropertyBoolean('EnergiediagrammeAnzeigen', false)", + "RegisterPropertyInteger('DiagrammPVModus', 0)", + "RegisterPropertyInteger('DiagrammBatterieModus', 0)", + "RegisterPropertyBoolean('LadestationenSeparatAnzeigen', true)", + "RegisterPropertyBoolean('VerbraucherSeparatAnzeigen', true)", + "RegisterAttributeBoolean('EnergieflussNeuErzeugen', false)", "RegisterPropertyBoolean('FunFactsAnzeigen', false)", "RegisterPropertyBoolean('EnergieflussAnzeigen', false)", "RegisterPropertyInteger('EnergieflussLeistungseinheit', 0)", @@ -38,6 +43,10 @@ final class ManagerEnergieModulstrukturTest extends TestCase 'ENERGY_PIE_MODULE_ID', 'ENERGY_FLOW_MODULE_ID', 'energieflussKnoten', + "'type' => \$typ", + "'bar'", + 'EnergiediagrammKonfiguration', + 'EnergieflussKonfiguration', 'IPS_DeleteInstance', 'IPS_DeleteMedia', ] as $erwartet) { @@ -45,11 +54,14 @@ final class ManagerEnergieModulstrukturTest extends TestCase } foreach ([ 'Energieaufzeichnung', - 'FormEnergyPieAnzeigen', - 'FormEnergiediagrammeAnzeigen', - 'FormFunFactsAnzeigen', - 'FormEnergieflussAnzeigen', 'EnergieflussLeistungseinheit', + 'DiagrammPVModus', + 'DiagrammBatterieModus', + 'LadestationenSeparatAnzeigen', + 'VerbraucherSeparatAnzeigen', + 'Bestehende Energy Pies, Diagramme und Energiefluesse bleiben bei Apply unveraendert', + 'Wechselrichter / AC-Wandler', + 'keine doppelte Batteriekonfiguration', 'MesswerteAnzeigen', 'IstleistungVariableID', 'LeistungVariableID', diff --git a/tests/Symcon/modules/Manager.php b/tests/Symcon/modules/Manager.php index 1ff7cb3..8ec14ad 100644 --- a/tests/Symcon/modules/Manager.php +++ b/tests/Symcon/modules/Manager.php @@ -6,8 +6,13 @@ use Belevo\EnelixEMS\SymconTest\TestContext; return static function (TestContext $test): void { $netzleistungId = $test->createVariable(2, 'Netzleistung', -800.0); + $netzbezugEnergieId = $test->createVariable(2, 'Netzbezug-Energie', 32.0); + $netzeinspeisungEnergieId = $test->createVariable(2, 'Netzeinspeisung-Energie', 16.0); $pvLeistungId = $test->createVariable(2, 'PV-Leistung', 4200.0); + $pvEnergieId = $test->createVariable(2, 'PV-Energie', 120.0); $batterieLeistungId = $test->createVariable(2, 'Batterieleistung', 700.0); + $batterieLadeenergieId = $test->createVariable(2, 'Batterie-Ladeenergie', 18.0); + $batterieEntladeenergieId = $test->createVariable(2, 'Batterie-Entladeenergie', 12.0); $batterieSocId = $test->createVariable(2, 'Batterie-Ladezustand', 62.0); $einKontaktId = $test->createActionVariable('Kontakt Verbraucher 1-Stufig'); $einStufigId = $test->createInstance( @@ -82,6 +87,8 @@ return static function (TestContext $test): void { 'ACNennleistung_kW' => 10.0, 'IstleistungVariableID' => $pvLeistungId, 'Istleistungsfaktor' => 1.0, + 'ErzeugungsenergieVariableID' => $pvEnergieId, + 'Erzeugungsenergiefaktor' => 1.0, ]]; $pvKonfiguration = [[ 'ID' => 'pv-1', @@ -106,6 +113,9 @@ return static function (TestContext $test): void { 'LeistungVariableID' => $batterieLeistungId, 'Leistungsfaktor' => 1.0, 'SOCVariableID' => $batterieSocId, + 'LadeenergieVariableID' => $batterieLadeenergieId, + 'EntladeenergieVariableID' => $batterieEntladeenergieId, + 'Energiefaktor' => 1.0, ]]; $lizenzcode = trim((string) getenv('ENELIX_TEST_LICENSE_CODE')); $managerId = $test->createInstance( @@ -113,6 +123,8 @@ return static function (TestContext $test): void { 'Manager', [ 'NetzleistungVariableID' => $netzleistungId, + 'NetzbezugEnergieVariableID' => $netzbezugEnergieId, + 'NetzeinspeisungEnergieVariableID' => $netzeinspeisungEnergieId, 'EnergyPieAnzeigen' => true, 'EnergiediagrammeAnzeigen' => true, 'FunFactsAnzeigen' => true, @@ -226,11 +238,17 @@ return static function (TestContext $test): void { }); $test->runCase('Energiewerte, Archivregeln und Unterobjekte werden erzeugt', static function ( TestContext $test - ) use ($managerId): void { + ) use ($managerId, $pvLeistungId): void { IPS_RequestAction($managerId, 'MesswerteAufzeichnen', true); $test->assertSame( 4200.0, - GetValue($test->objectByIdent('PVLeistungArchiv', $managerId)) + GetValue($test->objectByIdent('PVLeistungArchiv', $managerId)), + sprintf( + 'PV-Archiv unveraendert; Quelle %.1f W, Managerstatus %d, Konfiguration %s.', + (float) GetValue($pvLeistungId), + (int) IPS_GetInstance($managerId)['InstanceStatus'], + (string) IPS_GetProperty($managerId, 'AnlagenWechselrichter') + ) ); $test->assertSame( 2700.0, @@ -288,18 +306,69 @@ return static function (TestContext $test): void { } } $pvFlussID = $test->objectByIdent('EnergieflussPV', $managerId); - $test->assertSame(4200.0, (float) GetValue($pvFlussID)); + $test->assertSame( + 4200.0, + (float) GetValue($pvFlussID), + 'Der anfaengliche PV-Energiefluss ist falsch.' + ); $test->assertSame('~Power', IPS_GetVariable($pvFlussID)['VariableProfile']); - IPS_SetProperty($managerId, 'EnergieflussLeistungseinheit', 1); - IPS_ApplyChanges($managerId); - $test->assertSame(4.2, (float) GetValue($pvFlussID)); - $test->assertSame('Enelix.Power.kW', IPS_GetVariable($pvFlussID)['VariableProfile']); + $pieID = $test->objectByIdent('EnergyPie', $managerId); + $leistungsdiagrammID = $test->objectByIdent('Leistungsdiagramm', $managerId); + $energiediagrammID = $test->objectByIdent('Energiediagramm', $managerId); + $energiediagramm = json_decode( + base64_decode((string) IPS_GetMediaContent($energiediagrammID), true), + true, + 512, + JSON_THROW_ON_ERROR + ); + $energietitel = array_column($energiediagramm['datasets'] ?? [], 'title'); + foreach ($energiediagramm['datasets'] ?? [] as $datensatz) { + $test->assertSame('bar', $datensatz['type'] ?? null); + } + foreach ([ + 'Netzbezug', + 'Netzeinspeisung', + 'Batterie laden gesamt', + 'Batterie entladen gesamt', + ] as $titel) { + $test->assertTrue(in_array($titel, $energietitel, true)); + } - IPS_RequestAction($managerId, 'FormEnergieflussAnzeigen', false); - $test->assertSame(true, (bool) IPS_GetObject($energieflussID)['ObjectIsHidden']); - IPS_RequestAction($managerId, 'FormEnergieflussAnzeigen', true); - $test->assertSame(false, (bool) IPS_GetObject($energieflussID)['ObjectIsHidden']); + $angepasstesDiagramm = base64_encode( + json_encode(['datasets' => [], 'custom' => 'bleibt'], JSON_THROW_ON_ERROR) + ); + IPS_SetMediaContent($energiediagrammID, $angepasstesDiagramm); + IPS_SetProperty($pieID, 'Zaehlerfaktor', 2.0); + IPS_ApplyChanges($pieID); + IPS_SetProperty($energieflussID, 'Config', '[]'); + IPS_ApplyChanges($energieflussID); + IPS_SetProperty($managerId, 'EnergieflussLeistungseinheit', 1); + IPS_SetProperty($managerId, 'DiagrammPVModus', 2); + IPS_SetProperty($managerId, 'DiagrammBatterieModus', 2); + IPS_ApplyChanges($managerId); + + $test->assertSame($pieID, $test->objectByIdent('EnergyPie', $managerId)); + $test->assertSame( + $leistungsdiagrammID, + $test->objectByIdent('Leistungsdiagramm', $managerId) + ); + $test->assertSame( + $energiediagrammID, + $test->objectByIdent('Energiediagramm', $managerId) + ); + $test->assertSame( + $angepasstesDiagramm, + (string) IPS_GetMediaContent($energiediagrammID) + ); + $test->assertSame(2.0, (float) IPS_GetProperty($pieID, 'Zaehlerfaktor')); + $test->assertSame('[]', (string) IPS_GetProperty($energieflussID, 'Config')); + $test->assertSame( + 4200.0, + (float) GetValue($pvFlussID), + 'Der bestehende PV-Energiefluss wurde beim Apply veraendert.' + ); + $test->assertSame('~Power', IPS_GetVariable($pvFlussID)['VariableProfile']); foreach ([ 'EnergyPieAnzeigen', @@ -322,7 +391,44 @@ return static function (TestContext $test): void { $ident . ' wurde beim Ausschalten nicht entfernt.' ); } - $test->assertSame(true, (bool) IPS_GetObject($energieflussID)['ObjectIsHidden']); + $test->assertSame( + true, + (bool) IPS_GetObject($energieflussID)['ObjectIsHidden'] + ); + + foreach ([ + 'EnergyPieAnzeigen', + 'EnergiediagrammeAnzeigen', + 'EnergieflussAnzeigen', + ] as $property) { + IPS_SetProperty($managerId, $property, true); + } + IPS_ApplyChanges($managerId); + $neuerFlussID = $test->objectByIdent('Energiefluss', $managerId); + $test->assertSame($energieflussID, $neuerFlussID); + $test->assertSame(false, (bool) IPS_GetObject($neuerFlussID)['ObjectIsHidden']); + $test->assertTrue((string) IPS_GetProperty($neuerFlussID, 'Config') !== '[]'); + $test->assertSame(4.2, (float) GetValue($pvFlussID)); + $test->assertSame('Enelix.Power.kW', IPS_GetVariable($pvFlussID)['VariableProfile']); + + $neuesLeistungsdiagramm = json_decode( + base64_decode( + (string) IPS_GetMediaContent( + $test->objectByIdent('Leistungsdiagramm', $managerId) + ), + true + ), + true, + 512, + JSON_THROW_ON_ERROR + ); + $leistungstitel = array_column( + $neuesLeistungsdiagramm['datasets'] ?? [], + 'title' + ); + foreach (['PV gesamt', 'Hybridwechselrichter', 'Batterie gesamt', 'Hausspeicher'] as $titel) { + $test->assertTrue(in_array($titel, $leistungstitel, true)); + } }); $zustandswert = static function (array $daten, int $instanzId, string $kennung) { @@ -470,12 +576,11 @@ return static function (TestContext $test): void { )); }); - $test->runCase('Veralteter Verbraucher blockiert aktuelle Verbraucher nicht', static function ( + $test->runCase('Veraltete Verbraucher werden aktiv neu angefordert', static function ( TestContext $test ) use ( $managerId, $netzleistungId, - $einStufigId, $warmwasserId, $pufferspeicherId, $melden, @@ -496,17 +601,9 @@ return static function (TestContext $test): void { IPS_RequestAction($managerId, 'Regeln', true); $daten = $verbraucherstatus(); - $test->assertSame(false, $daten['Synchronisiert'] ?? null); - $test->assertSame(2, count($daten['Verbraucher'] ?? [])); - $test->assertSame(1, count($daten['NichtGeregelt'] ?? [])); - $test->assertSame( - $einStufigId, - $daten['NichtGeregelt'][0]['InstanzID'] ?? null - ); - $test->assertSame( - 'Meldung ist veraltet', - $daten['NichtGeregelt'][0]['Grund'] ?? null - ); + $test->assertSame(true, $daten['Synchronisiert'] ?? null); + $test->assertSame(3, count($daten['Verbraucher'] ?? [])); + $test->assertSame(0, count($daten['NichtGeregelt'] ?? [])); $test->assertTrue( (float) GetValue($test->objectByIdent('Verteilbudget', $managerId)) > 0.0 );