Tests / test (push) Failing after 1m12s
Add opt-in SDL measurement sources and independent visualization toggles. Exclude balancing power from household telemetry and new household energy. Preserve custom views and cover measurement, migration and Symcon 8.0 behavior.
1328 lines
51 KiB
PHP
1328 lines
51 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Belevo\EnelixEMS;
|
|
|
|
use RuntimeException;
|
|
use Throwable;
|
|
|
|
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);
|
|
$loggen = $aktiv && (!str_starts_with($ident, 'SDL') || $this->ReadPropertyBoolean('SDLAktiv'))
|
|
&& ($ident !== 'SDLSOCArchiv' || $this->ReadPropertyInteger('SDLSOCVariableID') > 0);
|
|
if (!$this->setzeArchivLogging($archivID, $variableID, $loggen)) {
|
|
continue;
|
|
}
|
|
AC_SetAggregationType($archivID, $variableID, 0);
|
|
AC_SetGraphStatus($archivID, $variableID, $loggen);
|
|
$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);
|
|
if (!$this->setzeArchivLogging($archivID, $variableID, $aktiv)) {
|
|
continue;
|
|
}
|
|
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;
|
|
}
|
|
}
|
|
|
|
foreach (IPS_GetChildrenIDs($this->InstanceID) as $variableID) {
|
|
$objekt = IPS_GetObject($variableID);
|
|
$ident = (string) ($objekt['ObjectIdent'] ?? '');
|
|
if ((int) ($objekt['ObjectType'] ?? -1) !== 2
|
|
|| !str_starts_with($ident, 'Diagramm')
|
|
) {
|
|
continue;
|
|
}
|
|
$istEnergie = str_contains($ident, 'Energie')
|
|
|| str_contains($ident, 'BatterieLaden_')
|
|
|| str_contains($ident, 'BatterieEntladen_');
|
|
if (!$this->setzeArchivLogging($archivID, $variableID, $aktiv)) {
|
|
continue;
|
|
}
|
|
AC_SetAggregationType($archivID, $variableID, $istEnergie ? 1 : 0);
|
|
AC_SetGraphStatus($archivID, $variableID, $aktiv);
|
|
$regeln = [-1 => $istEnergie ? $energietyp : $leistungstyp];
|
|
$verdichten = $istEnergie
|
|
? $this->ReadPropertyInteger('EnergieVerdichtenMonate')
|
|
: 0;
|
|
if ($verdichten > 0) {
|
|
$regeln[$verdichten] = 3;
|
|
}
|
|
$loeschen = $istEnergie
|
|
? $this->ReadPropertyInteger('EnergieLoeschenMonate')
|
|
: $this->ReadPropertyInteger('LeistungLoeschenMonate');
|
|
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);
|
|
}
|
|
}
|
|
|
|
private function setzeArchivLogging(int $archivID, int $variableID, bool $aktiv): bool
|
|
{
|
|
// Archiv-Metadaten sind nur bei aktivierter Aufzeichnung aenderbar.
|
|
if (!$aktiv && AC_GetLoggingStatus($archivID, $variableID)) {
|
|
AC_SetGraphStatus($archivID, $variableID, false);
|
|
}
|
|
AC_SetLoggingStatus($archivID, $variableID, $aktiv);
|
|
|
|
return $aktiv;
|
|
}
|
|
|
|
/** @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');
|
|
$this->WriteAttributeString('EnergiediagrammKonfiguration', '{}');
|
|
}
|
|
|
|
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) {
|
|
$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);
|
|
}
|
|
$this->WriteAttributeString('EnergieflussKonfiguration', '{}');
|
|
$this->WriteAttributeBoolean('EnergieflussNeuErzeugen', 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_SetHidden($instanzID, false);
|
|
|
|
return 'Bestehende Energy-Pie-Konfiguration bleibt unveraendert.';
|
|
}
|
|
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.');
|
|
}
|
|
$neuErzeugen = $this->ReadAttributeBoolean('EnergieflussNeuErzeugen');
|
|
$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.');
|
|
}
|
|
if (!$neuErzeugen) {
|
|
$this->visualisierungsKonfiguration('EnergieflussKonfiguration');
|
|
$this->synchronisiereSDLEnergiefluss($instanzID);
|
|
IPS_SetHidden($instanzID, false);
|
|
$this->aktualisiereEnergieflusswerte();
|
|
|
|
return 'Bestehende Energiefluss-Konfiguration bleibt unveraendert.';
|
|
}
|
|
}
|
|
$this->visualisierungsKonfiguration('EnergieflussKonfiguration');
|
|
IPS_SetName($instanzID, 'Energiefluss');
|
|
IPS_SetPosition($instanzID, 540);
|
|
IPS_SetHidden($instanzID, false);
|
|
|
|
$profil = $this->energieflussLeistungsprofil();
|
|
$pvID = $this->energieflussLeistungsvariable(
|
|
'EnergieflussPV',
|
|
'Energiefluss PV',
|
|
$profil,
|
|
600
|
|
);
|
|
$netzID = $this->energieflussLeistungsvariable(
|
|
'EnergieflussNetz',
|
|
'Energiefluss Netz',
|
|
$profil,
|
|
610
|
|
);
|
|
$hausID = $this->energieflussLeistungsvariable(
|
|
'EnergieflussHaus',
|
|
'Energiefluss Hausverbrauch',
|
|
$profil,
|
|
620
|
|
);
|
|
$batterieID = $this->energieflussLeistungsvariable(
|
|
'EnergieflussBatterie',
|
|
'Energiefluss Batteriespeicher',
|
|
$profil,
|
|
630
|
|
);
|
|
|
|
$verbraucher = $this->energieflussVerbraucher();
|
|
$knoten = [
|
|
$this->energieflussKnoten($pvID, 'PV-Anlage', 'solar'),
|
|
$this->energieflussKnoten($netzID, 'Stromnetz', 'grid'),
|
|
$this->energieflussKnoten($hausID, 'Hausverbrauch', 'consumer'),
|
|
];
|
|
$position = 640;
|
|
foreach ($verbraucher as $eintrag) {
|
|
$istID = $this->energieflussLeistungsvariable(
|
|
'EnergieflussIst_' . $eintrag['InstanzID'],
|
|
$eintrag['Name'] . ' Istleistung',
|
|
$profil,
|
|
$position
|
|
);
|
|
$sollID = $this->energieflussLeistungsvariable(
|
|
'EnergieflussSoll_' . $eintrag['InstanzID'],
|
|
$eintrag['Name'] . ' Sollleistung',
|
|
$profil,
|
|
$position + 1
|
|
);
|
|
$knoten[] = $this->energieflussKnoten(
|
|
$istID,
|
|
$eintrag['Name'],
|
|
$eintrag['Typ'],
|
|
true,
|
|
$sollID
|
|
);
|
|
$position += 10;
|
|
}
|
|
$knoten[] = $this->energieflussKnoten(
|
|
$batterieID,
|
|
'Batteriespeicher',
|
|
'storage'
|
|
);
|
|
|
|
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);
|
|
$this->WriteAttributeBoolean('EnergieflussNeuErzeugen', false);
|
|
$this->synchronisiereSDLEnergiefluss($instanzID, true);
|
|
$this->aktualisiereEnergieflusswerte($verbraucher);
|
|
|
|
return 'Der Energiefluss wurde erstellt oder aktualisiert.';
|
|
}
|
|
|
|
/** @return array<string, mixed> */
|
|
private function energieflussKnoten(
|
|
int $variableID,
|
|
string $name,
|
|
string $typ,
|
|
bool $gruppiert = false,
|
|
int $infoVariableID = 1
|
|
): array {
|
|
return [
|
|
'VariableConfigType' => 0,
|
|
'VariableID' => $variableID,
|
|
'SecondaryVariableID' => 1,
|
|
'Name' => $name,
|
|
'Type' => $typ,
|
|
'Grouped' => $gruppiert,
|
|
'CalculationType' => 0,
|
|
'InfoVariableID' => $infoVariableID,
|
|
'Invert' => false,
|
|
];
|
|
}
|
|
|
|
private function energieflussLeistungsprofil(): string
|
|
{
|
|
$konfiguration = $this->visualisierungsKonfiguration(
|
|
'EnergieflussKonfiguration'
|
|
);
|
|
if ($konfiguration['EnergieflussLeistungseinheit'] !== 1) {
|
|
return '~Power';
|
|
}
|
|
|
|
$profil = 'Enelix.Power.kW';
|
|
if (!IPS_VariableProfileExists($profil)) {
|
|
IPS_CreateVariableProfile($profil, 2);
|
|
IPS_SetVariableProfileValues($profil, -1000000.0, 1000000.0, 0.001);
|
|
IPS_SetVariableProfileDigits($profil, 3);
|
|
IPS_SetVariableProfileText($profil, '', ' kW');
|
|
}
|
|
|
|
return $profil;
|
|
}
|
|
|
|
private function energieflussLeistungsvariable(
|
|
string $ident,
|
|
string $name,
|
|
string $profil,
|
|
int $position
|
|
): int {
|
|
$this->RegisterVariableFloat($ident, $name, $profil, $position);
|
|
$variableID = $this->GetIDForIdent($ident);
|
|
IPS_SetHidden($variableID, true);
|
|
|
|
return $variableID;
|
|
}
|
|
|
|
/** @return array<string, bool|int> */
|
|
private function visualisierungsKonfiguration(string $attribut): array
|
|
{
|
|
$konfiguration = $this->leseJsonAttribut($attribut);
|
|
$gueltig = isset(
|
|
$konfiguration['DiagrammPVModus'],
|
|
$konfiguration['DiagrammBatterieModus'],
|
|
$konfiguration['LadestationenSeparatAnzeigen'],
|
|
$konfiguration['VerbraucherSeparatAnzeigen'],
|
|
$konfiguration['EnergieflussLeistungseinheit']
|
|
);
|
|
if ($gueltig) {
|
|
return $konfiguration;
|
|
}
|
|
|
|
$konfiguration = [
|
|
'DiagrammPVModus' => $this->ReadPropertyInteger('DiagrammPVModus'),
|
|
'DiagrammBatterieModus' => $this->ReadPropertyInteger('DiagrammBatterieModus'),
|
|
'LadestationenSeparatAnzeigen' =>
|
|
$this->ReadPropertyBoolean('LadestationenSeparatAnzeigen'),
|
|
'VerbraucherSeparatAnzeigen' =>
|
|
$this->ReadPropertyBoolean('VerbraucherSeparatAnzeigen'),
|
|
'EnergieflussLeistungseinheit' =>
|
|
$this->ReadPropertyInteger('EnergieflussLeistungseinheit'),
|
|
];
|
|
$this->WriteAttributeString(
|
|
$attribut,
|
|
json_encode($konfiguration, JSON_THROW_ON_ERROR)
|
|
);
|
|
|
|
return $konfiguration;
|
|
}
|
|
|
|
/**
|
|
* @return list<array{
|
|
* InstanzID: int,
|
|
* Name: string,
|
|
* Typ: string,
|
|
* Istleistung_W: float,
|
|
* Sollleistung_W: float,
|
|
* Energie_Wh: float
|
|
* }>
|
|
*/
|
|
private function energieflussVerbraucher(
|
|
string $konfigurationsAttribut = 'EnergieflussKonfiguration'
|
|
): array {
|
|
$konfiguration = $this->visualisierungsKonfiguration($konfigurationsAttribut);
|
|
$cache = $this->leseJsonAttribut('VerbraucherCache');
|
|
$energien = $this->leseJsonAttribut('VerbraucherEnergie');
|
|
$ergebnis = [];
|
|
foreach ($this->konfigurierteAktiveVerbraucherIDs() as $instanzID) {
|
|
if (!IPS_InstanceExists($instanzID)) {
|
|
continue;
|
|
}
|
|
$daten = $cache[(string) $instanzID]['Daten'] ?? null;
|
|
$istleistung = 0.0;
|
|
$sollleistung = 0.0;
|
|
if (is_array($daten)) {
|
|
$istleistung = $this->verbraucherleistung($daten);
|
|
$sollleistung = (float) (ManagerRegler::angenommenerSollwert($daten) ?? 0);
|
|
}
|
|
$modulID = strtoupper(
|
|
(string) IPS_GetInstance($instanzID)['ModuleInfo']['ModuleID']
|
|
);
|
|
$katalog = self::VERBRAUCHER_LIZENZEN[$modulID]['Katalog'] ?? '';
|
|
if ($katalog === 'battery') {
|
|
continue;
|
|
}
|
|
if ($katalog === 'ev_charger'
|
|
&& !$konfiguration['LadestationenSeparatAnzeigen']
|
|
) {
|
|
continue;
|
|
}
|
|
if ($katalog !== 'ev_charger'
|
|
&& !$konfiguration['VerbraucherSeparatAnzeigen']
|
|
) {
|
|
continue;
|
|
}
|
|
$verbraucherEnergie = $energien[(string) $instanzID]['Energie_Wh'] ?? 0.0;
|
|
|
|
$ergebnis[] = [
|
|
'InstanzID' => $instanzID,
|
|
'Name' => IPS_GetName($instanzID),
|
|
'Typ' => $katalog === 'ev_charger' ? 'wallbox' : 'consumer',
|
|
'Istleistung_W' => $istleistung,
|
|
'Sollleistung_W' => $sollleistung,
|
|
'Energie_Wh' => is_numeric($verbraucherEnergie)
|
|
? (float) $verbraucherEnergie
|
|
: 0.0,
|
|
];
|
|
}
|
|
|
|
return $ergebnis;
|
|
}
|
|
|
|
/**
|
|
* @param list<array<string, mixed>>|null $verbraucher
|
|
*/
|
|
private function aktualisiereEnergieflusswerte(?array $verbraucher = null): void
|
|
{
|
|
if (!$this->ReadPropertyBoolean('EnergieflussAnzeigen')) {
|
|
return;
|
|
}
|
|
$verbraucher = $verbraucher ?? $this->energieflussVerbraucher();
|
|
$verbraucherleistung = 0.0;
|
|
foreach ($verbraucher as $eintrag) {
|
|
$this->setzeEnergieflussLeistungswert(
|
|
'EnergieflussIst_' . $eintrag['InstanzID'],
|
|
(float) $eintrag['Istleistung_W']
|
|
);
|
|
$this->setzeEnergieflussLeistungswert(
|
|
'EnergieflussSoll_' . $eintrag['InstanzID'],
|
|
(float) $eintrag['Sollleistung_W']
|
|
);
|
|
$verbraucherleistung += max(0.0, (float) $eintrag['Istleistung_W']);
|
|
}
|
|
|
|
try {
|
|
$leistungen = $this->leseMessleistungen();
|
|
} catch (Throwable $fehler) {
|
|
$this->protokolliere('Energiefluss', $fehler->getMessage());
|
|
return;
|
|
}
|
|
$this->setzeEnergieflussLeistungswert('EnergieflussPV', $leistungen['PV']);
|
|
$this->setzeEnergieflussLeistungswert('EnergieflussNetz', $leistungen['Netz']);
|
|
$this->setzeEnergieflussLeistungswert(
|
|
'EnergieflussHaus',
|
|
max(0.0, $leistungen['Haus'] - $verbraucherleistung)
|
|
);
|
|
$this->setzeEnergieflussLeistungswert(
|
|
'EnergieflussBatterie',
|
|
$leistungen['Batterie'] + ($this->sdlAnzeigen('SDLEnergieflussAnzeigen') ? 0.0 : $leistungen['SDL'])
|
|
);
|
|
$this->setzeEnergieflussLeistungswert('EnergieflussSDL', $leistungen['SDL']);
|
|
$soc = $this->aktuelleSDLSOC();
|
|
if ($soc !== null) {
|
|
$this->SetValue('SDLSOCArchiv', $soc);
|
|
}
|
|
$flussID = $this->findeUnterobjekt('Energiefluss');
|
|
if ($flussID > 0) {
|
|
$this->synchronisiereSDLEnergiefluss($flussID);
|
|
}
|
|
}
|
|
|
|
private function setzeEnergieflussLeistungswert(string $ident, float $watt): void
|
|
{
|
|
$variableID = @IPS_GetObjectIDByIdent($ident, $this->InstanceID);
|
|
if (!is_int($variableID) || $variableID <= 0 || !IPS_VariableExists($variableID)) {
|
|
return;
|
|
}
|
|
$konfiguration = $this->visualisierungsKonfiguration(
|
|
'EnergieflussKonfiguration'
|
|
);
|
|
$faktor = $konfiguration['EnergieflussLeistungseinheit'] === 1 ? 0.001 : 1.0;
|
|
SetValue($variableID, $watt * $faktor);
|
|
}
|
|
|
|
private function erstelleEnergiediagramme(): string
|
|
{
|
|
$konfiguration = $this->visualisierungsKonfiguration(
|
|
'EnergiediagrammKonfiguration'
|
|
);
|
|
$pvAnlagen = $this->diagrammPVAnlagen();
|
|
$batterien = $this->diagrammBatterien();
|
|
$verbraucher = $this->energieflussVerbraucher(
|
|
'EnergiediagrammKonfiguration'
|
|
);
|
|
$this->registriereDiagrammVariablen($pvAnlagen, $batterien, $verbraucher);
|
|
$this->aktualisiereDiagrammMesswerte(true);
|
|
|
|
$leistung = [];
|
|
$energie = [];
|
|
if ($konfiguration['DiagrammPVModus'] !== 1) {
|
|
$leistung[] = $this->chartDatensatz(
|
|
'PVLeistungArchiv',
|
|
'PV gesamt',
|
|
'#F4C542',
|
|
'~Power'
|
|
);
|
|
$energie[] = $this->chartDatensatz(
|
|
'PVEnergie',
|
|
'PV gesamt',
|
|
'#F4C542',
|
|
'~Electricity',
|
|
'bar'
|
|
);
|
|
}
|
|
if ($konfiguration['DiagrammPVModus'] !== 0) {
|
|
foreach ($pvAnlagen as $index => $anlage) {
|
|
$farbe = $this->diagrammFarbe($index);
|
|
if ($anlage['LeistungVariableID'] > 0) {
|
|
$leistung[] = $this->chartDatensatz(
|
|
$anlage['LeistungIdent'],
|
|
$anlage['Name'],
|
|
$farbe,
|
|
'~Power'
|
|
);
|
|
}
|
|
if ($anlage['EnergieVariableID'] > 0) {
|
|
$energie[] = $this->chartDatensatz(
|
|
$anlage['EnergieIdent'],
|
|
$anlage['Name'],
|
|
$farbe,
|
|
'~Electricity',
|
|
'bar'
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
$hausTitel = $verbraucher === [] ? 'Haus' : 'Haus ohne separat dargestellte Verbraucher';
|
|
$leistung[] = $this->chartDatensatz(
|
|
'DiagrammHausLeistung',
|
|
$hausTitel,
|
|
'#22A06B',
|
|
'~Power'
|
|
);
|
|
$leistung[] = $this->chartDatensatz(
|
|
'NetzleistungArchiv',
|
|
'Netz',
|
|
'#E95D5D',
|
|
'~Power'
|
|
);
|
|
$energie[] = $this->chartDatensatz(
|
|
'DiagrammHausEnergie',
|
|
$hausTitel,
|
|
'#22A06B',
|
|
'~Electricity',
|
|
'bar'
|
|
);
|
|
$energie[] = $this->chartDatensatz(
|
|
'NetzbezugEnergie',
|
|
'Netzbezug',
|
|
'#E95D5D',
|
|
'~Electricity',
|
|
'bar'
|
|
);
|
|
$energie[] = $this->chartDatensatz(
|
|
'EinspeisungEnergie',
|
|
'Netzeinspeisung',
|
|
'#2F80ED',
|
|
'~Electricity',
|
|
'bar'
|
|
);
|
|
|
|
if ($konfiguration['DiagrammBatterieModus'] !== 1) {
|
|
$leistung[] = $this->chartDatensatz(
|
|
'BatterieleistungArchiv',
|
|
'Batterie gesamt',
|
|
'#8B5CF6',
|
|
'~Power'
|
|
);
|
|
$energie[] = $this->chartDatensatz(
|
|
'BatterieLadenEnergie',
|
|
'Batterie laden gesamt',
|
|
'#8B5CF6',
|
|
'~Electricity',
|
|
'bar'
|
|
);
|
|
$energie[] = $this->chartDatensatz(
|
|
'BatterieEntladenEnergie',
|
|
'Batterie entladen gesamt',
|
|
'#F59E0B',
|
|
'~Electricity',
|
|
'bar'
|
|
);
|
|
}
|
|
if ($konfiguration['DiagrammBatterieModus'] !== 0) {
|
|
foreach ($batterien as $index => $batterie) {
|
|
$farbe = $this->diagrammFarbe($index + count($pvAnlagen));
|
|
if ($batterie['LeistungVariableID'] > 0) {
|
|
$leistung[] = $this->chartDatensatz(
|
|
$batterie['LeistungIdent'],
|
|
$batterie['Name'],
|
|
$farbe,
|
|
'~Power'
|
|
);
|
|
}
|
|
if ($batterie['LadeenergieVariableID'] > 0) {
|
|
$energie[] = $this->chartDatensatz(
|
|
$batterie['LadeenergieIdent'],
|
|
$batterie['Name'] . ' laden',
|
|
$farbe,
|
|
'~Electricity',
|
|
'bar'
|
|
);
|
|
}
|
|
if ($batterie['EntladeenergieVariableID'] > 0) {
|
|
$energie[] = $this->chartDatensatz(
|
|
$batterie['EntladeenergieIdent'],
|
|
$batterie['Name'] . ' entladen',
|
|
$farbe,
|
|
'~Electricity',
|
|
'bar'
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
foreach ($verbraucher as $index => $eintrag) {
|
|
$farbe = $this->diagrammFarbe(
|
|
$index + count($pvAnlagen) + count($batterien)
|
|
);
|
|
$leistung[] = $this->chartDatensatz(
|
|
'DiagrammVerbraucherLeistung_' . $eintrag['InstanzID'],
|
|
$eintrag['Name'],
|
|
$farbe,
|
|
'~Power'
|
|
);
|
|
$energie[] = $this->chartDatensatz(
|
|
'DiagrammVerbraucherEnergie_' . $eintrag['InstanzID'],
|
|
$eintrag['Name'],
|
|
$farbe,
|
|
'~Electricity',
|
|
'bar'
|
|
);
|
|
}
|
|
|
|
$leistungID = $this->erstelleDiagramm('Leistungsdiagramm', 'Leistungen', $leistung, 510);
|
|
$energieID = $this->erstelleDiagramm('Energiediagramm', 'Energien', $energie, 520);
|
|
$this->synchronisiereSDLDiagramm($leistungID, false);
|
|
$this->synchronisiereSDLDiagramm($energieID, true);
|
|
|
|
return 'Die Diagramme Leistungen und Energien wurden erstellt oder aktualisiert.';
|
|
}
|
|
/** @return list<array<string, mixed>> */
|
|
private function diagrammPVAnlagen(): array
|
|
{
|
|
$verwendet = [];
|
|
foreach ($this->leseAnlagenKonfigurationsliste('AnlagenPVFlaechen') as $flaeche) {
|
|
$wechselrichterID = trim((string) ($flaeche['WechselrichterID'] ?? ''));
|
|
if ($wechselrichterID !== '') {
|
|
$verwendet[$wechselrichterID] = true;
|
|
}
|
|
}
|
|
|
|
$ergebnis = [];
|
|
foreach ($this->leseAnlagenKonfigurationsliste('AnlagenWechselrichter') as $eintrag) {
|
|
$id = trim((string) ($eintrag['ID'] ?? ''));
|
|
if ($id === '' || ($verwendet !== [] && !isset($verwendet[$id]))) {
|
|
continue;
|
|
}
|
|
if ($verwendet === []
|
|
&& !in_array((string) ($eintrag['Typ'] ?? ''), ['pv', 'hybrid'], true)
|
|
) {
|
|
continue;
|
|
}
|
|
$suffix = substr(hash('sha256', 'pv:' . $id), 0, 12);
|
|
$leistungID = (int) ($eintrag['IstleistungVariableID'] ?? 0);
|
|
$energieID = (int) ($eintrag['ErzeugungsenergieVariableID'] ?? 0);
|
|
$ergebnis[] = [
|
|
'Name' => trim((string) ($eintrag['Name'] ?? '')) ?: $id,
|
|
'LeistungVariableID' => $this->istNumerischeVariable($leistungID)
|
|
? $leistungID
|
|
: 0,
|
|
'Leistungsfaktor' => (float) ($eintrag['Istleistungsfaktor'] ?? 1.0),
|
|
'EnergieVariableID' => $this->istNumerischeVariable($energieID)
|
|
? $energieID
|
|
: 0,
|
|
'Energiefaktor' => (float) ($eintrag['Erzeugungsenergiefaktor'] ?? 1.0),
|
|
'LeistungIdent' => 'DiagrammPVLeistung_' . $suffix,
|
|
'EnergieIdent' => 'DiagrammPVEnergie_' . $suffix,
|
|
];
|
|
}
|
|
|
|
return $ergebnis;
|
|
}
|
|
|
|
/** @return list<array<string, mixed>> */
|
|
private function diagrammBatterien(): array
|
|
{
|
|
$ergebnis = [];
|
|
foreach ($this->leseAnlagenKonfigurationsliste('AnlagenBatterien') as $eintrag) {
|
|
$id = trim((string) ($eintrag['ID'] ?? ''));
|
|
if ($id === '') {
|
|
continue;
|
|
}
|
|
$suffix = substr(hash('sha256', 'batterie:' . $id), 0, 12);
|
|
$leistungID = (int) ($eintrag['LeistungVariableID'] ?? 0);
|
|
$ladenID = (int) ($eintrag['LadeenergieVariableID'] ?? 0);
|
|
$entladenID = (int) ($eintrag['EntladeenergieVariableID'] ?? 0);
|
|
$ergebnis[] = [
|
|
'Name' => trim((string) ($eintrag['Name'] ?? '')) ?: $id,
|
|
'LeistungVariableID' => $this->istNumerischeVariable($leistungID)
|
|
? $leistungID
|
|
: 0,
|
|
'Leistungsfaktor' => (float) ($eintrag['Leistungsfaktor'] ?? 1.0),
|
|
'LadeenergieVariableID' => $this->istNumerischeVariable($ladenID)
|
|
? $ladenID
|
|
: 0,
|
|
'EntladeenergieVariableID' => $this->istNumerischeVariable($entladenID)
|
|
? $entladenID
|
|
: 0,
|
|
'Energiefaktor' => (float) ($eintrag['Energiefaktor'] ?? 1.0),
|
|
'LeistungIdent' => 'DiagrammBatterieLeistung_' . $suffix,
|
|
'LadeenergieIdent' => 'DiagrammBatterieLaden_' . $suffix,
|
|
'EntladeenergieIdent' => 'DiagrammBatterieEntladen_' . $suffix,
|
|
];
|
|
}
|
|
|
|
return $ergebnis;
|
|
}
|
|
|
|
/**
|
|
* @param list<array<string, mixed>> $pvAnlagen
|
|
* @param list<array<string, mixed>> $batterien
|
|
* @param list<array<string, mixed>> $verbraucher
|
|
*/
|
|
private function registriereDiagrammVariablen(
|
|
array $pvAnlagen,
|
|
array $batterien,
|
|
array $verbraucher
|
|
): void {
|
|
$position = 800;
|
|
$this->diagrammMessvariable(
|
|
'DiagrammHausLeistung',
|
|
'Diagramm Hausleistung',
|
|
'~Power',
|
|
$position
|
|
);
|
|
$this->diagrammMessvariable(
|
|
'DiagrammHausEnergie',
|
|
'Diagramm Hausenergie',
|
|
'~Electricity',
|
|
$position + 1
|
|
);
|
|
$position += 10;
|
|
|
|
foreach ($pvAnlagen as $anlage) {
|
|
if ($anlage['LeistungVariableID'] > 0) {
|
|
$this->diagrammMessvariable(
|
|
$anlage['LeistungIdent'],
|
|
$anlage['Name'] . ' Leistung',
|
|
'~Power',
|
|
$position
|
|
);
|
|
}
|
|
if ($anlage['EnergieVariableID'] > 0) {
|
|
$this->diagrammMessvariable(
|
|
$anlage['EnergieIdent'],
|
|
$anlage['Name'] . ' Energie',
|
|
'~Electricity',
|
|
$position + 1
|
|
);
|
|
}
|
|
$position += 10;
|
|
}
|
|
foreach ($batterien as $batterie) {
|
|
if ($batterie['LeistungVariableID'] > 0) {
|
|
$this->diagrammMessvariable(
|
|
$batterie['LeistungIdent'],
|
|
$batterie['Name'] . ' Leistung',
|
|
'~Power',
|
|
$position
|
|
);
|
|
}
|
|
if ($batterie['LadeenergieVariableID'] > 0) {
|
|
$this->diagrammMessvariable(
|
|
$batterie['LadeenergieIdent'],
|
|
$batterie['Name'] . ' Ladeenergie',
|
|
'~Electricity',
|
|
$position + 1
|
|
);
|
|
}
|
|
if ($batterie['EntladeenergieVariableID'] > 0) {
|
|
$this->diagrammMessvariable(
|
|
$batterie['EntladeenergieIdent'],
|
|
$batterie['Name'] . ' Entladeenergie',
|
|
'~Electricity',
|
|
$position + 2
|
|
);
|
|
}
|
|
$position += 10;
|
|
}
|
|
foreach ($verbraucher as $eintrag) {
|
|
$this->diagrammMessvariable(
|
|
'DiagrammVerbraucherLeistung_' . $eintrag['InstanzID'],
|
|
$eintrag['Name'] . ' Leistung',
|
|
'~Power',
|
|
$position
|
|
);
|
|
$this->diagrammMessvariable(
|
|
'DiagrammVerbraucherEnergie_' . $eintrag['InstanzID'],
|
|
$eintrag['Name'] . ' Energie',
|
|
'~Electricity',
|
|
$position + 1
|
|
);
|
|
$position += 10;
|
|
}
|
|
}
|
|
|
|
private function diagrammMessvariable(
|
|
string $ident,
|
|
string $name,
|
|
string $profil,
|
|
int $position
|
|
): int {
|
|
$this->RegisterVariableFloat($ident, $name, $profil, $position);
|
|
$variableID = $this->GetIDForIdent($ident);
|
|
IPS_SetHidden($variableID, true);
|
|
|
|
return $variableID;
|
|
}
|
|
|
|
private function aktualisiereDiagrammMesswerte(bool $energieAktualisieren = false): void
|
|
{
|
|
if ($this->findeUnterobjekt('Leistungsdiagramm') === 0
|
|
&& $this->findeUnterobjekt('Energiediagramm') === 0
|
|
&& $this->findeUnterobjekt('DiagrammHausLeistung') === 0
|
|
) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
$leistungen = $this->leseMessleistungen();
|
|
$pvAnlagen = $this->diagrammPVAnlagen();
|
|
$batterien = $this->diagrammBatterien();
|
|
$verbraucher = $this->energieflussVerbraucher(
|
|
'EnergiediagrammKonfiguration'
|
|
);
|
|
} catch (Throwable $fehler) {
|
|
$this->protokolliere('Energiediagramme', $fehler->getMessage());
|
|
return;
|
|
}
|
|
|
|
$separateLeistung = 0.0;
|
|
$separateEnergieKWh = 0.0;
|
|
foreach ($verbraucher as $eintrag) {
|
|
$leistung = max(0.0, (float) $eintrag['Istleistung_W']);
|
|
$separateLeistung += $leistung;
|
|
$separateEnergieKWh += max(0.0, (float) $eintrag['Energie_Wh']) / 1000.0;
|
|
$this->setzeDiagrammMesswert(
|
|
'DiagrammVerbraucherLeistung_' . $eintrag['InstanzID'],
|
|
$leistung
|
|
);
|
|
if ($energieAktualisieren) {
|
|
$this->setzeDiagrammMesswert(
|
|
'DiagrammVerbraucherEnergie_' . $eintrag['InstanzID'],
|
|
max(0.0, (float) $eintrag['Energie_Wh']) / 1000.0
|
|
);
|
|
}
|
|
}
|
|
$this->setzeDiagrammMesswert(
|
|
'DiagrammHausLeistung',
|
|
max(0.0, $leistungen['Haus'] - $separateLeistung)
|
|
);
|
|
if ($energieAktualisieren) {
|
|
$this->setzeDiagrammMesswert(
|
|
'DiagrammHausEnergie',
|
|
max(
|
|
0.0,
|
|
(float) $this->GetValue('Hausenergie') - $separateEnergieKWh
|
|
)
|
|
);
|
|
}
|
|
|
|
foreach ($pvAnlagen as $anlage) {
|
|
if ($anlage['LeistungVariableID'] > 0) {
|
|
$this->setzeDiagrammMesswert(
|
|
$anlage['LeistungIdent'],
|
|
max(
|
|
0.0,
|
|
(float) GetValue($anlage['LeistungVariableID'])
|
|
* $anlage['Leistungsfaktor']
|
|
)
|
|
);
|
|
}
|
|
if ($energieAktualisieren && $anlage['EnergieVariableID'] > 0) {
|
|
$this->setzeDiagrammMesswert(
|
|
$anlage['EnergieIdent'],
|
|
max(
|
|
0.0,
|
|
(float) GetValue($anlage['EnergieVariableID'])
|
|
* $anlage['Energiefaktor']
|
|
)
|
|
);
|
|
}
|
|
}
|
|
foreach ($batterien as $batterie) {
|
|
if ($batterie['LeistungVariableID'] > 0) {
|
|
$this->setzeDiagrammMesswert(
|
|
$batterie['LeistungIdent'],
|
|
(float) GetValue($batterie['LeistungVariableID'])
|
|
* $batterie['Leistungsfaktor']
|
|
);
|
|
}
|
|
if ($energieAktualisieren && $batterie['LadeenergieVariableID'] > 0) {
|
|
$this->setzeDiagrammMesswert(
|
|
$batterie['LadeenergieIdent'],
|
|
max(
|
|
0.0,
|
|
(float) GetValue($batterie['LadeenergieVariableID'])
|
|
* $batterie['Energiefaktor']
|
|
)
|
|
);
|
|
}
|
|
if ($energieAktualisieren && $batterie['EntladeenergieVariableID'] > 0) {
|
|
$this->setzeDiagrammMesswert(
|
|
$batterie['EntladeenergieIdent'],
|
|
max(
|
|
0.0,
|
|
(float) GetValue($batterie['EntladeenergieVariableID'])
|
|
* $batterie['Energiefaktor']
|
|
)
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
private function setzeDiagrammMesswert(string $ident, float $wert): void
|
|
{
|
|
$variableID = @IPS_GetObjectIDByIdent($ident, $this->InstanceID);
|
|
if (is_int($variableID) && $variableID > 0 && IPS_VariableExists($variableID)) {
|
|
SetValue($variableID, $wert);
|
|
}
|
|
}
|
|
|
|
private function diagrammFarbe(int $index): string
|
|
{
|
|
$farben = [
|
|
'#A85D3C',
|
|
'#007C91',
|
|
'#6B5B95',
|
|
'#2E7D32',
|
|
'#C43E1C',
|
|
'#3D5A80',
|
|
'#8D6E63',
|
|
'#00897B',
|
|
];
|
|
|
|
return $farben[$index % count($farben)];
|
|
}
|
|
|
|
/** @param list<array<string, mixed>> $datensaetze */
|
|
private function erstelleDiagramm(
|
|
string $ident,
|
|
string $name,
|
|
array $datensaetze,
|
|
int $position
|
|
): int {
|
|
$medienID = $this->findeUnterobjekt($ident);
|
|
$neu = $medienID === 0;
|
|
if ($neu) {
|
|
$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.');
|
|
}
|
|
if (!$neu) {
|
|
IPS_SetHidden($medienID, false);
|
|
|
|
return $medienID;
|
|
}
|
|
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,
|
|
string $typ = 'line'
|
|
): array {
|
|
return [
|
|
'variableID' => $this->GetIDForIdent($ident),
|
|
'fillColor' => $typ === 'bar' ? $farbe : 'clear',
|
|
'strokeColor' => $farbe,
|
|
'timeOffset' => 0,
|
|
'visible' => true,
|
|
'title' => $titel,
|
|
'type' => $typ,
|
|
'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 & 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;
|
|
}
|
|
}
|