createVariable(2, 'Netzleistung', -800.0); $pvLeistungId = $test->createVariable(2, 'PV-Leistung', 4200.0); $batterieLeistungId = $test->createVariable(2, 'Batterieleistung', 700.0); $batterieSocId = $test->createVariable(2, 'Batterie-Ladezustand', 62.0); $einKontaktId = $test->createActionVariable('Kontakt Verbraucher 1-Stufig'); $einStufigId = $test->createInstance( '{15879A4E-D0C2-4495-83DE-46E1E462591E}', 'Verbraucher 1-Stufig', [ 'Nennleistung' => 1000, 'SchaltkontaktVariableID' => $einKontaktId, 'Mindesteinschaltdauer' => 0, 'Mindestausschaltdauer' => 0, 'PrioritaetPV' => 0, 'PrioritaetPeak' => 0, 'DiagnosevariablenAnzeigen' => true, ] ); $temperaturId = $test->createVariable(2, 'Boilertemperatur', 52.0); $warmKontakte = [ $test->createActionVariable('Warmwasser Stufe 1'), $test->createActionVariable('Warmwasser Stufe 2'), ]; $warmStufen = [ ['Stufe' => 1, 'Leistung' => 1000, 'Schaltkontakt_Stufe' => $warmKontakte[0]], ['Stufe' => 2, 'Leistung' => 2000, 'Schaltkontakt_Stufe' => $warmKontakte[1]], ]; $warmwasserId = $test->createInstance( '{B7C54AF4-AD7D-4FE4-B75D-203693906251}', 'Wassererwärmer', [ 'Boilerfuehler_PT1' => $temperaturId, 'LeistungsStufen' => json_encode($warmStufen, JSON_THROW_ON_ERROR), 'LastwechselSperrzeit' => 1, 'LegionellenfunktionAktiv' => false, 'PrioritaetPV' => 1, 'PrioritaetPeak' => 1, 'DiagnosevariablenAnzeigen' => true, ] ); $pufferTemperaturId = $test->createVariable(2, 'Puffertemperatur', 52.0); $aussenTemperaturId = $test->createVariable(2, 'Aussentemperatur', 5.0); $pufferKontakte = [ $test->createActionVariable('Pufferspeicher Stufe 1'), $test->createActionVariable('Pufferspeicher Stufe 2'), ]; $pufferspeicherId = $test->createInstance( '{C92D5EEF-9632-47A5-9659-4B02BF40FBE9}', 'Pufferspeicher', [ 'PufferfuehlerVariableID' => $pufferTemperaturId, 'AussentemperaturVariableID' => $aussenTemperaturId, 'LeistungsStufen' => json_encode([ ['Stufe' => 1, 'Leistung' => 1000, 'Schaltkontakt_Stufe' => $pufferKontakte[0]], ['Stufe' => 2, 'Leistung' => 2000, 'Schaltkontakt_Stufe' => $pufferKontakte[1]], ], JSON_THROW_ON_ERROR), 'LastwechselSperrzeit' => 0, 'PrioritaetPV' => 2, 'PrioritaetPeak' => 2, 'DiagnosevariablenAnzeigen' => true, ] ); $zuordnung = [ ['InstanzID' => $einStufigId, 'Aktiv' => true], ['InstanzID' => $warmwasserId, 'Aktiv' => true], ['InstanzID' => $pufferspeicherId, 'Aktiv' => true], ]; $wechselrichterKonfiguration = [[ 'ID' => 'hybrid-1', 'Name' => 'Hybridwechselrichter', 'Typ' => 'hybrid', 'ACNennleistung_kW' => 10.0, 'IstleistungVariableID' => $pvLeistungId, 'Istleistungsfaktor' => 1.0, ]]; $pvKonfiguration = [[ 'ID' => 'pv-1', 'Name' => 'Suedflaeche', 'DCLeistung_kWp' => 12.0, 'Neigung_Grad' => 30.0, 'Azimut_Grad' => 0.0, 'WechselrichterID' => 'hybrid-1', 'MPPT' => 'A', 'Modulanzahl' => 30, 'Modulleistung_Wp' => 400.0, ]]; $batterieKonfiguration = [[ 'ID' => 'bat-1', 'Name' => 'Hausspeicher', 'Nennkapazitaet_kWh' => 13.5, 'Nutzkapazitaet_kWh' => 12.8, 'MaxLadeleistung_kW' => 5.0, 'MaxEntladeleistung_kW' => 5.0, 'WechselrichterID' => 'hybrid-1', 'Kopplung' => 'hybrid', 'LeistungVariableID' => $batterieLeistungId, 'Leistungsfaktor' => 1.0, 'SOCVariableID' => $batterieSocId, ]]; $lizenzcode = trim((string) getenv('ENELIX_TEST_LICENSE_CODE')); $managerId = $test->createInstance( '{6F771B18-59D4-4C8A-B951-3B2FE9F6A2C4}', 'Manager', [ 'NetzleistungVariableID' => $netzleistungId, 'EnergyPieAnzeigen' => true, 'EnergiediagrammeAnzeigen' => true, 'FunFactsAnzeigen' => true, 'EnergieflussAnzeigen' => true, 'Lizenzcode' => $lizenzcode, 'AutomatischeSuche' => false, 'VerbraucherZuordnung' => json_encode($zuordnung, JSON_THROW_ON_ERROR), 'KeepAlive' => 30, 'VerbraucherTimeout' => 10, 'Lastspitzenmodus' => 1, 'Lastspitzengrenze' => 0.0, 'SollwertSolarladen' => 0.0, 'Umschaltdifferenz' => 0.0, 'AnlagenWechselrichter' => json_encode( $wechselrichterKonfiguration, JSON_THROW_ON_ERROR ), 'AnlagenPVFlaechen' => json_encode($pvKonfiguration, JSON_THROW_ON_ERROR), 'AnlagenBatterien' => json_encode($batterieKonfiguration, JSON_THROW_ON_ERROR), 'DiagnosevariablenAnzeigen' => true, ] ); $melden = static function () use ($einStufigId, $warmwasserId, $pufferspeicherId): void { foreach ([$einStufigId, $warmwasserId, $pufferspeicherId] as $instanzId) { IPS_RequestAction($instanzId, 'Aktiv', true); IPS_RequestAction($instanzId, 'Melden', true); } }; $regeln = static function (float $netzleistung) use ( $netzleistungId, $managerId, $einStufigId, $warmwasserId, $pufferspeicherId ): void { IPS_RequestAction($einStufigId, 'Melden', true); IPS_RequestAction($warmwasserId, 'Melden', true); IPS_RequestAction($pufferspeicherId, 'Melden', true); SetValue($netzleistungId, $netzleistung); IPS_RequestAction($managerId, 'Regeln', true); }; $kontaktzustand = static fn (): array => [ (bool) GetValue($einKontaktId), (bool) GetValue($warmKontakte[0]), (bool) GetValue($warmKontakte[1]), (bool) GetValue($pufferKontakte[0]), (bool) GetValue($pufferKontakte[1]), ]; $warteAufKontakte = static function (array $erwartet, string $schritt) use ( $test, $kontaktzustand, $managerId ): void { $deadline = microtime(true) + 5.0; do { if ($kontaktzustand() === $erwartet) { return; } IPS_Sleep(50); } while (microtime(true) < $deadline); $test->fail(sprintf( '%s: Kontaktbild %s statt %s; Betriebsart %s; Verteilbudget %.0f W.', $schritt, json_encode($kontaktzustand(), JSON_THROW_ON_ERROR), json_encode($erwartet, JSON_THROW_ON_ERROR), (string) GetValue($test->objectByIdent('Betriebsart', $managerId)), (float) GetValue($test->objectByIdent('Verteilbudget', $managerId)) )); }; $verbraucherstatus = static function () use ($test, $managerId): array { $json = (string) GetValue($test->objectByIdent('Verbraucherstatus', $managerId)); $daten = json_decode($json, true, 512, JSON_THROW_ON_ERROR); return is_array($daten) ? $daten : []; }; $lizenzInstallationID = static function () use ($test, $managerId): string { $formular = IPS_GetConfigurationForm($managerId); if (preg_match( '/Installations-ID: ([0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12})/i', $formular, $treffer ) !== 1) { $test->fail('Das Manager-Formular enthaelt keine gueltige Installations-ID.'); } return strtolower($treffer[1]); }; $test->runCase('Installations-ID bleibt bei ApplyChanges stabil', static function ( TestContext $test ) use ($managerId, $lizenzInstallationID): void { $vorher = $lizenzInstallationID(); IPS_ApplyChanges($managerId); $test->assertSame($vorher, $lizenzInstallationID()); $topologie = json_decode( ENELIX_AnlagentopologieExportieren($managerId), true, 512, JSON_THROW_ON_ERROR ); $test->assertSame($vorher, $topologie['installationId'] ?? null); $test->assertSame('1.0', $topologie['version'] ?? null); $test->assertSame(12.0, $topologie['summary']['pvDcKwp'] ?? null); $test->assertSame(10.0, $topologie['summary']['pvAcKw'] ?? null); $test->assertSame(12.8, $topologie['summary']['usableBatteryKwh'] ?? null); $test->assertSame('hybrid-1', $topologie['sharedAcLimits'][0]['inverterId'] ?? null); $test->assertSame(['pv-1'], $topologie['sharedAcLimits'][0]['pvArrayIds'] ?? null); $test->assertSame(['bat-1'], $topologie['sharedAcLimits'][0]['batteryIds'] ?? null); }); $test->runCase('Energiewerte, Archivregeln und Unterobjekte werden erzeugt', static function ( TestContext $test ) use ($managerId): void { IPS_RequestAction($managerId, 'MesswerteAufzeichnen', true); $test->assertSame( 4200.0, GetValue($test->objectByIdent('PVLeistungArchiv', $managerId)) ); $test->assertSame( 2700.0, GetValue($test->objectByIdent('HausverbrauchLeistungArchiv', $managerId)) ); $test->assertSame( -800.0, GetValue($test->objectByIdent('NetzleistungArchiv', $managerId)) ); $test->assertSame( 700.0, GetValue($test->objectByIdent('BatterieleistungArchiv', $managerId)) ); $archive = IPS_GetInstanceListByModuleID( '{43192F0B-135B-4CE7-A0A7-1475603F3060}' ); $test->assertTrue($archive !== []); $archivID = (int) $archive[0]; $leistungID = $test->objectByIdent('PVLeistungArchiv', $managerId); $energieID = $test->objectByIdent('PVEnergie', $managerId); $test->assertSame(true, AC_GetLoggingStatus($archivID, $leistungID)); $test->assertSame(0, AC_GetAggregationType($archivID, $leistungID)); $test->assertSame(1, AC_GetAggregationType($archivID, $energieID)); foreach (['Leistungsdiagramm', 'Energiediagramm'] as $ident) { $medienID = $test->objectByIdent($ident, $managerId); $test->assertSame(4, (int) IPS_GetMedia($medienID)['MediaType']); } $html = (string) GetValue($test->objectByIdent('EnergieFunFacts', $managerId)); $test->assertTrue(str_contains($html, 'Solar')); $test->assertTrue(str_contains($html, 'Netz & Haus')); $energieflussID = $test->objectByIdent('Energiefluss', $managerId); $test->assertSame( '{BA90FDB3-C203-8F26-B4D6-F3DB99905724}', IPS_GetInstance($energieflussID)['ModuleInfo']['ModuleID'] ); $knoten = json_decode( (string) IPS_GetProperty($energieflussID, 'Config'), true, 512, JSON_THROW_ON_ERROR ); $namen = array_column($knoten, 'Name'); foreach (['Verbraucher 1-Stufig', 'Wassererwärmer', 'Pufferspeicher'] as $name) { $test->assertTrue(in_array($name, $namen, true)); } foreach ($knoten as $knotenEintrag) { if (in_array( $knotenEintrag['Name'], ['Verbraucher 1-Stufig', 'Wassererwärmer', 'Pufferspeicher'], true )) { $test->assertTrue((int) $knotenEintrag['InfoVariableID'] > 1); } } $pvFlussID = $test->objectByIdent('EnergieflussPV', $managerId); $test->assertSame(4200.0, (float) GetValue($pvFlussID)); $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']); 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']); foreach ([ 'EnergyPieAnzeigen', 'EnergiediagrammeAnzeigen', 'FunFactsAnzeigen', 'EnergieflussAnzeigen', ] as $property) { IPS_SetProperty($managerId, $property, false); } IPS_ApplyChanges($managerId); foreach ([ 'EnergyPie', 'Leistungsdiagramm', 'Energiediagramm', 'EnergieFunFacts', ] as $ident) { $test->assertSame( false, @IPS_GetObjectIDByIdent($ident, $managerId), $ident . ' wurde beim Ausschalten nicht entfernt.' ); } $test->assertSame(true, (bool) IPS_GetObject($energieflussID)['ObjectIsHidden']); }); $zustandswert = static function (array $daten, int $instanzId, string $kennung) { foreach ($daten['Verbraucher'] ?? [] as $verbraucher) { if (($verbraucher['InstanzID'] ?? 0) !== $instanzId) { continue; } foreach ($verbraucher['Daten']['Zustand'] ?? [] as $zustand) { if (($zustand['Kennung'] ?? '') === $kennung) { return $zustand['Wert'] ?? null; } } } return null; }; if ($lizenzcode === '') { $test->runCase('Manager bleibt ohne Lizenz sicher gesperrt', static function ( TestContext $test ) use ($managerId, $verbraucherstatus): void { IPS_RequestAction($managerId, 'Aktiv', true); IPS_RequestAction($managerId, 'Regeln', true); $test->assertInstanceStatus($managerId, 203); $test->assertSame( 'Inaktiv', GetValue($test->objectByIdent('Betriebsart', $managerId)) ); $daten = $verbraucherstatus(); $test->assertSame(false, $daten['Synchronisiert'] ?? null); $test->assertSame(3, count($daten['NichtGeregelt'] ?? [])); foreach ($daten['NichtGeregelt'] as $detail) { $test->assertTrue(is_int($detail['InstanzID'] ?? null)); $test->assertTrue(is_string($detail['Name'] ?? null)); $test->assertTrue(is_string($detail['Typ'] ?? null)); $test->assertSame('Lizenz nicht freigegeben', $detail['Grund'] ?? null); } $detailtext = (string) GetValue( $test->objectByIdent('NichtGeregelteGeraete', $managerId) ); $test->assertTrue(str_contains($detailtext, 'Boiler')); $test->assertTrue(str_contains($detailtext, 'Lizenz nicht freigegeben')); }); return; } $test->runCase('Manager und alle vorhandenen Verbrauchertypen sind verknüpft', static function ( TestContext $test ) use ($managerId, $einStufigId, $warmwasserId, $pufferspeicherId, $melden): void { IPS_RequestAction($managerId, 'Aktiv', true); $melden(); IPS_RequestAction($managerId, 'Regeln', true); foreach ([$managerId, $einStufigId, $warmwasserId, $pufferspeicherId] as $instanzId) { $test->assertInstanceStatus($instanzId); } $test->assertSame( 3, GetValue($test->objectByIdent('VerbraucherAnzahl', $managerId)) ); $test->assertSame( 3, GetValue($test->objectByIdent('VerbraucherVerfuegbar', $managerId)) ); }); $test->runCase('Netzleistung wird in beide Richtungen in PV und Peak geregelt', static function ( TestContext $test ) use ($managerId, $regeln, $warteAufKontakte): void { $regeln(-3000.0); $warteAufKontakte([true, false, true, false, false], 'Netz -3000 W'); $test->assertSame('PV', GetValue($test->objectByIdent('Betriebsart', $managerId))); $regeln(-1000.0); $warteAufKontakte([true, false, true, false, false], 'Netz -1000 W'); $regeln(0.0); $warteAufKontakte([true, false, true, false, false], 'Netz 0 W'); IPS_Sleep(1100); $regeln(1000.0); $warteAufKontakte([false, false, false, false, false], 'Netz +1000 W'); $test->assertSame('Peak', GetValue($test->objectByIdent('Betriebsart', $managerId))); IPS_Sleep(1100); $regeln(3000.0); $warteAufKontakte([false, false, false, false, false], 'Netz +3000 W'); }); $test->runCase('Keep-alive ist aktiv und erneuert unveränderte Sollwerte', static function ( TestContext $test ) use ($managerId, $einStufigId): void { $sollwertId = $test->objectByIdent('Sollleistung', $einStufigId); $vorher = (int) IPS_GetVariable($sollwertId)['VariableUpdated']; IPS_Sleep(1100); IPS_RequestAction($managerId, 'Regeln', true); $test->waitUntil( static fn (): bool => (int) IPS_GetVariable($sollwertId)['VariableUpdated'] > $vorher ); }); $test->runCase('Alle definierten Nachrichtenauslöser aktualisieren den Manager', static function ( TestContext $test ) use ( $managerId, $einStufigId, $warmwasserId, $temperaturId, $melden, $verbraucherstatus, $zustandswert ): void { $melden(); IPS_RequestAction($managerId, 'Regeln', false); $daten = $verbraucherstatus(); $test->assertSame(3, count($daten['Verbraucher'] ?? [])); SetValue($temperaturId, 53.0); $test->waitUntil(static function () use ( $warmwasserId, $verbraucherstatus, $zustandswert ): bool { $wert = $zustandswert( $verbraucherstatus(), $warmwasserId, 'Wassertemperatur_C' ); return is_numeric($wert) && abs((float) $wert - 53.0) < 0.01; }, 4000, 100); IPS_RequestAction($einStufigId, 'Melden', true); IPS_RequestAction($warmwasserId, 'Melden', true); IPS_RequestAction($managerId, 'Regeln', true); $daten = $verbraucherstatus(); $test->assertTrue( is_bool($zustandswert($daten, $einStufigId, 'Schaltzustand')) ); $test->assertTrue( is_int($zustandswert($daten, $warmwasserId, 'AktiveStufe')) ); $test->assertSame(false, GetValue( $test->objectByIdent('Sammelstoerung', $managerId) )); }); $test->runCase('Veralteter Verbraucher blockiert aktuelle Verbraucher nicht', static function ( TestContext $test ) use ( $managerId, $netzleistungId, $einStufigId, $warmwasserId, $pufferspeicherId, $melden, $verbraucherstatus ): void { IPS_SetProperty($managerId, 'VerbraucherTimeout', 1); IPS_ApplyChanges($managerId); SetValue($netzleistungId, -2400.0); $melden(); IPS_RequestAction($managerId, 'Regeln', true); $test->waitUntil( static fn (): bool => ($verbraucherstatus()['Synchronisiert'] ?? false) === true ); IPS_Sleep(1100); IPS_RequestAction($warmwasserId, 'Melden', true); IPS_RequestAction($pufferspeicherId, 'Melden', true); 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->assertTrue( (float) GetValue($test->objectByIdent('Verteilbudget', $managerId)) > 0.0 ); }); };