feat: Batteriemodul ereignisbasiert integrieren
Tests / test (push) Successful in 45s

This commit is contained in:
dh
2026-09-22 09:27:49 +00:00
parent d03a7fd8fb
commit d7308663da
18 changed files with 2670 additions and 51 deletions
+279
View File
@@ -0,0 +1,279 @@
<?php
declare(strict_types=1);
namespace Belevo\EnelixEMS;
use InvalidArgumentException;
final class BatterieRegler
{
public const TYP_UNKONFIGURIERT = 0;
public const TYP_HERSTELLERUNABHAENGIG = 1;
public const TYP_GOODWE = 2;
public const TYP_SOLAREDGE = 3;
public const TYP_SIGENERGY = 4;
public const STEUERUNG_WECHSELRICHTER = 1;
public const STEUERUNG_ENELIX = 2;
/**
* Erzeugt das bisherige Raster: 250-W-Grundschritte und
* 50-W-Feinschritte im Bereich von -200 W bis +200 W.
*
* @return list<int>
*/
public static function leistungsraster(float $maxLadenW, float $maxEntladenW): array
{
self::pruefeLeistungsgrenzen($maxLadenW, $maxEntladenW);
$maxLaden = (int) floor($maxLadenW / 50.0) * 50;
$maxEntladen = (int) floor($maxEntladenW / 50.0) * 50;
$werte = [0];
for ($leistung = -$maxEntladen; $leistung <= 0; $leistung += 250) {
$werte[] = $leistung;
}
for ($leistung = 0; $leistung <= $maxLaden; $leistung += 250) {
$werte[] = $leistung;
}
for ($leistung = -200; $leistung <= 200; $leistung += 50) {
if ($leistung >= -$maxEntladen && $leistung <= $maxLaden) {
$werte[] = $leistung;
}
}
$werte = array_values(array_unique($werte));
sort($werte, SORT_NUMERIC);
return $werte;
}
public static function hystereseAktiv(
float $ladezustand,
float $reserve,
float $hysterese,
bool $bisherAktiv
): bool {
self::pruefeLadezustandsgrenzen($reserve, $hysterese);
if ($ladezustand > $reserve + $hysterese) {
return false;
}
if ($ladezustand <= $reserve) {
return true;
}
return $bisherAktiv;
}
/**
* Bildet das zustands- und betriebsartabhaengige Angebot aus Enelix 1 ab.
*
* @return list<int>
*/
public static function leistungsangebot(
bool $aktiv,
bool $enelixSteuert,
bool $peakbetrieb,
float $ladezustand,
float $reserve,
float $minimum,
bool $hystereseAktiv,
float $netzleistungW,
float $istleistungW,
float $maxLadenW,
float $maxEntladenW
): array {
self::pruefeLadezustandsgrenzen($reserve, 0.0);
if ($minimum < 0.0 || $minimum > 100.0 || $minimum > $reserve) {
throw new InvalidArgumentException(
'Mindestladezustand muss zwischen 0 und der Reserve liegen.'
);
}
self::pruefeLeistungsgrenzen($maxLadenW, $maxEntladenW);
if (!$aktiv || !$enelixSteuert) {
return [0];
}
$raster = self::leistungsraster($maxLadenW, $maxEntladenW);
if ($peakbetrieb) {
if ($ladezustand > $reserve && !$hystereseAktiv) {
$ziel = $netzleistungW > 0.0
? -min($netzleistungW, $maxEntladenW) + $istleistungW
: $netzleistungW;
return [self::begrenzeLeistung($ziel, $maxLadenW, $maxEntladenW)];
}
if ($ladezustand > $reserve && $hystereseAktiv) {
return self::filtere($raster, static fn (int $wert): bool => $wert <= 0);
}
if ($ladezustand > $minimum) {
return $raster;
}
return self::filtere($raster, static fn (int $wert): bool => $wert >= 0);
}
if ($ladezustand > 99.0) {
return self::filtere($raster, static fn (int $wert): bool => $wert <= 0);
}
if ($ladezustand > $reserve && !$hystereseAktiv) {
return $raster;
}
if ($ladezustand >= $reserve && $hystereseAktiv) {
return self::filtere($raster, static fn (int $wert): bool => $wert >= 0);
}
return [(int) round($maxLadenW)];
}
/**
* Liefert nur die Registerwerte, die der gewaehlte Batterietyp benoetigt.
*
* @return array<string, int|float>
*/
public static function registerwerte(
int $batterietyp,
int $steuerung,
int $sollleistungW
): array {
self::pruefeBatterietyp($batterietyp);
self::pruefeSteuerung($steuerung);
$management = self::managementCode($batterietyp, $steuerung);
if ($steuerung === self::STEUERUNG_WECHSELRICHTER) {
if ($batterietyp === self::TYP_GOODWE) {
return ['Leistung' => 0, 'Management' => $management];
}
return [
'Ladeleistung' => 0,
'Entladeleistung' => 0,
'Management' => $management,
];
}
$betrag = abs($sollleistungW);
if ($batterietyp === self::TYP_HERSTELLERUNABHAENGIG) {
return [
'Ladeleistung' => $sollleistungW >= 0 ? $betrag : 0,
'Entladeleistung' => $sollleistungW < 0 ? $betrag : 0,
'Modus' => $sollleistungW >= 0 ? 0 : 1,
'Management' => $management,
];
}
if ($batterietyp === self::TYP_GOODWE) {
return [
'Leistung' => $betrag,
'Modus' => $sollleistungW >= 0 ? 11 : 12,
'Management' => $management,
];
}
if ($batterietyp === self::TYP_SOLAREDGE) {
return [
'Ladeleistung' => $sollleistungW >= 0 ? $betrag : 0,
'Entladeleistung' => $sollleistungW < 0 ? $betrag : 0,
'Modus' => $sollleistungW >= 0 ? 3 : 4,
'Management' => $management,
];
}
return [
'Ladeleistung' => $sollleistungW >= 0 ? $betrag / 1000.0 : 0.0,
'Entladeleistung' => $sollleistungW < 0 ? $betrag / 1000.0 : 0.0,
'Modus' => $sollleistungW >= 0 ? 3 : 6,
'Management' => $management,
];
}
/** @return list<string> */
public static function erforderlicheRegister(int $batterietyp): array
{
self::pruefeBatterietyp($batterietyp);
if ($batterietyp === self::TYP_GOODWE) {
return ['Management', 'Modus', 'Leistung'];
}
return ['Management', 'Modus', 'Ladeleistung', 'Entladeleistung'];
}
private static function managementCode(int $batterietyp, int $steuerung): int
{
if ($steuerung === self::STEUERUNG_WECHSELRICHTER) {
return in_array(
$batterietyp,
[self::TYP_GOODWE, self::TYP_SOLAREDGE],
true
) ? 1 : 0;
}
if ($batterietyp === self::TYP_SOLAREDGE) {
return 4;
}
if ($batterietyp === self::TYP_GOODWE) {
return 2;
}
return 1;
}
/** @param list<int> $werte
* @return list<int>
*/
private static function filtere(array $werte, callable $filter): array
{
return array_values(array_filter($werte, $filter));
}
private static function begrenzeLeistung(
float $leistungW,
float $maxLadenW,
float $maxEntladenW
): int {
return (int) round(min($maxLadenW, max(-$maxEntladenW, $leistungW)));
}
private static function pruefeLeistungsgrenzen(float $maxLadenW, float $maxEntladenW): void
{
if ($maxLadenW < 0.0 || $maxEntladenW < 0.0) {
throw new InvalidArgumentException('Leistungsgrenzen duerfen nicht negativ sein.');
}
}
private static function pruefeLadezustandsgrenzen(float $reserve, float $hysterese): void
{
if ($reserve < 0.0 || $reserve > 100.0) {
throw new InvalidArgumentException('Reserve muss zwischen 0 und 100 Prozent liegen.');
}
if ($hysterese < 0.0 || $reserve + $hysterese > 100.0) {
throw new InvalidArgumentException(
'Hysterese muss positiv sein und mit der Reserve hoechstens 100 Prozent ergeben.'
);
}
}
private static function pruefeBatterietyp(int $batterietyp): void
{
if (!in_array($batterietyp, [
self::TYP_HERSTELLERUNABHAENGIG,
self::TYP_GOODWE,
self::TYP_SOLAREDGE,
self::TYP_SIGENERGY,
], true)) {
throw new InvalidArgumentException('Batterietyp ist nicht konfiguriert.');
}
}
private static function pruefeSteuerung(int $steuerung): void
{
if (!in_array($steuerung, [
self::STEUERUNG_WECHSELRICHTER,
self::STEUERUNG_ENELIX,
], true)) {
throw new InvalidArgumentException('Batteriesteuerung ist ungueltig.');
}
}
}