Zum Inhalt springen

Anleitung

NodeSets

Informationsmodelle als NodeSet in den Server importieren und deren Nodes implementieren.

Anleitung

NodeSets ​

NodeSets beschreiben den Inhalt des Adressraums eines Servers in XML (eXtensible Markup Language). Die Beschreibung definiert Datentypen mit ihrer transportierten logischen Struktur (datenabhängige Informationen) und ihrer physischen Struktur (im Speicher). Außerdem enthält ein NodeSet die Definitionen von Node-Typen und von konkreten Node-Instanzen. Das Stammelement UANodeSet beschreibt zudem die Beziehungen (engl. References) zwischen den Nodes. Das betrifft nicht nur die Nodes aus diesem NodeSet, sondern auch Nodes aus anderen NodeSets und aus der Spezifikation.

Companion-Spezifikationen erweitern nicht nur die allgemeine Definition des Adressraums, sondern liefern unter anderem auch eigene Datentypen und Node-Typen. Zu jeder Companion-Spezifikation gibt es deshalb ein oder mehrere NodeSets. Damit ein Server eine Companion-Spezifikation erfüllt, muss er die dort definierten Typen und Verhaltensweisen umsetzen. Was dabei ein Companion SDK übernimmt, zeigt der Abschnitt Companion-Spezifikationen.

NodeSets beschreiben den Adressraum auch bei der Projektierung von Steuerungen. So lässt sich die gesamte projektierte Konfiguration einer Steuerung aus einer Projektierungssoftware wie TIA Portal exportieren. Mit diesem Export lässt sich der OPC UA Server der Steuerung initialisieren.

Unabhängig von der Quelle gilt: Wenn ein Server ein NodeSet verwenden soll, muss er die Logik bereitstellen, um den darin beschriebenen Adressraum zu importieren und zu implementieren. Wie das funktioniert, zeigen die nächsten Abschnitte.

NodeSets importieren ​

Beteiligte TypenOpcNodeSetOpcNodeSetManagerOpcNodeManagerOpcNodeSetImportOptions

Mit dem OpcNodeSetManager importieren Sie die Nodes, die ein NodeSet beschreibt, 1:1:

csharp
var umatiManager = OpcNodeSetManager.Create(
        OpcNodeSet.Load(@".\umati.xml"),
        OpcNodeSet.Load(@".\umati-instances.xml"));

using (var server = new OpcServer("opc.tcp://localhost:4840/", umatiManager)) {
    server.Start();
    ...
}
vbnet
Dim umatiManager = OpcNodeSetManager.Create( _
        OpcNodeSet.Load(".\umati.xml"), _
        OpcNodeSet.Load(".\umati-instances.xml"))

Using server As New OpcServer("opc.tcp://localhost:4840/", umatiManager)
    server.Start()
    ' ...
End Using

Beim Aufruf von OpcNodeSetManager.Create können Sie ein oder mehrere NodeSets angeben. Der dabei erstellte OpcNodeManager importiert die NodeSets. Beim Start des Servers erzeugt er die darin definierten Nodes im Adressraum des Servers. Neben einem Dateipfad nimmt die Methode OpcNodeSet.Load auch einen Stream entgegen, zum Beispiel für ein NodeSet, das als Ressource in die Anwendung eingebettet ist. Mit den Methoden GetNodes und GetNode werten Sie die im NodeSet beschriebenen Nodes zudem aus:

csharp
using (var stream = File.OpenRead(@".\umati.xml")) {
    var nodeSet = OpcNodeSet.Load(stream);
 
    foreach (var node in nodeSet.GetNodes())
        Console.WriteLine("{0}: {1}", node.Id, node.DisplayName);
}
vbnet
Using stream = File.OpenRead(".\umati.xml")
    Dim nodeSet = OpcNodeSet.Load(stream)

    For Each node In nodeSet.GetNodes()
        Console.WriteLine("{0}: {1}", node.Id, node.DisplayName)
    Next
End Using

