Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions src/async_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,53 @@ impl AsyncClient {
Ok(results)
}

/// Reads historizied values of a node for a given period of time.
///
/// # Errors
///
/// This fails only when the entire request fails (e.g. communication error). When the node does
/// not exist or its value attribute cannot be read, the server returns a corresponding
/// [`DataValue`] with the appropriate [`status()`] and with [`value()`] unset.
///
/// [`status()`]: DataValue::status
/// [`value()`]: DataValue::value
pub async fn read_history_raw(
&self,
node_id: &ua::NodeId,
start_time: ua::DateTime,
end_time: ua::DateTime,
) -> Result<ua::HistoryReadResult> {
let nodes_to_read = [node_id]
.iter()
.map(|node_id| ua::HistoryReadValueId::init().with_node_id(node_id))
.collect::<Vec<_>>();

let history_read_details = ua::ReadRawModifiedDetails::init()
.with_is_read_modified(ua::Boolean::new(false))
.with_start_time(start_time)
.with_end_time(end_time)
.with_num_values_per_node(ua::UInt32::new(0))
.with_return_bounds(ua::Boolean::new(false));
let request = ua::HistoryReadRequest::init()
// TODO: Add method argument for this? We return timestamps in `DataValue` and they
// should not end up always being `None` by default.
.with_timestamps_to_return(&ua::TimestampsToReturn::BOTH)
.with_history_read_details(&history_read_details)
.with_nodes_to_read(&nodes_to_read);

let response = self.service_request(request).await?;

let Some(results) = response.results() else {
return Err(Error::internal("read should return results"));
};

results
.as_slice()
.first()
.map(std::borrow::ToOwned::to_owned)
.ok_or(Error::internal("unexpected number of read results"))
}

