Skip to content

How-to guide

Read and write values

Provide the values of nodes in the server and handle read and write access from clients.

This content was machine translated and may contain errors. Read in German

How-to guide

Read and write values ​

Read values ​

Types usedOpcServerOpcVariableNodeOpcDataVariableNodeOpcReadAttributeValueCallbackOpcAttributeValueOpcReadAttributeValueContextOpcReadVariableValueCallbackOpcReadVariableValueContextOpcVariableValueOpcText

An OpcNode defines its metadata through attributes. In general, all nodes provide attributes such as Name, DisplayName, or Description, while only variable nodes provide the Value attribute. By default, the node instances store the attribute values internally. If the value should come from another data source, define callback methods that provide the values. The signature of the ReadVariableValue callback method differs from the other ReadAttributeValue callback methods. For the Value attribute, the SDK expects an instance of OpcVariableValue instead of an instance of OpcAttributeValue. Besides the value itself, it contains a timestamp that states when the source determined the value (SourceTimestamp) and status information about the quality of the value. The server calls the read callbacks on every read operation in which a client reads the metadata through the Read service. When a client browses, however, the server returns the internally stored values (for example the DisplayName) without calling the callbacks. For the Description attribute, the value is of type OpcText.

Set the initial value of the Value attribute of a variable node:

csharp
var machineIsRunningNode = new OpcDataVariableNode<bool>("IsRunning", false);
vbnet
Dim machineIsRunningNode = New OpcDataVariableNode(Of Boolean)("IsRunning", False)

Set the value of the Value attribute of a variable node:

csharp
machineIsRunningNode.Value = true;
vbnet
machineIsRunningNode.Value = True

Set the value of the Description attribute:

csharp
machineIsRunningNode.Description = "My description";
vbnet
machineIsRunningNode.Description = "My description"

Notify all clients with an active subscription about the attribute changes and apply the changes:

csharp
machineIsRunningNode.ApplyChanges(server.SystemContext);
vbnet
machineIsRunningNode.ApplyChanges(server.SystemContext)

Determine the value of the Description attribute from a data source other than the internal one:

csharp
machineIsRunningNode.ReadDescriptionCallback = HandleReadDescription;
...
private OpcAttributeValue<OpcText> HandleReadDescription(
        OpcReadAttributeValueContext context,
        OpcAttributeValue<OpcText> value)
{
    return ReadDescriptionFromDataSource(context.Node) ?? value;
}
vbnet
machineIsRunningNode.ReadDescriptionCallback = AddressOf HandleReadDescription
...
Private Function HandleReadDescription( _
        ByVal context As OpcReadAttributeValueContext, _
        ByVal value As OpcAttributeValue(Of OpcText)) As OpcAttributeValue(Of OpcText)
    Return If(ReadDescriptionFromDataSource(context.Node), value)
End Function

Determine the value of the Value attribute of a variable node from a data source other than the internal one:

csharp
machineIsRunningNode.ReadVariableValueCallback = HandleReadVariableValue;
...
private OpcVariableValue<object> HandleReadVariableValue(
        OpcReadVariableValueContext context,
        OpcVariableValue<object> value)
{
    return ReadValueFromDataSource(context.Node) ?? value;
}
vbnet
machineIsRunningNode.ReadVariableValueCallback = AddressOf HandleReadVariableValue
...
Private Function HandleReadVariableValue( _
        ByVal context As OpcReadVariableValueContext, _
        ByVal value As OpcVariableValue(Of Object)) As OpcVariableValue(Of Object)
    Return If(ReadValueFromDataSource(context.Node), value)
End Function

Write values ​

Types usedOpcServerOpcVariableNodeOpcDataVariableNodeOpcWriteAttributeValueCallbackOpcAttributeValueOpcWriteAttributeValueContextOpcWriteVariableValueCallbackOpcWriteVariableValueContextOpcVariableValueOpcText