Wie der Node-Manager die importierten Nodes anlegt, steuert die Eigenschaft ImportOptions des OpcNodeSetManager (standardmäßig OpcNodeSetImportOptions.None):

  • CompleteDataTypeDefinitionFields: ergänzt die DataTypeDefinition eines Strukturtyps um die Felder seiner Basistypen.
  • CompleteDataTypeDefinitionDefaultEncoding: ergänzt eine fehlende DefaultEncodingId in der DataTypeDefinition.
  • CreateNonGenericNodeInstances: erzeugt für die importierten Nodes die typisierten Klassen des OPC UA .NET SDK statt generischer Instanzen.

Die beiden ersten Optionen wirken auf alle NodeSets des Servers:

csharp
umatiManager.ImportOptions = OpcNodeSetImportOptions.CompleteDataTypeDefinitionFields
        | OpcNodeSetImportOptions.CreateNonGenericNodeInstances;
vbnet
umatiManager.ImportOptions = OpcNodeSetImportOptions.CompleteDataTypeDefinitionFields _
        Or OpcNodeSetImportOptions.CreateNonGenericNodeInstances

Wenn stattdessen ein benutzerdefinierter Node-Manager ein NodeSet importieren soll, überschreiben Sie die Methode ImportNodes der Klasse OpcNodeManager:

csharp
protected override IEnumerable<OpcNodeSet> ImportNodes()
{
    yield return OpcNodeSet.Load(@".\umati.xml");
    yield return OpcNodeSet.Load(@".\umati-instances.xml");
}
vbnet
Protected Overrides Iterator Function ImportNodes() As IEnumerable(Of OpcNodeSet)
    Yield OpcNodeSet.Load(".\umati.xml")
    Yield OpcNodeSet.Load(".\umati-instances.xml")
End Function

Importierte NodeSets mit Logik verbinden ​

Beteiligte TypenOpcNodeManagerIOpcNode

Häufig genügt der Import eines NodeSets allein nicht, weil noch die Logik fehlt, die das zugrunde liegende System anbindet. Diese Logik bildet zum Beispiel das Lesen eines Nodes auf das Lesen eines Wortes in einem Datenbaustein ab. Gleiches gilt für das Schreiben eines Nodes.

Wenn Sie den Node-Manager über OpcNodeSetManager.Create erzeugen, legen Sie die Logik über die Eigenschaft ImplementNodeCallback des OpcNodeSetManager fest. Das SDK ruft den Callback für jeden importierten Node auf. Für ein UMATI-NodeSet könnte die Logik zur Simulation des Status einer Lampe zum Beispiel so aussehen:

csharp
var lampTypeId = OpcNodeId.Parse("ns=2;i=1041");
var random = new Random();
 
var umatiManager = OpcNodeSetManager.Create(
        OpcNodeSet.Load(@".\umati.xml"),
        OpcNodeSet.Load(@".\umati-instances.xml"));
 
umatiManager.ImplementNodeCallback = (context, node) => {
    var variableNode = node as OpcVariableNode;
 
    if (variableNode == null || variableNode.Name != "2:Status")
        return;
 
    var objectNode = variableNode.Parent as OpcObjectNode;
 
    if (objectNode != null && objectNode.TypeDefinitionId == lampTypeId) {
        variableNode.ReadVariableValueCallback = (readContext, value)
                => new OpcVariableValue<object>(random.Next(minValue: 0, maxValue: 2));
    }
};
vbnet
Dim lampTypeId = OpcNodeId.Parse("ns=2;i=1041")
Dim random = New Random()

Dim umatiManager = OpcNodeSetManager.Create( _
        OpcNodeSet.Load(".\umati.xml"), _
        OpcNodeSet.Load(".\umati-instances.xml"))

umatiManager.ImplementNodeCallback = Sub(context, node)
    Dim variableNode = TryCast(node, OpcVariableNode)

    If variableNode Is Nothing OrElse variableNode.Name <> "2:Status" Then
        Return
    End If

    Dim objectNode = TryCast(variableNode.Parent, OpcObjectNode)

    If objectNode IsNot Nothing AndAlso objectNode.TypeDefinitionId = lampTypeId Then
        variableNode.ReadVariableValueCallback = Function(readContext, value) _
                New OpcVariableValue(Of Object)(random.Next(minValue:=0, maxValue:=2))
    End If
