Client: Alarm events

808 views
Skip to first unread message

michael...@googlemail.com

unread,
Dec 5, 2016, 11:24:47 AM12/5/16
to open62541
I'm beginning a OPC UA client project to fetch some data from an existing server.
I know that this server will generate alarm events on some value changes and I will have to receive and process those alarm events.
I've read in the beginning of the doc that alarm events aren't available in open62541 version 0.2, but I'm not sure whether this only holds for open62541 server sdk or for the client sdk, too?

Will I be able to create a client with open62541 that can handle alarm events from an existing server?

Sorry, this is probably a opc ua very noob question and thx for answers.

Julius Pfrommer

unread,
Dec 5, 2016, 11:42:54 AM12/5/16
to open62541, michael...@googlemail.com
Hello Michael,

you have to distinguish between DataChange notifications and Event notifications.

DataChange notifications are generated by cyclically checking a value for changes (with the selected time interval).
They are supported by the server and client implementation of open62541.

Events are discrete notifications "manually" generated in the server.
They are currently unsupported in open62541.

Best regards,
Julius

michael...@googlemail.com

unread,
Dec 5, 2016, 11:58:07 AM12/5/16
to open62541, michael...@googlemail.com
Thank you Julius,

I guess I am talking about Event notifications (sorry if it was ambiguous).

Your answer isn't 100% clear to me, so there is some little hope left:
The open62541 server can't "manually" generate such an event notification, that's clear to me.
But if there is another OPC UA server (non open62541) that manually generates event notifications, will a open62541 client be able to receive and process them?

Julius Pfrommer

unread,
Dec 6, 2016, 12:13:07 PM12/6/16
to open62541, michael...@googlemail.com
Hello Michael,

there is a possibility. But it requires some "hacking".

I am writing the following to give some indication for future development (whoever might be involved).

The function UA_Client_Subscriptions_addMonitoredItem is currently geared towards the creation of DataChange notifications.
Creating an Event MonitoredItem is done over the same CreateMonitoredItems service that is already used.

For Events, the MonitoredItemCreateRequest data structure needs to be filled differently.
  • The itemToMonitor member needs to point the EventNotifier attribute of an object
  • The requestedParameters must contain a filter of the type EventFilter with appropriate settings

The EventFilter data type can be generated automatically by adding it (and its dependencies) to /tools/schema/datatypes_minimal.txt.


All of that should be rather easy to implement for experienced open62541 developers (and a bit harder for new ones ^^).
But it is not implemented so far in the client. People with an interest in the functionality and capacity fo development are welcome to step forward.
I created a new issue https://github.com/open62541/open62541/issues/905 to track the status of this feature request.

Best regards,
Julius

michael...@googlemail.com

unread,
Mar 31, 2017, 8:58:30 AM3/31/17
to open62541, michael...@googlemail.com
Hello Julius,

I've found some time to work on it, but I'm stuck and I'm not sure where to find the error. I don't have much OPC UA experience, maybe that's the problem.
Maybe the subscription itself works (at least not errorcode returned) but I don't receive any callbacks during my UA_Client_Subscriptions_manuallySendPublishRequest(client); calls (datachange subscriptions do work with the same callback function).

I'm testing on the public OPC UA server opc.tcp://uademo.prosysopc.com:53530/OPCUA/SimulationServer where I subscribed to events of Root\Objects\Server with the SoftIng Client. There I receive ExclusiveLevelAlarmType events once in a while and whenever manually enabled. I want to do the same hardcoded in my event notification subscription implementation of Open62541 before working on a more general implementation (to better understand how it works).

I guess there's something wrong with my selectClauses, because there seems to be no error if unknown types and fields are selected.

