$strom) { $nrg[4 + $index] = $strom; } $nrg[11] = $leistung; return json_encode(['car' => $status, 'nrg' => $nrg], JSON_THROW_ON_ERROR); }; $definitionen = [ 'go-e alt' => [ 'Geraetetyp' => 1, 'Geraeteadresse' => '192.0.2.10', 'Testantwort' => $goEAntwort(2, 1100, [160, 158, 159]), 'Phasenzahl' => 3, 'BefehlUrlTeil' => 'amp=16', ], 'go-e Gemini' => [ 'Geraetetyp' => 2, 'Geraeteadresse' => '192.0.2.11', 'Testantwort' => $goEAntwort(2, 2300, [10, 0, 0]), 'Phasenzahl' => 1, 'BefehlUrlTeil' => 'amp=16', ], 'smart-me Pico' => [ 'Geraetetyp' => 3, 'GeraeteID' => 'pico-id', 'Seriennummer' => 'serial-1', 'Benutzername' => 'api-user', 'Passwort' => $testPasswort, 'Testantwort' => json_encode([ 'State' => 2, 'ActiveChargingPower' => 11.0, 'MaxAllowedChargingCurrent' => 16, ], JSON_THROW_ON_ERROR), 'Phasenzahl' => 3, 'BefehlUrlTeil' => 'current=16000', ], ]; $stationen = []; foreach ($definitionen as $name => $definition) { $properties = array_merge($definition, [ 'MaximalerLadestrom' => 16, 'ZeitZwischenZustandswechseln' => 0, 'Ladefreigabe' => true, 'Solarladen' => true, 'EinstellungenInVisu' => true, 'DiagnosevariablenAnzeigen' => true, 'Testmodus' => true, ]); unset($properties['Phasenzahl'], $properties['BefehlUrlTeil']); $stationen[$name] = $test->createInstance( '{0D94913C-0F31-4C29-A685-6EB4AE58E55D}', 'Ladestation ' . $name, $properties ); } $solarPause = $test->createInstance( '{0D94913C-0F31-4C29-A685-6EB4AE58E55D}', 'Ladestation Solarpause', [ 'Geraetetyp' => 1, 'Geraeteadresse' => '192.0.2.12', 'MaximalerLadestrom' => 16, 'ZeitZwischenZustandswechseln' => 0, 'Ladefreigabe' => true, 'Solarladen' => true, 'EinstellungenInVisu' => true, 'DiagnosevariablenAnzeigen' => true, 'Testmodus' => true, 'Testantwort' => $goEAntwort(2, 0), ] ); $stromerkennung = $test->createInstance( '{0D94913C-0F31-4C29-A685-6EB4AE58E55D}', 'Ladestation Fahrzeugstromerkennung', [ 'Geraetetyp' => 2, 'Geraeteadresse' => '192.0.2.13', 'MaximalerLadestrom' => 20, 'FahrzeugstromErkennungszeit' => 1, 'ZeitZwischenZustandswechseln' => 0, 'Ladefreigabe' => true, 'Solarladen' => true, 'EinstellungenInVisu' => true, 'DiagnosevariablenAnzeigen' => true, 'Testmodus' => true, 'Testantwort' => $goEAntwort(2, 10260, [15.9, 15.8, 15.7]), ] ); $phasenerkennung = $test->createInstance( '{0D94913C-0F31-4C29-A685-6EB4AE58E55D}', 'Ladestation Phasenerkennung', [ 'Geraetetyp' => 2, 'Geraeteadresse' => '192.0.2.14', 'MaximalerLadestrom' => 16, 'Phasenerkennungszeit' => 1, 'ZeitZwischenZustandswechseln' => 0, 'Ladefreigabe' => true, 'Solarladen' => true, 'EinstellungenInVisu' => true, 'DiagnosevariablenAnzeigen' => true, 'Testmodus' => true, 'Testantwort' => $goEAntwort(2, 0), ] ); $phasenhaltung = $test->createInstance( '{0D94913C-0F31-4C29-A685-6EB4AE58E55D}', 'Ladestation Phasenhaltung', [ 'Geraetetyp' => 2, 'Geraeteadresse' => '192.0.2.14', 'MaximalerLadestrom' => 16, 'ZeitZwischenZustandswechseln' => 0, 'Ladefreigabe' => true, 'Solarladen' => true, 'EinstellungenInVisu' => true, 'DiagnosevariablenAnzeigen' => true, 'Testmodus' => true, 'Testantwort' => $goEAntwort(2, 10944, [16, 16, 16]), ] ); $schaltsperre = $test->createInstance( '{0D94913C-0F31-4C29-A685-6EB4AE58E55D}', 'Ladestation Schaltsperre', [ 'Geraetetyp' => 2, 'Geraeteadresse' => '192.0.2.15', 'MaximalerLadestrom' => 16, 'ZeitZwischenZustandswechseln' => 1, 'Mindesteinschaltdauer' => 2, 'Ladefreigabe' => true, 'Solarladen' => true, 'EinstellungenInVisu' => true, 'DiagnosevariablenAnzeigen' => true, 'Testmodus' => true, 'Testantwort' => $goEAntwort(2, 10944, [16, 16, 16]), ] ); $mindestzeiten = $test->createInstance( '{0D94913C-0F31-4C29-A685-6EB4AE58E55D}', 'Ladestation Mindestzeiten', [ 'Geraetetyp' => 2, 'Geraeteadresse' => '192.0.2.17', 'MaximalerLadestrom' => 16, 'ZeitZwischenZustandswechseln' => 0, 'Mindesteinschaltdauer' => 1, 'Mindestausschaltdauer' => 1, 'Ladefreigabe' => true, 'Solarladen' => true, 'EinstellungenInVisu' => true, 'DiagnosevariablenAnzeigen' => true, 'Testmodus' => true, 'Testantwort' => $goEAntwort(2, 10944, [16, 16, 16]), ] ); $managerId = $test->createInstance( '{6F771B18-59D4-4C8A-B951-3B2FE9F6A2C4}', 'Hilfsmanager Ladestation', [ 'AutomatischeSuche' => false, 'VerbraucherZuordnung' => json_encode(array_map( static fn (int $id): array => ['InstanzID' => $id, 'Aktiv' => true], array_merge( array_values($stationen), [ $stromerkennung, $phasenerkennung, $phasenhaltung, $schaltsperre, $mindestzeiten, ] ) ), JSON_THROW_ON_ERROR), ] ); $anforderungSenden = static function ( int $stationId, int $leistung, string $betriebsart = 'PV' ) use ($managerId): void { IPS_RequestAction($stationId, 'ManagerdatenEmpfangen', json_encode([ 'Kopf' => [ 'Version' => '4.0', 'AbsenderID' => $managerId, 'EmpfaengerID' => $stationId, 'Zeitpunkt' => time(), ], 'Betriebsart' => $betriebsart, 'Sollleistung_W' => $leistung, ], JSON_THROW_ON_ERROR)); }; $test->runCase('Alle drei Geraetevarianten erkennen Fahrzeug und Phasen', static function ( TestContext $test ) use ($definitionen, $stationen): void { foreach ($stationen as $name => $stationId) { IPS_RequestAction($stationId, 'Aktiv', true); $test->assertSame(true, GetValue($test->objectByIdent('FahrzeugVerbunden', $stationId))); $test->assertSame( $definitionen[$name]['Phasenzahl'], GetValue($test->objectByIdent('Phasenzahl', $stationId)) ); $test->assertSame(false, GetValue($test->objectByIdent('FahrzeugGeladen', $stationId))); IPS_RequestAction($stationId, 'Solarladen', false); $befehl = (string) GetValue($test->objectByIdent('LetzterGeraetebefehl', $stationId)); $diagnose = json_encode([ 'Aktiv' => GetValue($test->objectByIdent('Aktiv', $stationId)), 'Ladefreigabe' => GetValue($test->objectByIdent('Ladefreigabe', $stationId)), 'Solarladen' => GetValue($test->objectByIdent('Solarladen', $stationId)), 'FahrzeugGeladen' => GetValue($test->objectByIdent('FahrzeugGeladen', $stationId)), 'Phasenzahl' => GetValue($test->objectByIdent('Phasenzahl', $stationId)), 'Status' => IPS_GetInstance($stationId)['InstanceStatus'], 'Stoertext' => GetValue($test->objectByIdent('Stoertext', $stationId)), ], JSON_THROW_ON_ERROR); $test->assertTrue(str_contains( $befehl, $definitionen[$name]['BefehlUrlTeil'] ), $name . ': Der erwartete 16-A-Geraetebefehl fehlt. Erhalten: ' . $befehl . '; Diagnose: ' . $diagnose); } }); $test->runCase('Erkannte Phasen bleiben waehrend einer Ladepause stabil', static function ( TestContext $test ) use ($goEAntwort, $phasenhaltung): void { IPS_RequestAction($phasenhaltung, 'Aktiv', true); $test->assertSame( 3, GetValue($test->objectByIdent('Phasenzahl', $phasenhaltung)) ); IPS_SetProperty($phasenhaltung, 'Testantwort', $goEAntwort(2, 0)); IPS_ApplyChanges($phasenhaltung); IPS_RequestAction($phasenhaltung, 'Statuszyklus', false); $test->assertSame( 3, GetValue($test->objectByIdent('Phasenzahl', $phasenhaltung)) ); $test->assertSame( false, GetValue($test->objectByIdent('FahrzeugGeladen', $phasenhaltung)) ); }); $test->runCase('Schaltsperre haelt die aktuelle Leistung stabil', static function ( TestContext $test ) use ($schaltsperre, $anforderungSenden): void { IPS_RequestAction($schaltsperre, 'Aktiv', true); IPS_RequestAction($schaltsperre, 'Solarladen', true); $anforderungSenden($schaltsperre, 4104); IPS_RequestAction($schaltsperre, 'Statuszyklus', false); $diagnose = json_encode([ 'Angebot' => GetValue($test->objectByIdent('LeistungsangebotDiagnose', $schaltsperre)), 'Verbunden' => GetValue($test->objectByIdent('FahrzeugVerbunden', $schaltsperre)), 'Geladen' => GetValue($test->objectByIdent('FahrzeugGeladen', $schaltsperre)), 'Phasen' => GetValue($test->objectByIdent('Phasenzahl', $schaltsperre)), 'Sollleistung' => GetValue($test->objectByIdent('Sollleistung', $schaltsperre)), 'SollwertGueltig' => GetValue($test->objectByIdent('SollwertGueltig', $schaltsperre)), 'Befehl' => GetValue($test->objectByIdent('LetzterGeraetebefehl', $schaltsperre)), ], JSON_THROW_ON_ERROR); $test->assertSame( '[4104]', (string) GetValue($test->objectByIdent('LeistungsangebotDiagnose', $schaltsperre)), 'Diagnose: ' . $diagnose ); $abgewiesen = false; try { $anforderungSenden($schaltsperre, 4788); } catch (Throwable $fehler) { $abgewiesen = true; } if (!$abgewiesen) { $abgewiesen = GetValue( $test->objectByIdent('SollwertGueltig', $schaltsperre) ) !== true || GetValue( $test->objectByIdent('Sollleistung', $schaltsperre) ) !== 4788; } $test->assertSame(true, $abgewiesen); }); $test->runCase('Mindestzeiten erlauben Modulation aber verhindern Takten', static function ( TestContext $test ) use ($mindestzeiten, $anforderungSenden): void { IPS_RequestAction($mindestzeiten, 'Aktiv', true); $anforderungSenden($mindestzeiten, 4104); IPS_RequestAction($mindestzeiten, 'Statuszyklus', false); $angebot = json_decode( (string) GetValue($test->objectByIdent('LeistungsangebotDiagnose', $mindestzeiten)), true, 512, JSON_THROW_ON_ERROR ); $test->assertTrue(!in_array(0, $angebot, true)); $test->assertTrue(in_array(4788, $angebot, true)); $anforderungSenden($mindestzeiten, 4788); IPS_RequestAction($mindestzeiten, 'Ladefreigabe', false); IPS_RequestAction($mindestzeiten, 'Ladefreigabe', true); $test->assertSame( '[0]', (string) GetValue($test->objectByIdent('LeistungsangebotDiagnose', $mindestzeiten)) ); }); $test->runCase('Solarpause wird nicht als Ladeende erkannt', static function ( TestContext $test ) use ($solarPause): void { IPS_RequestAction($solarPause, 'Solarladen', true); $test->assertSame( false, GetValue($test->objectByIdent('FahrzeugGeladen', $solarPause)) ); $test->assertSame( 2, GetValue($test->objectByIdent('Fahrzeugstatus', $solarPause)) ); }); $test->runCase('Solar- und Peakbetrieb liefern die Enelix-1-Angebote', static function ( TestContext $test ) use ($stationen, $anforderungSenden): void { $stationId = $stationen['go-e alt']; IPS_RequestAction($stationId, 'Solarladen', true); $anforderungSenden($stationId, 4104); $befehl = (string) GetValue($test->objectByIdent('LetzterGeraetebefehl', $stationId)); $diagnose = json_encode([ 'Sollleistung' => GetValue($test->objectByIdent('Sollleistung', $stationId)), 'SollwertGueltig' => GetValue($test->objectByIdent('SollwertGueltig', $stationId)), 'Status' => IPS_GetInstance($stationId)['InstanceStatus'], 'Stoertext' => GetValue($test->objectByIdent('Stoertext', $stationId)), ], JSON_THROW_ON_ERROR); $test->assertTrue(str_contains( $befehl, 'amp=6' ), 'Solarladen: Der 6-A-Befehl fehlt. Erhalten: ' . $befehl . '; Diagnose: ' . $diagnose); IPS_RequestAction($stationId, 'Solarladen', false); $angebot = (string) GetValue($test->objectByIdent('LeistungsangebotDiagnose', $stationId)); $test->assertSame('[10944]', $angebot, 'PV ohne Solarladen muss nur das Maximum anbieten.'); $abgewiesen = false; try { $anforderungSenden($stationId, 4104); } catch (Throwable $fehler) { $abgewiesen = true; } if (!$abgewiesen) { $abgewiesen = GetValue($test->objectByIdent('SollwertGueltig', $stationId)) !== true || GetValue($test->objectByIdent('Sollleistung', $stationId)) !== 4104; } $test->assertSame(true, $abgewiesen, 'Normalbetrieb muss eine Zwischenstufe ablehnen.'); $anforderungSenden($stationId, 4104, 'Peak'); $test->assertSame( '[0,4104,4788,5472,6156,6840,7524,8208,8892,9576,10260,10944]', (string) GetValue($test->objectByIdent('LeistungsangebotDiagnose', $stationId)) ); $test->assertTrue(str_contains( (string) GetValue($test->objectByIdent('LetzterGeraetebefehl', $stationId)), 'amp=6' ), 'Peak ohne Solarladen muss die Ladestufen annehmen.'); IPS_RequestAction($stationId, 'Solarladen', true); $anforderungSenden($stationId, 0, 'Peak'); $test->assertSame( '[0]', (string) GetValue($test->objectByIdent('LeistungsangebotDiagnose', $stationId)) ); $test->assertTrue(str_contains( (string) GetValue($test->objectByIdent('LetzterGeraetebefehl', $stationId)), 'alw=0' ), 'Peak mit Solarladen muss die Ladestation stoppen.'); }); $test->runCase('Fahrzeugstrom wird nach stabiler Unterschreitung begrenzt', static function ( TestContext $test ) use ($stromerkennung, $anforderungSenden): void { IPS_RequestAction($stromerkennung, 'Aktiv', true); IPS_RequestAction($stromerkennung, 'Solarladen', true); $anforderungSenden($stromerkennung, 13680); IPS_RequestAction($stromerkennung, 'Statuszyklus', false); IPS_Sleep(2100); IPS_RequestAction($stromerkennung, 'Statuszyklus', false); $test->assertSame( 18, GetValue($test->objectByIdent('FahrzeugMaximalstrom', $stromerkennung)) ); $test->assertSame( '[0,4104,4788,5472,6156,6840,7524,8208,8892,9576,10260,10944,11628,12312]', (string) GetValue($test->objectByIdent('LeistungsangebotDiagnose', $stromerkennung)) ); $test->assertTrue(str_contains( (string) GetValue($test->objectByIdent('LetzterGeraetebefehl', $stromerkennung)), 'amp=18' )); }); $test->runCase('Neue Ladestation wird erst nach Freigabe mit Maximalstrom erkannt', static function ( TestContext $test ) use ($phasenerkennung, $anforderungSenden): void { $stationId = $phasenerkennung; $test->assertSame( 0, GetValue($test->objectByIdent('Phasenzahl', $stationId)) ); $test->assertTrue(!str_contains( (string) GetValue($test->objectByIdent('LetzterGeraetebefehl', $stationId)), 'amp=16' ), 'Eine inaktive Station darf keine Maximalstrom-Probe starten.'); IPS_RequestAction($stationId, 'Aktiv', true); $test->assertSame( 0, GetValue($test->objectByIdent('Phasenzahl', $stationId)) ); $test->assertTrue(str_contains( (string) GetValue($test->objectByIdent('LetzterGeraetebefehl', $stationId)), 'amp=16' ), 'Nach der Freigabe muss die Maximalstrom-Probe starten.'); IPS_Sleep(2100); IPS_RequestAction($stationId, 'Statuszyklus', false); $anforderungSenden($stationId, 3680); IPS_RequestAction($stationId, 'Statuszyklus', false); $test->assertSame( 1, GetValue($test->objectByIdent('Phasenzahl', $stationId)) ); $test->assertSame( false, GetValue($test->objectByIdent('FahrzeugGeladen', $stationId)) ); $test->assertSame( true, GetValue($test->objectByIdent('SollwertGueltig', $stationId)) ); $test->assertSame( 3680, GetValue($test->objectByIdent('Sollleistung', $stationId)) ); $test->assertSame( '[0,1380,1610,1840,2070,2300,2530,2760,2990,3220,3450,3680]', (string) GetValue($test->objectByIdent('LeistungsangebotDiagnose', $stationId)) ); }); };