ANSI C Based OPC UA Client/Server SDK  1.8.3.398
Lesson 5: Adding Support for Events

The goal of this lesson is to show how to provide Events in the Address Space.

Files used in this lesson:

Step 1: Adding HeaterSwitchedEventType

Figure 5-1 shows the machine type and the new event type that we will create in this lesson.

Figure 5-1 Machine Type and HeaterSwitchedEventType

gettingstarted1_lesson05_machine_objecttypeall.png

In our example, a machine shall send an event if its switch was toggled. Therefore we create a new event type that contains information about the new switch position as well as the current value of the temperature sensor. The new event type will be a subtype of the BaseEventType. For further information about event handling in the SDK, please see Alarms & Events.

New event types and new event fields have to be registered in the SDK, which is done in CustomProvider_RegisterEventTypes (see custom_provider_events.c):

/* HeaterSwitchedEventType */
a_pStartingNodeId->Identifier.Numeric++;
g_HeaterSwitchedEventTypeId = a_pStartingNodeId->Identifier.Numeric;
parentEventTypeId.NamespaceIndex = 0;
parentEventTypeId.Identifier.Numeric = OpcUaId_BaseEventType;
/* Register HeaterSwitchedEventType as new event type in the SDK */
uStatus = UaServer_Events_RegisterEventType(a_pStartingNodeId, &parentEventTypeId);
OpcUa_GotoErrorIfBad(uStatus);

The HeaterSwitchedEventType will have two additional event fields:

SwitchPosition
The new state of the machine’s switch that issued the event
CurrentTemperature
The temperature of the machine’s sensor when the event was sent

The event fields are registered in the SDK using their browse names and the returned local index is stored for later use.

/* Register the event fields of HeaterSwitchedEventType in the SDK */
/* SwitchPosition */
eventField.BrowsePath.NoOfElements = 1;
eventField.BrowsePath.Elements = (OpcUa_QualifiedName*)OpcUa_Alloc(sizeof(OpcUa_QualifiedName));
OpcUa_QualifiedName_Initialize(&eventField.BrowsePath.Elements[0]);
eventField.BrowsePath.Elements[0].NamespaceIndex = g_uCustomProvider_NamespaceIndex;
OpcUa_String_StrnCpy(&eventField.BrowsePath.Elements[0].Name,
OpcUa_String_FromCString("SwitchPosition"),
OPCUA_STRING_LENDONTCARE);
g_HeaterSwitchedEventType_IndexSwitchPosition = UaServer_Events_RegisterEventField(a_pStartingNodeId,
&eventField);
/* CurrentTemperature */
OpcUa_QualifiedName_Clear(&eventField.BrowsePath.Elements[0]);
eventField.BrowsePath.Elements[0].NamespaceIndex = g_uCustomProvider_NamespaceIndex;
OpcUa_String_StrnCpy(&eventField.BrowsePath.Elements[0].Name,
OpcUa_String_FromCString("CurrentTemperature"),
OPCUA_STRING_LENDONTCARE);
g_HeaterSwitchedEventType_IndexCurrentTemperature = UaServer_Events_RegisterEventField(a_pStartingNodeId,
&eventField);

In order to inform clients about the new event type, the respective nodes of the type and the event fields have to be created in the server’s address space:

/* Create nodes for HeaterSwitchedEventType in the address space */
nodeId.NamespaceIndex = 0;
nodeId.Identifier.Numeric = OpcUaId_BaseEventType;
UaServer_GetNode(pServerAddressSpace, &nodeId, &pBaseNode);
uStatus = UaServer_CreateObjectType(pAddressSpace,
&pEventType,
(OpcUa_BaseNode*)pBaseNode,
a_pStartingNodeId->Identifier.Numeric,
g_uCustomProvider_NamespaceIndex,
"HeaterSwitchedEventType");
OpcUa_GotoErrorIfBad(uStatus);
a_pStartingNodeId->Identifier.Numeric++;
uStatus = UaServer_CreateProperty(pAddressSpace,
&pProperty,
(OpcUa_BaseNode*)pEventType,
a_pStartingNodeId->Identifier.Numeric,
g_uCustomProvider_NamespaceIndex,
"SwitchPosition");
OpcUa_GotoErrorIfBad(uStatus);
OpcUa_Variable_SetDataType_Numeric(pProperty, OpcUaId_Boolean, 0);
a_pStartingNodeId->Identifier.Numeric++;
uStatus = UaServer_CreateProperty(pAddressSpace,
&pProperty,
(OpcUa_BaseNode*)pEventType,
a_pStartingNodeId->Identifier.Numeric,
g_uCustomProvider_NamespaceIndex,
"CurrentTemperature");
OpcUa_GotoErrorIfBad(uStatus);
OpcUa_Variable_SetDataType_Numeric(pProperty, OpcUaId_Double, 0);

