Skip to content

Commit

Permalink
add example client to read custom structs and enums from server
Browse files Browse the repository at this point in the history
  • Loading branch information
Olivier committed Jan 8, 2025
1 parent 06f1b2c commit d29c249
Show file tree
Hide file tree
Showing 5 changed files with 211 additions and 0 deletions.
10 changes: 10 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 16 additions & 0 deletions samples/custom-structures-client/Cargo.toml
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
6 changes: 6 additions & 0 deletions samples/custom-structures-client/README.md
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
178 changes: 178 additions & 0 deletions samples/custom-structures-client/src/main.rs
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,
}
1 change: 1 addition & 0 deletions samples/demo-server/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ Use `simple-server` as reference for a very simple OPC UA server.
Use `demo-server` (this project) for a more full-featured server that demonstrates the following.

- Exposes static and dynamically changing variables
- Expose custom structure and enumeration
- Variables of every supported data type including arrays
- Events
- Http access to diagnostics and other info
Expand Down

0 comments on commit d29c249

Please sign in to comment.