Files
Enelix-Utils/VirtuelleBatterie/libs/VirtuelleBatterieRegler.php
T
2026-09-27 12:28:32 +00:00

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));
}
}