-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add example client to read custom structs and enums from server
- Loading branch information
Olivier
committed
Jan 8, 2025
1 parent
06f1b2c
commit d29c249
Showing
5 changed files
with
211 additions
and
0 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
[package] | ||
name = "opcua-structure-client" | ||
version = "0.13.0" # OPCUARustVersion | ||
authors = ["Rust-OpcUa contributors"] | ||
edition = "2021" | ||
|
||
[dependencies] | ||
pico-args = "0.5" | ||
tokio = { version = "1.36.0", features = ["full"] } | ||
log = { workspace = true } | ||
|
||
[dependencies.opcua] | ||
path = "../../lib" | ||
version = "0.13.0" # OPCUARustVersion | ||
features = ["client", "console-logging"] | ||
default-features = false |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
To run this sample: | ||
|
||
1. Launch `samples/demo-server`. That servers exposes custom enums and variables | ||
2. Run as `cargo run` | ||
|
||
The client connects to the server, read a variable and disconnects |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,178 @@ | ||
// OPCUA for Rust | ||
// SPDX-License-Identifier: MPL-2.0 | ||
// Copyright (C) 2017-2024 Adam Lock | ||
|
||
//! This simple OPC UA client will do the following: | ||
//! | ||
//! 1. Create a client configuration | ||
//! 2. Connect to an endpoint specified by the url with security None | ||
//! 3. Read a variable on server with data type being a custom structure | ||
use std::sync::Arc; | ||
|
||
use opcua::{ | ||
client::{custom_types::DataTypeTreeBuilder, ClientBuilder, IdentityToken, Session}, | ||
crypto::SecurityPolicy, | ||
types::{ | ||
custom::{DynamicStructure, DynamicTypeLoader}, | ||
BrowsePath, MessageSecurityMode, ObjectId, StatusCode, TimestampsToReturn, TypeLoader, | ||
UserTokenPolicy, Variant, | ||
}, | ||
}; | ||
|
||
const NAMESPACE_URI: &str = "urn:DemoServer"; | ||
|
||
struct Args { | ||
help: bool, | ||
url: String, | ||
} | ||
|
||
impl Args { | ||
pub fn parse_args() -> Result<Args, Box<dyn std::error::Error>> { | ||
let mut args = pico_args::Arguments::from_env(); | ||
Ok(Args { | ||
help: args.contains(["-h", "--help"]), | ||
url: args | ||
.opt_value_from_str("--url")? | ||
.unwrap_or_else(|| String::from(DEFAULT_URL)), | ||
}) | ||
} | ||
|
||
pub fn usage() { | ||
println!( | ||
r#"Simple Client | ||
Usage: | ||
-h, --help Show help | ||
--url [url] Url to connect to (default: {})"#, | ||
DEFAULT_URL | ||
); | ||
} | ||
} | ||
|
||
const DEFAULT_URL: &str = "opc.tcp://localhost:4855"; | ||
|
||
#[tokio::main] | ||
async fn main() -> Result<(), Box<dyn std::error::Error>> { | ||
// Read command line arguments | ||
let args = Args::parse_args()?; | ||
if args.help { | ||
Args::usage(); | ||
return Ok(()); | ||
} | ||
// Optional - enable OPC UA logging | ||
opcua::console_logging::init(); | ||
|
||
// Make the client configuration | ||
let mut client = ClientBuilder::new() | ||
.application_name("Simple Client") | ||
.application_uri("urn:SimpleClient") | ||
.product_uri("urn:SimpleClient") | ||
.trust_server_certs(true) | ||
.create_sample_keypair(true) | ||
.session_retry_limit(3) | ||
.client() | ||
.unwrap(); | ||
|
||
let (session, event_loop) = client | ||
.connect_to_matching_endpoint( | ||
( | ||
args.url.as_ref(), | ||
SecurityPolicy::None.to_str(), | ||
MessageSecurityMode::None, | ||
UserTokenPolicy::anonymous(), | ||
), | ||
IdentityToken::Anonymous, | ||
) | ||
.await | ||
.unwrap(); | ||
let handle = event_loop.spawn(); | ||
session.wait_for_connection().await; | ||
|
||
let ns = session | ||
.get_namespace_index(NAMESPACE_URI) | ||
.await | ||
.map_err(|e| format!("Error getting namespace index {:?}", e))?; | ||
read_structure_var(&session, ns).await?; | ||
|
||
session.disconnect().await?; | ||
handle.await.unwrap(); | ||
Ok(()) | ||
} | ||
|
||
async fn read_structure_var(session: &Arc<Session>, ns: u16) -> Result<(), StatusCode> { | ||
let type_tree = DataTypeTreeBuilder::new(|f| f.namespace <= ns) | ||
.build(session) | ||
.await | ||
.unwrap(); | ||
let type_tree = Arc::new(type_tree); | ||
let loader = Arc::new(DynamicTypeLoader::new(type_tree.clone())) as Arc<dyn TypeLoader>; | ||
session.add_type_loader(loader.clone()); | ||
|
||
let res = session | ||
.translate_browse_paths_to_node_ids(&[BrowsePath { | ||
starting_node: ObjectId::ObjectsFolder.into(), | ||
relative_path: (&["ErrorData".into()][..]).into(), | ||
}]) | ||
.await?; | ||
let Some(target) = &res[0].targets else { | ||
panic!("translate browse path did not return a NodeId") | ||
}; | ||
let node_id = &target[0].target_id.node_id; | ||
let dv = session | ||
.read(&[node_id.into()], TimestampsToReturn::Neither, 0.0) | ||
.await? | ||
.into_iter() | ||
.next() | ||
.unwrap(); | ||
dbg!(&dv); | ||
let Some(Variant::ExtensionObject(val)) = dv.value else { | ||
panic!("Unexpected variant type"); | ||
}; | ||
let val: DynamicStructure = *val.into_inner_as().unwrap(); | ||
|
||
dbg!(&val.get_field(0)); | ||
dbg!(&val.get_field(1)); | ||
dbg!(&val.get_field(2)); | ||
Ok(()) | ||
} | ||
|
||
// the struct and enum code after that line could/should be shared with demo server | ||
// but having it here make the example selv contained | ||
|
||
#[derive( | ||
Debug, | ||
Copy, | ||
Clone, | ||
PartialEq, | ||
Eq, | ||
opcua::types::UaEnum, | ||
opcua::types::BinaryEncodable, | ||
opcua::types::BinaryDecodable, | ||
)] | ||
//#[cfg_attr( | ||
//feature = "json", | ||
//derive(opcua::types::JsonEncodable, opcua::types::JsonDecodable) | ||
//)] | ||
//#[cfg_attr(feature = "xml", derive(opcua::types::FromXml))] | ||
#[derive(Default)] | ||
#[repr(i32)] | ||
pub enum AxisState { | ||
#[default] | ||
Disabled = 1i32, | ||
Enabled = 2i32, | ||
Idle = 3i32, | ||
MoveAbs = 4i32, | ||
Error = 5i32, | ||
} | ||
|
||
#[derive(Debug, Clone, PartialEq, opcua::types::BinaryEncodable, opcua::types::BinaryDecodable)] | ||
//#[cfg_attr( | ||
//feature = "json", | ||
//derive(opcua::types::JsonEncodable, opcua::types::JsonDecodable) | ||
//)] | ||
//#[cfg_attr(feature = "xml", derive(opcua::types::FromXml))] | ||
#[derive(Default)] | ||
pub struct ErrorData { | ||
message: opcua::types::UAString, | ||
error_id: u32, | ||
last_state: AxisState, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters