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:
- Einen Node-Manager mit einem Node je Datenpunkt anlegen.
- Die Werte zyklisch aktualisieren und einen Ausfall der Steuerung als schlechte Qualität melden.
- Schreibzugriffe der Clients an die Steuerung weitergeben.
- 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):
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;
}
}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 ClassWerte 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:
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);
}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 SubDie Aktualisierung rufen Sie zum Beispiel in einer Schleife, über einen Timer oder aus einem Ereignis der Steuerungsbibliothek auf:
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);
}
}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 UsingSchreibzugriffe 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:
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;
}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 FunctionNur 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):
public void Poll()
{
// Only query values a client has subscribed to (see MachineNodeManager).
if (this.IsMonitored(this.temperatureNode.Id))
this.Update(Plc.ReadTemperature());
}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 SubWenn 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
- Die Werte zusätzlich historisch bereitstellen: Historische Daten
- Grenzwerte und Störungen der Steuerung als Alarme melden: Alarme und Bedingungen
- Den Server absichern und konfigurieren: Server absichern und Konfiguration

