C++ UA Server SDK  1.5.0.318
 All Classes Namespaces Functions Variables Typedefs Enumerations Enumerator Friends Modules Pages
Lesson 5: Adding Support for Events

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

In this example, Events are provided by the Server Object. Lesson 6 describes how Events can be provided by other Event Notifier Objects.

Step 1: Adding ControllerEventType

Figure 5-1 ControllerType

l4gettingstartedlesson05_controller_objecttypeall.png

Create the new ObjectType

Figure 5-1 indicates that we are going to create an additional ObjectType: the ControllerEventType. For this purpose, we add the following code at the end of NmBuildingAutomation::createTypeNodes():

UaStatus NmBuildingAutomation::createTypeNodes()
{
UaStatus ret;
UaStatus addStatus;
UaVariant defaultValue;
UaObjectTypeSimple* pControllerType = NULL;
UaObjectTypeSimple* pAirConditionerControllerType = NULL;
UaObjectTypeSimple* pFurnaceControllerType = NULL;
OpcUa::DataItemType* pDataItem;
OpcUa::AnalogItemType* pAnalogItem;
// Method helpers
OpcUa::BaseMethod* pMethod = NULL;
UaPropertyMethodArgument* pPropertyArg = NULL;
UaUInt32Array nullarray;
// New code begins
// Event helpers
UaObjectTypeSimple* pControllerEventType = NULL;
UaPropertyCache* pProperty = NULL;
// New code ends
...
// New code begins
/**************************************************************
* Create the ControllerEventType and its event field properties
*/
pControllerEventType = new UaObjectTypeSimple(
"ControllerEventType", // Used as string in browse name and display name
UaNodeId(Ba_ControllerEventType, getNameSpaceIndex()), // Numeric NodeId for types
m_defaultLocaleId, // Default LocaleId
OpcUa_True); // Object (Event) type is abstract
// Add Event Type node to address space as subtype of BaseEventType
addStatus = addNodeAndReference(OpcUaId_BaseEventType, pControllerEventType, OpcUaId_HasSubtype);
UA_ASSERT(addStatus.isGood());
// Temperature Event field property
defaultValue.setDouble(0);
pProperty = new UaPropertyCache(
"Temperature", // Used as string in browse name and display name
UaNodeId(Ba_ControllerEventType_Temperature, getNameSpaceIndex()), // Numeric NodeId for types
defaultValue, // Default value of the Property
Ua_AccessLevel_CurrentRead, // Access level of the Property
m_defaultLocaleId); // Default LocaleId
addStatus = addNodeAndReference(pControllerEventType, pProperty, OpcUaId_HasProperty);
UA_ASSERT(addStatus.isGood());
// State Event field property
defaultValue.setDouble(0);
pProperty = new UaPropertyCache(
"State",
UaNodeId(Ba_ControllerEventType_State, getNameSpaceIndex()),
defaultValue,
Ua_AccessLevel_CurrentRead,
m_defaultLocaleId);
addStatus = addNodeAndReference(pControllerEventType, pProperty, OpcUaId_HasProperty);
UA_ASSERT(addStatus.isGood());
// New code ends
return ret;
}

Our Event type is modelled as ObjectType. The two variables State and Temperature of the ControllerType correspond to the Event fields of the ControllerEventType. The Event fields are realized as Properties. Due to this, a HasProperty Reference is passed to addNodeAndReference().

Indicate that Controllers Fire Events

In order to indicate that the ControllerType fires ControllerEventType events, we create an additional GeneratesEvent reference after creating the event type:

...
// State Event field property
defaultValue.setDouble(0);
pProperty = new UaPropertyCache(
"State",
UaNodeId(Ba_ControllerEventType_State, getNameSpaceIndex()),
defaultValue,
Ua_AccessLevel_CurrentRead,
m_defaultLocaleId);
addStatus = addNodeAndReference(pControllerEventType, pProperty, OpcUaId_HasProperty);
UA_ASSERT(addStatus.isGood());
// New code begins
// Create Reference "GeneratesEvent" between Controller Type and ControllerEventType
addStatus = addUaReference(
UaNodeId(Ba_ControllerType, getNameSpaceIndex()),
UaNodeId(Ba_ControllerEventType, getNameSpaceIndex()),
OpcUaId_GeneratesEvent);
UA_ASSERT(addStatus.isGood());
// New code ends
}

The link between Controller and ControllerEventType is implemented as GeneratesEvent Reference. addUaReference() takes two type definition NodeIds representing the ObjectTypes, and the GeneratesEvent define.

Then we add the defines for the ControllerEventType and its event field properties to buildingautomationtypeids.h.

/************************************************************
ControllerEventType and its event field properties
*/
// ControllerEventType
#define Ba_ControllerEventType 4000
// Event field properties
#define Ba_ControllerEventType_Temperature 4001
#define Ba_ControllerEventType_State 4002
/************************************************************/

