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