Skip to content

Commit

Permalink
Restructure a bit, use some new library features to simplify
Browse files Browse the repository at this point in the history
  • Loading branch information
einarmo authored and oroulet committed Mar 6, 2025
1 parent a6085e0 commit cc0209d
Show file tree
Hide file tree
Showing 5 changed files with 73 additions and 83 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.

27 changes: 25 additions & 2 deletions async-opcua-server/src/address_space/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ use crate::node_manager::{ParsedReadValueId, ParsedWriteValue, RequestContext, S
use log::debug;
use opcua_nodes::TypeTree;
use opcua_types::{
AttributeId, DataEncoding, DataTypeId, DataValue, NumericRange, StatusCode, TimestampsToReturn,
Variant, WriteMask,
AttributeId, DataEncoding, DataTypeId, DataValue, DateTime, NumericRange, StatusCode,
TimestampsToReturn, Variant, WriteMask,
};

use super::{AccessLevel, AddressSpace, HasNodeId, NodeType, Variable};
Expand Down Expand Up @@ -272,6 +272,29 @@ pub fn read_node_value(
result_value
}

/// Invoke `Write` for the given `node_to_write` on `node`.
pub fn write_node_value(
node: &mut NodeType,
node_to_write: &ParsedWriteValue,
) -> Result<(), StatusCode> {
let now = DateTime::now();
if node_to_write.attribute_id == AttributeId::Value {
if let NodeType::Variable(variable) = node {
return variable.set_value_range(
node_to_write.value.value.clone().unwrap_or_default(),
&node_to_write.index_range,
node_to_write.value.status.unwrap_or_default(),
&now,
&node_to_write.value.source_timestamp.unwrap_or(now),
);
}
}
node.as_mut_node().set_attribute(
node_to_write.attribute_id,
node_to_write.value.value.clone().unwrap_or_default(),
)
}

/// Add the given list of namespaces to the type tree in `context` and
/// `address_space`.
pub fn add_namespaces(
Expand Down
40 changes: 23 additions & 17 deletions async-opcua-server/src/node_manager/memory/simple.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ use std::{collections::HashMap, sync::Arc, time::Duration};

use async_trait::async_trait;
use opcua_core::{trace_read_lock, trace_write_lock};
use opcua_nodes::NodeSetImport;
use opcua_nodes::{HasNodeId, NodeSetImport};

use crate::{
address_space::{read_node_value, AddressSpace},
address_space::{read_node_value, write_node_value, AddressSpace},
node_manager::{
DefaultTypeTree, MethodCall, MonitoredItemRef, MonitoredItemUpdateRef, NodeManagerBuilder,
NodeManagersRef, ParsedReadValueId, RequestContext, ServerContext, SyncSampler, WriteNode,
Expand Down Expand Up @@ -371,25 +371,31 @@ impl SimpleNodeManagerImpl {
return;
}

// If there is a callback registered, call that.
if let Some(cb) = cbs.get(node.as_node().node_id()) {
// If there is a callback registered, call that.
write.set_status(cb(write.value().value.clone(), &write.value().index_range));
return;
};

// No callback defined, we store the value in our memory address space
if let Some(value) = &write.value().value.value {
let res = node
.as_mut_node()
.set_attribute(AttributeId::Value, value.clone());
if let Err(status_code) = res {
write.set_status(status_code);
} else if write.value().value.value.is_some() {
// If not, write the value to the node hierarchy.
match write_node_value(node, write.value()) {
Ok(_) => write.set_status(StatusCode::Good),
Err(e) => write.set_status(e),
}
} else {
// If no value is passed return an error.
write.set_status(StatusCode::BadNothingToDo);
}
if write.status().is_good() {
if let Some(val) = node.as_mut_node().get_attribute(
TimestampsToReturn::Both,
write.value().attribute_id,
&NumericRange::None,
&opcua_types::DataEncoding::Binary,
) {
context.subscriptions.notify_data_change(
[(val, node.node_id(), write.value().attribute_id)].into_iter(),
);
}
write.set_status(StatusCode::Good);
return;
}

write.set_status(StatusCode::BadNothingToDo);
}

/// Add a callback called on `Write` for the node given by `id`.
Expand Down
77 changes: 14 additions & 63 deletions samples/custom-structures-client/src/bin/native_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ use std::sync::Arc;
use opcua::{
client::Session,
types::{
errors::OpcUaError, BrowsePath, ExpandedNodeId, ExtensionObject, NodeId, ObjectId,
TimestampsToReturn, Variant, WriteValue,
errors::OpcUaError, ua_encodable, BrowsePath, ExpandedNodeId, ExtensionObject, NodeId,
ObjectId, StaticTypeLoader, TimestampsToReturn, Variant, WriteValue,
},
};
use opcua_structure_client::{client_connect, NAMESPACE_URI};
Expand Down Expand Up @@ -73,39 +73,20 @@ async fn read_structure_var(session: &Arc<Session>, ns: u16) -> Result<(), OpcUa
// 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)]
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[ua_encodable]
#[repr(i32)]
pub enum AxisState {
#[default]
#[opcua(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)]
#[derive(Debug, Clone, PartialEq, Default)]
#[ua_encodable]
pub struct ErrorData {
message: opcua::types::UAString,
error_id: u32,
Expand All @@ -128,44 +109,14 @@ static TYPES: std::sync::LazyLock<opcua::types::TypeLoaderInstance> =

#[derive(Debug, Clone, Copy)]
pub struct CustomTypeLoader;
impl opcua::types::TypeLoader for CustomTypeLoader {
fn load_from_binary(
&self,
node_id: &opcua::types::NodeId,
stream: &mut dyn std::io::Read,
ctx: &opcua::types::Context<'_>,
) -> Option<opcua::types::EncodingResult<Box<dyn opcua::types::DynEncodable>>> {
let idx = ctx.namespaces().get_index(NAMESPACE_URI)?;
if idx != node_id.namespace {
return None;
}
let Some(num_id) = node_id.as_u32() else {
return Some(Err(opcua::types::Error::decoding(
"Unsupported encoding ID. Only numeric encoding IDs are currently supported",
)));
};
TYPES.decode_binary(num_id, stream, ctx)
}
#[cfg(feature = "xml")]
fn load_from_xml(
&self,
_node_id: &opcua::types::NodeId,
_stream: &opcua::types::xml::XmlElement,
_ctx: &opcua::types::xml::XmlContext<'_>,
) -> Option<Result<Box<dyn opcua::types::DynEncodable>, opcua::types::xml::FromXmlError>> {
todo!()
}
#[cfg(feature = "json")]
fn load_from_json(
&self,
_node_id: &opcua::types::NodeId,
_stream: &mut opcua::types::json::JsonStreamReader<&mut dyn std::io::Read>,
_ctx: &opcua::types::Context<'_>,
) -> Option<opcua::types::EncodingResult<Box<dyn opcua::types::DynEncodable>>> {
todo!()

impl StaticTypeLoader for CustomTypeLoader {
fn instance() -> &'static opcua::types::TypeLoaderInstance {
&TYPES
}
fn priority(&self) -> opcua::types::TypeLoaderPriority {
opcua::types::TypeLoaderPriority::Generated

fn namespace() -> &'static str {
NAMESPACE_URI
}
}

Expand Down
2 changes: 1 addition & 1 deletion samples/demo-server/src/customs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ pub struct CustomTypeLoader;

impl opcua::types::StaticTypeLoader for CustomTypeLoader {
fn instance() -> &'static opcua::types::TypeLoaderInstance {
&*TYPES
&TYPES
}

fn namespace() -> &'static str {
Expand Down

0 comments on commit cc0209d

Please sign in to comment.