学习open62541 --- [71] Alarm and Condition

2023-05-16

本文讲述Alarm and Condition的用法,主要以源码里提供的例子为基础进行讲解和演示,即tutorial_server_alarms_conditions.c,该例子写的有点乱,本文会对代码进行修改,便于理解分析。

PS:在open62541里Alarm&Condition功能还处于试验性质,不过用起来没啥问题,可以作为学习的参考。


文章目录

  • 一 概念理解
    • 1. Condition
    • 2. Alarm
    • 3. Condition Source
  • 二 open62541配置
  • 三 以Server为Condition Source
  • 四 以自定义对象为Condition Source
    • 1. 使能Condition
    • 2. 触发报警
    • 3. 回到Normal
  • 五 总结


一 概念理解

首先来理解一下Condition和Alarm,

1. Condition

Condition是指系统的某种状态,或者是系统中某个组件的某种状态,如,

  • 锅炉温度超过上限
  • 系统需要维护

这里以锅炉温度为例,平时正常运行时,温度在安全范围内,此时锅炉的状态就是“锅炉温度在正常范围内”,如果温度超限,那么锅炉的状态就变成了“锅炉温度超过上限

在官方文档介绍里,一个Condition有2种基本状态,如下图,
在这里插入图片描述
可以这样理解:这个condition可以使能变成enabled,也可以关闭变成disabled

但是只有这2种状态是不够用的,所以文档又介绍了另外一种状态模型 — Acknowledgeable Conditions,
在这里插入图片描述
这种Condition通过添加AckedState和ConfirmedState对Enabled状态进行了扩展。

这里再以锅炉温度为例子,如果温度超限状态是开启的,当锅炉进入了温度超限状态后并发出警报,此刻AckedState和ConfirmedState都是False,如果工作人员发现了这个警报,那么可以通知锅炉,表示已经知道了,这样就可以把AckedState置位True,接下来通过操作把锅炉温度降下来,然后再通知锅炉,这样就可以把ConfirmedState置为True。

这里再简单总结一下这2个状态,

  • AckedState:用于通知Server已经知道这个状态了。
  • ConfirmedState:用于通知Server已经针对该状态采取了有关措施。

2. Alarm

关于Alarm,官方文档解释如下,

Alarms are specializations of AcknowledgeableConditions that add the concepts of an Active state and other states like Shelving state and Suppressed state to a Condition.

个人理解Alarm也是Condition,这里再以锅炉温度为例,其有2种状态:锅炉温度正常锅炉温度超限,可以看出锅炉温度超限这个状态是需要发出警报的,那么这个状态就可以认为是Alarm。

所以,可以认为Alarm是那些需要发出警报的Condition

当系统进入Alarm对应的状态后,就会发出报警,其状态模型如下,
在这里插入图片描述
目前,open62541里只实现了以下4种状态,

  1. Enabled
  2. Active
  3. AckedState
  4. ConfirmedState

Active如果是True,表示这个Condition对应的实际情形正在发生。再以锅炉温度举例,如果温度超限状态的Active为True,那么此时锅炉温度超限了,如果是Inactive,那么表示并未发生。

3. Condition Source

理解了前面的2个概念,这个就很好理解了,Condition Source是指谁的身上会发生这个Condition,在之前的例子里,锅炉就是Condition Source


二 open62541配置

本文使用的open62541版本是v1.3.3,环境是Debian10,在CMakeLists.txt里需要把以下三个选项设置一下,

  • UA_ENABLE_AMALGAMATION设置为ON
  • UA_ENABLE_SUBSCRIPTIONS_ALARMS_CONDITIONS设置为ON
  • UA_NAMESPACE_ZERO设置为FULL

设置好后生成新的open62541文件,留在后面使用。


三 以Server为Condition Source

tutorial_server_alarms_conditions.c里有2个例子,这里先讲解第一个:以Server为Condition Source。

