From 2d337c630836496308f5c70b9814757c8358c981 Mon Sep 17 00:00:00 2001 From: jhartworks Date: Wed, 9 Sep 2026 11:35:48 +0200 Subject: [PATCH] Improve category checks in MonitoringServer for robustness and clarity --- Monitoring Client/module.php | 447 +++++++++++-------------- Monitoring Server Influx V1/module.php | 42 ++- 2 files changed, 224 insertions(+), 265 deletions(-) diff --git a/Monitoring Client/module.php b/Monitoring Client/module.php index a74ffb4..5dea58d 100644 --- a/Monitoring Client/module.php +++ b/Monitoring Client/module.php @@ -67,60 +67,70 @@ class MonitoringClient extends IPSModule { } $module_id = "{01C00ADD-D04E-452E-B66A-D253278743FE}" /* Module ID of MQTT Server Device */; - $ident = "TempMQTTDevice"; - - // enter semaphore to ensure the temporary device gets used by one thread at a time - if(IPS_SemaphoreEnter($ident, 100)) { - // get temporary MQTT Server Device or create if needed - $id = @IPS_GetObjectIDByIdent($ident, $_IPS['SELF']); - if($id === false) { - $id = @IPS_CreateInstance($module_id); - if($id === false) { - return false; - } - IPS_SetParent($id, $_IPS['SELF']); - IPS_SetIdent($id, $ident); - } - - // ensure the specified server instance is actually compatible - if(!IPS_IsInstanceCompatible($id, $server_id)) { - return false; - } - - // ensure that the temporary device is actually connected to the correct server instance - $inst_config = IPS_GetInstance($id); - if($inst_config["ConnectionID"] != $server_id) { - IPS_DisconnectInstance($id); - if(!@IPS_ConnectInstance($id, $server_id)) { - return false; - } - } - - // name object to help with debugging - IPS_SetName($id, "Temporary MQTT Device for: " . $topic); - - // configure temporary device - $config_arr = array( - "Retain" => $retain, - "Topic" => $topic, - "Type" => $ips_var_type - ); - $config_str = json_encode($config_arr); - IPS_SetConfiguration($id, $config_str); - IPS_SetHidden($id,true); - IPS_ApplyChanges($id); - - // get Value variable and use it to publish the payload - $var_id = @IPS_GetObjectIDByIdent("Value", $id); - RequestAction($var_id, $payload); - - IPS_SemaphoreLeave($ident); - } else { // semaphore timeout - return false; - } - - return true; - } // MQTT_Publish + $ident = "TempMQTTDevice"; + // Each client instance owns its temporary device. Do not let different + // Monitoring Client instances block each other during a force update. + $semaphore = $ident . "_" . $this->InstanceID; + + // enter semaphore to ensure the temporary device gets used by one thread at a time + if(IPS_SemaphoreEnter($semaphore, 5000)) { + try { + // get temporary MQTT Server Device or create if needed + $id = @IPS_GetObjectIDByIdent($ident, $this->InstanceID); + if($id === false) { + $id = @IPS_CreateInstance($module_id); + if($id === false) { + return false; + } + IPS_SetParent($id, $this->InstanceID); + IPS_SetIdent($id, $ident); + } + + // ensure the specified server instance is actually compatible + if(!IPS_IsInstanceCompatible($id, $server_id)) { + return false; + } + + // ensure that the temporary device is actually connected to the correct server instance + $inst_config = IPS_GetInstance($id); + if($inst_config["ConnectionID"] != $server_id) { + IPS_DisconnectInstance($id); + if(!@IPS_ConnectInstance($id, $server_id)) { + return false; + } + } + + // name object to help with debugging + IPS_SetName($id, "Temporary MQTT Device for: " . $topic); + + // configure temporary device + $config_arr = array( + "Retain" => $retain, + "Topic" => $topic, + "Type" => $ips_var_type + ); + $config_str = json_encode($config_arr); + IPS_SetConfiguration($id, $config_str); + IPS_SetHidden($id,true); + IPS_ApplyChanges($id); + + // get Value variable and use it to publish the payload + $var_id = @IPS_GetObjectIDByIdent("Value", $id); + if ($var_id === false) { + IPS_LogMessage("Monitoring Client", "MQTT device has no Value variable for topic: " . $topic); + return false; + } + RequestAction($var_id, $payload); + + return true; + } finally { + IPS_SemaphoreLeave($semaphore); + } + } else { // semaphore timeout + IPS_LogMessage("Monitoring Client", "MQTT publish timed out waiting for topic: " . $topic); + return false; + } + } // MQTT_Publish public function MqttSync($server_id, $topic, $payload, $retain, $ident, $source) { IPS_LogMessage("Monitoring Client", "MQTT Sync called with Topic: " . $topic . " Payload: " . $payload. " Source: " . $source . " Ident: " . $ident); @@ -240,201 +250,140 @@ class MonitoringClient extends IPSModule { return true; } // MQTT_Sync - public function SendTopic() { - - $mqttId = $this->ReadPropertyInteger("MqttCLientID"); - $projectyear = $this->ReadPropertyInteger("Projectyear"); - $projectnumber = $this->ReadPropertyInteger("Projectnumber"); - $projectname = $this->ReadPropertyString("Projectname"); - $ispnumber = $this->ReadPropertyInteger("ISP"); - $updatetime = $this->ReadPropertyInteger("Updatetime"); - - $catIds[0]["id"] = $this->ReadPropertyInteger("ParseNotifyCategoryID"); - $catIds[0]["top"] = "Projekte". $projectyear."/P".$projectnumber. "/ISP" .$ispnumber. "/Notify/"; - $catIds[0]["setpoint"] = false; - $catIds[1]["id"] = $this->ReadPropertyInteger("ParseAlarmCategoryID"); - $catIds[1]["top"] = "Projekte". $projectyear."/P".$projectnumber. "/ISP" .$ispnumber. "/Alarm/"; - $catIds[1]["setpoint"] = false; - $catIds[2]["id"] = $this->ReadPropertyInteger("ParseAnalogCategoryID"); - $catIds[2]["top"] = "Projekte". $projectyear."/P".$projectnumber. "/ISP" .$ispnumber. "/Analog/"; - $catIds[2]["setpoint"] = false; - $catIds[3]["id"] = $this->ReadPropertyInteger("ParseSetpointCategoryID"); - $catIds[3]["top"] = "Projekte". $projectyear."/P".$projectnumber. "/ISP" .$ispnumber. "/Setpoint/"; - $catIds[3]["setpoint"] = true; - - - foreach ($catIds as $catId){ - - $isSetpoint = isset($catId["setpoint"]) ? $catId["setpoint"] : false; - - $catChilds = IPS_GetChildrenIDs($catId["id"]); - - foreach ($catChilds as $catChild) { - - $childids = IPS_GetChildrenIDs($catChild); - - $parname = IPS_GetName($catChild); - - if (IPS_VariableExists($catChild) != 1 && IPS_LinkExists($catChild) != 1){ - - foreach ($childids as $childid){ - - foreach ($childids as $childid){ - if (IPS_VariableExists($childid) != 1){ - - if(IPS_LinkExists($childid) == 1){ - $linkInfo = IPS_GetLink($childid); - $linkTarget = $linkInfo["TargetID"]; - $childid = $linkTarget; - $varInfo = IPS_GetVariable($childid); - } - } - - $varInfo = IPS_GetVariable($childid); - $changedtime = $varInfo["VariableChanged"]; - $varname = IPS_GetName($childid); - $topic = $catId["top"]. $parname."_".$varname; - $syncname = $parname."_".$varname; - $payload = round(getvalue($childid), 2); - $time = time(); - - $isSetpoint = isset($catId["setpoint"]) ? $catId["setpoint"] : false; - - if ($isSetpoint == true){ - IPS_LogMessage("Monitoring Client", "Sync Setpoint: " . $topic . " mit Payload: " . $payload. " und Source: " . $childid . " und Ident: " . $syncname); - $this->MqttSync($mqttId, $topic, $payload, false, $syncname, $childid); - - }else{ - if($changedtime > $time - $updatetime){ - $this->MqttPublish($mqttId, $topic, $payload, false); - } - } - - } - - } - }else{ - $childid = $catChild; - if (IPS_VariableExists($childid) != 1){ - - if(IPS_LinkExists($childid) == 1){ - $linkInfo = IPS_GetLink($childid); - $linkTarget = $linkInfo["TargetID"]; - $childid = $linkTarget; - $varInfo = IPS_GetVariable($childid); - } - } - - $varInfo = IPS_GetVariable($childid); - $changedtime = $varInfo["VariableChanged"]; - $varname = IPS_GetName($childid); - $topic = $catId["top"]. $parname."_".$varname; - $payload = round(getvalue($childid), 2); - $time = time(); - - $isSetpoint = isset($catId["setpoint"]) ? $catId["setpoint"] : false; - - if ($isSetpoint == true){ - IPS_LogMessage("Monitoring Client", "Sync Setpoint: " . $topic . " mit Payload: " . $payload. " und Source: " . $childid . " und Ident: " . $syncname); - $this->MqttSync($mqttId, $topic, $payload, false, $parname."_".$varname, $childid); - - }else{ - if($changedtime > $time - $updatetime){ - $this->MqttPublish($mqttId, $topic, $payload, false); - } - } - } - } - - } - $topic = "Projekte". $projectyear."/".$projectnumber. "/ISP" .$ispnumber. "/Name"; - $this->MqttPublish($mqttId, $topic, $projectname, false); - - } - public function ForceSendTopic() { - - $mqttId = $this->ReadPropertyInteger("MqttCLientID"); - $projectyear = $this->ReadPropertyInteger("Projectyear"); - $projectnumber = $this->ReadPropertyInteger("Projectnumber"); - $projectname = $this->ReadPropertyString("Projectname"); - $ispnumber = $this->ReadPropertyInteger("ISP"); - $updatetime = $this->ReadPropertyInteger("Updatetime"); - - $catIds[0]["id"] = $this->ReadPropertyInteger("ParseNotifyCategoryID"); - $catIds[0]["top"] = "Projekte". $projectyear."/P".$projectnumber. "/ISP" .$ispnumber. "/Notify/"; - $catIds[1]["id"] = $this->ReadPropertyInteger("ParseAlarmCategoryID"); - $catIds[1]["top"] = "Projekte". $projectyear."/P".$projectnumber. "/ISP" .$ispnumber. "/Alarm/"; - $catIds[2]["id"] = $this->ReadPropertyInteger("ParseAnalogCategoryID"); - $catIds[2]["top"] = "Projekte". $projectyear."/P".$projectnumber. "/ISP" .$ispnumber. "/Analog/"; - - - - foreach ($catIds as $catId){ - - $catChilds = IPS_GetChildrenIDs($catId["id"]); - - foreach ($catChilds as $catChild) { - - $childids = IPS_GetChildrenIDs($catChild); - - $parname = IPS_GetName($catChild); - - if (IPS_VariableExists($catChild) != 1 && IPS_LinkExists($catChild) != 1){ - - foreach ($childids as $childid){ - - - - foreach ($childids as $childid){ - if (IPS_VariableExists($childid) != 1){ - - if(IPS_LinkExists($childid) == 1){ - $linkInfo = IPS_GetLink($childid); - $linkTarget = $linkInfo["TargetID"]; - $childid = $linkTarget; - $varInfo = IPS_GetVariable($childid); - } - } - - $varInfo = IPS_GetVariable($childid); - $varname = IPS_GetName($childid); - $topic = $catId["top"]. $parname."_".$varname; - $payload = round(getvalue($childid), 2); - - $this->MqttPublish($mqttId, $topic, $payload, false); - // IPS_LogMessage("Monitoring Client", "Force Update with Parent: " . $topic . " mit Payload: " . $payload); - - } - - } - }else{ - $childid = $catChild; - if (IPS_VariableExists($childid) != 1){ - - if(IPS_LinkExists($childid) == 1){ - $linkInfo = IPS_GetLink($childid); - $linkTarget = $linkInfo["TargetID"]; - $childid = $linkTarget; - $varInfo = IPS_GetVariable($childid); - } - } - - $varInfo = IPS_GetVariable($childid); - $varname = IPS_GetName($childid); - $topic = $catId["top"].$varname; - $payload = round(getvalue($childid), 2); - - $this->MqttPublish($mqttId, $topic, $payload, false); - // IPS_LogMessage("Monitoring Client", "Force Update none Parent: " . $topic . " mit Payload: " . $payload); - } - } - - } - $topic = "Projekte". $projectyear."/".$projectnumber. "/ISP" .$ispnumber. "/Name"; - $this->MqttPublish($mqttId, $topic, $projectname, false); - - } + private function resolveLinkTarget($objectId) { + $visited = array(); + while ($objectId > 0 && IPS_LinkExists($objectId)) { + if (isset($visited[$objectId])) { + return 0; + } + + $visited[$objectId] = true; + $objectId = IPS_GetLink($objectId)["TargetID"]; + } + + return $objectId; + } + + /* + * Return every variable directly represented by a monitoring category. + * The parent name is deliberately kept from the visible object/link in the + * configured tree, so existing MQTT topic names remain unchanged. + */ + private function getCategoryVariables($categoryId) { + $variables = array(); + if (!IPS_CategoryExists($categoryId)) { + return $variables; + } + + foreach (IPS_GetChildrenIDs($categoryId) as $entryId) { + $entryName = IPS_GetName($entryId); + $targetId = $this->resolveLinkTarget($entryId); + if ($targetId <= 0 || !IPS_ObjectExists($targetId)) { + continue; + } + + // A variable or a link to a variable is located directly in the category. + if (IPS_VariableExists($targetId)) { + $variables[] = array( + "id" => $targetId, + "parent" => $entryName, + "name" => IPS_GetName($targetId), + "direct" => true + ); + continue; + } + + // An object (or a link to an object) may contain one or several values. + foreach (IPS_GetChildrenIDs($targetId) as $childId) { + $variableId = $this->resolveLinkTarget($childId); + if ($variableId <= 0 || !IPS_VariableExists($variableId)) { + continue; + } + + $variables[] = array( + "id" => $variableId, + "parent" => $entryName, + "name" => IPS_GetName($variableId), + "direct" => false + ); + } + } + + return $variables; + } + + private function getTopicForVariable($top, $variable, $force) { + // The old force path used the bare variable name for values directly in + // the category. Keep that legacy topic spelling intact. + if ($force && $variable["direct"]) { + return $top . $variable["name"]; + } + + return $top . $variable["parent"] . "_" . $variable["name"]; + } + + public function SendTopic() { + $mqttId = $this->ReadPropertyInteger("MqttCLientID"); + $projectyear = $this->ReadPropertyInteger("Projectyear"); + $projectnumber = $this->ReadPropertyInteger("Projectnumber"); + $projectname = $this->ReadPropertyString("Projectname"); + $ispnumber = $this->ReadPropertyInteger("ISP"); + $updatetime = $this->ReadPropertyInteger("Updatetime"); + + $catIds = array( + array("id" => $this->ReadPropertyInteger("ParseNotifyCategoryID"), "top" => "Projekte" . $projectyear . "/P" . $projectnumber . "/ISP" . $ispnumber . "/Notify/", "setpoint" => false), + array("id" => $this->ReadPropertyInteger("ParseAlarmCategoryID"), "top" => "Projekte" . $projectyear . "/P" . $projectnumber . "/ISP" . $ispnumber . "/Alarm/", "setpoint" => false), + array("id" => $this->ReadPropertyInteger("ParseAnalogCategoryID"), "top" => "Projekte" . $projectyear . "/P" . $projectnumber . "/ISP" . $ispnumber . "/Analog/", "setpoint" => false), + array("id" => $this->ReadPropertyInteger("ParseSetpointCategoryID"), "top" => "Projekte" . $projectyear . "/P" . $projectnumber . "/ISP" . $ispnumber . "/Setpoint/", "setpoint" => true) + ); + + foreach ($catIds as $catId) { + foreach ($this->getCategoryVariables($catId["id"]) as $variable) { + $varInfo = IPS_GetVariable($variable["id"]); + $changedtime = $varInfo["VariableChanged"]; + $topic = $this->getTopicForVariable($catId["top"], $variable, false); + $payload = round(GetValue($variable["id"]), 2); + + if ($catId["setpoint"]) { + $syncname = $variable["parent"] . "_" . $variable["name"]; + IPS_LogMessage("Monitoring Client", "Sync Setpoint: " . $topic . " mit Payload: " . $payload . " und Source: " . $variable["id"] . " und Ident: " . $syncname); + $this->MqttSync($mqttId, $topic, $payload, false, $syncname, $variable["id"]); + } elseif ($changedtime > time() - $updatetime) { + $this->MqttPublish($mqttId, $topic, $payload, false); + } + } + } + + $topic = "Projekte" . $projectyear . "/" . $projectnumber . "/ISP" . $ispnumber . "/Name"; + $this->MqttPublish($mqttId, $topic, $projectname, false); + } + + public function ForceSendTopic() { + $mqttId = $this->ReadPropertyInteger("MqttCLientID"); + $projectyear = $this->ReadPropertyInteger("Projectyear"); + $projectnumber = $this->ReadPropertyInteger("Projectnumber"); + $projectname = $this->ReadPropertyString("Projectname"); + $ispnumber = $this->ReadPropertyInteger("ISP"); + + $catIds = array( + array("id" => $this->ReadPropertyInteger("ParseNotifyCategoryID"), "top" => "Projekte" . $projectyear . "/P" . $projectnumber . "/ISP" . $ispnumber . "/Notify/"), + array("id" => $this->ReadPropertyInteger("ParseAlarmCategoryID"), "top" => "Projekte" . $projectyear . "/P" . $projectnumber . "/ISP" . $ispnumber . "/Alarm/"), + array("id" => $this->ReadPropertyInteger("ParseAnalogCategoryID"), "top" => "Projekte" . $projectyear . "/P" . $projectnumber . "/ISP" . $ispnumber . "/Analog/"), + array("id" => $this->ReadPropertyInteger("ParseSetpointCategoryID"), "top" => "Projekte" . $projectyear . "/P" . $projectnumber . "/ISP" . $ispnumber . "/Setpoint/") + ); + + foreach ($catIds as $catId) { + foreach ($this->getCategoryVariables($catId["id"]) as $variable) { + $topic = $this->getTopicForVariable($catId["top"], $variable, true); + $payload = round(GetValue($variable["id"]), 2); + $this->MqttPublish($mqttId, $topic, $payload, false); + } + } + + $topic = "Projekte" . $projectyear . "/" . $projectnumber . "/ISP" . $ispnumber . "/Name"; + $this->MqttPublish($mqttId, $topic, $projectname, false); + } } -?> \ No newline at end of file +?> diff --git a/Monitoring Server Influx V1/module.php b/Monitoring Server Influx V1/module.php index 247624a..c5bd219 100644 --- a/Monitoring Server Influx V1/module.php +++ b/Monitoring Server Influx V1/module.php @@ -182,9 +182,8 @@ class MonitoringServer extends IPSModule { $inalarmcount = 0; $alarmvalues = 0; - $catChilds = IPS_GetChildrenIDs($catAlarmId); - - if ($catAlarmId > 0){ + if (IPS_CategoryExists($catAlarmId)){ + $catChilds = IPS_GetChildrenIDs($catAlarmId); foreach ($catChilds as $catChild) { @@ -192,6 +191,9 @@ class MonitoringServer extends IPSModule { $parname = IPS_GetName($catChild); foreach ($childids as $childid){ + if (!IPS_VariableExists($childid)) { + continue; + } $varInfo = IPS_GetVariable($childid); $changedtime = $varInfo["VariableChanged"]; @@ -212,11 +214,13 @@ class MonitoringServer extends IPSModule { } SetValueInteger($this->GetIDForIdent("AlarmVarCount"),$alarmvalues); SetValueInteger($this->GetIDForIdent("AlarmActiveCount"),$inalarmcount); - $rootcat = IPS_GetParent($catAlarmId); - if ($inalarmcount > 0 && $rootcat >0){ - IPS_SetIcon($rootcat,"Alert"); - }else{ - IPS_SetIcon($rootcat,""); + if (IPS_CategoryExists($catAlarmId)) { + $rootcat = IPS_GetParent($catAlarmId); + if ($inalarmcount > 0 && $rootcat >0){ + IPS_SetIcon($rootcat,"Alert"); + }else{ + IPS_SetIcon($rootcat,""); + } } } @@ -256,6 +260,7 @@ class MonitoringServer extends IPSModule { $catNotifyId = $this->ReadPropertyInteger("ParseNotifyCategoryID"); $catAnalogId = $this->ReadPropertyInteger("ParseAnalogCategoryID"); + $catSetpointId = $this->ReadPropertyInteger("ParseSetpointCategoryID"); $projectnumber = $this->ReadPropertyInteger("Projectnumber"); $projectname = $this->ReadPropertyString("Projectname"); @@ -274,7 +279,7 @@ class MonitoringServer extends IPSModule { - if ($catAnalogId > 0){ + if (IPS_CategoryExists($catAnalogId)){ $catChilds = IPS_GetChildrenIDs($catAnalogId); @@ -285,6 +290,9 @@ class MonitoringServer extends IPSModule { $parname = IPS_GetName($catChild); foreach ($objchildids as $objchildid){ + if (!IPS_VariableExists($objchildid)) { + continue; + } $varInfo = IPS_GetVariable($objchildid); $changedtime = $varInfo["VariableChanged"]; @@ -348,7 +356,7 @@ class MonitoringServer extends IPSModule { SetValueInteger($this->GetIDForIdent("AnalogVarCount"),$numberofvalues); $numberofvalues = 0; - if ($catSetpointId > 0){ + if (IPS_CategoryExists($catSetpointId)){ $catChilds = IPS_GetChildrenIDs($catSetpointId); @@ -366,11 +374,10 @@ class MonitoringServer extends IPSModule { } } - SetValueInteger($this->GetIDForIdent("SetpointVarCount"),$numberofvalues); $numberofvalues = 0; - if ($catNotifyId > 0){ + if (IPS_CategoryExists($catNotifyId)){ $catChilds = IPS_GetChildrenIDs($catNotifyId); @@ -381,6 +388,9 @@ class MonitoringServer extends IPSModule { $parname = IPS_GetName($catChild); foreach ($objchildids as $objchildid){ + if (!IPS_VariableExists($objchildid)) { + continue; + } $varInfo = IPS_GetVariable($objchildid); $changedtime = $varInfo["VariableChanged"]; @@ -709,7 +719,7 @@ class MonitoringServer extends IPSModule { $catAnalogId = $this->ReadPropertyInteger("ParseAnalogCategoryID"); $catAlarmId = $this->ReadPropertyInteger("ParseAlarmCategoryID"); - if ($catNotifyId > 0){ + if (IPS_CategoryExists($catNotifyId)){ $catChilds = IPS_GetChildrenIDs($catNotifyId); @@ -723,7 +733,7 @@ class MonitoringServer extends IPSModule { } } - if ($catAnalogId > 0){ + if (IPS_CategoryExists($catAnalogId)){ $catChilds = IPS_GetChildrenIDs($catAnalogId); @@ -737,7 +747,7 @@ class MonitoringServer extends IPSModule { } } - if ($catAlarmId > 0){ + if (IPS_CategoryExists($catAlarmId)){ $catChilds = IPS_GetChildrenIDs($catAlarmId); @@ -879,4 +889,4 @@ private function generateAndUploadCsv($catId, $ftp, $remotePath, $localPath) { } -?> \ No newline at end of file +?>