use opcua_types::{
AttributeId, AttributesMask, DataEncoding, DataValue, NumericRange, StatusCode,
TimestampsToReturn, TryFromVariant, VariableTypeAttributes, Variant,
};
use tracing::error;
use crate::FromAttributesError;
use super::{base::Base, node::Node, node::NodeBase};
node_builder_impl!(VariableTypeBuilder, VariableType);
node_builder_impl_generates_event!(VariableTypeBuilder);
node_builder_impl_subtype!(VariableTypeBuilder);
impl VariableTypeBuilder {
pub fn is_abstract(mut self, is_abstract: bool) -> Self {
self.node.set_is_abstract(is_abstract);
self
}
pub fn write_mask(mut self, write_mask: WriteMask) -> Self {
self.node.set_write_mask(write_mask);
self
}
pub fn data_type(mut self, data_type: impl Into<NodeId>) -> Self {
self.node.set_data_type(data_type);
self
}
pub fn value(mut self, value: impl Into<Variant>) -> Self {
self.node.set_value(value);
self
}
pub fn array_dimensions(mut self, array_dimensions: &[u32]) -> Self {
self.node.set_array_dimensions(array_dimensions);
self
}
pub fn value_rank(mut self, value_rank: i32) -> Self {
self.node.set_value_rank(value_rank);
self
}
}
#[derive(Debug)]
pub struct VariableType {
pub(super) base: Base,
pub(super) data_type: NodeId,
pub(super) is_abstract: bool,
pub(super) value_rank: i32,
pub(super) value: Option<DataValue>,
pub(super) array_dimensions: Option<Vec<u32>>,
}
impl Default for VariableType {
fn default() -> Self {
Self {
base: Base::new(NodeClass::VariableType, &NodeId::null(), "", ""),
data_type: NodeId::null(),
is_abstract: false,
value_rank: -1,
value: None,
array_dimensions: None,
}
}
}
node_base_impl!(VariableType);
impl Node for VariableType {
fn get_attribute_max_age(
&self,
timestamps_to_return: TimestampsToReturn,
attribute_id: AttributeId,
index_range: &NumericRange,
data_encoding: &DataEncoding,
max_age: f64,
) -> Option<DataValue> {
match attribute_id {
AttributeId::Value => self.value().cloned(),
AttributeId::DataType => Some(self.data_type().clone().into()),
AttributeId::IsAbstract => Some(self.is_abstract().into()),
AttributeId::ValueRank => Some(self.value_rank().into()),
AttributeId::ArrayDimensions => self.array_dimensions().map(DataValue::value_only),
_ => self.base.get_attribute_max_age(
timestamps_to_return,
attribute_id,
index_range,
data_encoding,
max_age,
),
}
}
fn set_attribute(
&mut self,
attribute_id: AttributeId,
value: Variant,
) -> Result<(), StatusCode> {
match attribute_id {
AttributeId::DataType => {
if let Variant::NodeId(v) = value {
self.set_data_type(*v);
Ok(())
} else {
Err(StatusCode::BadTypeMismatch)
}
}
AttributeId::IsAbstract => {
if let Variant::Boolean(v) = value {
self.set_is_abstract(v);
Ok(())
} else {
Err(StatusCode::BadTypeMismatch)
}
}
AttributeId::ValueRank => {
if let Variant::Int32(v) = value {
self.set_value_rank(v);
Ok(())
} else {
Err(StatusCode::BadTypeMismatch)
}
}
AttributeId::Value => {
self.set_value(value);
Ok(())
}
AttributeId::ArrayDimensions => {
let array_dimensions = <Vec<u32>>::try_from_variant(value);
if let Ok(array_dimensions) = array_dimensions {
self.set_array_dimensions(&array_dimensions);
Ok(())
} else {
Err(StatusCode::BadTypeMismatch)
}
}
_ => self.base.set_attribute(attribute_id, value),
}
}
}
impl VariableType {
pub fn new(
node_id: &NodeId,
browse_name: impl Into<QualifiedName>,
display_name: impl Into<LocalizedText>,
data_type: NodeId,
is_abstract: bool,
value_rank: i32,
) -> VariableType {
VariableType {
base: Base::new(NodeClass::VariableType, node_id, browse_name, display_name),
data_type,
is_abstract,
value_rank,
value: None,
array_dimensions: None,
}
}
pub fn new_full(
base: Base,
data_type: NodeId,
is_abstract: bool,
value_rank: i32,
value: Option<DataValue>,
array_dimensions: Option<Vec<u32>>,
) -> Self {
Self {
base,
data_type,
is_abstract,
value,
value_rank,
array_dimensions,
}
}
pub fn from_attributes(
node_id: &NodeId,
browse_name: impl Into<QualifiedName>,
attributes: VariableTypeAttributes,
) -> Result<Self, FromAttributesError> {
let mandatory_attributes = AttributesMask::DISPLAY_NAME
| AttributesMask::IS_ABSTRACT
| AttributesMask::DATA_TYPE
| AttributesMask::VALUE_RANK;
let mask = AttributesMask::from_bits(attributes.specified_attributes)
.ok_or(FromAttributesError::InvalidMask)?;
if mask.contains(mandatory_attributes) {
let mut node = Self::new(
node_id,
browse_name,
attributes.display_name,
attributes.data_type,
attributes.is_abstract,
attributes.value_rank,
);
if mask.contains(AttributesMask::DESCRIPTION) {
node.set_description(attributes.description);
}
if mask.contains(AttributesMask::WRITE_MASK) {
node.set_write_mask(WriteMask::from_bits_truncate(attributes.write_mask));
}
if mask.contains(AttributesMask::USER_WRITE_MASK) {
node.set_user_write_mask(WriteMask::from_bits_truncate(attributes.user_write_mask));
}
if mask.contains(AttributesMask::VALUE) {
node.set_value(attributes.value);
}
if mask.contains(AttributesMask::ARRAY_DIMENSIONS) {
node.set_array_dimensions(attributes.array_dimensions.unwrap().as_slice());
}
Ok(node)
} else {
error!("VariableType cannot be created from attributes - missing mandatory values");
Err(FromAttributesError::MissingMandatoryValues)
}
}
pub fn is_valid(&self) -> bool {
self.base.is_valid()
}
pub fn data_type(&self) -> &NodeId {
&self.data_type
}
pub fn set_data_type(&mut self, data_type: impl Into<NodeId>) {
self.data_type = data_type.into();
}
pub fn is_abstract(&self) -> bool {
self.is_abstract
}
pub fn set_is_abstract(&mut self, is_abstract: bool) {
self.is_abstract = is_abstract;
}
pub fn value_rank(&self) -> i32 {
self.value_rank
}
pub fn set_value_rank(&mut self, value_rank: i32) {
self.value_rank = value_rank;
}
pub fn array_dimensions(&self) -> Option<Vec<u32>> {
self.array_dimensions.clone()
}
pub fn set_array_dimensions(&mut self, array_dimensions: &[u32]) {
self.array_dimensions = Some(array_dimensions.to_vec());
}
pub fn value(&self) -> Option<&DataValue> {
self.value.as_ref()
}
pub fn set_value(&mut self, value: impl Into<Variant>) {
self.value = Some(DataValue::new_now(value));
}
pub fn set_data_value(&mut self, value: DataValue) {
self.value = Some(value);
}
}