2 Commits
Author SHA1 Message Date
dh 995b704e36 fix(manager): Prognoseberechtigungen verständlich diagnostizieren
Tests / test (push) Successful in 49s
2026-09-29 11:40:24 +00:00
dh 5ff0d405d4 fix(manager): feste Leistungsangebote immer zuteilen 2026-09-29 11:40:24 +00:00
7 changed files with 94 additions and 23 deletions
+5 -7
View File
@@ -2263,12 +2263,7 @@ class Manager extends IPSModule implements ManagerSchnittstelle
/** @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);
return Lizenzpruefung::hatPrognoseBerechtigung($lease);
}
private function berechtigungLizenziert(string $kennung, ?string $lizenzcode = null): bool
@@ -2418,7 +2413,10 @@ class Manager extends IPSModule implements ManagerSchnittstelle
if (!$lizenziert) {
return 'Prognosen sind deaktiviert. Fuer die Manager-ID '
. $managerID
. ' fehlen forecast_pv plus forecast_load oder grid_schedule.';
. ' ist keine Prognoseberechtigung vorhanden. Benoetigt wird entweder '
. 'PV-Ertragsprognose zusammen mit Verbrauchsprognose oder der '
. 'Intelligente Netzfahrplan. Nach der Erweiterung im Lizenzportal '
. 'die Lizenz erneut pruefen und binden.';
}
return ($aktiv ? 'Prognosen sind aktiviert. ' : 'Prognosen sind deaktiviert. ')
+6
View File
@@ -90,6 +90,12 @@ melden, ohne dass der Manager seine interne Geraetelogik kennen muss.
## Feste Regeln
- Prioritaeten beginnen bei 0; eine kleinere Zahl bedeutet hoehere Prioritaet.
- Jeder verfuegbare, synchronisierte Verbraucher mit einem nicht leeren
`Leistungswerte_W`-Angebot erhaelt einen Sollwert aus genau diesem Angebot.
- `AenderungMoeglich=false` kennzeichnet ein fixes Angebot und ist kein
Ausschlussgrund. Ein Angebot `[11000]` fuehrt deshalb zwingend zu `11000 W`.
- Prioritaeten verteilen nur die ueber den jeweiligen Mindestwert hinaus
verfuegbare Leistung; sie duerfen keinen angebotenen Mindestwert verdrängen.
- `Leistungsquelle`: 0 nicht vorhanden, 1 berechnet, 2 gemessen.
- Bei Leistungsquelle 0 ist `Istleistung_W` zwingend `null`.
- Leistungsbereiche enthalten jeden ganzen Wattwert von `Von_W` bis `Bis_W`.
+14 -8
View File
@@ -279,6 +279,7 @@ stabile ID gebunden werden.
| HTTP `429` | Zu viele Aktivierungsversuche; vor dem naechsten Versuch warten. |
| `Lizenzserver nicht erreichbar` | DNS, Internetzugang, HTTPS und Systemzeit des Symcon-Systems pruefen. Eine bestehende Lease gilt nur bis `offlineUntil`. |
| `Offline-Freigabe ist abgelaufen.` | Verbindung zum Lizenzserver wiederherstellen und Lizenz erneut pruefen. |
| `Keine Prognoseberechtigung vorhanden` | Im Lizenzportal entweder PV-Ertragsprognose zusammen mit Verbrauchsprognose oder den Intelligenten Netzfahrplan ergaenzen. Anschliessend im Manager `Lizenz pruefen und binden` ausloesen, die Prognose aktivieren und die Konfiguration speichern. |
Fuer eine genauere Diagnose koennen die Diagnosevariablen eingeblendet werden.
`Lizenzstatus` zeigt dann den aktuellen Zustand. Mit aktiviertem Debug-Logging
@@ -302,14 +303,19 @@ der aktiven Betriebsart und der aktuellen Leistung aller frisch gemeldeten
Verbraucher. Eine gültige Istleistung wird bevorzugt; fehlt sie, bleibt der im
Zustand bestätigte Sollwert konservativ reserviert.
Nicht verfügbare, nicht änderbare oder aktuell angebotlose Verbraucher behalten
ihre Leistung und erhalten keine neue Vorgabe. Die übrigen Verbraucher werden
nach der gemeldeten PV- beziehungsweise Peak-Priorität und danach stabil nach
Instanz-ID sortiert. Von ihrem jeweils kleinsten erlaubten Leistungswert aus
wird das Budget in dieser Reihenfolge aufgefüllt. Einzelwerte und ganzzahlige
Bereiche werden direkt verarbeitet; Lücken werden nie durch unzulässige Werte
geschlossen. Eine verbleibende Abweichung wird im `Verbraucherstatus`
dokumentiert.
Nicht verfügbare oder aktuell angebotlose Verbraucher behalten ihre Leistung
und erhalten keine neue Vorgabe. Jeder verfügbare, synchronisierte Verbraucher
mit einem nicht leeren Angebot erhält dagegen einen Sollwert aus diesem Angebot.
Das gilt auch bei `AenderungMoeglich=false`: Ein fixes Angebot wie `[11000]`
wird mit genau `11000 W` zugeteilt.
Die Verbraucher werden nach der gemeldeten PV- beziehungsweise Peak-Priorität
und danach stabil nach Instanz-ID sortiert. Zuerst reserviert der Manager für
jeden Verbraucher den kleinsten erlaubten Leistungswert. Nur das danach
verbleibende Budget wird in Prioritätsreihenfolge aufgefüllt. Einzelwerte und
ganzzahlige Bereiche werden direkt verarbeitet; Lücken werden nie durch
unzulässige Werte geschlossen. Eine verbleibende Abweichung wird im
`Verbraucherstatus` dokumentiert.
Die Betriebsart wechselt unterhalb des Solar-Sollwerts zu `PV` und oberhalb
der wirksamen Lastspitzengrenze zu `Peak`. Zwischen den beiden Zielwerten
+13
View File
@@ -117,6 +117,19 @@ final class Lizenzpruefung
return $menge;
}
/**
* Eine Prognose ist entweder durch den Netzfahrplan oder durch beide
* Einzelberechtigungen fuer PV und Verbrauch freigeschaltet.
*
* @param array<string, mixed> $lease
*/
public static function hatPrognoseBerechtigung(array $lease): bool
{
return self::berechtigungsMenge($lease, self::NETZFAHRPLAN) > 0
|| (self::berechtigungsMenge($lease, self::PROGNOSE_PV) > 0
&& self::berechtigungsMenge($lease, self::PROGNOSE_VERBRAUCH) > 0);
}
/**
* Rechnet freie, bestehende Einzellizenzen auf nicht separat lizenzierte Waermepumpen an.
*
+1 -1
View File
@@ -120,7 +120,7 @@ final class ManagerRegler
$aktuelleLeistung += $leistung;
$daten = $eintrag['Daten'];
if (!$daten['Verfuegbar'] || !$daten['AenderungMoeglich'] || $daten['Leistungswerte_W'] === []) {
if (!$daten['Verfuegbar'] || $daten['Leistungswerte_W'] === []) {
$nichtSteuerbar += $leistung;
continue;
}
+41
View File
@@ -228,6 +228,47 @@ final class LizenzpruefungTest extends TestCase
Lizenzpruefung::pruefeVerbrauchermengen($lease, $pruefmengen);
}
public function testPvUndVerbrauchGebenPrognoseFrei(): void
{
$lease = $this->lease('manager_standard');
$lease['entitlements'][] = [
'catalog_key' => Lizenzpruefung::PROGNOSE_PV,
'quantity' => 1,
'status' => 'active',
];
$lease['entitlements'][] = [
'catalog_key' => Lizenzpruefung::PROGNOSE_VERBRAUCH,
'quantity' => 1,
'status' => 'active',
];
self::assertTrue(Lizenzpruefung::hatPrognoseBerechtigung($lease));
}
public function testEinzelnePrognoseBerechtigungReichtNichtAus(): void
{
$lease = $this->lease('manager_standard');
$lease['entitlements'][] = [
'catalog_key' => Lizenzpruefung::PROGNOSE_PV,
'quantity' => 1,
'status' => 'active',
];
self::assertFalse(Lizenzpruefung::hatPrognoseBerechtigung($lease));
}
public function testNetzfahrplanGibtPrognoseAlleineFrei(): void
{
$lease = $this->lease('manager_standard');
$lease['entitlements'][] = [
'catalog_key' => Lizenzpruefung::NETZFAHRPLAN,
'quantity' => 1,
'status' => 'active',
];
self::assertTrue(Lizenzpruefung::hatPrognoseBerechtigung($lease));
}
public function testLizenzcodeWirdWieAufDemServerNormalisiert(): void
{
self::assertSame(
+14 -7
View File
@@ -113,19 +113,19 @@ final class ManagerReglerTest extends TestCase
self::assertSame(500, $ergebnis['Restabweichung_W']);
}
public function testNichtAenderbarerVerbraucherReserviertSeineIstleistung(): void
public function testFestwertWirdUnabhaengigVonAenderungMoeglichZugewiesen(): void
{
$fest = $this->verbraucher(10, 0, [0, 3000], 3000, false);
$fest = $this->verbraucher(10, 0, [3000], 3000, false);
$steuerbar = $this->verbraucher(20, 1, [0, ['Von_W' => 1, 'Bis_W' => 4000]], 0);
$ergebnis = ManagerRegler::verteile(3000.0, 5000.0, [$fest, $steuerbar], 'PrioritaetPV');
self::assertSame(3000, $ergebnis['NichtSteuerbar_W']);
self::assertSame([20 => 2000], $ergebnis['Sollwerte']);
self::assertSame(0, $ergebnis['NichtSteuerbar_W']);
self::assertSame([10 => 3000, 20 => 2000], $ergebnis['Sollwerte']);
self::assertSame(0, $ergebnis['Restabweichung_W']);
}
public function testVeralteterVerbraucherBlockiertUeberschussverteilungNicht(): void
public function testFestwerteWerdenMitHoeherPriorisierterBatterieZugewiesen(): void
{
$ergebnis = ManagerRegler::verteile(
-2411.0,
@@ -147,7 +147,13 @@ final class ManagerReglerTest extends TestCase
self::assertSame(12562, $ergebnis['Verteilbudget_W']);
self::assertSame(
[44234 => 2411, 29743 => 0, 38676 => 0],
[
44234 => -12288,
29743 => 0,
38676 => 0,
43878 => 13850,
46052 => 11000,
],
$ergebnis['Sollwerte']
);
self::assertSame(0, $ergebnis['Restabweichung_W']);
@@ -155,7 +161,7 @@ final class ManagerReglerTest extends TestCase
public function testFehlendeIstleistungFaelltAufAngenommenenSollwertZurueck(): void
{
$verbraucher = $this->verbraucher(10, 0, [0, 3000], null, false);
$verbraucher = $this->verbraucher(10, 0, [3000], null, false);
$verbraucher['Daten']['Leistungsquelle'] = Nachrichtenvertrag::LEISTUNGSQUELLE_NICHT_VORHANDEN;
$verbraucher['Daten']['Istleistung_W'] = null;
$verbraucher['Daten']['Zustand'][0]['Wert'] = 3000;
@@ -163,6 +169,7 @@ final class ManagerReglerTest extends TestCase
$ergebnis = ManagerRegler::verteile(3000.0, 0.0, [$verbraucher], 'PrioritaetPV');
self::assertSame(3000, $ergebnis['AktuelleVerbraucherleistung_W']);
self::assertSame([10 => 3000], $ergebnis['Sollwerte']);
self::assertSame(-3000, $ergebnis['Restabweichung_W']);
}