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
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()
{
...
"ControllerEventType",
UaNodeId(Ba_ControllerEventType, getNameSpaceIndex()),
m_defaultLocaleId,
OpcUa_True);
addStatus = addNodeAndReference(OpcUaId_BaseEventType, pControllerEventType, OpcUaId_HasSubtype);
UA_ASSERT(addStatus.
isGood());
"Temperature",
UaNodeId(Ba_ControllerEventType_Temperature, getNameSpaceIndex()),
defaultValue,
Ua_AccessLevel_CurrentRead,
m_defaultLocaleId);
addStatus = addNodeAndReference(pControllerEventType, pProperty, OpcUaId_HasProperty);
UA_ASSERT(addStatus.
isGood());
"State",
UaNodeId(Ba_ControllerEventType_State, getNameSpaceIndex()),
defaultValue,
Ua_AccessLevel_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 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",
UaNodeId(Ba_ControllerEventType_State, getNameSpaceIndex()),
defaultValue,
Ua_AccessLevel_CurrentRead,
m_defaultLocaleId);
addStatus = addNodeAndReference(pControllerEventType, pProperty, OpcUaId_HasProperty);
UA_ASSERT(addStatus.
isGood());
addStatus = addUaReference(
UaNodeId(Ba_ControllerType, getNameSpaceIndex()),
UaNodeId(Ba_ControllerEventType, getNameSpaceIndex()),
OpcUaId_GeneratesEvent);
UA_ASSERT(addStatus.isGood());
}
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.
#define Ba_ControllerEventType 4000
#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"
{
UA_DISABLE_COPY(ControllerEventTypeData);
public:
ControllerEventTypeData(OpcUa_Int16 nsIdx);
virtual ~ControllerEventTypeData();
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"
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()
{
s_ControllerEventTypeDataFields.clear();
}
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)
{
std::map<OpcUa_UInt32, OpcUa_UInt32>::iterator it;
it = s_ControllerEventTypeDataFields.find(index);
if ( it == s_ControllerEventTypeDataFields.end() )
{
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"
...
UaStatus NmBuildingAutomation::createTypeNodes()
{
...
ControllerEventTypeData eventTypeData(getNameSpaceIndex());
eventTypeData.registerEventFields();
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
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"
ControllerObject::ControllerObject(
NmBuildingAutomation* pNodeManager,
OpcUa_UInt32 deviceAddress,
BaCommunicationInterface *pCommIf)
m_pSharedMutex(NULL),
m_deviceAddress(deviceAddress),
m_pCommIf(pCommIf),
m_pNodeManager(pNodeManager)
...
OpcUa_UInt32 callbackHandle,
{
UaDiagnosticInfos inputArgumentDiag;
if(pMethodHandleUaNode)
{
if(pMethod)
{
...
else
{
ret = call(pMethod, inputArguments, outputArguments, inputArgumentResults, inputArgumentDiag);
}
{
ControllerEventTypeData eventData(browseName().namespaceIndex());
OpcUa_Double value;
m_pCommIf->getControllerData(m_deviceAddress, 0, value);
eventData.m_Temperature.setDouble(value);
BaCommunicationInterface::ControllerState state;
m_pCommIf->getControllerState(m_deviceAddress, state);
eventData.m_State.setUInt32(state);
eventData.m_SourceNode.setNodeId(nodeId());
eventData.m_SourceName.setString(browseName().toString());
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_Severity.setUInt16(500);
m_pNodeManager->fireEvent(&eventData);
}
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 :
{
...
protected:
OpcUa_UInt32 m_deviceAddress;
BaCommunicationInterface* m_pCommIf;
private:
NmBuildingAutomation* m_pNodeManager;
};
Step 4: Trigger and Receive Events with UaExpert
- 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.
- Drag and drop the Server Object from the Address Space window to the Configuration window of the Event View tab.
- Expand the tree and select Temperature and State beneath ControllerEventType to recieve the corresponding events.
- 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