Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d1fc62d43c | ||
|
|
aefe46352f | ||
|
|
6f76e6cd86 |
@@ -17,6 +17,9 @@ 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.
|
||||||
|
- Easee-Ladestationen laden einen fehlenden Initialstatus ueber die Observations-API nach und starten bei noch unbekannter Ausgangsphase konservativ einphasig.
|
||||||
|
- Fehlende oder veraltete Verbraucher blockieren die Verteilung an aktuelle Verbraucher nicht mehr.
|
||||||
|
- Die Stoerungsueberwachung fordert einen fehlenden Geraetezugang innerhalb ihres Retry-Zyklus automatisch neu an.
|
||||||
- Vollstaendige PHP-, Struktur- und Symcon-Modultests fuer alle Module.
|
- Vollstaendige PHP-, Struktur- und Symcon-Modultests fuer alle Module.
|
||||||
|
|
||||||
### Hinweise
|
### Hinweise
|
||||||
|
|||||||
+74
-10
@@ -530,6 +530,25 @@ class EaseeGateway extends IPSModule
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->speichereBeobachtungImCache($seriennummer, $id, $wert);
|
||||||
|
|
||||||
|
$this->SendDataToChildren(json_encode([
|
||||||
|
'DataID' => self::CHILD_EVENT_DATA_ID,
|
||||||
|
'Buffer' => json_encode([
|
||||||
|
'type' => 'Observation',
|
||||||
|
'serialNumber' => $seriennummer,
|
||||||
|
'id' => $id,
|
||||||
|
'value' => $wert,
|
||||||
|
'timestamp' => time(),
|
||||||
|
], JSON_THROW_ON_ERROR),
|
||||||
|
], JSON_THROW_ON_ERROR));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function speichereBeobachtungImCache(
|
||||||
|
string $seriennummer,
|
||||||
|
int $id,
|
||||||
|
$wert
|
||||||
|
): void {
|
||||||
$cache = $this->leseAttributArray('ObservationCache');
|
$cache = $this->leseAttributArray('ObservationCache');
|
||||||
if (!isset($cache[$seriennummer]) || !is_array($cache[$seriennummer])) {
|
if (!isset($cache[$seriennummer]) || !is_array($cache[$seriennummer])) {
|
||||||
$cache[$seriennummer] = [];
|
$cache[$seriennummer] = [];
|
||||||
@@ -545,21 +564,66 @@ class EaseeGateway extends IPSModule
|
|||||||
'ObservationCache',
|
'ObservationCache',
|
||||||
json_encode($cache, JSON_THROW_ON_ERROR)
|
json_encode($cache, JSON_THROW_ON_ERROR)
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
$this->SendDataToChildren(json_encode([
|
private function stelleAktuellenStatusSicher(string $seriennummer): void
|
||||||
'DataID' => self::CHILD_EVENT_DATA_ID,
|
{
|
||||||
'Buffer' => json_encode([
|
$cache = $this->leseAttributArray('ObservationCache');
|
||||||
'type' => 'Observation',
|
$zustand = $cache[$seriennummer] ?? [];
|
||||||
'serialNumber' => $seriennummer,
|
if (
|
||||||
'id' => $id,
|
(is_array($zustand) && array_key_exists('109', $zustand))
|
||||||
'value' => $wert,
|
|| $this->ReadPropertyBoolean('Testmodus')
|
||||||
'timestamp' => time(),
|
|| $this->GetBuffer('SignalRReady') !== '1'
|
||||||
], JSON_THROW_ON_ERROR),
|
) {
|
||||||
], JSON_THROW_ON_ERROR));
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$antwort = $this->autorisierteApiAnfrage(
|
||||||
|
'GET',
|
||||||
|
'/state/' . rawurlencode($seriennummer) . '/observations?ids='
|
||||||
|
. rawurlencode(implode(',', EaseeGatewayProtokoll::BEOBACHTUNGEN)),
|
||||||
|
''
|
||||||
|
);
|
||||||
|
if (!($antwort['success'] ?? false)) {
|
||||||
|
$this->setzeLetztenFehler(
|
||||||
|
'Easee-Status konnte nicht geladen werden: '
|
||||||
|
. (string) ($antwort['error'] ?? 'Unbekannter API-Fehler.')
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$daten = json_decode((string) ($antwort['body'] ?? ''), true);
|
||||||
|
if (!is_array($daten)) {
|
||||||
|
$this->setzeLetztenFehler('Easee-Statusantwort ist ungueltig.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$beobachtungen = EaseeGatewayProtokoll::extrahiereBeobachtungen(
|
||||||
|
$daten,
|
||||||
|
[$seriennummer],
|
||||||
|
$seriennummer
|
||||||
|
);
|
||||||
|
$betriebsstatusGeladen = false;
|
||||||
|
foreach ($beobachtungen as $beobachtung) {
|
||||||
|
$this->speichereBeobachtungImCache(
|
||||||
|
$beobachtung['Seriennummer'],
|
||||||
|
$beobachtung['ID'],
|
||||||
|
$beobachtung['Wert']
|
||||||
|
);
|
||||||
|
$betriebsstatusGeladen = $betriebsstatusGeladen
|
||||||
|
|| $beobachtung['ID'] === 109;
|
||||||
|
}
|
||||||
|
if (!$betriebsstatusGeladen) {
|
||||||
|
$this->setzeLetztenFehler(
|
||||||
|
'Easee-Statusantwort enthaelt keinen Betriebsstatus (109).'
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$this->setzeLetztenFehler('');
|
||||||
}
|
}
|
||||||
|
|
||||||
private function erstelleStatusantwort(string $seriennummer): string
|
private function erstelleStatusantwort(string $seriennummer): string
|
||||||
{
|
{
|
||||||
|
$this->stelleAktuellenStatusSicher($seriennummer);
|
||||||
$cache = $this->leseAttributArray('ObservationCache');
|
$cache = $this->leseAttributArray('ObservationCache');
|
||||||
return json_encode([
|
return json_encode([
|
||||||
'success' => true,
|
'success' => true,
|
||||||
|
|||||||
@@ -419,10 +419,12 @@ class LadestationGateway extends IPSModule implements VerbraucherSchnittstelle
|
|||||||
|
|
||||||
$gatewayVerbunden = $this->ReadAttributeBoolean('GatewayVerbunden');
|
$gatewayVerbunden = $this->ReadAttributeBoolean('GatewayVerbunden');
|
||||||
$phaseGueltig = in_array($status['Phasenzahl'], [1, 3], true);
|
$phaseGueltig = in_array($status['Phasenzahl'], [1, 3], true);
|
||||||
|
$regelPhasenzahl = $phaseGueltig ? $status['Phasenzahl'] : 1;
|
||||||
|
$phaseErforderlich = $status['Fahrzeugstatus'] === 3;
|
||||||
$steuerbar = $gatewayVerbunden
|
$steuerbar = $gatewayVerbunden
|
||||||
&& $status['StatusGueltig']
|
&& $status['StatusGueltig']
|
||||||
&& !$status['Stoerung']
|
&& !$status['Stoerung']
|
||||||
&& (!$status['FahrzeugVerbunden'] || $phaseGueltig);
|
&& (!$phaseErforderlich || $phaseGueltig);
|
||||||
|
|
||||||
$solarladen = $this->ReadAttributeBoolean('ZustandSolarladen');
|
$solarladen = $this->ReadAttributeBoolean('ZustandSolarladen');
|
||||||
$peakbetrieb = $this->ReadAttributeString('Betriebsart')
|
$peakbetrieb = $this->ReadAttributeString('Betriebsart')
|
||||||
@@ -432,7 +434,7 @@ class LadestationGateway extends IPSModule implements VerbraucherSchnittstelle
|
|||||||
$this->ReadAttributeBoolean('ZustandLadefreigabe'),
|
$this->ReadAttributeBoolean('ZustandLadefreigabe'),
|
||||||
$steuerbar && $status['FahrzeugVerbunden'],
|
$steuerbar && $status['FahrzeugVerbunden'],
|
||||||
$status['FahrzeugGeladen'],
|
$status['FahrzeugGeladen'],
|
||||||
$phaseGueltig ? $status['Phasenzahl'] : 1,
|
$regelPhasenzahl,
|
||||||
$status['ApiMaximalstrom_A'],
|
$status['ApiMaximalstrom_A'],
|
||||||
$solarladen,
|
$solarladen,
|
||||||
$peakbetrieb
|
$peakbetrieb
|
||||||
@@ -460,8 +462,8 @@ class LadestationGateway extends IPSModule implements VerbraucherSchnittstelle
|
|||||||
} elseif ($status['Stoerung']) {
|
} elseif ($status['Stoerung']) {
|
||||||
$stoertext = $status['Stoertext'];
|
$stoertext = $status['Stoertext'];
|
||||||
$this->SetStatus(self::STATUS_LADEFEHLER);
|
$this->SetStatus(self::STATUS_LADEFEHLER);
|
||||||
} elseif ($status['FahrzeugVerbunden'] && !$phaseGueltig) {
|
} elseif ($phaseErforderlich && !$phaseGueltig) {
|
||||||
$stoertext = 'Easee meldet keine unterstuetzte aktive Ausgangsphase.';
|
$stoertext = 'Easee meldet beim Laden keine unterstuetzte aktive Ausgangsphase.';
|
||||||
$this->SetStatus(self::STATUS_WARTET_AUF_DATEN);
|
$this->SetStatus(self::STATUS_WARTET_AUF_DATEN);
|
||||||
} else {
|
} else {
|
||||||
$this->SetStatus(self::STATUS_AKTIV);
|
$this->SetStatus(self::STATUS_AKTIV);
|
||||||
@@ -471,7 +473,7 @@ class LadestationGateway extends IPSModule implements VerbraucherSchnittstelle
|
|||||||
if ($gatewayVerbunden && $status['StatusGueltig']) {
|
if ($gatewayVerbunden && $status['StatusGueltig']) {
|
||||||
$this->setzeLadestrom(
|
$this->setzeLadestrom(
|
||||||
$this->bestimmeWirksameSollleistung($angebot),
|
$this->bestimmeWirksameSollleistung($angebot),
|
||||||
$status['Phasenzahl']
|
$regelPhasenzahl
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
$this->WriteAttributeInteger('LetzterGesetzterStrom', -1);
|
$this->WriteAttributeInteger('LetzterGesetzterStrom', -1);
|
||||||
|
|||||||
+17
-20
@@ -689,22 +689,6 @@ class Manager extends IPSModule implements ManagerSchnittstelle
|
|||||||
);
|
);
|
||||||
$this->setzeDiagnosewert('WirksameLastspitzengrenze', $lastspitzengrenze ?? 0.0);
|
$this->setzeDiagnosewert('WirksameLastspitzengrenze', $lastspitzengrenze ?? 0.0);
|
||||||
$this->regleEinspeisung($netzleistung, $stoerungen, $erneuern);
|
$this->regleEinspeisung($netzleistung, $stoerungen, $erneuern);
|
||||||
if (!$synchronisiert) {
|
|
||||||
$this->setzeDiagnosewert('Verteilbudget', 0.0);
|
|
||||||
$this->setzeDiagnosewert('Verbraucherstatus', json_encode([
|
|
||||||
'Betriebsart' => $betriebsart,
|
|
||||||
'Synchronisiert' => false,
|
|
||||||
'Verbraucher' => $verbraucher,
|
|
||||||
'NichtGeregelt' => $nichtGeregelt,
|
|
||||||
], JSON_THROW_ON_ERROR));
|
|
||||||
$this->setzeDiagnosewert(
|
|
||||||
'NichtGeregelteGeraete',
|
|
||||||
$this->nichtGeregelteGeraeteText($nichtGeregelt)
|
|
||||||
);
|
|
||||||
$this->aktualisiereStoerungen($verbraucher, $stoerungen);
|
|
||||||
$this->SetStatus(self::STATUS_AKTIV);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$fahrplanVorgabe = $this->aktuelleNetzfahrplanVorgabe();
|
$fahrplanVorgabe = $this->aktuelleNetzfahrplanVorgabe();
|
||||||
$zielnetzleistung = $betriebsart === ManagerRegler::BETRIEBSART_PEAK
|
$zielnetzleistung = $betriebsart === ManagerRegler::BETRIEBSART_PEAK
|
||||||
@@ -719,14 +703,16 @@ class Manager extends IPSModule implements ManagerSchnittstelle
|
|||||||
$this->setzeDiagnosewert('Verteilbudget', (float) $ergebnis['Verteilbudget_W']);
|
$this->setzeDiagnosewert('Verteilbudget', (float) $ergebnis['Verteilbudget_W']);
|
||||||
$this->setzeDiagnosewert('Verbraucherstatus', json_encode([
|
$this->setzeDiagnosewert('Verbraucherstatus', json_encode([
|
||||||
'Betriebsart' => $betriebsart,
|
'Betriebsart' => $betriebsart,
|
||||||
'Synchronisiert' => true,
|
'Synchronisiert' => $synchronisiert,
|
||||||
'Restabweichung_W' => $ergebnis['Restabweichung_W'],
|
'Restabweichung_W' => $ergebnis['Restabweichung_W'],
|
||||||
'Verbraucher' => $verbraucher,
|
'Verbraucher' => $verbraucher,
|
||||||
'NichtGeregelt' => [],
|
'NichtGeregelt' => $nichtGeregelt,
|
||||||
], JSON_THROW_ON_ERROR));
|
], JSON_THROW_ON_ERROR));
|
||||||
$this->setzeDiagnosewert(
|
$this->setzeDiagnosewert(
|
||||||
'NichtGeregelteGeraete',
|
'NichtGeregelteGeraete',
|
||||||
'Alle aktiven Verbraucher werden geregelt.'
|
$nichtGeregelt === []
|
||||||
|
? 'Alle aktiven Verbraucher werden geregelt.'
|
||||||
|
: $this->nichtGeregelteGeraeteText($nichtGeregelt)
|
||||||
);
|
);
|
||||||
|
|
||||||
$letzteSollwerte = $this->leseJsonAttribut('LetzteSollwerte');
|
$letzteSollwerte = $this->leseJsonAttribut('LetzteSollwerte');
|
||||||
@@ -1450,7 +1436,18 @@ class Manager extends IPSModule implements ManagerSchnittstelle
|
|||||||
|
|
||||||
$token = trim($this->ReadAttributeString('PrognoseInstallationsToken'));
|
$token = trim($this->ReadAttributeString('PrognoseInstallationsToken'));
|
||||||
if ($token === '') {
|
if ($token === '') {
|
||||||
throw new RuntimeException('Der Geraetezugang fuer die Stoerueberwachung fehlt.');
|
$this->aktualisiereLizenzfreigabe(
|
||||||
|
$this->ReadPropertyString('Lizenzcode'),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
$token = trim($this->ReadAttributeString('PrognoseInstallationsToken'));
|
||||||
|
}
|
||||||
|
if ($token === '') {
|
||||||
|
$lizenzfehler = trim($this->ReadAttributeString('LizenzLetzterFehler'));
|
||||||
|
throw new RuntimeException(
|
||||||
|
'Der Geraetezugang fuer die Stoerueberwachung fehlt.'
|
||||||
|
. ($lizenzfehler !== '' ? ' Lizenzpruefung: ' . $lizenzfehler : '')
|
||||||
|
);
|
||||||
}
|
}
|
||||||
$snapshot['capturedAt'] = gmdate('Y-m-d\TH:i:s\Z', $jetzt);
|
$snapshot['capturedAt'] = gmdate('Y-m-d\TH:i:s\Z', $jetzt);
|
||||||
$this->uebertrageStoerungsSnapshot(
|
$this->uebertrageStoerungsSnapshot(
|
||||||
|
|||||||
@@ -43,12 +43,16 @@ Ein Betriebsartwechsel wird synchronisiert:
|
|||||||
3. `null` ist nur eine Ankuendigung und kein Schaltbefehl.
|
3. `null` ist nur eine Ankuendigung und kein Schaltbefehl.
|
||||||
4. Jeder Verbraucher berechnet sein zustandsabhaengiges Leistungsangebot neu
|
4. Jeder Verbraucher berechnet sein zustandsabhaengiges Leistungsangebot neu
|
||||||
und meldet es mit derselben Betriebsart zurueck.
|
und meldet es mit derselben Betriebsart zurueck.
|
||||||
5. Erst wenn alle aktiven Verbraucher synchronisiert sind, verteilt der
|
5. Der Manager verteilt konkrete Sollleistungen an alle bereits
|
||||||
Manager konkrete Sollleistungen.
|
synchronisierten Verbraucher.
|
||||||
|
6. Fehlende, veraltete oder noch nicht umgeschaltete Verbraucher bleiben von
|
||||||
|
der Verteilung ausgeschlossen und werden als Stoerung ausgewiesen.
|
||||||
|
|
||||||
Der Manager verwendet damit nie ein PV-Angebot fuer Peak oder umgekehrt. Eine
|
Der Manager verwendet damit nie ein PV-Angebot fuer Peak oder umgekehrt. Ein
|
||||||
technische Schaltsperre, eine ausstehende Rueckmeldung oder eine Mindestzeit
|
nicht antwortender Verbraucher blockiert die Regelung der uebrigen Anlage
|
||||||
kann das Angebot weiterhin auf die aktuell gehaltene Leistung begrenzen.
|
nicht; sein aktueller Einfluss ist bereits in der Netzleistungsmessung
|
||||||
|
enthalten. Eine technische Schaltsperre oder eine Mindestzeit kann das Angebot
|
||||||
|
weiterhin auf die aktuell gehaltene Leistung begrenzen.
|
||||||
|
|
||||||
| Verbraucher | PV-Angebot | Peak-Angebot |
|
| Verbraucher | PV-Angebot | Peak-Angebot |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
|
|||||||
@@ -78,8 +78,11 @@ zulaessig ist.
|
|||||||
`Sollleistung_W=null`.
|
`Sollleistung_W=null`.
|
||||||
4. Jeder Verbraucher berechnet und meldet seine PowerSteps fuer diese
|
4. Jeder Verbraucher berechnet und meldet seine PowerSteps fuer diese
|
||||||
Betriebsart.
|
Betriebsart.
|
||||||
5. Erst wenn alle aktiven Verbraucher synchronisiert sind, verteilt der Manager
|
5. Der Manager verteilt Sollleistungen an alle bereits synchronisierten
|
||||||
Sollleistungen.
|
Verbraucher.
|
||||||
|
6. Fehlende, veraltete oder noch nicht umgeschaltete Verbraucher werden nicht
|
||||||
|
angesteuert und als Stoerung ausgewiesen; sie blockieren die aktuellen
|
||||||
|
Verbraucher nicht.
|
||||||
|
|
||||||
Damit kann jeder Verbrauchertyp unterschiedliche Angebote fuer PV und Peak
|
Damit kann jeder Verbrauchertyp unterschiedliche Angebote fuer PV und Peak
|
||||||
melden, ohne dass der Manager seine interne Geraetelogik kennen muss.
|
melden, ohne dass der Manager seine interne Geraetelogik kennen muss.
|
||||||
|
|||||||
@@ -45,6 +45,19 @@ final class EaseeGatewayProtokollTest extends TestCase
|
|||||||
self::assertSame([], $beobachtungen);
|
self::assertSame([], $beobachtungen);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testRestBeobachtungenVerwendenAngefragteSeriennummer(): void
|
||||||
|
{
|
||||||
|
$beobachtungen = EaseeGatewayProtokoll::extrahiereBeobachtungen([
|
||||||
|
['id' => 109, 'value' => 1],
|
||||||
|
['observationId' => 110, 'value' => 30],
|
||||||
|
], ['EU2FGKEU'], 'EU2FGKEU');
|
||||||
|
|
||||||
|
self::assertSame([
|
||||||
|
['Seriennummer' => 'EU2FGKEU', 'ID' => 109, 'Wert' => 1],
|
||||||
|
['Seriennummer' => 'EU2FGKEU', 'ID' => 110, 'Wert' => 30],
|
||||||
|
], $beobachtungen);
|
||||||
|
}
|
||||||
|
|
||||||
public function testSeriennummerWirdNormalisiert(): void
|
public function testSeriennummerWirdNormalisiert(): void
|
||||||
{
|
{
|
||||||
self::assertSame(
|
self::assertSame(
|
||||||
|
|||||||
@@ -254,6 +254,25 @@ final class ManagerModulstrukturTest extends TestCase
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testManagerRegeltAktuelleVerbraucherTrotzFehlenderRueckmeldungen(): void
|
||||||
|
{
|
||||||
|
$inhalt = (string) file_get_contents(__DIR__ . '/../Manager/module.php');
|
||||||
|
|
||||||
|
self::assertStringNotContainsString("if (!\$synchronisiert) {", $inhalt);
|
||||||
|
self::assertStringContainsString(
|
||||||
|
"'Synchronisiert' => \$synchronisiert",
|
||||||
|
$inhalt
|
||||||
|
);
|
||||||
|
self::assertStringContainsString(
|
||||||
|
"'NichtGeregelt' => \$nichtGeregelt",
|
||||||
|
$inhalt
|
||||||
|
);
|
||||||
|
self::assertStringContainsString(
|
||||||
|
"\$ergebnis = ManagerRegler::verteile(",
|
||||||
|
$inhalt
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public function testManagerUebertraegtStoerungsvollsnapshotsMitRetry(): void
|
public function testManagerUebertraegtStoerungsvollsnapshotsMitRetry(): void
|
||||||
{
|
{
|
||||||
$inhalt = (string) file_get_contents(__DIR__ . '/../Manager/module.php');
|
$inhalt = (string) file_get_contents(__DIR__ . '/../Manager/module.php');
|
||||||
@@ -280,6 +299,13 @@ final class ManagerModulstrukturTest extends TestCase
|
|||||||
'"name":"StoermeldeAnschluss"',
|
'"name":"StoermeldeAnschluss"',
|
||||||
str_replace(' ', '', (string) file_get_contents(__DIR__ . '/../Manager/form.json'))
|
str_replace(' ', '', (string) file_get_contents(__DIR__ . '/../Manager/form.json'))
|
||||||
);
|
);
|
||||||
|
self::assertStringContainsString(
|
||||||
|
"if (\$token === '') {\n"
|
||||||
|
. " \$this->aktualisiereLizenzfreigabe(\n"
|
||||||
|
. " \$this->ReadPropertyString('Lizenzcode'),\n"
|
||||||
|
. " true",
|
||||||
|
$inhalt
|
||||||
|
);
|
||||||
self::assertGreaterThanOrEqual(
|
self::assertGreaterThanOrEqual(
|
||||||
3,
|
3,
|
||||||
substr_count(
|
substr_count(
|
||||||
|
|||||||
@@ -125,6 +125,34 @@ final class ManagerReglerTest extends TestCase
|
|||||||
self::assertSame(0, $ergebnis['Restabweichung_W']);
|
self::assertSame(0, $ergebnis['Restabweichung_W']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testVeralteterVerbraucherBlockiertUeberschussverteilungNicht(): void
|
||||||
|
{
|
||||||
|
$ergebnis = ManagerRegler::verteile(
|
||||||
|
-2411.0,
|
||||||
|
0.0,
|
||||||
|
[
|
||||||
|
$this->verbraucher(
|
||||||
|
44234,
|
||||||
|
0,
|
||||||
|
[['Von_W' => -39000, 'Bis_W' => 39000]],
|
||||||
|
0
|
||||||
|
),
|
||||||
|
$this->verbraucher(38676, 1, [0, 4600], 0),
|
||||||
|
$this->verbraucher(29743, 1, [0, 6500], 0),
|
||||||
|
$this->verbraucher(43878, 2, [13850], 10151, false),
|
||||||
|
$this->verbraucher(46052, 2, [11000], 0, false),
|
||||||
|
],
|
||||||
|
'PrioritaetPV'
|
||||||
|
);
|
||||||
|
|
||||||
|
self::assertSame(12562, $ergebnis['Verteilbudget_W']);
|
||||||
|
self::assertSame(
|
||||||
|
[44234 => 2411, 29743 => 0, 38676 => 0],
|
||||||
|
$ergebnis['Sollwerte']
|
||||||
|
);
|
||||||
|
self::assertSame(0, $ergebnis['Restabweichung_W']);
|
||||||
|
}
|
||||||
|
|
||||||
public function testFehlendeIstleistungFaelltAufAngenommenenSollwertZurueck(): void
|
public function testFehlendeIstleistungFaelltAufAngenommenenSollwertZurueck(): void
|
||||||
{
|
{
|
||||||
$verbraucher = $this->verbraucher(10, 0, [0, 3000], null, false);
|
$verbraucher = $this->verbraucher(10, 0, [0, 3000], null, false);
|
||||||
|
|||||||
@@ -108,6 +108,36 @@ return static function (TestContext $test): void {
|
|||||||
));
|
));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$test->runCase('Wartezustand startet konservativ ohne aktive Phase', static function (
|
||||||
|
TestContext $test
|
||||||
|
) use ($sendeBeobachtung, $station): void {
|
||||||
|
$sendeBeobachtung('EH123456', 109, 2);
|
||||||
|
$sendeBeobachtung('EH123456', 110, 0);
|
||||||
|
IPS_RequestAction($station, 'Solarladen', false);
|
||||||
|
|
||||||
|
$test->assertSame(
|
||||||
|
0,
|
||||||
|
GetValue($test->objectByIdent('Phasenzahl', $station))
|
||||||
|
);
|
||||||
|
$test->assertInstanceStatus($station);
|
||||||
|
$test->assertSame(
|
||||||
|
'[3850]',
|
||||||
|
(string) GetValue(
|
||||||
|
$test->objectByIdent('LeistungsangebotDiagnose', $station)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
$test->assertTrue(str_contains(
|
||||||
|
(string) GetValue(
|
||||||
|
$test->objectByIdent('LetzterGeraetebefehl', $station)
|
||||||
|
),
|
||||||
|
'"Ampere":16'
|
||||||
|
));
|
||||||
|
|
||||||
|
IPS_RequestAction($station, 'Solarladen', true);
|
||||||
|
$sendeBeobachtung('EH123456', 110, 30);
|
||||||
|
$sendeBeobachtung('EH123456', 109, 3);
|
||||||
|
});
|
||||||
|
|
||||||
$test->runCase('Nur-Solar-Variante laesst Solarladen nicht abschalten', static function (
|
$test->runCase('Nur-Solar-Variante laesst Solarladen nicht abschalten', static function (
|
||||||
TestContext $test
|
TestContext $test
|
||||||
) use ($nurSolar): void {
|
) use ($nurSolar): void {
|
||||||
|
|||||||
@@ -444,4 +444,46 @@ return static function (TestContext $test): void {
|
|||||||
$test->objectByIdent('Sammelstoerung', $managerId)
|
$test->objectByIdent('Sammelstoerung', $managerId)
|
||||||
));
|
));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$test->runCase('Veralteter Verbraucher blockiert aktuelle Verbraucher nicht', static function (
|
||||||
|
TestContext $test
|
||||||
|
) use (
|
||||||
|
$managerId,
|
||||||
|
$netzleistungId,
|
||||||
|
$einStufigId,
|
||||||
|
$warmwasserId,
|
||||||
|
$pufferspeicherId,
|
||||||
|
$melden,
|
||||||
|
$verbraucherstatus
|
||||||
|
): void {
|
||||||
|
IPS_SetProperty($managerId, 'VerbraucherTimeout', 1);
|
||||||
|
IPS_ApplyChanges($managerId);
|
||||||
|
SetValue($netzleistungId, -2400.0);
|
||||||
|
$melden();
|
||||||
|
IPS_RequestAction($managerId, 'Regeln', true);
|
||||||
|
$test->waitUntil(
|
||||||
|
static fn (): bool => ($verbraucherstatus()['Synchronisiert'] ?? false) === true
|
||||||
|
);
|
||||||
|
|
||||||
|
IPS_Sleep(1100);
|
||||||
|
IPS_RequestAction($warmwasserId, 'Melden', true);
|
||||||
|
IPS_RequestAction($pufferspeicherId, 'Melden', true);
|
||||||
|
IPS_RequestAction($managerId, 'Regeln', true);
|
||||||
|
|
||||||
|
$daten = $verbraucherstatus();
|
||||||
|
$test->assertSame(false, $daten['Synchronisiert'] ?? null);
|
||||||
|
$test->assertSame(2, count($daten['Verbraucher'] ?? []));
|
||||||
|
$test->assertSame(1, count($daten['NichtGeregelt'] ?? []));
|
||||||
|
$test->assertSame(
|
||||||
|
$einStufigId,
|
||||||
|
$daten['NichtGeregelt'][0]['InstanzID'] ?? null
|
||||||
|
);
|
||||||
|
$test->assertSame(
|
||||||
|
'Meldung ist veraltet',
|
||||||
|
$daten['NichtGeregelt'][0]['Grund'] ?? null
|
||||||
|
);
|
||||||
|
$test->assertTrue(
|
||||||
|
(float) GetValue($test->objectByIdent('Verteilbudget', $managerId)) > 0.0
|
||||||
|
);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user