Step 2: Implementing ControllerEventType

Every Event type needs its own Event data class derived from BaseEventTypeData. This class contains the Event field data for an Event and serves as a base for the client-side implementation of the select clause.

Define Class BaEventData

Add a new header file named “baeventdata.h” containing the following code to your project:

#ifndef BAEVENTDATA_H
#define BAEVENTDATA_H
#include "uaeventdata.h"
class ControllerEventTypeData: public BaseEventTypeData
{
UA_DISABLE_COPY(ControllerEventTypeData);
public:
ControllerEventTypeData(OpcUa_Int16 nsIdx);
virtual ~ControllerEventTypeData();
/* Registers all event type fields with the EventManagerUaNode. */
/* Get the field value for the passed index. */
virtual void getFieldData(OpcUa_UInt32 index, Session* pSession, OpcUa_Variant& data);
/* Field 1 - Temperature */
UaVariant m_Temperature;
/* Field 2 - State */
UaVariant m_State;
OpcUa_Int16 m_nsIdx;
private:
static std::map<OpcUa_UInt32, OpcUa_UInt32> s_ControllerEventTypeDataFields;
};
#endif // BAEVENTDATA_H

Our class definition also declares a map as static member in order to manage registered Event fields.

Implement Class BaEventData

Add a new source file named “baeventdata.cpp” to your projects and add the following code snippets.

#include "baeventdata.h"
#include "buildingautomationtypeids.h"
#include "eventmanageruanode.h"
/* ----------------------------------------------------------------------------
Begin Class ControllerEventTypeData
constructors / destructors
-----------------------------------------------------------------------------*/
std::map<OpcUa_UInt32, OpcUa_UInt32> ControllerEventTypeData::s_ControllerEventTypeDataFields;

The map must be defined.

ControllerEventTypeData::ControllerEventTypeData(OpcUa_Int16 nsIdx)
{
m_nsIdx = nsIdx;
m_EventTypeId.setNodeId(Ba_ControllerEventType, m_nsIdx);
}
ControllerEventTypeData::~ControllerEventTypeData()
{
}

The constructor takes the corresponding NameSpace index and defines the Event type NodeId member. We leave the destructor empty.

void ControllerEventTypeData::registerEventFields()
{
// Register event type
EventManagerUaNode::registerEventType(OpcUaId_BaseEventType, m_EventTypeId);
// Register event fields
s_ControllerEventTypeDataFields.clear();
s_ControllerEventTypeDataFields[EventManagerUaNode::registerEventField(UaQualifiedName("Temperature", m_nsIdx).toFullString())] = 1;
s_ControllerEventTypeDataFields[EventManagerUaNode::registerEventField(UaQualifiedName("State", m_nsIdx).toFullString())] = 2;
}

This method registers all event type fields with the EventManagerUaNode. The handling of EventManager will be explained later.

void ControllerEventTypeData::getFieldData(OpcUa_UInt32 index, Session* pSession, OpcUa_Variant& data)
{
// Try to find the field index
std::map<OpcUa_UInt32, OpcUa_UInt32>::iterator it;
it = s_ControllerEventTypeDataFields.find(index);
if ( it == s_ControllerEventTypeDataFields.end() )
{
BaseEventTypeData::getFieldData(index, pSession, data);
return;
}
switch(it->second)
{
case 1:
{
m_Temperature.copyTo(&data);
break;
}
case 2:
{
m_State.copyTo(&data);
break;
}
default:
{
OpcUa_Variant_Initialize(&data);
}
}
}

getFieldData() takes the index of the selected field and returns the data for this field.

Finally, we have to register the event data class with the BaseEventType in order to allow a client to select the event fields. Add the following code to nmbuildingautomation.cpp:

#include "nmbuildingautomation.h"
#include "buildingautomationtypeids.h"
#include "airconditionercontrollerobject.h"
#include "furnacecontrollerobject.h"
#include "opcua_analogitemtype.h"
#include "bacommunicationinterface.h"
#include "baeventdata.h" // Add this line
...
UaStatus NmBuildingAutomation::createTypeNodes()
{
...
// New code begins
// Register the event data class with the BaseEventType to allow selection of custom event fields
ControllerEventTypeData eventTypeData(getNameSpaceIndex());
eventTypeData.registerEventFields();
// New code ends
return ret;
}

Step 3: Implementing Handling of EventManager

NmBuildingAutomation implements all necessary functionality for the event handling by deriving from NodeManagerBase. This class implements the EventManager interface by deriving from EventManagerUaNode and it provides the necessary mapping code between a NodeManager and an EventManager:

Figure 5-2 The EventManager implementation

l4gettingstartedlesson05_nodemanagerclassdiagramm.png

The only code that needs to be changed for the event handling support in NmBuildingAutomation is to pass in OpcUa_True for the firesEvents flag of the cunstructor of the NodeManagerBase base class:

