open62541 Alarm&Condition

概述

Alarm&Condition是基于事件之上的,它提供了更多的操作方式,比如对告警的确认(ack)、提交(commit)等。举个简单例子,一个机器开始启动时向操作人员发送了一个告警,在操作人员进行确认(ack)后机器才会继续运行。前面提到的事件是通过手动触发的,而对于Alarm来说,在某个状态改变后,事件是由服务器内部进行触发。事件类型的模型如下
在这里插入图片描述
更详细的说明在UA Part 9中有讲述,可以自行阅读,这里主要讲述OffNormalAlarm类型条件变量大致的使用,在此之前可先浏览事件这篇文章。

编译open62541

这里使用cmake进行编译,勾选选项如下
在这里插入图片描述
在这里插入图片描述
这里需要将UA_NAMESPACE_ZERO选择为FULL选项。点击Generate后会发现生成不成功,如下
在这里插入图片描述
可以看到提示信息我们缺少Opc.Ua.NodeSet2.xml这个NodeSet文件。这里我们去下载官方提供的一些NodeSet,其中就包括我们需要的这个NodeSet。NodeSet集下载地址下载完毕后可以看到其内容如下
在这里插入图片描述
其中Schema文件夹中就含有我们需要的Opc.Ua.NodeSet2.xml,将Schema整个文件夹复制去Generate生成的报错信息那个路径,以我路径为例
在这里插入图片描述
重新Generate后就可以看到成功了,
在这里插入图片描述
除此之外也可以修改CMakeLists.txt中的路径去进行设置,
在这里插入图片描述
最后编译vs工程就可得到open62541.h和open62541.c文件。

解释一下这里为啥要修改UA_NAMESPACE_ZERO为FULL,主要是MINIMAL和REDUCED属于不完全版本,有些类型并没有进行添加,我们可以发现编译FULL出来的文件大了很多主要就是这个原因。同时CMakeLists.txt中指明了我们需要进行修改
在这里插入图片描述

触发alarm&condition

这里以tutorial_server_alarms_conditions.c例程进行讲述,其代码如下,稍作修改。

/* This work is licensed under a Creative Commons CCZero 1.0 Universal License.
 * See http://creativecommons.org/publicdomain/zero/1.0/ for more information. */
//
//#include <open62541/plugin/log_stdout.h>
//#include <open62541/server.h>
//#include <open62541/server_config_default.h>
#include "open62541.h"

#include <signal.h>
#include <stdlib.h>

/**
 * Using Alarms and Conditions Server
 * ----------------------------------
 *
 * Besides the usage of monitored items and events to observe the changes in the
 * server, it is also important to make use of the Alarms and Conditions Server
 * Model. Alarms are events which are triggered automatically by the server
 * dependent on internal server logic or user specific logic when the states of
 * server components change. The state of a component is represented through a
 * condition. So the values of all the condition children (Fields) are the
 * actual state of the component.
 *
 * Trigger Alarm events by changing States
 * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 *
 * The following example will be based on the server events tutorial. Please
 * make sure to understand the principle of normal events before proceeding with
 * this example! */

static UA_NodeId conditionSource;
static UA_NodeId conditionInstance_1;
static UA_NodeId conditionInstance_2;

static UA_StatusCode
addConditionSourceObject(UA_Server *server) {
    UA_ObjectAttributes object_attr = UA_ObjectAttributes_default;
    object_attr.eventNotifier = 1;

    object_attr.displayName = UA_LOCALIZEDTEXT("en", "ConditionSourceObject");
    UA_StatusCode retval =  UA_Server_addObjectNode(server, UA_NODEID_NULL,
                                      UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
                                      UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
                                      UA_QUALIFIEDNAME(0, "ConditionSourceObject"),
                                      UA_NODEID_NUMERIC(0, UA_NS0ID_BASEOBJECTTYPE),
                                      object_attr, NULL, &conditionSource);

    if(retval != UA_STATUSCODE_GOOD) {
        UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
                     "Creating Condition Source failed. StatusCode %s",
                     UA_StatusCode_name(retval));
    }

    /* ConditionSource should be EventNotifier of another Object (usually the
     * Server Object). If this Reference is not created by user then the A&C
     * Server will create "HasEventSource" reference to the Server Object
     * automatically when the condition is created*/
    retval = UA_Server_addReference(server, UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER),
                                     UA_NODEID_NUMERIC(0, UA_NS0ID_HASNOTIFIER),
                                     UA_EXPANDEDNODEID_NUMERIC(conditionSource.namespaceIndex,
                                     							conditionSource.identifier.numeric),
                                     UA_TRUE);


    return retval;
}

/**
 * Create a condition instance from OffNormalAlarmType. The condition source is
 * the Object created in addConditionSourceObject(). The condition will be
 * exposed in Address Space through the HasComponent reference to the condition
 * source. */
static UA_StatusCode
addCondition_1(UA_Server *server) {
    UA_StatusCode retval = addConditionSourceObject(server);
    if(retval != UA_STATUSCODE_GOOD) {
        UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
                     "creating Condition Source failed. StatusCode %s",
                     UA_StatusCode_name(retval));
    }

    retval = UA_Server_createCondition(server,
                                       UA_NODEID_NULL,
									   UA_NODEID_NUMERIC(0,UA_NS0ID_OFFNORMALALARMTYPE),
                                       UA_QUALIFIEDNAME(0, "Condition 1"), 			conditionSource,
                                       UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
                                       &conditionInstance_1);

    return retval;
}

