Anleitung
Werte lesen und schreiben
Werte von Nodes im Server bereitstellen und Lese- sowie Schreibzugriffe von Clients behandeln.
Anleitung
Werte lesen und schreiben
Werte lesen
Beteiligte TypenOpcServerOpcVariableNodeOpcDataVariableNodeOpcReadAttributeValueCallbackOpcAttributeValueOpcReadAttributeValueContextOpcReadVariableValueCallbackOpcReadVariableValueContextOpcVariableValueOpcText
Ein OpcNode definiert seine Metadaten durch Attribute. Attribute wie Name, DisplayName oder Description stellen im Allgemeinen alle Nodes bereit, das Value-Attribut dagegen nur Variablen-Nodes. Die Werte der Attribute speichern die Node-Instanzen standardmäßig intern. Wenn der Wert aus einer anderen Datenquelle stammen soll, definieren Sie Callback-Methoden, die die Werte bereitstellen. Die Signatur der ReadVariableValue-Callback-Methode unterscheidet sich dabei von den anderen ReadAttributeValue-Callback-Methoden. Für das Value-Attribut erwartet das SDK statt einer Instanz von OpcAttributeValue eine Instanz von OpcVariableValue. Sie besteht neben dem eigentlichen Wert aus einem Zeitstempel, zu dem die Quelle den Wert festgestellt hat (SourceTimestamp), und aus Statusinformationen über die Qualität des Wertes. Der Server ruft die Read-Callbacks bei jedem Lesevorgang auf, mit dem ein Client die Metadaten über den Read-Service liest. Beim Browsen liefert der Server dagegen die intern gespeicherten Werte (zum Beispiel den DisplayName), ohne die Callbacks aufzurufen. Für das Description-Attribut ist der Wert vom Typ OpcText.
Den Initialwert des Value-Attributs eines Variablen-Nodes festlegen:
var machineIsRunningNode = new OpcDataVariableNode<bool>("IsRunning", false);Dim machineIsRunningNode = New OpcDataVariableNode(Of Boolean)("IsRunning", False)Den Wert des Value-Attributs eines Variablen-Nodes festlegen:
machineIsRunningNode.Value = true;machineIsRunningNode.Value = TrueDen Wert des Description-Attributs festlegen:
machineIsRunningNode.Description = "My description";machineIsRunningNode.Description = "My description"Alle Clients mit aktiver Subscription über die Attributänderungen informieren und Änderungen übernehmen:
machineIsRunningNode.ApplyChanges(server.SystemContext);machineIsRunningNode.ApplyChanges(server.SystemContext)Den Wert des Description-Attributs aus einer anderen Datenquelle als der internen ermitteln:
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 FunctionDen Wert des Value-Attributs eines Variablen-Nodes aus einer anderen Datenquelle als der internen ermitteln:
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 FunctionWerte schreiben
Beteiligte TypenOpcServerOpcVariableNodeOpcDataVariableNodeOpcWriteAttributeValueCallbackOpcAttributeValueOpcWriteAttributeValueContextOpcWriteVariableValueCallbackOpcWriteVariableValueContextOpcVariableValueOpcText
Ein OpcNode definiert seine Metadaten durch Attribute. Attribute wie Name, DisplayName oder Description stellen im Allgemeinen alle Nodes bereit, das Value-Attribut dagegen nur Variablen-Nodes. Die Werte der Attribute speichern die Node-Instanzen standardmäßig intern. Wenn der Wert in einer anderen Datenquelle gespeichert werden soll, definieren Sie Callback-Methoden, die die Werte speichern. Die Signatur der WriteVariableValue-Callback-Methode unterscheidet sich dabei von den anderen WriteAttributeValue-Callback-Methoden. Für das Value-Attribut verwendet das SDK statt einer Instanz von OpcAttributeValue eine Instanz von OpcVariableValue. Sie besteht neben dem eigentlichen Wert aus einem Zeitstempel, zu dem die Quelle den Wert festgestellt hat (SourceTimestamp), und aus Statusinformationen über die Qualität des Wertes. Der Server ruft die Write-Callbacks bei jedem Schreibvorgang auf, mit dem ein Client Metadaten schreibt. Das geschieht über den Write-Service.
Den Initialwert des Value-Attributs eines Variablen-Nodes festlegen:
var machineIsRunningNode = new OpcDataVariableNode<bool>("IsRunning", false);Dim machineIsRunningNode = New OpcDataVariableNode(Of Boolean)("IsRunning", False)Den Wert des Value-Attributs eines Variablen-Nodes festlegen:
machineIsRunningNode.Value = true;machineIsRunningNode.Value = TrueDen Wert des Description-Attributs festlegen:
machineIsRunningNode.Description = "My description";machineIsRunningNode.Description = "My description"Alle Clients mit aktiver Subscription über die Attributänderungen informieren und Änderungen übernehmen:
machineIsRunningNode.ApplyChanges(server.SystemContext);machineIsRunningNode.ApplyChanges(server.SystemContext)Den Wert des Description-Attributs in einer anderen Datenquelle als der internen speichern:
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 FunctionDen Wert des Value-Attributs eines Variablen-Nodes in einer anderen Datenquelle als der internen speichern:
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 FunctionWerte nur bei Bedarf erfassen
Beteiligte TypenOpcNodeManagerOpcMonitoredItemOpcMonitoredItemEventArgsOpcAttribute
Wenn die Werte aus einer Steuerung oder einem anderen Feldgerät stammen, ist es oft unnötig, alle Variablen zyklisch abzufragen. Der Node-Manager erfährt über die Ereignisse MonitoredItemCreated, MonitoredItemModified und MonitoredItemDeleted, welche seiner Nodes Clients gerade abonniert haben. Die Ereignisdaten enthalten das OpcMonitoredItem mit NodeId, Attribute, SamplingInterval und der Session des Clients sowie den betroffenen Node (Node). Für Anfragen, die mehrere MonitoredItems auf einmal anlegen oder entfernen, stehen zusätzlich die Ereignisse MonitoredItemsCreated, MonitoredItemsModified und MonitoredItemsDeleted zur Verfügung.
Mehrere Clients können denselben Node abonnieren, auch ein Client mehrfach. Das folgende Beispiel zählt deshalb die Abonnements je Node. Abonnements von Ereignissen schließt es über das Attribut aus:
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 ClassDie Abfrage der Datenquelle beschränkt sich dann auf die Nodes, für die IsMonitored den Wert true liefert. Ein vollständiges Beispiel zeigt die Anleitung Steuerungsdaten bereitstellen.
Zugriffe gebündelt verarbeiten
Beteiligte TypenOpcNodeManagerOpcNodeAccessEventArgsOpcNodeAccessTokenOpcAttributeOpcResult
Der Server ruft Callbacks wie den ReadVariableValueCallback je Node auf. Wenn ein Client mit einer Anfrage 500 Variablen liest, ergeben sich daraus 500 einzelne Aufrufe an die Datenquelle. Für solche Fälle stellt der Node-Manager die Ereignisse BeforeRead, AfterRead, BeforeWrite und AfterWrite bereit. Sie treten je Anfrage einmal auf und enthalten in Tokens alle Zugriffe der Anfrage auf Nodes dieses Node-Managers. Jedes OpcNodeAccessToken nennt den Node (Node), das Attribut (Attribute), einen optionalen Teilbereich (Range), den Wert (Value) und das Ergebnis des Zugriffs (Result).
BeforeReadtritt auf, bevor der Server die Attribute liest. Werte, die der Handler hier in die Nodes schreibt, liefert der Server mit derselben Anfrage aus.AfterReadtritt auf, nachdem der Server alle Attribute gelesen hat.BeforeWritetritt auf, bevor der Server die Werte in die Nodes schreibt.AfterWritetritt auf, nachdem der Server die Werte geschrieben hat.Resultgibt dann je Zugriff an, ob das Schreiben erfolgreich war.
Im folgenden Beispiel steht Device für die Anbindung an die Datenquelle. Der Node-Manager liest vor dem Lesen alle angefragten Werte mit einem Aufruf und gibt nach dem Schreiben alle erfolgreich geschriebenen Werte mit einem Aufruf weiter:
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 ClassDie Ereignisse betreffen alle Attribute. Das Beispiel filtert deshalb auf das Value-Attribut.

