blob: 6ff8e438e0b3e25532c7bd72816348cf0b5da9ef [file] [log] [blame]
Wyatt Heplerf9fb90f2020-09-30 18:59:33 -07001.. _module-pw_rpc:
Alexei Frolov26e3ae62020-05-04 17:06:17 -07002
3------
4pw_rpc
5------
6The ``pw_rpc`` module provides a system for defining and invoking remote
7procedure calls (RPCs) on a device.
8
Wyatt Hepler830d26d2021-02-17 09:07:43 -08009This document discusses the ``pw_rpc`` protocol and its C++ implementation.
10``pw_rpc`` implementations for other languages are described in their own
11documents:
12
13.. toctree::
14 :maxdepth: 1
15
16 py/docs
17
Wyatt Hepler455b4922020-09-18 00:19:21 -070018.. admonition:: Try it out!
19
Wyatt Heplerf9fb90f2020-09-30 18:59:33 -070020 For a quick intro to ``pw_rpc``, see the
Alexei Frolovd3e5cb72021-01-08 13:08:45 -080021 :ref:`module-pw_hdlc-rpc-example` in the :ref:`module-pw_hdlc` module.
Wyatt Hepler455b4922020-09-18 00:19:21 -070022
Wyatt Hepler067dd7e2020-07-14 19:34:32 -070023.. attention::
Alexei Frolov26e3ae62020-05-04 17:06:17 -070024
Wyatt Hepler455b4922020-09-18 00:19:21 -070025 This documentation is under construction.
26
27Creating an RPC
28===============
29
301. RPC service declaration
31--------------------------
32Pigweed RPCs are declared in a protocol buffer service definition.
33
34* `Protocol Buffer service documentation
35 <https://developers.google.com/protocol-buffers/docs/proto3#services>`_
36* `gRPC service definition documentation
37 <https://grpc.io/docs/what-is-grpc/core-concepts/#service-definition>`_
38
39.. code-block:: protobuf
40
41 syntax = "proto3";
42
43 package foo.bar;
44
45 message Request {}
46
47 message Response {
48 int32 number = 1;
49 }
50
51 service TheService {
52 rpc MethodOne(Request) returns (Response) {}
53 rpc MethodTwo(Request) returns (stream Response) {}
54 }
55
56This protocol buffer is declared in a ``BUILD.gn`` file as follows:
57
58.. code-block:: python
59
60 import("//build_overrides/pigweed.gni")
61 import("$dir_pw_protobuf_compiler/proto.gni")
62
63 pw_proto_library("the_service_proto") {
64 sources = [ "foo_bar/the_service.proto" ]
65 }
66
Wyatt Hepler830d26d2021-02-17 09:07:43 -080067.. admonition:: proto2 or proto3 syntax?
68
69 Always use proto3 syntax rather than proto2 for new protocol buffers. Proto2
70 protobufs can be compiled for ``pw_rpc``, but they are not as well supported
71 as proto3. Specifically, ``pw_rpc`` lacks support for non-zero default values
72 in proto2. When using Nanopb with ``pw_rpc``, proto2 response protobufs with
73 non-zero field defaults should be manually initialized to the default struct.
74
75 In the past, proto3 was sometimes avoided because it lacked support for field
76 presence detection. Fortunately, this has been fixed: proto3 now supports
77 ``optional`` fields, which are equivalent to proto2 ``optional`` fields.
78
79 If you need to distinguish between a default-valued field and a missing field,
80 mark the field as ``optional``. The presence of the field can be detected
81 with a ``HasField(name)`` or ``has_<field>`` member, depending on the library.
82
83 Optional fields have some overhead --- default-valued fields are included in
84 the encoded proto, and, if using Nanopb, the proto structs have a
85 ``has_<field>`` flag for each optional field. Use plain fields if field
86 presence detection is not needed.
87
88 .. code-block:: protobuf
89
90 syntax = "proto3";
91
92 message MyMessage {
93 // Leaving this field unset is equivalent to setting it to 0.
94 int32 number = 1;
95
96 // Setting this field to 0 is different from leaving it unset.
97 optional int32 other_number = 2;
98 }
99
Wyatt Hepler8779bcd2020-11-25 07:25:16 -08001002. RPC code generation
101----------------------
102``pw_rpc`` generates a C++ header file for each ``.proto`` file. This header is
103generated in the build output directory. Its exact location varies by build
104system and toolchain, but the C++ include path always matches the sources
105declaration in the ``pw_proto_library``. The ``.proto`` extension is replaced
106with an extension corresponding to the protobuf library in use.
Wyatt Hepler455b4922020-09-18 00:19:21 -0700107
Wyatt Hepler8779bcd2020-11-25 07:25:16 -0800108================== =============== =============== =============
109Protobuf libraries Build subtarget Protobuf header pw_rpc header
110================== =============== =============== =============
111Raw only .raw_rpc (none) .raw_rpc.pb.h
112Nanopb or raw .nanopb_rpc .pb.h .rpc.pb.h
113pw_protobuf or raw .pwpb_rpc .pwpb.h .rpc.pwpb.h
114================== =============== =============== =============
115
116For example, the generated RPC header for ``"foo_bar/the_service.proto"`` is
117``"foo_bar/the_service.rpc.pb.h"`` for Nanopb or
118``"foo_bar/the_service.raw_rpc.pb.h"`` for raw RPCs.
119
120The generated header defines a base class for each RPC service declared in the
121``.proto`` file. A service named ``TheService`` in package ``foo.bar`` would
122generate the following base class:
Wyatt Hepler455b4922020-09-18 00:19:21 -0700123
124.. cpp:class:: template <typename Implementation> foo::bar::generated::TheService
125
Wyatt Hepler8779bcd2020-11-25 07:25:16 -08001263. RPC service definition
127-------------------------
128The serivce class is implemented by inheriting from the generated RPC service
129base class and defining a method for each RPC. The methods must match the name
130and function signature for one of the supported protobuf implementations.
131Services may mix and match protobuf implementations within one service.
132
133.. tip::
134
135 The generated code includes RPC service implementation stubs. You can
136 reference or copy and paste these to get started with implementing a service.
137 These stub classes are generated at the bottom of the pw_rpc proto header.
138
Wyatt Heplerdf38ed12021-03-24 08:42:48 -0700139 To use the stubs, do the following:
140
141 #. Locate the generated RPC header in the build directory. For example:
142
143 .. code-block:: sh
144
145 find out/ -name <proto_name>.rpc.pb.h
146
147 #. Scroll to the bottom of the generated RPC header.
148 #. Copy the stub class declaration to a header file.
149 #. Copy the member function definitions to a source file.
150 #. Rename the class or change the namespace, if desired.
151 #. List these files in a build target with a dependency on the
152 ``pw_proto_library``.
153
Wyatt Hepler455b4922020-09-18 00:19:21 -0700154A Nanopb implementation of this service would be as follows:
155
156.. code-block:: cpp
157
Wyatt Hepler8779bcd2020-11-25 07:25:16 -0800158 #include "foo_bar/the_service.rpc.pb.h"
159
Wyatt Hepler455b4922020-09-18 00:19:21 -0700160 namespace foo::bar {
161
162 class TheService : public generated::TheService<TheService> {
163 public:
164 pw::Status MethodOne(ServerContext& ctx,
165 const foo_bar_Request& request,
166 foo_bar_Response& response) {
167 // implementation
Wyatt Hepler1b3da3a2021-01-07 13:26:57 -0800168 return pw::OkStatus();
Wyatt Hepler455b4922020-09-18 00:19:21 -0700169 }
170
171 void MethodTwo(ServerContext& ctx,
172 const foo_bar_Request& request,
173 ServerWriter<foo_bar_Response>& response) {
174 // implementation
175 response.Write(foo_bar_Response{.number = 123});
176 }
177 };
178
179 } // namespace foo::bar
180
181The Nanopb implementation would be declared in a ``BUILD.gn``:
182
183.. code-block:: python
184
185 import("//build_overrides/pigweed.gni")
186
187 import("$dir_pw_build/target_types.gni")
188
189 pw_source_set("the_service") {
190 public_configs = [ ":public" ]
191 public = [ "public/foo_bar/service.h" ]
Wyatt Hepler8779bcd2020-11-25 07:25:16 -0800192 public_deps = [ ":the_service_proto.nanopb_rpc" ]
Wyatt Hepler455b4922020-09-18 00:19:21 -0700193 }
194
195.. attention::
196
197 pw_rpc's generated classes will support using ``pw_protobuf`` or raw buffers
198 (no protobuf library) in the future.
199
Wyatt Hepler8779bcd2020-11-25 07:25:16 -08002004. Register the service with a server
Wyatt Hepler455b4922020-09-18 00:19:21 -0700201-------------------------------------
Alexei Frolovd3e5cb72021-01-08 13:08:45 -0800202This example code sets up an RPC server with an :ref:`HDLC<module-pw_hdlc>`
Wyatt Heplerf9fb90f2020-09-30 18:59:33 -0700203channel output and the example service.
Wyatt Hepler455b4922020-09-18 00:19:21 -0700204
205.. code-block:: cpp
206
207 // Set up the output channel for the pw_rpc server to use. This configures the
208 // pw_rpc server to use HDLC over UART; projects not using UART and HDLC must
209 // adapt this as necessary.
210 pw::stream::SysIoWriter writer;
211 pw::rpc::RpcChannelOutput<kMaxTransmissionUnit> hdlc_channel_output(
Alexei Frolovd3e5cb72021-01-08 13:08:45 -0800212 writer, pw::hdlc::kDefaultRpcAddress, "HDLC output");
Wyatt Hepler455b4922020-09-18 00:19:21 -0700213
214 pw::rpc::Channel channels[] = {
215 pw::rpc::Channel::Create<1>(&hdlc_channel_output)};
216
217 // Declare the pw_rpc server with the HDLC channel.
218 pw::rpc::Server server(channels);
219
220 pw::rpc::TheService the_service;
221
222 void RegisterServices() {
223 // Register the foo.bar.TheService example service.
224 server.Register(the_service);
225
226 // Register other services
227 }
228
229 int main() {
230 // Set up the server.
231 RegisterServices();
232
233 // Declare a buffer for decoding incoming HDLC frames.
234 std::array<std::byte, kMaxTransmissionUnit> input_buffer;
235
236 PW_LOG_INFO("Starting pw_rpc server");
Alexei Frolovd3e5cb72021-01-08 13:08:45 -0800237 pw::hdlc::ReadAndProcessPackets(
Wyatt Hepler455b4922020-09-18 00:19:21 -0700238 server, hdlc_channel_output, input_buffer);
239 }
Wyatt Hepler948f5472020-06-02 16:52:28 -0700240
Alexei Frolov1c670a22021-04-09 10:18:17 -0700241Channels
242========
243``pw_rpc`` sends all of its packets over channels. These are logical,
244application-layer routes used to tell the RPC system where a packet should go.
245
246Channels over a client-server connection must all have a unique ID, which can be
247assigned statically at compile time or dynamically.
248
249.. code-block:: cpp
250
251 // Creating a channel with the static ID 3.
252 pw::rpc::Channel static_channel = pw::rpc::Channel::Create<3>(&output);
253
254 // Grouping channel IDs within an enum can lead to clearer code.
255 enum ChannelId {
256 kUartChannel = 1,
257 kSpiChannel = 2,
258 };
259
260 // Creating a channel with a static ID defined within an enum.
261 pw::rpc::Channel another_static_channel =
262 pw::rpc::Channel::Create<ChannelId::kUartChannel>(&output);
263
264 // Creating a channel with a dynamic ID (note that no output is provided; it
265 // will be set when the channel is used.
266 pw::rpc::Channel dynamic_channel;
267
Alexei Frolov567e6702021-04-13 09:13:02 -0700268Sometimes, the ID and output of a channel are not known at compile time as they
269depend on information stored on the physical device. To support this use case, a
270dynamically-assignable channel can be configured once at runtime with an ID and
271output.
272
273.. code-block:: cpp
274
275 // Create a dynamic channel without a compile-time ID or output.
276 pw::rpc::Channel dynamic_channel;
277
278 void Init() {
279 // Called during boot to pull the channel configuration from the system.
280 dynamic_channel.Configure(GetChannelId(), some_output);
281 }
282
283
Alexei Frolov7c7a3862020-07-16 15:36:02 -0700284Services
285========
286A service is a logical grouping of RPCs defined within a .proto file. ``pw_rpc``
287uses these .proto definitions to generate code for a base service, from which
288user-defined RPCs are implemented.
289
290``pw_rpc`` supports multiple protobuf libraries, and the generated code API
291depends on which is used.
292
Wyatt Heplerf9fb90f2020-09-30 18:59:33 -0700293.. _module-pw_rpc-protobuf-library-apis:
Alexei Frolov4d2adde2020-08-04 10:19:24 -0700294
295Protobuf library APIs
296=====================
297
Alexei Frolov7c7a3862020-07-16 15:36:02 -0700298.. toctree::
299 :maxdepth: 1
300
301 nanopb/docs
302
303Testing a pw_rpc integration
304============================
305After setting up a ``pw_rpc`` server in your project, you can test that it is
306working as intended by registering the provided ``EchoService``, defined in
Wyatt Hepler752d7d32021-03-02 09:02:23 -0800307``echo.proto``, which echoes back a message that it receives.
Alexei Frolov7c7a3862020-07-16 15:36:02 -0700308
Wyatt Hepler752d7d32021-03-02 09:02:23 -0800309.. literalinclude:: echo.proto
Alexei Frolov7c7a3862020-07-16 15:36:02 -0700310 :language: protobuf
311 :lines: 14-
312
313For example, in C++ with nanopb:
314
315.. code:: c++
316
317 #include "pw_rpc/server.h"
318
319 // Include the apporpriate header for your protobuf library.
320 #include "pw_rpc/echo_service_nanopb.h"
321
322 constexpr pw::rpc::Channel kChannels[] = { /* ... */ };
323 static pw::rpc::Server server(kChannels);
324
325 static pw::rpc::EchoService echo_service;
326
327 void Init() {
328 server.RegisterService(&echo_service);
329 }
330
Wyatt Hepler067dd7e2020-07-14 19:34:32 -0700331Protocol description
332====================
333Pigweed RPC servers and clients communicate using ``pw_rpc`` packets. These
334packets are used to send requests and responses, control streams, cancel ongoing
335RPCs, and report errors.
336
337Packet format
338-------------
339Pigweed RPC packets consist of a type and a set of fields. The packets are
340encoded as protocol buffers. The full packet format is described in
Wyatt Heplerba325e42021-03-08 14:23:34 -0800341``pw_rpc/pw_rpc/internal/packet.proto``.
Wyatt Hepler067dd7e2020-07-14 19:34:32 -0700342
Wyatt Hepler752d7d32021-03-02 09:02:23 -0800343.. literalinclude:: internal/packet.proto
Wyatt Hepler067dd7e2020-07-14 19:34:32 -0700344 :language: protobuf
345 :lines: 14-
346
347The packet type and RPC type determine which fields are present in a Pigweed RPC
Wyatt Hepler0f262352020-07-29 09:51:27 -0700348packet. Each packet type is only sent by either the client or the server.
349These tables describe the meaning of and fields included with each packet type.
Wyatt Hepler067dd7e2020-07-14 19:34:32 -0700350
Wyatt Hepler0f262352020-07-29 09:51:27 -0700351Client-to-server packets
352^^^^^^^^^^^^^^^^^^^^^^^^
Wyatt Heplera9211162021-06-12 15:40:11 -0700353+-------------------+-------------------------------------+
354| packet type | description |
355+===================+=====================================+
356| REQUEST | Invoke an RPC |
357| | |
358| | .. code-block:: text |
359| | |
360| | - channel_id |
361| | - service_id |
362| | - method_id |
363| | - payload |
364| | (unary & server streaming only) |
365| | |
366+-------------------+-------------------------------------+
367| CLIENT_STREAM | Message in a client stream |
368| | |
369| | .. code-block:: text |
370| | |
371| | - channel_id |
372| | - service_id |
373| | - method_id |
374| | - payload |
375| | |
376+-------------------+-------------------------------------+
377| CLIENT_ERROR | Received unexpected packet |
378| | |
379| | .. code-block:: text |
380| | |
381| | - channel_id |
382| | - service_id |
383| | - method_id |
384| | - status |
385| | |
386+-------------------+-------------------------------------+
387| CANCEL | Cancel an ongoing RPC |
388| | |
389| | .. code-block:: text |
390| | |
391| | - channel_id |
392| | - service_id |
393| | - method_id |
394| | |
395+-------------------+-------------------------------------+
396| CLIENT_STREAM_END | Client stream is complete |
397| | |
398| | .. code-block:: text |
399| | |
400| | - channel_id |
401| | - service_id |
402| | - method_id |
403| | |
404+-------------------+-------------------------------------+
Wyatt Hepler067dd7e2020-07-14 19:34:32 -0700405
Wyatt Hepler0f262352020-07-29 09:51:27 -0700406**Errors**
Wyatt Hepler067dd7e2020-07-14 19:34:32 -0700407
Wyatt Hepler0f262352020-07-29 09:51:27 -0700408The client sends ``CLIENT_ERROR`` packets to a server when it receives a packet
409it did not request. If the RPC is a streaming RPC, the server should abort it.
410
Wyatt Heplera9211162021-06-12 15:40:11 -0700411The status code indicates the type of error. The status code is logged, but all
412status codes result in the same action by the server: aborting the RPC.
Wyatt Hepler0f262352020-07-29 09:51:27 -0700413
414* ``NOT_FOUND`` -- Received a packet for a service method the client does not
415 recognize.
416* ``FAILED_PRECONDITION`` -- Received a packet for a service method that the
417 client did not invoke.
418
419Server-to-client packets
420^^^^^^^^^^^^^^^^^^^^^^^^
Wyatt Heplera9211162021-06-12 15:40:11 -0700421+-------------------+-------------------------------------+
422| packet type | description |
423+===================+=====================================+
424| RESPONSE | RPC response |
425| | |
426| | .. code-block:: text |
427| | |
428| | - channel_id |
429| | - service_id |
430| | - method_id |
431| | - payload |
432| | - status |
433| | (unary & client streaming only) |
434| | |
435+-------------------+-------------------------------------+
436| SERVER_STREAM_END | Server stream and RPC finished |
437| | |
438| | .. code-block:: text |
439| | |
440| | - channel_id |
441| | - service_id |
442| | - method_id |
443| | - status |
444| | |
445+-------------------+-------------------------------------+
446| SERVER_ERROR | Received unexpected packet |
447| | |
448| | .. code-block:: text |
449| | |
450| | - channel_id |
451| | - service_id (if relevant) |
452| | - method_id (if relevant) |
453| | - status |
454| | |
455+-------------------+-------------------------------------+
Wyatt Hepler0f262352020-07-29 09:51:27 -0700456
457**Errors**
458
459The server sends ``SERVER_ERROR`` packets when it receives a packet it cannot
460process. The client should abort any RPC for which it receives an error. The
461status field indicates the type of error.
462
463* ``NOT_FOUND`` -- The requested service or method does not exist.
Wyatt Heplera9211162021-06-12 15:40:11 -0700464* ``FAILED_PRECONDITION`` -- A client stream or cancel packet was sent for an
465 RPC that is not pending.
Wyatt Hepler067dd7e2020-07-14 19:34:32 -0700466* ``RESOURCE_EXHAUSTED`` -- The request came on a new channel, but a channel
467 could not be allocated for it.
Wyatt Hepler712d3672020-07-13 15:52:11 -0700468* ``INTERNAL`` -- The server was unable to respond to an RPC due to an
469 unrecoverable internal error.
Wyatt Hepler067dd7e2020-07-14 19:34:32 -0700470
471Inovking a service method
472-------------------------
473Calling an RPC requires a specific sequence of packets. This section describes
474the protocol for calling service methods of each type: unary, server streaming,
475client streaming, and bidirectional streaming.
476
477Unary RPC
478^^^^^^^^^
479In a unary RPC, the client sends a single request and the server sends a single
480response.
481
482.. seqdiag::
483 :scale: 110
484
485 seqdiag {
486 default_note_color = aliceblue;
487
488 client -> server [
489 label = "request",
Wyatt Hepler0f262352020-07-29 09:51:27 -0700490 leftnote = "PacketType.REQUEST\nchannel ID\nservice ID\nmethod ID\npayload"
Wyatt Hepler067dd7e2020-07-14 19:34:32 -0700491 ];
492
493 client <- server [
494 label = "response",
Wyatt Hepler0f262352020-07-29 09:51:27 -0700495 rightnote = "PacketType.RESPONSE\nchannel ID\nservice ID\nmethod ID\npayload\nstatus"
Wyatt Hepler067dd7e2020-07-14 19:34:32 -0700496 ];
497 }
498
Wyatt Heplera9211162021-06-12 15:40:11 -0700499The client may attempt to cancel a unary RPC by sending a ``CANCEL`` packet. The
500server sends no response to a cancelled RPC. If the server processes the unary
501RPC synchronously (the handling thread sends the response), it may not be
502possible to cancel the RPC.
503
504.. seqdiag::
505 :scale: 110
506
507 seqdiag {
508 default_note_color = aliceblue;
509
510 client -> server [
511 label = "request",
512 leftnote = "PacketType.REQUEST\nchannel ID\nservice ID\nmethod ID\npayload"
513 ];
514
515 client -> server [
516 noactivate,
517 label = "cancel",
518 leftnote = "PacketType.CANCEL\nchannel ID\nservice ID\nmethod ID"
519 ];
520 }
521
Wyatt Hepler067dd7e2020-07-14 19:34:32 -0700522Server streaming RPC
523^^^^^^^^^^^^^^^^^^^^
524In a server streaming RPC, the client sends a single request and the server
Wyatt Hepler0f262352020-07-29 09:51:27 -0700525sends any number of responses followed by a ``SERVER_STREAM_END`` packet.
Wyatt Hepler067dd7e2020-07-14 19:34:32 -0700526
527.. seqdiag::
528 :scale: 110
529
530 seqdiag {
531 default_note_color = aliceblue;
532
533 client -> server [
534 label = "request",
Wyatt Hepler0f262352020-07-29 09:51:27 -0700535 leftnote = "PacketType.REQUEST\nchannel ID\nservice ID\nmethod ID\npayload"
Wyatt Hepler067dd7e2020-07-14 19:34:32 -0700536 ];
537
538 client <-- server [
539 noactivate,
Wyatt Heplera9211162021-06-12 15:40:11 -0700540 label = "messages (zero or more)",
Wyatt Hepler0f262352020-07-29 09:51:27 -0700541 rightnote = "PacketType.RESPONSE\nchannel ID\nservice ID\nmethod ID\npayload"
Wyatt Hepler067dd7e2020-07-14 19:34:32 -0700542 ];
543
544 client <- server [
545 label = "done",
Wyatt Hepler0f262352020-07-29 09:51:27 -0700546 rightnote = "PacketType.SERVER_STREAM_END\nchannel ID\nservice ID\nmethod ID\nstatus"
Wyatt Hepler067dd7e2020-07-14 19:34:32 -0700547 ];
548 }
549
Wyatt Heplera9211162021-06-12 15:40:11 -0700550The client may terminate a server streaming RPC by sending a ``CANCEL`` packet.
551The server sends no response.
Wyatt Hepler067dd7e2020-07-14 19:34:32 -0700552
553.. seqdiag::
554 :scale: 110
555
556 seqdiag {
557 default_note_color = aliceblue;
558
559 client -> server [
560 label = "request",
Wyatt Hepler0f262352020-07-29 09:51:27 -0700561 leftnote = "PacketType.REQUEST\nchannel ID\nservice ID\nmethod ID\npayload"
Wyatt Hepler067dd7e2020-07-14 19:34:32 -0700562 ];
563
564 client <-- server [
565 noactivate,
Wyatt Heplera9211162021-06-12 15:40:11 -0700566 label = "messages (zero or more)",
Wyatt Hepler0f262352020-07-29 09:51:27 -0700567 rightnote = "PacketType.RESPONSE\nchannel ID\nservice ID\nmethod ID\npayload"
Wyatt Hepler067dd7e2020-07-14 19:34:32 -0700568 ];
569
570 client -> server [
571 noactivate,
572 label = "cancel",
Wyatt Heplera9211162021-06-12 15:40:11 -0700573 leftnote = "PacketType.CANCEL\nchannel ID\nservice ID\nmethod ID"
Wyatt Hepler067dd7e2020-07-14 19:34:32 -0700574 ];
575 }
576
577Client streaming RPC
578^^^^^^^^^^^^^^^^^^^^
Wyatt Heplera9211162021-06-12 15:40:11 -0700579In a client streaming RPC, the client starts the RPC by sending a ``REQUEST``
580packet with no payload. It then sends any number of messages in
581``CLIENT_STREAM`` packets, followed by a ``CLIENT_STREAM_END``. The server sends
582a single response to finish the RPC.
Wyatt Hepler067dd7e2020-07-14 19:34:32 -0700583
584.. seqdiag::
585 :scale: 110
586
587 seqdiag {
588 default_note_color = aliceblue;
589
590 client -> server [
591 label = "start",
Wyatt Hepler0f262352020-07-29 09:51:27 -0700592 leftnote = "PacketType.REQUEST\nchannel ID\nservice ID\nmethod ID"
Wyatt Hepler067dd7e2020-07-14 19:34:32 -0700593 ];
594
595 client --> server [
596 noactivate,
Wyatt Heplera9211162021-06-12 15:40:11 -0700597 label = "messages (zero or more)",
598 leftnote = "PacketType.CLIENT_STREAM\nchannel ID\nservice ID\nmethod ID\npayload"
Wyatt Hepler067dd7e2020-07-14 19:34:32 -0700599 ];
600
601 client -> server [
602 noactivate,
603 label = "done",
Wyatt Hepler0f262352020-07-29 09:51:27 -0700604 leftnote = "PacketType.CLIENT_STREAM_END\nchannel ID\nservice ID\nmethod ID"
Wyatt Hepler067dd7e2020-07-14 19:34:32 -0700605 ];
606
607 client <- server [
608 label = "response",
Wyatt Hepler0f262352020-07-29 09:51:27 -0700609 rightnote = "PacketType.RESPONSE\nchannel ID\nservice ID\nmethod ID\npayload\nstatus"
Wyatt Hepler067dd7e2020-07-14 19:34:32 -0700610 ];
611 }
612
Wyatt Heplera9211162021-06-12 15:40:11 -0700613The server may finish the RPC at any time by sending its ``RESPONSE`` packet,
614even if it has not yet received the ``CLIENT_STREAM_END`` packet. The client may
615terminate the RPC at any time by sending a ``CANCEL`` packet.
Wyatt Hepler067dd7e2020-07-14 19:34:32 -0700616
617.. seqdiag::
618 :scale: 110
619
620 seqdiag {
621 default_note_color = aliceblue;
622
623 client -> server [
624 label = "start",
Wyatt Hepler0f262352020-07-29 09:51:27 -0700625 leftnote = "PacketType.REQUEST\nchannel ID\nservice ID\nmethod ID"
Wyatt Hepler067dd7e2020-07-14 19:34:32 -0700626 ];
627
628 client --> server [
629 noactivate,
Wyatt Heplera9211162021-06-12 15:40:11 -0700630 label = "messages (zero or more)",
631 leftnote = "PacketType.CLIENT_STREAM\nchannel ID\nservice ID\nmethod ID\npayload"
Wyatt Hepler067dd7e2020-07-14 19:34:32 -0700632 ];
633
Wyatt Heplera9211162021-06-12 15:40:11 -0700634 client -> server [
635 noactivate,
636 label = "cancel",
637 rightnote = "PacketType.CANCEL\nchannel ID\nservice ID\nmethod ID"
Wyatt Hepler067dd7e2020-07-14 19:34:32 -0700638 ];
639 }
640
641Bidirectional streaming RPC
642^^^^^^^^^^^^^^^^^^^^^^^^^^^
643In a bidirectional streaming RPC, the client sends any number of requests and
Wyatt Heplera9211162021-06-12 15:40:11 -0700644the server sends any number of responses. The client invokes the RPC by sending
645a ``REQUEST`` with no payload. It sends a ``CLIENT_STREAM_END`` packet when it
646has finished sending requests. The server sends a ``SERVER_STREAM_END`` packet
647to finish the RPC.
Wyatt Hepler067dd7e2020-07-14 19:34:32 -0700648
649.. seqdiag::
650 :scale: 110
651
652 seqdiag {
653 default_note_color = aliceblue;
654
655 client -> server [
656 label = "start",
Wyatt Hepler0f262352020-07-29 09:51:27 -0700657 leftnote = "PacketType.REQUEST\nchannel ID\nservice ID\nmethod ID"
Wyatt Hepler067dd7e2020-07-14 19:34:32 -0700658 ];
659
660 client --> server [
661 noactivate,
Wyatt Heplera9211162021-06-12 15:40:11 -0700662 label = "messages (zero or more)",
663 leftnote = "PacketType.CLIENT_STREAM\nchannel ID\nservice ID\nmethod ID\npayload"
Wyatt Hepler067dd7e2020-07-14 19:34:32 -0700664 ];
665
666 ... (messages in any order) ...
667
668 client <-- server [
669 noactivate,
Wyatt Heplera9211162021-06-12 15:40:11 -0700670 label = "messages (zero or more)",
Wyatt Hepler0f262352020-07-29 09:51:27 -0700671 rightnote = "PacketType.RESPONSE\nchannel ID\nservice ID\nmethod ID\npayload"
Wyatt Hepler067dd7e2020-07-14 19:34:32 -0700672 ];
673
674 client -> server [
675 noactivate,
676 label = "done",
Wyatt Hepler0f262352020-07-29 09:51:27 -0700677 leftnote = "PacketType.CLIENT_STREAM_END\nchannel ID\nservice ID\nmethod ID"
Wyatt Hepler067dd7e2020-07-14 19:34:32 -0700678 ];
679
Wyatt Hepler067dd7e2020-07-14 19:34:32 -0700680 client <- server [
681 label = "done",
Wyatt Hepler0f262352020-07-29 09:51:27 -0700682 rightnote = "PacketType.SERVER_STREAM_END\nchannel ID\nservice ID\nmethod ID\nstatus"
Wyatt Hepler067dd7e2020-07-14 19:34:32 -0700683 ];
684 }
685
Wyatt Heplera9211162021-06-12 15:40:11 -0700686The server may finish the RPC at any time by sending the ``SERVER_STREAM_END``
687packet, even if it has not received the ``CLIENT_STREAM_END`` packet. The client
688may terminate the RPC at any time by sending a ``CANCEL`` packet.
Wyatt Hepler067dd7e2020-07-14 19:34:32 -0700689
690.. seqdiag::
691 :scale: 110
692
693 seqdiag {
694 default_note_color = aliceblue;
695
696 client -> server [
697 label = "start",
Wyatt Heplera9211162021-06-12 15:40:11 -0700698 leftnote = "PacketType.REQUEST\nchannel ID\nservice ID\nmethod ID"
Wyatt Hepler067dd7e2020-07-14 19:34:32 -0700699 ];
700
701 client --> server [
702 noactivate,
Wyatt Heplera9211162021-06-12 15:40:11 -0700703 label = "messages (zero or more)",
704 leftnote = "PacketType.CLIENT_STREAM\nchannel ID\nservice ID\nmethod ID\npayload"
Wyatt Hepler067dd7e2020-07-14 19:34:32 -0700705 ];
706
707 client <-- server [
708 noactivate,
Wyatt Heplera9211162021-06-12 15:40:11 -0700709 label = "messages (zero or more)",
710 rightnote = "PacketType.RESPONSE\nchannel ID\nservice ID\nmethod ID\npayload"
Wyatt Hepler067dd7e2020-07-14 19:34:32 -0700711 ];
712
713 client -> server [
714 noactivate,
715 label = "cancel",
Wyatt Heplera9211162021-06-12 15:40:11 -0700716 leftnote = "PacketType.CANCEL\nchannel ID\nservice ID\nmethod ID" ];
Wyatt Hepler067dd7e2020-07-14 19:34:32 -0700717 }
718
Wyatt Hepler948f5472020-06-02 16:52:28 -0700719RPC server
720==========
721Declare an instance of ``rpc::Server`` and register services with it.
722
723.. admonition:: TODO
724
725 Document the public interface
726
Alexei Frolovbf33d212020-09-15 17:13:45 -0700727Size report
728-----------
729The following size report showcases the memory usage of the core RPC server. It
730is configured with a single channel using a basic transport interface that
731directly reads from and writes to ``pw_sys_io``. The transport has a 128-byte
732packet buffer, which comprises the plurality of the example's RAM usage. This is
733not a suitable transport for an actual product; a real implementation would have
734additional overhead proportional to the complexity of the transport.
735
736.. include:: server_size
737
Wyatt Hepler948f5472020-06-02 16:52:28 -0700738RPC server implementation
739-------------------------
740
741The Method class
742^^^^^^^^^^^^^^^^
743The RPC Server depends on the ``pw::rpc::internal::Method`` class. ``Method``
744serves as the bridge between the ``pw_rpc`` server library and the user-defined
Wyatt Heplere95bd722020-11-23 07:49:47 -0800745RPC functions. Each supported protobuf implementation extends ``Method`` to
746implement its request and response proto handling. The ``pw_rpc`` server
747calls into the ``Method`` implementation through the base class's ``Invoke``
748function.
Wyatt Hepler948f5472020-06-02 16:52:28 -0700749
Wyatt Heplere95bd722020-11-23 07:49:47 -0800750``Method`` implementations store metadata about each method, including a
751function pointer to the user-defined method implementation. They also provide
752``static constexpr`` functions for creating each type of method. ``Method``
753implementations must satisfy the ``MethodImplTester`` test class in
754``pw_rpc_private/method_impl_tester.h``.
Wyatt Hepler948f5472020-06-02 16:52:28 -0700755
Wyatt Heplere95bd722020-11-23 07:49:47 -0800756See ``pw_rpc/internal/method.h`` for more details about ``Method``.
Wyatt Hepler948f5472020-06-02 16:52:28 -0700757
758Packet flow
759^^^^^^^^^^^
760
761Requests
762~~~~~~~~
763
764.. blockdiag::
765
766 blockdiag {
767 packets [shape = beginpoint];
768
769 group {
Wyatt Hepler067dd7e2020-07-14 19:34:32 -0700770 label = "pw_rpc library";
Wyatt Hepler948f5472020-06-02 16:52:28 -0700771
772 server [label = "Server"];
Alexei Frolov9a4d6bf2020-08-04 10:33:26 -0700773 service [label = "Service"];
Wyatt Hepler948f5472020-06-02 16:52:28 -0700774 method [label = "internal::Method"];
775 }
776
777 stubs [label = "generated services", shape = ellipse];
778 user [label = "user-defined RPCs", shape = roundedbox];
779
780 packets -> server -> service -> method -> stubs -> user;
781 packets -> server [folded];
782 method -> stubs [folded];
783 }
784
785Responses
786~~~~~~~~~
787
788.. blockdiag::
789
790 blockdiag {
791 user -> stubs [folded];
792
793 group {
Wyatt Hepler067dd7e2020-07-14 19:34:32 -0700794 label = "pw_rpc library";
Wyatt Hepler948f5472020-06-02 16:52:28 -0700795
796 server [label = "Server"];
797 method [label = "internal::Method"];
798 channel [label = "Channel"];
799 }
800
801 stubs [label = "generated services", shape = ellipse];
802 user [label = "user-defined RPCs", shape = roundedbox];
803 packets [shape = beginpoint];
804
805 user -> stubs -> method [folded];
806 method -> server -> channel;
807 channel -> packets [folded];
808 }
Alexei Frolov4d2adde2020-08-04 10:19:24 -0700809
810RPC client
811==========
812The RPC client is used to send requests to a server and manages the contexts of
813ongoing RPCs.
814
815Setting up a client
816-------------------
817The ``pw::rpc::Client`` class is instantiated with a list of channels that it
818uses to communicate. These channels can be shared with a server, but multiple
819clients cannot use the same channels.
820
821To send incoming RPC packets from the transport layer to be processed by a
822client, the client's ``ProcessPacket`` function is called with the packet data.
823
824.. code:: c++
825
826 #include "pw_rpc/client.h"
827
828 namespace {
829
830 pw::rpc::Channel my_channels[] = {
831 pw::rpc::Channel::Create<1>(&my_channel_output)};
832 pw::rpc::Client my_client(my_channels);
833
834 } // namespace
835
836 // Called when the transport layer receives an RPC packet.
837 void ProcessRpcPacket(ConstByteSpan packet) {
838 my_client.ProcessPacket(packet);
839 }
840
Alexei Frolov5a3a61c2020-10-01 18:51:41 -0700841.. _module-pw_rpc-making-calls:
842
Alexei Frolov4d2adde2020-08-04 10:19:24 -0700843Making RPC calls
844----------------
845RPC calls are not made directly through the client, but using one of its
846registered channels instead. A service client class is generated from a .proto
847file for each selected protobuf library, which is then used to send RPC requests
848through a given channel. The API for this depends on the protobuf library;
Wyatt Heplerf9fb90f2020-09-30 18:59:33 -0700849please refer to the
850:ref:`appropriate documentation<module-pw_rpc-protobuf-library-apis>`. Multiple
851service client implementations can exist simulatenously and share the same
852``Client`` class.
Alexei Frolov4d2adde2020-08-04 10:19:24 -0700853
854When a call is made, a ``pw::rpc::ClientCall`` object is returned to the caller.
855This object tracks the ongoing RPC call, and can be used to manage it. An RPC
856call is only active as long as its ``ClientCall`` object is alive.
857
858.. tip::
859 Use ``std::move`` when passing around ``ClientCall`` objects to keep RPCs
860 alive.
861
Alexei Frolov2d737bc2021-04-27 23:03:09 -0700862Example
863^^^^^^^
864.. code-block:: c++
865
866 #include "pw_rpc/echo_service_nanopb.h"
867
868 namespace {
Alexei Frolov2b54ee62021-04-29 14:58:21 -0700869 // Generated clients are namespaced with their proto library.
870 using pw::rpc::nanopb::EchoServiceClient;
871
872 EchoServiceClient::EchoCall echo_call;
Alexei Frolovbebba902021-06-09 17:03:52 -0700873
874 // Callback invoked when a response is received. This is called synchronously
875 // from Client::ProcessPacket.
876 void EchoResponse(const pw_rpc_EchoMessage& response,
877 pw::Status status) {
878 if (status.ok()) {
879 PW_LOG_INFO("Received echo response: %s", response.msg);
880 } else {
881 PW_LOG_ERROR("Echo failed with status %d",
882 static_cast<int>(status.code()));
883 }
884 }
Alexei Frolov2d737bc2021-04-27 23:03:09 -0700885
886 } // namespace
887
Alexei Frolovbebba902021-06-09 17:03:52 -0700888
Alexei Frolov2d737bc2021-04-27 23:03:09 -0700889 void CallEcho(const char* message) {
890 pw_rpc_EchoMessage request = pw_rpc_EchoMessage_init_default;
891 pw::string::Copy(message, request.msg);
892
893 // By assigning the returned ClientCall to the global echo_call, the RPC
894 // call is kept alive until it completes. When a response is received, it
895 // will be logged by the handler function and the call will complete.
Alexei Frolovbebba902021-06-09 17:03:52 -0700896 echo_call = EchoServiceClient::Echo(my_channel, request, EchoResponse);
Alexei Frolov2d737bc2021-04-27 23:03:09 -0700897 }
898
Alexei Frolov4d2adde2020-08-04 10:19:24 -0700899Client implementation details
900-----------------------------
901
902The ClientCall class
903^^^^^^^^^^^^^^^^^^^^
904``ClientCall`` stores the context of an active RPC, and serves as the user's
905interface to the RPC client. The core RPC library provides a base ``ClientCall``
906class with common functionality, which is then extended for RPC client
907implementations tied to different protobuf libraries to provide convenient
908interfaces for working with RPCs.
909
910The RPC server stores a list of all of active ``ClientCall`` objects. When an
911incoming packet is recieved, it dispatches to one of its active calls, which
912then decodes the payload and presents it to the user.
Alexei Frolov3e280922021-04-12 14:53:06 -0700913
914ClientServer
915============
916Sometimes, a device needs to both process RPCs as a server, as well as making
917calls to another device as a client. To do this, both a client and server must
918be set up, and incoming packets must be sent to both of them.
919
920Pigweed simplifies this setup by providing a ``ClientServer`` class which wraps
921an RPC client and server with the same set of channels.
922
923.. code-block:: cpp
924
925 pw::rpc::Channel channels[] = {
926 pw::rpc::Channel::Create<1>(&channel_output)};
927
928 // Creates both a client and a server.
929 pw::rpc::ClientServer client_server(channels);
930
931 void ProcessRpcData(pw::ConstByteSpan packet) {
932 // Calls into both the client and the server, sending the packet to the
933 // appropriate one.
934 client_server.ProcessPacket(packet, output);
935 }