概述
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部分感觉其功能有点强大,内容还是比较多的,需要结合文档以及例程进行查看。