Skip to content

Commit

Permalink
Add example to browse exhaustively browse nodes (#57)
Browse files Browse the repository at this point in the history
## Description

This demonstrates how `browse_next()` can be used to exhaustively browse
nodes until all continuation points are consumed. (Continuation points
should always be consumed to release resources on the OPC UA server.)

This was inspired by
#52 (comment).
  • Loading branch information
sgoll committed Mar 1, 2024
1 parent 3e513cc commit 2a50b75
Show file tree
Hide file tree
Showing 2 changed files with 65 additions and 9 deletions.
65 changes: 56 additions & 9 deletions examples/async_browse.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use std::collections::{hash_map::Entry, HashMap, VecDeque};
use std::{
collections::{hash_map::Entry, HashMap, VecDeque},
mem,
};

use anyhow::Context as _;
use open62541::{ua, AsyncClient, DataType as _};
Expand Down Expand Up @@ -51,12 +54,11 @@ async fn browse_hierarchy(
browse_node_ids.len()
);

let results = client
.browse_many(&browse_node_ids)
let results = browse_many_contd(client, &browse_node_ids)
.await
.context("browse")?;

for (node_id, result) in browse_node_ids.into_iter().zip(results) {
for (node_id, references) in browse_node_ids.into_iter().zip(results) {
let children = match children.entry(node_id) {
Entry::Occupied(child) => {
let node_id = child.key();
Expand All @@ -66,11 +68,6 @@ async fn browse_hierarchy(
Entry::Vacant(entry) => entry.insert(Vec::new()),
};

// TODO: Use continuation point to fetch remaining references if necessary.
let Some((references, _continuation_point)) = result else {
continue;
};

for reference in references {
pending_node_ids.push_back(reference.node_id().node_id().clone());

Expand Down Expand Up @@ -103,6 +100,56 @@ async fn browse_hierarchy(
Ok(tree_node)
}

/// Exhaustively browses several nodes at once.
///
/// This consumes any continuation points that might be returned from browsing, ensuring that all
/// references are returned eventually.
async fn browse_many_contd(
client: &AsyncClient,
node_ids: &[ua::NodeId],
) -> anyhow::Result<Vec<Vec<ua::ReferenceDescription>>> {
let mut results = client.browse_many(node_ids).await?;

debug_assert_eq!(results.len(), node_ids.len());
// Tracks index of the original node ID for this result index.
let mut result_indices: Vec<usize> = (0..results.len()).collect();
// Collects all references for the given original node ID (index).
let mut collected_references: Vec<Vec<ua::ReferenceDescription>> =
vec![Vec::new(); results.len()];

loop {
let mut continuation_points = Vec::new();

// Walk results from previous iteration. Use associated index to know which node ID was
// browsed (or continued to be browsed). While consuming the old `result_indices`, build a
// list of continuation points with matching new `result_indices` for every browse that is
// still not complete.
for (index, result) in mem::take(&mut result_indices).into_iter().zip(results) {
if let Some((references, continuation_point)) = result {
collected_references[index].extend(references);

if let Some(continuation_point) = continuation_point {
continuation_points.push(continuation_point);
result_indices.push(index);
}
}
}

// When every browse has returned without continuation point, we are done.
if continuation_points.is_empty() {
break;
}

// Otherwise, use continuation points to continue browsing (only) those node IDs that have
// still references to be returned from the server. Use `result_indices` to remember which
// index from `continuation_points` belongs to which index in `collected_references`.
results = client.browse_next(&continuation_points).await?;
debug_assert_eq!(results.len(), result_indices.len());
}

Ok(collected_references)
}

#[derive(Debug)]
struct TreeNode<T> {
value: T,
Expand Down
9 changes: 9 additions & 0 deletions src/ua/data_types/browse_request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,15 @@ impl BrowseRequest {
array.move_into_raw(&mut self.0.nodesToBrowseSize, &mut self.0.nodesToBrowse);
self
}

#[must_use]
pub fn with_requested_max_references_per_node(
mut self,
requested_max_references_per_node: u32,
) -> Self {
self.0.requestedMaxReferencesPerNode = requested_max_references_per_node;
self
}
}

impl ServiceRequest for BrowseRequest {
Expand Down

0 comments on commit 2a50b75

Please sign in to comment.