1 // Copyright 2023 The Pigweed Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy of
5 // the License at
6 //
7 // https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations under
13 // the License.
14 #pragma once
15
16 #include <utility>
17
18 #include "pw_chrono/system_clock.h"
19 #include "pw_rpc/client.h"
20 #include "pw_rpc/internal/method_info.h"
21 #include "pw_rpc/internal/synchronous_call_impl.h"
22 #include "pw_rpc/synchronous_call_result.h"
23
24 /// @file pw_rpc/synchronous_call.h
25 ///
26 /// `pw_rpc` provides wrappers that convert the asynchronous client API to a
27 /// synchronous API. The `SynchronousCall<RpcMethod>` functions wrap the
28 /// asynchronous client RPC call with a timed thread notification and returns
29 /// once a result is known or a timeout has occurred. Only unary methods are
30 /// supported.
31 ///
32 /// The Nanopb and pwpb APIs return a `SynchronousCallResult<Response>` object,
33 /// which can be queried to determine whether any error scenarios occurred and,
34 /// if not, access the response. The raw API executes a function when the call
35 /// completes or returns a `pw::Status` if it does not.
36 ///
37 /// `SynchronousCall<RpcMethod>` blocks indefinitely, whereas
38 /// `SynchronousCallFor<RpcMethod>` and `SynchronousCallUntil<RpcMethod>` block
39 /// for a given timeout or until a deadline, respectively. All wrappers work
40 /// with either the standalone static RPC functions or the generated service
41 /// client member methods.
42 ///
43 /// @note Use of the SynchronousCall wrappers requires a
44 /// @cpp_class{pw::sync::TimedThreadNotification} backend.
45 ///
46 /// The following examples use the Nanopb API to make a call that blocks
47 /// indefinitely. If you'd like to include a timeout for how long the call
48 /// should block for, use the `SynchronousCallFor()` or `SynchronousCallUntil()`
49 /// variants.
50 ///
51 /// @code{.cpp}
52 /// pw_rpc_EchoMessage request{.msg = "hello" };
53 /// pw::rpc::SynchronousCallResult<pw_rpc_EchoMessage> result =
54 /// pw::rpc::SynchronousCall<EchoService::Echo>(rpc_client,
55 /// channel_id,
56 /// request);
57 /// if (result.ok()) {
58 /// PW_LOG_INFO("%s", result.response().msg);
59 /// }
60 /// @endcode
61 ///
62 /// Additionally, the use of a generated `Client` object is supported:
63 ///
64 /// @code{.cpp}
65 /// pw_rpc::nanopb::EchoService::Client client(rpc_client, channel_id);
66 /// pw_rpc_EchoMessage request{.msg = "hello" };
67 /// pw::rpc::SynchronousCallResult<pw_rpc_EchoMessage> result =
68 /// pw::rpc::SynchronousCall<EchoService::Echo>(client, request);
69 ///
70 /// if (result.ok()) {
71 /// PW_LOG_INFO("%s", result.response().msg);
72 /// }
73 /// @endcode
74 ///
75 /// `SynchronousCall<RpcMethod>` also supports using an optional custom response
76 /// message class, `SynchronousCall<RpcMethod, Response>`. This enables the use
77 /// of response messages with variable-length fields.
78 ///
79 /// @code{.cpp}
80 /// pw_rpc_MyMethodRequestMessage request{};
81 /// class CustomResponse : public pw_rpc_MyMethodResponseMessage {
82 /// public:
83 /// CustomResponse() {
84 /// repeated_field.SetDecoder([this](
85 /// MyMethodResponse::StreamDecoder& decoder) {
86 /// return decoder.ReadRepeatedField(values);
87 /// }
88 /// }
89 /// pw::Vector<uint32_t, 4> values();
90 /// };
91 /// pw::rpc::SynchronousCallResult<CustomResponse> result =
92 /// pw::rpc::SynchronousCall<EchoService::Echo, CustomResponse>(rpc_client,
93 /// channel_id,
94 /// request);
95 /// if (result.ok()) {
96 /// PW_LOG_INFO("%d", result.response().values[0]);
97 /// }
98 /// };
99 /// @endcode
100 ///
101 /// The raw API works similarly to the Nanopb API, but takes a
102 /// @cpp_type{pw::Function} and returns a @cpp_class{pw::Status}. If the RPC
103 /// completes, the @cpp_type{pw::Function} is called with the response and
104 /// returned status, and the `SynchronousCall` invocation returns
105 /// @pw_status{OK}. If the RPC fails, `SynchronousCall` returns an error.
106 ///
107 /// @code{.cpp}
108 /// pw::Status rpc_status = pw::rpc::SynchronousCall<EchoService::Echo>(
109 /// rpc_client, channel_id, encoded_request,
110 /// [](pw::ConstByteSpan reply, pw::Status status) {
111 /// PW_LOG_INFO("Received %zu bytes with status %s",
112 /// reply.size(),
113 /// status.str());
114 /// });
115 /// @endcode
116 ///
117 /// @warning These wrappers should not be used from any context that cannot be
118 /// blocked! This method will block the calling thread until the RPC completes,
119 /// and translate the response into a `pw::rpc::SynchronousCallResult` that
120 /// contains the error type and status or the proto response.
121 namespace pw::rpc {
122
123 /// Invokes a unary RPC synchronously using Nanopb or pwpb. Blocks indefinitely
124 /// until a response is received.
125 ///
126 /// @param client The `pw::rpc::Client` to use for the call
127 /// @param channel_id The ID of the RPC channel to make the call on
128 /// @param request The proto struct to send as the request
129 template <
130 auto kRpcMethod,
131 typename Response = typename internal::MethodInfo<kRpcMethod>::Response>
SynchronousCall(Client & client,uint32_t channel_id,const typename internal::MethodInfo<kRpcMethod>::Request & request)132 SynchronousCallResult<Response> SynchronousCall(
133 Client& client,
134 uint32_t channel_id,
135 const typename internal::MethodInfo<kRpcMethod>::Request& request) {
136 return internal::StructSynchronousCall<kRpcMethod, Response>(
137 internal::CallFreeFunctionWithCustomResponse<kRpcMethod, Response>(
138 client, channel_id, request));
139 }
140
141 /// Invokes a unary RPC synchronously using Nanopb or pwpb. Blocks indefinitely
142 /// until a response is received.
143 ///
144 /// @param client The generated service client to use for the call
145 /// @param request The proto struct to send as the request
146 template <auto kRpcMethod, typename GeneratedClient>
147 SynchronousCallResult<typename internal::MethodInfo<kRpcMethod>::Response>
SynchronousCall(const GeneratedClient & client,const typename internal::MethodInfo<kRpcMethod>::Request & request)148 SynchronousCall(
149 const GeneratedClient& client,
150 const typename internal::MethodInfo<kRpcMethod>::Request& request) {
151 return internal::StructSynchronousCall<kRpcMethod>(
152 internal::CallGeneratedClient<kRpcMethod>(client, request));
153 }
154
155 /// Invokes a unary RPC synchronously using the raw API. Blocks until a
156 /// response is received.
157 template <auto kRpcMethod>
SynchronousCall(Client & client,uint32_t channel_id,ConstByteSpan request,Function<void (ConstByteSpan,Status)> && on_completed)158 Status SynchronousCall(Client& client,
159 uint32_t channel_id,
160 ConstByteSpan request,
161 Function<void(ConstByteSpan, Status)>&& on_completed) {
162 return internal::RawSynchronousCall<kRpcMethod>(
163 std::move(on_completed),
164 internal::CallFreeFunction<kRpcMethod>(client, channel_id, request));
165 }
166
167 /// Invokes a unary RPC synchronously using the raw API. Blocks until a
168 /// response is received.
169 template <auto kRpcMethod>
SynchronousCall(const typename internal::MethodInfo<kRpcMethod>::GeneratedClient & client,ConstByteSpan request,Function<void (ConstByteSpan,Status)> && on_completed)170 Status SynchronousCall(
171 const typename internal::MethodInfo<kRpcMethod>::GeneratedClient& client,
172 ConstByteSpan request,
173 Function<void(ConstByteSpan, Status)>&& on_completed) {
174 return internal::RawSynchronousCall<kRpcMethod>(
175 std::move(on_completed),
176 internal::CallGeneratedClient<kRpcMethod>(client, request));
177 }
178
179 /// Invokes a unary RPC synchronously using Nanopb or pwpb. Blocks until a
180 /// response is received or the provided timeout passes.
181 ///
182 /// @param client The `pw::rpc::Client` to use for the call
183 /// @param channel_id The ID of the RPC channel to make the call on
184 /// @param request The proto struct to send as the request
185 /// @param timeout Duration to block for before returning with Timeout
186 template <auto kRpcMethod>
187 SynchronousCallResult<typename internal::MethodInfo<kRpcMethod>::Response>
SynchronousCallFor(Client & client,uint32_t channel_id,const typename internal::MethodInfo<kRpcMethod>::Request & request,chrono::SystemClock::duration timeout)188 SynchronousCallFor(
189 Client& client,
190 uint32_t channel_id,
191 const typename internal::MethodInfo<kRpcMethod>::Request& request,
192 chrono::SystemClock::duration timeout) {
193 return internal::StructSynchronousCall<kRpcMethod>(
194 internal::CallFreeFunction<kRpcMethod>(client, channel_id, request),
195 timeout);
196 }
197
198 /// Invokes a unary RPC synchronously using Nanopb or pwpb. Blocks until a
199 /// response is received or the provided timeout passes.
200 ///
201 /// @param client The generated service client to use for the call
202 /// @param request The proto struct to send as the request
203 /// @param timeout Duration to block for before returning with Timeout
204 template <auto kRpcMethod, typename GeneratedClient>
205 SynchronousCallResult<typename internal::MethodInfo<kRpcMethod>::Response>
SynchronousCallFor(const GeneratedClient & client,const typename internal::MethodInfo<kRpcMethod>::Request & request,chrono::SystemClock::duration timeout)206 SynchronousCallFor(
207 const GeneratedClient& client,
208 const typename internal::MethodInfo<kRpcMethod>::Request& request,
209 chrono::SystemClock::duration timeout) {
210 return internal::StructSynchronousCall<kRpcMethod>(
211 internal::CallGeneratedClient<kRpcMethod>(client, request), timeout);
212 }
213
214 /// Invokes a unary RPC synchronously using the raw API. Blocks until a
215 /// response is received or the provided timeout passes.
216 template <auto kRpcMethod>
SynchronousCallFor(Client & client,uint32_t channel_id,ConstByteSpan request,chrono::SystemClock::duration timeout,Function<void (ConstByteSpan,Status)> && on_completed)217 Status SynchronousCallFor(
218 Client& client,
219 uint32_t channel_id,
220 ConstByteSpan request,
221 chrono::SystemClock::duration timeout,
222 Function<void(ConstByteSpan, Status)>&& on_completed) {
223 return internal::RawSynchronousCall<kRpcMethod>(
224 std::move(on_completed),
225 internal::CallFreeFunction<kRpcMethod>(client, channel_id, request),
226 timeout);
227 }
228
229 /// Invokes a unary RPC synchronously using the raw API. Blocks until a
230 /// response is received or the provided timeout passes.
231 template <auto kRpcMethod>
SynchronousCallFor(const typename internal::MethodInfo<kRpcMethod>::GeneratedClient & client,ConstByteSpan request,chrono::SystemClock::duration timeout,Function<void (ConstByteSpan,Status)> && on_completed)232 Status SynchronousCallFor(
233 const typename internal::MethodInfo<kRpcMethod>::GeneratedClient& client,
234 ConstByteSpan request,
235 chrono::SystemClock::duration timeout,
236 Function<void(ConstByteSpan, Status)>&& on_completed) {
237 return internal::RawSynchronousCall<kRpcMethod>(
238 std::move(on_completed),
239 internal::CallGeneratedClient<kRpcMethod>(client, request),
240 timeout);
241 }
242
243 /// Invokes a unary RPC synchronously using Nanopb or pwpb. Blocks until a
244 /// response is received or the provided deadline arrives.
245 ///
246 /// @param client The `pw::rpc::Client` to use for the call
247 /// @param channel_id The ID of the RPC channel to make the call on
248 /// @param request The proto struct to send as the request
249 /// @param deadline Timepoint to block until before returning with Timeout
250 template <auto kRpcMethod>
251 SynchronousCallResult<typename internal::MethodInfo<kRpcMethod>::Response>
SynchronousCallUntil(Client & client,uint32_t channel_id,const typename internal::MethodInfo<kRpcMethod>::Request & request,chrono::SystemClock::time_point deadline)252 SynchronousCallUntil(
253 Client& client,
254 uint32_t channel_id,
255 const typename internal::MethodInfo<kRpcMethod>::Request& request,
256 chrono::SystemClock::time_point deadline) {
257 return internal::StructSynchronousCall<kRpcMethod>(
258 internal::CallFreeFunction<kRpcMethod>(client, channel_id, request),
259 deadline);
260 }
261
262 /// Invokes a unary RPC synchronously using Nanopb or pwpb. Blocks until a
263 /// response is received or the provided deadline arrives.
264 ///
265 /// @param client The generated service client to use for the call
266 /// @param request The proto struct to send as the request
267 /// @param deadline Timepoint to block until before returning with Timeout
268 template <auto kRpcMethod>
269 SynchronousCallResult<typename internal::MethodInfo<kRpcMethod>::Response>
SynchronousCallUntil(const typename internal::MethodInfo<kRpcMethod>::GeneratedClient & client,const typename internal::MethodInfo<kRpcMethod>::Request & request,chrono::SystemClock::time_point deadline)270 SynchronousCallUntil(
271 const typename internal::MethodInfo<kRpcMethod>::GeneratedClient& client,
272 const typename internal::MethodInfo<kRpcMethod>::Request& request,
273 chrono::SystemClock::time_point deadline) {
274 return internal::StructSynchronousCall<kRpcMethod>(
275 internal::CallGeneratedClient<kRpcMethod>(client, request), deadline);
276 }
277
278 /// Invokes a unary RPC synchronously using the raw API. Blocks until a
279 /// response is received or the provided deadline arrives.
280 template <auto kRpcMethod>
SynchronousCallUntil(Client & client,uint32_t channel_id,ConstByteSpan request,chrono::SystemClock::time_point deadline,Function<void (ConstByteSpan,Status)> && on_completed)281 Status SynchronousCallUntil(
282 Client& client,
283 uint32_t channel_id,
284 ConstByteSpan request,
285 chrono::SystemClock::time_point deadline,
286 Function<void(ConstByteSpan, Status)>&& on_completed) {
287 return internal::RawSynchronousCall<kRpcMethod>(
288 std::move(on_completed),
289 internal::CallFreeFunction<kRpcMethod>(client, channel_id, request),
290 deadline);
291 }
292
293 /// Invokes a unary RPC synchronously using the raw API. Blocks until a
294 /// response is received or the provided deadline arrives.
295 template <auto kRpcMethod>
SynchronousCallUntil(const typename internal::MethodInfo<kRpcMethod>::GeneratedClient & client,ConstByteSpan request,chrono::SystemClock::time_point deadline,Function<void (ConstByteSpan,Status)> && on_completed)296 Status SynchronousCallUntil(
297 const typename internal::MethodInfo<kRpcMethod>::GeneratedClient& client,
298 ConstByteSpan request,
299 chrono::SystemClock::time_point deadline,
300 Function<void(ConstByteSpan, Status)>&& on_completed) {
301 return internal::RawSynchronousCall<kRpcMethod>(
302 std::move(on_completed),
303 internal::CallGeneratedClient<kRpcMethod>(client, request),
304 deadline);
305 }
306
307 } // namespace pw::rpc
308