End Sub

In einem benutzerdefinierten Node-Manager überschreiben Sie für diese Logik stattdessen die Methode OpcNodeManager.ImplementNode. Das SDK ruft die Methode für jeden importierten oder über CreateNodes erzeugten Node auf. Nach dem Start des Servers ruft es sie zudem für jeden Node auf, den ein Client dem Adressraum des Node-Managers hinzufügt. Rufen Sie in der Methode die Basisimplementierung auf. Sie ergänzt unter anderem die Verweise auf die Schnittstellen (HasInterface), die ein Node implementiert:

csharp
protected override void ImplementNode(IOpcNode node)
{
    // Keeps the default implementation, e.g. the references to the interfaces of the node.
    base.ImplementNode(node);
 
    // Implement your Node(s) here.
}
vbnet
Protected Overrides Sub ImplementNode(ByVal node As IOpcNode)
    'Keeps the default implementation, e.g. the references to the interfaces of the node.
    MyBase.ImplementNode(node)

    'Implement your Node(s) here.
End Sub

Für das oben gezeigte UMATI-NodeSet sieht die Implementierung in einem benutzerdefinierten Node-Manager so aus:

csharp
private static readonly OpcNodeId LampTypeId = "ns=2;i=1041";
private readonly Random random = new Random();

protected override void ImplementNode(IOpcNode node)
{
    if (node is OpcVariableNode variableNode && variableNode.Name == "2:Status") {
        if (variableNode?.Parent is OpcObjectNode objectNode && objectNode.TypeDefinitionId == LampTypeId) {
            variableNode.ReadVariableValueCallback = (context, value) => new OpcVariableValue<object>(this.random.Next(0, 2));
        }
    }
}
vbnet
Private Shared ReadOnly LampTypeId As OpcNodeId = "ns=2;i=1041"
Private random As New Random()

Protected Overrides Sub ImplementNode(node As IOpcNode)
    Dim variableNode = TryCast(node, OpcVariableNode)
    
    If variableNode IsNot Nothing AndAlso variableNode.Name = "2:Status" Then
        Dim objectNode = TryCast(variableNode.Parent, OpcObjectNode)
        If objectNode IsNot Nothing AndAlso objectNode.TypeDefinitionId = LampTypeId Then
            variableNode.ReadVariableValueCallback = Function(context, value) New OpcVariableValue(Of Object)(Me.random.Next(0, 2))
        End If
    End If
End Sub

Companion-Spezifikationen ​

Beteiligte TypenOpcServerOpcNodeManagerOpcNodeSetManagerOpcNamespaceOpcObjectNodeOpcNodeModel

Eine Companion-Spezifikation lässt sich wie jedes andere NodeSet importieren und implementieren. Für viele Companion-Spezifikationen gibt es zusätzlich ein eigenes Paket des SDK (Companion SDK), zum Beispiel Opc.UaFx.Advanced.Machinery für OPC UA for Machinery (OPC 40001-1) oder Opc.UaFx.Advanced.ISA95.JobControl für ISA-95 Job Control (OPC 10031-4). Welche Companion SDKs verfügbar sind, zeigt die Produktseite OPC UA Companion SDKs. Die folgenden Abschnitte zeigen am Beispiel von OPC UA for Machinery, was ein solches Paket mitbringt und welche Schritte ohne Paket von Hand notwendig sind.

Was ein Companion SDK mitbringt ​

