1.. _module-pw_rpc_nanopb: 2 3------ 4nanopb 5------ 6``pw_rpc`` can generate services which encode/decode RPC requests and responses 7as nanopb message structs. 8 9Usage 10===== 11To enable nanopb code generation, the build argument 12``dir_pw_third_party_nanopb`` must be set to point to a local nanopb 13installation. Nanopb 0.4 is recommended, but Nanopb 0.3 is also supported. 14 15Define a ``pw_proto_library`` containing the .proto file defining your service 16(and optionally other related protos), then depend on the ``nanopb_rpc`` 17version of that library in the code implementing the service. 18 19.. code-block:: 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 Chat service. 31 pw_source_set("chat_service") { 32 sources = [ 33 "chat_service.cc", 34 "chat_service.h", 35 ] 36 public_deps = [ ":chat_protos.nanopb_rpc" ] 37 } 38 39A C++ header file is generated for each input .proto file, with the ``.proto`` 40extension replaced by ``.rpc.pb.h``. For example, given the input file 41``chat_protos/chat_service.proto``, the generated header file will be placed 42at the include path ``"chat_protos/chat_service.rpc.pb.h"``. 43 44Generated code API 45================== 46All examples in this document use the following RPC service definition. 47 48.. code-block:: protobuf 49 50 // chat/chat_protos/chat_service.proto 51 52 syntax = "proto3"; 53 54 service Chat { 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 69Server-side 70----------- 71A C++ class is generated for each service in the .proto file. The class is 72located within a special ``pw_rpc::nanopb`` sub-namespace of the file's package. 73 74The generated class is a base class which must be derived to implement the 75service's methods. The base class is templated on the derived class. 76 77.. code-block:: c++ 78 79 #include "chat_protos/chat_service.rpc.pb.h" 80 81 class ChatService final : public pw_rpc::nanopb::Chat::Service<ChatService> { 82 public: 83 // Implementations of the service's RPC methods; see below. 84 }; 85 86Unary RPC 87^^^^^^^^^ 88A unary RPC is implemented as a function which takes in the RPC's request struct 89and populates a response struct to send back, with a status indicating whether 90the request succeeded. 91 92.. code-block:: c++ 93 94 pw::Status GetRoomInformation(pw::rpc:: 95 const RoomInfoRequest& request, 96 RoomInfoResponse& response); 97 98Server streaming RPC 99^^^^^^^^^^^^^^^^^^^^ 100A server streaming RPC receives the client's request message alongside a 101``ServerWriter``, used to stream back responses. 102 103.. code-block:: c++ 104 105 void ListUsersInRoom(pw::rpc:: 106 const ListUsersRequest& request, 107 pw::rpc::ServerWriter<ListUsersResponse>& writer); 108 109The ``ServerWriter`` object is movable, and remains active until it is manually 110closed 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 117.. cpp:function:: void ServerWriter::Finish(Status status = OkStatus()) 118 119 Closes the stream and sends back the RPC's overall status to the client. 120 121.. cpp:function:: Status ServerWriter::TryFinish(Status status = OkStatus()) 122 123 Closes the stream and sends back the RPC's overall status to the client only 124 if the final packet is successfully sent. 125 126Once a ``ServerWriter`` has been closed, all future ``Write`` calls will fail. 127 128.. attention:: 129 130 Make sure to use ``std::move`` when passing the ``ServerWriter`` around to 131 avoid accidentally closing it and ending the RPC. 132 133Client streaming RPC 134^^^^^^^^^^^^^^^^^^^^ 135.. attention:: Supported, but the documentation is still under construction. 136 137Bidirectional streaming RPC 138^^^^^^^^^^^^^^^^^^^^^^^^^^^ 139.. attention:: Supported, but the documentation is still under construction. 140 141Client-side 142----------- 143A corresponding client class is generated for every service defined in the proto 144file. To allow multiple types of clients to exist, it is placed under the 145``pw_rpc::nanopb`` namespace. The ``Client`` class is nested under 146``pw_rpc::nanopb::ServiceName``. For example, the ``Chat`` service would create 147``pw_rpc::nanopb::Chat::Client``. 148 149Service clients are instantiated with a reference to the RPC client through 150which they will send requests, and the channel ID they will use. 151 152.. code-block:: c++ 153 154 // Nested under pw_rpc::nanopb::ServiceName. 155 class Client { 156 public: 157 Client(::pw::rpc::Client& client, uint32_t channel_id); 158 159 pw::rpc::NanopbUnaryReceiver<RoomInfoResponse> GetRoomInformation( 160 const RoomInfoRequest& request, 161 ::pw::Function<void(Status, const RoomInfoResponse&)> on_response, 162 ::pw::Function<void(Status)> on_rpc_error = nullptr); 163 164 // ...and more (see below). 165 }; 166 167RPCs can also be invoked individually as free functions: 168 169.. code-block:: c++ 170 171 pw::rpc::NanopbUnaryReceiver<RoomInfoResponse> call = pw_rpc::nanopb::Chat::GetRoomInformation( 172 client, channel_id, request, on_response, on_rpc_error); 173 174The client class has member functions for each method defined within the 175service's protobuf descriptor. The arguments to these methods vary depending on 176the type of RPC. Each method returns a client call object which stores the 177context of the ongoing RPC call. For more information on call objects, refer to 178the :ref:`core RPC docs <module-pw_rpc-making-calls>`. 179 180.. admonition:: Callback invocation 181 182 RPC callbacks are invoked synchronously from ``Client::ProcessPacket``. 183 184Method APIs 185^^^^^^^^^^^ 186The arguments provided when invoking a method depend on its type. 187 188Unary RPC 189~~~~~~~~~ 190A unary RPC call takes the request struct and a callback to invoke when a 191response is received. The callback receives the RPC's status and response 192struct. 193 194An optional second callback can be provided to handle internal errors. 195 196.. code-block:: c++ 197 198 pw::rpc::NanopbUnaryReceiver<RoomInfoResponse> GetRoomInformation( 199 const RoomInfoRequest& request, 200 ::pw::Function<void(const RoomInfoResponse&, Status)> on_response, 201 ::pw::Function<void(Status)> on_rpc_error = nullptr); 202 203Server streaming RPC 204~~~~~~~~~~~~~~~~~~~~ 205A server streaming RPC call takes the initial request struct and two callbacks. 206The first is invoked on every stream response received, and the second is 207invoked once the stream is complete with its overall status. 208 209An optional third callback can be provided to handle internal errors. 210 211.. code-block:: c++ 212 213 pw::rpc::NanopbClientReader<ListUsersResponse> ListUsersInRoom( 214 const ListUsersRequest& request, 215 ::pw::Function<void(const ListUsersResponse&)> on_response, 216 ::pw::Function<void(Status)> on_stream_end, 217 ::pw::Function<void(Status)> on_rpc_error = nullptr); 218 219Client streaming RPC 220~~~~~~~~~~~~~~~~~~~~ 221.. attention:: Supported, but the documentation is still under construction. 222 223Bidirectional streaming RPC 224~~~~~~~~~~~~~~~~~~~~~~~~~~~ 225.. attention:: Supported, but the documentation is still under construction. 226 227Example usage 228^^^^^^^^^^^^^ 229The following example demonstrates how to call an RPC method using a nanopb 230service client and receive the response. 231 232.. code-block:: c++ 233 234 #include "chat_protos/chat_service.rpc.pb.h" 235 236 namespace { 237 238 using ChatClient = pw_rpc::nanopb::Chat::Client; 239 240 MyChannelOutput output; 241 pw::rpc::Channel channels[] = {pw::rpc::Channel::Create<1>(&output)}; 242 pw::rpc::Client client(channels); 243 244 // Callback function for GetRoomInformation. 245 void LogRoomInformation(const RoomInfoResponse& response, Status status); 246 247 } // namespace 248 249 void InvokeSomeRpcs() { 250 // Instantiate a service client to call Chat service methods on channel 1. 251 ChatClient chat_client(client, 1); 252 253 // The RPC will remain active as long as `call` is alive. 254 auto call = chat_client.GetRoomInformation( 255 {.room = "pigweed"}, LogRoomInformation); 256 if (!call.active()) { 257 // The invocation may fail. This could occur due to an invalid channel ID, 258 // for example. The failure status is forwarded to the to call's 259 // on_rpc_error callback. 260 return; 261 } 262 263 // For simplicity, block until the call completes. An actual implementation 264 // would likely std::move the call somewhere to keep it active while doing 265 // other work. 266 while (call.active()) { 267 Wait(); 268 } 269 270 // Do other stuff now that we have the room information. 271 } 272 273Zephyr 274====== 275To enable ``pw_rpc.nanopb.*`` for Zephyr add ``CONFIG_PIGWEED_RPC_NANOPB=y`` to 276the project's configuration. This will enable the Kconfig menu for the 277following: 278 279* ``pw_rpc.nanopb.method`` which can be enabled via 280 ``CONFIG_PIGWEED_RPC_NANOPB_METHOD=y``. 281* ``pw_rpc.nanopb.method_union`` which can be enabled via 282 ``CONFIG_PIGWEED_RPC_NANOPB_METHOD_UNION=y``. 283* ``pw_rpc.nanopb.client`` which can be enabled via 284 ``CONFIG_PIGWEED_RPC_NANOPB_CLIENT=y``. 285* ``pw_rpc.nanopb.common`` which can be enabled via 286 ``CONFIG_PIGWEED_RPC_NANOPB_COMMON=y``. 287* ``pw_rpc.nanopb.echo_service`` which can be enabled via 288 ``CONFIG_PIGWEED_RPC_NANOPB_ECHO_SERVICE=y``. 289