# Alarms and conditions

> How-to guide: Provide event nodes with conditions: dialog and acknowledgeable conditions, alarm conditions up to limit alarms.

> This content was machine translated and may contain errors. [Read in German](https://www.traeger.de/docs/development/opcua/server/alarms-conditions)

**Sample applications:**

- Server.AE ([C#](https://github.com/Traeger-GmbH/opcuanet-samples/tree/master/cs/Basic/Server.AE), [VB.NET](https://github.com/Traeger-GmbH/opcuanet-samples/tree/master/vb/Basic/Server.AE)): This sample demonstrates how to setup an OPC UA server to provide AE to its nodes provided.
- Client.AE ([C#](https://github.com/Traeger-GmbH/opcuanet-samples/tree/master/cs/Basic/Client.AE), [VB.NET](https://github.com/Traeger-GmbH/opcuanet-samples/tree/master/vb/Basic/Client.AE)): This sample demonstrates how to perform handling of Alarm + Events (AE) on the nodes provided by the OPC UA server.

Event nodes with conditions report events that are tied to the state of a condition. This page shows how a server provides event nodes for each kind of condition, from simple conditions to limit alarms.

## Conditions

Types used: `OpcServer`, `OpcNodeManager`, `OpcConditionNode`, `OpcConditionRetainPolicy`

The `OpcConditionNode` class is a specialization of `OpcEventNode`, which [Provide events on a node](https://www.traeger.de/en/docs/development/opcua/server/events#provide-events-on-a-node) introduces. With `OpcConditionNode`, you define events that are tied to specific conditions. The server should trigger such an event only if the condition of the event node applies. The information of the node also includes the state of this condition and information tied to the evaluation of the condition.

Depending on the use case, this information can vary in complexity. For this reason, `OpcConditionNode` is the base class of all event nodes tied to a condition. You create such a node like an `OpcEventNode`. The following sections therefore show only the additional properties.

The `IsRetained` property states whether a condition is of interest to a client and is reported on a ConditionRefresh. By default, the OPC UA .NET SDK updates it automatically from the states of the condition. The `IsRetainedPolicy` property defines the rule it follows (enumeration `OpcConditionRetainPolicy`):

- `EnabledOnly` (default of `OpcConditionNode`): set as long as the condition is enabled.
- `ActiveOrUnacknowledged` (default of `OpcAcknowledgeableConditionNode` and `OpcAlarmConditionNode`): set as long as the alarm is active or not yet acknowledged.
- `ActiveOrUnconfirmed`: set as long as the alarm is active or not yet confirmed.
- `ActiveOnly`: set as long as the alarm is active, regardless of acknowledgment.
- `Manual`: the SDK does not change the property; the application sets it itself.

Regardless of the rule, `IsRetained` is never set for a disabled condition. A value of the `IsRetained` property that you set manually is therefore kept only with the `Manual` rule:

```csharp
var maintenanceEvent = new OpcConditionNode(machineOne, "Maintenance");
 
// Default: EnabledOnly for an OpcConditionNode,
// ActiveOrUnacknowledged for acknowledgeable conditions and alarms.
maintenanceEvent.IsRetainedPolicy = OpcConditionRetainPolicy.Manual;
 
// Interesting for a client yes or no (only kept in case of the Manual policy).
maintenanceEvent.IsRetained = true;
 
// Status of the source the condition is based upon.
maintenanceEvent.Quality = OpcStatusCode.Good;
```

```vbnet
Dim maintenanceEvent = New OpcConditionNode(machineOne, "Maintenance")

'Default: EnabledOnly for an OpcConditionNode,
'ActiveOrUnacknowledged for acknowledgeable conditions and alarms.
maintenanceEvent.IsRetainedPolicy = OpcConditionRetainPolicy.Manual

'Interesting for a client yes or no (only kept in case of the Manual policy).
maintenanceEvent.IsRetained = True

'Status of the source the condition is based upon.
maintenanceEvent.Quality = OpcStatusCode.Good
```

With the `AddComment` method and the child node of the same name, a client can change the `Comment` property of the node. The server responds to the call through the `AddCommentCallback` property. You evaluate the result of the change with the following properties:

```csharp
// Identifier of the user who supplied the Comment
maintenanceEvent.ClientUserId = ...;

// Last comment provided by a user
maintenanceEvent.Comment = ...;

```

```vbnet
' Identifier of the user who supplied the Comment
maintenanceEvent.ClientUserId = ...

' Last comment provided by a user
maintenanceEvent.Comment = ...

```

On the server side, you change the states of a condition with the methods whose names start with `Change`, for example `ChangeIsEnabled` or `ChangeComment`. These methods are available only once the node exists in the address space. Before that, they throw an `InvalidOperationException`. If the `AutoReportChanges` property is set (it is not by default), the node automatically reports an event after each of these changes:

```csharp
// Use the Change methods once the node has been added to the address space.
maintenanceEvent.ChangeIsEnabled(this.SystemContext, value: false);
maintenanceEvent.ChangeComment(this.SystemContext, value: "Planned maintenance");
 
// Reports an event whenever one of the Change methods completes.
maintenanceEvent.AutoReportChanges = true;
```

```vbnet
'Use the Change methods once the node has been added to the address space.
maintenanceEvent.ChangeIsEnabled(Me.SystemContext, value:=False)
maintenanceEvent.ChangeComment(Me.SystemContext, value:="Planned maintenance")

'Reports an event whenever one of the Change methods completes.
maintenanceEvent.AutoReportChanges = True
```

If the same event node should be processed on several tracks in parallel, you can open a new event branch. The `CreateBranch` method of the node does this. The node stores the unique key of the branch in the `BranchId` property. The following code shows the most important parts of `OpcConditionNode` for branches:

```csharp
// Uses a new GUID as BranchId.
var maintenanceBranchA = maintenanceEvent.CreateBranch(this.SystemContext);
 
// Uses a custom NodeId as BranchId.
var maintenanceBranchB = maintenanceEvent.CreateBranch(
        this.SystemContext,
        branchId: new OpcNodeId(10001));
```

```vbnet
'Uses a new GUID as BranchId.
Dim maintenanceBranchA = maintenanceEvent.CreateBranch(Me.SystemContext)

'Uses a custom NodeId as BranchId.
Dim maintenanceBranchB = maintenanceEvent.CreateBranch( _
        Me.SystemContext, _
        branchId:=New OpcNodeId(10001))
```

The `BranchId` property identifies the branch an event belongs to. A null reference stands for the current state. The `LastSeverity` property returns the previous severity of the branch.

## Dialog conditions

Types used: `OpcServer`, `OpcNodeManager`, `OpcDialogConditionNode`

The `OpcDialogConditionNode` class is a specialization of `OpcConditionNode`. The condition of this node is a dialog with the subscribers. Such a condition consists of a prompt and response options. Three more details complete it: the option selected by default (`DefaultResponse` property), the option that confirms the dialog (`OkResponse` property), and the option that cancels it (`CancelResponse` property). When the server triggers such a dialog event, it waits until one of the subscribers responds. The response is a choice from the given response options.

The condition for further processing of the operations tied to the dialog is therefore the response to a task, a question, a piece of information, or a warning. Once you have created the node as usual, set the properties to fit the use case:

```csharp
var outOfMaterial = new OpcDialogConditionNode(machineOne, "MaterialAlert");

outOfMaterial.Message = "Out of Material"; // Generic event message
outOfMaterial.Prompt = "The machine is out of material. Refill material supply to continue.";
outOfMaterial.ResponseOptions = new OpcText[] { "Continue", "Cancel" };
outOfMaterial.DefaultResponse = 0; // Index of ResponseOption to use
outOfMaterial.CancelResponse = 1;  // Index of ResponseOption to use
outOfMaterial.OkResponse = 0;      // Index of ResponseOption to use

```

```vbnet
Dim outOfMaterial As New OpcDialogConditionNode(machineOne, "MaterialAlert")

outOfMaterial.Message = "Out of Material" ' Generic event message
outOfMaterial.Prompt = "The machine is out of material. Refill material supply to continue."
outOfMaterial.ResponseOptions = New OpcText() {"Continue", "Cancel"}
outOfMaterial.DefaultResponse = 0 ' Index of ResponseOption to use
outOfMaterial.CancelResponse = 1 ' Index of ResponseOption to use
outOfMaterial.OkResponse = 0 ' Index of ResponseOption to use

```

To let the subscribers respond to the dialog, activate it with the `ActivateDialog` method once the node exists in the address space. You handle the response of a subscriber in the `RespondCallback` of the node. The `RespondDialog` method accepts the response and deactivates the dialog again. The `LastResponse` property returns the response accepted last:

```csharp
outOfMaterial.RespondCallback = this.HandleOutOfMaterialResponse;
outOfMaterial.ActivateDialog(this.SystemContext);
...
private OpcStatusCode HandleOutOfMaterialResponse(
        OpcNodeContext<OpcDialogConditionNode> context,
        int selectedResponse)
{
    // Handle the response.
    if (context.Node.OkResponse == selectedResponse)
        ContinueJob();
    else
        CancelJob();
 
    // Apply the response.
    context.Node.RespondDialog(context, selectedResponse);
 
    return OpcStatusCode.Good;
}
```

```vbnet
outOfMaterial.RespondCallback = AddressOf Me.HandleOutOfMaterialResponse
outOfMaterial.ActivateDialog(Me.SystemContext)
...
Private Function HandleOutOfMaterialResponse( _
        ByVal context As OpcNodeContext(Of OpcDialogConditionNode), _
        ByVal selectedResponse As Integer) As OpcStatusCode
    'Handle the response.
    If context.Node.OkResponse = selectedResponse Then
        ContinueJob()
    Else
        CancelJob()
    End If

    'Apply the response.
    context.Node.RespondDialog(context, selectedResponse)

    Return OpcStatusCode.Good
End Function
```

## Acknowledgeable conditions

Types used: `OpcServer`, `OpcNodeManager`, `OpcAcknowledgeableConditionNode`

The `OpcAcknowledgeableConditionNode` class is a specialization of `OpcConditionNode`. It is the base class for conditions that request feedback. When the condition of such an event is met, the server sends a kind of "message with return receipt". The return receipt, that is, the feedback, can control further processes or simply acknowledge notes and warnings.
The OPC UA specification (Part 9) provides a feedback mechanism with two kinds of feedback for this. With Acknowledge, the operators confirm that they have taken note of the message. With the optional Confirm, they then report that the cause has been resolved. For both kinds of feedback, the node provides the Acknowledge and Confirm child nodes.

The SDK handles both kinds of feedback independently of each other: acknowledging sets only the Acked state, confirming sets only the Confirmed state. The application defines the order the server requires. With both kinds of feedback, the operators can optionally add a comment. You create such a node as usual. Here too, you can set the states only once the node exists in the address space:

```csharp
var outOfProcessableBounds = new OpcAcknowledgeableConditionNode(machineOne, "OutOfBoundsAlert");
 
// Once the node has been added to the address space:
// Define the condition as: Needs to be acknowledged.
outOfProcessableBounds.ChangeIsAcked(this.SystemContext, value: false);
 
// Define the condition as: Needs to be confirmed.
outOfProcessableBounds.ChangeIsConfirmed(this.SystemContext, value: false);
```

```vbnet
Dim outOfProcessableBounds = New OpcAcknowledgeableConditionNode(machineOne, "OutOfBoundsAlert")

'Once the node has been added to the address space:
'Define the condition as: Needs to be acknowledged.
outOfProcessableBounds.ChangeIsAcked(Me.SystemContext, value:=False)

'Define the condition as: Needs to be confirmed.
outOfProcessableBounds.ChangeIsConfirmed(Me.SystemContext, value:=False)
```

Later in the process, you check received feedback with the `IsAcked` and `IsConfirmed` properties of the node:

```csharp
if (outOfProcessableBounds.IsAcked) {
    ...
}

if (outOfProcessableBounds.IsConfirmed) {
    ...
}

```

```vbnet
If outOfProcessableBounds.IsAcked Then
    ...
End If

If outOfProcessableBounds.IsConfirmed Then
    ...
End If

```

The server responds to the feedback of a client through the `AcknowledgeCallback` and `ConfirmCallback` properties. The server itself acknowledges or confirms a condition with the `Acknowledge` and `Confirm` methods:

```csharp
outOfProcessableBounds.AcknowledgeCallback = (context, eventId, comment) => {
    Console.WriteLine("Acknowledged: {0}", comment);
    return OpcStatusCode.Good;
};
 
// Acknowledge or confirm the condition on the server side.
outOfProcessableBounds.Acknowledge(this.SystemContext, comment: "Acknowledged by the server");
outOfProcessableBounds.Confirm(this.SystemContext);
```

```vbnet
outOfProcessableBounds.AcknowledgeCallback = Function(context, eventId, comment)
    Console.WriteLine("Acknowledged: {0}", comment)
    Return OpcStatusCode.Good
End Function

'Acknowledge or confirm the condition on the server side.
outOfProcessableBounds.Acknowledge(Me.SystemContext, comment:="Acknowledged by the server")
outOfProcessableBounds.Confirm(Me.SystemContext)
```

The server must always **define its own** interpretation of the feedback and the logic that follows. Whether a server uses both kinds of feedback or only one is up to the development team. A server should support at least Acknowledge. According to the specification, Confirm is optional.

## Alarm conditions

Types used: `OpcServer`, `OpcNodeManager`, `OpcAlarmConditionNode`

Probably the most important implementation of `OpcAcknowledgeableConditionNode` in OPC UA is the `OpcAlarmConditionNode` class. With it, you define event nodes that behave like a bedside alarm clock. The node becomes active (`IsActive` property) when the linked condition is met.
For the alarm clock, this is reaching the wake-up time, for example. An alarm that has a wake-up time but should not become active when that time is reached is called a suppressed alarm (`IsSuppressed` and `IsSuppressedOrShelved` properties). You can shelve an active alarm (`IsSuppressedOrShelved` property). An alarm can be shelved once ("One Shot Shelving") or for a period of time ("Timed Shelving") (`Shelving` property). You can also unshelve an alarm again (`Shelving` property).

The following code shows an example of the `OpcAlarmConditionNode` API:

```csharp
var overheating = new OpcAlarmConditionNode(machineOne, "OverheatingAlert");
var idle = new OpcAlarmConditionNode(machineOne, "IdleAlert");

...

overheating.ChangeIsActive(this.SystemContext, true);
idle.ChangeIsActive(this.SystemContext, true);

...

if (overheating.IsActive)
    CancelJob();

if (!idle.IsActive)
    ProcessJob();
else if (idle.IsSuppressed)
    SimulateJob();

```

```vbnet
Dim overheating As New OpcAlarmConditionNode(machineOne, "OverheatingAlert")
Dim idle As New OpcAlarmConditionNode(machineOne, "IdleAlert")
'...
overheating.ChangeIsActive(Me.SystemContext, True)
idle.ChangeIsActive(Me.SystemContext, True)

...

If overheating.IsActive Then
    CancelJob()
End If

If Not idle.IsActive Then
    ProcessJob()
ElseIf idle.IsSuppressed Then
    SimulateJob()
End If

```

On the server side, you suppress an alarm with the `ChangeIsSuppressed` method and shelve it with the `ChangeShelving` method:

```csharp
// Suppress the alarm, e.g. while the machine is in maintenance.
overheating.ChangeIsSuppressed(this.SystemContext, value: true);
 
// Shelve the alarm for 30 minutes.
overheating.ChangeShelving(
        this.SystemContext,
        shelved: true,
        oneShot: false,
        shelvingTime: TimeSpan.FromMinutes(30));
 
// Unshelve the alarm.
overheating.ChangeShelving(this.SystemContext, shelved: false);
```

```vbnet
'Suppress the alarm, e.g. while the machine is in maintenance.
overheating.ChangeIsSuppressed(Me.SystemContext, value:=True)

'Shelve the alarm for 30 minutes.
overheating.ChangeShelving( _
        Me.SystemContext, _
        shelved:=True, _
        oneShot:=False, _
        shelvingTime:=TimeSpan.FromMinutes(30))

'Unshelve the alarm.
overheating.ChangeShelving(Me.SystemContext, shelved:=False)
```

## Discrete alarms

Types used: `OpcServer`, `OpcNodeManager`, `OpcDiscreteAlarmNode`, `OpcOffNormalAlarmNode`, `OpcTripAlarmNode`

There are several specializations of `OpcAlarmConditionNode` for specific kinds of alarms. For them, the kind of alarm itself describes its form, its cause, or its content in more detail. One subclass of such self-describing alarms are the discrete alarms. The base of a discrete alarm is the `OpcDiscreteAlarmNode` class. It defines an alarm state node that classifies types into alarm states. The input for the alarm can take only a certain number of possible values, for example true and false, or running, stopped, and finished. If an alarm should represent a discrete state that is considered not normal, you should use the `OpcOffNormalAlarmNode` class or one of its subclasses.

Building on this alarm class, the SDK provides a further specialization with the `OpcTripAlarmNode` class. An `OpcTripAlarmNode` becomes active when an abnormal fault occurs on a monitored device, for example when the motor shuts down because of overload. To create these nodes:

```csharp
var x = new OpcDiscreteAlarmNode(machineOne, "discreteAlert");
var y = new OpcOffNormalAlarmNode(machineOne, "offNormalAlert");
var z = new OpcTripAlarmNode(machineOne, "tripAlert");

```

```vbnet
Dim x As New OpcDiscreteAlarmNode(machineOne, "discreteAlert")
Dim y As New OpcOffNormalAlarmNode(machineOne, "offNormalAlert")
Dim z As New OpcTripAlarmNode(machineOne, "tripAlert")

```

## Limit alarms

Types used: `OpcServer`, `OpcNodeManager`, `OpcLimitAlarmNode`

If the server should check process-specific limits and publish the result when a value exceeds or falls below them, the `OpcLimitAlarmNode` class is the entry point to the limit alarm classes. With it, you divide limits into up to four levels.

In the order of their metric ranking, the levels are called `LowLow`, `Low`, `High`, and `HighHigh`. By definition, not all limits have to be defined. For this reason, you can set the limits you need from the start:

```csharp
var positionLimit = new OpcLimitAlarmNode(
        machineOne, "PositionLimit", OpcLimitAlarmStates.HighHigh | OpcLimitAlarmStates.LowLow);

positionLimit.HighHighLimit = 120; // e.g. mm
positionLimit.LowLowLimit = 0;     // e.g. mm

```

```vbnet
Dim positionLimit As New OpcLimitAlarmNode(
        machineOne, "PositionLimit", OpcLimitAlarmStates.HighHigh Or OpcLimitAlarmStates.LowLow)

positionLimit.HighHighLimit = 120 ' e.g. mm
positionLimit.LowLowLimit = 0 ' e.g. mm

```

## Exclusive limit alarms

Types used: `OpcServer`, `OpcNodeManager`, `OpcExclusiveLimitAlarmNode`

The `OpcExclusiveLimitAlarmNode` class is a subclass of `OpcLimitAlarmNode`. As its name says, it defines limit alarms for exclusive limits. The limits of such an alarm are mutually exclusive. If one limit is exceeded or undershot, no other limit can be exceeded or undershot at the same time. The state machine in the `Limit` property of the node describes the violated limit. You set it with the `ChangeLimitState` method:

```csharp
var temperatureLimit = new OpcExclusiveLevelAlarmNode(
        machineOne,
        "TemperatureLimit",
        supportedLimits: OpcLimitAlarmStates.High | OpcLimitAlarmStates.HighHigh);
 
temperatureLimit.HighLimit = 80;      // e.g. degree Celsius
temperatureLimit.HighHighLimit = 100; // e.g. degree Celsius
 
// Once the node has been added to the address space:
temperatureLimit.ChangeIsActive(this.SystemContext, value: true);
temperatureLimit.ChangeLimitState(this.SystemContext, value: OpcLimitAlarmStates.High);
```

```vbnet
Dim temperatureLimit = New OpcExclusiveLevelAlarmNode( _
        machineOne, _
        "TemperatureLimit", _
        supportedLimits:=OpcLimitAlarmStates.High Or OpcLimitAlarmStates.HighHigh)

temperatureLimit.HighLimit = 80      'e.g. degree Celsius
temperatureLimit.HighHighLimit = 100 'e.g. degree Celsius

'Once the node has been added to the address space:
temperatureLimit.ChangeIsActive(Me.SystemContext, value:=True)
temperatureLimit.ChangeLimitState(Me.SystemContext, value:=OpcLimitAlarmStates.High)
```

OPC UA has three further specializations of `OpcExclusiveLimitAlarmNode`.

`OpcExclusiveDeviationAlarmNode`

Use this kind of alarm if the server monitors how far a value deviates from its setpoint. You specify the node of the setpoint with the `SetpointNodeId` property.

`OpcExclusiveLevelAlarmNode`

This kind of alarm usually reports that a limit is exceeded, typically on an instrument such as a temperature sensor. The alarm becomes active when the observed value is above an upper limit or below a lower limit.

`OpcExclusiveRateOfChangeAlarmNode`

This kind of alarm usually reports an unusual or missing change of a measured value, relative to the speed at which the value changes. The alarm becomes active when the rate of change of the value exceeds or falls below a defined limit.

## Non-exclusive limit alarms

Types used: `OpcServer`, `OpcNodeManager`, `OpcNonExclusiveLimitAlarmNode`

The `OpcNonExclusiveLimitAlarmNode` class is a subclass of `OpcLimitAlarmNode`. As its name says, it defines limit alarms for non-exclusive limits. The limits of such an alarm are **not** mutually exclusive. If one limit is exceeded or undershot, another limit can be exceeded or undershot at the same time. You check the violated limits with the `IsLowLow`, `IsLow`, `IsHigh`, and `IsHighHigh` properties of the node.

OPC UA has three further specializations of `OpcNonExclusiveLimitAlarmNode`.

`OpcNonExclusiveDeviationAlarmNode`

Use this kind of alarm if the server monitors how far a value deviates from its setpoint. You specify the node of the setpoint with the `SetpointNodeId` property.

`OpcNonExclusiveLevelAlarmNode`

This kind of alarm usually reports that a limit is exceeded, typically on an instrument such as a temperature sensor. The alarm becomes active when the observed value is above an upper limit or below a lower limit.

`OpcNonExclusiveRateOfChangeAlarmNode`

This kind of alarm usually reports an unusual or missing change of a measured value, relative to the speed at which the value changes. The alarm becomes active when the rate of change of the value exceeds or falls below a defined limit.
