High Performance OPC UA Server SDK  1.4.1.263
Lesson 4: Adding Support for Events

This lesson explains how to add own event types and fire events.

Files used in this lesson:

Preliminary Note

The server from the previous lesson is extended by a new event type and an event notifier. However it does not require knowledge about methods and can also be done based on Lesson 2.

In this lesson the new nodes are created manually in code to accomplish a better understanding. This is feasible for a very small number of event types and event notifiers, but for a higher quantity of nodes it is recommended to use an engineering tool and load the namespace as shown in Sensor Model Server.

For customers not interrested in the eventing functionality, but a minimum of resource usage it is also possible to disable events completely by setting the cmake option UASERVER_SUPPORT_EVENTS to OFF.

Step 1: Add Event Types

All event types are direct or indirect subtypes of the abstract BaseEventType which is below the EventTypes folder. There are already a lot of event types defined by the OPC UA Specification, which can be used in many use-cases. But to show how new event types are created, this lesson will add a new event type, it is called NewValueEventType and will be fired when the value attribute of the variables created in Lesson 2 is written.

Add NewValueEventType

Creating the new event type is implemented in custom_provider_event.c in the function custom_provider_create_eventtypes() and is rather straightforward, create a new node, that is a subtype of BaseEventType and of nodeclass Objecttype:

/* create the newvalue event type node */
base_eventtype = ua_node_find0(UA_ID_BASEEVENTTYPE);
if (base_eventtype == UA_NODE_INVALID) return UA_EBAD;
ua_nodeid_set_numeric(&nodeid, g_custom_provider_nsidx, EVENT_TYPE_NEW_VALUE);
newvalue_eventtype = ua_node_create_with_attributes(
&nodeid, /* nodeid for the new node */
UA_NODECLASS_OBJECTTYPE, /* nodeclass of properties is variable */
g_custom_provider_nsidx, /* ns index for browsename is own namespace */
"NewValueEventType", /* browsename */
NULL, /* displayname, NULL is same as browsename */
UA_NODE_INVALID, /* the node has no typedefinition */
base_eventtype, /* parent of the new node is the base event type node */
UA_NODE_HASSUBTYPE); /* new node is referenced with hassubtype by parent */
if (newvalue_eventtype == UA_NODE_INVALID) return UA_EBAD;
ret = ua_objecttype_set_is_abstract(newvalue_eventtype, false);
if (ret != 0) return ret;
g_newvalue_eventtype = newvalue_eventtype;

The only nodeclass specific attribute is IsAbstract, which is set to false as instances of this event will be fired. Also the node handle for this the new event type is saved in a global variable for later use.

Add NewValue event field

When firing the event, we also want to attach custom data to the event, so we also need to add a new event field to the newly created event. This field is called NewValue and is intended for the new value, that was written to the value attribute. It is implemented by adding a new property to the event type:

/* create the newvalue event field node */
ua_nodeid_set_numeric(&nodeid, g_custom_provider_nsidx, EVENT_FIELD_NEW_VALUE);
newvalue_eventfield = ua_node_create_with_attributes(
&nodeid, /* nodeid for the new node */
UA_NODECLASS_VARIABLE, /* nodeclass of properties is variable */
g_custom_provider_nsidx, /* ns index for browsename is own namespace */
"NewValue", /* browsename */
NULL, /* displayname, NULL is same as browsename */
UA_NODE_PROPERTYTYPE, /* the node has the typedefinition property type */
newvalue_eventtype, /* parent of the new node is the newvalue event type node */
UA_NODE_HASPROPERTY); /* new node is referenced with hasproperty by parent */
if (newvalue_eventfield == UA_NODE_INVALID) return UA_EBAD;
ua_nodeid_set_numeric(&datatype_id, 0, UA_ID_UINT32);
ret = ua_variable_set_attributes(newvalue_eventfield, &datatype_id, UA_VALUERANK_ONEDIMENSION, UA_ACCESSLEVEL_CURRENTREAD, false);
if (ret != 0) return ret;

What is important here is the browsename, it is used to identify the event field, both by clients when subscribing for events and by the server when setting the value of the field. The datatype is set to UINT32 as this is the datatype of the variables the event is used for.

Add OutOfRangeEventType

For the example another event type is added, the OutOfRangeEventType. It is a subtype of the NewValueEventType and fired when a new value is written, but the value is out of range of the EURange property of the node. Creating the new event type works similar to creating the NewValueEventType, the main difference is the parent of the new node which is not the BaseEventType, but the NewValueEventType:

/* create the outofrange event type node */
ua_nodeid_set_numeric(&nodeid, g_custom_provider_nsidx, EVENT_TYPE_OUT_OF_RANGE);
outofrange_eventtype = ua_node_create_with_attributes(
&nodeid, /* nodeid for the new node */
UA_NODECLASS_OBJECTTYPE, /* nodeclass of properties is variable */
g_custom_provider_nsidx, /* ns index for browsename is own namespace */
"OutOfRangeEventType", /* browsename */
NULL, /* displayname, NULL is same as browsename */
UA_NODE_INVALID, /* the node has no typedefinition */
newvalue_eventtype, /* parent of the new node is the newvalue event type node */
UA_NODE_HASSUBTYPE); /* new node is referenced with hassubtype by parent */
if (outofrange_eventtype == UA_NODE_INVALID) return UA_EBAD;
g_outofrange_eventtype = outofrange_eventtype;
ret = ua_objecttype_set_is_abstract(outofrange_eventtype, false);
if (ret != 0) return ret;

