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:: 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:: 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:: 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:: 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:: 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 121Once 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 128Client streaming RPC 129^^^^^^^^^^^^^^^^^^^^ 130.. attention:: Supported, but the documentation is still under construction. 131 132Bidirectional streaming RPC 133^^^^^^^^^^^^^^^^^^^^^^^^^^^ 134.. attention:: Supported, but the documentation is still under construction. 135 136Client-side 137----------- 138A corresponding client class is generated for every service defined in the proto 139file. To allow multiple types of clients to exist, it is placed under the 140``pw_rpc::nanopb`` namespace. The ``Client`` class is nested under 141``pw_rpc::nanopb::ServiceName``. For example, the ``Chat`` service would create 142``pw_rpc::nanopb::Chat::Client``. 143 144Service clients are instantiated with a reference to the RPC client through 145which they will send requests, and the channel ID they will use. 146 147.. code-block:: c++ 148 149 // Nested under pw_rpc::nanopb::ServiceName. 150 class Client { 151 public: 152 Client(::pw::rpc::Client& client, uint32_t channel_id); 153 154 pw::rpc::NanopbUnaryReceiver<RoomInfoResponse> GetRoomInformation( 155 const RoomInfoRequest& request, 156 ::pw::Function<void(Status, const RoomInfoResponse&)> on_response, 157 ::pw::Function<void(Status)> on_rpc_error = nullptr); 158 159 // ...and more (see below). 160 }; 161 162RPCs can also be invoked individually as free functions: 163 164.. code-block:: c++ 165 166 pw::rpc::NanopbUnaryReceiver<RoomInfoResponse> call = pw_rpc::nanopb::Chat::GetRoomInformation( 167 client, channel_id, request, on_response, on_rpc_error); 168 169The client class has member functions for each method defined within the 170service's protobuf descriptor. The arguments to these methods vary depending on 171the type of RPC. Each method returns a client call object which stores the 172context of the ongoing RPC call. For more information on call objects, refer to 173the :ref:`core RPC docs <module-pw_rpc-making-calls>`. 174 175.. admonition:: Callback invocation 176 177 RPC callbacks are invoked synchronously from ``Client::ProcessPacket``. 178 179Method APIs 180^^^^^^^^^^^ 181The arguments provided when invoking a method depend on its type. 182 183Unary RPC 184~~~~~~~~~ 185A unary RPC call takes the request struct and a callback to invoke when a 186response is received. The callback receives the RPC's status and response 187struct. 188 189An optional second callback can be provided to handle internal errors. 190 191.. code-block:: c++ 192 193 pw::rpc::NanopbUnaryReceiver<RoomInfoResponse> GetRoomInformation( 194 const RoomInfoRequest& request, 195 ::pw::Function<void(const RoomInfoResponse&, Status)> on_response, 196 ::pw::Function<void(Status)> on_rpc_error = nullptr); 197 198Server streaming RPC 199~~~~~~~~~~~~~~~~~~~~ 200A server streaming RPC call takes the initial request struct and two callbacks. 201The first is invoked on every stream response received, and the second is 202invoked once the stream is complete with its overall status. 203 204An optional third callback can be provided to handle internal errors. 205 206.. code-block:: c++ 207 208 pw::rpc::NanopbClientReader<ListUsersResponse> ListUsersInRoom( 209 const ListUsersRequest& request, 210 ::pw::Function<void(const ListUsersResponse&)> on_response, 211 ::pw::Function<void(Status)> on_stream_end, 212 ::pw::Function<void(Status)> on_rpc_error = nullptr); 213 214Client streaming RPC 215~~~~~~~~~~~~~~~~~~~~ 216.. attention:: Supported, but the documentation is still under construction. 217 218Bidirectional streaming RPC 219~~~~~~~~~~~~~~~~~~~~~~~~~~~ 220.. attention:: Supported, but the documentation is still under construction. 221 222Example usage 223^^^^^^^^^^^^^ 224The following example demonstrates how to call an RPC method using a nanopb 225service client and receive the response. 226 227.. code-block:: c++ 228 229 #include "chat_protos/chat_service.rpc.pb.h" 230 231 namespace { 232 233 using ChatClient = pw_rpc::nanopb::Chat::Client; 234 235 MyChannelOutput output; 236 pw::rpc::Channel channels[] = {pw::rpc::Channel::Create<1>(&output)}; 237 pw::rpc::Client client(channels); 238 239 // Callback function for GetRoomInformation. 240 void LogRoomInformation(const RoomInfoResponse& response, Status status); 241 242 } // namespace 243 244 void InvokeSomeRpcs() { 245 // Instantiate a service client to call Chat service methods on channel 1. 246 ChatClient chat_client(client, 1); 247 248 // The RPC will remain active as long as `call` is alive. 249 auto call = chat_client.GetRoomInformation( 250 {.room = "pigweed"}, LogRoomInformation); 251 if (!call.active()) { 252 // The invocation may fail. This could occur due to an invalid channel ID, 253 // for example. The failure status is forwarded to the to call's 254 // on_rpc_error callback. 255 return; 256 } 257 258 // For simplicity, block until the call completes. An actual implementation 259 // would likely std::move the call somewhere to keep it active while doing 260 // other work. 261 while (call.active()) { 262 Wait(); 263 } 264 265 // Do other stuff now that we have the room information. 266 } 267 268Zephyr 269====== 270To enable ``pw_rpc.nanopb.*`` for Zephyr add ``CONFIG_PIGWEED_RPC_NANOPB=y`` to 271the project's configuration. This will enable the Kconfig menu for the 272following: 273 274* ``pw_rpc.nanopb.method`` which can be enabled via 275 ``CONFIG_PIGWEED_RPC_NANOPB_METHOD=y``. 276* ``pw_rpc.nanopb.method_union`` which can be enabled via 277 ``CONFIG_PIGWEED_RPC_NANOPB_METHOD_UNION=y``. 278* ``pw_rpc.nanopb.client`` which can be enabled via 279 ``CONFIG_PIGWEED_RPC_NANOPB_CLIENT=y``. 280* ``pw_rpc.nanopb.common`` which can be enabled via 281 ``CONFIG_PIGWEED_RPC_NANOPB_COMMON=y``. 282* ``pw_rpc.nanopb.echo_service`` which can be enabled via 283 ``CONFIG_PIGWEED_RPC_NANOPB_ECHO_SERVICE=y``. 284