This commit is contained in:
+11
-4
@@ -82,19 +82,26 @@ Messwert, ist er veraltet oder liegt der Ladezustand ausserhalb von 0 bis
|
||||
ReserveLadezustand entspricht der bisherigen Peakshaving-Reserve.
|
||||
MindestLadezustand schuetzt vor weiterer Entladung.
|
||||
LadezustandHysterese ersetzt die bisher fest im Code hinterlegte
|
||||
2-Prozent-Hysterese.
|
||||
2-Prozent-Hysterese. NachladenMitMaximalleistung verwendet standardmaessig die
|
||||
dynamische maximale Ladeleistung. Wird der Schalter deaktiviert, begrenzt
|
||||
MaximaleNachladeleistung das schutzbedingte Nachladen in W. Der wirksame Wert
|
||||
wird nie hoeher als die dynamische Ladegrenze der Batterie.
|
||||
|
||||
Die bisherigen betriebsartabhaengigen Angebote bleiben erhalten:
|
||||
|
||||
- Im PV-Betrieb steht oberhalb der Reserve der durchgaengige Bereich von der
|
||||
maximalen Entlade- bis zur maximalen Ladeleistung zur Verfuegung.
|
||||
- Unterhalb der Reserve wird die maximale Ladeleistung als fester Wert angeboten.
|
||||
- Unterhalb der Reserve wird die wirksame Nachladeleistung als fester Wert
|
||||
angeboten.
|
||||
- Bei vollem Speicher wird der durchgaengige Bereich von der maximalen
|
||||
Entladeleistung bis 0 W angeboten.
|
||||
- Im Peakbetrieb wird oberhalb der Reserve der aus Netz- und aktueller
|
||||
Batterieleistung berechnete Entladewert angeboten.
|
||||
- Innerhalb der Reserve-Hysterese und unterhalb des Mindestladezustands werden
|
||||
Laden und Entladen wie bisher eingeschraenkt.
|
||||
Laden und Entladen wie bisher eingeschraenkt; die positive Grenze ist dabei
|
||||
hoechstens die konfigurierte Nachladeleistung.
|
||||
- Oberhalb der Reserve ohne aktive Hysterese bleibt der normale Ladebereich
|
||||
unveraendert und wird nicht durch die Nachladegrenze reduziert.
|
||||
|
||||
## Manager und Zeitverhalten
|
||||
|
||||
@@ -133,7 +140,7 @@ verwirft die aktuelle Vorgabe und schreibt eine Leistung von 0.
|
||||
1. Batterietyp und Steuerung auswaehlen.
|
||||
2. Alle fuenf Messwertvariablen zuordnen.
|
||||
3. Die automatisch eingeblendeten Registervariablen zuordnen.
|
||||
4. Reserve, Mindestladezustand und Hysterese pruefen.
|
||||
4. Reserve, Mindestladezustand, Hysterese und Nachladeleistung pruefen.
|
||||
5. Diagnosevariablen und Logging fuer die Erstinbetriebnahme aktivieren.
|
||||
6. Die Batterie im Manager zuordnen.
|
||||
7. Aktiv einschalten.
|
||||
|
||||
@@ -137,6 +137,20 @@
|
||||
"minimum": 0,
|
||||
"maximum": 100,
|
||||
"digits": 1
|
||||
},
|
||||
{
|
||||
"type": "CheckBox",
|
||||
"name": "NachladenMitMaximalleistung",
|
||||
"caption": "Beim Nachladen maximale Ladeleistung verwenden",
|
||||
"onChange": "IPS_RequestAction($id, 'FormNachladenMitMaximalleistung', $NachladenMitMaximalleistung);"
|
||||
},
|
||||
{
|
||||
"type": "NumberSpinner",
|
||||
"name": "MaximaleNachladeleistung",
|
||||
"caption": "Maximale Nachladeleistung",
|
||||
"suffix": " W",
|
||||
"minimum": 1,
|
||||
"visible": false
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
+47
-2
@@ -86,6 +86,8 @@ class Batterie extends IPSModule implements VerbraucherSchnittstelle
|
||||
$this->RegisterPropertyFloat('ReserveLadezustand', 20.0);
|
||||
$this->RegisterPropertyFloat('MindestLadezustand', 10.0);
|
||||
$this->RegisterPropertyFloat('LadezustandHysterese', 2.0);
|
||||
$this->RegisterPropertyBoolean('NachladenMitMaximalleistung', true);
|
||||
$this->RegisterPropertyInteger('MaximaleNachladeleistung', 0);
|
||||
$this->RegisterPropertyInteger('MesswertMaxAlter', 60);
|
||||
$this->RegisterPropertyInteger('Aenderungssperre', 4);
|
||||
$this->RegisterPropertyBoolean('DiagnosevariablenAnzeigen', false);
|
||||
@@ -185,6 +187,11 @@ class Batterie extends IPSModule implements VerbraucherSchnittstelle
|
||||
$formular['elements'],
|
||||
$this->ReadPropertyInteger('Batterietyp')
|
||||
);
|
||||
$this->setzeFormfeldSichtbar(
|
||||
$formular['elements'],
|
||||
'MaximaleNachladeleistung',
|
||||
!$this->ReadPropertyBoolean('NachladenMitMaximalleistung')
|
||||
);
|
||||
|
||||
return json_encode($formular, JSON_THROW_ON_ERROR);
|
||||
}
|
||||
@@ -251,6 +258,14 @@ class Batterie extends IPSModule implements VerbraucherSchnittstelle
|
||||
$this->aktualisiereFormRegisterfelder((int) $wert);
|
||||
return;
|
||||
|
||||
case 'FormNachladenMitMaximalleistung':
|
||||
$this->UpdateFormField(
|
||||
'MaximaleNachladeleistung',
|
||||
'visible',
|
||||
!(bool) $wert
|
||||
);
|
||||
return;
|
||||
|
||||
case 'ManagerdatenEmpfangen':
|
||||
if (!is_string($wert)) {
|
||||
throw new InvalidArgumentException(
|
||||
@@ -382,7 +397,10 @@ class Batterie extends IPSModule implements VerbraucherSchnittstelle
|
||||
$messwerte['Netzleistung'],
|
||||
$messwerte['Istleistung'],
|
||||
$messwerte['MaxLaden'],
|
||||
$messwerte['MaxEntladen']
|
||||
$messwerte['MaxEntladen'],
|
||||
$this->ReadPropertyBoolean('NachladenMitMaximalleistung')
|
||||
? null
|
||||
: (float) $this->ReadPropertyInteger('MaximaleNachladeleistung')
|
||||
)
|
||||
: [0];
|
||||
|
||||
@@ -776,6 +794,14 @@ class Batterie extends IPSModule implements VerbraucherSchnittstelle
|
||||
}
|
||||
}
|
||||
|
||||
if (!$this->ReadPropertyBoolean('NachladenMitMaximalleistung')
|
||||
&& $this->ReadPropertyInteger('MaximaleNachladeleistung') <= 0
|
||||
) {
|
||||
throw new InvalidArgumentException(
|
||||
'MaximaleNachladeleistung muss groesser als 0 W sein.'
|
||||
);
|
||||
}
|
||||
|
||||
BatterieRegler::hystereseAktiv(
|
||||
50.0,
|
||||
$this->ReadPropertyFloat('ReserveLadezustand'),
|
||||
@@ -793,7 +819,10 @@ class Batterie extends IPSModule implements VerbraucherSchnittstelle
|
||||
0.0,
|
||||
0.0,
|
||||
1.0,
|
||||
1.0
|
||||
1.0,
|
||||
$this->ReadPropertyBoolean('NachladenMitMaximalleistung')
|
||||
? null
|
||||
: (float) $this->ReadPropertyInteger('MaximaleNachladeleistung')
|
||||
);
|
||||
|
||||
foreach (self::MESSWERT_PROPERTIES as $property) {
|
||||
@@ -1096,6 +1125,22 @@ class Batterie extends IPSModule implements VerbraucherSchnittstelle
|
||||
$this->setzeZustand('Stoertext', $text);
|
||||
}
|
||||
|
||||
/** @param array<int, mixed> $elemente */
|
||||
private function setzeFormfeldSichtbar(
|
||||
array &$elemente,
|
||||
string $name,
|
||||
bool $sichtbar
|
||||
): void {
|
||||
foreach ($elemente as &$element) {
|
||||
if (($element['name'] ?? '') === $name) {
|
||||
$element['visible'] = $sichtbar;
|
||||
}
|
||||
if (isset($element['items']) && is_array($element['items'])) {
|
||||
$this->setzeFormfeldSichtbar($element['items'], $name, $sichtbar);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @param array<int, mixed> $elemente */
|
||||
private function setzeRegisterfelderSichtbar(
|
||||
array &$elemente,
|
||||
|
||||
@@ -18,6 +18,7 @@ Alle wesentlichen Aenderungen an Enelix EMS werden in dieser Datei dokumentiert.
|
||||
- Ladestationen halten beim Aktivieren und Umschalten von Solarladen den bisherigen Strom bis zur neuen Manager-Vorgabe; die Stand-alone-Phasenprobe verwendet nur noch 6 A.
|
||||
- Fahrzeugstatus ist bei beiden Ladestationsvarianten eine Diagnosevariable, Ist- und Sollleistung gehoeren zu den optionalen Visualisierungseinstellungen.
|
||||
- Stufenlose Leistungsbereiche fuer Batteriespeicher.
|
||||
- Schutzbedingtes Nachladen der Batterie kann wahlweise auf die dynamische Maximalleistung oder eine benutzerdefinierte Watt-Grenze begrenzt werden.
|
||||
- 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.
|
||||
|
||||
@@ -53,6 +53,8 @@ EinstellungenInVisu und LoggingEin stammen aus VerbraucherBasisTrait.
|
||||
| ReserveLadezustand | Float | 20 | Peakshaving-Reserve in Prozent |
|
||||
| MindestLadezustand | Float | 10 | Untere Entladegrenze in Prozent |
|
||||
| LadezustandHysterese | Float | 2 | Hysterese oberhalb der Reserve |
|
||||
| NachladenMitMaximalleistung | Boolean | true | Verwendet fuer schutzbedingtes Nachladen die dynamische maximale Ladeleistung |
|
||||
| MaximaleNachladeleistung | Integer | 0 | Benutzerdefinierte Nachladegrenze in W; bei deaktivierter Maximalleistung muss der Wert groesser als 0 sein |
|
||||
| MesswertMaxAlter | Integer | 60 | Maximales Messwertalter in Sekunden |
|
||||
| Aenderungssperre | Integer | 4 | Sperrzeit nach Sollwertaenderung |
|
||||
| DiagnosevariablenAnzeigen | Boolean | false | Technische Variablen einblenden |
|
||||
@@ -81,9 +83,12 @@ wird.
|
||||
|
||||
PV und Peak verwenden weiterhin die bisherige SoC-Logik. Feste Schutz- oder
|
||||
Peakvorgaben bleiben Einzelwerte; frei regelbare Angebote werden als Bereiche
|
||||
gemeldet. Die Hysterese wird als persistenter Modulzustand gefuehrt. Bei
|
||||
Wechselrichtersteuerung, lokaler Deaktivierung oder ungueltigen Messwerten
|
||||
lautet das Angebot [0].
|
||||
gemeldet. Bei schutzbedingtem Nachladen innerhalb oder unterhalb der Reserve
|
||||
begrenzt MaximaleNachladeleistung die positive Angebotsgrenze, wenn
|
||||
NachladenMitMaximalleistung deaktiviert ist. Der normale Ladebereich oberhalb
|
||||
der Reserve bleibt unveraendert. Die Hysterese wird als persistenter Modulzustand
|
||||
gefuehrt. Bei Wechselrichtersteuerung, lokaler Deaktivierung oder ungueltigen
|
||||
Messwerten lautet das Angebot [0].
|
||||
|
||||
## Variablen
|
||||
|
||||
|
||||
+10
-5
@@ -52,7 +52,8 @@ final class BatterieRegler
|
||||
float $netzleistungW,
|
||||
float $istleistungW,
|
||||
float $maxLadenW,
|
||||
float $maxEntladenW
|
||||
float $maxEntladenW,
|
||||
?float $maxNachladenW = null
|
||||
): array {
|
||||
self::pruefeLadezustandsgrenzen($reserve, 0.0);
|
||||
if ($minimum < 0.0 || $minimum > 100.0 || $minimum > $reserve) {
|
||||
@@ -61,12 +62,16 @@ final class BatterieRegler
|
||||
);
|
||||
}
|
||||
self::pruefeLeistungsgrenzen($maxLadenW, $maxEntladenW);
|
||||
if ($maxNachladenW !== null && $maxNachladenW < 0.0) {
|
||||
throw new InvalidArgumentException('Maximale Nachladeleistung darf nicht negativ sein.');
|
||||
}
|
||||
|
||||
if (!$aktiv || !$enelixSteuert) {
|
||||
return [0];
|
||||
}
|
||||
|
||||
$maxLaden = (int) floor($maxLadenW);
|
||||
$maxNachladen = (int) floor(min($maxLadenW, $maxNachladenW ?? $maxLadenW));
|
||||
$maxEntladen = (int) floor($maxEntladenW);
|
||||
if ($peakbetrieb) {
|
||||
if ($ladezustand > $reserve && !$hystereseAktiv) {
|
||||
@@ -80,10 +85,10 @@ final class BatterieRegler
|
||||
return self::leistungsbereich(-$maxEntladen, 0);
|
||||
}
|
||||
if ($ladezustand > $minimum) {
|
||||
return self::leistungsbereich(-$maxEntladen, $maxLaden);
|
||||
return self::leistungsbereich(-$maxEntladen, $maxNachladen);
|
||||
}
|
||||
|
||||
return self::leistungsbereich(0, $maxLaden);
|
||||
return self::leistungsbereich(0, $maxNachladen);
|
||||
}
|
||||
|
||||
if ($ladezustand > 99.0) {
|
||||
@@ -93,10 +98,10 @@ final class BatterieRegler
|
||||
return self::leistungsbereich(-$maxEntladen, $maxLaden);
|
||||
}
|
||||
if ($ladezustand >= $reserve && $hystereseAktiv) {
|
||||
return self::leistungsbereich(0, $maxLaden);
|
||||
return self::leistungsbereich(0, $maxNachladen);
|
||||
}
|
||||
|
||||
return [$maxLaden];
|
||||
return [$maxNachladen];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -83,6 +83,30 @@ final class BatterieModulstrukturTest extends TestCase
|
||||
}
|
||||
}
|
||||
|
||||
public function testNachladebegrenzungIstImFormularKonfigurierbar(): void
|
||||
{
|
||||
$modul = (string) file_get_contents(self::MODULPFAD . 'module.php');
|
||||
$formular = (string) file_get_contents(self::MODULPFAD . 'form.json');
|
||||
|
||||
self::assertStringContainsString(
|
||||
"RegisterPropertyBoolean('NachladenMitMaximalleistung', true)",
|
||||
$modul
|
||||
);
|
||||
self::assertStringContainsString(
|
||||
"RegisterPropertyInteger('MaximaleNachladeleistung', 0)",
|
||||
$modul
|
||||
);
|
||||
self::assertStringContainsString(
|
||||
"'FormNachladenMitMaximalleistung'",
|
||||
$formular
|
||||
);
|
||||
self::assertStringContainsString(
|
||||
'"name": "MaximaleNachladeleistung"',
|
||||
$formular
|
||||
);
|
||||
self::assertStringContainsString('"suffix": " W"', $formular);
|
||||
}
|
||||
|
||||
public function testLoggingDiagnoseUndAktionsbuttonsSindVorhanden(): void
|
||||
{
|
||||
$modul = (string) file_get_contents(self::MODULPFAD . 'module.php');
|
||||
|
||||
@@ -82,6 +82,38 @@ final class BatterieReglerTest extends TestCase
|
||||
);
|
||||
}
|
||||
|
||||
public function testNachladeleistungBegrenztNurSchutzbedingtesLaden(): void
|
||||
{
|
||||
self::assertSame(
|
||||
[400],
|
||||
BatterieRegler::leistungsangebot(
|
||||
true, true, false, 19.0, 20.0, 10.0, true,
|
||||
0.0, 0.0, 1000.0, 1000.0, 400.0
|
||||
)
|
||||
);
|
||||
self::assertSame(
|
||||
[['Von_W' => 0, 'Bis_W' => 400]],
|
||||
BatterieRegler::leistungsangebot(
|
||||
true, true, true, 10.0, 20.0, 10.0, true,
|
||||
0.0, 0.0, 1000.0, 1000.0, 400.0
|
||||
)
|
||||
);
|
||||
self::assertSame(
|
||||
[['Von_W' => -1000, 'Bis_W' => 400]],
|
||||
BatterieRegler::leistungsangebot(
|
||||
true, true, true, 15.0, 20.0, 10.0, true,
|
||||
0.0, 0.0, 1000.0, 1000.0, 400.0
|
||||
)
|
||||
);
|
||||
self::assertSame(
|
||||
[['Von_W' => -1000, 'Bis_W' => 1000]],
|
||||
BatterieRegler::leistungsangebot(
|
||||
true, true, false, 50.0, 20.0, 10.0, false,
|
||||
0.0, 0.0, 1000.0, 1000.0, 400.0
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public function testWechselrichtersteuerungBietetNurNull(): void
|
||||
{
|
||||
self::assertSame(
|
||||
|
||||
@@ -163,6 +163,32 @@ PHP
|
||||
}
|
||||
);
|
||||
|
||||
$test->runCase(
|
||||
'Benutzerdefinierte Nachladeleistung begrenzt das Schutzangebot',
|
||||
static function (TestContext $test) use (
|
||||
$batterieID,
|
||||
$ladezustandID,
|
||||
$managerdatenSenden
|
||||
): void {
|
||||
$test->configureInstance($batterieID, [
|
||||
'Batteriemanagement' => 2,
|
||||
'NachladenMitMaximalleistung' => false,
|
||||
'MaximaleNachladeleistung' => 400,
|
||||
]);
|
||||
IPS_RequestAction($batterieID, 'Aktiv', true);
|
||||
SetValue($ladezustandID, 19.0);
|
||||
$managerdatenSenden('PV', null);
|
||||
|
||||
$angebotID = $test->objectByIdent('LeistungsangebotDiagnose', $batterieID);
|
||||
$test->waitUntil(
|
||||
static fn (): bool => json_decode(
|
||||
(string) GetValue($angebotID),
|
||||
true
|
||||
) === [400]
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
$test->runCase(
|
||||
'Wechselrichtersteuerung setzt Leistung auf null und Automatikmodus',
|
||||
static function (TestContext $test) use (
|
||||
|
||||
Reference in New Issue
Block a user