Zum Inhalt springen

Anleitung

Nodes verwalten

Nodes im Adressraum des Servers erstellen, ihre Sichtbarkeit steuern und sie zur Laufzeit aktualisieren.

Anleitung

Nodes verwalten ​

Nodes anlegen ​

Beteiligte TypenOpcServerOpcNodeOpcNodeIdOpcNodeManagerOpcAddressSpaceOpcFolderNodeOpcObjectNodeOpcVariableNodeOpcDataVariableNodeOpcMethodNodeOpcFileNode

Ein OpcNode definiert einen Datenpunkt des Servers. Das kann ein logischer Ordner (OpcFolderNode), eine Variable (OpcVariableNode), eine Methode (OpcMethodNode), eine Datei (OpcFileNode) und vieles mehr sein. Eine OpcNodeId identifiziert einen OpcNode eindeutig. Sie besteht aus einem Wert, der eigentlichen ID, und dem Index des Namespaces, dem der Node zugeordnet ist. Der Wert ist zum Beispiel ein Text oder eine Zahl. Eine URI (= Uniform Resource Identifier) legt den Namespace fest. Die Node-Manager des Servers bestimmen, welche Namespaces verfügbar sind.

Jeder Node-Manager definiert mindestens einen Namespace. Über diese Namespaces ordnet der Server die Nodes eines Node-Managers ein. So lässt sich jeder Node einem bestimmten Node-Manager zuordnen. Der Standard-Namespace (engl. Default Namespace) eines Node-Managers gilt immer dann, wenn ein Node keinen anderen Namespace erhält. Die Nodes, die ein Node-Manager definiert, heißen auch Nodes im Adressraum (engl. Address Space) des Node-Managers.

Beim Start fordert der Server seine Node-Manager auf, ihren Adressraum zu erstellen, also ihre statischen Nodes. Während der Ausführung des Servers lassen sich weitere, dynamische Nodes zum Adressraum eines Node-Managers hinzufügen und wieder daraus entfernen. Einen stets statischen Adressraum können Sie auch ohne eigenen Node-Manager erzeugen. Dazu übergeben Sie dem Server die statischen Nodes für den Namespace http://{host}/{path}/nodes/ direkt. Der Port ist dabei nicht Teil des Namespaces. Statt der Nodes des statischen Adressraums können Sie auch benutzerdefinierte Node-Manager definieren.

Einen benutzerdefinierten Adressraum mit einem Root-Node für den Standard-Namespace http://{host}/{path}/nodes/ erstellen:

csharp
var machineNode = new OpcFolderNode("Machine");
var machineIsRunningNode = new OpcDataVariableNode<bool>(machineNode, "IsRunning");
 
// Note: An enumerable of nodes can be also passed.
var server = new OpcServer("opc.tcp://localhost:4840/", machineNode);
vbnet
Dim machineNode As New OpcFolderNode("Machine")
Dim machineIsRunningNode = New OpcDataVariableNode(Of Boolean)(machineNode, "IsRunning")
 
'Note: An enumerable of nodes can be also passed.
Dim server = New OpcServer("opc.tcp://localhost:4840/", machineNode)

Einen Adressraum über eine Methode erstellen: Alternativ übergeben Sie dem Server eine Methode, die den Adressraum beim Start erstellt. Sie erhält eine Instanz von OpcAddressSpace. Über deren Methode AddNode trägt sie die Nodes unterhalb des ObjectsFolder oder eines anderen Eltern-Nodes ein:

csharp
var server = new OpcServer("opc.tcp://localhost:4840/", addressSpace => {
    var machineNode = new OpcFolderNode("Machine");
    var isRunningNode = new OpcDataVariableNode<bool>(machineNode, "IsRunning");
 
    // Adds the node beneath the Objects folder (the root of all server nodes).
    addressSpace.AddNode(machineNode);
});
vbnet
Dim server = New OpcServer("opc.tcp://localhost:4840/", Sub(addressSpace)
    Dim machineNode = New OpcFolderNode("Machine")
    Dim isRunningNode = New OpcDataVariableNode(Of Boolean)(machineNode, "IsRunning")

    'Adds the node beneath the Objects folder (the root of all server nodes).
    addressSpace.AddNode(machineNode)
End Sub)

