Create(); $station->testProperties = array_replace($station->testProperties, [ 'Geraetetyp' => $type, 'Geraeteadresse' => '192.0.2.10', 'GeraeteID' => 'test-pico', 'Seriennummer' => 'test-pico', 'Benutzername' => 'test', 'Passwort' => hash('sha256', __FILE__), 'EinstellungenInVisu' => true, 'DiagnosevariablenAnzeigen' => true, 'Testmodus' => true, 'Testantwort' => $type === 3 ? $this->pico() : $this->goe(0), 'Solarladen' => $solar, 'ZeitZwischenZustandswechseln' => 0, ]); \LadestationRuntime::$managers[20001] = [ 'AutomatischeSuche' => false, 'VerbraucherZuordnung' => json_encode([['InstanzID' => 10001, 'Aktiv' => true]], JSON_THROW_ON_ERROR), ]; $station->SetValue('Aktiv', true); $station->ApplyChanges(); \LadestationRuntime::$commands = []; return $station; } private function goe(float $power, array $currents = [0, 0, 0], int $car = 2, int $error = 0): string { $nrg = array_fill(0, 12, 0); foreach ($currents as $i => $value) { $nrg[$i + 4] = $value; } $nrg[11] = $power; return json_encode(['car' => $car, 'err' => $error, 'nrg' => $nrg], JSON_THROW_ON_ERROR); } private function pico(array $changes = []): string { return json_encode(array_replace([ 'State' => 4, 'ActiveChargingPower' => 0.0, 'LastSeen' => gmdate('Y-m-d\TH:i:s\Z'), 'ValueDate' => gmdate('Y-m-d\TH:i:s\Z'), ], $changes), JSON_THROW_ON_ERROR); } private function poll(\LadestationStandAlone $station, string $reply): void { $station->testProperties['Testantwort'] = $reply; $station->RequestAction('Statuszyklus', false); } private function target(\LadestationStandAlone $station, ?int $power, string $mode = 'PV'): void { $station->ManagerdatenEmpfangen([ 'Kopf' => ['Version' => '4.0', 'AbsenderID' => 20001, 'EmpfaengerID' => 10001, 'Zeitpunkt' => time()], 'Betriebsart' => $mode, 'Sollleistung_W' => $power, ]); } private function offer(\LadestationStandAlone $station): array { return json_decode($station->testAttributes['Leistungsangebot'], true, 512, JSON_THROW_ON_ERROR); } public function testKurzerNullwertNachLadenSperrtKeinenNeustart(): void { $station = $this->station(); $this->poll($station, $this->goe(10944, [16, 16, 16])); $this->target($station, 10944); $this->poll($station, $this->goe(10944, [16, 16, 16])); $this->poll($station, $this->goe(0)); self::assertFalse($station->GetValue('FahrzeugGeladen')); self::assertContains(10944, $this->offer($station)); $this->poll($station, $this->goe(10944, [16, 16, 16])); self::assertFalse($station->GetValue('FahrzeugGeladen')); } public function testExplizitesLadeendeWirdBeiNeuLadendemFahrzeugAufgehoben(): void { $station = $this->station(); $this->poll($station, $this->goe(0, [0, 0, 0], 4)); self::assertTrue($station->GetValue('FahrzeugGeladen')); self::assertSame([0], $this->offer($station)); $this->poll($station, $this->goe(4104, [6, 6, 6])); self::assertFalse($station->GetValue('FahrzeugGeladen')); self::assertContains(4104, $this->offer($station)); } public function testPhasenprobeBrauchtManagerbudgetUndNullleistungBleibtUnbekannt(): void { $station = $this->station(); self::assertSame(0, $station->testAttributes['LetzterGesetzterStrom']); self::assertSame([0, 4104], $this->offer($station)); $this->target($station, 4104); self::assertSame(6, $station->testAttributes['LetzterGesetzterStrom']); $station->testAttributes['PhasenerkennungSeit'] = time() - 70; $this->poll($station, $this->goe(0)); self::assertSame(0, $station->GetValue('Phasenzahl')); self::assertFalse($station->GetValue('FahrzeugGeladen')); } public function testPhasenprobeOhneManagerbudgetStartetAuchOhneSolarNicht(): void { $station = $this->station(false); self::assertSame(0, $station->testAttributes['LetzterGesetzterStrom']); $this->target($station, 4104, 'Peak'); self::assertSame(6, $station->testAttributes['LetzterGesetzterStrom']); } public function testSolarImPeakStartetKeineProbeUndBeendetLaufendeProbe(): void { $station = $this->station(); $this->target($station, null, 'Peak'); $this->poll($station, $this->goe(0)); self::assertSame([0], $this->offer($station)); self::assertSame(0, $station->testAttributes['LetzterGesetzterStrom']); $this->target($station, 4104, 'PV'); self::assertSame(6, $station->testAttributes['LetzterGesetzterStrom']); $this->target($station, null, 'Peak'); self::assertSame(0, $station->testAttributes['LetzterGesetzterStrom']); } public function testDeaktivierungStopptAuchWaerendUnbekannterPhasen(): void { $station = $this->station(); $this->target($station, 4104); $station->RequestAction('Ladefreigabe', false); self::assertSame(0, $station->testAttributes['LetzterGesetzterStrom']); self::assertSame([0], $this->offer($station)); } public function testFrischeDreiphasigkeitKorrigiertAltenEinphasenwertUndLeistungssperre(): void { $station = $this->station(); $this->poll($station, $this->goe(1380, [6, 0, 0])); $this->target($station, 1380); $station->testAttributes['LeistungswechselGesperrtBis'] = time() + 60; $this->poll($station, $this->goe(4104, [6, 6, 6])); self::assertSame(3, $station->GetValue('Phasenzahl')); self::assertNotContains(1380, $this->offer($station)); self::assertContains(4104, $this->offer($station)); } public function testGeraetefehlerBleibtBisFrischerErfolgreicherAbfrageGesperrt(): void { $station = $this->station(); $this->target($station, 4104); $this->poll($station, $this->goe(0, [0, 0, 0], 2, 11)); self::assertSame(202, $station->testStatus); self::assertSame([0], $this->offer($station)); self::assertSame(0, $station->testAttributes['LetzterGesetzterStrom']); $this->target($station, null, 'PV'); self::assertTrue($station->GetValue('Stoerung')); self::assertSame([0], $this->offer($station)); try { $this->target($station, 4104); self::fail('Positiver Auftrag trotz Fehler angenommen'); } catch (\InvalidArgumentException $expected) { self::assertSame(0, $station->testAttributes['LetzterGesetzterStrom']); } $this->poll($station, $this->goe(0)); self::assertSame(102, $station->testStatus); self::assertFalse($station->GetValue('Stoerung')); } public function testDreiphasenbudgetBleibtBeiEinzelnerBelasteterPhaseBisZumAusstecken(): void { $station = $this->station(); $this->poll($station, $this->goe(4104, [6, 6, 6])); $this->poll($station, $this->goe(1380, [6, 0, 0])); self::assertSame(3, $station->GetValue('Phasenzahl')); $this->poll($station, $this->goe(0, [0, 0, 0], 1)); $this->poll($station, $this->goe(1380, [6, 0, 0])); self::assertSame(1, $station->GetValue('Phasenzahl')); } public function testAbgelaufenerStatusWirdNichtDurchManagerdatenErneuert(): void { $station = $this->station(); $this->target($station, 4104); $station->testAttributes['GeraetestatusGueltigBis'] = time() - 1; $this->target($station, 0); self::assertSame([0], $this->offer($station)); self::assertFalse($station->GetValue('Verfuegbar')); self::assertSame(0, $station->testAttributes['LetzterGesetzterStrom']); } public function testMeldungMitAbgelaufenemCacheMeldetNichtVerfuegbarOhneHttpAbruf(): void { $station = $this->station(); $station->testAttributes['GeraetestatusGueltigBis'] = time() - 1; $station->testProperties['Testantwort'] = 'UNGUELTIG'; $station->RequestAction('Melden', false); self::assertSame([], \LadestationRuntime::$commands); $message = end(\LadestationRuntime::$messages); $data = json_decode($message[2], true, 512, JSON_THROW_ON_ERROR); self::assertFalse($data['Verfuegbar']); self::assertSame([0], $data['Leistungswerte_W']); } public function testReineModusmeldungStopptBeiAbgelaufenemCache(): void { $station = $this->station(); $this->target($station, 4104); $station->testAttributes['GeraetestatusGueltigBis'] = time() - 1; $this->target($station, null); self::assertTrue($station->GetValue('Stoerung')); self::assertSame(0, $station->testAttributes['LetzterGesetzterStrom']); } public function testPicoVeralteteMessungSperrtUndFrischeMessungStelltWiederHer(): void { $station = $this->station(true, 3); $this->poll($station, $this->pico(['ValueDate' => '2020-01-01T00:00:00Z'])); self::assertTrue($station->GetValue('Stoerung')); self::assertSame([0], $this->offer($station)); $this->target($station, null); self::assertTrue($station->GetValue('Stoerung')); $this->poll($station, $this->pico()); self::assertFalse($station->GetValue('Stoerung')); } public function testPicoAutorisierungUndHistorieWerdenGetrenntBehandelt(): void { $station = $this->station(true, 3); $this->poll($station, $this->pico(['State' => 6, 'LastWarningOrError' => 19])); self::assertSame([0], $this->offer($station)); self::assertFalse($station->GetValue('Stoerung')); self::assertStringContainsString('Autorisierung', $station->GetValue('Geraetehinweis')); $this->poll($station, $this->pico(['LastWarningOrError' => 19])); self::assertSame([0, 4104], $this->offer($station)); self::assertStringContainsString('historisch', $station->GetValue('Geraetehinweis')); } public function testUpdateEntferntAlteUnbelegtePhasenUndVollErkennung(): void { $station = $this->station(); $station->testAttributes['PhasenerkennungVersion'] = 1; $station->testAttributes['ZustandPhasenzahl'] = 1; $station->testAttributes['ZustandFahrzeugGeladen'] = true; $station->ApplyChanges(); self::assertSame(2, $station->testAttributes['PhasenerkennungVersion']); self::assertSame(0, $station->GetValue('Phasenzahl')); self::assertFalse($station->GetValue('FahrzeugGeladen')); self::assertSame([0, 4104], $this->offer($station)); } }