/// Writes node value.
///
/// # Errors
Expand Down
12 changes: 12 additions & 0 deletions src/ua/data_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ mod expanded_node_id;
mod extension_object;
mod filter_operator;
mod guid;
mod history_data;
mod history_read_request;
mod history_read_response;
mod history_read_result;
mod history_read_value_id;
mod issued_identity_token;
mod literal_operand;
mod localized_text;
Expand All @@ -62,6 +67,7 @@ mod node_id;
mod node_id_type;
mod qualified_name;
mod range;
mod read_raw_modified_details;
mod read_request;
mod read_response;
mod read_value_id;
Expand Down Expand Up @@ -136,6 +142,11 @@ pub use self::{
extension_object::ExtensionObject,
filter_operator::FilterOperator,
guid::Guid,
history_data::HistoryData,
history_read_request::HistoryReadRequest,
history_read_response::HistoryReadResponse,
history_read_result::HistoryReadResult,
history_read_value_id::HistoryReadValueId,
issued_identity_token::IssuedIdentityToken,
literal_operand::LiteralOperand,
localized_text::LocalizedText,
Expand All @@ -154,6 +165,7 @@ pub use self::{
node_id_type::NodeIdType,
qualified_name::QualifiedName,
range::Range,
read_raw_modified_details::ReadRawModifiedDetails,
read_request::ReadRequest,
read_response::ReadResponse,
read_value_id::ReadValueId,
Expand Down
23 changes: 23 additions & 0 deletions src/ua/data_types/history_data.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
use crate::{DataType as _, ua};

crate::data_type!(HistoryData);

impl HistoryData {
#[must_use]
pub const fn is_empty(&self) -> bool {
self.0.dataValuesSize == 0
}
#[must_use]
pub const fn len(&self) -> usize {
self.0.dataValuesSize
}

#[must_use]
pub fn get(&self, i: usize) -> Option<ua::DataValue> {
if i < self.0.dataValuesSize {
Some(unsafe { ua::DataValue::clone_raw(&self.0.dataValues.add(i).read()) })
} else {
None
}
}
}
44 changes: 44 additions & 0 deletions src/ua/data_types/history_read_request.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
use crate::{DataType as _, ServiceRequest, ua};

crate::data_type!(HistoryReadRequest);

impl HistoryReadRequest {
#[must_use]
pub fn with_timestamps_to_return(
mut self,
timestamps_to_return: &ua::TimestampsToReturn,
) -> Self {
timestamps_to_return.clone_into_raw(&mut self.0.timestampsToReturn);
self
}

#[must_use]
pub fn with_nodes_to_read(mut self, nodes_to_read: &[ua::HistoryReadValueId]) -> Self {
let array = ua::Array::from_slice(nodes_to_read);
array.move_into_raw(&mut self.0.nodesToReadSize, &mut self.0.nodesToRead);
self
}

#[must_use]
pub fn with_history_read_details(
mut self,
history_read_details: &ua::ReadRawModifiedDetails,
) -> Self {
history_read_details
.to_extension_object()
.clone_into_raw(&mut self.0.historyReadDetails);
self
}
}

impl ServiceRequest for HistoryReadRequest {
type Response = ua::HistoryReadResponse;

fn request_header(&self) -> &ua::RequestHeader {
ua::RequestHeader::raw_ref(&self.0.requestHeader)
}

fn request_header_mut(&mut self) -> &mut ua::RequestHeader {
ua::RequestHeader::raw_mut(&mut self.0.requestHeader)
}
}
19 changes: 19 additions & 0 deletions src/ua/data_types/history_read_response.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
use crate::{DataType as _, ServiceResponse, ua};

crate::data_type!(HistoryReadResponse);

impl HistoryReadResponse {
#[must_use]
pub fn results(&self) -> Option<ua::Array<ua::HistoryReadResult>> {
// TODO: Adjust signature to return non-owned value instead.
ua::Array::from_raw_parts(self.0.resultsSize, self.0.results)
}
}

impl ServiceResponse for HistoryReadResponse {
type Request = ua::HistoryReadRequest;

fn response_header(&self) -> &ua::ResponseHeader {
ua::ResponseHeader::raw_ref(&self.0.responseHeader)
}
}
20 changes: 20 additions & 0 deletions src/ua/data_types/history_read_result.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
use crate::{DataType as _, ua};

crate::data_type!(HistoryReadResult);

impl HistoryReadResult {
#[must_use]
pub fn status(&self) -> &ua::StatusCode {
ua::StatusCode::raw_ref(&self.0.statusCode)
}

#[must_use]
pub fn continuation_point(&self) -> &ua::String {
ua::String::raw_ref(&self.0.continuationPoint)
}

#[must_use]
pub fn history_data(&mut self) -> &ua::ExtensionObject {
ua::ExtensionObject::raw_ref(&self.0.historyData)
}
}
11 changes: 11 additions & 0 deletions src/ua/data_types/history_read_value_id.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
use crate::{DataType as _, ua};

crate::data_type!(HistoryReadValueId);

impl HistoryReadValueId {
#[must_use]
pub fn with_node_id(mut self, node_id: &ua::NodeId) -> Self {
node_id.clone_into_raw(&mut self.0.nodeId);
self
}
}
40 changes: 40 additions & 0 deletions src/ua/data_types/read_raw_modified_details.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
use crate::{DataType as _, ua};

crate::data_type!(ReadRawModifiedDetails);

impl ReadRawModifiedDetails {
#[must_use]
pub fn with_is_read_modified(mut self, is_read_modified: ua::Boolean) -> Self {
is_read_modified.move_into_raw(&mut self.0.isReadModified);
self
}

#[must_use]
pub fn with_start_time(mut self, start_time: ua::DateTime) -> Self {
start_time.move_into_raw(&mut self.0.startTime);
self
}

#[must_use]
pub fn with_end_time(mut self, end_time: ua::DateTime) -> Self {
end_time.move_into_raw(&mut self.0.endTime);
self
}

#[must_use]
pub fn with_num_values_per_node(mut self, num_values_per_node: ua::UInt32) -> Self {
num_values_per_node.move_into_raw(&mut self.0.numValuesPerNode);
self
}

#[must_use]
pub fn with_return_bounds(mut self, return_bounds: ua::Boolean) -> Self {
return_bounds.move_into_raw(&mut self.0.returnBounds);
self
}

#[must_use]
pub fn to_extension_object(&self) -> ua::ExtensionObject {
ua::ExtensionObject::new(self)
}
}