An OpcNode defines its metadata through attributes. In general, all nodes provide attributes such as Name, DisplayName, or Description, while only variable nodes provide the Value attribute. By default, the node instances store the attribute values internally. If the value should be stored in another data source, define callback methods that store the values. The signature of the WriteVariableValue callback method differs from the other WriteAttributeValue callback methods. For the Value attribute, the SDK uses an instance of OpcVariableValue instead of an instance of OpcAttributeValue. Besides the value itself, it contains a timestamp that states when the source determined the value (SourceTimestamp) and status information about the quality of the value. The server calls the write callbacks on every write operation in which a client writes metadata. This happens through the Write service.

Set the initial value of the Value attribute of a variable node:

csharp
var machineIsRunningNode = new OpcDataVariableNode<bool>("IsRunning", false);
vbnet
Dim machineIsRunningNode = New OpcDataVariableNode(Of Boolean)("IsRunning", False)

Set the value of the Value attribute of a variable node:

csharp
machineIsRunningNode.Value = true;
vbnet
machineIsRunningNode.Value = True

Set the value of the Description attribute:

csharp
machineIsRunningNode.Description = "My description";
vbnet
machineIsRunningNode.Description = "My description"

Notify all clients with an active subscription about the attribute changes and apply the changes:

csharp
machineIsRunningNode.ApplyChanges(server.SystemContext);
vbnet
machineIsRunningNode.ApplyChanges(server.SystemContext)

Store the value of the Description attribute in a data source other than the internal one:

csharp
machineIsRunningNode.WriteDescriptionCallback = HandleWriteDescription;
...
private OpcAttributeValue<OpcText> HandleWriteDescription(
        OpcWriteAttributeValueContext context,
        OpcAttributeValue<OpcText> value)
{
    return WriteDescriptionToDataSource(context.Node, value) ?? value;
}
vbnet
machineIsRunningNode.WriteDescriptionCallback = AddressOf HandleWriteDescription
...
Private Function HandleWriteDescription( _
        ByVal context As OpcWriteAttributeValueContext, _
        ByVal value As OpcAttributeValue(Of OpcText)) As OpcAttributeValue(Of OpcText)
    Return If(WriteDescriptionToDataSource(context.Node, value), value)
End Function

Store the value of the Value attribute of a variable node in a data source other than the internal one:

csharp
machineIsRunningNode.WriteVariableValueCallback = HandleWriteVariableValue;
...
private OpcVariableValue<object> HandleWriteVariableValue(
        OpcWriteVariableValueContext context,
        OpcVariableValue<object> value)
{
    return WriteValueToDataSource(context.Node, value) ?? value;
}
vbnet
machineIsRunningNode.WriteVariableValueCallback = AddressOf HandleWriteVariableValue
...
Private Function HandleWriteVariableValue( _
        ByVal context As OpcWriteVariableValueContext, _
        ByVal value As OpcVariableValue(Of Object)) As OpcVariableValue(Of Object)
    Return If(WriteValueToDataSource(context.Node, value), value)
End Function

Sample values only on demand ​

Types usedOpcNodeManagerOpcMonitoredItemOpcMonitoredItemEventArgsOpcAttribute

If the values come from a PLC or another field device, polling all variables cyclically is often unnecessary. Through the MonitoredItemCreated, MonitoredItemModified, and MonitoredItemDeleted events, the node manager learns which of its nodes clients are currently subscribed to. The event data contains the OpcMonitoredItem with the NodeId, Attribute, SamplingInterval, and the Session of the client, as well as the affected node (Node). For requests that create or remove several MonitoredItems at once, the MonitoredItemsCreated, MonitoredItemsModified, and MonitoredItemsDeleted events are also available.

Several clients can subscribe to the same node, and one client can subscribe to it more than once. The following example therefore counts the subscriptions per node. It uses the attribute to exclude subscriptions to events:

