221 lines
6.8 KiB
PHP
221 lines
6.8 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Belevo\EnelixUtils;
|
|
|
|
use InvalidArgumentException;
|
|
|
|
final class VirtuelleBatterieRegler
|
|
{
|
|
/**
|
|
* @param list<array{
|
|
* Index:int,
|
|
* Name:string,
|
|
* Kapazitaet_kWh:float,
|
|
* MaxLaden_W:float,
|
|
* MaxEntladen_W:float,
|
|
* Ladezustand_Prozent:float,
|
|
* Istleistung_W:float
|
|
* }> $batterien
|
|
* @return array<string, float>
|
|
*/
|
|
public static function aggregiere(
|
|
array $batterien,
|
|
float $sdlReserveLadenW,
|
|
float $sdlReserveEntladenW,
|
|
float $reservezeitH
|
|
): array {
|
|
if ($sdlReserveLadenW < 0.0
|
|
|| $sdlReserveEntladenW < 0.0
|
|
|| $reservezeitH < 0.0
|
|
) {
|
|
throw new InvalidArgumentException(
|
|
'SDL-Reserven und Reservezeit duerfen nicht negativ sein.'
|
|
);
|
|
}
|
|
|
|
$kapazitaet = 0.0;
|
|
$energie = 0.0;
|
|
$maxLaden = 0.0;
|
|
$maxEntladen = 0.0;
|
|
$istleistung = 0.0;
|
|
|
|
foreach ($batterien as $batterie) {
|
|
$kapazitaetKWh = (float) $batterie['Kapazitaet_kWh'];
|
|
$ladezustand = (float) $batterie['Ladezustand_Prozent'];
|
|
$ladenW = (float) $batterie['MaxLaden_W'];
|
|
$entladenW = (float) $batterie['MaxEntladen_W'];
|
|
|
|
if ($kapazitaetKWh <= 0.0 || $ladenW < 0.0 || $entladenW < 0.0) {
|
|
throw new InvalidArgumentException(
|
|
'Kapazitaet muss positiv und Leistungsgrenzen duerfen nicht negativ sein.'
|
|
);
|
|
}
|
|
if ($ladezustand < 0.0 || $ladezustand > 100.0) {
|
|
throw new InvalidArgumentException(
|
|
'Ladezustand muss zwischen 0 und 100 Prozent liegen.'
|
|
);
|
|
}
|
|
|
|
$kapazitaet += $kapazitaetKWh;
|
|
$energie += $kapazitaetKWh * $ladezustand / 100.0;
|
|
$maxLaden += $ladenW;
|
|
$maxEntladen += $entladenW;
|
|
$istleistung += (float) $batterie['Istleistung_W'];
|
|
}
|
|
|
|
if ($kapazitaet <= 0.0) {
|
|
throw new InvalidArgumentException(
|
|
'Mindestens eine verfuegbare Batterie ist erforderlich.'
|
|
);
|
|
}
|
|
|
|
$sdlLadenW = min($sdlReserveLadenW, $maxLaden);
|
|
$sdlEntladenW = min($sdlReserveEntladenW, $maxEntladen);
|
|
$untereReserve = $sdlEntladenW * $reservezeitH / 1000.0;
|
|
$obereReserve = $sdlLadenW * $reservezeitH / 1000.0;
|
|
|
|
if ($untereReserve + $obereReserve > $kapazitaet) {
|
|
throw new InvalidArgumentException(
|
|
'Die SDL-Energiereserven sind groesser als die Gesamtkapazitaet.'
|
|
);
|
|
}
|
|
|
|
return [
|
|
'Kapazitaet_kWh' => $kapazitaet,
|
|
'Energie_kWh' => $energie,
|
|
'Ladezustand_Prozent' => $energie / $kapazitaet * 100.0,
|
|
'Istleistung_W' => $istleistung,
|
|
'MaxLaden_W' => $maxLaden,
|
|
'MaxEntladen_W' => $maxEntladen,
|
|
'SDLKapazitaet_kWh' => $untereReserve + $obereReserve,
|
|
'EigenverbrauchKapazitaet_kWh' =>
|
|
$kapazitaet - $untereReserve - $obereReserve,
|
|
'SDLMaxLaden_W' => $sdlLadenW,
|
|
'SDLMaxEntladen_W' => $sdlEntladenW,
|
|
'EigenverbrauchMaxLaden_W' => max(0.0, $maxLaden - $sdlLadenW),
|
|
'EigenverbrauchMaxEntladen_W' => max(0.0, $maxEntladen - $sdlEntladenW),
|
|
'UntereSDLReserve_kWh' => $untereReserve,
|
|
'ObereSDLReserve_kWh' => $obereReserve,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* SDL wird zuerst begrenzt. Eigenverbrauch erhaelt die verbleibende
|
|
* physische Leistung derselben Richtung.
|
|
*
|
|
* @param array<string, float> $aggregat
|
|
* @return array{Eigenverbrauch_W:float, SDL_W:float, Gesamt_W:float}
|
|
*/
|
|
public static function kombiniere(
|
|
float $eigenverbrauchW,
|
|
float $sdlW,
|
|
array $aggregat
|
|
): array {
|
|
$sdl = self::begrenze(
|
|
$sdlW,
|
|
(float) $aggregat['SDLMaxLaden_W'],
|
|
(float) $aggregat['SDLMaxEntladen_W']
|
|
);
|
|
$ev = self::begrenze(
|
|
$eigenverbrauchW,
|
|
(float) $aggregat['EigenverbrauchMaxLaden_W'],
|
|
(float) $aggregat['EigenverbrauchMaxEntladen_W']
|
|
);
|
|
|
|
if ($sdl > 0.0 && $ev > 0.0) {
|
|
$ev = min($ev, max(0.0, (float) $aggregat['MaxLaden_W'] - $sdl));
|
|
} elseif ($sdl < 0.0 && $ev < 0.0) {
|
|
$ev = max($ev, -max(
|
|
0.0,
|
|
(float) $aggregat['MaxEntladen_W'] - abs($sdl)
|
|
));
|
|
}
|
|
|
|
$gesamt = self::begrenze(
|
|
$sdl + $ev,
|
|
(float) $aggregat['MaxLaden_W'],
|
|
(float) $aggregat['MaxEntladen_W']
|
|
);
|
|
|
|
return [
|
|
'Eigenverbrauch_W' => $ev,
|
|
'SDL_W' => $sdl,
|
|
'Gesamt_W' => $gesamt,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @param list<array{
|
|
* Index:int,
|
|
* Name:string,
|
|
* Kapazitaet_kWh:float,
|
|
* MaxLaden_W:float,
|
|
* MaxEntladen_W:float,
|
|
* Ladezustand_Prozent:float,
|
|
* Istleistung_W:float
|
|
* }> $batterien
|
|
* @return array<int, float>
|
|
*/
|
|
public static function verteile(float $gesamtW, array $batterien): array
|
|
{
|
|
$ergebnis = [];
|
|
foreach ($batterien as $batterie) {
|
|
$ergebnis[(int) $batterie['Index']] = 0.0;
|
|
}
|
|
if (abs($gesamtW) < 0.5) {
|
|
return $ergebnis;
|
|
}
|
|
|
|
$laden = $gesamtW > 0.0;
|
|
usort(
|
|
$batterien,
|
|
static function (array $a, array $b) use ($laden): int {
|
|
$vergleich = (float) $a['Ladezustand_Prozent']
|
|
<=> (float) $b['Ladezustand_Prozent'];
|
|
return $laden ? $vergleich : -$vergleich;
|
|
}
|
|
);
|
|
|
|
$rest = abs($gesamtW);
|
|
foreach ($batterien as $batterie) {
|
|
if ($rest < 0.5) {
|
|
break;
|
|
}
|
|
$grenze = $laden
|
|
? (float) $batterie['MaxLaden_W']
|
|
: (float) $batterie['MaxEntladen_W'];
|
|
$anteil = min($rest, max(0.0, $grenze));
|
|
$ergebnis[(int) $batterie['Index']] = $laden ? $anteil : -$anteil;
|
|
$rest -= $anteil;
|
|
}
|
|
|
|
return $ergebnis;
|
|
}
|
|
|
|
/**
|
|
* Die SDL-Leistung wird bilanziell zuerst bedient. Die Differenz zur
|
|
* gemessenen Gesamtleistung wird dem Eigenverbrauch zugeordnet.
|
|
*
|
|
* @return array{Eigenverbrauch_W:float, SDL_W:float}
|
|
*/
|
|
public static function ordneIstleistungZu(
|
|
float $gesamtIstW,
|
|
float $sdlAuftragW
|
|
): array {
|
|
return [
|
|
'Eigenverbrauch_W' => $gesamtIstW - $sdlAuftragW,
|
|
'SDL_W' => $sdlAuftragW,
|
|
];
|
|
}
|
|
|
|
private static function begrenze(
|
|
float $leistungW,
|
|
float $maxLadenW,
|
|
float $maxEntladenW
|
|
): float {
|
|
return min($maxLadenW, max(-$maxEntladenW, $leistungW));
|
|
}
|
|
}
|