# Read and write values

> How-to guide: Read and write the values of single and multiple nodes, access other attributes and parts of arrays, and evaluate the results with their status.

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

**Sample applications:**

- 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)): This sample demonstrates how to implement a primitive OPC UA client.
- 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)): This sample demonstrates how to implement a primitive OPC UA server.

## Read values

Types used: `OpcClient`, `OpcNodeId`, `OpcValue`, `OpcAttribute`, `OpcReadNode`

The NodeId (`OpcNodeId`) defines which node the client reads. By default, the client always reads the current value of the Value attribute. Besides the value, the `OpcValue` that the client reads contains two timestamps. `SourceTimestamp` states when the source determined the value. `ServerTimestamp` states when the server recorded the value. To read another attribute of the node, pass the corresponding `OpcAttribute` when you call `ReadNode` or in the respective `OpcReadNode` instance.

**Read the value of the Value attribute of a single node**:

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

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

**Read the values of the Value attribute of multiple nodes**:

```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)
```

**Read the value of the DisplayName attribute of a single node**:

```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)
```

**Read the values of the DisplayName attribute of multiple nodes**:

```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)
```

## Write values

Types used: `OpcClient`, `OpcNodeId`, `OpcValue`, `OpcAttribute`, `OpcStatus`, `OpcStatusCollection`, `OpcWriteNode`

The NodeId (`OpcNodeId`) defines which node the client writes. By default, the client always writes the current value of the Value attribute. By default, the client sends the timestamp of the `OpcValue` you pass as the source timestamp (`SourceTimestamp`). If you pass a plain value, it sends no timestamp. The section [Timestamps when writing](https://www.traeger.de/en/docs/development/opcua/client/read-write-values#timestamps-when-writing) shows how to change this. To write another attribute of the node, pass the corresponding `OpcAttribute` when you call `WriteNode` or in the respective `OpcWriteNode` instance.

**Write the value of a single node**:

```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)
```

**Write the values of multiple nodes**:

```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)
```

**Write the value of the DisplayName attribute of a single node**:

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

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

**Write the values of the DisplayName attribute of multiple nodes**:

```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)
```

## Timestamps when writing

Types used: `OpcClient`, `OpcTimestampSource`

Some servers reject writing a value with the status `BadWriteNotSupported` if the timestamp sent does not meet their expectations. In that case, the client automatically retries the write with the other values of the `OpcTimestampSource` enumeration in turn (`MinValue`, `UtcNow`, `Now`, and `MaxValue`). It uses the first value the server accepts for all further writes of the session. To set the timestamp yourself instead, specify the source like this:

```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
```

## Read and write parts of arrays

Types used: `OpcClient`, `OpcNodeId`, `OpcRange`, `OpcValue`, `OpcStatus`

If a node provides an array as its value, the client does not always have to read or write the entire array. An instance of the `OpcRange` class defines which elements of the array the client reads or writes. In OPC UA, this range is called IndexRange. The first value is the index of the first element, the second value the index of the last element.

**Read a part of an array**:

```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))
```

**Write a part of an array**:

```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})
```

For multidimensional arrays, pass a further `OpcRange` instance for each dimension (`OpcRange(start, end, next)`). Whether a server supports reading and writing parts of arrays depends on the server.

## Evaluate the values read

Types used: `OpcClient`, `OpcNodeId`, `OpcValue`, `OpcStatus`, `OpcStatusCollection`

The `ReadNode` methods always return an `OpcValue`. The `ReadNodes` methods return a collection with one `OpcValue` per node read. The value read is in the `Value` property. You check the result of the read request with the `Status` property. The `SourceTimestamp` property returns the timestamp at which the source determined the value. The `ServerTimestamp` property returns the timestamp at which the server recorded the value.

**Read the value of a single node**:

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

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

**Check the result of the read request**:

```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
```

**Get the scalar value of the `OpcValue`**:

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

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

**Get the array value of the `OpcValue`**:

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

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

The `WriteNode` methods always return an `OpcStatus`. The `WriteNodes` methods return an `OpcStatusCollection` that contains one `OpcStatus` for each node written. You check the result of the write request with the properties of the individual `OpcStatus` objects.

**Write the scalar value of a single node**:

```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)
```

**Write the array value of a single node**:

```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)
```

**Check the result of the write request**:

```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
```

If you only change single elements, writing a part of the array is usually easier (section [Read and write parts of arrays](https://www.traeger.de/en/docs/development/opcua/client/read-write-values#read-and-write-parts-of-arrays)). With the steps shown for scalar values and array values, you change the array value of a variable node like this:

```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
```

## Read and write typed values

Types used: `OpcClient`, `OpcNodeValue`, `OpcStatus`

The `OpcNodeValue<T>` class combines the identifier of a node with the .NET type of its value. `Read` returns the value directly as `T`, without converting it through `OpcValue.Value`. `Write` writes a value of this type. If the value read has a bad status, `Read` returns the default value of `T`. The object holds the status in its `Status` property. Check it before you use the value:

```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
```

The client reads multiple typed values with a single call to `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)
```

## Multidimensional arrays

Types used: `OpcClient`, `OpcValue`

The client returns values with more than one dimension (a matrix in OPC UA) as a multidimensional .NET array, for example `double[,]`. It also writes a multidimensional array as a matrix. You do not have to manage the dimensions separately:

```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)
```

## Address nodes by their namespace URI

Types used: `OpcClient`, `OpcNodeId`, `OpcNamespace`

The namespace index in `ns=2;s=...` is only a reference into the namespace table of the server. It can differ between servers and change after a server restart. The URI of the namespace, in contrast, is stable. An identifier with `nsu=` instead of `ns=` names the URI. The client resolves it against the current namespace table of the session only when it sends the request, also after a reconnect:

```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")
```

Alternatively, `client.Namespaces.ResolveIndex` returns the current index of a namespace URI, or `-1` if the server does not know the namespace:

```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
```

## Register frequently read nodes

Types used: `OpcClient`, `OpcNodeId`, `OpcNodeIdCollection`, `OpcReadNode`

If a client reads or writes the same nodes again and again, it can register them with the server. `RegisterNodes` returns an identifier for each node that the server can optimize for repeated access. Whether and how much it does so depends on the server. The registered identifiers are valid only in the session in which the client registered them. After a new session, register them again (section [What to do after a new session](https://www.traeger.de/en/docs/development/opcua/client/long-running-clients#what-to-do-after-a-new-session)). When you no longer need the identifiers, `UnregisterNodes` releases them:

```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)
```
