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
+181 -5
View File
@@ -51,7 +51,11 @@ class LadestationStandAlone extends IPSModule implements VerbraucherSchnittstell
$this->RegisterPropertyString('Benutzername', '');
$this->RegisterPropertyString('Passwort', '');
$this->RegisterPropertyInteger('MaximalerLadestrom', 16);
$this->RegisterPropertyInteger('Phasenerkennungszeit', 60);
$this->RegisterPropertyInteger('FahrzeugstromErkennungszeit', 90);
$this->RegisterPropertyInteger('ZeitZwischenZustandswechseln', 1);
$this->RegisterPropertyInteger('Mindesteinschaltdauer', 0);
$this->RegisterPropertyInteger('Mindestausschaltdauer', 0);
$this->RegisterPropertyInteger('Abfrageintervall', 5);
$this->RegisterPropertyBoolean('Ladefreigabe', true);
$this->RegisterPropertyBoolean('Solarladen', true);
@@ -89,6 +93,12 @@ class LadestationStandAlone extends IPSModule implements VerbraucherSchnittstell
$this->RegisterAttributeInteger('LetzteManagerID', 0);
$this->RegisterAttributeInteger('LetzteVorgabeZeit', 0);
$this->RegisterAttributeInteger('LetzterGesetzterStrom', -1);
$this->RegisterAttributeInteger('LetzteGesetzteLeistung', 0);
$this->RegisterAttributeInteger('LeistungswechselGesperrtBis', 0);
$this->RegisterAttributeInteger('AusschaltenGesperrtBis', 0);
$this->RegisterAttributeInteger('EinschaltenGesperrtBis', 0);
$this->RegisterAttributeInteger('PhasenerkennungSeit', 0);
$this->RegisterAttributeInteger('PhasenerkennungVersion', 0);
$this->RegisterAttributeBoolean('LadeleistungSeitFreigabeErkannt', false);
$this->RegisterAttributeInteger('ErkannterMaximalstrom', 32);
$this->RegisterAttributeInteger('FahrzeugstromKandidat', 0);
@@ -126,6 +136,18 @@ class LadestationStandAlone extends IPSModule implements VerbraucherSchnittstell
// Ein persistiertes Ladeende nach Neueinrichtung oder Modulupdate einmal neu pruefen.
$this->setzeZustand('FahrzeugGeladen', false);
$this->WriteAttributeInteger('LetzterGesetzterStrom', -1);
$this->WriteAttributeInteger('LetzteGesetzteLeistung', 0);
$this->WriteAttributeInteger('LeistungswechselGesperrtBis', 0);
$this->WriteAttributeInteger('AusschaltenGesperrtBis', 0);
$this->WriteAttributeInteger('EinschaltenGesperrtBis', 0);
$this->WriteAttributeInteger('PhasenerkennungSeit', 0);
if ($this->ReadAttributeInteger('PhasenerkennungVersion') < 1) {
$this->WriteAttributeInteger(
'ZustandPhasenzahl',
LadestationRegler::PHASEN_UNBEKANNT
);
$this->WriteAttributeInteger('PhasenerkennungVersion', 1);
}
$this->WriteAttributeBoolean('LadeleistungSeitFreigabeErkannt', false);
$this->WriteAttributeInteger(
'ErkannterMaximalstrom',
@@ -283,6 +305,40 @@ class LadestationStandAlone extends IPSModule implements VerbraucherSchnittstell
$solarladen,
$peakbetrieb
);
if ($aktiv
&& $ladefreigabe
&& $verbunden
&& !$geladen
&& !($peakbetrieb && $solarladen)
&& $phasenzahl !== LadestationRegler::PHASEN_UNBEKANNT
) {
$jetzt = time();
$letzteLeistung = $this->ReadAttributeInteger('LetzteGesetzteLeistung');
if ($jetzt < $this->ReadAttributeInteger('LeistungswechselGesperrtBis')) {
$angebot = [$letzteLeistung];
} elseif ($letzteLeistung > 0
&& $jetzt < $this->ReadAttributeInteger('AusschaltenGesperrtBis')
) {
$positiveStufen = array_values(array_filter(
LadestationRegler::leistungsangebot(
$aktiv,
$ladefreigabe,
$verbunden,
$geladen,
$phasenzahl,
$maximalstrom,
$solarladen,
false
),
static fn (int $wert): bool => $wert > 0
));
$angebot = $positiveStufen !== [] ? $positiveStufen : [$letzteLeistung];
} elseif ($letzteLeistung <= 0
&& $jetzt < $this->ReadAttributeInteger('EinschaltenGesperrtBis')
) {
$angebot = [0];
}
}
$angebotJson = json_encode($angebot, JSON_THROW_ON_ERROR);
$this->WriteAttributeString('Leistungsangebot', $angebotJson);
$this->setzeSichtbareVariable('LeistungsangebotDiagnose', $angebotJson);
@@ -290,7 +346,14 @@ class LadestationStandAlone extends IPSModule implements VerbraucherSchnittstell
$this->setzeZustand('AenderungMoeglich', $verfuegbar && count($angebot) > 1);
$sollleistung = $this->bestimmeWirksameSollleistung($angebot);
$this->setzeLadestrom($sollleistung);
if (!($aktiv
&& $ladefreigabe
&& $verbunden
&& !$geladen
&& $phasenzahl === LadestationRegler::PHASEN_UNBEKANNT
)) {
$this->setzeLadestrom($sollleistung);
}
$this->setzeStoerung('');
$this->SetStatus(self::STATUS_AKTIV);
} catch (Throwable $fehler) {
@@ -319,19 +382,73 @@ class LadestationStandAlone extends IPSModule implements VerbraucherSchnittstell
$this->ReadPropertyInteger('Geraetetyp'),
$antwort
);
$warVerbunden = $this->ReadAttributeBoolean('ZustandFahrzeugVerbunden');
$verbunden = $rohstatus['Rohstatus'] !== 1;
$bekanntePhasenzahl = $warVerbunden
? $this->ReadAttributeInteger('ZustandPhasenzahl')
: LadestationRegler::PHASEN_UNBEKANNT;
if (!$verbunden) {
$bekanntePhasenzahl = LadestationRegler::PHASEN_UNBEKANNT;
$this->WriteAttributeInteger('PhasenerkennungSeit', 0);
$this->WriteAttributeInteger('LeistungswechselGesperrtBis', 0);
$this->WriteAttributeInteger('AusschaltenGesperrtBis', 0);
$this->WriteAttributeInteger('EinschaltenGesperrtBis', 0);
} elseif ($bekanntePhasenzahl === LadestationRegler::PHASEN_UNBEKANNT) {
$direktErkanntePhasen = (int) ($rohstatus['Phasenzahl']
?? LadestationRegler::PHASEN_UNBEKANNT);
if (in_array(
$direktErkanntePhasen,
[LadestationRegler::PHASEN_EIN, LadestationRegler::PHASEN_DREI],
true
)) {
$bekanntePhasenzahl = $direktErkanntePhasen;
$this->WriteAttributeInteger('PhasenerkennungSeit', 0);
} elseif ($rohstatus['Istleistung_W'] > 7500.0) {
$bekanntePhasenzahl = LadestationRegler::PHASEN_DREI;
$this->WriteAttributeInteger('PhasenerkennungSeit', 0);
} elseif ((bool) $this->GetValue('Aktiv')
&& $this->ReadAttributeBoolean('ZustandLadefreigabe')
) {
$erkennungSeit = $this->ReadAttributeInteger('PhasenerkennungSeit');
if ($erkennungSeit <= 0) {
$this->sendeLadestromAnGeraet(
$this->ReadPropertyInteger('MaximalerLadestrom')
);
$erkennungSeit = time();
$this->WriteAttributeInteger('PhasenerkennungSeit', $erkennungSeit);
} elseif ((time() - $erkennungSeit)
>= $this->ReadPropertyInteger('Phasenerkennungszeit')
) {
$bekanntePhasenzahl = $rohstatus['Istleistung_W'] > 7500.0
? LadestationRegler::PHASEN_DREI
: LadestationRegler::PHASEN_EIN;
$this->WriteAttributeInteger('PhasenerkennungSeit', 0);
}
} else {
$this->WriteAttributeInteger('PhasenerkennungSeit', 0);
}
}
$status = LadestationRegler::normalisiereStatus(
$rohstatus,
$this->ReadAttributeBoolean('ZustandFahrzeugVerbunden'),
$warVerbunden,
$this->ReadAttributeBoolean('ZustandFahrzeugGeladen'),
$this->ReadAttributeInteger('LetzterGesetzterStrom') >= 6,
$this->ReadAttributeBoolean('LadeleistungSeitFreigabeErkannt')
$this->ReadAttributeBoolean('LadeleistungSeitFreigabeErkannt'),
$bekanntePhasenzahl
);
if (!$status['FahrzeugVerbunden']) {
$this->WriteAttributeBoolean('LadeleistungSeitFreigabeErkannt', false);
} elseif ($status['Ladestrom_A'] >= 3.5) {
$this->WriteAttributeBoolean('LadeleistungSeitFreigabeErkannt', true);
}
$this->aktualisiereFahrzeugstromgrenze($status);
if (!$status['FahrzeugVerbunden']
|| $status['Phasenzahl'] !== LadestationRegler::PHASEN_UNBEKANNT
) {
$this->aktualisiereFahrzeugstromgrenze($status);
}
foreach ($status as $ident => $wert) {
$this->setzeZustand($ident, $wert);
@@ -374,6 +491,7 @@ class LadestationStandAlone extends IPSModule implements VerbraucherSchnittstell
}
$this->WriteAttributeInteger('ErkannterMaximalstrom', $kandidat);
$this->WriteAttributeInteger('LeistungswechselGesperrtBis', 0);
$this->setzeSichtbareVariable('FahrzeugMaximalstrom', $kandidat);
$this->setzeFahrzeugstromKandidatZurueck();
}
@@ -416,9 +534,56 @@ class LadestationStandAlone extends IPSModule implements VerbraucherSchnittstell
$strom = $leistung === 0
? 0
: LadestationRegler::stromFuerLeistung($leistung, $phasenzahl);
if ($strom === $this->ReadAttributeInteger('LetzterGesetzterStrom')) {
$bisherigeLeistung = $this->ReadAttributeInteger('LetzteGesetzteLeistung');
$stromUnveraendert = $strom === $this->ReadAttributeInteger('LetzterGesetzterStrom');
if ($stromUnveraendert && $leistung === $bisherigeLeistung) {
return;
}
if (!$stromUnveraendert) {
$this->sendeLadestromAnGeraet($strom);
}
if ($leistung === $bisherigeLeistung) {
return;
}
if (!$this->ReadAttributeBoolean('ZustandFahrzeugVerbunden')) {
$leistungswechselGesperrtBis = 0;
$this->WriteAttributeInteger('AusschaltenGesperrtBis', 0);
$this->WriteAttributeInteger('EinschaltenGesperrtBis', 0);
} else {
$jetzt = time();
$wechselzeit = $this->ReadPropertyInteger('ZeitZwischenZustandswechseln');
$leistungswechselGesperrtBis = $wechselzeit > 0
? $jetzt + ($wechselzeit * 60)
: 0;
if ($bisherigeLeistung <= 0 && $leistung > 0) {
$mindestzeit = $this->ReadPropertyInteger('Mindesteinschaltdauer');
$this->WriteAttributeInteger(
'AusschaltenGesperrtBis',
$mindestzeit > 0 ? $jetzt + ($mindestzeit * 60) : 0
);
$this->WriteAttributeInteger('EinschaltenGesperrtBis', 0);
} elseif ($bisherigeLeistung > 0 && $leistung <= 0) {
$mindestzeit = $this->ReadPropertyInteger('Mindestausschaltdauer');
$this->WriteAttributeInteger(
'EinschaltenGesperrtBis',
$mindestzeit > 0 ? $jetzt + ($mindestzeit * 60) : 0
);
$this->WriteAttributeInteger('AusschaltenGesperrtBis', 0);
}
}
$this->WriteAttributeInteger('LetzteGesetzteLeistung', $leistung);
$this->WriteAttributeInteger(
'LeistungswechselGesperrtBis',
$leistungswechselGesperrtBis
);
}
private function sendeLadestromAnGeraet(int $strom): void
{
$this->WriteAttributeBoolean('LadeleistungSeitFreigabeErkannt', false);
$this->setzeFahrzeugstromKandidatZurueck();
@@ -660,12 +825,23 @@ class LadestationStandAlone extends IPSModule implements VerbraucherSchnittstell
'Meldeintervall',
'VorgabeTimeout',
'Abfrageintervall',
'Phasenerkennungszeit',
'FahrzeugstromErkennungszeit',
] as $property) {
if ($this->ReadPropertyInteger($property) <= 0) {
throw new InvalidArgumentException($property . ' muss groesser als 0 sein.');
}
}
foreach ([
'ZeitZwischenZustandswechseln',
'Mindesteinschaltdauer',
'Mindestausschaltdauer',
] as $property) {
if ($this->ReadPropertyInteger($property) < 0) {
throw new InvalidArgumentException($property . ' darf nicht negativ sein.');
}
}
$maximalstrom = $this->ReadPropertyInteger('MaximalerLadestrom');
if ($maximalstrom < 6 || $maximalstrom > 32) {
throw new InvalidArgumentException('MaximalerLadestrom muss zwischen 6 und 32 A liegen.');