UA Server SDK C++ Bundle  1.4.1.271
 All Classes Namespaces Functions Variables Typedefs Enumerations Enumerator Friends Groups Pages
Lesson 6: Create EventMonitoredItem

Step 1: Create a Subscription with a Monitored Item

A client can subscribe to different types of monitored items (see OPC UA Subscription Concept). In Lesson 2: Create DataMonitoredItem a subscription containing a data monitored item was created. The next two steps explain how to create an event monitored item.

The monitored item is created in SampleSubscription::createMonitoredItem. We remove the code which creates data monitored items from configuration and create a single monitored item for the EventNotifier instead:

Replace these lines

UaStatus result;
OpcUa_UInt32 size, i;
ServiceSettings serviceSettings;
UaMonitoredItemCreateRequests itemsToCreate;
UaMonitoredItemCreateResults createResults;
// Configure items to add to the subscription
UaNodeIdArray lstNodeIds = m_pConfiguration->getNodesToMonitor();
size = lstNodeIds.length();
itemsToCreate.create(size);
for (i = 0; i < size; i++)
{
itemsToCreate[i].ItemToMonitor.AttributeId = OpcUa_Attributes_Value;
OpcUa_NodeId_CopyTo(&lstNodeIds[i], &itemsToCreate[i].ItemToMonitor.NodeId);
itemsToCreate[i].RequestedParameters.ClientHandle = i;
itemsToCreate[i].RequestedParameters.SamplingInterval = 500;
itemsToCreate[i].RequestedParameters.QueueSize = 1;
itemsToCreate[i].RequestedParameters.DiscardOldest = OpcUa_True;
itemsToCreate[i].MonitoringMode = OpcUa_MonitoringMode_Reporting;
}

with the following code

UaStatus result;
OpcUa_UInt32 i;
ServiceSettings serviceSettings;
UaMonitoredItemCreateRequests itemsToCreate;
UaMonitoredItemCreateResults createResults;
// Configure one event monitored item - we use the server object here
itemsToCreate.create(1);
itemsToCreate[0].ItemToMonitor.AttributeId = OpcUa_Attributes_EventNotifier;
itemsToCreate[0].ItemToMonitor.NodeId.Identifier.Numeric = OpcUaId_Server;
itemsToCreate[0].RequestedParameters.ClientHandle = 0;
itemsToCreate[0].RequestedParameters.SamplingInterval = 0;
itemsToCreate[0].RequestedParameters.QueueSize = 0;
itemsToCreate[0].RequestedParameters.DiscardOldest = OpcUa_True;
itemsToCreate[0].MonitoringMode = OpcUa_MonitoringMode_Reporting;

This code creates a data monitored item for the EventNotifier. To actually monitor events, it is necessary to set the EventFilter for this monitored item. This will be explained in the next step.

Step 2: Set an Event Filter for an Event Monitored Item

Event filters can be used to restrict the events to be recieved. This step explains how to set an event filter to receive only events of type ControllerEventType and how to specify the event fields to include in the notification message.

We read the event type to filter from the configuration file. Add the following code to configuration.h

...
public:
...
// get lists of NodeIds and values
UaNodeIdArray getNodesToRead() const;
UaNodeIdArray getNodesToWrite() const;
UaNodeIdArray getNodesToMonitor() const;
UaVariantArray getWriteValues() const;
UaNodeId getEventTypeToFilter() const; // Add this line
...
private:
...
// NamespaceArray and NodeIds
UaStringArray m_namespaceArray;
UaNodeIdArray m_nodesToRead;
UaNodeIdArray m_nodesToWrite;
UaNodeIdArray m_nodesToMonitor;
UaVariantArray m_writeValues;
UaNodeId m_eventTypeToFilter; // Add this line
...

Add the method getEventTypeToFilter to configuration.cpp:

UaNodeId Configuration::getEventTypeToFilter() const
{
return m_eventTypeToFilter;
}

We extend the method Configuration::loadConfiguration to read the event type for filtering from the configuration file.