BestandteilInhalt am Beispiel Opc.UaFx.Advanced.Machinery
Eingebettetes NodeSetDas NodeSet der Spezifikation ist in die Assembly eingebettet; es muss weder beschafft noch mit ausgeliefert werden.
AbhängigkeitenDie Server-Klasse OpcMachineryServer bringt die Node-Manager aller benötigten Spezifikationen mit (DI, IA und Machinery).
KonstantenOpcMachineryNodes enthält die Namespace-URI, den Namespace (Namespace, GetName) sowie die OpcNodeId aller Typen und Instanzen der Spezifikation, zum Beispiel MachineIdentificationType oder MachinesNode.
Typisierte Node-KlassenZu jedem Objekttyp eine Klasse, zum Beispiel OpcMachineIdentificationNode, mit einer Eigenschaft je Kind-Node (Manufacturer, SerialNumber, YearOfConstruction und so weiter). Beim Erzeugen legt die Klasse die Kind-Nodes mit Namen, Namespace und Datentyp aus der Spezifikation an.
SchnittstellenDie Schnittstellen der Spezifikation sind als .NET-Schnittstellen umgesetzt. Die zugehörigen HasInterface-Verweise legt das SDK beim Implementieren der Nodes automatisch an.
Typisierte MethodenMethoden der Spezifikation sind als überschreibbare Methoden mit typisierten Parametern umgesetzt. Ohne Implementierung lösen sie eine NotImplementedException aus.
Node-ManagerOpcMachineryNodeManager importiert das eingebettete NodeSet, löst den Namespace auf und stellt den Ordner Machines bereit.
Client-KlasseOpcMachineryClient registriert die Datentypen der Spezifikation und ihrer Abhängigkeiten und löst nach dem Verbinden die Namespaces auf, sodass die Konstanten aus OpcMachineryNodes auch im Client gelten.

Maschine mit Companion SDK ​

Das folgende Beispiel stellt eine Maschine mit ihrer Identifikation nach OPC UA for Machinery bereit. Die Kind-Nodes der Identifikation legt die Klasse OpcMachineIdentificationNode selbst an. Sie setzen nur noch die Werte:

csharp
using (var server = new OpcMachineryServer("opc.tcp://localhost:4840/")) {
    server.Start();
 
    OpcMachineryNodeManager machinery = server.NodeManagers
            .OfType<OpcMachineryNodeManager>()
            .First();
 
    var machine = new OpcObjectNode(machinery.Machines, "CNC-042");
    var identification = new OpcMachineIdentificationNode(machine, "Identification");
 
    identification.Manufacturer.Value = "Example Machines Ltd.";
    identification.SerialNumber.Value = "SN-0042";
    identification.ProductInstanceUri.Value = "urn:example:cnc-0042";
 
    machinery.AddNode(server.SystemContext, machine);
}
vbnet
Using server = New OpcMachineryServer("opc.tcp://localhost:4840/")
    server.Start()

    Dim machinery As OpcMachineryNodeManager = server.NodeManagers _
            .OfType(Of OpcMachineryNodeManager)() _
            .First()

    Dim machine = New OpcObjectNode(machinery.Machines, "CNC-042")
    Dim identification = New OpcMachineIdentificationNode(machine, "Identification")

    identification.Manufacturer.Value = "Example Machines Ltd."
    identification.SerialNumber.Value = "SN-0042"
    identification.ProductInstanceUri.Value = "urn:example:cnc-0042"

    machinery.AddNode(server.SystemContext, machine)
End Using

Ohne weitere Angabe legt eine Node-Klasse eines Companion SDK alle Kind-Nodes ihres Typs an, auch die optionalen. Welche optionalen Kind-Nodes entstehen, steuert ein OpcNodeModel. Das beschreibt die Seite Node-Modelle.

Maschine ohne Companion SDK ​

Ohne Companion SDK sind für denselben Server diese Schritte notwendig:

  1. Die NodeSet-Dateien der Spezifikation und aller Abhängigkeiten beschaffen, mit der Anwendung ausliefern und in der richtigen Reihenfolge laden (DI, IA, Machinery).
  2. Die Namespaces der Spezifikationen über ihre URI auflösen, um die OpcNodeId und OpcName der Typen und Instanzen zu bilden.
  3. Die Instanz anlegen und ihre Typdefinition (TypeDefinitionId) setzen.
  4. Jeden Kind-Node mit dem Namen, dem Namespace und dem Datentyp aus der Spezifikation selbst anlegen.
  5. Die Verweise auf die Schnittstellen des Typs (HasInterface) von Hand eintragen.
