This commit is contained in:
@@ -9,6 +9,8 @@ Die Demo erzeugt idempotent eine vollständige, spielbare EMS-Anlage in IP-Symco
|
|||||||
- einen bidirektionalen Batteriespeicher (10 kWh, +/-3,5 kW),
|
- einen bidirektionalen Batteriespeicher (10 kWh, +/-3,5 kW),
|
||||||
- eine technische Anlagentopologie mit Hybridwechselrichter, PV-Fläche und
|
- eine technische Anlagentopologie mit Hybridwechselrichter, PV-Fläche und
|
||||||
Batteriespeicher für die Prognoseanbindung,
|
Batteriespeicher für die Prognoseanbindung,
|
||||||
|
- simulierte Istleistungs- und Leistungsbegrenzungsregister des Wechselrichters,
|
||||||
|
- eine aktive anlagenweite Einspeisebegrenzung auf 3.000 W mit 100 W Toleranz,
|
||||||
- Prognosetelemetrie aus PV, Hausverbrauch, Netzleistung und Batterie-SOC im
|
- Prognosetelemetrie aus PV, Hausverbrauch, Netzleistung und Batterie-SOC im
|
||||||
60-Sekunden-Intervall,
|
60-Sekunden-Intervall,
|
||||||
- simulierte PV-Erzeugung, variable Bewölkung, Tageslastgang, Zusatzlast,
|
- simulierte PV-Erzeugung, variable Bewölkung, Tageslastgang, Zusatzlast,
|
||||||
@@ -49,7 +51,12 @@ Südausrichtung. Der 10-kWh-Speicher nutzt den gemeinsamen Hybridwechselrichter;
|
|||||||
seine dynamischen Lade- und Entladegrenzen werden beim Bootstrap in die
|
seine dynamischen Lade- und Entladegrenzen werden beim Bootstrap in die
|
||||||
technischen Stammdaten übernommen. Bei aktiver Manager-Lizenz uebertraegt der
|
technischen Stammdaten übernommen. Bei aktiver Manager-Lizenz uebertraegt der
|
||||||
Manager zusaetzlich PV-Leistung, Hausverbrauch, Netzleistung und Batterie-SOC
|
Manager zusaetzlich PV-Leistung, Hausverbrauch, Netzleistung und Batterie-SOC
|
||||||
alle 60 Sekunden an den Enelix-Prognosedienst.
|
alle 60 Sekunden an den Enelix-Prognosedienst. Die Demo verwendet ein absolutes
|
||||||
|
Wechselrichter-Leistungsregister in Watt. Der Manager begrenzt die tatsächliche
|
||||||
|
PV-Produktion so, dass die Gesamtanlage höchstens 3.000 W einspeist; innerhalb
|
||||||
|
einer Toleranz von 100 W bleibt der zuletzt gesetzte Stellwert bestehen. Der
|
||||||
|
intelligente Netzfahrplan bleibt deaktiviert, solange dessen Backendfassung nur
|
||||||
|
als ausstehende Version bereitliegt.
|
||||||
|
|
||||||
## Bedienung
|
## Bedienung
|
||||||
|
|
||||||
@@ -60,7 +67,9 @@ eine ungeregelte Zusatzlast verändern. Bei aktivierten dynamischen Profilen
|
|||||||
entstehen reproduzierbare Morgen- und Abendspitzen, ziehende Bewölkung und ein
|
entstehen reproduzierbare Morgen- und Abendspitzen, ziehende Bewölkung und ein
|
||||||
Tagesgang der Außentemperatur. Der Manager verteilt den verfügbaren
|
Tagesgang der Außentemperatur. Der Manager verteilt den verfügbaren
|
||||||
PV-Überschuss nach Priorität auf beide Boiler, Entfeuchter, Pufferspeicher und
|
PV-Überschuss nach Priorität auf beide Boiler, Entfeuchter, Pufferspeicher und
|
||||||
Batterie.
|
Batterie. Bei hoher PV-Erzeugung ist die Abregelung an den technischen Variablen
|
||||||
|
`PV-Wechselrichter Istleistung` und `PV-Wechselrichter Leistungsgrenze` sowie an
|
||||||
|
der auf etwa 3.000 W begrenzten Netzeinspeisung sichtbar.
|
||||||
|
|
||||||
`Übersicht` zeigt die native Symcon-Energieverteilung. Die Kacheln `Boiler 1`,
|
`Übersicht` zeigt die native Symcon-Energieverteilung. Die Kacheln `Boiler 1`,
|
||||||
`Boiler 2`, `Pufferspeicher` und `Batteriespeicher` öffnen direkt die jeweiligen
|
`Boiler 2`, `Pufferspeicher` und `Batteriespeicher` öffnen direkt die jeweiligen
|
||||||
@@ -76,6 +85,8 @@ Simulationszeit.
|
|||||||
- Die fünf Verbraucher müssen Status 102 besitzen; der Manager nach Lizenzaktivierung ebenfalls (vorher erwarteter Status 203).
|
- Die fünf Verbraucher müssen Status 102 besitzen; der Manager nach Lizenzaktivierung ebenfalls (vorher erwarteter Status 203).
|
||||||
- Der Manager muss fünf Teilnehmer aus vier unterschiedlichen Modultypen melden.
|
- Der Manager muss fünf Teilnehmer aus vier unterschiedlichen Modultypen melden.
|
||||||
- Bei ausreichendem PV-Überschuss müssen thermische Verbraucher schalten und die Batterie laden.
|
- Bei ausreichendem PV-Überschuss müssen thermische Verbraucher schalten und die Batterie laden.
|
||||||
|
- Bei höherem verbleibendem PV-Überschuss muss das Wechselrichterregister die PV-Leistung so begrenzen, dass ungefähr 3.000 W ins Netz eingespeist werden.
|
||||||
|
- Wechselrichter-Istleistung und Leistungsgrenze müssen in der technischen Kategorie vorhanden und im Manager zugeordnet sein.
|
||||||
- Puffertemperatur, Außentemperatur und Batterie-Ladezustand müssen sich über den Tageslauf verändern.
|
- Puffertemperatur, Außentemperatur und Batterie-Ladezustand müssen sich über den Tageslauf verändern.
|
||||||
- Netzleistung, PV-Leistung, Hausverbrauch, EMS-Leistung, Einzelverbräuche, Speicherleistung, Energiesummen und Temperaturen werden archiviert.
|
- Netzleistung, PV-Leistung, Hausverbrauch, EMS-Leistung, Einzelverbräuche, Speicherleistung, Energiesummen und Temperaturen werden archiviert.
|
||||||
- Die native Energieverteilung muss acht Knoten enthalten.
|
- Die native Energieverteilung muss acht Knoten enthalten.
|
||||||
|
|||||||
@@ -113,6 +113,8 @@ function enelixDemoInstall(): array
|
|||||||
$batteryManagement = enelixDemoEnsureVariable($technical, 'BatterieManagement', 'Batterie Managementregister', 1, '', 0, 340);
|
$batteryManagement = enelixDemoEnsureVariable($technical, 'BatterieManagement', 'Batterie Managementregister', 1, '', 0, 340);
|
||||||
$batteryMode = enelixDemoEnsureVariable($technical, 'BatterieModus', 'Batterie Modusregister', 1, '', 0, 350);
|
$batteryMode = enelixDemoEnsureVariable($technical, 'BatterieModus', 'Batterie Modusregister', 1, '', 0, 350);
|
||||||
$batteryRegisterPower = enelixDemoEnsureVariable($technical, 'BatterieRegisterLeistung', 'Batterie Leistungsregister', 2, 'ENELIX.Demo.PowerPositive', 0.0, 360);
|
$batteryRegisterPower = enelixDemoEnsureVariable($technical, 'BatterieRegisterLeistung', 'Batterie Leistungsregister', 2, 'ENELIX.Demo.PowerPositive', 0.0, 360);
|
||||||
|
$inverterActual = enelixDemoEnsureVariable($technical, 'WechselrichterIstleistung', 'PV-Wechselrichter Istleistung', 2, 'ENELIX.Demo.PowerPositive', 0.0, 370);
|
||||||
|
$inverterLimit = enelixDemoEnsureVariable($technical, 'WechselrichterLeistungsgrenze', 'PV-Wechselrichter Leistungsgrenze', 2, 'ENELIX.Demo.PowerPositive', max(0.0, (float) GetValue($peak)), 380);
|
||||||
$chartUpdated = enelixDemoEnsureVariable($technical, 'LetztesDiagrammUpdate', 'Letztes Diagramm-Update', 1, '', 0, 390);
|
$chartUpdated = enelixDemoEnsureVariable($technical, 'LetztesDiagrammUpdate', 'Letztes Diagramm-Update', 1, '', 0, 390);
|
||||||
IPS_SetHidden($chartUpdated, true);
|
IPS_SetHidden($chartUpdated, true);
|
||||||
|
|
||||||
@@ -136,10 +138,10 @@ function enelixDemoInstall(): array
|
|||||||
foreach ([$boiler11, $boiler12, $boiler21, $boiler22, $dryContact, $buffer1, $buffer2] as $actorId) {
|
foreach ([$boiler11, $boiler12, $boiler21, $boiler22, $dryContact, $buffer1, $buffer2] as $actorId) {
|
||||||
IPS_SetVariableCustomAction($actorId, $actorScript);
|
IPS_SetVariableCustomAction($actorId, $actorScript);
|
||||||
}
|
}
|
||||||
foreach ([$batteryManagement, $batteryMode, $batteryRegisterPower] as $registerId) {
|
foreach ([$batteryManagement, $batteryMode, $batteryRegisterPower, $inverterLimit] as $registerId) {
|
||||||
IPS_SetVariableCustomAction($registerId, $registerScript);
|
IPS_SetVariableCustomAction($registerId, $registerScript);
|
||||||
}
|
}
|
||||||
foreach ([$temp1, $temp2, $bufferTemp, $outsideTemp, $batteryMaxCharge, $batteryMaxDischarge, $batterySoc, $batteryActual] as $sensorId) {
|
foreach ([$temp1, $temp2, $bufferTemp, $outsideTemp, $batteryMaxCharge, $batteryMaxDischarge, $batterySoc, $batteryActual, $inverterActual] as $sensorId) {
|
||||||
SetValue($sensorId, GetValue($sensorId));
|
SetValue($sensorId, GetValue($sensorId));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -285,6 +287,9 @@ function enelixDemoInstall(): array
|
|||||||
'ACNennleistung_kW' => $pvNennleistungKw,
|
'ACNennleistung_kW' => $pvNennleistungKw,
|
||||||
'ACEinspeisegrenze_kW' => 0.0,
|
'ACEinspeisegrenze_kW' => 0.0,
|
||||||
'ACBezugsgrenze_kW' => 0.0,
|
'ACBezugsgrenze_kW' => 0.0,
|
||||||
|
'IstleistungVariableID' => $inverterActual,
|
||||||
|
'BegrenzungVariableID' => $inverterLimit,
|
||||||
|
'Begrenzungsart' => 'watt',
|
||||||
]];
|
]];
|
||||||
$pvKonfiguration = [[
|
$pvKonfiguration = [[
|
||||||
'ID' => 'demo-pv-1',
|
'ID' => 'demo-pv-1',
|
||||||
@@ -325,6 +330,10 @@ function enelixDemoInstall(): array
|
|||||||
'VerbraucherTimeout' => 15,
|
'VerbraucherTimeout' => 15,
|
||||||
'SollwertSolarladen' => 0.0,
|
'SollwertSolarladen' => 0.0,
|
||||||
'Umschaltdifferenz' => 0.0,
|
'Umschaltdifferenz' => 0.0,
|
||||||
|
'EinspeisebegrenzungAktiv' => true,
|
||||||
|
'Einspeisegrenze' => 3000.0,
|
||||||
|
'Einspeisetoleranz' => 100.0,
|
||||||
|
'NetzfahrplanAktiv' => false,
|
||||||
'AnlagenWechselrichter' => json_encode(
|
'AnlagenWechselrichter' => json_encode(
|
||||||
$wechselrichterKonfiguration,
|
$wechselrichterKonfiguration,
|
||||||
JSON_THROW_ON_ERROR
|
JSON_THROW_ON_ERROR
|
||||||
@@ -476,7 +485,11 @@ function enelixDemoTick(string $source = 'Manual'): void
|
|||||||
$sun = ($hour >= 6.0 && $hour <= 18.0)
|
$sun = ($hour >= 6.0 && $hour <= 18.0)
|
||||||
? max(0.0, sin(M_PI * ($hour - 6.0) / 12.0))
|
? max(0.0, sin(M_PI * ($hour - 6.0) / 12.0))
|
||||||
: 0.0;
|
: 0.0;
|
||||||
$pv = $peak * $sun * (1.0 - ($environment['cloud'] * 0.0085));
|
$pvAvailable = $peak * $sun * (1.0 - ($environment['cloud'] * 0.0085));
|
||||||
|
$inverterActual = enelixDemoRequiredId($technical, 'WechselrichterIstleistung');
|
||||||
|
$inverterLimit = enelixDemoRequiredId($technical, 'WechselrichterLeistungsgrenze');
|
||||||
|
$pv = enelixDemoLimitPvPower($pvAvailable, (float) GetValue($inverterLimit));
|
||||||
|
SetValue($inverterActual, round($pv, 1));
|
||||||
|
|
||||||
$contacts = [
|
$contacts = [
|
||||||
'boiler11' => enelixDemoRequiredId($technical, 'Boiler1Stufe1'),
|
'boiler11' => enelixDemoRequiredId($technical, 'Boiler1Stufe1'),
|
||||||
@@ -541,6 +554,11 @@ function enelixDemoTick(string $source = 'Manual'): void
|
|||||||
IPS_RequestAction($consumerId, 'Melden', true);
|
IPS_RequestAction($consumerId, 'Melden', true);
|
||||||
}
|
}
|
||||||
IPS_RequestAction($manager, 'Regeln', true);
|
IPS_RequestAction($manager, 'Regeln', true);
|
||||||
|
$pv = enelixDemoLimitPvPower(
|
||||||
|
$pvAvailable,
|
||||||
|
(float) GetValue($inverterLimit)
|
||||||
|
);
|
||||||
|
SetValue($inverterActual, round($pv, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
$battery = $managerReady
|
$battery = $managerReady
|
||||||
@@ -597,6 +615,11 @@ function enelixDemoTick(string $source = 'Manual'): void
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function enelixDemoLimitPvPower(float $availablePower, float $limitPower): float
|
||||||
|
{
|
||||||
|
return min(max(0.0, $availablePower), max(0.0, $limitPower));
|
||||||
|
}
|
||||||
|
|
||||||
function enelixDemoRenderFlow(array $state): string
|
function enelixDemoRenderFlow(array $state): string
|
||||||
{
|
{
|
||||||
$format = static fn (float $value): string => number_format($value, 0, ',', "'");
|
$format = static fn (float $value): string => number_format($value, 0, ',', "'");
|
||||||
|
|||||||
@@ -123,6 +123,13 @@ final class DemoanlageBootstrapTest extends TestCase
|
|||||||
self::assertStringContainsString("'PrognoseHausverbrauchVariableID' => \$houseDisplay", $source);
|
self::assertStringContainsString("'PrognoseHausverbrauchVariableID' => \$houseDisplay", $source);
|
||||||
self::assertStringContainsString("'PrognoseSOCVariableID' => \$batterySoc", $source);
|
self::assertStringContainsString("'PrognoseSOCVariableID' => \$batterySoc", $source);
|
||||||
self::assertStringContainsString("'PrognoseSendeintervall' => 60", $source);
|
self::assertStringContainsString("'PrognoseSendeintervall' => 60", $source);
|
||||||
|
self::assertStringContainsString("'IstleistungVariableID' => \$inverterActual", $source);
|
||||||
|
self::assertStringContainsString("'BegrenzungVariableID' => \$inverterLimit", $source);
|
||||||
|
self::assertStringContainsString("'Begrenzungsart' => 'watt'", $source);
|
||||||
|
self::assertStringContainsString("'EinspeisebegrenzungAktiv' => true", $source);
|
||||||
|
self::assertStringContainsString("'Einspeisegrenze' => 3000.0", $source);
|
||||||
|
self::assertStringContainsString("'Einspeisetoleranz' => 100.0", $source);
|
||||||
|
self::assertStringContainsString("'NetzfahrplanAktiv' => false", $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);
|
||||||
@@ -191,6 +198,14 @@ final class DemoanlageBootstrapTest extends TestCase
|
|||||||
self::assertSame(0.0, enelixDemoBatteryRegisterPower(2, 0, 1500.0));
|
self::assertSame(0.0, enelixDemoBatteryRegisterPower(2, 0, 1500.0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testWechselrichtergrenzeBegrenztNurDieTatsaechlichePvLeistung(): void
|
||||||
|
{
|
||||||
|
self::assertSame(3000.0, enelixDemoLimitPvPower(8000.0, 3000.0));
|
||||||
|
self::assertSame(2500.0, enelixDemoLimitPvPower(2500.0, 3000.0));
|
||||||
|
self::assertSame(0.0, enelixDemoLimitPvPower(-100.0, 3000.0));
|
||||||
|
self::assertSame(0.0, enelixDemoLimitPvPower(8000.0, -100.0));
|
||||||
|
}
|
||||||
|
|
||||||
public function testRollbackIstExplizitBestaetigungspflichtig(): void
|
public function testRollbackIstExplizitBestaetigungspflichtig(): void
|
||||||
{
|
{
|
||||||
$this->expectException(RuntimeException::class);
|
$this->expectException(RuntimeException::class);
|
||||||
|
|||||||
Reference in New Issue
Block a user