| .. _module-pw_rpc_nanopb: |
| |
| ------ |
| nanopb |
| ------ |
| ``pw_rpc`` can generate services which encode/decode RPC requests and responses |
| as nanopb message structs. |
| |
| Usage |
| ===== |
| To enable nanopb code generation, the build argument |
| ``dir_pw_third_party_nanopb`` must be set to point to a local nanopb |
| installation. Nanopb 0.4 is recommended, but Nanopb 0.3 is also supported. |
| |
| Define a ``pw_proto_library`` containing the .proto file defining your service |
| (and optionally other related protos), then depend on the ``nanopb_rpc`` |
| version of that library in the code implementing the service. |
| |
| .. code:: |
| |
| # chat/BUILD.gn |
| |
| import("$dir_pw_build/target_types.gni") |
| import("$dir_pw_protobuf_compiler/proto.gni") |
| |
| pw_proto_library("chat_protos") { |
| sources = [ "chat_protos/chat_service.proto" ] |
| } |
| |
| # Library that implements the Chat service. |
| pw_source_set("chat_service") { |
| sources = [ |
| "chat_service.cc", |
| "chat_service.h", |
| ] |
| public_deps = [ ":chat_protos.nanopb_rpc" ] |
| } |
| |
| A C++ header file is generated for each input .proto file, with the ``.proto`` |
| extension replaced by ``.rpc.pb.h``. For example, given the input file |
| ``chat_protos/chat_service.proto``, the generated header file will be placed |
| at the include path ``"chat_protos/chat_service.rpc.pb.h"``. |
| |
| Generated code API |
| ================== |
| All examples in this document use the following RPC service definition. |
| |
| .. code:: protobuf |
| |
| // chat/chat_protos/chat_service.proto |
| |
| syntax = "proto3"; |
| |
| service Chat { |
| // Returns information about a chatroom. |
| rpc GetRoomInformation(RoomInfoRequest) returns (RoomInfoResponse) {} |
| |
| // Lists all of the users in a chatroom. The response is streamed as there |
| // may be a large amount of users. |
| rpc ListUsersInRoom(ListUsersRequest) returns (stream ListUsersResponse) {} |
| |
| // Uploads a file, in chunks, to a chatroom. |
| rpc UploadFile(stream UploadFileRequest) returns (UploadFileResponse) {} |
| |
| // Sends messages to a chatroom while receiving messages from other users. |
| rpc Chat(stream ChatMessage) returns (stream ChatMessage) {} |
| } |
| |
| Server-side |
| ----------- |
| A C++ class is generated for each service in the .proto file. The class is |
| located within a special ``pw_rpc::nanopb`` sub-namespace of the file's package. |
| |
| The generated class is a base class which must be derived to implement the |
| service's methods. The base class is templated on the derived class. |
| |
| .. code:: c++ |
| |
| #include "chat_protos/chat_service.rpc.pb.h" |
| |
| class ChatService final : public pw_rpc::nanopb::Chat::Service<ChatService> { |
| public: |
| // Implementations of the service's RPC methods; see below. |
| }; |
| |
| Unary RPC |
| ^^^^^^^^^ |
| A unary RPC is implemented as a function which takes in the RPC's request struct |
| and populates a response struct to send back, with a status indicating whether |
| the request succeeded. |
| |
| .. code:: c++ |
| |
| pw::Status GetRoomInformation(pw::rpc:: |
| const RoomInfoRequest& request, |
| RoomInfoResponse& response); |
| |
| Server streaming RPC |
| ^^^^^^^^^^^^^^^^^^^^ |
| A server streaming RPC receives the client's request message alongside a |
| ``ServerWriter``, used to stream back responses. |
| |
| .. code:: c++ |
| |
| void ListUsersInRoom(pw::rpc:: |
| const ListUsersRequest& request, |
| pw::rpc::ServerWriter<ListUsersResponse>& writer); |
| |
| The ``ServerWriter`` object is movable, and remains active until it is manually |
| closed or goes out of scope. The writer has a simple API to return responses: |
| |
| .. cpp:function:: Status ServerWriter::Write(const T& response) |
| |
| Writes a single response message to the stream. The returned status indicates |
| whether the write was successful. |
| |
| .. cpp:function:: void ServerWriter::Finish(Status status = OkStatus()) |
| |
| Closes the stream and sends back the RPC's overall status to the client. |
| |
| Once a ``ServerWriter`` has been closed, all future ``Write`` calls will fail. |
| |
| .. attention:: |
| |
| Make sure to use ``std::move`` when passing the ``ServerWriter`` around to |
| avoid accidentally closing it and ending the RPC. |
| |
| Client streaming RPC |
| ^^^^^^^^^^^^^^^^^^^^ |
| .. attention:: |
| |
| ``pw_rpc`` does not yet support client streaming RPCs. |
| |
| Bidirectional streaming RPC |
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ |
| .. attention:: |
| |
| ``pw_rpc`` does not yet support bidirectional streaming RPCs. |
| |
| Client-side |
| ----------- |
| A corresponding client class is generated for every service defined in the proto |
| file. To allow multiple types of clients to exist, it is placed under the |
| ``pw_rpc::nanopb`` namespace. The ``Client`` class is nested under |
| ``pw_rpc::nanopb::ServiceName``. For example, the ``Chat`` service would create |
| ``pw_rpc::nanopb::Chat::Client``. |
| |
| Service clients are instantiated with a reference to the RPC client through |
| which they will send requests, and the channel ID they will use. |
| |
| .. code-block:: c++ |
| |
| // Nested under pw_rpc::nanopb::ServiceName. |
| class Client { |
| public: |
| Client(::pw::rpc::Client& client, uint32_t channel_id); |
| |
| GetRoomInformationCall GetRoomInformation( |
| const RoomInfoRequest& request, |
| ::pw::Function<void(Status, const RoomInfoResponse&)> on_response, |
| ::pw::Function<void(Status)> on_rpc_error = nullptr); |
| |
| // ...and more (see below). |
| }; |
| |
| RPCs can also be invoked individually as free functions: |
| |
| .. code-block:: c++ |
| |
| GetRoomInformationCall call = pw_rpc::nanopb::Chat::GetRoomInformation( |
| client, channel_id, request, on_response, on_rpc_error); |
| |
| The client class has member functions for each method defined within the |
| service's protobuf descriptor. The arguments to these methods vary depending on |
| the type of RPC. Each method returns a ``NanopbClientCall`` object which stores |
| the context of the ongoing RPC call. For more information on ``ClientCall`` |
| objects, refer to the :ref:`core RPC docs <module-pw_rpc-making-calls>`. The |
| type of the returned object is complex, so it is aliased using the method |
| name. |
| |
| .. admonition:: Callback invocation |
| |
| RPC callbacks are invoked synchronously from ``Client::ProcessPacket``. |
| |
| Method APIs |
| ^^^^^^^^^^^ |
| The arguments provided when invoking a method depend on its type. |
| |
| Unary RPC |
| ~~~~~~~~~ |
| A unary RPC call takes the request struct and a callback to invoke when a |
| response is received. The callback receives the RPC's status and response |
| struct. |
| |
| An optional second callback can be provided to handle internal errors. |
| |
| .. code-block:: c++ |
| |
| GetRoomInformationCall GetRoomInformation( |
| const RoomInfoRequest& request, |
| ::pw::Function<void(const RoomInfoResponse&, Status)> on_response, |
| ::pw::Function<void(Status)> on_rpc_error = nullptr); |
| |
| Server streaming RPC |
| ~~~~~~~~~~~~~~~~~~~~ |
| A server streaming RPC call takes the initial request struct and two callbacks. |
| The first is invoked on every stream response received, and the second is |
| invoked once the stream is complete with its overall status. |
| |
| An optional third callback can be provided to handle internal errors. |
| |
| .. code-block:: c++ |
| |
| ListUsersInRoomCall ListUsersInRoom( |
| const ListUsersRequest& request, |
| ::pw::Function<void(const ListUsersResponse&)> on_response, |
| ::pw::Function<void(Status)> on_stream_end, |
| ::pw::Function<void(Status)> on_rpc_error = nullptr); |
| |
| Example usage |
| ^^^^^^^^^^^^^ |
| The following example demonstrates how to call an RPC method using a nanopb |
| service client and receive the response. |
| |
| .. code-block:: c++ |
| |
| #include "chat_protos/chat_service.rpc.pb.h" |
| |
| namespace { |
| |
| using ChatClient = pw_rpc::nanopb::Chat::Client; |
| |
| MyChannelOutput output; |
| pw::rpc::Channel channels[] = {pw::rpc::Channel::Create<1>(&output)}; |
| pw::rpc::Client client(channels); |
| |
| // Callback function for GetRoomInformation. |
| void LogRoomInformation(const RoomInfoResponse& response, Status status); |
| |
| } // namespace |
| |
| void InvokeSomeRpcs() { |
| // Instantiate a service client to call Chat service methods on channel 1. |
| ChatClient chat_client(client, 1); |
| |
| // The RPC will remain active as long as `call` is alive. |
| auto call = chat_client.GetRoomInformation( |
| {.room = "pigweed"}, LogRoomInformation); |
| if (!call.active()) { |
| // The invocation may fail. This could occur due to an invalid channel ID, |
| // for example. The failure status is forwarded to the to call's |
| // on_rpc_error callback. |
| return; |
| } |
| |
| // For simplicity, block until the call completes. An actual implementation |
| // would likely std::move the call somewhere to keep it active while doing |
| // other work. |
| while (call.active()) { |
| Wait(); |
| } |
| |
| // Do other stuff now that we have the room information. |
| } |
| |
| Zephyr |
| ====== |
| To enable ``pw_rpc.nanopb.*`` for Zephyr add ``CONFIG_PIGWEED_RPC_NANOPB=y`` to |
| the project's configuration. This will enable the Kconfig menu for the |
| following: |
| |
| * ``pw_rpc.nanopb.method`` which can be enabled via |
| ``CONFIG_PIGWEED_RPC_NANOPB_METHOD=y``. |
| * ``pw_rpc.nanopb.method_union`` which can be enabled via |
| ``CONFIG_PIGWEED_RPC_NANOPB_METHOD_UNION=y``. |
| * ``pw_rpc.nanopb.client`` which can be enabled via |
| ``CONFIG_PIGWEED_RPC_NANOPB_CLIENT=y``. |
| * ``pw_rpc.nanopb.common`` which can be enabled via |
| ``CONFIG_PIGWEED_RPC_NANOPB_COMMON=y``. |
| * ``pw_rpc.nanopb.echo_service`` which can be enabled via |
| ``CONFIG_PIGWEED_RPC_NANOPB_ECHO_SERVICE=y``. |