UA Server SDK C++ Bundle  1.3.2.200
 All Data Structures Namespaces Functions Variables Typedefs Enumerations Enumerator Groups Pages
Lesson 5: Adding support for Events

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

Content:

Adding ControllerEventType

Figure 5-1: ControllerType

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 to NmBuildingAutomation::createTypeNodes():

. . .
/**************************************************************
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_False); // Object type is not 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
OpcUa_AccessLevels_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,
OpcUa_AccessLevels_CurrentRead,
m_defaultLocaleId);
addStatus = addNodeAndReference(pControllerEventType, pProperty, OpcUaId_HasProperty);
UA_ASSERT(addStatus.isGood());
return ret;
}

Our Event type is modelled as ObjectType. The two variables State and Temperature of the ControllerType corresponds with 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 begin +++++++++++++++++++++++++++++++++++++++++++
// 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 end +++++++++++++++++++++++++++++++++++++++++++++
}

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

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

#ifndef BAEVENTDATA_H
#define BAEVENTDATA_H
#include "uaeventdata.h"
class ControllerEventTypeData: public BaseEventTypeData
{
UA_DISABLE_COPY(ControllerEventTypeData);
public:
ControllerEventTypeData(OpcUa_Int16 nsIdx);
virtual ~ControllerEventTypeData();
virtual void getFieldData(OpcUa_UInt32 index, Session* pSession, OpcUa_Variant& data);
UaVariant m_Temperature;
UaVariant m_State;
OpcUa_Int16 m_nsIdx;
private:
static 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

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

The map must be defined.

#include "baeventdata.h"
#include "buildingautomationtypeids.h"
#include "eventmanageruanode.h"
/* ----------------------------------------------------------------------------
Begin Class ControllerEventTypeData
constructors / destructors
-----------------------------------------------------------------------------*/
map<OpcUa_UInt32, OpcUa_UInt32> ControllerEventTypeData::s_ControllerEventTypeDataFields;
ControllerEventTypeData::ControllerEventTypeData(OpcUa_Int16 nsIdx)
{
m_nsIdx = nsIdx;
m_EventTypeId.setNodeId(Ba_ControllerEventType, m_nsIdx);
}
ControllerEventTypeData::~ControllerEventTypeData()
{
}

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

{
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
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:

UaStatus NmBuildingAutomation::createTypeNodes()
{
// . . .
ControllerEventTypeData eventTypeData(getNameSpaceIndex());
eventTypeData.registerEventFields();
return ret;
}

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

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

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

Implement Event trigger

In order to achieve this we create controller Events in 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)
{
. . .
// ++
// Create Controller Events if state change succeeded
if ( ret.isGood() )
{
UaDateTime timestamp = UaDateTime::now();
// 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);
// Create unique event id
UaByteString eventId;
eventData.m_EventId.setByteString(eventId, OpcUa_True);
// Fill all default event fields
eventData.m_SourceNode.setNodeId(nodeId());
eventData.m_SourceName.setString(browseName().toString());
eventData.m_Time.setDateTime(timestamp);
eventData.m_ReceiveTime.setDateTime(timestamp);
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);
// Fire the event
m_pNodeManager->fireEvent(&eventData); }
// ++
pCallback->finishCall(
callbackHandle,
inputArgumentResults,
inputArgumentDiag,
outputArguments,
ret);
ret = OpcUa_Good;
}
else
{
assert(false);
ret = OpcUa_BadInvalidArgument;
}
}
else
{
assert(false);
ret = OpcUa_BadInvalidArgument;
}
return ret;
}

Trigger and receive Events with UA Expert

  1. Choose menu entry Document->Add and then document type Event View. The Event View tab appears.
  2. Drag and drop the ServerObject item from Address Space browser window to Monitored Items window of the tab.
  3. Select the item in the Monitored Items window to browse Event fields.
  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 UA Expert