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:
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();
...
}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 UsingBeim 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:
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);
}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 UsingWie 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 fehlendeDefaultEncodingIdin 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:
umatiManager.ImportOptions = OpcNodeSetImportOptions.CompleteDataTypeDefinitionFields
| OpcNodeSetImportOptions.CreateNonGenericNodeInstances;umatiManager.ImportOptions = OpcNodeSetImportOptions.CompleteDataTypeDefinitionFields _
Or OpcNodeSetImportOptions.CreateNonGenericNodeInstancesWenn stattdessen ein benutzerdefinierter Node-Manager ein NodeSet importieren soll, überschreiben Sie die Methode ImportNodes der Klasse OpcNodeManager:
protected override IEnumerable<OpcNodeSet> ImportNodes()
{
yield return OpcNodeSet.Load(@".\umati.xml");
yield return OpcNodeSet.Load(@".\umati-instances.xml");
}Protected Overrides Iterator Function ImportNodes() As IEnumerable(Of OpcNodeSet)
Yield OpcNodeSet.Load(".\umati.xml")
Yield OpcNodeSet.Load(".\umati-instances.xml")
End FunctionImportierte 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:
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));
}
};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 SubIn 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:
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.
}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 SubFür das oben gezeigte UMATI-NodeSet sieht die Implementierung in einem benutzerdefinierten Node-Manager so aus:
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));
}
}
}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 SubCompanion-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
| Bestandteil | Inhalt am Beispiel Opc. |
|---|---|
| Eingebettetes NodeSet | Das NodeSet der Spezifikation ist in die Assembly eingebettet; es muss weder beschafft noch mit ausgeliefert werden. |
| Abhängigkeiten | Die Server-Klasse Opc bringt die Node-Manager aller benötigten Spezifikationen mit (DI, IA und Machinery). |
| Konstanten | Opc 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-Klassen | Zu jedem Objekttyp eine Klasse, zum Beispiel Opc, 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. |
| Schnittstellen | Die Schnittstellen der Spezifikation sind als .NET-Schnittstellen umgesetzt. Die zugehörigen HasInterface-Verweise legt das SDK beim Implementieren der Nodes automatisch an. |
| Typisierte Methoden | Methoden der Spezifikation sind als überschreibbare Methoden mit typisierten Parametern umgesetzt. Ohne Implementierung lösen sie eine Not aus. |
| Node-Manager | Opc importiert das eingebettete NodeSet, löst den Namespace auf und stellt den Ordner Machines bereit. |
| Client-Klasse | Opc registriert die Datentypen der Spezifikation und ihrer Abhängigkeiten und löst nach dem Verbinden die Namespaces auf, sodass die Konstanten aus Opc 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:
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);
}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 UsingOhne 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:
- Die NodeSet-Dateien der Spezifikation und aller Abhängigkeiten beschaffen, mit der Anwendung ausliefern und in der richtigen Reihenfolge laden (DI, IA, Machinery).
- Die Namespaces der Spezifikationen über ihre URI auflösen, um die
OpcNodeIdundOpcNameder Typen und Instanzen zu bilden. - Die Instanz anlegen und ihre Typdefinition (
TypeDefinitionId) setzen. - Jeden Kind-Node mit dem Namen, dem Namespace und dem Datentyp aus der Spezifikation selbst anlegen.
- Die Verweise auf die Schnittstellen des Typs (HasInterface) von Hand eintragen.
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;
}
}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 ClassDie 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:
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
}
}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 ClassMit 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):
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;
}
}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 ClassWas das Companion SDK übernimmt
| Aufgabe | ohne Companion SDK | mit Companion SDK |
|---|---|---|
| NodeSets und Abhängigkeiten | Dateien beschaffen, ausliefern und in der richtigen Reihenfolge laden | eingebettet; die Server-Klasse bringt alle Abhängigkeiten mit |
| Namespaces | URIs kennen und je Namespace mit OpcNamespace. auflösen | Opc, GetName und Konstanten für alle OpcNodeId |
| Instanz eines Typs | Node anlegen, TypeDefinitionId setzen, jeden Kind-Node mit Name, Namespace und Datentyp selbst anlegen | new Opc legt die Kind-Nodes an |
| Optionale Kind-Nodes | einzeln entscheiden und anlegen | standardmäßig alle; gezielte Auswahl über ein OpcNodeModel |
| Schnittstellen | HasInterface-Verweise von Hand eintragen | automatisch aus den implementierten .NET-Schnittstellen |
| Werte | Kind-Nodes über Name oder OpcNodeId finden und den Wert setzen | typisierte Eigenschaften, zum Beispiel Manufacturer. |
| Methoden | Methoden-Nodes suchen, IOpc mit untypisierten Argumenten | typisierte Methode überschreiben |
| Attribute aus der Spezifikation (Beschreibung, Zugriffsrechte, Standardwerte) | von Hand übernehmen | über OpcNodeModel. aus der Typdeklaration |
| Client | Namespaces auflösen und Datentypen selbst registrieren | die 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:
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);
}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 UsingTypisierte 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.

