$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, ]); $energyDistribution = enelixDemoEnsureInstance( $dashboard, 'Energieverteilung', 'Energieverteilung', ENELIX_DEMO_ENERGY_DISTRIBUTION_MODULE, 10 ); enelixDemoConfigureEnergyDistribution($energyDistribution, [ enelixDemoEnergyNode($pvDisplay, 'PV-Anlage', 'solar'), enelixDemoEnergyNode($grid, 'Stromnetz', 'grid'), enelixDemoEnergyNode($base, 'Haus-Grundlast', 'consumer'), enelixDemoEnergyNode($boiler1Power, 'Boiler 1', 'consumer', $temp1), enelixDemoEnergyNode($boiler2Power, 'Boiler 2', 'consumer', $temp2), enelixDemoEnergyNode($dryerPower, 'Entfeuchter', 'consumer'), ]); 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); enelixDemoLinkInstanceVariables($boiler1View, $boiler1, $temp1); enelixDemoLinkInstanceVariables($boiler2View, $boiler2, $temp2); IPS_SetHidden($boiler1, true); IPS_SetHidden($boiler2, true); enelixDemoEnsureLink($dashboard, 'ManagerLink', 'Manager', $manager, 100); enelixDemoEnsureLink($dashboard, 'Boiler1Link', 'Boiler 1', $boiler1View, 110); enelixDemoEnsureLink($dashboard, 'Boiler2Link', 'Boiler 2', $boiler2View, 120); enelixDemoEnsureLink($dashboard, 'EntfeuchterLink', 'Entfeuchter', $dryer, 130); enelixDemoEnsureLink($simulation, 'Boiler1TemperaturLink', 'Boiler 1 Temperatur', $temp1, 70); enelixDemoEnsureLink($simulation, 'Boiler2TemperaturLink', 'Boiler 2 Temperatur', $temp2, 80); enelixDemoConfigureArchive([ $pvDisplay, $houseDisplay, $loadDisplay, $grid, $boiler1Power, $boiler2Power, $dryerPower, $pvEnergy, $houseEnergy, $gridImportEnergy, $gridExportEnergy, $temp1, $temp2, ]); enelixDemoConfigureVisualization($root, $energyDistribution); 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); $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; $power = $boiler1 + $boiler2 + $dryer; $house = $base + $power; $grid = $house - $pv; $pvId = enelixDemoRequiredId($dashboard, 'PVLeistung'); $houseId = enelixDemoRequiredId($dashboard, 'Hausverbrauch'); $loadId = enelixDemoRequiredId($dashboard, 'Verbraucherleistung'); SetValue($gridId, round($grid, 1)); SetValue($pvId, round($pv, 1)); SetValue($houseId, round($house, 1)); SetValue($loadId, (float) $power); SetValue(enelixDemoRequiredId($dashboard, 'Boiler1Leistung'), (float) $boiler1); SetValue(enelixDemoRequiredId($dashboard, 'Boiler2Leistung'), (float) $boiler2); SetValue(enelixDemoRequiredId($dashboard, 'EntfeuchterLeistung'), (float) $dryer); $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); } $chartUpdated = enelixDemoRequiredId($technical, 'LetztesDiagrammUpdate'); if ($source !== 'Timer' || time() - (int) GetValue($chartUpdated) >= 10) { enelixDemoUpdateCharts($history, [$pvId, $houseId, $gridId], $energyIds); SetValue($chartUpdated, time()); } } 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 '
Enelix 2 Energiefluss
Manager regelt drei flexible Verbraucher
Simulation ' . $time . ' Uhr
PV-Erzeugung' . $format((float) $state['pv']) . ' W
→
Haus + flexible Lasten' . $format((float) $state['base'] + (float) $state['boiler1'] + (float) $state['boiler2'] + (float) $state['dryer']) . ' 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
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); 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, 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, -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'); 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 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); 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); } 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', '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' => 0, 'Name' => $name, 'Type' => $type, 'Grouped' => false, 'CalculationType' => 0, 'InfoVariableID' => $infoVariableId, 'Invert' => false, ]; } function enelixDemoConfigureEnergyDistribution(int $instanceId, array $nodes): void { enelixDemoConfigure($instanceId, [ 'Calculation' => false, 'Config' => json_encode($nodes, JSON_THROW_ON_ERROR), 'EnableLimits' => false, 'Restrictions' => '[]', ]); } function enelixDemoLinkInstanceVariables(int $categoryId, int $instanceId, int $sensorId): void { enelixDemoEnsureLink($categoryId, 'SensorTemperatur', 'Gemessene Temperatur', $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 . '
'; }