Einen Adressraum aus einer Konfiguration aufbauen: Weil der Server die Methode beim Start ausführt, kann sie die Nodes auch aus einer Konfiguration erzeugen, zum Beispiel aus einer JSON-Datei, einer CSV-Datei oder einer Datenbank. Eine eigene Node-Manager-Klasse ist dafür nicht notwendig. Das folgende Beispiel legt für jeden Eintrag einer Liste einen Variablen-Node des passenden Typs an:

csharp
// Name and data type of each variable, e.g. read from a configuration file.
var tags = new Dictionary<string, string> {
    { "Temperature", "double" },
    { "IsRunning", "bool" },
    { "JobName", "string" }
};
 
var server = new OpcServer("opc.tcp://localhost:4840/", addressSpace => {
    var machine = new OpcFolderNode("Machine");
 
    foreach (var tag in tags) {
        switch (tag.Value) {
            case "double":
                new OpcDataVariableNode<double>(machine, tag.Key);
                break;
            case "bool":
                new OpcDataVariableNode<bool>(machine, tag.Key);
                break;
            default:
                new OpcDataVariableNode<string>(machine, tag.Key);
                break;
        }
    }
 
    addressSpace.AddNode(machine);
});
vbnet
'Name and data type of each variable, e.g. read from a configuration file.
Dim tags = New Dictionary(Of String, String) From { _
    {"Temperature", "double"}, _
    {"IsRunning", "bool"}, _
    {"JobName", "string"} _
}

Dim server = New OpcServer("opc.tcp://localhost:4840/", Sub(addressSpace)
    Dim machine = New OpcFolderNode("Machine")

    For Each tag In tags
        Select Case tag.Value
            Case "double"
                Dim node = New OpcDataVariableNode(Of Double)(machine, tag.Key)
            Case "bool"
                Dim node = New OpcDataVariableNode(Of Boolean)(machine, tag.Key)
            Case Else
                Dim node = New OpcDataVariableNode(Of String)(machine, tag.Key)
        End Select
    Next

    addressSpace.AddNode(machine)
End Sub)

Wenn auch die Datentypen erst aus der Konfiguration hervorgehen sollen, beschreibt die Seite Datentypen zur Laufzeit das Vorgehen.

Einen benutzerdefinierten Node-Manager definieren:

csharp
public class MyNodeManager : OpcNodeManager
{
    public MyNodeManager()
        : base("http://mynamespace/")
    {
    }
}
vbnet
Public Class MyNodeManager
    Inherits OpcNodeManager

    Public Sub New()
        MyBase.New("http://mynamespace/")
    End Sub
End Class

Einen benutzerdefinierten Adressraum mit einem Root-Node über einen benutzerdefinierten Node-Manager erstellen:

csharp
protected override IEnumerable<IOpcNode> CreateNodes(OpcNodeReferenceCollection references)
{
    // Define custom root node.
    var machineNode = new OpcFolderNode(new OpcName("Machine", this.DefaultNamespaceIndex));
 
    // Add custom root node to the Objects-Folder (the root of all server nodes):
    references.Add(machineNode, OpcObjectTypes.ObjectsFolder);
 
    // Add custom sub node beneath of the custom root node:
    var isMachineRunningNode = new OpcDataVariableNode<bool>(machineNode, "IsRunning");
 
    // Return each custom root node using yield return.
    yield return machineNode;
}
vbnet
Protected Overrides Iterator Function CreateNodes(ByVal references As OpcNodeReferenceCollection) As IEnumerable(Of IOpcNode)
    'Define custom root node.
    Dim machineNode = New OpcFolderNode(New OpcName("Machine", Me.DefaultNamespaceIndex))

    'Add custom root node to the Objects-Folder (the root of all server nodes):
    references.Add(machineNode, OpcObjectTypes.ObjectsFolder)

    'Add custom sub node beneath of the custom root node:
    Dim isMachineRunningNode = New OpcDataVariableNode(Of Boolean)(machineNode, "IsRunning")

    'Return each custom root node using yield return.
    Yield machineNode
End Function

Einen benutzerdefinierten Node-Manager dem Server bekannt machen:

