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