This commit is contained in:
@@ -0,0 +1,985 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/../libs/VerbraucherSchnittstelle.php';
|
||||
require_once __DIR__ . '/../libs/VerbraucherBasisTrait.php';
|
||||
require_once __DIR__ . '/../libs/Nachrichtenvertrag.php';
|
||||
require_once __DIR__ . '/../libs/LadestationRegler.php';
|
||||
require_once __DIR__ . '/../libs/EaseeLadestatus.php';
|
||||
require_once __DIR__ . '/../libs/EaseeGatewayProtokoll.php';
|
||||
|
||||
use Belevo\EnelixEMS\EaseeGatewayProtokoll;
|
||||
use Belevo\EnelixEMS\EaseeLadestatus;
|
||||
use Belevo\EnelixEMS\LadestationRegler;
|
||||
use Belevo\EnelixEMS\Nachrichtenvertrag;
|
||||
use Belevo\EnelixEMS\VerbraucherBasisTrait;
|
||||
use Belevo\EnelixEMS\VerbraucherSchnittstelle;
|
||||
|
||||
class LadestationGateway extends IPSModule implements VerbraucherSchnittstelle
|
||||
{
|
||||
use VerbraucherBasisTrait;
|
||||
|
||||
private const MANAGER_MODULE_ID = '{6F771B18-59D4-4C8A-B951-3B2FE9F6A2C4}';
|
||||
private const EASEE_GATEWAY_MODULE_ID = '{B7552FC9-87BC-49D0-8CCB-076BBB045BBE}';
|
||||
private const GATEWAY_REQUEST_DATA_ID = '{7AEF3DF7-DA5B-47C5-BCDC-0110D06DDC04}';
|
||||
private const STATUS_AKTIV = 102;
|
||||
private const STATUS_KONFIGURATION_UNGUELTIG = 201;
|
||||
private const STATUS_GATEWAY_GETRENNT = 202;
|
||||
private const STATUS_WARTET_AUF_DATEN = 203;
|
||||
private const STATUS_LADEFEHLER = 204;
|
||||
|
||||
/** @var list<string> */
|
||||
private const DIAGNOSE_VARIABLEN = [
|
||||
'Istleistung',
|
||||
'Leistungsquelle',
|
||||
'Sollleistung',
|
||||
'SollwertGueltig',
|
||||
'Verfuegbar',
|
||||
'AenderungMoeglich',
|
||||
'Stoerung',
|
||||
'Stoertext',
|
||||
'GatewayVerbunden',
|
||||
'ApiMaximalstrom',
|
||||
'LetzterGeraetebefehl',
|
||||
'LeistungsangebotDiagnose',
|
||||
];
|
||||
|
||||
public function Create(): void
|
||||
{
|
||||
parent::Create();
|
||||
|
||||
$this->registriereVerbraucherBasis();
|
||||
$this->RegisterPropertyInteger('Betriebsmodus', EaseeLadestatus::BETRIEBSMODUS_EASEE);
|
||||
$this->RegisterPropertyString('Ladestationskennung', '');
|
||||
$this->RegisterPropertyInteger('MaximalerLadestrom', 16);
|
||||
$this->RegisterPropertyBoolean('Ladefreigabe', false);
|
||||
$this->RegisterPropertyBoolean('Solarladen', true);
|
||||
$this->RegisterPropertyBoolean('DiagnosevariablenAnzeigen', false);
|
||||
|
||||
$this->RegisterVariableBoolean(
|
||||
'FahrzeugVerbunden',
|
||||
'Fahrzeug verbunden',
|
||||
'~Switch',
|
||||
100
|
||||
);
|
||||
$this->RegisterVariableBoolean(
|
||||
'FahrzeugGeladen',
|
||||
'Fahrzeug geladen',
|
||||
'~Switch',
|
||||
110
|
||||
);
|
||||
$this->RegisterVariableInteger('Fahrzeugstatus', 'Fahrzeugstatus', '', 120);
|
||||
$this->RegisterVariableFloat('Ladestrom', 'Ladestrom', '', 130);
|
||||
$this->RegisterVariableInteger('Phasenzahl', 'Phasenzahl', '', 140);
|
||||
|
||||
$this->RegisterAttributeBoolean('InitialwerteGesetzt', false);
|
||||
$this->RegisterAttributeBoolean('KonfigurationLadefreigabe', false);
|
||||
$this->RegisterAttributeBoolean('KonfigurationSolarladen', true);
|
||||
$this->RegisterAttributeBoolean('ZustandLadefreigabe', false);
|
||||
$this->RegisterAttributeBoolean('ZustandSolarladen', true);
|
||||
$this->RegisterAttributeBoolean('GatewayVerbunden', false);
|
||||
$this->RegisterAttributeString('EaseeBeobachtungen', '{}');
|
||||
$this->RegisterAttributeInteger('LetzteBeobachtung', 0);
|
||||
$this->RegisterAttributeInteger('ApiMaximalstrom', 16);
|
||||
$this->RegisterAttributeBoolean('ZustandFahrzeugVerbunden', false);
|
||||
$this->RegisterAttributeBoolean('ZustandFahrzeugGeladen', false);
|
||||
$this->RegisterAttributeInteger('ZustandFahrzeugstatus', 0);
|
||||
$this->RegisterAttributeFloat('ZustandLadestrom', 0.0);
|
||||
$this->RegisterAttributeInteger('ZustandPhasenzahl', 0);
|
||||
$this->RegisterAttributeFloat('ZustandIstleistung', 0.0);
|
||||
$this->RegisterAttributeInteger('ZustandSollleistung', 0);
|
||||
$this->RegisterAttributeBoolean('ZustandSollwertGueltig', false);
|
||||
$this->RegisterAttributeBoolean('ZustandVerfuegbar', false);
|
||||
$this->RegisterAttributeBoolean('ZustandAenderungMoeglich', false);
|
||||
$this->RegisterAttributeBoolean('ZustandStoerung', false);
|
||||
$this->RegisterAttributeString('ZustandStoertext', '');
|
||||
$this->RegisterAttributeString('Leistungsangebot', '[0]');
|
||||
$this->RegisterAttributeString('Betriebsart', Nachrichtenvertrag::BETRIEBSART_PV);
|
||||
$this->RegisterAttributeInteger('LetzteManagerID', 0);
|
||||
$this->RegisterAttributeInteger('LetzteVorgabeZeit', 0);
|
||||
$this->RegisterAttributeInteger('LetzterGesetzterStrom', -1);
|
||||
$this->RegisterAttributeString('LetzterGeraetebefehl', '');
|
||||
|
||||
$this->RegisterTimer(
|
||||
'Meldezyklus',
|
||||
0,
|
||||
"IPS_RequestAction(\$_IPS['TARGET'], 'Melden', false);"
|
||||
);
|
||||
$this->RegisterTimer(
|
||||
'RueckmeldungVerzoegert',
|
||||
0,
|
||||
"IPS_RequestAction(\$_IPS['TARGET'], 'Melden', true);"
|
||||
);
|
||||
$this->RegisterTimer(
|
||||
'VorgabeTimeout',
|
||||
0,
|
||||
"IPS_RequestAction(\$_IPS['TARGET'], 'VorgabeTimeout', false);"
|
||||
);
|
||||
|
||||
$this->ConnectParent(self::EASEE_GATEWAY_MODULE_ID);
|
||||
}
|
||||
|
||||
public function ApplyChanges(): void
|
||||
{
|
||||
parent::ApplyChanges();
|
||||
|
||||
$this->initialisiereLokaleEinstellungen();
|
||||
$this->aktualisiereVariablen();
|
||||
$this->WriteAttributeBoolean('GatewayVerbunden', false);
|
||||
$this->WriteAttributeInteger('LetzterGesetzterStrom', -1);
|
||||
|
||||
try {
|
||||
$this->pruefeKonfiguration();
|
||||
} catch (Throwable $fehler) {
|
||||
$this->deaktiviereTimer();
|
||||
$this->setzeZustand('Verfuegbar', false);
|
||||
$this->setzeZustand('AenderungMoeglich', false);
|
||||
$this->setzeStoerung($fehler->getMessage());
|
||||
$this->SetStatus(self::STATUS_KONFIGURATION_UNGUELTIG);
|
||||
$this->protokolliere('Konfiguration', $fehler->getMessage());
|
||||
return;
|
||||
}
|
||||
|
||||
$this->SetTimerInterval(
|
||||
'Meldezyklus',
|
||||
$this->ReadPropertyInteger('Meldeintervall') * 1000
|
||||
);
|
||||
$this->statusAnfordern();
|
||||
$this->aktualisiere(true);
|
||||
}
|
||||
|
||||
public function RequestAction($ident, $wert): void
|
||||
{
|
||||
switch ($ident) {
|
||||
case 'Aktiv':
|
||||
$this->SetValue('Aktiv', (bool) $wert);
|
||||
if (!(bool) $wert) {
|
||||
$this->setzeZustand('SollwertGueltig', false);
|
||||
}
|
||||
$this->aktualisiere(true);
|
||||
return;
|
||||
|
||||
case 'Ladefreigabe':
|
||||
$this->WriteAttributeBoolean('ZustandLadefreigabe', (bool) $wert);
|
||||
$this->setzeSichtbareVariable('Ladefreigabe', (bool) $wert);
|
||||
$this->setzeZustand('SollwertGueltig', false);
|
||||
$this->aktualisiere(true);
|
||||
return;
|
||||
|
||||
case 'Solarladen':
|
||||
$solarladen = EaseeLadestatus::solarladenErzwungen(
|
||||
$this->ReadPropertyInteger('Betriebsmodus')
|
||||
) ? true : (bool) $wert;
|
||||
$this->WriteAttributeBoolean('ZustandSolarladen', $solarladen);
|
||||
$this->setzeSichtbareVariable('Solarladen', $solarladen);
|
||||
$this->setzeZustand('SollwertGueltig', false);
|
||||
$this->aktualisiere(true);
|
||||
return;
|
||||
|
||||
case 'StatusAnfordern':
|
||||
$this->statusAnfordern();
|
||||
return;
|
||||
|
||||
case 'VorgabeTimeout':
|
||||
$this->SetTimerInterval('VorgabeTimeout', 0);
|
||||
$this->setzeZustand('SollwertGueltig', false);
|
||||
$this->aktualisiere(true);
|
||||
return;
|
||||
|
||||
case 'Melden':
|
||||
if ((bool) $wert) {
|
||||
$this->SetTimerInterval('RueckmeldungVerzoegert', 0);
|
||||
}
|
||||
$this->sendeVerbraucherdaten();
|
||||
return;
|
||||
|
||||
case 'ManagerdatenEmpfangen':
|
||||
if (!is_string($wert)) {
|
||||
throw new InvalidArgumentException(
|
||||
'Managerdaten muessen als JSON uebergeben werden.'
|
||||
);
|
||||
}
|
||||
$daten = json_decode($wert, true, 512, JSON_THROW_ON_ERROR);
|
||||
if (!is_array($daten)) {
|
||||
throw new InvalidArgumentException(
|
||||
'Managerdaten muessen ein JSON-Objekt sein.'
|
||||
);
|
||||
}
|
||||
$this->ManagerdatenEmpfangen($daten);
|
||||
return;
|
||||
}
|
||||
|
||||
throw new InvalidArgumentException('Unbekannte Aktion: ' . $ident);
|
||||
}
|
||||
|
||||
public function ReceiveData($jsonString): void
|
||||
{
|
||||
$paket = json_decode((string) $jsonString, true);
|
||||
if (!is_array($paket) || !isset($paket['Buffer'])) {
|
||||
return;
|
||||
}
|
||||
$ereignis = json_decode((string) $paket['Buffer'], true);
|
||||
if (!is_array($ereignis) || !isset($ereignis['type'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($ereignis['type'] === 'GatewayStatus') {
|
||||
$verbunden = (bool) ($ereignis['connected'] ?? false);
|
||||
$this->WriteAttributeBoolean('GatewayVerbunden', $verbunden);
|
||||
$this->setzeSichtbareVariable('GatewayVerbunden', $verbunden);
|
||||
$this->WriteAttributeInteger('LetzterGesetzterStrom', -1);
|
||||
if ($verbunden) {
|
||||
$this->statusAnfordern();
|
||||
} else {
|
||||
$this->verwerfeBeobachtungen();
|
||||
$this->aktualisiere(true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
$seriennummer = EaseeGatewayProtokoll::seriennummer(
|
||||
(string) ($ereignis['serialNumber'] ?? '')
|
||||
);
|
||||
if (
|
||||
$ereignis['type'] !== 'Observation'
|
||||
|| $seriennummer !== $this->seriennummer()
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->speichereBeobachtung(
|
||||
(int) ($ereignis['id'] ?? 0),
|
||||
$ereignis['value'] ?? null,
|
||||
(int) ($ereignis['timestamp'] ?? time())
|
||||
);
|
||||
$this->aktualisiere(true);
|
||||
}
|
||||
|
||||
/** @param array<string, mixed> $daten */
|
||||
public function ManagerdatenEmpfangen(array $daten): void
|
||||
{
|
||||
Nachrichtenvertrag::pruefeManagerdaten($daten);
|
||||
if ($daten['Kopf']['EmpfaengerID'] !== $this->InstanceID) {
|
||||
throw new InvalidArgumentException(
|
||||
'Managerdaten sind an eine andere Instanz adressiert.'
|
||||
);
|
||||
}
|
||||
|
||||
$managerID = $daten['Kopf']['AbsenderID'];
|
||||
if (!in_array($managerID, $this->zugeordneteManagerIDs(), true)) {
|
||||
throw new InvalidArgumentException(
|
||||
'Der Manager hat diese Ladestation nicht aktiv zugeordnet.'
|
||||
);
|
||||
}
|
||||
|
||||
$this->WriteAttributeString('Betriebsart', $daten['Betriebsart']);
|
||||
$this->aktualisiere(false);
|
||||
$sollleistung = $daten['Sollleistung_W'];
|
||||
if ($sollleistung === null) {
|
||||
if (
|
||||
(bool) $this->leseZustand('SollwertGueltig')
|
||||
&& !in_array(
|
||||
(int) $this->leseZustand('Sollleistung'),
|
||||
$this->leseLeistungsangebot(),
|
||||
true
|
||||
)
|
||||
) {
|
||||
$this->setzeZustand('SollwertGueltig', false);
|
||||
$this->aktualisiere(false);
|
||||
}
|
||||
$this->SetTimerInterval('RueckmeldungVerzoegert', 100);
|
||||
return;
|
||||
}
|
||||
if (!in_array($sollleistung, $this->leseLeistungsangebot(), true)) {
|
||||
throw new InvalidArgumentException(
|
||||
'Sollleistung_W liegt nicht im aktuell gemeldeten Leistungsangebot.'
|
||||
);
|
||||
}
|
||||
|
||||
$this->WriteAttributeInteger('LetzteManagerID', $managerID);
|
||||
$this->WriteAttributeInteger('LetzteVorgabeZeit', time());
|
||||
$this->setzeZustand('Sollleistung', $sollleistung);
|
||||
$this->setzeZustand('SollwertGueltig', true);
|
||||
$this->SetTimerInterval(
|
||||
'VorgabeTimeout',
|
||||
$this->ReadPropertyInteger('VorgabeTimeout') * 1000
|
||||
);
|
||||
$this->aktualisiere(false);
|
||||
$this->SetTimerInterval('RueckmeldungVerzoegert', 100);
|
||||
}
|
||||
|
||||
private function statusAnfordern(): void
|
||||
{
|
||||
try {
|
||||
$antwort = $this->gatewayAnfrage([
|
||||
'action' => 'Subscribe',
|
||||
'serialNumber' => $this->seriennummer(),
|
||||
]);
|
||||
if (!($antwort['success'] ?? false)) {
|
||||
throw new RuntimeException(
|
||||
(string) ($antwort['error'] ?? 'Easee Gateway nicht erreichbar.')
|
||||
);
|
||||
}
|
||||
$this->WriteAttributeBoolean(
|
||||
'GatewayVerbunden',
|
||||
(bool) ($antwort['connected'] ?? false)
|
||||
);
|
||||
$this->setzeSichtbareVariable(
|
||||
'GatewayVerbunden',
|
||||
$this->ReadAttributeBoolean('GatewayVerbunden')
|
||||
);
|
||||
$this->verwerfeBeobachtungen();
|
||||
if (is_array($antwort['state'] ?? null)) {
|
||||
$zustand = $antwort['state'];
|
||||
$zeitpunkt = (int) ($zustand['updated'] ?? time());
|
||||
foreach (EaseeGatewayProtokoll::BEOBACHTUNGEN as $id) {
|
||||
if (array_key_exists((string) $id, $zustand)) {
|
||||
$this->speichereBeobachtung(
|
||||
$id,
|
||||
$zustand[(string) $id],
|
||||
$zeitpunkt
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->aktualisiere(true);
|
||||
} catch (Throwable $fehler) {
|
||||
$this->WriteAttributeBoolean('GatewayVerbunden', false);
|
||||
$this->aktualisiere(true);
|
||||
$this->protokolliere('Gateway', $fehler->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/** @param array<string, mixed> $anfrage
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function gatewayAnfrage(array $anfrage): array
|
||||
{
|
||||
$antwort = $this->SendDataToParent(json_encode([
|
||||
'DataID' => self::GATEWAY_REQUEST_DATA_ID,
|
||||
'Buffer' => json_encode($anfrage, JSON_THROW_ON_ERROR),
|
||||
], JSON_THROW_ON_ERROR));
|
||||
|
||||
if (!is_string($antwort) || $antwort === '') {
|
||||
throw new RuntimeException('Easee Gateway liefert keine Antwort.');
|
||||
}
|
||||
$daten = json_decode($antwort, true, 512, JSON_THROW_ON_ERROR);
|
||||
if (!is_array($daten)) {
|
||||
throw new RuntimeException('Easee Gateway liefert ungueltige Daten.');
|
||||
}
|
||||
|
||||
return $daten;
|
||||
}
|
||||
|
||||
private function speichereBeobachtung(int $id, $wert, int $zeitpunkt): void
|
||||
{
|
||||
if (!in_array($id, EaseeGatewayProtokoll::BEOBACHTUNGEN, true)) {
|
||||
return;
|
||||
}
|
||||
$beobachtungen = $this->leseBeobachtungen();
|
||||
if ($id === 109 && (int) $wert === 1) {
|
||||
foreach ([110, 120, 182, 183, 184, 185] as $sessionID) {
|
||||
$beobachtungen[(string) $sessionID] = 0;
|
||||
}
|
||||
}
|
||||
$beobachtungen[(string) $id] = $wert;
|
||||
$this->WriteAttributeString(
|
||||
'EaseeBeobachtungen',
|
||||
json_encode($beobachtungen, JSON_THROW_ON_ERROR)
|
||||
);
|
||||
$this->WriteAttributeInteger('LetzteBeobachtung', $zeitpunkt);
|
||||
}
|
||||
|
||||
private function aktualisiere(bool $meldungPlanen): void
|
||||
{
|
||||
try {
|
||||
$status = EaseeLadestatus::ausBeobachtungen(
|
||||
$this->leseBeobachtungen(),
|
||||
$this->ReadPropertyInteger('MaximalerLadestrom')
|
||||
);
|
||||
foreach ([
|
||||
'FahrzeugVerbunden',
|
||||
'FahrzeugGeladen',
|
||||
'Fahrzeugstatus',
|
||||
'Phasenzahl',
|
||||
'Istleistung_W',
|
||||
'Ladestrom_A',
|
||||
] as $feld) {
|
||||
$this->setzeZustand($feld, $status[$feld]);
|
||||
}
|
||||
$this->WriteAttributeInteger(
|
||||
'ApiMaximalstrom',
|
||||
$status['ApiMaximalstrom_A']
|
||||
);
|
||||
$this->setzeSichtbareVariable(
|
||||
'ApiMaximalstrom',
|
||||
$status['ApiMaximalstrom_A']
|
||||
);
|
||||
|
||||
$gatewayVerbunden = $this->ReadAttributeBoolean('GatewayVerbunden');
|
||||
$phaseGueltig = in_array($status['Phasenzahl'], [1, 3], true);
|
||||
$steuerbar = $gatewayVerbunden
|
||||
&& $status['StatusGueltig']
|
||||
&& !$status['Stoerung']
|
||||
&& (!$status['FahrzeugVerbunden'] || $phaseGueltig);
|
||||
|
||||
$solarladen = $this->ReadAttributeBoolean('ZustandSolarladen');
|
||||
$peakbetrieb = $this->ReadAttributeString('Betriebsart')
|
||||
=== Nachrichtenvertrag::BETRIEBSART_PEAK;
|
||||
$angebot = LadestationRegler::leistungsangebot(
|
||||
(bool) $this->GetValue('Aktiv'),
|
||||
$this->ReadAttributeBoolean('ZustandLadefreigabe'),
|
||||
$steuerbar && $status['FahrzeugVerbunden'],
|
||||
$status['FahrzeugGeladen'],
|
||||
$phaseGueltig ? $status['Phasenzahl'] : 1,
|
||||
$status['ApiMaximalstrom_A'],
|
||||
$solarladen,
|
||||
$peakbetrieb
|
||||
);
|
||||
$verfuegbar = $steuerbar
|
||||
&& $status['FahrzeugVerbunden']
|
||||
&& !$status['FahrzeugGeladen']
|
||||
&& $angebot !== [0];
|
||||
$angebotJson = json_encode($angebot, JSON_THROW_ON_ERROR);
|
||||
$this->WriteAttributeString('Leistungsangebot', $angebotJson);
|
||||
$this->setzeSichtbareVariable('LeistungsangebotDiagnose', $angebotJson);
|
||||
$this->setzeZustand('Verfuegbar', $verfuegbar);
|
||||
$this->setzeZustand(
|
||||
'AenderungMoeglich',
|
||||
$verfuegbar && count($angebot) > 1
|
||||
);
|
||||
|
||||
$stoertext = '';
|
||||
if (!$gatewayVerbunden) {
|
||||
$stoertext = 'Easee Gateway ist nicht verbunden.';
|
||||
$this->SetStatus(self::STATUS_GATEWAY_GETRENNT);
|
||||
} elseif (!$status['StatusGueltig']) {
|
||||
$stoertext = 'Warte auf den aktuellen Easee-Ladestatus.';
|
||||
$this->SetStatus(self::STATUS_WARTET_AUF_DATEN);
|
||||
} elseif ($status['Stoerung']) {
|
||||
$stoertext = $status['Stoertext'];
|
||||
$this->SetStatus(self::STATUS_LADEFEHLER);
|
||||
} elseif ($status['FahrzeugVerbunden'] && !$phaseGueltig) {
|
||||
$stoertext = 'Easee meldet keine unterstuetzte aktive Ausgangsphase.';
|
||||
$this->SetStatus(self::STATUS_WARTET_AUF_DATEN);
|
||||
} else {
|
||||
$this->SetStatus(self::STATUS_AKTIV);
|
||||
}
|
||||
$this->setzeStoerung($stoertext);
|
||||
|
||||
if ($gatewayVerbunden && $status['StatusGueltig']) {
|
||||
$this->setzeLadestrom(
|
||||
$this->bestimmeWirksameSollleistung($angebot),
|
||||
$status['Phasenzahl']
|
||||
);
|
||||
} else {
|
||||
$this->WriteAttributeInteger('LetzterGesetzterStrom', -1);
|
||||
}
|
||||
} catch (Throwable $fehler) {
|
||||
$this->setzeZustand('Verfuegbar', false);
|
||||
$this->setzeZustand('AenderungMoeglich', false);
|
||||
$this->setzeStoerung($fehler->getMessage());
|
||||
$this->SetStatus(self::STATUS_GATEWAY_GETRENNT);
|
||||
$this->protokolliere('Easee', $fehler->getMessage());
|
||||
}
|
||||
|
||||
if ($meldungPlanen) {
|
||||
$this->SetTimerInterval('RueckmeldungVerzoegert', 100);
|
||||
}
|
||||
}
|
||||
|
||||
/** @param list<int> $angebot */
|
||||
private function bestimmeWirksameSollleistung(array $angebot): int
|
||||
{
|
||||
if ((bool) $this->leseZustand('SollwertGueltig')) {
|
||||
$sollleistung = (int) $this->leseZustand('Sollleistung');
|
||||
if (in_array($sollleistung, $angebot, true)) {
|
||||
return $sollleistung;
|
||||
}
|
||||
$this->setzeZustand('SollwertGueltig', false);
|
||||
}
|
||||
|
||||
if (
|
||||
!$this->ReadAttributeBoolean('ZustandSolarladen')
|
||||
&& $angebot !== [0]
|
||||
) {
|
||||
return $angebot[count($angebot) - 1];
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private function setzeLadestrom(int $leistung, int $phasenzahl): void
|
||||
{
|
||||
$strom = $leistung === 0
|
||||
? 0
|
||||
: LadestationRegler::stromFuerLeistung($leistung, $phasenzahl);
|
||||
if ($strom === $this->ReadAttributeInteger('LetzterGesetzterStrom')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$antwort = $this->gatewayAnfrage([
|
||||
'action' => 'SetDynamicChargerCurrent',
|
||||
'serialNumber' => $this->seriennummer(),
|
||||
'amps' => $strom,
|
||||
]);
|
||||
if (!($antwort['success'] ?? false)) {
|
||||
throw new RuntimeException(
|
||||
(string) ($antwort['error'] ?? 'Easee-Stromvorgabe fehlgeschlagen.')
|
||||
);
|
||||
}
|
||||
|
||||
$befehl = json_encode([
|
||||
'Aktion' => 'SetDynamicChargerCurrent',
|
||||
'Seriennummer' => $this->seriennummer(),
|
||||
'Ampere' => $strom,
|
||||
], JSON_THROW_ON_ERROR);
|
||||
$this->WriteAttributeString('LetzterGeraetebefehl', $befehl);
|
||||
$this->setzeSichtbareVariable('LetzterGeraetebefehl', $befehl);
|
||||
$this->WriteAttributeInteger('LetzterGesetzterStrom', $strom);
|
||||
}
|
||||
|
||||
private function seriennummer(): string
|
||||
{
|
||||
return EaseeGatewayProtokoll::seriennummer(
|
||||
$this->ReadPropertyString('Ladestationskennung')
|
||||
);
|
||||
}
|
||||
|
||||
private function verwerfeBeobachtungen(): void
|
||||
{
|
||||
$this->WriteAttributeString('EaseeBeobachtungen', '{}');
|
||||
$this->WriteAttributeInteger('LetzteBeobachtung', 0);
|
||||
}
|
||||
|
||||
/** @return array<string, mixed> */
|
||||
private function leseBeobachtungen(): array
|
||||
{
|
||||
$daten = json_decode($this->ReadAttributeString('EaseeBeobachtungen'), true);
|
||||
return is_array($daten) ? $daten : [];
|
||||
}
|
||||
|
||||
private function sendeVerbraucherdaten(): void
|
||||
{
|
||||
foreach ($this->zugeordneteManagerIDs() as $managerID) {
|
||||
$daten = $this->baueVerbraucherdaten($managerID);
|
||||
Nachrichtenvertrag::pruefeVerbraucherdaten($daten);
|
||||
try {
|
||||
IPS_RequestAction(
|
||||
$managerID,
|
||||
'VerbraucherdatenEmpfangen',
|
||||
json_encode($daten, JSON_THROW_ON_ERROR)
|
||||
);
|
||||
} catch (Throwable $fehler) {
|
||||
$this->protokolliere('Managerkommunikation', $fehler->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @return array<string, mixed> */
|
||||
private function baueVerbraucherdaten(int $managerID): array
|
||||
{
|
||||
$stoerung = (bool) $this->leseZustand('Stoerung');
|
||||
|
||||
return [
|
||||
'Kopf' => [
|
||||
'Version' => Nachrichtenvertrag::VERSION,
|
||||
'AbsenderID' => $this->InstanceID,
|
||||
'EmpfaengerID' => $managerID,
|
||||
'Zeitpunkt' => time(),
|
||||
],
|
||||
'Betriebsart' => $this->ReadAttributeString('Betriebsart'),
|
||||
'PrioritaetPV' => $this->ReadPropertyInteger('PrioritaetPV'),
|
||||
'PrioritaetPeak' => $this->ReadPropertyInteger('PrioritaetPeak'),
|
||||
'Leistungswerte_W' => $this->leseLeistungsangebot(),
|
||||
'AenderungMoeglich' => (bool) $this->leseZustand('AenderungMoeglich'),
|
||||
'Verfuegbar' => (bool) $this->leseZustand('Verfuegbar'),
|
||||
'Istleistung_W' => (float) $this->leseZustand('Istleistung'),
|
||||
'Leistungsquelle' => Nachrichtenvertrag::LEISTUNGSQUELLE_GEMESSEN,
|
||||
'Zustand' => [
|
||||
[
|
||||
'Kennung' => 'Sollleistung_W',
|
||||
'Art' => 'Sollwert',
|
||||
'Wert' => (bool) $this->leseZustand('SollwertGueltig')
|
||||
? (int) $this->leseZustand('Sollleistung')
|
||||
: null,
|
||||
'Einheit' => 'W',
|
||||
],
|
||||
[
|
||||
'Kennung' => 'FahrzeugVerbunden',
|
||||
'Art' => 'Status',
|
||||
'Wert' => (bool) $this->leseZustand('FahrzeugVerbunden'),
|
||||
'Einheit' => '',
|
||||
],
|
||||
[
|
||||
'Kennung' => 'FahrzeugGeladen',
|
||||
'Art' => 'Status',
|
||||
'Wert' => (bool) $this->leseZustand('FahrzeugGeladen'),
|
||||
'Einheit' => '',
|
||||
],
|
||||
[
|
||||
'Kennung' => 'Fahrzeugstatus',
|
||||
'Art' => 'Status',
|
||||
'Wert' => (int) $this->leseZustand('Fahrzeugstatus'),
|
||||
'Einheit' => '',
|
||||
],
|
||||
[
|
||||
'Kennung' => 'Phasenzahl',
|
||||
'Art' => 'Status',
|
||||
'Wert' => (int) $this->leseZustand('Phasenzahl'),
|
||||
'Einheit' => '',
|
||||
],
|
||||
[
|
||||
'Kennung' => 'Ladestrom_A',
|
||||
'Art' => 'Istwert',
|
||||
'Wert' => (float) $this->leseZustand('Ladestrom'),
|
||||
'Einheit' => 'A',
|
||||
],
|
||||
[
|
||||
'Kennung' => 'Ladefreigabe',
|
||||
'Art' => 'Status',
|
||||
'Wert' => $this->ReadAttributeBoolean('ZustandLadefreigabe'),
|
||||
'Einheit' => '',
|
||||
],
|
||||
[
|
||||
'Kennung' => 'Solarladen',
|
||||
'Art' => 'Status',
|
||||
'Wert' => $this->ReadAttributeBoolean('ZustandSolarladen'),
|
||||
'Einheit' => '',
|
||||
],
|
||||
[
|
||||
'Kennung' => 'Ladefehler',
|
||||
'Art' => 'Stoerung',
|
||||
'Wert' => $stoerung,
|
||||
'Einheit' => '',
|
||||
'Text' => (string) $this->leseZustand('Stoertext'),
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/** @return list<int> */
|
||||
private function zugeordneteManagerIDs(): array
|
||||
{
|
||||
$ergebnis = [];
|
||||
foreach (IPS_GetInstanceListByModuleID(self::MANAGER_MODULE_ID) as $managerID) {
|
||||
try {
|
||||
$automatisch = (bool) IPS_GetProperty(
|
||||
$managerID,
|
||||
'AutomatischeSuche'
|
||||
);
|
||||
$property = $automatisch
|
||||
? 'AutomatischeVerbraucherZuordnung'
|
||||
: 'VerbraucherZuordnung';
|
||||
$zuordnung = $this->leseManagerZuordnung($managerID, $property);
|
||||
if ($automatisch && $zuordnung === []) {
|
||||
$zuordnung = $this->leseManagerZuordnung(
|
||||
$managerID,
|
||||
'VerbraucherZuordnung'
|
||||
);
|
||||
}
|
||||
} catch (Throwable $fehler) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($zuordnung as $eintrag) {
|
||||
if (!is_array($eintrag) || ($eintrag['Aktiv'] ?? false) !== true) {
|
||||
continue;
|
||||
}
|
||||
$instanzID = $eintrag['InstanzID'] ?? $eintrag['Verbraucher'] ?? null;
|
||||
if ($instanzID === $this->InstanceID) {
|
||||
$ergebnis[] = (int) $managerID;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return array_values(array_unique($ergebnis));
|
||||
}
|
||||
|
||||
/** @return array<mixed> */
|
||||
private function leseManagerZuordnung(int $managerID, string $property): array
|
||||
{
|
||||
$zuordnung = json_decode(
|
||||
(string) IPS_GetProperty($managerID, $property),
|
||||
true,
|
||||
512,
|
||||
JSON_THROW_ON_ERROR
|
||||
);
|
||||
|
||||
return is_array($zuordnung) ? $zuordnung : [];
|
||||
}
|
||||
|
||||
private function pruefeKonfiguration(): void
|
||||
{
|
||||
foreach (['PrioritaetPV', 'PrioritaetPeak'] as $property) {
|
||||
if ($this->ReadPropertyInteger($property) < 0) {
|
||||
throw new InvalidArgumentException(
|
||||
$property . ' muss mindestens 0 sein.'
|
||||
);
|
||||
}
|
||||
}
|
||||
foreach (['Meldeintervall', 'VorgabeTimeout'] as $property) {
|
||||
if ($this->ReadPropertyInteger($property) <= 0) {
|
||||
throw new InvalidArgumentException(
|
||||
$property . ' muss groesser als 0 sein.'
|
||||
);
|
||||
}
|
||||
}
|
||||
$maximalstrom = $this->ReadPropertyInteger('MaximalerLadestrom');
|
||||
if ($maximalstrom < 6 || $maximalstrom > 32) {
|
||||
throw new InvalidArgumentException(
|
||||
'MaximalerLadestrom muss zwischen 6 und 32 A liegen.'
|
||||
);
|
||||
}
|
||||
EaseeLadestatus::solarladenErzwungen(
|
||||
$this->ReadPropertyInteger('Betriebsmodus')
|
||||
);
|
||||
if ($this->seriennummer() === '') {
|
||||
throw new InvalidArgumentException('Easee Seriennummer fehlt.');
|
||||
}
|
||||
}
|
||||
|
||||
private function initialisiereLokaleEinstellungen(): void
|
||||
{
|
||||
$initialisiert = $this->ReadAttributeBoolean('InitialwerteGesetzt');
|
||||
$ladefreigabe = $this->ReadPropertyBoolean('Ladefreigabe');
|
||||
$solarladen = EaseeLadestatus::solarladenErzwungen(
|
||||
$this->ReadPropertyInteger('Betriebsmodus')
|
||||
) ? true : $this->ReadPropertyBoolean('Solarladen');
|
||||
|
||||
if (
|
||||
!$initialisiert
|
||||
|| $ladefreigabe
|
||||
!== $this->ReadAttributeBoolean('KonfigurationLadefreigabe')
|
||||
) {
|
||||
$this->WriteAttributeBoolean('ZustandLadefreigabe', $ladefreigabe);
|
||||
}
|
||||
if (
|
||||
!$initialisiert
|
||||
|| $solarladen
|
||||
!== $this->ReadAttributeBoolean('KonfigurationSolarladen')
|
||||
) {
|
||||
$this->WriteAttributeBoolean('ZustandSolarladen', $solarladen);
|
||||
}
|
||||
|
||||
$this->WriteAttributeBoolean('KonfigurationLadefreigabe', $ladefreigabe);
|
||||
$this->WriteAttributeBoolean('KonfigurationSolarladen', $solarladen);
|
||||
$this->WriteAttributeBoolean('InitialwerteGesetzt', true);
|
||||
}
|
||||
|
||||
private function aktualisiereVariablen(): void
|
||||
{
|
||||
if ($this->ReadPropertyBoolean('EinstellungenInVisu')) {
|
||||
$this->RegisterVariableBoolean(
|
||||
'Ladefreigabe',
|
||||
'Ladefreigabe',
|
||||
'~Switch',
|
||||
150
|
||||
);
|
||||
$this->RegisterVariableBoolean(
|
||||
'Solarladen',
|
||||
'Solarladen',
|
||||
'~Switch',
|
||||
160
|
||||
);
|
||||
$this->EnableAction('Ladefreigabe');
|
||||
$this->EnableAction('Solarladen');
|
||||
$this->SetValue(
|
||||
'Ladefreigabe',
|
||||
$this->ReadAttributeBoolean('ZustandLadefreigabe')
|
||||
);
|
||||
$this->SetValue(
|
||||
'Solarladen',
|
||||
$this->ReadAttributeBoolean('ZustandSolarladen')
|
||||
);
|
||||
} else {
|
||||
$this->entferneVariable('Ladefreigabe');
|
||||
$this->entferneVariable('Solarladen');
|
||||
}
|
||||
|
||||
if ($this->ReadPropertyBoolean('DiagnosevariablenAnzeigen')) {
|
||||
$this->registriereVerbraucherDiagnose();
|
||||
$this->RegisterVariableBoolean(
|
||||
'GatewayVerbunden',
|
||||
'Easee Gateway verbunden',
|
||||
'~Switch',
|
||||
200
|
||||
);
|
||||
$this->RegisterVariableInteger(
|
||||
'ApiMaximalstrom',
|
||||
'API-Maximalstrom',
|
||||
'',
|
||||
210
|
||||
);
|
||||
$this->RegisterVariableString(
|
||||
'LetzterGeraetebefehl',
|
||||
'Letzter Geraetebefehl',
|
||||
'',
|
||||
220
|
||||
);
|
||||
$this->RegisterVariableString(
|
||||
'LeistungsangebotDiagnose',
|
||||
'Leistungsangebot',
|
||||
'',
|
||||
230
|
||||
);
|
||||
$this->SetValue(
|
||||
'LeistungsangebotDiagnose',
|
||||
$this->ReadAttributeString('Leistungsangebot')
|
||||
);
|
||||
$this->SetValue(
|
||||
'LetzterGeraetebefehl',
|
||||
$this->ReadAttributeString('LetzterGeraetebefehl')
|
||||
);
|
||||
foreach (self::DIAGNOSE_VARIABLEN as $ident) {
|
||||
if (!in_array($ident, [
|
||||
'GatewayVerbunden',
|
||||
'ApiMaximalstrom',
|
||||
'LetzterGeraetebefehl',
|
||||
'LeistungsangebotDiagnose',
|
||||
], true)) {
|
||||
$this->setzeSichtbareVariable($ident, $this->leseZustand($ident));
|
||||
}
|
||||
}
|
||||
$this->setzeSichtbareVariable(
|
||||
'GatewayVerbunden',
|
||||
$this->ReadAttributeBoolean('GatewayVerbunden')
|
||||
);
|
||||
$this->setzeSichtbareVariable(
|
||||
'ApiMaximalstrom',
|
||||
$this->ReadAttributeInteger('ApiMaximalstrom')
|
||||
);
|
||||
} else {
|
||||
foreach (self::DIAGNOSE_VARIABLEN as $ident) {
|
||||
$this->entferneVariable($ident);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function entferneVariable(string $ident): void
|
||||
{
|
||||
$id = @$this->GetIDForIdent($ident);
|
||||
if (is_int($id) && $id > 0 && IPS_VariableExists($id)) {
|
||||
$this->UnregisterVariable($ident);
|
||||
}
|
||||
}
|
||||
|
||||
/** @param mixed $wert */
|
||||
private function setzeZustand(string $ident, $wert): void
|
||||
{
|
||||
switch ($ident) {
|
||||
case 'FahrzeugVerbunden':
|
||||
case 'FahrzeugGeladen':
|
||||
case 'SollwertGueltig':
|
||||
case 'Verfuegbar':
|
||||
case 'AenderungMoeglich':
|
||||
case 'Stoerung':
|
||||
$this->WriteAttributeBoolean('Zustand' . $ident, (bool) $wert);
|
||||
break;
|
||||
case 'Fahrzeugstatus':
|
||||
case 'Phasenzahl':
|
||||
case 'Sollleistung':
|
||||
$this->WriteAttributeInteger('Zustand' . $ident, (int) $wert);
|
||||
break;
|
||||
case 'Ladestrom_A':
|
||||
$ident = 'Ladestrom';
|
||||
$this->WriteAttributeFloat('ZustandLadestrom', (float) $wert);
|
||||
break;
|
||||
case 'Istleistung_W':
|
||||
$ident = 'Istleistung';
|
||||
$this->WriteAttributeFloat('ZustandIstleistung', (float) $wert);
|
||||
break;
|
||||
case 'Stoertext':
|
||||
$this->WriteAttributeString('ZustandStoertext', (string) $wert);
|
||||
break;
|
||||
default:
|
||||
throw new LogicException('Unbekannter Zustand: ' . $ident);
|
||||
}
|
||||
$this->setzeSichtbareVariable($ident, $wert);
|
||||
if ($ident === 'Istleistung') {
|
||||
$this->setzeSichtbareVariable(
|
||||
'Leistungsquelle',
|
||||
Nachrichtenvertrag::LEISTUNGSQUELLE_GEMESSEN
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/** @return mixed */
|
||||
private function leseZustand(string $ident)
|
||||
{
|
||||
if ($ident === 'Ladefreigabe' || $ident === 'Solarladen') {
|
||||
return $this->ReadAttributeBoolean('Zustand' . $ident);
|
||||
}
|
||||
if (in_array($ident, [
|
||||
'FahrzeugVerbunden',
|
||||
'FahrzeugGeladen',
|
||||
'SollwertGueltig',
|
||||
'Verfuegbar',
|
||||
'AenderungMoeglich',
|
||||
'Stoerung',
|
||||
], true)) {
|
||||
return $this->ReadAttributeBoolean('Zustand' . $ident);
|
||||
}
|
||||
if (in_array($ident, [
|
||||
'Fahrzeugstatus',
|
||||
'Phasenzahl',
|
||||
'Sollleistung',
|
||||
], true)) {
|
||||
return $this->ReadAttributeInteger('Zustand' . $ident);
|
||||
}
|
||||
if ($ident === 'Ladestrom' || $ident === 'Istleistung') {
|
||||
return $this->ReadAttributeFloat('Zustand' . $ident);
|
||||
}
|
||||
if ($ident === 'Stoertext') {
|
||||
return $this->ReadAttributeString('ZustandStoertext');
|
||||
}
|
||||
if ($ident === 'Leistungsquelle') {
|
||||
return Nachrichtenvertrag::LEISTUNGSQUELLE_GEMESSEN;
|
||||
}
|
||||
throw new LogicException('Unbekannter Zustand: ' . $ident);
|
||||
}
|
||||
|
||||
/** @param mixed $wert */
|
||||
private function setzeSichtbareVariable(string $ident, $wert): void
|
||||
{
|
||||
$id = @$this->GetIDForIdent($ident);
|
||||
if (is_int($id) && $id > 0 && IPS_VariableExists($id)) {
|
||||
$this->SetValue($ident, $wert);
|
||||
}
|
||||
}
|
||||
|
||||
/** @return list<int> */
|
||||
private function leseLeistungsangebot(): array
|
||||
{
|
||||
$angebot = json_decode(
|
||||
$this->ReadAttributeString('Leistungsangebot'),
|
||||
true,
|
||||
512,
|
||||
JSON_THROW_ON_ERROR
|
||||
);
|
||||
|
||||
return is_array($angebot) ? array_map('intval', $angebot) : [0];
|
||||
}
|
||||
|
||||
private function setzeStoerung(string $text): void
|
||||
{
|
||||
$this->setzeZustand('Stoerung', $text !== '');
|
||||
$this->setzeZustand('Stoertext', $text);
|
||||
}
|
||||
|
||||
private function deaktiviereTimer(): void
|
||||
{
|
||||
foreach (['Meldezyklus', 'RueckmeldungVerzoegert', 'VorgabeTimeout'] as $timer) {
|
||||
$this->SetTimerInterval($timer, 0);
|
||||
}
|
||||
}
|
||||
|
||||
private function protokolliere(string $bereich, string $nachricht): void
|
||||
{
|
||||
if ($this->ReadPropertyBoolean('LoggingEin')) {
|
||||
$this->SendDebug($bereich, $nachricht, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user