/**
 * Create a condition instance from OffNormalAlarmType. The condition source is
 * the server Object. The condition won't be exposed in Address Space. */
static UA_StatusCode
addCondition_2(UA_Server *server) {
    UA_StatusCode retval =
        UA_Server_createCondition(server, UA_NODEID_NULL,
                                  UA_NODEID_NUMERIC(0,UA_NS0ID_OFFNORMALALARMTYPE),
                                  UA_QUALIFIEDNAME(0, "Condition 2"),
								  UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER),
								  UA_NODEID_NULL,
								  &conditionInstance_2);

    return retval;
}

static void
addVariable_1_triggerAlarmOfCondition_1(UA_Server *server, UA_NodeId* outNodeId) {
    UA_VariableAttributes attr = UA_VariableAttributes_default;
    attr.displayName = UA_LOCALIZEDTEXT("en", "Activate Condition 1");
    attr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE;
    UA_Boolean tboolValue = UA_FALSE;
    UA_Variant_setScalar(&attr.value, &tboolValue, &UA_TYPES[UA_TYPES_BOOLEAN]); 

    UA_QualifiedName CallbackTestVariableName = UA_QUALIFIEDNAME(0, "Activate Condition 1");
    UA_NodeId parentNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);
    UA_NodeId parentReferenceNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES);
    UA_NodeId variableTypeNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE);
    UA_Server_addVariableNode(server, UA_NODEID_NULL, parentNodeId,
                              parentReferenceNodeId, CallbackTestVariableName,
                              variableTypeNodeId, attr, NULL, outNodeId);
}

static void
addVariable_2_changeSeverityOfCondition_2(UA_Server *server,
                                          UA_NodeId* outNodeId) {
    UA_VariableAttributes attr = UA_VariableAttributes_default;
    attr.displayName = UA_LOCALIZEDTEXT("en", "Change Severity Condition 2");
    attr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE;
    UA_UInt16 severityValue = 0;
    UA_Variant_setScalar(&attr.value, &severityValue, &UA_TYPES[UA_TYPES_UINT16]);

    UA_QualifiedName CallbackTestVariableName =
        UA_QUALIFIEDNAME(0, "Change Severity Condition 2");
    UA_NodeId parentNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);
    UA_NodeId parentReferenceNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES);
    UA_NodeId variableTypeNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE);
    UA_Server_addVariableNode(server, UA_NODEID_NULL, parentNodeId,
                              parentReferenceNodeId, CallbackTestVariableName,
                              variableTypeNodeId, attr, NULL, outNodeId);
}

static void
addVariable_3_returnCondition_1_toNormalState(UA_Server *server,
                                              UA_NodeId* outNodeId) {
    UA_VariableAttributes attr = UA_VariableAttributes_default;
    attr.displayName = UA_LOCALIZEDTEXT("en", "Return to Normal Condition 1");
    attr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE;
    UA_Boolean rtn = 0;
    UA_Variant_setScalar(&attr.value, &rtn, &UA_TYPES[UA_TYPES_BOOLEAN]);

    UA_QualifiedName CallbackTestVariableName =
        UA_QUALIFIEDNAME(0, "Return to Normal Condition 1");
    UA_NodeId parentNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);
    UA_NodeId parentReferenceNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES);
    UA_NodeId variableTypeNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE);
    UA_Server_addVariableNode(server, UA_NODEID_NULL, parentNodeId,
                              parentReferenceNodeId, CallbackTestVariableName,
                              variableTypeNodeId, attr, NULL, outNodeId);
}

static void
afterWriteCallbackVariable_1(UA_Server *server, const UA_NodeId *sessionId,
                             void *sessionContext, const UA_NodeId *nodeId,
                             void *nodeContext, const UA_NumericRange *range,
                             const UA_DataValue *data) {
    UA_QualifiedName activeStateField = UA_QUALIFIEDNAME(0,"ActiveState");
    UA_QualifiedName activeStateIdField = UA_QUALIFIEDNAME(0,"Id");
    UA_Variant value;

    UA_StatusCode retval =
        UA_Server_writeObjectProperty_scalar(server, conditionInstance_1,
                                             UA_QUALIFIEDNAME(0, "Time"),
                                             &data->sourceTimestamp,
                                             &UA_TYPES[UA_TYPES_DATETIME]);

    if(*(UA_Boolean *)(data->value.data) == true) {
        /* By writing "true" in ActiveState/Id, the A&C server will set the
         * related fields automatically and then will trigger event
         * notification. */
        UA_Boolean activeStateId = true;
        UA_Variant_setScalar(&value, &activeStateId, &UA_TYPES[UA_TYPES_BOOLEAN]);
        retval |= UA_Server_setConditionVariableFieldProperty(server, conditionInstance_1,
                                                              &value, activeStateField,
                                                              activeStateIdField);
        if(retval != UA_STATUSCODE_GOOD) {
            UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
                         "Setting ActiveState/Id Field failed. StatusCode %s",
                         UA_StatusCode_name(retval));
            return;
        }
    } else {
        /* By writing "false" in ActiveState/Id, the A&C server will set only
         * the ActiveState field automatically to the value "Inactive". The user
         * should trigger the event manually by calling
         * UA_Server_triggerConditionEvent inside the application or call
         * ConditionRefresh method with client to update the event notification. */
        UA_Boolean activeStateId = false;
        UA_Variant_setScalar(&value, &activeStateId, &UA_TYPES[UA_TYPES_BOOLEAN]);
        retval = UA_Server_setConditionVariableFieldProperty(server, conditionInstance_1,
                                                             &value, activeStateField,
                                                             activeStateIdField);
        if(retval != UA_STATUSCODE_GOOD) {
            UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
                         "Setting ActiveState/Id Field failed. StatusCode %s",
                         UA_StatusCode_name(retval));
            return;
        }

        retval = UA_Server_triggerConditionEvent(server, conditionInstance_1,
                                                 conditionSource, NULL);
        if(retval != UA_STATUSCODE_GOOD) {
            UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
                           "Triggering condition event failed. StatusCode %s",
                           UA_StatusCode_name(retval));
            return;
        }
    }
}

