190 lines
6.0 KiB
PHP
190 lines
6.0 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Belevo\EnelixEMS;
|
|
|
|
use InvalidArgumentException;
|
|
|
|
final class LadestationRegler
|
|
{
|
|
public const PHASEN_UNBEKANNT = 0;
|
|
public const PHASEN_EIN = 1;
|
|
public const PHASEN_DREI = 3;
|
|
|
|
private const LEISTUNG_PRO_AMPERE_EINPHASIG = 230;
|
|
private const LEISTUNG_PRO_AMPERE_DREIPHASIG = 684;
|
|
|
|
/**
|
|
* Die Schwelle und der Zuschlag von 2.5 A stammen aus der Enelix-1-Ladestation.
|
|
* Ein Ladeende wird erst nach zuvor gemessener Ladeleistung angenommen, damit
|
|
* die Anlaufzeit nach einer neuen Stromvorgabe nicht als volles Fahrzeug gilt.
|
|
*
|
|
* @param array{Rohstatus: int, Istleistung_W: float, Iststrom_A?: float, Phasenzahl?: int} $rohstatus
|
|
* @return array<string, int|float|bool>
|
|
*/
|
|
public static function normalisiereStatus(
|
|
array $rohstatus,
|
|
bool $warVerbunden,
|
|
bool $warGeladen = false,
|
|
bool $ladungErwartet = true,
|
|
bool $ladeleistungWurdeErkannt = false,
|
|
int $bekanntePhasenzahl = self::PHASEN_UNBEKANNT
|
|
): array {
|
|
$verbunden = $rohstatus['Rohstatus'] !== 1;
|
|
$leistung = max(0.0, $rohstatus['Istleistung_W']);
|
|
$phasen = self::PHASEN_UNBEKANNT;
|
|
$ladestrom = 0.0;
|
|
$geladen = false;
|
|
|
|
if ($verbunden) {
|
|
if (in_array($bekanntePhasenzahl, [self::PHASEN_EIN, self::PHASEN_DREI], true)) {
|
|
$phasen = $bekanntePhasenzahl;
|
|
} elseif (isset($rohstatus['Phasenzahl'])
|
|
&& in_array($rohstatus['Phasenzahl'], [self::PHASEN_EIN, self::PHASEN_DREI], true)
|
|
) {
|
|
$phasen = (int) $rohstatus['Phasenzahl'];
|
|
} elseif ($leistung > 7500.0) {
|
|
$phasen = self::PHASEN_DREI;
|
|
}
|
|
|
|
$nenner = $phasen === self::PHASEN_DREI
|
|
? self::LEISTUNG_PRO_AMPERE_DREIPHASIG
|
|
: self::LEISTUNG_PRO_AMPERE_EINPHASIG;
|
|
$ladestrom = isset($rohstatus['Iststrom_A'])
|
|
? max(0.0, (float) $rohstatus['Iststrom_A'])
|
|
: ($phasen === self::PHASEN_UNBEKANNT ? 0.0 : $leistung / $nenner);
|
|
$ermittelterMaximalstrom = 2.5 + $ladestrom;
|
|
$geladen = $warGeladen
|
|
|| ($warVerbunden
|
|
&& $ladungErwartet
|
|
&& $ladeleistungWurdeErkannt
|
|
&& $ermittelterMaximalstrom < 6.0);
|
|
}
|
|
|
|
if (!$verbunden) {
|
|
$fahrzeugstatus = 1;
|
|
} elseif ($geladen) {
|
|
$fahrzeugstatus = 4;
|
|
} elseif ($leistung > 0.0) {
|
|
$fahrzeugstatus = 3;
|
|
} else {
|
|
$fahrzeugstatus = 2;
|
|
}
|
|
|
|
return [
|
|
'FahrzeugVerbunden' => $verbunden,
|
|
'FahrzeugGeladen' => $geladen,
|
|
'Fahrzeugstatus' => $fahrzeugstatus,
|
|
'Phasenzahl' => $phasen,
|
|
'Istleistung_W' => $leistung,
|
|
'Ladestrom_A' => $ladestrom,
|
|
];
|
|
}
|
|
|
|
public static function ermittleFahrzeugMaximalstrom(
|
|
int $vorgegebenerStrom,
|
|
float $gemessenerStrom,
|
|
int $konfigurierterMaximalstrom
|
|
): ?int {
|
|
if ($konfigurierterMaximalstrom < 6 || $konfigurierterMaximalstrom > 32) {
|
|
throw new InvalidArgumentException('MaximalerLadestrom muss zwischen 6 und 32 A liegen.');
|
|
}
|
|
if ($vorgegebenerStrom < 6 || $gemessenerStrom < 5.5) {
|
|
return null;
|
|
}
|
|
|
|
$kandidat = max(
|
|
6,
|
|
min($konfigurierterMaximalstrom, (int) floor($gemessenerStrom + 2.5))
|
|
);
|
|
if ($kandidat > $vorgegebenerStrom - 2 || $kandidat >= $konfigurierterMaximalstrom) {
|
|
return null;
|
|
}
|
|
|
|
return $kandidat;
|
|
}
|
|
|
|
/** @return list<int> */
|
|
public static function leistungsangebot(
|
|
bool $aktiv,
|
|
bool $ladefreigabe,
|
|
bool $verbunden,
|
|
bool $geladen,
|
|
int $phasenzahl,
|
|
int $maximalerLadestrom,
|
|
bool $solarladen,
|
|
bool $peakbetrieb
|
|
): array {
|
|
if (!$aktiv || !$ladefreigabe || !$verbunden || $geladen) {
|
|
return [0];
|
|
}
|
|
|
|
if ($phasenzahl === self::PHASEN_UNBEKANNT) {
|
|
return [0];
|
|
}
|
|
|
|
$stufen = self::leistungsstufen($phasenzahl, $maximalerLadestrom);
|
|
if ($stufen === []) {
|
|
return [0];
|
|
}
|
|
if ($solarladen) {
|
|
return $peakbetrieb ? [0] : array_merge([0], $stufen);
|
|
}
|
|
if ($peakbetrieb) {
|
|
return array_merge([0], $stufen);
|
|
}
|
|
|
|
return [$stufen[count($stufen) - 1]];
|
|
}
|
|
|
|
public static function stromFuerLeistung(int $leistung, int $phasenzahl): int
|
|
{
|
|
if ($leistung <= 0) {
|
|
return 0;
|
|
}
|
|
|
|
$stufen = self::stufenFuerPhasen($phasenzahl);
|
|
if ($leistung < $stufen[0]) {
|
|
return 0;
|
|
}
|
|
|
|
$strom = 6;
|
|
foreach ($stufen as $index => $schwelle) {
|
|
if ($leistung < $schwelle) {
|
|
break;
|
|
}
|
|
$strom = 6 + $index;
|
|
}
|
|
|
|
return min(32, $strom);
|
|
}
|
|
|
|
/** @return list<int> */
|
|
private static function leistungsstufen(int $phasenzahl, int $maximalerLadestrom): array
|
|
{
|
|
if ($maximalerLadestrom < 6 || $maximalerLadestrom > 32) {
|
|
throw new InvalidArgumentException('MaximalerLadestrom muss zwischen 6 und 32 A liegen.');
|
|
}
|
|
|
|
return array_slice(self::stufenFuerPhasen($phasenzahl), 0, $maximalerLadestrom - 5);
|
|
}
|
|
|
|
/** @return list<int> */
|
|
private static function stufenFuerPhasen(int $phasenzahl): array
|
|
{
|
|
if ($phasenzahl === self::PHASEN_EIN) {
|
|
$leistungProAmpere = self::LEISTUNG_PRO_AMPERE_EINPHASIG;
|
|
} elseif ($phasenzahl === self::PHASEN_DREI) {
|
|
$leistungProAmpere = self::LEISTUNG_PRO_AMPERE_DREIPHASIG;
|
|
} else {
|
|
throw new InvalidArgumentException('Phasenzahl muss 1 oder 3 sein.');
|
|
}
|
|
|
|
return array_map(
|
|
static fn (int $strom): int => $strom * $leistungProAmpere,
|
|
range(6, 32)
|
|
);
|
|
}
|
|
}
|