$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, 'PrognosePVVariableID' => $pvDisplay, 'PrognoseHausverbrauchVariableID' => $houseDisplay, 'PrognoseSOCVariableID' => $batterySoc, 'PrognoseSendeintervall' => 60, '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 '
Enelix 2 Energiefluss
Manager regelt fünf flexible Teilnehmer aus vier Modultypen
Simulation ' . $time . ' Uhr
PV-Erzeugung' . $format((float) $state['pv']) . ' W
→
Haus + EMS-Leistung' . $format((float) $state['base'] + (float) $state['boiler1'] + (float) $state['boiler2'] + (float) $state['dryer'] + $buffer + $battery) . ' W
' . $gridArrow . '
' . $gridLabel . '' . $format(abs($grid)) . ' W
Priorität 1Boiler 1 · ' . $format((float) $state['boiler1']) . ' W' . number_format((float) $state['temp1'], 1, ',', '') . ' °C
Priorität 2Boiler 2 · ' . $format((float) $state['boiler2']) . ' W' . number_format((float) $state['temp2'], 1, ',', '') . ' °C
Priorität 3Entfeuchter · ' . $format((float) $state['dryer']) . ' Weinstufiger Verbraucher
Priorität 4Pufferspeicher · ' . $format($buffer) . ' W' . number_format($bufferTemp, 1, ',', '') . ' °C · Heizkurve
Priorität 5 / Peak 1Batterie · ' . $format($battery) . ' W' . number_format($batterySoc, 1, ',', '') . ' % · bidirektional
Grundlast ' . $format((float) $state['base']) . ' WManager: ' . htmlspecialchars((string) $state['manager'], ENT_QUOTES, 'UTF-8') . '
'; } 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 .= ''; $grid .= '' . number_format($value, $unit === 'W' ? 0 : 2, ',', "'") . ''; } $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 .= ''; $legend .= '' . $name . ''; } return '

' . htmlspecialchars($title, ENT_QUOTES, 'UTF-8') . '

' . $grid . '' . $lines . ' ' . date('H:i', $start) . ' ' . date('H:i', $end) . ' ' . htmlspecialchars($unit, ENT_QUOTES, 'UTF-8') . '
' . $legend . '
'; }