Skip to main content

opcua_client/session/services/
method.rs

1use std::time::Duration;
2
3use crate::{
4    session::{
5        process_unexpected_response,
6        request_builder::{builder_base, builder_error, RequestHeaderBuilder},
7        session_error,
8    },
9    AsyncSecureChannel, Session, UARequest,
10};
11use opcua_core::ResponseMessage;
12use opcua_types::{
13    CallMethodRequest, CallMethodResult, CallRequest, CallResponse, Error, IntegerId, MethodId,
14    NodeId, ObjectId, StatusCode, TryFromVariant, Variant,
15};
16use tracing::{debug_span, Instrument};
17
18#[derive(Debug, Clone)]
19/// Calls a list of methods on the server by sending a [`CallRequest`] to the server.
20///
21/// See OPC UA Part 4 - Services 5.11.2 for complete description of the service and error responses.
22pub struct Call {
23    methods: Vec<CallMethodRequest>,
24
25    header: RequestHeaderBuilder,
26}
27
28builder_base!(Call);
29
30impl Call {
31    /// Create a new call to the `Call` service.
32    pub fn new(session: &Session) -> Self {
33        Self {
34            methods: Vec::new(),
35            header: RequestHeaderBuilder::new_from_session(session),
36        }
37    }
38
39    /// Construct a new call to the `Call` service, setting header parameters manually.
40    pub fn new_manual(
41        session_id: u32,
42        timeout: Duration,
43        auth_token: NodeId,
44        request_handle: IntegerId,
45    ) -> Self {
46        Self {
47            methods: Vec::new(),
48            header: RequestHeaderBuilder::new(session_id, timeout, auth_token, request_handle),
49        }
50    }
51
52    /// Set the list of methods to call.
53    pub fn methods_to_call(mut self, methods: Vec<CallMethodRequest>) -> Self {
54        self.methods = methods;
55        self
56    }
57
58    /// Add a method to call.
59    pub fn method(mut self, method: impl Into<CallMethodRequest>) -> Self {
60        self.methods.push(method.into());
61        self
62    }
63}
64
65impl UARequest for Call {
66    type Out = CallResponse;
67
68    async fn send<'a>(self, channel: &'a AsyncSecureChannel) -> Result<Self::Out, Error>
69    where
70        Self: 'a,
71    {
72        let span = debug_span!(
73            "Sending Call request",
74            num_method_calls = self.methods.len()
75        );
76        let cnt = self.methods.len();
77        let request = {
78            let _h = span.enter();
79            if self.methods.is_empty() {
80                builder_error!(self, "call(), was not supplied with any methods to call");
81                return Err(Error::new(
82                    StatusCode::BadNothingToDo,
83                    "call was not supplied with any methods to call",
84                ));
85            }
86
87            CallRequest {
88                request_header: self.header.header,
89                methods_to_call: Some(self.methods),
90            }
91        };
92
93        let response = channel
94            .send(request, self.header.timeout)
95            .instrument(span.clone())
96            .await?;
97        let _h = span.enter();
98        if let ResponseMessage::Call(response) = response {
99            if let Some(results) = &response.results {
100                if results.len() != cnt {
101                    builder_error!(
102                        self,
103                        "call(), expecting {cnt} results from the call to the server, got {} results",
104                        results.len()
105                    );
106                    Err(Error::new(StatusCode::BadUnexpectedError, format!("call(), expecting {cnt} results from the call to the server, got {} results", results.len())))
107                } else {
108                    Ok(*response)
109                }
110            } else {
111                builder_error!(
112                    self,
113                    "call(), expecting a result from the call to the server, got nothing"
114                );
115                Err(Error::new(
116                    StatusCode::BadUnexpectedError,
117                    "call(), expecting a result from the call to the server, got nothing",
118                ))
119            }
120        } else {
121            Err(process_unexpected_response(response))
122        }
123    }
124}
125
126impl Session {
127    /// Calls a list of methods on the server by sending a [`CallRequest`] to the server.
128    ///
129    /// See OPC UA Part 4 - Services 5.11.2 for complete description of the service and error responses.
130    ///
131    /// # Arguments
132    ///
133    /// * `methods` - The method to call.
134    ///
135    /// # Returns
136    ///
137    /// * `Ok(Vec<CallMethodResult>)` - A [`CallMethodResult`] for the Method call.
138    /// * `Err(Error)` - Request failed, [Status code](StatusCode) is the reason for failure.
139    ///
140    pub async fn call(
141        &self,
142        methods: Vec<CallMethodRequest>,
143    ) -> Result<Vec<CallMethodResult>, Error> {
144        Ok(Call::new(self)
145            .methods_to_call(methods)
146            .send(&self.channel)
147            .await?
148            .results
149            .unwrap_or_default())
150    }
151
152    /// Calls a single method on an object on the server by sending a [`CallRequest`] to the server.
153    ///
154    /// See OPC UA Part 4 - Services 5.11.2 for complete description of the service and error responses.
155    ///
156    /// # Arguments
157    ///
158    /// * `method` - The method to call. Note this function takes anything that can be turned into
159    ///   a [`CallMethodRequest`] which includes a ([`NodeId`], [`NodeId`], `Option<Vec<Variant>>`) tuple
160    ///   which refers to the object id, method id, and input arguments respectively.
161    ///
162    /// # Returns
163    ///
164    /// * `Ok(CallMethodResult)` - A [`CallMethodResult`] for the Method call.
165    /// * `Err(Error)` - Request failed, [Status code](StatusCode) is the reason for failure.
166    ///
167    pub async fn call_one(
168        &self,
169        method: impl Into<CallMethodRequest>,
170    ) -> Result<CallMethodResult, Error> {
171        Ok(self
172            .call(vec![method.into()])
173            .await?
174            .into_iter()
175            .next()
176            .unwrap())
177    }
178
179    /// Calls GetMonitoredItems via call_method(), putting a sane interface on the input / output.
180    ///
181    /// # Arguments
182    ///
183    /// * `subscription_id` - Server allocated identifier for the subscription to return monitored items for.
184    ///
185    /// # Returns
186    ///
187    /// * `Ok((Vec<u32>, Vec<u32>))` - Result for call, consisting a list of (monitored_item_id, client_handle)
188    /// * `Err(Error)` - Request failed, [Status code](StatusCode) is the reason for failure.
189    ///
190    pub async fn call_get_monitored_items(
191        &self,
192        subscription_id: u32,
193    ) -> Result<(Vec<u32>, Vec<u32>), Error> {
194        let args = Some(vec![Variant::from(subscription_id)]);
195        let object_id: NodeId = ObjectId::Server.into();
196        let method_id: NodeId = MethodId::Server_GetMonitoredItems.into();
197        let request: CallMethodRequest = (object_id, method_id, args).into();
198        let response = self.call_one(request).await?;
199        if let Some(mut result) = response.output_arguments {
200            if result.len() == 2 {
201                let server_handles = <Vec<u32>>::try_from_variant(result.remove(0))?;
202                let client_handles = <Vec<u32>>::try_from_variant(result.remove(0))?;
203                Ok((server_handles, client_handles))
204            } else {
205                session_error!(
206                    self,
207                    "Expected a result with 2 args but got {}",
208                    result.len()
209                );
210                Err(Error::new(
211                    StatusCode::BadUnexpectedError,
212                    format!("Expected a result with 2 args but got {}", result.len()),
213                ))
214            }
215        } else {
216            session_error!(self, "Expected 2 output arguments but got null");
217            Err(Error::new(
218                StatusCode::BadUnexpectedError,
219                "Expected 2 output arguments but got null",
220            ))
221        }
222    }
223}