这里先要了解Condition的三个重要的属性:Comment,severity和quality。文档解释如下,

Comment, severity and quality are important elements of Conditions and any change to them will cause Event Notifications.

通过修改这三个属性,可以让Condition Source发送事件通知。本节例子就是通过修改Condition Source的severity属性来触发事件通知,severity就是Condition的严重程度。

具体代码如下,

// server_as_condition_source.c
#include "open62541.h"

// Node id of Condition实例
static UA_NodeId gConditionInstance;


// 根据OffNormalAlarmType来创建Condition实例. Condition source是
// server Object. 这个condition不会在Address Space里显示出来.
static UA_StatusCode addCondition(UA_Server *server, UA_NodeId * pConditionInstance);

// 添加变量,用来修改Condition的Severity属性
static void addVariableChangeSeverityOfCondition(UA_Server *server,
                                                 UA_NodeId* outNodeId);

/**
 * 写变量的回调函数,在这个函数里修改Condition的Severity属性,因为Severity属性的类型
 * 是ConditionVariableType,所以修改它可以自动触发事件通知,也就是让Server给Client
 * 发送通知
*/
static void
afterWriteCallbackVariable(UA_Server *server, const UA_NodeId *sessionId,
                             void *sessionContext, const UA_NodeId *nodeId,
                             void *nodeContext, const UA_NumericRange *range,
                             const UA_DataValue *data);


UA_StatusCode setupCondition(UA_Server * pServer)
{
    if (!pServer)
    {
        return UA_STATUSCODE_BAD;
    }
    
    // 创建Condition实例
    UA_StatusCode retval = addCondition(pServer, &gConditionInstance);
    if(retval != UA_STATUSCODE_GOOD) {
        UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
                     "adding condition failed. StatusCode %s",
                     UA_StatusCode_name(retval));
        return retval;
    }

    // 把Condition的Retain属性设置为True,表示Client对这个Condition有兴趣
    UA_Boolean retain = UA_TRUE;
    UA_Server_writeObjectProperty_scalar(pServer, gConditionInstance,
                                         UA_QUALIFIEDNAME(0, "Retain"),
                                         &retain, &UA_TYPES[UA_TYPES_BOOLEAN]);

    // 设置"EnabledState/Id"为True,使能这个Condition
    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(pServer, gConditionInstance,
                                                         &value, enabledStateField,
                                                         enabledStateIdField);

    // 对变量添加写回调,该写回调可以修改Condition的Severity属性,触发事件通知
    UA_ValueCallback callback;
    callback.onRead = NULL;
    callback.onWrite = afterWriteCallbackVariable;

    UA_NodeId variableId;
    addVariableChangeSeverityOfCondition(pServer, &variableId);

    retval = UA_Server_setVariableNode_valueCallback(pServer, variableId, callback);
    if(retval != UA_STATUSCODE_GOOD) 
    {
        UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
                     "Setting variable Callback failed. StatusCode %s",
                     UA_StatusCode_name(retval));
        return retval;
    }

    return UA_STATUSCODE_GOOD;
}


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

int main()
{
    signal(SIGINT, stopHandler);
    signal(SIGTERM, stopHandler);


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

    UA_StatusCode retval = setupCondition(pServer);

    if(retval == UA_STATUSCODE_GOOD)
        retval = UA_Server_run(pServer, &running);

    UA_Server_delete(pServer);
    return retval == UA_STATUSCODE_GOOD ? EXIT_SUCCESS : EXIT_FAILURE;
}




/**
 * 写变量的回调函数,在这个函数里修改Condition的Severity属性,因为Severity属性的类型
 * 是ConditionVariableType,所以修改它可以自动触发事件通知,也就是让Server给Client
 * 发送通知
*/
static void
afterWriteCallbackVariable(UA_Server *server, const UA_NodeId *sessionId,
                             void *sessionContext, const UA_NodeId *nodeId,
                             void *nodeContext, const UA_NumericRange *range,
                             const UA_DataValue *data) {
   /* Another way to set fields of conditions */
    UA_Server_writeObjectProperty_scalar(server, gConditionInstance,
                                         UA_QUALIFIEDNAME(0, "Severity"),
                                         (UA_UInt16 *)data->value.data,
                                         &UA_TYPES[UA_TYPES_UINT16]);
}


