feat(manager): Energieaufzeichnung und Visualisierungen integrieren
Tests / test (push) Successful in 47s
Tests / test (push) Successful in 47s
This commit is contained in:
+378
-27
@@ -9,9 +9,13 @@ require_once __DIR__ . '/../libs/EinspeiseRegler.php';
|
||||
require_once __DIR__ . '/../libs/Lizenzpruefung.php';
|
||||
require_once __DIR__ . '/../libs/Anlagentopologie.php';
|
||||
require_once __DIR__ . '/../libs/StoerungsSnapshot.php';
|
||||
require_once __DIR__ . '/../libs/EnergieMessung.php';
|
||||
require_once __DIR__ . '/../libs/ManagerEnergieTrait.php';
|
||||
|
||||
use Belevo\EnelixEMS\Anlagentopologie;
|
||||
use Belevo\EnelixEMS\EinspeiseRegler;
|
||||
use Belevo\EnelixEMS\EnergieMessung;
|
||||
use Belevo\EnelixEMS\ManagerEnergieTrait;
|
||||
use Belevo\EnelixEMS\Lizenzpruefung;
|
||||
use Belevo\EnelixEMS\ManagerRegler;
|
||||
use Belevo\EnelixEMS\ManagerSchnittstelle;
|
||||
@@ -20,6 +24,8 @@ use Belevo\EnelixEMS\StoerungsSnapshot;
|
||||
|
||||
class Manager extends IPSModule implements ManagerSchnittstelle
|
||||
{
|
||||
use ManagerEnergieTrait;
|
||||
|
||||
private const STATUS_AKTIV = 102;
|
||||
private const STATUS_MESSWERT_UNGUELTIG = 201;
|
||||
private const STATUS_KONFIGURATION_UNGUELTIG = 202;
|
||||
@@ -32,6 +38,23 @@ class Manager extends IPSModule implements ManagerSchnittstelle
|
||||
private const LIZENZ_RETRY_SEKUNDEN = 3600;
|
||||
private const STOERUNGS_RETRY_MAX_SEKUNDEN = 900;
|
||||
private const VM_UPDATE = 10603;
|
||||
private const ARCHIVE_MODULE_ID = '{43192F0B-135B-4CE7-A0A7-1475603F3060}';
|
||||
private const ENERGY_PIE_MODULE_ID = '{F73B8F8D-5E36-4B23-B379-4632A517A1D6}';
|
||||
private const ENERGY_FLOW_MODULE_ID = '{BA90FDB3-C203-8F26-B4D6-F3DB99905724}';
|
||||
private const LEISTUNGS_VARIABLEN = [
|
||||
'PVLeistungArchiv',
|
||||
'HausverbrauchLeistungArchiv',
|
||||
'NetzleistungArchiv',
|
||||
'BatterieleistungArchiv',
|
||||
];
|
||||
private const ENERGIE_VARIABLEN = [
|
||||
'PVEnergie',
|
||||
'Hausenergie',
|
||||
'NetzbezugEnergie',
|
||||
'EinspeisungEnergie',
|
||||
'BatterieLadenEnergie',
|
||||
'BatterieEntladenEnergie',
|
||||
];
|
||||
private const VERBRAUCHER_LIZENZEN = [
|
||||
'{B7C54AF4-AD7D-4FE4-B75D-203693906251}' => [
|
||||
'Katalog' => 'boiler_multi',
|
||||
@@ -112,6 +135,7 @@ class Manager extends IPSModule implements ManagerSchnittstelle
|
||||
$this->RegisterPropertyString('AnlagenWechselrichter', '[]');
|
||||
$this->RegisterPropertyString('AnlagenPVFlaechen', '[]');
|
||||
$this->RegisterPropertyString('AnlagenBatterien', '[]');
|
||||
// Verdeckte Altproperties; Messquellen liegen neu ausschliesslich in der Anlagentopologie.
|
||||
$this->RegisterPropertyInteger('PrognosePVVariableID', 0);
|
||||
$this->RegisterPropertyInteger('PrognoseHausverbrauchVariableID', 0);
|
||||
$this->RegisterPropertyInteger('PrognoseSOCVariableID', 0);
|
||||
@@ -127,6 +151,24 @@ class Manager extends IPSModule implements ManagerSchnittstelle
|
||||
$this->RegisterPropertyInteger('StoerungsSendeintervall', 300);
|
||||
$this->RegisterPropertyBoolean('DiagnosevariablenAnzeigen', false);
|
||||
$this->RegisterPropertyBoolean('LoggingEin', false);
|
||||
$this->RegisterPropertyBoolean('EnergieaufzeichnungAktiv', true);
|
||||
// Verdeckte Altproperties; bestehende Konfigurationen bleiben lesbar.
|
||||
$this->RegisterPropertyInteger('MessungPVLeistungVariableID', 0);
|
||||
$this->RegisterPropertyInteger('MessungHausverbrauchLeistungVariableID', 0);
|
||||
$this->RegisterPropertyInteger('MessungBatterieleistungVariableID', 0);
|
||||
$this->RegisterPropertyFloat('MessungPVLeistungsfaktor', 1.0);
|
||||
$this->RegisterPropertyFloat('MessungHausverbrauchLeistungsfaktor', 1.0);
|
||||
$this->RegisterPropertyFloat('MessungBatterieleistungsfaktor', 1.0);
|
||||
$this->RegisterPropertyBoolean('MesswerteAnzeigen', false);
|
||||
$this->RegisterPropertyInteger('LeistungsaufzeichnungMinuten', 1);
|
||||
$this->RegisterPropertyInteger('LeistungLoeschenMonate', 12);
|
||||
$this->RegisterPropertyInteger('EnergieaufzeichnungMinuten', 5);
|
||||
$this->RegisterPropertyInteger('EnergieVerdichtenMonate', 12);
|
||||
$this->RegisterPropertyInteger('EnergieLoeschenMonate', 0);
|
||||
$this->RegisterPropertyBoolean('EnergyPieAnzeigen', false);
|
||||
$this->RegisterPropertyBoolean('EnergiediagrammeAnzeigen', false);
|
||||
$this->RegisterPropertyBoolean('FunFactsAnzeigen', false);
|
||||
$this->RegisterPropertyBoolean('EnergieflussAnzeigen', false);
|
||||
|
||||
$this->RegisterVariableBoolean('Aktiv', 'Aktiv', '~Switch', 10);
|
||||
$this->EnableAction('Aktiv');
|
||||
@@ -150,6 +192,10 @@ class Manager extends IPSModule implements ManagerSchnittstelle
|
||||
$this->RegisterAttributeInteger('StoerungsLetzterErfolg', 0);
|
||||
$this->RegisterAttributeInteger('StoerungsNaechsterVersuch', 0);
|
||||
$this->RegisterAttributeInteger('StoerungsFehlversuche', 0);
|
||||
$this->RegisterAttributeInteger('MessungLetzterZeitstempel', 0);
|
||||
$this->RegisterAttributeInteger('MessungLetzteLeistungsaufzeichnung', 0);
|
||||
$this->RegisterAttributeInteger('MessungLetzteEnergieaufzeichnung', 0);
|
||||
$this->RegisterAttributeString('MessungEnergiePuffer', '{}');
|
||||
|
||||
// Die alten Timer bleiben deaktiviert registriert, damit bestehende Instanzen sauber migrieren.
|
||||
$this->RegisterTimer('Regelzyklus', 0, '');
|
||||
@@ -169,6 +215,11 @@ class Manager extends IPSModule implements ManagerSchnittstelle
|
||||
0,
|
||||
"IPS_RequestAction(\$_IPS['TARGET'], 'StoerungenSenden', true);"
|
||||
);
|
||||
$this->RegisterTimer(
|
||||
'MesswerteAufzeichnen',
|
||||
0,
|
||||
"IPS_RequestAction(\$_IPS['TARGET'], 'MesswerteAufzeichnen', true);"
|
||||
);
|
||||
}
|
||||
|
||||
public function ApplyChanges(): void
|
||||
@@ -180,6 +231,8 @@ class Manager extends IPSModule implements ManagerSchnittstelle
|
||||
|
||||
try {
|
||||
$this->pruefeKonfiguration();
|
||||
$this->konfiguriereEnergiearchiv();
|
||||
$this->synchronisiereEnergievisualisierungen();
|
||||
} catch (Throwable $fehler) {
|
||||
$this->SetStatus(self::STATUS_KONFIGURATION_UNGUELTIG);
|
||||
$this->SetTimerInterval('Regelzyklus', 0);
|
||||
@@ -187,6 +240,7 @@ class Manager extends IPSModule implements ManagerSchnittstelle
|
||||
$this->SetTimerInterval('KeepAlive', 0);
|
||||
$this->SetTimerInterval('PrognoseSenden', 0);
|
||||
$this->SetTimerInterval('StoerungenSenden', 0);
|
||||
$this->SetTimerInterval('MesswerteAufzeichnen', 0);
|
||||
$this->protokolliere('Konfiguration', $fehler->getMessage());
|
||||
return;
|
||||
}
|
||||
@@ -200,6 +254,11 @@ class Manager extends IPSModule implements ManagerSchnittstelle
|
||||
'StoerungenSenden',
|
||||
$this->ReadPropertyBoolean('StoerueberwachungAktiv') ? 30000 : 0
|
||||
);
|
||||
$messungAktiv = $this->messungKonfiguriert();
|
||||
$this->SetTimerInterval('MesswerteAufzeichnen', $messungAktiv ? 60000 : 0);
|
||||
if ($messungAktiv) {
|
||||
$this->zeichneMesswerteAuf();
|
||||
}
|
||||
$this->setzeDiagnosewert('Monatsgrenzen', $this->ReadPropertyString('Monatsgrenzen'));
|
||||
$this->aktualisiereAnschlussstatus();
|
||||
$geraetezugangFehlt = ($this->hatAnlagentopologie()
|
||||
@@ -378,6 +437,26 @@ class Manager extends IPSModule implements ManagerSchnittstelle
|
||||
$this->sendeStoerungsSnapshot(false);
|
||||
return;
|
||||
|
||||
case 'MesswerteAufzeichnen':
|
||||
$this->zeichneMesswerteAuf();
|
||||
return;
|
||||
|
||||
case 'FormEnergyPieAnzeigen':
|
||||
$this->synchronisiereEnergyPie((bool) $wert);
|
||||
return;
|
||||
|
||||
case 'FormEnergiediagrammeAnzeigen':
|
||||
$this->synchronisiereEnergiediagramme((bool) $wert);
|
||||
return;
|
||||
|
||||
case 'FormFunFactsAnzeigen':
|
||||
$this->synchronisiereFunFacts((bool) $wert);
|
||||
return;
|
||||
|
||||
case 'FormEnergieflussAnzeigen':
|
||||
$this->synchronisiereEnergiefluss((bool) $wert);
|
||||
return;
|
||||
|
||||
case 'FormLizenzPruefen':
|
||||
if (!is_string($wert)) {
|
||||
throw new InvalidArgumentException('Der Lizenzcode muss als Text uebergeben werden.');
|
||||
@@ -1430,6 +1509,32 @@ class Manager extends IPSModule implements ManagerSchnittstelle
|
||||
|
||||
private function aktualisiereVariablen(): void
|
||||
{
|
||||
$leistungsvariablen = [
|
||||
'PVLeistungArchiv' => 'PV-Leistung',
|
||||
'HausverbrauchLeistungArchiv' => 'Hausverbrauch',
|
||||
'NetzleistungArchiv' => 'Netzleistung Aufzeichnung',
|
||||
'BatterieleistungArchiv' => 'Batterieleistung',
|
||||
];
|
||||
$energievariablen = [
|
||||
'PVEnergie' => 'PV-Energie',
|
||||
'Hausenergie' => 'Hausenergie',
|
||||
'NetzbezugEnergie' => 'Netzbezug Energie',
|
||||
'EinspeisungEnergie' => 'Einspeisung Energie',
|
||||
'BatterieLadenEnergie' => 'Batterie geladen',
|
||||
'BatterieEntladenEnergie' => 'Batterie entladen',
|
||||
];
|
||||
$position = 200;
|
||||
foreach ($leistungsvariablen as $ident => $name) {
|
||||
$this->RegisterVariableFloat($ident, $name, '~Power', $position);
|
||||
IPS_SetHidden($this->GetIDForIdent($ident), !$this->ReadPropertyBoolean('MesswerteAnzeigen'));
|
||||
$position += 10;
|
||||
}
|
||||
foreach ($energievariablen as $ident => $name) {
|
||||
$this->RegisterVariableFloat($ident, $name, '~Electricity', $position);
|
||||
IPS_SetHidden($this->GetIDForIdent($ident), !$this->ReadPropertyBoolean('MesswerteAnzeigen'));
|
||||
$position += 10;
|
||||
}
|
||||
|
||||
if ($this->ReadPropertyBoolean('DiagnosevariablenAnzeigen')) {
|
||||
$this->RegisterVariableBoolean(
|
||||
'NetzleistungGueltig',
|
||||
@@ -1495,6 +1600,249 @@ class Manager extends IPSModule implements ManagerSchnittstelle
|
||||
}
|
||||
}
|
||||
|
||||
private function messungKonfiguriert(): bool
|
||||
{
|
||||
return $this->ReadPropertyBoolean('EnergieaufzeichnungAktiv')
|
||||
&& $this->messquellenKonfiguriert();
|
||||
}
|
||||
|
||||
private function messquellenKonfiguriert(): bool
|
||||
{
|
||||
try {
|
||||
return $this->istNumerischeVariable($this->ReadPropertyInteger('NetzleistungVariableID'))
|
||||
&& $this->pvMessquellen() !== [];
|
||||
} catch (Throwable $fehler) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/** @return list<array{VariableID: int, Faktor: float}> */
|
||||
private function pvMessquellen(): array
|
||||
{
|
||||
$wechselrichter = $this->leseAnlagenKonfigurationsliste('AnlagenWechselrichter');
|
||||
$pvFlaechen = $this->leseAnlagenKonfigurationsliste('AnlagenPVFlaechen');
|
||||
$verwendeteWechselrichter = [];
|
||||
foreach ($pvFlaechen as $pvFlaeche) {
|
||||
$id = trim((string) ($pvFlaeche['WechselrichterID'] ?? ''));
|
||||
if ($id !== '') {
|
||||
$verwendeteWechselrichter[$id] = true;
|
||||
}
|
||||
}
|
||||
|
||||
$quellen = [];
|
||||
foreach ($wechselrichter as $index => $eintrag) {
|
||||
$typ = (string) ($eintrag['Typ'] ?? '');
|
||||
$id = trim((string) ($eintrag['ID'] ?? ''));
|
||||
if ($verwendeteWechselrichter !== []) {
|
||||
if (!isset($verwendeteWechselrichter[$id])) {
|
||||
continue;
|
||||
}
|
||||
} elseif (!in_array($typ, ['pv', 'hybrid'], true)) {
|
||||
continue;
|
||||
}
|
||||
$variableID = $eintrag['IstleistungVariableID'] ?? 0;
|
||||
if ($variableID === 0) {
|
||||
continue;
|
||||
}
|
||||
if (!is_int($variableID) || !$this->istNumerischeVariable($variableID)) {
|
||||
throw new InvalidArgumentException(
|
||||
'Wechselrichter ' . ($index + 1) . ': Istleistung muss eine numerische Variable sein.'
|
||||
);
|
||||
}
|
||||
$quellen[] = [
|
||||
'VariableID' => $variableID,
|
||||
'Faktor' => $this->anlagenMessfaktor(
|
||||
$eintrag['Istleistungsfaktor'] ?? 1.0,
|
||||
'Wechselrichter ' . ($index + 1)
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
return $quellen;
|
||||
}
|
||||
|
||||
/** @return list<array{VariableID: int, Faktor: float}> */
|
||||
private function batterieMessquellen(): array
|
||||
{
|
||||
$quellen = [];
|
||||
foreach ($this->leseAnlagenKonfigurationsliste('AnlagenBatterien') as $index => $eintrag) {
|
||||
$variableID = $eintrag['LeistungVariableID'] ?? 0;
|
||||
if ($variableID === 0) {
|
||||
continue;
|
||||
}
|
||||
if (!is_int($variableID) || !$this->istNumerischeVariable($variableID)) {
|
||||
throw new InvalidArgumentException(
|
||||
'Batterie ' . ($index + 1) . ': Leistung muss eine numerische Variable sein.'
|
||||
);
|
||||
}
|
||||
$quellen[] = [
|
||||
'VariableID' => $variableID,
|
||||
'Faktor' => $this->anlagenMessfaktor(
|
||||
$eintrag['Leistungsfaktor'] ?? 1.0,
|
||||
'Batterie ' . ($index + 1)
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
return $quellen;
|
||||
}
|
||||
|
||||
/** @return list<array{VariableID: int, Gewicht: float}> */
|
||||
private function batterieSocQuellen(): array
|
||||
{
|
||||
$quellen = [];
|
||||
foreach ($this->leseAnlagenKonfigurationsliste('AnlagenBatterien') as $index => $eintrag) {
|
||||
$variableID = $eintrag['SOCVariableID'] ?? 0;
|
||||
if ($variableID === 0) {
|
||||
continue;
|
||||
}
|
||||
if (!is_int($variableID) || !$this->istNumerischeVariable($variableID)) {
|
||||
throw new InvalidArgumentException(
|
||||
'Batterie ' . ($index + 1) . ': SOC muss eine numerische Variable sein.'
|
||||
);
|
||||
}
|
||||
$kapazitaet = $eintrag['Nutzkapazitaet_kWh'] ?? 0.0;
|
||||
$quellen[] = [
|
||||
'VariableID' => $variableID,
|
||||
'Gewicht' => is_numeric($kapazitaet) && (float) $kapazitaet > 0.0
|
||||
? (float) $kapazitaet
|
||||
: 1.0,
|
||||
];
|
||||
}
|
||||
|
||||
return $quellen;
|
||||
}
|
||||
|
||||
/** @return list<array<string, mixed>> */
|
||||
private function leseAnlagenKonfigurationsliste(string $property): array
|
||||
{
|
||||
$liste = json_decode($this->ReadPropertyString($property), true, 512, JSON_THROW_ON_ERROR);
|
||||
if (!is_array($liste) || !array_is_list($liste)) {
|
||||
throw new InvalidArgumentException($property . ' muss eine JSON-Liste sein.');
|
||||
}
|
||||
|
||||
return array_values(array_filter($liste, 'is_array'));
|
||||
}
|
||||
|
||||
/** @param mixed $wert */
|
||||
private function anlagenMessfaktor($wert, string $bezeichnung): float
|
||||
{
|
||||
if (!is_int($wert) && !is_float($wert)) {
|
||||
throw new InvalidArgumentException($bezeichnung . ': Messfaktor muss numerisch sein.');
|
||||
}
|
||||
$faktor = (float) $wert;
|
||||
if (!is_finite($faktor) || $faktor === 0.0) {
|
||||
throw new InvalidArgumentException($bezeichnung . ': Messfaktor muss endlich und ungleich 0 sein.');
|
||||
}
|
||||
|
||||
return $faktor;
|
||||
}
|
||||
|
||||
/** @return array{PV: float, Haus: float, Netz: float, Batterie: float} */
|
||||
private function leseMessleistungen(): array
|
||||
{
|
||||
$pv = 0.0;
|
||||
foreach ($this->pvMessquellen() as $quelle) {
|
||||
$pv += max(0.0, (float) GetValue($quelle['VariableID']) * $quelle['Faktor']);
|
||||
}
|
||||
$batterie = 0.0;
|
||||
foreach ($this->batterieMessquellen() as $quelle) {
|
||||
$batterie += (float) GetValue($quelle['VariableID']) * $quelle['Faktor'];
|
||||
}
|
||||
$netz = (float) GetValue($this->ReadPropertyInteger('NetzleistungVariableID'))
|
||||
* $this->ReadPropertyFloat('Netzleistungsfaktor');
|
||||
|
||||
return [
|
||||
'PV' => $pv,
|
||||
'Haus' => EnergieMessung::hausleistung($pv, $netz, $batterie),
|
||||
'Netz' => $netz,
|
||||
'Batterie' => $batterie,
|
||||
];
|
||||
}
|
||||
|
||||
private function aktuelleBatterieSoc(): ?float
|
||||
{
|
||||
$quellen = $this->batterieSocQuellen();
|
||||
if ($quellen === []) {
|
||||
return $this->leseAnlagenKonfigurationsliste('AnlagenBatterien') === [] ? 0.0 : null;
|
||||
}
|
||||
$summe = 0.0;
|
||||
$gewicht = 0.0;
|
||||
foreach ($quellen as $quelle) {
|
||||
$wert = (float) GetValue($quelle['VariableID']);
|
||||
if (!is_finite($wert) || $wert < 0.0 || $wert > 100.0) {
|
||||
return null;
|
||||
}
|
||||
$summe += $wert * $quelle['Gewicht'];
|
||||
$gewicht += $quelle['Gewicht'];
|
||||
}
|
||||
|
||||
return $gewicht > 0.0 ? $summe / $gewicht : null;
|
||||
}
|
||||
|
||||
private function zeichneMesswerteAuf(): void
|
||||
{
|
||||
if (!$this->messungKonfiguriert()) {
|
||||
return;
|
||||
}
|
||||
$jetzt = time();
|
||||
$leistungen = $this->leseMessleistungen();
|
||||
$letzterZeitstempel = $this->ReadAttributeInteger('MessungLetzterZeitstempel');
|
||||
$puffer = $this->leseJsonAttribut('MessungEnergiePuffer');
|
||||
if ($letzterZeitstempel > 0) {
|
||||
$sekunden = min(300, max(0, $jetzt - $letzterZeitstempel));
|
||||
foreach (EnergieMessung::energieDeltas($leistungen, $sekunden) as $ident => $delta) {
|
||||
$puffer[$ident] = max(0.0, (float) ($puffer[$ident] ?? 0.0)) + $delta;
|
||||
}
|
||||
}
|
||||
$this->WriteAttributeInteger('MessungLetzterZeitstempel', $jetzt);
|
||||
$this->WriteAttributeString(
|
||||
'MessungEnergiePuffer',
|
||||
json_encode($puffer, JSON_THROW_ON_ERROR | JSON_PRESERVE_ZERO_FRACTION)
|
||||
);
|
||||
|
||||
$leistungsintervall = $this->ReadPropertyInteger('LeistungsaufzeichnungMinuten') * 60;
|
||||
$letzteLeistung = $this->ReadAttributeInteger('MessungLetzteLeistungsaufzeichnung');
|
||||
if ($letzteLeistung === 0 || $jetzt - $letzteLeistung >= $leistungsintervall) {
|
||||
foreach ([
|
||||
'PVLeistungArchiv' => 'PV',
|
||||
'HausverbrauchLeistungArchiv' => 'Haus',
|
||||
'NetzleistungArchiv' => 'Netz',
|
||||
'BatterieleistungArchiv' => 'Batterie',
|
||||
] as $ident => $quelle) {
|
||||
$this->SetValue($ident, $leistungen[$quelle]);
|
||||
}
|
||||
$this->WriteAttributeInteger('MessungLetzteLeistungsaufzeichnung', $jetzt);
|
||||
}
|
||||
|
||||
$energieintervall = $this->ReadPropertyInteger('EnergieaufzeichnungMinuten') * 60;
|
||||
$letzteEnergie = $this->ReadAttributeInteger('MessungLetzteEnergieaufzeichnung');
|
||||
if ($letzteEnergie === 0) {
|
||||
$this->WriteAttributeInteger('MessungLetzteEnergieaufzeichnung', $jetzt);
|
||||
} elseif ($jetzt - $letzteEnergie >= $energieintervall) {
|
||||
foreach (self::ENERGIE_VARIABLEN as $ident) {
|
||||
$this->SetValue($ident, (float) $this->GetValue($ident) + (float) ($puffer[$ident] ?? 0.0));
|
||||
}
|
||||
$this->WriteAttributeString('MessungEnergiePuffer', '{}');
|
||||
$this->WriteAttributeInteger('MessungLetzteEnergieaufzeichnung', $jetzt);
|
||||
}
|
||||
|
||||
$funFactsID = @IPS_GetObjectIDByIdent('EnergieFunFacts', $this->InstanceID);
|
||||
if (is_int($funFactsID) && $funFactsID > 0 && IPS_VariableExists($funFactsID)) {
|
||||
$this->aktualisiereFunFacts($funFactsID);
|
||||
}
|
||||
}
|
||||
|
||||
private function istNumerischeVariable(int $variableID): bool
|
||||
{
|
||||
if ($variableID <= 0 || !IPS_VariableExists($variableID)) {
|
||||
return false;
|
||||
}
|
||||
$variable = IPS_GetVariable($variableID);
|
||||
|
||||
return in_array((int) ($variable['VariableType'] ?? -1), [1, 2], true);
|
||||
}
|
||||
|
||||
/** @param bool|float|int|string $wert */
|
||||
private function setzeDiagnosewert(string $ident, $wert): void
|
||||
{
|
||||
@@ -1929,6 +2277,22 @@ class Manager extends IPSModule implements ManagerSchnittstelle
|
||||
throw new InvalidArgumentException($property . ' muss groesser als 0 sein.');
|
||||
}
|
||||
}
|
||||
foreach (['LeistungsaufzeichnungMinuten', 'EnergieaufzeichnungMinuten'] as $property) {
|
||||
$wert = $this->ReadPropertyInteger($property);
|
||||
if (!in_array($wert, [1, 5, 60], true)) {
|
||||
throw new InvalidArgumentException($property . ' muss 1, 5 oder 60 Minuten betragen.');
|
||||
}
|
||||
}
|
||||
foreach (['LeistungLoeschenMonate', 'EnergieVerdichtenMonate', 'EnergieLoeschenMonate'] as $property) {
|
||||
if ($this->ReadPropertyInteger($property) < 0) {
|
||||
throw new InvalidArgumentException($property . ' darf nicht negativ sein.');
|
||||
}
|
||||
}
|
||||
$energieLoeschen = $this->ReadPropertyInteger('EnergieLoeschenMonate');
|
||||
$energieVerdichten = $this->ReadPropertyInteger('EnergieVerdichtenMonate');
|
||||
if ($energieLoeschen > 0 && $energieLoeschen <= $energieVerdichten) {
|
||||
throw new InvalidArgumentException('Energie darf erst nach der taeglichen Verdichtung geloescht werden.');
|
||||
}
|
||||
$intervall = $this->ReadPropertyInteger('PrognoseSendeintervall');
|
||||
if ($intervall < 60 || $intervall > 3600) {
|
||||
throw new InvalidArgumentException('PrognoseSendeintervall muss zwischen 60 und 3600 Sekunden liegen.');
|
||||
@@ -1937,25 +2301,6 @@ class Manager extends IPSModule implements ManagerSchnittstelle
|
||||
if ($stoerungsIntervall < 60 || $stoerungsIntervall > 3600) {
|
||||
throw new InvalidArgumentException('StoerungsSendeintervall muss zwischen 60 und 3600 Sekunden liegen.');
|
||||
}
|
||||
$telemetrieIDs = [
|
||||
$this->ReadPropertyInteger('PrognosePVVariableID'),
|
||||
$this->ReadPropertyInteger('PrognoseHausverbrauchVariableID'),
|
||||
$this->ReadPropertyInteger('PrognoseSOCVariableID'),
|
||||
];
|
||||
$konfigurierteIDs = array_values(array_filter($telemetrieIDs, static fn (int $id): bool => $id > 0));
|
||||
if ($konfigurierteIDs !== [] && count($konfigurierteIDs) !== count($telemetrieIDs)) {
|
||||
throw new InvalidArgumentException('Fuer die Prognose muessen PV, Hausverbrauch und SOC gemeinsam konfiguriert sein.');
|
||||
}
|
||||
foreach ($konfigurierteIDs as $variableID) {
|
||||
if (!IPS_VariableExists($variableID)) {
|
||||
throw new InvalidArgumentException('Eine konfigurierte Prognosevariable existiert nicht.');
|
||||
}
|
||||
}
|
||||
if ($konfigurierteIDs !== []
|
||||
&& !IPS_VariableExists($this->ReadPropertyInteger('NetzleistungVariableID'))
|
||||
) {
|
||||
throw new InvalidArgumentException('Die Netzleistungsvariable fuer die Prognose existiert nicht.');
|
||||
}
|
||||
if ($this->ReadPropertyFloat('Umschaltdifferenz') < 0
|
||||
|| $this->ReadPropertyFloat('Umschaltdifferenz') > 100
|
||||
) {
|
||||
@@ -1981,6 +2326,9 @@ class Manager extends IPSModule implements ManagerSchnittstelle
|
||||
$this->leseAktiveVerbraucherIDs('AutomatischeVerbraucherZuordnung');
|
||||
$this->dekodiereMonatsgrenzen($this->ReadPropertyString('Monatsgrenzen'));
|
||||
$this->leseAnlagentopologie();
|
||||
$this->pvMessquellen();
|
||||
$this->batterieMessquellen();
|
||||
$this->batterieSocQuellen();
|
||||
foreach (['PrognoseAnschluss', 'SDLAnschluss'] as $property) {
|
||||
$wert = json_decode($this->ReadPropertyString($property), true, 512, JSON_THROW_ON_ERROR);
|
||||
if (!is_array($wert)) {
|
||||
@@ -2011,9 +2359,8 @@ class Manager extends IPSModule implements ManagerSchnittstelle
|
||||
|
||||
private function prognoseTelemetrieKonfiguriert(): bool
|
||||
{
|
||||
return $this->ReadPropertyInteger('PrognosePVVariableID') > 0
|
||||
&& $this->ReadPropertyInteger('PrognoseHausverbrauchVariableID') > 0
|
||||
&& $this->ReadPropertyInteger('PrognoseSOCVariableID') > 0;
|
||||
return $this->messquellenKonfiguriert()
|
||||
&& $this->aktuelleBatterieSoc() !== null;
|
||||
}
|
||||
|
||||
private function aktuelleNetzfahrplanVorgabe(): ?float
|
||||
@@ -2158,11 +2505,16 @@ class Manager extends IPSModule implements ManagerSchnittstelle
|
||||
throw new RuntimeException('Der Geraetezugang fuer die Prognose fehlt.');
|
||||
}
|
||||
|
||||
$leistungen = $this->leseMessleistungen();
|
||||
$soc = $this->aktuelleBatterieSoc();
|
||||
if ($soc === null) {
|
||||
throw new RuntimeException('Der Batterie-Ladezustand ist nicht verfuegbar.');
|
||||
}
|
||||
$werte = [
|
||||
'PV' => GetValue($this->ReadPropertyInteger('PrognosePVVariableID')),
|
||||
'Hausverbrauch' => GetValue($this->ReadPropertyInteger('PrognoseHausverbrauchVariableID')),
|
||||
'Netzleistung' => GetValue($this->ReadPropertyInteger('NetzleistungVariableID')),
|
||||
'SOC' => GetValue($this->ReadPropertyInteger('PrognoseSOCVariableID')),
|
||||
'PV' => $leistungen['PV'],
|
||||
'Hausverbrauch' => $leistungen['Haus'],
|
||||
'Netzleistung' => $leistungen['Netz'],
|
||||
'SOC' => $soc,
|
||||
];
|
||||
foreach ($werte as $name => $wert) {
|
||||
if (!is_int($wert) && !is_float($wert)) {
|
||||
@@ -2173,7 +2525,6 @@ class Manager extends IPSModule implements ManagerSchnittstelle
|
||||
throw new RuntimeException('Der Prognosewert ' . $name . ' ist nicht endlich.');
|
||||
}
|
||||
}
|
||||
$werte['Netzleistung'] *= $this->ReadPropertyFloat('Netzleistungsfaktor');
|
||||
if ($werte['PV'] < 0.0 || $werte['Hausverbrauch'] < 0.0
|
||||
|| $werte['SOC'] < 0.0 || $werte['SOC'] > 100.0
|
||||
) {
|
||||
|
||||
Reference in New Issue
Block a user