C++ Based OPC UA Client/Server SDK  1.5.5.355
Lesson 6: Adding Support for Alarms & Conditions

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

Step 1: Adding Alarm Instance Declaration to ControllerType

In a first step, we want to indicate that our ControllerType has an alarm condition component. The following figure shows the object representing the Alarm as component of the ControllerType.

Figure 6-1 Object types including the Alarm node StateCondition

l4gettingstartedlesson06_controller_objecttypeall.png

The following include and code in nmbuildingautomation.cpp are necessary to add the alarm as instance declaration node to the ControllerType.

#include "nmbuildingautomation.h"
#include "buildingautomationtypeids.h"
#include "airconditionercontrollerobject.h"
#include "furnacecontrollerobject.h"
#include "opcua_analogitemtype.h"
#include "bacommunicationinterface.h"
#include "baeventdata.h"
#include "opcua_offnormalalarmtype.h" // Add this line
...
UaStatus NmBuildingAutomation::createTypeNodes()
{
...
/***************************************************************
* Create the Controller Type Instance declaration
*/
// Add Variable "State" as BaseDataVariable
defaultValue.setUInt32(0);
pDataVariable = new OpcUa::BaseDataVariableType(
UaNodeId(Ba_ControllerType_State, getNameSpaceIndex()), // NodeId of the Variable
"State", // Name of the Variable
getNameSpaceIndex(), // Namespace index of the browse name (same like NodeId)
defaultValue, // Initial value
Ua_AccessLevel_CurrentRead, // Access level
this); // Node manager for this variable
// Set Modelling Rule to Mandatory
pDataVariable->setModellingRuleId(OpcUaId_ModellingRule_Mandatory);
addStatus = addNodeAndReference(pControllerType, pDataVariable, OpcUaId_HasComponent);
UA_ASSERT(addStatus.isGood());
//New code begins
// Add state alarm condition "StateCondition" as component to the object type
UaNodeId(Ba_ControllerType_StateCondition, getNameSpaceIndex()), // NodeId of the node
"StateCondition", // Name of the node used for browse name and display name
getNameSpaceIndex(), // Namespace index of the browse name
this, // Node manager responsible for this node
pControllerType->nodeId(), // NodeId of the source node
pControllerType->browseName().name()); // Name of the source node
pOffNormalAlarm->setModellingRuleId(OpcUaId_ModellingRule_Mandatory);
addStatus = addNodeAndReference(pControllerType, pOffNormalAlarm, OpcUaId_HasComponent);
UA_ASSERT(addStatus.isGood());
//New code ends

The code creates an OffNormalAlarm condition with the name StateCondition as component of the ControllerType. The creation of the OffNormalAlarm object creates all components of this condition.

Add the corresponding define to buildingautomationtypeids.h

/************************************************************
Controller Type and its instance declaration
*/
// Controller Type
#define Ba_ControllerType 1000
// Instance declaration
#define Ba_ControllerType_State 1001
#define Ba_ControllerType_Temperature 1002
#define Ba_ControllerType_TemperatureSetPoint 1003
#define Ba_ControllerType_PowerConsumption 1004
#define Ba_ControllerType_Start 1006
#define Ba_ControllerType_Stop 1007
#define Ba_ControllerType_Temperature_EURange 1008
#define Ba_ControllerType_Temperature_EngineeringUnits 1009
#define Ba_ControllerType_TemperatureSetPoint_EURange 1010
#define Ba_ControllerType_TemperatureSetPoint_EngineeringUnits 1011
#define Ba_ControllerType_StateCondition 1012 // Add this line
/************************************************************/
...

Step 2: Adding Alarm to Controller object

In a first step, we are adding a member variable to the class ControllerObject that represents the Alarm condition. We also need an include for the OffNormalAlarmType class. Add the marked lines to controllerobject.h.