// 根据OffNormalAlarmType来创建Condition实例. Condition source是
// server Object. 这个condition不会在Address Space里显示出来.
// UA_Server_createCondition()里倒数第二个参数为UA_NODEID_NULL,就可以让
// 这个condition不在Address Space里显示.
static UA_StatusCode addCondition(UA_Server *server, UA_NodeId * pConditionInstance)
{
    UA_StatusCode retval =
        UA_Server_createCondition(server, UA_NODEID_NULL,
                                  UA_NODEID_NUMERIC(0, UA_NS0ID_OFFNORMALALARMTYPE),
                                  UA_QUALIFIEDNAME(0, "Condition"),
                                  UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER),
                                  UA_NODEID_NULL, pConditionInstance);

    return retval;
}

// 添加变量,用来修改Condition的Severity属性
static void addVariableChangeSeverityOfCondition(UA_Server *server,
                                                 UA_NodeId* outNodeId) 
{
    UA_VariableAttributes attr = UA_VariableAttributes_default;
    attr.displayName = UA_LOCALIZEDTEXT("en", "Change Severity Condition");
    attr.dataType = UA_TYPES[UA_TYPES_UINT16].typeId;
    attr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE;
    UA_UInt32 severityValue = 0;
    UA_Variant_setScalar(&attr.value, &severityValue, &UA_TYPES[UA_TYPES_UINT16]);

    UA_QualifiedName CallbackTestVariableName =
        UA_QUALIFIEDNAME(0, "Change Severity Condition");
    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);
}

代码里添加了一个变量,这个变量的写回调会修改Condition的Severity;创建了Condition之后,把Retain设置True,表示这个Condition是Client关心的,然后把Condition的"EnabledState/Id"设置为True,使能这个Condition。

编译运行后,使用UaExpert连接Server后显示如下,
在这里插入图片描述
此时右击Documents点击Add按钮,
在这里插入图片描述

选择Event view,然后点击Add,
在这里插入图片描述
然后把Server对象拖到Event view的Configuration窗口里,因为Server是Condition Source,
在这里插入图片描述
此时在Events窗口里可以看到三个Event,拉开再看看,如下,
在这里插入图片描述
可以看到第二个的类型和Condition的类型保持一致,即OffNormalAlarmType,另外2个是干什么的呢?文档介绍如下,

Clients request a Refresh by calling the ConditionRefresh Method. The Server will respond with a RefreshStartEventType Event. This Event is followed by the Retained Conditions. The Server may also send new Event Notifications interspersed with the Refresh related Event Notifications. After the Server is done with the Refresh, a RefreshEndEvent is issued marking the completion of the Refresh.

Client发出Refresh请求后,Server发出第一个事件(RefreshStartEvent类型的事件)进行回应,然后把Retain属性为True的Condition通过事件发送过去,即第二个事件,最后发送第三个事件(RefreshEndEvent类型的事件)

可以通过点击刷新按钮进行测试,也可以先点击左侧的X来清除窗口,然后再刷新,如下,
在这里插入图片描述
最后我们把变量“Change Severity Condition”的值修改为1,此时可以看到Server主动发送了事件,
在这里插入图片描述
PS:代码里把Condition的属性Retain设置为True,这样Client这边才能看到这个Condition


四 以自定义对象为Condition Source

上节代码虽然创建了Alarm,但是并没有触发它,只是设置了它的Severity属性。这一节以自定义对象为Condition Source,同时会触发Alarm,是比较全面的例子。

代码如下,

// customized_as_condition_source.c
#include "open62541.h"