What I've done:
  • I've added EventFilter and its dependency SimpleAttributeOperand to datatypes_minimal.txt
  • I've created a UA_EventFilter with UA_EventFilter_new()
  • I've created a single selectClause with UA_Array_new(1, &UA_TYPES[UA_TYPES_SIMPLEATTRIBUTEOPERAND]) and initialized it with UA_SimpleAttributeOperand_init(...);
  • I've set the selectClause's typeDefinitionId to UA_NODEID_NUMERIC(0, 2041) which is BaseEventType, is this right? The generated events are of type ExclusiveLevelAlarmType the selected field is from BaseEventType.
  • I've created a browsePath in the selectClause (again an array with a single element) which I set to  UA_QUALIFIEDNAME(0, "EventId"); Again: Is this right? My intention is to select the EventId of the generated ExclusiveLevelAlarmType but EventId is a field of BaseEventType!
  • I've set the selectClause's attributeId to UA_ATTRIBUTEID_VALUE - is that right?
  • I did not set any whereClause information (intention is to receive all events)
  • I did not set/change the selectClause's indexRange - no idea what it's for
  • requestedParameters.filter.encoding = UA_EXTENSIONOBJECT_DECODED_NODELETE;
  • requestedParameters.filter.content.decoded.type = &UA_TYPES[UA_TYPES_EVENTFILTER];
  • requestedParameters.filter.content.decoded.data = eFilter; // my created EventFilter object. eFilter is type EventFilter*

I didn't change anything else, except playing around with samplingInterval, discardOldest etc. I've set those at both positions in the code to the values shown in the SoftIng Client.


To subscribe I use


        UA_UInt32 subId = 0;
        UA_Client_Subscriptions_new(client, UA_SubscriptionSettings_standard, &subId);
        if (subId)
            printf("Create subscription succeeded, id %u\n", subId);
        UA_NodeId monitorThis = UA_NODEID_NUMERIC(0, 2253); // Server
        UA_UInt32 monId = 0;
        retval = UA_Client_Subscriptions_addMonitoredEventTEST(client, subId, monitorThis, UA_ATTRIBUTEID_EVENTNOTIFIER, &handler_Event, NULL, &monId); 