csharp
public class MachineNodeManager : OpcNodeManager
{
    private readonly Dictionary<OpcNodeId, int> monitored
            = new Dictionary<OpcNodeId, int>();
 
    public MachineNodeManager()
        : base("http://example.com/machine")
    {
        this.MonitoredItemCreated += this.HandleMonitoredItemCreated;
        this.MonitoredItemDeleted += this.HandleMonitoredItemDeleted;
    }
 
    public bool IsMonitored(OpcNodeId nodeId)
    {
        lock (this.monitored)
            return this.monitored.ContainsKey(nodeId);
    }
 
    private void HandleMonitoredItemCreated(
            object sender,
            OpcMonitoredItemEventArgs e)
    {
        if (e.Item.Attribute != OpcAttribute.Value)
            return;
 
        lock (this.monitored) {
            int count;
            this.monitored.TryGetValue(e.Item.NodeId, out count);
            this.monitored[e.Item.NodeId] = count + 1;
        }
    }
 
    private void HandleMonitoredItemDeleted(
            object sender,
            OpcMonitoredItemEventArgs e)
    {
        if (e.Item.Attribute != OpcAttribute.Value)
            return;
 
        lock (this.monitored) {
            int count;
 
            if (!this.monitored.TryGetValue(e.Item.NodeId, out count))
                return;
 
            if (count > 1)
                this.monitored[e.Item.NodeId] = count - 1;
            else
                this.monitored.Remove(e.Item.NodeId);
        }
    }
}
vbnet
Public Class MachineNodeManager
    Inherits OpcNodeManager

    Private ReadOnly monitored As New Dictionary(Of OpcNodeId, Integer)()

    Public Sub New()
        MyBase.New("http://example.com/machine")

        AddHandler Me.MonitoredItemCreated, AddressOf Me.HandleMonitoredItemCreated
        AddHandler Me.MonitoredItemDeleted, AddressOf Me.HandleMonitoredItemDeleted
    End Sub

    Public Function IsMonitored(ByVal nodeId As OpcNodeId) As Boolean
        SyncLock Me.monitored
            Return Me.monitored.ContainsKey(nodeId)
        End SyncLock
    End Function

    Private Sub HandleMonitoredItemCreated( _
            ByVal sender As Object, _
            ByVal e As OpcMonitoredItemEventArgs)
        If e.Item.Attribute <> OpcAttribute.Value Then
            Return
        End If

        SyncLock Me.monitored
            Dim count As Integer
            Me.monitored.TryGetValue(e.Item.NodeId, count)
            Me.monitored(e.Item.NodeId) = count + 1
        End SyncLock
    End Sub

    Private Sub HandleMonitoredItemDeleted( _
            ByVal sender As Object, _
            ByVal e As OpcMonitoredItemEventArgs)
        If e.Item.Attribute <> OpcAttribute.Value Then
            Return
        End If

        SyncLock Me.monitored
            Dim count As Integer

            If Not Me.monitored.TryGetValue(e.Item.NodeId, count) Then
                Return
            End If

            If count > 1 Then
                Me.monitored(e.Item.NodeId) = count - 1
            Else
                Me.monitored.Remove(e.Item.NodeId)
            End If
        End SyncLock
    End Sub
End Class

The queries to the data source are then limited to the nodes for which IsMonitored returns true. The Provide data from a device tutorial shows a complete example.

Process access in batches ​

Types usedOpcNodeManagerOpcNodeAccessEventArgsOpcNodeAccessTokenOpcAttributeOpcResult

The server calls callbacks such as the ReadVariableValueCallback per node. If a client reads 500 variables in one request, this results in 500 individual calls to the data source. For such cases, the node manager provides the BeforeRead, AfterRead, BeforeWrite, and AfterWrite events. They occur once per request, and their Tokens contain all accesses of the request to nodes of this node manager. Each OpcNodeAccessToken names the node (Node), the attribute (Attribute), an optional range (Range), the value (Value), and the result of the access (Result).

  • BeforeRead occurs before the server reads the attributes. The server returns values that the handler writes to the nodes here in the same request.
  • AfterRead occurs after the server has read all attributes.
  • BeforeWrite occurs before the server writes the values to the nodes.
  • AfterWrite occurs after the server has written the values. Result then states for each access whether the write succeeded.

