*/ 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 */ 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 */ 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 */ 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 $werte * @return list */ 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.'); } } }