csharp
// Note: An enumerable of node managers can be also passed.
var server = new OpcServer("opc.tcp://localhost:4840/", new MyNodeManager());
vbnet
'Note: An enumerable of node managers can be also passed.
Dim server = New OpcServer("opc.tcp://localhost:4840/", New MyNodeManager())

Nodes zur Laufzeit hinzufügen und entfernen: Nach dem Start des Servers fügen Sie weitere Nodes über die Methode AddNode des Node-Managers hinzu und entfernen sie über die Methode RemoveNode wieder. Der Node-Manager trägt einen hinzugefügten Node unterhalb seines Eltern-Nodes ein. Der Eltern-Node muss bereits Teil des Adressraums sein:

csharp
// The parent node is already part of the address space of the node manager.
var jobNode = new OpcDataVariableNode<string>(machineNode, "Job", value: "JOB0815");
nodeManager.AddNode(jobNode);
 
// Removes the node from the address space.
nodeManager.RemoveNode(jobNode);
vbnet
'The parent node is already part of the address space of the node manager.
Dim jobNode = New OpcDataVariableNode(Of String)(machineNode, "Job", value:="JOB0815")
nodeManager.AddNode(jobNode)

'Removes the node from the address space.
nodeManager.RemoveNode(jobNode)

Sichtbarkeit von Nodes einschränken ​

Beteiligte TypenOpcServerIOpcNodeOpcNodeIdOpcNodeManagerOpcObjectNodeOpcDataVariableNode

Nicht immer sollen alle Nodes für alle Benutzer des Servers sichtbar sein. Im eigenen Node-Manager können Sie die Sichtbarkeit der Nodes nach beliebigen Kriterien einschränken. Häufig reicht auch die Einschränkung über die Benutzeridentität und einen bestimmten Node. Der folgende einfache Baum veranschaulicht das:

csharp
protected override IEnumerable<IOpcNode> CreateNodes(OpcNodeReferenceCollection references)
{
    var machine = new OpcObjectNode(
            "Machine",
            new OpcDataVariableNode<int>("Speed", value: 123),
            new OpcDataVariableNode<string>("Job", value: "JOB0815"));

    references.Add(machine, OpcObjectTypes.ObjectsFolder);
    yield return machine;
}
vbnet
Protected Overrides Iterator Function CreateNodes(ByVal references As OpcNodeReferenceCollection) As IEnumerable(Of IOpcNode)
    Dim machine = New OpcObjectNode( _
            "Machine", _
            New OpcDataVariableNode(Of Integer)("Speed", value:=123), _
            New OpcDataVariableNode(Of String)("Job", value:="JOB0815"))

    references.Add(machine, OpcObjectTypes.ObjectsFolder)
    Yield machine
End Function

Wenn ein Benutzer auf alle Nodes außer dem Node „Speed“ zugreifen darf, kann die Implementierung der Methode IsNodeAccessible so aussehen. Rufen Sie für alle übrigen Nodes die Basisimplementierung auf. Sie blendet unter anderem Variablen aus, deren AccessLevel beziehungsweise UserAccessLevel den Wert None hat:

csharp
protected override bool IsNodeAccessible(OpcContext context, OpcNodeId viewId, IOpcNodeInfo node)
{
    if (context.Identity.DisplayName == "a")
        return true;

    if (context.Identity.DisplayName == "b" && node.Name.Value == "Speed")
        return false;

    return base.IsNodeAccessible(context, viewId, node);
}
vbnet
Protected Overrides Function IsNodeAccessible(ByVal context As OpcContext, ByVal viewId As OpcNodeId, ByVal node As IOpcNodeInfo) As Boolean
    If context.Identity.DisplayName = "a" Then
        Return True
    End If
 
    If context.Identity.DisplayName = "b" AndAlso node.Name.Value = "Speed" Then
        Return False
    End If
 
    Return MyBase.IsNodeAccessible(context, viewId, node)
End Function

Nodes aktualisieren ​

Beteiligte TypenOpcDataVariableNodeOpcStatusCodeOpcContext