/**
 * The callback only changes the severity field of the condition 2. The severity
 * field is of ConditionVariableType, so changes in it triggers an event
 * notification automatically by the server. */
static void
afterWriteCallbackVariable_2(UA_Server *server, const UA_NodeId *sessionId,
                             void *sessionContext, const UA_NodeId *nodeId,
                             void *nodeContext, const UA_NumericRange *range,
                             const UA_DataValue *data) {

	UA_String str = UA_STRING_ALLOC("test call");
   /* Another way to set fields of conditions */
	UA_Server_writeObjectProperty_scalar(server, conditionInstance_2,
		UA_QUALIFIEDNAME(0, "Severity"),
		(UA_UInt16 *)data->value.data,
		&UA_TYPES[UA_TYPES_UINT16]);
}

/**
 * RTN = return to normal.
 *
 * Retain will be set to false, thus no events will be generated for condition 1
 * (although EnabledState/=true). To set Retain to true again, the disable and
 * enable methods should be called respectively.
 */
static void
afterWriteCallbackVariable_3(UA_Server *server,
               const UA_NodeId *sessionId, void *sessionContext,
               const UA_NodeId *nodeId, void *nodeContext,
               const UA_NumericRange *range, const UA_DataValue *data) {

    //UA_QualifiedName enabledStateField = UA_QUALIFIEDNAME(0,"EnabledState");
    UA_QualifiedName ackedStateField = UA_QUALIFIEDNAME(0,"AckedState");
    UA_QualifiedName confirmedStateField = UA_QUALIFIEDNAME(0,"ConfirmedState");
    UA_QualifiedName activeStateField = UA_QUALIFIEDNAME(0,"ActiveState");
    UA_QualifiedName severityField = UA_QUALIFIEDNAME(0,"Severity");
    UA_QualifiedName messageField = UA_QUALIFIEDNAME(0,"Message");
    UA_QualifiedName commentField = UA_QUALIFIEDNAME(0,"Comment");
    UA_QualifiedName retainField = UA_QUALIFIEDNAME(0,"Retain");
    UA_QualifiedName idField = UA_QUALIFIEDNAME(0,"Id");

    UA_StatusCode retval =
        UA_Server_writeObjectProperty_scalar(server, conditionInstance_1,
                                             UA_QUALIFIEDNAME(0, "Time"),
                                             &data->serverTimestamp,
                                             &UA_TYPES[UA_TYPES_DATETIME]);
    UA_Variant value;
    UA_Boolean idValue = false;
    UA_Variant_setScalar(&value, &idValue, &UA_TYPES[UA_TYPES_BOOLEAN]);
    retval |= UA_Server_setConditionVariableFieldProperty(server, conditionInstance_1,
                                                          &value, activeStateField,
                                                          idField);
    if(retval != UA_STATUSCODE_GOOD) {
        UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
                     "Setting ActiveState/Id Field failed. StatusCode %s",
                     UA_StatusCode_name(retval));
        return;
    }

    retval = UA_Server_setConditionVariableFieldProperty(server, conditionInstance_1,
                                                         &value, ackedStateField,
                                                         idField);
    if(retval != UA_STATUSCODE_GOOD) {
        UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
                     "Setting AckedState/Id Field failed. StatusCode %s",
                     UA_StatusCode_name(retval));
        return;
    }

    retval = UA_Server_setConditionVariableFieldProperty(server, conditionInstance_1,
                                                         &value, confirmedStateField,
                                                         idField);
    if(retval != UA_STATUSCODE_GOOD) {
        UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
                     "Setting ConfirmedState/Id Field failed. StatusCode %s",
                     UA_StatusCode_name(retval));
        return;
    }

    UA_UInt16 severityValue = 100;
    UA_Variant_setScalar(&value, &severityValue, &UA_TYPES[UA_TYPES_UINT16]);
    retval = UA_Server_setConditionField(server, conditionInstance_1,
                                         &value, severityField);
    if(retval != UA_STATUSCODE_GOOD) {
        UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
                     "Setting Severity Field failed. StatusCode %s",
                     UA_StatusCode_name(retval));
        return;
    }

    UA_LocalizedText messageValue =
        UA_LOCALIZEDTEXT("en", "Condition returned to normal state");
    UA_Variant_setScalar(&value, &messageValue, &UA_TYPES[UA_TYPES_LOCALIZEDTEXT]);
    retval = UA_Server_setConditionField(server, conditionInstance_1,
                                         &value, messageField);
    if(retval != UA_STATUSCODE_GOOD) {
        UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
                     "Setting Message Field failed. StatusCode %s",
                     UA_StatusCode_name(retval));
        return;
    }

    UA_LocalizedText commentValue = UA_LOCALIZEDTEXT("en", "Normal State");
    UA_Variant_setScalar(&value, &commentValue, &UA_TYPES[UA_TYPES_LOCALIZEDTEXT]);
    retval = UA_Server_setConditionField(server, conditionInstance_1,
                                         &value, commentField);
    if(retval != UA_STATUSCODE_GOOD) {
        UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
                     "Setting Comment Field failed. StatusCode %s",
                     UA_StatusCode_name(retval));
        return;
    }

    UA_Boolean retainValue = false;
    UA_Variant_setScalar(&value, &retainValue, &UA_TYPES[UA_TYPES_BOOLEAN]);
    retval = UA_Server_setConditionField(server, conditionInstance_1,
                                         &value, retainField);
    if(retval != UA_STATUSCODE_GOOD) {
        UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
                     "Setting Retain Field failed. StatusCode %s",
                     UA_StatusCode_name(retval));
        return;
    }

    retval = UA_Server_triggerConditionEvent(server, conditionInstance_1,
                                             conditionSource, NULL);
    if (retval != UA_STATUSCODE_GOOD) {
     UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
                    "Triggering condition event failed. StatusCode %s",
                    UA_StatusCode_name(retval));
     return;
    }
}

