Zum Inhalt springen

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:

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

Den Wert des Value-Attributs eines Variablen-Nodes festlegen:

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

Den Wert des Description-Attributs festlegen:

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

Alle Clients mit aktiver Subscription über die Attributänderungen informieren und Änderungen übernehmen:

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

Den Wert des Description-Attributs aus einer anderen Datenquelle als der internen ermitteln:

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

Den Wert des Value-Attributs eines Variablen-Nodes aus einer anderen Datenquelle als der internen ermitteln:

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

Werte 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:

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

Den Wert des Value-Attributs eines Variablen-Nodes festlegen:

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

Den Wert des Description-Attributs festlegen:

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

Alle Clients mit aktiver Subscription über die Attributänderungen informieren und Änderungen übernehmen:

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

Den Wert des Description-Attributs in einer anderen Datenquelle als der internen speichern:

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

Den Wert des Value-Attributs eines Variablen-Nodes in einer anderen Datenquelle als der internen speichern:

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

Werte 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:

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

Die 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).

  • BeforeRead tritt auf, bevor der Server die Attribute liest. Werte, die der Handler hier in die Nodes schreibt, liefert der Server mit derselben Anfrage aus.
  • AfterRead tritt auf, nachdem der Server alle Attribute gelesen hat.
  • BeforeWrite tritt auf, bevor der Server die Werte in die Nodes schreibt.
  • AfterWrite tritt auf, nachdem der Server die Werte geschrieben hat. Result gibt 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:

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

Die Ereignisse betreffen alle Attribute. Das Beispiel filtert deshalb auf das Value-Attribut.