diff --git a/samples/custom-structures-client/src/bin/dynamic_client.rs b/samples/custom-structures-client/src/bin/dynamic_client.rs new file mode 100644 index 00000000..683fc32f --- /dev/null +++ b/samples/custom-structures-client/src/bin/dynamic_client.rs @@ -0,0 +1,71 @@ +// 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, Session}, + types::{ + custom::{DynamicStructure, DynamicTypeLoader}, + errors::OpcUaError, + BrowsePath, ObjectId, TimestampsToReturn, TypeLoader, Variant, + }, +}; +use opcua_structure_client::client_connect; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let (session, handle, ns) = client_connect().await?; + read_structure_var(&session, ns).await?; + + session.disconnect().await?; + handle.await.unwrap(); + Ok(()) +} + +async fn read_structure_var(session: &Arc, ns: u16) -> Result<(), OpcUaError> { + 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; + session.add_type_loader(loader.clone()); + + let res = session + .translate_browse_paths_to_node_ids(&[BrowsePath { + starting_node: ObjectId::ObjectsFolder.into(), + relative_path: "/0:ErrorData".try_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(()) +} diff --git a/samples/custom-structures-client/src/bin/native_client.rs b/samples/custom-structures-client/src/bin/native_client.rs new file mode 100644 index 00000000..257d0e91 --- /dev/null +++ b/samples/custom-structures-client/src/bin/native_client.rs @@ -0,0 +1,89 @@ +use std::sync::Arc; + +use opcua::{ + client::{custom_types::DataTypeTreeBuilder, Session}, + types::{errors::OpcUaError, BrowsePath, ObjectId, TimestampsToReturn, TypeLoader, Variant}, +}; +use opcua_structure_client::client_connect; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let (session, handle, ns) = client_connect().await?; + read_structure_var(&session, ns).await?; + + session.disconnect().await?; + handle.await.unwrap(); + Ok(()) +} + +async fn read_structure_var(session: &Arc, ns: u16) -> Result<(), OpcUaError> { + let type_tree = DataTypeTreeBuilder::new(|f| f.namespace <= ns) + .build(session) + .await + .unwrap(); + let type_tree = Arc::new(type_tree); + + //session.add_type_loader(loader.clone()); + + let res = session + .translate_browse_paths_to_node_ids(&[BrowsePath { + starting_node: ObjectId::ObjectsFolder.into(), + relative_path: "/0:ErrorData".try_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); + Ok(()) +} + +// The struct and enum code after this line could/should be shared with demo server, +// but having it here makes the example self-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, +} diff --git a/samples/custom-structures-client/src/lib.rs b/samples/custom-structures-client/src/lib.rs new file mode 100644 index 00000000..345794ec --- /dev/null +++ b/samples/custom-structures-client/src/lib.rs @@ -0,0 +1,85 @@ +use std::sync::Arc; + +use opcua::{ + client::{ClientBuilder, IdentityToken, Session}, + crypto::SecurityPolicy, + types::{MessageSecurityMode, StatusCode, UserTokenPolicy}, +}; +use tokio::task::JoinHandle; + +const NAMESPACE_URI: &str = "urn:DemoServer"; + +struct Args { + help: bool, + url: String, +} + +impl Args { + pub fn parse_args() -> Result> { + 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 + ); + } +} + +pub const DEFAULT_URL: &str = "opc.tcp://localhost:4855"; + +pub async fn client_connect( +) -> Result<(Arc, JoinHandle, u16), Box> { + // Read command line arguments + let args = Args::parse_args()?; + if args.help { + Args::usage(); + return Err("Help requested, exiting".into()); + } + // 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))?; + + Ok((session, handle, ns)) +} diff --git a/samples/custom-structures-client/src/main.rs b/samples/custom-structures-client/src/main.rs deleted file mode 100644 index 29643dbc..00000000 --- a/samples/custom-structures-client/src/main.rs +++ /dev/null @@ -1,184 +0,0 @@ -// 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}, - errors::OpcUaError, - BrowsePath, MessageSecurityMode, ObjectId, TimestampsToReturn, TypeLoader, UserTokenPolicy, - Variant, - }, -}; - -const NAMESPACE_URI: &str = "urn:DemoServer"; - -struct Args { - help: bool, - url: String, -} - -impl Args { - pub fn parse_args() -> Result> { - 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> { - // 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, ns: u16) -> Result<(), OpcUaError> { - 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; - session.add_type_loader(loader.clone()); - - let res = session - .translate_browse_paths_to_node_ids(&[BrowsePath { - starting_node: ObjectId::ObjectsFolder.into(), - relative_path: "/0:ErrorData".try_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 this line could/should be shared with demo server, -// but having it here makes the example self-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, -}