Wyatt Hepler | f9fb90f | 2020-09-30 18:59:33 -0700 | [diff] [blame] | 1 | .. _module-pw_rpc_nanopb: |
Alexei Frolov | 7c7a386 | 2020-07-16 15:36:02 -0700 | [diff] [blame] | 2 | |
| 3 | ------ |
| 4 | nanopb |
| 5 | ------ |
Alexei Frolov | 3ab26ff | 2020-07-21 10:44:58 -0700 | [diff] [blame] | 6 | ``pw_rpc`` can generate services which encode/decode RPC requests and responses |
| 7 | as nanopb message structs. |
Alexei Frolov | 7c7a386 | 2020-07-16 15:36:02 -0700 | [diff] [blame] | 8 | |
Alexei Frolov | 3ab26ff | 2020-07-21 10:44:58 -0700 | [diff] [blame] | 9 | Usage |
| 10 | ===== |
Alexei Frolov | b499d3f | 2020-10-28 13:00:08 -0700 | [diff] [blame] | 11 | To enable nanopb code generation, the build argument |
| 12 | ``dir_pw_third_party_nanopb`` must be set to point to a local nanopb |
| 13 | installation. |
Alexei Frolov | 3ab26ff | 2020-07-21 10:44:58 -0700 | [diff] [blame] | 14 | |
| 15 | Define a ``pw_proto_library`` containing the .proto file defining your service |
Alexei Frolov | b499d3f | 2020-10-28 13:00:08 -0700 | [diff] [blame] | 16 | (and optionally other related protos), then depend on the ``nanopb_rpc`` |
Alexei Frolov | 3ab26ff | 2020-07-21 10:44:58 -0700 | [diff] [blame] | 17 | version of that library in the code implementing the service. |
| 18 | |
| 19 | .. code:: |
| 20 | |
| 21 | # chat/BUILD.gn |
| 22 | |
| 23 | import("$dir_pw_build/target_types.gni") |
| 24 | import("$dir_pw_protobuf_compiler/proto.gni") |
| 25 | |
| 26 | pw_proto_library("chat_protos") { |
| 27 | sources = [ "chat_protos/chat_service.proto" ] |
| 28 | } |
| 29 | |
| 30 | # Library that implements the ChatService. |
| 31 | pw_source_set("chat_service") { |
| 32 | sources = [ |
| 33 | "chat_service.cc", |
| 34 | "chat_service.h", |
| 35 | ] |
Alexei Frolov | b499d3f | 2020-10-28 13:00:08 -0700 | [diff] [blame] | 36 | public_deps = [ ":chat_protos.nanopb_rpc" ] |
Alexei Frolov | 3ab26ff | 2020-07-21 10:44:58 -0700 | [diff] [blame] | 37 | } |
| 38 | |
| 39 | A C++ header file is generated for each input .proto file, with the ``.proto`` |
| 40 | extension replaced by ``.rpc.pb.h``. For example, given the input file |
| 41 | ``chat_protos/chat_service.proto``, the generated header file will be placed |
| 42 | at the include path ``"chat_protos/chat_service.rpc.pb.h"``. |
| 43 | |
| 44 | Generated code API |
| 45 | ================== |
Alexei Frolov | bebba90 | 2021-06-09 17:03:52 -0700 | [diff] [blame] | 46 | All examples in this document use the following RPC service definition. |
Alexei Frolov | 3ab26ff | 2020-07-21 10:44:58 -0700 | [diff] [blame] | 47 | |
| 48 | .. code:: protobuf |
| 49 | |
| 50 | // chat/chat_protos/chat_service.proto |
| 51 | |
| 52 | syntax = "proto3"; |
| 53 | |
| 54 | service ChatService { |
| 55 | // Returns information about a chatroom. |
| 56 | rpc GetRoomInformation(RoomInfoRequest) returns (RoomInfoResponse) {} |
| 57 | |
| 58 | // Lists all of the users in a chatroom. The response is streamed as there |
| 59 | // may be a large amount of users. |
| 60 | rpc ListUsersInRoom(ListUsersRequest) returns (stream ListUsersResponse) {} |
| 61 | |
| 62 | // Uploads a file, in chunks, to a chatroom. |
| 63 | rpc UploadFile(stream UploadFileRequest) returns (UploadFileResponse) {} |
| 64 | |
| 65 | // Sends messages to a chatroom while receiving messages from other users. |
| 66 | rpc Chat(stream ChatMessage) returns (stream ChatMessage) {} |
| 67 | } |
| 68 | |
| 69 | Server-side |
| 70 | ----------- |
| 71 | A C++ class is generated for each service in the .proto file. The class is |
| 72 | located within a special ``generated`` sub-namespace of the file's package. |
| 73 | |
| 74 | The generated class is a base class which must be derived to implement the |
| 75 | service's methods. The base class is templated on the derived class. |
| 76 | |
| 77 | .. code:: c++ |
| 78 | |
| 79 | #include "chat_protos/chat_service.rpc.pb.h" |
| 80 | |
| 81 | class ChatService final : public generated::ChatService<ChatService> { |
| 82 | public: |
| 83 | // Implementations of the service's RPC methods; see below. |
| 84 | }; |
| 85 | |
| 86 | Unary RPC |
| 87 | ^^^^^^^^^ |
| 88 | A unary RPC is implemented as a function which takes in the RPC's request struct |
| 89 | and populates a response struct to send back, with a status indicating whether |
| 90 | the request succeeded. |
| 91 | |
| 92 | .. code:: c++ |
| 93 | |
| 94 | pw::Status GetRoomInformation(pw::rpc::ServerContext& ctx, |
| 95 | const RoomInfoRequest& request, |
| 96 | RoomInfoResponse& response); |
| 97 | |
| 98 | Server streaming RPC |
| 99 | ^^^^^^^^^^^^^^^^^^^^ |
| 100 | A server streaming RPC receives the client's request message alongside a |
| 101 | ``ServerWriter``, used to stream back responses. |
| 102 | |
| 103 | .. code:: c++ |
| 104 | |
| 105 | void ListUsersInRoom(pw::rpc::ServerContext& ctx, |
| 106 | const ListUsersRequest& request, |
| 107 | pw::rpc::ServerWriter<ListUsersResponse>& writer); |
| 108 | |
| 109 | The ``ServerWriter`` object is movable, and remains active until it is manually |
| 110 | closed or goes out of scope. The writer has a simple API to return responses: |
| 111 | |
| 112 | .. cpp:function:: Status ServerWriter::Write(const T& response) |
| 113 | |
| 114 | Writes a single response message to the stream. The returned status indicates |
| 115 | whether the write was successful. |
| 116 | |
Wyatt Hepler | 1b3da3a | 2021-01-07 13:26:57 -0800 | [diff] [blame] | 117 | .. cpp:function:: void ServerWriter::Finish(Status status = OkStatus()) |
Alexei Frolov | 3ab26ff | 2020-07-21 10:44:58 -0700 | [diff] [blame] | 118 | |
| 119 | Closes the stream and sends back the RPC's overall status to the client. |
| 120 | |
| 121 | Once a ``ServerWriter`` has been closed, all future ``Write`` calls will fail. |
| 122 | |
| 123 | .. attention:: |
| 124 | |
| 125 | Make sure to use ``std::move`` when passing the ``ServerWriter`` around to |
| 126 | avoid accidentally closing it and ending the RPC. |
| 127 | |
| 128 | Client streaming RPC |
| 129 | ^^^^^^^^^^^^^^^^^^^^ |
| 130 | .. attention:: |
| 131 | |
| 132 | ``pw_rpc`` does not yet support client streaming RPCs. |
| 133 | |
| 134 | Bidirectional streaming RPC |
| 135 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ |
| 136 | .. attention:: |
| 137 | |
| 138 | ``pw_rpc`` does not yet support bidirectional streaming RPCs. |
Alexei Frolov | 4d2adde | 2020-08-04 10:19:24 -0700 | [diff] [blame] | 139 | |
| 140 | Client-side |
| 141 | ----------- |
Alexei Frolov | 5a3a61c | 2020-10-01 18:51:41 -0700 | [diff] [blame] | 142 | A corresponding client class is generated for every service defined in the proto |
| 143 | file. Like the service class, it is placed under the ``generated`` namespace. |
| 144 | The class is named after the service, with a ``Client`` suffix. For example, the |
| 145 | ``ChatService`` would create a ``generated::ChatServiceClient``. |
Alexei Frolov | 4d2adde | 2020-08-04 10:19:24 -0700 | [diff] [blame] | 146 | |
Alexei Frolov | 5a3a61c | 2020-10-01 18:51:41 -0700 | [diff] [blame] | 147 | The client class contains static methods to call each of the service's methods. |
Alexei Frolov | bebba90 | 2021-06-09 17:03:52 -0700 | [diff] [blame] | 148 | It is not meant to be instantiated. |
Alexei Frolov | 4d2adde | 2020-08-04 10:19:24 -0700 | [diff] [blame] | 149 | |
Alexei Frolov | 5a3a61c | 2020-10-01 18:51:41 -0700 | [diff] [blame] | 150 | .. code-block:: c++ |
| 151 | |
Alexei Frolov | bebba90 | 2021-06-09 17:03:52 -0700 | [diff] [blame] | 152 | static GetRoomInformationCall GetRoomInformation( |
| 153 | Channel& channel, |
| 154 | const RoomInfoRequest& request, |
| 155 | ::pw::Function<void(Status, const RoomInfoResponse&)> on_response, |
| 156 | ::pw::Function<void(Status)> on_rpc_error = nullptr); |
Alexei Frolov | 5a3a61c | 2020-10-01 18:51:41 -0700 | [diff] [blame] | 157 | |
| 158 | The ``NanopbClientCall`` object returned by the RPC invocation stores the active |
| 159 | RPC's context. For more information on ``ClientCall`` objects, refer to the |
Alexei Frolov | bebba90 | 2021-06-09 17:03:52 -0700 | [diff] [blame] | 160 | :ref:`core RPC documentation <module-pw_rpc-making-calls>`. The type of the |
| 161 | returned object is complex, so it is aliased using the method name. |
Alexei Frolov | 5a3a61c | 2020-10-01 18:51:41 -0700 | [diff] [blame] | 162 | |
Alexei Frolov | bebba90 | 2021-06-09 17:03:52 -0700 | [diff] [blame] | 163 | .. admonition:: Callback invocation |
Alexei Frolov | 5a3a61c | 2020-10-01 18:51:41 -0700 | [diff] [blame] | 164 | |
Alexei Frolov | bebba90 | 2021-06-09 17:03:52 -0700 | [diff] [blame] | 165 | RPC callbacks are invoked synchronously from ``Client::ProcessPacket``. |
Alexei Frolov | 5a3a61c | 2020-10-01 18:51:41 -0700 | [diff] [blame] | 166 | |
Alexei Frolov | bebba90 | 2021-06-09 17:03:52 -0700 | [diff] [blame] | 167 | Method APIs |
| 168 | ^^^^^^^^^^^ |
| 169 | The first argument to each client call method is the channel through which to |
| 170 | send the RPC. Following that, the arguments depend on the method type. |
| 171 | |
| 172 | Unary RPC |
| 173 | ~~~~~~~~~ |
| 174 | A unary RPC call takes the request struct and a callback to invoke when a |
| 175 | response is received. The callback receives the RPC's status and response |
| 176 | struct. |
| 177 | |
| 178 | An optional second callback can be provided to handle internal errors. |
Alexei Frolov | 5a3a61c | 2020-10-01 18:51:41 -0700 | [diff] [blame] | 179 | |
| 180 | .. code-block:: c++ |
| 181 | |
Alexei Frolov | bebba90 | 2021-06-09 17:03:52 -0700 | [diff] [blame] | 182 | static GetRoomInformationCall GetRoomInformation( |
| 183 | Channel& channel, |
| 184 | const RoomInfoRequest& request, |
| 185 | ::pw::Function<void(const RoomInfoResponse&, Status)> on_response, |
| 186 | ::pw::Function<void(Status)> on_rpc_error = nullptr); |
Alexei Frolov | 5a3a61c | 2020-10-01 18:51:41 -0700 | [diff] [blame] | 187 | |
Alexei Frolov | bebba90 | 2021-06-09 17:03:52 -0700 | [diff] [blame] | 188 | Server streaming RPC |
| 189 | ~~~~~~~~~~~~~~~~~~~~ |
| 190 | A server streaming RPC call takes the initial request struct and two callbacks. |
| 191 | The first is invoked on every stream response received, and the second is |
| 192 | invoked once the stream is complete with its overall status. |
Alexei Frolov | 5a3a61c | 2020-10-01 18:51:41 -0700 | [diff] [blame] | 193 | |
Alexei Frolov | bebba90 | 2021-06-09 17:03:52 -0700 | [diff] [blame] | 194 | An optional third callback can be provided to handle internal errors. |
Alexei Frolov | 5a3a61c | 2020-10-01 18:51:41 -0700 | [diff] [blame] | 195 | |
| 196 | .. code-block:: c++ |
| 197 | |
Alexei Frolov | bebba90 | 2021-06-09 17:03:52 -0700 | [diff] [blame] | 198 | static ListUsersInRoomCall ListUsersInRoom( |
| 199 | Channel& channel, |
| 200 | const ListUsersRequest& request, |
| 201 | ::pw::Function<void(const ListUsersResponse&)> on_response, |
| 202 | ::pw::Function<void(Status)> on_stream_end, |
| 203 | ::pw::Function<void(Status)> on_rpc_error = nullptr); |
Alexei Frolov | 5a3a61c | 2020-10-01 18:51:41 -0700 | [diff] [blame] | 204 | |
| 205 | Example usage |
Alexei Frolov | bebba90 | 2021-06-09 17:03:52 -0700 | [diff] [blame] | 206 | ^^^^^^^^^^^^^ |
Alexei Frolov | 5a3a61c | 2020-10-01 18:51:41 -0700 | [diff] [blame] | 207 | The following example demonstrates how to call an RPC method using a nanopb |
| 208 | service client and receive the response. |
| 209 | |
| 210 | .. code-block:: c++ |
| 211 | |
| 212 | #include "chat_protos/chat_service.rpc.pb.h" |
| 213 | |
| 214 | namespace { |
| 215 | MyChannelOutput output; |
| 216 | pw::rpc::Channel channels[] = {pw::rpc::Channel::Create<0>(&output)}; |
| 217 | pw::rpc::Client client(channels); |
Alexei Frolov | bebba90 | 2021-06-09 17:03:52 -0700 | [diff] [blame] | 218 | |
| 219 | // Callback function for GetRoomInformation. |
| 220 | void LogRoomInformation(const RoomInfoResponse& response, Status status); |
Alexei Frolov | 5a3a61c | 2020-10-01 18:51:41 -0700 | [diff] [blame] | 221 | } |
| 222 | |
| 223 | void InvokeSomeRpcs() { |
Alexei Frolov | 5a3a61c | 2020-10-01 18:51:41 -0700 | [diff] [blame] | 224 | // The RPC will remain active as long as `call` is alive. |
| 225 | auto call = ChatServiceClient::GetRoomInformation(channels[0], |
| 226 | {.room = "pigweed"}, |
Alexei Frolov | bebba90 | 2021-06-09 17:03:52 -0700 | [diff] [blame] | 227 | LogRoomInformation); |
Alexei Frolov | 5a3a61c | 2020-10-01 18:51:41 -0700 | [diff] [blame] | 228 | |
| 229 | // For simplicity, block here. An actual implementation would likely |
| 230 | // std::move the call somewhere to keep it active while doing other work. |
| 231 | while (call.active()) { |
| 232 | Wait(); |
| 233 | } |
| 234 | |
Alexei Frolov | bebba90 | 2021-06-09 17:03:52 -0700 | [diff] [blame] | 235 | // Do other stuff now that we have the room information. |
Alexei Frolov | 5a3a61c | 2020-10-01 18:51:41 -0700 | [diff] [blame] | 236 | } |