# Subscriptions

> Anleitung: Wertänderungen und Ereignisse von Nodes per Subscription abonnieren, Benachrichtigungen behandeln, Subscriptions verwalten und mit Filtern eingrenzen.

**Beispielanwendungen:**

- Client.BulkChanges.HiRes ([C#](https://github.com/Traeger-GmbH/opcuanet-samples/tree/master/cs/Subscriptions/Client.BulkChanges.HiRes), [VB.NET](https://github.com/Traeger-GmbH/opcuanet-samples/tree/master/vb/Subscriptions/Client.BulkChanges.HiRes)): Empfängt Datenänderungen sehr vieler Nodes mit minimaler Verzögerung, mit PublishingInterval 0 und einem Callback je Datensatz.
- Client.HandshakeChanges ([C#](https://github.com/Traeger-GmbH/opcuanet-samples/tree/master/cs/Subscriptions/Client.HandshakeChanges), [VB.NET](https://github.com/Traeger-GmbH/opcuanet-samples/tree/master/vb/Subscriptions/Client.HandshakeChanges)): Liest einen Datenblock, sobald der Server ihn als verfügbar meldet, und bestätigt die Verarbeitung über einen Handshake-Node.
- Server.BulkChanges.HiRes ([C#](https://github.com/Traeger-GmbH/opcuanet-samples/tree/master/cs/Subscriptions/Server.BulkChanges.HiRes), [VB.NET](https://github.com/Traeger-GmbH/opcuanet-samples/tree/master/vb/Subscriptions/Server.BulkChanges.HiRes)): Ändert die Werte sehr vieler Nodes im Abstand weniger Millisekunden, mit hoch aufgelöstem Publishing.
- Server.HandshakeChanges ([C#](https://github.com/Traeger-GmbH/opcuanet-samples/tree/master/cs/Subscriptions/Server.HandshakeChanges), [VB.NET](https://github.com/Traeger-GmbH/opcuanet-samples/tree/master/vb/Subscriptions/Server.HandshakeChanges)): Stellt Datenblöcke mit Handshake bereit: meldet neue Daten und wartet, bis ein Client deren Verarbeitung bestätigt.

## Subscriptions erstellen

Beteiligte Typen: `OpcClient`, `OpcNodeId`, `OpcSubscribeDataChange`, `OpcSubscribeEvent`, `OpcSubscription`, `OpcMonitoredItem`, `OpcMonitoredItemCollection`, `OpcDataChangeReceivedEventHandler`, `OpcDataChangeReceivedEventArgs`, `OpcEventReceivedEventHandler`, `OpcEventReceivedEventArgs`, `OpcNotification`

Eine Subscription in OPC UA lässt sich mit einem Abonnement vergleichen, das eine oder mehrere Zeitschriften umfasst. Statt Zeitschriften abonniert der Client jedoch Ereignisse von Nodes oder Änderungen ihrer Werte. Für jeden abonnierten Node enthält die Subscription ein MonitoredItem. Je Subscription legen Sie fest, in welchem Intervall der Server die Benachrichtigungen der MonitoredItems veröffentlicht (`OpcSubscription.PublishingInterval`). Die Benachrichtigungen sind Instanzen von `OpcNotification`. Die NodeId legt fest, welchen Node der Client abonniert. Standardmäßig überwacht (engl. monitored) der Server das Value-Attribut. Wenn der Server ein anderes Attribut des Nodes überwachen soll, geben Sie das passende `OpcAttribute` beim Aufruf von `SubscribeDataChange` oder in der jeweiligen Instanz von `OpcSubscribeDataChange` an.

**Wertänderungen eines Nodes abonnieren**:

```csharp
OpcSubscription subscription = client.SubscribeDataChange(
        "ns=2;s=Machine/IsRunning",
        HandleDataChanged);
```

```vbnet
Dim subscription As OpcSubscription = client.SubscribeDataChange( _
        "ns=2;s=Machine/IsRunning", _
        AddressOf HandleDataChanged)
```

**Wertänderungen mehrerer Nodes abonnieren**:

```csharp
OpcSubscribeDataChange[] commands = new OpcSubscribeDataChange[] {
    new OpcSubscribeDataChange("ns=2;s=Machine/IsRunning", HandleDataChanged),
    new OpcSubscribeDataChange("ns=2;s=Machine/Job/Speed", HandleDataChanged)
};
 
OpcSubscription subscription = client.SubscribeNodes(commands);
```

```vbnet
Dim commands As OpcSubscribeDataChange() = New OpcSubscribeDataChange() { _
    New OpcSubscribeDataChange("ns=2;s=Machine/IsRunning", AddressOf HandleDataChanged), _
    New OpcSubscribeDataChange("ns=2;s=Machine/Job/Speed", AddressOf HandleDataChanged) _
}

Dim subscription As OpcSubscription = client.SubscribeNodes(commands)
```

**Benachrichtigungen über Wertänderungen behandeln**:

```csharp
private static void HandleDataChanged(
        object sender,
        OpcDataChangeReceivedEventArgs e)
{
    // Your code to execute on each data change.
    // The 'sender' variable contains the OpcMonitoredItem with the NodeId.
    OpcMonitoredItem item = (OpcMonitoredItem)sender;

    Console.WriteLine(
            "Data Change from NodeId '{0}': {1}",
            item.NodeId,
            e.Item.Value);
}
```

```vbnet
Private Shared Sub HandleDataChanged( _
        ByVal sender As Object, _
        ByVal e As OpcDataChangeReceivedEventArgs)
    'Your code to execute on each data change.
    'The 'sender' variable contains the OpcMonitoredItem with the NodeId.
    Dim item As OpcMonitoredItem = CType(sender, OpcMonitoredItem)

    Console.WriteLine( _
            "Data Change from NodeId '{0}': {1}", _
            item.NodeId, _
            e.Item.Value)
End Sub

```

**Ereignisse eines Nodes abonnieren**:

```csharp
OpcSubscription subscription = client.SubscribeEvent(
        "ns=2;s=Machine",
        HandleEvent);
```

```vbnet
Dim subscription As OpcSubscription = client.SubscribeEvent( _
        "ns=2;s=Machine", _
        AddressOf HandleEvent)
```

**Ereignisse mehrerer Nodes abonnieren**:

```csharp
OpcSubscribeEvent[] commands = new OpcSubscribeEvent[] {
    new OpcSubscribeEvent("ns=2;s=Machine", HandleEvent),
    new OpcSubscribeEvent("ns=2;s=Machine/Job", HandleEvent)
};
 
OpcSubscription subscription = client.SubscribeNodes(commands);
```

```vbnet
Dim commands As OpcSubscribeEvent() = New OpcSubscribeEvent() { _
    New OpcSubscribeEvent("ns=2;s=Machine", AddressOf HandleEvent), _
    New OpcSubscribeEvent("ns=2;s=Machine/Job", AddressOf HandleEvent) _
}

Dim subscription As OpcSubscription = client.SubscribeNodes(commands)
```

**Benachrichtigungen über Ereignisse behandeln**:

```csharp
private void HandleEvent(object sender, OpcEventReceivedEventArgs e)
{
    // Your code to execute on each event raise.
}
```

```vbnet
Private Sub HandleEvent(ByVal sender As Object, ByVal e As OpcEventReceivedEventArgs)
    'Your code to execute on each event raise.
End Sub

```

**Subscription konfigurieren**: Standardmäßig verwendet eine Subscription einen `KeepAliveCount` von 10, einen `LifetimeCount` von 100 und höchstens 1000 Benachrichtigungen je Veröffentlichung (`MaxNotificationsPerPublish`). Das `PublishingInterval` ist nicht vorbelegt. Ein Wert von 0 oder kleiner fordert das kleinste vom Server unterstützte Intervall an. Der Client übermittelt Änderungen an der Subscription erst mit dem Aufruf von `ApplyChanges` an den Server:

```csharp
subscription.PublishingInterval = 2000;
 
// Always call apply changes after modifying the subscription; otherwise
// the server will not know the new subscription configuration.
subscription.ApplyChanges();
```

```vbnet
subscription.PublishingInterval = 2000

'Always call apply changes after modifying the subscription; otherwise
'the server will not know the new subscription configuration.
subscription.ApplyChanges()
```

**Mehrere Nodes über eine Subscription abonnieren**: Das Beispiel abonniert Wertänderungen mehrerer Nodes über eine einzige Subscription und legt benutzerdefinierte Werte der Eigenschaft `Tag` der MonitoredItems fest. Die Eigenschaft `SamplingInterval` legt fest, wie oft der Server den Wert prüft. Der Wert 0 fordert die schnellste Rate an, der Wert -1 verwendet das PublishingInterval der Subscription:

```csharp
string[] nodeIds = {
    "ns=2;s=Machine/IsRunning",
    "ns=2;s=Machine/Job/Speed",
    "ns=2;s=Machine/Diagnostics"
};
 
// Create an (empty) subscription to which we will addd OpcMonitoredItems.
OpcSubscription subscription = client.SubscribeNodes();
 
for (int index = 0; index < nodeIds.Length; index++) {
    // Create an OpcMonitoredItem for the NodeId.
    var item = new OpcMonitoredItem(nodeIds[index], OpcAttribute.Value);
    item.DataChangeReceived += HandleDataChanged;
 
    // You can set your own values on the "Tag" property
    // that allows you to identify the source later.
    item.Tag = index;
 
    // Set a custom sampling interval on the 
    // monitored item.
    item.SamplingInterval = 200;
 
    // Add the item to the subscription.
    subscription.AddMonitoredItem(item);
}
 
// After adding the items (or configuring the subscription), apply the changes.
subscription.ApplyChanges();
```

```vbnet
Dim nodeIds As String() = { _
    "ns=2;s=Machine/IsRunning", _
    "ns=2;s=Machine/Job/Speed", _
    "ns=2;s=Machine/Diagnostics" _
}

'Create an (empty) subscription to which we will addd OpcMonitoredItems.
Dim subscription As OpcSubscription = client.SubscribeNodes()

For index As Integer = 0 To nodeIds.Length - 1
    'Create an OpcMonitoredItem for the NodeId.
    Dim item = New OpcMonitoredItem(nodeIds(index), OpcAttribute.Value)
    AddHandler item.DataChangeReceived, AddressOf HandleDataChanged

    'You can set your own values on the "Tag" property
    'that allows you to identify the source later.
    item.Tag = index

    'Set a custom sampling interval on the 
    'monitored item.
    item.SamplingInterval = 200

    'Add the item to the subscription.
    subscription.AddMonitoredItem(item)
Next

'After adding the items (or configuring the subscription), apply the changes.
subscription.ApplyChanges()
```

Der zugehörige Handler:

```csharp
private static void HandleDataChanged(
        object sender,
        OpcDataChangeReceivedEventArgs e)
{
    // The tag property contains the previously set value.
    OpcMonitoredItem item = (OpcMonitoredItem)sender;
    
    Console.WriteLine(
            "Data Change from Index {0}: {1}",
            item.Tag,
            e.Item.Value);
}
```

```vbnet
Private Shared Sub HandleDataChanged( _
        ByVal sender As Object, _
        ByVal e As OpcDataChangeReceivedEventArgs)
    'The tag property contains the previously set value.
    Dim item As OpcMonitoredItem = CType(sender, OpcMonitoredItem)

    Console.WriteLine( _
        "Data Change from Index {0}: {1}", _
        item.Tag,
        e.Item.Value)
End Sub

```

## Benachrichtigungen behandeln

Beteiligte Typen: `OpcClient`, `OpcSubscription`, `OpcMonitoredItem`, `OpcDataChangeReceivedEventArgs`, `OpcEventReceivedEventArgs`, `OpcNotificationFailedEventArgs`

Eine Benachrichtigung über eine Wertänderung oder ein Ereignis durchläuft die Handler in einer festen Reihenfolge. Zuerst erhält sie der Handler des MonitoredItems, zum Beispiel der beim Aufruf von `SubscribeDataChange` angegebene Handler. Danach folgen das Ereignis `DataChangeReceived` beziehungsweise `EventReceived` der Klasse `OpcSubscription` und schließlich das gleichnamige Ereignis der Klasse `OpcClient`. Der Client reicht die Benachrichtigung nur weiter, solange kein Handler die Eigenschaft `Handled` der Ereignisargumente auf `true` setzt:

```csharp
OpcSubscription subscription = client.SubscribeDataChange(
        "ns=2;s=Machine/IsRunning",
        HandleItemDataChanged);
 
subscription.DataChangeReceived += HandleSubscriptionDataChanged;
client.DataChangeReceived += HandleClientDataChanged;
...
private static void HandleItemDataChanged(
        object sender,
        OpcDataChangeReceivedEventArgs e)
{
    if (e.Item.Value.Status.IsGood) {
        // Your code to process the value.
 
        // The handlers of the subscription and the client are not called.
        e.Handled = true;
    }
}
 
private static void HandleSubscriptionDataChanged(
        object sender,
        OpcDataChangeReceivedEventArgs e)
{
    // Called for notifications not handled by the monitored item.
}
 
private static void HandleClientDataChanged(
        object sender,
        OpcDataChangeReceivedEventArgs e)
{
    // Called for notifications not handled by the monitored item or the subscription.
}
```

```vbnet
Dim subscription As OpcSubscription = client.SubscribeDataChange( _
        "ns=2;s=Machine/IsRunning", _
        AddressOf HandleItemDataChanged)

AddHandler subscription.DataChangeReceived, AddressOf HandleSubscriptionDataChanged
AddHandler client.DataChangeReceived, AddressOf HandleClientDataChanged
...
Private Shared Sub HandleItemDataChanged( _
        ByVal sender As Object, _
        ByVal e As OpcDataChangeReceivedEventArgs)
    If e.Item.Value.Status.IsGood Then
        'Your code to process the value.

        'The handlers of the subscription and the client are not called.
        e.Handled = True
    End If
End Sub

Private Shared Sub HandleSubscriptionDataChanged( _
        ByVal sender As Object, _
        ByVal e As OpcDataChangeReceivedEventArgs)
    'Called for notifications not handled by the monitored item.
End Sub

Private Shared Sub HandleClientDataChanged( _
        ByVal sender As Object, _
        ByVal e As OpcDataChangeReceivedEventArgs)
    'Called for notifications not handled by the monitored item or the subscription.
End Sub

```

Wenn der Client eine Benachrichtigungsnachricht des Servers nicht empfangen kann, erreichen deren Wertänderungen und Ereignisse die MonitoredItems nicht. Das ist zum Beispiel der Fall, wenn sich ein Wert darin nicht dekodieren lässt. Stattdessen löst der Client das Ereignis `NotificationFailed` aus. Er löst es zuerst an der Subscription aus, zu der die Nachricht gehört. Wenn es dort nicht behandelt wird, löst er es am `OpcClient` aus. Die Eigenschaft `Exception` der Ereignisargumente nennt den Grund, zum Beispiel `BadDecodingError` oder `BadEncodingLimitsExceeded`.

Der Client fordert eine fehlgeschlagene Nachricht erneut vom Server an, solange der Server sie bereithält. Das Ereignis tritt je Nachricht einmal auf, unabhängig davon, wie oft sie fehlschlägt. Wenn die Nachricht verloren ist, tritt es ein weiteres Mal mit dem Status `BadMessageNotAvailable` auf. Wenn sich eine Nachricht keiner Subscription zuordnen lässt, meldet sie nur der `OpcClient`. Die Eigenschaft `Subscription` der Ereignisargumente ist dann `null`:

```csharp
subscription.NotificationFailed += HandleSubscriptionNotificationFailed;
client.NotificationFailed += HandleClientNotificationFailed;
...
private static void HandleSubscriptionNotificationFailed(
        object sender,
        OpcNotificationFailedEventArgs e)
{
    Console.WriteLine("Notification failed: {0}", e.Exception.Code);
 
    // The OpcClient.NotificationFailed event is not raised for this failure.
    e.Handled = true;
}
 
private static void HandleClientNotificationFailed(
        object sender,
        OpcNotificationFailedEventArgs e)
{
    // The subscription is a null reference in case the message cannot be assigned to one.
    if (e.Subscription == null) {
        Console.WriteLine("Notification failed: {0}", e.Exception.Message);
    }
    else {
        Console.WriteLine(
                "Notification of subscription {0} failed: {1}",
                e.Subscription.Id,
                e.Exception.Message);
    }
}
```

```vbnet
AddHandler subscription.NotificationFailed, AddressOf HandleSubscriptionNotificationFailed
AddHandler client.NotificationFailed, AddressOf HandleClientNotificationFailed
...
Private Shared Sub HandleSubscriptionNotificationFailed( _
        ByVal sender As Object, _
        ByVal e As OpcNotificationFailedEventArgs)
    Console.WriteLine("Notification failed: {0}", e.Exception.Code)

    'The OpcClient.NotificationFailed event is not raised for this failure.
    e.Handled = True
End Sub

Private Shared Sub HandleClientNotificationFailed( _
        ByVal sender As Object, _
        ByVal e As OpcNotificationFailedEventArgs)
    'The subscription is Nothing in case the message cannot be assigned to one.
    If e.Subscription Is Nothing Then
        Console.WriteLine("Notification failed: {0}", e.Exception.Message)
    Else
        Console.WriteLine( _
                "Notification of subscription {0} failed: {1}", _
                e.Subscription.Id, _
                e.Exception.Message)
    End If
End Sub

```

Wenn das Dekodieren wegen zu großer Werte fehlschlägt, erhöhen Sie die [Grenzen der Datenübertragung](https://www.traeger.de/docs/development/opcua/client/configuration#grenzen-der-datenubertragung).

Zusätzlich löst der Client für jede empfangene Benachrichtigung das Ereignis `OpcClient.NotificationReceived` aus. Das gilt auch für Benachrichtigungen ohne bestimmtes MonitoredItem: Wenn das PublishingInterval einer Subscription abgelaufen ist und der Server zwischenzeitlich keine Benachrichtigung gesendet hat, fordert der Client automatisch eine Aktualisierung an, und der Server antwortet mit einer leeren Benachrichtigung. Meist müssen Sie dieses Ereignis nicht behandeln, weil Sie die relevanten Benachrichtigungen über die Handler der MonitoredItems verarbeiten.

## Subscriptions verwalten

Beteiligte Typen: `OpcClient`, `OpcSubscription`, `OpcSubscriptionSetup`, `OpcMonitoredItem`, `OpcMonitoringMode`, `OpcMonitoredItemQueueMode`

**Subscription in einem Schritt anlegen**: Über eine Instanz der Klasse `OpcSubscriptionSetup` legen Sie die MonitoredItems, die Eigenschaften und die Handler der Subscription fest. Die Methode `CreateSubscription` legt die Subscription dann zusammen mit ihren MonitoredItems auf dem Server an. Wenn der Client nicht verbunden ist, löst `CreateSubscription` ab Version 2.59 eine `OpcException` mit dem Status `BadServerNotConnected` aus. Ein anschließender Aufruf von `ApplyChanges` ist nicht nötig:

```csharp
var setup = new OpcSubscriptionSetup(
        new OpcMonitoredItem("ns=2;s=Machine/IsRunning", OpcAttribute.Value),
        new OpcMonitoredItem("ns=2;s=Machine/Job/Speed", OpcAttribute.Value)) {
    PublishingInterval = 1000,
    DataChangeReceived = HandleDataChanged,
    NotificationFailed = HandleNotificationFailed
};
 
// Creates the subscription including its monitored items on the server.
OpcSubscription subscription = client.CreateSubscription(setup);
```

```vbnet
Dim setup = New OpcSubscriptionSetup( _
        New OpcMonitoredItem("ns=2;s=Machine/IsRunning", OpcAttribute.Value), _
        New OpcMonitoredItem("ns=2;s=Machine/Job/Speed", OpcAttribute.Value)) With { _
    .PublishingInterval = 1000, _
    .DataChangeReceived = AddressOf HandleDataChanged, _
    .NotificationFailed = AddressOf HandleNotificationFailed _
}

'Creates the subscription including its monitored items on the server.
Dim subscription As OpcSubscription = client.CreateSubscription(setup)
```

**Bestehende Subscription steuern**: Sie können die Veröffentlichung der Benachrichtigungen anhalten und fortsetzen, die aktuellen Werte erneut anfordern und einzelne MonitoredItems deaktivieren oder entfernen. Die Methoden `StopPublishing`, `StartPublishing`, `ResendData` und `ChangeMonitoringMode` wirken direkt auf dem Server. Das Entfernen eines MonitoredItems übermittelt der Client erst mit `ApplyChanges`:

```csharp
// Pause and resume the delivery of notifications of the subscription.
subscription.StopPublishing();
subscription.StartPublishing();
 
// Request the current values of all reporting monitored items again.
subscription.ResendData();
 
// Stop the sampling of a single monitored item and remove it afterwards.
subscription.ChangeMonitoringMode(OpcMonitoringMode.Disabled, item);
subscription.RemoveMonitoredItem(item);
 
// Always call apply changes after modifying the monitored items.
subscription.ApplyChanges();
```

```vbnet
'Pause and resume the delivery of notifications of the subscription.
subscription.StopPublishing()
subscription.StartPublishing()

'Request the current values of all reporting monitored items again.
subscription.ResendData()

'Stop the sampling of a single monitored item and remove it afterwards.
subscription.ChangeMonitoringMode(OpcMonitoringMode.Disabled, item)
subscription.RemoveMonitoredItem(item)

'Always call apply changes after modifying the monitored items.
subscription.ApplyChanges()
```

**Warteschlange eines MonitoredItems konfigurieren**: Wenn mehr Wertänderungen auftreten, als je Veröffentlichung übertragen werden, puffert der Server sie in der Warteschlange des MonitoredItems (`QueueSize`, standardmäßig 1000). Wenn die Warteschlange voll ist, verwirft der Server standardmäßig den ältesten Wert (`OpcMonitoredItemQueueMode.DiscardOldest`):

```csharp
var item = new OpcMonitoredItem("ns=2;s=Machine/Job/Speed", OpcAttribute.Value);
 
item.SamplingInterval = 100;    // 0 = fastest rate, -1 = publishing interval
item.QueueSize = 10;            // Default: 1000
item.QueueMode = OpcMonitoredItemQueueMode.DiscardOldest;
 
subscription.AddMonitoredItem(item);
subscription.ApplyChanges();
```

```vbnet
Dim item = New OpcMonitoredItem("ns=2;s=Machine/Job/Speed", OpcAttribute.Value)

item.SamplingInterval = 100     '0 = fastest rate, -1 = publishing interval
item.QueueSize = 10             'Default: 1000
item.QueueMode = OpcMonitoredItemQueueMode.DiscardOldest

subscription.AddMonitoredItem(item)
subscription.ApplyChanges()
```

## Änderungsfilter festlegen

Beteiligte Typen: `OpcClient`, `OpcNodeId`, `OpcSubscribeDataChange`, `OpcDataChangeFilter`, `OpcDataChangeTrigger`, `OpcDeadbandType`, `OpcSubscription`, `OpcMonitoredItem`, `OpcMonitoredItemCollection`, `OpcDataChangeReceivedEventHandler`, `OpcDataChangeReceivedEventArgs`, `OpcNotification`

Über eine Subscription erhält eine Clientanwendung grundsätzlich nur Benachrichtigungen über Änderungen des Status und des Werts eines Nodes. Damit der Server den Client nach anderen Regeln benachrichtigt, legen Sie den Auslöser (engl. Trigger) einer Benachrichtigung fest. Den Auslöser bestimmen Sie über einen Wert der Enumeration `OpcDataChangeTrigger`. Die folgenden Beispiele abonnieren so Benachrichtigungen über Änderungen des Status, des Werts oder des Zeitstempels eines Nodes.

**Wert- und Zeitstempeländerungen eines Nodes abonnieren**:

```csharp
OpcSubscription subscription = client.SubscribeDataChange(
        "ns=2;s=Machine/IsRunning",
        OpcDataChangeTrigger.StatusValueTimestamp,
        HandleDataChanged);
```

```vbnet
Dim subscription As OpcSubscription = client.SubscribeDataChange( _
        "ns=2;s=Machine/IsRunning", _
        OpcDataChangeTrigger.StatusValueTimestamp, _
        AddressOf HandleDataChanged)
```

**Wert- und Zeitstempeländerungen mehrerer Nodes abonnieren**:

```csharp
OpcSubscribeDataChange[] commands = new OpcSubscribeDataChange[] {
    new OpcSubscribeDataChange(
            "ns=2;s=Machine/IsRunning",
            OpcDataChangeTrigger.StatusValueTimestamp,
            HandleDataChanged),
    new OpcSubscribeDataChange(
            "ns=2;s=Machine/Job/Speed",
            OpcDataChangeTrigger.StatusValueTimestamp,
            HandleDataChanged)
};
 
OpcSubscription subscription = client.SubscribeNodes(commands);
```

```vbnet
Dim commands As OpcSubscribeDataChange() = New OpcSubscribeDataChange() { _
    New OpcSubscribeDataChange( _
            "ns=2;s=Machine/IsRunning", _
            OpcDataChangeTrigger.StatusValueTimestamp, _
            AddressOf HandleDataChanged), _
    New OpcSubscribeDataChange( _
            "ns=2;s=Machine/Job/Speed", _
            OpcDataChangeTrigger.StatusValueTimestamp, _
            AddressOf HandleDataChanged) _
}

Dim subscription As OpcSubscription = client.SubscribeNodes(commands)
```

**Benachrichtigungen über Wert- und Zeitstempeländerungen behandeln**:

```csharp
private static void HandleDataChanged(
        object sender,
        OpcDataChangeReceivedEventArgs e)
{
    // Your code to execute on each data change.
    // The 'sender' variable contains the OpcMonitoredItem with the NodeId.
    OpcMonitoredItem item = (OpcMonitoredItem)sender;

    Console.WriteLine(
            "Data Change from NodeId '{0}': {1} at {2}",
            item.NodeId,
            e.Item.Value,
            e.Item.Value.SourceTimestamp);
}
```

```vbnet
Private Shared Sub HandleDataChanged( _
        ByVal sender As Object, _
        ByVal e As OpcDataChangeReceivedEventArgs)
    'Your code to execute on each data change.
    'The 'sender' variable contains the OpcMonitoredItem with the NodeId.
    Dim item As OpcMonitoredItem = CType(sender, OpcMonitoredItem)

    Console.WriteLine( _
            "Data Change from NodeId '{0}': {1} at {2}", _
            item.NodeId, _
            e.Item.Value, _
            e.Item.Value.SourceTimestamp)
End Sub

```

Den Wert von `OpcDataChangeTrigger` aus den vorangegangenen Beispielen können Sie ebenso über eine Instanz der Klasse `OpcDataChangeFilter` festlegen. So verwenden Sie einen einmal definierten Filter für mehrere Subscriptions und MonitoredItems:

```csharp
var filter = new OpcDataChangeFilter();
filter.Trigger = OpcDataChangeTrigger.StatusValueTimestamp;
 
OpcSubscription subscriptionA = client.SubscribeDataChange(
        "ns=2;s=Machine/IsRunning",
        filter,
        HandleDataChanged);
 
// or
 
OpcSubscribeDataChange[] commands = new OpcSubscribeDataChange[] {
    new OpcSubscribeDataChange(
            "ns=2;s=Machine/IsRunning",
            filter,
            HandleDataChanged),
    new OpcSubscribeDataChange(
            "ns=2;s=Machine/Job/Speed",
            filter,
            HandleDataChanged)
};
 
OpcSubscription subscriptionB = client.SubscribeNodes(commands);
```

```vbnet
Dim filter = New OpcDataChangeFilter()
filter.Trigger = OpcDataChangeTrigger.StatusValueTimestamp

Dim subscriptionA As OpcSubscription = client.SubscribeDataChange( _
        "ns=2;s=Machine/IsRunning", _
        filter, _
        AddressOf HandleDataChanged)

'or

Dim commands As OpcSubscribeDataChange() = New OpcSubscribeDataChange() { _
    New OpcSubscribeDataChange( _
            "ns=2;s=Machine/IsRunning", _
            filter, _
            AddressOf HandleDataChanged), _
    New OpcSubscribeDataChange( _
            "ns=2;s=Machine/Job/Speed", _
            filter, _
            AddressOf HandleDataChanged) _
}

Dim subscriptionB As OpcSubscription = client.SubscribeNodes(commands)
```

**Nur Wertänderungen ab einer bestimmten Größe melden**: Die Eigenschaften `DeadbandType` und `DeadbandValue` der Klasse `OpcDataChangeFilter` legen fest, ab welcher Wertänderung der Server benachrichtigt. Mit `OpcDeadbandType.Absolute` gibt `DeadbandValue` die absolute Änderung an, mit `OpcDeadbandType.Percent` die prozentuale Änderung bezogen auf den Wertebereich des Nodes (`EngineeringUnitRange`). Ob ein Server einen prozentualen Deadband unterstützt, hängt vom Server und vom Node ab:

```csharp
// Report only changes of the value which exceed 0.5 (absolute).
var filter = new OpcDataChangeFilter(OpcDataChangeTrigger.StatusValue) {
    DeadbandType = OpcDeadbandType.Absolute,
    DeadbandValue = 0.5
};
 
OpcSubscription subscription = client.SubscribeDataChange(
        "ns=2;s=Machine/Job/Speed",
        filter,
        HandleDataChanged);
```

```vbnet
'Report only changes of the value which exceed 0.5 (absolute).
Dim filter = New OpcDataChangeFilter(OpcDataChangeTrigger.StatusValue) With { _
    .DeadbandType = OpcDeadbandType.Absolute, _
    .DeadbandValue = 0.5 _
}

Dim subscription As OpcSubscription = client.SubscribeDataChange( _
        "ns=2;s=Machine/Job/Speed", _
        filter, _
        AddressOf HandleDataChanged)
```