csharp
public class HandmadeMachineManager : OpcNodeManager
{
    private static readonly OpcNodeId HasInterface = OpcNodeId.Parse("i=17603");
 
    public HandmadeMachineManager()
        : base("http://example.com/machines/")
    {
    }
 
    public static OpcServer CreateServer()
    {
        var nodeSets = OpcNodeSetManager.Create(
                OpcNodeSet.Load(@"NodeSets\Opc.Ua.Di.NodeSet2.xml"),
                OpcNodeSet.Load(@"NodeSets\Opc.Ua.IA.NodeSet2.xml"),
                OpcNodeSet.Load(@"NodeSets\Opc.Ua.Machinery.NodeSet2.xml"));
 
        return new OpcServer("opc.tcp://localhost:4840/", nodeSets, new HandmadeMachineManager());
    }
 
    protected override IEnumerable<IOpcNode> CreateNodes(OpcNodeReferenceCollection references)
    {
        var di = OpcNamespace.Create("http://opcfoundation.org/UA/DI/");
        var machinery = OpcNamespace.Create("http://opcfoundation.org/UA/Machinery/");
        di.Resolve(this);
        machinery.Resolve(this);
 
        // Machinery: Machines folder (i=1001).
        var machine = new OpcObjectNode("CNC-042");
 
        references.Add(
                machine,
                machinery.GetId(1001),
                direction: OpcReferenceDirection.ChildToParent,
                referenceType: OpcReferenceType.Organizes);
 
        // Machinery: MachineIdentificationType (i=1012).
        var identification = new OpcObjectNode(machine, machinery.GetName("Identification"));
        identification.TypeDefinitionId = machinery.GetId(1012);
 
        // Mandatory properties: names, namespaces and data types from the specification.
        var manufacturer = new OpcTextPropertyNode(
                identification,
                di.GetName("Manufacturer"),
                value: new OpcText("Example Machines Ltd."));
 
        var serialNumber = new OpcPropertyNode<string>(
                identification,
                di.GetName("SerialNumber"),
                id: OpcNodeId.Null,
                value: "SN-0042");
 
        var instanceUri = new OpcPropertyNode<string>(
                identification,
                di.GetName("ProductInstanceUri"),
                id: OpcNodeId.Null,
                value: "urn:example:cnc-0042");
 
        // Interfaces of the type: IMachineVendorNameplateType (i=1010),
        // IMachineTagNameplateType (i=1011).
        references.Add(
                identification,
                machinery.GetId(1010),
                direction: OpcReferenceDirection.ParentToChild,
                referenceTypeId: HasInterface);
 
        references.Add(
                identification,
                machinery.GetId(1011),
                direction: OpcReferenceDirection.ParentToChild,
                referenceTypeId: HasInterface);
 
        yield return machine;
    }
}
vbnet
Public Class HandmadeMachineManager
    Inherits OpcNodeManager

    Private Shared ReadOnly HasInterface As OpcNodeId = OpcNodeId.Parse("i=17603")

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

    Public Shared Function CreateServer() As OpcServer
        Dim nodeSets = OpcNodeSetManager.Create( _
                OpcNodeSet.Load("NodeSets\Opc.Ua.Di.NodeSet2.xml"), _
                OpcNodeSet.Load("NodeSets\Opc.Ua.IA.NodeSet2.xml"), _
                OpcNodeSet.Load("NodeSets\Opc.Ua.Machinery.NodeSet2.xml"))

        Return New OpcServer("opc.tcp://localhost:4840/", nodeSets, New HandmadeMachineManager())
    End Function

    Protected Overrides Iterator Function CreateNodes( _
            ByVal references As OpcNodeReferenceCollection) As IEnumerable(Of IOpcNode)
        Dim di = OpcNamespace.Create("http://opcfoundation.org/UA/DI/")
        Dim machinery = OpcNamespace.Create("http://opcfoundation.org/UA/Machinery/")
        di.Resolve(Me)
        machinery.Resolve(Me)

        'Machinery: Machines folder (i=1001).
        Dim machine = New OpcObjectNode("CNC-042")

        references.Add( _
                machine, _
                machinery.GetId(1001), _
                direction:=OpcReferenceDirection.ChildToParent, _
                referenceType:=OpcReferenceType.Organizes)

        'Machinery: MachineIdentificationType (i=1012).
        Dim identification = New OpcObjectNode(machine, machinery.GetName("Identification"))
        identification.TypeDefinitionId = machinery.GetId(1012)

        'Mandatory properties: names, namespaces and data types from the specification.
        Dim manufacturer = New OpcTextPropertyNode( _
                identification, _
                di.GetName("Manufacturer"), _
                value:=New OpcText("Example Machines Ltd."))

        Dim serialNumber = New OpcPropertyNode(Of String)( _
                identification, _
                di.GetName("SerialNumber"), _
                id:=OpcNodeId.Null, _
                value:="SN-0042")

        Dim instanceUri = New OpcPropertyNode(Of String)( _
                identification, _
                di.GetName("ProductInstanceUri"), _
                id:=OpcNodeId.Null, _
                value:="urn:example:cnc-0042")

        'Interfaces of the type: IMachineVendorNameplateType (i=1010),
        'IMachineTagNameplateType (i=1011).
        references.Add( _
                identification, _
                machinery.GetId(1010), _
                direction:=OpcReferenceDirection.ParentToChild, _
                referenceTypeId:=HasInterface)

        references.Add( _
                identification, _
                machinery.GetId(1011), _
                direction:=OpcReferenceDirection.ParentToChild, _
                referenceTypeId:=HasInterface)

        Yield machine
    End Function
