Files
Enelix-EMS/examples/Demoanlage/bootstrap.php
T
2026-09-27 19:29:37 +00:00

1251 lines
58 KiB
PHP

<?php
declare(strict_types=1);
const ENELIX_DEMO_ROOT_IDENT = 'ENELIX_DEMO_2';
const ENELIX_DEMO_MANAGER_MODULE = '{6F771B18-59D4-4C8A-B951-3B2FE9F6A2C4}';
const ENELIX_DEMO_BOILER_MODULE = '{B7C54AF4-AD7D-4FE4-B75D-203693906251}';
const ENELIX_DEMO_SINGLE_MODULE = '{15879A4E-D0C2-4495-83DE-46E1E462591E}';
const ENELIX_DEMO_BUFFER_MODULE = '{C92D5EEF-9632-47A5-9659-4B02BF40FBE9}';
const ENELIX_DEMO_BATTERY_MODULE = '{437FB683-517F-4FEC-8CCB-FE6B0A62B69E}';
const ENELIX_DEMO_DUMMY_MODULE = '{485D0419-BE97-4548-AA9C-C083EB82E61E}';
const ENELIX_DEMO_TILE_MODULE = '{B5B875BB-9B76-45FD-4E67-2607E45B3AC4}';
const ENELIX_DEMO_ARCHIVE_MODULE = '{43192F0B-135B-4CE7-A0A7-1475603F3060}';
const ENELIX_DEMO_ENERGY_DISTRIBUTION_MODULE = '{BA90FDB3-C203-8F26-B4D6-F3DB99905724}';
function enelixDemoInstall(): array
{
enelixDemoValidateRuntime();
$root = enelixDemoEnsureCategory(0, ENELIX_DEMO_ROOT_IDENT, 'Enelix 2 Demoanlage', 100);
IPS_SetInfo($root, 'Idempotent erzeugte Enelix-2-Demo. Entfernen nur mit enelixDemoRemove(true).');
$dashboard = enelixDemoEnsureCategory($root, 'Dashboard', 'Übersicht', 10);
$simulation = enelixDemoEnsureCategory($root, 'Simulation', 'Simulation', 20);
$boiler1View = enelixDemoEnsureCategory($root, 'Boiler1Ansicht', 'Boiler 1', 30);
$boiler2View = enelixDemoEnsureCategory($root, 'Boiler2Ansicht', 'Boiler 2', 40);
$history = enelixDemoEnsureCategory($root, 'Historie', 'Leistung und Energie', 50);
$ems = enelixDemoEnsureCategory($root, 'EMS', 'Manager und Verbraucher', 60);
$technical = enelixDemoEnsureCategory($root, 'Technik', 'Simulationstechnik', 90, true);
enelixDemoEnsureProfiles();
$legacyFlow = enelixDemoEnsureVariable($dashboard, 'Energiefluss', 'Energiefluss (Legacy)', 3, '~HTMLBox', '', 900);
IPS_SetHidden($legacyFlow, true);
$pvDisplay = enelixDemoEnsureVariable($dashboard, 'PVLeistung', 'PV-Leistung', 2, 'ENELIX.Demo.PowerPositive', 0.0, 20);
$houseDisplay = enelixDemoEnsureVariable($dashboard, 'Hausverbrauch', 'Hausverbrauch gesamt', 2, 'ENELIX.Demo.PowerPositive', 0.0, 30);
$loadDisplay = enelixDemoEnsureVariable($dashboard, 'Verbraucherleistung', 'EMS-Leistung inkl. Speicher', 2, 'ENELIX.Demo.Power', 0.0, 40);
$grid = enelixDemoEnsureVariable($dashboard, 'Netzleistung', 'Netzleistung', 2, 'ENELIX.Demo.Power', 0.0, 50);
$boiler1Power = enelixDemoEnsureVariable($dashboard, 'Boiler1Leistung', 'Boiler 1 Leistung', 2, 'ENELIX.Demo.PowerPositive', 0.0, 60);
$boiler2Power = enelixDemoEnsureVariable($dashboard, 'Boiler2Leistung', 'Boiler 2 Leistung', 2, 'ENELIX.Demo.PowerPositive', 0.0, 70);
$dryerPower = enelixDemoEnsureVariable($dashboard, 'EntfeuchterLeistung', 'Entfeuchter Leistung', 2, 'ENELIX.Demo.PowerPositive', 0.0, 80);
$bufferPower = enelixDemoEnsureVariable($dashboard, 'PufferLeistung', 'Pufferspeicher Leistung', 2, 'ENELIX.Demo.PowerPositive', 0.0, 90);
$batteryPower = enelixDemoEnsureVariable($dashboard, 'BatterieLeistung', 'Batterieleistung', 2, 'ENELIX.Demo.Power', 0.0, 100);
$powerChart = enelixDemoEnsureVariable($history, 'Leistungsdiagramm', 'Leistungsverlauf (Legacy)', 3, '~HTMLBox', '', 90);
$energyChart = enelixDemoEnsureVariable($history, 'Energiediagramm', 'Kumulierte Energien (Legacy)', 3, '~HTMLBox', '', 100);
IPS_SetHidden($powerChart, true);
IPS_SetHidden($energyChart, true);
$pvEnergy = enelixDemoEnsureVariable($history, 'PVEnergie', 'PV-Energie', 2, '~Electricity', 0.0, 30);
$houseEnergy = enelixDemoEnsureVariable($history, 'HausEnergie', 'Hausverbrauch Energie', 2, '~Electricity', 0.0, 40);
$gridImportEnergy = enelixDemoEnsureVariable($history, 'NetzbezugEnergie', 'Netzbezug Energie', 2, '~Electricity', 0.0, 50);
$gridExportEnergy = enelixDemoEnsureVariable($history, 'EinspeisungEnergie', 'Einspeisung Energie', 2, '~Electricity', 0.0, 60);
$auto = enelixDemoEnsureVariable($simulation, 'Automatik', 'Tageslauf automatisch', 0, '~Switch', true, 10);
$hour = enelixDemoEnsureVariable($simulation, 'Tageszeit', 'Simulierte Tageszeit', 2, 'ENELIX.Demo.Hour', 8.0, 20);
$speed = enelixDemoEnsureVariable($simulation, 'Geschwindigkeit', 'Minuten je Schritt', 1, 'ENELIX.Demo.Speed', 10, 30);
$dynamic = enelixDemoEnsureVariable($simulation, 'DynamischeProfile', 'Dynamische Tagesprofile', 0, '~Switch', true, 40);
$peak = enelixDemoEnsureVariable($simulation, 'PVSpitze', 'PV-Spitzenleistung', 2, 'ENELIX.Demo.PowerPositive', 9000.0, 50);
$cloud = enelixDemoEnsureVariable($simulation, 'Bewoelkung', 'Mittlere Bewölkung', 1, 'ENELIX.Demo.Percent', 20, 60);
$base = enelixDemoEnsureVariable($simulation, 'Grundlast', 'Mittlere Haus-Grundlast', 2, 'ENELIX.Demo.PowerPositive', 900.0, 70);
$extraLoad = enelixDemoEnsureVariable($simulation, 'Zusatzlast', 'Ungeregelte Zusatzlast', 2, 'ENELIX.Demo.PowerPositive', 0.0, 80);
$actorScript = enelixDemoEnsureScript(
$technical,
'AktorAktion',
'Simulierte Aktoren',
"<?php\nSetValue((int) \$_IPS['VARIABLE'], (bool) \$_IPS['VALUE']);\n",
10
);
$registerScript = enelixDemoEnsureScript(
$technical,
'RegisterAktion',
'Simulierte Register',
"<?php\nSetValue((int) \$_IPS['VARIABLE'], \$_IPS['VALUE']);\n",
15
);
$tickScript = enelixDemoEnsureScript(
$technical,
'SimulationTick',
'Simulation aktualisieren',
"<?php\nrequire_once IPS_GetKernelDir() . 'modules/Enelix-EMS/examples/Demoanlage/bootstrap.php';\nenelixDemoTick('Timer');\n",
20
);
$controlScript = enelixDemoEnsureScript(
$technical,
'BedienAktion',
'Bedienwerte übernehmen',
"<?php\nSetValue((int) \$_IPS['VARIABLE'], \$_IPS['VALUE']);\nIPS_RunScript(" . $tickScript . ");\n",
30
);
foreach ([$auto, $hour, $speed, $dynamic, $peak, $cloud, $base, $extraLoad] as $variableId) {
IPS_SetVariableCustomAction($variableId, $controlScript);
}
enelixDemoEnsureCyclicEvent($tickScript, 'Simulationszyklus', 2);
$temp1 = enelixDemoEnsureVariable($technical, 'Boiler1Temperatur', 'Boiler 1 Temperatur', 2, '~Temperature', 49.0, 100);
$temp2 = enelixDemoEnsureVariable($technical, 'Boiler2Temperatur', 'Boiler 2 Temperatur', 2, '~Temperature', 47.0, 110);
$bufferTemp = enelixDemoEnsureVariable($technical, 'PufferTemperatur', 'Puffertemperatur', 2, '~Temperature', 36.0, 120);
$outsideTemp = enelixDemoEnsureVariable($technical, 'AussenTemperatur', 'Aussentemperatur', 2, '~Temperature', 6.0, 130);
$boiler11 = enelixDemoEnsureVariable($technical, 'Boiler1Stufe1', 'Boiler 1 Stufe 1', 0, '~Switch', false, 200);
$boiler12 = enelixDemoEnsureVariable($technical, 'Boiler1Stufe2', 'Boiler 1 Stufe 2', 0, '~Switch', false, 210);
$boiler21 = enelixDemoEnsureVariable($technical, 'Boiler2Stufe1', 'Boiler 2 Stufe 1', 0, '~Switch', false, 220);
$boiler22 = enelixDemoEnsureVariable($technical, 'Boiler2Stufe2', 'Boiler 2 Stufe 2', 0, '~Switch', false, 230);
$dryContact = enelixDemoEnsureVariable($technical, 'EntfeuchterKontakt', 'Entfeuchter Kontakt', 0, '~Switch', false, 240);
$buffer1 = enelixDemoEnsureVariable($technical, 'PufferStufe1', 'Puffer Stufe 1', 0, '~Switch', false, 250);
$buffer2 = enelixDemoEnsureVariable($technical, 'PufferStufe2', 'Puffer Stufe 2', 0, '~Switch', false, 260);
$batteryMaxCharge = enelixDemoEnsureVariable($technical, 'BatterieMaxLaden', 'Batterie max. laden', 2, 'ENELIX.Demo.PowerPositive', 3500.0, 300);
$batteryMaxDischarge = enelixDemoEnsureVariable($technical, 'BatterieMaxEntladen', 'Batterie max. entladen', 2, 'ENELIX.Demo.PowerPositive', 3500.0, 310);
$batterySoc = enelixDemoEnsureVariable($technical, 'BatterieLadezustand', 'Batterie Ladezustand', 2, 'ENELIX.Demo.PercentFloat', 52.0, 320);
$batteryActual = enelixDemoEnsureVariable($technical, 'BatterieIstleistung', 'Batterie Istleistung', 2, 'ENELIX.Demo.Power', 0.0, 330);
$batteryManagement = enelixDemoEnsureVariable($technical, 'BatterieManagement', 'Batterie Managementregister', 1, '', 0, 340);
$batteryMode = enelixDemoEnsureVariable($technical, 'BatterieModus', 'Batterie Modusregister', 1, '', 0, 350);
$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);
IPS_SetHidden($chartUpdated, true);
$flowPv = enelixDemoEnsureVariable($technical, 'EnergieflussPVkW', 'PV-Leistung', 2, '~Power', 0.0, 500);
$flowGrid = enelixDemoEnsureVariable($technical, 'EnergieflussNetzkW', 'Netzleistung', 2, '~Power', 0.0, 510);
$flowBase = enelixDemoEnsureVariable($technical, 'EnergieflussGrundlastkW', 'Haus-Grundlast', 2, '~Power', 0.0, 520);
$flowBoiler1 = enelixDemoEnsureVariable($technical, 'EnergieflussBoiler1kW', 'Boiler 1', 2, '~Power', 0.0, 530);
$flowBoiler2 = enelixDemoEnsureVariable($technical, 'EnergieflussBoiler2kW', 'Boiler 2', 2, '~Power', 0.0, 540);
$flowDryer = enelixDemoEnsureVariable($technical, 'EnergieflussEntfeuchterkW', 'Entfeuchter', 2, '~Power', 0.0, 550);
$flowBuffer = enelixDemoEnsureVariable($technical, 'EnergieflussPufferkW', 'Pufferspeicher', 2, '~Power', 0.0, 560);
$flowBattery = enelixDemoEnsureVariable($technical, 'EnergieflussBatteriekW', 'Batteriespeicher', 2, '~Power', 0.0, 570);
$flowHouse = enelixDemoEnsureVariable($technical, 'DiagrammHausverbrauchkW', 'Hausverbrauch gesamt', 2, '~Power', 0.0, 580);
$flowLoads = enelixDemoEnsureVariable($technical, 'DiagrammVerbraucherkW', 'EMS-Leistung inkl. Speicher', 2, '~Power', 0.0, 590);
foreach ([
$flowPv, $flowGrid, $flowBase, $flowBoiler1, $flowBoiler2,
$flowDryer, $flowBuffer, $flowBattery, $flowHouse, $flowLoads,
] as $flowVariableId) {
IPS_SetHidden($flowVariableId, true);
}
foreach ([$boiler11, $boiler12, $boiler21, $boiler22, $dryContact, $buffer1, $buffer2] as $actorId) {
IPS_SetVariableCustomAction($actorId, $actorScript);
}
foreach ([$batteryManagement, $batteryMode, $batteryRegisterPower, $inverterLimit] as $registerId) {
IPS_SetVariableCustomAction($registerId, $registerScript);
}
foreach ([$temp1, $temp2, $bufferTemp, $outsideTemp, $batteryMaxCharge, $batteryMaxDischarge, $batterySoc, $batteryActual, $inverterActual] as $sensorId) {
SetValue($sensorId, GetValue($sensorId));
}
$boiler1 = enelixDemoEnsureInstance($ems, 'Boiler1', 'Boiler 1 · 300 Liter', ENELIX_DEMO_BOILER_MODULE, 20);
$boiler2 = enelixDemoEnsureInstance($ems, 'Boiler2', 'Boiler 2 · 500 Liter', ENELIX_DEMO_BOILER_MODULE, 30);
$dryer = enelixDemoEnsureInstance($ems, 'Entfeuchter', 'Entfeuchter · 1-stufig', ENELIX_DEMO_SINGLE_MODULE, 40);
$buffer = enelixDemoEnsureInstance($ems, 'Pufferspeicher', 'Pufferspeicher · Heizkurve', ENELIX_DEMO_BUFFER_MODULE, 50);
$battery = enelixDemoEnsureInstance($ems, 'Batterie', 'Batteriespeicher · 10 kWh', ENELIX_DEMO_BATTERY_MODULE, 60);
enelixDemoConfigure($boiler1, [
'Boilerfuehler_PT1' => $temp1,
'LeistungsStufen' => json_encode([
['Stufe' => 1, 'Leistung' => 1200, 'Schaltkontakt_Stufe' => $boiler11],
['Stufe' => 2, 'Leistung' => 2400, 'Schaltkontakt_Stufe' => $boiler12],
], JSON_THROW_ON_ERROR),
'Boilervolumen' => 300,
'LastwechselSperrzeit' => 2,
'TemperaturMaxAlter' => 30,
'LegionellenfunktionAktiv' => false,
'PrioritaetPV' => 0,
'PrioritaetPeak' => 2,
'Meldeintervall' => 5,
'VorgabeTimeout' => 30,
'EinstellungenInVisu' => true,
'DiagnosevariablenAnzeigen' => true,
]);
enelixDemoConfigure($boiler2, [
'Boilerfuehler_PT1' => $temp2,
'LeistungsStufen' => json_encode([
['Stufe' => 1, 'Leistung' => 1500, 'Schaltkontakt_Stufe' => $boiler21],
['Stufe' => 2, 'Leistung' => 3000, 'Schaltkontakt_Stufe' => $boiler22],
], JSON_THROW_ON_ERROR),
'Boilervolumen' => 500,
'LastwechselSperrzeit' => 2,
'TemperaturMaxAlter' => 30,
'LegionellenfunktionAktiv' => false,
'PrioritaetPV' => 1,
'PrioritaetPeak' => 1,
'Meldeintervall' => 5,
'VorgabeTimeout' => 30,
'EinstellungenInVisu' => true,
'DiagnosevariablenAnzeigen' => true,
]);
enelixDemoConfigure($dryer, [
'Nennleistung' => 900,
'SchaltkontaktVariableID' => $dryContact,
'RueckmeldungVariableID' => $dryContact,
'Mindesteinschaltdauer' => 2,
'Mindestausschaltdauer' => 2,
'Mindestlaufzeit' => 0,
'PrioritaetPV' => 2,
'PrioritaetPeak' => 3,
'Meldeintervall' => 5,
'VorgabeTimeout' => 30,
'EinstellungenInVisu' => true,
'DiagnosevariablenAnzeigen' => true,
]);
enelixDemoConfigure($buffer, [
'LeistungsStufen' => json_encode([
['Stufe' => 1, 'Leistung' => 1800, 'Schaltkontakt_Stufe' => $buffer1],
['Stufe' => 2, 'Leistung' => 3600, 'Schaltkontakt_Stufe' => $buffer2],
], JSON_THROW_ON_ERROR),
'PufferfuehlerVariableID' => $bufferTemp,
'AussentemperaturVariableID' => $outsideTemp,
'FusspunktVorlauftemperatur' => 34.0,
'HeizkurvenSteigung' => 0.8,
'HeizkurveMinimaltemperatur' => 28.0,
'HeizkurveMaximaltemperatur' => 55.0,
'Hysterese' => 4.0,
'MindesttemperaturModus' => 0,
'LastwechselSperrzeit' => 2,
'TemperaturMaxAlter' => 30,
'PuffertemperaturGlaetten' => false,
'PrioritaetPV' => 3,
'PrioritaetPeak' => 4,
'Meldeintervall' => 5,
'VorgabeTimeout' => 30,
'EinstellungenInVisu' => true,
'DiagnosevariablenAnzeigen' => true,
]);
enelixDemoConfigure($battery, [
'Batterietyp' => 2,
'Batteriemanagement' => 2,
'MaxLadeleistungVariableID' => $batteryMaxCharge,
'MaxEntladeleistungVariableID' => $batteryMaxDischarge,
'LadezustandVariableID' => $batterySoc,
'NetzleistungVariableID' => $grid,
'IstleistungVariableID' => $batteryActual,
'ManagementRegisterVariableID' => $batteryManagement,
'ModusRegisterVariableID' => $batteryMode,
'LeistungsRegisterVariableID' => $batteryRegisterPower,
'ReserveLadezustand' => 20.0,
'MindestLadezustand' => 10.0,
'LadezustandHysterese' => 3.0,
'MesswertMaxAlter' => 30,
'Aenderungssperre' => 0,
'PrioritaetPV' => 4,
'PrioritaetPeak' => 0,
'Meldeintervall' => 5,
'VorgabeTimeout' => 30,
'EinstellungenInVisu' => true,
'DiagnosevariablenAnzeigen' => true,
]);
$energyDistribution = enelixDemoEnsureInstance(
$dashboard,
'EnergieverteilungNativ',
'Energiefluss',
ENELIX_DEMO_ENERGY_DISTRIBUTION_MODULE,
10
);
enelixDemoConfigureEnergyDistribution($energyDistribution, [
enelixDemoEnergyNode($flowPv, 'PV-Anlage', 'solar'),
enelixDemoEnergyNode($flowGrid, 'Stromnetz', 'grid'),
enelixDemoEnergyNode($flowBase, 'Haus-Grundlast', 'consumer'),
enelixDemoEnergyNode($flowBoiler1, 'Boiler 1', 'consumer', $temp1),
enelixDemoEnergyNode($flowBoiler2, 'Boiler 2', 'consumer', $temp2),
enelixDemoEnergyNode($flowDryer, 'Entfeuchter', 'consumer'),
enelixDemoEnergyNode($flowBuffer, 'Pufferspeicher', 'consumer', $bufferTemp),
enelixDemoEnergyNode($flowBattery, 'Batteriespeicher', 'battery', $batterySoc),
]);
IPS_SetHidden($energyDistribution, false);
$oldEnergyDistribution = enelixDemoFind($dashboard, 'Energieverteilung');
if ($oldEnergyDistribution > 0 && $oldEnergyDistribution !== $energyDistribution) {
IPS_SetHidden($oldEnergyDistribution, true);
}
foreach ([$boiler1, $boiler2] as $boilerId) {
IPS_RequestAction($boilerId, 'Mindesttemperatur', 42.0);
IPS_RequestAction($boilerId, 'Maximaltemperatur', 62.0);
}
$manager = enelixDemoEnsureInstance($ems, 'Manager', 'Enelix Manager', ENELIX_DEMO_MANAGER_MODULE, 10);
$assignment = array_map(
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,
'IstleistungVariableID' => $inverterActual,
'Istleistungsfaktor' => 1.0,
'BegrenzungVariableID' => $inverterLimit,
'Begrenzungsart' => 'watt',
]];
$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',
'LeistungVariableID' => $batteryActual,
'Leistungsfaktor' => 1.0,
'SOCVariableID' => $batterySoc,
]];
$managerProperties = [
'NetzleistungVariableID' => $grid,
'Netzleistungsfaktor' => 1.0,
'MesswerteAnzeigen' => true,
'EnergyPieAnzeigen' => true,
'EnergiediagrammeAnzeigen' => true,
'FunFactsAnzeigen' => true,
'EnergieflussAnzeigen' => true,
'PrognoseSendeintervall' => 60,
'MesswertMaxAlter' => 10,
'AutomatischeSuche' => false,
'SuchbereichID' => $root,
'VerbraucherZuordnung' => '[]',
'KeepAlive' => 5,
'VerbraucherTimeout' => 15,
'SollwertSolarladen' => 0.0,
'Umschaltdifferenz' => 0.0,
'EinspeisebegrenzungAktiv' => true,
'Einspeisegrenze' => 3000.0,
'Einspeisetoleranz' => 100.0,
'NetzfahrplanAktiv' => false,
'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,
];
enelixDemoConfigure($manager, $managerProperties, [102, 203]);
$managerReady = (int) IPS_GetInstance($manager)['InstanceStatus'] === 102;
if ($managerReady) {
$managerProperties['VerbraucherZuordnung'] = json_encode($assignment, JSON_THROW_ON_ERROR);
enelixDemoConfigure($manager, $managerProperties, [102, 203]);
$managerReady = (int) IPS_GetInstance($manager)['InstanceStatus'] === 102;
}
if (!$managerReady && IPS_GetProperty($manager, 'VerbraucherZuordnung') !== '[]') {
$managerProperties['VerbraucherZuordnung'] = '[]';
enelixDemoConfigure($manager, $managerProperties, [203]);
}
$consumerIds = [$boiler1, $boiler2, $dryer, $buffer, $battery];
foreach ($consumerIds as $consumerId) {
IPS_RequestAction($consumerId, 'Aktiv', $managerReady);
if ($managerReady) {
IPS_RequestAction($consumerId, 'Melden', true);
}
}
IPS_RequestAction($manager, 'Aktiv', $managerReady);
IPS_RequestAction($manager, 'MesswerteAufzeichnen', true);
$boiler1List = enelixDemoEnsureInstance(
$dashboard,
'Boiler1Liste',
'Boiler 1',
ENELIX_DEMO_DUMMY_MODULE,
110
);
$boiler2List = enelixDemoEnsureInstance(
$dashboard,
'Boiler2Liste',
'Boiler 2',
ENELIX_DEMO_DUMMY_MODULE,
120
);
$bufferList = enelixDemoEnsureInstance(
$dashboard,
'PufferListe',
'Pufferspeicher',
ENELIX_DEMO_DUMMY_MODULE,
130
);
$batteryList = enelixDemoEnsureInstance(
$dashboard,
'BatterieListe',
'Batteriespeicher',
ENELIX_DEMO_DUMMY_MODULE,
140
);
foreach ([$boiler1List, $boiler2List, $bufferList, $batteryList] as $listId) {
enelixDemoConfigure($listId, []);
}
enelixDemoLinkInstanceVariables($boiler1List, $boiler1, $temp1);
enelixDemoLinkInstanceVariables($boiler2List, $boiler2, $temp2);
enelixDemoLinkInstanceVariables($bufferList, $buffer, $bufferTemp, 'Puffertemperatur', 'SensorPuffertemperatur');
enelixDemoLinkInstanceVariables($batteryList, $battery, $batterySoc, 'Ladezustand', 'SensorLadezustand');
IPS_SetHidden($boiler1View, true);
IPS_SetHidden($boiler2View, true);
foreach ([$boiler1, $boiler2, $buffer, $battery] as $hiddenInstanceId) {
IPS_SetHidden($hiddenInstanceId, true);
}
foreach (['Boiler1Link', 'Boiler2Link'] as $oldLinkIdent) {
$oldLink = enelixDemoFind($dashboard, $oldLinkIdent);
if ($oldLink > 0) {
IPS_SetHidden($oldLink, true);
}
}
enelixDemoEnsureLink($dashboard, 'ManagerLink', 'Manager', $manager, 100);
enelixDemoEnsureLink($dashboard, 'EntfeuchterLink', 'Entfeuchter', $dryer, 150);
enelixDemoEnsureLink($simulation, 'Boiler1TemperaturLink', 'Boiler 1 Temperatur', $temp1, 100);
enelixDemoEnsureLink($simulation, 'Boiler2TemperaturLink', 'Boiler 2 Temperatur', $temp2, 110);
enelixDemoEnsureLink($simulation, 'PufferTemperaturLink', 'Puffertemperatur', $bufferTemp, 120);
enelixDemoEnsureLink($simulation, 'AussenTemperaturLink', 'Aussentemperatur', $outsideTemp, 130);
enelixDemoEnsureLink($simulation, 'BatterieLadezustandLink', 'Batterie Ladezustand', $batterySoc, 140);
enelixDemoConfigureArchive([
$pvDisplay, $houseDisplay, $loadDisplay, $grid,
$boiler1Power, $boiler2Power, $dryerPower, $bufferPower, $batteryPower,
$pvEnergy, $houseEnergy, $gridImportEnergy, $gridExportEnergy,
$temp1, $temp2, $bufferTemp, $outsideTemp, $batterySoc, $batteryActual,
$flowPv, $flowGrid, $flowBase, $flowBoiler1, $flowBoiler2, $flowDryer,
$flowBuffer, $flowBattery, $flowHouse, $flowLoads,
]);
enelixDemoEnsureChart($history, 'LeistungsdiagrammNativ', 'Leistungsverlauf', [
enelixDemoChartDataset($flowPv, 'PV', '#f4c542', '~Power'),
enelixDemoChartDataset($flowHouse, 'Hausverbrauch', '#2bb673', '~Power'),
enelixDemoChartDataset($flowLoads, 'EMS-Leistung inkl. Speicher', '#2786ac', '~Power'),
enelixDemoChartDataset($flowGrid, 'Netzleistung', '#e95d5d', '~Power'),
], 10);
enelixDemoEnsureChart($history, 'EnergiediagrammNativ', 'Kumulierte Energien', [
enelixDemoChartDataset($pvEnergy, 'PV', '#f4c542', '~Electricity'),
enelixDemoChartDataset($houseEnergy, 'Hausverbrauch', '#2bb673', '~Electricity'),
enelixDemoChartDataset($gridImportEnergy, 'Netzbezug', '#e95d5d', '~Electricity'),
enelixDemoChartDataset($gridExportEnergy, 'Einspeisung', '#38a3db', '~Electricity'),
], 20);
enelixDemoConfigureVisualization($root, $energyDistribution);
enelixDemoTick('Install');
return [
'rootId' => $root,
'visualizationId' => enelixDemoFirstInstance(ENELIX_DEMO_TILE_MODULE),
'managerId' => $manager,
'consumerIds' => $consumerIds,
'tickScriptId' => $tickScript,
];
}
function enelixDemoTick(string $source = 'Manual'): void
{
$root = enelixDemoRequiredId(0, ENELIX_DEMO_ROOT_IDENT);
$dashboard = enelixDemoRequiredId($root, 'Dashboard');
$simulation = enelixDemoRequiredId($root, 'Simulation');
$ems = enelixDemoRequiredId($root, 'EMS');
$technical = enelixDemoRequiredId($root, 'Technik');
$autoId = enelixDemoRequiredId($simulation, 'Automatik');
$hourId = enelixDemoRequiredId($simulation, 'Tageszeit');
$minutes = max(1, (int) GetValue(enelixDemoRequiredId($simulation, 'Geschwindigkeit')));
$hour = (float) GetValue($hourId);
if ($source === 'Timer' && (bool) GetValue($autoId)) {
$hour = fmod($hour + ($minutes / 60.0), 24.0);
SetValue($hourId, $hour);
}
$dynamic = (bool) GetValue(enelixDemoRequiredId($simulation, 'DynamischeProfile'));
$environment = enelixDemoEnvironment(
$hour,
(float) GetValue(enelixDemoRequiredId($simulation, 'Grundlast')),
(int) GetValue(enelixDemoRequiredId($simulation, 'Bewoelkung')),
$dynamic
);
$base = $environment['base']
+ (float) GetValue(enelixDemoRequiredId($simulation, 'Zusatzlast'));
$peak = (float) GetValue(enelixDemoRequiredId($simulation, 'PVSpitze'));
$sun = ($hour >= 6.0 && $hour <= 18.0)
? max(0.0, sin(M_PI * ($hour - 6.0) / 12.0))
: 0.0;
$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 = [
'boiler11' => enelixDemoRequiredId($technical, 'Boiler1Stufe1'),
'boiler12' => enelixDemoRequiredId($technical, 'Boiler1Stufe2'),
'boiler21' => enelixDemoRequiredId($technical, 'Boiler2Stufe1'),
'boiler22' => enelixDemoRequiredId($technical, 'Boiler2Stufe2'),
'dryer' => enelixDemoRequiredId($technical, 'EntfeuchterKontakt'),
'buffer1' => enelixDemoRequiredId($technical, 'PufferStufe1'),
'buffer2' => enelixDemoRequiredId($technical, 'PufferStufe2'),
'battery' => enelixDemoRequiredId($technical, 'BatterieIstleistung'),
];
$temp1 = enelixDemoRequiredId($technical, 'Boiler1Temperatur');
$temp2 = enelixDemoRequiredId($technical, 'Boiler2Temperatur');
$bufferTemp = enelixDemoRequiredId($technical, 'PufferTemperatur');
$outsideTemp = enelixDemoRequiredId($technical, 'AussenTemperatur');
$batterySoc = enelixDemoRequiredId($technical, 'BatterieLadezustand');
$batteryMaxCharge = enelixDemoRequiredId($technical, 'BatterieMaxLaden');
$batteryMaxDischarge = enelixDemoRequiredId($technical, 'BatterieMaxEntladen');
$previousBatteryPower = (float) GetValue($contacts['battery']);
SetValue($outsideTemp, round($environment['outside'], 2));
SetValue($batteryMaxCharge, GetValue($batteryMaxCharge));
SetValue($batteryMaxDischarge, GetValue($batteryMaxDischarge));
if ($source === 'Timer') {
enelixDemoUpdateTemperature(
$temp1,
(bool) GetValue($contacts['boiler12']) ? 2400 : ((bool) GetValue($contacts['boiler11']) ? 1200 : 0),
$minutes
);
enelixDemoUpdateTemperature(
$temp2,
(bool) GetValue($contacts['boiler22']) ? 3000 : ((bool) GetValue($contacts['boiler21']) ? 1500 : 0),
$minutes
);
enelixDemoUpdateBufferTemperature(
$bufferTemp,
(bool) GetValue($contacts['buffer2']) ? 3600 : ((bool) GetValue($contacts['buffer1']) ? 1800 : 0),
$environment['outside'],
$minutes
);
enelixDemoUpdateBatterySoc($batterySoc, $previousBatteryPower, $minutes);
} else {
SetValue($bufferTemp, GetValue($bufferTemp));
SetValue($batterySoc, GetValue($batterySoc));
}
$powerBefore = enelixDemoControlledPower($contacts);
$gridId = enelixDemoRequiredId($dashboard, 'Netzleistung');
SetValue($gridId, round($base + $powerBefore - $pv, 1));
$consumers = [
enelixDemoRequiredId($ems, 'Boiler1'),
enelixDemoRequiredId($ems, 'Boiler2'),
enelixDemoRequiredId($ems, 'Entfeuchter'),
enelixDemoRequiredId($ems, 'Pufferspeicher'),
enelixDemoRequiredId($ems, 'Batterie'),
];
$manager = enelixDemoRequiredId($ems, 'Manager');
$managerReady = (int) IPS_GetInstance($manager)['InstanceStatus'] === 102;
if ($managerReady) {
foreach ($consumers as $consumerId) {
IPS_RequestAction($consumerId, 'Melden', true);
}
IPS_RequestAction($manager, 'Regeln', true);
$pv = enelixDemoLimitPvPower(
$pvAvailable,
(float) GetValue($inverterLimit)
);
SetValue($inverterActual, round($pv, 1));
}
$battery = $managerReady
? enelixDemoBatteryRegisterPower(
(int) GetValue(enelixDemoRequiredId($technical, 'BatterieManagement')),
(int) GetValue(enelixDemoRequiredId($technical, 'BatterieModus')),
(float) GetValue(enelixDemoRequiredId($technical, 'BatterieRegisterLeistung'))
)
: 0.0;
SetValue($contacts['battery'], $battery);
$boiler1 = (bool) GetValue($contacts['boiler12']) ? 2400 : ((bool) GetValue($contacts['boiler11']) ? 1200 : 0);
$boiler2 = (bool) GetValue($contacts['boiler22']) ? 3000 : ((bool) GetValue($contacts['boiler21']) ? 1500 : 0);
$dryer = (bool) GetValue($contacts['dryer']) ? 900 : 0;
$buffer = (bool) GetValue($contacts['buffer2']) ? 3600 : ((bool) GetValue($contacts['buffer1']) ? 1800 : 0);
$loads = $boiler1 + $boiler2 + $dryer + $buffer;
$emsPower = $loads + $battery;
$house = $base + $loads;
$grid = $house + $battery - $pv;
SetValue($gridId, round($grid, 1));
SetValue(enelixDemoRequiredId($dashboard, 'PVLeistung'), round($pv, 1));
SetValue(enelixDemoRequiredId($dashboard, 'Hausverbrauch'), round($house, 1));
SetValue(enelixDemoRequiredId($dashboard, 'Verbraucherleistung'), (float) $emsPower);
SetValue(enelixDemoRequiredId($dashboard, 'Boiler1Leistung'), (float) $boiler1);
SetValue(enelixDemoRequiredId($dashboard, 'Boiler2Leistung'), (float) $boiler2);
SetValue(enelixDemoRequiredId($dashboard, 'EntfeuchterLeistung'), (float) $dryer);
SetValue(enelixDemoRequiredId($dashboard, 'PufferLeistung'), (float) $buffer);
SetValue(enelixDemoRequiredId($dashboard, 'BatterieLeistung'), $battery);
SetValue(enelixDemoRequiredId($technical, 'EnergieflussPVkW'), round($pv / 1000.0, 3));
SetValue(enelixDemoRequiredId($technical, 'EnergieflussNetzkW'), round($grid / 1000.0, 3));
SetValue(enelixDemoRequiredId($technical, 'EnergieflussGrundlastkW'), round($base / 1000.0, 3));
SetValue(enelixDemoRequiredId($technical, 'EnergieflussBoiler1kW'), round($boiler1 / 1000.0, 3));
SetValue(enelixDemoRequiredId($technical, 'EnergieflussBoiler2kW'), round($boiler2 / 1000.0, 3));
SetValue(enelixDemoRequiredId($technical, 'EnergieflussEntfeuchterkW'), round($dryer / 1000.0, 3));
SetValue(enelixDemoRequiredId($technical, 'EnergieflussPufferkW'), round($buffer / 1000.0, 3));
SetValue(enelixDemoRequiredId($technical, 'EnergieflussBatteriekW'), round($battery / 1000.0, 3));
SetValue(enelixDemoRequiredId($technical, 'DiagrammHausverbrauchkW'), round($house / 1000.0, 3));
SetValue(enelixDemoRequiredId($technical, 'DiagrammVerbraucherkW'), round($emsPower / 1000.0, 3));
$history = enelixDemoRequiredId($root, 'Historie');
$energyIds = [
'pv' => enelixDemoRequiredId($history, 'PVEnergie'),
'house' => enelixDemoRequiredId($history, 'HausEnergie'),
'import' => enelixDemoRequiredId($history, 'NetzbezugEnergie'),
'export' => enelixDemoRequiredId($history, 'EinspeisungEnergie'),
];
if ($source === 'Timer') {
$hours = $minutes / 60.0;
enelixDemoAddEnergy($energyIds['pv'], $pv, $hours);
enelixDemoAddEnergy($energyIds['house'], $house, $hours);
enelixDemoAddEnergy($energyIds['import'], max(0.0, $grid), $hours);
enelixDemoAddEnergy($energyIds['export'], max(0.0, -$grid), $hours);
}
}
function enelixDemoLimitPvPower(float $availablePower, float $limitPower): float
{
return min(max(0.0, $availablePower), max(0.0, $limitPower));
}
function enelixDemoRenderFlow(array $state): string
{
$format = static fn (float $value): string => number_format($value, 0, ',', "'");
$hour = (float) $state['hour'];
$time = sprintf('%02d:%02d', (int) floor($hour), (int) round(($hour - floor($hour)) * 60) % 60);
$grid = (float) $state['grid'];
$gridLabel = $grid < -25 ? 'Einspeisung' : ($grid > 25 ? 'Netzbezug' : 'Ausgeglichen');
$gridArrow = $grid < -25 ? '&rarr;' : ($grid > 25 ? '&larr;' : '&harr;');
$gridClass = abs($grid) <= 250 ? 'good' : ($grid < 0 ? 'export' : 'import');
$buffer = (float) ($state['buffer'] ?? 0.0);
$battery = (float) ($state['battery'] ?? 0.0);
$bufferTemp = (float) ($state['bufferTemp'] ?? 36.0);
$batterySoc = (float) ($state['batterySoc'] ?? 50.0);
$b1Class = (float) $state['boiler1'] > 0 ? 'on' : '';
$b2Class = (float) $state['boiler2'] > 0 ? 'on' : '';
$dryClass = (float) $state['dryer'] > 0 ? 'on' : '';
$bufferClass = $buffer > 0 ? 'on' : '';
$batteryClass = abs($battery) > 25 ? 'on' : '';
return '<style>
.enelix-flow{box-sizing:border-box;font-family:Inter,Segoe UI,Arial,sans-serif;background:#10151c;color:#f4f7fa;padding:18px;min-height:420px}
.enelix-flow *{box-sizing:border-box}.enelix-head{display:flex;justify-content:space-between;align-items:end;margin-bottom:18px}
.enelix-title{font-size:24px;font-weight:750;letter-spacing:0}.enelix-sub{color:#9dacbd;font-size:13px}
.enelix-line{display:grid;grid-template-columns:minmax(120px,1fr) 48px minmax(140px,1.2fr) 48px minmax(120px,1fr);align-items:center;gap:8px}
.enelix-node{border:1px solid #34404d;background:#17202a;padding:16px;text-align:center;border-radius:8px;min-height:92px}
.enelix-node small{display:block;color:#9dacbd;margin-bottom:8px}.enelix-node b{font-size:22px}
.enelix-node.solar{border-color:#b99628}.enelix-node.bus{border-color:#2786ac}.enelix-node.good{border-color:#2bb673}
.enelix-node.export{border-color:#38a3db}.enelix-node.import{border-color:#e95d5d}.enelix-arrow{text-align:center;font-size:30px;color:#9dacbd}
.enelix-loads{display:grid;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));gap:10px;margin-top:18px}
.enelix-load{border-left:4px solid #566371;background:#17202a;padding:13px 14px;border-radius:6px}
.enelix-load.on{border-left-color:#2bb673}.enelix-load b{display:block;font-size:17px;margin:4px 0}.enelix-load span{color:#9dacbd;font-size:12px}
.enelix-foot{display:flex;justify-content:space-between;gap:12px;margin-top:16px;padding-top:12px;border-top:1px solid #2a3541;color:#9dacbd;font-size:12px}
@media(max-width:640px){.enelix-line{grid-template-columns:1fr}.enelix-arrow{transform:rotate(90deg)}.enelix-loads{grid-template-columns:1fr}.enelix-head{align-items:start;flex-direction:column;gap:4px}}
</style><div class="enelix-flow"><div class="enelix-head"><div><div class="enelix-title">Enelix 2 Energiefluss</div><div class="enelix-sub">Manager regelt fünf flexible Teilnehmer aus vier Modultypen</div></div><div class="enelix-sub">Simulation ' . $time . ' Uhr</div></div>
<div class="enelix-line"><div class="enelix-node solar"><small>PV-Erzeugung</small><b>' . $format((float) $state['pv']) . ' W</b></div><div class="enelix-arrow">&rarr;</div><div class="enelix-node bus"><small>Haus + EMS-Leistung</small><b>' . $format((float) $state['base'] + (float) $state['boiler1'] + (float) $state['boiler2'] + (float) $state['dryer'] + $buffer + $battery) . ' W</b></div><div class="enelix-arrow">' . $gridArrow . '</div><div class="enelix-node ' . $gridClass . '"><small>' . $gridLabel . '</small><b>' . $format(abs($grid)) . ' W</b></div></div>
<div class="enelix-loads"><div class="enelix-load ' . $b1Class . '"><span>Priorität 1</span><b>Boiler 1 · ' . $format((float) $state['boiler1']) . ' W</b><span>' . number_format((float) $state['temp1'], 1, ',', '') . ' °C</span></div><div class="enelix-load ' . $b2Class . '"><span>Priorität 2</span><b>Boiler 2 · ' . $format((float) $state['boiler2']) . ' W</b><span>' . number_format((float) $state['temp2'], 1, ',', '') . ' °C</span></div><div class="enelix-load ' . $dryClass . '"><span>Priorität 3</span><b>Entfeuchter · ' . $format((float) $state['dryer']) . ' W</b><span>einstufiger Verbraucher</span></div><div class="enelix-load ' . $bufferClass . '"><span>Priorität 4</span><b>Pufferspeicher · ' . $format($buffer) . ' W</b><span>' . number_format($bufferTemp, 1, ',', '') . ' °C · Heizkurve</span></div><div class="enelix-load ' . $batteryClass . '"><span>Priorität 5 / Peak 1</span><b>Batterie · ' . $format($battery) . ' W</b><span>' . number_format($batterySoc, 1, ',', '') . ' % · bidirektional</span></div></div>
<div class="enelix-foot"><span>Grundlast ' . $format((float) $state['base']) . ' W</span><span>Manager: ' . htmlspecialchars((string) $state['manager'], ENT_QUOTES, 'UTF-8') . '</span></div></div>';
}
function enelixDemoControlledPower(array $contacts): int
{
$boiler1 = (bool) GetValue($contacts['boiler12']) ? 2400 : ((bool) GetValue($contacts['boiler11']) ? 1200 : 0);
$boiler2 = (bool) GetValue($contacts['boiler22']) ? 3000 : ((bool) GetValue($contacts['boiler21']) ? 1500 : 0);
$buffer = (bool) GetValue($contacts['buffer2']) ? 3600 : ((bool) GetValue($contacts['buffer1']) ? 1800 : 0);
$battery = (int) round((float) GetValue($contacts['battery']));
return $boiler1 + $boiler2 + ((bool) GetValue($contacts['dryer']) ? 900 : 0) + $buffer + $battery;
}
/** @return array{base: float, cloud: float, outside: float} */
function enelixDemoEnvironment(float $hour, float $base, int $cloud, bool $dynamic): array
{
$hour = fmod(max(0.0, $hour), 24.0);
$cloud = min(100, max(0, $cloud));
if (!$dynamic) {
return ['base' => $base, 'cloud' => (float) $cloud, 'outside' => 6.0];
}
$morning = exp(-pow(($hour - 7.5) / 1.5, 2));
$evening = exp(-pow(($hour - 19.0) / 2.2, 2));
$night = ($hour < 5.0 || $hour >= 23.0) ? 0.65 : 0.82;
$baseFactor = $night + (0.48 * $morning) + (0.72 * $evening);
$cloudVariation = (18.0 * sin(($hour + 0.4) * 1.7)) + (8.0 * sin($hour * 4.6));
$outside = 7.0 + (6.0 * sin(2.0 * M_PI * ($hour - 9.0) / 24.0));
return [
'base' => max(150.0, $base * $baseFactor),
'cloud' => min(100.0, max(0.0, $cloud + $cloudVariation)),
'outside' => $outside,
];
}
function enelixDemoBatteryRegisterPower(int $management, int $mode, float $power): float
{
if ($management !== 2) {
return 0.0;
}
if ($mode === 11) {
return max(0.0, $power);
}
if ($mode === 12) {
return -max(0.0, $power);
}
return 0.0;
}
function enelixDemoUpdateBufferTemperature(int $variableId, int $power, float $outside, int $minutes): void
{
$current = (float) GetValue($variableId);
$heating = ($power / 1000.0) * 0.018 * $minutes;
$loss = max(0.0, $current - $outside) * 0.0007 * $minutes;
SetValue($variableId, round(min(65.0, max(22.0, $current + $heating - $loss)), 2));
}
function enelixDemoUpdateBatterySoc(int $variableId, float $power, int $minutes): void
{
$current = (float) GetValue($variableId);
$energy = ($power / 1000.0) * ($minutes / 60.0);
$storedEnergy = $energy >= 0.0 ? $energy * 0.94 : $energy / 0.94;
$delta = ($storedEnergy / 10.0) * 100.0;
SetValue($variableId, round(min(100.0, max(5.0, $current + $delta)), 2));
}
function enelixDemoUpdateTemperature(int $variableId, int $power, int $minutes): void
{
$current = (float) GetValue($variableId);
$delta = (($power / 1000.0) * 0.03 * $minutes) - (0.01 * $minutes);
SetValue($variableId, round(min(70.0, max(38.0, $current + $delta)), 2));
}
function enelixDemoValidateRuntime(): void
{
$version = IPS_GetKernelVersion();
if (version_compare($version, '8.0', '<') || version_compare($version, '9.0', '>=')) {
throw new RuntimeException('Die Demo benötigt IP-Symcon 8.x; aktiv ist ' . $version . '.');
}
$available = IPS_GetModuleList();
foreach ([
ENELIX_DEMO_MANAGER_MODULE,
ENELIX_DEMO_BOILER_MODULE,
ENELIX_DEMO_SINGLE_MODULE,
ENELIX_DEMO_BUFFER_MODULE,
ENELIX_DEMO_BATTERY_MODULE,
ENELIX_DEMO_DUMMY_MODULE,
ENELIX_DEMO_ENERGY_DISTRIBUTION_MODULE,
] as $moduleId) {
if (!in_array($moduleId, $available, true)) {
throw new RuntimeException('Benötigtes Enelix-Modul fehlt: ' . $moduleId);
}
}
}
function enelixDemoEnsureProfiles(): void
{
enelixDemoEnsureProfile('ENELIX.Demo.Power', 2, -20000, 20000, 100, 0, ' W');
enelixDemoEnsureProfile('ENELIX.Demo.PowerPositive', 2, 0, 20000, 100, 0, ' W');
enelixDemoEnsureProfile('ENELIX.Demo.Hour', 2, 0, 23.75, 0.25, 2, ' h');
enelixDemoEnsureProfile('ENELIX.Demo.Percent', 1, 0, 100, 5, 0, ' %');
enelixDemoEnsureProfile('ENELIX.Demo.PercentFloat', 2, 0, 100, 0.1, 1, ' %');
enelixDemoEnsureProfile('ENELIX.Demo.Speed', 1, 1, 60, 1, 0, ' min');
enelixDemoEnsureProfile('ENELIX.Demo.Energy', 2, 0, 1000000, 0.01, 2, ' kWh');
}
function enelixDemoEnsureProfile(string $name, int $type, float $min, float $max, float $step, int $digits, string $suffix): void
{
if (!IPS_VariableProfileExists($name)) {
IPS_CreateVariableProfile($name, $type);
}
$profile = IPS_GetVariableProfile($name);
if ((int) $profile['ProfileType'] !== $type) {
throw new RuntimeException('Variablenprofil hat einen unpassenden Typ: ' . $name);
}
IPS_SetVariableProfileValues($name, $min, $max, $step);
IPS_SetVariableProfileDigits($name, $digits);
IPS_SetVariableProfileText($name, '', $suffix);
}
function enelixDemoEnsureCategory(int $parent, string $ident, string $name, int $position, bool $hidden = false): int
{
$id = enelixDemoFind($parent, $ident);
if ($id === 0) {
$id = IPS_CreateCategory();
IPS_SetParent($id, $parent);
IPS_SetIdent($id, $ident);
}
enelixDemoRequireType($id, 0);
IPS_SetName($id, $name);
IPS_SetPosition($id, $position);
IPS_SetHidden($id, $hidden);
return $id;
}
function enelixDemoEnsureVariable(int $parent, string $ident, string $name, int $type, string $profile, $default, int $position): int
{
$id = enelixDemoFind($parent, $ident);
$created = false;
if ($id === 0) {
$id = IPS_CreateVariable($type);
IPS_SetParent($id, $parent);
IPS_SetIdent($id, $ident);
$created = true;
}
enelixDemoRequireType($id, 2);
$variable = IPS_GetVariable($id);
if ((int) $variable['VariableType'] !== $type) {
throw new RuntimeException('Variablentyp-Konflikt bei ' . $ident);
}
IPS_SetName($id, $name);
IPS_SetPosition($id, $position);
if ($profile !== '') {
IPS_SetVariableCustomProfile($id, $profile);
}
if ($created) {
SetValue($id, $default);
}
return $id;
}
function enelixDemoEnsureChart(
int $parent,
string $ident,
string $name,
array $datasets,
int $position
): int {
$id = enelixDemoFind($parent, $ident);
if ($id === 0) {
$id = IPS_CreateMedia(4);
IPS_SetParent($id, $parent);
IPS_SetIdent($id, $ident);
}
enelixDemoRequireType($id, 5);
if ((int) IPS_GetMedia($id)['MediaType'] !== 4) {
throw new RuntimeException('Medientyp-Konflikt bei ' . $ident);
}
IPS_SetName($id, $name);
IPS_SetPosition($id, $position);
IPS_SetHidden($id, false);
IPS_SetMediaFile($id, 'media/' . $id . '.chart', false);
IPS_SetMediaContent(
$id,
base64_encode(json_encode(['datasets' => $datasets], JSON_THROW_ON_ERROR))
);
return $id;
}
function enelixDemoChartDataset(
int $variableId,
string $title,
string $strokeColor,
string $profile
): array {
return [
'variableID' => $variableId,
'fillColor' => 'clear',
'strokeColor' => $strokeColor,
'timeOffset' => 0,
'visible' => true,
'title' => $title,
'type' => 'line',
'side' => 'left',
'profile' => $profile,
];
}
function enelixDemoEnsureScript(int $parent, string $ident, string $name, string $content, int $position): int
{
$id = enelixDemoFind($parent, $ident);
if ($id === 0) {
$id = IPS_CreateScript(0);
IPS_SetParent($id, $parent);
IPS_SetIdent($id, $ident);
}
enelixDemoRequireType($id, 3);
IPS_SetName($id, $name);
IPS_SetPosition($id, $position);
IPS_SetScriptContent($id, $content);
return $id;
}
function enelixDemoEnsureInstance(int $parent, string $ident, string $name, string $moduleId, int $position): int
{
$id = enelixDemoFind($parent, $ident);
if ($id === 0) {
$id = IPS_CreateInstance($moduleId);
IPS_SetParent($id, $parent);
IPS_SetIdent($id, $ident);
}
enelixDemoRequireType($id, 1);
$actualModule = IPS_GetInstance($id)['ModuleInfo']['ModuleID'];
if ($actualModule !== $moduleId) {
throw new RuntimeException('Modulkonflikt bei Instanz ' . $ident);
}
IPS_SetName($id, $name);
IPS_SetPosition($id, $position);
return $id;
}
function enelixDemoEnsureLink(int $parent, string $ident, string $name, int $target, int $position): int
{
$id = enelixDemoFind($parent, $ident);
if ($id === 0) {
$id = IPS_CreateLink();
IPS_SetParent($id, $parent);
IPS_SetIdent($id, $ident);
}
enelixDemoRequireType($id, 6);
IPS_SetName($id, $name);
IPS_SetPosition($id, $position);
IPS_SetLinkTargetID($id, $target);
return $id;
}
function enelixDemoEnsureCyclicEvent(int $scriptId, string $ident, int $seconds): int
{
$id = enelixDemoFind($scriptId, $ident);
if ($id === 0) {
$id = IPS_CreateEvent(1);
IPS_SetParent($id, $scriptId);
IPS_SetIdent($id, $ident);
}
enelixDemoRequireType($id, 4);
IPS_SetName($id, 'Alle ' . $seconds . ' Sekunden');
IPS_SetEventCyclic($id, 0, 0, 0, 0, 1, $seconds);
IPS_SetEventAction(
$id,
'{346AA8C1-30E0-1663-78EF-93EFADFAC650}',
['SCRIPT' => 'IPS_RunScript(' . $scriptId . ');']
);
IPS_SetEventActive($id, true);
return $id;
}
function enelixDemoConfigure(
int $instanceId,
array $properties,
array $allowedStatuses = [102]
): void
{
foreach ($properties as $name => $value) {
IPS_SetProperty($instanceId, (string) $name, $value);
}
IPS_ApplyChanges($instanceId);
$deadline = microtime(true) + 2.0;
do {
$status = (int) IPS_GetInstance($instanceId)['InstanceStatus'];
if ($status === 102) {
return;
}
IPS_Sleep(50);
} while (microtime(true) < $deadline);
if (in_array($status, $allowedStatuses, true)) {
return;
}
throw new RuntimeException(
'Instanz ist nach der Konfiguration nicht aktiv: '
. IPS_GetName($instanceId)
. ' (Status ' . $status . ', erwartet: ' . implode(', ', $allowedStatuses) . ')'
);
}
function enelixDemoConfigureArchive(array $variableIds): void
{
$archive = enelixDemoFirstInstance(ENELIX_DEMO_ARCHIVE_MODULE);
if ($archive === 0) {
return;
}
foreach ($variableIds as $variableId) {
AC_SetLoggingStatus($archive, $variableId, true);
AC_SetAggregationType($archive, $variableId, 0);
AC_SetGraphStatus($archive, $variableId, true);
}
IPS_ApplyChanges($archive);
}
function enelixDemoConfigureVisualization(int $root, int $flow): void
{
$visualization = enelixDemoFirstInstance(ENELIX_DEMO_TILE_MODULE);
if ($visualization === 0) {
return;
}
IPS_SetProperty($visualization, 'BaseID', $root);
IPS_SetProperty($visualization, 'CategoryDisplay', 'Cards');
IPS_SetProperty($visualization, 'ShowGreeting', 3);
IPS_SetProperty($visualization, 'GreetingName', 'Enelix 2 Demo');
IPS_SetProperty($visualization, 'ShowDateTime', true);
IPS_SetProperty($visualization, 'Favorites', json_encode([['ObjectID' => $flow]], JSON_THROW_ON_ERROR));
IPS_ApplyChanges($visualization);
VISU_Reload($visualization);
}
function enelixDemoFirstInstance(string $moduleId): int
{
$ids = IPS_GetInstanceListByModuleID($moduleId);
return $ids === [] ? 0 : (int) $ids[0];
}
function enelixDemoFind(int $parent, string $ident): int
{
$id = @IPS_GetObjectIDByIdent($ident, $parent);
return $id === false ? 0 : (int) $id;
}
function enelixDemoRequiredId(int $parent, string $ident): int
{
$id = enelixDemoFind($parent, $ident);
if ($id === 0) {
throw new RuntimeException('Demo-Objekt fehlt: ' . $ident);
}
return $id;
}
function enelixDemoRequireType(int $objectId, int $type): void
{
$actual = (int) IPS_GetObject($objectId)['ObjectType'];
if ($actual !== $type) {
throw new RuntimeException('Objekttyp-Konflikt bei ID ' . $objectId);
}
}
function enelixDemoRemove(bool $confirmed = false): void
{
if (!$confirmed) {
throw new RuntimeException('Rollback nur mit enelixDemoRemove(true) ausführen.');
}
$root = enelixDemoFind(0, ENELIX_DEMO_ROOT_IDENT);
if ($root === 0) {
return;
}
$visualization = enelixDemoFirstInstance(ENELIX_DEMO_TILE_MODULE);
if ($visualization > 0 && (int) json_decode(IPS_GetConfiguration($visualization), true)['BaseID'] === $root) {
IPS_SetProperty($visualization, 'BaseID', 0);
IPS_ApplyChanges($visualization);
}
enelixDemoDeleteTree($root);
foreach (['ENELIX.Demo.Power', 'ENELIX.Demo.PowerPositive', 'ENELIX.Demo.Hour', 'ENELIX.Demo.Percent', 'ENELIX.Demo.PercentFloat', 'ENELIX.Demo.Speed', 'ENELIX.Demo.Energy'] as $profile) {
if (IPS_VariableProfileExists($profile)) {
IPS_DeleteVariableProfile($profile);
}
}
}
function enelixDemoDeleteTree(int $objectId): void
{
foreach (array_reverse(IPS_GetChildrenIDs($objectId)) as $childId) {
enelixDemoDeleteTree((int) $childId);
}
$type = (int) IPS_GetObject($objectId)['ObjectType'];
if ($type === 1 && (int) IPS_GetInstance($objectId)['ConnectionID'] > 0) {
IPS_DisconnectInstance($objectId);
}
switch ($type) {
case 0: IPS_DeleteCategory($objectId); break;
case 1: IPS_DeleteInstance($objectId); break;
case 2: IPS_DeleteVariable($objectId); break;
case 3: IPS_DeleteScript($objectId, true); break;
case 4: IPS_DeleteEvent($objectId); break;
case 5: IPS_DeleteMedia($objectId, true); break;
case 6: IPS_DeleteLink($objectId); break;
default: throw new RuntimeException('Unbekannter Objekttyp: ' . $type);
}
}
function enelixDemoEnergyNode(
int $variableId,
string $name,
string $type,
int $infoVariableId = 0
): array {
return [
'VariableConfigType' => 0,
'VariableID' => $variableId,
'SecondaryVariableID' => 1,
'Name' => $name,
'Type' => $type,
'Grouped' => false,
'CalculationType' => 0,
'InfoVariableID' => $infoVariableId > 0 ? $infoVariableId : 1,
'Invert' => false,
];
}
function enelixDemoConfigureEnergyDistribution(int $instanceId, array $nodes): void
{
enelixDemoConfigure($instanceId, [
'Calculation' => true,
'Config' => json_encode($nodes, JSON_THROW_ON_ERROR),
'EnableLimits' => false,
'Restrictions' => '[]',
]);
}
function enelixDemoLinkInstanceVariables(
int $categoryId,
int $instanceId,
int $sensorId,
string $sensorName = 'Gemessene Temperatur',
string $sensorIdent = 'SensorTemperatur'
): void {
enelixDemoEnsureLink($categoryId, $sensorIdent, $sensorName, $sensorId, 5);
$position = 10;
foreach (IPS_GetChildrenIDs($instanceId) as $childId) {
if ((int) IPS_GetObject($childId)['ObjectType'] !== 2) {
continue;
}
$object = IPS_GetObject($childId);
$ident = $object['ObjectIdent'] !== '' ? $object['ObjectIdent'] : (string) $childId;
enelixDemoEnsureLink(
$categoryId,
'Wert_' . preg_replace('/[^A-Za-z0-9_]/', '_', $ident),
(string) $object['ObjectName'],
(int) $childId,
$position
);
$position += 10;
}
}
function enelixDemoAddEnergy(int $variableId, float $powerW, float $hours): void
{
$energy = (float) GetValue($variableId) + (($powerW * $hours) / 1000.0);
SetValue($variableId, round($energy, 4));
}
function enelixDemoUpdateCharts(int $historyId, array $powerIds, array $energyIds): void
{
$archive = enelixDemoFirstInstance(ENELIX_DEMO_ARCHIVE_MODULE);
if ($archive === 0) {
return;
}
$end = time();
$start = $end - 1200;
$powerSeries = [
['name' => 'PV', 'color' => '#f4c542', 'values' => enelixDemoLoggedSeries($archive, $powerIds[0], $start, $end)],
['name' => 'Hausverbrauch', 'color' => '#2bb673', 'values' => enelixDemoLoggedSeries($archive, $powerIds[1], $start, $end)],
['name' => 'Netzleistung', 'color' => '#e95d5d', 'values' => enelixDemoLoggedSeries($archive, $powerIds[2], $start, $end)],
];
$energySeries = [
['name' => 'PV', 'color' => '#f4c542', 'values' => enelixDemoLoggedSeries($archive, $energyIds['pv'], $start, $end)],
['name' => 'Haus', 'color' => '#2bb673', 'values' => enelixDemoLoggedSeries($archive, $energyIds['house'], $start, $end)],
['name' => 'Netzbezug', 'color' => '#e95d5d', 'values' => enelixDemoLoggedSeries($archive, $energyIds['import'], $start, $end)],
['name' => 'Einspeisung', 'color' => '#38a3db', 'values' => enelixDemoLoggedSeries($archive, $energyIds['export'], $start, $end)],
];
SetValue(
enelixDemoRequiredId($historyId, 'Leistungsdiagramm'),
enelixDemoRenderChart('Leistungsverlauf · letzte 20 Minuten', $powerSeries, 'W', $start, $end)
);
SetValue(
enelixDemoRequiredId($historyId, 'Energiediagramm'),
enelixDemoRenderChart('Kumulierte Energie · Simulationszeit', $energySeries, 'kWh', $start, $end)
);
}
function enelixDemoLoggedSeries(int $archiveId, int $variableId, int $start, int $end): array
{
$values = array_reverse(AC_GetLoggedValues($archiveId, $variableId, $start, $end, 0));
if ($values === []) {
return [['time' => $end, 'value' => (float) GetValue($variableId)]];
}
$step = max(1, (int) ceil(count($values) / 90));
$result = [];
foreach ($values as $index => $value) {
if ($index % $step !== 0 && $index !== count($values) - 1) {
continue;
}
$result[] = [
'time' => (int) $value['TimeStamp'],
'value' => (float) $value['Value'],
];
}
return $result;
}
function enelixDemoRenderChart(
string $title,
array $series,
string $unit,
int $start,
int $end
): string {
$allValues = [0.0];
foreach ($series as $line) {
foreach ($line['values'] as $point) {
$allValues[] = (float) $point['value'];
}
}
$min = min($allValues);
$max = max($allValues);
if (abs($max - $min) < 0.0001) {
$max = $min + 1.0;
}
$left = 72.0;
$top = 34.0;
$width = 650.0;
$height = 210.0;
$timeRange = max(1, $end - $start);
$valueRange = $max - $min;
$grid = '';
for ($index = 0; $index <= 4; $index++) {
$y = $top + (($height / 4) * $index);
$value = $max - (($valueRange / 4) * $index);
$grid .= '<line x1="' . $left . '" y1="' . $y . '" x2="' . ($left + $width) . '" y2="' . $y . '" />';
$grid .= '<text x="' . ($left - 10) . '" y="' . ($y + 4) . '" text-anchor="end">'
. number_format($value, $unit === 'W' ? 0 : 2, ',', "'") . '</text>';
}
$lines = '';
$legend = '';
foreach ($series as $line) {
$points = [];
foreach ($line['values'] as $point) {
$x = $left + ((((int) $point['time'] - $start) / $timeRange) * $width);
$y = $top + (($max - (float) $point['value']) / $valueRange * $height);
$points[] = round($x, 1) . ',' . round($y, 1);
}
$color = htmlspecialchars((string) $line['color'], ENT_QUOTES, 'UTF-8');
$name = htmlspecialchars((string) $line['name'], ENT_QUOTES, 'UTF-8');
$lines .= '<polyline points="' . implode(' ', $points) . '" stroke="' . $color . '" />';
$legend .= '<span><i style="background:' . $color . '"></i>' . $name . '</span>';
}
return '<style>
.enelix-chart{font-family:Inter,Segoe UI,Arial,sans-serif;background:#10151c;color:#f4f7fa;padding:14px;border-radius:6px}
.enelix-chart h3{font-size:17px;margin:0 0 8px}.enelix-chart svg{display:block;width:100%;height:auto;min-height:230px}
.enelix-chart .grid line{stroke:#33404d;stroke-width:1}.enelix-chart text{fill:#9dacbd;font-size:11px}
.enelix-chart polyline{fill:none;stroke-width:3;stroke-linejoin:round;stroke-linecap:round}
.enelix-legend{display:flex;flex-wrap:wrap;gap:14px;margin-top:4px;color:#c5ced8;font-size:12px}
.enelix-legend span{display:flex;align-items:center;gap:6px}.enelix-legend i{display:inline-block;width:18px;height:3px}
</style><div class="enelix-chart"><h3>' . htmlspecialchars($title, ENT_QUOTES, 'UTF-8') . '</h3>
<svg viewBox="0 0 760 280" role="img" aria-label="' . htmlspecialchars($title, ENT_QUOTES, 'UTF-8') . '">
<g class="grid">' . $grid . '</g>' . $lines . '
<text x="' . $left . '" y="266">' . date('H:i', $start) . '</text>
<text x="' . ($left + $width) . '" y="266" text-anchor="end">' . date('H:i', $end) . '</text>
<text x="18" y="20">' . htmlspecialchars($unit, ENT_QUOTES, 'UTF-8') . '</text>
</svg><div class="enelix-legend">' . $legend . '</div></div>';
}