287 lines
10 KiB
PHP
287 lines
10 KiB
PHP
<?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.');
|
|
}
|
|
}
|
|
}
|
|
|