End Class

Die Bezeichner der Nodes (zum Beispiel i=1001 für den Ordner Machines oder i=1012 für MachineIdentificationType) stammen aus dem NodeSet der Spezifikation. Für den Verweistyp HasInterface bietet das SDK keine Konstante. Das Beispiel verwendet deshalb dessen OpcNodeId (i=17603).

Methoden implementieren ​

Auch bei Methoden der Spezifikation unterscheidet sich der Aufwand. Ohne Companion SDK suchen Sie den importierten Methoden-Node über seinen Namen und implementieren ihn über einen Befehl (IOpcMethodCommand). Reihenfolge und Typen der Argumente entnehmen Sie der Spezifikation. Die Argumente kommen als untypisierte Listen an:

csharp
nodeSets.ImplementNodeCallback = (context, node) => {
    var method = node as OpcMethodNode;
 
    if (method != null && method.Name.Value == "TransferToDevice")
        method.Command = new TransferToDeviceCommand();
};
...
public sealed class TransferToDeviceCommand : IOpcMethodCommand
{
    public bool CanExecute(OpcContext context)
    {
        return true;
    }
 
    public bool CanUserExecute(OpcContext context)
    {
        return true;
    }
 
    public void Execute(
            OpcMethodContext context,
            IList<object> inputArguments,
            IList<object> outputArguments)
    {
        // Order and types of the arguments as defined by the specification.
        outputArguments[0] = 1; // TransferID
        outputArguments[1] = 0; // InitTransferStatus
    }
}
vbnet
nodeSets.ImplementNodeCallback = Sub(context, node)
    Dim method = TryCast(node, OpcMethodNode)

    If method IsNot Nothing AndAlso method.Name.Value = "TransferToDevice" Then
        method.Command = New TransferToDeviceCommand()
    End If
End Sub
...
Public NotInheritable Class TransferToDeviceCommand
    Implements IOpcMethodCommand

    Public Function CanExecute(ByVal context As OpcContext) As Boolean _
            Implements IOpcMethodCommand.CanExecute
        Return True
    End Function

    Public Function CanUserExecute(ByVal context As OpcContext) As Boolean _
            Implements IOpcMethodCommand.CanUserExecute
        Return True
    End Function

    Public Sub Execute( _
            ByVal context As OpcMethodContext, _
            ByVal inputArguments As IList(Of Object), _
            ByVal outputArguments As IList(Of Object)) _
            Implements IOpcMethodCommand.Execute
        'Order and types of the arguments as defined by the specification.
        outputArguments(0) = 1 'TransferID
        outputArguments(1) = 0 'InitTransferStatus
    End Sub
