Skip to main content

opcua_nodes/
method.rs

1// OPCUA for Rust
2// SPDX-License-Identifier: MPL-2.0
3// Copyright (C) 2017-2024 Adam Lock
4
5//! Contains the implementation of `Method` and `MethodBuilder`.
6
7use opcua_types::{
8    Argument, AttributeId, AttributesMask, DataEncoding, DataTypeId, DataValue, ExtensionObject,
9    MethodAttributes, NumericRange, StatusCode, TimestampsToReturn, VariableTypeId, Variant,
10    VariantScalarTypeId,
11};
12use tracing::error;
13
14use crate::{FromAttributesError, NodeInsertTarget};
15
16use super::{
17    base::Base,
18    node::{Node, NodeBase},
19    variable::VariableBuilder,
20};
21
22node_builder_impl!(MethodBuilder, Method);
23node_builder_impl_component_of!(MethodBuilder);
24node_builder_impl_generates_event!(MethodBuilder);
25
26impl MethodBuilder {
27    /// Specify output arguments from the method. This will create an OutputArguments
28    /// variable child of the method which describes the out parameters.
29    pub fn output_args(
30        self,
31        address_space: &mut impl NodeInsertTarget,
32        node_id: &NodeId,
33        arguments: &[Argument],
34    ) -> Self {
35        self.insert_args(node_id, "OutputArguments", address_space, arguments);
36        self
37    }
38
39    /// Specify input arguments to the method. This will create an InputArguments
40    /// variable child of the method which describes the in parameters.
41    pub fn input_args(
42        self,
43        address_space: &mut impl NodeInsertTarget,
44        node_id: &NodeId,
45        arguments: &[Argument],
46    ) -> Self {
47        self.insert_args(node_id, "InputArguments", address_space, arguments);
48        self
49    }
50
51    /// Set whether this method is executable, meaning it can be
52    /// called by users at all.
53    pub fn executable(mut self, executable: bool) -> Self {
54        self.node.set_executable(executable);
55        self
56    }
57
58    /// Set whether this method is executable by the current user.
59    /// This value is usually modified by the server depending on the
60    /// user asking for it.
61    pub fn user_executable(mut self, executable: bool) -> Self {
62        self.node.set_user_executable(executable);
63        self
64    }
65
66    /// Set the write mask for this method.
67    pub fn write_mask(mut self, write_mask: WriteMask) -> Self {
68        self.node.set_write_mask(write_mask);
69        self
70    }
71
72    fn args_to_variant(arguments: &[Argument]) -> Variant {
73        let arguments = arguments
74            .iter()
75            .map(|arg| Variant::from(ExtensionObject::from_message(arg.clone())))
76            .collect::<Vec<Variant>>();
77        Variant::from((VariantScalarTypeId::ExtensionObject, arguments))
78    }
79
80    fn insert_args(
81        &self,
82        node_id: &NodeId,
83        args_name: &str,
84        address_space: &mut impl NodeInsertTarget,
85        arguments: &[Argument],
86    ) {
87        let fn_node_id = self.node.node_id();
88        let args_value = Self::args_to_variant(arguments);
89        VariableBuilder::new(node_id, args_name, args_name)
90            .property_of(fn_node_id)
91            .has_type_definition(VariableTypeId::PropertyType)
92            .data_type(DataTypeId::Argument)
93            .value_rank(1)
94            .array_dimensions(&[arguments.len() as u32])
95            .value(args_value)
96            .insert(address_space);
97    }
98}
99
100/// A `Method` is a type of node within the `AddressSpace`.
101#[derive(Debug)]
102pub struct Method {
103    pub(super) base: Base,
104    pub(super) executable: bool,
105    pub(super) user_executable: bool,
106}
107
108impl Default for Method {
109    fn default() -> Self {
110        Self {
111            base: Base::new(NodeClass::Method, &NodeId::null(), "", ""),
112            executable: true,
113            user_executable: true,
114        }
115    }
116}
117
118node_base_impl!(Method);
119
120impl Node for Method {
121    fn get_attribute_max_age(
122        &self,
123        timestamps_to_return: TimestampsToReturn,
124        attribute_id: AttributeId,
125        index_range: &NumericRange,
126        data_encoding: &DataEncoding,
127        max_age: f64,
128    ) -> Option<DataValue> {
129        match attribute_id {
130            AttributeId::Executable => Some(self.executable().into()),
131            AttributeId::UserExecutable => Some(self.user_executable().into()),
132            _ => self.base.get_attribute_max_age(
133                timestamps_to_return,
134                attribute_id,
135                index_range,
136                data_encoding,
137                max_age,
138            ),
139        }
140    }
141
142    fn set_attribute(
143        &mut self,
144        attribute_id: AttributeId,
145        value: Variant,
146    ) -> Result<(), StatusCode> {
147        match attribute_id {
148            AttributeId::Executable => {
149                if let Variant::Boolean(v) = value {
150                    self.set_executable(v);
151                    Ok(())
152                } else {
153                    Err(StatusCode::BadTypeMismatch)
154                }
155            }
156            AttributeId::UserExecutable => {
157                if let Variant::Boolean(v) = value {
158                    self.set_user_executable(v);
159                    Ok(())
160                } else {
161                    Err(StatusCode::BadTypeMismatch)
162                }
163            }
164            _ => self.base.set_attribute(attribute_id, value),
165        }
166    }
167}
168
169impl Method {
170    /// Create a new method.
171    pub fn new(
172        node_id: &NodeId,
173        browse_name: impl Into<QualifiedName>,
174        display_name: impl Into<LocalizedText>,
175        executable: bool,
176        user_executable: bool,
177    ) -> Method {
178        Method {
179            base: Base::new(NodeClass::Method, node_id, browse_name, display_name),
180            executable,
181            user_executable,
182        }
183    }
184
185    /// Create a new method with all attributes, may change if
186    /// new attributes are added to the OPC-UA standard.
187    pub fn new_full(base: Base, executable: bool, user_executable: bool) -> Self {
188        Self {
189            base,
190            executable,
191            user_executable,
192        }
193    }
194
195    /// Create a new method from [MethodAttributes].
196    pub fn from_attributes(
197        node_id: &NodeId,
198        browse_name: impl Into<QualifiedName>,
199        attributes: MethodAttributes,
200    ) -> Result<Self, FromAttributesError> {
201        let mandatory_attributes = AttributesMask::DISPLAY_NAME
202            | AttributesMask::EXECUTABLE
203            | AttributesMask::USER_EXECUTABLE;
204        let mask = AttributesMask::from_bits(attributes.specified_attributes)
205            .ok_or(FromAttributesError::InvalidMask)?;
206        if mask.contains(mandatory_attributes) {
207            let mut node = Self::new(
208                node_id,
209                browse_name,
210                attributes.display_name,
211                attributes.executable,
212                attributes.user_executable,
213            );
214            if mask.contains(AttributesMask::DESCRIPTION) {
215                node.set_description(attributes.description);
216            }
217            if mask.contains(AttributesMask::WRITE_MASK) {
218                node.set_write_mask(WriteMask::from_bits_truncate(attributes.write_mask));
219            }
220            if mask.contains(AttributesMask::USER_WRITE_MASK) {
221                node.set_user_write_mask(WriteMask::from_bits_truncate(attributes.user_write_mask));
222            }
223            Ok(node)
224        } else {
225            error!("Method cannot be created from attributes - missing mandatory values");
226            Err(FromAttributesError::MissingMandatoryValues)
227        }
228    }
229
230    /// Get whether this method is valid.
231    pub fn is_valid(&self) -> bool {
232        self.base.is_valid()
233    }
234
235    /// Get whether this method is executable.
236    pub fn executable(&self) -> bool {
237        self.executable
238    }
239
240    /// Set whether this method is executable.
241    pub fn set_executable(&mut self, executable: bool) {
242        self.executable = executable;
243    }
244
245    /// Get whether this method is executable by the current user by default.
246    pub fn user_executable(&self) -> bool {
247        // User executable cannot be true unless executable is true
248        self.executable && self.user_executable
249    }
250
251    /// Set whether this method is executable by the current user by default.
252    pub fn set_user_executable(&mut self, user_executable: bool) {
253        self.user_executable = user_executable;
254    }
255}