feat(manager): Energieaufzeichnung und Visualisierungen integrieren
Tests / test (push) Successful in 47s
Tests / test (push) Successful in 47s
This commit is contained in:
+126
-13
@@ -321,6 +321,13 @@
|
|||||||
"add": 0,
|
"add": 0,
|
||||||
"edit": {"type": "SelectVariable"}
|
"edit": {"type": "SelectVariable"}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"caption": "Messfaktor",
|
||||||
|
"name": "Istleistungsfaktor",
|
||||||
|
"width": "110px",
|
||||||
|
"add": 1.0,
|
||||||
|
"edit": {"type": "NumberSpinner", "digits": 4}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"caption": "PV-Begrenzung",
|
"caption": "PV-Begrenzung",
|
||||||
"name": "BegrenzungVariableID",
|
"name": "BegrenzungVariableID",
|
||||||
@@ -534,6 +541,27 @@
|
|||||||
{"caption": "Hybrid", "value": "hybrid"}
|
{"caption": "Hybrid", "value": "hybrid"}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"caption": "Leistung",
|
||||||
|
"name": "LeistungVariableID",
|
||||||
|
"width": "140px",
|
||||||
|
"add": 0,
|
||||||
|
"edit": {"type": "SelectVariable"}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"caption": "Messfaktor",
|
||||||
|
"name": "Leistungsfaktor",
|
||||||
|
"width": "110px",
|
||||||
|
"add": 1.0,
|
||||||
|
"edit": {"type": "NumberSpinner", "digits": 4}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"caption": "Ladezustand",
|
||||||
|
"name": "SOCVariableID",
|
||||||
|
"width": "140px",
|
||||||
|
"add": 0,
|
||||||
|
"edit": {"type": "SelectVariable"}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -591,24 +619,109 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "ExpansionPanel",
|
||||||
|
"caption": "Energieaufzeichnung",
|
||||||
|
"expanded": true,
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"type": "CheckBox",
|
||||||
|
"name": "EnergieaufzeichnungAktiv",
|
||||||
|
"caption": "Leistungs- und Energiewerte automatisch aufzeichnen"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Label",
|
||||||
|
"caption": "PV und Batterie werden aus der Anlagentopologie summiert. Der Hausverbrauch wird aus PV + Netz - Batterie berechnet. Netz: positiv Bezug; Batterie: positiv Laden."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "CheckBox",
|
||||||
|
"name": "MesswerteAnzeigen",
|
||||||
|
"caption": "Einzelne Leistungs- und Energiewerte unter dem Manager anzeigen"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "RowLayout",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"type": "Select",
|
||||||
|
"name": "LeistungsaufzeichnungMinuten",
|
||||||
|
"caption": "Leistung verdichten",
|
||||||
|
"options": [
|
||||||
|
{"caption": "1 Minute", "value": 1},
|
||||||
|
{"caption": "5 Minuten", "value": 5},
|
||||||
|
{"caption": "1 Stunde", "value": 60}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "NumberSpinner",
|
||||||
|
"name": "LeistungLoeschenMonate",
|
||||||
|
"caption": "Leistung loeschen nach",
|
||||||
|
"minimum": 0,
|
||||||
|
"suffix": " Monaten (0 = nie)"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "RowLayout",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"type": "Select",
|
||||||
|
"name": "EnergieaufzeichnungMinuten",
|
||||||
|
"caption": "Energie verdichten",
|
||||||
|
"options": [
|
||||||
|
{"caption": "1 Minute", "value": 1},
|
||||||
|
{"caption": "5 Minuten", "value": 5},
|
||||||
|
{"caption": "1 Stunde", "value": 60}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "NumberSpinner",
|
||||||
|
"name": "EnergieVerdichtenMonate",
|
||||||
|
"caption": "Auf Tageswerte verdichten nach",
|
||||||
|
"minimum": 0,
|
||||||
|
"suffix": " Monaten"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "NumberSpinner",
|
||||||
|
"name": "EnergieLoeschenMonate",
|
||||||
|
"caption": "Energie loeschen nach",
|
||||||
|
"minimum": 0,
|
||||||
|
"suffix": " Monaten (0 = nie)"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "CheckBox",
|
||||||
|
"name": "EnergyPieAnzeigen",
|
||||||
|
"caption": "Energy Pie unter dem Manager anzeigen",
|
||||||
|
"onChange": "IPS_RequestAction($id, 'FormEnergyPieAnzeigen', $EnergyPieAnzeigen);"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "CheckBox",
|
||||||
|
"name": "EnergiediagrammeAnzeigen",
|
||||||
|
"caption": "Leistungs- und Energiediagramm unter dem Manager anzeigen",
|
||||||
|
"onChange": "IPS_RequestAction($id, 'FormEnergiediagrammeAnzeigen', $EnergiediagrammeAnzeigen);"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "CheckBox",
|
||||||
|
"name": "FunFactsAnzeigen",
|
||||||
|
"caption": "Energy Facts unter dem Manager anzeigen",
|
||||||
|
"onChange": "IPS_RequestAction($id, 'FormFunFactsAnzeigen', $FunFactsAnzeigen);"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "CheckBox",
|
||||||
|
"name": "EnergieflussAnzeigen",
|
||||||
|
"caption": "Energiefluss unter dem Manager anzeigen",
|
||||||
|
"onChange": "IPS_RequestAction($id, 'FormEnergieflussAnzeigen', $EnergieflussAnzeigen);"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "ExpansionPanel",
|
"type": "ExpansionPanel",
|
||||||
"caption": "Prognose / Forecast",
|
"caption": "Prognose / Forecast",
|
||||||
"items": [
|
"items": [
|
||||||
{
|
{
|
||||||
"type": "SelectVariable",
|
"type": "Label",
|
||||||
"name": "PrognosePVVariableID",
|
"caption": "Forecast verwendet dieselben Messquellen aus der Anlagentopologie und den daraus berechneten Hausverbrauch."
|
||||||
"caption": "PV-Leistung"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "SelectVariable",
|
|
||||||
"name": "PrognoseHausverbrauchVariableID",
|
|
||||||
"caption": "Hausverbrauch"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "SelectVariable",
|
|
||||||
"name": "PrognoseSOCVariableID",
|
|
||||||
"caption": "Batterie-Ladezustand"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "NumberSpinner",
|
"type": "NumberSpinner",
|
||||||
|
|||||||
+378
-27
@@ -9,9 +9,13 @@ require_once __DIR__ . '/../libs/EinspeiseRegler.php';
|
|||||||
require_once __DIR__ . '/../libs/Lizenzpruefung.php';
|
require_once __DIR__ . '/../libs/Lizenzpruefung.php';
|
||||||
require_once __DIR__ . '/../libs/Anlagentopologie.php';
|
require_once __DIR__ . '/../libs/Anlagentopologie.php';
|
||||||
require_once __DIR__ . '/../libs/StoerungsSnapshot.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\Anlagentopologie;
|
||||||
use Belevo\EnelixEMS\EinspeiseRegler;
|
use Belevo\EnelixEMS\EinspeiseRegler;
|
||||||
|
use Belevo\EnelixEMS\EnergieMessung;
|
||||||
|
use Belevo\EnelixEMS\ManagerEnergieTrait;
|
||||||
use Belevo\EnelixEMS\Lizenzpruefung;
|
use Belevo\EnelixEMS\Lizenzpruefung;
|
||||||
use Belevo\EnelixEMS\ManagerRegler;
|
use Belevo\EnelixEMS\ManagerRegler;
|
||||||
use Belevo\EnelixEMS\ManagerSchnittstelle;
|
use Belevo\EnelixEMS\ManagerSchnittstelle;
|
||||||
@@ -20,6 +24,8 @@ use Belevo\EnelixEMS\StoerungsSnapshot;
|
|||||||
|
|
||||||
class Manager extends IPSModule implements ManagerSchnittstelle
|
class Manager extends IPSModule implements ManagerSchnittstelle
|
||||||
{
|
{
|
||||||
|
use ManagerEnergieTrait;
|
||||||
|
|
||||||
private const STATUS_AKTIV = 102;
|
private const STATUS_AKTIV = 102;
|
||||||
private const STATUS_MESSWERT_UNGUELTIG = 201;
|
private const STATUS_MESSWERT_UNGUELTIG = 201;
|
||||||
private const STATUS_KONFIGURATION_UNGUELTIG = 202;
|
private const STATUS_KONFIGURATION_UNGUELTIG = 202;
|
||||||
@@ -32,6 +38,23 @@ class Manager extends IPSModule implements ManagerSchnittstelle
|
|||||||
private const LIZENZ_RETRY_SEKUNDEN = 3600;
|
private const LIZENZ_RETRY_SEKUNDEN = 3600;
|
||||||
private const STOERUNGS_RETRY_MAX_SEKUNDEN = 900;
|
private const STOERUNGS_RETRY_MAX_SEKUNDEN = 900;
|
||||||
private const VM_UPDATE = 10603;
|
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 = [
|
private const VERBRAUCHER_LIZENZEN = [
|
||||||
'{B7C54AF4-AD7D-4FE4-B75D-203693906251}' => [
|
'{B7C54AF4-AD7D-4FE4-B75D-203693906251}' => [
|
||||||
'Katalog' => 'boiler_multi',
|
'Katalog' => 'boiler_multi',
|
||||||
@@ -112,6 +135,7 @@ class Manager extends IPSModule implements ManagerSchnittstelle
|
|||||||
$this->RegisterPropertyString('AnlagenWechselrichter', '[]');
|
$this->RegisterPropertyString('AnlagenWechselrichter', '[]');
|
||||||
$this->RegisterPropertyString('AnlagenPVFlaechen', '[]');
|
$this->RegisterPropertyString('AnlagenPVFlaechen', '[]');
|
||||||
$this->RegisterPropertyString('AnlagenBatterien', '[]');
|
$this->RegisterPropertyString('AnlagenBatterien', '[]');
|
||||||
|
// Verdeckte Altproperties; Messquellen liegen neu ausschliesslich in der Anlagentopologie.
|
||||||
$this->RegisterPropertyInteger('PrognosePVVariableID', 0);
|
$this->RegisterPropertyInteger('PrognosePVVariableID', 0);
|
||||||
$this->RegisterPropertyInteger('PrognoseHausverbrauchVariableID', 0);
|
$this->RegisterPropertyInteger('PrognoseHausverbrauchVariableID', 0);
|
||||||
$this->RegisterPropertyInteger('PrognoseSOCVariableID', 0);
|
$this->RegisterPropertyInteger('PrognoseSOCVariableID', 0);
|
||||||
@@ -127,6 +151,24 @@ class Manager extends IPSModule implements ManagerSchnittstelle
|
|||||||
$this->RegisterPropertyInteger('StoerungsSendeintervall', 300);
|
$this->RegisterPropertyInteger('StoerungsSendeintervall', 300);
|
||||||
$this->RegisterPropertyBoolean('DiagnosevariablenAnzeigen', false);
|
$this->RegisterPropertyBoolean('DiagnosevariablenAnzeigen', false);
|
||||||
$this->RegisterPropertyBoolean('LoggingEin', 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->RegisterVariableBoolean('Aktiv', 'Aktiv', '~Switch', 10);
|
||||||
$this->EnableAction('Aktiv');
|
$this->EnableAction('Aktiv');
|
||||||
@@ -150,6 +192,10 @@ class Manager extends IPSModule implements ManagerSchnittstelle
|
|||||||
$this->RegisterAttributeInteger('StoerungsLetzterErfolg', 0);
|
$this->RegisterAttributeInteger('StoerungsLetzterErfolg', 0);
|
||||||
$this->RegisterAttributeInteger('StoerungsNaechsterVersuch', 0);
|
$this->RegisterAttributeInteger('StoerungsNaechsterVersuch', 0);
|
||||||
$this->RegisterAttributeInteger('StoerungsFehlversuche', 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.
|
// Die alten Timer bleiben deaktiviert registriert, damit bestehende Instanzen sauber migrieren.
|
||||||
$this->RegisterTimer('Regelzyklus', 0, '');
|
$this->RegisterTimer('Regelzyklus', 0, '');
|
||||||
@@ -169,6 +215,11 @@ class Manager extends IPSModule implements ManagerSchnittstelle
|
|||||||
0,
|
0,
|
||||||
"IPS_RequestAction(\$_IPS['TARGET'], 'StoerungenSenden', true);"
|
"IPS_RequestAction(\$_IPS['TARGET'], 'StoerungenSenden', true);"
|
||||||
);
|
);
|
||||||
|
$this->RegisterTimer(
|
||||||
|
'MesswerteAufzeichnen',
|
||||||
|
0,
|
||||||
|
"IPS_RequestAction(\$_IPS['TARGET'], 'MesswerteAufzeichnen', true);"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function ApplyChanges(): void
|
public function ApplyChanges(): void
|
||||||
@@ -180,6 +231,8 @@ class Manager extends IPSModule implements ManagerSchnittstelle
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
$this->pruefeKonfiguration();
|
$this->pruefeKonfiguration();
|
||||||
|
$this->konfiguriereEnergiearchiv();
|
||||||
|
$this->synchronisiereEnergievisualisierungen();
|
||||||
} catch (Throwable $fehler) {
|
} catch (Throwable $fehler) {
|
||||||
$this->SetStatus(self::STATUS_KONFIGURATION_UNGUELTIG);
|
$this->SetStatus(self::STATUS_KONFIGURATION_UNGUELTIG);
|
||||||
$this->SetTimerInterval('Regelzyklus', 0);
|
$this->SetTimerInterval('Regelzyklus', 0);
|
||||||
@@ -187,6 +240,7 @@ class Manager extends IPSModule implements ManagerSchnittstelle
|
|||||||
$this->SetTimerInterval('KeepAlive', 0);
|
$this->SetTimerInterval('KeepAlive', 0);
|
||||||
$this->SetTimerInterval('PrognoseSenden', 0);
|
$this->SetTimerInterval('PrognoseSenden', 0);
|
||||||
$this->SetTimerInterval('StoerungenSenden', 0);
|
$this->SetTimerInterval('StoerungenSenden', 0);
|
||||||
|
$this->SetTimerInterval('MesswerteAufzeichnen', 0);
|
||||||
$this->protokolliere('Konfiguration', $fehler->getMessage());
|
$this->protokolliere('Konfiguration', $fehler->getMessage());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -200,6 +254,11 @@ class Manager extends IPSModule implements ManagerSchnittstelle
|
|||||||
'StoerungenSenden',
|
'StoerungenSenden',
|
||||||
$this->ReadPropertyBoolean('StoerueberwachungAktiv') ? 30000 : 0
|
$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->setzeDiagnosewert('Monatsgrenzen', $this->ReadPropertyString('Monatsgrenzen'));
|
||||||
$this->aktualisiereAnschlussstatus();
|
$this->aktualisiereAnschlussstatus();
|
||||||
$geraetezugangFehlt = ($this->hatAnlagentopologie()
|
$geraetezugangFehlt = ($this->hatAnlagentopologie()
|
||||||
@@ -378,6 +437,26 @@ class Manager extends IPSModule implements ManagerSchnittstelle
|
|||||||
$this->sendeStoerungsSnapshot(false);
|
$this->sendeStoerungsSnapshot(false);
|
||||||
return;
|
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':
|
case 'FormLizenzPruefen':
|
||||||
if (!is_string($wert)) {
|
if (!is_string($wert)) {
|
||||||
throw new InvalidArgumentException('Der Lizenzcode muss als Text uebergeben werden.');
|
throw new InvalidArgumentException('Der Lizenzcode muss als Text uebergeben werden.');
|
||||||
@@ -1430,6 +1509,32 @@ class Manager extends IPSModule implements ManagerSchnittstelle
|
|||||||
|
|
||||||
private function aktualisiereVariablen(): void
|
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')) {
|
if ($this->ReadPropertyBoolean('DiagnosevariablenAnzeigen')) {
|
||||||
$this->RegisterVariableBoolean(
|
$this->RegisterVariableBoolean(
|
||||||
'NetzleistungGueltig',
|
'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 */
|
/** @param bool|float|int|string $wert */
|
||||||
private function setzeDiagnosewert(string $ident, $wert): void
|
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.');
|
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');
|
$intervall = $this->ReadPropertyInteger('PrognoseSendeintervall');
|
||||||
if ($intervall < 60 || $intervall > 3600) {
|
if ($intervall < 60 || $intervall > 3600) {
|
||||||
throw new InvalidArgumentException('PrognoseSendeintervall muss zwischen 60 und 3600 Sekunden liegen.');
|
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) {
|
if ($stoerungsIntervall < 60 || $stoerungsIntervall > 3600) {
|
||||||
throw new InvalidArgumentException('StoerungsSendeintervall muss zwischen 60 und 3600 Sekunden liegen.');
|
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
|
if ($this->ReadPropertyFloat('Umschaltdifferenz') < 0
|
||||||
|| $this->ReadPropertyFloat('Umschaltdifferenz') > 100
|
|| $this->ReadPropertyFloat('Umschaltdifferenz') > 100
|
||||||
) {
|
) {
|
||||||
@@ -1981,6 +2326,9 @@ class Manager extends IPSModule implements ManagerSchnittstelle
|
|||||||
$this->leseAktiveVerbraucherIDs('AutomatischeVerbraucherZuordnung');
|
$this->leseAktiveVerbraucherIDs('AutomatischeVerbraucherZuordnung');
|
||||||
$this->dekodiereMonatsgrenzen($this->ReadPropertyString('Monatsgrenzen'));
|
$this->dekodiereMonatsgrenzen($this->ReadPropertyString('Monatsgrenzen'));
|
||||||
$this->leseAnlagentopologie();
|
$this->leseAnlagentopologie();
|
||||||
|
$this->pvMessquellen();
|
||||||
|
$this->batterieMessquellen();
|
||||||
|
$this->batterieSocQuellen();
|
||||||
foreach (['PrognoseAnschluss', 'SDLAnschluss'] as $property) {
|
foreach (['PrognoseAnschluss', 'SDLAnschluss'] as $property) {
|
||||||
$wert = json_decode($this->ReadPropertyString($property), true, 512, JSON_THROW_ON_ERROR);
|
$wert = json_decode($this->ReadPropertyString($property), true, 512, JSON_THROW_ON_ERROR);
|
||||||
if (!is_array($wert)) {
|
if (!is_array($wert)) {
|
||||||
@@ -2011,9 +2359,8 @@ class Manager extends IPSModule implements ManagerSchnittstelle
|
|||||||
|
|
||||||
private function prognoseTelemetrieKonfiguriert(): bool
|
private function prognoseTelemetrieKonfiguriert(): bool
|
||||||
{
|
{
|
||||||
return $this->ReadPropertyInteger('PrognosePVVariableID') > 0
|
return $this->messquellenKonfiguriert()
|
||||||
&& $this->ReadPropertyInteger('PrognoseHausverbrauchVariableID') > 0
|
&& $this->aktuelleBatterieSoc() !== null;
|
||||||
&& $this->ReadPropertyInteger('PrognoseSOCVariableID') > 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function aktuelleNetzfahrplanVorgabe(): ?float
|
private function aktuelleNetzfahrplanVorgabe(): ?float
|
||||||
@@ -2158,11 +2505,16 @@ class Manager extends IPSModule implements ManagerSchnittstelle
|
|||||||
throw new RuntimeException('Der Geraetezugang fuer die Prognose fehlt.');
|
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 = [
|
$werte = [
|
||||||
'PV' => GetValue($this->ReadPropertyInteger('PrognosePVVariableID')),
|
'PV' => $leistungen['PV'],
|
||||||
'Hausverbrauch' => GetValue($this->ReadPropertyInteger('PrognoseHausverbrauchVariableID')),
|
'Hausverbrauch' => $leistungen['Haus'],
|
||||||
'Netzleistung' => GetValue($this->ReadPropertyInteger('NetzleistungVariableID')),
|
'Netzleistung' => $leistungen['Netz'],
|
||||||
'SOC' => GetValue($this->ReadPropertyInteger('PrognoseSOCVariableID')),
|
'SOC' => $soc,
|
||||||
];
|
];
|
||||||
foreach ($werte as $name => $wert) {
|
foreach ($werte as $name => $wert) {
|
||||||
if (!is_int($wert) && !is_float($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.');
|
throw new RuntimeException('Der Prognosewert ' . $name . ' ist nicht endlich.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$werte['Netzleistung'] *= $this->ReadPropertyFloat('Netzleistungsfaktor');
|
|
||||||
if ($werte['PV'] < 0.0 || $werte['Hausverbrauch'] < 0.0
|
if ($werte['PV'] < 0.0 || $werte['Hausverbrauch'] < 0.0
|
||||||
|| $werte['SOC'] < 0.0 || $werte['SOC'] > 100.0
|
|| $werte['SOC'] < 0.0 || $werte['SOC'] > 100.0
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -8,13 +8,23 @@ kommen aus den Verbrauchermeldungen.
|
|||||||
|
|
||||||
## Variablen
|
## Variablen
|
||||||
|
|
||||||
Ohne angelegte Diagnosevariablen existieren nur `Aktiv`, `Betriebsart` und `Netzleistung`.
|
Die drei Regelungsvariablen `Aktiv`, `Betriebsart` und `Netzleistung` sind immer sichtbar. Die vom Manager gefuehrten Mess- und Energievariablen werden immer angelegt und archiviert, koennen aber gemeinsam ausgeblendet werden.
|
||||||
|
|
||||||
| Ident | Typ / Zugriff | Beschreibung |
|
| Ident | Typ / Zugriff | Beschreibung |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| `Aktiv` | Boolean / bedienbar | Regelung ein/aus; Start `false`. |
|
| `Aktiv` | Boolean / bedienbar | Regelung ein/aus; Start `false`. |
|
||||||
| `Betriebsart` | String / Anzeige | `Inaktiv`, `PV` oder `Peak`. |
|
| `Betriebsart` | String / Anzeige | `Inaktiv`, `PV` oder `Peak`. |
|
||||||
| `Netzleistung` | Float / Anzeige | Aktuelle Netzleistung in W; positiv Bezug, negativ Einspeisung. |
|
| `Netzleistung` | Float / Anzeige | Aktuelle Netzleistung in W; positiv Bezug, negativ Einspeisung. |
|
||||||
|
| `PVLeistungArchiv` | Float / Logging | Normalisierte PV-Leistung in W. |
|
||||||
|
| `HausverbrauchLeistungArchiv` | Float / Logging | Normalisierter Hausverbrauch in W. |
|
||||||
|
| `NetzleistungArchiv` | Float / Logging | Normalisierte Netzleistung in W; positiv Bezug, negativ Einspeisung. |
|
||||||
|
| `BatterieleistungArchiv` | Float / Logging | Batterieleistung in W; positiv Laden, negativ Entladen. |
|
||||||
|
| `PVEnergie` | Float / Zaehler | Integrierte PV-Erzeugung in kWh. |
|
||||||
|
| `Hausenergie` | Float / Zaehler | Integrierter Hausverbrauch in kWh. |
|
||||||
|
| `NetzbezugEnergie` | Float / Zaehler | Integrierter Netzbezug in kWh. |
|
||||||
|
| `EinspeisungEnergie` | Float / Zaehler | Integrierte Einspeisung in kWh. |
|
||||||
|
| `BatterieLadenEnergie` | Float / Zaehler | Integrierte Batterieladung in kWh. |
|
||||||
|
| `BatterieEntladenEnergie` | Float / Zaehler | Integrierte Batterieentladung in kWh. |
|
||||||
| `NetzleistungGueltig` | Boolean / Logging | Messquelle vorhanden und aktuell. |
|
| `NetzleistungGueltig` | Boolean / Logging | Messquelle vorhanden und aktuell. |
|
||||||
| `WirksameLastspitzengrenze` | Float / Logging | Aktuelle feste oder monatliche Bezugsgrenze in W. |
|
| `WirksameLastspitzengrenze` | Float / Logging | Aktuelle feste oder monatliche Bezugsgrenze in W. |
|
||||||
| `WirksameEinspeisegrenze` | Float / Logging | Anlagenweite Einspeisegrenze in W. |
|
| `WirksameEinspeisegrenze` | Float / Logging | Anlagenweite Einspeisegrenze in W. |
|
||||||
@@ -54,12 +64,9 @@ Ohne angelegte Diagnosevariablen existieren nur `Aktiv`, `Betriebsart` und `Netz
|
|||||||
| `Einspeisegrenze` | Float | Maximale Einspeisung der Gesamtanlage in W; `0` bedeutet Nulleinspeisung. |
|
| `Einspeisegrenze` | Float | Maximale Einspeisung der Gesamtanlage in W; `0` bedeutet Nulleinspeisung. |
|
||||||
| `Einspeisetoleranz` | Float | `100` W; Totband gegen pendelnde Wechselrichtervorgaben. |
|
| `Einspeisetoleranz` | Float | `100` W; Totband gegen pendelnde Wechselrichtervorgaben. |
|
||||||
| `NetzfahrplanAktiv` | Boolean | `false`; lizenzierten, tarif- und prognosebasierten Netzzielwert verwenden. |
|
| `NetzfahrplanAktiv` | Boolean | `false`; lizenzierten, tarif- und prognosebasierten Netzzielwert verwenden. |
|
||||||
| `AnlagenWechselrichter` | String/JSON | `[]`; Wechselrichter mit Typ, AC-Grenzen sowie optionalen Istleistungs- und Begrenzungsregistern. |
|
| `AnlagenWechselrichter` | String/JSON | `[]`; Wechselrichter mit Typ, AC-Grenzen, Istleistungsmessung und optionalem Begrenzungsregister. |
|
||||||
| `AnlagenPVFlaechen` | String/JSON | `[]`; PV-Flaechen mit DC-Leistung, Ausrichtung und Wechselrichter-ID. |
|
| `AnlagenPVFlaechen` | String/JSON | `[]`; PV-Flaechen mit DC-Leistung, Ausrichtung und Wechselrichter-ID. |
|
||||||
| `AnlagenBatterien` | String/JSON | `[]`; Batteriespeicher mit Kapazitaeten, Leistungen, Kopplung und Wechselrichter-ID. |
|
| `AnlagenBatterien` | String/JSON | `[]`; Batteriespeicher mit Kapazitaeten, Kopplung, Leistungs- und SOC-Messung. |
|
||||||
| `PrognosePVVariableID` | Integer | `0`; PV-Leistung in W fuer die Prognosetelemetrie. |
|
|
||||||
| `PrognoseHausverbrauchVariableID` | Integer | `0`; Hausverbrauch in W fuer die Prognosetelemetrie. |
|
|
||||||
| `PrognoseSOCVariableID` | Integer | `0`; Batterie-Ladezustand in Prozent. |
|
|
||||||
| `PrognoseSendeintervall` | Integer | `60` s; zulaessig sind 60 bis 3600 Sekunden. |
|
| `PrognoseSendeintervall` | Integer | `60` s; zulaessig sind 60 bis 3600 Sekunden. |
|
||||||
| `PrognoseAnschluss` | String/JSON | Verdeckte Altproperty fuer bestehende Konfigurationen. |
|
| `PrognoseAnschluss` | String/JSON | Verdeckte Altproperty fuer bestehende Konfigurationen. |
|
||||||
| `SDLAnschluss` | String/JSON | Optionaler SDL/VGT-Anschluss. |
|
| `SDLAnschluss` | String/JSON | Optionaler SDL/VGT-Anschluss. |
|
||||||
@@ -67,6 +74,17 @@ Ohne angelegte Diagnosevariablen existieren nur `Aktiv`, `Betriebsart` und `Netz
|
|||||||
| `StoermeldeAnschluss` | String/JSON | Optionaler Anschluss zur Störüberwachung. |
|
| `StoermeldeAnschluss` | String/JSON | Optionaler Anschluss zur Störüberwachung. |
|
||||||
| `DiagnosevariablenAnzeigen` | Boolean | `false`; zusätzliche Diagnosevariablen anlegen oder entfernen. |
|
| `DiagnosevariablenAnzeigen` | Boolean | `false`; zusätzliche Diagnosevariablen anlegen oder entfernen. |
|
||||||
| `LoggingEin` | Boolean | `false`; laufende Meldungen im Debug-Fenster ausgeben. |
|
| `LoggingEin` | Boolean | `false`; laufende Meldungen im Debug-Fenster ausgeben. |
|
||||||
|
| `EnergieaufzeichnungAktiv` | Boolean | `true`; managergefuehrte Leistungs- und Energieaufzeichnung aktivieren. |
|
||||||
|
| `MesswerteAnzeigen` | Boolean | `false`; einzelne Manager-Messwerte in der Objektstruktur anzeigen. |
|
||||||
|
| `EnergyPieAnzeigen` | Boolean | `false`; Energy Pie unter dem Manager anlegen oder entfernen. |
|
||||||
|
| `EnergiediagrammeAnzeigen` | Boolean | `false`; native Leistungs- und Energiediagramme anlegen oder entfernen. |
|
||||||
|
| `FunFactsAnzeigen` | Boolean | `false`; responsive Energy Facts anlegen oder entfernen. |
|
||||||
|
| `EnergieflussAnzeigen` | Boolean | `false`; native Energy Distribution anlegen oder entfernen. |
|
||||||
|
| `LeistungsaufzeichnungMinuten` | Auswahl | `1`; Verdichtung auf 1 Minute, 5 Minuten oder 1 Stunde. |
|
||||||
|
| `LeistungLoeschenMonate` | Integer | `12`; Leistungswerte nach zwoelf Monaten loeschen, `0` deaktiviert die Loeschung. |
|
||||||
|
| `EnergieaufzeichnungMinuten` | Auswahl | `5`; Verdichtung auf 1 Minute, 5 Minuten oder 1 Stunde. |
|
||||||
|
| `EnergieVerdichtenMonate` | Integer | `12`; Energiezaehler nach zwoelf Monaten auf Tageswerte verdichten. |
|
||||||
|
| `EnergieLoeschenMonate` | Integer | `0`; Energiezaehler nicht loeschen. |
|
||||||
|
|
||||||
Es gibt keine Sollwertquellenauswahl und keine zweite Prioritätseinstellung im
|
Es gibt keine Sollwertquellenauswahl und keine zweite Prioritätseinstellung im
|
||||||
Manager. Im Modus `Aus` bleibt Solarladen aktiv, nur die Peak-Begrenzung entfällt.
|
Manager. Im Modus `Aus` bleibt Solarladen aktiv, nur die Peak-Begrenzung entfällt.
|
||||||
@@ -74,6 +92,40 @@ Die Monatsgrenzen werden in den Grundeinstellungen über eine Schaltfläche
|
|||||||
ein- und ausgeblendet. Die automatische Verbrauchersuche kann dort erneut
|
ein- und ausgeblendet. Die automatische Verbrauchersuche kann dort erneut
|
||||||
ausgeführt werden, ohne andere ungespeicherte Formulareingaben zu verlieren.
|
ausgeführt werden, ohne andere ungespeicherte Formulareingaben zu verlieren.
|
||||||
|
|
||||||
|
## Energieaufzeichnung und Visualisierung
|
||||||
|
|
||||||
|
Sobald Netzleistung und mindestens eine PV-Istleistung in der Anlagentopologie
|
||||||
|
konfiguriert sind, tastet der Manager die Quellen minuetlich ab. PV-Leistung ist
|
||||||
|
die Summe der den PV-Flaechen zugeordneten Wechselrichter. Batterieleistung und
|
||||||
|
der kapazitaetsgewichtete SOC stammen aus den Batteriespeichern. Positive
|
||||||
|
Batterieleistung bedeutet Laden, negative Entladen. Der Hausverbrauch wird als
|
||||||
|
`PV + Netz - Batterie` bilanziert. Dieselben Werte versorgen ohne zweite
|
||||||
|
Eingabe auch die Prognosetelemetrie. Ausfallluecken ueber fuenf Minuten werden
|
||||||
|
nicht nachberechnet.
|
||||||
|
|
||||||
|
Batteriespeicher bleiben als physische Komponenten samt Messquellen in der
|
||||||
|
Anlagentopologie. Ein zusaetzliches Batterieverbrauchermodul beschreibt dagegen
|
||||||
|
Regelung, Leistungsangebot und Betriebszustand; beide Rollen sind bewusst
|
||||||
|
getrennt.
|
||||||
|
|
||||||
|
Die eigenen Variablen werden automatisch im ersten Archive Control aktiviert.
|
||||||
|
Leistungen verwenden die Standardaggregation, Energievariablen den Zaehlermodus.
|
||||||
|
Standardmaessig werden Leistungen auf eine Minute verdichtet und nach zwoelf
|
||||||
|
Monaten geloescht. Energie wird auf fuenf Minuten verdichtet, nach zwoelf
|
||||||
|
Monaten auf Tageswerte reduziert und nie geloescht. Aenderungen an diesen
|
||||||
|
Regeln werden idempotent ueber die Archiv-API gesetzt und reaggregiert.
|
||||||
|
|
||||||
|
Vier Schalter im Bereich `Energieaufzeichnung` verwalten die
|
||||||
|
Visualisierungen idempotent. Beim Einschalten werden die Objekte direkt unter
|
||||||
|
dem Manager angelegt oder aktualisiert. Beim Ausschalten werden ausschliesslich
|
||||||
|
die vom Manager anhand ihrer festen Kennung und ihres Typs erkannten Objekte
|
||||||
|
entfernt beziehungsweise die native Energy Distribution ausgeblendet:
|
||||||
|
|
||||||
|
- Energy Pie aus Enelix Utils mit den vier relevanten Energiezaehlern,
|
||||||
|
- zwei native IP-Symcon-Diagrammmedien fuer Leistungen und Energien,
|
||||||
|
- responsive Energy Facts mit Solar-, Netz-, Haus- und Vergleichswerten,
|
||||||
|
- native Energy Distribution fuer PV, Netz, Haus und Batterie.
|
||||||
|
|
||||||
## Anlagenweite Einspeisebegrenzung
|
## Anlagenweite Einspeisebegrenzung
|
||||||
|
|
||||||
Die Einspeisebegrenzung ist Bestandteil von Peak Shaving und arbeitet auf der
|
Die Einspeisebegrenzung ist Bestandteil von Peak Shaving und arbeitet auf der
|
||||||
|
|||||||
@@ -288,6 +288,7 @@ function enelixDemoInstall(): array
|
|||||||
'ACEinspeisegrenze_kW' => 0.0,
|
'ACEinspeisegrenze_kW' => 0.0,
|
||||||
'ACBezugsgrenze_kW' => 0.0,
|
'ACBezugsgrenze_kW' => 0.0,
|
||||||
'IstleistungVariableID' => $inverterActual,
|
'IstleistungVariableID' => $inverterActual,
|
||||||
|
'Istleistungsfaktor' => 1.0,
|
||||||
'BegrenzungVariableID' => $inverterLimit,
|
'BegrenzungVariableID' => $inverterLimit,
|
||||||
'Begrenzungsart' => 'watt',
|
'Begrenzungsart' => 'watt',
|
||||||
]];
|
]];
|
||||||
@@ -314,13 +315,18 @@ function enelixDemoInstall(): array
|
|||||||
),
|
),
|
||||||
'WechselrichterID' => 'demo-hybrid-1',
|
'WechselrichterID' => 'demo-hybrid-1',
|
||||||
'Kopplung' => 'hybrid',
|
'Kopplung' => 'hybrid',
|
||||||
|
'LeistungVariableID' => $batteryActual,
|
||||||
|
'Leistungsfaktor' => 1.0,
|
||||||
|
'SOCVariableID' => $batterySoc,
|
||||||
]];
|
]];
|
||||||
$managerProperties = [
|
$managerProperties = [
|
||||||
'NetzleistungVariableID' => $grid,
|
'NetzleistungVariableID' => $grid,
|
||||||
'Netzleistungsfaktor' => 1.0,
|
'Netzleistungsfaktor' => 1.0,
|
||||||
'PrognosePVVariableID' => $pvDisplay,
|
'MesswerteAnzeigen' => true,
|
||||||
'PrognoseHausverbrauchVariableID' => $houseDisplay,
|
'EnergyPieAnzeigen' => true,
|
||||||
'PrognoseSOCVariableID' => $batterySoc,
|
'EnergiediagrammeAnzeigen' => true,
|
||||||
|
'FunFactsAnzeigen' => true,
|
||||||
|
'EnergieflussAnzeigen' => true,
|
||||||
'PrognoseSendeintervall' => 60,
|
'PrognoseSendeintervall' => 60,
|
||||||
'MesswertMaxAlter' => 10,
|
'MesswertMaxAlter' => 10,
|
||||||
'AutomatischeSuche' => false,
|
'AutomatischeSuche' => false,
|
||||||
@@ -366,6 +372,7 @@ function enelixDemoInstall(): array
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
IPS_RequestAction($manager, 'Aktiv', $managerReady);
|
IPS_RequestAction($manager, 'Aktiv', $managerReady);
|
||||||
|
IPS_RequestAction($manager, 'MesswerteAufzeichnen', true);
|
||||||
|
|
||||||
$boiler1List = enelixDemoEnsureInstance(
|
$boiler1List = enelixDemoEnsureInstance(
|
||||||
$dashboard,
|
$dashboard,
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Belevo\EnelixEMS;
|
||||||
|
|
||||||
|
final class EnergieMessung
|
||||||
|
{
|
||||||
|
private const WATTSEKUNDEN_PRO_KWH = 3600000.0;
|
||||||
|
|
||||||
|
public static function hausleistung(float $pv, float $netz, float $batterie): float
|
||||||
|
{
|
||||||
|
return max(
|
||||||
|
0.0,
|
||||||
|
self::endlich($pv) + self::endlich($netz) - self::endlich($batterie)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array{PV: float, Haus: float, Netz: float, Batterie: float} $leistungen
|
||||||
|
* @return array{
|
||||||
|
* PVEnergie: float,
|
||||||
|
* Hausenergie: float,
|
||||||
|
* NetzbezugEnergie: float,
|
||||||
|
* EinspeisungEnergie: float,
|
||||||
|
* BatterieLadenEnergie: float,
|
||||||
|
* BatterieEntladenEnergie: float
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
public static function energieDeltas(array $leistungen, int $sekunden): array
|
||||||
|
{
|
||||||
|
$faktor = max(0, $sekunden) / self::WATTSEKUNDEN_PRO_KWH;
|
||||||
|
$pv = self::endlich($leistungen['PV']);
|
||||||
|
$haus = self::endlich($leistungen['Haus']);
|
||||||
|
$netz = self::endlich($leistungen['Netz']);
|
||||||
|
$batterie = self::endlich($leistungen['Batterie']);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'PVEnergie' => max(0.0, $pv) * $faktor,
|
||||||
|
'Hausenergie' => max(0.0, $haus) * $faktor,
|
||||||
|
'NetzbezugEnergie' => max(0.0, $netz) * $faktor,
|
||||||
|
'EinspeisungEnergie' => max(0.0, -$netz) * $faktor,
|
||||||
|
'BatterieLadenEnergie' => max(0.0, $batterie) * $faktor,
|
||||||
|
'BatterieEntladenEnergie' => max(0.0, -$batterie) * $faktor,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function endlich(float $wert): float
|
||||||
|
{
|
||||||
|
return is_finite($wert) ? $wert : 0.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,542 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Belevo\EnelixEMS;
|
||||||
|
|
||||||
|
trait ManagerEnergieTrait
|
||||||
|
{
|
||||||
|
private function konfiguriereEnergiearchiv(): void
|
||||||
|
{
|
||||||
|
$archivID = $this->archivID();
|
||||||
|
if ($archivID === 0) {
|
||||||
|
$this->protokolliere('Energiearchiv', 'Kein Archiv Control vorhanden.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$aktiv = $this->ReadPropertyBoolean('EnergieaufzeichnungAktiv');
|
||||||
|
$geaendert = [];
|
||||||
|
$leistungstyp = $this->verdichtungstyp(
|
||||||
|
$this->ReadPropertyInteger('LeistungsaufzeichnungMinuten')
|
||||||
|
);
|
||||||
|
$energietyp = $this->verdichtungstyp(
|
||||||
|
$this->ReadPropertyInteger('EnergieaufzeichnungMinuten')
|
||||||
|
);
|
||||||
|
foreach (self::LEISTUNGS_VARIABLEN as $ident) {
|
||||||
|
$variableID = $this->GetIDForIdent($ident);
|
||||||
|
AC_SetLoggingStatus($archivID, $variableID, $aktiv);
|
||||||
|
AC_SetAggregationType($archivID, $variableID, 0);
|
||||||
|
AC_SetGraphStatus($archivID, $variableID, $aktiv);
|
||||||
|
$regeln = [-1 => $leistungstyp];
|
||||||
|
$loeschen = $this->ReadPropertyInteger('LeistungLoeschenMonate');
|
||||||
|
if ($loeschen > 0) {
|
||||||
|
$regeln[$loeschen] = 7;
|
||||||
|
}
|
||||||
|
if ($aktiv && $this->setzeVerdichtung($archivID, $variableID, $regeln)) {
|
||||||
|
$geaendert[] = $variableID;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (self::ENERGIE_VARIABLEN as $ident) {
|
||||||
|
$variableID = $this->GetIDForIdent($ident);
|
||||||
|
AC_SetLoggingStatus($archivID, $variableID, $aktiv);
|
||||||
|
AC_SetAggregationType($archivID, $variableID, 1);
|
||||||
|
AC_SetGraphStatus($archivID, $variableID, $aktiv);
|
||||||
|
$regeln = [-1 => $energietyp];
|
||||||
|
$verdichten = $this->ReadPropertyInteger('EnergieVerdichtenMonate');
|
||||||
|
if ($verdichten > 0) {
|
||||||
|
$regeln[$verdichten] = 3;
|
||||||
|
}
|
||||||
|
$loeschen = $this->ReadPropertyInteger('EnergieLoeschenMonate');
|
||||||
|
if ($loeschen > 0) {
|
||||||
|
$regeln[$loeschen] = 7;
|
||||||
|
}
|
||||||
|
if ($aktiv && $this->setzeVerdichtung($archivID, $variableID, $regeln)) {
|
||||||
|
$geaendert[] = $variableID;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
IPS_ApplyChanges($archivID);
|
||||||
|
foreach (array_unique($geaendert) as $variableID) {
|
||||||
|
AC_ReAggregateVariable($archivID, $variableID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param array<int, int> $regeln */
|
||||||
|
private function setzeVerdichtung(int $archivID, int $variableID, array $regeln): bool
|
||||||
|
{
|
||||||
|
ksort($regeln);
|
||||||
|
$aktuell = [];
|
||||||
|
$eintraege = AC_GetCompaction($archivID, $variableID);
|
||||||
|
if (is_array($eintraege)) {
|
||||||
|
foreach ($eintraege as $eintrag) {
|
||||||
|
$monat = (int) ($eintrag['MonthOffset'] ?? -999);
|
||||||
|
$typ = (int) ($eintrag['CompactionType'] ?? -1);
|
||||||
|
if ($monat >= -1 && $typ >= 0) {
|
||||||
|
$aktuell[$monat] = $typ;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ksort($aktuell);
|
||||||
|
if ($aktuell === $regeln) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
foreach (array_keys($aktuell) as $monat) {
|
||||||
|
AC_SetCompaction($archivID, $variableID, $monat, -1);
|
||||||
|
}
|
||||||
|
foreach ($regeln as $monat => $typ) {
|
||||||
|
AC_SetCompaction($archivID, $variableID, $monat, $typ);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function verdichtungstyp(int $minuten): int
|
||||||
|
{
|
||||||
|
return [1 => 0, 5 => 1, 60 => 2][$minuten] ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function archivID(): int
|
||||||
|
{
|
||||||
|
$archive = IPS_GetInstanceListByModuleID(self::ARCHIVE_MODULE_ID);
|
||||||
|
|
||||||
|
return $archive === [] ? 0 : (int) $archive[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function synchronisiereEnergievisualisierungen(): void
|
||||||
|
{
|
||||||
|
$this->synchronisiereEnergyPie($this->ReadPropertyBoolean('EnergyPieAnzeigen'));
|
||||||
|
$this->synchronisiereEnergiediagramme(
|
||||||
|
$this->ReadPropertyBoolean('EnergiediagrammeAnzeigen')
|
||||||
|
);
|
||||||
|
$this->synchronisiereFunFacts($this->ReadPropertyBoolean('FunFactsAnzeigen'));
|
||||||
|
$this->synchronisiereEnergiefluss($this->ReadPropertyBoolean('EnergieflussAnzeigen'));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function synchronisiereEnergyPie(bool $anzeigen): void
|
||||||
|
{
|
||||||
|
if ($anzeigen) {
|
||||||
|
$this->erstelleEnergyPie();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$this->entferneInstanzUnterobjekt('EnergyPie', self::ENERGY_PIE_MODULE_ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function synchronisiereEnergiediagramme(bool $anzeigen): void
|
||||||
|
{
|
||||||
|
if ($anzeigen) {
|
||||||
|
$this->erstelleEnergiediagramme();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$this->entferneDiagrammUnterobjekt('Leistungsdiagramm');
|
||||||
|
$this->entferneDiagrammUnterobjekt('Energiediagramm');
|
||||||
|
}
|
||||||
|
|
||||||
|
private function synchronisiereFunFacts(bool $anzeigen): void
|
||||||
|
{
|
||||||
|
if ($anzeigen) {
|
||||||
|
$this->erstelleFunFacts();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$variableID = $this->findeUnterobjekt('EnergieFunFacts');
|
||||||
|
if ($variableID === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ((int) IPS_GetObject($variableID)['ObjectType'] !== 2) {
|
||||||
|
throw new RuntimeException('Unterobjekt EnergieFunFacts ist keine Variable.');
|
||||||
|
}
|
||||||
|
$this->UnregisterVariable('EnergieFunFacts');
|
||||||
|
}
|
||||||
|
|
||||||
|
private function synchronisiereEnergiefluss(bool $anzeigen): void
|
||||||
|
{
|
||||||
|
if ($anzeigen) {
|
||||||
|
$this->erstelleEnergiefluss();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$instanzID = $this->findeUnterobjekt('Energiefluss');
|
||||||
|
if ($instanzID === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$objekt = IPS_GetObject($instanzID);
|
||||||
|
$modulID = $objekt['ObjectType'] === 1
|
||||||
|
? (string) IPS_GetInstance($instanzID)['ModuleInfo']['ModuleID']
|
||||||
|
: '';
|
||||||
|
if ($modulID !== self::ENERGY_FLOW_MODULE_ID) {
|
||||||
|
throw new RuntimeException('Unterobjekt Energiefluss hat einen unpassenden Typ.');
|
||||||
|
}
|
||||||
|
IPS_SetHidden($instanzID, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function entferneInstanzUnterobjekt(string $ident, string $modulID): void
|
||||||
|
{
|
||||||
|
$instanzID = $this->findeUnterobjekt($ident);
|
||||||
|
if ($instanzID === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$objekt = IPS_GetObject($instanzID);
|
||||||
|
$istModulID = $objekt['ObjectType'] === 1
|
||||||
|
? (string) IPS_GetInstance($instanzID)['ModuleInfo']['ModuleID']
|
||||||
|
: '';
|
||||||
|
if ($istModulID !== $modulID) {
|
||||||
|
throw new RuntimeException('Unterobjekt ' . $ident . ' hat einen unpassenden Typ.');
|
||||||
|
}
|
||||||
|
IPS_DeleteInstance($instanzID);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function entferneDiagrammUnterobjekt(string $ident): void
|
||||||
|
{
|
||||||
|
$medienID = $this->findeUnterobjekt($ident);
|
||||||
|
if ($medienID === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$objekt = IPS_GetObject($medienID);
|
||||||
|
if ($objekt['ObjectType'] !== 5 || (int) IPS_GetMedia($medienID)['MediaType'] !== 4) {
|
||||||
|
throw new RuntimeException('Unterobjekt ' . $ident . ' ist kein Diagramm.');
|
||||||
|
}
|
||||||
|
IPS_DeleteMedia($medienID, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function erstelleEnergyPie(): string
|
||||||
|
{
|
||||||
|
if (!in_array(self::ENERGY_PIE_MODULE_ID, IPS_GetModuleList(), true)) {
|
||||||
|
throw new RuntimeException(
|
||||||
|
'Das Modul Energy Pie aus Enelix Utils ist nicht installiert.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
$instanzID = $this->findeUnterobjekt('EnergyPie');
|
||||||
|
if ($instanzID === 0) {
|
||||||
|
$instanzID = IPS_CreateInstance(self::ENERGY_PIE_MODULE_ID);
|
||||||
|
IPS_SetParent($instanzID, $this->InstanceID);
|
||||||
|
IPS_SetIdent($instanzID, 'EnergyPie');
|
||||||
|
} else {
|
||||||
|
$objekt = IPS_GetObject($instanzID);
|
||||||
|
$modulID = $objekt['ObjectType'] === 1
|
||||||
|
? (string) IPS_GetInstance($instanzID)['ModuleInfo']['ModuleID']
|
||||||
|
: '';
|
||||||
|
if ($modulID !== self::ENERGY_PIE_MODULE_ID) {
|
||||||
|
throw new RuntimeException('Unterobjekt EnergyPie hat einen unpassenden Typ.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
IPS_SetName($instanzID, 'Energy Pie');
|
||||||
|
IPS_SetPosition($instanzID, 500);
|
||||||
|
IPS_SetHidden($instanzID, false);
|
||||||
|
foreach ([
|
||||||
|
'ProduktionZaehlerID' => $this->GetIDForIdent('PVEnergie'),
|
||||||
|
'EinspeisungZaehlerID' => $this->GetIDForIdent('EinspeisungEnergie'),
|
||||||
|
'NetzbezugZaehlerID' => $this->GetIDForIdent('NetzbezugEnergie'),
|
||||||
|
'VerbrauchZaehlerID' => $this->GetIDForIdent('Hausenergie'),
|
||||||
|
'Zaehlerfaktor' => 1.0,
|
||||||
|
] as $property => $wert) {
|
||||||
|
IPS_SetProperty($instanzID, $property, $wert);
|
||||||
|
}
|
||||||
|
IPS_ApplyChanges($instanzID);
|
||||||
|
|
||||||
|
return 'Energy Pie wurde erstellt oder aktualisiert.';
|
||||||
|
}
|
||||||
|
|
||||||
|
private function erstelleEnergiefluss(): string
|
||||||
|
{
|
||||||
|
if (!in_array(self::ENERGY_FLOW_MODULE_ID, IPS_GetModuleList(), true)) {
|
||||||
|
throw new RuntimeException('Das Symcon-Modul Energy Distribution ist nicht installiert.');
|
||||||
|
}
|
||||||
|
$instanzID = $this->findeUnterobjekt('Energiefluss');
|
||||||
|
if ($instanzID === 0) {
|
||||||
|
$instanzID = IPS_CreateInstance(self::ENERGY_FLOW_MODULE_ID);
|
||||||
|
IPS_SetParent($instanzID, $this->InstanceID);
|
||||||
|
IPS_SetIdent($instanzID, 'Energiefluss');
|
||||||
|
} else {
|
||||||
|
$objekt = IPS_GetObject($instanzID);
|
||||||
|
$modulID = $objekt['ObjectType'] === 1
|
||||||
|
? (string) IPS_GetInstance($instanzID)['ModuleInfo']['ModuleID']
|
||||||
|
: '';
|
||||||
|
if ($modulID !== self::ENERGY_FLOW_MODULE_ID) {
|
||||||
|
throw new RuntimeException('Unterobjekt Energiefluss hat einen unpassenden Typ.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
IPS_SetName($instanzID, 'Energiefluss');
|
||||||
|
IPS_SetPosition($instanzID, 540);
|
||||||
|
IPS_SetHidden($instanzID, false);
|
||||||
|
$knoten = [
|
||||||
|
$this->energieflussKnoten('PVLeistungArchiv', 'PV-Anlage', 'solar'),
|
||||||
|
$this->energieflussKnoten('NetzleistungArchiv', 'Stromnetz', 'grid'),
|
||||||
|
$this->energieflussKnoten('HausverbrauchLeistungArchiv', 'Hausverbrauch', 'consumer'),
|
||||||
|
$this->energieflussKnoten('BatterieleistungArchiv', 'Batteriespeicher', 'battery'),
|
||||||
|
];
|
||||||
|
IPS_SetProperty($instanzID, 'Calculation', true);
|
||||||
|
IPS_SetProperty(
|
||||||
|
$instanzID,
|
||||||
|
'Config',
|
||||||
|
json_encode($knoten, JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES)
|
||||||
|
);
|
||||||
|
IPS_SetProperty($instanzID, 'EnableLimits', false);
|
||||||
|
IPS_SetProperty($instanzID, 'Restrictions', '[]');
|
||||||
|
IPS_ApplyChanges($instanzID);
|
||||||
|
|
||||||
|
return 'Der Energiefluss wurde erstellt oder aktualisiert.';
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return array<string, mixed> */
|
||||||
|
private function energieflussKnoten(string $ident, string $name, string $typ): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'VariableConfigType' => 0,
|
||||||
|
'VariableID' => $this->GetIDForIdent($ident),
|
||||||
|
'SecondaryVariableID' => 1,
|
||||||
|
'Name' => $name,
|
||||||
|
'Type' => $typ,
|
||||||
|
'Grouped' => false,
|
||||||
|
'CalculationType' => 0,
|
||||||
|
'InfoVariableID' => 1,
|
||||||
|
'Invert' => false,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function erstelleEnergiediagramme(): string
|
||||||
|
{
|
||||||
|
$leistung = [
|
||||||
|
$this->chartDatensatz('PVLeistungArchiv', 'PV', '#F4C542', '~Power'),
|
||||||
|
$this->chartDatensatz('HausverbrauchLeistungArchiv', 'Haus', '#22A06B', '~Power'),
|
||||||
|
$this->chartDatensatz('NetzleistungArchiv', 'Netz', '#E95D5D', '~Power'),
|
||||||
|
$this->chartDatensatz('BatterieleistungArchiv', 'Batterie', '#8B5CF6', '~Power'),
|
||||||
|
];
|
||||||
|
$energie = [
|
||||||
|
$this->chartDatensatz('PVEnergie', 'PV', '#F4C542', '~Electricity'),
|
||||||
|
$this->chartDatensatz('Hausenergie', 'Haus', '#22A06B', '~Electricity'),
|
||||||
|
$this->chartDatensatz('NetzbezugEnergie', 'Netzbezug', '#E95D5D', '~Electricity'),
|
||||||
|
$this->chartDatensatz('EinspeisungEnergie', 'Einspeisung', '#2F80ED', '~Electricity'),
|
||||||
|
$this->chartDatensatz('BatterieLadenEnergie', 'Batterie laden', '#8B5CF6', '~Electricity'),
|
||||||
|
$this->chartDatensatz('BatterieEntladenEnergie', 'Batterie entladen', '#F59E0B', '~Electricity'),
|
||||||
|
];
|
||||||
|
$this->erstelleDiagramm('Leistungsdiagramm', 'Leistungen', $leistung, 510);
|
||||||
|
$this->erstelleDiagramm('Energiediagramm', 'Energien', $energie, 520);
|
||||||
|
|
||||||
|
return 'Die Diagramme Leistungen und Energien wurden erstellt oder aktualisiert.';
|
||||||
|
}
|
||||||
|
/** @param list<array<string, mixed>> $datensaetze */
|
||||||
|
private function erstelleDiagramm(
|
||||||
|
string $ident,
|
||||||
|
string $name,
|
||||||
|
array $datensaetze,
|
||||||
|
int $position
|
||||||
|
): int {
|
||||||
|
$medienID = $this->findeUnterobjekt($ident);
|
||||||
|
if ($medienID === 0) {
|
||||||
|
$medienID = IPS_CreateMedia(4);
|
||||||
|
IPS_SetParent($medienID, $this->InstanceID);
|
||||||
|
IPS_SetIdent($medienID, $ident);
|
||||||
|
}
|
||||||
|
$objekt = IPS_GetObject($medienID);
|
||||||
|
if ($objekt['ObjectType'] !== 5 || (int) IPS_GetMedia($medienID)['MediaType'] !== 4) {
|
||||||
|
throw new RuntimeException('Unterobjekt ' . $ident . ' ist kein Diagramm.');
|
||||||
|
}
|
||||||
|
IPS_SetName($medienID, $name);
|
||||||
|
IPS_SetPosition($medienID, $position);
|
||||||
|
IPS_SetHidden($medienID, false);
|
||||||
|
IPS_SetMediaFile($medienID, 'media/' . $medienID . '.chart', false);
|
||||||
|
IPS_SetMediaContent(
|
||||||
|
$medienID,
|
||||||
|
base64_encode(json_encode(['datasets' => $datensaetze], JSON_THROW_ON_ERROR))
|
||||||
|
);
|
||||||
|
|
||||||
|
return $medienID;
|
||||||
|
}
|
||||||
|
/** @return array<string, mixed> */
|
||||||
|
private function chartDatensatz(
|
||||||
|
string $ident,
|
||||||
|
string $titel,
|
||||||
|
string $farbe,
|
||||||
|
string $profil
|
||||||
|
): array {
|
||||||
|
return [
|
||||||
|
'variableID' => $this->GetIDForIdent($ident),
|
||||||
|
'fillColor' => 'clear',
|
||||||
|
'strokeColor' => $farbe,
|
||||||
|
'timeOffset' => 0,
|
||||||
|
'visible' => true,
|
||||||
|
'title' => $titel,
|
||||||
|
'type' => 'line',
|
||||||
|
'side' => 'left',
|
||||||
|
'profile' => $profil,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function findeUnterobjekt(string $ident): int
|
||||||
|
{
|
||||||
|
$objektID = @IPS_GetObjectIDByIdent($ident, $this->InstanceID);
|
||||||
|
|
||||||
|
return is_int($objektID) && $objektID > 0 ? $objektID : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function erstelleFunFacts(): string
|
||||||
|
{
|
||||||
|
$this->RegisterVariableString('EnergieFunFacts', 'Energy Facts', '~HTMLBox', 530);
|
||||||
|
$variableID = $this->GetIDForIdent('EnergieFunFacts');
|
||||||
|
IPS_SetHidden($variableID, false);
|
||||||
|
$this->aktualisiereFunFacts($variableID);
|
||||||
|
|
||||||
|
return 'Energy Facts wurden erstellt oder aktualisiert.';
|
||||||
|
}
|
||||||
|
|
||||||
|
private function aktualisiereFunFacts(int $variableID): void
|
||||||
|
{
|
||||||
|
$archivID = $this->archivID();
|
||||||
|
if ($archivID === 0) {
|
||||||
|
SetValue($variableID, '<div style="padding:16px">Archiv nicht verfuegbar.</div>');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$heute = strtotime('today');
|
||||||
|
$jahr = mktime(0, 0, 0, 1, 1, (int) date('Y'));
|
||||||
|
$jetzt = time();
|
||||||
|
$pvID = $this->GetIDForIdent('PVEnergie');
|
||||||
|
$hausID = $this->GetIDForIdent('Hausenergie');
|
||||||
|
$bezugID = $this->GetIDForIdent('NetzbezugEnergie');
|
||||||
|
$exportID = $this->GetIDForIdent('EinspeisungEnergie');
|
||||||
|
$pvHeute = $this->energieSumme($archivID, $pvID, $heute, $jetzt);
|
||||||
|
$pvJahr = $this->energieSumme($archivID, $pvID, $jahr, $jetzt);
|
||||||
|
$pvTotal = max(0.0, (float) GetValue($pvID));
|
||||||
|
[$besterTag, $besterTagZeit] = $this->maximalerTageswert($archivID, $pvID);
|
||||||
|
[$maxPV, $maxPVZeit] = $this->maximaleLeistung(
|
||||||
|
$archivID,
|
||||||
|
$this->GetIDForIdent('PVLeistungArchiv')
|
||||||
|
);
|
||||||
|
[$maxHausTag, $maxHausTagZeit] = $this->maximalerTageswert($archivID, $hausID);
|
||||||
|
[$maxHaus, $maxHausZeit] = $this->maximaleLeistung(
|
||||||
|
$archivID,
|
||||||
|
$this->GetIDForIdent('HausverbrauchLeistungArchiv')
|
||||||
|
);
|
||||||
|
[$maxBezug, $maxBezugZeit] = $this->maximalerTageswert($archivID, $bezugID);
|
||||||
|
[$maxExport, $maxExportZeit] = $this->maximalerTageswert($archivID, $exportID);
|
||||||
|
|
||||||
|
$werte = [
|
||||||
|
'{{PV_HEUTE}}' => $this->formatiereEnergie($pvHeute),
|
||||||
|
'{{PV_JAHR}}' => $this->formatiereEnergie($pvJahr),
|
||||||
|
'{{PV_TOTAL}}' => $this->formatiereEnergie($pvTotal),
|
||||||
|
'{{BESTER_TAG}}' => $this->formatiereEnergie($besterTag),
|
||||||
|
'{{BESTER_TAG_DATUM}}' => $this->formatiereDatum($besterTagZeit),
|
||||||
|
'{{MAX_PV}}' => $this->formatiereLeistung($maxPV),
|
||||||
|
'{{MAX_PV_DATUM}}' => $this->formatiereDatum($maxPVZeit),
|
||||||
|
'{{MAX_HAUS_TAG}}' => $this->formatiereEnergie($maxHausTag),
|
||||||
|
'{{MAX_HAUS_TAG_DATUM}}' => $this->formatiereDatum($maxHausTagZeit),
|
||||||
|
'{{MAX_HAUS}}' => $this->formatiereLeistung($maxHaus),
|
||||||
|
'{{MAX_HAUS_DATUM}}' => $this->formatiereDatum($maxHausZeit),
|
||||||
|
'{{MAX_BEZUG}}' => $this->formatiereEnergie($maxBezug),
|
||||||
|
'{{MAX_BEZUG_DATUM}}' => $this->formatiereDatum($maxBezugZeit),
|
||||||
|
'{{MAX_EXPORT}}' => $this->formatiereEnergie($maxExport),
|
||||||
|
'{{MAX_EXPORT_DATUM}}' => $this->formatiereDatum($maxExportZeit),
|
||||||
|
'{{HEIZOEL}}' => number_format($pvTotal / 10.0, 0, ',', "'") . ' Liter',
|
||||||
|
'{{FLUGKM}}' => number_format($pvTotal * 2.4, 0, ',', "'") . ' km',
|
||||||
|
];
|
||||||
|
SetValue($variableID, strtr($this->funFactsHtml(), $werte));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function energieSumme(int $archivID, int $variableID, int $start, int $ende): float
|
||||||
|
{
|
||||||
|
$werte = @AC_GetAggregatedValues($archivID, $variableID, 1, $start, $ende, 0);
|
||||||
|
if (!is_array($werte)) {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return array_sum(array_map(
|
||||||
|
static fn (array $wert): float => max(0.0, (float) ($wert['Avg'] ?? 0.0)),
|
||||||
|
$werte
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return array{0: float, 1: int} */
|
||||||
|
private function maximalerTageswert(int $archivID, int $variableID): array
|
||||||
|
{
|
||||||
|
$werte = @AC_GetAggregatedValues($archivID, $variableID, 1, 0, time(), 0);
|
||||||
|
$maximum = 0.0;
|
||||||
|
$zeit = 0;
|
||||||
|
if (is_array($werte)) {
|
||||||
|
foreach ($werte as $wert) {
|
||||||
|
$kandidat = max(0.0, (float) ($wert['Avg'] ?? 0.0));
|
||||||
|
if ($kandidat > $maximum) {
|
||||||
|
$maximum = $kandidat;
|
||||||
|
$zeit = (int) ($wert['TimeStamp'] ?? 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [$maximum, $zeit];
|
||||||
|
}
|
||||||
|
/** @return array{0: float, 1: int} */
|
||||||
|
private function maximaleLeistung(int $archivID, int $variableID): array
|
||||||
|
{
|
||||||
|
$werte = @AC_GetAggregatedValues($archivID, $variableID, 1, 0, time(), 0);
|
||||||
|
$maximum = 0.0;
|
||||||
|
$zeit = 0;
|
||||||
|
if (is_array($werte)) {
|
||||||
|
foreach ($werte as $wert) {
|
||||||
|
$kandidat = max(0.0, (float) ($wert['Max'] ?? 0.0));
|
||||||
|
if ($kandidat > $maximum) {
|
||||||
|
$maximum = $kandidat;
|
||||||
|
$zeit = (int) ($wert['MaxTime'] ?? $wert['TimeStamp'] ?? 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [$maximum, $zeit];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function formatiereEnergie(float $wert): string
|
||||||
|
{
|
||||||
|
if ($wert >= 1000.0) {
|
||||||
|
return number_format($wert / 1000.0, 2, ',', "'") . ' MWh';
|
||||||
|
}
|
||||||
|
|
||||||
|
return number_format($wert, 1, ',', "'") . ' kWh';
|
||||||
|
}
|
||||||
|
private function formatiereLeistung(float $wert): string
|
||||||
|
{
|
||||||
|
if ($wert >= 10000.0) {
|
||||||
|
return number_format($wert / 1000.0, 1, ',', "'") . ' kW';
|
||||||
|
}
|
||||||
|
|
||||||
|
return number_format($wert, 0, ',', "'") . ' W';
|
||||||
|
}
|
||||||
|
|
||||||
|
private function formatiereDatum(int $zeit): string
|
||||||
|
{
|
||||||
|
return $zeit > 0 ? date('d.m.Y', $zeit) : 'Noch keine Daten';
|
||||||
|
}
|
||||||
|
|
||||||
|
private function funFactsHtml(): string
|
||||||
|
{
|
||||||
|
return <<<'HTML'
|
||||||
|
<style>
|
||||||
|
.enx-facts{box-sizing:border-box;font-family:system-ui,-apple-system,sans-serif;color:#182230;padding:16px;background:#f4f7f6}
|
||||||
|
.enx-facts *{box-sizing:border-box}.enx-facts-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:14px}
|
||||||
|
.enx-fact-panel{min-width:0;background:#fff;border:1px solid #dfe7e4;border-radius:8px;overflow:hidden;box-shadow:0 4px 18px rgba(17,44,36,.08)}
|
||||||
|
.enx-fact-head{padding:15px 18px 13px;border-bottom:3px solid #22a06b;display:flex;align-items:center;justify-content:space-between}
|
||||||
|
.enx-fact-head b{font-size:18px}.enx-fact-head span{font-size:11px;letter-spacing:.08em;color:#60736c}
|
||||||
|
.enx-fact-body{padding:5px 18px 10px}.enx-fact-row{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:12px;padding:11px 0;border-bottom:1px solid #edf1ef}
|
||||||
|
.enx-fact-row:last-child{border-bottom:0}.enx-fact-label{min-width:0;color:#52635d;font-size:13px}.enx-fact-value{text-align:right;font-size:15px;font-weight:700;white-space:nowrap}
|
||||||
|
.enx-fact-date{display:block;color:#81908b;font-size:11px;font-weight:500;margin-top:2px}.enx-fact-panel.grid .enx-fact-head{border-color:#2f80ed}
|
||||||
|
.enx-fact-panel.impact .enx-fact-head{border-color:#f4c542}.enx-impact{padding:18px}.enx-impact-value{font-size:30px;font-weight:750;line-height:1.15}
|
||||||
|
.enx-impact-label{font-size:12px;color:#60736c;margin:5px 0 20px}.enx-impact-note{font-size:11px;color:#81908b;line-height:1.45}
|
||||||
|
@media(max-width:820px){.enx-facts-grid{grid-template-columns:1fr}.enx-facts{padding:10px}}
|
||||||
|
</style>
|
||||||
|
<div class="enx-facts"><div class="enx-facts-grid">
|
||||||
|
<section class="enx-fact-panel"><div class="enx-fact-head"><b>Solar</b><span>ERZEUGUNG</span></div><div class="enx-fact-body">
|
||||||
|
<div class="enx-fact-row"><span class="enx-fact-label">Heute</span><span class="enx-fact-value">{{PV_HEUTE}}</span></div>
|
||||||
|
<div class="enx-fact-row"><span class="enx-fact-label">Dieses Jahr</span><span class="enx-fact-value">{{PV_JAHR}}</span></div>
|
||||||
|
<div class="enx-fact-row"><span class="enx-fact-label">Total</span><span class="enx-fact-value">{{PV_TOTAL}}</span></div>
|
||||||
|
<div class="enx-fact-row"><span class="enx-fact-label">Bester Tag</span><span class="enx-fact-value">{{BESTER_TAG}}<small class="enx-fact-date">{{BESTER_TAG_DATUM}}</small></span></div>
|
||||||
|
<div class="enx-fact-row"><span class="enx-fact-label">Max. Leistung</span><span class="enx-fact-value">{{MAX_PV}}<small class="enx-fact-date">{{MAX_PV_DATUM}}</small></span></div>
|
||||||
|
</div></section>
|
||||||
|
<section class="enx-fact-panel grid"><div class="enx-fact-head"><b>Netz & Haus</b><span>SPITZENWERTE</span></div><div class="enx-fact-body">
|
||||||
|
<div class="enx-fact-row"><span class="enx-fact-label">Max. Tagesverbrauch</span><span class="enx-fact-value">{{MAX_HAUS_TAG}}<small class="enx-fact-date">{{MAX_HAUS_TAG_DATUM}}</small></span></div>
|
||||||
|
<div class="enx-fact-row"><span class="enx-fact-label">Max. Hauslast</span><span class="enx-fact-value">{{MAX_HAUS}}<small class="enx-fact-date">{{MAX_HAUS_DATUM}}</small></span></div>
|
||||||
|
<div class="enx-fact-row"><span class="enx-fact-label">Max. Tagesbezug</span><span class="enx-fact-value">{{MAX_BEZUG}}<small class="enx-fact-date">{{MAX_BEZUG_DATUM}}</small></span></div>
|
||||||
|
<div class="enx-fact-row"><span class="enx-fact-label">Max. Einspeisung</span><span class="enx-fact-value">{{MAX_EXPORT}}<small class="enx-fact-date">{{MAX_EXPORT_DATUM}}</small></span></div>
|
||||||
|
</div></section>
|
||||||
|
<section class="enx-fact-panel impact"><div class="enx-fact-head"><b>Wirkung</b><span>VERGLEICHE</span></div><div class="enx-impact">
|
||||||
|
<div class="enx-impact-value">{{HEIZOEL}}</div><div class="enx-impact-label">rechnerisch kompensiertes Heizoel</div>
|
||||||
|
<div class="enx-impact-value">{{FLUGKM}}</div><div class="enx-impact-label">rechnerisch kompensierte Flugkilometer</div>
|
||||||
|
<div class="enx-impact-note">Vergleichswerte auf Basis von 10 kWh pro Liter Heizoel und 2,4 Flugkilometern pro erzeugter kWh.</div>
|
||||||
|
</div></section>
|
||||||
|
</div></div>
|
||||||
|
HTML;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -119,9 +119,12 @@ final class DemoanlageBootstrapTest extends TestCase
|
|||||||
self::assertStringContainsString("'AnlagenWechselrichter'", $source);
|
self::assertStringContainsString("'AnlagenWechselrichter'", $source);
|
||||||
self::assertStringContainsString("'AnlagenPVFlaechen'", $source);
|
self::assertStringContainsString("'AnlagenPVFlaechen'", $source);
|
||||||
self::assertStringContainsString("'AnlagenBatterien'", $source);
|
self::assertStringContainsString("'AnlagenBatterien'", $source);
|
||||||
self::assertStringContainsString("'PrognosePVVariableID' => \$pvDisplay", $source);
|
self::assertStringContainsString("'EnergyPieAnzeigen' => true", $source);
|
||||||
self::assertStringContainsString("'PrognoseHausverbrauchVariableID' => \$houseDisplay", $source);
|
self::assertStringContainsString("'EnergiediagrammeAnzeigen' => true", $source);
|
||||||
self::assertStringContainsString("'PrognoseSOCVariableID' => \$batterySoc", $source);
|
self::assertStringContainsString("'FunFactsAnzeigen' => true", $source);
|
||||||
|
self::assertStringContainsString("'EnergieflussAnzeigen' => true", $source);
|
||||||
|
self::assertStringContainsString("'LeistungVariableID' => \$batteryActual", $source);
|
||||||
|
self::assertStringContainsString("'SOCVariableID' => \$batterySoc", $source);
|
||||||
self::assertStringContainsString("'PrognoseSendeintervall' => 60", $source);
|
self::assertStringContainsString("'PrognoseSendeintervall' => 60", $source);
|
||||||
self::assertStringContainsString("'IstleistungVariableID' => \$inverterActual", $source);
|
self::assertStringContainsString("'IstleistungVariableID' => \$inverterActual", $source);
|
||||||
self::assertStringContainsString("'BegrenzungVariableID' => \$inverterLimit", $source);
|
self::assertStringContainsString("'BegrenzungVariableID' => \$inverterLimit", $source);
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Belevo\EnelixEMS\Tests;
|
||||||
|
|
||||||
|
use Belevo\EnelixEMS\EnergieMessung;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
final class EnergieMessungTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testIntegriertVierLeistungenMitVorzeichenrichtung(): void
|
||||||
|
{
|
||||||
|
$werte = EnergieMessung::energieDeltas([
|
||||||
|
'PV' => 7200.0,
|
||||||
|
'Haus' => 3600.0,
|
||||||
|
'Netz' => -1800.0,
|
||||||
|
'Batterie' => 900.0,
|
||||||
|
], 300);
|
||||||
|
|
||||||
|
self::assertEqualsWithDelta(0.6, $werte['PVEnergie'], 0.000001);
|
||||||
|
self::assertEqualsWithDelta(0.3, $werte['Hausenergie'], 0.000001);
|
||||||
|
self::assertSame(0.0, $werte['NetzbezugEnergie']);
|
||||||
|
self::assertEqualsWithDelta(0.15, $werte['EinspeisungEnergie'], 0.000001);
|
||||||
|
self::assertEqualsWithDelta(0.075, $werte['BatterieLadenEnergie'], 0.000001);
|
||||||
|
self::assertSame(0.0, $werte['BatterieEntladenEnergie']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testTrenntBezugUndBatterieentladung(): void
|
||||||
|
{
|
||||||
|
$werte = EnergieMessung::energieDeltas([
|
||||||
|
'PV' => -10.0,
|
||||||
|
'Haus' => -10.0,
|
||||||
|
'Netz' => 1000.0,
|
||||||
|
'Batterie' => -2000.0,
|
||||||
|
], 3600);
|
||||||
|
|
||||||
|
self::assertSame(0.0, $werte['PVEnergie']);
|
||||||
|
self::assertSame(0.0, $werte['Hausenergie']);
|
||||||
|
self::assertSame(1.0, $werte['NetzbezugEnergie']);
|
||||||
|
self::assertSame(0.0, $werte['EinspeisungEnergie']);
|
||||||
|
self::assertSame(0.0, $werte['BatterieLadenEnergie']);
|
||||||
|
self::assertSame(2.0, $werte['BatterieEntladenEnergie']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testBerechnetHausverbrauchAusDerAnlagenbilanz(): void
|
||||||
|
{
|
||||||
|
self::assertSame(2700.0, EnergieMessung::hausleistung(4200.0, -800.0, 700.0));
|
||||||
|
self::assertSame(3500.0, EnergieMessung::hausleistung(2000.0, 500.0, -1000.0));
|
||||||
|
self::assertSame(0.0, EnergieMessung::hausleistung(500.0, -1200.0, 0.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testIgnoriertUngueltigeDauerUndNichtEndlicheWerte(): void
|
||||||
|
{
|
||||||
|
$werte = EnergieMessung::energieDeltas([
|
||||||
|
'PV' => INF,
|
||||||
|
'Haus' => NAN,
|
||||||
|
'Netz' => 1000.0,
|
||||||
|
'Batterie' => -1000.0,
|
||||||
|
], -1);
|
||||||
|
|
||||||
|
foreach ($werte as $wert) {
|
||||||
|
self::assertSame(0.0, $wert);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Belevo\EnelixEMS\Tests;
|
||||||
|
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
final class ManagerEnergieModulstrukturTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testManagerKonfiguriertMessquellenUndArchivregeln(): void
|
||||||
|
{
|
||||||
|
$modul = (string) file_get_contents(__DIR__ . '/../Manager/module.php');
|
||||||
|
$trait = (string) file_get_contents(__DIR__ . '/../libs/ManagerEnergieTrait.php');
|
||||||
|
$formular = (string) file_get_contents(__DIR__ . '/../Manager/form.json');
|
||||||
|
|
||||||
|
foreach ([
|
||||||
|
"RegisterPropertyBoolean('EnergyPieAnzeigen', false)",
|
||||||
|
"RegisterPropertyBoolean('EnergiediagrammeAnzeigen', false)",
|
||||||
|
"RegisterPropertyBoolean('FunFactsAnzeigen', false)",
|
||||||
|
"RegisterPropertyBoolean('EnergieflussAnzeigen', false)",
|
||||||
|
"RegisterPropertyInteger('LeistungsaufzeichnungMinuten', 1)",
|
||||||
|
"RegisterPropertyInteger('LeistungLoeschenMonate', 12)",
|
||||||
|
"RegisterPropertyInteger('EnergieaufzeichnungMinuten', 5)",
|
||||||
|
"RegisterPropertyInteger('EnergieVerdichtenMonate', 12)",
|
||||||
|
"RegisterPropertyInteger('EnergieLoeschenMonate', 0)",
|
||||||
|
] as $erwartet) {
|
||||||
|
self::assertStringContainsString($erwartet, $modul);
|
||||||
|
}
|
||||||
|
foreach ([
|
||||||
|
'AC_SetLoggingStatus',
|
||||||
|
'AC_SetAggregationType',
|
||||||
|
'AC_SetCompaction',
|
||||||
|
'AC_ReAggregateVariable',
|
||||||
|
'IPS_CreateMedia(4)',
|
||||||
|
'IPS_SetMediaContent',
|
||||||
|
'ENERGY_PIE_MODULE_ID',
|
||||||
|
'ENERGY_FLOW_MODULE_ID',
|
||||||
|
'energieflussKnoten',
|
||||||
|
'IPS_DeleteInstance',
|
||||||
|
'IPS_DeleteMedia',
|
||||||
|
] as $erwartet) {
|
||||||
|
self::assertStringContainsString($erwartet, $modul . $trait);
|
||||||
|
}
|
||||||
|
foreach ([
|
||||||
|
'Energieaufzeichnung',
|
||||||
|
'FormEnergyPieAnzeigen',
|
||||||
|
'FormEnergiediagrammeAnzeigen',
|
||||||
|
'FormFunFactsAnzeigen',
|
||||||
|
'FormEnergieflussAnzeigen',
|
||||||
|
'MesswerteAnzeigen',
|
||||||
|
'IstleistungVariableID',
|
||||||
|
'LeistungVariableID',
|
||||||
|
'SOCVariableID',
|
||||||
|
] as $erwartet) {
|
||||||
|
self::assertStringContainsString($erwartet, $formular);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testManagerFuehrtAlleMessvariablenUndRichtungszaehler(): void
|
||||||
|
{
|
||||||
|
$modul = (string) file_get_contents(__DIR__ . '/../Manager/module.php');
|
||||||
|
|
||||||
|
foreach ([
|
||||||
|
'PVLeistungArchiv',
|
||||||
|
'HausverbrauchLeistungArchiv',
|
||||||
|
'NetzleistungArchiv',
|
||||||
|
'BatterieleistungArchiv',
|
||||||
|
'PVEnergie',
|
||||||
|
'Hausenergie',
|
||||||
|
'NetzbezugEnergie',
|
||||||
|
'EinspeisungEnergie',
|
||||||
|
'BatterieLadenEnergie',
|
||||||
|
'BatterieEntladenEnergie',
|
||||||
|
] as $ident) {
|
||||||
|
self::assertStringContainsString("'" . $ident . "'", $modul);
|
||||||
|
}
|
||||||
|
self::assertStringContainsString("RegisterTimer(
|
||||||
|
'MesswerteAufzeichnen'", $modul);
|
||||||
|
self::assertStringContainsString('EnergieMessung::energieDeltas', $modul);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testFunFactsSindResponsivUndErklaerenUmrechnungen(): void
|
||||||
|
{
|
||||||
|
$trait = (string) file_get_contents(__DIR__ . '/../libs/ManagerEnergieTrait.php');
|
||||||
|
|
||||||
|
self::assertStringContainsString('grid-template-columns:repeat(3,minmax(0,1fr))', $trait);
|
||||||
|
self::assertStringContainsString('@media(max-width:820px)', $trait);
|
||||||
|
self::assertStringContainsString('10 kWh pro Liter Heizoel', $trait);
|
||||||
|
self::assertStringContainsString('2,4 Flugkilometern', $trait);
|
||||||
|
self::assertStringContainsString('AC_GetAggregatedValues', $trait);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -66,9 +66,14 @@ final class ManagerModulstrukturTest extends TestCase
|
|||||||
'AnlagenWechselrichter',
|
'AnlagenWechselrichter',
|
||||||
'AnlagenPVFlaechen',
|
'AnlagenPVFlaechen',
|
||||||
'AnlagenBatterien',
|
'AnlagenBatterien',
|
||||||
'PrognosePVVariableID',
|
'Istleistungsfaktor',
|
||||||
'PrognoseHausverbrauchVariableID',
|
'LeistungVariableID',
|
||||||
'PrognoseSOCVariableID',
|
'Leistungsfaktor',
|
||||||
|
'SOCVariableID',
|
||||||
|
'EnergyPieAnzeigen',
|
||||||
|
'EnergiediagrammeAnzeigen',
|
||||||
|
'FunFactsAnzeigen',
|
||||||
|
'EnergieflussAnzeigen',
|
||||||
'PrognoseSendeintervall',
|
'PrognoseSendeintervall',
|
||||||
'StoerueberwachungAktiv',
|
'StoerueberwachungAktiv',
|
||||||
'StoerungsSendeintervall',
|
'StoerungsSendeintervall',
|
||||||
|
|||||||
@@ -5,7 +5,10 @@ declare(strict_types=1);
|
|||||||
use Belevo\EnelixEMS\SymconTest\TestContext;
|
use Belevo\EnelixEMS\SymconTest\TestContext;
|
||||||
|
|
||||||
return static function (TestContext $test): void {
|
return static function (TestContext $test): void {
|
||||||
$netzleistungId = $test->createVariable(2, 'Netzleistung', 0.0);
|
$netzleistungId = $test->createVariable(2, 'Netzleistung', -800.0);
|
||||||
|
$pvLeistungId = $test->createVariable(2, 'PV-Leistung', 4200.0);
|
||||||
|
$batterieLeistungId = $test->createVariable(2, 'Batterieleistung', 700.0);
|
||||||
|
$batterieSocId = $test->createVariable(2, 'Batterie-Ladezustand', 62.0);
|
||||||
$einKontaktId = $test->createActionVariable('Kontakt Verbraucher 1-Stufig');
|
$einKontaktId = $test->createActionVariable('Kontakt Verbraucher 1-Stufig');
|
||||||
$einStufigId = $test->createInstance(
|
$einStufigId = $test->createInstance(
|
||||||
'{15879A4E-D0C2-4495-83DE-46E1E462591E}',
|
'{15879A4E-D0C2-4495-83DE-46E1E462591E}',
|
||||||
@@ -79,6 +82,8 @@ return static function (TestContext $test): void {
|
|||||||
'ACNennleistung_kW' => 10.0,
|
'ACNennleistung_kW' => 10.0,
|
||||||
'ACEinspeisegrenze_kW' => 8.0,
|
'ACEinspeisegrenze_kW' => 8.0,
|
||||||
'ACBezugsgrenze_kW' => 0.0,
|
'ACBezugsgrenze_kW' => 0.0,
|
||||||
|
'IstleistungVariableID' => $pvLeistungId,
|
||||||
|
'Istleistungsfaktor' => 1.0,
|
||||||
]];
|
]];
|
||||||
$pvKonfiguration = [[
|
$pvKonfiguration = [[
|
||||||
'ID' => 'pv-1',
|
'ID' => 'pv-1',
|
||||||
@@ -100,6 +105,9 @@ return static function (TestContext $test): void {
|
|||||||
'MaxEntladeleistung_kW' => 5.0,
|
'MaxEntladeleistung_kW' => 5.0,
|
||||||
'WechselrichterID' => 'hybrid-1',
|
'WechselrichterID' => 'hybrid-1',
|
||||||
'Kopplung' => 'hybrid',
|
'Kopplung' => 'hybrid',
|
||||||
|
'LeistungVariableID' => $batterieLeistungId,
|
||||||
|
'Leistungsfaktor' => 1.0,
|
||||||
|
'SOCVariableID' => $batterieSocId,
|
||||||
]];
|
]];
|
||||||
$lizenzcode = trim((string) getenv('ENELIX_TEST_LICENSE_CODE'));
|
$lizenzcode = trim((string) getenv('ENELIX_TEST_LICENSE_CODE'));
|
||||||
$managerId = $test->createInstance(
|
$managerId = $test->createInstance(
|
||||||
@@ -107,6 +115,10 @@ return static function (TestContext $test): void {
|
|||||||
'Manager',
|
'Manager',
|
||||||
[
|
[
|
||||||
'NetzleistungVariableID' => $netzleistungId,
|
'NetzleistungVariableID' => $netzleistungId,
|
||||||
|
'EnergyPieAnzeigen' => true,
|
||||||
|
'EnergiediagrammeAnzeigen' => true,
|
||||||
|
'FunFactsAnzeigen' => true,
|
||||||
|
'EnergieflussAnzeigen' => true,
|
||||||
'Lizenzcode' => $lizenzcode,
|
'Lizenzcode' => $lizenzcode,
|
||||||
'AutomatischeSuche' => false,
|
'AutomatischeSuche' => false,
|
||||||
'VerbraucherZuordnung' => json_encode($zuordnung, JSON_THROW_ON_ERROR),
|
'VerbraucherZuordnung' => json_encode($zuordnung, JSON_THROW_ON_ERROR),
|
||||||
@@ -214,6 +226,80 @@ return static function (TestContext $test): void {
|
|||||||
$test->assertSame(['pv-1'], $topologie['sharedAcLimits'][0]['pvArrayIds'] ?? null);
|
$test->assertSame(['pv-1'], $topologie['sharedAcLimits'][0]['pvArrayIds'] ?? null);
|
||||||
$test->assertSame(['bat-1'], $topologie['sharedAcLimits'][0]['batteryIds'] ?? null);
|
$test->assertSame(['bat-1'], $topologie['sharedAcLimits'][0]['batteryIds'] ?? null);
|
||||||
});
|
});
|
||||||
|
$test->runCase('Energiewerte, Archivregeln und Unterobjekte werden erzeugt', static function (
|
||||||
|
TestContext $test
|
||||||
|
) use ($managerId): void {
|
||||||
|
IPS_RequestAction($managerId, 'MesswerteAufzeichnen', true);
|
||||||
|
$test->assertSame(
|
||||||
|
4200.0,
|
||||||
|
GetValue($test->objectByIdent('PVLeistungArchiv', $managerId))
|
||||||
|
);
|
||||||
|
$test->assertSame(
|
||||||
|
2700.0,
|
||||||
|
GetValue($test->objectByIdent('HausverbrauchLeistungArchiv', $managerId))
|
||||||
|
);
|
||||||
|
$test->assertSame(
|
||||||
|
-800.0,
|
||||||
|
GetValue($test->objectByIdent('NetzleistungArchiv', $managerId))
|
||||||
|
);
|
||||||
|
$test->assertSame(
|
||||||
|
700.0,
|
||||||
|
GetValue($test->objectByIdent('BatterieleistungArchiv', $managerId))
|
||||||
|
);
|
||||||
|
|
||||||
|
$archive = IPS_GetInstanceListByModuleID(
|
||||||
|
'{43192F0B-135B-4CE7-A0A7-1475603F3060}'
|
||||||
|
);
|
||||||
|
$test->assertTrue($archive !== []);
|
||||||
|
$archivID = (int) $archive[0];
|
||||||
|
$leistungID = $test->objectByIdent('PVLeistungArchiv', $managerId);
|
||||||
|
$energieID = $test->objectByIdent('PVEnergie', $managerId);
|
||||||
|
$test->assertSame(true, AC_GetLoggingStatus($archivID, $leistungID));
|
||||||
|
$test->assertSame(0, AC_GetAggregationType($archivID, $leistungID));
|
||||||
|
$test->assertSame(1, AC_GetAggregationType($archivID, $energieID));
|
||||||
|
|
||||||
|
foreach (['Leistungsdiagramm', 'Energiediagramm'] as $ident) {
|
||||||
|
$medienID = $test->objectByIdent($ident, $managerId);
|
||||||
|
$test->assertSame(4, (int) IPS_GetMedia($medienID)['MediaType']);
|
||||||
|
}
|
||||||
|
$html = (string) GetValue($test->objectByIdent('EnergieFunFacts', $managerId));
|
||||||
|
$test->assertTrue(str_contains($html, 'Solar'));
|
||||||
|
$test->assertTrue(str_contains($html, 'Netz & Haus'));
|
||||||
|
$energieflussID = $test->objectByIdent('Energiefluss', $managerId);
|
||||||
|
$test->assertSame(
|
||||||
|
'{BA90FDB3-C203-8F26-B4D6-F3DB99905724}',
|
||||||
|
IPS_GetInstance($energieflussID)['ModuleInfo']['ModuleID']
|
||||||
|
);
|
||||||
|
|
||||||
|
IPS_RequestAction($managerId, 'FormEnergieflussAnzeigen', false);
|
||||||
|
$test->assertSame(true, (bool) IPS_GetObject($energieflussID)['ObjectIsHidden']);
|
||||||
|
IPS_RequestAction($managerId, 'FormEnergieflussAnzeigen', true);
|
||||||
|
$test->assertSame(false, (bool) IPS_GetObject($energieflussID)['ObjectIsHidden']);
|
||||||
|
|
||||||
|
foreach ([
|
||||||
|
'EnergyPieAnzeigen',
|
||||||
|
'EnergiediagrammeAnzeigen',
|
||||||
|
'FunFactsAnzeigen',
|
||||||
|
'EnergieflussAnzeigen',
|
||||||
|
] as $property) {
|
||||||
|
IPS_SetProperty($managerId, $property, false);
|
||||||
|
}
|
||||||
|
IPS_ApplyChanges($managerId);
|
||||||
|
foreach ([
|
||||||
|
'EnergyPie',
|
||||||
|
'Leistungsdiagramm',
|
||||||
|
'Energiediagramm',
|
||||||
|
'EnergieFunFacts',
|
||||||
|
] as $ident) {
|
||||||
|
$test->assertSame(
|
||||||
|
false,
|
||||||
|
@IPS_GetObjectIDByIdent($ident, $managerId),
|
||||||
|
$ident . ' wurde beim Ausschalten nicht entfernt.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
$test->assertSame(true, (bool) IPS_GetObject($energieflussID)['ObjectIsHidden']);
|
||||||
|
});
|
||||||
|
|
||||||
$zustandswert = static function (array $daten, int $instanzId, string $kennung) {
|
$zustandswert = static function (array $daten, int $instanzId, string $kennung) {
|
||||||
foreach ($daten['Verbraucher'] ?? [] as $verbraucher) {
|
foreach ($daten['Verbraucher'] ?? [] as $verbraucher) {
|
||||||
if (($verbraucher['InstanzID'] ?? 0) !== $instanzId) {
|
if (($verbraucher['InstanzID'] ?? 0) !== $instanzId) {
|
||||||
|
|||||||
Reference in New Issue
Block a user