End Class

Mit Companion SDK genügt es, die typisierte Methode der Node-Klasse zu überschreiben, hier am Beispiel der Methode TransferToDevice aus OPC UA for Devices (DI):

csharp
public class DeviceTransferServices : OpcTransferServicesNode
{
    public DeviceTransferServices(IOpcNode parent)
        : base(parent, "TransferServices")
    {
    }
 
    public override void TransferToDevice(
            OpcMethodContext methodContext,
            out int transferID,
            out int initTransferStatus)
    {
        transferID = 1;
        initTransferStatus = 0;
    }
}
vbnet
Public Class DeviceTransferServices
    Inherits OpcTransferServicesNode

    Public Sub New(ByVal parent As IOpcNode)
        MyBase.New(parent, "TransferServices")
    End Sub

    Public Overrides Sub TransferToDevice( _
            ByVal methodContext As OpcMethodContext, _
            ByRef transferID As Integer, _
            ByRef initTransferStatus As Integer)
        transferID = 1
        initTransferStatus = 0
    End Sub
End Class

Was das Companion SDK übernimmt ​

Aufgabeohne Companion SDKmit Companion SDK
NodeSets und AbhängigkeitenDateien beschaffen, ausliefern und in der richtigen Reihenfolge ladeneingebettet; die Server-Klasse bringt alle Abhängigkeiten mit
NamespacesURIs kennen und je Namespace mit OpcNamespace.Resolve auflösenOpcMachineryNodes.Namespace, GetName und Konstanten für alle OpcNodeId
Instanz eines TypsNode anlegen, TypeDefinitionId setzen, jeden Kind-Node mit Name, Namespace und Datentyp selbst anlegennew OpcMachineIdentificationNode(parent, name) legt die Kind-Nodes an
Optionale Kind-Nodeseinzeln entscheiden und anlegenstandardmäßig alle; gezielte Auswahl über ein OpcNodeModel
SchnittstellenHasInterface-Verweise von Hand eintragenautomatisch aus den implementierten .NET-Schnittstellen
WerteKind-Nodes über Name oder OpcNodeId finden und den Wert setzentypisierte Eigenschaften, zum Beispiel Manufacturer.Value
MethodenMethoden-Nodes suchen, IOpcMethodCommand mit untypisierten Argumententypisierte Methode überschreiben
Attribute aus der Spezifikation (Beschreibung, Zugriffsrechte, Standardwerte)von Hand übernehmenüber OpcNodeModel.Of aus der Typdeklaration
ClientNamespaces auflösen und Datentypen selbst registrierendie Client-Klasse registriert die Datentypen und löst die Namespaces auf

Companion SDK im Client ​

Auch ein Client profitiert von einem Companion SDK. Die Client-Klasse (hier OpcMachineryClient) registriert die Datentypen der Spezifikation. Damit liest der Client Strukturen und Enumerationen der Spezifikation direkt als .NET-Typen. Nach dem Verbinden löst die Client-Klasse außerdem die Namespaces auf. Die Konstanten aus OpcMachineryNodes gelten danach auch für den verbundenen Server:

csharp
using (var client = new OpcMachineryClient("opc.tcp://localhost:4840/")) {
    client.Connect();
 
    OpcNodeInfo machines = client.BrowseNode(OpcMachineryNodes.MachinesNode);
 
    foreach (OpcNodeInfo machine in machines.Children())
        Console.WriteLine(machine.DisplayName.Value);
}
vbnet
Using client = New OpcMachineryClient("opc.tcp://localhost:4840/")
    client.Connect()

    Dim machines As OpcNodeInfo = client.BrowseNode(OpcMachineryNodes.MachinesNode)

    For Each machine As OpcNodeInfo In machines.Children()
        Console.WriteLine(machine.DisplayName.Value)
    Next
End Using

Typisierte Node-Klassen wie OpcMachineIdentificationNode sind Nodes des Servers. Ein Client arbeitet mit den Nodes eines Companion-Servers über die üblichen Methoden zum Browsen, Lesen und Schreiben.