#ifndef __CONTROLLEROBJECT_H__
#define __CONTROLLEROBJECT_H__
#include "uaobjecttypes.h"
#include "userdatabase.h"
#include "methodmanager.h"
#include "opcua_offnormalalarmtype.h" // Add this line
class NmBuildingAutomation;
class BaCommunicationInterface;
class ControllerObject :
public UaObjectBase,
{
...
private:
NmBuildingAutomation* m_pNodeManager;
UaMethodGeneric* m_pMethodStart;
UaMethodGeneric* m_pMethodStop;
OpcUa::OffNormalAlarmType* m_pStateOffNormalAlarm; // Add this line
};
...

In a second step, we are creating the OffNormalAlarm object with its components in ControllerObject::ControllerObject, we create the references and we are setting the initial states of the condition.

...
/**************************************************************
* Create the Controller components
*/
// Add Variable "State"
...
// Change value handling to get read and write calls to the node manager
// New code begins
// Add state alarm condition "StateCondition" as component to the object
m_pStateOffNormalAlarm = new OpcUa::OffNormalAlarmType(
UaNodeId(UaString("%1.StateCondition").arg(this->nodeId().toString()), nsIdx), // NodeId
"StateCondition", // Name of the node used for browse name and display name
nsIdx, // Namespace index of the browse name
pNodeManager, // Node manager responsible for this node
this->nodeId(), // NodeId of the source node
this->browseName().name()); // Name of the source node
addStatus = pNodeManager->addNodeAndReference(this, m_pStateOffNormalAlarm, OpcUaId_HasComponent);
UA_ASSERT(addStatus.isGood());
// Create HasEventSource reference from object to state variable
addStatus = pNodeManager->addUaReference(this, pDataVariable, OpcUaId_HasEventSource);
UA_ASSERT(addStatus.isGood());
// Create HasCondition reference from state variable to condition
addStatus = pNodeManager->addUaReference(pDataVariable, m_pStateOffNormalAlarm, OpcUaId_HasCondition);
UA_ASSERT(addStatus.isGood());
m_pStateOffNormalAlarm->setConditionName("StateCondition");
m_pStateOffNormalAlarm->setAckedState(OpcUa_True);
m_pStateOffNormalAlarm->setEnabledState(OpcUa_True);
// New code ends

In addition to normal objects, the constructor of a condition object takes the NodeId of the source and the source name as additional parameters. The source node is the controller object. After creating the condition object, additional setters are used to set the condition name and initial states of the condition like the AckedState and the EnabledState.

In addition to the HasComponent reference between the controller and the condition object, two more references are created to indicate the event hierarchy. Starting from the controller object a HasEventSource reference to the State variable is created to indicate that changes of the state are triggering events. A HasCondition reference from the State variable to the StateCondition object indicates that the variable has an Alarm condition. The full event hierarchy is explained in the next step.

Change the marked line in controllerobject.cpp

OpcUa_Byte ControllerObject::eventNotifier() const
{
return Ua_EventNotifier_SubscribeToEvents; // Replace existing code with this line
}

Since we are now using the Controller object as event notifier, the ControllerObject::eventNotifier() method must return SubscribeToEvents.

Step 3: Creating Full Event Hierarchy

The object instance and component hierarchy starts at the Objects folder and uses Organizes references in the first levels and HasComponent and HasProperty references in the component hierarchy levels beneath objects. This hierarchy is typically used for browsing by data access or historical data access clients or by clients calling methods.

Clients interested in the event hierarchy are browsing a hierarchy starting from the Server object by following HasNotifier and HasEventSource references. HasCondition references are used to indicate that an Event source has one or more conditions. The node that is the target of a HasNotifier reference can be used to subscribe for events. It must be an Object with the EventNotifier attribute set to SubscribeToEvents. The target of a HasEventSource reference can be a Variable, Object or Method indicating that the node is the source of the event notification. Figure 6-2 shows an example of such an event hierarchy together with the component hierarchy.

Figure 6-2 Event hierarchy

l4gettingstartedlesson06_controller_objects.png

The two areas AreaAirConditioner and AreaFurnace are used to group the different objects for event filtering. They reference the corresponding object instances with a HasNotifier reference. The necessary code to create these references and to register the event hierarchy for event filtering is contained in the next code section.

The HasEventSource reference from the controller object to the State variable indicates that this variable is the source of the events from this object. The HasCondition reference from the variable to the Alarm condition object indicates that the condition is based on the State variable. The necessary references were already created inside the ControllerObject class in Step 2: Adding Alarm to Controller object.

