390 lines
17 KiB
PHP
390 lines
17 KiB
PHP
<?
|
|
// Klassendefinition
|
|
class MonitoringClient extends IPSModule {
|
|
// Überschreibt die interne IPS_Create($id) Funktion
|
|
public function Create() {
|
|
// Diese Zeile nicht löschen.
|
|
parent::Create();
|
|
|
|
$this->RegisterPropertyInteger("ParseNotifyCategoryID","0");
|
|
$this->RegisterPropertyInteger("ParseAlarmCategoryID","0");
|
|
$this->RegisterPropertyInteger("ParseAnalogCategoryID","0");
|
|
$this->RegisterPropertyInteger("ParseSetpointCategoryID",-1);
|
|
$this->RegisterPropertyInteger("ParseSetpointCacheCategoryID",-1);
|
|
|
|
$this->RegisterPropertyInteger("MqttCLientID","0");
|
|
$this->RegisterPropertyInteger("Projectnumber","230001");
|
|
$this->RegisterPropertyString("Projectname","");
|
|
$this->RegisterPropertyInteger("ISP","1");
|
|
$this->RegisterPropertyInteger("Projectyear","2023");
|
|
$this->RegisterPropertyInteger("Updatetime","20");
|
|
$this->RegisterPropertyInteger("ForceUpdatetime","30");
|
|
|
|
$this->RegisterTimer("Update", 0, 'SEMC_SendTopic('.$this->InstanceID.');');
|
|
$this->RegisterTimer("ForceUpdate", 0, 'SEMC_ForceSendTopic('.$this->InstanceID.');');
|
|
|
|
}
|
|
|
|
// Überschreibt die intere IPS_ApplyChanges($id) Funktion
|
|
public function ApplyChanges() {
|
|
// Diese Zeile nicht löschen
|
|
parent::ApplyChanges();
|
|
|
|
$this->SetTimerInterval("Update", $this->ReadPropertyInteger("Updatetime") * 1000);
|
|
$this->SetTimerInterval("ForceUpdate", $this->ReadPropertyInteger("ForceUpdatetime") * 1000 * 60);
|
|
|
|
}
|
|
/**
|
|
* Die folgenden Funktionen stehen automatisch zur Verfügung, wenn das Modul über die "Module Control" eingefügt wurden.
|
|
* Die Funktionen werden, mit dem selbst eingerichteten Prefix, in PHP und JSON-RPC wiefolgt zur Verfügung gestellt:
|
|
*
|
|
* DWM_SendMessage($id);
|
|
*
|
|
*/
|
|
/* Publish a given payload over a MQTT server under
|
|
a given topic without having to manually create a
|
|
MQTT server device */
|
|
public function MqttPublish($server_id, $topic, $payload, $retain) {
|
|
// ensure server instance exists
|
|
if(!IPS_InstanceExists($server_id)) {
|
|
return false;
|
|
}
|
|
|
|
// convert array structure to json string
|
|
if(is_array($payload)) $payload = json_encode($payload);
|
|
|
|
// determine data type
|
|
if(is_string($payload)) {
|
|
$ips_var_type = 3;
|
|
} else if(is_float($payload)) {
|
|
$ips_var_type = 2;
|
|
} else if(is_int($payload)) {
|
|
$ips_var_type = 1;
|
|
} else if(is_bool($payload)) {
|
|
$ips_var_type = 0;
|
|
} else { // unsupported
|
|
return false;
|
|
}
|
|
|
|
$module_id = "{01C00ADD-D04E-452E-B66A-D253278743FE}" /* Module ID of MQTT Server Device */;
|
|
$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);
|
|
$cacheCat = $this->ReadPropertyInteger("ParseSetpointCacheCategoryID");
|
|
|
|
if ($cacheCat < 1){
|
|
IPS_LogMessage("Monitoring Client", "No Cache Category defined for Setpoint Sync. Please define a Cache Category to use this feature.");
|
|
return false;
|
|
}
|
|
|
|
if (IPS_CategoryExists($cacheCat) == false){
|
|
IPS_LogMessage("Monitoring Client", "Cache Category does not exist for Setpoint Sync.");
|
|
return false;
|
|
}
|
|
|
|
if (IPS_VariableExists($source) == false){
|
|
IPS_LogMessage("Monitoring Client", "Source variable does not exist for Setpoint Sync.");
|
|
|
|
return false;
|
|
}
|
|
|
|
// ensure server instance exists
|
|
if(!IPS_InstanceExists($server_id)) {
|
|
IPS_LogMessage("Monitoring Client", "Server instance does not exist for Setpoint Sync.");
|
|
return false;
|
|
}
|
|
|
|
// determine data type
|
|
if(is_string($payload)) {
|
|
|
|
$ips_var_type = 3;
|
|
} else if(is_float($payload)) {
|
|
$ips_var_type = 2;
|
|
} else if(is_int($payload)) {
|
|
$ips_var_type = 1;
|
|
} else if(is_bool($payload)) {
|
|
$ips_var_type = 0;
|
|
} else { // unsupported
|
|
return false;
|
|
IPS_LogMessage("Monitoring Client", "Unsupported data type for Setpoint Sync.");
|
|
}
|
|
IPS_LogMessage("Monitoring Client", "Data type for Setpoint Sync determined as: " . $ips_var_type);
|
|
|
|
$module_id = "{01C00ADD-D04E-452E-B66A-D253278743FE}" /* Module ID of MQTT Server Device */;
|
|
|
|
// get temporary MQTT Server Device or create if needed
|
|
$id = @IPS_GetObjectIDByIdent($ident, $cacheCat);
|
|
if($id === false) {
|
|
$id = @IPS_CreateInstance($module_id);
|
|
if($id === false) {
|
|
return false;
|
|
}
|
|
IPS_SetParent($id, $cacheCat);
|
|
IPS_SetIdent($id, $ident);
|
|
// name object to help with debugging
|
|
IPS_SetName($id, "CS_" . $ident);
|
|
|
|
// 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_ApplyChanges($id);
|
|
}
|
|
|
|
// ensure the specified server instance is actually compatible
|
|
if(!IPS_IsInstanceCompatible($id, $server_id)) {
|
|
return false;
|
|
IPS_LogMessage("Monitoring Client", "Device is not compatible with server instance for Setpoint Sync.");
|
|
}
|
|
|
|
$inst_config = IPS_GetInstance($id);
|
|
if($inst_config["ConnectionID"] != $server_id) {
|
|
IPS_DisconnectInstance($id);
|
|
if(!@IPS_ConnectInstance($id, $server_id)) {
|
|
return false;
|
|
IPS_LogMessage("Monitoring Client", "Could not connect device to server instance for Setpoint Sync.");
|
|
}
|
|
}
|
|
IPS_LogMessage("Monitoring Client", "Parentid was :".$id);
|
|
$id = IPS_GetChildrenIDs($id)[0];
|
|
IPS_LogMessage("Monitoring Client", "ID was :".$id);
|
|
|
|
$valSelf = GetValue($id);
|
|
$valSource = GetValue($source);
|
|
|
|
IPS_LogMessage("Monitoring Client", "Self value: " . $valSelf . " Source value: " . $valSource);
|
|
|
|
$varInfoSelf = IPS_GetVariable($id);
|
|
$changedtimeSelf = $varInfoSelf["VariableChanged"];
|
|
|
|
$varInfoSource = IPS_GetVariable($source);
|
|
$changedtimeSource = $varInfoSource["VariableChanged"];
|
|
|
|
if($valSelf != $valSource) {
|
|
|
|
//self is newer than source, so update source with self value
|
|
if ($changedtimeSelf > $changedtimeSource) {
|
|
RequestAction($source, $valSelf);
|
|
IPS_LogMessage("Monitoring Client", "Source variable updated with self value for Setpoint Sync. Source: " . $source . " Value: " . $valSelf);
|
|
}
|
|
|
|
//source is newer than self, so update self with source value
|
|
if ($changedtimeSource > $changedtimeSelf) {
|
|
RequestAction($id, $valSource);
|
|
IPS_LogMessage("Monitoring Client", "Self variable updated with source value for Setpoint Sync. Self: " . $id . " Value: " . $valSource);
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
} // MQTT_Sync
|
|
|
|
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);
|
|
}
|
|
|
|
|
|
}
|
|
?>
|