1.. _module-pw_rpc_pw_protobuf: 2 3----------- 4pw_protobuf 5----------- 6.. caution:: 7 8 If you're starting a new project, Pigweed recommends Nanopb over 9 ``pw_protobuf``. See :ref:`module-pw_rpc-guides-headers`. 10 11``pw_rpc`` can generate services which encode/decode RPC requests and responses 12as ``pw_protobuf`` message structs. 13 14Usage 15===== 16Define a ``pw_proto_library`` containing the .proto file defining your service 17(and optionally other related protos), then depend on the ``pwpb_rpc`` 18version of that library in the code implementing the service. 19 20.. code-block:: 21 22 # chat/BUILD.gn 23 24 import("$dir_pw_build/target_types.gni") 25 import("$dir_pw_protobuf_compiler/proto.gni") 26 27 pw_proto_library("chat_protos") { 28 sources = [ "chat_protos/chat_service.proto" ] 29 } 30 31 # Library that implements the Chat service. 32 pw_source_set("chat_service") { 33 sources = [ 34 "chat_service.cc", 35 "chat_service.h", 36 ] 37 public_deps = [ ":chat_protos.pwpb_rpc" ] 38 } 39 40A C++ header file is generated for each input .proto file, with the ``.proto`` 41extension replaced by ``.rpc.pwpb.h``. For example, given the input file 42``chat_protos/chat_service.proto``, the generated header file will be placed 43at the include path ``"chat_protos/chat_service.rpc.pwpb.h"``. 44 45Generated code API 46================== 47All examples in this document use the following RPC service definition. 48 49.. code-block:: protobuf 50 51 // chat/chat_protos/chat_service.proto 52 53 syntax = "proto3"; 54 55 service Chat { 56 // Returns information about a chatroom. 57 rpc GetRoomInformation(RoomInfoRequest) returns (RoomInfoResponse) {} 58 59 // Lists all of the users in a chatroom. The response is streamed as there 60 // may be a large amount of users. 61 rpc ListUsersInRoom(ListUsersRequest) returns (stream ListUsersResponse) {} 62 63 // Uploads a file, in chunks, to a chatroom. 64 rpc UploadFile(stream UploadFileRequest) returns (UploadFileResponse) {} 65 66 // Sends messages to a chatroom while receiving messages from other users. 67 rpc Chat(stream ChatMessage) returns (stream ChatMessage) {} 68 } 69 70Server-side 71----------- 72A C++ class is generated for each service in the .proto file. The class is 73located within a special ``pw_rpc::pwpb`` sub-namespace of the file's package. 74 75The generated class is a base class which must be derived to implement the 76service's methods. The base class is templated on the derived class. 77 78.. code-block:: c++ 79 80 #include "chat_protos/chat_service.rpc.pwpb.h" 81 82 class ChatService final : public pw_rpc::pwpb::Chat::Service<ChatService> { 83 public: 84 // Implementations of the service's RPC methods; see below. 85 }; 86 87Unary RPC 88^^^^^^^^^ 89A unary RPC is implemented as a function which takes in the RPC's request struct 90and populates a response struct to send back, with a status indicating whether 91the request succeeded. 92 93.. code-block:: c++ 94 95 pw::Status GetRoomInformation(const RoomInfoRequest::Message& request, 96 RoomInfoResponse::Message& 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(const ListUsersRequest::Message& request, 106 pw::rpc::ServerWriter<ListUsersResponse::Message>& writer); 107 108The ``ServerWriter`` object is movable, and remains active until it is manually 109closed or goes out of scope. The writer has a simple API to return responses: 110 111.. cpp:function:: Status PwpbServerWriter::Write(const T::Message& response) 112 113 Writes a single response message to the stream. The returned status indicates 114 whether the write was successful. 115 116.. cpp:function:: void PwpbServerWriter::Finish(Status status = OkStatus()) 117 118 Closes the stream and sends back the RPC's overall status to the client. 119 120.. cpp:function:: Status PwpbServerWriter::TryFinish(Status status = OkStatus()) 121 122 Closes the stream and sends back the RPC's overall status to the client only 123 if the final packet is successfully sent. 124 125Once a ``ServerWriter`` has been closed, all future ``Write`` calls will fail. 126 127.. attention:: 128 129 Make sure to use ``std::move`` when passing the ``ServerWriter`` around to 130 avoid accidentally closing it and ending the RPC. 131 132Client streaming RPC 133^^^^^^^^^^^^^^^^^^^^ 134.. attention:: Supported, but the documentation is still under construction. 135 136Bidirectional streaming RPC 137^^^^^^^^^^^^^^^^^^^^^^^^^^^ 138.. attention:: Supported, but the documentation is still under construction. 139 140 141.. _module-pw_rpc_pw_protobuf-client: 142 143Client-side 144----------- 145A corresponding client class is generated for every service defined in the proto 146file. To allow multiple types of clients to exist, it is placed under the 147``pw_rpc::pwpb`` namespace. The ``Client`` class is nested under 148``pw_rpc::pwpb::ServiceName``. For example, the ``Chat`` service would create 149``pw_rpc::pwpb::Chat::Client``. 150 151Service clients are instantiated with a reference to the RPC client through 152which they will send requests, and the channel ID they will use. 153 154.. code-block:: c++ 155 156 // Nested under pw_rpc::pwpb::ServiceName. 157 class Client { 158 public: 159 Client(::pw::rpc::Client& client, uint32_t channel_id); 160 161 pw::rpc::PwpbUnaryReceiver<RoomInfoResponse::Message> GetRoomInformation( 162 const RoomInfoRequest::Message& request, 163 ::pw::Function<void(Status, const RoomInfoResponse::Message&)> on_response, 164 ::pw::Function<void(Status)> on_rpc_error = nullptr); 165 166 // ...and more (see below). 167 }; 168 169RPCs can also be invoked individually as free functions: 170 171.. code-block:: c++ 172 173 pw::rpc::PwpbUnaryReceiver<RoomInfoResponse::Message> call = pw_rpc::pwpb::Chat::GetRoomInformation( 174 client, channel_id, request, on_response, on_rpc_error); 175 176The client class has member functions for each method defined within the 177service's protobuf descriptor. The arguments to these methods vary depending on 178the type of RPC. Each method returns a client call object which stores the 179context of the ongoing RPC call. For more information on call objects, refer to 180the :ref:`core RPC docs <module-pw_rpc-making-calls>`. 181 182If dynamic allocation is enabled (:c:macro:`PW_RPC_DYNAMIC_ALLOCATION` is 1), a 183``DynamicClient`` is generated, which dynamically allocates the call object with 184:c:macro:`PW_RPC_MAKE_UNIQUE_PTR`. For example: 185 186.. code-block:: c++ 187 188 my_namespace::pw_rpc::pwpb::ServiceName::DynamicClient dynamic_client( 189 client, channel_id); 190 auto call = dynamic_client.TestUnaryRpc(request, response_callback); 191 192 if (call->active()) { // Access the call as a std::unique_ptr 193 // ... 194 195.. admonition:: Callback invocation 196 197 RPC callbacks are invoked synchronously from ``Client::ProcessPacket``. 198 199Method APIs 200^^^^^^^^^^^ 201The arguments provided when invoking a method depend on its type. 202 203Unary RPC 204~~~~~~~~~ 205A unary RPC call takes the request struct and a callback to invoke when a 206response is received. The callback receives the RPC's status and response 207struct. 208 209An optional second callback can be provided to handle internal errors. 210 211.. code-block:: c++ 212 213 pw::rpc::PwpbUnaryReceiver<RoomInfoResponse::Message> GetRoomInformation( 214 const RoomInfoRequest::Message& request, 215 ::pw::Function<void(const RoomInfoResponse::Message&, Status)> on_response, 216 ::pw::Function<void(Status)> on_rpc_error = nullptr); 217 218Server streaming RPC 219~~~~~~~~~~~~~~~~~~~~ 220A server streaming RPC call takes the initial request struct and two callbacks. 221The first is invoked on every stream response received, and the second is 222invoked once the stream is complete with its overall status. 223 224An optional third callback can be provided to handle internal errors. 225 226.. code-block:: c++ 227 228 pw::rpc::PwpbClientReader<ListUsersResponse::Message> ListUsersInRoom( 229 const ListUsersRequest::Message& request, 230 ::pw::Function<void(const ListUsersResponse::Message&)> on_response, 231 ::pw::Function<void(Status)> on_stream_end, 232 ::pw::Function<void(Status)> on_rpc_error = nullptr); 233 234Client streaming RPC 235~~~~~~~~~~~~~~~~~~~~ 236.. attention:: Supported, but the documentation is still under construction. 237 238Bidirectional streaming RPC 239~~~~~~~~~~~~~~~~~~~~~~~~~~~ 240.. attention:: Supported, but the documentation is still under construction. 241 242Example usage 243^^^^^^^^^^^^^ 244The following example demonstrates how to call an RPC method using a pw_protobuf 245service client and receive the response. 246 247.. code-block:: c++ 248 249 #include "chat_protos/chat_service.rpc.pwpb.h" 250 251 namespace { 252 253 using ChatClient = pw_rpc::pwpb::Chat::Client; 254 255 MyChannelOutput output; 256 pw::rpc::Channel channels[] = {pw::rpc::Channel::Create<1>(&output)}; 257 pw::rpc::Client client(channels); 258 259 // Callback function for GetRoomInformation. 260 void LogRoomInformation(const RoomInfoResponse::Message& response, 261 Status status); 262 263 } // namespace 264 265 void InvokeSomeRpcs() { 266 // Instantiate a service client to call Chat service methods on channel 1. 267 ChatClient chat_client(client, 1); 268 269 // The RPC will remain active as long as `call` is alive. 270 auto call = chat_client.GetRoomInformation( 271 {.room = "pigweed"}, LogRoomInformation); 272 if (!call.active()) { 273 // The invocation may fail. This could occur due to an invalid channel ID, 274 // for example. The failure status is forwarded to the to call's 275 // on_rpc_error callback. 276 return; 277 } 278 279 // For simplicity, block until the call completes. An actual implementation 280 // would likely std::move the call somewhere to keep it active while doing 281 // other work. 282 while (call.active()) { 283 Wait(); 284 } 285 286 // Do other stuff now that we have the room information. 287 } 288