# Nodes browsen

> Anleitung: Den Adressraum eines Servers untersuchen: vom ObjectsFolder aus Node für Node oder mehrere Ebenen in einem Durchgang.

**Beispielanwendungen:**

- Client.Browsing ([C#](https://github.com/Traeger-GmbH/opcuanet-samples/tree/master/cs/Basic/Client.Browsing), [VB.NET](https://github.com/Traeger-GmbH/opcuanet-samples/tree/master/vb/Basic/Client.Browsing)): Durchsucht den Adressraum des Servers ab dem ObjectsFolder und gibt die Nodes mit ihren Attributen eingerückt aus.
- Client.NodeExplorer ([C#](https://github.com/Traeger-GmbH/opcuanet-samples/tree/master/cs/Scenarios/Client.NodeExplorer), [VB.NET](https://github.com/Traeger-GmbH/opcuanet-samples/tree/master/vb/Scenarios/Client.NodeExplorer)): Durchsucht den Adressraum eines Servers und zeigt ihn als Baum in einer Windows-Forms-Anwendung.
- Server ([C#](https://github.com/Traeger-GmbH/opcuanet-samples/tree/master/cs/Basic/Server), [VB.NET](https://github.com/Traeger-GmbH/opcuanet-samples/tree/master/vb/Basic/Server)): Stellt mit einem eigenen Node-Manager eine Maschine mit einigen Nodes bereit.

## Adressraum erkunden

Beteiligte Typen: `OpcClient`, `OpcNodeId`, `OpcNodeInfo`, `OpcAttribute`, `OpcAttributeInfo`, `OpcObjectTypes`

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](https://www.traeger.de/docs/development/opcua/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:

```csharp
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);
}
```

```vbnet
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 Sub
```

Die 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](https://www.traeger.de/docs/development/opcua/client/read-write-values#werte-lesen).

## Nodes und Kind-Nodes abfragen

Beteiligte Typen: `OpcClient`, `OpcNodeId`, `OpcNodeInfo`, `OpcAttribute`, `OpcAttributeInfo`, `OpcMethodNodeInfo`, `OpcArgumentInfo`, `OpcNodeCategory`, `OpcObjectTypes`

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**:

```csharp
OpcNodeInfo machineNode = client.BrowseNode("ns=2;s=Machine");
```

```vbnet
Dim machineNode As OpcNodeInfo = client.BrowseNode("ns=2;s=Machine")
```

**Kind-Nodes mit den Methoden `Child` oder `Children` browsen**:

```csharp
OpcNodeInfo jobNode = machineNode.Child("Job");
 
foreach (var childNode in machineNode.Children()) {
    // Your code to operate on each child node.
}
```

```vbnet
Dim jobNode As OpcNodeInfo = machineNode.Child("Job")

For Each childNode In machineNode.Children()
    'Your code to operate on each child node.
Next
```

**Attribute mit den Methoden `Attribute` oder `Attributes` browsen**:

```csharp
OpcAttributeInfo displayName = machineNode.Attribute(OpcAttribute.DisplayName);
 
foreach (var attribute in machineNode.Attributes()) {
    // Your code to operate on each attribute.
}
```

```vbnet
Dim displayName As OpcAttributeInfo = machineNode.Attribute(OpcAttribute.DisplayName)

For Each attribute In machineNode.Attributes()
    'Your code to operate on each attribute.
Next
```

**Einen Methoden-Node untersuchen**:

```csharp
if (childNode.Category == OpcNodeCategory.Method) {
    var methodNode = (OpcMethodNodeInfo)childNode;
 
    foreach (var argument in methodNode.GetInputArguments()) {
        // Your code to operate on each argument.
    }
}
```

```vbnet
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 If
```

**Eltern-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:

```csharp
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);
```

```vbnet
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 Typen: `OpcNodeInfo`, `OpcNodeId`, `OpcBrowseNode`, `OpcReferenceType`, `OpcBrowseNodeDegree`, `OpcBrowseOptions`

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:

```csharp
// 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...
}
```

```vbnet
'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...
Next
```

Auch 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 Typen: `OpcClient`, `OpcNodeId`, `OpcTranslatePath`, `OpcTranslatePathResult`

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`:

```csharp
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));

```

```vbnet
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 If
```

Der Pfad kann auch ab einem bekannten Node aus einzelnen Namen angegeben werden:

```csharp
OpcNodeId temperatureId = client.TranslatePath(
        OpcObjectTypes.ObjectsFolder,
        "2:Machine",
        "2:Temperature");
```

```vbnet
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](https://www.traeger.de/docs/development/opcua/client/bulk-access#wie-das-sdk-gro%C3%9Fe-anfragen-aufteilt)). Jedes Ergebnis trägt den aufgelösten Identifier und einen Status:

```csharp
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);
}

```

```vbnet
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
Next
```

## Große Ergebnisse durchlaufen

Beteiligte Typen: `OpcClient`, `OpcNodeInfo`, `OpcNodeCategory`

`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:

```csharp
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;
    }
}

```

```vbnet
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 Function
```

Wenn Sie einen großen Teilbaum vollständig untersuchen, ist das Verfahren aus [Schneller browsen](https://www.traeger.de/docs/development/opcua/client/browse#schneller-browsen) meist schneller, weil es mehrere Ebenen in einer Anfrage abruft.
