From f1f6943c93bbe170f6f1594bd4b3a1607c8495f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20H=C3=A4fliger?= Date: Fri, 21 Nov 2025 11:58:03 +0100 Subject: [PATCH] no message --- Shelly_Parser_MQTT/module.php | 327 ++++++++++------------------------ 1 file changed, 90 insertions(+), 237 deletions(-) diff --git a/Shelly_Parser_MQTT/module.php b/Shelly_Parser_MQTT/module.php index 8541c2e..aa85ff9 100644 --- a/Shelly_Parser_MQTT/module.php +++ b/Shelly_Parser_MQTT/module.php @@ -6,11 +6,13 @@ require_once __DIR__ . '/libs/ShellyParser.php'; class Shelly_Parser_MQTT extends IPSModule { + private bool $Subscribed = false; + public function Create() { parent::Create(); - // MQTT verbinden (nur Verknüpfung, noch kein Subscribe!) + // MQTT verbinden $this->ConnectParent('{C6D2AEB3-6E1F-4B2E-8E69-3A1A00246850}'); } @@ -21,87 +23,26 @@ class Shelly_Parser_MQTT extends IPSModule // Parent verbinden $this->ConnectParent('{C6D2AEB3-6E1F-4B2E-8E69-3A1A00246850}'); - // Auf Statusänderungen der eigenen Instanz hören - $this->RegisterMessage($this->InstanceID, IM_CHANGESTATUS); - - // Auf Statusänderungen des Parents hören, falls vorhanden - $inst = IPS_GetInstance($this->InstanceID); - $parentID = $inst['ConnectionID'] ?? 0; - if ($parentID > 0 && IPS_InstanceExists($parentID)) { - $this->RegisterMessage($parentID, IM_CHANGESTATUS); - } - - // WICHTIG: - // KEIN Subscribe() hier aufrufen! - // Subscribe passiert erst, wenn Instanz/Parent wirklich aktiv ist (MessageSink). + // Niemals im ApplyChanges Publish/Subscribe senden! + $this->Subscribed = false; } /* --------------------------------------------------------- - * MESSAGE SINK – REAKTION AUF STATUSÄNDERUNGEN + * SICHER SENDEN * ---------------------------------------------------------*/ - public function MessageSink($TimeStamp, $SenderID, $Message, $Data) + private function SafeSend(array $packet) { - $this->SendDebug('MessageSink', 'Sender=' . $SenderID . ' Message=' . $Message . ' Data=' . print_r($Data, true), 0); + $parent = @IPS_GetInstance($this->InstanceID)['ConnectionID'] ?? 0; - if ($Message === IM_CHANGESTATUS) { - - // Eigene Instanz-Statusänderung - if ($SenderID === $this->InstanceID) { - $newStatus = $Data[0] ?? 0; - $this->Log('STATUS', 'Instance status changed to ' . $newStatus); - if ($newStatus === IS_ACTIVE) { - // Jetzt ist das Modul aktiv → Subscribe starten - $this->Log('STATUS', 'Instance is ACTIVE → performing MQTT subscribe'); - $this->Subscribe('#'); - } - return; - } - - // Parent-Statusänderung - $inst = IPS_GetInstance($this->InstanceID); - $parentID = $inst['ConnectionID'] ?? 0; - - if (($parentID > 0) && ($SenderID === $parentID)) { - $newStatus = $Data[0] ?? 0; - $this->Log('STATUS', 'Parent status changed to ' . $newStatus); - - if ($newStatus === IS_ACTIVE) { - // Parent wieder aktiv → neu subscriben - $this->Log('STATUS', 'Parent is ACTIVE → performing MQTT subscribe'); - $this->Subscribe('#'); - } - } - } - } - - /* --------------------------------------------------------- - * HILFSFUNKTIONEN FÜR PARENT-CHECK & SICHERES SENDEN - * ---------------------------------------------------------*/ - private function HasActiveParent(): bool - { - if (!IPS_InstanceExists($this->InstanceID)) { - return false; - } - - $inst = IPS_GetInstance($this->InstanceID); - $parentID = $inst['ConnectionID'] ?? 0; - if ($parentID <= 0 || !IPS_InstanceExists($parentID)) { - return false; - } - - $parentStatus = IPS_GetInstance($parentID)['InstanceStatus']; - return ($parentStatus === IS_ACTIVE); - } - - private function SafeSendToParent(array $packet): void - { - if (!$this->HasActiveParent()) { - $this->Log('MQTT', 'Cannot send to parent – parent not active or not available'); + if ($parent === 0 || !IPS_InstanceExists($parent)) { return; } - // Fehler unterdrücken, falls doch mal etwas schiefgeht - @$this->SendDataToParent(json_encode($packet)); + if (IPS_GetInstance($parent)['InstanceStatus'] !== 102) { // IS_ACTIVE + return; + } + + @ $this->SendDataToParent(json_encode($packet)); } /* --------------------------------------------------------- @@ -114,47 +55,47 @@ class Shelly_Parser_MQTT extends IPSModule } /* --------------------------------------------------------- - * MQTT SUBSCRIBE + * SUBSCRIBE – nur wenn der Parent sicher aktiv ist! * ---------------------------------------------------------*/ - private function Subscribe(string $topic): void + private function EnsureSubscribe() { - $packet = [ + if ($this->Subscribed) { + return; + } + + $this->Subscribed = true; + + $this->SafeSend([ + 'DataID' => '{043EA491-0325-4ADD-8FC2-A30C8EEB4D3F}', 'PacketType' => 8, 'QualityOfService' => 0, 'Retain' => false, - 'Topic' => $topic, + 'Topic' => '#', 'Payload' => '' - ]; + ]); - $this->SafeSendToParent([ - 'DataID' => '{043EA491-0325-4ADD-8FC2-A30C8EEB4D3F}' - ] + $packet); - - $this->Log('Subscribe', $topic); + $this->Log("Subscribe", "#"); } /* --------------------------------------------------------- - * MQTT PUBLISH + * MQTT SEND * ---------------------------------------------------------*/ - private function Publish(string $topic, string $payload): void + private function Publish(string $topic, string $payload) { - $packet = [ + $this->SafeSend([ + 'DataID' => '{043EA491-0325-4ADD-8FC2-A30C8EEB4D3F}', 'PacketType' => 3, 'QualityOfService' => 0, 'Retain' => false, 'Topic' => $topic, 'Payload' => $payload - ]; + ]); - $this->SafeSendToParent([ - 'DataID' => '{043EA491-0325-4ADD-8FC2-A30C8EEB4D3F}' - ] + $packet); - - $this->Log('Publish', "$topic → $payload"); + $this->Log("Publish", "$topic → $payload"); } /* --------------------------------------------------------- - * REQUEST ACTION (Schalten von Outputs) + * REQUEST ACTION * ---------------------------------------------------------*/ public function RequestAction($Ident, $Value) { @@ -164,20 +105,15 @@ class Shelly_Parser_MQTT extends IPSModule throw new Exception("Unknown Ident: $Ident"); } - // Lokale Variable updaten $varID = $this->FindVariableByIdent($Ident); if ($varID > 0) { SetValue($varID, $Value); } - // Gerät + Index extrahieren - [$deviceID, $suffix] = explode('_output_', $Ident, 2); + [$deviceID, $suffix] = explode('_output_', $Ident); $index = intval($suffix); - // Shelly RPC Topic $topic = $deviceID . '/rpc'; - - // RPC JSON $payload = json_encode([ 'id' => 1, 'src' => 'ips', @@ -188,248 +124,165 @@ class Shelly_Parser_MQTT extends IPSModule ] ]); - $this->Log('MQTT SEND', "$topic : $payload"); - - // Senden $this->Publish($topic, $payload); } /* --------------------------------------------------------- - * RECEIVE MQTT + * RECEIVE DATA * ---------------------------------------------------------*/ public function ReceiveData($JSONString) { $this->Log('ReceiveData', $JSONString); - $data = json_decode($JSONString, true); - if (!is_array($data)) { - return; - } + // Erst beim ersten Datenpaket subscriben + $this->EnsureSubscribe(); - $topic = $data['Topic'] ?? ''; + $data = json_decode($JSONString, true); + if (!is_array($data)) return; + + $topic = $data['Topic'] ?? ''; $payload = $data['Payload'] ?? ''; - if ($topic === '') { - return; - } + if ($topic === '') return; $this->Log('ReceiveTopic', "$topic → $payload"); - $parts = explode('/', $topic); + $parts = explode('/', $topic); $deviceID = $parts[0] ?? ''; - if ($deviceID === '') { - return; - } + if ($deviceID === '') return; - // Online-Status + // Online if (($parts[1] ?? '') === 'online') { $this->HandleOnline($deviceID, $payload); return; } - // RPC Event - if (($parts[1] ?? '') === 'events' && - ($parts[2] ?? '') === 'rpc') { + // RPC + if (($parts[1] ?? '') === 'events' && ($parts[2] ?? '') === 'rpc') { $this->HandleRPC($deviceID, $payload); return; } } /* --------------------------------------------------------- - * ONLINE STATUS + * ONLINE * ---------------------------------------------------------*/ - private function HandleOnline(string $deviceID, string $payload): void + private function HandleOnline(string $deviceID, string $payload) { $value = ($payload === 'true' || $payload === '1'); - $this->Log('Online', "$deviceID = " . ($value ? 'true' : 'false')); - $varID = $this->EnsureBooleanVariable($deviceID, $deviceID . '_online', 'Online'); SetValue($varID, $value); } /* --------------------------------------------------------- - * RPC-HANDLING + * RPC * ---------------------------------------------------------*/ - private function HandleRPC(string $deviceID, string $payload): void + private function HandleRPC(string $deviceID, string $payload) { - $this->Log('RPC', "$deviceID : $payload"); - $json = json_decode($payload, true); - if (!is_array($json)) { - return; - } + if (!is_array($json)) return; $src = $json['src'] ?? ''; - if (!str_starts_with($src, 'shelly')) { - return; - } + if (!str_starts_with($src, 'shelly')) return; // Typ - $type = ShellyParser::ExtractType($src); + $type = ShellyParser::ExtractType($src); $typeID = $this->EnsureStringVariable($deviceID, $deviceID . '_type', 'Typ'); SetValue($typeID, $type); - // Parameter $params = $json['params'] ?? []; $mapped = ShellyParser::MapParams($params); // Outputs - foreach ($mapped['outputs'] as $index => $value) { - $ident = $deviceID . '_output_' . $index; - $varID = $this->EnsureBooleanVariable($deviceID, $ident, 'Output ' . $index); - SetValue($varID, $value); + foreach ($mapped['outputs'] as $i => $val) { + $vid = $this->EnsureBooleanVariable($deviceID, $deviceID . '_output_' . $i, 'Output ' . $i); + SetValue($vid, $val); } // Inputs - foreach ($mapped['inputs'] as $index => $value) { - $ident = $deviceID . '_input_' . $index; - $varID = $this->EnsureBooleanVariable($deviceID, $ident, 'Input ' . $index); - SetValue($varID, $value); + foreach ($mapped['inputs'] as $i => $val) { + $vid = $this->EnsureBooleanVariable($deviceID, $deviceID . '_input_' . $i, 'Input ' . $i); + SetValue($vid, $val); } // Temperatur if ($mapped['temperature'] !== null) { - $tempID = $this->EnsureFloatVariable($deviceID, $deviceID . '_temperature', 'Temperatur'); - SetValue($tempID, $mapped['temperature']); + $tid = $this->EnsureFloatVariable($deviceID, $deviceID . '_temperature', 'Temperatur'); + SetValue($tid, $mapped['temperature']); } } /* --------------------------------------------------------- - * ACTIONSCRIPT – UNSICHTBAR - * ---------------------------------------------------------*/ - private function EnsureActionScript(): int - { - $scriptName = '~ShellyMQTT_Action_' . $this->InstanceID; - - foreach (IPS_GetChildrenIDs($this->InstanceID) as $cid) { - $obj = IPS_GetObject($cid); - - if ($obj['ObjectType'] === OBJECTTYPE_SCRIPT && $obj['ObjectName'] === $scriptName) { - return $cid; - } - } - - // neues verstecktes Script - $scriptID = IPS_CreateScript(0); - IPS_SetName($scriptID, $scriptName); - IPS_SetParent($scriptID, $this->InstanceID); - - // unsichtbar - IPS_SetHidden($scriptID, true); - IPS_SetPosition($scriptID, -9999); - - // Scriptcode - $php = 'InstanceID . '; -$object = IPS_GetObject($_IPS["VARIABLE"]); -$ident = $object["ObjectIdent"]; -$value = $_IPS["VALUE"]; -IPS_RequestAction($instance, $ident, $value); -?>'; - - IPS_SetScriptContent($scriptID, $php); - - return $scriptID; - } - - /* --------------------------------------------------------- - * VARIABLEN – DYNAMISCH + * VARIABLEN ETC. * ---------------------------------------------------------*/ private function EnsureBooleanVariable(string $deviceID, string $ident, string $name): int { - $folderID = $this->GetDeviceFolder($deviceID); - - foreach (IPS_GetChildrenIDs($folderID) as $cid) { - $o = IPS_GetObject($cid); - - if ($o['ObjectIdent'] === $ident) { - - // Outputs schaltbar machen - if (str_contains($ident, '_output_')) { - $actionScript = $this->EnsureActionScript(); - IPS_SetVariableCustomAction($cid, $actionScript); - - $var = IPS_GetVariable($cid); - if ($var['VariableProfile'] === '' && $var['VariableCustomProfile'] === '') { - IPS_SetVariableCustomProfile($cid, '~Switch'); - } - } + $folder = $this->GetDeviceFolder($deviceID); + foreach (IPS_GetChildrenIDs($folder) as $cid) { + if (IPS_GetObject($cid)['ObjectIdent'] === $ident) { return $cid; } } - // Neu erzeugen - $varID = IPS_CreateVariable(0); - IPS_SetIdent($varID, $ident); - IPS_SetName($varID, $name); - IPS_SetParent($varID, $folderID); + $vid = IPS_CreateVariable(0); + IPS_SetName($vid, $name); + IPS_SetIdent($vid, $ident); + IPS_SetParent($vid, $folder); - if (str_contains($ident, '_output_')) { - $actionScript = $this->EnsureActionScript(); - IPS_SetVariableCustomAction($varID, $actionScript); - IPS_SetVariableCustomProfile($varID, '~Switch'); - } - - return $varID; + return $vid; } private function EnsureFloatVariable(string $deviceID, string $ident, string $name): int { - $folderID = $this->GetDeviceFolder($deviceID); + $folder = $this->GetDeviceFolder($deviceID); - foreach (IPS_GetChildrenIDs($folderID) as $cid) { + foreach (IPS_GetChildrenIDs($folder) as $cid) { if (IPS_GetObject($cid)['ObjectIdent'] === $ident) { return $cid; } } - $id = $this->RegisterVariableFloat($ident, $name); - IPS_SetParent($id, $folderID); - return $id; + $vid = $this->RegisterVariableFloat($ident, $name); + IPS_SetParent($vid, $folder); + return $vid; } private function EnsureStringVariable(string $deviceID, string $ident, string $name): int { - $folderID = $this->GetDeviceFolder($deviceID); + $folder = $this->GetDeviceFolder($deviceID); - foreach (IPS_GetChildrenIDs($folderID) as $cid) { + foreach (IPS_GetChildrenIDs($folder) as $cid) { if (IPS_GetObject($cid)['ObjectIdent'] === $ident) { return $cid; } } - $id = $this->RegisterVariableString($ident, $name); - IPS_SetParent($id, $folderID); - return $id; + $vid = $this->RegisterVariableString($ident, $name); + IPS_SetParent($vid, $folder); + return $vid; } - /* --------------------------------------------------------- - * GERÄTE-ORDNER - * ---------------------------------------------------------*/ private function GetDeviceFolder(string $deviceID): int { - $folderIdent = 'folder_' . $deviceID; + $ident = 'folder_' . $deviceID; foreach (IPS_GetChildrenIDs($this->InstanceID) as $cid) { - if (IPS_GetObject($cid)['ObjectIdent'] === $folderIdent) { + if (IPS_GetObject($cid)['ObjectIdent'] === $ident) { return $cid; } } - $folderID = IPS_CreateCategory(); - IPS_SetParent($folderID, $this->InstanceID); - IPS_SetName($folderID, $deviceID); - IPS_SetIdent($folderID, $folderIdent); + $fid = IPS_CreateCategory(); + IPS_SetName($fid, $deviceID); + IPS_SetIdent($fid, $ident); + IPS_SetParent($fid, $this->InstanceID); - return $folderID; + return $fid; } - /* --------------------------------------------------------- - * VARIABLE FINDEN - * ---------------------------------------------------------*/ private function FindVariableByIdent(string $Ident) { foreach (IPS_GetChildrenIDs($this->InstanceID) as $folder) { @@ -443,4 +296,4 @@ IPS_RequestAction($instance, $ident, $value); } } -?> \ No newline at end of file +?>