dh_Agent: libs/Nachrichtenvertrag.php anlegen
Tests / test (push) Canceled after 0s

This commit is contained in:
dh
2026-09-10 07:27:22 +00:00
parent 721ec68f5f
commit 74d5cb80fc
+286
View File
@@ -0,0 +1,286 @@
<?php
declare(strict_types=1);
namespace Belevo\EnelixEMS;
use InvalidArgumentException;
final class Nachrichtenvertrag
{
public const VERSION = '3.0';
public const LEISTUNGSQUELLE_NICHT_VORHANDEN = 0;
public const LEISTUNGSQUELLE_BERECHNET = 1;
public const LEISTUNGSQUELLE_GEMESSEN = 2;
/**
* @param array<string, mixed> $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<string, mixed> $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<mixed> $wert
*/
private static function istListe(array $wert): bool
{
return $wert === [] || array_keys($wert) === range(0, count($wert) - 1);
}
/**
* @param array<string, mixed> $daten
* @param list<string> $pflicht
* @param list<string> $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.');
}
}
}