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.
This commit is contained in:
@@ -0,0 +1,211 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Belevo\EnelixEMS;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use RuntimeException;
|
||||
use Throwable;
|
||||
|
||||
trait ManagerSDLTrait
|
||||
{
|
||||
private function pruefeSDLKonfiguration(): void
|
||||
{
|
||||
if (!$this->ReadPropertyBoolean('SDLAktiv')) {
|
||||
return;
|
||||
}
|
||||
$leistungID = $this->ReadPropertyInteger('SDLLeistungVariableID');
|
||||
if (!$this->istNumerischeVariable($leistungID)) {
|
||||
throw new InvalidArgumentException('SDL benoetigt eine numerische Istleistungsvariable.');
|
||||
}
|
||||
$this->anlagenMessfaktor($this->ReadPropertyFloat('SDLLeistungsfaktor'), 'SDL');
|
||||
$socID = $this->ReadPropertyInteger('SDLSOCVariableID');
|
||||
if ($socID !== 0 && (!$this->istNumerischeVariable($socID) || $socID === $leistungID)) {
|
||||
throw new InvalidArgumentException('SDL-SOC muss eine separate numerische Variable sein.');
|
||||
}
|
||||
$andereQuellen = array_merge($this->pvMessquellen(), $this->batterieMessquellen());
|
||||
$andereQuellen[] = ['VariableID' => $this->ReadPropertyInteger('NetzleistungVariableID')];
|
||||
foreach ($andereQuellen as $quelle) {
|
||||
if ($leistungID === $quelle['VariableID']) {
|
||||
throw new InvalidArgumentException(
|
||||
'SDL-Leistung darf nicht zugleich als PV-, Netz- oder Batterieleistung bilanziert werden.'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function synchronisiereSDLMesskonfiguration(): void
|
||||
{
|
||||
$neu = json_encode([
|
||||
$this->ReadPropertyBoolean('SDLAktiv'),
|
||||
$this->ReadPropertyInteger('SDLLeistungVariableID'),
|
||||
$this->ReadPropertyFloat('SDLLeistungsfaktor'),
|
||||
], JSON_THROW_ON_ERROR);
|
||||
$alt = $this->ReadAttributeString('SDLMesskonfiguration');
|
||||
if ($neu !== $alt && ($alt !== '' || $this->ReadPropertyBoolean('SDLAktiv'))) {
|
||||
// Bereits erfasste Energien bleiben erhalten; keine Rueckrechnung mit neuen Quellen.
|
||||
$this->WriteAttributeInteger('MessungLetzterZeitstempel', 0);
|
||||
$this->WriteAttributeString('MessungZaehlerstaende', '{}');
|
||||
$this->WriteAttributeInteger('MessungLetzteLeistungsaufzeichnung', 0);
|
||||
}
|
||||
$this->WriteAttributeString('SDLMesskonfiguration', $neu);
|
||||
}
|
||||
|
||||
private function aktuelleSDLLeistung(): float
|
||||
{
|
||||
if (!$this->ReadPropertyBoolean('SDLAktiv')) {
|
||||
return 0.0;
|
||||
}
|
||||
$variableID = $this->ReadPropertyInteger('SDLLeistungVariableID');
|
||||
if (!$this->istNumerischeVariable($variableID)) {
|
||||
throw new RuntimeException('Die SDL-Istleistung ist nicht verfuegbar.');
|
||||
}
|
||||
$wert = GetValue($variableID);
|
||||
if ((!is_int($wert) && !is_float($wert)) || !is_finite((float) $wert)) {
|
||||
throw new RuntimeException('Die SDL-Istleistung ist keine endliche Zahl.');
|
||||
}
|
||||
$leistung = (float) $wert * $this->ReadPropertyFloat('SDLLeistungsfaktor');
|
||||
if (!is_finite($leistung)) {
|
||||
throw new RuntimeException('Die skalierte SDL-Istleistung ist nicht endlich.');
|
||||
}
|
||||
if ($this->ReadPropertyInteger('SDLSOCVariableID') === 0 || $this->aktuelleSDLSOC() !== null) {
|
||||
$this->setzeDiagnosewert('SDLStatus', 'Aktiv');
|
||||
}
|
||||
|
||||
return $leistung;
|
||||
}
|
||||
|
||||
private function aktuelleSDLSOC(): ?float
|
||||
{
|
||||
if (!$this->ReadPropertyBoolean('SDLAktiv')) {
|
||||
return null;
|
||||
}
|
||||
$variableID = $this->ReadPropertyInteger('SDLSOCVariableID');
|
||||
if ($variableID === 0) {
|
||||
return null;
|
||||
}
|
||||
if ($this->istNumerischeVariable($variableID)) {
|
||||
$wert = (float) GetValue($variableID);
|
||||
if (is_finite($wert) && $wert >= 0.0 && $wert <= 100.0) {
|
||||
return $wert;
|
||||
}
|
||||
}
|
||||
$this->setzeDiagnosewert('SDLStatus', 'Fehler: SDL-SOC muss zwischen 0 und 100 Prozent liegen.');
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function sdlAnzeigen(string $property): bool
|
||||
{
|
||||
return $this->ReadPropertyBoolean('SDLAktiv') && $this->ReadPropertyBoolean($property);
|
||||
}
|
||||
|
||||
private function aktualisiereSDLStatus(): void
|
||||
{
|
||||
if (!$this->ReadPropertyBoolean('SDLAktiv')) {
|
||||
$this->setzeDiagnosewert('SDLStatus', 'NichtVerwendet');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
$this->aktuelleSDLLeistung();
|
||||
$this->aktuelleSDLSOC();
|
||||
} catch (Throwable $fehler) {
|
||||
$this->setzeDiagnosewert('SDLStatus', 'Fehler: ' . $fehler->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private function synchronisiereSDLEnergiefluss(int $instanzID, bool $profilAktualisieren = false): void
|
||||
{
|
||||
$anzeigen = $this->sdlAnzeigen('SDLEnergieflussAnzeigen');
|
||||
$variableID = $this->findeUnterobjekt('EnergieflussSDL');
|
||||
if (!$anzeigen && $variableID === 0) {
|
||||
return;
|
||||
}
|
||||
if ($anzeigen && ($variableID === 0 || $profilAktualisieren)) {
|
||||
$variableID = $this->energieflussLeistungsvariable(
|
||||
'EnergieflussSDL', 'Energiefluss SDL', $this->energieflussLeistungsprofil(), 635
|
||||
);
|
||||
}
|
||||
$alt = json_decode(IPS_GetProperty($instanzID, 'Config'), true, 512, JSON_THROW_ON_ERROR);
|
||||
if (!is_array($alt) || !array_is_list($alt)) {
|
||||
throw new RuntimeException('Die bestehende Energiefluss-Konfiguration ist keine Liste.');
|
||||
}
|
||||
$neu = [];
|
||||
$gefunden = false;
|
||||
$socID = $this->aktuelleSDLSOC() !== null
|
||||
? $this->GetIDForIdent('SDLSOCArchiv') : 1;
|
||||
foreach ($alt as $knoten) {
|
||||
if ((int) ($knoten['VariableID'] ?? 0) !== $variableID) {
|
||||
$neu[] = $knoten;
|
||||
} elseif ($anzeigen && !$gefunden) {
|
||||
$knoten['InfoVariableID'] = $socID;
|
||||
$neu[] = $knoten;
|
||||
$gefunden = true;
|
||||
}
|
||||
}
|
||||
if ($anzeigen && !$gefunden) {
|
||||
$neu[] = $this->energieflussKnoten($variableID, 'SDL / Regelenergie', 'storage', false, $socID);
|
||||
}
|
||||
if ($neu !== $alt) {
|
||||
IPS_SetProperty($instanzID, 'Config', json_encode($neu, JSON_THROW_ON_ERROR));
|
||||
IPS_ApplyChanges($instanzID);
|
||||
}
|
||||
}
|
||||
|
||||
private function synchronisiereSDLDiagramm(int $medienID, bool $energie): void
|
||||
{
|
||||
$idents = $energie
|
||||
? ['SDLLadenEnergie', 'SDLEntladenEnergie', 'SDLSOCArchiv']
|
||||
: ['SDLLeistungArchiv', 'SDLSOCArchiv'];
|
||||
$verwaltet = array_map(fn (string $ident): int => $this->GetIDForIdent($ident), $idents);
|
||||
$gewuenscht = [];
|
||||
if ($this->sdlAnzeigen('SDLDiagrammeAnzeigen')) {
|
||||
if ($energie) {
|
||||
$gewuenscht[] = $this->chartDatensatz(
|
||||
'SDLLadenEnergie', 'SDL laden', '#007C91', '~Electricity', 'bar'
|
||||
);
|
||||
$gewuenscht[] = $this->chartDatensatz(
|
||||
'SDLEntladenEnergie', 'SDL entladen', '#C43E1C', '~Electricity', 'bar'
|
||||
);
|
||||
} else {
|
||||
$gewuenscht[] = $this->chartDatensatz(
|
||||
'SDLLeistungArchiv', 'SDL Istleistung', '#007C91', '~Power'
|
||||
);
|
||||
}
|
||||
if ($this->ReadPropertyInteger('SDLSOCVariableID') > 0) {
|
||||
$soc = $this->chartDatensatz('SDLSOCArchiv', 'SDL SOC', '#C43E1C', '~Intensity.100');
|
||||
$soc['side'] = 'right';
|
||||
$gewuenscht[] = $soc;
|
||||
}
|
||||
}
|
||||
$inhalt = json_decode((string) base64_decode(IPS_GetMediaContent($medienID), true), true);
|
||||
if (!is_array($inhalt) || !isset($inhalt['datasets']) || !is_array($inhalt['datasets'])) {
|
||||
if (!$this->sdlAnzeigen('SDLDiagrammeAnzeigen')) {
|
||||
return;
|
||||
}
|
||||
throw new RuntimeException('Das bestehende Diagramm hat keine gueltigen Datensaetze.');
|
||||
}
|
||||
$alt = $inhalt['datasets'];
|
||||
$neu = [];
|
||||
foreach ($alt as $datensatz) {
|
||||
$id = (int) ($datensatz['variableID'] ?? 0);
|
||||
if (!in_array($id, $verwaltet, true)) {
|
||||
$neu[] = $datensatz;
|
||||
continue;
|
||||
}
|
||||
foreach ($gewuenscht as $index => $ziel) {
|
||||
if ($id === $ziel['variableID']) {
|
||||
$neu[] = $datensatz;
|
||||
unset($gewuenscht[$index]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
$neu = array_merge($neu, array_values($gewuenscht));
|
||||
if ($neu !== $alt) {
|
||||
$inhalt['datasets'] = $neu;
|
||||
IPS_SetMediaContent($medienID, base64_encode(json_encode($inhalt, JSON_THROW_ON_ERROR)));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user