Step 2: Adding Event References to the Address Space

Nodes that are able to emit events have to be marked as such in the address space. In our case, a reference of type GeneratesEvent from MachineType to HeaterSwitchedEventType is created. This reference is optional and helps clients to determine which events are to be expected by instances of certain object or variable types (see custom_provider.c).

OpcUa_StatusCode CustomProvider_CreateMachineType(OpcUa_NodeId *a_pStartingNodeId,
OpcUa_ObjectType **a_ppMachineType,
OpcUa_ObjectType **a_ppTemperatureSensorType)
{
...
/* Create GeneratesEvent reference from machine type to HeaterSwitchedEventType */
nodeId.NamespaceIndex = g_uCustomProvider_NamespaceIndex;
nodeId.Identifier.Numeric = g_HeaterSwitchedEventTypeId;
UaServer_GetNode(pAddressSpace, &nodeId, &pBaseNode);
referenceNodeId.Identifier.Numeric = OpcUaId_GeneratesEvent;
uStatus = OpcUa_BaseNode_AddReferenceToNode(pNewMachineType, pBaseNode, &referenceNodeId, OpcUa_True);
OpcUa_GotoErrorIfBad(uStatus);

For the instance of the MachineType, a reference of type HasNotifier has to be created from the Server object to the new Machine instance. Using this reference, the area hierarchy described in Alarms & Events is built up.

OpcUa_StatusCode CustomProvider_CreateMachine(const OpcUa_CharA *a_sMachineName,
const OpcUa_CharA *a_sTemperatureSensorName,
OpcUa_BaseNode *a_pOwner,
OpcUa_NodeId *a_pMachineTypeId,
OpcUa_NodeId *a_pTemperatureSensorTypeId,
OpcUa_NodeId *a_pStartingNodeId,
Machine **a_ppNewMachine)
{
...
/* Create HasNotifier reference from server object to new machine */
nodeId.NamespaceIndex = 0;
nodeId.Identifier.Numeric = OpcUaId_Server;
UaServer_GetNode(pServerAddressSpace, &nodeId, (OpcUa_BaseNode**)&pBaseNode);
referenceNodeId.Identifier.Numeric = OpcUaId_HasNotifier;
uStatus = OpcUa_BaseNode_AddReferenceToNode(pBaseNode, (OpcUa_BaseNode*)pObject, &referenceNodeId, OpcUa_True);
OpcUa_GotoErrorIfBad(uStatus);

Furthermore, the SDK and clients need to know that the machine instance is able to emit events. Therefore the EventNotifier attribute of the machine node is set and the machine is registered as EventNotifier in the SDK.

OpcUa_StatusCode CustomProvider_CreateMachine(const OpcUa_CharA *a_sMachineName,
const OpcUa_CharA *a_sTemperatureSensorName,
OpcUa_BaseNode *a_pOwner,
OpcUa_NodeId *a_pMachineTypeId,
OpcUa_NodeId *a_pTemperatureSensorTypeId,
OpcUa_NodeId *a_pStartingNodeId,
Machine **a_ppNewMachine)
{
...
/* Set the EventNotifier attribute of the node that clients know that it emits events */
OpcUa_Object_SetEventNotifier(pObject, OpcUa_EventNotifiers_SubscribeToEvents);
/* Register machine as event notifier */
nodeId.NamespaceIndex = 0;
nodeId.Identifier.Numeric = OpcUaId_Server;
uStatus = UaServer_Events_RegisterEventNotifier(a_pStartingNodeId, &nodeId);
OpcUa_GotoErrorIfBad(uStatus);
OpcUa_NodeId_CopyTo(a_pStartingNodeId, &nodeId);

The HeaterSwitch is registered as EventSource in the SDK as well and a reference of type HasEventSource is created between the new Machine instance and its HeaterSwitch property.

/* Register HeaterSwitch as event source */
uStatus = UaServer_Events_RegisterEventSource(a_pStartingNodeId, &nodeId);
OpcUa_GotoErrorIfBad(uStatus);
/* Create HasEventSource reference from machine object to new heater switch */
referenceNodeId.Identifier.Numeric = OpcUaId_HasEventSource;
uStatus = OpcUa_BaseNode_AddReferenceToNode(pObject, (OpcUa_BaseNode*)pVariable, &referenceNodeId, OpcUa_True);
OpcUa_GotoErrorIfBad(uStatus);

Step 3: Firing Events

Now we are prepared to fire events. In our example, the simulation timer is used to check whether the switch of the machine was toggled. If this is true, a HeaterSwitchedEvent is fired:

IFMETHODIMP(CustomProvider_SimulationTimerCallback)(OpcUa_Void *a_pvCallbackData,
OpcUa_Timer a_hTimer,
OpcUa_UInt32 a_msecElapsed)
{
static OpcUa_Boolean bLastSwitchPosition = OpcUa_False;
...
if (bLastSwitchPosition != g_bMyCustomMachineSwitch)
{
CustomProvider_FireHeaterSwitchedEvent(g_pMyCustomMachine);
}
bLastSwitchPosition = g_bMyCustomMachineSwitch;

The helper function CustomProvider_FireHeaterSwitchedEvent is used to generate and fire the event. It creates a new instance of HeaterSwitchedEventType using the NodeId which was assigned to the event type (see custom_provider.c):

OpcUa_StatusCode CustomProvider_FireHeaterSwitchedEvent(Machine *a_pMachine)
{
...
eventTypeId.NamespaceIndex = g_uCustomProvider_NamespaceIndex;
eventTypeId.Identifier.Numeric = g_HeaterSwitchedEventTypeId;
pEvent = UaServer_Events_CreateEvent(&eventTypeId);

Now the additional event fields are filled with reasonable values. For setting the event fields of the BaseEventType, convenience functions are provided by the SDK.

A unique EventId is created by using the SDK’s helper function UaServer_Events_CreateEventId and depending on the state of the switch, a message is generated. Please remember that the OpcUa_ByteString created by UaServer_Events_CreateEventId needs to be freed by the caller after use.

/* Set event fields of BaseEventType */
UaServer_Events_SetEventId(pEvent, &bsEventId);
UaServer_Events_SetEventType(pEvent, &eventTypeId);
UaServer_Events_SetSourceNode(pEvent, &a_pMachine->nodeId);
UaServer_Events_SetSourceName(pEvent, OpcUa_String_GetRawString(&a_pMachine->sMachineName));
UaServer_Events_SetTime(pEvent, OpcUa_DateTime_UtcNow());
UaServer_Events_SetReceiveTime(pEvent, OpcUa_DateTime_UtcNow());
if (*a_pMachine->pHeaterSwitch->pValue == OpcUa_False)
{
UaServer_Events_SetMessage(pEvent, "en", "The switch was set to OFF");
}
else
{
UaServer_Events_SetMessage(pEvent, "en", "The switch was set to ON");
}

The custom event fields are filled by using the local indices we retrieved when registering the event fields in Step 1: Adding HeaterSwitchedEventType.

/* Set custom event fields */
value.Datatype = OpcUaType_Boolean;
value.Value.Boolean = *a_pMachine->pHeaterSwitch->pValue;
UaServer_Events_SetEventField(pEvent, g_HeaterSwitchedEventType_IndexSwitchPosition, &value);
value.Datatype = OpcUaType_Double;
value.Value.Double = *a_pMachine->pTemperatureSensor->pValue;
UaServer_Events_SetEventField(pEvent, g_HeaterSwitchedEventType_IndexCurrentTemperature, &value);

Finally, the event is fired by calling UaServer_Events_FireEvent. As events are fired once and then forgot, we can delete it directly after emission:

uStatus = UaServer_Events_FireEvent(pEvent);
OpcUa_ByteString_Clear(&bsEventId);

Step 4: Receiving Events with a Client

After implementing firing of events, we can test if they can be received by a client.

For receiving events with UaExpert, create an Event View by clicking “Add Document”, selecting “Event View”, and clicking “Add” (see Figure 5-2).

Figure 5-2 Create an Event View using UaExpert

gettingstarted1_lesson05_create_eventview_uaexpert.png

Now you can drag and drop either the “Server” or the “MyCustomMachine” object into the Configuration section of the Event View. The client will then subscribe for events that are emitted by the respective object. To trigger an event, you can simply call the method we created in Lesson 4: Adding Support for Methods and toggle the state of the switch. Whenever the method is called our custom event will be fired and displayed in the client (see Figure 5-3).

Figure 5-3 Receive Events using UaExpert

gettingstarted1_lesson05_receive_machine_events_with_uaexpert.png