This commit is contained in:
+84
-38
@@ -129,6 +129,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "Label",
|
"type": "Label",
|
||||||
|
"name": "PeakShavingTitel",
|
||||||
"caption": "Peak Shaving am Netzanschlusspunkt"
|
"caption": "Peak Shaving am Netzanschlusspunkt"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -144,19 +145,6 @@
|
|||||||
"minimum": 0.0,
|
"minimum": 0.0,
|
||||||
"digits": 1
|
"digits": 1
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"type": "NumberSpinner",
|
|
||||||
"name": "Einspeisetoleranz",
|
|
||||||
"caption": "Regeltoleranz",
|
|
||||||
"suffix": " W",
|
|
||||||
"minimum": 0.0,
|
|
||||||
"digits": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "CheckBox",
|
|
||||||
"name": "NetzfahrplanAktiv",
|
|
||||||
"caption": "Intelligenten Netzfahrplan zur Vermeidung von Abregelung verwenden"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"type": "CheckBox",
|
"type": "CheckBox",
|
||||||
"name": "AutomatischeSuche",
|
"name": "AutomatischeSuche",
|
||||||
@@ -241,6 +229,37 @@
|
|||||||
"caption": "Anlagentopologie",
|
"caption": "Anlagentopologie",
|
||||||
"expanded": true,
|
"expanded": true,
|
||||||
"items": [
|
"items": [
|
||||||
|
{
|
||||||
|
"type": "RowLayout",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"type": "SelectVariable",
|
||||||
|
"name": "NetzbezugEnergieVariableID",
|
||||||
|
"caption": "Netzbezugszaehler"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "NumberSpinner",
|
||||||
|
"name": "NetzbezugEnergiefaktor",
|
||||||
|
"caption": "Faktor",
|
||||||
|
"digits": 6
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "SelectVariable",
|
||||||
|
"name": "NetzeinspeisungEnergieVariableID",
|
||||||
|
"caption": "Einspeisezaehler"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "NumberSpinner",
|
||||||
|
"name": "NetzeinspeisungEnergiefaktor",
|
||||||
|
"caption": "Faktor",
|
||||||
|
"digits": 6
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Label",
|
||||||
|
"caption": "Energiezaehler werden in kWh erwartet; ueber den Faktor koennen beispielsweise Wh (0,001) umgerechnet werden."
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "List",
|
"type": "List",
|
||||||
"name": "AnlagenWechselrichter",
|
"name": "AnlagenWechselrichter",
|
||||||
@@ -290,30 +309,6 @@
|
|||||||
"digits": 3
|
"digits": 3
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"caption": "Einspeisegrenze",
|
|
||||||
"name": "ACEinspeisegrenze_kW",
|
|
||||||
"width": "150px",
|
|
||||||
"add": 0.0,
|
|
||||||
"edit": {
|
|
||||||
"type": "NumberSpinner",
|
|
||||||
"suffix": " kW",
|
|
||||||
"minimum": 0.0,
|
|
||||||
"digits": 3
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"caption": "Bezugsgrenze",
|
|
||||||
"name": "ACBezugsgrenze_kW",
|
|
||||||
"width": "140px",
|
|
||||||
"add": 0.0,
|
|
||||||
"edit": {
|
|
||||||
"type": "NumberSpinner",
|
|
||||||
"suffix": " kW",
|
|
||||||
"minimum": 0.0,
|
|
||||||
"digits": 3
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"caption": "Istleistung",
|
"caption": "Istleistung",
|
||||||
"name": "IstleistungVariableID",
|
"name": "IstleistungVariableID",
|
||||||
@@ -328,6 +323,20 @@
|
|||||||
"add": 1.0,
|
"add": 1.0,
|
||||||
"edit": {"type": "NumberSpinner", "digits": 4}
|
"edit": {"type": "NumberSpinner", "digits": 4}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"caption": "Erzeugungsenergie",
|
||||||
|
"name": "ErzeugungsenergieVariableID",
|
||||||
|
"width": "160px",
|
||||||
|
"add": 0,
|
||||||
|
"edit": {"type": "SelectVariable"}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"caption": "Energiefaktor",
|
||||||
|
"name": "Erzeugungsenergiefaktor",
|
||||||
|
"width": "110px",
|
||||||
|
"add": 1.0,
|
||||||
|
"edit": {"type": "NumberSpinner", "digits": 6}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"caption": "PV-Begrenzung",
|
"caption": "PV-Begrenzung",
|
||||||
"name": "BegrenzungVariableID",
|
"name": "BegrenzungVariableID",
|
||||||
@@ -562,6 +571,27 @@
|
|||||||
"width": "140px",
|
"width": "140px",
|
||||||
"add": 0,
|
"add": 0,
|
||||||
"edit": {"type": "SelectVariable"}
|
"edit": {"type": "SelectVariable"}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"caption": "Ladeenergie",
|
||||||
|
"name": "LadeenergieVariableID",
|
||||||
|
"width": "140px",
|
||||||
|
"add": 0,
|
||||||
|
"edit": {"type": "SelectVariable"}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"caption": "Entladeenergie",
|
||||||
|
"name": "EntladeenergieVariableID",
|
||||||
|
"width": "150px",
|
||||||
|
"add": 0,
|
||||||
|
"edit": {"type": "SelectVariable"}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"caption": "Energiefaktor",
|
||||||
|
"name": "Energiefaktor",
|
||||||
|
"width": "110px",
|
||||||
|
"add": 1.0,
|
||||||
|
"edit": {"type": "NumberSpinner", "digits": 6}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -719,14 +749,30 @@
|
|||||||
"type": "ExpansionPanel",
|
"type": "ExpansionPanel",
|
||||||
"caption": "Prognose / Forecast",
|
"caption": "Prognose / Forecast",
|
||||||
"items": [
|
"items": [
|
||||||
|
{
|
||||||
|
"type": "CheckBox",
|
||||||
|
"name": "PrognoseAktiv",
|
||||||
|
"caption": "Prognosen aktivieren",
|
||||||
|
"onChange": "IPS_RequestAction($id, 'FormPrognoseAktiv', $PrognoseAktiv);"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Label",
|
||||||
|
"name": "PrognoseLizenzInformation",
|
||||||
|
"caption": "Prognoselizenz wird geprueft."
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "Label",
|
"type": "Label",
|
||||||
"caption": "Forecast verwendet dieselben Messquellen aus der Anlagentopologie und den daraus berechneten Hausverbrauch."
|
"caption": "Forecast verwendet dieselben Messquellen aus der Anlagentopologie und den daraus berechneten Hausverbrauch."
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "CheckBox",
|
||||||
|
"name": "NetzfahrplanAktiv",
|
||||||
|
"caption": "Intelligenten Netzfahrplan zur Vermeidung von PV-Abregelung verwenden"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "NumberSpinner",
|
"type": "NumberSpinner",
|
||||||
"name": "PrognoseSendeintervall",
|
"name": "PrognoseSendeintervall",
|
||||||
"caption": "Sendeintervall",
|
"caption": "Aktuelle Messwerte an den Prognosedienst senden alle",
|
||||||
"minimum": 60,
|
"minimum": 60,
|
||||||
"maximum": 3600,
|
"maximum": 3600,
|
||||||
"suffix": " Sekunden"
|
"suffix": " Sekunden"
|
||||||
|
|||||||
+332
-40
@@ -130,11 +130,15 @@ class Manager extends IPSModule implements ManagerSchnittstelle
|
|||||||
$this->RegisterPropertyFloat('Umschaltdifferenz', 5.0);
|
$this->RegisterPropertyFloat('Umschaltdifferenz', 5.0);
|
||||||
$this->RegisterPropertyBoolean('EinspeisebegrenzungAktiv', false);
|
$this->RegisterPropertyBoolean('EinspeisebegrenzungAktiv', false);
|
||||||
$this->RegisterPropertyFloat('Einspeisegrenze', 0.0);
|
$this->RegisterPropertyFloat('Einspeisegrenze', 0.0);
|
||||||
$this->RegisterPropertyFloat('Einspeisetoleranz', 100.0);
|
$this->RegisterPropertyBoolean('PrognoseAktiv', false);
|
||||||
$this->RegisterPropertyBoolean('NetzfahrplanAktiv', false);
|
$this->RegisterPropertyBoolean('NetzfahrplanAktiv', false);
|
||||||
$this->RegisterPropertyString('AnlagenWechselrichter', '[]');
|
$this->RegisterPropertyString('AnlagenWechselrichter', '[]');
|
||||||
$this->RegisterPropertyString('AnlagenPVFlaechen', '[]');
|
$this->RegisterPropertyString('AnlagenPVFlaechen', '[]');
|
||||||
$this->RegisterPropertyString('AnlagenBatterien', '[]');
|
$this->RegisterPropertyString('AnlagenBatterien', '[]');
|
||||||
|
$this->RegisterPropertyInteger('NetzbezugEnergieVariableID', 0);
|
||||||
|
$this->RegisterPropertyFloat('NetzbezugEnergiefaktor', 1.0);
|
||||||
|
$this->RegisterPropertyInteger('NetzeinspeisungEnergieVariableID', 0);
|
||||||
|
$this->RegisterPropertyFloat('NetzeinspeisungEnergiefaktor', 1.0);
|
||||||
// Verdeckte Altproperties; Messquellen liegen neu ausschliesslich in der Anlagentopologie.
|
// 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);
|
||||||
@@ -196,6 +200,7 @@ class Manager extends IPSModule implements ManagerSchnittstelle
|
|||||||
$this->RegisterAttributeInteger('MessungLetzteLeistungsaufzeichnung', 0);
|
$this->RegisterAttributeInteger('MessungLetzteLeistungsaufzeichnung', 0);
|
||||||
$this->RegisterAttributeInteger('MessungLetzteEnergieaufzeichnung', 0);
|
$this->RegisterAttributeInteger('MessungLetzteEnergieaufzeichnung', 0);
|
||||||
$this->RegisterAttributeString('MessungEnergiePuffer', '{}');
|
$this->RegisterAttributeString('MessungEnergiePuffer', '{}');
|
||||||
|
$this->RegisterAttributeString('MessungZaehlerstaende', '{}');
|
||||||
|
|
||||||
// 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, '');
|
||||||
@@ -261,8 +266,7 @@ class Manager extends IPSModule implements ManagerSchnittstelle
|
|||||||
}
|
}
|
||||||
$this->setzeDiagnosewert('Monatsgrenzen', $this->ReadPropertyString('Monatsgrenzen'));
|
$this->setzeDiagnosewert('Monatsgrenzen', $this->ReadPropertyString('Monatsgrenzen'));
|
||||||
$this->aktualisiereAnschlussstatus();
|
$this->aktualisiereAnschlussstatus();
|
||||||
$geraetezugangFehlt = ($this->hatAnlagentopologie()
|
$geraetezugangFehlt = ($this->ReadPropertyBoolean('PrognoseAktiv')
|
||||||
|| $this->prognoseTelemetrieKonfiguriert()
|
|
||||||
|| $this->ReadPropertyBoolean('StoerueberwachungAktiv'))
|
|| $this->ReadPropertyBoolean('StoerueberwachungAktiv'))
|
||||||
&& $this->ReadAttributeString('PrognoseInstallationsToken') === '';
|
&& $this->ReadAttributeString('PrognoseInstallationsToken') === '';
|
||||||
if (!$this->aktualisiereLizenzfreigabe(
|
if (!$this->aktualisiereLizenzfreigabe(
|
||||||
@@ -306,6 +310,7 @@ class Manager extends IPSModule implements ManagerSchnittstelle
|
|||||||
$automatisch = $this->ReadPropertyBoolean('AutomatischeSuche');
|
$automatisch = $this->ReadPropertyBoolean('AutomatischeSuche');
|
||||||
$lastspitzenmodus = $this->ReadPropertyInteger('Lastspitzenmodus');
|
$lastspitzenmodus = $this->ReadPropertyInteger('Lastspitzenmodus');
|
||||||
$peakShavingLizenziert = $this->peakShavingLizenziert();
|
$peakShavingLizenziert = $this->peakShavingLizenziert();
|
||||||
|
$prognoseLizenziert = $this->prognoseLizenziert();
|
||||||
$netzfahrplanLizenziert = $this->berechtigungLizenziert(
|
$netzfahrplanLizenziert = $this->berechtigungLizenziert(
|
||||||
Lizenzpruefung::NETZFAHRPLAN
|
Lizenzpruefung::NETZFAHRPLAN
|
||||||
);
|
);
|
||||||
@@ -352,19 +357,32 @@ class Manager extends IPSModule implements ManagerSchnittstelle
|
|||||||
'options',
|
'options',
|
||||||
$lastspitzenOptionen
|
$lastspitzenOptionen
|
||||||
);
|
);
|
||||||
foreach (['EinspeisebegrenzungAktiv', 'Einspeisegrenze', 'Einspeisetoleranz'] as $feld) {
|
foreach (['PeakShavingTitel', 'EinspeisebegrenzungAktiv', 'Einspeisegrenze'] as $feld) {
|
||||||
$this->setzeFormularParameter(
|
$this->setzeFormularParameter(
|
||||||
$formular['elements'],
|
$formular['elements'],
|
||||||
$feld,
|
$feld,
|
||||||
'enabled',
|
'visible',
|
||||||
$peakShavingLizenziert
|
$peakShavingLizenziert
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
$prognoseAktiv = $this->ReadPropertyBoolean('PrognoseAktiv');
|
||||||
|
$this->setzeFormularParameter(
|
||||||
|
$formular['elements'],
|
||||||
|
'PrognoseAktiv',
|
||||||
|
'enabled',
|
||||||
|
$prognoseLizenziert || $prognoseAktiv
|
||||||
|
);
|
||||||
|
$this->setzeFormularParameter(
|
||||||
|
$formular['elements'],
|
||||||
|
'PrognoseLizenzInformation',
|
||||||
|
'caption',
|
||||||
|
$this->prognoseLizenzStatustext($prognoseAktiv, $prognoseLizenziert)
|
||||||
|
);
|
||||||
$this->setzeFormularParameter(
|
$this->setzeFormularParameter(
|
||||||
$formular['elements'],
|
$formular['elements'],
|
||||||
'NetzfahrplanAktiv',
|
'NetzfahrplanAktiv',
|
||||||
'enabled',
|
'enabled',
|
||||||
$peakShavingLizenziert && $netzfahrplanLizenziert
|
$prognoseAktiv && $peakShavingLizenziert && $netzfahrplanLizenziert
|
||||||
);
|
);
|
||||||
$this->setzeFormularParameter(
|
$this->setzeFormularParameter(
|
||||||
$formular['elements'],
|
$formular['elements'],
|
||||||
@@ -441,6 +459,27 @@ class Manager extends IPSModule implements ManagerSchnittstelle
|
|||||||
$this->zeichneMesswerteAuf();
|
$this->zeichneMesswerteAuf();
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
case 'FormPrognoseAktiv':
|
||||||
|
$aktiv = (bool) $wert;
|
||||||
|
$lizenziert = $this->prognoseLizenziert();
|
||||||
|
$netzfahrplanLizenziert = $this->berechtigungLizenziert(
|
||||||
|
Lizenzpruefung::NETZFAHRPLAN
|
||||||
|
);
|
||||||
|
$this->UpdateFormField(
|
||||||
|
'PrognoseLizenzInformation',
|
||||||
|
'caption',
|
||||||
|
$this->prognoseLizenzStatustext($aktiv, $lizenziert)
|
||||||
|
);
|
||||||
|
$this->UpdateFormField(
|
||||||
|
'NetzfahrplanAktiv',
|
||||||
|
'enabled',
|
||||||
|
$aktiv && $netzfahrplanLizenziert && $this->peakShavingLizenziert()
|
||||||
|
);
|
||||||
|
if (!$aktiv) {
|
||||||
|
$this->UpdateFormField('NetzfahrplanAktiv', 'value', false);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
|
||||||
case 'FormEnergyPieAnzeigen':
|
case 'FormEnergyPieAnzeigen':
|
||||||
$this->synchronisiereEnergyPie((bool) $wert);
|
$this->synchronisiereEnergyPie((bool) $wert);
|
||||||
return;
|
return;
|
||||||
@@ -463,7 +502,8 @@ class Manager extends IPSModule implements ManagerSchnittstelle
|
|||||||
}
|
}
|
||||||
$this->aktualisiereLizenzfreigabe($wert, true);
|
$this->aktualisiereLizenzfreigabe($wert, true);
|
||||||
$this->UpdateFormField('LizenzInformation', 'caption', $this->lizenzStatustext($wert));
|
$this->UpdateFormField('LizenzInformation', 'caption', $this->lizenzStatustext($wert));
|
||||||
$this->aktualisiereLastspitzenformular();
|
$this->aktualisiereLastspitzenformular($wert);
|
||||||
|
$this->aktualisierePrognoseformular($wert);
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case 'FormAutomatischeSuche':
|
case 'FormAutomatischeSuche':
|
||||||
@@ -737,7 +777,6 @@ class Manager extends IPSModule implements ManagerSchnittstelle
|
|||||||
$ergebnis = EinspeiseRegler::verteile(
|
$ergebnis = EinspeiseRegler::verteile(
|
||||||
$netzleistung,
|
$netzleistung,
|
||||||
$this->ReadPropertyFloat('Einspeisegrenze'),
|
$this->ReadPropertyFloat('Einspeisegrenze'),
|
||||||
$this->ReadPropertyFloat('Einspeisetoleranz'),
|
|
||||||
$wechselrichter,
|
$wechselrichter,
|
||||||
$letzteGrenzen
|
$letzteGrenzen
|
||||||
);
|
);
|
||||||
@@ -1713,6 +1752,105 @@ class Manager extends IPSModule implements ManagerSchnittstelle
|
|||||||
return $quellen;
|
return $quellen;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<string, list<array{Schluessel: string, VariableID: int, Faktor: float}>>|null
|
||||||
|
*/
|
||||||
|
private function energieZaehlerquellen(): ?array
|
||||||
|
{
|
||||||
|
$wechselrichter = $this->leseAnlagenKonfigurationsliste('AnlagenWechselrichter');
|
||||||
|
$pvFlaechen = $this->leseAnlagenKonfigurationsliste('AnlagenPVFlaechen');
|
||||||
|
$batterien = $this->leseAnlagenKonfigurationsliste('AnlagenBatterien');
|
||||||
|
$pvWechselrichter = [];
|
||||||
|
foreach ($pvFlaechen as $pvFlaeche) {
|
||||||
|
$id = trim((string) ($pvFlaeche['WechselrichterID'] ?? ''));
|
||||||
|
if ($id !== '') {
|
||||||
|
$pvWechselrichter[$id] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$hatZaehler = $this->ReadPropertyInteger('NetzbezugEnergieVariableID') > 0
|
||||||
|
|| $this->ReadPropertyInteger('NetzeinspeisungEnergieVariableID') > 0;
|
||||||
|
foreach ($wechselrichter as $eintrag) {
|
||||||
|
$hatZaehler = $hatZaehler || (int) ($eintrag['ErzeugungsenergieVariableID'] ?? 0) > 0;
|
||||||
|
}
|
||||||
|
foreach ($batterien as $eintrag) {
|
||||||
|
$hatZaehler = $hatZaehler
|
||||||
|
|| (int) ($eintrag['LadeenergieVariableID'] ?? 0) > 0
|
||||||
|
|| (int) ($eintrag['EntladeenergieVariableID'] ?? 0) > 0;
|
||||||
|
}
|
||||||
|
if (!$hatZaehler) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$quellen = [
|
||||||
|
'PVEnergie' => [],
|
||||||
|
'NetzbezugEnergie' => [],
|
||||||
|
'EinspeisungEnergie' => [],
|
||||||
|
'BatterieLadenEnergie' => [],
|
||||||
|
'BatterieEntladenEnergie' => [],
|
||||||
|
];
|
||||||
|
$netz = [
|
||||||
|
'NetzbezugEnergie' => [
|
||||||
|
$this->ReadPropertyInteger('NetzbezugEnergieVariableID'),
|
||||||
|
$this->ReadPropertyFloat('NetzbezugEnergiefaktor'),
|
||||||
|
'Netzbezug',
|
||||||
|
],
|
||||||
|
'EinspeisungEnergie' => [
|
||||||
|
$this->ReadPropertyInteger('NetzeinspeisungEnergieVariableID'),
|
||||||
|
$this->ReadPropertyFloat('NetzeinspeisungEnergiefaktor'),
|
||||||
|
'Netzeinspeisung',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
foreach ($netz as $ident => [$variableID, $faktor, $name]) {
|
||||||
|
$quellen[$ident][] = $this->energieZaehlerquelle(
|
||||||
|
$name,
|
||||||
|
$variableID,
|
||||||
|
$faktor,
|
||||||
|
strtolower($ident)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($wechselrichter as $index => $eintrag) {
|
||||||
|
$typ = (string) ($eintrag['Typ'] ?? '');
|
||||||
|
$id = trim((string) ($eintrag['ID'] ?? ''));
|
||||||
|
if ($pvWechselrichter !== []) {
|
||||||
|
if (!isset($pvWechselrichter[$id])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
} elseif (!in_array($typ, ['pv', 'hybrid'], true)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$quellen['PVEnergie'][] = $this->energieZaehlerquelle(
|
||||||
|
'Wechselrichter ' . ($index + 1) . ': Erzeugungsenergie',
|
||||||
|
(int) ($eintrag['ErzeugungsenergieVariableID'] ?? 0),
|
||||||
|
$eintrag['Erzeugungsenergiefaktor'] ?? 1.0,
|
||||||
|
'pv:' . $id
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if ($quellen['PVEnergie'] === []) {
|
||||||
|
throw new InvalidArgumentException('Mindestens ein PV-Erzeugungszaehler ist erforderlich.');
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($batterien as $index => $eintrag) {
|
||||||
|
$id = trim((string) ($eintrag['ID'] ?? ''));
|
||||||
|
$faktor = $eintrag['Energiefaktor'] ?? 1.0;
|
||||||
|
$quellen['BatterieLadenEnergie'][] = $this->energieZaehlerquelle(
|
||||||
|
'Batterie ' . ($index + 1) . ': Ladeenergie',
|
||||||
|
(int) ($eintrag['LadeenergieVariableID'] ?? 0),
|
||||||
|
$faktor,
|
||||||
|
'batterie-laden:' . $id
|
||||||
|
);
|
||||||
|
$quellen['BatterieEntladenEnergie'][] = $this->energieZaehlerquelle(
|
||||||
|
'Batterie ' . ($index + 1) . ': Entladeenergie',
|
||||||
|
(int) ($eintrag['EntladeenergieVariableID'] ?? 0),
|
||||||
|
$faktor,
|
||||||
|
'batterie-entladen:' . $id
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $quellen;
|
||||||
|
}
|
||||||
|
|
||||||
/** @return list<array<string, mixed>> */
|
/** @return list<array<string, mixed>> */
|
||||||
private function leseAnlagenKonfigurationsliste(string $property): array
|
private function leseAnlagenKonfigurationsliste(string $property): array
|
||||||
{
|
{
|
||||||
@@ -1738,6 +1876,37 @@ class Manager extends IPSModule implements ManagerSchnittstelle
|
|||||||
return $faktor;
|
return $faktor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param mixed $faktor
|
||||||
|
* @return array{Schluessel: string, VariableID: int, Faktor: float}
|
||||||
|
*/
|
||||||
|
private function energieZaehlerquelle(
|
||||||
|
string $bezeichnung,
|
||||||
|
int $variableID,
|
||||||
|
$faktor,
|
||||||
|
string $schluessel
|
||||||
|
): array {
|
||||||
|
if (!$this->istNumerischeVariable($variableID)) {
|
||||||
|
throw new InvalidArgumentException(
|
||||||
|
$bezeichnung . ' muss eine numerische Variable sein.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if ((!is_int($faktor) && !is_float($faktor))
|
||||||
|
|| !is_finite((float) $faktor)
|
||||||
|
|| (float) $faktor <= 0.0
|
||||||
|
) {
|
||||||
|
throw new InvalidArgumentException(
|
||||||
|
$bezeichnung . ': Energiefaktor muss groesser als 0 sein.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'Schluessel' => $schluessel,
|
||||||
|
'VariableID' => $variableID,
|
||||||
|
'Faktor' => (float) $faktor,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
/** @return array{PV: float, Haus: float, Netz: float, Batterie: float} */
|
/** @return array{PV: float, Haus: float, Netz: float, Batterie: float} */
|
||||||
private function leseMessleistungen(): array
|
private function leseMessleistungen(): array
|
||||||
{
|
{
|
||||||
@@ -1780,6 +1949,58 @@ class Manager extends IPSModule implements ManagerSchnittstelle
|
|||||||
return $gewicht > 0.0 ? $summe / $gewicht : null;
|
return $gewicht > 0.0 ? $summe / $gewicht : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @return array<string, float>|null */
|
||||||
|
private function leseEnergieZaehlerDeltas(): ?array
|
||||||
|
{
|
||||||
|
$quellen = $this->energieZaehlerquellen();
|
||||||
|
if ($quellen === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$alt = $this->leseJsonAttribut('MessungZaehlerstaende');
|
||||||
|
$neu = [];
|
||||||
|
$deltas = [];
|
||||||
|
$vollstaendigeBasis = true;
|
||||||
|
foreach ($quellen as $ident => $identQuellen) {
|
||||||
|
$deltas[$ident] = 0.0;
|
||||||
|
foreach ($identQuellen as $quelle) {
|
||||||
|
$schluessel = $quelle['Schluessel'];
|
||||||
|
$stand = (float) GetValue($quelle['VariableID']) * $quelle['Faktor'];
|
||||||
|
if (!is_finite($stand) || $stand < 0.0) {
|
||||||
|
throw new RuntimeException(
|
||||||
|
'Energiezaehler ' . $schluessel . ' liefert keinen gueltigen Stand.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
$neu[$schluessel] = $stand;
|
||||||
|
if (!isset($alt[$schluessel]) || !is_numeric($alt[$schluessel])) {
|
||||||
|
$vollstaendigeBasis = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$delta = $stand - (float) $alt[$schluessel];
|
||||||
|
if ($delta >= 0.0) {
|
||||||
|
$deltas[$ident] += $delta;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->WriteAttributeString(
|
||||||
|
'MessungZaehlerstaende',
|
||||||
|
json_encode($neu, JSON_THROW_ON_ERROR | JSON_PRESERVE_ZERO_FRACTION)
|
||||||
|
);
|
||||||
|
if (!$vollstaendigeBasis) {
|
||||||
|
return array_fill_keys(self::ENERGIE_VARIABLEN, 0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
$deltas['Hausenergie'] = EnergieMessung::hausenergie(
|
||||||
|
$deltas['PVEnergie'],
|
||||||
|
$deltas['NetzbezugEnergie'],
|
||||||
|
$deltas['EinspeisungEnergie'],
|
||||||
|
$deltas['BatterieLadenEnergie'],
|
||||||
|
$deltas['BatterieEntladenEnergie']
|
||||||
|
);
|
||||||
|
|
||||||
|
return $deltas;
|
||||||
|
}
|
||||||
|
|
||||||
private function zeichneMesswerteAuf(): void
|
private function zeichneMesswerteAuf(): void
|
||||||
{
|
{
|
||||||
if (!$this->messungKonfiguriert()) {
|
if (!$this->messungKonfiguriert()) {
|
||||||
@@ -1789,11 +2010,13 @@ class Manager extends IPSModule implements ManagerSchnittstelle
|
|||||||
$leistungen = $this->leseMessleistungen();
|
$leistungen = $this->leseMessleistungen();
|
||||||
$letzterZeitstempel = $this->ReadAttributeInteger('MessungLetzterZeitstempel');
|
$letzterZeitstempel = $this->ReadAttributeInteger('MessungLetzterZeitstempel');
|
||||||
$puffer = $this->leseJsonAttribut('MessungEnergiePuffer');
|
$puffer = $this->leseJsonAttribut('MessungEnergiePuffer');
|
||||||
if ($letzterZeitstempel > 0) {
|
$energieDeltas = $this->leseEnergieZaehlerDeltas();
|
||||||
|
if ($energieDeltas === null && $letzterZeitstempel > 0) {
|
||||||
$sekunden = min(300, max(0, $jetzt - $letzterZeitstempel));
|
$sekunden = min(300, max(0, $jetzt - $letzterZeitstempel));
|
||||||
foreach (EnergieMessung::energieDeltas($leistungen, $sekunden) as $ident => $delta) {
|
$energieDeltas = EnergieMessung::energieDeltas($leistungen, $sekunden);
|
||||||
$puffer[$ident] = max(0.0, (float) ($puffer[$ident] ?? 0.0)) + $delta;
|
|
||||||
}
|
}
|
||||||
|
foreach ($energieDeltas ?? [] as $ident => $delta) {
|
||||||
|
$puffer[$ident] = max(0.0, (float) ($puffer[$ident] ?? 0.0)) + $delta;
|
||||||
}
|
}
|
||||||
$this->WriteAttributeInteger('MessungLetzterZeitstempel', $jetzt);
|
$this->WriteAttributeInteger('MessungLetzterZeitstempel', $jetzt);
|
||||||
$this->WriteAttributeString(
|
$this->WriteAttributeString(
|
||||||
@@ -1899,8 +2122,7 @@ class Manager extends IPSModule implements ManagerSchnittstelle
|
|||||||
&& $this->lizenzBerechtigungenPassen($lease, $lastspitzenmodus);
|
&& $this->lizenzBerechtigungenPassen($lease, $lastspitzenmodus);
|
||||||
$gueltig = $leaseGueltig && $berechtigungenPassen;
|
$gueltig = $leaseGueltig && $berechtigungenPassen;
|
||||||
$berechtigungenVeraltet = $basisGueltig && !$berechtigungenPassen;
|
$berechtigungenVeraltet = $basisGueltig && !$berechtigungenPassen;
|
||||||
$geraetezugangFehlt = ($this->hatAnlagentopologie()
|
$geraetezugangFehlt = ($this->ReadPropertyBoolean('PrognoseAktiv')
|
||||||
|| $this->prognoseTelemetrieKonfiguriert()
|
|
||||||
|| $this->ReadPropertyBoolean('StoerueberwachungAktiv'))
|
|| $this->ReadPropertyBoolean('StoerueberwachungAktiv'))
|
||||||
&& $this->ReadAttributeString('PrognoseInstallationsToken') === '';
|
&& $this->ReadAttributeString('PrognoseInstallationsToken') === '';
|
||||||
$routineFaellig = (!$gueltig && !$berechtigungenVeraltet)
|
$routineFaellig = (!$gueltig && !$berechtigungenVeraltet)
|
||||||
@@ -2022,15 +2244,36 @@ class Manager extends IPSModule implements ManagerSchnittstelle
|
|||||||
return $this->ReadPropertyBoolean('EinspeisebegrenzungAktiv') ? 1 : 0;
|
return $this->ReadPropertyBoolean('EinspeisebegrenzungAktiv') ? 1 : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function peakShavingLizenziert(): bool
|
private function peakShavingLizenziert(?string $lizenzcode = null): bool
|
||||||
{
|
{
|
||||||
return $this->berechtigungLizenziert(Lizenzpruefung::MANAGER_PEAK);
|
return $this->berechtigungLizenziert(Lizenzpruefung::MANAGER_PEAK, $lizenzcode);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function berechtigungLizenziert(string $kennung): bool
|
private function prognoseLizenziert(?string $lizenzcode = null): bool
|
||||||
|
{
|
||||||
|
return $this->berechtigungLizenziert(Lizenzpruefung::NETZFAHRPLAN, $lizenzcode)
|
||||||
|
|| ($this->berechtigungLizenziert(Lizenzpruefung::PROGNOSE_PV, $lizenzcode)
|
||||||
|
&& $this->berechtigungLizenziert(
|
||||||
|
Lizenzpruefung::PROGNOSE_VERBRAUCH,
|
||||||
|
$lizenzcode
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param array<string, mixed> $lease */
|
||||||
|
private function prognoseBerechtigungInLease(array $lease): bool
|
||||||
|
{
|
||||||
|
return Lizenzpruefung::berechtigungsMenge($lease, Lizenzpruefung::NETZFAHRPLAN) > 0
|
||||||
|
|| (Lizenzpruefung::berechtigungsMenge($lease, Lizenzpruefung::PROGNOSE_PV) > 0
|
||||||
|
&& Lizenzpruefung::berechtigungsMenge(
|
||||||
|
$lease,
|
||||||
|
Lizenzpruefung::PROGNOSE_VERBRAUCH
|
||||||
|
) > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function berechtigungLizenziert(string $kennung, ?string $lizenzcode = null): bool
|
||||||
{
|
{
|
||||||
$lizenzcode = Lizenzpruefung::normalisiereCode(
|
$lizenzcode = Lizenzpruefung::normalisiereCode(
|
||||||
$this->ReadPropertyString('Lizenzcode')
|
$lizenzcode ?? $this->ReadPropertyString('Lizenzcode')
|
||||||
);
|
);
|
||||||
if (!Lizenzpruefung::istLizenzcodeGueltig($lizenzcode)) {
|
if (!Lizenzpruefung::istLizenzcodeGueltig($lizenzcode)) {
|
||||||
return false;
|
return false;
|
||||||
@@ -2068,6 +2311,11 @@ class Manager extends IPSModule implements ManagerSchnittstelle
|
|||||||
) {
|
) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if ($this->ReadPropertyBoolean('PrognoseAktiv')
|
||||||
|
&& !$this->prognoseBerechtigungInLease($lease)
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if ($this->ReadPropertyBoolean('NetzfahrplanAktiv')
|
if ($this->ReadPropertyBoolean('NetzfahrplanAktiv')
|
||||||
&& Lizenzpruefung::berechtigungsMenge(
|
&& Lizenzpruefung::berechtigungsMenge(
|
||||||
$lease,
|
$lease,
|
||||||
@@ -2108,9 +2356,9 @@ class Manager extends IPSModule implements ManagerSchnittstelle
|
|||||||
return $optionen;
|
return $optionen;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function aktualisiereLastspitzenformular(): void
|
private function aktualisiereLastspitzenformular(?string $lizenzcode = null): void
|
||||||
{
|
{
|
||||||
$lizenziert = $this->peakShavingLizenziert();
|
$lizenziert = $this->peakShavingLizenziert($lizenzcode);
|
||||||
$modus = $lizenziert ? $this->ReadPropertyInteger('Lastspitzenmodus') : 0;
|
$modus = $lizenziert ? $this->ReadPropertyInteger('Lastspitzenmodus') : 0;
|
||||||
$this->UpdateFormField(
|
$this->UpdateFormField(
|
||||||
'Lastspitzenmodus',
|
'Lastspitzenmodus',
|
||||||
@@ -2118,6 +2366,9 @@ class Manager extends IPSModule implements ManagerSchnittstelle
|
|||||||
json_encode($this->lastspitzenOptionen($lizenziert), JSON_THROW_ON_ERROR)
|
json_encode($this->lastspitzenOptionen($lizenziert), JSON_THROW_ON_ERROR)
|
||||||
);
|
);
|
||||||
$this->UpdateFormField('Lastspitzenmodus', 'value', $modus);
|
$this->UpdateFormField('Lastspitzenmodus', 'value', $modus);
|
||||||
|
foreach (['PeakShavingTitel', 'EinspeisebegrenzungAktiv', 'Einspeisegrenze'] as $feld) {
|
||||||
|
$this->UpdateFormField($feld, 'visible', $lizenziert);
|
||||||
|
}
|
||||||
$this->UpdateFormField(
|
$this->UpdateFormField(
|
||||||
'Lastspitzengrenze',
|
'Lastspitzengrenze',
|
||||||
'visible',
|
'visible',
|
||||||
@@ -2134,6 +2385,43 @@ class Manager extends IPSModule implements ManagerSchnittstelle
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function aktualisierePrognoseformular(?string $lizenzcode = null): void
|
||||||
|
{
|
||||||
|
$aktiv = $this->ReadPropertyBoolean('PrognoseAktiv');
|
||||||
|
$lizenziert = $this->prognoseLizenziert($lizenzcode);
|
||||||
|
$netzfahrplanLizenziert = $this->berechtigungLizenziert(
|
||||||
|
Lizenzpruefung::NETZFAHRPLAN,
|
||||||
|
$lizenzcode
|
||||||
|
);
|
||||||
|
$this->UpdateFormField('PrognoseAktiv', 'enabled', $lizenziert || $aktiv);
|
||||||
|
$this->UpdateFormField(
|
||||||
|
'PrognoseLizenzInformation',
|
||||||
|
'caption',
|
||||||
|
$this->prognoseLizenzStatustext($aktiv, $lizenziert)
|
||||||
|
);
|
||||||
|
$this->UpdateFormField(
|
||||||
|
'NetzfahrplanAktiv',
|
||||||
|
'enabled',
|
||||||
|
$aktiv && $netzfahrplanLizenziert
|
||||||
|
&& $this->peakShavingLizenziert($lizenzcode)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function prognoseLizenzStatustext(bool $aktiv, bool $lizenziert): string
|
||||||
|
{
|
||||||
|
$managerID = $this->ReadAttributeString('LizenzInstallationID');
|
||||||
|
if (!$lizenziert) {
|
||||||
|
return 'Prognosen sind deaktiviert. Fuer die Manager-ID '
|
||||||
|
. $managerID
|
||||||
|
. ' fehlen forecast_pv plus forecast_load oder grid_schedule.';
|
||||||
|
}
|
||||||
|
|
||||||
|
return ($aktiv ? 'Prognosen sind aktiviert. ' : 'Prognosen sind deaktiviert. ')
|
||||||
|
. 'Die passende Lizenz fuer die Manager-ID '
|
||||||
|
. $managerID
|
||||||
|
. ' ist vorhanden.';
|
||||||
|
}
|
||||||
|
|
||||||
private function lizenzStatustext(?string $lizenzcode = null): string
|
private function lizenzStatustext(?string $lizenzcode = null): string
|
||||||
{
|
{
|
||||||
$code = Lizenzpruefung::normalisiereCode(
|
$code = Lizenzpruefung::normalisiereCode(
|
||||||
@@ -2184,8 +2472,7 @@ class Manager extends IPSModule implements ManagerSchnittstelle
|
|||||||
$nutzlast = json_encode([
|
$nutzlast = json_encode([
|
||||||
'code' => $lizenzcode,
|
'code' => $lizenzcode,
|
||||||
'installationId' => $installationID,
|
'installationId' => $installationID,
|
||||||
'issueDeviceToken' => ($this->hatAnlagentopologie()
|
'issueDeviceToken' => ($this->ReadPropertyBoolean('PrognoseAktiv')
|
||||||
|| $this->prognoseTelemetrieKonfiguriert()
|
|
||||||
|| $this->ReadPropertyBoolean('StoerueberwachungAktiv'))
|
|| $this->ReadPropertyBoolean('StoerueberwachungAktiv'))
|
||||||
&& $this->ReadAttributeString('PrognoseInstallationsToken') === '',
|
&& $this->ReadAttributeString('PrognoseInstallationsToken') === '',
|
||||||
], JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES);
|
], JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES);
|
||||||
@@ -2256,10 +2543,11 @@ class Manager extends IPSModule implements ManagerSchnittstelle
|
|||||||
|
|
||||||
private function aktualisiereAnschlussstatus(): void
|
private function aktualisiereAnschlussstatus(): void
|
||||||
{
|
{
|
||||||
foreach ([
|
$this->setzeDiagnosewert(
|
||||||
'PrognoseAnschluss' => 'Prognosestatus',
|
'Prognosestatus',
|
||||||
'SDLAnschluss' => 'SDLStatus',
|
$this->ReadPropertyBoolean('PrognoseAktiv') ? 'Wartet' : 'NichtVerwendet'
|
||||||
] as $property => $variable) {
|
);
|
||||||
|
foreach (['SDLAnschluss' => 'SDLStatus'] as $property => $variable) {
|
||||||
$konfiguration = json_decode($this->ReadPropertyString($property), true, 512, JSON_THROW_ON_ERROR);
|
$konfiguration = json_decode($this->ReadPropertyString($property), true, 512, JSON_THROW_ON_ERROR);
|
||||||
$aktiv = is_array($konfiguration) && ($konfiguration['Aktiv'] ?? false) === true;
|
$aktiv = is_array($konfiguration) && ($konfiguration['Aktiv'] ?? false) === true;
|
||||||
$this->setzeDiagnosewert($variable, $aktiv ? 'Fehler' : 'NichtVerwendet');
|
$this->setzeDiagnosewert($variable, $aktiv ? 'Fehler' : 'NichtVerwendet');
|
||||||
@@ -2306,16 +2594,22 @@ class Manager extends IPSModule implements ManagerSchnittstelle
|
|||||||
) {
|
) {
|
||||||
throw new InvalidArgumentException('Umschaltdifferenz muss zwischen 0 und 100 liegen.');
|
throw new InvalidArgumentException('Umschaltdifferenz muss zwischen 0 und 100 liegen.');
|
||||||
}
|
}
|
||||||
if ($this->ReadPropertyFloat('Einspeisegrenze') < 0.0
|
if ($this->ReadPropertyFloat('Einspeisegrenze') < 0.0) {
|
||||||
|| $this->ReadPropertyFloat('Einspeisetoleranz') < 0.0
|
throw new InvalidArgumentException('Einspeisegrenze darf nicht negativ sein.');
|
||||||
) {
|
|
||||||
throw new InvalidArgumentException('Einspeisegrenze und Einspeisetoleranz duerfen nicht negativ sein.');
|
|
||||||
}
|
}
|
||||||
if ($this->ReadPropertyBoolean('EinspeisebegrenzungAktiv')) {
|
if ($this->ReadPropertyBoolean('EinspeisebegrenzungAktiv')) {
|
||||||
$this->regelbareWechselrichter();
|
$this->regelbareWechselrichter();
|
||||||
}
|
}
|
||||||
|
if ($this->ReadPropertyBoolean('PrognoseAktiv')
|
||||||
|
&& !$this->prognoseTelemetrieKonfiguriert()
|
||||||
|
) {
|
||||||
|
throw new InvalidArgumentException(
|
||||||
|
'Aktive Prognosen benoetigen Netzleistung, PV-Leistung und Batterie-Ladezustand.'
|
||||||
|
);
|
||||||
|
}
|
||||||
if ($this->ReadPropertyBoolean('NetzfahrplanAktiv')
|
if ($this->ReadPropertyBoolean('NetzfahrplanAktiv')
|
||||||
&& (!$this->ReadPropertyBoolean('EinspeisebegrenzungAktiv')
|
&& (!$this->ReadPropertyBoolean('PrognoseAktiv')
|
||||||
|
|| !$this->ReadPropertyBoolean('EinspeisebegrenzungAktiv')
|
||||||
|| !$this->prognoseTelemetrieKonfiguriert())
|
|| !$this->prognoseTelemetrieKonfiguriert())
|
||||||
) {
|
) {
|
||||||
throw new InvalidArgumentException(
|
throw new InvalidArgumentException(
|
||||||
@@ -2329,6 +2623,7 @@ class Manager extends IPSModule implements ManagerSchnittstelle
|
|||||||
$this->pvMessquellen();
|
$this->pvMessquellen();
|
||||||
$this->batterieMessquellen();
|
$this->batterieMessquellen();
|
||||||
$this->batterieSocQuellen();
|
$this->batterieSocQuellen();
|
||||||
|
$this->energieZaehlerquellen();
|
||||||
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)) {
|
||||||
@@ -2348,18 +2643,10 @@ class Manager extends IPSModule implements ManagerSchnittstelle
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function hatAnlagentopologie(): bool
|
|
||||||
{
|
|
||||||
$topologie = $this->leseAnlagentopologie();
|
|
||||||
|
|
||||||
return $topologie['inverters'] !== []
|
|
||||||
|| $topologie['pvArrays'] !== []
|
|
||||||
|| $topologie['batteries'] !== [];
|
|
||||||
}
|
|
||||||
|
|
||||||
private function prognoseTelemetrieKonfiguriert(): bool
|
private function prognoseTelemetrieKonfiguriert(): bool
|
||||||
{
|
{
|
||||||
return $this->messquellenKonfiguriert()
|
return $this->ReadPropertyBoolean('PrognoseAktiv')
|
||||||
|
&& $this->messquellenKonfiguriert()
|
||||||
&& $this->aktuelleBatterieSoc() !== null;
|
&& $this->aktuelleBatterieSoc() !== null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2407,6 +2694,11 @@ class Manager extends IPSModule implements ManagerSchnittstelle
|
|||||||
|
|
||||||
private function synchronisiereAnlagentopologie(): void
|
private function synchronisiereAnlagentopologie(): void
|
||||||
{
|
{
|
||||||
|
if (!$this->ReadPropertyBoolean('PrognoseAktiv')) {
|
||||||
|
$this->setzeDiagnosewert('Prognosestatus', 'NichtVerwendet');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$topologie = $this->leseAnlagentopologie();
|
$topologie = $this->leseAnlagentopologie();
|
||||||
if ($topologie['inverters'] === []
|
if ($topologie['inverters'] === []
|
||||||
|
|||||||
@@ -62,12 +62,15 @@ Die drei Regelungsvariablen `Aktiv`, `Betriebsart` und `Netzleistung` sind immer
|
|||||||
| `Umschaltdifferenz` | Float | `5` %; Mindestdifferenz der berechneten Sollleistungen vor Umschaltung. |
|
| `Umschaltdifferenz` | Float | `5` %; Mindestdifferenz der berechneten Sollleistungen vor Umschaltung. |
|
||||||
| `EinspeisebegrenzungAktiv` | Boolean | `false`; gemeinsame Exportgrenze am Netzanschlusspunkt aktivieren. |
|
| `EinspeisebegrenzungAktiv` | Boolean | `false`; gemeinsame Exportgrenze am Netzanschlusspunkt aktivieren. |
|
||||||
| `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. |
|
| `PrognoseAktiv` | Boolean | `false`; Prognosetelemetrie und Topologiesynchronisation mit `forecast_pv` plus `forecast_load` oder `grid_schedule` aktivieren. |
|
||||||
| `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, Istleistungsmessung und optionalem Begrenzungsregister. |
|
| `NetzbezugEnergieVariableID` | Integer | Optionaler kumulativer Netzbezugszaehler. |
|
||||||
|
| `NetzeinspeisungEnergieVariableID` | Integer | Optionaler kumulativer Einspeisezaehler. |
|
||||||
|
| `NetzbezugEnergiefaktor`, `NetzeinspeisungEnergiefaktor` | Float | Umrechnung der Netzenergiezaehler nach kWh. |
|
||||||
|
| `AnlagenWechselrichter` | String/JSON | `[]`; Wechselrichter mit Typ, AC-Nennleistung, Leistungs- und Erzeugungsenergiemessung sowie 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, Kopplung, Leistungs- und SOC-Messung. |
|
| `AnlagenBatterien` | String/JSON | `[]`; Batteriespeicher mit Kapazitaeten, Kopplung, Leistung, SOC sowie Lade- und Entladeenergiezaehlern. |
|
||||||
| `PrognoseSendeintervall` | Integer | `60` s; zulaessig sind 60 bis 3600 Sekunden. |
|
| `PrognoseSendeintervall` | Integer | `60` s; Intervall fuer den Upload aktueller Messwerte, 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. |
|
||||||
| `Lizenzcode` | String | Im Enelix-Lizenzportal erworbener Aktivierungscode. |
|
| `Lizenzcode` | String | Im Enelix-Lizenzportal erworbener Aktivierungscode. |
|
||||||
@@ -99,9 +102,14 @@ konfiguriert sind, tastet der Manager die Quellen minuetlich ab. PV-Leistung ist
|
|||||||
die Summe der den PV-Flaechen zugeordneten Wechselrichter. Batterieleistung und
|
die Summe der den PV-Flaechen zugeordneten Wechselrichter. Batterieleistung und
|
||||||
der kapazitaetsgewichtete SOC stammen aus den Batteriespeichern. Positive
|
der kapazitaetsgewichtete SOC stammen aus den Batteriespeichern. Positive
|
||||||
Batterieleistung bedeutet Laden, negative Entladen. Der Hausverbrauch wird als
|
Batterieleistung bedeutet Laden, negative Entladen. Der Hausverbrauch wird als
|
||||||
`PV + Netz - Batterie` bilanziert. Dieselben Werte versorgen ohne zweite
|
`PV + Netz - Batterie` bilanziert. Sind Netzbezugs-, Einspeise-, PV-Erzeugungs-
|
||||||
Eingabe auch die Prognosetelemetrie. Ausfallluecken ueber fuenf Minuten werden
|
sowie gegebenenfalls Batterie-Lade- und Entladezaehler vollstaendig hinterlegt,
|
||||||
nicht nachberechnet.
|
verwendet der Manager deren Deltas fuer die Energieaufzeichnung. Die Hausenergie
|
||||||
|
wird dann als `PV + Netzbezug + Batterieentladung - Einspeisung - Batterieladung`
|
||||||
|
berechnet. Ohne vollstaendigen Zaehlerdatensatz integriert der Manager weiterhin
|
||||||
|
die Leistungswerte. Dieselben Quellen versorgen ohne zweite Eingabe auch die
|
||||||
|
Prognosetelemetrie. Ausfallluecken ueber fuenf Minuten werden bei der
|
||||||
|
Leistungsintegration nicht nachberechnet.
|
||||||
|
|
||||||
Batteriespeicher bleiben als physische Komponenten samt Messquellen in der
|
Batteriespeicher bleiben als physische Komponenten samt Messquellen in der
|
||||||
Anlagentopologie. Ein zusaetzliches Batterieverbrauchermodul beschreibt dagegen
|
Anlagentopologie. Ein zusaetzliches Batterieverbrauchermodul beschreibt dagegen
|
||||||
@@ -136,9 +144,10 @@ absolute Leistung in Watt ausgegeben werden.
|
|||||||
|
|
||||||
Der Manager berechnet eine einzige Grenze fuer die Gesamtanlage. Beim Abregeln
|
Der Manager berechnet eine einzige Grenze fuer die Gesamtanlage. Beim Abregeln
|
||||||
verteilt er sie nach der tatsaechlichen Erzeugung, beim Freigeben nach der
|
verteilt er sie nach der tatsaechlichen Erzeugung, beim Freigeben nach der
|
||||||
AC-Nennleistung auf alle angebotenen Wechselrichter. Das Totband verhindert
|
AC-Nennleistung auf alle angebotenen Wechselrichter. Jede gemessene Abweichung
|
||||||
Pendeln am Grenzwert. Beim Abschalten des Managers oder der
|
wird stufenlos nachgefuehrt; ein zusaetzliches Toleranzfenster wird nicht
|
||||||
Funktion werden zuvor gesetzte Grenzen kontrolliert auf die jeweilige
|
verwendet. Beim Abschalten des Managers oder der Funktion werden zuvor gesetzte
|
||||||
|
Grenzen kontrolliert auf die jeweilige
|
||||||
Nennleistung zurueckgesetzt. Wechselrichter ohne beide Register werden nicht
|
Nennleistung zurueckgesetzt. Wechselrichter ohne beide Register werden nicht
|
||||||
geregelt; sobald eines der beiden Register gesetzt ist, muessen beide gueltig
|
geregelt; sobald eines der beiden Register gesetzt ist, muessen beide gueltig
|
||||||
sein und das Begrenzungsregister eine IP-Symcon-Aktion besitzen.
|
sein und das Begrenzungsregister eine IP-Symcon-Aktion besitzen.
|
||||||
@@ -157,8 +166,10 @@ davon aktiv.
|
|||||||
|
|
||||||
Der Manager ist die fuehrende Quelle fuer die technischen Stammdaten. Die
|
Der Manager ist die fuehrende Quelle fuer die technischen Stammdaten. Die
|
||||||
Konfiguration trennt Wechselrichter, PV-Flaechen und Batteriespeicher. AC-Leistung
|
Konfiguration trennt Wechselrichter, PV-Flaechen und Batteriespeicher. AC-Leistung
|
||||||
wird am Wechselrichter, DC-Leistung an der PV-Flaeche gepflegt. Modulanzahl und
|
wird am Wechselrichter, DC-Leistung an der PV-Flaeche gepflegt. Individuelle
|
||||||
Modulleistung sind optional und muessen gemeinsam gesetzt werden.
|
AC-Einspeise- und Bezugsgrenzen werden nicht erfasst, weil die Begrenzung
|
||||||
|
anlagenweit am Netzanschlusspunkt erfolgt. Modulanzahl und Modulleistung sind
|
||||||
|
optional und muessen gemeinsam gesetzt werden.
|
||||||
|
|
||||||
PV-Flaechen und Batterien referenzieren ihren Wechselrichter ueber dessen stabile
|
PV-Flaechen und Batterien referenzieren ihren Wechselrichter ueber dessen stabile
|
||||||
ID. Bei `hybrid` koennen beide denselben Wechselrichter verwenden. Der Export
|
ID. Bei `hybrid` koennen beide denselben Wechselrichter verwenden. Der Export
|
||||||
@@ -167,9 +178,10 @@ AC-Grenze aus, damit PV und Batterie die Nennleistung nicht unabhaengig doppelt
|
|||||||
beanspruchen.
|
beanspruchen.
|
||||||
|
|
||||||
Der JSON-Export enthaelt die Vertragsversion `1.0`, die Lizenz-Installations-ID,
|
Der JSON-Export enthaelt die Vertragsversion `1.0`, die Lizenz-Installations-ID,
|
||||||
die drei Komponentenlisten, gemeinsame AC-Grenzen und aggregierte Summen fuer
|
die drei Komponentenlisten, gemeinsame AC-Nennleistungen und aggregierte Summen
|
||||||
die schrittweise Prognoseanbindung. Tarif- und Variantenparameter bleiben im
|
fuer die schrittweise Prognoseanbindung. Interne Symcon-Variablen-IDs werden
|
||||||
Prognoseportal. Eine nichtleere Topologie wird beim Speichern automatisch ueber
|
nicht exportiert. Tarif- und Variantenparameter bleiben im Prognoseportal. Bei
|
||||||
|
aktivierter Prognose wird eine nichtleere Topologie beim Speichern automatisch ueber
|
||||||
einen separaten, widerrufbaren Installationszugang synchronisiert. Der
|
einen separaten, widerrufbaren Installationszugang synchronisiert. Der
|
||||||
Lizenzserver liefert diesen Zugang nur auf ausdrueckliche Geraeteanforderung;
|
Lizenzserver liefert diesen Zugang nur auf ausdrueckliche Geraeteanforderung;
|
||||||
der Manager entfernt ihn vor dem Speichern aus der Lease und haelt ihn in einem
|
der Manager entfernt ihn vor dem Speichern aus der Lease und haelt ihn in einem
|
||||||
@@ -177,9 +189,12 @@ internen Attribut. Der Lizenzcode wird nicht als API-Token verwendet. Ein
|
|||||||
Synchronisationsfehler erscheint im `Prognosestatus`, blockiert die lokale
|
Synchronisationsfehler erscheint im `Prognosestatus`, blockiert die lokale
|
||||||
EMS-Regelung aber nicht.
|
EMS-Regelung aber nicht.
|
||||||
|
|
||||||
Sind PV-Leistung, Hausverbrauch und Batterie-SOC gemeinsam konfiguriert, sendet
|
Der Forecast-Schalter zeigt direkt an, ob fuer die Manager-ID eine passende
|
||||||
der Manager diese Werte zusammen mit der faktorbereinigten Netzleistung und
|
Kombination `forecast_pv` plus `forecast_load` oder `grid_schedule` vorhanden ist. Nach der Aktivierung sendet der
|
||||||
einem UTC-Zeitstempel im eingestellten Intervall. Das Minimum von 60 Sekunden
|
Manager PV-Leistung, berechneten Hausverbrauch, Netzleistung und Batterie-SOC
|
||||||
|
mit UTC-Zeitstempel im eingestellten Sendeintervall. Dieses Intervall bestimmt
|
||||||
|
also, wie oft aktuelle Messwerte zum Prognosedienst hochgeladen werden; es ist
|
||||||
|
kein Regelintervall. Das Minimum von 60 Sekunden
|
||||||
passt zum Rate-Limit des Lizenzportals. Die Uebertragung nutzt denselben
|
passt zum Rate-Limit des Lizenzportals. Die Uebertragung nutzt denselben
|
||||||
widerrufbaren Installationszugang wie die Topologie; Lizenzcode und Geraet Token
|
widerrufbaren Installationszugang wie die Topologie; Lizenzcode und Geraet Token
|
||||||
werden weder als Telemetriefelder noch im Debug-Log ausgegeben. Bei HTTP 401
|
werden weder als Telemetriefelder noch im Debug-Log ausgegeben. Bei HTTP 401
|
||||||
|
|||||||
@@ -285,8 +285,6 @@ function enelixDemoInstall(): array
|
|||||||
'Name' => 'Demo-Hybridwechselrichter',
|
'Name' => 'Demo-Hybridwechselrichter',
|
||||||
'Typ' => 'hybrid',
|
'Typ' => 'hybrid',
|
||||||
'ACNennleistung_kW' => $pvNennleistungKw,
|
'ACNennleistung_kW' => $pvNennleistungKw,
|
||||||
'ACEinspeisegrenze_kW' => 0.0,
|
|
||||||
'ACBezugsgrenze_kW' => 0.0,
|
|
||||||
'IstleistungVariableID' => $inverterActual,
|
'IstleistungVariableID' => $inverterActual,
|
||||||
'Istleistungsfaktor' => 1.0,
|
'Istleistungsfaktor' => 1.0,
|
||||||
'BegrenzungVariableID' => $inverterLimit,
|
'BegrenzungVariableID' => $inverterLimit,
|
||||||
@@ -338,7 +336,7 @@ function enelixDemoInstall(): array
|
|||||||
'Umschaltdifferenz' => 0.0,
|
'Umschaltdifferenz' => 0.0,
|
||||||
'EinspeisebegrenzungAktiv' => true,
|
'EinspeisebegrenzungAktiv' => true,
|
||||||
'Einspeisegrenze' => 3000.0,
|
'Einspeisegrenze' => 3000.0,
|
||||||
'Einspeisetoleranz' => 100.0,
|
'PrognoseAktiv' => false,
|
||||||
'NetzfahrplanAktiv' => false,
|
'NetzfahrplanAktiv' => false,
|
||||||
'AnlagenWechselrichter' => json_encode(
|
'AnlagenWechselrichter' => json_encode(
|
||||||
$wechselrichterKonfiguration,
|
$wechselrichterKonfiguration,
|
||||||
|
|||||||
@@ -47,16 +47,6 @@ final class Anlagentopologie
|
|||||||
'name' => self::name($eintrag['Name'] ?? null, $id),
|
'name' => self::name($eintrag['Name'] ?? null, $id),
|
||||||
'type' => $typ,
|
'type' => $typ,
|
||||||
'acRatedKw' => $acRatedKw,
|
'acRatedKw' => $acRatedKw,
|
||||||
'acExportLimitKw' => self::optionaleGrenze(
|
|
||||||
$eintrag['ACEinspeisegrenze_kW'] ?? null,
|
|
||||||
$acRatedKw,
|
|
||||||
$pfad . ': AC-Einspeisegrenze'
|
|
||||||
),
|
|
||||||
'acImportLimitKw' => self::optionaleGrenze(
|
|
||||||
$eintrag['ACBezugsgrenze_kW'] ?? null,
|
|
||||||
$acRatedKw,
|
|
||||||
$pfad . ': AC-Bezugsgrenze'
|
|
||||||
),
|
|
||||||
];
|
];
|
||||||
$inverters[] = $inverter;
|
$inverters[] = $inverter;
|
||||||
$inverterById[$id] = $inverter;
|
$inverterById[$id] = $inverter;
|
||||||
@@ -304,18 +294,6 @@ final class Anlagentopologie
|
|||||||
return (float) $wert;
|
return (float) $wert;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static function optionaleGrenze($wert, float $nennleistung, string $feld): ?float
|
|
||||||
{
|
|
||||||
$grenze = self::optionalePositiveZahl($wert, $feld);
|
|
||||||
if ($grenze !== null && $grenze > $nennleistung) {
|
|
||||||
throw new InvalidArgumentException(
|
|
||||||
$feld . ' darf die AC-Nennleistung nicht uebersteigen.'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $grenze;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static function optionalerText($wert): ?string
|
private static function optionalerText($wert): ?string
|
||||||
{
|
{
|
||||||
if ($wert === null || $wert === '') {
|
if ($wert === null || $wert === '') {
|
||||||
|
|||||||
@@ -26,16 +26,12 @@ final class EinspeiseRegler
|
|||||||
public static function verteile(
|
public static function verteile(
|
||||||
float $netzleistung,
|
float $netzleistung,
|
||||||
float $einspeisegrenze,
|
float $einspeisegrenze,
|
||||||
float $toleranz,
|
|
||||||
array $wechselrichter,
|
array $wechselrichter,
|
||||||
array $letzteGrenzen = []
|
array $letzteGrenzen = []
|
||||||
): array {
|
): array {
|
||||||
if ($einspeisegrenze < 0.0) {
|
if ($einspeisegrenze < 0.0) {
|
||||||
throw new InvalidArgumentException('Einspeisegrenze darf nicht negativ sein.');
|
throw new InvalidArgumentException('Einspeisegrenze darf nicht negativ sein.');
|
||||||
}
|
}
|
||||||
if ($toleranz < 0.0) {
|
|
||||||
throw new InvalidArgumentException('Einspeisetoleranz darf nicht negativ sein.');
|
|
||||||
}
|
|
||||||
if ($wechselrichter === []) {
|
if ($wechselrichter === []) {
|
||||||
throw new InvalidArgumentException('Mindestens ein regelbarer Wechselrichter ist erforderlich.');
|
throw new InvalidArgumentException('Mindestens ein regelbarer Wechselrichter ist erforderlich.');
|
||||||
}
|
}
|
||||||
@@ -81,12 +77,11 @@ final class EinspeiseRegler
|
|||||||
$netzabweichung = $netzleistung + $einspeisegrenze;
|
$netzabweichung = $netzleistung + $einspeisegrenze;
|
||||||
$zielGesamt = $bisherigeGrenzeGesamt;
|
$zielGesamt = $bisherigeGrenzeGesamt;
|
||||||
$nachIstleistungVerteilen = false;
|
$nachIstleistungVerteilen = false;
|
||||||
if ($netzabweichung < -$toleranz) {
|
if ($netzabweichung < 0.0) {
|
||||||
$nachIstleistungVerteilen = $istleistungGesamt > 0.0;
|
$nachIstleistungVerteilen = $istleistungGesamt > 0.0;
|
||||||
$wirksameAbweichung = $netzabweichung + $toleranz;
|
$zielGesamt = min($bisherigeGrenzeGesamt, $istleistungGesamt) + $netzabweichung;
|
||||||
$zielGesamt = min($bisherigeGrenzeGesamt, $istleistungGesamt) + $wirksameAbweichung;
|
} elseif ($netzabweichung > 0.0) {
|
||||||
} elseif ($netzabweichung > $toleranz) {
|
$zielGesamt = $bisherigeGrenzeGesamt + $netzabweichung;
|
||||||
$zielGesamt = $bisherigeGrenzeGesamt + $netzabweichung - $toleranz;
|
|
||||||
}
|
}
|
||||||
$zielGesamt = max(0.0, min($zielGesamt, $nennleistungGesamt));
|
$zielGesamt = max(0.0, min($zielGesamt, $nennleistungGesamt));
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,23 @@ final class EnergieMessung
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function hausenergie(
|
||||||
|
float $pv,
|
||||||
|
float $netzbezug,
|
||||||
|
float $netzeinspeisung,
|
||||||
|
float $batterieLaden,
|
||||||
|
float $batterieEntladen
|
||||||
|
): float {
|
||||||
|
return max(
|
||||||
|
0.0,
|
||||||
|
self::endlich($pv)
|
||||||
|
+ self::endlich($netzbezug)
|
||||||
|
+ self::endlich($batterieEntladen)
|
||||||
|
- self::endlich($netzeinspeisung)
|
||||||
|
- self::endlich($batterieLaden)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param array{PV: float, Haus: float, Netz: float, Batterie: float} $leistungen
|
* @param array{PV: float, Haus: float, Netz: float, Batterie: float} $leistungen
|
||||||
* @return array{
|
* @return array{
|
||||||
|
|||||||
@@ -18,8 +18,6 @@ final class AnlagentopologieTest extends TestCase
|
|||||||
'Name' => 'Hybrid Ost',
|
'Name' => 'Hybrid Ost',
|
||||||
'Typ' => 'hybrid',
|
'Typ' => 'hybrid',
|
||||||
'ACNennleistung_kW' => 10.0,
|
'ACNennleistung_kW' => 10.0,
|
||||||
'ACEinspeisegrenze_kW' => 8.0,
|
|
||||||
'ACBezugsgrenze_kW' => 0.0,
|
|
||||||
]], JSON_THROW_ON_ERROR),
|
]], JSON_THROW_ON_ERROR),
|
||||||
json_encode([
|
json_encode([
|
||||||
[
|
[
|
||||||
@@ -62,6 +60,8 @@ final class AnlagentopologieTest extends TestCase
|
|||||||
self::assertSame(12.8, $topologie['summary']['pvDcKwp']);
|
self::assertSame(12.8, $topologie['summary']['pvDcKwp']);
|
||||||
self::assertSame(10.0, $topologie['summary']['pvAcKw']);
|
self::assertSame(10.0, $topologie['summary']['pvAcKw']);
|
||||||
self::assertSame(12.8, $topologie['summary']['usableBatteryKwh']);
|
self::assertSame(12.8, $topologie['summary']['usableBatteryKwh']);
|
||||||
|
self::assertArrayNotHasKey('acExportLimitKw', $topologie['inverters'][0]);
|
||||||
|
self::assertArrayNotHasKey('acImportLimitKw', $topologie['inverters'][0]);
|
||||||
self::assertSame([[
|
self::assertSame([[
|
||||||
'inverterId' => 'hybrid-1',
|
'inverterId' => 'hybrid-1',
|
||||||
'acRatedKw' => 10.0,
|
'acRatedKw' => 10.0,
|
||||||
|
|||||||
@@ -131,8 +131,9 @@ final class DemoanlageBootstrapTest extends TestCase
|
|||||||
self::assertStringContainsString("'Begrenzungsart' => 'watt'", $source);
|
self::assertStringContainsString("'Begrenzungsart' => 'watt'", $source);
|
||||||
self::assertStringContainsString("'EinspeisebegrenzungAktiv' => true", $source);
|
self::assertStringContainsString("'EinspeisebegrenzungAktiv' => true", $source);
|
||||||
self::assertStringContainsString("'Einspeisegrenze' => 3000.0", $source);
|
self::assertStringContainsString("'Einspeisegrenze' => 3000.0", $source);
|
||||||
self::assertStringContainsString("'Einspeisetoleranz' => 100.0", $source);
|
self::assertStringContainsString("'PrognoseAktiv' => false", $source);
|
||||||
self::assertStringContainsString("'NetzfahrplanAktiv' => false", $source);
|
self::assertStringContainsString("'NetzfahrplanAktiv' => false", $source);
|
||||||
|
self::assertStringNotContainsString("'Einspeisetoleranz'", $source);
|
||||||
self::assertStringContainsString("'demo-hybrid-1'", $source);
|
self::assertStringContainsString("'demo-hybrid-1'", $source);
|
||||||
self::assertStringContainsString("'demo-pv-1'", $source);
|
self::assertStringContainsString("'demo-pv-1'", $source);
|
||||||
self::assertStringContainsString("'demo-battery-1'", $source);
|
self::assertStringContainsString("'demo-battery-1'", $source);
|
||||||
|
|||||||
@@ -15,17 +15,16 @@ final class EinspeiseReglerTest extends TestCase
|
|||||||
$ergebnis = EinspeiseRegler::verteile(
|
$ergebnis = EinspeiseRegler::verteile(
|
||||||
-15000.0,
|
-15000.0,
|
||||||
10000.0,
|
10000.0,
|
||||||
100.0,
|
|
||||||
[
|
[
|
||||||
['ID' => 'wr-1', 'Nennleistung_W' => 10000, 'Istleistung_W' => 10000],
|
['ID' => 'wr-1', 'Nennleistung_W' => 10000, 'Istleistung_W' => 10000],
|
||||||
['ID' => 'wr-2', 'Nennleistung_W' => 5000, 'Istleistung_W' => 5000],
|
['ID' => 'wr-2', 'Nennleistung_W' => 5000, 'Istleistung_W' => 5000],
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
self::assertSame(10100.0, $ergebnis['ZielGesamtleistung_W']);
|
self::assertSame(10000.0, $ergebnis['ZielGesamtleistung_W']);
|
||||||
self::assertSame(4900.0, $ergebnis['Abregelbedarf_W']);
|
self::assertSame(5000.0, $ergebnis['Abregelbedarf_W']);
|
||||||
self::assertSame(6733.333, $ergebnis['Grenzen_W']['wr-1']);
|
self::assertSame(6666.667, $ergebnis['Grenzen_W']['wr-1']);
|
||||||
self::assertSame(3366.667, $ergebnis['Grenzen_W']['wr-2']);
|
self::assertSame(3333.333, $ergebnis['Grenzen_W']['wr-2']);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testAbregelungFolgtDerTatsaechlichenErzeugung(): void
|
public function testAbregelungFolgtDerTatsaechlichenErzeugung(): void
|
||||||
@@ -33,7 +32,6 @@ final class EinspeiseReglerTest extends TestCase
|
|||||||
$ergebnis = EinspeiseRegler::verteile(
|
$ergebnis = EinspeiseRegler::verteile(
|
||||||
-10000.0,
|
-10000.0,
|
||||||
5000.0,
|
5000.0,
|
||||||
0.0,
|
|
||||||
[
|
[
|
||||||
['ID' => 'wr-1', 'Nennleistung_W' => 10000, 'Istleistung_W' => 10000],
|
['ID' => 'wr-1', 'Nennleistung_W' => 10000, 'Istleistung_W' => 10000],
|
||||||
['ID' => 'wr-2', 'Nennleistung_W' => 10000, 'Istleistung_W' => 0],
|
['ID' => 'wr-2', 'Nennleistung_W' => 10000, 'Istleistung_W' => 0],
|
||||||
@@ -49,7 +47,6 @@ final class EinspeiseReglerTest extends TestCase
|
|||||||
$ergebnis = EinspeiseRegler::verteile(
|
$ergebnis = EinspeiseRegler::verteile(
|
||||||
-6000.0,
|
-6000.0,
|
||||||
10000.0,
|
10000.0,
|
||||||
100.0,
|
|
||||||
[
|
[
|
||||||
['ID' => 'wr-1', 'Nennleistung_W' => 10000, 'Istleistung_W' => 4000],
|
['ID' => 'wr-1', 'Nennleistung_W' => 10000, 'Istleistung_W' => 4000],
|
||||||
['ID' => 'wr-2', 'Nennleistung_W' => 10000, 'Istleistung_W' => 2000],
|
['ID' => 'wr-2', 'Nennleistung_W' => 10000, 'Istleistung_W' => 2000],
|
||||||
@@ -57,22 +54,21 @@ final class EinspeiseReglerTest extends TestCase
|
|||||||
['wr-1' => 5000, 'wr-2' => 5000]
|
['wr-1' => 5000, 'wr-2' => 5000]
|
||||||
);
|
);
|
||||||
|
|
||||||
self::assertSame(13900.0, $ergebnis['ZielGesamtleistung_W']);
|
self::assertSame(14000.0, $ergebnis['ZielGesamtleistung_W']);
|
||||||
self::assertSame(6950.0, $ergebnis['Grenzen_W']['wr-1']);
|
self::assertSame(7000.0, $ergebnis['Grenzen_W']['wr-1']);
|
||||||
self::assertSame(6950.0, $ergebnis['Grenzen_W']['wr-2']);
|
self::assertSame(7000.0, $ergebnis['Grenzen_W']['wr-2']);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testToleranzVerhindertPendelnAmGrenzwert(): void
|
public function testRegelungFolgtAuchKleinenAbweichungenStufenlos(): void
|
||||||
{
|
{
|
||||||
$ergebnis = EinspeiseRegler::verteile(
|
$ergebnis = EinspeiseRegler::verteile(
|
||||||
-10050.0,
|
-10050.0,
|
||||||
10000.0,
|
10000.0,
|
||||||
100.0,
|
|
||||||
[['ID' => 'wr-1', 'Nennleistung_W' => 10000, 'Istleistung_W' => 9000]],
|
[['ID' => 'wr-1', 'Nennleistung_W' => 10000, 'Istleistung_W' => 9000]],
|
||||||
['wr-1' => 8000]
|
['wr-1' => 8000]
|
||||||
);
|
);
|
||||||
|
|
||||||
self::assertSame(8000.0, $ergebnis['ZielGesamtleistung_W']);
|
self::assertSame(7950.0, $ergebnis['ZielGesamtleistung_W']);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testStellwerteWerdenInProzentOderWattAusgegeben(): void
|
public function testStellwerteWerdenInProzentOderWattAusgegeben(): void
|
||||||
|
|||||||
@@ -50,6 +50,12 @@ final class EnergieMessungTest extends TestCase
|
|||||||
self::assertSame(0.0, EnergieMessung::hausleistung(500.0, -1200.0, 0.0));
|
self::assertSame(0.0, EnergieMessung::hausleistung(500.0, -1200.0, 0.0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testBerechnetHausenergieAusDenZaehlerdeltas(): void
|
||||||
|
{
|
||||||
|
self::assertSame(8.5, EnergieMessung::hausenergie(10.0, 2.0, 4.0, 1.5, 2.0));
|
||||||
|
self::assertSame(0.0, EnergieMessung::hausenergie(1.0, 0.0, 5.0, 0.0, 0.0));
|
||||||
|
}
|
||||||
|
|
||||||
public function testIgnoriertUngueltigeDauerUndNichtEndlicheWerte(): void
|
public function testIgnoriertUngueltigeDauerUndNichtEndlicheWerte(): void
|
||||||
{
|
{
|
||||||
$werte = EnergieMessung::energieDeltas([
|
$werte = EnergieMessung::energieDeltas([
|
||||||
|
|||||||
@@ -52,6 +52,11 @@ final class ManagerEnergieModulstrukturTest extends TestCase
|
|||||||
'IstleistungVariableID',
|
'IstleistungVariableID',
|
||||||
'LeistungVariableID',
|
'LeistungVariableID',
|
||||||
'SOCVariableID',
|
'SOCVariableID',
|
||||||
|
'NetzbezugEnergieVariableID',
|
||||||
|
'NetzeinspeisungEnergieVariableID',
|
||||||
|
'ErzeugungsenergieVariableID',
|
||||||
|
'LadeenergieVariableID',
|
||||||
|
'EntladeenergieVariableID',
|
||||||
] as $erwartet) {
|
] as $erwartet) {
|
||||||
self::assertStringContainsString($erwartet, $formular);
|
self::assertStringContainsString($erwartet, $formular);
|
||||||
}
|
}
|
||||||
@@ -78,6 +83,8 @@ final class ManagerEnergieModulstrukturTest extends TestCase
|
|||||||
self::assertStringContainsString("RegisterTimer(
|
self::assertStringContainsString("RegisterTimer(
|
||||||
'MesswerteAufzeichnen'", $modul);
|
'MesswerteAufzeichnen'", $modul);
|
||||||
self::assertStringContainsString('EnergieMessung::energieDeltas', $modul);
|
self::assertStringContainsString('EnergieMessung::energieDeltas', $modul);
|
||||||
|
self::assertStringContainsString('EnergieMessung::hausenergie', $modul);
|
||||||
|
self::assertStringContainsString("RegisterAttributeString('MessungZaehlerstaende'", $modul);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testFunFactsSindResponsivUndErklaerenUmrechnungen(): void
|
public function testFunFactsSindResponsivUndErklaerenUmrechnungen(): void
|
||||||
|
|||||||
@@ -50,9 +50,14 @@ final class ManagerModulstrukturTest extends TestCase
|
|||||||
'Umschaltdifferenz',
|
'Umschaltdifferenz',
|
||||||
'EinspeisebegrenzungAktiv',
|
'EinspeisebegrenzungAktiv',
|
||||||
'Einspeisegrenze',
|
'Einspeisegrenze',
|
||||||
'Einspeisetoleranz',
|
'PrognoseAktiv',
|
||||||
|
'PrognoseLizenzInformation',
|
||||||
'NetzfahrplanAktiv',
|
'NetzfahrplanAktiv',
|
||||||
|
'NetzbezugEnergieVariableID',
|
||||||
|
'NetzeinspeisungEnergieVariableID',
|
||||||
'IstleistungVariableID',
|
'IstleistungVariableID',
|
||||||
|
'ErzeugungsenergieVariableID',
|
||||||
|
'Erzeugungsenergiefaktor',
|
||||||
'BegrenzungVariableID',
|
'BegrenzungVariableID',
|
||||||
'Begrenzungsart',
|
'Begrenzungsart',
|
||||||
'DiagnosevariablenAnzeigen',
|
'DiagnosevariablenAnzeigen',
|
||||||
@@ -70,6 +75,9 @@ final class ManagerModulstrukturTest extends TestCase
|
|||||||
'LeistungVariableID',
|
'LeistungVariableID',
|
||||||
'Leistungsfaktor',
|
'Leistungsfaktor',
|
||||||
'SOCVariableID',
|
'SOCVariableID',
|
||||||
|
'LadeenergieVariableID',
|
||||||
|
'EntladeenergieVariableID',
|
||||||
|
'Energiefaktor',
|
||||||
'EnergyPieAnzeigen',
|
'EnergyPieAnzeigen',
|
||||||
'EnergiediagrammeAnzeigen',
|
'EnergiediagrammeAnzeigen',
|
||||||
'FunFactsAnzeigen',
|
'FunFactsAnzeigen',
|
||||||
@@ -81,7 +89,8 @@ final class ManagerModulstrukturTest extends TestCase
|
|||||||
self::assertStringContainsString($property, $json);
|
self::assertStringContainsString($property, $json);
|
||||||
}
|
}
|
||||||
foreach (['Regelintervall', 'Vorgabeintervall', 'UmschaltschwelleUeberschuss',
|
foreach (['Regelintervall', 'Vorgabeintervall', 'UmschaltschwelleUeberschuss',
|
||||||
'UmschaltschwelleLastspitze', 'Optionale obere Anschluesse', 'LizenzAnschluss'] as $entfernt
|
'UmschaltschwelleLastspitze', 'Optionale obere Anschluesse', 'LizenzAnschluss',
|
||||||
|
'Einspeisetoleranz', 'ACEinspeisegrenze_kW', 'ACBezugsgrenze_kW'] as $entfernt
|
||||||
) {
|
) {
|
||||||
self::assertStringNotContainsString($entfernt, $json);
|
self::assertStringNotContainsString($entfernt, $json);
|
||||||
}
|
}
|
||||||
@@ -294,8 +303,16 @@ final class ManagerModulstrukturTest extends TestCase
|
|||||||
$inhalt
|
$inhalt
|
||||||
);
|
);
|
||||||
self::assertStringContainsString('Lizenzpruefung::NETZFAHRPLAN', $inhalt);
|
self::assertStringContainsString('Lizenzpruefung::NETZFAHRPLAN', $inhalt);
|
||||||
|
self::assertStringContainsString('Lizenzpruefung::PROGNOSE_PV', $inhalt);
|
||||||
|
self::assertStringContainsString('Lizenzpruefung::PROGNOSE_VERBRAUCH', $inhalt);
|
||||||
|
self::assertStringContainsString('prognoseLizenzStatustext(', $inhalt);
|
||||||
|
self::assertStringContainsString(
|
||||||
|
"foreach (['PeakShavingTitel', 'EinspeisebegrenzungAktiv', 'Einspeisegrenze']",
|
||||||
|
$inhalt
|
||||||
|
);
|
||||||
self::assertStringContainsString('aktuelleNetzfahrplanVorgabe()', $inhalt);
|
self::assertStringContainsString('aktuelleNetzfahrplanVorgabe()', $inhalt);
|
||||||
self::assertStringContainsString('gibEinspeisebegrenzungFrei()', $inhalt);
|
self::assertStringContainsString('gibEinspeisebegrenzungFrei()', $inhalt);
|
||||||
|
self::assertStringNotContainsString("RegisterPropertyFloat('Einspeisetoleranz'", $inhalt);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testManagerVerwendetDieNeuenZeitdefaults(): void
|
public function testManagerVerwendetDieNeuenZeitdefaults(): void
|
||||||
|
|||||||
@@ -80,8 +80,6 @@ return static function (TestContext $test): void {
|
|||||||
'Name' => 'Hybridwechselrichter',
|
'Name' => 'Hybridwechselrichter',
|
||||||
'Typ' => 'hybrid',
|
'Typ' => 'hybrid',
|
||||||
'ACNennleistung_kW' => 10.0,
|
'ACNennleistung_kW' => 10.0,
|
||||||
'ACEinspeisegrenze_kW' => 8.0,
|
|
||||||
'ACBezugsgrenze_kW' => 0.0,
|
|
||||||
'IstleistungVariableID' => $pvLeistungId,
|
'IstleistungVariableID' => $pvLeistungId,
|
||||||
'Istleistungsfaktor' => 1.0,
|
'Istleistungsfaktor' => 1.0,
|
||||||
]];
|
]];
|
||||||
|
|||||||
Reference in New Issue
Block a user