*/ private const DIAGNOSE_VARIABLEN = [ 'Leistungsquelle', 'SollwertGueltig', 'Verfuegbar', 'AenderungMoeglich', 'Stoerung', 'Stoertext', 'Fahrzeugstatus', 'GatewayVerbunden', 'ApiMaximalstrom', 'EaseeAktiviert', 'AutorisierungErforderlich', 'RemoteStartErforderlich', 'SmartCharging', 'GrundKeinLadestrom', 'LetzterGeraetebefehl', 'LetzterStartbefehl', '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', true); $this->RegisterPropertyBoolean('Solarladen', true); $this->RegisterPropertyBoolean('DiagnosevariablenAnzeigen', false); $this->RegisterVariableBoolean( 'FahrzeugVerbunden', 'Fahrzeug verbunden', '~Switch', 100 ); $this->RegisterVariableBoolean( 'FahrzeugGeladen', 'Fahrzeug geladen', '~Switch', 110 ); $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('ZustandEaseeAktiviert', true); $this->RegisterAttributeBoolean('ZustandAutorisierungErforderlich', false); $this->RegisterAttributeBoolean('ZustandRemoteStartErforderlich', false); $this->RegisterAttributeBoolean('ZustandSmartCharging', false); $this->RegisterAttributeInteger('ZustandGrundKeinLadestrom', 0); $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->RegisterAttributeBoolean('LadestartFuerVerbindungGesendet', false); $this->RegisterAttributeString('LetzterGeraetebefehl', ''); $this->RegisterAttributeString('LetzterStartbefehl', ''); $this->RegisterAttributeBoolean('RegeluebergangAktiv', false); $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->RegisterTimer( 'RegeluebergangTimeout', 0, "IPS_RequestAction(\$_IPS['TARGET'], 'RegeluebergangTimeout', 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); $this->WriteAttributeBoolean('LadestartFuerVerbindungGesendet', false); $this->beendeRegeluebergang(); 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': $aktiv = (bool) $wert; $this->SetValue('Aktiv', $aktiv); if ($aktiv) { $this->starteRegeluebergang(); } else { $this->beendeRegeluebergang(); $this->setzeZustand('SollwertGueltig', false); } $this->aktualisiere(true); return; case 'Ladefreigabe': $ladefreigabe = (bool) $wert; $this->WriteAttributeBoolean('ZustandLadefreigabe', $ladefreigabe); $this->setzeSichtbareVariable('Ladefreigabe', $ladefreigabe); if ($ladefreigabe) { $this->starteRegeluebergang(); } else { $this->beendeRegeluebergang(); $this->setzeZustand('SollwertGueltig', false); } $this->aktualisiere(true); return; case 'Solarladen': $solarladen = EaseeLadestatus::solarladenErzwungen( $this->ReadPropertyInteger('Betriebsmodus') ) ? true : (bool) $wert; if ($solarladen !== $this->ReadAttributeBoolean('ZustandSolarladen')) { $this->starteRegeluebergang(); } $this->WriteAttributeBoolean('ZustandSolarladen', $solarladen); $this->setzeSichtbareVariable('Solarladen', $solarladen); $this->aktualisiere(true); return; case 'StatusAnfordern': $this->statusAnfordern(); return; case 'VorgabeTimeout': $this->SetTimerInterval('VorgabeTimeout', 0); $this->setzeZustand('SollwertGueltig', false); $this->aktualisiere(true); return; case 'RegeluebergangTimeout': $this->beendeRegeluebergang(); $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 $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, 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->beendeRegeluebergang(); $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 $anfrage * @return array */ 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, bool $steuerungAusfuehren = true ): void { try { $status = EaseeLadestatus::ausBeobachtungen( $this->leseBeobachtungen(), $this->ReadPropertyInteger('MaximalerLadestrom') ); foreach ([ 'FahrzeugVerbunden', 'FahrzeugGeladen', 'Fahrzeugstatus', 'Phasenzahl', 'Istleistung_W', 'Ladestrom_A', 'EaseeAktiviert', 'AutorisierungErforderlich', 'RemoteStartErforderlich', 'SmartCharging', 'GrundKeinLadestrom', ] as $feld) { $this->setzeZustand($feld, $status[$feld]); } $this->WriteAttributeInteger( 'ApiMaximalstrom', $status['ApiMaximalstrom_A'] ); $this->setzeSichtbareVariable( 'ApiMaximalstrom', $status['ApiMaximalstrom_A'] ); if (!$status['FahrzeugVerbunden']) { $this->WriteAttributeBoolean('LadestartFuerVerbindungGesendet', false); } $gatewayVerbunden = $this->ReadAttributeBoolean('GatewayVerbunden'); $phaseGueltig = in_array($status['Phasenzahl'], [1, 3], true); $regelPhasenzahl = $phaseGueltig ? $status['Phasenzahl'] : 1; $phaseErforderlich = $status['Fahrzeugstatus'] === 3; $steuerbar = $gatewayVerbunden && $status['StatusGueltig'] && $status['EaseeAktiviert'] && !$status['Stoerung'] && (!$phaseErforderlich || $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'], $regelPhasenzahl, $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 ($phaseErforderlich && !$phaseGueltig) { $stoertext = 'Easee meldet beim Laden keine unterstuetzte aktive Ausgangsphase.'; $this->SetStatus(self::STATUS_WARTET_AUF_DATEN); } else { $this->SetStatus(self::STATUS_AKTIV); } $this->setzeStoerung($stoertext); $regeluebergangAktiv = (bool) $this->GetValue('Aktiv') && $this->ReadAttributeBoolean('ZustandLadefreigabe') && $steuerbar && $status['FahrzeugVerbunden'] && !$status['FahrzeugGeladen'] && $this->ReadAttributeBoolean('RegeluebergangAktiv'); if ($gatewayVerbunden && $status['StatusGueltig']) { $sollleistung = $this->bestimmeWirksameSollleistung($angebot); if ($steuerungAusfuehren && !$regeluebergangAktiv) { $this->setzeLadestrom( $sollleistung, $regelPhasenzahl, $status['Fahrzeugstatus'] === 2 ); } } 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 $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, bool $wartetAufStart ): void { $strom = $leistung === 0 ? 0 : LadestationRegler::stromFuerLeistung($leistung, $phasenzahl); if ($strom !== $this->ReadAttributeInteger('LetzterGesetzterStrom')) { $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); } if ( $strom > 0 && $wartetAufStart && !$this->ReadAttributeBoolean('LadestartFuerVerbindungGesendet') ) { $this->sendeLadestart(); } } private function sendeLadestart(): void { $antwort = $this->gatewayAnfrage([ 'action' => 'StartCharging', 'serialNumber' => $this->seriennummer(), ]); if (!($antwort['success'] ?? false)) { throw new RuntimeException( (string) ($antwort['error'] ?? 'Easee-Ladestart fehlgeschlagen.') ); } $befehl = json_encode([ 'Aktion' => 'StartCharging', 'Seriennummer' => $this->seriennummer(), ], JSON_THROW_ON_ERROR); $this->WriteAttributeBoolean('LadestartFuerVerbindungGesendet', true); $this->WriteAttributeString('LetzterStartbefehl', $befehl); $this->setzeSichtbareVariable('LetzterStartbefehl', $befehl); } private function seriennummer(): string { return EaseeGatewayProtokoll::seriennummer( $this->ReadPropertyString('Ladestationskennung') ); } private function verwerfeBeobachtungen(): void { $this->WriteAttributeString('EaseeBeobachtungen', '{}'); $this->WriteAttributeInteger('LetzteBeobachtung', 0); } /** @return array */ 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 */ 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' => 'EaseeAktiviert', 'Art' => 'Status', 'Wert' => (bool) $this->leseZustand('EaseeAktiviert'), 'Einheit' => '', ], [ 'Kennung' => 'AutorisierungErforderlich', 'Art' => 'Status', 'Wert' => (bool) $this->leseZustand('AutorisierungErforderlich'), 'Einheit' => '', ], [ 'Kennung' => 'RemoteStartErforderlich', 'Art' => 'Status', 'Wert' => (bool) $this->leseZustand('RemoteStartErforderlich'), 'Einheit' => '', ], [ 'Kennung' => 'SmartCharging', 'Art' => 'Status', 'Wert' => (bool) $this->leseZustand('SmartCharging'), 'Einheit' => '', ], [ 'Kennung' => 'GrundKeinLadestrom', 'Art' => 'Status', 'Wert' => (int) $this->leseZustand('GrundKeinLadestrom'), 'Einheit' => '', ], [ 'Kennung' => 'Ladefehler', 'Art' => 'Stoerung', 'Wert' => $stoerung, 'Einheit' => '', 'Text' => (string) $this->leseZustand('Stoertext'), ], ], ]; } /** @return list */ 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 */ 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 starteRegeluebergang(): void { if ($this->zugeordneteManagerIDs() === []) { $this->beendeRegeluebergang(); return; } $this->WriteAttributeBoolean('RegeluebergangAktiv', true); $this->SetTimerInterval('RegeluebergangTimeout', self::REGELUEBERGANG_TIMEOUT_MS); } private function beendeRegeluebergang(): void { $this->WriteAttributeBoolean('RegeluebergangAktiv', false); $this->SetTimerInterval('RegeluebergangTimeout', 0); } 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('DiagnosevariablenAnzeigen')) { $this->registriereVerbraucherDiagnose(); $this->RegisterVariableInteger('Fahrzeugstatus', 'Fahrzeugstatus', '', 200); $this->RegisterVariableBoolean( 'GatewayVerbunden', 'Easee Gateway verbunden', '~Switch', 210 ); $this->RegisterVariableInteger('ApiMaximalstrom', 'API-Maximalstrom', '', 220); $this->RegisterVariableBoolean( 'EaseeAktiviert', 'Easee aktiviert', '~Switch', 230 ); $this->RegisterVariableBoolean( 'AutorisierungErforderlich', 'Autorisierung erforderlich', '~Switch', 240 ); $this->RegisterVariableBoolean( 'RemoteStartErforderlich', 'Remote-Start erforderlich', '~Switch', 250 ); $this->RegisterVariableBoolean( 'SmartCharging', 'Easee Smart Charging', '~Switch', 260 ); $this->RegisterVariableInteger( 'GrundKeinLadestrom', 'Grund kein Ladestrom', '', 270 ); $this->RegisterVariableString( 'LetzterGeraetebefehl', 'Letzter Geraetebefehl', '', 280 ); $this->RegisterVariableString( 'LetzterStartbefehl', 'Letzter Startbefehl', '', 290 ); $this->RegisterVariableString( 'LeistungsangebotDiagnose', 'Leistungsangebot', '', 300 ); $this->SetValue( 'LeistungsangebotDiagnose', $this->ReadAttributeString('Leistungsangebot') ); $this->SetValue( 'LetzterGeraetebefehl', $this->ReadAttributeString('LetzterGeraetebefehl') ); $this->SetValue( 'LetzterStartbefehl', $this->ReadAttributeString('LetzterStartbefehl') ); foreach (self::DIAGNOSE_VARIABLEN as $ident) { if (!in_array($ident, [ 'GatewayVerbunden', 'ApiMaximalstrom', 'LetzterGeraetebefehl', 'LetzterStartbefehl', '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); } } if ($this->ReadPropertyBoolean('EinstellungenInVisu')) { $this->RegisterVariableBoolean( 'Ladefreigabe', 'Ladefreigabe', '~Switch', 150 ); $this->RegisterVariableBoolean( 'Solarladen', 'Solarladen', '~Switch', 160 ); $this->RegisterVariableFloat('Istleistung', 'Istleistung', '', 170); $this->RegisterVariableInteger('Sollleistung', 'Sollleistung', '', 180); $this->EnableAction('Ladefreigabe'); $this->EnableAction('Solarladen'); $this->SetValue( 'Ladefreigabe', $this->ReadAttributeBoolean('ZustandLadefreigabe') ); $this->SetValue( 'Solarladen', $this->ReadAttributeBoolean('ZustandSolarladen') ); $this->SetValue('Istleistung', $this->ReadAttributeFloat('ZustandIstleistung')); $this->SetValue('Sollleistung', $this->ReadAttributeInteger('ZustandSollleistung')); } else { $this->entferneVariable('Ladefreigabe'); $this->entferneVariable('Solarladen'); $this->entferneVariable('Istleistung'); $this->entferneVariable('Sollleistung'); } } 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 'EaseeAktiviert': case 'AutorisierungErforderlich': case 'RemoteStartErforderlich': case 'SmartCharging': case 'SollwertGueltig': case 'Verfuegbar': case 'AenderungMoeglich': case 'Stoerung': $this->WriteAttributeBoolean('Zustand' . $ident, (bool) $wert); break; case 'Fahrzeugstatus': case 'Phasenzahl': case 'GrundKeinLadestrom': 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', 'EaseeAktiviert', 'AutorisierungErforderlich', 'RemoteStartErforderlich', 'SmartCharging', 'SollwertGueltig', 'Verfuegbar', 'AenderungMoeglich', 'Stoerung', ], true)) { return $this->ReadAttributeBoolean('Zustand' . $ident); } if (in_array($ident, [ 'Fahrzeugstatus', 'Phasenzahl', 'GrundKeinLadestrom', '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 */ 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', 'RegeluebergangTimeout', ] as $timer) { $this->SetTimerInterval($timer, 0); } } private function protokolliere(string $bereich, string $nachricht): void { if ($this->ReadPropertyBoolean('LoggingEin')) { $this->SendDebug($bereich, $nachricht, 0); } } }