1079 lines
42 KiB
PHP
1079 lines
42 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
require_once __DIR__ . '/../libs/ManagerSchnittstelle.php';
|
|
require_once __DIR__ . '/../libs/Nachrichtenvertrag.php';
|
|
require_once __DIR__ . '/../libs/ManagerRegler.php';
|
|
require_once __DIR__ . '/../libs/Lizenzpruefung.php';
|
|
|
|
use Belevo\EnelixEMS\Lizenzpruefung;
|
|
use Belevo\EnelixEMS\ManagerRegler;
|
|
use Belevo\EnelixEMS\ManagerSchnittstelle;
|
|
use Belevo\EnelixEMS\Nachrichtenvertrag;
|
|
|
|
class Manager extends IPSModule implements ManagerSchnittstelle
|
|
{
|
|
private const STATUS_AKTIV = 102;
|
|
private const STATUS_MESSWERT_UNGUELTIG = 201;
|
|
private const STATUS_KONFIGURATION_UNGUELTIG = 202;
|
|
private const STATUS_LIZENZ_UNGUELTIG = 203;
|
|
private const LIZENZ_ENDPOINT = 'https://license.enelix.ch/api/v1/licenses/activate';
|
|
private const LIZENZ_RETRY_SEKUNDEN = 3600;
|
|
private const VM_UPDATE = 10603;
|
|
private const VERBRAUCHER_MODUL_IDS = [
|
|
'{B7C54AF4-AD7D-4FE4-B75D-203693906251}',
|
|
'{15879A4E-D0C2-4495-83DE-46E1E462591E}',
|
|
'{0D94913C-0F31-4C29-A685-6EB4AE58E55D}',
|
|
'{C92D5EEF-9632-47A5-9659-4B02BF40FBE9}',
|
|
];
|
|
private const MONATSNAMEN = [
|
|
1 => 'Januar', 'Februar', 'Maerz', 'April', 'Mai', 'Juni',
|
|
'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember',
|
|
];
|
|
private const DIAGNOSE_VARIABLEN = [
|
|
'NetzleistungGueltig',
|
|
'WirksameLastspitzengrenze',
|
|
'Verteilbudget',
|
|
'VerbraucherAnzahl',
|
|
'VerbraucherVerfuegbar',
|
|
'Verbraucherstatus',
|
|
'Monatsgrenzen',
|
|
'Prognosestatus',
|
|
'SDLStatus',
|
|
'Lizenzstatus',
|
|
'Stoerueberwachungsstatus',
|
|
'Sammelstoerung',
|
|
'Stoertext',
|
|
];
|
|
|
|
public function Create(): void
|
|
{
|
|
parent::Create();
|
|
|
|
$this->RegisterPropertyInteger('Rolle', 0);
|
|
$this->RegisterPropertyInteger('NetzleistungVariableID', 0);
|
|
$this->RegisterPropertyFloat('Netzleistungsfaktor', 1.0);
|
|
$this->RegisterPropertyInteger('MesswertMaxAlter', 60);
|
|
$this->RegisterPropertyString('VerbraucherZuordnung', '[]');
|
|
$this->RegisterPropertyString('AutomatischeVerbraucherZuordnung', '[]');
|
|
$this->RegisterPropertyBoolean('AutomatischeSuche', true);
|
|
$this->RegisterPropertyInteger('SuchbereichID', 0);
|
|
$this->RegisterPropertyInteger('KeepAlive', 60);
|
|
$this->RegisterPropertyInteger('VerbraucherTimeout', 60);
|
|
$this->RegisterPropertyInteger('Lastspitzenmodus', 0);
|
|
$this->RegisterPropertyFloat('Lastspitzengrenze', 0.0);
|
|
$this->RegisterPropertyString('Monatsgrenzen', $this->standardMonatsgrenzen());
|
|
$this->RegisterPropertyFloat('SollwertSolarladen', 0.0);
|
|
$this->RegisterPropertyFloat('Umschaltdifferenz', 5.0);
|
|
$this->RegisterPropertyString('PrognoseAnschluss', '{}');
|
|
$this->RegisterPropertyString('SDLAnschluss', '{}');
|
|
$this->RegisterPropertyString('Lizenzcode', '');
|
|
// Verdeckte Altproperty fuer bestehende Instanzkonfigurationen.
|
|
$this->RegisterPropertyString('LizenzAnschluss', '{}');
|
|
$this->RegisterPropertyString('StoermeldeAnschluss', '{}');
|
|
$this->RegisterPropertyBoolean('DiagnosevariablenAnzeigen', false);
|
|
$this->RegisterPropertyBoolean('LoggingEin', false);
|
|
|
|
$this->RegisterVariableBoolean('Aktiv', 'Aktiv', '~Switch', 10);
|
|
$this->EnableAction('Aktiv');
|
|
$this->RegisterVariableString('Betriebsart', 'Betriebsart', '', 20);
|
|
$this->RegisterVariableFloat('Netzleistung', 'Netzleistung', '', 30);
|
|
|
|
$this->RegisterAttributeString('VerbraucherCache', '{}');
|
|
$this->RegisterAttributeString('LetzteSollwerte', '{}');
|
|
$this->RegisterAttributeInteger('RegistrierteNetzleistungVariableID', 0);
|
|
$this->RegisterAttributeString('LizenzInstallationID', '');
|
|
$this->RegisterAttributeString('LizenzLease', '{}');
|
|
$this->RegisterAttributeString('LizenzcodeHash', '');
|
|
$this->RegisterAttributeInteger('LizenzNaechsterVersuch', 0);
|
|
$this->RegisterAttributeString('LizenzLetzterFehler', '');
|
|
if ($this->ReadAttributeString('LizenzInstallationID') === '') {
|
|
$this->WriteAttributeString('LizenzInstallationID', $this->erzeugeUuidV4());
|
|
}
|
|
|
|
// Die alten Timer bleiben deaktiviert registriert, damit bestehende Instanzen sauber migrieren.
|
|
$this->RegisterTimer('Regelzyklus', 0, '');
|
|
$this->RegisterTimer('VorgabenErneuern', 0, '');
|
|
$this->RegisterTimer(
|
|
'KeepAlive',
|
|
0,
|
|
"IPS_RequestAction(\$_IPS['TARGET'], 'Regeln', true);"
|
|
);
|
|
}
|
|
|
|
public function ApplyChanges(): void
|
|
{
|
|
parent::ApplyChanges();
|
|
|
|
$this->aktualisiereVariablen();
|
|
|
|
try {
|
|
$this->pruefeKonfiguration();
|
|
} catch (Throwable $fehler) {
|
|
$this->SetStatus(self::STATUS_KONFIGURATION_UNGUELTIG);
|
|
$this->SetTimerInterval('Regelzyklus', 0);
|
|
$this->SetTimerInterval('VorgabenErneuern', 0);
|
|
$this->SetTimerInterval('KeepAlive', 0);
|
|
$this->protokolliere('Konfiguration', $fehler->getMessage());
|
|
return;
|
|
}
|
|
|
|
$this->registriereNetzleistungsmeldung();
|
|
$this->SetTimerInterval('Regelzyklus', 0);
|
|
$this->SetTimerInterval('VorgabenErneuern', 0);
|
|
$this->SetTimerInterval('KeepAlive', $this->ReadPropertyInteger('KeepAlive') * 1000);
|
|
$this->setzeDiagnosewert('Monatsgrenzen', $this->ReadPropertyString('Monatsgrenzen'));
|
|
$this->aktualisiereAnschlussstatus();
|
|
if (!$this->aktualisiereLizenzfreigabe($this->ReadPropertyString('Lizenzcode'), false)) {
|
|
$this->SetValue('Betriebsart', ManagerRegler::BETRIEBSART_INAKTIV);
|
|
$this->SetStatus(self::STATUS_LIZENZ_UNGUELTIG);
|
|
return;
|
|
}
|
|
$this->SetStatus(self::STATUS_AKTIV);
|
|
}
|
|
|
|
public function GetConfigurationForm(): string
|
|
{
|
|
$formular = json_decode(
|
|
(string) file_get_contents(__DIR__ . '/form.json'),
|
|
true,
|
|
512,
|
|
JSON_THROW_ON_ERROR
|
|
);
|
|
$automatisch = $this->ReadPropertyBoolean('AutomatischeSuche');
|
|
$lastspitzenmodus = $this->ReadPropertyInteger('Lastspitzenmodus');
|
|
$monatsgrenzenSichtbar = $lastspitzenmodus === 2
|
|
&& $this->GetBuffer('MonatsgrenzenSichtbar') === '1';
|
|
|
|
$this->setzeFormularParameter(
|
|
$formular['elements'],
|
|
'LizenzInstallationID',
|
|
'caption',
|
|
'Installations-ID: ' . $this->ReadAttributeString('LizenzInstallationID')
|
|
);
|
|
$this->setzeFormularParameter(
|
|
$formular['elements'],
|
|
'LizenzInformation',
|
|
'caption',
|
|
$this->lizenzStatustext()
|
|
);
|
|
$this->setzeFormularParameter($formular['elements'], 'VerbraucherZuordnung', 'visible', !$automatisch);
|
|
$this->setzeFormularParameter(
|
|
$formular['elements'],
|
|
'AutomatischeVerbraucherZuordnung',
|
|
'visible',
|
|
$automatisch
|
|
);
|
|
$this->setzeFormularParameter($formular['elements'], 'SuchbereichID', 'visible', $automatisch);
|
|
$this->setzeFormularParameter(
|
|
$formular['elements'],
|
|
'VerbraucherAktualisieren',
|
|
'visible',
|
|
$automatisch
|
|
);
|
|
$this->setzeFormularParameter(
|
|
$formular['elements'],
|
|
'AutomatischeVerbraucherZuordnung',
|
|
'values',
|
|
$this->automatischeVerbraucherFuerFormular()
|
|
);
|
|
$this->setzeFormularParameter(
|
|
$formular['elements'],
|
|
'Lastspitzengrenze',
|
|
'visible',
|
|
$lastspitzenmodus === 1
|
|
);
|
|
$this->setzeFormularParameter(
|
|
$formular['elements'],
|
|
'MonatsgrenzenUmschalten',
|
|
'visible',
|
|
$lastspitzenmodus === 2
|
|
);
|
|
$this->setzeFormularParameter(
|
|
$formular['elements'],
|
|
'MonatsgrenzenUmschalten',
|
|
'caption',
|
|
$monatsgrenzenSichtbar ? 'Monatsgrenzen ausblenden' : 'Monatsgrenzen anzeigen'
|
|
);
|
|
$this->setzeFormularParameter(
|
|
$formular['elements'],
|
|
'Monatsgrenzen',
|
|
'visible',
|
|
$monatsgrenzenSichtbar
|
|
);
|
|
$this->setzeFormularParameter(
|
|
$formular['elements'],
|
|
'Monatsgrenzen',
|
|
'values',
|
|
$this->monatsgrenzenFuerFormular()
|
|
);
|
|
|
|
return json_encode($formular, JSON_THROW_ON_ERROR);
|
|
}
|
|
|
|
public function MessageSink($zeitstempel, $senderID, $nachricht, $daten): void
|
|
{
|
|
if ((int) $senderID === $this->ReadPropertyInteger('NetzleistungVariableID')
|
|
&& (int) $nachricht === self::VM_UPDATE
|
|
) {
|
|
$this->regeln(false);
|
|
}
|
|
}
|
|
|
|
public function RequestAction($ident, $wert): void
|
|
{
|
|
switch ($ident) {
|
|
case 'Aktiv':
|
|
$this->SetValue('Aktiv', (bool) $wert);
|
|
$this->regeln(false);
|
|
return;
|
|
|
|
case 'Regeln':
|
|
$this->regeln((bool) $wert);
|
|
return;
|
|
|
|
case 'FormLizenzPruefen':
|
|
if (!is_string($wert)) {
|
|
throw new InvalidArgumentException('Der Lizenzcode muss als Text uebergeben werden.');
|
|
}
|
|
$this->aktualisiereLizenzfreigabe($wert, true);
|
|
$this->UpdateFormField('LizenzInformation', 'caption', $this->lizenzStatustext($wert));
|
|
return;
|
|
|
|
case 'FormAutomatischeSuche':
|
|
$automatisch = (bool) $wert;
|
|
$this->UpdateFormField('VerbraucherZuordnung', 'visible', !$automatisch);
|
|
$this->UpdateFormField('AutomatischeVerbraucherZuordnung', 'visible', $automatisch);
|
|
$this->UpdateFormField('SuchbereichID', 'visible', $automatisch);
|
|
$this->UpdateFormField('VerbraucherAktualisieren', 'visible', $automatisch);
|
|
return;
|
|
|
|
case 'FormVerbraucherAktualisieren':
|
|
if (!is_string($wert)) {
|
|
throw new InvalidArgumentException('Suchparameter muessen als JSON uebergeben werden.');
|
|
}
|
|
$suchparameter = json_decode($wert, true, 512, JSON_THROW_ON_ERROR);
|
|
if (!is_array($suchparameter) || !is_array($suchparameter['Zuordnung'] ?? null)) {
|
|
throw new InvalidArgumentException('Suchparameter sind ungueltig.');
|
|
}
|
|
$suchbereichID = $suchparameter['SuchbereichID'] ?? 0;
|
|
if (!is_int($suchbereichID)) {
|
|
throw new InvalidArgumentException('SuchbereichID muss eine Ganzzahl sein.');
|
|
}
|
|
$werte = $this->automatischeVerbraucherFuerFormular(
|
|
$suchparameter['Zuordnung'],
|
|
$suchbereichID
|
|
);
|
|
$this->UpdateFormField(
|
|
'AutomatischeVerbraucherZuordnung',
|
|
'values',
|
|
json_encode($werte, JSON_THROW_ON_ERROR)
|
|
);
|
|
return;
|
|
|
|
case 'FormLastspitzenmodus':
|
|
$modus = (int) $wert;
|
|
$this->UpdateFormField('Lastspitzengrenze', 'visible', $modus === 1);
|
|
$this->UpdateFormField('MonatsgrenzenUmschalten', 'visible', $modus === 2);
|
|
if ($modus !== 2) {
|
|
$this->SetBuffer('MonatsgrenzenSichtbar', '0');
|
|
$this->UpdateFormField('Monatsgrenzen', 'visible', false);
|
|
$this->UpdateFormField(
|
|
'MonatsgrenzenUmschalten',
|
|
'caption',
|
|
'Monatsgrenzen anzeigen'
|
|
);
|
|
}
|
|
return;
|
|
|
|
case 'FormMonatsgrenzenUmschalten':
|
|
$sichtbar = $this->GetBuffer('MonatsgrenzenSichtbar') !== '1';
|
|
$this->SetBuffer('MonatsgrenzenSichtbar', $sichtbar ? '1' : '0');
|
|
$this->UpdateFormField('Monatsgrenzen', 'visible', $sichtbar);
|
|
$this->UpdateFormField(
|
|
'MonatsgrenzenUmschalten',
|
|
'caption',
|
|
$sichtbar ? 'Monatsgrenzen ausblenden' : 'Monatsgrenzen anzeigen'
|
|
);
|
|
return;
|
|
|
|
case 'VerbraucherdatenEmpfangen':
|
|
if (!is_string($wert)) {
|
|
throw new InvalidArgumentException('Verbraucherdaten muessen als JSON uebergeben werden.');
|
|
}
|
|
$daten = json_decode($wert, true, 512, JSON_THROW_ON_ERROR);
|
|
if (!is_array($daten)) {
|
|
throw new InvalidArgumentException('Verbraucherdaten muessen ein JSON-Objekt sein.');
|
|
}
|
|
$this->VerbraucherdatenEmpfangen($daten);
|
|
return;
|
|
|
|
}
|
|
|
|
throw new InvalidArgumentException('Unbekannte Aktion: ' . $ident);
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $daten
|
|
*/
|
|
public function VerbraucherdatenEmpfangen(array $daten): void
|
|
{
|
|
Nachrichtenvertrag::pruefeVerbraucherdaten($daten);
|
|
if ($daten['Kopf']['EmpfaengerID'] !== $this->InstanceID) {
|
|
throw new InvalidArgumentException('Verbraucherdaten sind an eine andere Instanz adressiert.');
|
|
}
|
|
|
|
$absenderID = $daten['Kopf']['AbsenderID'];
|
|
if (!in_array($absenderID, $this->aktiveVerbraucherIDs(), true)) {
|
|
throw new InvalidArgumentException('Der Absender ist diesem Manager nicht aktiv zugeordnet.');
|
|
}
|
|
|
|
$cache = $this->leseJsonAttribut('VerbraucherCache');
|
|
$cache[(string) $absenderID] = [
|
|
'EmpfangenAm' => time(),
|
|
'Daten' => $daten,
|
|
];
|
|
$this->WriteAttributeString('VerbraucherCache', json_encode($cache, JSON_THROW_ON_ERROR));
|
|
$this->regeln(false);
|
|
}
|
|
|
|
private function regeln(bool $erneuern): void
|
|
{
|
|
if (!$this->aktualisiereLizenzfreigabe($this->ReadPropertyString('Lizenzcode'), false)) {
|
|
$this->SetValue('Betriebsart', ManagerRegler::BETRIEBSART_INAKTIV);
|
|
$this->setzeDiagnosewert('Verteilbudget', 0.0);
|
|
$this->SetStatus(self::STATUS_LIZENZ_UNGUELTIG);
|
|
return;
|
|
}
|
|
|
|
[$netzleistung, $netzleistungGueltig] = $this->leseNetzleistung();
|
|
$this->SetValue('Netzleistung', $netzleistung);
|
|
$this->setzeDiagnosewert('NetzleistungGueltig', $netzleistungGueltig);
|
|
|
|
$aktiv = (bool) $this->GetValue('Aktiv');
|
|
if (!$aktiv || !$netzleistungGueltig) {
|
|
$this->SetValue('Betriebsart', ManagerRegler::BETRIEBSART_INAKTIV);
|
|
$this->setzeDiagnosewert('Verteilbudget', 0.0);
|
|
$this->SetStatus($aktiv ? self::STATUS_MESSWERT_UNGUELTIG : self::STATUS_AKTIV);
|
|
return;
|
|
}
|
|
|
|
$lastspitzengrenze = ManagerRegler::wirksameLastspitzengrenze(
|
|
$this->ReadPropertyInteger('Lastspitzenmodus'),
|
|
$this->ReadPropertyFloat('Lastspitzengrenze'),
|
|
$this->dekodiereMonatsgrenzen($this->ReadPropertyString('Monatsgrenzen')),
|
|
(int) date('n')
|
|
);
|
|
$bisherigeBetriebsart = (string) $this->GetValue('Betriebsart');
|
|
$betriebsart = ManagerRegler::bestimmeBetriebsart(
|
|
true,
|
|
true,
|
|
$netzleistung,
|
|
$bisherigeBetriebsart,
|
|
$lastspitzengrenze !== null,
|
|
$this->ReadPropertyFloat('SollwertSolarladen'),
|
|
$lastspitzengrenze ?? 0.0,
|
|
$this->ReadPropertyFloat('Umschaltdifferenz')
|
|
);
|
|
$this->SetValue('Betriebsart', $betriebsart);
|
|
if ($betriebsart !== $bisherigeBetriebsart) {
|
|
$this->WriteAttributeString('LetzteSollwerte', '{}');
|
|
}
|
|
|
|
$stoerungen = [];
|
|
$synchronisiert = true;
|
|
$verbraucher = $this->aktuelleVerbraucher($stoerungen, $betriebsart, $synchronisiert);
|
|
$this->setzeDiagnosewert('WirksameLastspitzengrenze', $lastspitzengrenze ?? 0.0);
|
|
if (!$synchronisiert) {
|
|
$this->setzeDiagnosewert('Verteilbudget', 0.0);
|
|
$this->setzeDiagnosewert('Verbraucherstatus', json_encode([
|
|
'Betriebsart' => $betriebsart,
|
|
'Synchronisiert' => false,
|
|
'Verbraucher' => $verbraucher,
|
|
], JSON_THROW_ON_ERROR));
|
|
$this->aktualisiereStoerungen($verbraucher, $stoerungen);
|
|
$this->SetStatus(self::STATUS_AKTIV);
|
|
return;
|
|
}
|
|
|
|
$zielnetzleistung = $betriebsart === ManagerRegler::BETRIEBSART_PEAK
|
|
? (float) $lastspitzengrenze
|
|
: $this->ReadPropertyFloat('SollwertSolarladen');
|
|
$ergebnis = ManagerRegler::verteile(
|
|
$netzleistung,
|
|
$zielnetzleistung,
|
|
$verbraucher,
|
|
$betriebsart === ManagerRegler::BETRIEBSART_PEAK ? 'PrioritaetPeak' : 'PrioritaetPV'
|
|
);
|
|
$this->setzeDiagnosewert('Verteilbudget', (float) $ergebnis['Verteilbudget_W']);
|
|
$this->setzeDiagnosewert('Verbraucherstatus', json_encode([
|
|
'Betriebsart' => $betriebsart,
|
|
'Synchronisiert' => true,
|
|
'Restabweichung_W' => $ergebnis['Restabweichung_W'],
|
|
'Verbraucher' => $verbraucher,
|
|
], JSON_THROW_ON_ERROR));
|
|
|
|
$letzteSollwerte = $this->leseJsonAttribut('LetzteSollwerte');
|
|
foreach ($ergebnis['Sollwerte'] as $instanzID => $sollleistung) {
|
|
if (!$erneuern && ($letzteSollwerte[(string) $instanzID] ?? null) === $sollleistung) {
|
|
continue;
|
|
}
|
|
try {
|
|
$this->sendeManagerdaten((int) $instanzID, $betriebsart, $sollleistung);
|
|
$letzteSollwerte[(string) $instanzID] = $sollleistung;
|
|
} catch (Throwable $fehler) {
|
|
$stoerungen[] = 'Sollwert an Instanz ' . $instanzID . ': ' . $fehler->getMessage();
|
|
}
|
|
}
|
|
$this->WriteAttributeString('LetzteSollwerte', json_encode($letzteSollwerte, JSON_THROW_ON_ERROR));
|
|
|
|
$this->aktualisiereStoerungen($verbraucher, $stoerungen);
|
|
$this->SetStatus(self::STATUS_AKTIV);
|
|
}
|
|
|
|
/** @return array{0: float, 1: bool} */
|
|
private function leseNetzleistung(): array
|
|
{
|
|
$variablenID = $this->ReadPropertyInteger('NetzleistungVariableID');
|
|
if ($variablenID <= 0 || !IPS_VariableExists($variablenID)) {
|
|
return [0.0, false];
|
|
}
|
|
|
|
$variable = IPS_GetVariable($variablenID);
|
|
if (time() - (int) $variable['VariableUpdated'] > $this->ReadPropertyInteger('MesswertMaxAlter')) {
|
|
return [0.0, false];
|
|
}
|
|
$wert = GetValue($variablenID);
|
|
if (!is_int($wert) && !is_float($wert)) {
|
|
return [0.0, false];
|
|
}
|
|
|
|
return [(float) $wert * $this->ReadPropertyFloat('Netzleistungsfaktor'), true];
|
|
}
|
|
|
|
/**
|
|
* @param list<string> $stoerungen
|
|
* @return list<array{InstanzID: int, Daten: array<string, mixed>}>
|
|
*/
|
|
private function aktuelleVerbraucher(
|
|
array &$stoerungen,
|
|
string $betriebsart,
|
|
bool &$synchronisiert
|
|
): array {
|
|
$ids = $this->aktiveVerbraucherIDs();
|
|
$this->setzeDiagnosewert('VerbraucherAnzahl', count($ids));
|
|
$cache = $this->leseJsonAttribut('VerbraucherCache');
|
|
$timeout = $this->ReadPropertyInteger('VerbraucherTimeout');
|
|
$aktuell = [];
|
|
|
|
foreach ($ids as $instanzID) {
|
|
$eintrag = $cache[(string) $instanzID] ?? null;
|
|
$grund = '';
|
|
if (!is_array($eintrag) || !isset($eintrag['EmpfangenAm'], $eintrag['Daten'])) {
|
|
$grund = 'Noch keine Meldung';
|
|
} elseif (time() - (int) $eintrag['EmpfangenAm'] > $timeout) {
|
|
$grund = 'Meldung ist veraltet';
|
|
} elseif (($eintrag['Daten']['Betriebsart'] ?? null) !== $betriebsart) {
|
|
$grund = 'Leistungsangebot gehoert nicht zur Betriebsart ' . $betriebsart;
|
|
}
|
|
|
|
if ($grund !== '') {
|
|
$synchronisiert = false;
|
|
$stoerungen[] = $grund . ' von Instanz ' . $instanzID . '.';
|
|
try {
|
|
$this->sendeManagerdaten($instanzID, $betriebsart, null);
|
|
} catch (Throwable $fehler) {
|
|
$stoerungen[] = 'Betriebsart an Instanz ' . $instanzID . ': ' . $fehler->getMessage();
|
|
}
|
|
continue;
|
|
}
|
|
$aktuell[] = ['InstanzID' => $instanzID, 'Daten' => $eintrag['Daten']];
|
|
}
|
|
|
|
$verfuegbar = array_filter($aktuell, static fn (array $eintrag): bool => $eintrag['Daten']['Verfuegbar']);
|
|
$this->setzeDiagnosewert('VerbraucherVerfuegbar', count($verfuegbar));
|
|
|
|
return $aktuell;
|
|
}
|
|
|
|
/** @return list<int> */
|
|
private function aktiveVerbraucherIDs(): array
|
|
{
|
|
if (!$this->ReadPropertyBoolean('AutomatischeSuche')) {
|
|
return $this->leseAktiveVerbraucherIDs('VerbraucherZuordnung');
|
|
}
|
|
|
|
$gespeichert = json_decode(
|
|
$this->ReadPropertyString('AutomatischeVerbraucherZuordnung'),
|
|
true,
|
|
512,
|
|
JSON_THROW_ON_ERROR
|
|
);
|
|
if ($gespeichert === []) {
|
|
// Bestehende Installationen behalten ihre Auswahl, bis die neue Liste gespeichert wurde.
|
|
return $this->leseAktiveVerbraucherIDs('VerbraucherZuordnung');
|
|
}
|
|
|
|
return $this->leseAktiveVerbraucherIDs('AutomatischeVerbraucherZuordnung');
|
|
}
|
|
|
|
/** @return list<int> */
|
|
private function leseAktiveVerbraucherIDs(string $property): array
|
|
{
|
|
$zuordnung = json_decode($this->ReadPropertyString($property), true, 512, JSON_THROW_ON_ERROR);
|
|
if (!is_array($zuordnung)) {
|
|
throw new InvalidArgumentException($property . ' muss eine JSON-Liste sein.');
|
|
}
|
|
|
|
return $this->aktiveVerbraucherIDsAusZuordnung($zuordnung);
|
|
}
|
|
|
|
/**
|
|
* @param list<array<string, mixed>> $zuordnung
|
|
* @return list<int>
|
|
*/
|
|
private function aktiveVerbraucherIDsAusZuordnung(array $zuordnung): array
|
|
{
|
|
$ids = [];
|
|
foreach ($zuordnung as $eintrag) {
|
|
if (!is_array($eintrag)) {
|
|
throw new InvalidArgumentException('Jede Verbraucherzuordnung muss ein Objekt sein.');
|
|
}
|
|
$instanzID = $eintrag['InstanzID'] ?? $eintrag['Verbraucher'] ?? null;
|
|
if (!is_int($instanzID) || $instanzID <= 0 || !is_bool($eintrag['Aktiv'] ?? null)) {
|
|
throw new InvalidArgumentException('Verbraucherzuordnung benoetigt InstanzID und Aktiv.');
|
|
}
|
|
if ($eintrag['Aktiv']) {
|
|
$ids[] = $instanzID;
|
|
}
|
|
}
|
|
|
|
return array_values(array_unique($ids));
|
|
}
|
|
|
|
/**
|
|
* @param list<array<string, mixed>>|null $aktuelleZuordnung
|
|
* @return list<array{Name: string, InstanzID: int, Aktiv: bool}>
|
|
*/
|
|
private function automatischeVerbraucherFuerFormular(
|
|
?array $aktuelleZuordnung = null,
|
|
?int $suchbereichID = null
|
|
): array {
|
|
if ($aktuelleZuordnung === null) {
|
|
$gespeichert = json_decode(
|
|
$this->ReadPropertyString('AutomatischeVerbraucherZuordnung'),
|
|
true,
|
|
512,
|
|
JSON_THROW_ON_ERROR
|
|
);
|
|
$ausgewaehlt = $gespeichert === []
|
|
? $this->leseAktiveVerbraucherIDs('VerbraucherZuordnung')
|
|
: $this->aktiveVerbraucherIDsAusZuordnung($gespeichert);
|
|
} else {
|
|
$ausgewaehlt = $this->aktiveVerbraucherIDsAusZuordnung($aktuelleZuordnung);
|
|
}
|
|
|
|
$suchbereichID ??= $this->ReadPropertyInteger('SuchbereichID');
|
|
$werte = [];
|
|
foreach (self::VERBRAUCHER_MODUL_IDS as $modulID) {
|
|
foreach (IPS_GetInstanceListByModuleID($modulID) as $instanzID) {
|
|
if (!$this->istImSuchbereich($instanzID, $suchbereichID)) {
|
|
continue;
|
|
}
|
|
$werte[] = [
|
|
'Name' => IPS_GetName($instanzID),
|
|
'InstanzID' => $instanzID,
|
|
'Aktiv' => in_array($instanzID, $ausgewaehlt, true),
|
|
];
|
|
}
|
|
}
|
|
|
|
usort($werte, static function (array $links, array $rechts): int {
|
|
$nameVergleich = strcasecmp($links['Name'], $rechts['Name']);
|
|
|
|
return $nameVergleich !== 0
|
|
? $nameVergleich
|
|
: $links['InstanzID'] <=> $rechts['InstanzID'];
|
|
});
|
|
|
|
return $werte;
|
|
}
|
|
|
|
private function istImSuchbereich(int $objektID, int $suchbereichID): bool
|
|
{
|
|
if ($suchbereichID <= 0) {
|
|
return true;
|
|
}
|
|
|
|
while ($objektID > 0) {
|
|
if ($objektID === $suchbereichID) {
|
|
return true;
|
|
}
|
|
$objektID = IPS_GetParent($objektID);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/** @return list<array{Monat: string, Grenze_W: float, MonatIndex: int}> */
|
|
private function monatsgrenzenFuerFormular(): array
|
|
{
|
|
$grenzen = $this->dekodiereMonatsgrenzen($this->ReadPropertyString('Monatsgrenzen'));
|
|
$werte = [];
|
|
foreach (self::MONATSNAMEN as $monat => $name) {
|
|
$werte[] = [
|
|
'Monat' => $name,
|
|
'Grenze_W' => $grenzen[$monat],
|
|
'MonatIndex' => $monat,
|
|
];
|
|
}
|
|
|
|
return $werte;
|
|
}
|
|
|
|
/** @param list<array<string, mixed>> $elemente */
|
|
private function setzeFormularParameter(
|
|
array &$elemente,
|
|
string $name,
|
|
string $parameter,
|
|
$wert
|
|
): bool {
|
|
foreach ($elemente as &$element) {
|
|
if (($element['name'] ?? null) === $name) {
|
|
$element[$parameter] = $wert;
|
|
|
|
return true;
|
|
}
|
|
if (isset($element['items'])
|
|
&& is_array($element['items'])
|
|
&& $this->setzeFormularParameter($element['items'], $name, $parameter, $wert)
|
|
) {
|
|
return true;
|
|
}
|
|
}
|
|
unset($element);
|
|
|
|
return false;
|
|
}
|
|
|
|
private function sendeManagerdaten(
|
|
int $instanzID,
|
|
string $betriebsart,
|
|
?int $sollleistung
|
|
): void {
|
|
if (!IPS_InstanceExists($instanzID)) {
|
|
throw new RuntimeException('Instanz existiert nicht.');
|
|
}
|
|
$paket = [
|
|
'Kopf' => [
|
|
'Version' => Nachrichtenvertrag::VERSION,
|
|
'AbsenderID' => $this->InstanceID,
|
|
'EmpfaengerID' => $instanzID,
|
|
'Zeitpunkt' => time(),
|
|
],
|
|
'Betriebsart' => $betriebsart,
|
|
'Sollleistung_W' => $sollleistung,
|
|
];
|
|
Nachrichtenvertrag::pruefeManagerdaten($paket);
|
|
IPS_RequestAction($instanzID, 'ManagerdatenEmpfangen', json_encode($paket, JSON_THROW_ON_ERROR));
|
|
$this->protokolliere('Managerdaten', [
|
|
'InstanzID' => $instanzID,
|
|
'Betriebsart' => $betriebsart,
|
|
'Sollleistung_W' => $sollleistung,
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* @param list<array{InstanzID: int, Daten: array<string, mixed>}> $verbraucher
|
|
* @param list<string> $stoerungen
|
|
*/
|
|
private function aktualisiereStoerungen(array $verbraucher, array $stoerungen): void
|
|
{
|
|
foreach ($verbraucher as $eintrag) {
|
|
foreach ($eintrag['Daten']['Zustand'] as $zustand) {
|
|
if ($zustand['Art'] === 'Stoerung' && $zustand['Wert'] === true) {
|
|
$stoerungen[] = 'Instanz ' . $eintrag['InstanzID'] . ': '
|
|
. ($zustand['Text'] ?? $zustand['Kennung']);
|
|
}
|
|
}
|
|
}
|
|
$stoerungen = array_values(array_unique($stoerungen));
|
|
$this->setzeDiagnosewert('Sammelstoerung', $stoerungen !== []);
|
|
$this->setzeDiagnosewert('Stoertext', implode("\n", $stoerungen));
|
|
}
|
|
|
|
private function registriereNetzleistungsmeldung(): void
|
|
{
|
|
$alt = $this->ReadAttributeInteger('RegistrierteNetzleistungVariableID');
|
|
$neu = $this->ReadPropertyInteger('NetzleistungVariableID');
|
|
if ($alt > 0 && $alt !== $neu) {
|
|
$this->UnregisterMessage($alt, self::VM_UPDATE);
|
|
}
|
|
if ($neu > 0 && $neu !== $alt) {
|
|
$this->RegisterMessage($neu, self::VM_UPDATE);
|
|
}
|
|
$this->WriteAttributeInteger('RegistrierteNetzleistungVariableID', $neu);
|
|
}
|
|
|
|
private function aktualisiereVariablen(): void
|
|
{
|
|
if ($this->ReadPropertyBoolean('DiagnosevariablenAnzeigen')) {
|
|
$this->RegisterVariableBoolean(
|
|
'NetzleistungGueltig',
|
|
'Netzleistung gueltig',
|
|
'~Switch',
|
|
40
|
|
);
|
|
$this->RegisterVariableFloat(
|
|
'WirksameLastspitzengrenze',
|
|
'Wirksame Lastspitzengrenze',
|
|
'',
|
|
50
|
|
);
|
|
$this->RegisterVariableFloat('Verteilbudget', 'Verteilbudget', '', 60);
|
|
$this->RegisterVariableInteger('VerbraucherAnzahl', 'Verbraucher Anzahl', '', 70);
|
|
$this->RegisterVariableInteger(
|
|
'VerbraucherVerfuegbar',
|
|
'Verbraucher verfuegbar',
|
|
'',
|
|
80
|
|
);
|
|
$this->RegisterVariableString('Verbraucherstatus', 'Verbraucherstatus', '', 90);
|
|
$this->RegisterVariableString('Monatsgrenzen', 'Monatsgrenzen', '', 100);
|
|
$this->RegisterVariableString('Prognosestatus', 'Prognosestatus', '', 110);
|
|
$this->RegisterVariableString('SDLStatus', 'SDL-Status', '', 120);
|
|
$this->RegisterVariableString('Lizenzstatus', 'Lizenzstatus', '', 130);
|
|
$this->RegisterVariableString(
|
|
'Stoerueberwachungsstatus',
|
|
'Stoerueberwachungsstatus',
|
|
'',
|
|
140
|
|
);
|
|
$this->RegisterVariableBoolean('Sammelstoerung', 'Sammelstoerung', '~Alert', 150);
|
|
$this->RegisterVariableString('Stoertext', 'Stoertext', '', 160);
|
|
|
|
return;
|
|
}
|
|
|
|
foreach (self::DIAGNOSE_VARIABLEN as $ident) {
|
|
$variablenID = @IPS_GetObjectIDByIdent($ident, $this->InstanceID);
|
|
if (is_int($variablenID) && $variablenID > 0 && IPS_VariableExists($variablenID)) {
|
|
$this->UnregisterVariable($ident);
|
|
}
|
|
}
|
|
}
|
|
|
|
/** @param bool|float|int|string $wert */
|
|
private function setzeDiagnosewert(string $ident, $wert): void
|
|
{
|
|
$variablenID = @IPS_GetObjectIDByIdent($ident, $this->InstanceID);
|
|
if (is_int($variablenID) && $variablenID > 0 && IPS_VariableExists($variablenID)) {
|
|
$this->SetValue($ident, $wert);
|
|
}
|
|
}
|
|
|
|
private function aktualisiereLizenzfreigabe(string $lizenzcode, bool $erzwingen): bool
|
|
{
|
|
$normalisiert = Lizenzpruefung::normalisiereCode($lizenzcode);
|
|
if (!Lizenzpruefung::istLizenzcodeGueltig($normalisiert)) {
|
|
$fehler = $normalisiert === '' ? 'Lizenzcode fehlt.' : 'Lizenzcode ist ungueltig.';
|
|
$this->WriteAttributeString('LizenzLetzterFehler', $fehler);
|
|
$this->setzeDiagnosewert('Lizenzstatus', $fehler);
|
|
|
|
return false;
|
|
}
|
|
|
|
$installationID = $this->ReadAttributeString('LizenzInstallationID');
|
|
if ($installationID === '') {
|
|
$installationID = $this->erzeugeUuidV4();
|
|
$this->WriteAttributeString('LizenzInstallationID', $installationID);
|
|
}
|
|
$codeHash = hash('sha256', $normalisiert);
|
|
$gespeicherterHash = $this->ReadAttributeString('LizenzcodeHash');
|
|
$lease = $this->leseLizenzLease();
|
|
$codePasst = $gespeicherterHash !== '' && hash_equals($gespeicherterHash, $codeHash);
|
|
$gueltig = $codePasst && Lizenzpruefung::istLeaseGueltig(
|
|
$lease,
|
|
$installationID,
|
|
$this->ReadPropertyInteger('Lastspitzenmodus')
|
|
);
|
|
$faellig = !$gueltig || Lizenzpruefung::erneuerungFaellig($lease);
|
|
$jetzt = time();
|
|
|
|
if (!$erzwingen
|
|
&& (!$faellig || $jetzt < $this->ReadAttributeInteger('LizenzNaechsterVersuch'))
|
|
) {
|
|
$this->setzeDiagnosewert('Lizenzstatus', $this->lizenzStatustext($lizenzcode));
|
|
|
|
return $gueltig;
|
|
}
|
|
|
|
$this->WriteAttributeInteger('LizenzNaechsterVersuch', $jetzt + self::LIZENZ_RETRY_SEKUNDEN);
|
|
try {
|
|
$neueLease = $this->rufeLizenzserverAuf($normalisiert, $installationID);
|
|
Lizenzpruefung::pruefeLease(
|
|
$neueLease,
|
|
$installationID,
|
|
$this->ReadPropertyInteger('Lastspitzenmodus')
|
|
);
|
|
$this->WriteAttributeString(
|
|
'LizenzLease',
|
|
json_encode($neueLease, JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES)
|
|
);
|
|
$this->WriteAttributeString('LizenzcodeHash', $codeHash);
|
|
$this->WriteAttributeInteger(
|
|
'LizenzNaechsterVersuch',
|
|
Lizenzpruefung::erneuernAb($neueLease)
|
|
);
|
|
$this->WriteAttributeString('LizenzLetzterFehler', '');
|
|
$this->setzeDiagnosewert('Lizenzstatus', $this->lizenzStatustext($lizenzcode));
|
|
|
|
return true;
|
|
} catch (Throwable $fehler) {
|
|
$meldung = substr($fehler->getMessage(), 0, 500);
|
|
$this->WriteAttributeString('LizenzLetzterFehler', $meldung);
|
|
$this->setzeDiagnosewert('Lizenzstatus', $this->lizenzStatustext($lizenzcode));
|
|
$this->protokolliere('Lizenzpruefung', $meldung);
|
|
|
|
return $gueltig;
|
|
}
|
|
}
|
|
|
|
/** @return array<string, mixed> */
|
|
private function leseLizenzLease(): array
|
|
{
|
|
try {
|
|
$lease = json_decode(
|
|
$this->ReadAttributeString('LizenzLease'),
|
|
true,
|
|
512,
|
|
JSON_THROW_ON_ERROR
|
|
);
|
|
|
|
return is_array($lease) ? $lease : [];
|
|
} catch (Throwable $fehler) {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
private function lizenzStatustext(?string $lizenzcode = null): string
|
|
{
|
|
$code = Lizenzpruefung::normalisiereCode(
|
|
$lizenzcode ?? $this->ReadPropertyString('Lizenzcode')
|
|
);
|
|
if ($code === '') {
|
|
return 'Lizenzcode fehlt.';
|
|
}
|
|
if (!Lizenzpruefung::istLizenzcodeGueltig($code)) {
|
|
return 'Lizenzcode ist ungueltig.';
|
|
}
|
|
|
|
$codeHash = hash('sha256', $code);
|
|
$gespeicherterHash = $this->ReadAttributeString('LizenzcodeHash');
|
|
$fehler = $this->ReadAttributeString('LizenzLetzterFehler');
|
|
if ($gespeicherterHash === '' || !hash_equals($gespeicherterHash, $codeHash)) {
|
|
return $fehler !== '' ? 'Nicht freigegeben: ' . $fehler : 'Lizenz noch nicht bestaetigt.';
|
|
}
|
|
|
|
$lease = $this->leseLizenzLease();
|
|
try {
|
|
Lizenzpruefung::pruefeLease(
|
|
$lease,
|
|
$this->ReadAttributeString('LizenzInstallationID'),
|
|
$this->ReadPropertyInteger('Lastspitzenmodus')
|
|
);
|
|
$status = ($lease['development'] ?? false) === true
|
|
? 'Aktiv (Entwicklung)'
|
|
: 'Aktiv';
|
|
$status .= ' bis ' . date('d.m.Y H:i', Lizenzpruefung::offlineBis($lease));
|
|
if ($fehler !== '') {
|
|
$status .= '; Erneuerung fehlgeschlagen: ' . $fehler;
|
|
}
|
|
|
|
return $status;
|
|
} catch (Throwable $ungueltig) {
|
|
return 'Nicht freigegeben: ' . $ungueltig->getMessage();
|
|
}
|
|
}
|
|
|
|
/** @return array<string, mixed> */
|
|
private function rufeLizenzserverAuf(string $lizenzcode, string $installationID): array
|
|
{
|
|
$nutzlast = json_encode([
|
|
'code' => $lizenzcode,
|
|
'installationId' => $installationID,
|
|
], JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES);
|
|
$curl = curl_init(self::LIZENZ_ENDPOINT);
|
|
if ($curl === false) {
|
|
throw new RuntimeException('Die HTTPS-Anfrage konnte nicht vorbereitet werden.');
|
|
}
|
|
|
|
try {
|
|
curl_setopt_array($curl, [
|
|
CURLOPT_POST => true,
|
|
CURLOPT_POSTFIELDS => $nutzlast,
|
|
CURLOPT_RETURNTRANSFER => true,
|
|
CURLOPT_CONNECTTIMEOUT => 5,
|
|
CURLOPT_TIMEOUT => 10,
|
|
CURLOPT_HTTPHEADER => [
|
|
'Accept: application/json',
|
|
'Content-Type: application/json',
|
|
],
|
|
CURLOPT_USERAGENT => 'Enelix-EMS-Manager/0.1',
|
|
]);
|
|
$antwort = curl_exec($curl);
|
|
if ($antwort === false) {
|
|
throw new RuntimeException('Lizenzserver nicht erreichbar: ' . curl_error($curl));
|
|
}
|
|
if (strlen($antwort) > 65536) {
|
|
throw new RuntimeException('Die Antwort des Lizenzservers ist zu gross.');
|
|
}
|
|
$httpStatus = (int) curl_getinfo($curl, CURLINFO_HTTP_CODE);
|
|
} finally {
|
|
curl_close($curl);
|
|
}
|
|
|
|
try {
|
|
$daten = json_decode($antwort, true, 512, JSON_THROW_ON_ERROR);
|
|
} catch (Throwable $fehler) {
|
|
throw new RuntimeException('Der Lizenzserver lieferte kein gueltiges JSON.', 0, $fehler);
|
|
}
|
|
if (!is_array($daten)) {
|
|
throw new RuntimeException('Der Lizenzserver lieferte kein JSON-Objekt.');
|
|
}
|
|
if ($httpStatus !== 200) {
|
|
$meldung = is_string($daten['error'] ?? null)
|
|
? $daten['error']
|
|
: 'Unbekannter Fehler';
|
|
throw new RuntimeException('Lizenzserver HTTP ' . $httpStatus . ': ' . $meldung);
|
|
}
|
|
|
|
return $daten;
|
|
}
|
|
|
|
private function erzeugeUuidV4(): string
|
|
{
|
|
$bytes = random_bytes(16);
|
|
$bytes[6] = chr((ord($bytes[6]) & 0x0f) | 0x40);
|
|
$bytes[8] = chr((ord($bytes[8]) & 0x3f) | 0x80);
|
|
$hex = bin2hex($bytes);
|
|
|
|
return sprintf(
|
|
'%s-%s-%s-%s-%s',
|
|
substr($hex, 0, 8),
|
|
substr($hex, 8, 4),
|
|
substr($hex, 12, 4),
|
|
substr($hex, 16, 4),
|
|
substr($hex, 20, 12)
|
|
);
|
|
}
|
|
|
|
private function aktualisiereAnschlussstatus(): void
|
|
{
|
|
foreach ([
|
|
'PrognoseAnschluss' => 'Prognosestatus',
|
|
'SDLAnschluss' => 'SDLStatus',
|
|
'StoermeldeAnschluss' => 'Stoerueberwachungsstatus',
|
|
] as $property => $variable) {
|
|
$konfiguration = json_decode($this->ReadPropertyString($property), true, 512, JSON_THROW_ON_ERROR);
|
|
$aktiv = is_array($konfiguration) && ($konfiguration['Aktiv'] ?? false) === true;
|
|
$this->setzeDiagnosewert($variable, $aktiv ? 'Fehler' : 'NichtVerwendet');
|
|
}
|
|
}
|
|
|
|
private function pruefeKonfiguration(): void
|
|
{
|
|
foreach (['MesswertMaxAlter', 'KeepAlive', 'VerbraucherTimeout'] as $property) {
|
|
if ($this->ReadPropertyInteger($property) <= 0) {
|
|
throw new InvalidArgumentException($property . ' muss groesser als 0 sein.');
|
|
}
|
|
}
|
|
if ($this->ReadPropertyFloat('Umschaltdifferenz') < 0
|
|
|| $this->ReadPropertyFloat('Umschaltdifferenz') > 100
|
|
) {
|
|
throw new InvalidArgumentException('Umschaltdifferenz muss zwischen 0 und 100 liegen.');
|
|
}
|
|
$this->leseAktiveVerbraucherIDs('VerbraucherZuordnung');
|
|
$this->leseAktiveVerbraucherIDs('AutomatischeVerbraucherZuordnung');
|
|
$this->dekodiereMonatsgrenzen($this->ReadPropertyString('Monatsgrenzen'));
|
|
foreach (['PrognoseAnschluss', 'SDLAnschluss', 'StoermeldeAnschluss'] as $property) {
|
|
$wert = json_decode($this->ReadPropertyString($property), true, 512, JSON_THROW_ON_ERROR);
|
|
if (!is_array($wert)) {
|
|
throw new InvalidArgumentException($property . ' muss ein JSON-Objekt sein.');
|
|
}
|
|
}
|
|
}
|
|
|
|
/** @return array<int, float> */
|
|
private function dekodiereMonatsgrenzen(string $json): array
|
|
{
|
|
$werte = json_decode($json, true, 512, JSON_THROW_ON_ERROR);
|
|
if (!is_array($werte)) {
|
|
throw new InvalidArgumentException('Monatsgrenzen muss ein JSON-Objekt sein.');
|
|
}
|
|
$ergebnis = [];
|
|
if (isset($werte[0]) && is_array($werte[0])) {
|
|
foreach ($werte as $eintrag) {
|
|
if (!is_array($eintrag)) {
|
|
throw new InvalidArgumentException('Monatsgrenzen muss eine Liste von Monatswerten sein.');
|
|
}
|
|
$monat = $eintrag['MonatIndex'] ?? null;
|
|
$wert = $eintrag['Grenze_W'] ?? null;
|
|
if (!is_int($monat) || $monat < 1 || $monat > 12
|
|
|| (!is_int($wert) && !is_float($wert))
|
|
) {
|
|
throw new InvalidArgumentException('Jeder Monatswert benoetigt Monatsnummer und Grenze.');
|
|
}
|
|
$ergebnis[$monat] = (float) $wert;
|
|
}
|
|
} else {
|
|
for ($monat = 1; $monat <= 12; ++$monat) {
|
|
$wert = $werte[(string) $monat] ?? $werte[$monat] ?? null;
|
|
if (!is_int($wert) && !is_float($wert)) {
|
|
throw new InvalidArgumentException(
|
|
'Monatsgrenzen muss fuer jeden Monat einen Zahlenwert enthalten.'
|
|
);
|
|
}
|
|
$ergebnis[$monat] = (float) $wert;
|
|
}
|
|
}
|
|
if (count($ergebnis) !== 12) {
|
|
throw new InvalidArgumentException('Monatsgrenzen muss genau zwoelf Monatswerte enthalten.');
|
|
}
|
|
ksort($ergebnis);
|
|
|
|
return $ergebnis;
|
|
}
|
|
|
|
/** @return array<string, mixed> */
|
|
private function leseJsonAttribut(string $name): array
|
|
{
|
|
$wert = json_decode($this->ReadAttributeString($name), true, 512, JSON_THROW_ON_ERROR);
|
|
|
|
return is_array($wert) ? $wert : [];
|
|
}
|
|
|
|
private function standardMonatsgrenzen(): string
|
|
{
|
|
$werte = [];
|
|
foreach (self::MONATSNAMEN as $monat => $name) {
|
|
$werte[] = [
|
|
'Monat' => $name,
|
|
'Grenze_W' => 0.0,
|
|
'MonatIndex' => $monat,
|
|
];
|
|
}
|
|
|
|
return json_encode($werte, JSON_THROW_ON_ERROR);
|
|
}
|
|
|
|
/** @param mixed $daten */
|
|
private function protokolliere(string $bezeichnung, $daten): void
|
|
{
|
|
if ($this->ReadPropertyBoolean('LoggingEin')) {
|
|
$this->SendDebug($bezeichnung, is_string($daten) ? $daten : json_encode($daten), 0);
|
|
}
|
|
}
|
|
}
|