Skip to content

Commit a8005bd

Browse files
authored
Recursive browser (#56)
Utility for recursively browsing the node hierarchy clientside. This is something a lot of OPC-UA client applications need to do at some point, which isn't necessarily trivial to implement well. It makes sense to include a generic utility for doing this in the client library. If it isn't sufficiently flexible users are always free to use it as a reference and build something using the services directly.
1 parent f1bc40d commit a8005bd

File tree

15 files changed

+1336
-413
lines changed

15 files changed

+1336
-413
lines changed

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/tests/integration/browse.rs

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ use opcua::{
88
RelativePathElement, StatusCode, VariableTypeId,
99
},
1010
};
11-
use opcua_types::{AttributeId, ReadValueId, TimestampsToReturn, Variant};
11+
use opcua_client::browser::BrowseFilter;
12+
use opcua_nodes::DefaultTypeTree;
13+
use opcua_types::{AttributeId, ReadValueId, TimestampsToReturn, VariableId, Variant};
1214

1315
fn hierarchical_desc(node_id: NodeId) -> BrowseDescription {
1416
BrowseDescription {
@@ -579,3 +581,74 @@ async fn translate_browse_paths_auto_impl() {
579581
)))
580582
);
581583
}
584+
585+
#[tokio::test]
586+
async fn test_recursive_browser() {
587+
let (_tester, _nm, session) = setup().await;
588+
589+
let filter = BrowseFilter::new_hierarchical();
590+
let to_browse = vec![filter.new_description_from_node(ObjectId::TypesFolder.into())];
591+
let res = session
592+
.browser()
593+
.max_concurrent_requests(3)
594+
.handler(filter)
595+
.run_into_result(to_browse)
596+
.await
597+
.unwrap();
598+
599+
assert_eq!(3740, res.nodes.len());
600+
601+
// Try to get some event fields.
602+
let rs: Vec<_> = res
603+
.references
604+
.find_references(
605+
&ObjectTypeId::BaseEventType.into(),
606+
None::<(NodeId, _)>,
607+
&DefaultTypeTree::new(),
608+
BrowseDirection::Forward,
609+
)
610+
.collect();
611+
612+
assert_eq!(rs.len(), 21);
613+
614+
assert!(rs
615+
.iter()
616+
.find(|r| *r.target_node == VariableId::BaseEventType_EventId)
617+
.is_some());
618+
}
619+
620+
#[tokio::test]
621+
// Hit the same node multiple times.
622+
async fn test_recursive_browser_multi_hit() {
623+
let (_tester, _nm, session) = setup().await;
624+
625+
let filter = BrowseFilter::new(BrowseDirection::Forward, ReferenceTypeId::References, true);
626+
let to_browse = vec![filter.new_description_from_node(ObjectId::TypesFolder.into())];
627+
let res = session
628+
.browser()
629+
.max_concurrent_requests(3)
630+
.handler(filter)
631+
.run_into_result(to_browse)
632+
.await
633+
.unwrap();
634+
635+
assert_eq!(4228, res.nodes.len());
636+
637+
// This one will be referenced from many places.
638+
assert!(res
639+
.nodes
640+
.contains_key(&NodeId::from(ObjectId::ModellingRule_Mandatory)));
641+
642+
// Get the nodes that have a mandatory modelling rule
643+
let rs: Vec<_> = res
644+
.references
645+
.find_references(
646+
&ObjectId::ModellingRule_Mandatory.into(),
647+
None::<(NodeId, _)>,
648+
&DefaultTypeTree::new(),
649+
BrowseDirection::Inverse,
650+
)
651+
.collect();
652+
653+
assert_eq!(rs.len(), 2164);
654+
}

opcua-client/Cargo.toml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
[package]
22
name = "opcua-client"
3-
version = "0.13.0" # OPCUARustVersion
3+
version = "0.13.0" # OPCUARustVersion
44
description = "OPC UA client API"
55
authors = ["Adam Lock <[email protected]>", "Einar Omang <[email protected]>"]
66
homepage = "https://github.com/locka99/opcua"
77
repository = "https://github.com/locka99/opcua"
88
license = "MPL-2.0"
9-
keywords = ["opcua","opc","ua"]
10-
categories = ["embedded","network-programming"]
9+
keywords = ["opcua", "opc", "ua"]
10+
categories = ["embedded", "network-programming"]
1111
readme = "../README.md"
1212
documentation = "https://docs.rs/opcua/"
1313
edition = "2021"
@@ -20,6 +20,7 @@ arc-swap = { workspace = true }
2020
async-trait = { workspace = true }
2121
chrono = { workspace = true }
2222
futures = { workspace = true }
23+
hashbrown = { workspace = true }
2324
lazy_static = { workspace = true }
2425
log = { workspace = true }
2526
parking_lot = { workspace = true }
@@ -32,4 +33,5 @@ serde = { workspace = true }
3233
opcua-types = { path = "../opcua-types" }
3334
opcua-core = { path = "../opcua-core" }
3435
opcua-crypto = { path = "../opcua-crypto" }
36+
opcua-nodes = { path = "../opcua-nodes" }
3537
opcua-xml = { path = "../opcua-xml", optional = true }

0 commit comments

Comments
 (0)