The OutOfRangeEventType has no own event field, but inherits the NewValue event field from the NewValueEventType. Both created event types (like every other event type) also inherit the event fields from the BaseEventType, this will be explained in more detail when firing an event.

Step 2: Add Event Notifier

When a client wants to receive events, it needs to subscribe to an event notifier. These are nodes of nodeclass Object, which are hierarchically organized, where the top level node of this hierarchy is the Server object (ns=0;i=2253). An extended description and example regarding this hierarchy can be found in the OPC UA Specification, Part 3, Section HasNotifier.

For the example an event notifier called MyEventNotifier is created inside the CustomNodes folder. The attribute EventNotifier is set to indicate the node can be used to subscribe to events, the node is also added in the event notifier hierarchy directly below the Server object, this is implemented in the function custom_provider_create_eventnotifier():

/* create the event notifier node */
ua_nodeid_set_numeric(&nodeid, g_custom_provider_nsidx, EVENT_NOTIFIER_ID);
&nodeid, /* nodeid for the new node */
UA_NODECLASS_OBJECT, /* nodeclass of the new node */
g_custom_provider_nsidx, /* ns index for browsename is own namespace */
"MyEventNotfifier", /* browsename */
NULL, /* displayname, NULL for same as browsename */
UA_NODE_BASEOBJECTTYPE, /* typedefinition is basedatavariabletype */
custom_nodes, /* parent node of the new node */
UA_NODE_ORGANIZES); /* new node is referenced with organizes by parent */
if (notifier == UA_NODE_INVALID) return UA_EBAD;
ret = ua_object_set_event_notifier(notifier, UA_EVENTNOTIFIER_SUBSCRIBETOEVENTS);
if (ret != 0) return ret;
/* create HasNotifier reference from the Server object to the new notifier */
new_ref = ua_reference_add(UA_NODE_SERVEROBJECT, notifier, UA_NODE_HASNOTIFIER);
if (new_ref == UA_REF_INVALID) return UA_EBAD;

Each event also requires a source node, the source nodes are connected to the event notifier with reference from type HasEventSource. In this example all nodes of class Variable are possible event sources for MyEventNotifier, so references from MyEventNotifier to each of these variables are added:

/* ceate HasEventSource references to variable nodes */
ua_node_foreach(ref, custom_nodes) {
target = ua_reference_target(ref);
if (ua_node_get_nodeclass(target) != UA_NODECLASS_VARIABLE) {
continue;
}
new_ref = ua_reference_add(notifier, target, UA_NODE_HASEVENTSOURCE);
if (new_ref == UA_REF_INVALID) return UA_EBAD;
}

In cases where there is no distinct source node for an event, it is also possible to use the event notifier itself as the source node.

Step 3: Register Event Fields and Event Notifiers

The event fields of the new event types and the created event notifiers need to be registered at the SDK. This can be done for each field/notifier individually by using uaserver_eventfield_register and uaserver_eventnotifier_register or by using the *_register_all functions to register all fields/notifiers from a given namespace in a single function call. The latter is especially useful when loading the addressspace from a file created by an engineering tool. In this case all event fields/notifiers must still be registered, but it can be achieved with few lines of code, independent of the amount of nodes in the addressspace.

In the example there is a function for initializing the event functionality, custom_provider_event_init(), which is called at provider initialization. It call the functions custom_provider_create_eventtypes() and custom_provider_create_eventnotifier() explained above and calls the *_register_all functions to register the event fields/notifiers. Furthermore, the handle for the NewValue event field is looked up and saved in a global variable for later use:

/* initialize eventing for the provider */
int custom_provider_event_init(void)
{
int ret;
/* create nodes */
ret = custom_provider_create_eventtypes();
if (ret != 0) return ret;
ret = custom_provider_create_eventnotifier();
if (ret != 0) return ret;
/* register nodes */
ret = uaserver_eventfield_register_all(g_custom_provider_nsidx);
if (ret != 0) return ret;
ret = uaserver_eventnotifier_register_all(g_custom_provider_nsidx);
if (ret != 0) return ret;
/* lookup handle for later use */
g_newvalue_eventfield_handle = uaserver_eventfield_lookup_qn("NewValue", g_custom_provider_nsidx);
if (g_newvalue_eventfield_handle < 0) {
return g_newvalue_eventfield_handle;
}
return ret;
}

The counterpart is the custom_provider_event_clear() function, which invalidates the handles saved in global variables and unregisters event fields/notifiers:

