This commit is contained in:
@@ -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.');
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user