Skip to main content

opcua_nodes/
base.rs

1// OPCUA for Rust
2// SPDX-License-Identifier: MPL-2.0
3// Copyright (C) 2017-2024 Adam Lock
4
5use opcua_types::{
6    status_code::StatusCode, AttributeId, DataEncoding, DataValue, LocalizedText, NodeClass,
7    NodeId, NumericRange, QualifiedName, TimestampsToReturn, Variant, WriteMask,
8};
9
10use super::node::{Node, NodeBase};
11
12/// Base node class contains the attributes that all other kinds of nodes need. Part 3, diagram B.4
13#[derive(Debug)]
14pub struct Base {
15    /// The node id of this node
16    pub(super) node_id: NodeId,
17    /// The node class of this node
18    pub(super) node_class: NodeClass,
19    /// The node's browse name which must be unique amongst its siblings
20    pub(super) browse_name: QualifiedName,
21    /// The human readable display name
22    pub(super) display_name: LocalizedText,
23    /// The description of the node (optional)
24    pub(super) description: Option<LocalizedText>,
25    /// Write mask bits (optional)
26    pub(super) write_mask: Option<u32>,
27    /// User write mask bits (optional)
28    pub(super) user_write_mask: Option<u32>,
29}
30
31impl NodeBase for Base {
32    fn node_class(&self) -> NodeClass {
33        self.node_class
34    }
35
36    fn node_id(&self) -> &NodeId {
37        &self.node_id
38    }
39
40    fn browse_name(&self) -> &QualifiedName {
41        &self.browse_name
42    }
43
44    fn display_name(&self) -> &LocalizedText {
45        &self.display_name
46    }
47
48    fn set_display_name(&mut self, display_name: LocalizedText) {
49        self.display_name = display_name;
50    }
51
52    fn description(&self) -> Option<&LocalizedText> {
53        self.description.as_ref()
54    }
55
56    fn set_description(&mut self, description: LocalizedText) {
57        self.description = Some(description)
58    }
59
60    fn write_mask(&self) -> Option<WriteMask> {
61        self.write_mask.map(WriteMask::from_bits_truncate)
62    }
63
64    fn set_write_mask(&mut self, write_mask: WriteMask) {
65        self.write_mask = Some(write_mask.bits());
66    }
67
68    fn user_write_mask(&self) -> Option<WriteMask> {
69        self.user_write_mask.map(WriteMask::from_bits_truncate)
70    }
71
72    fn set_user_write_mask(&mut self, user_write_mask: WriteMask) {
73        self.user_write_mask = Some(user_write_mask.bits());
74    }
75}
76
77impl Node for Base {
78    fn get_attribute_max_age(
79        &self,
80        _timestamps_to_return: TimestampsToReturn,
81        attribute_id: AttributeId,
82        _index_range: &NumericRange,
83        _data_encoding: &DataEncoding,
84        _max_age: f64,
85    ) -> Option<DataValue> {
86        match attribute_id {
87            AttributeId::NodeClass => Some((self.node_class as i32).into()),
88            AttributeId::NodeId => Some(self.node_id().clone().into()),
89            AttributeId::BrowseName => Some(self.browse_name().clone().into()),
90            AttributeId::DisplayName => Some(self.display_name().clone().into()),
91            AttributeId::Description => self
92                .description()
93                .cloned()
94                .map(|description| description.into()),
95            AttributeId::WriteMask => self.write_mask.map(|v| v.into()),
96            AttributeId::UserWriteMask => self.user_write_mask.map(|v| v.into()),
97            _ => None,
98        }
99    }
100
101    /// Tries to set the attribute if its one of the common attribute, otherwise it returns the value
102    /// for the subclass to handle.
103    fn set_attribute(
104        &mut self,
105        attribute_id: AttributeId,
106        value: Variant,
107    ) -> Result<(), StatusCode> {
108        match attribute_id {
109            AttributeId::NodeClass => {
110                if let Variant::Int32(v) = value {
111                    self.node_class = match v {
112                        1 => NodeClass::Object,
113                        2 => NodeClass::Variable,
114                        4 => NodeClass::Method,
115                        8 => NodeClass::ObjectType,
116                        16 => NodeClass::VariableType,
117                        32 => NodeClass::ReferenceType,
118                        64 => NodeClass::DataType,
119                        128 => NodeClass::View,
120                        _ => {
121                            return Ok(());
122                        }
123                    };
124                    Ok(())
125                } else {
126                    Err(StatusCode::BadTypeMismatch)
127                }
128            }
129            AttributeId::NodeId => {
130                if let Variant::NodeId(v) = value {
131                    self.node_id = *v;
132                    Ok(())
133                } else {
134                    Err(StatusCode::BadTypeMismatch)
135                }
136            }
137            AttributeId::BrowseName => {
138                if let Variant::QualifiedName(v) = value {
139                    self.browse_name = *v;
140                    Ok(())
141                } else {
142                    Err(StatusCode::BadTypeMismatch)
143                }
144            }
145            AttributeId::DisplayName => {
146                if let Variant::LocalizedText(v) = value {
147                    self.display_name = *v;
148                    Ok(())
149                } else {
150                    Err(StatusCode::BadTypeMismatch)
151                }
152            }
153            AttributeId::Description => {
154                if let Variant::LocalizedText(v) = value {
155                    self.description = Some(*v);
156                    Ok(())
157                } else {
158                    Err(StatusCode::BadTypeMismatch)
159                }
160            }
161            AttributeId::WriteMask => {
162                if let Variant::UInt32(v) = value {
163                    self.write_mask = Some(v);
164                    Ok(())
165                } else {
166                    Err(StatusCode::BadTypeMismatch)
167                }
168            }
169            AttributeId::UserWriteMask => {
170                if let Variant::UInt32(v) = value {
171                    self.user_write_mask = Some(v);
172                    Ok(())
173                } else {
174                    Err(StatusCode::BadTypeMismatch)
175                }
176            }
177            _ => Err(StatusCode::BadAttributeIdInvalid),
178        }
179    }
180}
181
182impl Base {
183    /// Create a new base node.
184    pub fn new(
185        node_class: NodeClass,
186        node_id: &NodeId,
187        browse_name: impl Into<QualifiedName>,
188        display_name: impl Into<LocalizedText>,
189    ) -> Base {
190        Base {
191            node_id: node_id.clone(),
192            node_class,
193            browse_name: browse_name.into(),
194            display_name: display_name.into(),
195            description: None,
196            write_mask: None,
197            user_write_mask: None,
198        }
199    }
200
201    /// Create a new base node with all attributes, may change if
202    /// new attributes are added to the OPC-UA standard.
203    pub fn new_full(
204        node_id: NodeId,
205        node_class: NodeClass,
206        browse_name: QualifiedName,
207        display_name: LocalizedText,
208        description: Option<LocalizedText>,
209        write_mask: Option<u32>,
210        user_write_mask: Option<u32>,
211    ) -> Self {
212        Self {
213            node_id,
214            node_class,
215            browse_name,
216            display_name,
217            description,
218            write_mask,
219            user_write_mask,
220        }
221    }
222
223    /// Get whether this base node is valid.
224    pub fn is_valid(&self) -> bool {
225        let invalid = self.node_id().is_null() || self.browse_name.is_null();
226        !invalid
227    }
228
229    /// Set the node ID of this node.
230    pub fn set_node_id(&mut self, node_id: NodeId) {
231        self.node_id = node_id;
232    }
233
234    /// Set the browse name of this node.
235    pub fn set_browse_name(&mut self, browse_name: impl Into<QualifiedName>) {
236        self.browse_name = browse_name.into();
237    }
238}