UA Ansi C Server Professional  1.3.3.242
 All Data Structures 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:

Step 1: Adding TemperatureAlarmType

Figure 6-1 shows the new alarm type that we will create in this lesson.

Figure 6-1 TemperatureAlarmType

In this lesson, the machine will be extended to have an alarm that is active if the temperature sensor's value is below or above certain limits. The alarm type will have the same event fields as the event type of Lesson 5: Adding Support for Events . For this, CustomProvider_RegisterEventTypes is extended to create and register the new alarm type.

/* TemperatureAlarmType */
a_pStartingNodeId->Identifier.Numeric++;
g_TemperatureAlarmTypeId = a_pStartingNodeId->Identifier.Numeric;
parentEventTypeId.NamespaceIndex = 0;
parentEventTypeId.Identifier.Numeric = OpcUaId_LimitAlarmType;
/* Register TemperatureAlarmType as new event type in the SDK */
uStatus = UaServer_Events_RegisterEventType(a_pStartingNodeId, &parentEventTypeId);
OpcUa_GotoErrorIfBad(uStatus);
/* Register the event fields of TemperatureAlarmType in the SDK */
/* SwitchPosition */
OpcUa_QualifiedName_Clear(&eventField.BrowsePath.Elements[0]);
eventField.BrowsePath.Elements[0].NamespaceIndex = g_uCustomProvider_NamespaceIndex;
OpcUa_String_StrnCpy(&eventField.BrowsePath.Elements[0].Name,
OpcUa_String_FromCString("SwitchPosition"),
OPCUA_STRING_LENDONTCARE);
g_TemperatureAlarmType_IndexSwitchPosition = UaServer_Events_RegisterEventField(a_pStartingNodeId,
&eventField);
/* CurrentTemperature */
OpcUa_QualifiedName_Clear(&eventField.BrowsePath.Elements[0]);
eventField.BrowsePath.Elements[0].NamespaceIndex = g_uCustomProvider_NamespaceIndex;
OpcUa_String_StrnCpy(&eventField.BrowsePath.Elements[0].Name,
OpcUa_String_FromCString("CurrentTemperature"),
OPCUA_STRING_LENDONTCARE);
g_TemperatureAlarmType_IndexCurrentTemperature = UaServer_Events_RegisterEventField(a_pStartingNodeId,
&eventField);

The function CustomProvider_CreateMachine is used to create and initialize an instance of the new alarm type. In our case, the lifetime of the alarm is the same as of the machine, so we will append it to the user date of the machine to be able to work with it later. The helper function CustomProvider_InitializeTemperatureAlarm is used to set reasonable values to all the event fields that won't change during the lifetime of the alarm:

