admin管理员组

文章数量:1599412

本文讲述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个,这样更容易理解。此外,通过分析可以看出在各种情况下的操作都可以通过代码来实现,主要取决于用户的需要。

本文标签: conditionAlarm