Do not latch full on transient zero current; require explicit completion. Validate go-e errors and Pico status/timestamps, keep historical events diagnostic, and preserve faults until a fresh successful poll. Budget the 6 A phase probe through the manager and correct stale phase counts conservatively. Add adapter, regulator, isolated runtime and Symcon regression cases plus migration documentation. Verified: composer check (402 tests, 1875 assertions), 92 pure-function assertions in Symcon 8.0, git diff --check. No plant deployment or hardware commands.
This commit is contained in:
@@ -34,6 +34,7 @@ class LadestationStandAlone extends IPSModule implements VerbraucherSchnittstell
|
||||
'AenderungMoeglich',
|
||||
'Stoerung',
|
||||
'Stoertext',
|
||||
'Geraetehinweis',
|
||||
'Fahrzeugstatus',
|
||||
'FahrzeugMaximalstrom',
|
||||
'LetzterGeraetebefehl',
|
||||
@@ -88,6 +89,9 @@ class LadestationStandAlone extends IPSModule implements VerbraucherSchnittstell
|
||||
$this->RegisterAttributeBoolean('ZustandAenderungMoeglich', false);
|
||||
$this->RegisterAttributeBoolean('ZustandStoerung', false);
|
||||
$this->RegisterAttributeString('ZustandStoertext', '');
|
||||
$this->RegisterAttributeString('ZustandGeraetehinweis', '');
|
||||
$this->RegisterAttributeInteger('GeraetestatusGueltigBis', 0);
|
||||
$this->RegisterAttributeBoolean('GeraetLadebereit', false);
|
||||
$this->RegisterAttributeString('Leistungsangebot', '[0]');
|
||||
$this->RegisterAttributeString('Betriebsart', Nachrichtenvertrag::BETRIEBSART_PV);
|
||||
$this->RegisterAttributeInteger('LetzteManagerID', 0);
|
||||
@@ -149,12 +153,13 @@ class LadestationStandAlone extends IPSModule implements VerbraucherSchnittstell
|
||||
$this->WriteAttributeInteger('AusschaltenGesperrtBis', 0);
|
||||
$this->WriteAttributeInteger('EinschaltenGesperrtBis', 0);
|
||||
$this->WriteAttributeInteger('PhasenerkennungSeit', 0);
|
||||
if ($this->ReadAttributeInteger('PhasenerkennungVersion') < 1) {
|
||||
$this->WriteAttributeInteger('GeraetestatusGueltigBis', 0);
|
||||
if ($this->ReadAttributeInteger('PhasenerkennungVersion') < 2) {
|
||||
$this->WriteAttributeInteger(
|
||||
'ZustandPhasenzahl',
|
||||
LadestationRegler::PHASEN_UNBEKANNT
|
||||
);
|
||||
$this->WriteAttributeInteger('PhasenerkennungVersion', 1);
|
||||
$this->WriteAttributeInteger('PhasenerkennungVersion', 2);
|
||||
}
|
||||
$this->WriteAttributeBoolean('LadeleistungSeitFreigabeErkannt', false);
|
||||
$this->WriteAttributeInteger(
|
||||
@@ -273,8 +278,10 @@ class LadestationStandAlone extends IPSModule implements VerbraucherSchnittstell
|
||||
$this->aktualisiere(false, false, false);
|
||||
$sollleistung = $daten['Sollleistung_W'];
|
||||
if ($sollleistung === null) {
|
||||
if ((bool) $this->leseZustand('SollwertGueltig')
|
||||
&& !in_array((int) $this->leseZustand('Sollleistung'), $this->leseLeistungsangebot(), true)
|
||||
if (($this->leseLeistungsangebot() === [0]
|
||||
&& $this->ReadAttributeInteger('LetzterGesetzterStrom') !== 0)
|
||||
|| ((bool) $this->leseZustand('SollwertGueltig')
|
||||
&& !in_array((int) $this->leseZustand('Sollleistung'), $this->leseLeistungsangebot(), true))
|
||||
) {
|
||||
$this->verwerfeSollwert();
|
||||
$this->aktualisiere(false, false);
|
||||
@@ -308,9 +315,14 @@ class LadestationStandAlone extends IPSModule implements VerbraucherSchnittstell
|
||||
if ($statusLesen) {
|
||||
$this->leseGeraetestatus();
|
||||
}
|
||||
if ($this->ReadAttributeInteger('GeraetestatusGueltigBis') <= time()) {
|
||||
$fehlertext = (string) $this->leseZustand('Stoertext');
|
||||
throw new RuntimeException($fehlertext !== '' ? $fehlertext : 'Kein frischer gueltiger Geraetestatus.');
|
||||
}
|
||||
|
||||
$aktiv = (bool) $this->GetValue('Aktiv');
|
||||
$ladefreigabe = $this->ReadAttributeBoolean('ZustandLadefreigabe');
|
||||
$ladefreigabe = $this->ReadAttributeBoolean('ZustandLadefreigabe')
|
||||
&& $this->ReadAttributeBoolean('GeraetLadebereit');
|
||||
$verbunden = $this->ReadAttributeBoolean('ZustandFahrzeugVerbunden');
|
||||
$geladen = $this->ReadAttributeBoolean('ZustandFahrzeugGeladen');
|
||||
$phasenzahl = $this->ReadAttributeInteger('ZustandPhasenzahl');
|
||||
@@ -384,25 +396,33 @@ class LadestationStandAlone extends IPSModule implements VerbraucherSchnittstell
|
||||
$this->setzeZustand('AenderungMoeglich', $verfuegbar && count($angebot) > 1);
|
||||
|
||||
$sollleistung = $this->bestimmeWirksameSollleistung($angebot);
|
||||
$phasenerkennungAktiv = $aktiv
|
||||
&& $ladefreigabe
|
||||
&& $verbunden
|
||||
&& !$geladen
|
||||
&& $phasenzahl === LadestationRegler::PHASEN_UNBEKANNT;
|
||||
$regeluebergangAktiv = $aktiv
|
||||
&& $ladefreigabe
|
||||
&& $verbunden
|
||||
&& !$geladen
|
||||
&& $this->ReadAttributeBoolean('RegeluebergangAktiv');
|
||||
if ($steuerungAusfuehren && !$phasenerkennungAktiv && !$regeluebergangAktiv) {
|
||||
if ($steuerungAusfuehren && (!$regeluebergangAktiv || $angebot === [0])) {
|
||||
$this->setzeLadestrom($sollleistung);
|
||||
}
|
||||
$this->setzeStoerung('');
|
||||
$this->SetStatus(self::STATUS_AKTIV);
|
||||
} catch (Throwable $fehler) {
|
||||
$this->WriteAttributeInteger('GeraetestatusGueltigBis', 0);
|
||||
$this->WriteAttributeInteger('PhasenerkennungSeit', 0);
|
||||
$this->WriteAttributeString('Leistungsangebot', '[0]');
|
||||
$this->setzeSichtbareVariable('LeistungsangebotDiagnose', '[0]');
|
||||
$this->verwerfeSollwert();
|
||||
$this->setzeZustand('Verfuegbar', false);
|
||||
$this->setzeZustand('AenderungMoeglich', false);
|
||||
$this->setzeStoerung($fehler->getMessage());
|
||||
$text = $fehler->getMessage();
|
||||
if ($steuerungAusfuehren && $this->ReadAttributeInteger('LetzterGesetzterStrom') !== 0) {
|
||||
try {
|
||||
$this->setzeLadestrom(0);
|
||||
} catch (Throwable $stoppfehler) {
|
||||
$text .= ' Nullvorgabe nicht bestaetigt: ' . $stoppfehler->getMessage();
|
||||
}
|
||||
}
|
||||
$this->setzeStoerung($text);
|
||||
$this->SetStatus(self::STATUS_KOMMUNIKATIONSFEHLER);
|
||||
$this->protokolliere('Geraetekommunikation', $fehler->getMessage());
|
||||
}
|
||||
@@ -447,7 +467,7 @@ class LadestationStandAlone extends IPSModule implements VerbraucherSchnittstell
|
||||
$this->WriteAttributeInteger('LeistungswechselGesperrtBis', 0);
|
||||
$this->WriteAttributeInteger('AusschaltenGesperrtBis', 0);
|
||||
$this->WriteAttributeInteger('EinschaltenGesperrtBis', 0);
|
||||
} elseif ($bekanntePhasenzahl === LadestationRegler::PHASEN_UNBEKANNT) {
|
||||
} else {
|
||||
$direktErkanntePhasen = (int) ($rohstatus['Phasenzahl']
|
||||
?? LadestationRegler::PHASEN_UNBEKANNT);
|
||||
if (in_array(
|
||||
@@ -455,30 +475,25 @@ class LadestationStandAlone extends IPSModule implements VerbraucherSchnittstell
|
||||
[LadestationRegler::PHASEN_EIN, LadestationRegler::PHASEN_DREI],
|
||||
true
|
||||
)) {
|
||||
$bekanntePhasenzahl = $direktErkanntePhasen;
|
||||
$bekanntePhasenzahl = max($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')
|
||||
) {
|
||||
} elseif ($bekanntePhasenzahl === LadestationRegler::PHASEN_UNBEKANNT) {
|
||||
$erkennungSeit = $this->ReadAttributeInteger('PhasenerkennungSeit');
|
||||
if ($erkennungSeit <= 0) {
|
||||
$this->sendeLadestromAnGeraet(self::PHASENERKENNUNGSSTROM);
|
||||
$erkennungSeit = time();
|
||||
$this->WriteAttributeInteger('PhasenerkennungSeit', $erkennungSeit);
|
||||
} elseif ((time() - $erkennungSeit)
|
||||
>= $this->ReadPropertyInteger('Phasenerkennungszeit')
|
||||
if ($erkennungSeit > 0
|
||||
&& $this->ReadAttributeInteger('LetzterGesetzterStrom') === self::PHASENERKENNUNGSSTROM
|
||||
&& (time() - $erkennungSeit) >= $this->ReadPropertyInteger('Phasenerkennungszeit')
|
||||
) {
|
||||
$bekanntePhasenzahl = LadestationRegler::erkennePhasenzahlAusLeistung(
|
||||
(float) $rohstatus['Istleistung_W'],
|
||||
$this->ReadAttributeInteger('LetzterGesetzterStrom')
|
||||
);
|
||||
$this->WriteAttributeInteger('PhasenerkennungSeit', 0);
|
||||
if ($bekanntePhasenzahl !== LadestationRegler::PHASEN_UNBEKANNT) {
|
||||
$this->WriteAttributeInteger('PhasenerkennungSeit', 0);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$this->WriteAttributeInteger('PhasenerkennungSeit', 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -490,6 +505,10 @@ class LadestationStandAlone extends IPSModule implements VerbraucherSchnittstell
|
||||
$this->ReadAttributeBoolean('LadeleistungSeitFreigabeErkannt'),
|
||||
$bekanntePhasenzahl
|
||||
);
|
||||
if ($status['Phasenzahl'] !== $this->ReadAttributeInteger('ZustandPhasenzahl')) {
|
||||
// Eine alte Leistungsstufe darf nach einer Phasenkorrektur nicht festgehalten werden.
|
||||
$this->WriteAttributeInteger('LeistungswechselGesperrtBis', 0);
|
||||
}
|
||||
if (!$status['FahrzeugVerbunden']) {
|
||||
$this->WriteAttributeBoolean('LadeleistungSeitFreigabeErkannt', false);
|
||||
} elseif ($status['Ladestrom_A'] >= 3.5) {
|
||||
@@ -504,6 +523,13 @@ class LadestationStandAlone extends IPSModule implements VerbraucherSchnittstell
|
||||
foreach ($status as $ident => $wert) {
|
||||
$this->setzeZustand($ident, $wert);
|
||||
}
|
||||
$this->setzeZustand('Geraetehinweis', (string) ($rohstatus['Geraetehinweis'] ?? ''));
|
||||
$this->WriteAttributeBoolean('GeraetLadebereit', (bool) ($rohstatus['Ladebereit'] ?? true));
|
||||
$cacheAlter = min(LadestationAdapter::STATUS_MAXIMALALTER, max(15, 3 * $this->ReadPropertyInteger('Abfrageintervall')));
|
||||
$this->WriteAttributeInteger('GeraetestatusGueltigBis', min(
|
||||
time() + $cacheAlter,
|
||||
(int) ($rohstatus['GueltigBis'] ?? PHP_INT_MAX)
|
||||
));
|
||||
}
|
||||
|
||||
/** @param array<string, int|float|bool> $status */
|
||||
@@ -573,6 +599,11 @@ class LadestationStandAlone extends IPSModule implements VerbraucherSchnittstell
|
||||
}
|
||||
|
||||
if (!$this->ReadAttributeBoolean('ZustandSolarladen') && $angebot !== [0]) {
|
||||
if ($this->ReadAttributeInteger('ZustandPhasenzahl') === LadestationRegler::PHASEN_UNBEKANNT
|
||||
&& $this->zugeordneteManagerIDs() !== []
|
||||
) {
|
||||
return 0;
|
||||
}
|
||||
return $angebot[count($angebot) - 1];
|
||||
}
|
||||
|
||||
@@ -594,6 +625,12 @@ class LadestationStandAlone extends IPSModule implements VerbraucherSchnittstell
|
||||
if (!$stromUnveraendert) {
|
||||
$this->sendeLadestromAnGeraet($strom);
|
||||
}
|
||||
if ($phasenzahl === LadestationRegler::PHASEN_UNBEKANNT
|
||||
&& $strom === self::PHASENERKENNUNGSSTROM
|
||||
&& $this->ReadAttributeInteger('PhasenerkennungSeit') === 0
|
||||
) {
|
||||
$this->WriteAttributeInteger('PhasenerkennungSeit', time());
|
||||
}
|
||||
if ($leistung === $bisherigeLeistung) {
|
||||
return;
|
||||
}
|
||||
@@ -647,6 +684,7 @@ class LadestationStandAlone extends IPSModule implements VerbraucherSchnittstell
|
||||
$strom
|
||||
);
|
||||
$this->WriteAttributeInteger('LetzterGesetzterStrom', $strom);
|
||||
$this->WriteAttributeInteger('PhasenerkennungSeit', 0);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -729,6 +767,11 @@ class LadestationStandAlone extends IPSModule implements VerbraucherSchnittstell
|
||||
|
||||
private function sendeVerbraucherdaten(): void
|
||||
{
|
||||
if ($this->ReadAttributeInteger('GeraetestatusGueltigBis') <= time()
|
||||
&& (bool) $this->leseZustand('Verfuegbar')
|
||||
) {
|
||||
$this->aktualisiere(false, false, false);
|
||||
}
|
||||
foreach ($this->zugeordneteManagerIDs() as $managerID) {
|
||||
$daten = $this->baueVerbraucherdaten($managerID);
|
||||
Nachrichtenvertrag::pruefeVerbraucherdaten($daten);
|
||||
@@ -822,6 +865,12 @@ class LadestationStandAlone extends IPSModule implements VerbraucherSchnittstell
|
||||
'Einheit' => '',
|
||||
'Text' => (string) $this->leseZustand('Stoertext'),
|
||||
],
|
||||
[
|
||||
'Kennung' => 'Geraetehinweis',
|
||||
'Art' => 'Status',
|
||||
'Wert' => (string) $this->leseZustand('Geraetehinweis'),
|
||||
'Einheit' => '',
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
@@ -955,6 +1004,7 @@ class LadestationStandAlone extends IPSModule implements VerbraucherSchnittstell
|
||||
$this->RegisterVariableInteger('Fahrzeugstatus', 'Fahrzeugstatus', '', 200);
|
||||
$this->RegisterVariableString('LetzterGeraetebefehl', 'Letzter Geraetebefehl', '', 210);
|
||||
$this->RegisterVariableString('LeistungsangebotDiagnose', 'Leistungsangebot', '', 220);
|
||||
$this->RegisterVariableString('Geraetehinweis', 'Geraetehinweis', '', 240);
|
||||
$this->RegisterVariableInteger(
|
||||
'FahrzeugMaximalstrom',
|
||||
'Erkannter Fahrzeug-Maximalstrom',
|
||||
@@ -1037,7 +1087,8 @@ class LadestationStandAlone extends IPSModule implements VerbraucherSchnittstell
|
||||
$this->WriteAttributeFloat('ZustandIstleistung', (float) $wert);
|
||||
break;
|
||||
case 'Stoertext':
|
||||
$this->WriteAttributeString('ZustandStoertext', (string) $wert);
|
||||
case 'Geraetehinweis':
|
||||
$this->WriteAttributeString('Zustand' . $ident, (string) $wert);
|
||||
break;
|
||||
default:
|
||||
throw new LogicException('Unbekannter Zustand: ' . $ident);
|
||||
@@ -1069,8 +1120,8 @@ class LadestationStandAlone extends IPSModule implements VerbraucherSchnittstell
|
||||
if ($ident === 'Ladestrom' || $ident === 'Istleistung') {
|
||||
return $this->ReadAttributeFloat('Zustand' . $ident);
|
||||
}
|
||||
if ($ident === 'Stoertext') {
|
||||
return $this->ReadAttributeString('ZustandStoertext');
|
||||
if ($ident === 'Stoertext' || $ident === 'Geraetehinweis') {
|
||||
return $this->ReadAttributeString('Zustand' . $ident);
|
||||
}
|
||||
if ($ident === 'Leistungsquelle') {
|
||||
return Nachrichtenvertrag::LEISTUNGSQUELLE_GEMESSEN;
|
||||
|
||||
Reference in New Issue
Block a user