In the following example, Device stands for the connection to the data source. Before the read, the node manager reads all requested values in one call, and after the write it passes on all successfully written values in one call:

csharp
public class DeviceNodeManager : OpcNodeManager
{
    public DeviceNodeManager()
        : base("http://example.com/device")
    {
        this.BeforeRead += this.HandleBeforeRead;
        this.AfterWrite += this.HandleAfterWrite;
    }
 
    private void HandleBeforeRead(object sender, OpcNodeAccessEventArgs e)
    {
        var nodes = new List<OpcDataVariableNode>();
 
        foreach (var token in e.Tokens) {
            var node = token.Node as OpcDataVariableNode;
 
            if (token.Attribute == OpcAttribute.Value && node != null && !nodes.Contains(node))
                nodes.Add(node);
        }
 
        var names = new string[nodes.Count];
 
        for (int index = 0; index < nodes.Count; index++)
            names[index] = nodes[index].SymbolicName;
 
        // Device stands for the connection to the data source:
        // one request for all nodes of the client request.
        object[] values = Device.Read(names);
 
        for (int index = 0; index < nodes.Count; index++)
            nodes[index].Value = values[index];
    }
 
    private void HandleAfterWrite(object sender, OpcNodeAccessEventArgs e)
    {
        var names = new List<string>();
        var values = new List<object>();
 
        foreach (var token in e.Tokens) {
            if (token.Attribute == OpcAttribute.Value && token.Result.IsGood) {
                names.Add(token.Node.SymbolicName);
                values.Add(token.Value.Value);
            }
        }
 
        // One request to the device for all values written by the client.
        Device.Write(names.ToArray(), values.ToArray());
    }
}
vbnet
Public Class DeviceNodeManager
    Inherits OpcNodeManager

    Public Sub New()
        MyBase.New("http://example.com/device")

        AddHandler Me.BeforeRead, AddressOf Me.HandleBeforeRead
        AddHandler Me.AfterWrite, AddressOf Me.HandleAfterWrite
    End Sub

    Private Sub HandleBeforeRead(ByVal sender As Object, ByVal e As OpcNodeAccessEventArgs)
        Dim nodes = New List(Of OpcDataVariableNode)()

        For Each token In e.Tokens
            Dim node = TryCast(token.Node, OpcDataVariableNode)

            If token.Attribute = OpcAttribute.Value AndAlso node IsNot Nothing _
                    AndAlso Not nodes.Contains(node) Then
                nodes.Add(node)
            End If
        Next

        Dim names = New String(nodes.Count - 1) {}

        For index As Integer = 0 To nodes.Count - 1
            names(index) = nodes(index).SymbolicName
        Next

        'Device stands for the connection to the data source:
        'one request for all nodes of the client request.
        Dim values As Object() = Device.Read(names)

        For index As Integer = 0 To nodes.Count - 1
            nodes(index).Value = values(index)
        Next
    End Sub

    Private Sub HandleAfterWrite(ByVal sender As Object, ByVal e As OpcNodeAccessEventArgs)
        Dim names = New List(Of String)()
        Dim values = New List(Of Object)()

        For Each token In e.Tokens
            If token.Attribute = OpcAttribute.Value AndAlso token.Result.IsGood Then
                names.Add(token.Node.SymbolicName)
                values.Add(token.Value.Value)
            End If
        Next

        'One request to the device for all values written by the client.
        Device.Write(names.ToArray(), values.ToArray())
    End Sub
End Class

The events apply to all attributes. The example therefore filters for the Value attribute.