/* clear eventing for the provider */
void custom_provider_event_clear(void)
{
int ret;
/* invalidate handles */
g_outofrange_eventtype = UA_NODE_INVALID;
g_newvalue_eventtype = UA_NODE_INVALID;
g_newvalue_eventfield_handle = -1;
/* unregister nodes */
ret = uaserver_eventfield_unregister_all(g_custom_provider_nsidx);
if (ret != 0) {
TRACE_ERROR(TRACE_FAC_APPLICATION, "%s: uaserver_eventfield_unregister_all() failed with errorcode=%i\n", __func__, ret);
}
ret = uaserver_eventnotifier_unregister_all(g_custom_provider_nsidx);
if (ret != 0) {
TRACE_ERROR(TRACE_FAC_APPLICATION, "%s: uaserver_eventnotifier_unregister_all() failed with errorcode=%i\n", __func__, ret);
}
return;
}

The number of event fields and event notifiers is limited by the settings subscription.num_eventfields and subscription.num_eventnotifiers. These settings determine the total number of event fields/notifiers from all namespaces including namespace zero.

Step 4: Create and Fire Events

Firing events is implemented in the function custom_provider_event_notifiy_value_change():

int custom_provider_event_notifiy_value_change(ua_node_t node, uint32_t value, bool outofrange)

This function is called whenever a value is written to the custom store implemented in custom_provider_store.c by custom_store_attach_value():

newval = value->value.value.ui32;
/* check range */
if (newval > g_custom_provider_ranges[idx].eu_high ||
newval < g_custom_provider_ranges[idx].eu_low) {
custom_provider_event_notifiy_value_change(node, newval, true);
} else {
custom_provider_event_notifiy_value_change(node, newval, false);
}

Every time a new value is written to a node this function will fire a new event for the respective node. The function receives the new value and whether it is out of range of the EURange, depending on this information a different type of Event is created and fired.

Create Event

At the beginning of the function the event structure is created, this requires the event type and the event source. As event type one of the global variables set in custom_provider_create_eventtypes() above is used, depending if the value is out of range. It would also be possible to look up the node handle dynamically, but doing the lookup only once in the server lifetime is more efficient. As source node the node handle provided by the caller is used.

Each event has multiple event fields based on its event type, these fields are inherited from supertypes and each event type can have its own fields. As every event is inherited from the BaseEventType, every event has its fields. Most of these event fields are mandatory and thus initialized at event creation with reasonable values. So the application is not required to set any of these event fields, but can use convenience functions from event to overwrite the default values. In the example only in case the value is out of range, the severity is set to a rather high value of 700, else the rather low severity of 200 is kept:

/* create event */
if (outofrange) {
e = uaserver_event_create(g_outofrange_eventtype, node);
if (e == NULL) {
ret = UA_EBAD;
goto error;
}
/* change severity */
if (ret != 0) goto error;
} else {
e = uaserver_event_create(g_newvalue_eventtype, node);
if (e == NULL) {
ret = UA_EBAD;
goto error;
}
}

Set Custom Event Field

The event has one event field, that is inherited from BaseEventType, the NewValue event field. To set this field first a ua_variant must be prepared with the value, next the variant must be copied into the event structure. Event fields are identified SDK internally by unique handles, to set the value of the NewValue event field, there are multiple alternatives:

  1. Lookup the handle once during the initialization and save it for later use. This is the most efficient solution.
  2. When the browsepath of the event field has only one element, there is a convenience function for easy lookup.
  3. When the browsepath of the event field has multiple elements, an array of qualifiedname is required and each element must be set individually. The array is used to lookup the field handle and set the value.
/* fill variant for custom field */
/* alternative 1 */
ret = uaserver_event_add_field(e, g_newvalue_eventfield_handle, &v);
if (ret != 0) goto error;
/* alternative 2 */
ret = uaserver_event_overwrite_field_qn(e, "NewValue", g_custom_provider_nsidx, &v);
if (ret != 0) goto error;
/* alternative 3 */
IPC_CLEAR(browse_path);
ret = ua_qualifiedname_set(&browse_path[0], g_custom_provider_nsidx, "NewValue");
if (ret != 0) goto error;
field_handle = uaserver_eventfield_lookup(browse_path, 1);
ret = uaserver_event_overwrite_field(e, field_handle, &v);
ua_qualifiedname_clear(&browse_path[0]);
if (ret != 0) goto error;
/* clear variant with custom field */

To set the event field there are add and overwrite functions. When first setting the event field, the add function should be used, when overwriting an already set field, the overwrite function must be used.

Fire Event

The event can now be fired and is deleted afterwards:

/* fire event */
if (ret != 0) goto error;
/* delete event */
return 0;

Step 5: Receive Events with UAExpert

After building the example and connecting with UAExpert the Event View can be opened by clicking Document->Add->Event View->Add. In the top most window event notifiers can be added via drag and drop, for the example either the Server or MyEventNotifier object.

The event fields to subscribe can be changed by clicking the little arrow on the left of the event notifier, selecting the desired fields like SimpleEvents->NewValueEventType->NewValue and clicking Apply.

When now writing values to Variable1, Variable2 or Variable3 events are fired and can be viewed in the middle window. Depending on the value written different types of events with different severity will appear. The bottom window shows the values of the subscribed event fields including the NewValue field.