赞
踩
监控项目能够监测到数据的变化,但是有时候并不能传递足够的信息,使用事件可以带有信息以及其可以在任何时候进行触发,同时其属性可以被客户端进行过滤,只接收自己感兴趣的特定属性。本文将描述server端事件的触发以及client端事件的过滤及接收。
在使用之前我们需要编译open62541,我们使用CMake勾选对应的选项进行编译即可得到对应的.h和.c文件,勾选选项如下
重新编译后即可得到对应.h和.c文件。
这里以例程tutorial_server_events.c为例,描述server端去触发事件,代码如下
/* 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> /** * Generating events * ----------------- * To make sense of the many things going on in a server, monitoring items can be useful. Though in many cases, data * change does not convey enough information to be the optimal solution. Events can be generated at any time, * hold a lot of information and can be filtered so the client only receives the specific attributes he is interested in. * * Emitting events by calling methods * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ * The following example will be based on the server method tutorial. We will be * creating a method node which generates an event from the server node. * * The event we want to generate should be very simple. Since the `BaseEventType` is abstract, * we will have to create our own event type. `EventTypes` are saved internally as `ObjectTypes`, * so add the type as you would a new `ObjectType`. */ static UA_NodeId eventType; static UA_StatusCode addNewEventType(UA_Server *server) { UA_ObjectTypeAttributes attr = UA_ObjectTypeAttributes_default; attr.displayName = UA_LOCALIZEDTEXT("en-US", "SimpleEventType"); attr.description = UA_LOCALIZEDTEXT("en-US", "The simple event type we created"); return UA_Server_addObjectTypeNode(server, UA_NODEID_NULL, UA_NODEID_NUMERIC(0, UA_NS0ID_BASEEVENTTYPE), UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE), UA_QUALIFIEDNAME(0, "SimpleEventType"), attr, NULL, &eventType); } /** * Setting up an event * ^^^^^^^^^^^^^^^^^^^ * In order to set up the event, we can first use ``UA_Server_createEvent`` to give us a node representation of the event. * All we need for this is our `EventType`. Once we have our event node, which is saved internally as an `ObjectNode`, * we can define the attributes the event has the same way we would define the attributes of an object node. It is not * necessary to define the attributes `EventId`, `ReceiveTime`, `SourceNode` or `EventType` since these are set * automatically by the server. In this example, we will be setting the fields 'Message' and 'Severity' in addition * to `Time` which is needed to make the example UaExpert compliant. */ static UA_StatusCode setUpEvent(UA_Server *server, UA_NodeId *outId) { UA_StatusCode retval = UA_Server_createEvent(server, eventType, outId); if (retval != UA_STATUSCODE_GOOD) { UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "createEvent failed. StatusCode %s", UA_StatusCode_name(retval)); return retval; } /* Set the Event Attributes */ /* Setting the Time is required or else the event will not show up in UAExpert! */ UA_DateTime eventTime = UA_DateTime_now(); UA_Server_writeObjectProperty_scalar(server, *outId, UA_QUALIFIEDNAME(0, "Time"), &eventTime, &UA_TYPES[UA_TYPES_DATETIME]); UA_UInt16 eventSeverity = 500; UA_Server_writeObjectProperty_scalar(server, *outId, UA_QUALIFIEDNAME(0, "Severity"), &eventSeverity, &UA_TYPES[UA_TYPES_UINT16]); UA_LocalizedText eventMessage = UA_LOCALIZEDTEXT("en-US", "An event has been generated."); UA_Server_writeObjectProperty_scalar(server, *outId, UA_QUALIFIEDNAME(0, "Message"), &eventMessage, &UA_TYPES[UA_TYPES_LOCALIZEDTEXT]); UA_String eventSourceName = UA_STRING("Server"); UA_Server_writeObjectProperty_scalar(server, *outId, UA_QUALIFIEDNAME(0, "SourceName"), &eventSourceName, &UA_TYPES[UA_TYPES_STRING]); return UA_STATUSCODE_GOOD; } /** * Triggering an event * ^^^^^^^^^^^^^^^^^^^ * First a node representing an event is generated using ``setUpEvent``. Once our event is good to go, we specify * a node which emits the event - in this case the server node. We can use ``UA_Server_triggerEvent`` to trigger our * event onto said node. Passing ``NULL`` as the second-last argument means we will not receive the `EventId`. * The last boolean argument states whether the node should be deleted. */ static UA_StatusCode generateEventMethodCallback(UA_Server *server, const UA_NodeId *sessionId, void *sessionHandle, const UA_NodeId *methodId, void *methodContext, const UA_NodeId *objectId, void *objectContext, size_t inputSize, const UA_Variant *input, size_t outputSize, UA_Variant *output) { UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Creating event"); /* set up event */ UA_NodeId eventNodeId; UA_StatusCode retval = setUpEvent(server, &eventNodeId); if(retval != UA_STATUSCODE_GOOD) { UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Creating event failed. StatusCode %s", UA_StatusCode_name(retval)); return retval; } retval = UA_Server_triggerEvent(server, eventNodeId, UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_SERVERSTATUS), NULL, UA_TRUE); if(retval != UA_STATUSCODE_GOOD) UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Triggering event failed. StatusCode %s", UA_StatusCode_name(retval)); return retval; } /** * Now, all that is left to do is to create a method node which uses our callback. We do not * require any input and as output we will be using the `EventId` we receive from ``triggerEvent``. The `EventId` is * generated by the server internally and is a random unique ID which identifies that specific event. * * This method node will be added to a basic server setup. */ static void addGenerateEventMethod(UA_Server *server) { UA_MethodAttributes generateAttr = UA_MethodAttributes_default; generateAttr.description = UA_LOCALIZEDTEXT("en-US","Generate an event."); generateAttr.displayName = UA_LOCALIZEDTEXT("en-US","Generate Event"); generateAttr.executable = true; generateAttr.userExecutable = true; UA_Server_addMethodNode(server, UA_NODEID_NUMERIC(1, 62541), UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER), UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT), UA_QUALIFIEDNAME(1, "Generate Event"), generateAttr, &generateEventMethodCallback, 0, NULL, 0, NULL, NULL, NULL); } /** It follows the main server code, making use of the above definitions. */ static volatile 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)); addNewEventType(server); addGenerateEventMethod(server); UA_StatusCode retval = UA_Server_run(server, &running); UA_Server_delete(server); return retval == UA_STATUSCODE_GOOD ? EXIT_SUCCESS : EXIT_FAILURE; }
在这个例程中我们创建了自己的事件类型SimpleEventType,其继承于BaseEventType。另外添加了一个方法节点用于进行事件的触发,在每次触发前都需要先创建一个新的事件,同时设置事件的字段值,此例程设置了四个字段,Time、Severity、Message和SourceName。我们也可以添加其他一些字段信息,其他定义我们可以使用Uaexpert来进行查看
可以看到总共有9个字段,我们可以单独的进行设置。我们调用Generate Event方法后就会通过回调去触发一个事件。我们可以使用Uaexpert来进行验证,
添加事件窗口后我们需要添加Server对象节点
我们调用生成事件方法,在event view中能看到接收到的事件
点击这个事件可以获取相关的信息。
这里以例程tutorial_client_events.c为例,描述client端如何去接收server端发出的事件,代码如下
/* 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) { 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 { #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 = 2; 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"); 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; 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; }
这里与监控变量操作大致一致,只是这里监控的是server节点下的事件通知,
item.itemToMonitor.nodeId = UA_NODEID_NUMERIC(0, 2253); // Root->Objects->Server
item.itemToMonitor.attributeId = UA_ATTRIBUTEID_EVENTNOTIFIER;
item.monitoringMode = UA_MONITORINGMODE_REPORTING;
同时这里使用的事件过滤器,只选择了Message和Severity两个字段。调用UA_Client_MonitoredItems_createEvent设置好接收事件回调函数后我们只需在回调函数中做处理即可
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) { 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 { #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 } } }
这里打印了事件相关字段的值。我们使用Uaexpert触发事件,
这样我们client就能正常接收事件信息了,如果想获取更多信息,在过滤字段那里进行添加即可。
在server例程中我们创建了自定义的事件类型,其注释表面BaseEventType是抽象的不能直接实例化对象,但实际uaexpert中查看它并不是抽象的,
尝试使用BaseEventType去创建事件也是OK的。打个问号*-*
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。