static UA_StatusCode
enteringEnabledStateCallback(UA_Server *server, const UA_NodeId *condition) {
	printf("enteringEnabledStateCallback\n");
    UA_Boolean retain = true;
    return UA_Server_writeObjectProperty_scalar(server, *condition,
                                                UA_QUALIFIEDNAME(0, "Retain"),
                                                &retain,
                                                &UA_TYPES[UA_TYPES_BOOLEAN]);
}

/**
 * This is user specific function which will be called upon acknowledging an
 * alarm notification. In this example we will set the Alarm to Inactive state.
 * The server is responsible of setting standard fields related to Acknowledge
 * Method and triggering the alarm notification. */
static UA_StatusCode
enteringAckedStateCallback(UA_Server *server, const UA_NodeId *condition) {
	printf("enteringAckedStateCallback\n");
    /* deactivate Alarm when acknowledging*/
    UA_Boolean activeStateId = false;
    UA_Variant value;
    UA_QualifiedName activeStateField = UA_QUALIFIEDNAME(0,"ActiveState");
    UA_QualifiedName activeStateIdField = UA_QUALIFIEDNAME(0,"Id");

    UA_Variant_setScalar(&value, &activeStateId, &UA_TYPES[UA_TYPES_BOOLEAN]);
    UA_StatusCode retval =
        UA_Server_setConditionVariableFieldProperty(server, *condition,
                                                    &value, activeStateField,
                                                    activeStateIdField);

    if(retval != UA_STATUSCODE_GOOD) {
        UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
                     "Setting ActiveState/Id Field failed. StatusCode %s",
                     UA_StatusCode_name(retval));
    }

    return retval;
}

static UA_StatusCode
enteringConfirmedStateCallback(UA_Server *server, const UA_NodeId *condition) {
	printf("enteringConfirmedStateCallback\n");
	/* Deactivate Alarm and put it out of the interesting state (by writing
     * false to Retain field) when confirming*/
    UA_Boolean activeStateId = false;
    UA_Boolean retain = false;
    UA_Variant value;
    UA_QualifiedName activeStateField = UA_QUALIFIEDNAME(0,"ActiveState");
    UA_QualifiedName activeStateIdField = UA_QUALIFIEDNAME(0,"Id");
    UA_QualifiedName retainField = UA_QUALIFIEDNAME(0,"Retain");

    UA_Variant_setScalar(&value, &activeStateId, &UA_TYPES[UA_TYPES_BOOLEAN]);
    UA_StatusCode retval =
        UA_Server_setConditionVariableFieldProperty(server, *condition,
                                                    &value, activeStateField,
                                                    activeStateIdField);
    if(retval != UA_STATUSCODE_GOOD) {
        UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
                     "Setting ActiveState/Id Field failed. StatusCode %s",
                     UA_StatusCode_name(retval));
        return retval;
    }

    UA_Variant_setScalar(&value, &retain, &UA_TYPES[UA_TYPES_BOOLEAN]);
    retval = UA_Server_setConditionField(server, *condition,
                                         &value, retainField);
    if(retval != UA_STATUSCODE_GOOD) {
        UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
                     "Setting ActiveState/Id Field failed. StatusCode %s",
                     UA_StatusCode_name(retval));
    }

    return retval;
}

static UA_StatusCode
setUpEnvironment(UA_Server *server) {
    UA_NodeId variable_1;
    UA_NodeId variable_2;
    UA_NodeId variable_3;
    UA_ValueCallback callback;
    callback.onRead = NULL;

    /* Exposed condition 1. We will add to it user specific callbacks when
     * entering enabled state, when acknowledging and when confirming. */
    UA_StatusCode retval = addCondition_1(server);
    if(retval != UA_STATUSCODE_GOOD) {
        UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
                     "adding condition 1 failed. StatusCode %s",
                     UA_StatusCode_name(retval));
        return retval;
    }
