This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
# Enelix-2-Demoanlage
|
||||
|
||||
Die Demo erzeugt idempotent eine vollständige, spielbare EMS-Anlage in IP-Symcon 8.x:
|
||||
|
||||
- Enelix Manager mit manueller Zuordnung,
|
||||
- zwei mehrstufige Boiler (1,2/2,4 kW und 1,5/3,0 kW),
|
||||
- einen einstufigen Entfeuchter (0,9 kW),
|
||||
- simulierte PV-Erzeugung, Grundlast, Netzleistung und Boilertemperaturen,
|
||||
- Kachelvisualisierung mit Energiefluss und bedienbaren Simulationswerten,
|
||||
- Archivierung der wichtigsten Leistungs- und Temperaturwerte.
|
||||
|
||||
## Installation
|
||||
|
||||
Das Repository muss in der IP-Symcon-Modulverwaltung auf dem gewünschten Stand installiert sein.
|
||||
Danach in der Schnellausführung starten:
|
||||
|
||||
```php
|
||||
require_once IPS_GetKernelDir() . 'modules/Enelix-EMS/examples/Demoanlage/bootstrap.php';
|
||||
|
||||
$result = enelixDemoInstall();
|
||||
print_r($result);
|
||||
```
|
||||
|
||||
Ein weiterer Lauf aktualisiert dieselbe Anlage anhand stabiler Idents und erzeugt keine Duplikate.
|
||||
Bestehende, nicht zur Demo gehörende Objekte werden nicht verändert oder gelöscht.
|
||||
|
||||
## Bedienung
|
||||
|
||||
In der Kachelvisualisierung öffnet die Startkategorie `Enelix 2 Demoanlage`.
|
||||
Unter `Simulation` lassen sich Tageslauf, Tageszeit, Geschwindigkeit,
|
||||
PV-Spitzenleistung, Bewölkung und Haus-Grundlast verändern. Der Manager verteilt
|
||||
den verfügbaren PV-Überschuss nach Priorität auf beide Boiler und den Entfeuchter.
|
||||
|
||||
## Validierung
|
||||
|
||||
- Alle vier Enelix-Instanzen müssen Status 102 besitzen.
|
||||
- Der Manager muss drei Verbraucher melden.
|
||||
- Bei ausreichendem PV-Überschuss müssen Verbraucher schalten.
|
||||
- Netzleistung, PV-Leistung, Verbraucherleistung und Temperaturen werden archiviert.
|
||||
- Ein erneuter Installationslauf muss dieselbe Root-ID zurückgeben.
|
||||
|
||||
## Rollback
|
||||
|
||||
Nur die eindeutig gekennzeichnete Demo wird entfernt:
|
||||
|
||||
```php
|
||||
require_once IPS_GetKernelDir() . 'modules/Enelix-EMS/examples/Demoanlage/bootstrap.php';
|
||||
|
||||
enelixDemoRemove(true);
|
||||
```
|
||||
|
||||
Der boolesche Parameter ist eine bewusste Löschbestätigung. Fremde Objektbäume
|
||||
und Systeminstanzen bleiben unberührt.
|
||||
@@ -0,0 +1,555 @@
|
||||
<?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_TILE_MODULE = '{B5B875BB-9B76-45FD-4E67-2607E45B3AC4}';
|
||||
const ENELIX_DEMO_ARCHIVE_MODULE = '{43192F0B-135B-4CE7-A0A7-1475603F3060}';
|
||||
|
||||
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', 'Energiefluss', 10);
|
||||
$simulation = enelixDemoEnsureCategory($root, 'Simulation', 'Simulation', 20);
|
||||
$ems = enelixDemoEnsureCategory($root, 'EMS', 'Manager und Verbraucher', 30);
|
||||
$technical = enelixDemoEnsureCategory($root, 'Technik', 'Simulationstechnik', 90, true);
|
||||
|
||||
enelixDemoEnsureProfiles();
|
||||
|
||||
$flow = enelixDemoEnsureVariable($dashboard, 'Energiefluss', 'Energiefluss', 3, '~HTMLBox', '', 10);
|
||||
$pvDisplay = enelixDemoEnsureVariable($dashboard, 'PVLeistung', 'PV-Leistung', 2, 'ENELIX.Demo.PowerPositive', 0.0, 20);
|
||||
$loadDisplay = enelixDemoEnsureVariable($dashboard, 'Verbraucherleistung', 'Geregelte Verbraucher', 2, 'ENELIX.Demo.PowerPositive', 0.0, 30);
|
||||
$grid = enelixDemoEnsureVariable($dashboard, 'Netzleistung', 'Netzleistung', 2, 'ENELIX.Demo.Power', 0.0, 40);
|
||||
|
||||
$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);
|
||||
$peak = enelixDemoEnsureVariable($simulation, 'PVSpitze', 'PV-Spitzenleistung', 2, 'ENELIX.Demo.PowerPositive', 7000.0, 40);
|
||||
$cloud = enelixDemoEnsureVariable($simulation, 'Bewoelkung', 'Bewölkung', 1, 'ENELIX.Demo.Percent', 20, 50);
|
||||
$base = enelixDemoEnsureVariable($simulation, 'Grundlast', 'Haus-Grundlast', 2, 'ENELIX.Demo.PowerPositive', 900.0, 60);
|
||||
|
||||
$actorScript = enelixDemoEnsureScript(
|
||||
$technical,
|
||||
'AktorAktion',
|
||||
'Simulierte Aktoren',
|
||||
"<?php\nSetValue((int) \$_IPS['VARIABLE'], (bool) \$_IPS['VALUE']);\n",
|
||||
10
|
||||
);
|
||||
$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, $peak, $cloud, $base] 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);
|
||||
$boiler11 = enelixDemoEnsureVariable($technical, 'Boiler1Stufe1', 'Boiler 1 Stufe 1', 0, '~Switch', false, 120);
|
||||
$boiler12 = enelixDemoEnsureVariable($technical, 'Boiler1Stufe2', 'Boiler 1 Stufe 2', 0, '~Switch', false, 130);
|
||||
$boiler21 = enelixDemoEnsureVariable($technical, 'Boiler2Stufe1', 'Boiler 2 Stufe 1', 0, '~Switch', false, 140);
|
||||
$boiler22 = enelixDemoEnsureVariable($technical, 'Boiler2Stufe2', 'Boiler 2 Stufe 2', 0, '~Switch', false, 150);
|
||||
$dryContact = enelixDemoEnsureVariable($technical, 'EntfeuchterKontakt', 'Entfeuchter Kontakt', 0, '~Switch', false, 160);
|
||||
|
||||
foreach ([$boiler11, $boiler12, $boiler21, $boiler22, $dryContact] as $actorId) {
|
||||
IPS_SetVariableCustomAction($actorId, $actorScript);
|
||||
}
|
||||
|
||||
$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);
|
||||
|
||||
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' => 0,
|
||||
'Meldeintervall' => 5,
|
||||
'VorgabeTimeout' => 30,
|
||||
'EinstellungenInVisu' => true,
|
||||
'DiagnosevariablenAnzeigen' => true,
|
||||
]);
|
||||
|
||||
foreach ([$boiler1, $boiler2] as $boilerId) {
|
||||
IPS_RequestAction($boilerId, 'Mindesttemperatur', 42.0);
|
||||
IPS_RequestAction($boilerId, 'Maximaltemperatur', 62.0);
|
||||
IPS_RequestAction($boilerId, 'Aktiv', true);
|
||||
}
|
||||
IPS_RequestAction($dryer, 'Aktiv', true);
|
||||
|
||||
$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]
|
||||
);
|
||||
enelixDemoConfigure($manager, [
|
||||
'NetzleistungVariableID' => $grid,
|
||||
'Netzleistungsfaktor' => 1.0,
|
||||
'MesswertMaxAlter' => 10,
|
||||
'AutomatischeSuche' => false,
|
||||
'SuchbereichID' => $root,
|
||||
'VerbraucherZuordnung' => json_encode($assignment, JSON_THROW_ON_ERROR),
|
||||
'KeepAlive' => 5,
|
||||
'VerbraucherTimeout' => 15,
|
||||
'SollwertSolarladen' => 0.0,
|
||||
'Umschaltdifferenz' => 0.0,
|
||||
'DiagnosevariablenAnzeigen' => true,
|
||||
'LoggingEin' => false,
|
||||
]);
|
||||
|
||||
foreach ([$boiler1, $boiler2, $dryer] as $consumerId) {
|
||||
IPS_RequestAction($consumerId, 'Melden', true);
|
||||
}
|
||||
IPS_RequestAction($manager, 'Aktiv', true);
|
||||
|
||||
enelixDemoEnsureLink($dashboard, 'ManagerLink', 'Manager', $manager, 50);
|
||||
enelixDemoEnsureLink($dashboard, 'Boiler1Link', 'Boiler 1', $boiler1, 60);
|
||||
enelixDemoEnsureLink($dashboard, 'Boiler2Link', 'Boiler 2', $boiler2, 70);
|
||||
enelixDemoEnsureLink($dashboard, 'EntfeuchterLink', 'Entfeuchter', $dryer, 80);
|
||||
enelixDemoEnsureLink($simulation, 'Boiler1TemperaturLink', 'Boiler 1 Temperatur', $temp1, 70);
|
||||
enelixDemoEnsureLink($simulation, 'Boiler2TemperaturLink', 'Boiler 2 Temperatur', $temp2, 80);
|
||||
|
||||
enelixDemoConfigureArchive([$pvDisplay, $loadDisplay, $grid, $temp1, $temp2]);
|
||||
enelixDemoConfigureVisualization($root, $flow);
|
||||
enelixDemoTick('Install');
|
||||
|
||||
return [
|
||||
'rootId' => $root,
|
||||
'visualizationId' => enelixDemoFirstInstance(ENELIX_DEMO_TILE_MODULE),
|
||||
'managerId' => $manager,
|
||||
'consumerIds' => [$boiler1, $boiler2, $dryer],
|
||||
'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);
|
||||
}
|
||||
|
||||
$peak = (float) GetValue(enelixDemoRequiredId($simulation, 'PVSpitze'));
|
||||
$cloud = (int) GetValue(enelixDemoRequiredId($simulation, 'Bewoelkung'));
|
||||
$base = (float) GetValue(enelixDemoRequiredId($simulation, 'Grundlast'));
|
||||
$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 - (min(100, max(0, $cloud)) * 0.0075));
|
||||
|
||||
$contacts = [
|
||||
'boiler11' => enelixDemoRequiredId($technical, 'Boiler1Stufe1'),
|
||||
'boiler12' => enelixDemoRequiredId($technical, 'Boiler1Stufe2'),
|
||||
'boiler21' => enelixDemoRequiredId($technical, 'Boiler2Stufe1'),
|
||||
'boiler22' => enelixDemoRequiredId($technical, 'Boiler2Stufe2'),
|
||||
'dryer' => enelixDemoRequiredId($technical, 'EntfeuchterKontakt'),
|
||||
];
|
||||
$powerBefore = enelixDemoControlledPower($contacts);
|
||||
$temp1 = enelixDemoRequiredId($technical, 'Boiler1Temperatur');
|
||||
$temp2 = enelixDemoRequiredId($technical, 'Boiler2Temperatur');
|
||||
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);
|
||||
}
|
||||
|
||||
$gridId = enelixDemoRequiredId($dashboard, 'Netzleistung');
|
||||
SetValue($gridId, round($base + $powerBefore - $pv, 1));
|
||||
|
||||
$consumers = [
|
||||
enelixDemoRequiredId($ems, 'Boiler1'),
|
||||
enelixDemoRequiredId($ems, 'Boiler2'),
|
||||
enelixDemoRequiredId($ems, 'Entfeuchter'),
|
||||
];
|
||||
foreach ($consumers as $consumerId) {
|
||||
IPS_RequestAction($consumerId, 'Melden', true);
|
||||
}
|
||||
$manager = enelixDemoRequiredId($ems, 'Manager');
|
||||
IPS_RequestAction($manager, 'Regeln', true);
|
||||
|
||||
$power = enelixDemoControlledPower($contacts);
|
||||
$grid = $base + $power - $pv;
|
||||
SetValue($gridId, round($grid, 1));
|
||||
SetValue(enelixDemoRequiredId($dashboard, 'PVLeistung'), round($pv, 1));
|
||||
SetValue(enelixDemoRequiredId($dashboard, 'Verbraucherleistung'), (float) $power);
|
||||
|
||||
$state = [
|
||||
'hour' => $hour,
|
||||
'pv' => $pv,
|
||||
'base' => $base,
|
||||
'grid' => $grid,
|
||||
'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,
|
||||
'temp1' => (float) GetValue($temp1),
|
||||
'temp2' => (float) GetValue($temp2),
|
||||
'manager' => (string) GetValue(enelixDemoRequiredId($manager, 'Betriebsart')),
|
||||
];
|
||||
SetValue(enelixDemoRequiredId($dashboard, 'Energiefluss'), enelixDemoRenderFlow($state));
|
||||
}
|
||||
|
||||
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');
|
||||
$b1Class = (float) $state['boiler1'] > 0 ? 'on' : '';
|
||||
$b2Class = (float) $state['boiler2'] > 0 ? 'on' : '';
|
||||
$dryClass = (float) $state['dryer'] > 0 ? '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(3,minmax(0,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 drei flexible Verbraucher</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 + flexible Lasten</small><b>' . $format((float) $state['base'] + (float) $state['boiler1'] + (float) $state['boiler2'] + (float) $state['dryer']) . ' 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>
|
||||
<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);
|
||||
return $boiler1 + $boiler2 + ((bool) GetValue($contacts['dryer']) ? 900 : 0);
|
||||
}
|
||||
|
||||
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] 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, -12000, 12000, 100, 0, ' W');
|
||||
enelixDemoEnsureProfile('ENELIX.Demo.PowerPositive', 2, 0, 12000, 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.Speed', 1, 1, 60, 1, 0, ' min');
|
||||
}
|
||||
|
||||
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 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_SetEventActive($id, true);
|
||||
return $id;
|
||||
}
|
||||
|
||||
function enelixDemoConfigure(int $instanceId, array $properties): void
|
||||
{
|
||||
foreach ($properties as $name => $value) {
|
||||
IPS_SetProperty($instanceId, (string) $name, $value);
|
||||
}
|
||||
IPS_ApplyChanges($instanceId);
|
||||
if ((int) IPS_GetInstance($instanceId)['InstanceStatus'] !== 102) {
|
||||
throw new RuntimeException('Instanz ist nach der Konfiguration nicht aktiv: ' . IPS_GetName($instanceId));
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
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.Speed'] 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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user