*/ private const DIAGNOSE_VARIABLEN = [ 'Istleistung', 'Leistungsquelle', 'Sollleistung', 'SollwertGueltig', 'Verfuegbar', 'AenderungMoeglich', 'Stoerung', 'Stoertext', 'AktiveStufe', 'BezogeneEnergie', 'TemperaturenGueltig', ]; public function Create(): void { parent::Create(); $this->registriereVerbraucherBasis(); $this->RegisterPropertyString('LeistungsStufen', '[]'); $this->RegisterPropertyInteger('PufferfuehlerVariableID', 0); $this->RegisterPropertyInteger('AussentemperaturVariableID', 0); $this->RegisterPropertyInteger('WaermepumpenSolltemperaturVariableID', 0); $this->RegisterPropertyFloat('FusspunktVorlauftemperatur', 35.0); $this->RegisterPropertyFloat('HeizkurvenSteigung', 1.0); $this->RegisterPropertyFloat('HeizkurveMinimaltemperatur', 20.0); $this->RegisterPropertyFloat('HeizkurveMaximaltemperatur', 80.0); $this->RegisterPropertyFloat('Hysterese', 5.0); $this->RegisterPropertyInteger('MindesttemperaturModus', PufferspeicherRegler::MINIMUM_AUS); $this->RegisterPropertyFloat('Mindesttemperatur', 20.0); $this->RegisterPropertyFloat('MindesttemperaturDifferenz', 5.0); $this->RegisterPropertyInteger('LastwechselSperrzeit', 5); $this->RegisterPropertyInteger('TemperaturMaxAlter', 120); $this->RegisterPropertyBoolean('PuffertemperaturGlaetten', false); $this->RegisterPropertyInteger('ZeitKonstante', 120); $this->RegisterPropertyBoolean('DiagnosevariablenAnzeigen', false); $this->RegisterVariableFloat('Puffertemperatur', 'Puffertemperatur', '~Temperature', 100); $this->RegisterVariableFloat('Aussentemperatur', 'Aussentemperatur', '~Temperature', 110); $this->RegisterVariableFloat('Solltemperatur', 'Solltemperatur', '~Temperature', 120); $this->RegisterVariableBoolean('Heizbedarf', 'Heizbedarf', '~Switch', 130); $this->RegisterAttributeInteger('RegistriertePufferID', 0); $this->RegisterAttributeInteger('RegistrierteAussenID', 0); $this->RegisterAttributeInteger('LetzteBerechnung', 0); $this->RegisterAttributeInteger('LetzteTemperaturberechnung', 0); $this->RegisterAttributeInteger('LetzteVorgabeZeit', 0); $this->RegisterAttributeInteger('LetzterLastwechsel', 0); $this->RegisterAttributeInteger('LetzteManagerID', 0); $this->RegisterAttributeFloat('Glaettungswert', 0.0); $this->RegisterAttributeBoolean('GlaettungInitialisiert', false); $this->RegisterAttributeString('Leistungsangebot', '[0]'); $this->RegisterAttributeString('Betriebsart', Nachrichtenvertrag::BETRIEBSART_PV); $this->RegisterAttributeString('Schaltfehler', ''); $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->RegisterAttributeInteger('ZustandAktiveStufe', 0); $this->RegisterAttributeFloat('ZustandBezogeneEnergie', 0.0); $this->RegisterAttributeBoolean('ZustandTemperaturenGueltig', false); $this->RegisterTimer( 'LastwechselFreigabe', 0, "IPS_RequestAction(\$_IPS['TARGET'], 'LastwechselFreigabe', false);" ); $this->RegisterTimer( 'Meldezyklus', 0, "IPS_RequestAction(\$_IPS['TARGET'], 'Melden', false);" ); $this->RegisterTimer( 'RueckmeldungVerzoegert', 0, "IPS_RequestAction(\$_IPS['TARGET'], 'Melden', true);" ); } public function ApplyChanges(): void { parent::ApplyChanges(); $this->aktualisiereVariablen(); try { $this->pruefeKonfiguration(); } catch (Throwable $fehler) { $this->deaktiviereTimer(); $this->setzeZustand('Verfuegbar', false); $this->setzeZustand('AenderungMoeglich', false); $this->setzeZustand('Stoerung', true); $this->setzeZustand('Stoertext', $fehler->getMessage()); $this->SetStatus(self::STATUS_KONFIGURATION_UNGUELTIG); $this->protokolliere('Konfiguration', $fehler->getMessage()); return; } $this->registriereTemperaturmeldungen(); $this->SetTimerInterval('LastwechselFreigabe', 0); $this->SetTimerInterval('Meldezyklus', $this->ReadPropertyInteger('Meldeintervall') * 1000); $this->regelzyklus(true); } public function MessageSink($zeitstempel, $senderID, $nachricht, $daten): void { if ((int) $nachricht !== self::VM_UPDATE) { return; } if (in_array((int) $senderID, [ $this->ReadPropertyInteger('PufferfuehlerVariableID'), $this->ReadPropertyInteger('AussentemperaturVariableID'), ], true)) { $this->regelzyklus(true); } } public function RequestAction($ident, $wert): void { switch ($ident) { case 'Aktiv': $this->SetValue('Aktiv', (bool) $wert); if (!(bool) $wert) { $this->setzeZustand('SollwertGueltig', false); } $this->regelzyklus(true); return; case 'HeizkurveVonWaermepumpeUebernehmen': $this->uebernehmeWaermepumpenwert(); return; case 'LastwechselFreigabe': $this->SetTimerInterval('LastwechselFreigabe', 0); $this->regelzyklus(true); return; case 'Melden': if ((bool) $wert) { $this->SetTimerInterval('RueckmeldungVerzoegert', 0); } $this->regelzyklus(false); $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); } /** @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 diesen Pufferspeicher nicht aktiv zugeordnet.'); } $this->WriteAttributeString('Betriebsart', $daten['Betriebsart']); $this->regelzyklus(false); $angebot = $this->leseLeistungsangebot(); $sollleistung = $daten['Sollleistung_W']; if ($sollleistung === null) { if ((bool) $this->leseZustand('SollwertGueltig') && !in_array((int) $this->leseZustand('Sollleistung'), $angebot, true) ) { $this->setzeZustand('SollwertGueltig', false); $this->regelzyklus(false); } $this->SetTimerInterval('RueckmeldungVerzoegert', 100); return; } if (!in_array($sollleistung, $angebot, 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->regelzyklus(false); $this->SetTimerInterval('RueckmeldungVerzoegert', 100); } private function regelzyklus(bool $meldungPlanen): void { try { $jetzt = time(); $this->aktualisiereEnergie($jetzt); $temperaturenGueltig = $this->aktualisiereTemperaturen(); $stufen = PufferspeicherRegler::dekodiereLeistungsstufen( $this->ReadPropertyString('LeistungsStufen') ); $solltemperatur = 0.0; $einschaltschwelle = 0.0; if ($temperaturenGueltig) { $solltemperatur = PufferspeicherRegler::solltemperatur( (float) $this->GetValue('Aussentemperatur'), $this->ReadPropertyFloat('FusspunktVorlauftemperatur'), $this->ReadPropertyFloat('HeizkurvenSteigung'), $this->ReadPropertyFloat('HeizkurveMinimaltemperatur'), $this->ReadPropertyFloat('HeizkurveMaximaltemperatur') ); $einschaltschwelle = PufferspeicherRegler::einschaltschwelle( $solltemperatur, $this->ReadPropertyFloat('Hysterese'), $this->ReadPropertyInteger('MindesttemperaturModus'), $this->ReadPropertyFloat('Mindesttemperatur'), $this->ReadPropertyFloat('MindesttemperaturDifferenz') ); } $this->SetValue('Solltemperatur', $solltemperatur); $aktiv = (bool) $this->GetValue('Aktiv'); $peak = $this->ReadAttributeString('Betriebsart') === Nachrichtenvertrag::BETRIEBSART_PEAK; $angebot = PufferspeicherRegler::leistungsangebot( $aktiv, $peak, $temperaturenGueltig, (float) $this->GetValue('Puffertemperatur'), $einschaltschwelle, $stufen ); $heizbedarf = count($angebot) > 1; $this->SetValue('Heizbedarf', $heizbedarf); $verfuegbar = $aktiv && $temperaturenGueltig && $stufen !== []; $aenderungMoeglich = $verfuegbar && count($angebot) > 1; $zwangsleistung = (!$verfuegbar || !$heizbedarf) ? 0 : null; $aktuelleLeistung = (int) round((float) $this->leseZustand('Istleistung')); $restzeit = PufferspeicherRegler::restzeitBisLastwechsel( $this->ReadAttributeInteger('LetzterLastwechsel'), $jetzt, $this->ReadPropertyInteger('LastwechselSperrzeit') ); $lastwechselGesperrt = $restzeit > 0 && $verfuegbar && $zwangsleistung !== 0; $this->SetTimerInterval('LastwechselFreigabe', $lastwechselGesperrt ? $restzeit * 1000 : 0); if ($lastwechselGesperrt) { $angebot = [$aktuelleLeistung]; $aenderungMoeglich = false; } $letzteVorgabe = $this->ReadAttributeInteger('LetzteVorgabeZeit'); if ($letzteVorgabe <= 0 || $jetzt - $letzteVorgabe > $this->ReadPropertyInteger('VorgabeTimeout') ) { $this->setzeZustand('SollwertGueltig', false); } $ziel = 0; if ($lastwechselGesperrt) { $ziel = $aktuelleLeistung; } elseif ($zwangsleistung !== null) { $ziel = $zwangsleistung; } elseif ((bool) $this->leseZustand('SollwertGueltig')) { $ziel = (int) $this->leseZustand('Sollleistung'); if (!in_array($ziel, $angebot, true)) { $this->setzeZustand('SollwertGueltig', false); $ziel = 0; } } $lastwechsel = $this->schalteLeistung($ziel, $jetzt); if ($lastwechsel) { $angebot = [$ziel]; $aenderungMoeglich = false; } $this->WriteAttributeString('Leistungsangebot', json_encode($angebot, JSON_THROW_ON_ERROR)); $this->setzeZustand('Sollleistung', $ziel); $this->setzeZustand('Verfuegbar', $verfuegbar); $this->setzeZustand('AenderungMoeglich', $aenderungMoeglich); $this->aktualisiereStoerungen($temperaturenGueltig); if ($meldungPlanen) { $this->SetTimerInterval('RueckmeldungVerzoegert', 100); } } catch (Throwable $fehler) { $this->WriteAttributeString('Schaltfehler', $fehler->getMessage()); $this->setzeZustand('Verfuegbar', false); $this->setzeZustand('AenderungMoeglich', false); $this->setzeZustand('Stoerung', true); $this->setzeZustand('Stoertext', $fehler->getMessage()); $this->SetStatus(self::STATUS_SCHALTFEHLER); $this->protokolliere('Regelzyklus', $fehler->getMessage()); } } private function aktualisiereTemperaturen(): bool { $pufferID = $this->ReadPropertyInteger('PufferfuehlerVariableID'); $aussenID = $this->ReadPropertyInteger('AussentemperaturVariableID'); if (!$this->temperaturwertGueltig($pufferID) || !$this->temperaturwertGueltig($aussenID)) { $this->setzeZustand('TemperaturenGueltig', false); return false; } $puffertemperatur = (float) GetValue($pufferID); $aussentemperatur = (float) GetValue($aussenID); $jetzt = time(); $letzteBerechnung = $this->ReadAttributeInteger('LetzteTemperaturberechnung'); $this->WriteAttributeInteger('LetzteTemperaturberechnung', $jetzt); if ($this->ReadPropertyBoolean('PuffertemperaturGlaetten')) { if (!$this->ReadAttributeBoolean('GlaettungInitialisiert')) { $this->WriteAttributeFloat('Glaettungswert', $puffertemperatur); $this->WriteAttributeBoolean('GlaettungInitialisiert', true); } elseif ($letzteBerechnung > 0 && $jetzt > $letzteBerechnung) { $puffertemperatur = PufferspeicherRegler::pt1( $this->ReadAttributeFloat('Glaettungswert'), $puffertemperatur, $jetzt - $letzteBerechnung, $this->ReadPropertyInteger('ZeitKonstante') ); $this->WriteAttributeFloat('Glaettungswert', $puffertemperatur); } else { $puffertemperatur = $this->ReadAttributeFloat('Glaettungswert'); } } else { $this->WriteAttributeBoolean('GlaettungInitialisiert', false); } $this->SetValue('Puffertemperatur', $puffertemperatur); $this->SetValue('Aussentemperatur', $aussentemperatur); $this->setzeZustand('TemperaturenGueltig', true); return true; } private function temperaturwertGueltig(int $variablenID): bool { if ($variablenID <= 0 || !IPS_VariableExists($variablenID)) { return false; } $variable = IPS_GetVariable($variablenID); if (!in_array((int) $variable['VariableType'], [1, 2], true)) { return false; } if (time() - (int) $variable['VariableUpdated'] > $this->ReadPropertyInteger('TemperaturMaxAlter')) { return false; } $wert = GetValue($variablenID); return is_int($wert) || is_float($wert); } private function uebernehmeWaermepumpenwert(): void { $wpID = $this->ReadPropertyInteger('WaermepumpenSolltemperaturVariableID'); $aussenID = $this->ReadPropertyInteger('AussentemperaturVariableID'); if (!$this->temperaturwertGueltig($wpID) || !$this->temperaturwertGueltig($aussenID)) { throw new RuntimeException('Waermepumpen-Solltemperatur oder Aussentemperatur ist ungueltig.'); } $fusspunkt = PufferspeicherRegler::fusspunktAusWaermepumpenwert( (float) GetValue($wpID), (float) GetValue($aussenID), $this->ReadPropertyFloat('HeizkurvenSteigung') ); IPS_SetProperty($this->InstanceID, 'FusspunktVorlauftemperatur', $fusspunkt); IPS_ApplyChanges($this->InstanceID); $this->protokolliere('Heizkurve', ['FusspunktVorlauftemperatur' => $fusspunkt]); } private function schalteLeistung(int $zielLeistung, int $jetzt): bool { $stufen = PufferspeicherRegler::dekodiereLeistungsstufen( $this->ReadPropertyString('LeistungsStufen') ); $zielStufe = PufferspeicherRegler::stufeFuerLeistung($stufen, $zielLeistung); $bisher = (int) round((float) $this->leseZustand('Istleistung')); if ($zielLeistung === $bisher && $this->kontakteEntsprechenZiel($stufen, $zielLeistung)) { $this->WriteAttributeString('Schaltfehler', ''); return false; } try { foreach ($stufen as $stufe) { RequestAction($stufe['KontaktID'], false); } foreach ($stufen as $stufe) { if ($stufe['Leistung_W'] === $zielLeistung && $zielLeistung > 0) { RequestAction($stufe['KontaktID'], true); break; } } } catch (Throwable $fehler) { foreach ($stufen as $stufe) { try { RequestAction($stufe['KontaktID'], false); } catch (Throwable $ignoriert) { } } $this->setzeZustand('Istleistung', 0.0); $this->setzeZustand('AktiveStufe', 0); throw new RuntimeException('Leistungsstufe konnte nicht sicher geschaltet werden: ' . $fehler->getMessage()); } $this->WriteAttributeString('Schaltfehler', ''); $this->setzeZustand('Istleistung', (float) $zielLeistung); $this->setzeZustand('Leistungsquelle', Nachrichtenvertrag::LEISTUNGSQUELLE_BERECHNET); $this->setzeZustand('AktiveStufe', $zielStufe); $this->WriteAttributeInteger('LetzterLastwechsel', $jetzt); $this->protokolliere('Leistungsstufe', ['Leistung_W' => $zielLeistung, 'Stufe' => $zielStufe]); return true; } /** @param list $stufen */ private function kontakteEntsprechenZiel(array $stufen, int $zielLeistung): bool { foreach ($stufen as $stufe) { if (!IPS_VariableExists($stufe['KontaktID'])) { return false; } $sollEin = $zielLeistung > 0 && $stufe['Leistung_W'] === $zielLeistung; if ((bool) GetValue($stufe['KontaktID']) !== $sollEin) { return false; } } return true; } private function aktualisiereEnergie(int $jetzt): void { $letzteBerechnung = $this->ReadAttributeInteger('LetzteBerechnung'); $this->WriteAttributeInteger('LetzteBerechnung', $jetzt); if ($letzteBerechnung <= 0 || $jetzt <= $letzteBerechnung) { return; } $zuwachs = PufferspeicherRegler::energieKWh( (float) $this->leseZustand('Istleistung'), $jetzt - $letzteBerechnung ); $this->setzeZustand( 'BezogeneEnergie', (float) $this->leseZustand('BezogeneEnergie') + $zuwachs ); } private function aktualisiereStoerungen(bool $temperaturenGueltig): void { $stoerungen = []; if (!$temperaturenGueltig) { $stoerungen[] = 'Puffer- oder Aussentemperatur fehlt oder ist veraltet.'; } if ($this->ReadAttributeString('Schaltfehler') !== '') { $stoerungen[] = $this->ReadAttributeString('Schaltfehler'); } $this->setzeZustand('Stoerung', $stoerungen !== []); $this->setzeZustand('Stoertext', implode(" ", array_values(array_unique($stoerungen)))); if ($this->ReadAttributeString('Schaltfehler') !== '') { $this->SetStatus(self::STATUS_SCHALTFEHLER); } elseif (!$temperaturenGueltig) { $this->SetStatus(self::STATUS_TEMPERATUR_UNGUELTIG); } else { $this->SetStatus(self::STATUS_AKTIV); } } 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 { $temperaturenGueltig = (bool) $this->leseZustand('TemperaturenGueltig'); $schaltfehler = $this->ReadAttributeString('Schaltfehler'); 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_BERECHNET, 'Zustand' => [ [ 'Kennung' => 'Sollleistung_W', 'Art' => 'Sollwert', 'Wert' => (bool) $this->leseZustand('SollwertGueltig') ? (int) $this->leseZustand('Sollleistung') : null, 'Einheit' => 'W', ], [ 'Kennung' => 'Puffertemperatur_C', 'Art' => 'Istwert', 'Wert' => $temperaturenGueltig ? (float) $this->GetValue('Puffertemperatur') : null, 'Einheit' => 'C', ], [ 'Kennung' => 'Aussentemperatur_C', 'Art' => 'Istwert', 'Wert' => $temperaturenGueltig ? (float) $this->GetValue('Aussentemperatur') : null, 'Einheit' => 'C', ], [ 'Kennung' => 'Solltemperatur_C', 'Art' => 'Sollwert', 'Wert' => $temperaturenGueltig ? (float) $this->GetValue('Solltemperatur') : null, 'Einheit' => 'C', ], [ 'Kennung' => 'Heizbedarf', 'Art' => 'Status', 'Wert' => (bool) $this->GetValue('Heizbedarf'), 'Einheit' => '', ], [ 'Kennung' => 'AktiveStufe', 'Art' => 'Status', 'Wert' => (int) $this->leseZustand('AktiveStufe'), 'Einheit' => '', ], [ 'Kennung' => 'Fuehlerfehler', 'Art' => 'Stoerung', 'Wert' => !$temperaturenGueltig, 'Einheit' => '', 'Text' => $temperaturenGueltig ? '' : 'Puffer- oder Aussentemperatur fehlt oder ist veraltet.', ], [ 'Kennung' => 'Schaltfehler', 'Art' => 'Stoerung', 'Wert' => $schaltfehler !== '', 'Einheit' => '', 'Text' => $schaltfehler, ], ], ]; } /** @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 pruefeKonfiguration(): void { foreach (['PrioritaetPV', 'PrioritaetPeak'] as $property) { if ($this->ReadPropertyInteger($property) < 0) { throw new InvalidArgumentException($property . ' muss mindestens 0 sein.'); } } foreach ([ 'Meldeintervall', 'VorgabeTimeout', 'TemperaturMaxAlter', 'ZeitKonstante', ] as $property) { if ($this->ReadPropertyInteger($property) <= 0) { throw new InvalidArgumentException($property . ' muss groesser als 0 sein.'); } } if ($this->ReadPropertyInteger('LastwechselSperrzeit') < 0) { throw new InvalidArgumentException('LastwechselSperrzeit darf nicht negativ sein.'); } if ($this->ReadPropertyFloat('Hysterese') <= 0.0) { throw new InvalidArgumentException('Hysterese muss groesser als 0 sein.'); } PufferspeicherRegler::solltemperatur( 20.0, $this->ReadPropertyFloat('FusspunktVorlauftemperatur'), $this->ReadPropertyFloat('HeizkurvenSteigung'), $this->ReadPropertyFloat('HeizkurveMinimaltemperatur'), $this->ReadPropertyFloat('HeizkurveMaximaltemperatur') ); PufferspeicherRegler::einschaltschwelle( 50.0, $this->ReadPropertyFloat('Hysterese'), $this->ReadPropertyInteger('MindesttemperaturModus'), $this->ReadPropertyFloat('Mindesttemperatur'), $this->ReadPropertyFloat('MindesttemperaturDifferenz') ); foreach (['PufferfuehlerVariableID', 'AussentemperaturVariableID'] as $property) { $variablenID = $this->ReadPropertyInteger($property); if ($variablenID <= 0 || !IPS_VariableExists($variablenID)) { throw new InvalidArgumentException($property . ' ist nicht eingerichtet.'); } $variable = IPS_GetVariable($variablenID); if (!in_array((int) $variable['VariableType'], [1, 2], true)) { throw new InvalidArgumentException($property . ' muss eine Integer- oder Floatvariable sein.'); } } $wpID = $this->ReadPropertyInteger('WaermepumpenSolltemperaturVariableID'); if ($wpID > 0) { if (!IPS_VariableExists($wpID)) { throw new InvalidArgumentException('WaermepumpenSolltemperaturVariableID existiert nicht.'); } $wpVariable = IPS_GetVariable($wpID); if (!in_array((int) $wpVariable['VariableType'], [1, 2], true)) { throw new InvalidArgumentException( 'WaermepumpenSolltemperaturVariableID muss eine Integer- oder Floatvariable sein.' ); } } $stufen = PufferspeicherRegler::dekodiereLeistungsstufen( $this->ReadPropertyString('LeistungsStufen') ); if ($stufen === []) { throw new InvalidArgumentException('Mindestens eine Leistungsstufe ist erforderlich.'); } foreach ($stufen as $stufe) { if (!IPS_VariableExists($stufe['KontaktID'])) { throw new InvalidArgumentException('Schaltkontakt ' . $stufe['KontaktID'] . ' existiert nicht.'); } $kontakt = IPS_GetVariable($stufe['KontaktID']); if ((int) $kontakt['VariableType'] !== 0) { throw new InvalidArgumentException('Alle Schaltkontakte muessen Booleanvariablen sein.'); } } } private function registriereTemperaturmeldungen(): void { $zuordnungen = [ 'RegistriertePufferID' => $this->ReadPropertyInteger('PufferfuehlerVariableID'), 'RegistrierteAussenID' => $this->ReadPropertyInteger('AussentemperaturVariableID'), ]; foreach ($zuordnungen as $attribut => $neueID) { $alteID = $this->ReadAttributeInteger($attribut); if ($alteID > 0 && $alteID !== $neueID) { $this->UnregisterMessage($alteID, self::VM_UPDATE); } if ($neueID > 0 && $neueID !== $alteID) { $this->RegisterMessage($neueID, self::VM_UPDATE); } $this->WriteAttributeInteger($attribut, $neueID); } } private function aktualisiereVariablen(): void { foreach (self::DIAGNOSE_VARIABLEN as $ident) { if ($ident === 'Leistungsquelle') { continue; } $variablenID = @IPS_GetObjectIDByIdent($ident, $this->InstanceID); if (is_int($variablenID) && $variablenID > 0 && IPS_VariableExists($variablenID)) { $this->setzeZustand($ident, GetValue($variablenID)); } } if ($this->ReadPropertyBoolean('DiagnosevariablenAnzeigen')) { $this->registriereVerbraucherDiagnose(); $this->RegisterVariableInteger('AktiveStufe', 'Aktive Stufe', '', 200); $this->RegisterVariableFloat('BezogeneEnergie', 'Bezogene Energie', '~Electricity', 210); $this->RegisterVariableBoolean( 'TemperaturenGueltig', 'Temperaturen gueltig', '~Switch', 220 ); foreach (self::DIAGNOSE_VARIABLEN as $ident) { $wert = $ident === 'Leistungsquelle' ? Nachrichtenvertrag::LEISTUNGSQUELLE_BERECHNET : $this->leseZustand($ident); $this->setzeZustand($ident, $wert); } } else { foreach (self::DIAGNOSE_VARIABLEN as $ident) { $variablenID = @IPS_GetObjectIDByIdent($ident, $this->InstanceID); if (is_int($variablenID) && $variablenID > 0 && IPS_VariableExists($variablenID)) { $this->UnregisterVariable($ident); } } } } /** @param bool|int|float|string $wert */ private function setzeZustand(string $ident, $wert): void { $booleanAttribute = [ 'SollwertGueltig' => 'ZustandSollwertGueltig', 'Verfuegbar' => 'ZustandVerfuegbar', 'AenderungMoeglich' => 'ZustandAenderungMoeglich', 'Stoerung' => 'ZustandStoerung', 'TemperaturenGueltig' => 'ZustandTemperaturenGueltig', ]; $integerAttribute = [ 'Sollleistung' => 'ZustandSollleistung', 'AktiveStufe' => 'ZustandAktiveStufe', ]; $floatAttribute = [ 'Istleistung' => 'ZustandIstleistung', 'BezogeneEnergie' => 'ZustandBezogeneEnergie', ]; $stringAttribute = ['Stoertext' => 'ZustandStoertext']; if (isset($booleanAttribute[$ident])) { $wert = (bool) $wert; $this->WriteAttributeBoolean($booleanAttribute[$ident], $wert); } elseif (isset($integerAttribute[$ident])) { $wert = (int) $wert; $this->WriteAttributeInteger($integerAttribute[$ident], $wert); } elseif (isset($floatAttribute[$ident])) { $wert = (float) $wert; $this->WriteAttributeFloat($floatAttribute[$ident], $wert); } elseif (isset($stringAttribute[$ident])) { $wert = (string) $wert; $this->WriteAttributeString($stringAttribute[$ident], $wert); } elseif ($ident !== 'Leistungsquelle') { throw new LogicException('Unbekannter interner Zustand: ' . $ident); } $variablenID = @IPS_GetObjectIDByIdent($ident, $this->InstanceID); if (is_int($variablenID) && $variablenID > 0 && IPS_VariableExists($variablenID)) { SetValue($variablenID, $wert); } } /** @return bool|int|float|string */ private function leseZustand(string $ident) { $booleanAttribute = [ 'SollwertGueltig' => 'ZustandSollwertGueltig', 'Verfuegbar' => 'ZustandVerfuegbar', 'AenderungMoeglich' => 'ZustandAenderungMoeglich', 'Stoerung' => 'ZustandStoerung', 'TemperaturenGueltig' => 'ZustandTemperaturenGueltig', ]; $integerAttribute = [ 'Sollleistung' => 'ZustandSollleistung', 'AktiveStufe' => 'ZustandAktiveStufe', ]; $floatAttribute = [ 'Istleistung' => 'ZustandIstleistung', 'BezogeneEnergie' => 'ZustandBezogeneEnergie', ]; $stringAttribute = ['Stoertext' => 'ZustandStoertext']; if (isset($booleanAttribute[$ident])) { return $this->ReadAttributeBoolean($booleanAttribute[$ident]); } if (isset($integerAttribute[$ident])) { return $this->ReadAttributeInteger($integerAttribute[$ident]); } if (isset($floatAttribute[$ident])) { return $this->ReadAttributeFloat($floatAttribute[$ident]); } if (isset($stringAttribute[$ident])) { return $this->ReadAttributeString($stringAttribute[$ident]); } throw new LogicException('Unbekannter interner Zustand: ' . $ident); } /** @return list */ private function leseLeistungsangebot(): array { $angebot = json_decode( $this->ReadAttributeString('Leistungsangebot'), true, 512, JSON_THROW_ON_ERROR ); if (!is_array($angebot)) { return [0]; } return array_values(array_map('intval', $angebot)); } private function deaktiviereTimer(): void { foreach (['LastwechselFreigabe', 'Meldezyklus', 'RueckmeldungVerzoegert'] as $timer) { $this->SetTimerInterval($timer, 0); } } /** @param mixed $daten */ private function protokolliere(string $bezeichnung, $daten): void { if (!$this->ReadPropertyBoolean('LoggingEin')) { return; } $this->SendDebug( $bezeichnung, is_string($daten) ? $daten : json_encode($daten, JSON_THROW_ON_ERROR), 0 ); } }