Anleitung
Nodes browsen
Den Adressraum eines Servers untersuchen: vom ObjectsFolder aus Node für Node oder mehrere Ebenen in einem Durchgang.
Anleitung
Nodes browsen
Adressraum erkunden
Beteiligte TypenOpcClientOpcNodeIdOpcNodeInfoOpcAttributeOpcAttributeInfoOpcObjectTypes
Wenn der Adressraum eines Servers, also die Gesamtheit seiner Nodes, noch weitgehend unbekannt ist, untersuchen Sie zuerst die bereitgestellten Nodes. Dafür eignet sich ein grafischer OPC UA Client wie OPC Watch (Seite Downloads). Alternativ untersuchen Sie den Adressraum des Servers manuell.
Mit der Klasse OpcObjectTypes browsen Sie vordefinierte Server-Nodes. Die Wurzel (engl. Root) aller Nodes des Servers ist der Standard-Node ObjectsFolder. Wenn Sie das Browsen beim ObjectsFolder beginnen, ermitteln Sie den gesamten Adressraum des Servers:
var node = client.BrowseNode(OpcObjectTypes.ObjectsFolder);
Browse(node);
...
private void Browse(OpcNodeInfo node, int level = 0)
{
Console.WriteLine("{0}{1}({2})",
new string('.', level * 4),
node.Attribute(OpcAttribute.DisplayName).Value,
node.NodeId);
level++;
foreach (var childNode in node.Children())
Browse(childNode, level);
}Dim node = client.BrowseNode(OpcObjectTypes.ObjectsFolder)
Browse(node)
...
Private Sub Browse(ByVal node As OpcNodeInfo, ByVal Optional level As Integer = 0)
Console.WriteLine("{0}{1}({2})", _
New String("."c, level * 4), _
node.Attribute(OpcAttribute.DisplayName).Value, _
node.NodeId)
level += 1
For Each childNode In node.Children()
Browse(childNode, level)
Next
End SubDie Ausgabe des Codes enthält unter anderem die NodeIds der Nodes in runden Klammern. Über diese NodeIds spricht der Client die Nodes des Servers direkt an. Übergeben Sie dazu die NodeId als Zeichenkette in doppelten Anführungszeichen an die passende Methode der Klasse OpcClient. Wie das beim Lesen eines Node-Werts aussieht, zeigt der Abschnitt Werte lesen.
Nodes und Kind-Nodes abfragen
Beteiligte TypenOpcClientOpcNodeIdOpcNodeInfoOpcAttributeOpcAttributeInfoOpcMethodNodeInfoOpcArgumentInfoOpcNodeCategoryOpcObjectTypes
Das Browsen in OPC UA lässt sich mit Reflection in .NET vergleichen. Ein Client ermittelt und untersucht damit den gesamten Adressraum eines Servers dynamisch. Dazu gehören unter anderem alle Nodes, ihre Referenzen untereinander und ihre Node-Typen. Das Browsen beginnt mit der NodeId (OpcNodeId) des Nodes, bei dem der Browse-Vorgang starten soll. Ausgehend von der erhaltenen OpcNodeInfo setzt der Client das Browsen bei den Kind-Nodes, den Eltern-Nodes oder den Attributen fort.
Wenn ein Node eine Methode darstellt, liefert das Browsen eine Instanz von OpcMethodNodeInfo. Darüber untersuchen Sie zusätzlich die Input- und Output-Argumente der Methode.
Die OpcNodeInfo des gewünschten Nodes ermitteln:
OpcNodeInfo machineNode = client.BrowseNode("ns=2;s=Machine");Dim machineNode As OpcNodeInfo = client.BrowseNode("ns=2;s=Machine")Kind-Nodes mit den Methoden Child oder Children browsen:
OpcNodeInfo jobNode = machineNode.Child("Job");
foreach (var childNode in machineNode.Children()) {
// Your code to operate on each child node.
}Dim jobNode As OpcNodeInfo = machineNode.Child("Job")
For Each childNode In machineNode.Children()
'Your code to operate on each child node.
NextAttribute mit den Methoden Attribute oder Attributes browsen:
OpcAttributeInfo displayName = machineNode.Attribute(OpcAttribute.DisplayName);
foreach (var attribute in machineNode.Attributes()) {
// Your code to operate on each attribute.
}Dim displayName As OpcAttributeInfo = machineNode.Attribute(OpcAttribute.DisplayName)
For Each attribute In machineNode.Attributes()
'Your code to operate on each attribute.
NextEinen Methoden-Node untersuchen:
if (childNode.Category == OpcNodeCategory.Method) {
var methodNode = (OpcMethodNodeInfo)childNode;
foreach (var argument in methodNode.GetInputArguments()) {
// Your code to operate on each argument.
}
}If childNode.Category = OpcNodeCategory.Method Then
Dim methodNode = CType(childNode, OpcMethodNodeInfo)
For Each argument In methodNode.GetInputArguments()
'Your code to operate on each argument.
Next
End IfEltern-Nodes mit den Methoden Parent oder Parents browsen. Die Methoden Children und Parents lassen sich zudem auf Nodes einer bestimmten OpcNodeCategory oder eines bestimmten OpcReferenceType einschränken. Die Methode AttributeValue liefert den Wert eines Attributs direkt im erwarteten Typ:
foreach (OpcNodeInfo variableNode in machineNode.Children(OpcNodeCategory.Variable)) {
// Your code to operate on each variable node.
}
foreach (OpcNodeInfo parentNode in jobNode.Parents()) {
// Your code to operate on each parent node.
}
int speed = jobNode.Child("Speed").AttributeValue<int>(OpcAttribute.Value);For Each variableNode As OpcNodeInfo In machineNode.Children(OpcNodeCategory.Variable)
'Your code to operate on each variable node.
Next
For Each parentNode As OpcNodeInfo In jobNode.Parents()
'Your code to operate on each parent node.
Next
Dim speed As Integer = jobNode.Child("Speed").AttributeValue(Of Integer)(OpcAttribute.Value)Schneller browsen
Beteiligte TypenOpcNodeInfoOpcNodeIdOpcBrowseNodeOpcReferenceTypeOpcBrowseNodeDegreeOpcBrowseOptions
Wenn ein Server einen sehr umfangreichen Adressraum bereitstellt, ist das Browsen aller Nodes häufig langsam. Das gilt besonders, wenn die vielen Node-Informationen über schwache Hardware oder ein schwaches Netzwerk laufen. Wenn Clients den gesamten Node-Baum Node für Node durchlaufen, belastet das zusätzlich den Server und mindert seine Leistung.
Deshalb legen Sie im SDK fest, wie viele Nodes und Node-Ebenen der Client zugleich untersucht. Dafür dient die Enumeration OpcBrowseNodeDegree. Abhängig davon, wie viele Nodes Sie zugleich benötigen, setzen Sie den passenden Wert über die Eigenschaft Degree oder im Konstruktor der Klasse OpcBrowseNode. Zusätzlich können Sie die Nutzdaten beim Browsen auf ein Minimum oder auf die relevanten Daten reduzieren. Dafür bietet die Enumeration OpcBrowseOptions mehrere Werte. Standardmäßig untersucht eine Instanz von OpcBrowseNode mit dem Grad Self alle hierarchischen Verweise (HierarchicalReferences) und fordert alle Node-Informationen an (OpcBrowseOptions.IncludeAll). Wenn Sie die Node-Informationen reduzieren und die Nodes später nach ihrem Verweistyp filtern, zum Beispiel über Children(OpcReferenceType), muss die ReferenceTypeId Teil der Node-Informationen bleiben. Wenn Sie weitere Node-Informationen wie den DisplayName benötigen, schließen Sie diese in der Eigenschaft Options mit ein.
Mit den folgenden Eigenschaften parametrisieren und optimieren Sie das Browsen:
// Create a browse command to browse all hierarchical references.
var browse = new OpcBrowseNode(
nodeId: OpcNodeId.Parse("ns=2;s=Machine"),
degree: OpcBrowseNodeDegree.Generation);
// Create a browse command to browse specific types of references.
var browse = new OpcBrowseNode(
nodeId: OpcNodeId.Parse("ns=2;s=Machine"),
degree: OpcBrowseNodeDegree.Generation,
referenceTypes: new[] {
OpcReferenceType.Organizes,
OpcReferenceType.HasComponent,
OpcReferenceType.HasProperty
});
// Reduce browsing to the smallest possible amount of data.
browse.Options = OpcBrowseOptions.IncludeReferenceTypeId
| OpcBrowseOptions.IncludeBrowseName;
var node = client.BrowseNode(browse);
foreach (var childNode in node.Children()) {
// Continue recursively...
}'Create a browse command to browse all hierarchical references.
Dim browse = New OpcBrowseNode( _
nodeId:=OpcNodeId.Parse("ns=2;s=Machine"), _
degree:=OpcBrowseNodeDegree.Generation)
'Create a browse command to browse specific types of references.
Dim browse = New OpcBrowseNode( _
nodeId:=OpcNodeId.Parse("ns=2;s=Machine"), _
degree:=OpcBrowseNodeDegree.Generation, _
referenceTypes:={ _
OpcReferenceType.Organizes, _
OpcReferenceType.HasComponent, _
OpcReferenceType.HasProperty _
})
'Reduce browsing to the smallest possible amount of data.
browse.Options = OpcBrowseOptions.IncludeReferenceTypeId _
Or OpcBrowseOptions.IncludeBrowseName
Dim node = client.BrowseNode(browse)
For Each childNode In node.Children()
'Continue recursively...
NextAuch wenn Sie nur Nodes untersuchen, die in einer bestimmten Beziehung zueinander stehen, lässt sich das Browsen weiter optimieren. Diese Beziehung drücken die ReferenceTypes aus. Übergeben Sie die relevanten Referenzen als Parameter der Browse-Operation, wie im Code oben gezeigt. Dann besucht der Client nur die Nodes, die einen der angegebenen ReferenceTypes verwenden.
Browse-Parameter im Detail
Bei einem umfangreichen Adressraum sollten die Last auf dem Server und die gesamte Kommunikation zwischen Client und Server so gering wie möglich bleiben. Dafür bietet OPC UA mehrere Mechanismen, mit denen ein Client dem Server signalisiert, dass er deutlich mehr Nodes als nur die einer Ebene untersucht. Der Server kann die Informationen dann vorbereiten und dem Client paketweise liefern. So muss der Server den Node-Baum nicht bei jeder Anfrage des Clients erneut intern verarbeiten. Zusätzlich kann ein Client mit passender Logik weitere Nodes im Voraus vom Server abfragen. Das betrifft Nodes, deren Informationen er sicher noch verarbeitet, ebenso wie die ihrer Kinder, Kindeskinder und so weiter.
Das SDK bietet mehrere Browse-Parameter, die das Verhalten beim Browsen steuern. Anhand dieser Parameter und des bisherigen Browse-Verlaufs entscheidet das SDK, welche Nodes mit welchen Informationen der Server vorab vorbereitet. Diese Nodes ruft das SDK beim weiteren Browsen automatisch ab und speichert sie zwischen. Danach liefert es sie aus dem internen Cache, statt den Server erneut anzufragen.
Welche Nodes das SDK automatisch zugleich beim Server anfordert, steuert ein Wert der Enumeration OpcBrowseNodeDegree. Der Wert Self („selbst“) entspricht dem Standardverhalten: Der Client untersucht immer nur die Kinder des aktuellen Nodes. Im Schema links beginnt das Browsen beim Node A1. Der Server liefert nur die Nodes direkt unterhalb von A1, also A1-B1 und A1-B2. Die Nodes aller weiteren Teilbäume gehören nicht zur Browse-Operation, auch nicht die der Nodes neben dem Start-Node A1.
Mit dem Wert Sibling („Geschwister“) ruft das SDK beim Untersuchen der Kind-Nodes des aktuellen Nodes zugleich alle Kinder der Geschwister-Nodes vom Server ab. Wenn die Browse-Operation bei A1 beginnt, ermittelt das SDK nicht nur die direkten Kind-Nodes von A1, sondern auch die aller Geschwister von A1. Die Browse-Operation ermittelt also auch die Teilbäume von A2 bis An. Das SDK speichert die erhaltenen Node-Informationen zwischen. Sobald Ihre Anwendung zum Beispiel den Teilbaum von A2 benötigt, liefert das SDK ihn aus dem Speicher, statt ihn erneut vom Server abzurufen. Das Bild rechts zeigt: Eine Browse-Operation ab A1 verhält sich so, als würde der Client die Nodes A1 bis An gleichzeitig browsen.
Mit dem Wert Generation untersucht das SDK alle Nodes derselben Generation zugleich, also alle Nodes derselben Ebenentiefe. Das Schema links zeigt eine Browse-Operation, die beim Node A1-B1 beginnt. Das SDK ruft nicht nur dessen Teilbaum ab, wie beim Browsen mit OpcBrowseNodeDegree.Self. Es ruft auch die Teilbäume seiner Geschwister ab, wie beim Browsen mit OpcBrowseNodeDegree.Sibling, und die Teilbäume seiner Cousinen und Cousins n-ten Grades. Das Browsen verhält sich also so, als würde der Client die Nodes A1-B1, A1-B2, A2-B1 und A2-B2 zugleich browsen. Auch hier speichert das SDK die Node-Informationen der Teilbäume zwischen und stellt sie bei Bedarf bereit.
Setzen Sie die Werte von OpcBrowseNodeDegree mit Vorsicht ein. Wenn ein Server zum Beispiel einen relativ flachen Adressraum mit sehr vielen Nodes je Teilbaum bereitstellt, kann Generation zu langen und speicherintensiven Browse-Operationen führen. Gleiches gilt für Sibling, besonders wenn ein Node sehr viele kleine Teilbäume auf derselben Ebene besitzt, die ihrerseits viele Kind-Nodes enthalten.
Wir empfehlen deshalb: Wenn Sie die Komplexität des Servers vorab kennen, wählen Sie den Grad der Browse-Operationen danach. Wenn der Server unbekannt ist oder wechselt und Sie den Grad Sibling oder Generation wählen, reduzieren Sie die Node-Informationen über die Eigenschaft Options. Weitere benötigte Node-Informationen rufen Sie bei Bedarf mit einer gesonderten Browse-Operation ab.
Nodes über Pfade finden
Beteiligte TypenOpcClientOpcNodeIdOpcTranslatePathOpcTranslatePathResult
Statt Identifier fest einzutragen, lassen sich Nodes über ihren Pfad aus BrowseNames ermitteln. Das ist der übliche Weg bei standardisierten Informationsmodellen (Companion-Spezifikationen): Geräte verschiedener Hersteller verwenden unterschiedliche Identifier, aber dieselbe Struktur. Der Pfad folgt dem Textformat der OPC-UA-Spezifikation. Ein / folgt einem hierarchischen Verweis, ein . einem Aggregations-Verweis (Komponente oder Eigenschaft). Der Namespace-Index steht vor dem Namen (2:Machine). Ohne Start-Node beginnt der Pfad beim RootFolder. Wenn sich der Pfad nicht auflösen lässt, liefert die Methode TranslatePath den Wert null:
OpcNodeId speedId = client.TranslatePath("/Objects/2:Machine/2:Speed");
if (speedId == null)
Console.WriteLine("The path does not exist.");
else
Console.WriteLine("Speed: {0}", client.ReadNode(speedId));Dim speedId As OpcNodeId = client.TranslatePath("/Objects/2:Machine/2:Speed")
If speedId Is Nothing Then
Console.WriteLine("The path does not exist.")
Else
Console.WriteLine("Speed: {0}", client.ReadNode(speedId))
End IfDer Pfad kann auch ab einem bekannten Node aus einzelnen Namen angegeben werden:
OpcNodeId temperatureId = client.TranslatePath(
OpcObjectTypes.ObjectsFolder,
"2:Machine",
"2:Temperature");Dim temperatureId As OpcNodeId = client.TranslatePath( _
OpcObjectTypes.ObjectsFolder, _
"2:Machine", _
"2:Temperature")Viele Pfade löst TranslatePaths in einem Aufruf auf. Das SDK teilt große Anfragen nach den Grenzen des Servers auf (Abschnitt Wie das SDK große Anfragen aufteilt). Jedes Ergebnis trägt den aufgelösten Identifier und einen Status:
OpcTranslatePathResultCollection results = client.TranslatePaths(
new OpcTranslatePath("/Objects/2:Machine/2:Speed"),
new OpcTranslatePath("/Objects/2:Machine/2:Temperature"),
new OpcTranslatePath("/Objects/2:Machine/2:IsRunning"));
foreach (OpcTranslatePathResult result in results) {
if (result.IsGood)
Console.WriteLine(result.NodeId);
else
Console.WriteLine("Not found: {0}", result.Code);
}Dim results As OpcTranslatePathResultCollection = client.TranslatePaths( _
New OpcTranslatePath("/Objects/2:Machine/2:Speed"), _
New OpcTranslatePath("/Objects/2:Machine/2:Temperature"), _
New OpcTranslatePath("/Objects/2:Machine/2:IsRunning"))
For Each result As OpcTranslatePathResult In results
If result.IsGood Then
Console.WriteLine(result.NodeId)
Else
Console.WriteLine("Not found: {0}", result.Code)
End If
NextGroße Ergebnisse durchlaufen
Beteiligte TypenOpcClientOpcNodeInfoOpcNodeCategory
Children() liefert die Kind-Nodes als Aufzählung. Wenn der Server ein zu großes Ergebnis aufteilt, fordert der Client die Fortsetzungen (BrowseNext) selbst an, während Ihre Anwendung die Aufzählung durchläuft. Mit einer rekursiven Aufzählung und LINQ lässt sich ein Teilbaum deshalb nach beliebigen Kriterien durchsuchen. FirstOrDefault beendet die Suche, sobald ein passender Node gefunden ist. Weitere Teilbäume fragt der Client dann nicht mehr an:
OpcNodeInfo machine = client.BrowseNode("ns=2;s=Machine");
OpcNodeInfo firstAlarm = Descendants(machine)
.Where(node => node.Category == OpcNodeCategory.Variable)
.FirstOrDefault(node => node.Name.Value.StartsWith("Alarm"));
...
private static IEnumerable<OpcNodeInfo> Descendants(OpcNodeInfo node)
{
foreach (OpcNodeInfo child in node.Children()) {
yield return child;
foreach (OpcNodeInfo descendant in Descendants(child))
yield return descendant;
}
}Dim machine As OpcNodeInfo = client.BrowseNode("ns=2;s=Machine")
Dim firstAlarm As OpcNodeInfo = Descendants(machine) _
.Where(Function(node) node.Category = OpcNodeCategory.Variable) _
.FirstOrDefault(Function(node) node.Name.Value.StartsWith("Alarm"))
...
Private Shared Iterator Function Descendants( _
ByVal node As OpcNodeInfo) As IEnumerable(Of OpcNodeInfo)
For Each child As OpcNodeInfo In node.Children()
Yield child
For Each descendant As OpcNodeInfo In Descendants(child)
Yield descendant
Next
Next
End FunctionWenn Sie einen großen Teilbaum vollständig untersuchen, ist das Verfahren aus Schneller browsen meist schneller, weil es mehrere Ebenen in einer Anfrage abruft.

