feat(manager): Energieaufzeichnung und Visualisierungen integrieren
Tests / test (push) Successful in 47s

This commit is contained in:
dh
2026-09-27 19:29:37 +00:00
parent 40f41a2fb8
commit 708a8ecea2
11 changed files with 1426 additions and 56 deletions
+542
View File
@@ -0,0 +1,542 @@
<?php
declare(strict_types=1);
namespace Belevo\EnelixEMS;
trait ManagerEnergieTrait
{
private function konfiguriereEnergiearchiv(): void
{
$archivID = $this->archivID();
if ($archivID === 0) {
$this->protokolliere('Energiearchiv', 'Kein Archiv Control vorhanden.');
return;
}
$aktiv = $this->ReadPropertyBoolean('EnergieaufzeichnungAktiv');
$geaendert = [];
$leistungstyp = $this->verdichtungstyp(
$this->ReadPropertyInteger('LeistungsaufzeichnungMinuten')
);
$energietyp = $this->verdichtungstyp(
$this->ReadPropertyInteger('EnergieaufzeichnungMinuten')
);
foreach (self::LEISTUNGS_VARIABLEN as $ident) {
$variableID = $this->GetIDForIdent($ident);
AC_SetLoggingStatus($archivID, $variableID, $aktiv);
AC_SetAggregationType($archivID, $variableID, 0);
AC_SetGraphStatus($archivID, $variableID, $aktiv);
$regeln = [-1 => $leistungstyp];
$loeschen = $this->ReadPropertyInteger('LeistungLoeschenMonate');
if ($loeschen > 0) {
$regeln[$loeschen] = 7;
}
if ($aktiv && $this->setzeVerdichtung($archivID, $variableID, $regeln)) {
$geaendert[] = $variableID;
}
}
foreach (self::ENERGIE_VARIABLEN as $ident) {
$variableID = $this->GetIDForIdent($ident);
AC_SetLoggingStatus($archivID, $variableID, $aktiv);
AC_SetAggregationType($archivID, $variableID, 1);
AC_SetGraphStatus($archivID, $variableID, $aktiv);
$regeln = [-1 => $energietyp];
$verdichten = $this->ReadPropertyInteger('EnergieVerdichtenMonate');
if ($verdichten > 0) {
$regeln[$verdichten] = 3;
}
$loeschen = $this->ReadPropertyInteger('EnergieLoeschenMonate');
if ($loeschen > 0) {
$regeln[$loeschen] = 7;
}
if ($aktiv && $this->setzeVerdichtung($archivID, $variableID, $regeln)) {
$geaendert[] = $variableID;
}
}
IPS_ApplyChanges($archivID);
foreach (array_unique($geaendert) as $variableID) {
AC_ReAggregateVariable($archivID, $variableID);
}
}
/** @param array<int, int> $regeln */
private function setzeVerdichtung(int $archivID, int $variableID, array $regeln): bool
{
ksort($regeln);
$aktuell = [];
$eintraege = AC_GetCompaction($archivID, $variableID);
if (is_array($eintraege)) {
foreach ($eintraege as $eintrag) {
$monat = (int) ($eintrag['MonthOffset'] ?? -999);
$typ = (int) ($eintrag['CompactionType'] ?? -1);
if ($monat >= -1 && $typ >= 0) {
$aktuell[$monat] = $typ;
}
}
}
ksort($aktuell);
if ($aktuell === $regeln) {
return false;
}
foreach (array_keys($aktuell) as $monat) {
AC_SetCompaction($archivID, $variableID, $monat, -1);
}
foreach ($regeln as $monat => $typ) {
AC_SetCompaction($archivID, $variableID, $monat, $typ);
}
return true;
}
private function verdichtungstyp(int $minuten): int
{
return [1 => 0, 5 => 1, 60 => 2][$minuten] ?? 0;
}
private function archivID(): int
{
$archive = IPS_GetInstanceListByModuleID(self::ARCHIVE_MODULE_ID);
return $archive === [] ? 0 : (int) $archive[0];
}
private function synchronisiereEnergievisualisierungen(): void
{
$this->synchronisiereEnergyPie($this->ReadPropertyBoolean('EnergyPieAnzeigen'));
$this->synchronisiereEnergiediagramme(
$this->ReadPropertyBoolean('EnergiediagrammeAnzeigen')
);
$this->synchronisiereFunFacts($this->ReadPropertyBoolean('FunFactsAnzeigen'));
$this->synchronisiereEnergiefluss($this->ReadPropertyBoolean('EnergieflussAnzeigen'));
}
private function synchronisiereEnergyPie(bool $anzeigen): void
{
if ($anzeigen) {
$this->erstelleEnergyPie();
return;
}
$this->entferneInstanzUnterobjekt('EnergyPie', self::ENERGY_PIE_MODULE_ID);
}
private function synchronisiereEnergiediagramme(bool $anzeigen): void
{
if ($anzeigen) {
$this->erstelleEnergiediagramme();
return;
}
$this->entferneDiagrammUnterobjekt('Leistungsdiagramm');
$this->entferneDiagrammUnterobjekt('Energiediagramm');
}
private function synchronisiereFunFacts(bool $anzeigen): void
{
if ($anzeigen) {
$this->erstelleFunFacts();
return;
}
$variableID = $this->findeUnterobjekt('EnergieFunFacts');
if ($variableID === 0) {
return;
}
if ((int) IPS_GetObject($variableID)['ObjectType'] !== 2) {
throw new RuntimeException('Unterobjekt EnergieFunFacts ist keine Variable.');
}
$this->UnregisterVariable('EnergieFunFacts');
}
private function synchronisiereEnergiefluss(bool $anzeigen): void
{
if ($anzeigen) {
$this->erstelleEnergiefluss();
return;
}
$instanzID = $this->findeUnterobjekt('Energiefluss');
if ($instanzID === 0) {
return;
}
$objekt = IPS_GetObject($instanzID);
$modulID = $objekt['ObjectType'] === 1
? (string) IPS_GetInstance($instanzID)['ModuleInfo']['ModuleID']
: '';
if ($modulID !== self::ENERGY_FLOW_MODULE_ID) {
throw new RuntimeException('Unterobjekt Energiefluss hat einen unpassenden Typ.');
}
IPS_SetHidden($instanzID, true);
}
private function entferneInstanzUnterobjekt(string $ident, string $modulID): void
{
$instanzID = $this->findeUnterobjekt($ident);
if ($instanzID === 0) {
return;
}
$objekt = IPS_GetObject($instanzID);
$istModulID = $objekt['ObjectType'] === 1
? (string) IPS_GetInstance($instanzID)['ModuleInfo']['ModuleID']
: '';
if ($istModulID !== $modulID) {
throw new RuntimeException('Unterobjekt ' . $ident . ' hat einen unpassenden Typ.');
}
IPS_DeleteInstance($instanzID);
}
private function entferneDiagrammUnterobjekt(string $ident): void
{
$medienID = $this->findeUnterobjekt($ident);
if ($medienID === 0) {
return;
}
$objekt = IPS_GetObject($medienID);
if ($objekt['ObjectType'] !== 5 || (int) IPS_GetMedia($medienID)['MediaType'] !== 4) {
throw new RuntimeException('Unterobjekt ' . $ident . ' ist kein Diagramm.');
}
IPS_DeleteMedia($medienID, true);
}
private function erstelleEnergyPie(): string
{
if (!in_array(self::ENERGY_PIE_MODULE_ID, IPS_GetModuleList(), true)) {
throw new RuntimeException(
'Das Modul Energy Pie aus Enelix Utils ist nicht installiert.'
);
}
$instanzID = $this->findeUnterobjekt('EnergyPie');
if ($instanzID === 0) {
$instanzID = IPS_CreateInstance(self::ENERGY_PIE_MODULE_ID);
IPS_SetParent($instanzID, $this->InstanceID);
IPS_SetIdent($instanzID, 'EnergyPie');
} else {
$objekt = IPS_GetObject($instanzID);
$modulID = $objekt['ObjectType'] === 1
? (string) IPS_GetInstance($instanzID)['ModuleInfo']['ModuleID']
: '';
if ($modulID !== self::ENERGY_PIE_MODULE_ID) {
throw new RuntimeException('Unterobjekt EnergyPie hat einen unpassenden Typ.');
}
}
IPS_SetName($instanzID, 'Energy Pie');
IPS_SetPosition($instanzID, 500);
IPS_SetHidden($instanzID, false);
foreach ([
'ProduktionZaehlerID' => $this->GetIDForIdent('PVEnergie'),
'EinspeisungZaehlerID' => $this->GetIDForIdent('EinspeisungEnergie'),
'NetzbezugZaehlerID' => $this->GetIDForIdent('NetzbezugEnergie'),
'VerbrauchZaehlerID' => $this->GetIDForIdent('Hausenergie'),
'Zaehlerfaktor' => 1.0,
] as $property => $wert) {
IPS_SetProperty($instanzID, $property, $wert);
}
IPS_ApplyChanges($instanzID);
return 'Energy Pie wurde erstellt oder aktualisiert.';
}
private function erstelleEnergiefluss(): string
{
if (!in_array(self::ENERGY_FLOW_MODULE_ID, IPS_GetModuleList(), true)) {
throw new RuntimeException('Das Symcon-Modul Energy Distribution ist nicht installiert.');
}
$instanzID = $this->findeUnterobjekt('Energiefluss');
if ($instanzID === 0) {
$instanzID = IPS_CreateInstance(self::ENERGY_FLOW_MODULE_ID);
IPS_SetParent($instanzID, $this->InstanceID);
IPS_SetIdent($instanzID, 'Energiefluss');
} else {
$objekt = IPS_GetObject($instanzID);
$modulID = $objekt['ObjectType'] === 1
? (string) IPS_GetInstance($instanzID)['ModuleInfo']['ModuleID']
: '';
if ($modulID !== self::ENERGY_FLOW_MODULE_ID) {
throw new RuntimeException('Unterobjekt Energiefluss hat einen unpassenden Typ.');
}
}
IPS_SetName($instanzID, 'Energiefluss');
IPS_SetPosition($instanzID, 540);
IPS_SetHidden($instanzID, false);
$knoten = [
$this->energieflussKnoten('PVLeistungArchiv', 'PV-Anlage', 'solar'),
$this->energieflussKnoten('NetzleistungArchiv', 'Stromnetz', 'grid'),
$this->energieflussKnoten('HausverbrauchLeistungArchiv', 'Hausverbrauch', 'consumer'),
$this->energieflussKnoten('BatterieleistungArchiv', 'Batteriespeicher', 'battery'),
];
IPS_SetProperty($instanzID, 'Calculation', true);
IPS_SetProperty(
$instanzID,
'Config',
json_encode($knoten, JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES)
);
IPS_SetProperty($instanzID, 'EnableLimits', false);
IPS_SetProperty($instanzID, 'Restrictions', '[]');
IPS_ApplyChanges($instanzID);
return 'Der Energiefluss wurde erstellt oder aktualisiert.';
}
/** @return array<string, mixed> */
private function energieflussKnoten(string $ident, string $name, string $typ): array
{
return [
'VariableConfigType' => 0,
'VariableID' => $this->GetIDForIdent($ident),
'SecondaryVariableID' => 1,
'Name' => $name,
'Type' => $typ,
'Grouped' => false,
'CalculationType' => 0,
'InfoVariableID' => 1,
'Invert' => false,
];
}
private function erstelleEnergiediagramme(): string
{
$leistung = [
$this->chartDatensatz('PVLeistungArchiv', 'PV', '#F4C542', '~Power'),
$this->chartDatensatz('HausverbrauchLeistungArchiv', 'Haus', '#22A06B', '~Power'),
$this->chartDatensatz('NetzleistungArchiv', 'Netz', '#E95D5D', '~Power'),
$this->chartDatensatz('BatterieleistungArchiv', 'Batterie', '#8B5CF6', '~Power'),
];
$energie = [
$this->chartDatensatz('PVEnergie', 'PV', '#F4C542', '~Electricity'),
$this->chartDatensatz('Hausenergie', 'Haus', '#22A06B', '~Electricity'),
$this->chartDatensatz('NetzbezugEnergie', 'Netzbezug', '#E95D5D', '~Electricity'),
$this->chartDatensatz('EinspeisungEnergie', 'Einspeisung', '#2F80ED', '~Electricity'),
$this->chartDatensatz('BatterieLadenEnergie', 'Batterie laden', '#8B5CF6', '~Electricity'),
$this->chartDatensatz('BatterieEntladenEnergie', 'Batterie entladen', '#F59E0B', '~Electricity'),
];
$this->erstelleDiagramm('Leistungsdiagramm', 'Leistungen', $leistung, 510);
$this->erstelleDiagramm('Energiediagramm', 'Energien', $energie, 520);
return 'Die Diagramme Leistungen und Energien wurden erstellt oder aktualisiert.';
}
/** @param list<array<string, mixed>> $datensaetze */
private function erstelleDiagramm(
string $ident,
string $name,
array $datensaetze,
int $position
): int {
$medienID = $this->findeUnterobjekt($ident);
if ($medienID === 0) {
$medienID = IPS_CreateMedia(4);
IPS_SetParent($medienID, $this->InstanceID);
IPS_SetIdent($medienID, $ident);
}
$objekt = IPS_GetObject($medienID);
if ($objekt['ObjectType'] !== 5 || (int) IPS_GetMedia($medienID)['MediaType'] !== 4) {
throw new RuntimeException('Unterobjekt ' . $ident . ' ist kein Diagramm.');
}
IPS_SetName($medienID, $name);
IPS_SetPosition($medienID, $position);
IPS_SetHidden($medienID, false);
IPS_SetMediaFile($medienID, 'media/' . $medienID . '.chart', false);
IPS_SetMediaContent(
$medienID,
base64_encode(json_encode(['datasets' => $datensaetze], JSON_THROW_ON_ERROR))
);
return $medienID;
}
/** @return array<string, mixed> */
private function chartDatensatz(
string $ident,
string $titel,
string $farbe,
string $profil
): array {
return [
'variableID' => $this->GetIDForIdent($ident),
'fillColor' => 'clear',
'strokeColor' => $farbe,
'timeOffset' => 0,
'visible' => true,
'title' => $titel,
'type' => 'line',
'side' => 'left',
'profile' => $profil,
];
}
private function findeUnterobjekt(string $ident): int
{
$objektID = @IPS_GetObjectIDByIdent($ident, $this->InstanceID);
return is_int($objektID) && $objektID > 0 ? $objektID : 0;
}
private function erstelleFunFacts(): string
{
$this->RegisterVariableString('EnergieFunFacts', 'Energy Facts', '~HTMLBox', 530);
$variableID = $this->GetIDForIdent('EnergieFunFacts');
IPS_SetHidden($variableID, false);
$this->aktualisiereFunFacts($variableID);
return 'Energy Facts wurden erstellt oder aktualisiert.';
}
private function aktualisiereFunFacts(int $variableID): void
{
$archivID = $this->archivID();
if ($archivID === 0) {
SetValue($variableID, '<div style="padding:16px">Archiv nicht verfuegbar.</div>');
return;
}
$heute = strtotime('today');
$jahr = mktime(0, 0, 0, 1, 1, (int) date('Y'));
$jetzt = time();
$pvID = $this->GetIDForIdent('PVEnergie');
$hausID = $this->GetIDForIdent('Hausenergie');
$bezugID = $this->GetIDForIdent('NetzbezugEnergie');
$exportID = $this->GetIDForIdent('EinspeisungEnergie');
$pvHeute = $this->energieSumme($archivID, $pvID, $heute, $jetzt);
$pvJahr = $this->energieSumme($archivID, $pvID, $jahr, $jetzt);
$pvTotal = max(0.0, (float) GetValue($pvID));
[$besterTag, $besterTagZeit] = $this->maximalerTageswert($archivID, $pvID);
[$maxPV, $maxPVZeit] = $this->maximaleLeistung(
$archivID,
$this->GetIDForIdent('PVLeistungArchiv')
);
[$maxHausTag, $maxHausTagZeit] = $this->maximalerTageswert($archivID, $hausID);
[$maxHaus, $maxHausZeit] = $this->maximaleLeistung(
$archivID,
$this->GetIDForIdent('HausverbrauchLeistungArchiv')
);
[$maxBezug, $maxBezugZeit] = $this->maximalerTageswert($archivID, $bezugID);
[$maxExport, $maxExportZeit] = $this->maximalerTageswert($archivID, $exportID);
$werte = [
'{{PV_HEUTE}}' => $this->formatiereEnergie($pvHeute),
'{{PV_JAHR}}' => $this->formatiereEnergie($pvJahr),
'{{PV_TOTAL}}' => $this->formatiereEnergie($pvTotal),
'{{BESTER_TAG}}' => $this->formatiereEnergie($besterTag),
'{{BESTER_TAG_DATUM}}' => $this->formatiereDatum($besterTagZeit),
'{{MAX_PV}}' => $this->formatiereLeistung($maxPV),
'{{MAX_PV_DATUM}}' => $this->formatiereDatum($maxPVZeit),
'{{MAX_HAUS_TAG}}' => $this->formatiereEnergie($maxHausTag),
'{{MAX_HAUS_TAG_DATUM}}' => $this->formatiereDatum($maxHausTagZeit),
'{{MAX_HAUS}}' => $this->formatiereLeistung($maxHaus),
'{{MAX_HAUS_DATUM}}' => $this->formatiereDatum($maxHausZeit),
'{{MAX_BEZUG}}' => $this->formatiereEnergie($maxBezug),
'{{MAX_BEZUG_DATUM}}' => $this->formatiereDatum($maxBezugZeit),
'{{MAX_EXPORT}}' => $this->formatiereEnergie($maxExport),
'{{MAX_EXPORT_DATUM}}' => $this->formatiereDatum($maxExportZeit),
'{{HEIZOEL}}' => number_format($pvTotal / 10.0, 0, ',', "'") . ' Liter',
'{{FLUGKM}}' => number_format($pvTotal * 2.4, 0, ',', "'") . ' km',
];
SetValue($variableID, strtr($this->funFactsHtml(), $werte));
}
private function energieSumme(int $archivID, int $variableID, int $start, int $ende): float
{
$werte = @AC_GetAggregatedValues($archivID, $variableID, 1, $start, $ende, 0);
if (!is_array($werte)) {
return 0.0;
}
return array_sum(array_map(
static fn (array $wert): float => max(0.0, (float) ($wert['Avg'] ?? 0.0)),
$werte
));
}
/** @return array{0: float, 1: int} */
private function maximalerTageswert(int $archivID, int $variableID): array
{
$werte = @AC_GetAggregatedValues($archivID, $variableID, 1, 0, time(), 0);
$maximum = 0.0;
$zeit = 0;
if (is_array($werte)) {
foreach ($werte as $wert) {
$kandidat = max(0.0, (float) ($wert['Avg'] ?? 0.0));
if ($kandidat > $maximum) {
$maximum = $kandidat;
$zeit = (int) ($wert['TimeStamp'] ?? 0);
}
}
}
return [$maximum, $zeit];
}
/** @return array{0: float, 1: int} */
private function maximaleLeistung(int $archivID, int $variableID): array
{
$werte = @AC_GetAggregatedValues($archivID, $variableID, 1, 0, time(), 0);
$maximum = 0.0;
$zeit = 0;
if (is_array($werte)) {
foreach ($werte as $wert) {
$kandidat = max(0.0, (float) ($wert['Max'] ?? 0.0));
if ($kandidat > $maximum) {
$maximum = $kandidat;
$zeit = (int) ($wert['MaxTime'] ?? $wert['TimeStamp'] ?? 0);
}
}
}
return [$maximum, $zeit];
}
private function formatiereEnergie(float $wert): string
{
if ($wert >= 1000.0) {
return number_format($wert / 1000.0, 2, ',', "'") . ' MWh';
}
return number_format($wert, 1, ',', "'") . ' kWh';
}
private function formatiereLeistung(float $wert): string
{
if ($wert >= 10000.0) {
return number_format($wert / 1000.0, 1, ',', "'") . ' kW';
}
return number_format($wert, 0, ',', "'") . ' W';
}
private function formatiereDatum(int $zeit): string
{
return $zeit > 0 ? date('d.m.Y', $zeit) : 'Noch keine Daten';
}
private function funFactsHtml(): string
{
return <<<'HTML'
<style>
.enx-facts{box-sizing:border-box;font-family:system-ui,-apple-system,sans-serif;color:#182230;padding:16px;background:#f4f7f6}
.enx-facts *{box-sizing:border-box}.enx-facts-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:14px}
.enx-fact-panel{min-width:0;background:#fff;border:1px solid #dfe7e4;border-radius:8px;overflow:hidden;box-shadow:0 4px 18px rgba(17,44,36,.08)}
.enx-fact-head{padding:15px 18px 13px;border-bottom:3px solid #22a06b;display:flex;align-items:center;justify-content:space-between}
.enx-fact-head b{font-size:18px}.enx-fact-head span{font-size:11px;letter-spacing:.08em;color:#60736c}
.enx-fact-body{padding:5px 18px 10px}.enx-fact-row{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:12px;padding:11px 0;border-bottom:1px solid #edf1ef}
.enx-fact-row:last-child{border-bottom:0}.enx-fact-label{min-width:0;color:#52635d;font-size:13px}.enx-fact-value{text-align:right;font-size:15px;font-weight:700;white-space:nowrap}
.enx-fact-date{display:block;color:#81908b;font-size:11px;font-weight:500;margin-top:2px}.enx-fact-panel.grid .enx-fact-head{border-color:#2f80ed}
.enx-fact-panel.impact .enx-fact-head{border-color:#f4c542}.enx-impact{padding:18px}.enx-impact-value{font-size:30px;font-weight:750;line-height:1.15}
.enx-impact-label{font-size:12px;color:#60736c;margin:5px 0 20px}.enx-impact-note{font-size:11px;color:#81908b;line-height:1.45}
@media(max-width:820px){.enx-facts-grid{grid-template-columns:1fr}.enx-facts{padding:10px}}
</style>
<div class="enx-facts"><div class="enx-facts-grid">
<section class="enx-fact-panel"><div class="enx-fact-head"><b>Solar</b><span>ERZEUGUNG</span></div><div class="enx-fact-body">
<div class="enx-fact-row"><span class="enx-fact-label">Heute</span><span class="enx-fact-value">{{PV_HEUTE}}</span></div>
<div class="enx-fact-row"><span class="enx-fact-label">Dieses Jahr</span><span class="enx-fact-value">{{PV_JAHR}}</span></div>
<div class="enx-fact-row"><span class="enx-fact-label">Total</span><span class="enx-fact-value">{{PV_TOTAL}}</span></div>
<div class="enx-fact-row"><span class="enx-fact-label">Bester Tag</span><span class="enx-fact-value">{{BESTER_TAG}}<small class="enx-fact-date">{{BESTER_TAG_DATUM}}</small></span></div>
<div class="enx-fact-row"><span class="enx-fact-label">Max. Leistung</span><span class="enx-fact-value">{{MAX_PV}}<small class="enx-fact-date">{{MAX_PV_DATUM}}</small></span></div>
</div></section>
<section class="enx-fact-panel grid"><div class="enx-fact-head"><b>Netz &amp; Haus</b><span>SPITZENWERTE</span></div><div class="enx-fact-body">
<div class="enx-fact-row"><span class="enx-fact-label">Max. Tagesverbrauch</span><span class="enx-fact-value">{{MAX_HAUS_TAG}}<small class="enx-fact-date">{{MAX_HAUS_TAG_DATUM}}</small></span></div>
<div class="enx-fact-row"><span class="enx-fact-label">Max. Hauslast</span><span class="enx-fact-value">{{MAX_HAUS}}<small class="enx-fact-date">{{MAX_HAUS_DATUM}}</small></span></div>
<div class="enx-fact-row"><span class="enx-fact-label">Max. Tagesbezug</span><span class="enx-fact-value">{{MAX_BEZUG}}<small class="enx-fact-date">{{MAX_BEZUG_DATUM}}</small></span></div>
<div class="enx-fact-row"><span class="enx-fact-label">Max. Einspeisung</span><span class="enx-fact-value">{{MAX_EXPORT}}<small class="enx-fact-date">{{MAX_EXPORT_DATUM}}</small></span></div>
</div></section>
<section class="enx-fact-panel impact"><div class="enx-fact-head"><b>Wirkung</b><span>VERGLEICHE</span></div><div class="enx-impact">
<div class="enx-impact-value">{{HEIZOEL}}</div><div class="enx-impact-label">rechnerisch kompensiertes Heizoel</div>
<div class="enx-impact-value">{{FLUGKM}}</div><div class="enx-impact-label">rechnerisch kompensierte Flugkilometer</div>
<div class="enx-impact-note">Vergleichswerte auf Basis von 10 kWh pro Liter Heizoel und 2,4 Flugkilometern pro erzeugter kWh.</div>
</div></section>
</div></div>
HTML;
}
}