# Werte lesen und schreiben

> Anleitung: Werte einzelner und mehrerer Nodes lesen und schreiben, andere Attribute und Teile von Arrays ansprechen und Ergebnisse mit Status auswerten.

**Beispielanwendungen:**

- Client ([C#](https://github.com/Traeger-GmbH/opcuanet-samples/tree/master/cs/Basic/Client), [VB.NET](https://github.com/Traeger-GmbH/opcuanet-samples/tree/master/vb/Basic/Client)): Verbindet sich mit einem Server, liest einen Node, schreibt einen neuen Wert und liest ihn erneut.
- Server ([C#](https://github.com/Traeger-GmbH/opcuanet-samples/tree/master/cs/Basic/Server), [VB.NET](https://github.com/Traeger-GmbH/opcuanet-samples/tree/master/vb/Basic/Server)): Stellt mit einem eigenen Node-Manager eine Maschine mit einigen Nodes bereit.

## Werte lesen

Beteiligte Typen: `OpcClient`, `OpcNodeId`, `OpcValue`, `OpcAttribute`, `OpcReadNode`

Die NodeId (`OpcNodeId`) legt fest, welchen Node der Client liest. Standardmäßig liest der Client immer den aktuellen Wert des Value-Attributs. Der gelesene `OpcValue` enthält neben dem Wert zwei Zeitstempel. `SourceTimestamp` gibt an, wann die Quelle den Wert festgestellt hat. `ServerTimestamp` gibt an, wann der Server den Wert erfasst hat. Wenn Sie ein anderes Attribut des Nodes lesen, geben Sie das passende `OpcAttribute` beim Aufruf von `ReadNode` oder in der jeweiligen Instanz von `OpcReadNode` an.

**Den Wert des Value-Attributs eines einzelnen Nodes lesen**:

```csharp
OpcValue isRunning = client.ReadNode("ns=2;s=Machine/IsRunning");
```

```vbnet
Dim isRunning As OpcValue = client.ReadNode("ns=2;s=Machine/IsRunning")
```

**Die Werte des Value-Attributs mehrerer Nodes lesen**:

```csharp
OpcReadNode[] commands = new OpcReadNode[] {
    new OpcReadNode("ns=2;s=Machine/Job/Number"),
    new OpcReadNode("ns=2;s=Machine/Job/Name"),
    new OpcReadNode("ns=2;s=Machine/Job/Speed")
};

IEnumerable<OpcValue> job = client.ReadNodes(commands);
```

```vbnet
Dim commands As OpcReadNode() = New OpcReadNode() { _
    New OpcReadNode("ns=2;s=Machine/Job/Number"), _
    New OpcReadNode("ns=2;s=Machine/Job/Name"), _
    New OpcReadNode("ns=2;s=Machine/Job/Speed") _
}

Dim job As IEnumerable(Of OpcValue) = client.ReadNodes(commands)
```

**Den Wert des DisplayName-Attributs eines einzelnen Nodes lesen**:

```csharp
OpcValue isRunningDisplayName = client.ReadNode("ns=2;s=Machine/IsRunning", OpcAttribute.DisplayName);
```

```vbnet
Dim isRunningDisplayName As OpcValue = client.ReadNode("ns=2;s=Machine/IsRunning", OpcAttribute.DisplayName)
```

**Die Werte des DisplayName-Attributs mehrerer Nodes lesen**:

```csharp
OpcReadNode[] commands = new OpcReadNode[] {
    new OpcReadNode("ns=2;s=Machine/Job/Number", OpcAttribute.DisplayName),
    new OpcReadNode("ns=2;s=Machine/Job/Name", OpcAttribute.DisplayName),
    new OpcReadNode("ns=2;s=Machine/Job/Speed", OpcAttribute.DisplayName)
};
 
IEnumerable<OpcValue> jobDisplayNames = client.ReadNodes(commands);
```

```vbnet
Dim commands As OpcReadNode() = New OpcReadNode() { _
    New OpcReadNode("ns=2;s=Machine/Job/Number", OpcAttribute.DisplayName), _
    New OpcReadNode("ns=2;s=Machine/Job/Name", OpcAttribute.DisplayName), _
    New OpcReadNode("ns=2;s=Machine/Job/Speed", OpcAttribute.DisplayName) _
}

Dim jobDisplayNames As IEnumerable(Of OpcValue) = client.ReadNodes(commands)
```

## Werte schreiben

Beteiligte Typen: `OpcClient`, `OpcNodeId`, `OpcValue`, `OpcAttribute`, `OpcStatus`, `OpcStatusCollection`, `OpcWriteNode`

Die NodeId (`OpcNodeId`) legt fest, welchen Node der Client schreibt. Standardmäßig schreibt der Client immer den aktuellen Wert des Value-Attributs. Als Zeitstempel der Quelle (`SourceTimestamp`) sendet der Client standardmäßig den Zeitstempel des übergebenen `OpcValue`. Wenn Sie nur einen Wert übergeben, sendet er keinen Zeitstempel. Wie sich das ändern lässt, zeigt der Abschnitt [Zeitstempel beim Schreiben](https://www.traeger.de/docs/development/opcua/client/read-write-values#zeitstempel-beim-schreiben). Wenn Sie ein anderes Attribut des Nodes schreiben, geben Sie das passende `OpcAttribute` beim Aufruf von `WriteNode` oder in der jeweiligen Instanz von `OpcWriteNode` an.

**Den Wert eines einzelnen Nodes schreiben**:

```csharp
OpcStatus result = client.WriteNode("ns=2;s=Machine/Job/Cancel", true);
```

```vbnet
Dim result As OpcStatus = client.WriteNode("ns=2;s=Machine/Job/Cancel", True)
```

**Die Werte mehrerer Nodes schreiben**:

```csharp
OpcWriteNode[] commands = new OpcWriteNode[] {
    new OpcWriteNode("ns=2;s=Machine/Job/Number", "0002"),
    new OpcWriteNode("ns=2;s=Machine/Job/Name", "MAN_F01_78910"),
    new OpcWriteNode("ns=2;s=Machine/Job/Speed", 1220.5)
};
 
OpcStatusCollection results = client.WriteNodes(commands);
```

```vbnet
Dim commands As OpcWriteNode() = New OpcWriteNode() { _
    New OpcWriteNode("ns=2;s=Machine/Job/Number", "0002"), _
    New OpcWriteNode("ns=2;s=Machine/Job/Name", "MAN_F01_78910"), _
    New OpcWriteNode("ns=2;s=Machine/Job/Speed", 1220.5) _
}

Dim results As OpcStatusCollection = client.WriteNodes(commands)
```

**Den Wert des DisplayName-Attributs eines einzelnen Nodes schreiben**:

```csharp
client.WriteNode("ns=2;s=Machine/IsRunning", OpcAttribute.DisplayName, "IsActive");
```

```vbnet
client.WriteNode("ns=2;s=Machine/IsRunning", OpcAttribute.DisplayName, "IsActive")
```

**Die Werte des DisplayName-Attributs mehrerer Nodes schreiben**:

```csharp
OpcWriteNode[] commands = new OpcWriteNode[] {
    new OpcWriteNode("ns=2;s=Machine/Job/Number", OpcAttribute.DisplayName, "Serial"),
    new OpcWriteNode("ns=2;s=Machine/Job/Name", OpcAttribute.DisplayName, "Description"),
    new OpcWriteNode("ns=2;s=Machine/Job/Speed", OpcAttribute.DisplayName, "Rotations per Second")
};
 
OpcStatusCollection results = client.WriteNodes(commands);
```

```vbnet
Dim commands As OpcWriteNode() = New OpcWriteNode() { _
    New OpcWriteNode("ns=2;s=Machine/Job/Number", OpcAttribute.DisplayName, "Serial"), _
    New OpcWriteNode("ns=2;s=Machine/Job/Name", OpcAttribute.DisplayName, "Description"), _
    New OpcWriteNode("ns=2;s=Machine/Job/Speed", OpcAttribute.DisplayName, "Rotations per Second") _
}

Dim results As OpcStatusCollection = client.WriteNodes(commands)
```

## Zeitstempel beim Schreiben

Beteiligte Typen: `OpcClient`, `OpcTimestampSource`

Manche Server lehnen das Schreiben eines Werts mit dem Status `BadWriteNotSupported` ab, wenn der mitgesendete Zeitstempel nicht ihren Erwartungen entspricht. In diesem Fall versucht der Client das Schreiben automatisch nacheinander mit den übrigen Werten der Enumeration `OpcTimestampSource` (`MinValue`, `UtcNow`, `Now` und `MaxValue`). Den ersten Wert, den der Server akzeptiert, verwendet er für alle weiteren Schreibvorgänge der Sitzung. Wenn Sie den Zeitstempel stattdessen fest vorgeben möchten, legen Sie die gewünschte Quelle so fest:

```csharp
// Always send the current time as the source timestamp of the values to write.
client.Services.WriteNodes.TimestampSource = OpcTimestampSource.UtcNow;
```

```vbnet
'Always send the current time as the source timestamp of the values to write.
client.Services.WriteNodes.TimestampSource = OpcTimestampSource.UtcNow
```

## Teilbereiche von Arrays lesen und schreiben

Beteiligte Typen: `OpcClient`, `OpcNodeId`, `OpcRange`, `OpcValue`, `OpcStatus`

Wenn ein Node ein Array als Wert bereitstellt, muss der Client nicht immer das gesamte Array lesen oder schreiben. Eine Instanz der Klasse `OpcRange` legt fest, welche Elemente des Arrays der Client liest oder schreibt. In OPC UA heißt dieser Bereich IndexRange. Der erste Wert gibt den Index des ersten Elements an, der zweite Wert den Index des letzten Elements.

**Einen Teilbereich eines Arrays lesen**:

```csharp
// Read the elements with the index 2 to 4 of the array.
OpcValue speeds = client.ReadNode(
        "ns=2;s=Machine/Job/Speeds",
        new OpcRange(start: 2, end: 4));
```

```vbnet
'Read the elements with the index 2 to 4 of the array.
Dim speeds As OpcValue = client.ReadNode( _
        "ns=2;s=Machine/Job/Speeds", _
        New OpcRange(start:=2, [end]:=4))
```

**Einen Teilbereich eines Arrays schreiben**:

```csharp
// Write the elements with the index 2 to 4 of the array.
OpcStatus result = client.WriteNode(
        "ns=2;s=Machine/Job/Speeds",
        new OpcRange(start: 2, end: 4),
        new int[] { 1200, 1300, 1400 });
```

```vbnet
'Write the elements with the index 2 to 4 of the array.
Dim result As OpcStatus = client.WriteNode( _
        "ns=2;s=Machine/Job/Speeds", _
        New OpcRange(start:=2, [end]:=4), _
        New Integer() {1200, 1300, 1400})
```

Für mehrdimensionale Arrays geben Sie je Dimension eine weitere Instanz von `OpcRange` an (`OpcRange(start, end, next)`). Ob ein Server das Lesen und Schreiben von Teilbereichen unterstützt, hängt vom jeweiligen Server ab.

## Gelesene Werte auswerten

Beteiligte Typen: `OpcClient`, `OpcNodeId`, `OpcValue`, `OpcStatus`, `OpcStatusCollection`

Die Methoden `ReadNode` liefern immer einen `OpcValue`. Die Methoden `ReadNodes` liefern eine Auflistung mit einem `OpcValue` je gelesenem Node. Der gelesene Wert steht in der Eigenschaft `Value`. Das Ergebnis der Leseanforderung prüfen Sie über die Eigenschaft `Status`. Die Eigenschaft `SourceTimestamp` liefert den Zeitstempel, zu dem die Quelle den Wert festgestellt hat. Die Eigenschaft `ServerTimestamp` liefert den Zeitstempel, zu dem der Server den Wert erfasst hat.

**Den Wert eines einzelnen Nodes lesen**:

```csharp
OpcValue value = client.ReadNode("ns=2;s=Machine/Job/Speed");
```

```vbnet
Dim value As OpcValue = client.ReadNode("ns=2;s=Machine/Job/Speed")
```

**Das Ergebnis der Leseanforderung prüfen**:

```csharp
if (value.Status.IsGood) {
    // Your code to operate on the value.
}
```

```vbnet
If value.Status.IsGood Then
    'Your code to operate on the value.
End If
```

**Den skalaren Wert des `OpcValue` abrufen**:

```csharp
int intValue = (int)value.Value;
```

```vbnet
Dim intValue As Integer = CInt(value.Value)
```

**Den Arraywert des `OpcValue` abrufen**:

```csharp
int[] intValues = (int[])value.Value;
```

```vbnet
Dim intValues As Integer() = CType(value.Value, Integer())
```

Die Methoden `WriteNode` liefern immer einen `OpcStatus`. Die Methoden `WriteNodes` liefern eine `OpcStatusCollection`, die für jeden geschriebenen Node einen `OpcStatus` enthält. Das Ergebnis der Schreibanforderung prüfen Sie über die Eigenschaften der einzelnen `OpcStatus`.

**Den skalaren Wert eines einzelnen Nodes schreiben**:

```csharp
OpcStatus status = client.WriteNode("ns=2;s=Machine/Job/Speed", 1200);
```

```vbnet
Dim status As OpcStatus = client.WriteNode("ns=2;s=Machine/Job/Speed", 1200)
```

**Den Arraywert eines einzelnen Nodes schreiben**:

```csharp
int[] values = new int[3] { 1200, 1350, 1780 };
OpcStatus status = client.WriteNode("ns=2;s=Machine/Job/Speeds", values);
```

```vbnet
Dim values As Integer() = New Integer(2) {1200, 1350, 1780}
Dim status As OpcStatus = client.WriteNode("ns=2;s=Machine/Job/Speeds", values)
```

**Das Ergebnis der Schreibanforderung prüfen**:

```csharp
if (!status.IsGood) {
    // Your code to handle a failed write operation.
}
```

```vbnet
If Not status.IsGood Then
    'Your code to handle a failed write operation.
End If
```

Wenn Sie nur einzelne Elemente ändern, ist das Schreiben eines Teilbereichs meist einfacher (Abschnitt [Teilbereiche von Arrays lesen und schreiben](https://www.traeger.de/docs/development/opcua/client/read-write-values#teilbereiche-von-arrays-lesen-und-schreiben)). Mit den gezeigten Schritten für skalare Werte und Arraywerte ändern Sie den Arraywert eines Variablen-Nodes so:

```csharp
using (var client = new OpcClient("opc.tcp://localhost:4840")) {
    client.Connect();
    OpcValue arrayValue = client.ReadNode("ns=2;s=Machine/Job/Speeds");
 
    if (arrayValue.Status.IsGood) {
        int[] intArrayValue = (int[])arrayValue.Value;
 
        intArrayValue[2] = 100;
        intArrayValue[4] = 200;
        intArrayValue[9] = 300;
 
        OpcStatus status = client.WriteNode("ns=2;s=Machine/Job/Speeds", intArrayValue);
 
        if (!status.IsGood)
            Console.WriteLine("Failed to write array value!");
    }
}
```

```vbnet
Using client As New OpcClient("opc.tcp://localhost:4840")
    client.Connect()
    Dim arrayValue As OpcValue = client.ReadNode("ns=2;s=Machine/Job/Speeds")

    If arrayValue.Status.IsGood Then
        Dim intArrayValue As Integer() = CType(arrayValue.Value, Integer())

        intArrayValue(2) = 100
        intArrayValue(4) = 200
        intArrayValue(9) = 300

        Dim status As OpcStatus = client.WriteNode("ns=2;s=Machine/Job/Speeds", intArrayValue)

        If Not status.IsGood Then _
            Console.WriteLine("Failed to write array value!")
    End If
End Using
```

## Werte typisiert lesen und schreiben

Beteiligte Typen: `OpcClient`, `OpcNodeValue`, `OpcStatus`

Die Klasse `OpcNodeValue<T>` verbindet den Identifier eines Nodes mit dem .NET-Typ seines Werts. `Read` liefert den Wert direkt als `T`, ohne Umwandlung über `OpcValue.Value`. `Write` schreibt einen Wert dieses Typs. Wenn der gelesene Wert einen schlechten Status hat, liefert `Read` den Standardwert von `T`. Den Status hält das Objekt in seiner Eigenschaft `Status`. Prüfen Sie ihn deshalb, bevor Sie den Wert verwenden:

```csharp
var speed = new OpcNodeValue<double>("ns=2;s=Machine/Speed");
 
double current = speed.Read(client);
 
if (speed.Status.IsGood) {
    OpcStatus result = speed.Write(client, current + 10);
}
```

```vbnet
Dim speed = New OpcNodeValue(Of Double)("ns=2;s=Machine/Speed")

Dim current As Double = speed.Read(client)

If speed.Status.IsGood Then
    Dim result As OpcStatus = speed.Write(client, current + 10)
End If
```

Mehrere typisierte Werte liest der Client mit einem einzigen Aufruf von `ReadNodes`:

```csharp
var temperatures = new OpcNodeValue<double>[] {
    new OpcNodeValue<double>("ns=2;s=Machine/Temperature1"),
    new OpcNodeValue<double>("ns=2;s=Machine/Temperature2"),
    new OpcNodeValue<double>("ns=2;s=Machine/Temperature3")
};
 
double[] values = temperatures.Read(client);
```

```vbnet
Dim temperatures = New OpcNodeValue(Of Double)() { _
    New OpcNodeValue(Of Double)("ns=2;s=Machine/Temperature1"), _
    New OpcNodeValue(Of Double)("ns=2;s=Machine/Temperature2"), _
    New OpcNodeValue(Of Double)("ns=2;s=Machine/Temperature3") _
}

Dim values As Double() = temperatures.Read(client)
```

## Mehrdimensionale Arrays

Beteiligte Typen: `OpcClient`, `OpcValue`

Werte mit mehr als einer Dimension (in OPC UA eine Matrix) liefert der Client als mehrdimensionales .NET-Array, zum Beispiel `double[,]`. Ebenso schreibt er ein mehrdimensionales Array als Matrix. Sie müssen die Dimensionen nicht getrennt verwalten:

```csharp
var grid = new double[2, 3] { { 1, 2, 3 }, { 4, 5, 6 } };
client.WriteNode("ns=2;s=Machine/Grid", grid);
 
double[,] values = (double[,])client.ReadNode("ns=2;s=Machine/Grid").Value;
double value = values[1, 2];
```

```vbnet
Dim grid = New Double(1, 2) {{1, 2, 3}, {4, 5, 6}}
client.WriteNode("ns=2;s=Machine/Grid", grid)

Dim values As Double(,) = CType(client.ReadNode("ns=2;s=Machine/Grid").Value, Double(,))
Dim value As Double = values(1, 2)
```

## Nodes über die Namespace-URI adressieren

Beteiligte Typen: `OpcClient`, `OpcNodeId`, `OpcNamespace`

Der Namespace-Index in `ns=2;s=...` ist nur ein Verweis in die Namespace-Tabelle des Servers. Er kann sich je Server und nach einem Neustart des Servers ändern. Stabil ist dagegen die URI des Namespaces. Ein Identifier mit `nsu=` statt `ns=` nennt die URI. Der Client löst sie erst beim Senden der Anfrage gegen die aktuelle Namespace-Tabelle der Sitzung auf, auch nach einem Wiederverbinden:

```csharp
OpcValue speed = client.ReadNode("nsu=http://example.com/machine/;s=Machine/Speed");
```

```vbnet
Dim speed As OpcValue = client.ReadNode("nsu=http://example.com/machine/;s=Machine/Speed")
```

Alternativ liefert `client.Namespaces.ResolveIndex` den aktuellen Index einer Namespace-URI oder `-1`, wenn der Server den Namespace nicht kennt:

```csharp
int index = client.Namespaces.ResolveIndex("http://example.com/machine/");
 
if (index >= 0) {
    var temperatureId = new OpcNodeId("Machine/Temperature", namespaceIndex: index);
    OpcValue temperature = client.ReadNode(temperatureId);
}
```

```vbnet
Dim index As Integer = client.Namespaces.ResolveIndex("http://example.com/machine/")

If index >= 0 Then
    Dim temperatureId = New OpcNodeId("Machine/Temperature", namespaceIndex:=index)
    Dim temperature As OpcValue = client.ReadNode(temperatureId)
End If
```

## Häufig gelesene Nodes registrieren

Beteiligte Typen: `OpcClient`, `OpcNodeId`, `OpcNodeIdCollection`, `OpcReadNode`

Wenn ein Client dieselben Nodes immer wieder liest oder schreibt, kann er sie beim Server registrieren. `RegisterNodes` liefert für jeden Node einen Identifier, den der Server für wiederholte Zugriffe optimieren kann. Ob und wie stark er das tut, hängt vom Server ab. Die registrierten Identifier gelten nur in der Sitzung, in der der Client sie registriert hat. Nach einer neuen Sitzung registrieren Sie sie neu (Abschnitt [Was nach einer neuen Sitzung zu tun bleibt](https://www.traeger.de/docs/development/opcua/client/long-running-clients#was-nach-einer-neuen-sitzung-zu-tun-bleibt)). Wenn Sie die Identifier nicht mehr brauchen, gibt `UnregisterNodes` sie frei:

```csharp
OpcNodeIdCollection registeredNodeIds = client.RegisterNodes(
        "ns=2;s=Machine/Speed",
        "ns=2;s=Machine/Temperature");
 
var commands = new OpcReadNode[registeredNodeIds.Count];
 
for (int index = 0; index < commands.Length; index++)
    commands[index] = new OpcReadNode(registeredNodeIds[index]);
 
for (int cycle = 0; cycle < 10; cycle++)
    client.ReadNodes(commands);
 
client.UnregisterNodes(registeredNodeIds);

```

```vbnet
Dim registeredNodeIds As OpcNodeIdCollection = client.RegisterNodes( _
        "ns=2;s=Machine/Speed", _
        "ns=2;s=Machine/Temperature")

Dim commands = New OpcReadNode(registeredNodeIds.Count - 1) {}

For index As Integer = 0 To commands.Length - 1
    commands(index) = New OpcReadNode(registeredNodeIds(index))
Next

For cycle As Integer = 0 To 9
    client.ReadNodes(commands)
Next

client.UnregisterNodes(registeredNodeIds)
```