pSettings->endGroup(); // NodesToMonitor
// New code begins
// Read NodeIds for EventType we use for filtering
value = pSettings->value("EventTypeToFilter", UaString(""));
m_eventTypeToFilter = UaNodeId::fromXmlString(value.toString());
// New code ends
pSettings->endGroup(); // UaClientConfig

Extend Configuration::updateNamespaceIndexes:

...
// NodesToMonitor
for (i = 0; i < m_nodesToMonitor.length(); i++)
{
m_nodesToMonitor[i].NamespaceIndex = mappingTable[m_nodesToMonitor[i].NamespaceIndex];
}
// New code begins
// EventType to filter
m_eventTypeToFilter.setNamespaceIndex(mappingTable[m_eventTypeToFilter.namespaceIndex()]);
// New code ends
// update namespace array
...

The event filter and the event fields have to be added to SampleSubscription::createMonitoredItems

...
// Configure one event monitored item - we use the server object here
itemsToCreate.create(1);
// New code begins
UaEventFilter eventFilter;
UaContentFilter* pContentFilter = NULL;
UaContentFilterElement* pContentFilterElement = NULL;
UaFilterOperand* pOperand = NULL;
// New code ends
itemsToCreate[0].ItemToMonitor.AttributeId = OpcUa_Attributes_EventNotifier;
itemsToCreate[0].ItemToMonitor.NodeId.Identifier.Numeric = OpcUaId_Server;
itemsToCreate[0].RequestedParameters.ClientHandle = 0;
itemsToCreate[0].RequestedParameters.SamplingInterval = 0;
itemsToCreate[0].RequestedParameters.QueueSize = 0;
itemsToCreate[0].RequestedParameters.DiscardOldest = OpcUa_True;
itemsToCreate[0].MonitoringMode = OpcUa_MonitoringMode_Reporting;
// New code begins
// Define which eventfields to send with each event
selectElement.setBrowsePathElement(0, UaQualifiedName("Message", 0), 1);
eventFilter.setSelectClauseElement(0, selectElement, 3);
selectElement.setBrowsePathElement(0, UaQualifiedName("SourceName", 0), 1);
eventFilter.setSelectClauseElement(1, selectElement, 3);
selectElement.setBrowsePathElement(0, UaQualifiedName("Severity", 0), 1);
eventFilter.setSelectClauseElement(2, selectElement, 3);
// We only want to receive events of type ControllerEventType
pContentFilter = new UaContentFilter;
pContentFilterElement = new UaContentFilterElement;
// Operator OfType
pContentFilterElement->setFilterOperator(OpcUa_FilterOperator_OfType);
// Operand 1 (Literal)
pOperand = new UaLiteralOperand;
((UaLiteralOperand*)pOperand)->setLiteralValue(m_pConfiguration->getEventTypeToFilter());
pContentFilterElement->setFilterOperand(0, pOperand, 1);
pContentFilter->setContentFilterElement(0, pContentFilterElement, 1);
eventFilter.setWhereClause(pContentFilter);
// set filter for monitored items
eventFilter.detachFilter(itemsToCreate[0].RequestedParameters.Filter);
// New code ends
printf("\nAdding monitored items to subscription ...\n");
...

Step 3: Receive Event Notifications via UaSubscriptionCallback::newEvents()

To actually receive events, we have to modify SampleSubscription::newEvents. Replace the existing Method with the following code:

void SampleSubscription::newEvents(
OpcUa_UInt32 clientSubscriptionHandle,
UaEventFieldLists& eventFieldList)
{
OpcUa_UInt32 i = 0;
printf("-- Event newEvents -----------------------------------------\n");
printf("clientSubscriptionHandle %d \n", clientSubscriptionHandle);
for ( i=0; i<eventFieldList.length(); i++ )
{
UaVariant message = eventFieldList[i].EventFields[0];
UaVariant sourceName = eventFieldList[i].EventFields[1];
UaVariant severity = eventFieldList[i].EventFields[2];
printf("Event[%d] Message = %s SourceName = %s Severity = %s\n",
i,
message.toString().toUtf8(),
sourceName.toString().toUtf8(),
severity.toString().toUtf8());
}
printf("------------------------------------------------------------\n");
}