#if 1
    /* Unexposed condition 2. No user specific callbacks, so the server will
     * behave in a standard manner upon entering enabled state, acknowledging
     * and confirming. We will set Retain field to true and enable the condition
     * so we can receive event notifications (we cannot call enable method on
     * unexposed condition using a client like UaExpert or Softing). */
    retval = addCondition_2(server);
    if(retval != UA_STATUSCODE_GOOD) {
        UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
                     "adding condition 2 failed. StatusCode %s",
                     UA_StatusCode_name(retval));
        return retval;
    }

    UA_Boolean retain = UA_TRUE;
    UA_Server_writeObjectProperty_scalar(server, conditionInstance_2,
                                         UA_QUALIFIEDNAME(0, "Retain"),
                                         &retain, &UA_TYPES[UA_TYPES_BOOLEAN]);

    UA_Variant value;
    UA_Boolean enabledStateId = true;
    UA_QualifiedName enabledStateField = UA_QUALIFIEDNAME(0,"EnabledState");
    UA_QualifiedName enabledStateIdField = UA_QUALIFIEDNAME(0,"Id");
    UA_Variant_setScalar(&value, &enabledStateId, &UA_TYPES[UA_TYPES_BOOLEAN]);
    retval = UA_Server_setConditionVariableFieldProperty(server, conditionInstance_2,
                                                         &value, enabledStateField,
                                                         enabledStateIdField);

    if(retval != UA_STATUSCODE_GOOD) {
        UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
                     "Setting EnabledState/Id Field failed. StatusCode %s",
                     UA_StatusCode_name(retval));
        return retval;
    }


    /* Add 3 variables to trigger condition events */
    addVariable_1_triggerAlarmOfCondition_1(server, &variable_1);

    callback.onWrite = afterWriteCallbackVariable_1;
    retval = UA_Server_setVariableNode_valueCallback(server, variable_1, callback);
    if(retval != UA_STATUSCODE_GOOD) {
        UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
                     "Setting variable 1 Callback failed. StatusCode %s",
                     UA_StatusCode_name(retval));
        return retval;
    }

    /* Severity can change internally also when the condition disabled and
     * retain is false. However, in this case no events will be generated. */
    addVariable_2_changeSeverityOfCondition_2(server, &variable_2);

    callback.onWrite = afterWriteCallbackVariable_2;
    retval = UA_Server_setVariableNode_valueCallback(server, variable_2, callback);
    if(retval != UA_STATUSCODE_GOOD) {
        UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
                     "Setting variable 2 Callback failed. StatusCode %s",
                     UA_StatusCode_name(retval));
        return retval;
    }

    addVariable_3_returnCondition_1_toNormalState(server, &variable_3);

    callback.onWrite = afterWriteCallbackVariable_3;
    retval = UA_Server_setVariableNode_valueCallback(server, variable_3, callback);
    if(retval != UA_STATUSCODE_GOOD) {
        UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
                     "Setting variable 3 Callback failed. StatusCode %s",
                     UA_StatusCode_name(retval));
    }
#endif
    return retval;
}

/**
 * It follows the main server code, making use of the above definitions. */

static UA_Boolean running = true;
static void stopHandler(int sig) {
    running = false;
}

int main (void) {
    /* default server values */
    signal(SIGINT, stopHandler);
    signal(SIGTERM, stopHandler);

    UA_Server *server = UA_Server_new();
    UA_ServerConfig_setDefault(UA_Server_getConfig(server));

    UA_StatusCode retval = setUpEnvironment(server);

    //if(retval == UA_STATUSCODE_GOOD)
        retval = UA_Server_run(server, &running);

    UA_Server_delete(server);

	system("pause");
    return retval == UA_STATUSCODE_GOOD ? EXIT_SUCCESS : EXIT_FAILURE;
}

这里共添加了两个条件变量conditionInstance_1和conditionInstance_2,中conditionInstance_1是以显示的方式展现出来,conditionInstance_2以隐示方式进行添加的。主要区别就是在createCondition时没有设置条件源对象以及引用。三个变量variable_1、variable_2和variable_3都绑定了对应的写回调函数,目的是为了通过改变条件变量的字段从而产生报警事件。条件变量相关字段信息在open62541.c中有对应的定义,如下