// Node id of Condition Source
static UA_NodeId gConditionSource;
// Node id of Condition实例
static UA_NodeId gConditionInstance;


// 添加自定义的Condition Source对象
static UA_StatusCode addConditionSourceObject(UA_Server *server);

// 添加Condition实例
static UA_StatusCode addCondition(UA_Server *server);

// 添加变量1,用来触发Condition报警
static void
addVariable_1_triggerAlarmOfCondition(UA_Server *server, UA_NodeId* outNodeId);

// 添加变量3,用于让Condition返回Normal状态
static void
addVariable_3_returnConditionToNormalState(UA_Server *server,
                                           UA_NodeId* outNodeId);

// 变量1的写回调
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);
// 变量3的写回调
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);


// 当Condition实例进入Enabled state时的回调函数
static UA_StatusCode
enteringEnabledStateCallback(UA_Server *server, const UA_NodeId *condition);

// 当Condition实例进入Acked state时的回调函数
static UA_StatusCode
enteringAckedStateCallback(UA_Server *server, const UA_NodeId *condition);

// 当Condition实例进入Confirmed state时的回调函数
static UA_StatusCode
enteringConfirmedStateCallback(UA_Server *server, const UA_NodeId *condition);


UA_StatusCode setupCondition(UA_Server * pServer)
{
    if (!pServer)
    {
        return UA_STATUSCODE_BAD;
    }

    UA_NodeId variable_1;
    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. */
    // 添加Condition实例
    UA_StatusCode retval = addCondition(pServer);
    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;
    }

    // 设置Condition进入EnabledState的回调函数
    UA_TwoStateVariableChangeCallback userSpecificCallback = enteringEnabledStateCallback;
    retval = UA_Server_setConditionTwoStateVariableCallback(pServer, gConditionInstance,
                                                            gConditionSource, false,
                                                            userSpecificCallback,
                                                            UA_ENTERING_ENABLEDSTATE);
    if(retval != UA_STATUSCODE_GOOD) {
        UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
                     "adding entering enabled state callback failed. StatusCode %s",
                     UA_StatusCode_name(retval));
        return retval;
    }

    // 设置Condition进入AckedState的回调函数
    userSpecificCallback = enteringAckedStateCallback;
    retval = UA_Server_setConditionTwoStateVariableCallback(pServer, gConditionInstance,
                                                            gConditionSource, false,
                                                            userSpecificCallback,
                                                            UA_ENTERING_ACKEDSTATE);
    if(retval != UA_STATUSCODE_GOOD) {
        UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
                     "adding entering acked state callback failed. StatusCode %s",
                     UA_StatusCode_name(retval));
        return retval;
    }

    // 设置Condition进入ConfirmedState的回调函数
    userSpecificCallback = enteringConfirmedStateCallback;
    retval = UA_Server_setConditionTwoStateVariableCallback(pServer, gConditionInstance,
                                                            gConditionSource, false,
                                                            userSpecificCallback,
                                                            UA_ENTERING_CONFIRMEDSTATE);
    if(retval != UA_STATUSCODE_GOOD) {
        UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
                     "adding entering confirmed state callback failed. StatusCode %s",
                     UA_StatusCode_name(retval));
        return retval;
    }

    // 添加变量1,用于触发Alarm
    addVariable_1_triggerAlarmOfCondition(pServer, &variable_1);

    callback.onWrite = afterWriteCallbackVariable_1;
    retval = UA_Server_setVariableNode_valueCallback(pServer, 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;
    }

    // 添加变量3,用来让Condition回到Normal
    addVariable_3_returnConditionToNormalState(pServer, &variable_3);

    callback.onWrite = afterWriteCallbackVariable_3;
    retval = UA_Server_setVariableNode_valueCallback(pServer, 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));
    }

    return retval;
}


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

