This commit is contained in:
@@ -17,6 +17,7 @@ Alle wesentlichen Aenderungen an Enelix EMS werden in dieser Datei dokumentiert.
|
|||||||
|
|
||||||
- Stufenlose Leistungsbereiche fuer Batteriespeicher.
|
- Stufenlose Leistungsbereiche fuer Batteriespeicher.
|
||||||
- Ladefreigabe neuer Ladestationsinstanzen standardmaessig aktiviert.
|
- Ladefreigabe neuer Ladestationsinstanzen standardmaessig aktiviert.
|
||||||
|
- Stand-alone-Ladestationen erkennen nach einer einstellbaren Beobachtungszeit den niedrigeren Fahrzeug-Maximalstrom und begrenzen ihr Leistungsangebot ohne Ladeunterbruch.
|
||||||
- Easee-Ladestationen laden einen fehlenden Initialstatus ueber die Observations-API nach und starten bei noch unbekannter Ausgangsphase konservativ einphasig.
|
- Easee-Ladestationen laden einen fehlenden Initialstatus ueber die Observations-API nach und starten bei noch unbekannter Ausgangsphase konservativ einphasig.
|
||||||
- Wartende Easee-Ladestationen erhalten bei positiver Stromvorgabe einmal pro Fahrzeugverbindung einen Startbefehl; Freigabe- und Blockiergruende werden diagnostiziert.
|
- Wartende Easee-Ladestationen erhalten bei positiver Stromvorgabe einmal pro Fahrzeugverbindung einen Startbefehl; Freigabe- und Blockiergruende werden diagnostiziert.
|
||||||
- Fehlende oder veraltete Verbraucher blockieren die Verteilung an aktuelle Verbraucher nicht mehr.
|
- Fehlende oder veraltete Verbraucher blockieren die Verteilung an aktuelle Verbraucher nicht mehr.
|
||||||
|
|||||||
@@ -37,6 +37,13 @@
|
|||||||
"caption": "Geraetestatus abfragen",
|
"caption": "Geraetestatus abfragen",
|
||||||
"suffix": " s",
|
"suffix": " s",
|
||||||
"minimum": 1
|
"minimum": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "NumberSpinner",
|
||||||
|
"name": "FahrzeugstromErkennungszeit",
|
||||||
|
"caption": "Fahrzeug-Maximalstrom erkennen nach",
|
||||||
|
"suffix": " s",
|
||||||
|
"minimum": 30
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ class LadestationStandAlone extends IPSModule implements VerbraucherSchnittstell
|
|||||||
'AenderungMoeglich',
|
'AenderungMoeglich',
|
||||||
'Stoerung',
|
'Stoerung',
|
||||||
'Stoertext',
|
'Stoertext',
|
||||||
|
'FahrzeugMaximalstrom',
|
||||||
'LetzterGeraetebefehl',
|
'LetzterGeraetebefehl',
|
||||||
'LeistungsangebotDiagnose',
|
'LeistungsangebotDiagnose',
|
||||||
];
|
];
|
||||||
@@ -50,6 +51,7 @@ class LadestationStandAlone extends IPSModule implements VerbraucherSchnittstell
|
|||||||
$this->RegisterPropertyString('Benutzername', '');
|
$this->RegisterPropertyString('Benutzername', '');
|
||||||
$this->RegisterPropertyString('Passwort', '');
|
$this->RegisterPropertyString('Passwort', '');
|
||||||
$this->RegisterPropertyInteger('MaximalerLadestrom', 16);
|
$this->RegisterPropertyInteger('MaximalerLadestrom', 16);
|
||||||
|
$this->RegisterPropertyInteger('FahrzeugstromErkennungszeit', 90);
|
||||||
$this->RegisterPropertyInteger('Abfrageintervall', 5);
|
$this->RegisterPropertyInteger('Abfrageintervall', 5);
|
||||||
$this->RegisterPropertyBoolean('Ladefreigabe', true);
|
$this->RegisterPropertyBoolean('Ladefreigabe', true);
|
||||||
$this->RegisterPropertyBoolean('Solarladen', true);
|
$this->RegisterPropertyBoolean('Solarladen', true);
|
||||||
@@ -88,6 +90,9 @@ class LadestationStandAlone extends IPSModule implements VerbraucherSchnittstell
|
|||||||
$this->RegisterAttributeInteger('LetzteVorgabeZeit', 0);
|
$this->RegisterAttributeInteger('LetzteVorgabeZeit', 0);
|
||||||
$this->RegisterAttributeInteger('LetzterGesetzterStrom', -1);
|
$this->RegisterAttributeInteger('LetzterGesetzterStrom', -1);
|
||||||
$this->RegisterAttributeBoolean('LadeleistungSeitFreigabeErkannt', false);
|
$this->RegisterAttributeBoolean('LadeleistungSeitFreigabeErkannt', false);
|
||||||
|
$this->RegisterAttributeInteger('ErkannterMaximalstrom', 32);
|
||||||
|
$this->RegisterAttributeInteger('FahrzeugstromKandidat', 0);
|
||||||
|
$this->RegisterAttributeInteger('FahrzeugstromKandidatSeit', 0);
|
||||||
|
|
||||||
$this->RegisterTimer(
|
$this->RegisterTimer(
|
||||||
'Statuszyklus',
|
'Statuszyklus',
|
||||||
@@ -122,6 +127,11 @@ class LadestationStandAlone extends IPSModule implements VerbraucherSchnittstell
|
|||||||
$this->setzeZustand('FahrzeugGeladen', false);
|
$this->setzeZustand('FahrzeugGeladen', false);
|
||||||
$this->WriteAttributeInteger('LetzterGesetzterStrom', -1);
|
$this->WriteAttributeInteger('LetzterGesetzterStrom', -1);
|
||||||
$this->WriteAttributeBoolean('LadeleistungSeitFreigabeErkannt', false);
|
$this->WriteAttributeBoolean('LadeleistungSeitFreigabeErkannt', false);
|
||||||
|
$this->WriteAttributeInteger(
|
||||||
|
'ErkannterMaximalstrom',
|
||||||
|
$this->ReadPropertyInteger('MaximalerLadestrom')
|
||||||
|
);
|
||||||
|
$this->setzeFahrzeugstromKandidatZurueck();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$this->pruefeKonfiguration();
|
$this->pruefeKonfiguration();
|
||||||
@@ -245,7 +255,11 @@ class LadestationStandAlone extends IPSModule implements VerbraucherSchnittstell
|
|||||||
$verbunden = $this->ReadAttributeBoolean('ZustandFahrzeugVerbunden');
|
$verbunden = $this->ReadAttributeBoolean('ZustandFahrzeugVerbunden');
|
||||||
$geladen = $this->ReadAttributeBoolean('ZustandFahrzeugGeladen');
|
$geladen = $this->ReadAttributeBoolean('ZustandFahrzeugGeladen');
|
||||||
$phasenzahl = $this->ReadAttributeInteger('ZustandPhasenzahl');
|
$phasenzahl = $this->ReadAttributeInteger('ZustandPhasenzahl');
|
||||||
$maximalstrom = $this->ReadPropertyInteger('MaximalerLadestrom');
|
$maximalstrom = min(
|
||||||
|
$this->ReadPropertyInteger('MaximalerLadestrom'),
|
||||||
|
max(6, $this->ReadAttributeInteger('ErkannterMaximalstrom'))
|
||||||
|
);
|
||||||
|
$this->setzeSichtbareVariable('FahrzeugMaximalstrom', $maximalstrom);
|
||||||
$solarladen = $this->ReadAttributeBoolean('ZustandSolarladen');
|
$solarladen = $this->ReadAttributeBoolean('ZustandSolarladen');
|
||||||
$peakbetrieb = $this->ReadAttributeString('Betriebsart')
|
$peakbetrieb = $this->ReadAttributeString('Betriebsart')
|
||||||
=== Nachrichtenvertrag::BETRIEBSART_PEAK;
|
=== Nachrichtenvertrag::BETRIEBSART_PEAK;
|
||||||
@@ -317,12 +331,59 @@ class LadestationStandAlone extends IPSModule implements VerbraucherSchnittstell
|
|||||||
} elseif ($status['Ladestrom_A'] >= 3.5) {
|
} elseif ($status['Ladestrom_A'] >= 3.5) {
|
||||||
$this->WriteAttributeBoolean('LadeleistungSeitFreigabeErkannt', true);
|
$this->WriteAttributeBoolean('LadeleistungSeitFreigabeErkannt', true);
|
||||||
}
|
}
|
||||||
|
$this->aktualisiereFahrzeugstromgrenze($status);
|
||||||
|
|
||||||
foreach ($status as $ident => $wert) {
|
foreach ($status as $ident => $wert) {
|
||||||
$this->setzeZustand($ident, $wert);
|
$this->setzeZustand($ident, $wert);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @param array<string, int|float|bool> $status */
|
||||||
|
private function aktualisiereFahrzeugstromgrenze(array $status): void
|
||||||
|
{
|
||||||
|
$konfigurierterMaximalstrom = $this->ReadPropertyInteger('MaximalerLadestrom');
|
||||||
|
if (!$status['FahrzeugVerbunden']) {
|
||||||
|
$this->WriteAttributeInteger('ErkannterMaximalstrom', $konfigurierterMaximalstrom);
|
||||||
|
$this->setzeSichtbareVariable('FahrzeugMaximalstrom', $konfigurierterMaximalstrom);
|
||||||
|
$this->setzeFahrzeugstromKandidatZurueck();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$kandidat = LadestationRegler::ermittleFahrzeugMaximalstrom(
|
||||||
|
$this->ReadAttributeInteger('LetzterGesetzterStrom'),
|
||||||
|
(float) $status['Ladestrom_A'],
|
||||||
|
$konfigurierterMaximalstrom
|
||||||
|
);
|
||||||
|
if ($kandidat === null
|
||||||
|
|| $kandidat >= $this->ReadAttributeInteger('ErkannterMaximalstrom')
|
||||||
|
) {
|
||||||
|
$this->setzeFahrzeugstromKandidatZurueck();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$jetzt = time();
|
||||||
|
if ($this->ReadAttributeInteger('FahrzeugstromKandidat') !== $kandidat) {
|
||||||
|
$this->WriteAttributeInteger('FahrzeugstromKandidat', $kandidat);
|
||||||
|
$this->WriteAttributeInteger('FahrzeugstromKandidatSeit', $jetzt);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (($jetzt - $this->ReadAttributeInteger('FahrzeugstromKandidatSeit'))
|
||||||
|
< $this->ReadPropertyInteger('FahrzeugstromErkennungszeit')
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->WriteAttributeInteger('ErkannterMaximalstrom', $kandidat);
|
||||||
|
$this->setzeSichtbareVariable('FahrzeugMaximalstrom', $kandidat);
|
||||||
|
$this->setzeFahrzeugstromKandidatZurueck();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function setzeFahrzeugstromKandidatZurueck(): void
|
||||||
|
{
|
||||||
|
$this->WriteAttributeInteger('FahrzeugstromKandidat', 0);
|
||||||
|
$this->WriteAttributeInteger('FahrzeugstromKandidatSeit', 0);
|
||||||
|
}
|
||||||
|
|
||||||
/** @param list<int> $angebot */
|
/** @param list<int> $angebot */
|
||||||
private function bestimmeWirksameSollleistung(array $angebot): int
|
private function bestimmeWirksameSollleistung(array $angebot): int
|
||||||
{
|
{
|
||||||
@@ -332,6 +393,14 @@ class LadestationStandAlone extends IPSModule implements VerbraucherSchnittstell
|
|||||||
return $sollleistung;
|
return $sollleistung;
|
||||||
}
|
}
|
||||||
$this->setzeZustand('SollwertGueltig', false);
|
$this->setzeZustand('SollwertGueltig', false);
|
||||||
|
$ersatz = 0;
|
||||||
|
foreach ($angebot as $leistungswert) {
|
||||||
|
if ($leistungswert > $sollleistung) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
$ersatz = $leistungswert;
|
||||||
|
}
|
||||||
|
return $ersatz;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$this->ReadAttributeBoolean('ZustandSolarladen') && $angebot !== [0]) {
|
if (!$this->ReadAttributeBoolean('ZustandSolarladen') && $angebot !== [0]) {
|
||||||
@@ -351,6 +420,7 @@ class LadestationStandAlone extends IPSModule implements VerbraucherSchnittstell
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$this->WriteAttributeBoolean('LadeleistungSeitFreigabeErkannt', false);
|
$this->WriteAttributeBoolean('LadeleistungSeitFreigabeErkannt', false);
|
||||||
|
$this->setzeFahrzeugstromKandidatZurueck();
|
||||||
|
|
||||||
$transport = new LadestationTransport(
|
$transport = new LadestationTransport(
|
||||||
fn (array $anfrage): string => $this->fuehreHttpAnfrageAus($anfrage)
|
fn (array $anfrage): string => $this->fuehreHttpAnfrageAus($anfrage)
|
||||||
@@ -586,7 +656,12 @@ class LadestationStandAlone extends IPSModule implements VerbraucherSchnittstell
|
|||||||
throw new InvalidArgumentException($property . ' muss mindestens 0 sein.');
|
throw new InvalidArgumentException($property . ' muss mindestens 0 sein.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
foreach (['Meldeintervall', 'VorgabeTimeout', 'Abfrageintervall'] as $property) {
|
foreach ([
|
||||||
|
'Meldeintervall',
|
||||||
|
'VorgabeTimeout',
|
||||||
|
'Abfrageintervall',
|
||||||
|
'FahrzeugstromErkennungszeit',
|
||||||
|
] as $property) {
|
||||||
if ($this->ReadPropertyInteger($property) <= 0) {
|
if ($this->ReadPropertyInteger($property) <= 0) {
|
||||||
throw new InvalidArgumentException($property . ' muss groesser als 0 sein.');
|
throw new InvalidArgumentException($property . ' muss groesser als 0 sein.');
|
||||||
}
|
}
|
||||||
@@ -642,12 +717,26 @@ class LadestationStandAlone extends IPSModule implements VerbraucherSchnittstell
|
|||||||
$this->registriereVerbraucherDiagnose();
|
$this->registriereVerbraucherDiagnose();
|
||||||
$this->RegisterVariableString('LetzterGeraetebefehl', 'Letzter Geraetebefehl', '', 200);
|
$this->RegisterVariableString('LetzterGeraetebefehl', 'Letzter Geraetebefehl', '', 200);
|
||||||
$this->RegisterVariableString('LeistungsangebotDiagnose', 'Leistungsangebot', '', 210);
|
$this->RegisterVariableString('LeistungsangebotDiagnose', 'Leistungsangebot', '', 210);
|
||||||
|
$this->RegisterVariableInteger(
|
||||||
|
'FahrzeugMaximalstrom',
|
||||||
|
'Erkannter Fahrzeug-Maximalstrom',
|
||||||
|
'',
|
||||||
|
220
|
||||||
|
);
|
||||||
|
$this->SetValue(
|
||||||
|
'FahrzeugMaximalstrom',
|
||||||
|
$this->ReadAttributeInteger('ErkannterMaximalstrom')
|
||||||
|
);
|
||||||
$this->SetValue(
|
$this->SetValue(
|
||||||
'LeistungsangebotDiagnose',
|
'LeistungsangebotDiagnose',
|
||||||
$this->ReadAttributeString('Leistungsangebot')
|
$this->ReadAttributeString('Leistungsangebot')
|
||||||
);
|
);
|
||||||
foreach (self::DIAGNOSE_VARIABLEN as $ident) {
|
foreach (self::DIAGNOSE_VARIABLEN as $ident) {
|
||||||
if (!in_array($ident, ['LetzterGeraetebefehl', 'LeistungsangebotDiagnose'], true)) {
|
if (!in_array($ident, [
|
||||||
|
'LetzterGeraetebefehl',
|
||||||
|
'LeistungsangebotDiagnose',
|
||||||
|
'FahrzeugMaximalstrom',
|
||||||
|
], true)) {
|
||||||
$this->setzeSichtbareVariable($ident, $this->leseZustand($ident));
|
$this->setzeSichtbareVariable($ident, $this->leseZustand($ident));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ final class LadestationAdapter
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return array{Rohstatus: int, Istleistung_W: float}
|
* @return array{Rohstatus: int, Istleistung_W: float, Iststrom_A?: float}
|
||||||
*/
|
*/
|
||||||
public static function dekodiereStatus(int $geraetetyp, string $antwort): array
|
public static function dekodiereStatus(int $geraetetyp, string $antwort): array
|
||||||
{
|
{
|
||||||
@@ -110,12 +110,25 @@ final class LadestationAdapter
|
|||||||
throw new RuntimeException('Die go-e-Antwort enthaelt nicht alle Statusfelder.');
|
throw new RuntimeException('Die go-e-Antwort enthaelt nicht alle Statusfelder.');
|
||||||
}
|
}
|
||||||
|
|
||||||
$faktor = $geraetetyp === self::GO_E_ALT ? 10.0 : 1.0;
|
$leistungsfaktor = $geraetetyp === self::GO_E_ALT ? 10.0 : 1.0;
|
||||||
|
$status = [
|
||||||
return [
|
|
||||||
'Rohstatus' => (int) $daten['car'],
|
'Rohstatus' => (int) $daten['car'],
|
||||||
'Istleistung_W' => (float) $daten['nrg'][11] * $faktor,
|
'Istleistung_W' => (float) $daten['nrg'][11] * $leistungsfaktor,
|
||||||
];
|
];
|
||||||
|
if (isset($daten['nrg'][4], $daten['nrg'][5], $daten['nrg'][6])
|
||||||
|
&& is_numeric($daten['nrg'][4])
|
||||||
|
&& is_numeric($daten['nrg'][5])
|
||||||
|
&& is_numeric($daten['nrg'][6])
|
||||||
|
) {
|
||||||
|
$stromfaktor = $geraetetyp === self::GO_E_ALT ? 0.1 : 1.0;
|
||||||
|
$status['Iststrom_A'] = max(
|
||||||
|
(float) $daten['nrg'][4],
|
||||||
|
(float) $daten['nrg'][5],
|
||||||
|
(float) $daten['nrg'][6]
|
||||||
|
) * $stromfaktor;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $status;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($geraetetyp === self::SMART_ME_PICO) {
|
if ($geraetetyp === self::SMART_ME_PICO) {
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ final class LadestationRegler
|
|||||||
* Ein Ladeende wird erst nach zuvor gemessener Ladeleistung angenommen, damit
|
* Ein Ladeende wird erst nach zuvor gemessener Ladeleistung angenommen, damit
|
||||||
* die Anlaufzeit nach einer neuen Stromvorgabe nicht als volles Fahrzeug gilt.
|
* die Anlaufzeit nach einer neuen Stromvorgabe nicht als volles Fahrzeug gilt.
|
||||||
*
|
*
|
||||||
* @param array{Rohstatus: int, Istleistung_W: float} $rohstatus
|
* @param array{Rohstatus: int, Istleistung_W: float, Iststrom_A?: float} $rohstatus
|
||||||
* @return array<string, int|float|bool>
|
* @return array<string, int|float|bool>
|
||||||
*/
|
*/
|
||||||
public static function normalisiereStatus(
|
public static function normalisiereStatus(
|
||||||
@@ -51,7 +51,9 @@ final class LadestationRegler
|
|||||||
if ($verbunden) {
|
if ($verbunden) {
|
||||||
$phasen = $leistung > 7500.0 ? self::PHASEN_DREI : self::PHASEN_EIN;
|
$phasen = $leistung > 7500.0 ? self::PHASEN_DREI : self::PHASEN_EIN;
|
||||||
$nenner = $phasen === self::PHASEN_EIN ? 230.0 : 1.71 * 400.0;
|
$nenner = $phasen === self::PHASEN_EIN ? 230.0 : 1.71 * 400.0;
|
||||||
$ladestrom = $leistung / $nenner;
|
$ladestrom = isset($rohstatus['Iststrom_A'])
|
||||||
|
? max(0.0, (float) $rohstatus['Iststrom_A'])
|
||||||
|
: $leistung / $nenner;
|
||||||
$ermittelterMaximalstrom = 2.5 + $ladestrom;
|
$ermittelterMaximalstrom = 2.5 + $ladestrom;
|
||||||
$geladen = $warGeladen
|
$geladen = $warGeladen
|
||||||
|| ($warVerbunden
|
|| ($warVerbunden
|
||||||
@@ -80,6 +82,26 @@ final class LadestationRegler
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function ermittleFahrzeugMaximalstrom(
|
||||||
|
int $vorgegebenerStrom,
|
||||||
|
float $gemessenerStrom,
|
||||||
|
int $konfigurierterMaximalstrom
|
||||||
|
): ?int {
|
||||||
|
if ($konfigurierterMaximalstrom < 6 || $konfigurierterMaximalstrom > 32) {
|
||||||
|
throw new InvalidArgumentException('MaximalerLadestrom muss zwischen 6 und 32 A liegen.');
|
||||||
|
}
|
||||||
|
if ($vorgegebenerStrom < 6 || $gemessenerStrom < 5.5) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$kandidat = max(6, min($konfigurierterMaximalstrom, (int) round($gemessenerStrom)));
|
||||||
|
if ($kandidat > $vorgegebenerStrom - 2 || $kandidat >= $konfigurierterMaximalstrom) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $kandidat;
|
||||||
|
}
|
||||||
|
|
||||||
/** @return list<int> */
|
/** @return list<int> */
|
||||||
public static function leistungsangebot(
|
public static function leistungsangebot(
|
||||||
bool $aktiv,
|
bool $aktiv,
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ final class LadestationAdapterTest extends TestCase
|
|||||||
/**
|
/**
|
||||||
* @dataProvider statusFaelle
|
* @dataProvider statusFaelle
|
||||||
* @param array<string, string> $konfiguration
|
* @param array<string, string> $konfiguration
|
||||||
* @param array{Rohstatus: int, Istleistung_W: float} $erwartet
|
* @param array{Rohstatus: int, Istleistung_W: float, Iststrom_A?: float} $erwartet
|
||||||
*/
|
*/
|
||||||
public function testStatusaufrufUndDekodierungFuerAlleGeraete(
|
public function testStatusaufrufUndDekodierungFuerAlleGeraete(
|
||||||
int $geraetetyp,
|
int $geraetetyp,
|
||||||
@@ -47,16 +47,16 @@ final class LadestationAdapterTest extends TestCase
|
|||||||
yield 'go-e alt' => [
|
yield 'go-e alt' => [
|
||||||
LadestationAdapter::GO_E_ALT,
|
LadestationAdapter::GO_E_ALT,
|
||||||
$this->lokaleKonfiguration(),
|
$this->lokaleKonfiguration(),
|
||||||
$this->goEAntwort(2, 1100),
|
$this->goEAntwort(2, 1100, [160, 158, 159]),
|
||||||
'http://192.0.2.10/mqtt?payload=',
|
'http://192.0.2.10/mqtt?payload=',
|
||||||
['Rohstatus' => 2, 'Istleistung_W' => 11000.0],
|
['Rohstatus' => 2, 'Istleistung_W' => 11000.0, 'Iststrom_A' => 16.0],
|
||||||
];
|
];
|
||||||
yield 'go-e Gemini' => [
|
yield 'go-e Gemini' => [
|
||||||
LadestationAdapter::GO_E_GEMINI,
|
LadestationAdapter::GO_E_GEMINI,
|
||||||
$this->lokaleKonfiguration(),
|
$this->lokaleKonfiguration(),
|
||||||
$this->goEAntwort(2, 2300),
|
$this->goEAntwort(2, 2300, [9.8, 0, 0]),
|
||||||
'http://192.0.2.10/api/status',
|
'http://192.0.2.10/api/status',
|
||||||
['Rohstatus' => 2, 'Istleistung_W' => 2300.0],
|
['Rohstatus' => 2, 'Istleistung_W' => 2300.0, 'Iststrom_A' => 9.8],
|
||||||
];
|
];
|
||||||
yield 'Smart-me Pico' => [
|
yield 'Smart-me Pico' => [
|
||||||
LadestationAdapter::SMART_ME_PICO,
|
LadestationAdapter::SMART_ME_PICO,
|
||||||
@@ -161,9 +161,13 @@ final class LadestationAdapterTest extends TestCase
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
private function goEAntwort(int $status, float $leistung): string
|
/** @param list<int|float> $stroeme */
|
||||||
|
private function goEAntwort(int $status, float $leistung, array $stroeme): string
|
||||||
{
|
{
|
||||||
$nrg = array_fill(0, 12, 0);
|
$nrg = array_fill(0, 12, 0);
|
||||||
|
foreach (array_values($stroeme) as $index => $strom) {
|
||||||
|
$nrg[4 + $index] = $strom;
|
||||||
|
}
|
||||||
$nrg[11] = $leistung;
|
$nrg[11] = $leistung;
|
||||||
|
|
||||||
return json_encode(['car' => $status, 'nrg' => $nrg], JSON_THROW_ON_ERROR);
|
return json_encode(['car' => $status, 'nrg' => $nrg], JSON_THROW_ON_ERROR);
|
||||||
|
|||||||
@@ -77,6 +77,24 @@ final class LadestationReglerTest extends TestCase
|
|||||||
self::assertSame(4, $status['Fahrzeugstatus']);
|
self::assertSame(4, $status['Fahrzeugstatus']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testDirekterPhasenstromHatVorrangVorNominalerLeistungsumrechnung(): void
|
||||||
|
{
|
||||||
|
$status = LadestationRegler::normalisiereStatus([
|
||||||
|
'Rohstatus' => 2,
|
||||||
|
'Istleistung_W' => 10263.0,
|
||||||
|
'Iststrom_A' => 15.9,
|
||||||
|
], true);
|
||||||
|
|
||||||
|
self::assertEqualsWithDelta(15.9, $status['Ladestrom_A'], 0.01);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testFahrzeugMaximalstromWirdMitZweiAmpereAbstandErkannt(): void
|
||||||
|
{
|
||||||
|
self::assertSame(16, LadestationRegler::ermittleFahrzeugMaximalstrom(20, 15.9, 20));
|
||||||
|
self::assertNull(LadestationRegler::ermittleFahrzeugMaximalstrom(17, 15.9, 20));
|
||||||
|
self::assertNull(LadestationRegler::ermittleFahrzeugMaximalstrom(20, 0.0, 20));
|
||||||
|
}
|
||||||
|
|
||||||
public function testBetriebsartUndSolarladenBestimmenDasLeistungsangebot(): void
|
public function testBetriebsartUndSolarladenBestimmenDasLeistungsangebot(): void
|
||||||
{
|
{
|
||||||
$solarPV = LadestationRegler::leistungsangebot(true, true, true, false, 1, 16, true, false);
|
$solarPV = LadestationRegler::leistungsangebot(true, true, true, false, 1, 16, true, false);
|
||||||
|
|||||||
@@ -159,6 +159,34 @@ final class ManagerReglerTest extends TestCase
|
|||||||
self::assertSame(0, $ergebnis['Restabweichung_W']);
|
self::assertSame(0, $ergebnis['Restabweichung_W']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testBegrenztesFahrzeugLaedtVorBatterieOhneBatterieentladung(): void
|
||||||
|
{
|
||||||
|
$ergebnis = ManagerRegler::verteile(
|
||||||
|
-3587.0,
|
||||||
|
0.0,
|
||||||
|
[
|
||||||
|
$this->verbraucher(
|
||||||
|
10,
|
||||||
|
0,
|
||||||
|
[0, 4150, 4850, 5550, 6250, 6950, 7600, 8300, 9000, 9700, 10300, 11000],
|
||||||
|
10263
|
||||||
|
),
|
||||||
|
$this->verbraucher(
|
||||||
|
20,
|
||||||
|
1,
|
||||||
|
[['Von_W' => -39000, 'Bis_W' => 39000]],
|
||||||
|
0
|
||||||
|
),
|
||||||
|
],
|
||||||
|
'PrioritaetPV'
|
||||||
|
);
|
||||||
|
|
||||||
|
self::assertSame(13850, $ergebnis['Verteilbudget_W']);
|
||||||
|
self::assertSame([10 => 11000, 20 => 2850], $ergebnis['Sollwerte']);
|
||||||
|
self::assertGreaterThanOrEqual(0, $ergebnis['Sollwerte'][20]);
|
||||||
|
self::assertSame(0, $ergebnis['Restabweichung_W']);
|
||||||
|
}
|
||||||
|
|
||||||
public function testFehlendeIstleistungFaelltAufAngenommenenSollwertZurueck(): void
|
public function testFehlendeIstleistungFaelltAufAngenommenenSollwertZurueck(): void
|
||||||
{
|
{
|
||||||
$verbraucher = $this->verbraucher(10, 0, [3000], null, false);
|
$verbraucher = $this->verbraucher(10, 0, [3000], null, false);
|
||||||
|
|||||||
@@ -6,8 +6,11 @@ use Belevo\EnelixEMS\SymconTest\TestContext;
|
|||||||
|
|
||||||
return static function (TestContext $test): void {
|
return static function (TestContext $test): void {
|
||||||
$testPasswort = hash('sha256', __FILE__);
|
$testPasswort = hash('sha256', __FILE__);
|
||||||
$goEAntwort = static function (int $status, float $leistung): string {
|
$goEAntwort = static function (int $status, float $leistung, array $stroeme = []): string {
|
||||||
$nrg = array_fill(0, 12, 0);
|
$nrg = array_fill(0, 12, 0);
|
||||||
|
foreach (array_values($stroeme) as $index => $strom) {
|
||||||
|
$nrg[4 + $index] = $strom;
|
||||||
|
}
|
||||||
$nrg[11] = $leistung;
|
$nrg[11] = $leistung;
|
||||||
return json_encode(['car' => $status, 'nrg' => $nrg], JSON_THROW_ON_ERROR);
|
return json_encode(['car' => $status, 'nrg' => $nrg], JSON_THROW_ON_ERROR);
|
||||||
};
|
};
|
||||||
@@ -16,14 +19,14 @@ return static function (TestContext $test): void {
|
|||||||
'go-e alt' => [
|
'go-e alt' => [
|
||||||
'Geraetetyp' => 1,
|
'Geraetetyp' => 1,
|
||||||
'Geraeteadresse' => '192.0.2.10',
|
'Geraeteadresse' => '192.0.2.10',
|
||||||
'Testantwort' => $goEAntwort(2, 1100),
|
'Testantwort' => $goEAntwort(2, 1100, [160, 158, 159]),
|
||||||
'Phasenzahl' => 3,
|
'Phasenzahl' => 3,
|
||||||
'BefehlUrlTeil' => 'amp=16',
|
'BefehlUrlTeil' => 'amp=16',
|
||||||
],
|
],
|
||||||
'go-e Gemini' => [
|
'go-e Gemini' => [
|
||||||
'Geraetetyp' => 2,
|
'Geraetetyp' => 2,
|
||||||
'Geraeteadresse' => '192.0.2.11',
|
'Geraeteadresse' => '192.0.2.11',
|
||||||
'Testantwort' => $goEAntwort(2, 2300),
|
'Testantwort' => $goEAntwort(2, 2300, [10, 0, 0]),
|
||||||
'Phasenzahl' => 1,
|
'Phasenzahl' => 1,
|
||||||
'BefehlUrlTeil' => 'amp=16',
|
'BefehlUrlTeil' => 'amp=16',
|
||||||
],
|
],
|
||||||
@@ -77,6 +80,23 @@ return static function (TestContext $test): void {
|
|||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
$stromerkennung = $test->createInstance(
|
||||||
|
'{0D94913C-0F31-4C29-A685-6EB4AE58E55D}',
|
||||||
|
'Ladestation Fahrzeugstromerkennung',
|
||||||
|
[
|
||||||
|
'Geraetetyp' => 2,
|
||||||
|
'Geraeteadresse' => '192.0.2.13',
|
||||||
|
'MaximalerLadestrom' => 20,
|
||||||
|
'FahrzeugstromErkennungszeit' => 1,
|
||||||
|
'Ladefreigabe' => true,
|
||||||
|
'Solarladen' => true,
|
||||||
|
'EinstellungenInVisu' => true,
|
||||||
|
'DiagnosevariablenAnzeigen' => true,
|
||||||
|
'Testmodus' => true,
|
||||||
|
'Testantwort' => $goEAntwort(2, 10263, [15.9, 15.8, 15.7]),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
$managerId = $test->createInstance(
|
$managerId = $test->createInstance(
|
||||||
'{6F771B18-59D4-4C8A-B951-3B2FE9F6A2C4}',
|
'{6F771B18-59D4-4C8A-B951-3B2FE9F6A2C4}',
|
||||||
'Hilfsmanager Ladestation',
|
'Hilfsmanager Ladestation',
|
||||||
@@ -84,7 +104,7 @@ return static function (TestContext $test): void {
|
|||||||
'AutomatischeSuche' => false,
|
'AutomatischeSuche' => false,
|
||||||
'VerbraucherZuordnung' => json_encode(array_map(
|
'VerbraucherZuordnung' => json_encode(array_map(
|
||||||
static fn (int $id): array => ['InstanzID' => $id, 'Aktiv' => true],
|
static fn (int $id): array => ['InstanzID' => $id, 'Aktiv' => true],
|
||||||
array_values($stationen)
|
array_merge(array_values($stationen), [$stromerkennung])
|
||||||
), JSON_THROW_ON_ERROR),
|
), JSON_THROW_ON_ERROR),
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
@@ -161,7 +181,7 @@ return static function (TestContext $test): void {
|
|||||||
GetValue($test->objectByIdent('FahrzeugGeladen', $solarPause))
|
GetValue($test->objectByIdent('FahrzeugGeladen', $solarPause))
|
||||||
);
|
);
|
||||||
|
|
||||||
IPS_SetProperty($solarPause, 'Testantwort', $goEAntwort(2, 2300));
|
IPS_SetProperty($solarPause, 'Testantwort', $goEAntwort(2, 2300, [100, 100, 100]));
|
||||||
IPS_ApplyChanges($solarPause);
|
IPS_ApplyChanges($solarPause);
|
||||||
|
|
||||||
$test->assertSame(
|
$test->assertSame(
|
||||||
@@ -229,6 +249,30 @@ return static function (TestContext $test): void {
|
|||||||
), 'Peak mit Solarladen muss die Ladestation stoppen.');
|
), '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, 13850);
|
||||||
|
IPS_RequestAction($stromerkennung, 'Statuszyklus', false);
|
||||||
|
IPS_Sleep(2100);
|
||||||
|
IPS_RequestAction($stromerkennung, 'Statuszyklus', false);
|
||||||
|
|
||||||
|
$test->assertSame(
|
||||||
|
16,
|
||||||
|
GetValue($test->objectByIdent('FahrzeugMaximalstrom', $stromerkennung))
|
||||||
|
);
|
||||||
|
$test->assertSame(
|
||||||
|
'[0,4150,4850,5550,6250,6950,7600,8300,9000,9700,10300,11000]',
|
||||||
|
(string) GetValue($test->objectByIdent('LeistungsangebotDiagnose', $stromerkennung))
|
||||||
|
);
|
||||||
|
$test->assertTrue(str_contains(
|
||||||
|
(string) GetValue($test->objectByIdent('LetzterGeraetebefehl', $stromerkennung)),
|
||||||
|
'amp=16'
|
||||||
|
));
|
||||||
|
});
|
||||||
|
|
||||||
$test->runCase('Sollwert bleibt waehrend der Ladeanlaufzeit gueltig', static function (
|
$test->runCase('Sollwert bleibt waehrend der Ladeanlaufzeit gueltig', static function (
|
||||||
TestContext $test
|
TestContext $test
|
||||||
) use ($goEAntwort, $stationen, $anforderungSenden): void {
|
) use ($goEAntwort, $stationen, $anforderungSenden): void {
|
||||||
|
|||||||
Reference in New Issue
Block a user