Während der Ausführung des Servers müssen Sie häufig Nodes passend zum zugrunde liegenden System aktualisieren. Einfache Objekt-, Ordner- oder Methoden-Nodes ändern sich eher selten, Variablen-Nodes dagegen regelmäßig. Je nachdem, welche Informationen vorliegen, hinterlegen oder aktualisieren Sie im Variablen-Node neben dem eigentlichen Wert auch einen Zeitstempel und die Güte des Wertes. Das folgende Beispiel skizziert die Handhabung:

csharp
speedNode.Status.Update(OpcStatusCode.Good);
speedNode.Timestamp = DateTime.UtcNow;
speedNode.Value = 1200;
 
// Notifies subscribed clients about the changes.
speedNode.ApplyChanges(server.SystemContext);
vbnet
speedNode.Status.Update(OpcStatusCode.Good)
speedNode.Timestamp = DateTime.UtcNow
speedNode.Value = 1200

'Notifies subscribed clients about the changes.
speedNode.ApplyChanges(server.SystemContext)

Erst mit dem Aufruf von ApplyChanges informiert der Server auch Clientanwendungen über die Änderung des Nodes, sofern diese eine aktive Subscription für den Node und das Value-Attribut eingerichtet haben. Als Kontext dient außerhalb eines Node-Managers die Eigenschaft SystemContext des Servers, innerhalb eines Node-Managers dessen eigene Eigenschaft SystemContext. Die Überladung ApplyChanges(context, recursive: true) übernimmt zudem die Änderungen aller Kind-Nodes.

Namespaces festlegen ​

Beteiligte TypenOpcServerOpcNamespace

Die Indizes der Namespaces vergibt der Server beim Start in der Reihenfolge, in der seine Node-Manager ihre Namespaces anmelden. Clients, die Nodes über einen festen NamespaceIndex ansprechen (zum Beispiel konfigurierte SCADA- oder Steuerungs-Clients), sind deshalb auf eine feste Reihenfolge angewiesen. Mit RegisterNamespace beziehungsweise RegisterNamespaces tragen Sie die eigenen Namespaces vor dem Start in einer festen Reihenfolge ein. Einen bereits eingetragenen Namespace fügt der Server nicht erneut hinzu. Die Methoden liefern Instanzen von OpcNamespace, deren Eigenschaft Index den vergebenen Index enthält:

csharp
using (var server = new OpcServer("opc.tcp://localhost:4840/")) {
    OpcNamespace[] namespaces = server.RegisterNamespaces(
            "http://example.com/machine",
            "http://example.com/plant");
 
    Console.WriteLine("Machine: ns={0}", namespaces[0].Index);
    Console.WriteLine("Plant: ns={0}", namespaces[1].Index);
 
    server.Start();
}
vbnet
Using server = New OpcServer("opc.tcp://localhost:4840/")
    Dim namespaces As OpcNamespace() = server.RegisterNamespaces( _
            "http://example.com/machine", _
            "http://example.com/plant")

    Console.WriteLine("Machine: ns={0}", namespaces(0).Index)
    Console.WriteLine("Plant: ns={0}", namespaces(1).Index)

    server.Start()
End Using

Standardmäßig liegen alle Nodes im benutzerdefinierten Namespace mit dem Index zwei. Der Namespace mit dem Index eins ist der Namespace der Serveranwendung: Das SDK trägt dort die Eigenschaft OpcServer.ApplicationUri ein, sofern Sie vor dem Start des Servers keine Namespaces registrieren. Damit Sie Nodes im Namespace mit dem Index eins verwalten können, muss Ihr eigener OpcNodeManager den Wert von OpcServer.ApplicationUri als NamespaceUri verwenden.

Mehrsprachige Texte ​

Beteiligte TypenOpcServerOpcServerGlobalizationOpcText

Anzeigenamen, Beschreibungen und andere Texte eines Nodes (OpcText) lassen sich in mehreren Sprachen bereitstellen, ohne dass für jede Sprache eigene Nodes entstehen. Dazu erhält der Text neben dem Standardtext und dessen Sprache einen Schlüssel. Über die Eigenschaft Globalization des Servers hinterlegen Sie zu jedem Schlüssel die Übersetzungen je Sprache (AddResource für einen Eintrag, AddResources für mehrere). Geben Sie als Sprache eine konkrete Kultur an (zum Beispiel de-DE, nicht nur de):

