This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
# Virtuelle Batterie
|
||||
|
||||
Installierbares IP-Symcon-Modul fuer Enelix Utils ab Version 8.0.
|
||||
|
||||
Es aggregiert mehrere physische Batterien, reserviert einen priorisierten
|
||||
SDL-Anteil und stellt dem vorhandenen Enelix-EMS-Batteriemodul generische
|
||||
Proxyregister fuer den Eigenverbrauch bereit.
|
||||
|
||||
Konfiguration, Verdrahtung und Sicherheitsverhalten stehen in
|
||||
[`docs/module/Virtuelle-Batterie/README.md`](../docs/module/Virtuelle-Batterie/README.md).
|
||||
@@ -0,0 +1,167 @@
|
||||
{
|
||||
"elements": [
|
||||
{
|
||||
"type": "Label",
|
||||
"caption": "Fasst mehrere physische Batterien zu einer virtuellen Batterie zusammen. SDL hat bei der Leistungsverteilung Vorrang."
|
||||
},
|
||||
{
|
||||
"type": "List",
|
||||
"name": "Batterieliste",
|
||||
"caption": "Physische Batterien",
|
||||
"rowCount": 5,
|
||||
"add": true,
|
||||
"delete": true,
|
||||
"columns": [
|
||||
{
|
||||
"caption": "Name",
|
||||
"name": "Name",
|
||||
"width": "150px",
|
||||
"add": "",
|
||||
"edit": {"type": "ValidationTextBox"}
|
||||
},
|
||||
{
|
||||
"caption": "Kapazitaet",
|
||||
"name": "Kapazitaet_kWh",
|
||||
"width": "110px",
|
||||
"suffix": " kWh",
|
||||
"add": 0,
|
||||
"edit": {"type": "NumberSpinner", "minimum": 0, "digits": 2}
|
||||
},
|
||||
{
|
||||
"caption": "Max. Laden",
|
||||
"name": "MaxLaden_W",
|
||||
"width": "110px",
|
||||
"suffix": " W",
|
||||
"add": 0,
|
||||
"edit": {"type": "NumberSpinner", "minimum": 0, "digits": 0}
|
||||
},
|
||||
{
|
||||
"caption": "Max. Entladen",
|
||||
"name": "MaxEntladen_W",
|
||||
"width": "120px",
|
||||
"suffix": " W",
|
||||
"add": 0,
|
||||
"edit": {"type": "NumberSpinner", "minimum": 0, "digits": 0}
|
||||
},
|
||||
{
|
||||
"caption": "Ladezustand",
|
||||
"name": "LadezustandVariableID",
|
||||
"width": "130px",
|
||||
"add": 0,
|
||||
"edit": {"type": "SelectVariable"}
|
||||
},
|
||||
{
|
||||
"caption": "Istleistung",
|
||||
"name": "IstleistungVariableID",
|
||||
"width": "130px",
|
||||
"add": 0,
|
||||
"edit": {"type": "SelectVariable"}
|
||||
},
|
||||
{
|
||||
"caption": "Sollleistung",
|
||||
"name": "SollleistungVariableID",
|
||||
"width": "140px",
|
||||
"add": 0,
|
||||
"edit": {"type": "SelectVariable"}
|
||||
}
|
||||
],
|
||||
"values": []
|
||||
},
|
||||
{
|
||||
"type": "ExpansionPanel",
|
||||
"caption": "SDL-Reserve",
|
||||
"items": [
|
||||
{
|
||||
"type": "NumberSpinner",
|
||||
"name": "SDLReserveLaden",
|
||||
"caption": "Reservierte SDL-Ladeleistung",
|
||||
"suffix": " W",
|
||||
"minimum": 0
|
||||
},
|
||||
{
|
||||
"type": "NumberSpinner",
|
||||
"name": "SDLReserveEntladen",
|
||||
"caption": "Reservierte SDL-Entladeleistung",
|
||||
"suffix": " W",
|
||||
"minimum": 0
|
||||
},
|
||||
{
|
||||
"type": "NumberSpinner",
|
||||
"name": "Reservezeit",
|
||||
"caption": "SDL-Reservezeit",
|
||||
"suffix": " h",
|
||||
"minimum": 0,
|
||||
"maximum": 24,
|
||||
"digits": 2
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "ExpansionPanel",
|
||||
"caption": "Regelung und Sicherheit",
|
||||
"items": [
|
||||
{
|
||||
"type": "NumberSpinner",
|
||||
"name": "Aktualisierungsintervall",
|
||||
"caption": "Aktualisierungsintervall",
|
||||
"suffix": " s",
|
||||
"minimum": 1
|
||||
},
|
||||
{
|
||||
"type": "NumberSpinner",
|
||||
"name": "MesswertMaxAlter",
|
||||
"caption": "Maximales Messwertalter",
|
||||
"suffix": " s",
|
||||
"minimum": 1
|
||||
},
|
||||
{
|
||||
"type": "NumberSpinner",
|
||||
"name": "VorgabeTimeout",
|
||||
"caption": "Timeout fuer EV- und SDL-Vorgaben",
|
||||
"suffix": " s",
|
||||
"minimum": 1
|
||||
},
|
||||
{
|
||||
"type": "NumberSpinner",
|
||||
"name": "EigenverbrauchStart",
|
||||
"caption": "Startwert Eigenverbrauchskonto",
|
||||
"suffix": " %",
|
||||
"minimum": 0,
|
||||
"maximum": 100,
|
||||
"digits": 1
|
||||
},
|
||||
{
|
||||
"type": "NumberSpinner",
|
||||
"name": "SDLStart",
|
||||
"caption": "Startwert SDL-Konto",
|
||||
"suffix": " %",
|
||||
"minimum": 0,
|
||||
"maximum": 100,
|
||||
"digits": 1
|
||||
},
|
||||
{
|
||||
"type": "CheckBox",
|
||||
"name": "DiagnoseAnzeigen",
|
||||
"caption": "Diagnosevariablen anzeigen"
|
||||
},
|
||||
{
|
||||
"type": "CheckBox",
|
||||
"name": "LoggingEin",
|
||||
"caption": "Debug-Logging aktivieren"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"actions": [
|
||||
{
|
||||
"type": "Button",
|
||||
"caption": "Virtuelle Konten auf Startwerte zuruecksetzen",
|
||||
"onClick": "IPS_RequestAction($id, 'KontenReset', true);"
|
||||
},
|
||||
{
|
||||
"type": "Button",
|
||||
"caption": "Jetzt aktualisieren",
|
||||
"onClick": "IPS_RequestAction($id, 'Aktualisieren', 0);"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Belevo\EnelixUtils;
|
||||
|
||||
use InvalidArgumentException;
|
||||
|
||||
final class VirtuelleBatterieRegler
|
||||
{
|
||||
/**
|
||||
* @param list<array{
|
||||
* Index:int,
|
||||
* Name:string,
|
||||
* Kapazitaet_kWh:float,
|
||||
* MaxLaden_W:float,
|
||||
* MaxEntladen_W:float,
|
||||
* Ladezustand_Prozent:float,
|
||||
* Istleistung_W:float
|
||||
* }> $batterien
|
||||
* @return array<string, float>
|
||||
*/
|
||||
public static function aggregiere(
|
||||
array $batterien,
|
||||
float $sdlReserveLadenW,
|
||||
float $sdlReserveEntladenW,
|
||||
float $reservezeitH
|
||||
): array {
|
||||
if ($sdlReserveLadenW < 0.0
|
||||
|| $sdlReserveEntladenW < 0.0
|
||||
|| $reservezeitH < 0.0
|
||||
) {
|
||||
throw new InvalidArgumentException(
|
||||
'SDL-Reserven und Reservezeit duerfen nicht negativ sein.'
|
||||
);
|
||||
}
|
||||
|
||||
$kapazitaet = 0.0;
|
||||
$energie = 0.0;
|
||||
$maxLaden = 0.0;
|
||||
$maxEntladen = 0.0;
|
||||
$istleistung = 0.0;
|
||||
|
||||
foreach ($batterien as $batterie) {
|
||||
$kapazitaetKWh = (float) $batterie['Kapazitaet_kWh'];
|
||||
$ladezustand = (float) $batterie['Ladezustand_Prozent'];
|
||||
$ladenW = (float) $batterie['MaxLaden_W'];
|
||||
$entladenW = (float) $batterie['MaxEntladen_W'];
|
||||
|
||||
if ($kapazitaetKWh <= 0.0 || $ladenW < 0.0 || $entladenW < 0.0) {
|
||||
throw new InvalidArgumentException(
|
||||
'Kapazitaet muss positiv und Leistungsgrenzen duerfen nicht negativ sein.'
|
||||
);
|
||||
}
|
||||
if ($ladezustand < 0.0 || $ladezustand > 100.0) {
|
||||
throw new InvalidArgumentException(
|
||||
'Ladezustand muss zwischen 0 und 100 Prozent liegen.'
|
||||
);
|
||||
}
|
||||
|
||||
$kapazitaet += $kapazitaetKWh;
|
||||
$energie += $kapazitaetKWh * $ladezustand / 100.0;
|
||||
$maxLaden += $ladenW;
|
||||
$maxEntladen += $entladenW;
|
||||
$istleistung += (float) $batterie['Istleistung_W'];
|
||||
}
|
||||
|
||||
if ($kapazitaet <= 0.0) {
|
||||
throw new InvalidArgumentException(
|
||||
'Mindestens eine verfuegbare Batterie ist erforderlich.'
|
||||
);
|
||||
}
|
||||
|
||||
$sdlLadenW = min($sdlReserveLadenW, $maxLaden);
|
||||
$sdlEntladenW = min($sdlReserveEntladenW, $maxEntladen);
|
||||
$untereReserve = $sdlEntladenW * $reservezeitH / 1000.0;
|
||||
$obereReserve = $sdlLadenW * $reservezeitH / 1000.0;
|
||||
|
||||
if ($untereReserve + $obereReserve > $kapazitaet) {
|
||||
throw new InvalidArgumentException(
|
||||
'Die SDL-Energiereserven sind groesser als die Gesamtkapazitaet.'
|
||||
);
|
||||
}
|
||||
|
||||
return [
|
||||
'Kapazitaet_kWh' => $kapazitaet,
|
||||
'Energie_kWh' => $energie,
|
||||
'Ladezustand_Prozent' => $energie / $kapazitaet * 100.0,
|
||||
'Istleistung_W' => $istleistung,
|
||||
'MaxLaden_W' => $maxLaden,
|
||||
'MaxEntladen_W' => $maxEntladen,
|
||||
'SDLKapazitaet_kWh' => $untereReserve + $obereReserve,
|
||||
'EigenverbrauchKapazitaet_kWh' =>
|
||||
$kapazitaet - $untereReserve - $obereReserve,
|
||||
'SDLMaxLaden_W' => $sdlLadenW,
|
||||
'SDLMaxEntladen_W' => $sdlEntladenW,
|
||||
'EigenverbrauchMaxLaden_W' => max(0.0, $maxLaden - $sdlLadenW),
|
||||
'EigenverbrauchMaxEntladen_W' => max(0.0, $maxEntladen - $sdlEntladenW),
|
||||
'UntereSDLReserve_kWh' => $untereReserve,
|
||||
'ObereSDLReserve_kWh' => $obereReserve,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* SDL wird zuerst begrenzt. Eigenverbrauch erhaelt die verbleibende
|
||||
* physische Leistung derselben Richtung.
|
||||
*
|
||||
* @param array<string, float> $aggregat
|
||||
* @return array{Eigenverbrauch_W:float, SDL_W:float, Gesamt_W:float}
|
||||
*/
|
||||
public static function kombiniere(
|
||||
float $eigenverbrauchW,
|
||||
float $sdlW,
|
||||
array $aggregat
|
||||
): array {
|
||||
$sdl = self::begrenze(
|
||||
$sdlW,
|
||||
(float) $aggregat['SDLMaxLaden_W'],
|
||||
(float) $aggregat['SDLMaxEntladen_W']
|
||||
);
|
||||
$ev = self::begrenze(
|
||||
$eigenverbrauchW,
|
||||
(float) $aggregat['EigenverbrauchMaxLaden_W'],
|
||||
(float) $aggregat['EigenverbrauchMaxEntladen_W']
|
||||
);
|
||||
|
||||
if ($sdl > 0.0 && $ev > 0.0) {
|
||||
$ev = min($ev, max(0.0, (float) $aggregat['MaxLaden_W'] - $sdl));
|
||||
} elseif ($sdl < 0.0 && $ev < 0.0) {
|
||||
$ev = max($ev, -max(
|
||||
0.0,
|
||||
(float) $aggregat['MaxEntladen_W'] - abs($sdl)
|
||||
));
|
||||
}
|
||||
|
||||
$gesamt = self::begrenze(
|
||||
$sdl + $ev,
|
||||
(float) $aggregat['MaxLaden_W'],
|
||||
(float) $aggregat['MaxEntladen_W']
|
||||
);
|
||||
|
||||
return [
|
||||
'Eigenverbrauch_W' => $ev,
|
||||
'SDL_W' => $sdl,
|
||||
'Gesamt_W' => $gesamt,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<array{
|
||||
* Index:int,
|
||||
* Name:string,
|
||||
* Kapazitaet_kWh:float,
|
||||
* MaxLaden_W:float,
|
||||
* MaxEntladen_W:float,
|
||||
* Ladezustand_Prozent:float,
|
||||
* Istleistung_W:float
|
||||
* }> $batterien
|
||||
* @return array<int, float>
|
||||
*/
|
||||
public static function verteile(float $gesamtW, array $batterien): array
|
||||
{
|
||||
$ergebnis = [];
|
||||
foreach ($batterien as $batterie) {
|
||||
$ergebnis[(int) $batterie['Index']] = 0.0;
|
||||
}
|
||||
if (abs($gesamtW) < 0.5) {
|
||||
return $ergebnis;
|
||||
}
|
||||
|
||||
$laden = $gesamtW > 0.0;
|
||||
usort(
|
||||
$batterien,
|
||||
static function (array $a, array $b) use ($laden): int {
|
||||
$vergleich = (float) $a['Ladezustand_Prozent']
|
||||
<=> (float) $b['Ladezustand_Prozent'];
|
||||
return $laden ? $vergleich : -$vergleich;
|
||||
}
|
||||
);
|
||||
|
||||
$rest = abs($gesamtW);
|
||||
foreach ($batterien as $batterie) {
|
||||
if ($rest < 0.5) {
|
||||
break;
|
||||
}
|
||||
$grenze = $laden
|
||||
? (float) $batterie['MaxLaden_W']
|
||||
: (float) $batterie['MaxEntladen_W'];
|
||||
$anteil = min($rest, max(0.0, $grenze));
|
||||
$ergebnis[(int) $batterie['Index']] = $laden ? $anteil : -$anteil;
|
||||
$rest -= $anteil;
|
||||
}
|
||||
|
||||
return $ergebnis;
|
||||
}
|
||||
|
||||
/**
|
||||
* Die SDL-Leistung wird bilanziell zuerst bedient. Die Differenz zur
|
||||
* gemessenen Gesamtleistung wird dem Eigenverbrauch zugeordnet.
|
||||
*
|
||||
* @return array{Eigenverbrauch_W:float, SDL_W:float}
|
||||
*/
|
||||
public static function ordneIstleistungZu(
|
||||
float $gesamtIstW,
|
||||
float $sdlAuftragW
|
||||
): array {
|
||||
return [
|
||||
'Eigenverbrauch_W' => $gesamtIstW - $sdlAuftragW,
|
||||
'SDL_W' => $sdlAuftragW,
|
||||
];
|
||||
}
|
||||
|
||||
private static function begrenze(
|
||||
float $leistungW,
|
||||
float $maxLadenW,
|
||||
float $maxEntladenW
|
||||
): float {
|
||||
return min($maxLadenW, max(-$maxEntladenW, $leistungW));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"id": "{5B2A199B-2226-411F-B83B-FC1CBA58496B}",
|
||||
"name": "Virtuelle Batterie",
|
||||
"type": 3,
|
||||
"vendor": "Enelix",
|
||||
"aliases": [],
|
||||
"parentRequirements": [],
|
||||
"childRequirements": [],
|
||||
"implemented": [],
|
||||
"prefix": "ENLVB",
|
||||
"url": ""
|
||||
}
|
||||
@@ -0,0 +1,781 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/libs/VirtuelleBatterieRegler.php';
|
||||
|
||||
use Belevo\EnelixUtils\VirtuelleBatterieRegler;
|
||||
|
||||
class VirtuelleBatterie extends IPSModule
|
||||
{
|
||||
private const STATUS_AKTIV = 102;
|
||||
private const STATUS_INAKTIV = 104;
|
||||
private const STATUS_KONFIGURATION_UNGUELTIG = 201;
|
||||
private const STATUS_MESSWERTE_UNGUELTIG = 202;
|
||||
private const STATUS_AUSGABE_FEHLER = 203;
|
||||
private const VM_UPDATE = 10603;
|
||||
|
||||
public function Create(): void
|
||||
{
|
||||
parent::Create();
|
||||
|
||||
$this->RegisterPropertyString('Batterieliste', '[]');
|
||||
$this->RegisterPropertyInteger('SDLReserveLaden', 0);
|
||||
$this->RegisterPropertyInteger('SDLReserveEntladen', 0);
|
||||
$this->RegisterPropertyFloat('Reservezeit', 0.5);
|
||||
$this->RegisterPropertyInteger('Aktualisierungsintervall', 2);
|
||||
$this->RegisterPropertyInteger('MesswertMaxAlter', 30);
|
||||
$this->RegisterPropertyInteger('VorgabeTimeout', 30);
|
||||
$this->RegisterPropertyFloat('EigenverbrauchStart', 50.0);
|
||||
$this->RegisterPropertyFloat('SDLStart', 50.0);
|
||||
$this->RegisterPropertyBoolean('DiagnoseAnzeigen', false);
|
||||
$this->RegisterPropertyBoolean('LoggingEin', false);
|
||||
|
||||
$this->RegisterVariableBoolean('Aktiv', 'Aktiv', '~Switch', 10);
|
||||
$this->EnableAction('Aktiv');
|
||||
|
||||
$this->RegisterVariableInteger(
|
||||
'EVManagement',
|
||||
'EV Managementregister',
|
||||
'',
|
||||
20
|
||||
);
|
||||
$this->EnableAction('EVManagement');
|
||||
$this->RegisterVariableInteger('EVModus', 'EV Modusregister', '', 30);
|
||||
$this->EnableAction('EVModus');
|
||||
$this->RegisterVariableFloat(
|
||||
'EVLadeleistung',
|
||||
'EV Laderegister',
|
||||
'',
|
||||
40
|
||||
);
|
||||
$this->EnableAction('EVLadeleistung');
|
||||
$this->RegisterVariableFloat(
|
||||
'EVEntladeleistung',
|
||||
'EV Entladeregister',
|
||||
'',
|
||||
50
|
||||
);
|
||||
$this->EnableAction('EVEntladeleistung');
|
||||
|
||||
$this->RegisterVariableFloat(
|
||||
'SDLSollleistung',
|
||||
'SDL Sollleistung',
|
||||
'',
|
||||
60
|
||||
);
|
||||
$this->EnableAction('SDLSollleistung');
|
||||
|
||||
$this->RegisterVariableFloat(
|
||||
'GesamtLadezustand',
|
||||
'Gesamtladezustand',
|
||||
'',
|
||||
100
|
||||
);
|
||||
$this->RegisterVariableFloat(
|
||||
'GesamtIstleistung',
|
||||
'Gesamte Istleistung',
|
||||
'',
|
||||
110
|
||||
);
|
||||
$this->RegisterVariableFloat(
|
||||
'GesamtMaxLaden',
|
||||
'Gesamte maximale Ladeleistung',
|
||||
'',
|
||||
120
|
||||
);
|
||||
$this->RegisterVariableFloat(
|
||||
'GesamtMaxEntladen',
|
||||
'Gesamte maximale Entladeleistung',
|
||||
'',
|
||||
130
|
||||
);
|
||||
|
||||
$this->RegisterVariableFloat(
|
||||
'EigenverbrauchLadezustand',
|
||||
'Eigenverbrauch Ladezustand',
|
||||
'',
|
||||
200
|
||||
);
|
||||
$this->RegisterVariableFloat(
|
||||
'EigenverbrauchSollleistung',
|
||||
'Eigenverbrauch Sollleistung',
|
||||
'',
|
||||
210
|
||||
);
|
||||
$this->RegisterVariableFloat(
|
||||
'EigenverbrauchIstleistung',
|
||||
'Eigenverbrauch Istleistung',
|
||||
'',
|
||||
220
|
||||
);
|
||||
$this->RegisterVariableFloat(
|
||||
'EigenverbrauchMaxLaden',
|
||||
'Eigenverbrauch maximal Laden',
|
||||
'',
|
||||
230
|
||||
);
|
||||
$this->RegisterVariableFloat(
|
||||
'EigenverbrauchMaxEntladen',
|
||||
'Eigenverbrauch maximal Entladen',
|
||||
'',
|
||||
240
|
||||
);
|
||||
|
||||
$this->RegisterVariableFloat(
|
||||
'SDLLadezustand',
|
||||
'SDL Ladezustand',
|
||||
'',
|
||||
300
|
||||
);
|
||||
$this->RegisterVariableFloat(
|
||||
'SDLIstleistung',
|
||||
'SDL Istleistung',
|
||||
'',
|
||||
310
|
||||
);
|
||||
$this->RegisterVariableFloat(
|
||||
'SDLMaxLaden',
|
||||
'SDL maximal Laden',
|
||||
'',
|
||||
320
|
||||
);
|
||||
$this->RegisterVariableFloat(
|
||||
'SDLMaxEntladen',
|
||||
'SDL maximal Entladen',
|
||||
'',
|
||||
330
|
||||
);
|
||||
|
||||
$this->RegisterVariableBoolean('Stoerung', 'Stoerung', '~Switch', 400);
|
||||
$this->RegisterVariableString('Stoertext', 'Stoertext', '', 410);
|
||||
$this->RegisterVariableString(
|
||||
'Verteilstatus',
|
||||
'Verteilstatus',
|
||||
'',
|
||||
420
|
||||
);
|
||||
|
||||
$this->RegisterAttributeString('RegistrierteMesswerte', '[]');
|
||||
$this->RegisterAttributeString('LetzteZielwerte', '{}');
|
||||
$this->RegisterAttributeFloat('EigenverbrauchEnergie_kWh', 0.0);
|
||||
$this->RegisterAttributeFloat('SDLEnergie_kWh', 0.0);
|
||||
$this->RegisterAttributeBoolean('KontenInitialisiert', false);
|
||||
$this->RegisterAttributeInteger('LetzteIntegration', 0);
|
||||
$this->RegisterAttributeInteger('LetzteEVVorgabe', 0);
|
||||
$this->RegisterAttributeInteger('LetzteSDLVorgabe', 0);
|
||||
$this->RegisterAttributeFloat('EigenverbrauchAuftrag_W', 0.0);
|
||||
|
||||
$this->RegisterTimer(
|
||||
'Aktualisierung',
|
||||
0,
|
||||
'IPS_RequestAction($_IPS["TARGET"], "Aktualisieren", 0);'
|
||||
);
|
||||
$this->RegisterTimer(
|
||||
'EVUebernahme',
|
||||
0,
|
||||
'IPS_RequestAction($_IPS["TARGET"], "EVUebernehmen", 0);'
|
||||
);
|
||||
}
|
||||
|
||||
public function ApplyChanges(): void
|
||||
{
|
||||
parent::ApplyChanges();
|
||||
|
||||
$this->SetTimerInterval('Aktualisierung', 0);
|
||||
$this->SetTimerInterval('EVUebernahme', 0);
|
||||
$this->registriereMesswertmeldungen();
|
||||
|
||||
try {
|
||||
$this->pruefeKonfiguration();
|
||||
$this->SetTimerInterval(
|
||||
'Aktualisierung',
|
||||
$this->ReadPropertyInteger('Aktualisierungsintervall') * 1000
|
||||
);
|
||||
$this->aktualisiere();
|
||||
} catch (Throwable $fehler) {
|
||||
$this->sichererZustand($fehler->getMessage());
|
||||
$this->SetStatus(self::STATUS_KONFIGURATION_UNGUELTIG);
|
||||
}
|
||||
}
|
||||
|
||||
public function MessageSink($zeitstempel, $senderID, $nachricht, $daten): void
|
||||
{
|
||||
if ((int) $nachricht !== self::VM_UPDATE) {
|
||||
return;
|
||||
}
|
||||
$ids = json_decode(
|
||||
$this->ReadAttributeString('RegistrierteMesswerte'),
|
||||
true
|
||||
);
|
||||
if (is_array($ids) && in_array((int) $senderID, $ids, true)) {
|
||||
$this->aktualisiere();
|
||||
}
|
||||
}
|
||||
|
||||
public function RequestAction($ident, $wert): void
|
||||
{
|
||||
switch ($ident) {
|
||||
case 'Aktiv':
|
||||
$this->SetValue('Aktiv', (bool) $wert);
|
||||
if (!(bool) $wert) {
|
||||
$this->WriteAttributeFloat('EigenverbrauchAuftrag_W', 0.0);
|
||||
$this->SetValue('SDLSollleistung', 0.0);
|
||||
$this->sichererZustand('');
|
||||
$this->SetStatus(self::STATUS_INAKTIV);
|
||||
return;
|
||||
}
|
||||
$this->aktualisiere();
|
||||
return;
|
||||
|
||||
case 'EVManagement':
|
||||
$this->SetValue('EVManagement', (int) $wert);
|
||||
$this->uebernehmeEVVorgabe();
|
||||
return;
|
||||
|
||||
case 'EVModus':
|
||||
$this->SetValue('EVModus', (int) $wert);
|
||||
$this->planeEVUebernahme();
|
||||
return;
|
||||
|
||||
case 'EVLadeleistung':
|
||||
$this->SetValue(
|
||||
'EVLadeleistung',
|
||||
max(0.0, (float) $wert)
|
||||
);
|
||||
$this->planeEVUebernahme();
|
||||
return;
|
||||
|
||||
case 'EVEntladeleistung':
|
||||
$this->SetValue(
|
||||
'EVEntladeleistung',
|
||||
max(0.0, (float) $wert)
|
||||
);
|
||||
$this->planeEVUebernahme();
|
||||
return;
|
||||
|
||||
case 'SDLSollleistung':
|
||||
$this->SetValue('SDLSollleistung', (float) $wert);
|
||||
$this->WriteAttributeInteger('LetzteSDLVorgabe', time());
|
||||
$this->aktualisiere();
|
||||
return;
|
||||
|
||||
case 'EVUebernehmen':
|
||||
$this->SetTimerInterval('EVUebernahme', 0);
|
||||
$this->uebernehmeEVVorgabe();
|
||||
return;
|
||||
|
||||
case 'Aktualisieren':
|
||||
$this->aktualisiere();
|
||||
return;
|
||||
|
||||
case 'KontenReset':
|
||||
$this->WriteAttributeBoolean('KontenInitialisiert', false);
|
||||
$this->WriteAttributeInteger('LetzteIntegration', time());
|
||||
$this->aktualisiere();
|
||||
return;
|
||||
}
|
||||
|
||||
throw new InvalidArgumentException('Unbekannte Aktion: ' . $ident);
|
||||
}
|
||||
|
||||
private function planeEVUebernahme(): void
|
||||
{
|
||||
$this->SetTimerInterval('EVUebernahme', 50);
|
||||
}
|
||||
|
||||
private function uebernehmeEVVorgabe(): void
|
||||
{
|
||||
$management = (int) $this->GetValue('EVManagement');
|
||||
$modus = (int) $this->GetValue('EVModus');
|
||||
$ziel = 0.0;
|
||||
if ($management === 1) {
|
||||
$ziel = $modus === 1
|
||||
? -(float) $this->GetValue('EVEntladeleistung')
|
||||
: (float) $this->GetValue('EVLadeleistung');
|
||||
}
|
||||
|
||||
$this->WriteAttributeFloat('EigenverbrauchAuftrag_W', $ziel);
|
||||
$this->WriteAttributeInteger('LetzteEVVorgabe', time());
|
||||
$this->SetValue('EigenverbrauchSollleistung', $ziel);
|
||||
$this->aktualisiere();
|
||||
}
|
||||
|
||||
private function aktualisiere(): void
|
||||
{
|
||||
$semaphore = 'EnelixVirtuelleBatterie_' . $this->InstanceID;
|
||||
if (!IPS_SemaphoreEnter($semaphore, 1000)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (!(bool) $this->GetValue('Aktiv')) {
|
||||
$this->sichererZustand('');
|
||||
$this->SetStatus(self::STATUS_INAKTIV);
|
||||
return;
|
||||
}
|
||||
|
||||
$this->pruefeKonfiguration();
|
||||
[$batterien, $fehler] = $this->leseBatterien();
|
||||
if ($batterien === []) {
|
||||
throw new RuntimeException(
|
||||
'Keine Batterie besitzt aktuelle gueltige Messwerte.'
|
||||
);
|
||||
}
|
||||
|
||||
$aggregat = VirtuelleBatterieRegler::aggregiere(
|
||||
$batterien,
|
||||
(float) $this->ReadPropertyInteger('SDLReserveLaden'),
|
||||
(float) $this->ReadPropertyInteger('SDLReserveEntladen'),
|
||||
$this->ReadPropertyFloat('Reservezeit')
|
||||
);
|
||||
$this->initialisiereKonten($aggregat);
|
||||
|
||||
$jetzt = time();
|
||||
$timeout = $this->ReadPropertyInteger('VorgabeTimeout');
|
||||
$ev = $jetzt - $this->ReadAttributeInteger('LetzteEVVorgabe')
|
||||
<= $timeout
|
||||
? $this->ReadAttributeFloat('EigenverbrauchAuftrag_W')
|
||||
: 0.0;
|
||||
$sdl = $jetzt - $this->ReadAttributeInteger('LetzteSDLVorgabe')
|
||||
<= $timeout
|
||||
? (float) $this->GetValue('SDLSollleistung')
|
||||
: 0.0;
|
||||
|
||||
$auftrag = VirtuelleBatterieRegler::kombiniere(
|
||||
$ev,
|
||||
$sdl,
|
||||
$aggregat
|
||||
);
|
||||
$verteilung = VirtuelleBatterieRegler::verteile(
|
||||
$auftrag['Gesamt_W'],
|
||||
$batterien
|
||||
);
|
||||
$this->schreibeVerteilung($verteilung);
|
||||
$zuordnung = VirtuelleBatterieRegler::ordneIstleistungZu(
|
||||
$aggregat['Istleistung_W'],
|
||||
$auftrag['SDL_W']
|
||||
);
|
||||
$integrationsfehler = $this->integriereKonten(
|
||||
$zuordnung,
|
||||
$aggregat,
|
||||
$jetzt
|
||||
);
|
||||
|
||||
$this->SetValue(
|
||||
'GesamtLadezustand',
|
||||
$aggregat['Ladezustand_Prozent']
|
||||
);
|
||||
$this->SetValue(
|
||||
'GesamtIstleistung',
|
||||
$aggregat['Istleistung_W']
|
||||
);
|
||||
$this->SetValue('GesamtMaxLaden', $aggregat['MaxLaden_W']);
|
||||
$this->SetValue(
|
||||
'GesamtMaxEntladen',
|
||||
$aggregat['MaxEntladen_W']
|
||||
);
|
||||
$this->SetValue(
|
||||
'EigenverbrauchSollleistung',
|
||||
$auftrag['Eigenverbrauch_W']
|
||||
);
|
||||
$this->SetValue(
|
||||
'EigenverbrauchIstleistung',
|
||||
$zuordnung['Eigenverbrauch_W']
|
||||
);
|
||||
$this->SetValue(
|
||||
'EigenverbrauchMaxLaden',
|
||||
$aggregat['EigenverbrauchMaxLaden_W']
|
||||
);
|
||||
$this->SetValue(
|
||||
'EigenverbrauchMaxEntladen',
|
||||
$aggregat['EigenverbrauchMaxEntladen_W']
|
||||
);
|
||||
$this->SetValue('SDLIstleistung', $zuordnung['SDL_W']);
|
||||
$this->SetValue('SDLMaxLaden', $aggregat['SDLMaxLaden_W']);
|
||||
$this->SetValue(
|
||||
'SDLMaxEntladen',
|
||||
$aggregat['SDLMaxEntladen_W']
|
||||
);
|
||||
$this->aktualisiereKontenanzeigen($aggregat);
|
||||
|
||||
$status = [
|
||||
'Eigenverbrauch_W' => round($auftrag['Eigenverbrauch_W'], 1),
|
||||
'SDL_W' => round($auftrag['SDL_W'], 1),
|
||||
'Gesamt_W' => round($auftrag['Gesamt_W'], 1),
|
||||
'Batterien' => count($batterien),
|
||||
'Verteilung_W' => $verteilung,
|
||||
];
|
||||
$this->SetValue(
|
||||
'Verteilstatus',
|
||||
json_encode($status, JSON_THROW_ON_ERROR)
|
||||
);
|
||||
|
||||
$fehler = array_values(array_filter(array_merge(
|
||||
$fehler,
|
||||
$integrationsfehler === '' ? [] : [$integrationsfehler]
|
||||
)));
|
||||
$this->setzeFehler(implode(' ', $fehler));
|
||||
$this->SetStatus(
|
||||
$fehler === []
|
||||
? self::STATUS_AKTIV
|
||||
: self::STATUS_MESSWERTE_UNGUELTIG
|
||||
);
|
||||
$this->protokolliere('Verteilung', $status);
|
||||
} catch (InvalidArgumentException $fehler) {
|
||||
$this->sichererZustand($fehler->getMessage());
|
||||
$this->SetStatus(self::STATUS_KONFIGURATION_UNGUELTIG);
|
||||
} catch (Throwable $fehler) {
|
||||
$this->sichererZustand($fehler->getMessage());
|
||||
$this->SetStatus(self::STATUS_AUSGABE_FEHLER);
|
||||
} finally {
|
||||
IPS_SemaphoreLeave($semaphore);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{0:list<array<string, int|float|string>>, 1:list<string>}
|
||||
*/
|
||||
private function leseBatterien(): array
|
||||
{
|
||||
$konfiguration = $this->leseBatterieliste();
|
||||
$batterien = [];
|
||||
$fehler = [];
|
||||
$maxAlter = $this->ReadPropertyInteger('MesswertMaxAlter');
|
||||
|
||||
foreach ($konfiguration as $index => $eintrag) {
|
||||
$name = trim((string) ($eintrag['Name'] ?? ''));
|
||||
if ($name === '') {
|
||||
$name = 'Batterie ' . ($index + 1);
|
||||
}
|
||||
$socID = (int) ($eintrag['LadezustandVariableID'] ?? 0);
|
||||
$leistungID = (int) ($eintrag['IstleistungVariableID'] ?? 0);
|
||||
if (!$this->istAktuellerMesswert($socID, $maxAlter)
|
||||
|| !$this->istAktuellerMesswert($leistungID, $maxAlter)
|
||||
) {
|
||||
$fehler[] = $name . ': Messwerte fehlen oder sind veraltet.';
|
||||
continue;
|
||||
}
|
||||
|
||||
$batterien[] = [
|
||||
'Index' => $index,
|
||||
'Name' => $name,
|
||||
'Kapazitaet_kWh' =>
|
||||
(float) ($eintrag['Kapazitaet_kWh'] ?? 0.0),
|
||||
'MaxLaden_W' => (float) ($eintrag['MaxLaden_W'] ?? 0.0),
|
||||
'MaxEntladen_W' =>
|
||||
(float) ($eintrag['MaxEntladen_W'] ?? 0.0),
|
||||
'Ladezustand_Prozent' => (float) GetValue($socID),
|
||||
'Istleistung_W' => (float) GetValue($leistungID),
|
||||
];
|
||||
}
|
||||
|
||||
return [$batterien, $fehler];
|
||||
}
|
||||
|
||||
/** @return list<array<string, mixed>> */
|
||||
private function leseBatterieliste(): array
|
||||
{
|
||||
$liste = json_decode(
|
||||
$this->ReadPropertyString('Batterieliste'),
|
||||
true,
|
||||
512,
|
||||
JSON_THROW_ON_ERROR
|
||||
);
|
||||
if (!is_array($liste)) {
|
||||
throw new InvalidArgumentException(
|
||||
'Batterieliste muss eine JSON-Liste sein.'
|
||||
);
|
||||
}
|
||||
|
||||
return array_values($liste);
|
||||
}
|
||||
|
||||
private function pruefeKonfiguration(): void
|
||||
{
|
||||
foreach ([
|
||||
'Aktualisierungsintervall',
|
||||
'MesswertMaxAlter',
|
||||
'VorgabeTimeout',
|
||||
] as $property) {
|
||||
if ($this->ReadPropertyInteger($property) <= 0) {
|
||||
throw new InvalidArgumentException(
|
||||
$property . ' muss groesser als 0 sein.'
|
||||
);
|
||||
}
|
||||
}
|
||||
if ($this->ReadPropertyFloat('Reservezeit') < 0.0) {
|
||||
throw new InvalidArgumentException(
|
||||
'Reservezeit darf nicht negativ sein.'
|
||||
);
|
||||
}
|
||||
|
||||
$liste = $this->leseBatterieliste();
|
||||
if ($liste === []) {
|
||||
throw new InvalidArgumentException(
|
||||
'Mindestens eine physische Batterie ist erforderlich.'
|
||||
);
|
||||
}
|
||||
|
||||
$zielIDs = [];
|
||||
foreach ($liste as $index => $eintrag) {
|
||||
$name = 'Batterie ' . ($index + 1);
|
||||
if ((float) ($eintrag['Kapazitaet_kWh'] ?? 0.0) <= 0.0) {
|
||||
throw new InvalidArgumentException(
|
||||
$name . ': Kapazitaet muss groesser als 0 sein.'
|
||||
);
|
||||
}
|
||||
foreach ([
|
||||
'LadezustandVariableID',
|
||||
'IstleistungVariableID',
|
||||
] as $feld) {
|
||||
$this->pruefeNumerischeVariable(
|
||||
(int) ($eintrag[$feld] ?? 0),
|
||||
$name . ' ' . $feld,
|
||||
false
|
||||
);
|
||||
}
|
||||
$zielID = (int) ($eintrag['SollleistungVariableID'] ?? 0);
|
||||
$this->pruefeNumerischeVariable(
|
||||
$zielID,
|
||||
$name . ' SollleistungVariableID',
|
||||
true
|
||||
);
|
||||
if (isset($zielIDs[$zielID])) {
|
||||
throw new InvalidArgumentException(
|
||||
'Jede Batterie benoetigt eine eigene Sollleistungsvariable.'
|
||||
);
|
||||
}
|
||||
$zielIDs[$zielID] = true;
|
||||
}
|
||||
}
|
||||
|
||||
private function pruefeNumerischeVariable(
|
||||
int $id,
|
||||
string $name,
|
||||
bool $aktion
|
||||
): void {
|
||||
if ($id <= 0 || !IPS_VariableExists($id)) {
|
||||
throw new InvalidArgumentException($name . ' ist nicht eingerichtet.');
|
||||
}
|
||||
$variable = IPS_GetVariable($id);
|
||||
if (!in_array((int) $variable['VariableType'], [1, 2], true)) {
|
||||
throw new InvalidArgumentException($name . ' muss numerisch sein.');
|
||||
}
|
||||
if ($aktion
|
||||
&& (int) ($variable['VariableAction'] ?? 0) <= 0
|
||||
&& (int) ($variable['VariableCustomAction'] ?? 0) <= 0
|
||||
) {
|
||||
throw new InvalidArgumentException(
|
||||
$name . ' benoetigt eine Variablenaktion.'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private function istAktuellerMesswert(int $id, int $maxAlter): bool
|
||||
{
|
||||
if ($id <= 0 || !IPS_VariableExists($id)) {
|
||||
return false;
|
||||
}
|
||||
$variable = IPS_GetVariable($id);
|
||||
if (!in_array((int) $variable['VariableType'], [1, 2], true)) {
|
||||
return false;
|
||||
}
|
||||
$aktualisiert = (int) ($variable['VariableUpdated'] ?? 0);
|
||||
|
||||
return $aktualisiert > 0 && time() - $aktualisiert <= $maxAlter;
|
||||
}
|
||||
|
||||
/** @param array<int, float> $verteilung */
|
||||
private function schreibeVerteilung(array $verteilung): void
|
||||
{
|
||||
$alt = json_decode(
|
||||
$this->ReadAttributeString('LetzteZielwerte'),
|
||||
true
|
||||
);
|
||||
if (!is_array($alt)) {
|
||||
$alt = [];
|
||||
}
|
||||
$neu = [];
|
||||
foreach ($this->leseBatterieliste() as $index => $eintrag) {
|
||||
$zielID = (int) ($eintrag['SollleistungVariableID'] ?? 0);
|
||||
$ziel = (float) ($verteilung[$index] ?? 0.0);
|
||||
$neu[(string) $zielID] = $ziel;
|
||||
if (!isset($alt[(string) $zielID])
|
||||
|| abs((float) $alt[(string) $zielID] - $ziel) >= 0.5
|
||||
) {
|
||||
RequestAction($zielID, $ziel);
|
||||
}
|
||||
}
|
||||
$this->WriteAttributeString(
|
||||
'LetzteZielwerte',
|
||||
json_encode($neu, JSON_THROW_ON_ERROR)
|
||||
);
|
||||
}
|
||||
|
||||
private function sichererZustand(string $fehler): void
|
||||
{
|
||||
try {
|
||||
$this->schreibeVerteilung([]);
|
||||
} catch (Throwable $ausgabeFehler) {
|
||||
if ($fehler === '') {
|
||||
$fehler = $ausgabeFehler->getMessage();
|
||||
} else {
|
||||
$fehler .= ' ' . $ausgabeFehler->getMessage();
|
||||
}
|
||||
}
|
||||
$this->SetValue('EigenverbrauchSollleistung', 0.0);
|
||||
$this->SetValue('EigenverbrauchIstleistung', 0.0);
|
||||
$this->SetValue('SDLIstleistung', 0.0);
|
||||
$this->setzeFehler($fehler);
|
||||
}
|
||||
|
||||
/** @param array<string, float> $aggregat */
|
||||
private function initialisiereKonten(array $aggregat): void
|
||||
{
|
||||
if ($this->ReadAttributeBoolean('KontenInitialisiert')) {
|
||||
return;
|
||||
}
|
||||
$evProzent = min(
|
||||
100.0,
|
||||
max(0.0, $this->ReadPropertyFloat('EigenverbrauchStart'))
|
||||
);
|
||||
$sdlProzent = min(
|
||||
100.0,
|
||||
max(0.0, $this->ReadPropertyFloat('SDLStart'))
|
||||
);
|
||||
$this->WriteAttributeFloat(
|
||||
'EigenverbrauchEnergie_kWh',
|
||||
$aggregat['EigenverbrauchKapazitaet_kWh'] * $evProzent / 100.0
|
||||
);
|
||||
$this->WriteAttributeFloat(
|
||||
'SDLEnergie_kWh',
|
||||
$aggregat['SDLKapazitaet_kWh'] * $sdlProzent / 100.0
|
||||
);
|
||||
$this->WriteAttributeInteger('LetzteIntegration', time());
|
||||
$this->WriteAttributeBoolean('KontenInitialisiert', true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array{Eigenverbrauch_W:float, SDL_W:float} $zuordnung
|
||||
* @param array<string, float> $aggregat
|
||||
*/
|
||||
private function integriereKonten(
|
||||
array $zuordnung,
|
||||
array $aggregat,
|
||||
int $jetzt
|
||||
): string {
|
||||
$letzteZeit = $this->ReadAttributeInteger('LetzteIntegration');
|
||||
$this->WriteAttributeInteger('LetzteIntegration', $jetzt);
|
||||
if ($letzteZeit <= 0 || $jetzt <= $letzteZeit) {
|
||||
return '';
|
||||
}
|
||||
$dauer = $jetzt - $letzteZeit;
|
||||
$maxLuecke = max(
|
||||
$this->ReadPropertyInteger('MesswertMaxAlter'),
|
||||
$this->ReadPropertyInteger('Aktualisierungsintervall') * 3
|
||||
);
|
||||
if ($dauer > $maxLuecke) {
|
||||
return 'Integrationsluecke erkannt; Konten wurden nicht fortgeschrieben.';
|
||||
}
|
||||
|
||||
$evKapazitaet = $aggregat['EigenverbrauchKapazitaet_kWh'];
|
||||
$sdlKapazitaet = $aggregat['SDLKapazitaet_kWh'];
|
||||
$evEnergie = $this->ReadAttributeFloat('EigenverbrauchEnergie_kWh')
|
||||
+ $zuordnung['Eigenverbrauch_W'] * $dauer / 3600000.0;
|
||||
$sdlEnergie = $this->ReadAttributeFloat('SDLEnergie_kWh')
|
||||
+ $zuordnung['SDL_W'] * $dauer / 3600000.0;
|
||||
$this->WriteAttributeFloat(
|
||||
'EigenverbrauchEnergie_kWh',
|
||||
min($evKapazitaet, max(0.0, $evEnergie))
|
||||
);
|
||||
$this->WriteAttributeFloat(
|
||||
'SDLEnergie_kWh',
|
||||
min($sdlKapazitaet, max(0.0, $sdlEnergie))
|
||||
);
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/** @param array<string, float> $aggregat */
|
||||
private function aktualisiereKontenanzeigen(array $aggregat): void
|
||||
{
|
||||
$evKapazitaet = $aggregat['EigenverbrauchKapazitaet_kWh'];
|
||||
$sdlKapazitaet = $aggregat['SDLKapazitaet_kWh'];
|
||||
$this->SetValue(
|
||||
'EigenverbrauchLadezustand',
|
||||
$evKapazitaet > 0.0
|
||||
? $this->ReadAttributeFloat('EigenverbrauchEnergie_kWh')
|
||||
/ $evKapazitaet * 100.0
|
||||
: 0.0
|
||||
);
|
||||
$this->SetValue(
|
||||
'SDLLadezustand',
|
||||
$sdlKapazitaet > 0.0
|
||||
? $this->ReadAttributeFloat('SDLEnergie_kWh')
|
||||
/ $sdlKapazitaet * 100.0
|
||||
: 0.0
|
||||
);
|
||||
}
|
||||
|
||||
private function registriereMesswertmeldungen(): void
|
||||
{
|
||||
$alteIDs = json_decode(
|
||||
$this->ReadAttributeString('RegistrierteMesswerte'),
|
||||
true
|
||||
);
|
||||
if (!is_array($alteIDs)) {
|
||||
$alteIDs = [];
|
||||
}
|
||||
$neueIDs = [];
|
||||
try {
|
||||
foreach ($this->leseBatterieliste() as $eintrag) {
|
||||
foreach ([
|
||||
'LadezustandVariableID',
|
||||
'IstleistungVariableID',
|
||||
] as $feld) {
|
||||
$id = (int) ($eintrag[$feld] ?? 0);
|
||||
if ($id > 0 && IPS_VariableExists($id)) {
|
||||
$neueIDs[] = $id;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Throwable $fehler) {
|
||||
$neueIDs = [];
|
||||
}
|
||||
$neueIDs = array_values(array_unique($neueIDs));
|
||||
|
||||
foreach ($alteIDs as $id) {
|
||||
if (!in_array((int) $id, $neueIDs, true)) {
|
||||
$this->UnregisterMessage((int) $id, self::VM_UPDATE);
|
||||
}
|
||||
}
|
||||
foreach ($neueIDs as $id) {
|
||||
if (!in_array($id, $alteIDs, true)) {
|
||||
$this->RegisterMessage($id, self::VM_UPDATE);
|
||||
}
|
||||
}
|
||||
$this->WriteAttributeString(
|
||||
'RegistrierteMesswerte',
|
||||
json_encode($neueIDs, JSON_THROW_ON_ERROR)
|
||||
);
|
||||
}
|
||||
|
||||
private function setzeFehler(string $text): void
|
||||
{
|
||||
$this->SetValue('Stoerung', $text !== '');
|
||||
$this->SetValue('Stoertext', $text);
|
||||
}
|
||||
|
||||
/** @param mixed $daten */
|
||||
private function protokolliere(string $titel, $daten): void
|
||||
{
|
||||
if (!$this->ReadPropertyBoolean('LoggingEin')) {
|
||||
return;
|
||||
}
|
||||
$text = is_string($daten)
|
||||
? $daten
|
||||
: json_encode($daten, JSON_THROW_ON_ERROR);
|
||||
$this->SendDebug($titel, $text, 0);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user