diff --git a/Manager/module.php b/Manager/module.php index 8d9dc7e..7a0d1b6 100644 --- a/Manager/module.php +++ b/Manager/module.php @@ -156,7 +156,12 @@ class Manager extends IPSModule implements ManagerSchnittstelle $this->SetTimerInterval('KeepAlive', $this->ReadPropertyInteger('KeepAlive') * 1000); $this->setzeDiagnosewert('Monatsgrenzen', $this->ReadPropertyString('Monatsgrenzen')); $this->aktualisiereAnschlussstatus(); - if (!$this->aktualisiereLizenzfreigabe($this->ReadPropertyString('Lizenzcode'), false)) { + $geraetezugangFehlt = $this->hatAnlagentopologie() + && $this->ReadAttributeString('PrognoseInstallationsToken') === ''; + if (!$this->aktualisiereLizenzfreigabe( + $this->ReadPropertyString('Lizenzcode'), + $geraetezugangFehlt + )) { $this->SetValue('Betriebsart', ManagerRegler::BETRIEBSART_INAKTIV); $this->SetStatus(self::STATUS_LIZENZ_UNGUELTIG); return; @@ -265,7 +270,7 @@ class Manager extends IPSModule implements ManagerSchnittstelle { return json_encode( $this->leseAnlagentopologie(), - JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES + JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES | JSON_PRESERVE_ZERO_FRACTION ); } diff --git a/examples/Demoanlage/README.md b/examples/Demoanlage/README.md index fb96beb..179b68e 100644 --- a/examples/Demoanlage/README.md +++ b/examples/Demoanlage/README.md @@ -7,6 +7,8 @@ Die Demo erzeugt idempotent eine vollständige, spielbare EMS-Anlage in IP-Symco - einen einstufigen Entfeuchter (0,9 kW), - einen zweistufigen Pufferspeicher (1,8/3,6 kW) mit Heizkurve, - einen bidirektionalen Batteriespeicher (10 kWh, +/-3,5 kW), +- eine technische Anlagentopologie mit Hybridwechselrichter, PV-Fläche und + Batteriespeicher für die Prognoseanbindung, - simulierte PV-Erzeugung, variable Bewölkung, Tageslastgang, Zusatzlast, Außentemperatur, Wärmeverluste, Netzleistung und Speicherzustände, - native Symcon-Instanz `Energy Distribution` für acht Energieflussknoten, @@ -39,6 +41,12 @@ damit keine Lizenzfehler protokolliert werden. Nach der Aktivierung den Bootstrap erneut ausführen; er ordnet dann alle fünf Teilnehmer zu und startet die Regelung. +Die Demo übernimmt die einstellbare PV-Spitzenleistung als AC- und +DC-Anlagenleistung. Für die feste Beispieltopologie gelten 30 Grad Neigung und +Südausrichtung. Der 10-kWh-Speicher nutzt den gemeinsamen Hybridwechselrichter; +seine dynamischen Lade- und Entladegrenzen werden beim Bootstrap in die +technischen Stammdaten übernommen. + ## Bedienung In der Kachelvisualisierung öffnet die Startkategorie `Enelix 2 Demoanlage`. diff --git a/examples/Demoanlage/bootstrap.php b/examples/Demoanlage/bootstrap.php index 11b4966..4b2e7a4 100644 --- a/examples/Demoanlage/bootstrap.php +++ b/examples/Demoanlage/bootstrap.php @@ -277,6 +277,39 @@ function enelixDemoInstall(): array static fn (int $id): array => ['InstanzID' => $id, 'Aktiv' => true], [$boiler1, $boiler2, $dryer, $buffer, $battery] ); + $pvNennleistungKw = max(0.1, (float) GetValue($peak) / 1000.0); + $wechselrichterKonfiguration = [[ + 'ID' => 'demo-hybrid-1', + 'Name' => 'Demo-Hybridwechselrichter', + 'Typ' => 'hybrid', + 'ACNennleistung_kW' => $pvNennleistungKw, + 'ACEinspeisegrenze_kW' => 0.0, + 'ACBezugsgrenze_kW' => 0.0, + ]]; + $pvKonfiguration = [[ + 'ID' => 'demo-pv-1', + 'Name' => 'Demo-PV-Anlage', + 'DCLeistung_kWp' => $pvNennleistungKw, + 'Neigung_Grad' => 30.0, + 'Azimut_Grad' => 0.0, + 'WechselrichterID' => 'demo-hybrid-1', + 'MPPT' => 'A', + 'Modulanzahl' => 0, + 'Modulleistung_Wp' => 0.0, + ]]; + $batterieKonfiguration = [[ + 'ID' => 'demo-battery-1', + 'Name' => 'Batteriespeicher 10 kWh', + 'Nennkapazitaet_kWh' => 10.0, + 'Nutzkapazitaet_kWh' => 10.0, + 'MaxLadeleistung_kW' => max(0.1, (float) GetValue($batteryMaxCharge) / 1000.0), + 'MaxEntladeleistung_kW' => max( + 0.1, + (float) GetValue($batteryMaxDischarge) / 1000.0 + ), + 'WechselrichterID' => 'demo-hybrid-1', + 'Kopplung' => 'hybrid', + ]]; $managerProperties = [ 'NetzleistungVariableID' => $grid, 'Netzleistungsfaktor' => 1.0, @@ -288,6 +321,15 @@ function enelixDemoInstall(): array 'VerbraucherTimeout' => 15, 'SollwertSolarladen' => 0.0, 'Umschaltdifferenz' => 0.0, + 'AnlagenWechselrichter' => json_encode( + $wechselrichterKonfiguration, + JSON_THROW_ON_ERROR + ), + 'AnlagenPVFlaechen' => json_encode($pvKonfiguration, JSON_THROW_ON_ERROR), + 'AnlagenBatterien' => json_encode( + $batterieKonfiguration, + JSON_THROW_ON_ERROR + ), 'DiagnosevariablenAnzeigen' => true, 'LoggingEin' => false, ]; diff --git a/tests/DemoanlageBootstrapTest.php b/tests/DemoanlageBootstrapTest.php index faa7654..13225f7 100644 --- a/tests/DemoanlageBootstrapTest.php +++ b/tests/DemoanlageBootstrapTest.php @@ -116,6 +116,12 @@ final class DemoanlageBootstrapTest extends TestCase self::assertStringContainsString("'BatterieListe'", $source); self::assertStringContainsString('ENELIX_DEMO_BUFFER_MODULE', $source); self::assertStringContainsString('ENELIX_DEMO_BATTERY_MODULE', $source); + self::assertStringContainsString("'AnlagenWechselrichter'", $source); + self::assertStringContainsString("'AnlagenPVFlaechen'", $source); + self::assertStringContainsString("'AnlagenBatterien'", $source); + self::assertStringContainsString("'demo-hybrid-1'", $source); + self::assertStringContainsString("'demo-pv-1'", $source); + self::assertStringContainsString("'demo-battery-1'", $source); self::assertStringContainsString("'EnergieflussPVkW'", $source); self::assertStringContainsString("'~Power'", $source); self::assertStringContainsString("'~Electricity'", $source); diff --git a/tests/ManagerModulstrukturTest.php b/tests/ManagerModulstrukturTest.php index f649c2a..18d6745 100644 --- a/tests/ManagerModulstrukturTest.php +++ b/tests/ManagerModulstrukturTest.php @@ -186,8 +186,17 @@ final class ManagerModulstrukturTest extends TestCase "RegisterAttributeString('PrognoseInstallationsToken', '')", $inhalt ); + self::assertStringContainsString('JSON_PRESERVE_ZERO_FRACTION', $inhalt); self::assertStringContainsString("'issueDeviceToken' =>", $inhalt); self::assertStringContainsString("unset(\$neueLease['deviceToken'])", $inhalt); + self::assertStringContainsString( + "\$geraetezugangFehlt = \$this->hatAnlagentopologie()", + $inhalt + ); + self::assertStringContainsString( + "\$this->ReadPropertyString('Lizenzcode'),\n \$geraetezugangFehlt", + $inhalt + ); self::assertStringContainsString('synchronisiereAnlagentopologie()', $inhalt); self::assertStringContainsString("'Authorization: Bearer ' . \$token", $inhalt); self::assertStringNotContainsString(