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