This commit is contained in:
+394
-13
@@ -5,10 +5,12 @@ 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/EinspeiseRegler.php';
|
||||
require_once __DIR__ . '/../libs/Lizenzpruefung.php';
|
||||
require_once __DIR__ . '/../libs/Anlagentopologie.php';
|
||||
|
||||
use Belevo\EnelixEMS\Anlagentopologie;
|
||||
use Belevo\EnelixEMS\EinspeiseRegler;
|
||||
use Belevo\EnelixEMS\Lizenzpruefung;
|
||||
use Belevo\EnelixEMS\ManagerRegler;
|
||||
use Belevo\EnelixEMS\ManagerSchnittstelle;
|
||||
@@ -23,6 +25,7 @@ class Manager extends IPSModule implements ManagerSchnittstelle
|
||||
private const LIZENZ_ENDPOINT = 'https://license.enelix.ch/api/v1/licenses/activate';
|
||||
private const PROGNOSE_TOPOLOGIE_ENDPOINT = 'https://license.enelix.ch/api/v1/installations/%s/prognosis/topology';
|
||||
private const PROGNOSE_TELEMETRIE_ENDPOINT = 'https://license.enelix.ch/api/v1/installations/%s/prognosis/telemetry';
|
||||
private const PROGNOSE_FAHRPLAN_ENDPOINT = 'https://license.enelix.ch/api/v1/installations/%s/prognosis/schedule';
|
||||
private const LIZENZ_RETRY_SEKUNDEN = 3600;
|
||||
private const VM_UPDATE = 10603;
|
||||
private const VERBRAUCHER_LIZENZEN = [
|
||||
@@ -62,6 +65,9 @@ class Manager extends IPSModule implements ManagerSchnittstelle
|
||||
private const DIAGNOSE_VARIABLEN = [
|
||||
'NetzleistungGueltig',
|
||||
'WirksameLastspitzengrenze',
|
||||
'WirksameEinspeisegrenze',
|
||||
'Abregelbedarf',
|
||||
'Wechselrichterstatus',
|
||||
'Verteilbudget',
|
||||
'VerbraucherAnzahl',
|
||||
'VerbraucherVerfuegbar',
|
||||
@@ -95,6 +101,10 @@ class Manager extends IPSModule implements ManagerSchnittstelle
|
||||
$this->RegisterPropertyString('Monatsgrenzen', $this->standardMonatsgrenzen());
|
||||
$this->RegisterPropertyFloat('SollwertSolarladen', 0.0);
|
||||
$this->RegisterPropertyFloat('Umschaltdifferenz', 5.0);
|
||||
$this->RegisterPropertyBoolean('EinspeisebegrenzungAktiv', false);
|
||||
$this->RegisterPropertyFloat('Einspeisegrenze', 0.0);
|
||||
$this->RegisterPropertyFloat('Einspeisetoleranz', 100.0);
|
||||
$this->RegisterPropertyBoolean('NetzfahrplanAktiv', false);
|
||||
$this->RegisterPropertyString('AnlagenWechselrichter', '[]');
|
||||
$this->RegisterPropertyString('AnlagenPVFlaechen', '[]');
|
||||
$this->RegisterPropertyString('AnlagenBatterien', '[]');
|
||||
@@ -118,6 +128,8 @@ class Manager extends IPSModule implements ManagerSchnittstelle
|
||||
|
||||
$this->RegisterAttributeString('VerbraucherCache', '{}');
|
||||
$this->RegisterAttributeString('LetzteSollwerte', '{}');
|
||||
$this->RegisterAttributeString('LetztePvGrenzen', '{}');
|
||||
$this->RegisterAttributeString('Netzfahrplan', '{}');
|
||||
$this->RegisterAttributeInteger('RegistrierteNetzleistungVariableID', 0);
|
||||
$this->RegisterAttributeString('LizenzInstallationID', '');
|
||||
$this->RegisterAttributeString('LizenzLease', '{}');
|
||||
@@ -208,6 +220,9 @@ class Manager extends IPSModule implements ManagerSchnittstelle
|
||||
$automatisch = $this->ReadPropertyBoolean('AutomatischeSuche');
|
||||
$lastspitzenmodus = $this->ReadPropertyInteger('Lastspitzenmodus');
|
||||
$peakShavingLizenziert = $this->peakShavingLizenziert();
|
||||
$netzfahrplanLizenziert = $this->berechtigungLizenziert(
|
||||
Lizenzpruefung::NETZFAHRPLAN
|
||||
);
|
||||
$monatsgrenzenSichtbar = $peakShavingLizenziert
|
||||
&& $lastspitzenmodus === 2
|
||||
&& $this->GetBuffer('MonatsgrenzenSichtbar') === '1';
|
||||
@@ -251,6 +266,20 @@ class Manager extends IPSModule implements ManagerSchnittstelle
|
||||
'options',
|
||||
$lastspitzenOptionen
|
||||
);
|
||||
foreach (['EinspeisebegrenzungAktiv', 'Einspeisegrenze', 'Einspeisetoleranz'] as $feld) {
|
||||
$this->setzeFormularParameter(
|
||||
$formular['elements'],
|
||||
$feld,
|
||||
'enabled',
|
||||
$peakShavingLizenziert
|
||||
);
|
||||
}
|
||||
$this->setzeFormularParameter(
|
||||
$formular['elements'],
|
||||
'NetzfahrplanAktiv',
|
||||
'enabled',
|
||||
$peakShavingLizenziert && $netzfahrplanLizenziert
|
||||
);
|
||||
$this->setzeFormularParameter(
|
||||
$formular['elements'],
|
||||
'Lastspitzengrenze',
|
||||
@@ -458,6 +487,16 @@ class Manager extends IPSModule implements ManagerSchnittstelle
|
||||
|
||||
$aktiv = (bool) $this->GetValue('Aktiv');
|
||||
if (!$aktiv || !$netzleistungGueltig) {
|
||||
if (!$aktiv) {
|
||||
try {
|
||||
$this->gibEinspeisebegrenzungFrei();
|
||||
} catch (Throwable $fehler) {
|
||||
$this->setzeDiagnosewert(
|
||||
'Wechselrichterstatus',
|
||||
'Ruecksetzen fehlgeschlagen: ' . substr($fehler->getMessage(), 0, 500)
|
||||
);
|
||||
}
|
||||
}
|
||||
$this->SetValue('Betriebsart', ManagerRegler::BETRIEBSART_INAKTIV);
|
||||
$this->setzeDiagnosewert('Verteilbudget', 0.0);
|
||||
$this->setzeNichtGeregelteVerbraucher(
|
||||
@@ -499,6 +538,7 @@ class Manager extends IPSModule implements ManagerSchnittstelle
|
||||
$synchronisiert
|
||||
);
|
||||
$this->setzeDiagnosewert('WirksameLastspitzengrenze', $lastspitzengrenze ?? 0.0);
|
||||
$this->regleEinspeisung($netzleistung, $stoerungen, $erneuern);
|
||||
if (!$synchronisiert) {
|
||||
$this->setzeDiagnosewert('Verteilbudget', 0.0);
|
||||
$this->setzeDiagnosewert('Verbraucherstatus', json_encode([
|
||||
@@ -516,9 +556,10 @@ class Manager extends IPSModule implements ManagerSchnittstelle
|
||||
return;
|
||||
}
|
||||
|
||||
$fahrplanVorgabe = $this->aktuelleNetzfahrplanVorgabe();
|
||||
$zielnetzleistung = $betriebsart === ManagerRegler::BETRIEBSART_PEAK
|
||||
? (float) $lastspitzengrenze
|
||||
: $this->ReadPropertyFloat('SollwertSolarladen');
|
||||
: ($fahrplanVorgabe ?? $this->ReadPropertyFloat('SollwertSolarladen'));
|
||||
$ergebnis = ManagerRegler::verteile(
|
||||
$netzleistung,
|
||||
$zielnetzleistung,
|
||||
@@ -556,6 +597,174 @@ class Manager extends IPSModule implements ManagerSchnittstelle
|
||||
$this->SetStatus(self::STATUS_AKTIV);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<string> $stoerungen
|
||||
*/
|
||||
private function regleEinspeisung(
|
||||
float $netzleistung,
|
||||
array &$stoerungen,
|
||||
bool $erneuern
|
||||
): void {
|
||||
if (!$this->ReadPropertyBoolean('EinspeisebegrenzungAktiv')) {
|
||||
try {
|
||||
$this->gibEinspeisebegrenzungFrei();
|
||||
$this->setzeDiagnosewert('Wechselrichterstatus', 'Nicht aktiv');
|
||||
} catch (Throwable $fehler) {
|
||||
$stoerungen[] = 'Einspeisebegrenzung zuruecksetzen: ' . $fehler->getMessage();
|
||||
$this->setzeDiagnosewert(
|
||||
'Wechselrichterstatus',
|
||||
'Ruecksetzen fehlgeschlagen: ' . substr($fehler->getMessage(), 0, 500)
|
||||
);
|
||||
}
|
||||
$this->setzeDiagnosewert('WirksameEinspeisegrenze', 0.0);
|
||||
$this->setzeDiagnosewert('Abregelbedarf', 0.0);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$wechselrichter = $this->regelbareWechselrichter();
|
||||
$letzteGrenzen = $this->leseJsonAttribut('LetztePvGrenzen');
|
||||
$ergebnis = EinspeiseRegler::verteile(
|
||||
$netzleistung,
|
||||
$this->ReadPropertyFloat('Einspeisegrenze'),
|
||||
$this->ReadPropertyFloat('Einspeisetoleranz'),
|
||||
$wechselrichter,
|
||||
$letzteGrenzen
|
||||
);
|
||||
$konfiguration = [];
|
||||
foreach ($wechselrichter as $eintrag) {
|
||||
$konfiguration[$eintrag['ID']] = $eintrag;
|
||||
}
|
||||
foreach ($ergebnis['Grenzen_W'] as $id => $grenzeW) {
|
||||
if (!$erneuern
|
||||
&& isset($letzteGrenzen[$id])
|
||||
&& abs((float) $letzteGrenzen[$id] - $grenzeW) < 0.5
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
$eintrag = $konfiguration[$id];
|
||||
$stellwert = EinspeiseRegler::stellwert(
|
||||
$grenzeW,
|
||||
(float) $eintrag['Nennleistung_W'],
|
||||
$eintrag['Begrenzungsart']
|
||||
);
|
||||
RequestAction($eintrag['BegrenzungVariableID'], $stellwert);
|
||||
}
|
||||
$this->WriteAttributeString(
|
||||
'LetztePvGrenzen',
|
||||
json_encode($ergebnis['Grenzen_W'], JSON_THROW_ON_ERROR)
|
||||
);
|
||||
$this->setzeDiagnosewert(
|
||||
'WirksameEinspeisegrenze',
|
||||
$this->ReadPropertyFloat('Einspeisegrenze')
|
||||
);
|
||||
$this->setzeDiagnosewert('Abregelbedarf', $ergebnis['Abregelbedarf_W']);
|
||||
$this->setzeDiagnosewert(
|
||||
'Wechselrichterstatus',
|
||||
json_encode($ergebnis, JSON_THROW_ON_ERROR)
|
||||
);
|
||||
} catch (Throwable $fehler) {
|
||||
$stoerungen[] = 'Einspeisebegrenzung: ' . $fehler->getMessage();
|
||||
$this->setzeDiagnosewert(
|
||||
'Wechselrichterstatus',
|
||||
'Fehler: ' . substr($fehler->getMessage(), 0, 500)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private function gibEinspeisebegrenzungFrei(): void
|
||||
{
|
||||
if ($this->leseJsonAttribut('LetztePvGrenzen') === []) {
|
||||
return;
|
||||
}
|
||||
foreach ($this->regelbareWechselrichter() as $eintrag) {
|
||||
RequestAction(
|
||||
$eintrag['BegrenzungVariableID'],
|
||||
EinspeiseRegler::stellwert(
|
||||
(float) $eintrag['Nennleistung_W'],
|
||||
(float) $eintrag['Nennleistung_W'],
|
||||
$eintrag['Begrenzungsart']
|
||||
)
|
||||
);
|
||||
}
|
||||
$this->WriteAttributeString('LetztePvGrenzen', '{}');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<array{
|
||||
* ID: string,
|
||||
* Nennleistung_W: float,
|
||||
* Istleistung_W: float,
|
||||
* BegrenzungVariableID: int,
|
||||
* Begrenzungsart: string
|
||||
* }>
|
||||
*/
|
||||
private function regelbareWechselrichter(): array
|
||||
{
|
||||
$liste = json_decode(
|
||||
$this->ReadPropertyString('AnlagenWechselrichter'),
|
||||
true,
|
||||
512,
|
||||
JSON_THROW_ON_ERROR
|
||||
);
|
||||
if (!is_array($liste)) {
|
||||
throw new InvalidArgumentException('AnlagenWechselrichter muss eine JSON-Liste sein.');
|
||||
}
|
||||
|
||||
$ergebnis = [];
|
||||
foreach ($liste as $index => $eintrag) {
|
||||
if (!is_array($eintrag)
|
||||
|| !in_array($eintrag['Typ'] ?? null, ['pv', 'hybrid'], true)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
$istID = $eintrag['IstleistungVariableID'] ?? 0;
|
||||
$begrenzungID = $eintrag['BegrenzungVariableID'] ?? 0;
|
||||
if ($istID === 0 && $begrenzungID === 0) {
|
||||
continue;
|
||||
}
|
||||
if (!is_int($istID) || !is_int($begrenzungID)
|
||||
|| $istID <= 0 || $begrenzungID <= 0
|
||||
|| !IPS_VariableExists($istID) || !IPS_VariableExists($begrenzungID)
|
||||
) {
|
||||
throw new InvalidArgumentException(
|
||||
'Wechselrichter ' . ($index + 1) . ': Istleistung und Begrenzungsregister fehlen.'
|
||||
);
|
||||
}
|
||||
$begrenzungsvariable = IPS_GetVariable($begrenzungID);
|
||||
if ((int) ($begrenzungsvariable['VariableAction'] ?? 0) <= 0
|
||||
&& (int) ($begrenzungsvariable['VariableCustomAction'] ?? 0) <= 0
|
||||
) {
|
||||
throw new InvalidArgumentException(
|
||||
'Wechselrichter ' . ($index + 1) . ': Begrenzungsregister hat keine Aktion.'
|
||||
);
|
||||
}
|
||||
$istleistung = GetValue($istID);
|
||||
$nennleistung = $eintrag['ACNennleistung_kW'] ?? null;
|
||||
$art = $eintrag['Begrenzungsart'] ?? 'percent';
|
||||
if ((!is_int($istleistung) && !is_float($istleistung))
|
||||
|| (!is_int($nennleistung) && !is_float($nennleistung))
|
||||
|| !in_array($art, ['percent', 'watt'], true)
|
||||
) {
|
||||
throw new InvalidArgumentException(
|
||||
'Wechselrichter ' . ($index + 1) . ': Regelparameter sind ungueltig.'
|
||||
);
|
||||
}
|
||||
$ergebnis[] = [
|
||||
'ID' => (string) ($eintrag['ID'] ?? ''),
|
||||
'Nennleistung_W' => (float) $nennleistung * 1000.0,
|
||||
'Istleistung_W' => max(0.0, (float) $istleistung),
|
||||
'BegrenzungVariableID' => $begrenzungID,
|
||||
'Begrenzungsart' => $art,
|
||||
];
|
||||
}
|
||||
if ($ergebnis === []) {
|
||||
throw new InvalidArgumentException('Kein PV-Wechselrichter mit Stellregister konfiguriert.');
|
||||
}
|
||||
|
||||
return $ergebnis;
|
||||
}
|
||||
|
||||
/** @return array{0: float, 1: bool} */
|
||||
private function leseNetzleistung(): array
|
||||
{
|
||||
@@ -1034,6 +1243,19 @@ class Manager extends IPSModule implements ManagerSchnittstelle
|
||||
'',
|
||||
50
|
||||
);
|
||||
$this->RegisterVariableFloat(
|
||||
'WirksameEinspeisegrenze',
|
||||
'Wirksame Einspeisegrenze',
|
||||
'',
|
||||
52
|
||||
);
|
||||
$this->RegisterVariableFloat('Abregelbedarf', 'Abregelbedarf', '', 54);
|
||||
$this->RegisterVariableString(
|
||||
'Wechselrichterstatus',
|
||||
'Wechselrichterstatus',
|
||||
'',
|
||||
56
|
||||
);
|
||||
$this->RegisterVariableFloat('Verteilbudget', 'Verteilbudget', '', 60);
|
||||
$this->RegisterVariableInteger('VerbraucherAnzahl', 'Verbraucher Anzahl', '', 70);
|
||||
$this->RegisterVariableInteger(
|
||||
@@ -1114,20 +1336,23 @@ class Manager extends IPSModule implements ManagerSchnittstelle
|
||||
}
|
||||
$lease = $this->leseLizenzLease();
|
||||
$codePasst = $gespeicherterHash !== '' && hash_equals($gespeicherterHash, $codeHash);
|
||||
$lastspitzenmodus = $this->ReadPropertyInteger('Lastspitzenmodus');
|
||||
$lastspitzenmodus = $this->lizenzPeakModus();
|
||||
$basisGueltig = $codePasst && Lizenzpruefung::istLeaseGueltig(
|
||||
$lease,
|
||||
$installationID,
|
||||
0
|
||||
);
|
||||
$gueltig = $codePasst && Lizenzpruefung::istLeaseGueltig(
|
||||
$leaseGueltig = $codePasst && Lizenzpruefung::istLeaseGueltig(
|
||||
$lease,
|
||||
$installationID,
|
||||
$lastspitzenmodus
|
||||
);
|
||||
$berechtigungenVeraltet = $basisGueltig
|
||||
&& !$this->lizenzBerechtigungenPassen($lease, $lastspitzenmodus);
|
||||
$geraetezugangFehlt = $this->hatAnlagentopologie()
|
||||
$berechtigungenPassen = $leaseGueltig
|
||||
&& $this->lizenzBerechtigungenPassen($lease, $lastspitzenmodus);
|
||||
$gueltig = $leaseGueltig && $berechtigungenPassen;
|
||||
$berechtigungenVeraltet = $basisGueltig && !$berechtigungenPassen;
|
||||
$geraetezugangFehlt = ($this->hatAnlagentopologie()
|
||||
|| $this->prognoseTelemetrieKonfiguriert())
|
||||
&& $this->ReadAttributeString('PrognoseInstallationsToken') === '';
|
||||
$routineFaellig = (!$gueltig && !$berechtigungenVeraltet)
|
||||
|| $geraetezugangFehlt
|
||||
@@ -1183,8 +1408,16 @@ class Manager extends IPSModule implements ManagerSchnittstelle
|
||||
Lizenzpruefung::pruefeLease(
|
||||
$neueLease,
|
||||
$installationID,
|
||||
$this->ReadPropertyInteger('Lastspitzenmodus')
|
||||
$this->lizenzPeakModus()
|
||||
);
|
||||
if (!$this->lizenzBerechtigungenPassen(
|
||||
$neueLease,
|
||||
$this->lizenzPeakModus()
|
||||
)) {
|
||||
throw new InvalidArgumentException(
|
||||
'Die Lizenz deckt die konfigurierten Funktionen oder Geraetemengen nicht ab.'
|
||||
);
|
||||
}
|
||||
if ($geraetezugangFehlt) {
|
||||
$this->WriteAttributeString('PrognoseInstallationsToken', $geraeteToken);
|
||||
}
|
||||
@@ -1230,7 +1463,22 @@ class Manager extends IPSModule implements ManagerSchnittstelle
|
||||
}
|
||||
}
|
||||
|
||||
private function lizenzPeakModus(): int
|
||||
{
|
||||
$modus = $this->ReadPropertyInteger('Lastspitzenmodus');
|
||||
if ($modus !== 0) {
|
||||
return $modus;
|
||||
}
|
||||
|
||||
return $this->ReadPropertyBoolean('EinspeisebegrenzungAktiv') ? 1 : 0;
|
||||
}
|
||||
|
||||
private function peakShavingLizenziert(): bool
|
||||
{
|
||||
return $this->berechtigungLizenziert(Lizenzpruefung::MANAGER_PEAK);
|
||||
}
|
||||
|
||||
private function berechtigungLizenziert(string $kennung): bool
|
||||
{
|
||||
$lizenzcode = Lizenzpruefung::normalisiereCode(
|
||||
$this->ReadPropertyString('Lizenzcode')
|
||||
@@ -1255,10 +1503,7 @@ class Manager extends IPSModule implements ManagerSchnittstelle
|
||||
return false;
|
||||
}
|
||||
|
||||
return Lizenzpruefung::berechtigungsMenge(
|
||||
$lease,
|
||||
Lizenzpruefung::MANAGER_PEAK
|
||||
) > 0;
|
||||
return Lizenzpruefung::berechtigungsMenge($lease, $kennung) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1266,7 +1511,7 @@ class Manager extends IPSModule implements ManagerSchnittstelle
|
||||
*/
|
||||
private function lizenzBerechtigungenPassen(array $lease, int $lastspitzenmodus): bool
|
||||
{
|
||||
if ($lastspitzenmodus !== 0
|
||||
if (($lastspitzenmodus !== 0 || $this->ReadPropertyBoolean('EinspeisebegrenzungAktiv'))
|
||||
&& Lizenzpruefung::berechtigungsMenge(
|
||||
$lease,
|
||||
Lizenzpruefung::MANAGER_PEAK
|
||||
@@ -1274,6 +1519,14 @@ class Manager extends IPSModule implements ManagerSchnittstelle
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if ($this->ReadPropertyBoolean('NetzfahrplanAktiv')
|
||||
&& Lizenzpruefung::berechtigungsMenge(
|
||||
$lease,
|
||||
Lizenzpruefung::NETZFAHRPLAN
|
||||
) < 1
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
[$mengen] = $this->verbraucherLizenzbelegung(
|
||||
@@ -1356,7 +1609,7 @@ class Manager extends IPSModule implements ManagerSchnittstelle
|
||||
Lizenzpruefung::pruefeLease(
|
||||
$lease,
|
||||
$this->ReadAttributeString('LizenzInstallationID'),
|
||||
$this->ReadPropertyInteger('Lastspitzenmodus')
|
||||
$this->lizenzPeakModus()
|
||||
);
|
||||
$this->aktiveVerbraucherIDs();
|
||||
$status = ($lease['development'] ?? false) === true
|
||||
@@ -1498,6 +1751,22 @@ class Manager extends IPSModule implements ManagerSchnittstelle
|
||||
) {
|
||||
throw new InvalidArgumentException('Umschaltdifferenz muss zwischen 0 und 100 liegen.');
|
||||
}
|
||||
if ($this->ReadPropertyFloat('Einspeisegrenze') < 0.0
|
||||
|| $this->ReadPropertyFloat('Einspeisetoleranz') < 0.0
|
||||
) {
|
||||
throw new InvalidArgumentException('Einspeisegrenze und Einspeisetoleranz duerfen nicht negativ sein.');
|
||||
}
|
||||
if ($this->ReadPropertyBoolean('EinspeisebegrenzungAktiv')) {
|
||||
$this->regelbareWechselrichter();
|
||||
}
|
||||
if ($this->ReadPropertyBoolean('NetzfahrplanAktiv')
|
||||
&& (!$this->ReadPropertyBoolean('EinspeisebegrenzungAktiv')
|
||||
|| !$this->prognoseTelemetrieKonfiguriert())
|
||||
) {
|
||||
throw new InvalidArgumentException(
|
||||
'Der intelligente Netzfahrplan benoetigt Einspeisebegrenzung und Prognosetelemetrie.'
|
||||
);
|
||||
}
|
||||
$this->leseAktiveVerbraucherIDs('VerbraucherZuordnung');
|
||||
$this->leseAktiveVerbraucherIDs('AutomatischeVerbraucherZuordnung');
|
||||
$this->dekodiereMonatsgrenzen($this->ReadPropertyString('Monatsgrenzen'));
|
||||
@@ -1537,6 +1806,48 @@ class Manager extends IPSModule implements ManagerSchnittstelle
|
||||
&& $this->ReadPropertyInteger('PrognoseSOCVariableID') > 0;
|
||||
}
|
||||
|
||||
private function aktuelleNetzfahrplanVorgabe(): ?float
|
||||
{
|
||||
if (!$this->ReadPropertyBoolean('NetzfahrplanAktiv')) {
|
||||
return null;
|
||||
}
|
||||
$fahrplan = $this->leseJsonAttribut('Netzfahrplan');
|
||||
if (($fahrplan['status'] ?? null) !== 'available'
|
||||
|| !is_array($fahrplan['points'] ?? null)
|
||||
|| !is_string($fahrplan['validUntil'] ?? null)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
$gueltigBis = strtotime($fahrplan['validUntil']);
|
||||
if ($gueltigBis === false || $gueltigBis <= time()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$jetzt = time();
|
||||
$naechsterAbstand = PHP_INT_MAX;
|
||||
$vorgabe = null;
|
||||
foreach ($fahrplan['points'] as $punkt) {
|
||||
if (!is_array($punkt)
|
||||
|| !is_string($punkt['time'] ?? null)
|
||||
|| (!is_int($punkt['gridTargetW'] ?? null)
|
||||
&& !is_float($punkt['gridTargetW'] ?? null))
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
$zeitpunkt = strtotime($punkt['time']);
|
||||
if ($zeitpunkt === false) {
|
||||
continue;
|
||||
}
|
||||
$abstand = abs($zeitpunkt - $jetzt);
|
||||
if ($abstand < $naechsterAbstand) {
|
||||
$naechsterAbstand = $abstand;
|
||||
$vorgabe = (float) $punkt['gridTargetW'];
|
||||
}
|
||||
}
|
||||
|
||||
return $naechsterAbstand <= 1800 ? $vorgabe : null;
|
||||
}
|
||||
|
||||
private function synchronisiereAnlagentopologie(): void
|
||||
{
|
||||
try {
|
||||
@@ -1667,6 +1978,9 @@ class Manager extends IPSModule implements ManagerSchnittstelle
|
||||
'values' => $werte,
|
||||
];
|
||||
$this->uebertragePrognoseTelemetrie($installationID, $nutzlast, $token);
|
||||
if ($this->ReadPropertyBoolean('NetzfahrplanAktiv')) {
|
||||
$this->aktualisiereNetzfahrplan($installationID, $token);
|
||||
}
|
||||
$this->setzeDiagnosewert('Prognosestatus', 'Verbunden');
|
||||
} catch (Throwable $fehler) {
|
||||
$meldung = substr($fehler->getMessage(), 0, 500);
|
||||
@@ -1675,6 +1989,73 @@ class Manager extends IPSModule implements ManagerSchnittstelle
|
||||
}
|
||||
}
|
||||
|
||||
private function aktualisiereNetzfahrplan(string $installationID, string $token): void
|
||||
{
|
||||
$url = sprintf(
|
||||
self::PROGNOSE_FAHRPLAN_ENDPOINT,
|
||||
rawurlencode($installationID)
|
||||
) . '?' . http_build_query([
|
||||
'exportLimitW' => $this->ReadPropertyFloat('Einspeisegrenze'),
|
||||
]);
|
||||
$curl = curl_init($url);
|
||||
if ($curl === false) {
|
||||
throw new RuntimeException('Der Netzfahrplan konnte nicht vorbereitet werden.');
|
||||
}
|
||||
|
||||
try {
|
||||
curl_setopt_array($curl, [
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_CONNECTTIMEOUT => 5,
|
||||
CURLOPT_TIMEOUT => 10,
|
||||
CURLOPT_HTTPHEADER => [
|
||||
'Accept: application/json',
|
||||
'Authorization: Bearer ' . $token,
|
||||
],
|
||||
CURLOPT_USERAGENT => 'Enelix-EMS-Manager/0.2',
|
||||
]);
|
||||
$antwort = curl_exec($curl);
|
||||
if ($antwort === false) {
|
||||
throw new RuntimeException('Netzfahrplan nicht erreichbar: ' . curl_error($curl));
|
||||
}
|
||||
if (strlen($antwort) > 524288) {
|
||||
throw new RuntimeException('Die Antwort des Netzfahrplans ist zu gross.');
|
||||
}
|
||||
$httpStatus = (int) curl_getinfo($curl, CURLINFO_HTTP_CODE);
|
||||
} finally {
|
||||
curl_close($curl);
|
||||
}
|
||||
|
||||
if ($httpStatus === 401) {
|
||||
$this->WriteAttributeString('PrognoseInstallationsToken', '');
|
||||
}
|
||||
if ($httpStatus !== 200) {
|
||||
$meldung = 'Unbekannter Fehler';
|
||||
try {
|
||||
$fehlerdaten = json_decode($antwort, true, 512, JSON_THROW_ON_ERROR);
|
||||
if (is_array($fehlerdaten) && is_string($fehlerdaten['error'] ?? null)) {
|
||||
$meldung = $fehlerdaten['error'];
|
||||
}
|
||||
} catch (Throwable $fehler) {
|
||||
// Der HTTP-Status bleibt die fuehrende Fehlerinformation.
|
||||
}
|
||||
throw new RuntimeException(
|
||||
'Netzfahrplan HTTP ' . $httpStatus . ': ' . $meldung
|
||||
);
|
||||
}
|
||||
|
||||
$fahrplan = json_decode($antwort, true, 512, JSON_THROW_ON_ERROR);
|
||||
if (!is_array($fahrplan)
|
||||
|| ($fahrplan['schemaVersion'] ?? null) !== 1
|
||||
|| !is_array($fahrplan['points'] ?? null)
|
||||
) {
|
||||
throw new RuntimeException('Der Netzfahrplan hat ein ungueltiges Format.');
|
||||
}
|
||||
$this->WriteAttributeString(
|
||||
'Netzfahrplan',
|
||||
json_encode($fahrplan, JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES)
|
||||
);
|
||||
}
|
||||
|
||||
/** @param array<string, mixed> $nutzlast */
|
||||
private function uebertragePrognoseTelemetrie(
|
||||
string $installationID,
|
||||
|
||||
Reference in New Issue
Block a user