*/ public static function dekodiereLeistungsstufen(string $json): array { $werte = json_decode($json, true, 512, JSON_THROW_ON_ERROR); if (!is_array($werte) || !self::istListe($werte)) { throw new InvalidArgumentException('LeistungsStufen muss eine JSON-Liste sein.'); } $ergebnis = []; $leistungen = []; $kontakte = []; foreach ($werte as $index => $eintrag) { if (!is_array($eintrag)) { throw new InvalidArgumentException('Jede Leistungsstufe muss ein Objekt sein.'); } $stufe = $eintrag['Stufe'] ?? $index + 1; $leistung = $eintrag['Leistung'] ?? null; $kontakt = $eintrag['Schaltkontakt_Stufe'] ?? null; if (!is_int($stufe) || $stufe <= 0) { throw new InvalidArgumentException('Stufe muss eine positive Ganzzahl sein.'); } if (!is_int($leistung) || $leistung <= 0) { throw new InvalidArgumentException('Leistung muss eine positive Ganzzahl sein.'); } if (!is_int($kontakt) || $kontakt <= 0) { throw new InvalidArgumentException('Schaltkontakt_Stufe muss eine positive Variablen-ID sein.'); } if (isset($leistungen[$leistung])) { throw new InvalidArgumentException('Leistungen muessen eindeutig sein.'); } if (isset($kontakte[$kontakt])) { throw new InvalidArgumentException('Schaltkontakte duerfen nur einer Stufe zugeordnet sein.'); } $leistungen[$leistung] = true; $kontakte[$kontakt] = true; $ergebnis[] = [ 'Stufe' => $stufe, 'Leistung_W' => $leistung, 'KontaktID' => $kontakt, ]; } usort( $ergebnis, static fn (array $links, array $rechts): int => $links['Leistung_W'] <=> $rechts['Leistung_W'] ); return $ergebnis; } /** * @param list $stufen * @return list */ public static function leistungswerte(array $stufen): array { return array_merge([0], array_column($stufen, 'Leistung_W')); } /** * @param list $leistungswerte * @return list */ public static function leistungsangebot( bool $peakbetrieb, float $temperatur, float $mindesttemperatur, float $maximaltemperatur, float $hysterese, bool $stufeAktiv, array $leistungswerte ): array { if ($hysterese < 0.0 || $leistungswerte === []) { throw new InvalidArgumentException('Hysterese und Leistungswerte sind ungueltig.'); } $mindestladung = $temperatur < $mindesttemperatur || ($temperatur < $mindesttemperatur + $hysterese && $stufeAktiv); if ($peakbetrieb) { return $mindestladung ? $leistungswerte : [0]; } if ($mindestladung) { return [max($leistungswerte)]; } if ($temperatur < $maximaltemperatur - $hysterese || ($temperatur < $maximaltemperatur && $stufeAktiv) ) { return $leistungswerte; } return [0]; } /** * @param list $stufen */ public static function stufeFuerLeistung(array $stufen, int $leistung): int { if ($leistung === 0) { return 0; } foreach ($stufen as $stufe) { if ($stufe['Leistung_W'] === $leistung) { return $stufe['Stufe']; } } throw new InvalidArgumentException('Die Sollleistung entspricht keiner konfigurierten Leistungsstufe.'); } public static function pt1(float $bisher, float $eingang, int $deltaSekunden, int $zeitkonstante): float { if ($deltaSekunden <= 0) { throw new InvalidArgumentException('Das Zeitintervall muss groesser als 0 sein.'); } if ($zeitkonstante <= 0) { throw new InvalidArgumentException('Die Zeitkonstante muss groesser als 0 sein.'); } $alpha = $deltaSekunden / ($zeitkonstante + $deltaSekunden); return $bisher + $alpha * ($eingang - $bisher); } public static function restzeitBisLastwechsel( int $letzterLastwechsel, int $jetzt, int $sperrzeitSekunden ): int { if ($sperrzeitSekunden < 0) { throw new InvalidArgumentException('Die Lastwechsel-Sperrzeit darf nicht negativ sein.'); } if ($letzterLastwechsel <= 0) { return 0; } $verstrichen = max(0, $jetzt - $letzterLastwechsel); return max(0, $sperrzeitSekunden - $verstrichen); } public static function energieKWh(float $leistungW, int $dauerSekunden): float { if ($dauerSekunden < 0) { throw new InvalidArgumentException('Die Dauer darf nicht negativ sein.'); } return $leistungW * $dauerSekunden / 3600000.0; } public static function kannZielErreichen( float $volumenLiter, float $isttemperatur, float $zieltemperatur, int $verbleibendeSekunden, int $maximalleistungW ): bool { if ($zieltemperatur <= $isttemperatur) { return true; } if ($volumenLiter <= 0 || $verbleibendeSekunden < 0 || $maximalleistungW <= 0) { return false; } $erforderlicheWaermeJ = 4186.0 * $volumenLiter * ($zieltemperatur - $isttemperatur); $verfuegbareWaermeJ = $maximalleistungW * $verbleibendeSekunden; return $verfuegbareWaermeJ >= $erforderlicheWaermeJ; } /** * @return array{0: float, 1: float} */ public static function legionellenTemperaturgrenzen( bool $aktiv, int $sekundenSeitAbschluss, int $minimalintervallTage, int $maximalintervallTage, float $mindesttemperatur, float $maximaltemperatur, float $legionellentemperatur ): array { if (!$aktiv) { return [$mindesttemperatur, $maximaltemperatur]; } if ($sekundenSeitAbschluss >= $minimalintervallTage * 86400) { $maximaltemperatur = $legionellentemperatur; } if ($sekundenSeitAbschluss >= $maximalintervallTage * 86400) { $mindesttemperatur = $legionellentemperatur; } return [$mindesttemperatur, $maximaltemperatur]; } /** * @return array{Zeitpunkt: int, Solltemperatur: float}|null */ public static function naechsterZeitplaneintrag(string $json, DateTimeImmutable $jetzt): ?array { if (trim($json) === '') { return null; } $werte = json_decode($json, true, 512, JSON_THROW_ON_ERROR); if (!is_array($werte) || !self::istListe($werte)) { throw new InvalidArgumentException('Zeitplan muss eine JSON-Liste sein.'); } $naechster = null; foreach ($werte as $eintrag) { if (!is_array($eintrag) || !is_string($eintrag['Uhrzeit'] ?? null) || !is_numeric($eintrag['Solltemperatur'] ?? null) || !preg_match('/^(?:[01]\\d|2[0-3]):[0-5]\\d$/', $eintrag['Uhrzeit']) ) { throw new InvalidArgumentException('Zeitplaneintraege benoetigen Uhrzeit HH:MM und Solltemperatur.'); } [$stunde, $minute] = array_map('intval', explode(':', $eintrag['Uhrzeit'])); $zeitpunkt = $jetzt->setTime($stunde, $minute, 0); if ($zeitpunkt <= $jetzt) { $zeitpunkt = $zeitpunkt->modify('+1 day'); } if ($naechster === null || $zeitpunkt < $naechster['Datum']) { $naechster = [ 'Datum' => $zeitpunkt, 'Solltemperatur' => (float) $eintrag['Solltemperatur'], ]; } } if ($naechster === null) { return null; } return [ 'Zeitpunkt' => $naechster['Datum']->getTimestamp(), 'Solltemperatur' => $naechster['Solltemperatur'], ]; } /** @param array $wert */ private static function istListe(array $wert): bool { return $wert === [] || array_keys($wert) === range(0, count($wert) - 1); } }