Zum Inhalt springen

Tutorial

Steuerungsdaten bereitstellen

Werte einer Steuerung oder eines Feldgeräts als OPC UA Server bereitstellen, mit Datenqualität bei Ausfall, Schreiben und Abfrage nur bei Bedarf.

Tutorial

Steuerungsdaten bereitstellen ​

Beteiligte TypenOpcServerOpcNodeManagerOpcDataVariableNodeOpcStatusCodeOpcVariableValueOpcWriteVariableValueContextOpcAccessLevel

Ein typischer Einsatz des OPC UA Servers ist ein Gateway: Er liest Werte aus einer Steuerung oder einem anderen Feldgerät und stellt sie Clients über OPC UA bereit. Diese Anleitung verbindet dafür die Bausteine aus den übrigen Seiten des Kapitels zu einem durchgängigen Ablauf. Im Code steht Plc stellvertretend für die Anbindung an die Steuerung, zum Beispiel über eine Bibliothek für das jeweilige Protokoll.

Der Ablauf besteht aus vier Schritten:

  1. Einen Node-Manager mit einem Node je Datenpunkt anlegen.
  2. Die Werte zyklisch aktualisieren und einen Ausfall der Steuerung als schlechte Qualität melden.
  3. Schreibzugriffe der Clients an die Steuerung weitergeben.
  4. Optional nur die Werte abfragen, die Clients gerade abonniert haben.

Adressraum anlegen ​

Der Node-Manager legt beim Start des Servers für jeden Datenpunkt einen Variablen-Node an. Datenpunkte, die Clients schreiben dürfen, erhalten die passenden Zugriffsrechte und einen Callback, der den Wert an die Steuerung weitergibt (siehe Schreibzugriffe weitergeben):

csharp
public class PlcNodeManager : OpcNodeManager
{
    private OpcDataVariableNode<double> temperatureNode;
    private OpcDataVariableNode<int> speedNode;
 
    public PlcNodeManager()
        : base("http://example.com/plc")
    {
    }
 
    protected override IEnumerable<IOpcNode> CreateNodes(OpcNodeReferenceCollection references)
    {
        var machine = new OpcObjectNode(new OpcName("Machine", this.DefaultNamespaceIndex));
        references.Add(machine, OpcObjectTypes.ObjectsFolder);
 
        this.temperatureNode = new OpcDataVariableNode<double>(machine, "Temperature");
        this.speedNode = new OpcDataVariableNode<int>(machine, "Speed");
 
        // Clients may write the speed; the value is forwarded to the device.
        this.speedNode.AccessLevel = OpcAccessLevel.CurrentReadOrWrite;
        this.speedNode.UserAccessLevel = OpcAccessLevel.CurrentReadOrWrite;
        this.speedNode.WriteVariableValueCallback = this.HandleWriteSpeed;
 
        yield return machine;
    }
}
vbnet
Public Class PlcNodeManager
    Inherits OpcNodeManager

    Private temperatureNode As OpcDataVariableNode(Of Double)
    Private speedNode As OpcDataVariableNode(Of Integer)

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

    Protected Overrides Iterator Function CreateNodes( _
            ByVal references As OpcNodeReferenceCollection) As IEnumerable(Of IOpcNode)
        Dim machine = New OpcObjectNode(New OpcName("Machine", Me.DefaultNamespaceIndex))
        references.Add(machine, OpcObjectTypes.ObjectsFolder)

        Me.temperatureNode = New OpcDataVariableNode(Of Double)(machine, "Temperature")
        Me.speedNode = New OpcDataVariableNode(Of Integer)(machine, "Speed")

        'Clients may write the speed; the value is forwarded to the device.
        Me.speedNode.AccessLevel = OpcAccessLevel.CurrentReadOrWrite
        Me.speedNode.UserAccessLevel = OpcAccessLevel.CurrentReadOrWrite
        Me.speedNode.WriteVariableValueCallback = AddressOf Me.HandleWriteSpeed

        Yield machine
    End Function
End Class

Werte aktualisieren ​

Bei jeder Aktualisierung erhält der Node den neuen Wert, den Status und den Zeitstempel der Quelle. Erst ApplyChanges benachrichtigt die Clients, die den Node abonniert haben (siehe Nodes aktualisieren).

Wenn die Steuerung nicht erreichbar ist, behält der Node seinen letzten Wert, meldet aber den Status BadCommunicationError. Clients erkennen so, dass der Wert nicht mehr aktuell ist, statt einen eingefrorenen Wert als gültig zu übernehmen:

csharp
public void Update(PlcReading reading)
{
    if (reading.IsAvailable) {
        this.temperatureNode.Value = reading.Temperature;
        this.temperatureNode.Status.Update(OpcStatusCode.Good);
        this.temperatureNode.Timestamp = reading.Timestamp;
    }
    else {
        // Keep the last value, but report that it is no longer up to date.
        this.temperatureNode.Status.Update(OpcStatusCode.BadCommunicationError);
        this.temperatureNode.Timestamp = DateTime.UtcNow;
    }
 
    this.temperatureNode.ApplyChanges(this.SystemContext);
}
vbnet
Public Sub Update(ByVal reading As PlcReading)
    If reading.IsAvailable Then
        Me.temperatureNode.Value = reading.Temperature
        Me.temperatureNode.Status.Update(OpcStatusCode.Good)
        Me.temperatureNode.Timestamp = reading.Timestamp
    Else
        'Keep the last value, but report that it is no longer up to date.
        Me.temperatureNode.Status.Update(OpcStatusCode.BadCommunicationError)
        Me.temperatureNode.Timestamp = DateTime.UtcNow
    End If

    Me.temperatureNode.ApplyChanges(Me.SystemContext)
End Sub

Die Aktualisierung rufen Sie zum Beispiel in einer Schleife, über einen Timer oder aus einem Ereignis der Steuerungsbibliothek auf:

csharp
using (var plcManager = new PlcNodeManager())
using (var server = new OpcServer("opc.tcp://localhost:4840/", plcManager)) {
    server.Start();
 
    while (!cancellation.IsCancellationRequested) {
        plcManager.Update(Plc.ReadTemperature());
        Thread.Sleep(millisecondsTimeout: 1000);
    }
}
vbnet
Using plcManager = New PlcNodeManager()
    Using server = New OpcServer("opc.tcp://localhost:4840/", plcManager)
        server.Start()

        While Not cancellation.IsCancellationRequested
            plcManager.Update(Plc.ReadTemperature())
            Thread.Sleep(millisecondsTimeout:=1000)
        End While
    End Using
End Using

Schreibzugriffe weitergeben ​

Wenn ein Client einen Wert schreibt, ruft der Server den WriteVariableValueCallback des Nodes auf. Der Callback gibt den Wert an die Steuerung weiter. Wenn das nicht gelingt, liefert er einen Wert mit schlechtem Status zurück. Diesen Status erhält der Client als Ergebnis seiner Schreibanfrage:

csharp
private OpcVariableValue<object> HandleWriteSpeed(
        OpcWriteVariableValueContext context,
        OpcVariableValue<object> value)
{
    if (!Plc.TryWriteSpeed((int)value.Value)) {
        // The client receives this status as the result of its write request.
        return new OpcVariableValue<object>(
                value.Value,
                timestamp: DateTime.UtcNow,
                statusCode: OpcStatusCode.BadCommunicationError);
    }
 
    return value;
}
vbnet
Private Function HandleWriteSpeed( _
        ByVal context As OpcWriteVariableValueContext, _
        ByVal value As OpcVariableValue(Of Object)) As OpcVariableValue(Of Object)
    If Not Plc.TryWriteSpeed(CInt(value.Value)) Then
        'The client receives this status as the result of its write request.
        Return New OpcVariableValue(Of Object)( _
                value.Value, _
                timestamp:=DateTime.UtcNow, _
                statusCode:=OpcStatusCode.BadCommunicationError)
    End If

    Return value
End Function

Nur bei Bedarf abfragen ​

Viele Steuerungen vertragen keine beliebig häufigen Abfragen. Wenn der Node-Manager zählt, welche Nodes Clients gerade abonniert haben, fragt er nur diese Werte ab (siehe Werte nur bei Bedarf erfassen):

csharp
public void Poll()
{
    // Only query values a client has subscribed to (see MachineNodeManager).
    if (this.IsMonitored(this.temperatureNode.Id))
        this.Update(Plc.ReadTemperature());
}
vbnet
Public Sub Poll()
    'Only query values a client has subscribed to (see MachineNodeManager).
    If Me.IsMonitored(Me.temperatureNode.Id) Then
        Me.Update(Plc.ReadTemperature())
    End If
End Sub

Wenn Clients viele Werte direkt lesen (ohne Abonnement), lassen sich die Zugriffe einer Anfrage zudem mit einer einzigen Abfrage an die Steuerung beantworten (siehe Zugriffe gebündelt verarbeiten).

Weiterführende Themen ​