fix(manager): Fahrplanvorgabe linear interpolieren
This commit is contained in:
+4
-23
@@ -2757,29 +2757,10 @@ class Manager extends IPSModule implements ManagerSchnittstelle
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
$jetzt = time();
|
return ManagerRegler::interpolierteFahrplanVorgabe(
|
||||||
$naechsterAbstand = PHP_INT_MAX;
|
$fahrplan['points'],
|
||||||
$vorgabe = null;
|
time()
|
||||||
foreach ($fahrplan['points'] as $punkt) {
|
);
|
||||||
if (!is_array($punkt)
|
|
||||||
|| !is_string($punkt['time'] ?? null)
|
|
||||||
|| (!is_int($punkt['gridTargetW'] ?? null)
|
|
||||||
&& !is_float($punkt['gridTargetW'] ?? null))
|
|
||||||
) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
$zeitpunkt = strtotime($punkt['time']);
|
|
||||||
if ($zeitpunkt === false) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
$abstand = abs($zeitpunkt - $jetzt);
|
|
||||||
if ($abstand < $naechsterAbstand) {
|
|
||||||
$naechsterAbstand = $abstand;
|
|
||||||
$vorgabe = (float) $punkt['gridTargetW'];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $naechsterAbstand <= 1800 ? $vorgabe : null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function synchronisiereAnlagentopologie(): void
|
private function synchronisiereAnlagentopologie(): void
|
||||||
|
|||||||
@@ -19,6 +19,71 @@ final class ManagerRegler
|
|||||||
public const BETRIEBSART_PV = Nachrichtenvertrag::BETRIEBSART_PV;
|
public const BETRIEBSART_PV = Nachrichtenvertrag::BETRIEBSART_PV;
|
||||||
public const BETRIEBSART_PEAK = Nachrichtenvertrag::BETRIEBSART_PEAK;
|
public const BETRIEBSART_PEAK = Nachrichtenvertrag::BETRIEBSART_PEAK;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param list<array<string, mixed>> $punkte
|
||||||
|
*/
|
||||||
|
public static function interpolierteFahrplanVorgabe(
|
||||||
|
array $punkte,
|
||||||
|
int $jetzt,
|
||||||
|
int $maximalerPunktabstandSekunden = 1800
|
||||||
|
): ?float {
|
||||||
|
if ($maximalerPunktabstandSekunden < 0) {
|
||||||
|
throw new InvalidArgumentException('Maximaler Fahrplanpunktabstand darf nicht negativ sein.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$gueltigePunkte = [];
|
||||||
|
foreach ($punkte as $punkt) {
|
||||||
|
if (!is_array($punkt)
|
||||||
|
|| !is_string($punkt['time'] ?? null)
|
||||||
|
|| (!is_int($punkt['gridTargetW'] ?? null)
|
||||||
|
&& !is_float($punkt['gridTargetW'] ?? null))
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$zeitpunkt = strtotime($punkt['time']);
|
||||||
|
$vorgabe = (float) $punkt['gridTargetW'];
|
||||||
|
if ($zeitpunkt === false || !is_finite($vorgabe)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$gueltigePunkte[$zeitpunkt] = $vorgabe;
|
||||||
|
}
|
||||||
|
if ($gueltigePunkte === []) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
ksort($gueltigePunkte, SORT_NUMERIC);
|
||||||
|
|
||||||
|
$vorherigerZeitpunkt = null;
|
||||||
|
$vorherigeVorgabe = null;
|
||||||
|
foreach ($gueltigePunkte as $zeitpunkt => $vorgabe) {
|
||||||
|
if ($zeitpunkt === $jetzt) {
|
||||||
|
return $vorgabe;
|
||||||
|
}
|
||||||
|
if ($zeitpunkt > $jetzt) {
|
||||||
|
if ($vorherigerZeitpunkt === null) {
|
||||||
|
return $zeitpunkt - $jetzt <= $maximalerPunktabstandSekunden
|
||||||
|
? $vorgabe
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
if ($jetzt - $vorherigerZeitpunkt > $maximalerPunktabstandSekunden
|
||||||
|
|| $zeitpunkt - $jetzt > $maximalerPunktabstandSekunden
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$anteil = ($jetzt - $vorherigerZeitpunkt) / ($zeitpunkt - $vorherigerZeitpunkt);
|
||||||
|
|
||||||
|
return $vorherigeVorgabe + (($vorgabe - $vorherigeVorgabe) * $anteil);
|
||||||
|
}
|
||||||
|
$vorherigerZeitpunkt = $zeitpunkt;
|
||||||
|
$vorherigeVorgabe = $vorgabe;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $vorherigerZeitpunkt !== null
|
||||||
|
&& $jetzt - $vorherigerZeitpunkt <= $maximalerPunktabstandSekunden
|
||||||
|
? $vorherigeVorgabe
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param array<int, float|int> $monatsgrenzen Monatsnummer 1 bis 12 als Schluessel
|
* @param array<int, float|int> $monatsgrenzen Monatsnummer 1 bis 12 als Schluessel
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -11,6 +11,59 @@ use PHPUnit\Framework\TestCase;
|
|||||||
|
|
||||||
final class ManagerReglerTest extends TestCase
|
final class ManagerReglerTest extends TestCase
|
||||||
{
|
{
|
||||||
|
public function testFahrplanVorgabeWirdZwischenPunktenLinearInterpoliert(): void
|
||||||
|
{
|
||||||
|
$punkte = [
|
||||||
|
['time' => '2026-09-30T11:35:00+00:00', 'gridTargetW' => 20486.254],
|
||||||
|
['time' => '2026-09-30T11:30:00+00:00', 'gridTargetW' => 15664.283],
|
||||||
|
];
|
||||||
|
|
||||||
|
$vorgabe = ManagerRegler::interpolierteFahrplanVorgabe(
|
||||||
|
$punkte,
|
||||||
|
(int) strtotime('2026-09-30T11:32:30+00:00')
|
||||||
|
);
|
||||||
|
|
||||||
|
self::assertEqualsWithDelta(18075.2685, $vorgabe, 0.0001);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testFahrplanVorgabeVerwendetRandpunktInnerhalbDerToleranz(): void
|
||||||
|
{
|
||||||
|
$punkte = [
|
||||||
|
['time' => '2026-09-30T11:30:00+00:00', 'gridTargetW' => 15664.283],
|
||||||
|
['time' => '2026-09-30T11:35:00+00:00', 'gridTargetW' => 20486.254],
|
||||||
|
];
|
||||||
|
|
||||||
|
self::assertSame(
|
||||||
|
15664.283,
|
||||||
|
ManagerRegler::interpolierteFahrplanVorgabe(
|
||||||
|
$punkte,
|
||||||
|
(int) strtotime('2026-09-30T11:29:44+00:00')
|
||||||
|
)
|
||||||
|
);
|
||||||
|
self::assertSame(
|
||||||
|
20486.254,
|
||||||
|
ManagerRegler::interpolierteFahrplanVorgabe(
|
||||||
|
$punkte,
|
||||||
|
(int) strtotime('2026-09-30T11:35:10+00:00')
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testFahrplanVorgabeVerwirftZuGrosseZeitlicheLuecke(): void
|
||||||
|
{
|
||||||
|
$punkte = [
|
||||||
|
['time' => '2026-09-30T11:00:00+00:00', 'gridTargetW' => 10000],
|
||||||
|
['time' => '2026-09-30T12:00:00+00:00', 'gridTargetW' => 20000],
|
||||||
|
];
|
||||||
|
|
||||||
|
self::assertNull(
|
||||||
|
ManagerRegler::interpolierteFahrplanVorgabe(
|
||||||
|
$punkte,
|
||||||
|
(int) strtotime('2026-09-30T11:31:00+00:00')
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public function testOhnePeakBegrenzungBleibtSolarladenAktiv(): void
|
public function testOhnePeakBegrenzungBleibtSolarladenAktiv(): void
|
||||||
{
|
{
|
||||||
self::assertSame(
|
self::assertSame(
|
||||||
|
|||||||
Reference in New Issue
Block a user