Here's my full function code with marked changes from original UA_Client_Subscriptions_addMonitoredItem function


   
UA_StatusCode
UA_Client_Subscriptions_addMonitoredEventTEST(UA_Client *client, UA_UInt32 subscriptionId,
                                         UA_NodeId nodeId, UA_UInt32 attributeID,
                                         UA_MonitoredItemHandlingFunction hf,
                                         void *hfContext, UA_UInt32 *newMonitoredItemId) {
    UA_Client_Subscription *sub;
    LIST_FOREACH(sub, &client->subscriptions, listEntry) {
        if(sub->subscriptionID == subscriptionId)
            break;
    }
    if(!sub)
        return UA_STATUSCODE_BADSUBSCRIPTIONIDINVALID;

    /* Send the request */
    UA_CreateMonitoredItemsRequest request;
    UA_CreateMonitoredItemsRequest_init(&request);
    request.subscriptionId = subscriptionId;
    UA_MonitoredItemCreateRequest item;
    UA_MonitoredItemCreateRequest_init(&item);
    item.itemToMonitor.nodeId = nodeId;
    item.itemToMonitor.attributeId = attributeID;
    item.monitoringMode = UA_MONITORINGMODE_REPORTING;
    item.requestedParameters.clientHandle = ++(client->monitoredItemHandles);
    item.requestedParameters.samplingInterval = 0;        // mbreiter: changed
    item.requestedParameters.discardOldest = false;        // mbreiter: changed
    item.requestedParameters.queueSize = 0;                // mbreiter: changed


    // ---------------------------------------------------------------------
    //mbreiter:
        UA_EventFilter * eFilter = UA_EventFilter_new();
        UA_EventFilter_init(eFilter);

        // creating a single element array for the select clause of length 1
        const size_t nSelectClauses = 1;
        UA_SimpleAttributeOperand * selectClauses = UA_Array_new(nSelectClauses, &UA_TYPES[UA_TYPES_SIMPLEATTRIBUTEOPERAND]);
        if(selectClauses == 0)
        {
            // TODO: error handling: array could not be created
        }

        // set and init
        eFilter->selectClausesSize = nSelectClauses;
        for(size_t i =0; i<nSelectClauses; ++i )
        {
            UA_SimpleAttributeOperand_init(&selectClauses[i]);
        }

        selectClauses[0].typeDefinitionId = UA_NODEID_NUMERIC(0, 2041) ; // mbreiter: BaseEventType hardcoded
        selectClauses[0].browsePathSize = 1;    // mbreiter: hardcoded single browsePath for testing
            {
                selectClauses[0].browsePath = (UA_QualifiedName*)UA_Array_new(selectClauses[0].browsePathSize, &UA_TYPES[UA_TYPES_QUALIFIEDNAME]);
                if(selectClauses[0].browsePath == 0)
                {
                    // TODO: error handling: array could not be created
                }
                selectClauses[0].browsePath[0] = UA_QUALIFIEDNAME(0, "EventId");
            }

        selectClauses[0].attributeId = UA_ATTRIBUTEID_VALUE;
        //    selectClauses[0].indexRange = ???

        eFilter->selectClauses = selectClauses;
        eFilter->selectClausesSize = nSelectClauses;
        eFilter->whereClause.elementsSize = 0;


        // mbreiter: setting the requestedParameters filter information previously created
        item.requestedParameters.filter.encoding = UA_EXTENSIONOBJECT_DECODED_NODELETE; // UA_EXTENSIONOBJECT_ENCODED_NOBODY = 0, UA_EXTENSIONOBJECT_ENCODED_BYTESTRING = 1, UA_EXTENSIONOBJECT_ENCODED_XML = 2, UA_EXTENSIONOBJECT_DECODED = 3, UA_EXTENSIONOBJECT_DECODED_NODELETE   = 4
        item.requestedParameters.filter.content.decoded.type = &UA_TYPES[UA_TYPES_EVENTFILTER];
        item.requestedParameters.filter.content.decoded.data = eFilter;
    // ---------------------------------------------------------------------

    request.itemsToCreate = &item;
    request.itemsToCreateSize = 1;
    UA_CreateMonitoredItemsResponse response = UA_Client_Service_createMonitoredItems(client, request);

    // slight misuse of retval here to check if the deletion was successfull.
    UA_StatusCode retval;
    if(response.resultsSize == 0)
        retval = response.responseHeader.serviceResult;
    else
        retval = response.results[0].statusCode;
    if(retval != UA_STATUSCODE_GOOD) {
        UA_CreateMonitoredItemsResponse_deleteMembers(&response);
        return retval;
    }

    /* Create the handler */
    UA_Client_MonitoredItem *newMon = (UA_Client_MonitoredItem *)UA_malloc(sizeof(UA_Client_MonitoredItem));
    newMon->monitoringMode = UA_MONITORINGMODE_REPORTING;
    UA_NodeId_copy(&nodeId, &newMon->monitoredNodeId);
    newMon->attributeID = attributeID;
    newMon->clientHandle = client->monitoredItemHandles;
    newMon->samplingInterval = 0;         // mbreiter: changed // = sub->publishingInterval;
    newMon->queueSize = 0;                 // mbreiter: changed // = 1;        // mbreiter: should be set to same value as above instead of hardcoding?!?
    newMon->discardOldest = false;         // mbreiter: changed // = true;
    newMon->handler = hf;
    newMon->handlerContext = hfContext;
    newMon->monitoredItemId = response.results[0].monitoredItemId;
    LIST_INSERT_HEAD(&sub->monitoredItems, newMon, listEntry);
    *newMonitoredItemId = newMon->monitoredItemId;

    UA_LOG_DEBUG(client->config.logger, UA_LOGCATEGORY_CLIENT,
                 "Created a monitored item with client handle %u", client->monitoredItemHandles);

    UA_CreateMonitoredItemsResponse_deleteMembers(&response);
    return UA_STATUSCODE_GOOD;
}




Any idea where I'm missing something or did a mistake?

michael...@googlemail.com

