How-to guide
Read and write values
Provide the values of nodes in the server and handle read and write access from clients.
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:
var machineIsRunningNode = new OpcDataVariableNode<bool>("IsRunning", false);Dim machineIsRunningNode = New OpcDataVariableNode(Of Boolean)("IsRunning", False)Set the value of the Value attribute of a variable node:
machineIsRunningNode.Value = true;machineIsRunningNode.Value = TrueSet the value of the Description attribute:
machineIsRunningNode.Description = "My description";machineIsRunningNode.Description = "My description"Notify all clients with an active subscription about the attribute changes and apply the changes:
machineIsRunningNode.ApplyChanges(server.SystemContext);machineIsRunningNode.ApplyChanges(server.SystemContext)Determine the value of the Description attribute from a data source other than the internal one:
machineIsRunningNode.ReadDescriptionCallback = HandleReadDescription;
...
private OpcAttributeValue<OpcText> HandleReadDescription(
OpcReadAttributeValueContext context,
OpcAttributeValue<OpcText> value)
{
return ReadDescriptionFromDataSource(context.Node) ?? value;
}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 FunctionDetermine the value of the Value attribute of a variable node from a data source other than the internal one:
machineIsRunningNode.ReadVariableValueCallback = HandleReadVariableValue;
...
private OpcVariableValue<object> HandleReadVariableValue(
OpcReadVariableValueContext context,
OpcVariableValue<object> value)
{
return ReadValueFromDataSource(context.Node) ?? value;
}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 FunctionWrite 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:
var machineIsRunningNode = new OpcDataVariableNode<bool>("IsRunning", false);Dim machineIsRunningNode = New OpcDataVariableNode(Of Boolean)("IsRunning", False)Set the value of the Value attribute of a variable node:
machineIsRunningNode.Value = true;machineIsRunningNode.Value = TrueSet the value of the Description attribute:
machineIsRunningNode.Description = "My description";machineIsRunningNode.Description = "My description"Notify all clients with an active subscription about the attribute changes and apply the changes:
machineIsRunningNode.ApplyChanges(server.SystemContext);machineIsRunningNode.ApplyChanges(server.SystemContext)Store the value of the Description attribute in a data source other than the internal one:
machineIsRunningNode.WriteDescriptionCallback = HandleWriteDescription;
...
private OpcAttributeValue<OpcText> HandleWriteDescription(
OpcWriteAttributeValueContext context,
OpcAttributeValue<OpcText> value)
{
return WriteDescriptionToDataSource(context.Node, value) ?? value;
}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 FunctionStore the value of the Value attribute of a variable node in a data source other than the internal one:
machineIsRunningNode.WriteVariableValueCallback = HandleWriteVariableValue;
...
private OpcVariableValue<object> HandleWriteVariableValue(
OpcWriteVariableValueContext context,
OpcVariableValue<object> value)
{
return WriteValueToDataSource(context.Node, value) ?? value;
}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 FunctionSample 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:
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);
}
}
}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 ClassThe 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).
BeforeReadoccurs before the server reads the attributes. The server returns values that the handler writes to the nodes here in the same request.AfterReadoccurs after the server has read all attributes.BeforeWriteoccurs before the server writes the values to the nodes.AfterWriteoccurs after the server has written the values.Resultthen 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:
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());
}
}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 ClassThe events apply to all attributes. The example therefore filters for the Value attribute.