int main()
{
    signal(SIGINT, stopHandler);
    signal(SIGTERM, stopHandler);


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

    UA_StatusCode retval = setupCondition(pServer);

    if(retval == UA_STATUSCODE_GOOD)
        retval = UA_Server_run(pServer, &running);

    UA_Server_delete(pServer);
    return retval == UA_STATUSCODE_GOOD ? EXIT_SUCCESS : EXIT_FAILURE;
}



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, &gConditionSource);

    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(gConditionSource.namespaceIndex,
                                                               gConditionSource.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(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"), gConditionSource,
                                       UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
                                       &gConditionInstance);

    return retval;
}


static void
addVariable_1_triggerAlarmOfCondition(UA_Server *server, UA_NodeId* outNodeId) {
    UA_VariableAttributes attr = UA_VariableAttributes_default;
    attr.displayName = UA_LOCALIZEDTEXT("en", "Activate Condition");
    attr.dataType = UA_TYPES[UA_TYPES_BOOLEAN].typeId;
    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");
    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_returnConditionToNormalState(UA_Server *server,
                                              UA_NodeId* outNodeId) {
    UA_VariableAttributes attr = UA_VariableAttributes_default;
    attr.displayName = UA_LOCALIZEDTEXT("en", "Return to Normal Condition");
    attr.dataType = UA_TYPES[UA_TYPES_BOOLEAN].typeId;
    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");
    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, gConditionInstance,
                                             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, gConditionInstance,
                                                              &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, gConditionInstance,
                                                             &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, gConditionInstance,
                                                 gConditionSource, 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;
        }
    }
}