See the documentation for UaSubscriptionCallback::newEvents. Event fields are set in Step 2.

Update main:

Replace

if (status.isGood())
{
// register nodes and do a write with the registered nodeIds
pMyClient->registerNodes();
pMyClient->writeRegistered();
// Wait for user command.
printf("\n*************************************************************************\n");
printf("* Press Enter to create a subscription\n");
printf("* Once you see DataChange Notifications - kill and restart the server.\n");
printf("* You will see the client reconnect and recover the subscription.\n");
printf("* Press Enter again to stop the subscription\n");
printf("*************************************************************************\n");
getchar();
// Create subscription
status = pMyClient->subscribe();
if (status.isGood())
{
// Wait for user command.
getchar();
// Delete subscription
status = pMyClient->unsubscribe();
}
// after the connection interruption the registered nodes have been restored in the SampleClient.
// write registered again and unregister the nodes
pMyClient->writeRegistered();
pMyClient->unregisterNodes();

with

if (status.isGood())
{
// Wait for user command.
printf("\n*************************************************************************\n");
printf("* Press Enter to create a subscription\n");
printf("* Press Enter again to stop the subscription\n");
printf("*************************************************************************\n");
getchar();
// Create subscription
status = pMyClient->subscribe();
if (status.isGood())
{
// Wait for user command.
getchar();
// Delete subscription
status = pMyClient->unsubscribe();
}

Step 4: Call Methods to Trigger Events on Demoserver

This step explains how to call a method on an object. To test the event monitored item set up in the previous steps, we will call a method that triggers an event that matches our filter criterium: Start/Stop an AirConditionerController object.

The method and object to call is read from the configuration file. Add the following code to configuration.h

...
public:
...
// get lists of NodeIds and values
UaNodeIdArray getNodesToRead() const;
UaNodeIdArray getNodesToWrite() const;
UaNodeIdArray getNodesToMonitor() const;
UaVariantArray getWriteValues() const;
UaNodeId getEventTypeToFilter() const;
UaNodeIdArray getMethodsToCall() const; // Add this line
UaNodeIdArray getObjectsToCall() const; // Add this line
...
private:
...
// NamespaceArray and NodeIds
UaStringArray m_namespaceArray;
UaNodeIdArray m_nodesToRead;
UaNodeIdArray m_nodesToWrite;
UaNodeIdArray m_nodesToMonitor;
UaVariantArray m_writeValues;
UaNodeId m_eventTypeToFilter;
UaNodeIdArray m_methodsToCall; // Add this line
UaNodeIdArray m_objectToCall; // Add this line

Add the methods Configuration::getMethodsToCall and Configuration::getObjectsToCall to configuration.cpp.

UaNodeIdArray Configuration::getMethodsToCall() const
{
return m_methodsToCall;
}
UaNodeIdArray Configuration::getObjectsToCall() const
{
return m_objectToCall;
}

Extend Configuration::loadConfiguration to read the method to call and the object to call the method on from the configuration.

// Read NodeIds for EventType we use for filtering
value = pSettings->value("EventTypeToFilter", UaString(""));
m_eventTypeToFilter = UaNodeId::fromXmlString(value.toString());
// New code begins
// Read NodeIds for calling methods
m_methodsToCall.clear();
m_objectToCall.clear();
pSettings->beginGroup("MethodsToCall");
value = pSettings->value("size", (OpcUa_UInt32)0);
value.toUInt32(size);
m_methodsToCall.resize(size);
m_objectToCall.resize(size);
for (i=0; i<size;i++)
{
sTempKey = UaString("Method0%1").arg((int)i);
value = pSettings->value(sTempKey.toUtf16(), UaString(""));
UaNodeId::fromXmlString(value.toString()).copyTo(&m_methodsToCall[i]);
sTempKey = UaString("Object0%1").arg((int)i);
value = pSettings->value(sTempKey.toUtf16(), UaString(""));
UaNodeId::fromXmlString(value.toString()).copyTo(&m_objectToCall[i]);
}
pSettings->endGroup(); // MethodsToCall
// New code ends
pSettings->endGroup(); // UaClientConfig

Extend Configuration::updateNamespaceIndexes

// EventType to filter
m_eventTypeToFilter.setNamespaceIndex(mappingTable[m_eventTypeToFilter.namespaceIndex()]);
// New code begins
// Methods and Objects
for (i = 0; i < m_methodsToCall.length(); i++)
{
m_methodsToCall[i].NamespaceIndex = mappingTable[m_methodsToCall[i].NamespaceIndex];
}
for (i = 0; i < m_objectToCall.length(); i++)
{
m_objectToCall[i].NamespaceIndex = mappingTable[m_objectToCall[i].NamespaceIndex];
}
// New code ends
// update namespace array

To actually call a method, we add the method callMethods and the helper method callMethodInternal to the class SampleClient.

Add the following code to sampleclient.h:

public:
...
// OPC UA service calls
...
UaStatus registerNodes();
UaStatus unregisterNodes();
UaStatus callMethods(); // Add this line
private:
// helper methods
UaStatus browseInternal(const UaNodeId& nodeToBrowse, OpcUa_UInt32 maxReferencesToReturn);
UaStatus connectInternal(const UaString& serverUrl, SessionSecurityInfo& sessionSecurityInfo);
UaStatus writeInternal(const UaNodeIdArray& nodesToWrite, const UaVariantArray& valuesToWrite);
UaStatus findSecureEndpoint(SessionSecurityInfo& sessionSecurityInfo);
UaStatus checkServerCertificateTrust(SessionSecurityInfo& sessionSecurityInfo);
UaStatus callMethodInternal(const UaNodeId& objectNodeId, const UaNodeId& methodNodeId); // Add this line
void printBrowseResults(const UaReferenceDescriptions& referenceDescriptions);
void printCertificateData(const UaByteString& serverCertificate);
int userAcceptCertificate();

Implement the method callMethods

UaStatus SampleClient::callMethods()
{
UaStatus result;
// call all methods from the configuration
UaNodeIdArray objectNodeIds = m_pConfiguration->getObjectsToCall();
UaNodeIdArray methodNodeIds = m_pConfiguration->getMethodsToCall();
for (OpcUa_UInt32 i = 0; i < methodNodeIds.length(); i++)
{
UaStatus stat = callMethodInternal(objectNodeIds[i], methodNodeIds[i]);
if (stat.isNotGood())
{
result = stat;
}
}
return result;
}

The method reads the NodeIds of the method to call and the object on which to call the method from the configuration and calls the helper method callMethodInternal.

Add the helper method callMethodInternal to sampleclient.cpp:

UaStatus SampleClient::callMethodInternal(const UaNodeId& objectNodeId, const UaNodeId& methodNodeId)
{
UaStatus result;
ServiceSettings serviceSettings;
CallIn callRequest;
CallOut callResult;
// NodeIds for Object and Method
// we set no call parameters here
callRequest.methodId = methodNodeId;
callRequest.objectId = objectNodeId;
printf("\nCalling method %s on object %s\n", methodNodeId.toString().toUtf8(), objectNodeId.toString().toUtf8());
result = m_pSession->call(
serviceSettings,
callRequest,
callResult);
if (result.isGood())
{
// Call service succeded - check result
if (callResult.callResult.isGood())
{
printf("Call succeeded\n");
}
else
{
printf("Call failed with status %s\n", callResult.callResult.toString().toUtf8());
}
}
else
{
// Service call failed
printf("Call failed with status %s\n", result.toString().toUtf8());
}
return result;
}

The method takes the node ids of the method and the object as input parameters and calls UaSession::call.

Extend main to call the method:

...
// Create subscription
status = pMyClient->subscribe();
if (status.isGood())
{
// New code begins
// call methods;
pMyClient->callMethods();
// New code ends
// Wait for user command.
getchar();
// Delete subscription
status = pMyClient->unsubscribe();
}
...