-
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.
- Loading branch information
Olivier
committed
Jan 11, 2025
1 parent
b013103
commit 541695b
Showing
4 changed files
with
245 additions
and
184 deletions.
There are no files selected for viewing
71 changes: 71 additions & 0 deletions
71
samples/custom-structures-client/src/bin/dynamic_client.rs
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,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(()) | ||
} |
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,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, | ||
} |
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,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)) | ||
} |
Oops, something went wrong.