feat(ladestation): Phasenerkennung und Schaltsperren erweitern
Tests / test (push) Successful in 45s

This commit is contained in:
dh
2026-09-29 22:49:26 +00:00
parent 53fca5e2bb
commit faa5dfb33f
10 changed files with 593 additions and 113 deletions
+13 -3
View File
@@ -13,7 +13,7 @@ final class LadestationAdapterTest extends TestCase
/**
* @dataProvider statusFaelle
* @param array<string, string> $konfiguration
* @param array{Rohstatus: int, Istleistung_W: float, Iststrom_A?: float} $erwartet
* @param array{Rohstatus: int, Istleistung_W: float, Iststrom_A?: float, Phasenzahl?: int} $erwartet
*/
public function testStatusaufrufUndDekodierungFuerAlleGeraete(
int $geraetetyp,
@@ -49,14 +49,24 @@ final class LadestationAdapterTest extends TestCase
$this->lokaleKonfiguration(),
$this->goEAntwort(2, 1100, [160, 158, 159]),
'http://192.0.2.10/mqtt?payload=',
['Rohstatus' => 2, 'Istleistung_W' => 11000.0, 'Iststrom_A' => 16.0],
[
'Rohstatus' => 2,
'Istleistung_W' => 11000.0,
'Iststrom_A' => 16.0,
'Phasenzahl' => 3,
],
];
yield 'go-e Gemini' => [
LadestationAdapter::GO_E_GEMINI,
$this->lokaleKonfiguration(),
$this->goEAntwort(2, 2300, [9.8, 0, 0]),
'http://192.0.2.10/api/status',
['Rohstatus' => 2, 'Istleistung_W' => 2300.0, 'Iststrom_A' => 9.8],
[
'Rohstatus' => 2,
'Istleistung_W' => 2300.0,
'Iststrom_A' => 9.8,
'Phasenzahl' => 1,
],
];
yield 'Smart-me Pico' => [
LadestationAdapter::SMART_ME_PICO,
+37 -5
View File
@@ -22,17 +22,49 @@ final class LadestationReglerTest extends TestCase
self::assertSame(LadestationRegler::PHASEN_DREI, $status['Phasenzahl']);
}
public function testEinphasigesFahrzeugWirdUnterhalb7500WErkannt(): void
public function testDirektGemeldeteEinphasigkeitWirdAuchUnter7500WErkannt(): void
{
$status = LadestationRegler::normalisiereStatus([
'Rohstatus' => 2,
'Istleistung_W' => 2300.0,
'Phasenzahl' => 1,
], false);
self::assertSame(LadestationRegler::PHASEN_EIN, $status['Phasenzahl']);
self::assertEqualsWithDelta(10.0, $status['Ladestrom_A'], 0.01);
}
public function testErkannteDreiphasigkeitBleibtWaehrendLadepauseErhalten(): void
{
$status = LadestationRegler::normalisiereStatus([
'Rohstatus' => 2,
'Istleistung_W' => 0.0,
], true, false, false, true, LadestationRegler::PHASEN_DREI);
self::assertSame(LadestationRegler::PHASEN_DREI, $status['Phasenzahl']);
self::assertSame(0.0, $status['Ladestrom_A']);
}
public function testPhasenBleibenOhneMessungBisZurErkennungUnbekannt(): void
{
$status = LadestationRegler::normalisiereStatus([
'Rohstatus' => 2,
'Istleistung_W' => 0.0,
], false);
self::assertSame(LadestationRegler::PHASEN_UNBEKANNT, $status['Phasenzahl']);
self::assertSame([0], LadestationRegler::leistungsangebot(
true,
true,
true,
false,
LadestationRegler::PHASEN_UNBEKANNT,
16,
true,
false
));
}
public function testAnlaufOhneGemesseneLadeleistungGiltNichtAlsLadeende(): void
{
$status = LadestationRegler::normalisiereStatus([
@@ -90,7 +122,7 @@ final class LadestationReglerTest extends TestCase
public function testFahrzeugMaximalstromWirdMitZweiAmpereAbstandErkannt(): void
{
self::assertSame(16, LadestationRegler::ermittleFahrzeugMaximalstrom(20, 15.9, 20));
self::assertSame(18, LadestationRegler::ermittleFahrzeugMaximalstrom(20, 15.9, 20));
self::assertNull(LadestationRegler::ermittleFahrzeugMaximalstrom(17, 15.9, 20));
self::assertNull(LadestationRegler::ermittleFahrzeugMaximalstrom(20, 0.0, 20));
}
@@ -104,7 +136,7 @@ final class LadestationReglerTest extends TestCase
self::assertSame(0, $solarPV[0]);
self::assertCount(12, $solarPV);
self::assertSame([3850], $normalPV);
self::assertSame([3680], $normalPV);
self::assertSame([0], $solarPeak);
self::assertSame($solarPV, $normalPeak);
}
@@ -112,7 +144,7 @@ final class LadestationReglerTest extends TestCase
public function testPeakKannJedesFahrzeugAufNullSetzen(): void
{
self::assertSame(0, LadestationRegler::stromFuerLeistung(0, 3));
self::assertSame(6, LadestationRegler::stromFuerLeistung(4150, 3));
self::assertSame(16, LadestationRegler::stromFuerLeistung(11000, 3));
self::assertSame(6, LadestationRegler::stromFuerLeistung(4104, 3));
self::assertSame(16, LadestationRegler::stromFuerLeistung(10944, 3));
}
}
@@ -68,6 +68,22 @@ final class LadestationStandAloneModulstrukturTest extends TestCase
"RegisterPropertyBoolean('Ladefreigabe', true)",
$modul
);
self::assertStringContainsString(
"RegisterPropertyInteger('Phasenerkennungszeit', 60)",
$modul
);
self::assertStringContainsString(
"RegisterPropertyInteger('ZeitZwischenZustandswechseln', 1)",
$modul
);
self::assertStringContainsString(
"RegisterPropertyInteger('Mindesteinschaltdauer', 0)",
$modul
);
self::assertStringContainsString(
"RegisterPropertyInteger('Mindestausschaltdauer', 0)",
$modul
);
self::assertStringContainsString("EnableAction('Ladefreigabe')", $modul);
self::assertStringContainsString("EnableAction('Solarladen')", $modul);
self::assertStringContainsString("ReadPropertyBoolean('LoggingEin')", $modul);
+2 -2
View File
@@ -95,7 +95,7 @@ return static function (TestContext $test): void {
) use ($station): void {
IPS_RequestAction($station, 'Aktiv', true);
$test->assertSame(
'[0,4150,4850,5550,6250,6950,7600,8300,9000,9700,10300,11000]',
'[0,4104,4788,5472,6156,6840,7524,8208,8892,9576,10260,10944]',
(string) GetValue(
$test->objectByIdent('LeistungsangebotDiagnose', $station)
)
@@ -125,7 +125,7 @@ return static function (TestContext $test): void {
);
$test->assertInstanceStatus($station);
$test->assertSame(
'[3850]',
'[3680]',
(string) GetValue(
$test->objectByIdent('LeistungsangebotDiagnose', $station)
)
+215 -45
View File
@@ -50,6 +50,7 @@ return static function (TestContext $test): void {
foreach ($definitionen as $name => $definition) {
$properties = array_merge($definition, [
'MaximalerLadestrom' => 16,
'ZeitZwischenZustandswechseln' => 0,
'Ladefreigabe' => true,
'Solarladen' => true,
'EinstellungenInVisu' => true,
@@ -71,6 +72,7 @@ return static function (TestContext $test): void {
'Geraetetyp' => 1,
'Geraeteadresse' => '192.0.2.12',
'MaximalerLadestrom' => 16,
'ZeitZwischenZustandswechseln' => 0,
'Ladefreigabe' => true,
'Solarladen' => true,
'EinstellungenInVisu' => true,
@@ -88,12 +90,85 @@ return static function (TestContext $test): void {
'Geraeteadresse' => '192.0.2.13',
'MaximalerLadestrom' => 20,
'FahrzeugstromErkennungszeit' => 1,
'ZeitZwischenZustandswechseln' => 0,
'Ladefreigabe' => true,
'Solarladen' => true,
'EinstellungenInVisu' => true,
'DiagnosevariablenAnzeigen' => true,
'Testmodus' => true,
'Testantwort' => $goEAntwort(2, 10263, [15.9, 15.8, 15.7]),
'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]),
]
);
@@ -104,7 +179,16 @@ return static function (TestContext $test): void {
'AutomatischeSuche' => false,
'VerbraucherZuordnung' => json_encode(array_map(
static fn (int $id): array => ['InstanzID' => $id, 'Aktiv' => true],
array_merge(array_values($stationen), [$stromerkennung])
array_merge(
array_values($stationen),
[
$stromerkennung,
$phasenerkennung,
$phasenhaltung,
$schaltsperre,
$mindestzeiten,
]
)
), JSON_THROW_ON_ERROR),
]
);
@@ -156,6 +240,94 @@ return static function (TestContext $test): void {
}
});
$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 {
@@ -171,35 +343,12 @@ return static function (TestContext $test): void {
);
});
$test->runCase('ApplyChanges prueft ein gespeichertes Ladeende neu', static function (
TestContext $test
) use ($goEAntwort, $solarPause): void {
IPS_RequestAction($solarPause, 'Solarladen', false);
IPS_RequestAction($solarPause, 'Statuszyklus', false);
$test->assertSame(
true,
GetValue($test->objectByIdent('FahrzeugGeladen', $solarPause))
);
IPS_SetProperty($solarPause, 'Testantwort', $goEAntwort(2, 2300, [100, 100, 100]));
IPS_ApplyChanges($solarPause);
$test->assertSame(
false,
GetValue($test->objectByIdent('FahrzeugGeladen', $solarPause))
);
$test->assertSame(
3,
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, 4150);
$anforderungSenden($stationId, 4104);
$befehl = (string) GetValue($test->objectByIdent('LetzterGeraetebefehl', $stationId));
$diagnose = json_encode([
'Sollleistung' => GetValue($test->objectByIdent('Sollleistung', $stationId)),
@@ -214,22 +363,22 @@ return static function (TestContext $test): void {
IPS_RequestAction($stationId, 'Solarladen', false);
$angebot = (string) GetValue($test->objectByIdent('LeistungsangebotDiagnose', $stationId));
$test->assertSame('[11000]', $angebot, 'PV ohne Solarladen muss nur das Maximum anbieten.');
$test->assertSame('[10944]', $angebot, 'PV ohne Solarladen muss nur das Maximum anbieten.');
$abgewiesen = false;
try {
$anforderungSenden($stationId, 4150);
$anforderungSenden($stationId, 4104);
} catch (Throwable $fehler) {
$abgewiesen = true;
}
if (!$abgewiesen) {
$abgewiesen = GetValue($test->objectByIdent('SollwertGueltig', $stationId)) !== true
|| GetValue($test->objectByIdent('Sollleistung', $stationId)) !== 4150;
|| GetValue($test->objectByIdent('Sollleistung', $stationId)) !== 4104;
}
$test->assertSame(true, $abgewiesen, 'Normalbetrieb muss eine Zwischenstufe ablehnen.');
$anforderungSenden($stationId, 4150, 'Peak');
$anforderungSenden($stationId, 4104, 'Peak');
$test->assertSame(
'[0,4150,4850,5550,6250,6950,7600,8300,9000,9700,10300,11000]',
'[0,4104,4788,5472,6156,6840,7524,8208,8892,9576,10260,10944]',
(string) GetValue($test->objectByIdent('LeistungsangebotDiagnose', $stationId))
);
$test->assertTrue(str_contains(
@@ -254,37 +403,58 @@ return static function (TestContext $test): void {
) use ($stromerkennung, $anforderungSenden): void {
IPS_RequestAction($stromerkennung, 'Aktiv', true);
IPS_RequestAction($stromerkennung, 'Solarladen', true);
$anforderungSenden($stromerkennung, 13850);
$anforderungSenden($stromerkennung, 13680);
IPS_RequestAction($stromerkennung, 'Statuszyklus', false);
IPS_Sleep(2100);
IPS_RequestAction($stromerkennung, 'Statuszyklus', false);
$test->assertSame(
16,
18,
GetValue($test->objectByIdent('FahrzeugMaximalstrom', $stromerkennung))
);
$test->assertSame(
'[0,4150,4850,5550,6250,6950,7600,8300,9000,9700,10300,11000]',
'[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=16'
'amp=18'
));
});
$test->runCase('Sollwert bleibt waehrend der Ladeanlaufzeit gueltig', static function (
$test->runCase('Neue Ladestation wird erst nach Freigabe mit Maximalstrom erkannt', static function (
TestContext $test
) use ($goEAntwort, $stationen, $anforderungSenden): void {
$stationId = $stationen['go-e Gemini'];
IPS_SetProperty($stationId, 'Testantwort', $goEAntwort(2, 0));
IPS_ApplyChanges($stationId);
IPS_RequestAction($stationId, 'Aktiv', true);
IPS_RequestAction($stationId, 'Solarladen', true);
) 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.');
$anforderungSenden($stationId, 3850);
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))
@@ -294,11 +464,11 @@ return static function (TestContext $test): void {
GetValue($test->objectByIdent('SollwertGueltig', $stationId))
);
$test->assertSame(
3850,
3680,
GetValue($test->objectByIdent('Sollleistung', $stationId))
);
$test->assertSame(
'[0,1450,1690,1930,2170,2410,2650,2890,3130,3370,3610,3850]',
'[0,1380,1610,1840,2070,2300,2530,2760,2990,3220,3450,3680]',
(string) GetValue($test->objectByIdent('LeistungsangebotDiagnose', $stationId))
);
});