Add the following code to nmbuildingautomation.cpp:

UaStatus NmBuildingAutomation::afterStartUp()
{
...
/**************************************************************
Create a folder for the controller objects and add the folder to the ObjectsFolder
*/
pFolder = new UaFolder("BuildingAutomation", UaNodeId("BuildingAutomation", getNameSpaceIndex()), m_defaultLocaleId);
ret = addNodeAndReference(OpcUaId_ObjectsFolder, pFolder, OpcUaId_Organizes);
// New code begins
/**************************************************************
Create alarm area folders for the controller objects and add them to the Server object
*/
UaAreaFolder* pAreaAirConditioner = new UaAreaFolder(
"AreaAirConditioner", UaNodeId("AreaAirConditioner", getNameSpaceIndex()), m_defaultLocaleId);
addNodeAndReference(OpcUaId_Server, pAreaAirConditioner, OpcUaId_HasNotifier);
// Register event notifier tree
registerEventNotifier(OpcUaId_Server, pAreaAirConditioner->nodeId());
UaAreaFolder* pAreaFurnace = new UaAreaFolder(
"AreaFurnace", UaNodeId("AreaFurnace", getNameSpaceIndex()), m_defaultLocaleId);
addNodeAndReference(OpcUaId_Server, pAreaFurnace, OpcUaId_HasNotifier);
// Register event notifier tree
registerEventNotifier(OpcUaId_Server, pAreaFurnace->nodeId());
// New code ends
/**************************************************************
* Create the Controller Object Instances
*/

This code creates the two area folders, adds them with HasNotifier to the Server object and registers each area for the event filter hierarchy with the EventManager. For this registration, the NodeId of the Server object and the NodeId of the area are passed to the method EventManagerUaNode::registerEventNotifier().

UaStatus NmBuildingAutomation::afterStartUp()
{
...
/**************************************************************
* Create the Controller Object Instances
*/
for ( i=0; i<count; i++ )
{
ret = m_pCommIf->getControllerConfig(
i,
controllerType,
sControllerName,
controllerAddress);
if ( controllerType == BaCommunicationInterface::AIR_CONDITIONER )
{
pAirConditioner = new AirConditionerControllerObject(
sControllerName,
UaNodeId(sControllerName, getNameSpaceIndex()),
m_defaultLocaleId,
this,
controllerAddress,
m_pCommIf);
ret = addNodeAndReference(pFolder, pAirConditioner, OpcUaId_Organizes);
UA_ASSERT(ret.isGood());
// New code begins
// Add HasNotifier reference from alarm area to controller object
ret = addUaReference(pAreaAirConditioner, pAirConditioner, OpcUaId_HasNotifier);
UA_ASSERT(ret.isGood());
// Register event notifier tree
registerEventNotifier(pAreaAirConditioner->nodeId(), pAirConditioner->nodeId());
// New code ends
}
else
{
pFurnace = new FurnaceControllerObject(
sControllerName,
UaNodeId(sControllerName, getNameSpaceIndex()),
m_defaultLocaleId,
this,
controllerAddress,
m_pCommIf);
ret = addNodeAndReference(pFolder, pFurnace, OpcUaId_Organizes);
UA_ASSERT(ret.isGood());
// New code begins
// Add HasNotifier reference from alarm area to controller object
ret = addUaReference(pAreaFurnace, pFurnace, OpcUaId_HasNotifier);
UA_ASSERT(ret.isGood());
// Register event notifier tree
registerEventNotifier(pAreaFurnace->nodeId(), pFurnace->nodeId());
// New code ends
}
}

This code adds the HasNotifier reference from the area to the controller object and registers the controller object in the event hierarchy after creating the object.

Figure 6-3 Condition type, Controller type and Condition instance

l4gettingstartedlesson06_conditions_with_uaexpert.png

Figure 6-3 shows

  • on the left side the event type hierarchy with the OffNormalAlarmType
  • in the middle the controller type with the Condition instance declaration
  • on the right side the controller object instance with the condition instance

Step 4: Trigger State Change Events

After creating all necessary nodes and references, we need to add the code to change the state of the condition and to trigger the state change events for the condition.

Add the marked code to controllerobject.cpp:

...
UaStatus ControllerObject::beginCall(
...
// Create Controller Events if state change succeeded
if ( ret.isGood() )
{
UaDateTime timestamp = UaDateTime::now();
// Create event
ControllerEventTypeData eventData(browseName().namespaceIndex());
...
// Fire the event
m_pNodeManager->fireEvent(&eventData);
// New code begins
// Change state of alarm condition
if ( state == BaCommunicationInterface::Ba_ControllerState_Off )
{
m_pStateOffNormalAlarm->setAckedState(OpcUa_False);
m_pStateOffNormalAlarm->setActiveState(OpcUa_True);
m_pStateOffNormalAlarm->setRetain(OpcUa_True);
}
else
{
m_pStateOffNormalAlarm->setAckedState(OpcUa_True);
m_pStateOffNormalAlarm->setActiveState(OpcUa_False);
m_pStateOffNormalAlarm->setRetain(OpcUa_False);
}
m_pStateOffNormalAlarm->setMessage(UaLocalizedText("en", messageText));
m_pStateOffNormalAlarm->triggerEvent(UaDateTime::now(), UaDateTime::now(), UaByteString());
// New code ends
}
...

The code is added in the same place where the simple event is triggered. This trigger is based on calling the start or stop methods of the controller. Depending on the controller state, the Alarm Active state, the AckedState and the Retain flag are set. After setting the message text the state change event is triggered by calling triggerEvent on the condition object.

Step 5: Alarm Acknowledgment

To allow alarm acknowledgment we need to overwrite the method EventManagerUaNode::OnAcknowledge() in the class NmBuildingAutomation.

class NmBuildingAutomation : public NodeManagerBase
{
UA_DISABLE_COPY(NmBuildingAutomation);
public:
...
// New code begins
// EventManagerUaNode implementation
virtual UaStatus OnAcknowledge(const ServiceContext& serviceContext, OpcUa::AcknowledgeableConditionType* pCondition, const UaByteString& EventId, const UaLocalizedText& Comment);
// New code ends
UaVariable* getInstanceDeclarationVariable(OpcUa_UInt32 numericIdentifier);
...

Add the following code to nmbuildingautomation.cpp:

UaStatus NmBuildingAutomation::OnAcknowledge(
const ServiceContext& serviceContext,
const UaByteString& EventId,
const UaLocalizedText& Comment)
{
OpcUa_ReferenceParameter(EventId);
// Check if the condition is unacknowledged
if ( pCondition->getAckedStateBool() != OpcUa_False )
{
return OpcUa_BadInvalidState;
}
// Chance condition to acknowledged
pCondition->setAckedState(OpcUa_True);
pCondition->setComment(Comment);
pCondition->setClientUserId(serviceContext.pSession()->getClientUserId());
pCondition->setMessage(UaLocalizedText("en", "Condition state acknowledged by UA client"));
OpcUa::AlarmConditionType* pAlarmCondition = (OpcUa::AlarmConditionType*)pCondition;
if ( pAlarmCondition->getActiveStateBool() == OpcUa_False )
{
pCondition->setRetain(OpcUa_False);
}
// Trigger state change event
return OpcUa_Good;
}

This code implements the method OnAcknowledge in the class NmBuildingAutomation. It checks if the condition is unacknowleged, changes the state of the AckedState to OpcUa_True, sets the passed comment to the Comment field, sets the client user id to the ClientUserId field and triggers a new state change event.

To test the alarm acknowledgement, connect to the server with UaExpert and select the Event View as described in the previous lesson. Then call the Stop Method on several Controller Objects as described in Lesson 4. The corresponding alarms will show up in the Alarms tab (see figure 6-4).

To acknowledge an alarm, select Acknowledge from the context menu. A dialog window for entering a comment will show up.

Figure 6-4 Alarm Condition acknowledgement

l4gettingstartedlesson06_alarm_ack_with_uaexpert.png
l4gettingstartedlesson06_alarm_ack_with_uaexpert2.png