diff --git a/libs/Nachrichtenvertrag.php b/libs/Nachrichtenvertrag.php new file mode 100644 index 0000000..3810a2b --- /dev/null +++ b/libs/Nachrichtenvertrag.php @@ -0,0 +1,286 @@ + $daten + */ + public static function pruefeManagerdaten(array $daten): void + { + self::pruefeSchluessel($daten, ['Kopf', 'Sollleistung_W'], 'Managerdaten'); + self::pruefeKopf($daten['Kopf']); + + if (!is_int($daten['Sollleistung_W'])) { + throw new InvalidArgumentException('Sollleistung_W muss eine Ganzzahl sein.'); + } + } + + /** + * @param array $daten + */ + public static function pruefeVerbraucherdaten(array $daten): void + { + self::pruefeSchluessel( + $daten, + [ + 'Kopf', + 'PrioritaetPV', + 'PrioritaetPeak', + 'Leistungswerte_W', + 'AenderungMoeglich', + 'Verfuegbar', + 'Istleistung_W', + 'Leistungsquelle', + 'Zustand', + ], + 'Verbraucherdaten' + ); + + self::pruefeKopf($daten['Kopf']); + self::pruefePrioritaet($daten['PrioritaetPV'], 'PrioritaetPV'); + self::pruefePrioritaet($daten['PrioritaetPeak'], 'PrioritaetPeak'); + self::pruefeLeistungswerte($daten['Leistungswerte_W']); + + if (!is_bool($daten['AenderungMoeglich'])) { + throw new InvalidArgumentException('AenderungMoeglich muss ein Wahrheitswert sein.'); + } + if (!is_bool($daten['Verfuegbar'])) { + throw new InvalidArgumentException('Verfuegbar muss ein Wahrheitswert sein.'); + } + + self::pruefeIstleistung($daten['Istleistung_W'], $daten['Leistungsquelle']); + self::pruefeZustand($daten['Zustand']); + } + + /** + * @param mixed $kopf + */ + private static function pruefeKopf($kopf): void + { + if (!is_array($kopf)) { + throw new InvalidArgumentException('Kopf muss ein Objekt sein.'); + } + + self::pruefeSchluessel( + $kopf, + ['Version', 'AbsenderID', 'EmpfaengerID', 'Zeitpunkt'], + 'Kopf' + ); + + if ($kopf['Version'] !== self::VERSION) { + throw new InvalidArgumentException('Die Vertragsversion wird nicht unterstuetzt.'); + } + if (!is_int($kopf['AbsenderID']) || $kopf['AbsenderID'] <= 0) { + throw new InvalidArgumentException('AbsenderID muss eine positive Ganzzahl sein.'); + } + if (!is_int($kopf['EmpfaengerID']) || $kopf['EmpfaengerID'] <= 0) { + throw new InvalidArgumentException('EmpfaengerID muss eine positive Ganzzahl sein.'); + } + if (!is_int($kopf['Zeitpunkt']) || $kopf['Zeitpunkt'] < 0) { + throw new InvalidArgumentException('Zeitpunkt muss eine nicht negative Unixzeit sein.'); + } + } + + /** + * @param mixed $prioritaet + */ + private static function pruefePrioritaet($prioritaet, string $name): void + { + if (!is_int($prioritaet) || $prioritaet < 0) { + throw new InvalidArgumentException($name . ' muss eine Ganzzahl ab 0 sein.'); + } + } + + /** + * @param mixed $leistungswerte + */ + private static function pruefeLeistungswerte($leistungswerte): void + { + if (!is_array($leistungswerte) || !self::istListe($leistungswerte)) { + throw new InvalidArgumentException('Leistungswerte_W muss eine Liste sein.'); + } + + $letztesMaximum = null; + foreach ($leistungswerte as $index => $eintrag) { + if (is_int($eintrag)) { + $minimum = $eintrag; + $maximum = $eintrag; + } elseif (is_array($eintrag)) { + self::pruefeSchluessel($eintrag, ['Von_W', 'Bis_W'], 'Leistungsbereich'); + if (!is_int($eintrag['Von_W']) || !is_int($eintrag['Bis_W'])) { + throw new InvalidArgumentException('Bereichsgrenzen muessen Ganzzahlen sein.'); + } + if ($eintrag['Von_W'] > $eintrag['Bis_W']) { + throw new InvalidArgumentException('Von_W darf nicht groesser als Bis_W sein.'); + } + $minimum = $eintrag['Von_W']; + $maximum = $eintrag['Bis_W']; + } else { + throw new InvalidArgumentException( + 'Leistungswert an Position ' . $index . ' ist weder Ganzzahl noch Bereich.' + ); + } + + if ($letztesMaximum !== null && $minimum <= $letztesMaximum) { + throw new InvalidArgumentException( + 'Leistungswerte_W muss aufsteigend und ohne Ueberschneidungen sein.' + ); + } + $letztesMaximum = $maximum; + } + } + + /** + * @param mixed $istleistung + * @param mixed $leistungsquelle + */ + private static function pruefeIstleistung($istleistung, $leistungsquelle): void + { + if (!is_int($leistungsquelle) || !in_array( + $leistungsquelle, + [ + self::LEISTUNGSQUELLE_NICHT_VORHANDEN, + self::LEISTUNGSQUELLE_BERECHNET, + self::LEISTUNGSQUELLE_GEMESSEN, + ], + true + )) { + throw new InvalidArgumentException('Leistungsquelle muss 0, 1 oder 2 sein.'); + } + + if ($leistungsquelle === self::LEISTUNGSQUELLE_NICHT_VORHANDEN) { + if ($istleistung !== null) { + throw new InvalidArgumentException('Ohne Leistungsquelle muss Istleistung_W null sein.'); + } + return; + } + + if (!is_int($istleistung) && !is_float($istleistung)) { + throw new InvalidArgumentException('Bei berechneter oder gemessener Leistung ist ein Zahlenwert erforderlich.'); + } + if (is_float($istleistung) && !is_finite($istleistung)) { + throw new InvalidArgumentException('Istleistung_W muss endlich sein.'); + } + } + + /** + * @param mixed $zustand + */ + private static function pruefeZustand($zustand): void + { + if (!is_array($zustand) || !self::istListe($zustand)) { + throw new InvalidArgumentException('Zustand muss eine Liste sein.'); + } + + $kennungen = []; + $sollleistungVorhanden = false; + foreach ($zustand as $eintrag) { + if (!is_array($eintrag)) { + throw new InvalidArgumentException('Jeder Zustandseintrag muss ein Objekt sein.'); + } + + self::pruefeSchluessel( + $eintrag, + ['Kennung', 'Art', 'Wert', 'Einheit'], + 'Zustandseintrag', + ['Text'] + ); + + if (!is_string($eintrag['Kennung']) || trim($eintrag['Kennung']) === '') { + throw new InvalidArgumentException('Kennung muss ein nicht leerer Text sein.'); + } + if (isset($kennungen[$eintrag['Kennung']])) { + throw new InvalidArgumentException('Kennungen im Zustand muessen eindeutig sein.'); + } + $kennungen[$eintrag['Kennung']] = true; + + if (!is_string($eintrag['Art']) || !in_array( + $eintrag['Art'], + ['Istwert', 'Sollwert', 'Status', 'Stoerung'], + true + )) { + throw new InvalidArgumentException('Art des Zustandseintrags ist ungueltig.'); + } + if (!is_string($eintrag['Einheit'])) { + throw new InvalidArgumentException('Einheit muss ein Text sein.'); + } + if (array_key_exists('Text', $eintrag) && !is_string($eintrag['Text'])) { + throw new InvalidArgumentException('Text im Zustandseintrag muss ein Text sein.'); + } + if (!self::istZustandswert($eintrag['Wert'])) { + throw new InvalidArgumentException('Wert im Zustandseintrag hat einen ungueltigen Typ.'); + } + if ($eintrag['Art'] === 'Stoerung' && !is_bool($eintrag['Wert'])) { + throw new InvalidArgumentException('Eine Stoerung muss true oder false melden.'); + } + + if ($eintrag['Kennung'] === 'Sollleistung_W') { + if ($eintrag['Art'] !== 'Sollwert' || $eintrag['Einheit'] !== 'W') { + throw new InvalidArgumentException('Sollleistung_W muss als Sollwert in W gemeldet werden.'); + } + if ($eintrag['Wert'] !== null && !is_int($eintrag['Wert'])) { + throw new InvalidArgumentException('Sollleistung_W im Zustand muss Ganzzahl oder null sein.'); + } + $sollleistungVorhanden = true; + } + } + + if (!$sollleistungVorhanden) { + throw new InvalidArgumentException('Der gemeinsame Zustandseintrag Sollleistung_W fehlt.'); + } + } + + /** + * @param mixed $wert + */ + private static function istZustandswert($wert): bool + { + if ($wert === null || is_string($wert) || is_bool($wert) || is_int($wert)) { + return true; + } + + return is_float($wert) && is_finite($wert); + } + + /** + * @param array $wert + */ + private static function istListe(array $wert): bool + { + return $wert === [] || array_keys($wert) === range(0, count($wert) - 1); + } + + /** + * @param array $daten + * @param list $pflicht + * @param list $optional + */ + private static function pruefeSchluessel( + array $daten, + array $pflicht, + string $bezeichnung, + array $optional = [] + ): void { + $vorhanden = array_keys($daten); + $erlaubt = array_merge($pflicht, $optional); + + $fehlend = array_diff($pflicht, $vorhanden); + $unbekannt = array_diff($vorhanden, $erlaubt); + if ($fehlend !== [] || $unbekannt !== []) { + throw new InvalidArgumentException($bezeichnung . ' enthaelt fehlende oder unbekannte Felder.'); + } + } +} +