csharp
var temperature = new OpcDataVariableNode<double>("Temperature");
 
temperature.DisplayName = new OpcText(
        "Temperature",
        cultureName: "en-US",
        key: "Temperature.DisplayName");
 
temperature.Description = new OpcText(
        "Temperature of the machine",
        cultureName: "en-US",
        key: "Temperature.Description");
 
using (var server = new OpcServer("opc.tcp://localhost:4840/", temperature)) {
    server.Globalization.AddResource(
            cultureName: "de-DE",
            key: "Temperature.DisplayName",
            value: "Temperatur");
 
    server.Globalization.AddResource(
            cultureName: "de-DE",
            key: "Temperature.Description",
            value: "Temperatur der Maschine");
 
    server.Start();
}
vbnet
Dim temperature = New OpcDataVariableNode(Of Double)("Temperature")

temperature.DisplayName = New OpcText( _
        "Temperature", _
        cultureName:="en-US", _
        key:="Temperature.DisplayName")

temperature.Description = New OpcText( _
        "Temperature of the machine", _
        cultureName:="en-US", _
        key:="Temperature.Description")

Using server = New OpcServer("opc.tcp://localhost:4840/", temperature)
    server.Globalization.AddResource( _
            cultureName:="de-DE", _
            key:="Temperature.DisplayName", _
            value:="Temperatur")

    server.Globalization.AddResource( _
            cultureName:="de-DE", _
            key:="Temperature.Description", _
            value:="Temperatur der Maschine")

    server.Start()
End Using

Wenn ein Client einen solchen Text liest, übersetzt der Server ihn anhand der Sprachen, die der Client beim Erstellen der Sitzung bevorzugt. Im SDK legt der Client diese Sprachen über OpcClient.Locales fest. Das beschreibt im Client-Kapitel der Abschnitt Anwendung und Sitzung konfigurieren. Wenn es für keine der bevorzugten Sprachen eine Übersetzung gibt, liefert der Server den Standardtext.

Änderungen am Adressraum melden ​

Beteiligte TypenOpcAutomatismOpcNodeOpcNodeManager

Wenn sich der Adressraum während der Laufzeit ändert, zum Beispiel weil eine Maschine oder ein Modul hinzukommt, kann der Server Clients über Model-Change-Ereignisse (GeneralModelChangeEvent) informieren. Standardmäßig erzeugt der Server diese Ereignisse nicht. Für alle Nodes außerhalb der Namespaces der OPC Foundation schalten Sie sie über die statische Eigenschaft OpcAutomatism.RaiseModelChangeEvents ein:

csharp
OpcAutomatism.RaiseModelChangeEvents = true;
vbnet
OpcAutomatism.RaiseModelChangeEvents = True

Danach meldet der Server zum Beispiel das Hinzufügen eines Nodes zur Laufzeit an die Clients, die Ereignisse des Servers abonniert haben:

csharp
var machine = new OpcObjectNode(machines, "Machine3");
var temperature = new OpcDataVariableNode<double>(machine, "Temperature");
 
manager.AddNode(server.SystemContext, machine);
vbnet
Dim machine = New OpcObjectNode(machines, "Machine3")
Dim temperature = New OpcDataVariableNode(Of Double)(machine, "Temperature")

manager.AddNode(server.SystemContext, machine)

Wenn Sie viele Nodes auf einmal ändern, setzen Sie die Erfassung der Änderungen für einen Node mit SuspendModelChangeEvents vorübergehend aus und nehmen sie mit ResumeModelChangeEvents wieder auf. Mit recursive: true gilt das auch für alle seine Kind-Nodes:

csharp
machines.SuspendModelChangeEvents(recursive: true);
 
foreach (var name in new string[] { "Machine4", "Machine5", "Machine6" })
    manager.AddNode(server.SystemContext, new OpcObjectNode(machines, name));
 
machines.ResumeModelChangeEvents(recursive: true);
vbnet
machines.SuspendModelChangeEvents(recursive:=True)

For Each name In New String() {"Machine4", "Machine5", "Machine6"}
    manager.AddNode(server.SystemContext, New OpcObjectNode(machines, name))
Next

machines.ResumeModelChangeEvents(recursive:=True)