unread,
Apr 3, 2017, 10:46:54 AM4/3/17
to open62541, michael...@googlemail.com
Update: After some Debugging I've seen that the UA_Client_processPublishResponse differs in cases where an event occured and in cases where no event occured.
In the case that an event was generated previously on the server, the UA_Client_processPublishResponse comes to state if(msg->notificationData[k].encoding != UA_EXTENSIONOBJECT_DECODED) continue; so a response IS generated but it looks like there is problem in the encoding of the response?

I've observed that the the response of A_Client_Service_createMonitoredItems(client, request); during subscription could not be decoded, I tried with a BadDecodingError

    UA_DataType eType;
    retval = ExtensionObject_decodeBinary(&response.results[0].filterResult, &eType);



I tried to encode during subscription:

    ExtensionObject_encodeBinary(&item.requestedParameters.filter, 0);

But here I get a BadEncodingLimitsExceeded

Julius Pfrommer

unread,
Apr 4, 2017, 3:59:22 AM4/4/17
to open62541, michael...@googlemail.com
Hello Michael,

it looks as if you get Event messages returned from the server. But some of the datatypes have not been compiled into the stack.
The line


if(msg->notificationData[k].encoding != UA_EXTENSIONOBJECT_DECODED)

says that data was returned, but it could not be decoded since the type was unknown.
In that case, notificationData[k] contains the decoded NodeId of the data type and the data in binary or xml encoding.
If decoding with a known type fails, an error code is returned instead.

The possible data types for NotificationData are detailed in the spec, part 4 §7.20.
Just enable

- DataChangeNotification
- EventNotificationList
- EventFieldList
- StatusChangeNotification

to get all of them. Or, for an iterative appraoch, you can break in a debugger to see the NodeId of the undecoded data type in notificationData[k].

Best regards,
Julius

michael...@googlemail.com

unread,
Apr 11, 2017, 2:39:18 PM4/11/17
to open62541, michael...@googlemail.com
Hello again,

thank you! It's working. I'm getting the EventNotificationList and I added some code to UA_Client_processPublishResponse to handle the EventNotificationList case.
Now I'm facing the problem, that the event notification only provides the EventFields list with single UA_Variant items, but the callback handle assumes a single UA_DataValue [ void (*UA_MonitoredItemHandlingFunction)(UA_UInt32 monId, UA_DataValue *value, void *context) ].
I see at least thosedifferent possibilities to solve my problem, but I don't know which is the right/best one:
  1. For each EventField (UA_Variant) I create a (new or shared) UA_DataValue holding that variant and use the callback for each single element.
  2. I add a second handle to the UA_Client_MonitoredItem struct that is used during subscription and during response for events only: void (*handlerEvents)(UA_UInt32 monId, size_t nEventFields, UA_Variant *eventFields, void *context);
  3. Use the handlerConext to encode/communicate the event notification response information to the callback function.
Comments:
1.: Problems: This will split the event message and as far as I see I don't have any of the timestamps usable in the UA_DataValue type. I don't like that solution.
2.: Problems: This solution will add some overhead and unwanted structure to UA_Client_MonitoredItem and the library
3.: Problems: Error-prone and restricts/limits the Context usage

Any suggestions about what should be the "right" way to solve it? For the moment I'll try to use the 2. version...

Best regards,
Michael

Julius Pfrommer

unread,
Apr 15, 2017, 7:22:43 AM4/15/17
to open62541, michael...@googlemail.com
Hello there,

I like the second option best as well. It is clean and exposes the messages going over the network as direct as possible.
Did you add different versions of UA_Client_Subscriptions_addMonitoredItem to accomodate for the different callbacks?

If you want, we can include your changes into the master branch.
Just make a pull request on github when you are ready or send us the changes over a private channel.

Best regards,
Julius

Stefan Profanter

unread,
Aug 24, 2017, 5:00:48 AM8/24/17
to open62541, michael...@googlemail.com
Client side events are now supported in open62541:
https://github.com/open62541/open62541/pull/1161


Am Montag, 5. Dezember 2016 17:24:47 UTC+1 schrieb michael...@googlemail.com:
Reply all
Reply to author
Forward
0 new messages