NmBuildingAutomation::NmBuildingAutomation()
: NodeManagerBase("urn:UnifiedAutomation:CppDemoServer:BuildingAutomation" /*+++*/, OpcUa_True /*+++*/)
{
m_pCommIf = new BaCommunicationInterface;
}

Implement Event Trigger

In order to achieve this, we create controller Events in ControllerObject::beginCall(). Add the marked lines to controllerobject.cpp

#include "controllerobject.h"
#include "nmbuildingautomation.h"
#include "buildingautomationtypeids.h"
#include "opcua_analogitemtype.h"
#include "bacommunicationinterface.h"
#include "methodhandleuanode.h"
#include "baeventdata.h" // Add this line
ControllerObject::ControllerObject(
const UaString& name,
const UaNodeId& newNodeId,
const UaString& defaultLocaleId,
NmBuildingAutomation* pNodeManager,
OpcUa_UInt32 deviceAddress,
BaCommunicationInterface *pCommIf)
: UaObjectBase(name, newNodeId, defaultLocaleId),
m_pSharedMutex(NULL),
m_deviceAddress(deviceAddress),
m_pCommIf(pCommIf), // Line has changed
m_pNodeManager(pNodeManager) // Add this line
...
UaStatus ControllerObject::beginCall(
const ServiceContext& serviceContext,
OpcUa_UInt32 callbackHandle,
MethodHandle* pMethodHandle,
const UaVariantArray& inputArguments)
{
UaStatus ret;
UaVariantArray outputArguments;
UaStatusCodeArray inputArgumentResults;
UaDiagnosticInfos inputArgumentDiag;
MethodHandleUaNode* pMethodHandleUaNode = static_cast<MethodHandleUaNode*>(pMethodHandle);
UaMethod* pMethod = NULL;
if(pMethodHandleUaNode)
{
pMethod = pMethodHandleUaNode->pUaMethod();
if(pMethod)
{
...
else
{
ret = call(pMethod, inputArguments, outputArguments, inputArgumentResults, inputArgumentDiag);
}
// New code begins
// Create Controller Events if state change succeeded
if ( ret.isGood() )
{
// Create event
ControllerEventTypeData eventData(browseName().namespaceIndex());
// Handle ControllerEventType specific fields
// Temperature
OpcUa_Double value;
m_pCommIf->getControllerData(m_deviceAddress, 0, value);
eventData.m_Temperature.setDouble(value);
// State
BaCommunicationInterface::ControllerState state;
m_pCommIf->getControllerState(m_deviceAddress, state);
eventData.m_State.setUInt32(state);
// Fill all default event fields
eventData.m_SourceNode.setNodeId(nodeId());
eventData.m_SourceName.setString(browseName().toString());
UaString messageText;
if ( state == BaCommunicationInterface::Ba_ControllerState_Off )
{
messageText = UaString("State of %1 changed to OFF").arg(browseName().toString());
}
else
{
messageText = UaString("State of %1 changed to ON").arg(browseName().toString());
}
eventData.m_Message.setLocalizedText(UaLocalizedText("en", messageText));
eventData.m_Severity.setUInt16(500);
// Set timestamps and unique EventId
eventData.prepareNewEvent(UaDateTime::now(), UaDateTime::now(), UaByteString());
// Fire the event
m_pNodeManager->fireEvent(&eventData);
}
// New code ends
pCallback->finishCall(
callbackHandle,
inputArgumentResults,
inputArgumentDiag,
outputArguments,
ret);
ret = OpcUa_Good;
}
else
{
assert(false);
ret = OpcUa_BadInvalidArgument;
}
}
else
{
assert(false);
ret = OpcUa_BadInvalidArgument;
}
return ret;
}

And the marked line to controllerobject.h

class ControllerObject :
public UaObjectBase,
{
...
protected:
UaMutexRefCounted* m_pSharedMutex;
OpcUa_UInt32 m_deviceAddress;
BaCommunicationInterface* m_pCommIf;
private:
NmBuildingAutomation* m_pNodeManager; // Add this line
UaMethodGeneric* m_pMethodStart;
UaMethodGeneric* m_pMethodStop;
};

Step 4: Trigger and Receive Events with UaExpert

  1. Choose Document → Add from the menu bar, select Event View from the drop down menu Document Type and confirm with Add to add the Event View tab to the main window.
  2. Drag and drop the Server Object from the Address Space window to the Configuration window of the Event View tab.
  3. Expand the tree and select Temperature and State beneath ControllerEventType to recieve the corresponding events.
  4. Trigger a State change Event by calling at first Start and then Stop as explained in Lesson 4: Adding Support for Methods.

The result is shown in Figure 5-3.

Figure 5-3 Events triggered and monitored with UaExpert

l4gettingstartedlesson04_monitor_events_with_uaexpert.png