OpcUa_StatusCode CustomProvider_CreateMachine(OpcUa_StringA a_sMachineName,
OpcUa_StringA a_sTemperatureSensorName,
OpcUa_BaseNode *a_pOwner,
OpcUa_NodeId *a_pMachineTypeId,
OpcUa_NodeId *a_pTemperatureSensorTypeId,
OpcUa_NodeId *a_pStartingNodeId,
Machine **a_ppNewMachine)
{
...
/* Create and initialize TemperatureAlarm */
nodeId.NamespaceIndex = g_uCustomProvider_NamespaceIndex;
nodeId.Identifier.Numeric = g_TemperatureAlarmTypeId;
pNewMachine->pTemperatureAlarm = UaServer_Events_CreateEvent(&nodeId);
uStatus = CustomProvider_InitializeTemperatureAlarm(pNewMachine,
pNewMachine->pTemperatureAlarm,
a_pStartingNodeId);
OpcUa_GotoErrorIfBad(uStatus);

Step 2: Firing the TemperatureAlarm

In our example, we use the CustomProvider_SimulationTimerCallback function to check if the alarm should be sent. A static variable is used to remember if the alarm is currently active. Depending on this variable and the current temperature of the machine, the alarm is sent either active or inactive.

IFMETHODIMP(CustomProvider_SimulationTimerCallback)(OpcUa_Void *a_pvCallbackData,
OpcUa_Timer a_hTimer,
OpcUa_UInt32 a_msecElapsed)
{
static OpcUa_Boolean bLastSwitchPosition = OpcUa_False;
static OpcUa_Boolean bTemperatureAlarmActive = OpcUa_False;
...
if ( bTemperatureAlarmActive == OpcUa_False &&
(g_bMyCustomMachineTemperature >= 100.0 || g_bMyCustomMachineTemperature <= 0.0) )
{
CustomProvider_FireTemperatureAlarm(g_pMyCustomMachine, g_pMyCustomMachine->pTemperatureAlarm);
bTemperatureAlarmActive = OpcUa_True;
}
else if ( bTemperatureAlarmActive != OpcUa_False &&
(g_bMyCustomMachineTemperature < 100.0 && g_bMyCustomMachineTemperature > 0.0) )
{
CustomProvider_FireTemperatureAlarm(g_pMyCustomMachine, g_pMyCustomMachine->pTemperatureAlarm);
bTemperatureAlarmActive = OpcUa_False;
}

The convenience function CustomProvider_FireTemperatureAlarm is used to fill the dynamic event fields of the alarm and to fire it. Each time the alarm is fired, a new EventId is set and all the timestamps are updated.

OpcUa_StatusCode CustomProvider_FireTemperatureAlarm(Machine *a_pMachine, UaServer_Event *a_pTemperatureAlarm)
{
...
/* Set event fields of BaseEventType */
UaServer_Events_SetEventId(a_pTemperatureAlarm, &bsEventId);
UaServer_Events_SetTime(a_pTemperatureAlarm, OpcUa_DateTime_UtcNow());
UaServer_Events_SetReceiveTime(a_pTemperatureAlarm, OpcUa_DateTime_UtcNow());
UaServer_Events_SetLocalTime(a_pTemperatureAlarm, OpcUa_DateTime_UtcNow());

Also, the EnabledState, Retain and ActiveState event fields are set depending on the state of the machine. This results in the alarm being displayed (Retain == true) only if the temperature is above or below the limits, otherwise the alarm is inactive and not being displayed.

if (*a_pMachine->pTemperatureSensor->pValue >= 100.0 || *a_pMachine->pTemperatureSensor->pValue <= 0.0)
{
if (*a_pMachine->pTemperatureSensor->pValue >= 100.0)
{
UaServer_Events_SetMessage(a_pTemperatureAlarm, "en", "Temperature above the limit!");
}
else if (*a_pMachine->pTemperatureSensor->pValue <= 0.0)
{
UaServer_Events_SetMessage(a_pTemperatureAlarm, "en", "Temperature below the limit!");
}
/* Set event fields of ConditionType */
value.Datatype = OpcUaType_Boolean;
value.Value.Boolean = OpcUa_True;
UaServer_Events_SetEventField(a_pTemperatureAlarm, ConditionTypeField_EnabledState_Id, &value);
value.Datatype = OpcUaType_Boolean;
value.Value.Boolean = OpcUa_True;
UaServer_Events_SetEventField(a_pTemperatureAlarm, ConditionTypeField_Retain, &value);
/* Set event fields of AlarmConditionType */
value.Datatype = OpcUaType_Boolean;
value.Value.Boolean = OpcUa_True;
UaServer_Events_SetEventField(a_pTemperatureAlarm, AlarmConditionTypeField_ActiveState_Id, &value);
}
else
{
/* Set event fields of ConditionType */
value.Datatype = OpcUaType_Boolean;
value.Value.Boolean = OpcUa_False;
UaServer_Events_SetEventField(a_pTemperatureAlarm, ConditionTypeField_EnabledState_Id, &value);
value.Datatype = OpcUaType_Boolean;
value.Value.Boolean = OpcUa_False;
UaServer_Events_SetEventField(a_pTemperatureAlarm, ConditionTypeField_Retain, &value);
/* Set event fields of AlarmConditionType */
value.Datatype = OpcUaType_Boolean;
value.Value.Boolean = OpcUa_False;
UaServer_Events_SetEventField(a_pTemperatureAlarm, AlarmConditionTypeField_ActiveState_Id, &value);
}

Finally the custom event fields SwitchPosition and CurrentTemperature are filled with the current values and the alarm is fired:

/* Set custom event fields */
value.Datatype = OpcUaType_Boolean;
value.Value.Boolean = *a_pMachine->pHeaterSwitch->pValue;
UaServer_Events_SetEventField(a_pTemperatureAlarm, g_TemperatureAlarmType_IndexSwitchPosition, &value);
value.Datatype = OpcUaType_Double;
value.Value.Double = *a_pMachine->pTemperatureSensor->pValue;
UaServer_Events_SetEventField(a_pTemperatureAlarm, g_TemperatureAlarmType_IndexCurrentTemperature, &value);
uStatus = UaServer_Events_FireEvent(a_pTemperatureAlarm);

Step 3: Receiving Alarms with a Client

After implementing firing of alarms, we can test it by using a client to receive them.

Figure 6-2 Create an Event View using UaExpert

For receiving events with UaExpert, create an Event View by clicking 'Add Document', selecting 'Event View' and clicking 'Add'.

Figure 6-3 Receive Alarms using UaExpert

Now you can drag&drop either the 'Server' or the 'MyCustomMachine' object into the Configuration section of the Event View. The client will then subscribe for events that are emitted by the according object.

If the temperature excesses 100 degrees or falls below 0 degrees, the machine's TemperatureAlarm is fired and displayed in the 'Alarms' tab of the Event View. Toggling the switch after that will let the temperature change to a value between 0 and 100 degrees, so that the alarm will get disabled with the Retain event field set to false. This is the signal for the client to not display the alarm anymore and it will disappear from the 'Alarms' tab of the Event View.