UA Server SDK C++ Bundle  1.3.2.200
 All Data Structures Namespaces Functions Variables Typedefs Enumerations Enumerator Groups Pages
Lesson 6: Adding support for Alarms & Conditions

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

Content:

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

The following include and code is necessary to add the alarm as instance declaration node to the ControllerType. The code is added to the method NmBuildingAutomation::createTypeNodes().

#include "opcua_offnormalalarmtype.h"
/***************************************************************
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
addStatus = addNodeAndReference(pControllerType, pDataVariable, OpcUaId_HasComponent);
UA_ASSERT(addStatus.isGood());
//++++++ new code begin +++++++++++++++++++++++++++++++++++++++++++
// 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
addStatus = addNodeAndReference(pControllerType, pOffNormalAlarm, OpcUaId_HasComponent);
UA_ASSERT(addStatus.isGood());
//++++++ new code end +++++++++++++++++++++++++++++++++++++++++++++

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 the condition.

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 need also an include for the OffNormalAlarmType class.

// controllerobject.h
#include "opcua_offnormalalarmtype.h"
// . . .
private:
// ....
OpcUa::OffNormalAlarmType* m_pStateOffNormalAlarm;
};

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

/**************************************************************
Create the Controller components
**************************************************************/
// Add Variable "State"
// . . .
// 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
m_pStateOffNormalAlarm->setConditionName("StateCondition");
m_pStateOffNormalAlarm->setAckedState(OpcUa_True);
m_pStateOffNormalAlarm->setEnabledState(OpcUa_True);
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());

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.

{
return Ua_EventNotifier_SubscribeToEvents;
}

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

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 below 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 EvetnNotifier 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

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.
From the controller object the HasEventSource reference 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 where created already inside the ControllerObject class in Adding Alarm to Controller object

// 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 begin +++++++++++++++++++++++++++++++++++++++++++
/**************************************************************
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 end +++++++++++++++++++++++++++++++++++++++++++++
/**************************************************************
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 is passed to the method EventManagerUaNode::registerEventNotifier().

// 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 begin +++++++++++++++++++++++++++++++++++++++++++
// 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 end +++++++++++++++++++++++++++++++++++++++++++++
}
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 begin +++++++++++++++++++++++++++++++++++++++++++
// 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 end +++++++++++++++++++++++++++++++++++++++++++++
}
}

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

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

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.

// ControllerObject::beginCall()
// 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);
//eventData.m_EventId.setByteString(UaByteString());
eventData.m_SourceNode.setNodeId(nodeId());
eventData.m_SourceName.setString(nodeId().toString());
eventData.m_Time.setDateTime(timestamp);
eventData.m_ReceiveTime.setDateTime(timestamp);
char messageText[200];
if ( state == BaCommunicationInterface::Ba_ControllerState_Off )
{
snprintf( messageText, 200, "State of %s changed to OFF", nodeId().toString().toUtf8());
}
else
{
snprintf( messageText, 200, "State of %s changed to ON", nodeId().toString().toUtf8());
}
eventData.m_Message.setLocalizedText(UaLocalizedText("en", messageText));
eventData.m_Severity.setUInt16(500);
m_pNodeManager->fireEvent(&eventData);
//++++++ new code begin +++++++++++++++++++++++++++++++++++++++++++
// 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 end +++++++++++++++++++++++++++++++++++++++++++++
}

The code is added to the same place where the simple event is triggered. This trigger is based on calling the start or stop methods of the controller. Depending of 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.

Alarm acknowledgment

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

class NmBuildingAutomation : public NodeManagerBase
{
// . . .
// EventManagerUaNode implementation
virtual UaStatus OnAcknowledge(const ServiceContext& serviceContext, OpcUa::AcknowledgeableConditionType* pCondition, const UaByteString& EventId, const UaLocalizedText& Comment);

This code adds the method OnAcknowledge to the class NmBuildingAutomation.

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.

The following figure shows the alarm view of the UaExpert and the alarm acknowledgement.

Figure 6-4 Alarm Condition acknowledgement