/**
 * 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, gConditionInstance,
                                             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, gConditionInstance,
                                                          &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, gConditionInstance,
                                                         &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, gConditionInstance,
                                                         &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, gConditionInstance,
                                         &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, gConditionInstance,
                                         &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, gConditionInstance,
                                         &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, gConditionInstance,
                                         &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, gConditionInstance,
                                             gConditionSource, 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) {
    // 把Retain属性设置为True
    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) {
    /* 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) {
	/* 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;
}

代码很长,思路比较简单:就是创建自定义的Condition Source,然后为其添加一个Alarm,再添加2个变量,一个用来触发报警,一个用来回到Normal,这里还要重申一下,Alarm也是Condition

编译运行后,使用UaExpert连接后如下,可以在地址空间里看到我们添加的东西,
在这里插入图片描述
然后把ConditionSourceObject拖到Event view里,如下,
在这里插入图片描述
可以看到Client发送Refresh请求后,并没有看到我们创建的Condition,这是为什么呢?因为代码里创建Condition后并没有使能它,Retain属性也是False

1. 使能Condition

让我们先使能这个Condition,展开Condition,找到Enable方法,
在这里插入图片描述
右击该方法,点击Call,然后在弹出的界面里点击Call,
在这里插入图片描述
此时就可以看到发送的事件了,通知Client这个Condition已经使能了
在这里插入图片描述
在上面的代码里,进入EnabledState后会执行回调把Retain设置为True,这样Client就能收到这个Condition发送的事件了。

2. 触发报警

通过把“Activate Condition”变量设置为True,就可以触发报警了,同时Client也能收到对应的事件,如下红框,
在这里插入图片描述
而且其Active属性里的值是“Active”,上一次事件是“Inactive”,此时在Alarms窗口下,同样能看到此次的报警事件,如下,
在这里插入图片描述
选中这个事件,可以在下面的Details窗口中看到这个报警事件的各种详细信息,
在这里插入图片描述

3. 回到Normal

回到Norma有2种办法,一种是把变量“Return to Normal Condition”设置为True,通过执行其写回调来达到目的,另外一种是通过AckedState和ConfirmedState来实现。

先看第一种方法,修改值之后,Alarms窗口下那个报警事件就消失了,
在这里插入图片描述
在Events窗口可以看到收到2个额外的事件,此时Active属性值已经是“Inactive”了
在这里插入图片描述
这是因为该变量的写回调里会修改Condition的Severity属性和Message属性,根据上一节的分析,修改这2个属性都会触发事件通知,所以就收到了这2个事件。

此时我们清除并刷新一下Events窗口,这样就没有Condition发出的事件了。
在这里插入图片描述
到这一步之后,Condition的Retain属性值是False,但还是使能的,可以通过观察Condition的属性来得知,
在这里插入图片描述
然后我们来看第二种回到Normal的方法,此时先执行Disable方法,然后再执行一下Enable方法,
在这里插入图片描述

这样可以把Retain属性重新置为True,并产生2个事件如下,
在这里插入图片描述
接着把变量“Activate Condition”由True置为False,然后再由False置为True,这样就可以再次触发警报了。

此时在Alarms窗口下,右击这个报警事件,
在这里插入图片描述
先点击Acknowledge,在弹出的框里随便写点Comment,然后点击OK,如下,
在这里插入图片描述
此时在A下面的那个感叹号就变成了绿色对号,表示Client已经观察到这个警报了,
在这里插入图片描述
在Condition那边就会执行进入AckedState的回调函数,把“ActiveState/Id”置为False,具体可以看例子代码。

接着再右击这个事件,点击Confirm,在弹出的框里随便写点Comment,然后点击OK,写完之后这个事件直接消失了,因为执行了进入ConfirmedState的回调函数,把Retain属性又置为了False

此时切到Events窗口,可以看到历史事件,
在这里插入图片描述
以上就是2种回到Normal的方法,其实主要还是看代码怎么写的。


五 总结

本文讲解了Alarm和Condition,并以open62541自带例子为蓝本重新规划,把例子拆成2个,这样更容易理解。此外,通过分析可以看出在各种情况下的操作都可以通过代码来实现,主要取决于用户的需要。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

学习open62541 --- [71] Alarm and Condition 的相关文章

随机推荐

  • TCP/IP (一) accept建立连接

    七层网络协议 三次握手 四次分手 xff0c 这些大家都比较熟知 xff0c 这里主要是带着一些问题来思考整个TCP IP流程 1 三次握手的具体流程是怎么样的 xff1f 2 socket编程中int listen int fd int
  • http 的认证模式

    周海汉 2006 7 11 ablozhou 64 gmail com SIP类似Http协议 其认证模式也一样 Http协议 xff08 RFC 2616 xff09 规定可以采用Base模式和摘要模式 xff08 Digest sche
  • Java Agent

    在 Java 字节码 一文中有提到 xff0c 使用 Java Agent 操控字节码 xff0c 本文将讨论 Java Agent xff0c 这是普通 Java 开发人员的真正的黑魔法 Java Agent 能够通过执行字节码的直接修改
  • 通过gitlab远程统计git代码量

    git的代码量大多数都是根据命令行统计 xff0c 或者根据第三方插件统计 但是都不满足我的需求 xff0c 因为我们代码都由gitlab管理 xff0c 于是想到了通过gitlab暴露出来的接口获取数据 第一步 xff0c 生成私钥 登录
  • Qt第二十二章:将控件放到另一个控件的后面或前面

    话不多说 xff1a 看图
  • 缓存行填充与@sun.misc.Contended注解

    1 缓存模型 CPU和主内存之间有好几层缓存 xff0c 因为与cpu的速度相比 xff0c 访问主内存的速度是非常慢的 如果频繁对同一个数据做运算 xff0c 每次都从内存中加载 xff0c 运算完之后再写回到主内存中 xff0c 将会严
  • ThreadLocal那点事

    目录 1 ThreadLocal原理 2 ThreadLocal内存泄漏 3 ThreadLocal最佳实践 4 FastThreadLocal原理 5 FastThreadLocal最佳实践 6 ThreadLocal与FastThrea
  • 关于雪花算法的设计与思考

    2017年的时候项目组在开发一款大区游戏 xff0c 由于之前demo阶段的玩家id都是单服生成的 xff0c 只能保证单进程中的唯一 xff0c 而无法保证在分布式服务器端的唯一性 随着项目的开发进展 xff0c 需要设计能保证在分布式的
  • java反射之Method的invoke方法实现

    在框架中经常会会用到method invoke 方法 xff0c 用来执行某个的对象的目标方法 以前写代码用到反射时 xff0c 总是获取先获取Method xff0c 然后传入对应的Class实例对象执行方法 然而前段时间研究invoke
  • A*寻路算法之解决路径多拐点问题

    1 问题描述 最近公司正在开发的游戏涉及到了寻路算法 xff0c 然后我从网上找了一份A 算法代码 xff0c 整理了一下写了一个A 算法基础实现 然而 xff0c 在真正实用时A 寻路时 xff0c 却发现了几个问题 xff1a 基础实现
  • 代理模式与委托模式的异同点

    在 设计模式之禅 xff08 第二版 xff09 中 xff0c 作者说 代理模式也叫做委托模式 xff0c 显然是认为代理模式和委托模式是毫无差别的 然而在实际开发中 xff0c 我们通常可以很明确的知道一个模式究竟是代理模式还是委托模式
  • TCP/IP编程之select函数详解

    前述 xff1a linux下的I O复用模型目前很多都已经不用select函数了 xff0c 而是用epoll xff0c 但是为什么还需要了解select编程呢 xff0c 其实是从两个方面考虑的 xff1a 一是为了通过select去
  • 堆栈的详细解释

    一 在c中分为这几个存储区 1 栈 由编译器自动分配释放 2 堆 一般由程序员分配释放 xff0c 若程序员不释放 xff0c 程序结束时可能由OS回收 3 全局区 xff08 静态区 xff09 xff0c 全局变量和静态变量的存储是放在
  • Gmapping、hector、Cartographer三种激光SLAM算法简单对比

    文章目录 一 Gmapping是基于粒子滤波的算法 二 Hector SLAM三 Cartographer 一 Gmapping是基于粒子滤波的算法 缺点 xff1a 严重依赖里程计 xff0c 无法适应无人机及地面不平坦的区域 xff0c
  • TCP连接建立过程

    TCP连接建立过程 浏览器访问网站 xff0c 通过域名解析找到ip地址后会与服务器端建立连接 其中TCP xff08 Transmission Control Protocol xff0c 传输控制协议 xff09 是一种面向连接的 可靠
  • 海康威视错误代码文档大全【完整版】

    简介 本文收录了海康各大设备错误码 xff0c 按ctrl 43 f查询 xff1b 包含网络通讯库错误码 阵列错误码 安全激活相关错误码 智能设备错误码 RTSP通讯库错误码 软解码库错误码 转封装库错误码 语音对讲库错误码 Qos流控库
  • lighttpd http响应报文(Response)增加安全头Referrer-Policy和X-Permitted-Cross-Domain-Policies方法

    X Permitted Cross Domain Policies和Referrer Policy说明 X Permitted Cross Domain Policies X Permitted Cross Domain Policies
  • ROS使用ARUCO识别二维码获取位置信息做定位使用

    使用ARUCO识别二维码获取位置信息 1 安装软件 cd catkin ws src git clone b kinetic devel https github com pal robotics aruco ros cd catkin m
  • Keil 编译时无法找到头文件解决

    Keil 编译时无法找到头文件解决方法 1 背景 Keil 编译的时候无法找到头文件 xff0c 搜了下相关问题及解决方法 xff0c 有介绍说是因为文件夹中有数字 xff0c 无法搜到头文件 xff0c 进行了更改 xff0c 还是找不到
  • 学习open62541 --- [71] Alarm and Condition

    本文讲述Alarm and Condition的用法 xff0c 主要以源码里提供的例子为基础进行讲解和演示 xff0c 即tutorial server alarms conditions c xff0c 该例子写的有点乱 xff0c 本