/* Condition Field Names */
#define CONDITION_FIELD_EVENTID                                "EventId"
#define CONDITION_FIELD_EVENTTYPE                              "EventType"
#define CONDITION_FIELD_SOURCENODE                             "SourceNode"
#define CONDITION_FIELD_SOURCENAME                             "SourceName"
#define CONDITION_FIELD_TIME                                   "Time"
#define CONDITION_FIELD_RECEIVETIME                            "ReceiveTime"
#define CONDITION_FIELD_MESSAGE                                "Message"
#define CONDITION_FIELD_SEVERITY                               "Severity"
#define CONDITION_FIELD_CONDITIONNAME                          "ConditionName"
#define CONDITION_FIELD_BRANCHID                               "BranchId"
#define CONDITION_FIELD_RETAIN                                 "Retain"
#define CONDITION_FIELD_ENABLEDSTATE                           "EnabledState"
#define CONDITION_FIELD_TWOSTATEVARIABLE_ID                    "Id"
#define CONDITION_FIELD_QUALITY                                "Quality"
#define CONDITION_FIELD_LASTSEVERITY                           "LastSeverity"
#define CONDITION_FIELD_COMMENT                                "Comment"
#define CONDITION_FIELD_CLIENTUSERID                           "ClientUserId"
#define CONDITION_FIELD_CONDITIONVARIABLE_SOURCETIMESTAMP      "SourceTimestamp"
#define CONDITION_FIELD_DISABLE                                "Disable"
#define CONDITION_FIELD_ENABLE                                 "Enable"
#define CONDITION_FIELD_ADDCOMMENT                             "AddComment"
#define CONDITION_FIELD_CONDITIONREFRESH                       "ConditionRefresh"
#define CONDITION_FIELD_ACKEDSTATE                             "AckedState"
#define CONDITION_FIELD_CONFIRMEDSTATE                         "ConfirmedState"
#define CONDITION_FIELD_ACKNOWLEDGE                            "Acknowledge"
#define CONDITION_FIELD_CONFIRM                                "Confirm"
#define CONDITION_FIELD_ACTIVESTATE                            "ActiveState"
#define CONDITION_FIELD_INPUTNODE                              "InputNode"
#define CONDITION_FIELD_SUPPRESSEDORSHELVED                    "SuppressedOrShelved"
#define CONDITION_FIELD_NORMALSTATE                            "NormalState"
#define CONDITION_FIELD_HIGHHIGHLIMIT                          "HighHighLimit"
#define CONDITION_FIELD_HIGHLIMIT                              "HighLimit"
#define CONDITION_FIELD_LOWLIMIT                               "LowLimit"
#define CONDITION_FIELD_LOWLOWLIMIT                            "LowLowLimit"
#define CONDITION_FIELD_PROPERTY_EFFECTIVEDISPLAYNAME          "EffectiveDisplayName"
#define CONDITION_FIELD_LIMITSTATE                             "LimitState"
#define CONDITION_FIELD_CURRENTSTATE                           "CurrentState"
#define CONDITION_FIELD_HIGHHIGHSTATE                          "HighHighState"
#define CONDITION_FIELD_HIGHSTATE                              "HighState"
#define CONDITION_FIELD_LOWSTATE                               "LowState"
#define CONDITION_FIELD_LOWLOWSTATE                            "LowLowState"
#define CONDITION_FIELD_DIALOGSTATE                            "DialogState"
#define CONDITION_FIELD_PROMPT                                 "Prompt"
#define CONDITION_FIELD_RESPONSEOPTIONSET                      "ResponseOptionSet"
#define CONDITION_FIELD_DEFAULTRESPONSE                        "DefaultResponse"
#define CONDITION_FIELD_LASTRESPONSE                           "LastResponse"
#define CONDITION_FIELD_OKRESPONSE                             "OkResponse"
#define CONDITION_FIELD_CANCELRESPONSE                         "CancelResponse"
#define CONDITION_FIELD_RESPOND                                "Respond"

使用Uaexpert连接server后,Root目录如下
在这里插入图片描述
其中Condition1可见,Condition2不可见。由于condition2在初始化的时候就设置了EnabledState/Id字段为true表示为启动这个条件变量,在没有对这个报警做处理的情况下,每次连接我们都能获取到这个报警信息,
在这里插入图片描述
通过右键我们可以对这个报警进行确认(Ack)和提交信息(Commit)
在这里插入图片描述
在确认和提交后我们可以看到对应的报警事件其字段已改变
在这里插入图片描述
除此之外,我们还可以通过调用method的方式,
在这里插入图片描述
每次修改后eventId都会进行变化,我们要找到其对应的id,在确认和提交后就表示这个警报已经被处理了。

接收Alarm

client端接收Alarm的方式与接收普通事件的方式一致,在筛选字段那部分做做处理即可,代码如下

/* This work is licensed under a Creative Commons CCZero 1.0 Universal License.
* See http://creativecommons.org/publicdomain/zero/1.0/ for more information. */

//#include <open62541/client.h>
//#include <open62541/client_config_default.h>
//#include <open62541/client_highlevel.h>
//#include <open62541/client_subscriptions.h>
//#include <open62541/plugin/log_stdout.h>
//#include <open62541/server.h>
//#include <open62541/server_config_default.h>
//#include <open62541/util.h>

#include "open62541.h"

#include <signal.h>

#ifdef _MSC_VER
#pragma warning(disable:4996) // warning C4996: 'UA_Client_Subscriptions_addMonitoredEvent': was declared deprecated
#endif

#ifdef __clang__
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
#endif

#ifdef __GNUC__
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#endif

static UA_Boolean running = true;
static void stopHandler(int sig) {
	UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "received ctrl-c");
	running = false;
}

#ifdef UA_ENABLE_SUBSCRIPTIONS

