280 lines
8.6 KiB
PHP
280 lines
8.6 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;
|
|
|
|
/**
|
|
* 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.');
|
|
}
|
|
}
|
|
}
|