Skip to content

Commit

Permalink
split client in two
Browse files Browse the repository at this point in the history
  • Loading branch information
Olivier committed Jan 11, 2025
1 parent 5b2f57e commit 1dd962c
Show file tree
Hide file tree
Showing 4 changed files with 245 additions and 184 deletions.
71 changes: 71 additions & 0 deletions samples/custom-structures-client/src/bin/dynamic_client.rs
Original file line number Diff line number Diff line change
@@ -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<dyn std::error::Error>> {
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<Session>, 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<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: "/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(())
}
89 changes: 89 additions & 0 deletions samples/custom-structures-client/src/bin/native_client.rs
Original file line number Diff line number Diff line change
@@ -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<dyn std::error::Error>> {
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<Session>, 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,
}
85 changes: 85 additions & 0 deletions samples/custom-structures-client/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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<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
);
}
}

pub const DEFAULT_URL: &str = "opc.tcp://localhost:4855";

pub async fn client_connect(
) -> Result<(Arc<Session>, JoinHandle<StatusCode>, u16), Box<dyn std::error::Error>> {
// 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))
}
Loading

0 comments on commit 1dd962c

Please sign in to comment.