static void
handler_events(UA_Client *client, UA_UInt32 subId, void *subContext,
UA_UInt32 monId, void *monContext,
size_t nEventFields, UA_Variant *eventFields) {
	UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Notification");

	/* The context should point to the monId on the stack */
	UA_assert(*(UA_UInt32*)monContext == monId);

	for (size_t i = 0; i < nEventFields; ++i) {
		UA_Variant *variant = &eventFields[i];
		if (UA_Variant_hasScalarType(&eventFields[i], &UA_TYPES[UA_TYPES_UINT16])) {
			UA_UInt16 severity = *(UA_UInt16 *)eventFields[i].data;
			UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Severity: %u", severity);
		}
		else if (UA_Variant_hasScalarType(&eventFields[i], &UA_TYPES[UA_TYPES_LOCALIZEDTEXT])) {
			UA_LocalizedText *lt = (UA_LocalizedText *)eventFields[i].data;
			UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
				"Message: '%.*s'", (int)lt->text.length, lt->text.data);
		}
		else if (UA_Variant_hasScalarType(&eventFields[i], &UA_TYPES[UA_TYPES_STRING])) {
			UA_String *lt = (UA_LocalizedText *)eventFields[i].data;
			UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
				"SourceName: '%.*s'", (int)lt->length, lt->data);
		}
		else if (UA_Variant_hasScalarType(&eventFields[i], &UA_TYPES[UA_TYPES_DATETIME])) {
			UA_DateTime *lt = (UA_DateTime *)eventFields[i].data;
			UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
				"Time: '%u'", *lt);
		}
		else if (UA_Variant_hasScalarType(&eventFields[i], &UA_TYPES[UA_TYPES_NODEID]))
		{
			UA_NodeId *lt = (UA_NodeId *)eventFields[i].data;
			UA_String str;
			UA_NodeId_print(lt, &str);
			UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
				"Node: '%.*s'", (int)str.length, str.data);
		}
		else {
#ifdef UA_ENABLE_TYPEDESCRIPTION
			UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
				"Don't know how to handle type: '%s'", eventFields[i].type->typeName);
#else
			UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
				"Don't know how to handle type, enable UA_ENABLE_TYPEDESCRIPTION "
				"for typename");
#endif
		}
	}
}

const size_t nSelectClauses = 4;

static UA_SimpleAttributeOperand *
setupSelectClauses(void) {
	UA_SimpleAttributeOperand *selectClauses = (UA_SimpleAttributeOperand*)
		UA_Array_new(nSelectClauses, &UA_TYPES[UA_TYPES_SIMPLEATTRIBUTEOPERAND]);
	if (!selectClauses)
		return NULL;
	
	for (size_t i = 0; i<nSelectClauses; ++i) {
		UA_SimpleAttributeOperand_init(&selectClauses[i]);
	}

	selectClauses[0].typeDefinitionId = UA_NODEID_NUMERIC(0, UA_NS0ID_BASEEVENTTYPE);
	selectClauses[0].browsePathSize = 1;
	selectClauses[0].browsePath = (UA_QualifiedName*)
		UA_Array_new(selectClauses[0].browsePathSize, &UA_TYPES[UA_TYPES_QUALIFIEDNAME]);
	if (!selectClauses[0].browsePath) {
		UA_SimpleAttributeOperand_delete(selectClauses);
		return NULL;
	}
	selectClauses[0].attributeId = UA_ATTRIBUTEID_VALUE;
	selectClauses[0].browsePath[0] = UA_QUALIFIEDNAME_ALLOC(0, "Message");

	selectClauses[1].typeDefinitionId = UA_NODEID_NUMERIC(0, UA_NS0ID_BASEEVENTTYPE);
	selectClauses[1].browsePathSize = 1;
	selectClauses[1].browsePath = (UA_QualifiedName*)
		UA_Array_new(selectClauses[1].browsePathSize, &UA_TYPES[UA_TYPES_QUALIFIEDNAME]);
	if (!selectClauses[1].browsePath) {
		UA_SimpleAttributeOperand_delete(selectClauses);
		return NULL;
	}
	selectClauses[1].attributeId = UA_ATTRIBUTEID_VALUE;
	selectClauses[1].browsePath[0] = UA_QUALIFIEDNAME_ALLOC(0, "Severity");

	selectClauses[2].typeDefinitionId = UA_NODEID_NUMERIC(0, UA_NS0ID_BASEEVENTTYPE);
	selectClauses[2].browsePathSize = 1;
	selectClauses[2].browsePath = (UA_QualifiedName*)
		UA_Array_new(selectClauses[2].browsePathSize, &UA_TYPES[UA_TYPES_QUALIFIEDNAME]);
	if (!selectClauses[2].browsePath) {
		UA_SimpleAttributeOperand_delete(selectClauses);
		return NULL;
	}
	selectClauses[2].attributeId = UA_ATTRIBUTEID_VALUE;
	selectClauses[2].browsePath[0] = UA_QUALIFIEDNAME_ALLOC(0, "SourceName");

	selectClauses[3].typeDefinitionId = UA_NODEID_NUMERIC(0, UA_NS0ID_BASEEVENTTYPE);
	selectClauses[3].browsePathSize = 1;
	selectClauses[3].browsePath = (UA_QualifiedName*)
		UA_Array_new(selectClauses[3].browsePathSize, &UA_TYPES[UA_TYPES_QUALIFIEDNAME]);
	if (!selectClauses[3].browsePath) {
		UA_SimpleAttributeOperand_delete(selectClauses);
		return NULL;
	}
	selectClauses[3].attributeId = UA_ATTRIBUTEID_VALUE;
	selectClauses[3].browsePath[0] = UA_QUALIFIEDNAME_ALLOC(0, "EventType");


	return selectClauses;
}

#endif

