# Ereignisse

> Anleitung: Ereignisse eines Servers empfangen, filtern und auswerten, mit Ereignis-Nodes arbeiten und Audit-Ereignisse empfangen.

**Beispielanwendungen:**

- Client.EventTypes ([C#](https://github.com/Traeger-GmbH/opcuanet-samples/tree/master/cs/Basic/Client.EventTypes), [VB.NET](https://github.com/Traeger-GmbH/opcuanet-samples/tree/master/vb/Basic/Client.EventTypes)): Abonniert Ereignisse eines eigenen Ereignistyps und liest ihre strukturierten Ereignisdaten über eine eigene Ereignisklasse.
- Client.GenericEvents ([C#](https://github.com/Traeger-GmbH/opcuanet-samples/tree/master/cs/Scenarios/Client.GenericEvents), [VB.NET](https://github.com/Traeger-GmbH/opcuanet-samples/tree/master/vb/Scenarios/Client.GenericEvents)): Empfängt Ereignisse eines eigenen Ereignistyps ohne eigene Klasse für dessen Felder und gibt alle ausgewählten Felder über eine generische Ereignisklasse aus.
- Server.EventTypes ([C#](https://github.com/Traeger-GmbH/opcuanet-samples/tree/master/cs/Basic/Server.EventTypes), [VB.NET](https://github.com/Traeger-GmbH/opcuanet-samples/tree/master/vb/Basic/Server.EventTypes)): Stellt einen eigenen Ereignistyp aus einem NodeSet bereit; ein Methodenaufruf durch den Client löst Ereignisse mit strukturierten Daten aus.

Diese Seite zeigt, wie ein Client Ereignisse (Alarm & Events) eines Servers empfängt, filtert und auswertet. Ereignisse mit Bedingungen und Alarme beschreibt die Seite [Alarme und Bedingungen](https://www.traeger.de/docs/development/opcua/client/alarms-conditions).

Beteiligte Typen: `OpcClient`, `OpcObjectTypes`, `OpcEventTypes`, `OpcEventReceivedEventArgs`, `OpcEvent`, `OpcAlarmCondition`, `OpcSimpleAttributeOperand`, `OpcFilter`, `OpcEventFilter`, `OpcEventSeverity`

## Globale Ereignisse empfangen

Ereignisse informieren einen Abonnenten, zum Beispiel einen Client, über Abläufe, Zustände und systemspezifische Begebenheiten. Über **globale Ereignisse** erhalten Abonnenten solche Informationen direkt. Ein Server veröffentlicht ein globales Ereignis immer über sein Server-Objekt. Deshalb abonniert der Client globale Ereignisse auch über diesen Node:

```csharp
client.SubscribeEvent(OpcObjectTypes.Server, HandleGlobalEvents);

```

```vbnet
client.SubscribeEvent(OpcObjectTypes.Server, AddressOf HandleGlobalEvents)

```

Ein Ereignis behandeln Sie in der Regel mit einer Methode dieser Signatur:

```csharp
private static void HandleGlobalEvents(
        object sender,
        OpcEventReceivedEventArgs e)
{
    Console.WriteLine(e.Event.Message);
}

```

```vbnet
Private Shared Sub HandleGlobalEvents( _
        ByVal sender As Object, _
        ByVal e As OpcEventReceivedEventArgs)
    Console.WriteLine(e.Event.Message)
End Sub

```

Die Eigenschaft `Event` der Ereignisargumente (Klasse `OpcEventReceivedEventArgs`) liefert die empfangenen Ereignisdaten. Sie liefert immer eine Instanz der Klasse `OpcEvent`. Wenn die Ereignisdaten zu einer Spezialisierung der Klasse `OpcEvent` gehören, casten Sie sie einfach:

```csharp
private static void HandleGlobalEvents(
        object sender,
        OpcEventReceivedEventArgs e)
{
    var alarm = e.Event as OpcAlarmCondition;
 
    if (alarm != null)
        Console.WriteLine("Alarm: " + alarm.Message);
}
```

```vbnet
Private Shared Sub HandleGlobalEvents( _
        ByVal sender As Object, _
        ByVal e As OpcEventReceivedEventArgs)
    Dim alarm = TryCast(e.Event, OpcAlarmCondition)

    If alarm IsNot Nothing Then
        Console.WriteLine("Alarm: " & alarm.Message.Value)
    End If
End Sub
```

## Ereignisse filtern

Der Aufruf oben abonniert alle Ereignisse, die der Server global veröffentlicht. Mit einem Ereignisfilter (engl. EventFilter) schränken Sie die erhaltenen Informationen ein und auch die Ereignisse, die der Server überhaupt an den Client sendet. Der Client übermittelt den Filter beim Anlegen der Subscription an den Server:

```csharp
// Define an attribute operand using the identifier of the type which defines the
// attribute / property including the name of the attribute / property to evaluate
// by the operand.
var severity = new OpcSimpleAttributeOperand(OpcEventTypes.Event, "Severity");
var conditionName = new OpcSimpleAttributeOperand(OpcEventTypes.Condition, "ConditionName");
 
var filter = OpcFilter.Using(client)
        .FromEvents(OpcEventTypes.AlarmCondition)
        .Where(severity > OpcEventSeverity.Medium & conditionName.Like("Temperature"))
        .Select();
 
client.SubscribeEvent(
        OpcObjectTypes.Server,
        filter,
        HandleGlobalEvents);

```

```vbnet
'Define an attribute operand using the identifier of the type which defines the
'attribute / property including the name of the attribute / property to evaluate
'by the operand.
Dim severity = New OpcSimpleAttributeOperand(OpcEventTypes.Event, "Severity")
Dim conditionName = New OpcSimpleAttributeOperand(OpcEventTypes.Condition, "ConditionName")

Dim filter = OpcFilter.Using(client) _
        .FromEvents(OpcEventTypes.AlarmCondition) _
        .Where(severity > OpcEventSeverity.Medium And conditionName.Like("Temperature")) _
        .Select()

client.SubscribeEvent(
        OpcObjectTypes.Server,
        filter,
        AddressOf HandleGlobalEvents)

```

Um einen Ereignisfilter zu erstellen, benötigen Sie immer eine Instanz von `OpcClient`, die bereits mit dem Zielserver verbunden ist. Beim Zusammenstellen des Filters ruft der Client nämlich die relevanten Typinformationen vom Server ab. Im Beispiel oben sammelt der Client so alle Eigenschaften des Node-Typs `OpcAlarmConditionNode` rekursiv bis zum `OpcNode`. Anhand der Where- und Select-Klauseln erstellt er daraus Regeln für die Auswahl der Ereignisdaten. Die zu analysierenden Node-Typen übergeben Sie durch Komma getrennt an die Methode `FromEvents`:

```csharp
var severity = new OpcSimpleAttributeOperand(OpcEventTypes.Event, "Severity");
var conditionName = new OpcSimpleAttributeOperand(OpcEventTypes.Condition, "ConditionName");
 
var filter = OpcFilter.Using(client)
        .FromEvents(
            OpcEventTypes.AlarmCondition,
            OpcEventTypes.ExclusiveLimitAlarm,
            OpcEventTypes.DialogCondition)
        .Where(severity > OpcEventSeverity.Medium & conditionName.Like("Temperature"))
        .Select();
 
client.SubscribeEvent(
        OpcObjectTypes.Server,
        filter,
        HandleGlobalEvents);

```

```vbnet
Dim severity = New OpcSimpleAttributeOperand(OpcEventTypes.Event, "Severity")
Dim conditionName = New OpcSimpleAttributeOperand(OpcEventTypes.Condition, "ConditionName")

Dim filter = OpcFilter.Using(client) _
        .FromEvents( _
            OpcEventTypes.AlarmCondition, _
            OpcEventTypes.ExclusiveLimitAlarm, _
            OpcEventTypes.DialogCondition) _
        .Where(severity > OpcEventSeverity.Medium And conditionName.Like("Temperature")) _
        .Select()

client.SubscribeEvent(
        OpcObjectTypes.Server,
        filter,
        AddressOf HandleGlobalEvents)

```

Mit der Methode `Where` schränken Sie die von `FromEvents` gesammelten Informationen über die Eigenschaften der Node-Typen ein. Dafür bietet das SDK verschiedene Operatorüberladungen (`<=`, `<`, `>`, `>=` und `==`). Der Operator `!=` löst eine `NotSupportedException` aus. Verwenden Sie stattdessen `(a < b) | (a > b)`. Um Operanden logisch zu kombinieren, stehen außerdem die Operatoren ODER (`|`) und UND (`&`) bereit. Für weitere Einschränkungen gibt es Methoden wie `Like`, `Between`, `InList`, `IsNull`, `Not`, `OfType`, `Cast` und `InView`. Für Sprachen ohne diese Operatoren (wie Visual Basic) stehen die gleichwertigen Methoden `EqualsTo`, `GreaterThan`, `GreaterThanOrEqualsTo`, `LessThan` und `LessThanOrEqualsTo` bereit.

Ab Version 2.59 erwartet `Between` die Operanden in der Reihenfolge von OPC UA: `a.Between(lower, upper)` prüft, ob `a` zwischen `lower` und `upper` liegt. Mehrere `Where`-Aufrufe hintereinander verknüpfen ihre Bedingungen ab Version 2.59 mit UND, wie ein einzelnes `Where` mit `&`. In älteren Versionen gilt nur die Bedingung des letzten Aufrufs.

Danach wählen Sie mit der Methode `Select` die Eigenschaften aus, die der Server zusätzlich an den Client überträgt. Das geschieht, wenn ein Ereignis eintritt, das den Bedingungen aus `Where` entspricht.

Das folgende Beispiel abonniert alle Ereignisse, die aktuell aktiv, aber noch nicht bestätigt sind:

```csharp
var isActive = new OpcSimpleAttributeOperand(
        OpcEventTypes.AlarmCondition,
        "ActiveState", "Id");
var isAcked = new OpcSimpleAttributeOperand(
        OpcEventTypes.AcknowledgeableCondition,
        "AckedState", "Id");

var filter = OpcFilter.Using(client)
    .FromEvents(OpcEventTypes.AlarmCondition)
    .Where(OpcFilterOperand.OfType(OpcEventTypes.AlarmCondition)
        & isAcked == false
        & isActive == true)
    .Select();

var subscription = client.SubscribeEvent("ns=2;s=Machine", filter, (sender, e) => {
    if (e.Event is OpcAlarmCondition alarm) {
        Console.WriteLine(
                "{0}: {1}, IsActive = {2}, IsAcked = {3} ({4})",
                alarm.GetType().Name,
                alarm.Message,
                alarm.IsActive,
                alarm.IsAcked,
                alarm.Severity);
    }
});
```

```vbnet
Dim isActive = New OpcSimpleAttributeOperand( _
        OpcEventTypes.AlarmCondition, _
        "ActiveState", "Id")
Dim isAcked = New OpcSimpleAttributeOperand( _
        OpcEventTypes.AcknowledgeableCondition, _
        "AckedState", "Id")

Dim filter = OpcFilter.Using(client) _
    .FromEvents(OpcEventTypes.AlarmCondition) _
    .Where(OpcFilterOperand.OfType(OpcEventTypes.AlarmCondition) _
        And isAcked = False _
        And isActive = True) _
    .Select()

Dim subscription = client.SubscribeEvent("ns=2;s=Machine", filter, _
    Sub(sender, e)
        Dim alarm = TryCast(e.Event, OpcAlarmCondition)

        If alarm IsNot Nothing Then
            Console.WriteLine( _
                    "{0}: {1}, IsActive = {2}, IsAcked = {3} ({4})", _
                    alarm.GetType().Name, alarm.Message, alarm.IsActive, alarm.IsAcked, alarm.Severity)
        End If
    End Sub)

```

## Ereignisse eines Nodes empfangen

Beteiligte Typen: `OpcClient`, `OpcEventReceivedEventArgs`, `OpcEvent`, `OpcSubscription`

Es ist nicht immer sinnvoll, dass ein Server Ereignisse durchweg global über das Server-Objekt an alle Abonnenten sendet. Oft entscheidet der Kontext, ob ein Ereignis für einen Abonnenten von Interesse ist. Für solche lokalen Ereignisse gibt es Ereignis-Nodes. Wenn der Server einen Ereignis-Node verwendet, um Ereignisdaten für einen Node bereitzustellen, ist der Ereignis-Node ein sogenannter Benachrichtiger (engl. Notifier) des Nodes. Ein HasNotifier-Verweis zeigt deshalb, welcher Ereignis-Node Ereignisdaten an einen Node meldet. Ein Ereignis-Node kann Benachrichtiger für mehrere Nodes sein. Deshalb abonniert der Client lokale Ereignisse immer über die benachrichtigten Nodes:

```csharp
client.SubscribeEvent(machineNodeId, HandleLocalEvents);

```

```vbnet
client.SubscribeEvent(machineNodeId, AddressOf HandleLocalEvents)

```

Ein Ereignis behandeln Sie in der Regel mit einer Methode dieser Signatur. Die weitere Verarbeitung der Ereignisdaten entspricht der Verarbeitung globaler Ereignisse (siehe Abschnitt [Globale Ereignisse empfangen](https://www.traeger.de/docs/development/opcua/client/events#globale-ereignisse-empfangen)).

```csharp
private static void HandleLocalEvents(
        object sender,
        OpcEventReceivedEventArgs e)
{
    Console.WriteLine(e.Event.Message);
}

```

```vbnet
Private Shared Sub HandleLocalEvents( _
        ByVal sender As Object, _
        ByVal e As OpcEventReceivedEventArgs)
    Console.WriteLine(e.Event.Message)
End Sub

```

Auch lokale Ereignisse können Sie filtern, wie im Abschnitt [Ereignisse filtern](https://www.traeger.de/docs/development/opcua/client/events#ereignisse-filtern) gezeigt.

Ein Abonnent (ein Client) erfährt nur von Ereignissen, solange er mit dem Server verbunden ist und eine Subscription angelegt hat. Nach dem Verbindungsaufbau kennt er deshalb den aktuellen Zustand der Bedingungen des Servers nicht (siehe [Alarme und Bedingungen](https://www.traeger.de/docs/development/opcua/client/alarms-conditions)). Mit dem folgenden Aufruf fordert er diesen Zustand an. Der Server sendet daraufhin für alle Bedingungen, die sich aktuell in einem relevanten Zustand befinden (`IsRetained` ist `true`), erneut ein Ereignis. Vergangene Ereignisse liefert dieser Aufruf nicht. Wenn der Server sie bereitstellt, liest der Client sie über die Ereignis-Historie, siehe [Ereignis-Historie lesen](https://www.traeger.de/docs/development/opcua/client/historical-data#ereignis-historie-lesen).

```csharp
var subscription = client.SubscribeEvent(
        machineNodeId,
        HandleLocalEvents);
 
// Query most recent event information.
subscription.RefreshConditions();

```

```vbnet
Dim subscription = client.SubscribeEvent( _
        machineNodeId, _
        AddressOf HandleLocalEvents)
 
'Query most recent event information.
subscription.RefreshConditions()

```

## Audit-Ereignisse empfangen

Beteiligte Typen: `OpcClient`, `OpcFilter`, `OpcEventFilter`, `OpcObjectTypes`, `OpcEvent`

Audit-Ereignisse protokollieren sicherheitsrelevante Aktionen eines Servers. Ein mit dem SDK entwickelter Server meldet sie über das Server-Objekt, sofern das Auditing im Server eingeschaltet ist (siehe [Audit-Ereignisse](https://www.traeger.de/docs/development/opcua/security/server#audit-ereignisse)). Er meldet sie beim Öffnen und Schließen eines sicheren Kanals (AuditOpenSecureChannelEventType, AuditChannelEventType) sowie beim Erstellen (AuditCreateSessionEventType) und beim Aktivieren (AuditActivateSessionEventType) einer Sitzung. Wie in OPC UA festgelegt, erhält ein Client sie nur über einen Endpunkt mit dem Modus `SignAndEncrypt` oder über HTTPS und nur, wenn der angemeldete Benutzer der Rolle `SecurityAdmin` angehört. Ein Client abonniert sie wie andere Ereignisse über einen Filter auf die Ereignistypen, im folgenden Beispiel auf die beiden Ereignistypen der Sitzung:

```csharp
OpcEventFilter filter = OpcFilter.Using(client)
        .FromEvents(
                OpcObjectTypes.GetNodeId(OpcObjectType.AuditCreateSessionEventType),
                OpcObjectTypes.GetNodeId(OpcObjectType.AuditActivateSessionEventType))
        .Select();
 
client.SubscribeEvent(OpcObjectTypes.Server, filter, HandleAuditEvent);
...
private static void HandleAuditEvent(
        object sender,
        OpcEventReceivedEventArgs e)
{
    OpcEvent audit = e.Event;
 
    Console.WriteLine(
            "{0:u} {1} {2}: {3}",
            audit.Time,
            audit.EventTypeId,
            audit.SourceName,
            audit.Message);
}
```

```vbnet
Dim filter As OpcEventFilter = OpcFilter.Using(client) _
        .FromEvents( _
                OpcObjectTypes.GetNodeId(OpcObjectType.AuditCreateSessionEventType), _
                OpcObjectTypes.GetNodeId(OpcObjectType.AuditActivateSessionEventType)) _
        .Select()

client.SubscribeEvent(OpcObjectTypes.Server, filter, AddressOf HandleAuditEvent)
...
Private Shared Sub HandleAuditEvent( _
        ByVal sender As Object, _
        ByVal e As OpcEventReceivedEventArgs)
    Dim audit As OpcEvent = e.Event

    Console.WriteLine( _
            "{0:u} {1} {2}: {3}", _
            audit.Time, _
            audit.EventTypeId, _
            audit.SourceName, _
            audit.Message)
End Sub

```

Der Client kann die allgemeinen Felder eines Ereignisses lesen, zum Beispiel `Time`, `EventTypeId`, `SourceName` und `Message`. Die besonderen Felder der Audit-Ereignisse, etwa die Identität des Benutzers oder die Kennung der Sitzung, stellt das SDK im Client nicht bereit.
