1.. _module-pw_rpc-guides: 2 3=================== 4Quickstart & guides 5=================== 6.. pigweed-module-subpage:: 7 :name: pw_rpc 8 9.. _module-pw_rpc-quickstart: 10 11---------- 12Quickstart 13---------- 14This section guides you through the process of adding ``pw_rpc`` to your project. 15 16Check out :ref:`module-pw_rpc-design-overview` for a conceptual explanation 17of the RPC lifecycle. 18 191. RPC service declaration 20========================== 21Declare a :ref:`service <module-pw_rpc-design-services>` in a service 22definition: 23 24.. code-block:: protobuf 25 26 /* //applications/blinky/blinky.proto */ 27 28 syntax = "proto3"; 29 30 package blinky; 31 32 import "pw_protobuf_protos/common.proto"; 33 34 service Blinky { 35 // Toggles the LED on or off. 36 rpc ToggleLed(pw.protobuf.Empty) returns (pw.protobuf.Empty); 37 // Continuously blinks the board LED a specified number of times. 38 rpc Blink(BlinkRequest) returns (pw.protobuf.Empty); 39 } 40 41 message BlinkRequest { 42 // The interval at which to blink the LED, in milliseconds. 43 uint32 interval_ms = 1; 44 // The number of times to blink the LED. 45 optional uint32 blink_count = 2; 46 } // BlinkRequest 47 48Declare the protocol buffer in ``BUILD.bazel``: 49 50.. code-block:: python 51 52 # //applications/blinky/BUILD.bazel 53 54 load( 55 "@pigweed//pw_protobuf_compiler:pw_proto_library.bzl", 56 "nanopb_proto_library", 57 "nanopb_rpc_proto_library", 58 ) 59 60 proto_library( 61 name = "proto", 62 srcs = ["blinky.proto"], 63 deps = [ 64 "@pigweed//pw_protobuf:common_proto", 65 ], 66 ) 67 68 nanopb_proto_library( 69 name = "nanopb", 70 deps = [":proto"], 71 ) 72 73 nanopb_rpc_proto_library( 74 name = "nanopb_rpc", 75 nanopb_proto_library_deps = [":nanopb"], 76 deps = [":proto"], 77 ) 78 79.. _module-pw_rpc-quickstart-codegen: 80 812. RPC code generation 82====================== 83How do you interact with this protobuf from C++? ``pw_rpc`` uses 84codegen libraries to automatically transform ``.proto`` definitions 85into C++ header files. These headers are generated in the build 86output directory. Their exact location varies by build system and toolchain 87but the C++ include path always matches the source declarations in 88``proto_library``. The ``.proto`` extension is replaced with an extension 89corresponding to the protobuf codegen library in use: 90 91.. csv-table:: 92 :header: "Protobuf libraries", "Build subtarget", "Protobuf header", "pw_rpc header" 93 94 "Raw only", "``.raw_rpc``", "(none)", "``.raw_rpc.pb.h``" 95 "Nanopb or raw", "``.nanopb_rpc``", "``.pb.h``", "``.rpc.pb.h``" 96 "pw_protobuf or raw", "``.pwpb_rpc``", "``.pwpb.h``", "``.rpc.pwpb.h``" 97 98Most projects should default to Nanopb. See 99:ref:`module-pw_rpc-guides-headers`. 100 101For example, the generated RPC header for ``applications/blinky.proto`` is 102``applications/blinky.rpc.pb.h`` for Nanopb or 103``applications/blinky.raw_rpc.pb.h`` for raw RPCs. 104 105The generated header defines a base class for each RPC service declared in the 106``.proto`` file. A service named ``TheService`` in package ``foo.bar`` would 107generate the following base class for ``pw_protobuf``: 108 109.. cpp:class:: template <typename Implementation> foo::bar::pw_rpc::pwpb::TheService::Service 110 1113. RPC service definition 112========================= 113Implement a service class by inheriting from the generated RPC service 114base class and defining a method for each RPC. The methods must match the name 115and function signature for one of the supported protobuf implementations. 116Services may mix and match protobuf implementations within one service. 117 118A Nanopb implementation of the ``Blinky`` service looks like this: 119 120.. code-block:: cpp 121 122 /* //applications/blinky/main.cc */ 123 124 // ... 125 #include "applications/blinky/blinky.rpc.pb.h" 126 #include "pw_system/rpc_server.h" 127 // ... 128 129 class BlinkyService final 130 : public blinky::pw_rpc::nanopb::Blinky::Service<BlinkyService> { 131 public: 132 pw::Status ToggleLed(const pw_protobuf_Empty &, pw_protobuf_Empty &) { 133 // Turn the LED off if it's currently on and vice versa 134 } 135 pw::Status Blink(const blinky_BlinkRequest &request, pw_protobuf_Empty &) { 136 if (request.blink_count == 0) { 137 // Stop blinking 138 } 139 if (request.interval_ms > 0) { 140 // Change the blink interval 141 } 142 if (request.has_blink_count) { // Auto-generated property 143 // Blink request.blink_count times 144 } 145 } 146 }; 147 148 BlinkyService blinky_service; 149 150 namespace pw::system { 151 152 void UserAppInit() { 153 // ... 154 pw::system::GetRpcServer().RegisterService(blinky_service); 155 } 156 157 } // namespace pw::system 158 159Declare the implementation in ``BUILD.bazel``: 160 161.. code-block:: python 162 163 # //applications/blinky/BUILD.bazel 164 165 cc_binary( 166 name = "blinky", 167 srcs = ["main.cc"], 168 deps = [ 169 ":nanopb_rpc", 170 # ... 171 "@pigweed//pw_system", 172 ], 173 ) 174 175.. tip:: 176 177 The generated code includes RPC service implementation stubs. You can 178 reference or copy and paste these to get started with implementing a service. 179 These stub classes are generated at the bottom of the pw_rpc proto header. 180 181 To use the stubs, do the following: 182 183 #. Locate the generated RPC header in the build directory. For example: 184 185 .. code-block:: sh 186 187 cd bazel-out && find . -name blinky.rpc.pb.h 188 189 #. Scroll to the bottom of the generated RPC header. 190 #. Copy the stub class declaration to a header file. 191 #. Copy the member function definitions to a source file. 192 #. Rename the class or change the namespace, if desired. 193 #. List these files in a build target with a dependency on 194 ``proto_library``. 195 1964. Register the service with a server 197===================================== 198Set up the server and register the service: 199 200.. code-block:: cpp 201 202 /* //applications/blinky/main.cc */ 203 204 // ... 205 #include "pw_system/rpc_server.h" 206 // ... 207 208 namespace pw::system { 209 210 void UserAppInit() { 211 // ... 212 pw::system::GetRpcServer().RegisterService(blinky_service); 213 } 214 215 } // namespace pw::system 216 217Next steps 218========== 219That's the end of the quickstart! Learn more about ``pw_rpc``: 220 221* Check out :ref:`module-pw_rpc-cpp` for detailed guidance on using the C++ 222 client and server libraries. 223 224* If you have any questions, you can talk to the Pigweed team in the ``#pw_rpc`` 225 channel of our `Discord <https://discord.gg/M9NSeTA>`_. 226 227* The rest of this page provides general guidance on common questions and use cases. 228 229* The quickstart code was based off these real-world examples of the Pigweed 230 team adding ``pw_rpc`` to a project: 231 232 * `applications/blinky: Add Blinky RPC service <https://pwrev.dev/218225>`_ 233 234 * `rpc: Use nanopb instead of pw_protobuf <https://pwrev.dev/218732>`_ 235 236* You can build clients in other languages, such as Python and TypeScript. 237 See :ref:`module-pw_rpc-libraries`. 238 239.. _module-pw_rpc-zephyr: 240 241--------------------------- 242Setting up pw_rpc in Zephyr 243--------------------------- 244To enable ``pw_rpc.*`` for Zephyr add ``CONFIG_PIGWEED_RPC=y`` to the project's 245configuration. This will enable the Kconfig menu for the following: 246 247* ``pw_rpc.server`` which can be enabled via ``CONFIG_PIGWEED_RPC_SERVER=y``. 248* ``pw_rpc.client`` which can be enabled via ``CONFIG_PIGWEED_RPC_CLIENT=y``. 249* ``pw_rpc.client_server`` which can be enabled via 250 ``CONFIG_PIGWEED_RPC_CLIENT_SERVER=y``. 251* ``pw_rpc.common`` which can be enabled via ``CONFIG_PIGWEED_RPC_COMMON=y``. 252 253.. _module-pw_rpc-syntax-versions: 254 255--------------------------- 256proto2 versus proto3 syntax 257--------------------------- 258Always use proto3 syntax rather than proto2 for new protocol buffers. proto2 259protobufs can be compiled for ``pw_rpc``, but they are not as well supported 260as proto3. Specifically, ``pw_rpc`` lacks support for non-zero default values 261in proto2. When using Nanopb with ``pw_rpc``, proto2 response protobufs with 262non-zero field defaults should be manually initialized to the default struct. 263 264In the past, proto3 was sometimes avoided because it lacked support for field 265presence detection. Fortunately, this has been fixed: proto3 now supports 266``optional`` fields, which are equivalent to proto2 ``optional`` fields. 267 268If you need to distinguish between a default-valued field and a missing field, 269mark the field as ``optional``. The presence of the field can be detected 270with ``std::optional``, a ``HasField(name)``, or ``has_<field>`` member, 271depending on the library. 272 273Optional fields have some overhead. If using Nanopb, default-valued fields 274are included in the encoded proto, and the proto structs have a 275``has_<field>`` flag for each optional field. Use plain fields if field 276presence detection is not needed. 277 278.. code-block:: protobuf 279 280 syntax = "proto3"; 281 282 message MyMessage { 283 // Leaving this field unset is equivalent to setting it to 0. 284 int32 number = 1; 285 286 // Setting this field to 0 is different from leaving it unset. 287 optional int32 other_number = 2; 288 } 289 290.. _module-pw_rpc-guides-headers: 291 292----------------------------------------------------------- 293When to use raw, Nanopb, or pw_protobuf headers and methods 294----------------------------------------------------------- 295There are three types of generated headers and methods available: 296 297* Raw 298* Nanopb 299* ``pw_protobuf`` 300 301This section explains when to use each one. See 302:ref:`module-pw_rpc-quickstart-codegen` for context. 303 304``pw_rpc`` doesn't generate raw headers unless you specifically request them 305in your build. These headers allow you to use raw methods. Raw methods only 306give you a serialized request buffer and an output buffer. Projects typically 307only work with raw headers and methods when they have large, complex proto 308definitions (e.g. lots of callbacks) that are difficult to work with. Advanced 309projects might use raw headers and methods when they need finer control over 310how a proto is encoded. 311 312Nanopb and ``pw_protobuf`` are higher-level libraries that make it easier 313to serialize or deserialize protos inside raw bytes. Most new projects should 314default to Nanopb for the time being. Pigweed has plans to improve ``pw_protobuf`` 315but those plans will take a while to implement. 316 317The Nanopb and ``pw_protobuf`` APIs and codegen are both built on top of the 318underlying raw APIs, which is why it's always possible to fallback to 319raw APIs. If you define a Nanopb or ``pw_protobuf`` service, you can choose to 320make individual methods raw by defining them using the raw method signature. 321You still import the Nanopb or ``pw_protobuf`` header and can use the 322methods from those libraries elsewhere. Unless you believe your entire service 323requires pure raw methods, it's better to use Nanopb or ``pw_protobuf`` for 324most things and fallback to raw only when needed. 325 326.. caution:: Mixing Nanopb and pw_protobuf within the same service not supported 327 328 You can have a mix of Nanopb, ``pw_protobuf``, and raw services on the 329 same server. Within a service, you can mix raw and Nanopb or raw and ``pw_protobuf`` 330 methods. You can't currently mix Nanopb and ``pw_protobuf`` methods but Pigweed 331 can implement this if needed. :bug:`234874320` outlines some conflicts you may 332 encounter if you try to include Nanopb and ``pw_protobuf`` headers in the 333 same source file. 334 335.. _module-pw_rpc-guides-raw-fallback: 336 337Falling back to raw methods 338=========================== 339When implementing an RPC service using Nanopb or ``pw_protobuf``, you may 340sometimes run into limitations of the protobuf library when used in conjunction 341with ``pw_rpc``. For example, fields which use callbacks require those callbacks 342to be set prior to the decode operation, but ``pw_rpc`` internally decodes every 343message passed into a method implementation without any opportunity to set 344these. Alternatively, you may simply want finer control over how your messages 345are encoded. 346 347To assist with these cases, ``pw_rpc`` allows any method within a Nanopb or 348``pw_protobuf`` service to use its raw APIs without having to define the entire 349service as raw. Implementors may choose on a method-by-method basis where they 350desire to have access to the raw protobuf messages. 351 352To implement a method using the raw APIs, all you have to do is change the 353signature of the function --- ``pw_rpc`` will automatically handle the rest. 354Examples are provided below, each showing a Nanopb method and its equivalent 355raw signature. 356 357Unary method 358------------ 359When defining a unary method using the raw APIs, it is important to note that 360there is no synchronous raw unary API. The asynchronous unary method signature 361must be used instead. 362 363**Nanopb** 364 365.. code-block:: c++ 366 367 // Synchronous unary method. 368 pw::Status DoFoo(const FooRequest& request, FooResponse response); 369 370 // Asynchronous unary method. 371 void DoFoo(const FooRequest& request, 372 pw::rpc::NanopbUnaryResponder<FooResponse>& responder); 373 374**Raw** 375 376.. code-block:: c++ 377 378 // Only asynchronous unary methods are supported. 379 void DoFoo(pw::ConstByteSpan request, pw::rpc::RawUnaryResponder& responder); 380 381Server streaming method 382----------------------- 383 384**Nanopb** 385 386.. code-block:: c++ 387 388 void DoFoo(const FooRequest& request, 389 pw::rpc::NanopbServerWriter<FooResponse>& writer); 390 391**Raw** 392 393.. code-block:: c++ 394 395 void DoFoo(pw::ConstByteSpan request, pw::rpc::RawServerWriter& writer); 396 397Client streaming method 398----------------------- 399 400**Nanopb** 401 402.. code-block:: c++ 403 404 void DoFoo(pw::rpc::NanopbServerReader<FooRequest, FooResponse>&); 405 406**Raw** 407 408.. code-block:: c++ 409 410 void DoFoo(RawServerReader&); 411 412Bidirectional streaming method 413------------------------------ 414 415**Nanopb** 416 417.. code-block:: c++ 418 419 void DoFoo(pw::rpc::NanopbServerReaderWriter<Request, Response>&); 420 421**Raw** 422 423.. code-block:: c++ 424 425 void DoFoo(RawServerReaderWriter&); 426 427---------------------------- 428Testing a pw_rpc integration 429---------------------------- 430After setting up a ``pw_rpc`` server in your project, you can test that it is 431working as intended by registering the provided ``EchoService``, defined in 432``echo.proto``, which echoes back a message that it receives. 433 434.. literalinclude:: echo.proto 435 :language: protobuf 436 :lines: 14- 437 438For example, in C++ with pw_protobuf: 439 440.. code-block:: c++ 441 442 #include "pw_rpc/server.h" 443 444 // Include the apporpriate header for your protobuf library. 445 #include "pw_rpc/echo_service_pwpb.h" 446 447 constexpr pw::rpc::Channel kChannels[] = { /* ... */ }; 448 static pw::rpc::Server server(kChannels); 449 450 static pw::rpc::EchoService echo_service; 451 452 void Init() { 453 server.RegisterService(echo_service); 454 } 455 456See :ref:`module-pw_rpc-cpp-testing` for more C++-specific testing 457guidance. 458 459------------------------------- 460Benchmarking and stress testing 461------------------------------- 462``pw_rpc`` provides an RPC service and Python module for stress testing and 463benchmarking a ``pw_rpc`` deployment. 464 465pw_rpc provides tools for stress testing and benchmarking a Pigweed RPC 466deployment and the transport it is running over. Two components are included: 467 468* The pw.rpc.Benchmark service and its implementation. 469* A Python module that runs tests using the Benchmark service. 470 471pw.rpc.Benchmark service 472======================== 473The Benchmark service provides a low-level RPC service for sending data between 474the client and server. The service is defined in ``pw_rpc/benchmark.proto``. 475 476A raw RPC implementation of the benchmark service is provided. This 477implementation is suitable for use in any system with pw_rpc. To access this 478service, add a dependency on ``"$dir_pw_rpc:benchmark"`` in GN or 479``pw_rpc.benchmark`` in CMake. Then, include the service 480(``#include "pw_rpc/benchmark.h"``), instantiate it, and register it with your 481RPC server, like any other RPC service. 482 483The Benchmark service was designed with the Python-based benchmarking tools in 484mind, but it may be used directly to test basic RPC functionality. The service 485is well suited for use in automated integration tests or in an interactive 486console. 487 488Benchmark service 489----------------- 490.. literalinclude:: benchmark.proto 491 :language: protobuf 492 :lines: 14- 493 494Example 495------- 496.. code-block:: c++ 497 498 #include "pw_rpc/benchmark.h" 499 #include "pw_rpc/server.h" 500 501 constexpr pw::rpc::Channel kChannels[] = { /* ... */ }; 502 static pw::rpc::Server server(kChannels); 503 504 static pw::rpc::BenchmarkService benchmark_service; 505 506 void RegisterServices() { 507 server.RegisterService(benchmark_service); 508 } 509 510Stress testing 511============== 512.. attention:: 513 This section is experimental and liable to change. 514 515The Benchmark service is also used as part of a stress test of the ``pw_rpc`` 516module. This stress test is implemented as an unguided fuzzer that uses 517multiple worker threads to perform generated sequences of actions using RPC 518``Call`` objects. The test is included as an integration test, and can found and 519be run locally using GN: 520 521.. code-block:: bash 522 523 $ gn desc out //:integration_tests deps | grep fuzz 524 //pw_rpc/fuzz:cpp_client_server_fuzz_test(//targets/host/pigweed_internal:pw_strict_host_clang_debug) 525 526 $ gn outputs out '//pw_rpc/fuzz:cpp_client_server_fuzz_test(//targets/host/pigweed_internal:pw_strict_host_clang_debug)' 527 pw_strict_host_clang_debug/gen/pw_rpc/fuzz/cpp_client_server_fuzz_test.pw_pystamp 528 529 $ ninja -C out pw_strict_host_clang_debug/gen/pw_rpc/fuzz/cpp_client_server_fuzz_test.pw_pystamp 530