int main(int argc, char *argv[]) {
	signal(SIGINT, stopHandler);
	signal(SIGTERM, stopHandler);


	UA_Client *client = UA_Client_new();
	UA_ClientConfig_setDefault(UA_Client_getConfig(client));

	/* opc.tcp://uademo.prosysopc.com:53530/OPCUA/SimulationServer */
	/* opc.tcp://opcua.demo-this.com:51210/UA/SampleServer */
	UA_StatusCode retval = UA_Client_connect(client, "opc.tcp://127.0.0.1:4840");
	if (retval != UA_STATUSCODE_GOOD) {
		UA_Client_delete(client);
		return EXIT_FAILURE;
	}

#ifdef UA_ENABLE_SUBSCRIPTIONS
	/* Create a subscription */
	UA_CreateSubscriptionRequest request = UA_CreateSubscriptionRequest_default();
	UA_CreateSubscriptionResponse response = UA_Client_Subscriptions_create(client, request,
		NULL, NULL, NULL);
	if (response.responseHeader.serviceResult != UA_STATUSCODE_GOOD) {
		UA_Client_disconnect(client);
		UA_Client_delete(client);
		return EXIT_FAILURE;
	}
	UA_UInt32 subId = response.subscriptionId;
	UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Create subscription succeeded, id %u", subId);

	/* Add a MonitoredItem */
	UA_MonitoredItemCreateRequest item;
	UA_MonitoredItemCreateRequest_init(&item);
	item.itemToMonitor.nodeId = UA_NODEID_NUMERIC(0, 2253); // Root->Objects->Server
	item.itemToMonitor.attributeId = UA_ATTRIBUTEID_EVENTNOTIFIER;
	item.monitoringMode = UA_MONITORINGMODE_REPORTING;

	UA_EventFilter filter;
	UA_EventFilter_init(&filter);
	filter.selectClauses = setupSelectClauses();
	filter.selectClausesSize = nSelectClauses;

	item.requestedParameters.filter.encoding = UA_EXTENSIONOBJECT_DECODED;
	item.requestedParameters.filter.content.decoded.data = &filter;
	item.requestedParameters.filter.content.decoded.type = &UA_TYPES[UA_TYPES_EVENTFILTER];

	UA_UInt32 monId = 0;

	UA_MonitoredItemCreateResult result =
		UA_Client_MonitoredItems_createEvent(client, subId,
		UA_TIMESTAMPSTORETURN_BOTH, item,
		&monId, handler_events, NULL);

	if (result.statusCode != UA_STATUSCODE_GOOD) {
		UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
			"Could not add the MonitoredItem with %s", UA_StatusCode_name(retval));
		goto cleanup;
	}
	else {
		UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
			"Monitoring 'Root->Objects->Server', id %u", response.subscriptionId);
	}

	monId = result.monitoredItemId;

	UA_Variant input;
	UA_Int32 value = subId;
	UA_Variant_init(&input);
	UA_Variant_setScalarCopy(&input, &value, &UA_TYPES[UA_TYPES_INTEGERID]);
	UA_StatusCode lret = 0;
	lret = UA_Client_call(client, UA_NODEID_NUMERIC(0, 2782),
		UA_NODEID_NUMERIC(0, 3875), 1, &input, NULL, NULL);
	if (lret != 0)
	{
		printf("call failed, %s\n", UA_StatusCode_name(lret));
	}

	while (running)
		retval = UA_Client_run_iterate(client, 100);

	/* Delete the subscription */
cleanup:
	UA_MonitoredItemCreateResult_clear(&result);
	UA_Client_Subscriptions_deleteSingle(client, response.subscriptionId);
	UA_Array_delete(filter.selectClauses, nSelectClauses, &UA_TYPES[UA_TYPES_SIMPLEATTRIBUTEOPERAND]);
#endif

	UA_Client_disconnect(client);
	UA_Client_delete(client);
	return retval == UA_STATUSCODE_GOOD ? EXIT_SUCCESS : EXIT_FAILURE;
}

这里多添加了一个EventType字段,其他的字段也可以进行添加,以便于区分是Alarm还是普通事件。

字段名称以及数据类型可通过文档或者使用Uaexpert连接后去进行查看,每个字段类型需要一一进行对应。

UA_Variant input;
	UA_Int32 value = subId;
	UA_Variant_init(&input);
	UA_Variant_setScalarCopy(&input, &value, &UA_TYPES[UA_TYPES_INTEGERID]);
	UA_StatusCode lret = 0;
	lret = UA_Client_call(client, UA_NODEID_NUMERIC(0, UA_NS0ID_CONDITIONTYPE),
		UA_NODEID_NUMERIC(0, UA_NS0ID_CONDITIONTYPE_CONDITIONREFRESH), 1, &input, NULL, NULL);
	if (lret != UA_STATUSCODE_GOOD)
	{
		printf("call failed, %s\n", UA_StatusCode_name(lret));
	}

注意这部分代码是用来刷新alarm的,在连接server端发现原先存在的Alarm我们并不知晓,通过refresh一下能够获取原触发的Alarm以及状态。
在这里插入图片描述
Uaexpert的刷新按钮便是这个含义。运行client端结果如下,
在这里插入图片描述
可以看到与Uaexpert中结果是一致的。

结尾

Alarm&Condition部分感觉其功能有点强大,内容还是比较多的,需要结合文档以及例程进行查看。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

复杂的世界311

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值