1217 lines
56 KiB
PHP
1217 lines
56 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);
|
|
$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] as $registerId) {
|
|
IPS_SetVariableCustomAction($registerId, $registerScript);
|
|
}
|
|
foreach ([$temp1, $temp2, $bufferTemp, $outsideTemp, $batteryMaxCharge, $batteryMaxDischarge, $batterySoc, $batteryActual] 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,
|
|
]];
|
|
$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,
|
|
'MesswertMaxAlter' => 10,
|
|
'AutomatischeSuche' => false,
|
|
'SuchbereichID' => $root,
|
|
'VerbraucherZuordnung' => '[]',
|
|
'KeepAlive' => 5,
|
|
'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,
|
|
];
|
|
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);
|
|
|
|
$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;
|
|
$pv = $peak * $sun * (1.0 - ($environment['cloud'] * 0.0085));
|
|
|
|
$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);
|
|
}
|
|
|
|
$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 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 ? '→' : ($grid > 25 ? '←' : '↔');
|
|
$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">→</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>';
|
|
}
|