feat: anlagenweite Einspeisebegrenzung und Netzfahrplan
Tests / test (push) Successful in 50s

This commit is contained in:
dh
2026-09-26 09:09:08 +00:00
parent efa8fe698c
commit dcf51bd8be
8 changed files with 737 additions and 17 deletions
+57
View File
@@ -127,6 +127,36 @@
], ],
"values": [] "values": []
}, },
{
"type": "Label",
"caption": "Peak Shaving am Netzanschlusspunkt"
},
{
"type": "CheckBox",
"name": "EinspeisebegrenzungAktiv",
"caption": "Anlagenweite Einspeisebegrenzung aktivieren"
},
{
"type": "NumberSpinner",
"name": "Einspeisegrenze",
"caption": "Maximale Einspeisung der Gesamtanlage",
"suffix": " W",
"minimum": 0.0,
"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",
@@ -283,6 +313,33 @@
"minimum": 0.0, "minimum": 0.0,
"digits": 3 "digits": 3
} }
},
{
"caption": "Istleistung",
"name": "IstleistungVariableID",
"width": "140px",
"add": 0,
"edit": {"type": "SelectVariable"}
},
{
"caption": "PV-Begrenzung",
"name": "BegrenzungVariableID",
"width": "150px",
"add": 0,
"edit": {"type": "SelectVariable"}
},
{
"caption": "Stellwert",
"name": "Begrenzungsart",
"width": "120px",
"add": "percent",
"edit": {
"type": "Select",
"options": [
{"caption": "Prozent", "value": "percent"},
{"caption": "Watt", "value": "watt"}
]
}
} }
] ]
}, },
+394 -13
View File
@@ -5,10 +5,12 @@ declare(strict_types=1);
require_once __DIR__ . '/../libs/ManagerSchnittstelle.php'; require_once __DIR__ . '/../libs/ManagerSchnittstelle.php';
require_once __DIR__ . '/../libs/Nachrichtenvertrag.php'; require_once __DIR__ . '/../libs/Nachrichtenvertrag.php';
require_once __DIR__ . '/../libs/ManagerRegler.php'; require_once __DIR__ . '/../libs/ManagerRegler.php';
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';
use Belevo\EnelixEMS\Anlagentopologie; use Belevo\EnelixEMS\Anlagentopologie;
use Belevo\EnelixEMS\EinspeiseRegler;
use Belevo\EnelixEMS\Lizenzpruefung; use Belevo\EnelixEMS\Lizenzpruefung;
use Belevo\EnelixEMS\ManagerRegler; use Belevo\EnelixEMS\ManagerRegler;
use Belevo\EnelixEMS\ManagerSchnittstelle; use Belevo\EnelixEMS\ManagerSchnittstelle;
@@ -23,6 +25,7 @@ class Manager extends IPSModule implements ManagerSchnittstelle
private const LIZENZ_ENDPOINT = 'https://license.enelix.ch/api/v1/licenses/activate'; private const LIZENZ_ENDPOINT = 'https://license.enelix.ch/api/v1/licenses/activate';
private const PROGNOSE_TOPOLOGIE_ENDPOINT = 'https://license.enelix.ch/api/v1/installations/%s/prognosis/topology'; private const PROGNOSE_TOPOLOGIE_ENDPOINT = 'https://license.enelix.ch/api/v1/installations/%s/prognosis/topology';
private const PROGNOSE_TELEMETRIE_ENDPOINT = 'https://license.enelix.ch/api/v1/installations/%s/prognosis/telemetry'; private const PROGNOSE_TELEMETRIE_ENDPOINT = 'https://license.enelix.ch/api/v1/installations/%s/prognosis/telemetry';
private const PROGNOSE_FAHRPLAN_ENDPOINT = 'https://license.enelix.ch/api/v1/installations/%s/prognosis/schedule';
private const LIZENZ_RETRY_SEKUNDEN = 3600; private const LIZENZ_RETRY_SEKUNDEN = 3600;
private const VM_UPDATE = 10603; private const VM_UPDATE = 10603;
private const VERBRAUCHER_LIZENZEN = [ private const VERBRAUCHER_LIZENZEN = [
@@ -62,6 +65,9 @@ class Manager extends IPSModule implements ManagerSchnittstelle
private const DIAGNOSE_VARIABLEN = [ private const DIAGNOSE_VARIABLEN = [
'NetzleistungGueltig', 'NetzleistungGueltig',
'WirksameLastspitzengrenze', 'WirksameLastspitzengrenze',
'WirksameEinspeisegrenze',
'Abregelbedarf',
'Wechselrichterstatus',
'Verteilbudget', 'Verteilbudget',
'VerbraucherAnzahl', 'VerbraucherAnzahl',
'VerbraucherVerfuegbar', 'VerbraucherVerfuegbar',
@@ -95,6 +101,10 @@ class Manager extends IPSModule implements ManagerSchnittstelle
$this->RegisterPropertyString('Monatsgrenzen', $this->standardMonatsgrenzen()); $this->RegisterPropertyString('Monatsgrenzen', $this->standardMonatsgrenzen());
$this->RegisterPropertyFloat('SollwertSolarladen', 0.0); $this->RegisterPropertyFloat('SollwertSolarladen', 0.0);
$this->RegisterPropertyFloat('Umschaltdifferenz', 5.0); $this->RegisterPropertyFloat('Umschaltdifferenz', 5.0);
$this->RegisterPropertyBoolean('EinspeisebegrenzungAktiv', false);
$this->RegisterPropertyFloat('Einspeisegrenze', 0.0);
$this->RegisterPropertyFloat('Einspeisetoleranz', 100.0);
$this->RegisterPropertyBoolean('NetzfahrplanAktiv', false);
$this->RegisterPropertyString('AnlagenWechselrichter', '[]'); $this->RegisterPropertyString('AnlagenWechselrichter', '[]');
$this->RegisterPropertyString('AnlagenPVFlaechen', '[]'); $this->RegisterPropertyString('AnlagenPVFlaechen', '[]');
$this->RegisterPropertyString('AnlagenBatterien', '[]'); $this->RegisterPropertyString('AnlagenBatterien', '[]');
@@ -118,6 +128,8 @@ class Manager extends IPSModule implements ManagerSchnittstelle
$this->RegisterAttributeString('VerbraucherCache', '{}'); $this->RegisterAttributeString('VerbraucherCache', '{}');
$this->RegisterAttributeString('LetzteSollwerte', '{}'); $this->RegisterAttributeString('LetzteSollwerte', '{}');
$this->RegisterAttributeString('LetztePvGrenzen', '{}');
$this->RegisterAttributeString('Netzfahrplan', '{}');
$this->RegisterAttributeInteger('RegistrierteNetzleistungVariableID', 0); $this->RegisterAttributeInteger('RegistrierteNetzleistungVariableID', 0);
$this->RegisterAttributeString('LizenzInstallationID', ''); $this->RegisterAttributeString('LizenzInstallationID', '');
$this->RegisterAttributeString('LizenzLease', '{}'); $this->RegisterAttributeString('LizenzLease', '{}');
@@ -208,6 +220,9 @@ 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();
$netzfahrplanLizenziert = $this->berechtigungLizenziert(
Lizenzpruefung::NETZFAHRPLAN
);
$monatsgrenzenSichtbar = $peakShavingLizenziert $monatsgrenzenSichtbar = $peakShavingLizenziert
&& $lastspitzenmodus === 2 && $lastspitzenmodus === 2
&& $this->GetBuffer('MonatsgrenzenSichtbar') === '1'; && $this->GetBuffer('MonatsgrenzenSichtbar') === '1';
@@ -251,6 +266,20 @@ class Manager extends IPSModule implements ManagerSchnittstelle
'options', 'options',
$lastspitzenOptionen $lastspitzenOptionen
); );
foreach (['EinspeisebegrenzungAktiv', 'Einspeisegrenze', 'Einspeisetoleranz'] as $feld) {
$this->setzeFormularParameter(
$formular['elements'],
$feld,
'enabled',
$peakShavingLizenziert
);
}
$this->setzeFormularParameter(
$formular['elements'],
'NetzfahrplanAktiv',
'enabled',
$peakShavingLizenziert && $netzfahrplanLizenziert
);
$this->setzeFormularParameter( $this->setzeFormularParameter(
$formular['elements'], $formular['elements'],
'Lastspitzengrenze', 'Lastspitzengrenze',
@@ -458,6 +487,16 @@ class Manager extends IPSModule implements ManagerSchnittstelle
$aktiv = (bool) $this->GetValue('Aktiv'); $aktiv = (bool) $this->GetValue('Aktiv');
if (!$aktiv || !$netzleistungGueltig) { if (!$aktiv || !$netzleistungGueltig) {
if (!$aktiv) {
try {
$this->gibEinspeisebegrenzungFrei();
} catch (Throwable $fehler) {
$this->setzeDiagnosewert(
'Wechselrichterstatus',
'Ruecksetzen fehlgeschlagen: ' . substr($fehler->getMessage(), 0, 500)
);
}
}
$this->SetValue('Betriebsart', ManagerRegler::BETRIEBSART_INAKTIV); $this->SetValue('Betriebsart', ManagerRegler::BETRIEBSART_INAKTIV);
$this->setzeDiagnosewert('Verteilbudget', 0.0); $this->setzeDiagnosewert('Verteilbudget', 0.0);
$this->setzeNichtGeregelteVerbraucher( $this->setzeNichtGeregelteVerbraucher(
@@ -499,6 +538,7 @@ class Manager extends IPSModule implements ManagerSchnittstelle
$synchronisiert $synchronisiert
); );
$this->setzeDiagnosewert('WirksameLastspitzengrenze', $lastspitzengrenze ?? 0.0); $this->setzeDiagnosewert('WirksameLastspitzengrenze', $lastspitzengrenze ?? 0.0);
$this->regleEinspeisung($netzleistung, $stoerungen, $erneuern);
if (!$synchronisiert) { if (!$synchronisiert) {
$this->setzeDiagnosewert('Verteilbudget', 0.0); $this->setzeDiagnosewert('Verteilbudget', 0.0);
$this->setzeDiagnosewert('Verbraucherstatus', json_encode([ $this->setzeDiagnosewert('Verbraucherstatus', json_encode([
@@ -516,9 +556,10 @@ class Manager extends IPSModule implements ManagerSchnittstelle
return; return;
} }
$fahrplanVorgabe = $this->aktuelleNetzfahrplanVorgabe();
$zielnetzleistung = $betriebsart === ManagerRegler::BETRIEBSART_PEAK $zielnetzleistung = $betriebsart === ManagerRegler::BETRIEBSART_PEAK
? (float) $lastspitzengrenze ? (float) $lastspitzengrenze
: $this->ReadPropertyFloat('SollwertSolarladen'); : ($fahrplanVorgabe ?? $this->ReadPropertyFloat('SollwertSolarladen'));
$ergebnis = ManagerRegler::verteile( $ergebnis = ManagerRegler::verteile(
$netzleistung, $netzleistung,
$zielnetzleistung, $zielnetzleistung,
@@ -556,6 +597,174 @@ class Manager extends IPSModule implements ManagerSchnittstelle
$this->SetStatus(self::STATUS_AKTIV); $this->SetStatus(self::STATUS_AKTIV);
} }
/**
* @param list<string> $stoerungen
*/
private function regleEinspeisung(
float $netzleistung,
array &$stoerungen,
bool $erneuern
): void {
if (!$this->ReadPropertyBoolean('EinspeisebegrenzungAktiv')) {
try {
$this->gibEinspeisebegrenzungFrei();
$this->setzeDiagnosewert('Wechselrichterstatus', 'Nicht aktiv');
} catch (Throwable $fehler) {
$stoerungen[] = 'Einspeisebegrenzung zuruecksetzen: ' . $fehler->getMessage();
$this->setzeDiagnosewert(
'Wechselrichterstatus',
'Ruecksetzen fehlgeschlagen: ' . substr($fehler->getMessage(), 0, 500)
);
}
$this->setzeDiagnosewert('WirksameEinspeisegrenze', 0.0);
$this->setzeDiagnosewert('Abregelbedarf', 0.0);
return;
}
try {
$wechselrichter = $this->regelbareWechselrichter();
$letzteGrenzen = $this->leseJsonAttribut('LetztePvGrenzen');
$ergebnis = EinspeiseRegler::verteile(
$netzleistung,
$this->ReadPropertyFloat('Einspeisegrenze'),
$this->ReadPropertyFloat('Einspeisetoleranz'),
$wechselrichter,
$letzteGrenzen
);
$konfiguration = [];
foreach ($wechselrichter as $eintrag) {
$konfiguration[$eintrag['ID']] = $eintrag;
}
foreach ($ergebnis['Grenzen_W'] as $id => $grenzeW) {
if (!$erneuern
&& isset($letzteGrenzen[$id])
&& abs((float) $letzteGrenzen[$id] - $grenzeW) < 0.5
) {
continue;
}
$eintrag = $konfiguration[$id];
$stellwert = EinspeiseRegler::stellwert(
$grenzeW,
(float) $eintrag['Nennleistung_W'],
$eintrag['Begrenzungsart']
);
RequestAction($eintrag['BegrenzungVariableID'], $stellwert);
}
$this->WriteAttributeString(
'LetztePvGrenzen',
json_encode($ergebnis['Grenzen_W'], JSON_THROW_ON_ERROR)
);
$this->setzeDiagnosewert(
'WirksameEinspeisegrenze',
$this->ReadPropertyFloat('Einspeisegrenze')
);
$this->setzeDiagnosewert('Abregelbedarf', $ergebnis['Abregelbedarf_W']);
$this->setzeDiagnosewert(
'Wechselrichterstatus',
json_encode($ergebnis, JSON_THROW_ON_ERROR)
);
} catch (Throwable $fehler) {
$stoerungen[] = 'Einspeisebegrenzung: ' . $fehler->getMessage();
$this->setzeDiagnosewert(
'Wechselrichterstatus',
'Fehler: ' . substr($fehler->getMessage(), 0, 500)
);
}
}
private function gibEinspeisebegrenzungFrei(): void
{
if ($this->leseJsonAttribut('LetztePvGrenzen') === []) {
return;
}
foreach ($this->regelbareWechselrichter() as $eintrag) {
RequestAction(
$eintrag['BegrenzungVariableID'],
EinspeiseRegler::stellwert(
(float) $eintrag['Nennleistung_W'],
(float) $eintrag['Nennleistung_W'],
$eintrag['Begrenzungsart']
)
);
}
$this->WriteAttributeString('LetztePvGrenzen', '{}');
}
/**
* @return list<array{
* ID: string,
* Nennleistung_W: float,
* Istleistung_W: float,
* BegrenzungVariableID: int,
* Begrenzungsart: string
* }>
*/
private function regelbareWechselrichter(): array
{
$liste = json_decode(
$this->ReadPropertyString('AnlagenWechselrichter'),
true,
512,
JSON_THROW_ON_ERROR
);
if (!is_array($liste)) {
throw new InvalidArgumentException('AnlagenWechselrichter muss eine JSON-Liste sein.');
}
$ergebnis = [];
foreach ($liste as $index => $eintrag) {
if (!is_array($eintrag)
|| !in_array($eintrag['Typ'] ?? null, ['pv', 'hybrid'], true)
) {
continue;
}
$istID = $eintrag['IstleistungVariableID'] ?? 0;
$begrenzungID = $eintrag['BegrenzungVariableID'] ?? 0;
if ($istID === 0 && $begrenzungID === 0) {
continue;
}
if (!is_int($istID) || !is_int($begrenzungID)
|| $istID <= 0 || $begrenzungID <= 0
|| !IPS_VariableExists($istID) || !IPS_VariableExists($begrenzungID)
) {
throw new InvalidArgumentException(
'Wechselrichter ' . ($index + 1) . ': Istleistung und Begrenzungsregister fehlen.'
);
}
$begrenzungsvariable = IPS_GetVariable($begrenzungID);
if ((int) ($begrenzungsvariable['VariableAction'] ?? 0) <= 0
&& (int) ($begrenzungsvariable['VariableCustomAction'] ?? 0) <= 0
) {
throw new InvalidArgumentException(
'Wechselrichter ' . ($index + 1) . ': Begrenzungsregister hat keine Aktion.'
);
}
$istleistung = GetValue($istID);
$nennleistung = $eintrag['ACNennleistung_kW'] ?? null;
$art = $eintrag['Begrenzungsart'] ?? 'percent';
if ((!is_int($istleistung) && !is_float($istleistung))
|| (!is_int($nennleistung) && !is_float($nennleistung))
|| !in_array($art, ['percent', 'watt'], true)
) {
throw new InvalidArgumentException(
'Wechselrichter ' . ($index + 1) . ': Regelparameter sind ungueltig.'
);
}
$ergebnis[] = [
'ID' => (string) ($eintrag['ID'] ?? ''),
'Nennleistung_W' => (float) $nennleistung * 1000.0,
'Istleistung_W' => max(0.0, (float) $istleistung),
'BegrenzungVariableID' => $begrenzungID,
'Begrenzungsart' => $art,
];
}
if ($ergebnis === []) {
throw new InvalidArgumentException('Kein PV-Wechselrichter mit Stellregister konfiguriert.');
}
return $ergebnis;
}
/** @return array{0: float, 1: bool} */ /** @return array{0: float, 1: bool} */
private function leseNetzleistung(): array private function leseNetzleistung(): array
{ {
@@ -1034,6 +1243,19 @@ class Manager extends IPSModule implements ManagerSchnittstelle
'', '',
50 50
); );
$this->RegisterVariableFloat(
'WirksameEinspeisegrenze',
'Wirksame Einspeisegrenze',
'',
52
);
$this->RegisterVariableFloat('Abregelbedarf', 'Abregelbedarf', '', 54);
$this->RegisterVariableString(
'Wechselrichterstatus',
'Wechselrichterstatus',
'',
56
);
$this->RegisterVariableFloat('Verteilbudget', 'Verteilbudget', '', 60); $this->RegisterVariableFloat('Verteilbudget', 'Verteilbudget', '', 60);
$this->RegisterVariableInteger('VerbraucherAnzahl', 'Verbraucher Anzahl', '', 70); $this->RegisterVariableInteger('VerbraucherAnzahl', 'Verbraucher Anzahl', '', 70);
$this->RegisterVariableInteger( $this->RegisterVariableInteger(
@@ -1114,20 +1336,23 @@ class Manager extends IPSModule implements ManagerSchnittstelle
} }
$lease = $this->leseLizenzLease(); $lease = $this->leseLizenzLease();
$codePasst = $gespeicherterHash !== '' && hash_equals($gespeicherterHash, $codeHash); $codePasst = $gespeicherterHash !== '' && hash_equals($gespeicherterHash, $codeHash);
$lastspitzenmodus = $this->ReadPropertyInteger('Lastspitzenmodus'); $lastspitzenmodus = $this->lizenzPeakModus();
$basisGueltig = $codePasst && Lizenzpruefung::istLeaseGueltig( $basisGueltig = $codePasst && Lizenzpruefung::istLeaseGueltig(
$lease, $lease,
$installationID, $installationID,
0 0
); );
$gueltig = $codePasst && Lizenzpruefung::istLeaseGueltig( $leaseGueltig = $codePasst && Lizenzpruefung::istLeaseGueltig(
$lease, $lease,
$installationID, $installationID,
$lastspitzenmodus $lastspitzenmodus
); );
$berechtigungenVeraltet = $basisGueltig $berechtigungenPassen = $leaseGueltig
&& !$this->lizenzBerechtigungenPassen($lease, $lastspitzenmodus); && $this->lizenzBerechtigungenPassen($lease, $lastspitzenmodus);
$geraetezugangFehlt = $this->hatAnlagentopologie() $gueltig = $leaseGueltig && $berechtigungenPassen;
$berechtigungenVeraltet = $basisGueltig && !$berechtigungenPassen;
$geraetezugangFehlt = ($this->hatAnlagentopologie()
|| $this->prognoseTelemetrieKonfiguriert())
&& $this->ReadAttributeString('PrognoseInstallationsToken') === ''; && $this->ReadAttributeString('PrognoseInstallationsToken') === '';
$routineFaellig = (!$gueltig && !$berechtigungenVeraltet) $routineFaellig = (!$gueltig && !$berechtigungenVeraltet)
|| $geraetezugangFehlt || $geraetezugangFehlt
@@ -1183,8 +1408,16 @@ class Manager extends IPSModule implements ManagerSchnittstelle
Lizenzpruefung::pruefeLease( Lizenzpruefung::pruefeLease(
$neueLease, $neueLease,
$installationID, $installationID,
$this->ReadPropertyInteger('Lastspitzenmodus') $this->lizenzPeakModus()
); );
if (!$this->lizenzBerechtigungenPassen(
$neueLease,
$this->lizenzPeakModus()
)) {
throw new InvalidArgumentException(
'Die Lizenz deckt die konfigurierten Funktionen oder Geraetemengen nicht ab.'
);
}
if ($geraetezugangFehlt) { if ($geraetezugangFehlt) {
$this->WriteAttributeString('PrognoseInstallationsToken', $geraeteToken); $this->WriteAttributeString('PrognoseInstallationsToken', $geraeteToken);
} }
@@ -1230,7 +1463,22 @@ class Manager extends IPSModule implements ManagerSchnittstelle
} }
} }
private function lizenzPeakModus(): int
{
$modus = $this->ReadPropertyInteger('Lastspitzenmodus');
if ($modus !== 0) {
return $modus;
}
return $this->ReadPropertyBoolean('EinspeisebegrenzungAktiv') ? 1 : 0;
}
private function peakShavingLizenziert(): bool private function peakShavingLizenziert(): bool
{
return $this->berechtigungLizenziert(Lizenzpruefung::MANAGER_PEAK);
}
private function berechtigungLizenziert(string $kennung): bool
{ {
$lizenzcode = Lizenzpruefung::normalisiereCode( $lizenzcode = Lizenzpruefung::normalisiereCode(
$this->ReadPropertyString('Lizenzcode') $this->ReadPropertyString('Lizenzcode')
@@ -1255,10 +1503,7 @@ class Manager extends IPSModule implements ManagerSchnittstelle
return false; return false;
} }
return Lizenzpruefung::berechtigungsMenge( return Lizenzpruefung::berechtigungsMenge($lease, $kennung) > 0;
$lease,
Lizenzpruefung::MANAGER_PEAK
) > 0;
} }
/** /**
@@ -1266,7 +1511,7 @@ class Manager extends IPSModule implements ManagerSchnittstelle
*/ */
private function lizenzBerechtigungenPassen(array $lease, int $lastspitzenmodus): bool private function lizenzBerechtigungenPassen(array $lease, int $lastspitzenmodus): bool
{ {
if ($lastspitzenmodus !== 0 if (($lastspitzenmodus !== 0 || $this->ReadPropertyBoolean('EinspeisebegrenzungAktiv'))
&& Lizenzpruefung::berechtigungsMenge( && Lizenzpruefung::berechtigungsMenge(
$lease, $lease,
Lizenzpruefung::MANAGER_PEAK Lizenzpruefung::MANAGER_PEAK
@@ -1274,6 +1519,14 @@ class Manager extends IPSModule implements ManagerSchnittstelle
) { ) {
return false; return false;
} }
if ($this->ReadPropertyBoolean('NetzfahrplanAktiv')
&& Lizenzpruefung::berechtigungsMenge(
$lease,
Lizenzpruefung::NETZFAHRPLAN
) < 1
) {
return false;
}
try { try {
[$mengen] = $this->verbraucherLizenzbelegung( [$mengen] = $this->verbraucherLizenzbelegung(
@@ -1356,7 +1609,7 @@ class Manager extends IPSModule implements ManagerSchnittstelle
Lizenzpruefung::pruefeLease( Lizenzpruefung::pruefeLease(
$lease, $lease,
$this->ReadAttributeString('LizenzInstallationID'), $this->ReadAttributeString('LizenzInstallationID'),
$this->ReadPropertyInteger('Lastspitzenmodus') $this->lizenzPeakModus()
); );
$this->aktiveVerbraucherIDs(); $this->aktiveVerbraucherIDs();
$status = ($lease['development'] ?? false) === true $status = ($lease['development'] ?? false) === true
@@ -1498,6 +1751,22 @@ class Manager extends IPSModule implements ManagerSchnittstelle
) { ) {
throw new InvalidArgumentException('Umschaltdifferenz muss zwischen 0 und 100 liegen.'); throw new InvalidArgumentException('Umschaltdifferenz muss zwischen 0 und 100 liegen.');
} }
if ($this->ReadPropertyFloat('Einspeisegrenze') < 0.0
|| $this->ReadPropertyFloat('Einspeisetoleranz') < 0.0
) {
throw new InvalidArgumentException('Einspeisegrenze und Einspeisetoleranz duerfen nicht negativ sein.');
}
if ($this->ReadPropertyBoolean('EinspeisebegrenzungAktiv')) {
$this->regelbareWechselrichter();
}
if ($this->ReadPropertyBoolean('NetzfahrplanAktiv')
&& (!$this->ReadPropertyBoolean('EinspeisebegrenzungAktiv')
|| !$this->prognoseTelemetrieKonfiguriert())
) {
throw new InvalidArgumentException(
'Der intelligente Netzfahrplan benoetigt Einspeisebegrenzung und Prognosetelemetrie.'
);
}
$this->leseAktiveVerbraucherIDs('VerbraucherZuordnung'); $this->leseAktiveVerbraucherIDs('VerbraucherZuordnung');
$this->leseAktiveVerbraucherIDs('AutomatischeVerbraucherZuordnung'); $this->leseAktiveVerbraucherIDs('AutomatischeVerbraucherZuordnung');
$this->dekodiereMonatsgrenzen($this->ReadPropertyString('Monatsgrenzen')); $this->dekodiereMonatsgrenzen($this->ReadPropertyString('Monatsgrenzen'));
@@ -1537,6 +1806,48 @@ class Manager extends IPSModule implements ManagerSchnittstelle
&& $this->ReadPropertyInteger('PrognoseSOCVariableID') > 0; && $this->ReadPropertyInteger('PrognoseSOCVariableID') > 0;
} }
private function aktuelleNetzfahrplanVorgabe(): ?float
{
if (!$this->ReadPropertyBoolean('NetzfahrplanAktiv')) {
return null;
}
$fahrplan = $this->leseJsonAttribut('Netzfahrplan');
if (($fahrplan['status'] ?? null) !== 'available'
|| !is_array($fahrplan['points'] ?? null)
|| !is_string($fahrplan['validUntil'] ?? null)
) {
return null;
}
$gueltigBis = strtotime($fahrplan['validUntil']);
if ($gueltigBis === false || $gueltigBis <= time()) {
return null;
}
$jetzt = time();
$naechsterAbstand = PHP_INT_MAX;
$vorgabe = null;
foreach ($fahrplan['points'] as $punkt) {
if (!is_array($punkt)
|| !is_string($punkt['time'] ?? null)
|| (!is_int($punkt['gridTargetW'] ?? null)
&& !is_float($punkt['gridTargetW'] ?? null))
) {
continue;
}
$zeitpunkt = strtotime($punkt['time']);
if ($zeitpunkt === false) {
continue;
}
$abstand = abs($zeitpunkt - $jetzt);
if ($abstand < $naechsterAbstand) {
$naechsterAbstand = $abstand;
$vorgabe = (float) $punkt['gridTargetW'];
}
}
return $naechsterAbstand <= 1800 ? $vorgabe : null;
}
private function synchronisiereAnlagentopologie(): void private function synchronisiereAnlagentopologie(): void
{ {
try { try {
@@ -1667,6 +1978,9 @@ class Manager extends IPSModule implements ManagerSchnittstelle
'values' => $werte, 'values' => $werte,
]; ];
$this->uebertragePrognoseTelemetrie($installationID, $nutzlast, $token); $this->uebertragePrognoseTelemetrie($installationID, $nutzlast, $token);
if ($this->ReadPropertyBoolean('NetzfahrplanAktiv')) {
$this->aktualisiereNetzfahrplan($installationID, $token);
}
$this->setzeDiagnosewert('Prognosestatus', 'Verbunden'); $this->setzeDiagnosewert('Prognosestatus', 'Verbunden');
} catch (Throwable $fehler) { } catch (Throwable $fehler) {
$meldung = substr($fehler->getMessage(), 0, 500); $meldung = substr($fehler->getMessage(), 0, 500);
@@ -1675,6 +1989,73 @@ class Manager extends IPSModule implements ManagerSchnittstelle
} }
} }
private function aktualisiereNetzfahrplan(string $installationID, string $token): void
{
$url = sprintf(
self::PROGNOSE_FAHRPLAN_ENDPOINT,
rawurlencode($installationID)
) . '?' . http_build_query([
'exportLimitW' => $this->ReadPropertyFloat('Einspeisegrenze'),
]);
$curl = curl_init($url);
if ($curl === false) {
throw new RuntimeException('Der Netzfahrplan konnte nicht vorbereitet werden.');
}
try {
curl_setopt_array($curl, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CONNECTTIMEOUT => 5,
CURLOPT_TIMEOUT => 10,
CURLOPT_HTTPHEADER => [
'Accept: application/json',
'Authorization: Bearer ' . $token,
],
CURLOPT_USERAGENT => 'Enelix-EMS-Manager/0.2',
]);
$antwort = curl_exec($curl);
if ($antwort === false) {
throw new RuntimeException('Netzfahrplan nicht erreichbar: ' . curl_error($curl));
}
if (strlen($antwort) > 524288) {
throw new RuntimeException('Die Antwort des Netzfahrplans ist zu gross.');
}
$httpStatus = (int) curl_getinfo($curl, CURLINFO_HTTP_CODE);
} finally {
curl_close($curl);
}
if ($httpStatus === 401) {
$this->WriteAttributeString('PrognoseInstallationsToken', '');
}
if ($httpStatus !== 200) {
$meldung = 'Unbekannter Fehler';
try {
$fehlerdaten = json_decode($antwort, true, 512, JSON_THROW_ON_ERROR);
if (is_array($fehlerdaten) && is_string($fehlerdaten['error'] ?? null)) {
$meldung = $fehlerdaten['error'];
}
} catch (Throwable $fehler) {
// Der HTTP-Status bleibt die fuehrende Fehlerinformation.
}
throw new RuntimeException(
'Netzfahrplan HTTP ' . $httpStatus . ': ' . $meldung
);
}
$fahrplan = json_decode($antwort, true, 512, JSON_THROW_ON_ERROR);
if (!is_array($fahrplan)
|| ($fahrplan['schemaVersion'] ?? null) !== 1
|| !is_array($fahrplan['points'] ?? null)
) {
throw new RuntimeException('Der Netzfahrplan hat ein ungueltiges Format.');
}
$this->WriteAttributeString(
'Netzfahrplan',
json_encode($fahrplan, JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES)
);
}
/** @param array<string, mixed> $nutzlast */ /** @param array<string, mixed> $nutzlast */
private function uebertragePrognoseTelemetrie( private function uebertragePrognoseTelemetrie(
string $installationID, string $installationID,
+41 -4
View File
@@ -16,7 +16,10 @@ Ohne angelegte Diagnosevariablen existieren nur `Aktiv`, `Betriebsart` und `Netz
| `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. |
| `NetzleistungGueltig` | Boolean / Logging | Messquelle vorhanden und aktuell. | | `NetzleistungGueltig` | Boolean / Logging | Messquelle vorhanden und aktuell. |
| `WirksameLastspitzengrenze` | Float / Logging | Aktuelle feste oder monatliche Grenze in W. | | `WirksameLastspitzengrenze` | Float / Logging | Aktuelle feste oder monatliche Bezugsgrenze in W. |
| `WirksameEinspeisegrenze` | Float / Logging | Anlagenweite Einspeisegrenze in W. |
| `Abregelbedarf` | Float / Logging | Aktuell erforderliche PV-Leistungsreduktion in W. |
| `Wechselrichterstatus` | String/JSON / Logging | Verteilte Grenzen und Zustand der PV-Regelung. |
| `Verteilbudget` | Float / Logging | Aktuell verfügbares Budget in W. | | `Verteilbudget` | Float / Logging | Aktuell verfügbares Budget in W. |
| `VerbraucherAnzahl` | Integer / Logging | Anzahl zugeordneter Verbraucher. | | `VerbraucherAnzahl` | Integer / Logging | Anzahl zugeordneter Verbraucher. |
| `VerbraucherVerfuegbar` | Integer / Logging | Anzahl aktuell verfügbarer Verbraucher. | | `VerbraucherVerfuegbar` | Integer / Logging | Anzahl aktuell verfügbarer Verbraucher. |
@@ -47,7 +50,11 @@ Ohne angelegte Diagnosevariablen existieren nur `Aktiv`, `Betriebsart` und `Netz
| `Monatsgrenzen` | String/JSON | Editierbare Liste mit zwölf Monatswerten in W. | | `Monatsgrenzen` | String/JSON | Editierbare Liste mit zwölf Monatswerten in W. |
| `SollwertSolarladen` | Float | `0` W; gewünschte Netzleistung im Solarladebetrieb. | | `SollwertSolarladen` | Float | `0` W; gewünschte Netzleistung im Solarladebetrieb. |
| `Umschaltdifferenz` | Float | `5` %; Mindestdifferenz der berechneten Sollleistungen vor Umschaltung. | | `Umschaltdifferenz` | Float | `5` %; Mindestdifferenz der berechneten Sollleistungen vor Umschaltung. |
| `AnlagenWechselrichter` | String/JSON | `[]`; Wechselrichter mit Typ und AC-Grenzen. | | `EinspeisebegrenzungAktiv` | Boolean | `false`; gemeinsame Exportgrenze am Netzanschlusspunkt aktivieren. |
| `Einspeisegrenze` | Float | Maximale Einspeisung der Gesamtanlage in W; `0` bedeutet Nulleinspeisung. |
| `Einspeisetoleranz` | Float | `100` W; Totband gegen pendelnde Wechselrichtervorgaben. |
| `NetzfahrplanAktiv` | Boolean | `false`; lizenzierten, tarif- und prognosebasierten Netzzielwert verwenden. |
| `AnlagenWechselrichter` | String/JSON | `[]`; Wechselrichter mit Typ, AC-Grenzen sowie optionalen Istleistungs- und Begrenzungsregistern. |
| `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, Leistungen, Kopplung und Wechselrichter-ID. |
| `PrognosePVVariableID` | Integer | `0`; PV-Leistung in W fuer die Prognosetelemetrie. | | `PrognosePVVariableID` | Integer | `0`; PV-Leistung in W fuer die Prognosetelemetrie. |
@@ -67,6 +74,33 @@ 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.
## Anlagenweite Einspeisebegrenzung
Die Einspeisebegrenzung ist Bestandteil von Peak Shaving und arbeitet auf der
Messung am Netzanschlusspunkt. Fuer jeden regelbaren PV- oder Hybridwechselrichter
werden die Istleistungsvariable und eine bedienbare Begrenzungsvariable
konfiguriert. Der Stellwert kann als Prozent der AC-Nennleistung oder als
absolute Leistung in Watt ausgegeben werden.
Der Manager berechnet eine einzige Grenze fuer die Gesamtanlage. Beim Abregeln
verteilt er sie nach der tatsaechlichen Erzeugung, beim Freigeben nach der
AC-Nennleistung auf alle angebotenen Wechselrichter. Das Totband verhindert
Pendeln am Grenzwert. Beim Abschalten des Managers oder der
Funktion werden zuvor gesetzte Grenzen kontrolliert auf die jeweilige
Nennleistung zurueckgesetzt. Wechselrichter ohne beide Register werden nicht
geregelt; sobald eines der beiden Register gesetzt ist, muessen beide gueltig
sein und das Begrenzungsregister eine IP-Symcon-Aktion besitzen.
Der intelligente Netzfahrplan verwendet die PV-, Verbrauchs- und
Netzleistungsprognose sowie Tarife und Batteriespeicher. Liegt im
Prognosezeitraum mehr PV-Ertrag als zulaessige Einspeisung vor, wird dieses
Potenzial als flexibler Verbrauch beziehungsweise Speicherladung eingeplant.
Damit werden Verbraucher in ertragreiche Zeitfenster verschoben, bevor der
harte Anlagenregler die Wechselrichter reduziert. Ein fehlender oder
abgelaufener Fahrplan fuehrt automatisch zum konfigurierten
`SollwertSolarladen` zurueck; die harte Einspeisebegrenzung bleibt unabhaengig
davon aktiv.
## Anlagentopologie und Prognoseexport ## Anlagentopologie und Prognoseexport
Der Manager ist die fuehrende Quelle fuer die technischen Stammdaten. Die Der Manager ist die fuehrende Quelle fuer die technischen Stammdaten. Die
@@ -137,8 +171,11 @@ wird vom Lizenzserver abgelehnt.
| Berechtigung | Freigegebene Funktion | | Berechtigung | Freigegebene Funktion |
| --- | --- | | --- | --- |
| `manager_standard` | Manager-Grundregelung ohne Lastspitzenmodus. | | `manager_standard` | Manager-Grundregelung ohne Peak Shaving. |
| `manager_peak` | Manager-Grundregelung einschliesslich konstantem oder monatlichem Lastspitzenmodus. | | `manager_peak` | Bezugs- und Einspeisebegrenzung am Netzanschlusspunkt. |
| `forecast_pv` | PV-Ertragsprognose; interne Modellvarianten bleiben verborgen. |
| `forecast_load` | Verbrauchsprognose; interne Modellvarianten bleiben verborgen. |
| `grid_schedule` | Intelligenter Netzfahrplan inklusive PV- und Verbrauchsprognose sowie Vermeidung von Abregelung. |
Wird mit `manager_standard` ein Lastspitzenmodus aktiviert, bleibt der Manager Wird mit `manager_standard` ein Lastspitzenmodus aktiviert, bleibt der Manager
mit dem Hinweis `Peak Shaving ist nicht lizenziert` gesperrt. `manager_peak` mit dem Hinweis `Peak Shaving ist nicht lizenziert` gesperrt. `manager_peak`
+127
View File
@@ -0,0 +1,127 @@
<?php
declare(strict_types=1);
namespace Belevo\EnelixEMS;
use InvalidArgumentException;
/**
* Verteilt eine gemeinsame Einspeisegrenze auf regelbare PV-Wechselrichter.
*
* Netzleistung: positiv Bezug, negativ Einspeisung.
*/
final class EinspeiseRegler
{
/**
* @param list<array{ID: string, Nennleistung_W: float|int, Istleistung_W: float|int}> $wechselrichter
* @param array<string, float|int> $letzteGrenzen
* @return array{
* Netzabweichung_W: float,
* ZielGesamtleistung_W: float,
* Abregelbedarf_W: float,
* Grenzen_W: array<string, float>
* }
*/
public static function verteile(
float $netzleistung,
float $einspeisegrenze,
float $toleranz,
array $wechselrichter,
array $letzteGrenzen = []
): array {
if ($einspeisegrenze < 0.0) {
throw new InvalidArgumentException('Einspeisegrenze darf nicht negativ sein.');
}
if ($toleranz < 0.0) {
throw new InvalidArgumentException('Einspeisetoleranz darf nicht negativ sein.');
}
if ($wechselrichter === []) {
throw new InvalidArgumentException('Mindestens ein regelbarer Wechselrichter ist erforderlich.');
}
$nennleistungGesamt = 0.0;
$istleistungGesamt = 0.0;
$bisherigeGrenzeGesamt = 0.0;
$normalisiert = [];
$kennungen = [];
foreach ($wechselrichter as $eintrag) {
$id = $eintrag['ID'] ?? null;
$nennleistung = $eintrag['Nennleistung_W'] ?? null;
$istleistung = $eintrag['Istleistung_W'] ?? null;
if (!is_string($id) || trim($id) === '' || isset($kennungen[$id])) {
throw new InvalidArgumentException('Wechselrichter-ID fehlt oder ist mehrfach vorhanden.');
}
if (!is_numeric($nennleistung) || (float) $nennleistung <= 0.0) {
throw new InvalidArgumentException('Wechselrichter-Nennleistung muss groesser als 0 sein.');
}
if (!is_numeric($istleistung) || (float) $istleistung < 0.0) {
throw new InvalidArgumentException('Wechselrichter-Istleistung darf nicht negativ sein.');
}
$nennleistung = (float) $nennleistung;
$istleistung = min((float) $istleistung, $nennleistung);
$bisherigeGrenze = $letzteGrenzen[$id] ?? $nennleistung;
if (!is_numeric($bisherigeGrenze)) {
throw new InvalidArgumentException('Gespeicherte Wechselrichtergrenze ist ungueltig.');
}
$bisherigeGrenze = max(0.0, min((float) $bisherigeGrenze, $nennleistung));
$normalisiert[] = [
'ID' => $id,
'Nennleistung_W' => $nennleistung,
'Istleistung_W' => $istleistung,
];
$kennungen[$id] = true;
$nennleistungGesamt += $nennleistung;
$istleistungGesamt += $istleistung;
$bisherigeGrenzeGesamt += $bisherigeGrenze;
}
$netzabweichung = $netzleistung + $einspeisegrenze;
$zielGesamt = $bisherigeGrenzeGesamt;
$nachIstleistungVerteilen = false;
if ($netzabweichung < -$toleranz) {
$nachIstleistungVerteilen = $istleistungGesamt > 0.0;
$wirksameAbweichung = $netzabweichung + $toleranz;
$zielGesamt = min($bisherigeGrenzeGesamt, $istleistungGesamt) + $wirksameAbweichung;
} elseif ($netzabweichung > $toleranz) {
$zielGesamt = $bisherigeGrenzeGesamt + $netzabweichung - $toleranz;
}
$zielGesamt = max(0.0, min($zielGesamt, $nennleistungGesamt));
$grenzen = [];
foreach ($normalisiert as $eintrag) {
$anteil = $nachIstleistungVerteilen
? $eintrag['Istleistung_W'] / $istleistungGesamt
: $eintrag['Nennleistung_W'] / $nennleistungGesamt;
$grenzen[$eintrag['ID']] = round(
min($eintrag['Nennleistung_W'], $zielGesamt * $anteil),
3
);
}
return [
'Netzabweichung_W' => $netzabweichung,
'ZielGesamtleistung_W' => round($zielGesamt, 3),
'Abregelbedarf_W' => round(max(0.0, $istleistungGesamt - $zielGesamt), 3),
'Grenzen_W' => $grenzen,
];
}
public static function stellwert(float $grenzeW, float $nennleistungW, string $art): float
{
if ($nennleistungW <= 0.0 || $grenzeW < 0.0) {
throw new InvalidArgumentException('Leistungsgrenzen sind ungueltig.');
}
$grenzeW = min($grenzeW, $nennleistungW);
if ($art === 'percent') {
return round($grenzeW / $nennleistungW * 100.0, 2);
}
if ($art === 'watt') {
return round($grenzeW, 1);
}
throw new InvalidArgumentException('Begrenzungsart muss percent oder watt sein.');
}
}
+3
View File
@@ -12,6 +12,9 @@ final class Lizenzpruefung
{ {
public const MANAGER_STANDARD = 'manager_standard'; public const MANAGER_STANDARD = 'manager_standard';
public const MANAGER_PEAK = 'manager_peak'; public const MANAGER_PEAK = 'manager_peak';
public const PROGNOSE_PV = 'forecast_pv';
public const PROGNOSE_VERBRAUCH = 'forecast_load';
public const NETZFAHRPLAN = 'grid_schedule';
public static function normalisiereCode(string $code): string public static function normalisiereCode(string $code): string
{ {
+89
View File
@@ -0,0 +1,89 @@
<?php
declare(strict_types=1);
namespace Belevo\EnelixEMS\Tests;
use Belevo\EnelixEMS\EinspeiseRegler;
use InvalidArgumentException;
use PHPUnit\Framework\TestCase;
final class EinspeiseReglerTest extends TestCase
{
public function testUeberschreitungWirdAufDieGesamtanlageBegrenzt(): void
{
$ergebnis = EinspeiseRegler::verteile(
-15000.0,
10000.0,
100.0,
[
['ID' => 'wr-1', 'Nennleistung_W' => 10000, 'Istleistung_W' => 10000],
['ID' => 'wr-2', 'Nennleistung_W' => 5000, 'Istleistung_W' => 5000],
]
);
self::assertSame(10100.0, $ergebnis['ZielGesamtleistung_W']);
self::assertSame(4900.0, $ergebnis['Abregelbedarf_W']);
self::assertSame(6733.333, $ergebnis['Grenzen_W']['wr-1']);
self::assertSame(3366.667, $ergebnis['Grenzen_W']['wr-2']);
}
public function testAbregelungFolgtDerTatsaechlichenErzeugung(): void
{
$ergebnis = EinspeiseRegler::verteile(
-10000.0,
5000.0,
0.0,
[
['ID' => 'wr-1', 'Nennleistung_W' => 10000, 'Istleistung_W' => 10000],
['ID' => 'wr-2', 'Nennleistung_W' => 10000, 'Istleistung_W' => 0],
]
);
self::assertSame(5000.0, $ergebnis['Grenzen_W']['wr-1']);
self::assertSame(0.0, $ergebnis['Grenzen_W']['wr-2']);
}
public function testFreierNetzspielraumHebtEineBestehendeBegrenzungAn(): void
{
$ergebnis = EinspeiseRegler::verteile(
-6000.0,
10000.0,
100.0,
[
['ID' => 'wr-1', 'Nennleistung_W' => 10000, 'Istleistung_W' => 4000],
['ID' => 'wr-2', 'Nennleistung_W' => 10000, 'Istleistung_W' => 2000],
],
['wr-1' => 5000, 'wr-2' => 5000]
);
self::assertSame(13900.0, $ergebnis['ZielGesamtleistung_W']);
self::assertSame(6950.0, $ergebnis['Grenzen_W']['wr-1']);
self::assertSame(6950.0, $ergebnis['Grenzen_W']['wr-2']);
}
public function testToleranzVerhindertPendelnAmGrenzwert(): void
{
$ergebnis = EinspeiseRegler::verteile(
-10050.0,
10000.0,
100.0,
[['ID' => 'wr-1', 'Nennleistung_W' => 10000, 'Istleistung_W' => 9000]],
['wr-1' => 8000]
);
self::assertSame(8000.0, $ergebnis['ZielGesamtleistung_W']);
}
public function testStellwerteWerdenInProzentOderWattAusgegeben(): void
{
self::assertSame(62.5, EinspeiseRegler::stellwert(5000.0, 8000.0, 'percent'));
self::assertSame(5000.0, EinspeiseRegler::stellwert(5000.0, 8000.0, 'watt'));
}
public function testUngueltigeBegrenzungsartWirdAbgelehnt(): void
{
$this->expectException(InvalidArgumentException::class);
EinspeiseRegler::stellwert(5000.0, 8000.0, 'volt');
}
}
+25
View File
@@ -48,6 +48,13 @@ final class ManagerModulstrukturTest extends TestCase
'VerbraucherTimeout', 'VerbraucherTimeout',
'MesswertMaxAlter', 'MesswertMaxAlter',
'Umschaltdifferenz', 'Umschaltdifferenz',
'EinspeisebegrenzungAktiv',
'Einspeisegrenze',
'Einspeisetoleranz',
'NetzfahrplanAktiv',
'IstleistungVariableID',
'BegrenzungVariableID',
'Begrenzungsart',
'DiagnosevariablenAnzeigen', 'DiagnosevariablenAnzeigen',
'LoggingEin', 'LoggingEin',
'Lizenzcode', 'Lizenzcode',
@@ -230,6 +237,24 @@ final class ManagerModulstrukturTest extends TestCase
); );
} }
public function testManagerRegeltDieAnlagenweiteEinspeisegrenzeUndDenNetzfahrplan(): void
{
$inhalt = (string) file_get_contents(__DIR__ . '/../Manager/module.php');
self::assertFileExists(__DIR__ . '/../libs/EinspeiseRegler.php');
self::assertStringContainsString('EinspeiseRegler::verteile(', $inhalt);
self::assertStringContainsString('EinspeiseRegler::stellwert(', $inhalt);
self::assertStringContainsString("RegisterAttributeString('LetztePvGrenzen'", $inhalt);
self::assertStringContainsString("RegisterAttributeString('Netzfahrplan'", $inhalt);
self::assertStringContainsString(
'https://license.enelix.ch/api/v1/installations/%s/prognosis/schedule',
$inhalt
);
self::assertStringContainsString('Lizenzpruefung::NETZFAHRPLAN', $inhalt);
self::assertStringContainsString('aktuelleNetzfahrplanVorgabe()', $inhalt);
self::assertStringContainsString('gibEinspeisebegrenzungFrei()', $inhalt);
}
public function testManagerVerwendetDieNeuenZeitdefaults(): void public function testManagerVerwendetDieNeuenZeitdefaults(): void
{ {
$inhalt = (string) file_get_contents(__DIR__ . '/../Manager/module.php'); $inhalt = (string) file_get_contents(__DIR__ . '/../Manager/module.php');
+1
View File
@@ -47,6 +47,7 @@ return [
'file' => __DIR__ . '/modules/Manager.php', 'file' => __DIR__ . '/modules/Manager.php',
'paths' => [ 'paths' => [
'Manager/*', 'Manager/*',
'libs/EinspeiseRegler.php',
'libs/Lizenzpruefung.php', 'libs/Lizenzpruefung.php',
'libs/ManagerRegler.php', 'libs/ManagerRegler.php',
'libs/ManagerSchnittstelle.php', 'libs/ManagerSchnittstelle.php',