Files
Enelix-EMS/libs/BatterieRegler.php
dh 8d83b083bf
Tests / test (push) Successful in 48s
feat(batterie): Nachladeleistung begrenzen
2026-10-01 15:56:23 +00:00

260 lines
8.1 KiB
PHP

<?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;
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 als
* durchgaengige Leistungsbereiche in ganzen Watt ab.
*
* @return list<int|array{Von_W: int, Bis_W: 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,
?float $maxNachladenW = null
): 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 ($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) {
$ziel = $netzleistungW > 0.0
? -min($netzleistungW, $maxEntladenW) + $istleistungW
: $netzleistungW;
return [self::begrenzeLeistung($ziel, $maxLadenW, $maxEntladenW)];
}
if ($ladezustand > $reserve && $hystereseAktiv) {
return self::leistungsbereich(-$maxEntladen, 0);
}
if ($ladezustand > $minimum) {
return self::leistungsbereich(-$maxEntladen, $maxNachladen);
}
return self::leistungsbereich(0, $maxNachladen);
}
if ($ladezustand > 99.0) {
return self::leistungsbereich(-$maxEntladen, 0);
}
if ($ladezustand > $reserve && !$hystereseAktiv) {
return self::leistungsbereich(-$maxEntladen, $maxLaden);
}
if ($ladezustand >= $reserve && $hystereseAktiv) {
return self::leistungsbereich(0, $maxNachladen);
}
return [$maxNachladen];
}
/**
* 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;
}
/** @return list<int|array{Von_W: int, Bis_W: int}> */
private static function leistungsbereich(int $vonW, int $bisW): array
{
if ($vonW === $bisW) {
return [$vonW];
}
return [['Von_W' => $vonW, 'Bis_W' => $bisW]];
}
private static function begrenzeLeistung(
float $leistungW,
float $maxLadenW,
float $maxEntladenW
): int {
$maxLaden = (int) floor($maxLadenW);
$maxEntladen = (int) floor($maxEntladenW);
return (int) round(min($maxLaden, max(-$maxEntladen, $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.');
}
}
}