pw_rpc, pw_hdlc_lite: Documentation update

Change-Id: Ie4b7121177133e1539ae3dca066a15e9fba689e7
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/19161
Commit-Queue: Wyatt Hepler <hepler@google.com>
Reviewed-by: Keir Mierle <keir@google.com>
Reviewed-by: Ewout van Bekkum <ewout@google.com>
diff --git a/pw_checksum/docs.rst b/pw_checksum/docs.rst
index 6157fe6..8d5f849 100644
--- a/pw_checksum/docs.rst
+++ b/pw_checksum/docs.rst
@@ -2,6 +2,8 @@
 
 .. highlight:: sh
 
+.. _chapter-pw-checksum:
+
 -----------
 pw_checksum
 -----------
diff --git a/pw_hdlc_lite/BUILD.gn b/pw_hdlc_lite/BUILD.gn
index def4fd0..8165eb8 100644
--- a/pw_hdlc_lite/BUILD.gn
+++ b/pw_hdlc_lite/BUILD.gn
@@ -123,5 +123,12 @@
 }
 
 pw_doc_group("docs") {
-  sources = [ "docs.rst" ]
+  sources = [
+    "docs.rst",
+    "rpc_example/docs.rst",
+  ]
+  inputs = [
+    "py/pw_hdlc_lite/decode.py",
+    "py/pw_hdlc_lite/encode.py",
+  ]
 }
diff --git a/pw_hdlc_lite/docs.rst b/pw_hdlc_lite/docs.rst
index fbb283c..7b76bcf 100644
--- a/pw_hdlc_lite/docs.rst
+++ b/pw_hdlc_lite/docs.rst
@@ -1,130 +1,117 @@
-.. _chapter-pw-hdlc:
-
 .. default-domain:: cpp
 
 .. highlight:: sh
 
+.. _chapter-pw-hdlc-lite:
+
 ------------
 pw_hdlc_lite
 ------------
-pw_hdlc_lite is a module that enables serial communication between devices
-using the HDLC-Lite protocol.
+`High-Level Data Link Control (HDLC)
+<https://en.wikipedia.org/wiki/High-Level_Data_Link_Control>`_ is a data link
+layer protocol intended for serial communication between devices. HDLC is
+standardized as `ISO/IEC 13239:2002 <https://www.iso.org/standard/37010.html>`_.
 
-Compatibility
-=============
-C++17
+The ``pw_hdlc_lite`` module provides a simple, robust frame-oriented
+transport that uses a subset of the HDLC protocol. ``pw_hdlc_lite`` supports
+sending between embedded devices or the host. It can be used with
+:ref:`chapter-pw-rpc` to enable remote procedure calls (RPCs) on embedded on
+devices.
 
-Dependencies
-============
-* ``pw_bytes``
-* ``pw_log``
-* ``pw_preprocessor``
-* ``pw_result``
-* ``pw_rpc``
-* ``pw_status``
-* ``pw_span``
-* ``pw_stream``
-* ``pw_sys_io``
+**Why use the pw_hdlc_lite module?**
 
-HDLC-Lite Overview
-==================
-High-Level Data Link Control (HDLC) is a data link layer protocol which uses
-synchronous serial transmissions for communication between two devices. Unlike
-the standard HDLC protocol which uses six fields of embedded information, the
-HDLC-Lite protocol is a minimal version that only uses the bare essentials.
+  * Enables the transmission of RPCs and other data between devices over serial.
+  * Detects corruption and data loss.
+  * Light-weight, simple, and easy to use.
+  * Supports streaming to transport without buffering, since the length is not
+    encoded.
 
-The HDLC-Lite data frame in ``pw_hdlc_lite`` uses a start and end frame
-delimiter (0x7E), the escaped binary payload and the CCITT-CRC16 value.
-It looks like:
+.. admonition:: Try it out!
 
-.. code-block:: text
+  For an example of how to use HDLC with :ref:`chapter-pw-rpc`, see the
+  :ref:`chapter-pw-hdlc-rpc-example`.
 
-                                        [More frames]
-    _________________________________________   _______
-    | |                              |  | | |...|   | |
-    | |                              |  | | |...|   | |
-    |_|______________________________|__|_|_|...|___|_|
-     F         Payload               CRC F F     CRC F
+.. toctree::
+  :maxdepth: 1
+  :hidden:
 
-Basic Overview
-==============
-The ``pw_hdlc_lite`` module provides a simple, reliable packet-oriented
-transport that uses the HDLC-Lite protocol to send and receive data to and from
-embedded devices. This is especially needed for making RPC calls on devices
-during testing because the ``pw_rpc`` module does not handle the transmission of
-RPCs. This module enables the transmission of RPCs and other bytes through a
-serial connection.
-
-There are essentially two main functions of the ``pw_hdlc_lite`` module:
-
-  * **Encoding** the data by escaping the bytes of the payload, calculating the
-    CCITT-CRC16 value, constructing a data frame and sending the
-    resulting data packet through serial.
-  * **Decoding** the data by unescaping the received bytes, verifying the
-    CCITT-CRC16 value and returning the successfully decoded packets.
-
-**Why use the ``pw_hdlc_lite`` module?**
-
-  * Enables the transmission of RPCs and other data between devices over serial
-  * Resilient to corruption and data loss.
-  * Light-weight, simple and easy to use.
-  * Supports streaming to transport without buffering - e.g. protocol buffers
-    have length-prefix.
+  rpc_example/docs
 
 Protocol Description
 ====================
 
+Frames
+------
+The HDLC implementation in ``pw_hdlc_lite`` supports only HDLC information
+frames. These frames are encoded as follows:
+
+.. code-block:: text
+
+    _________________________________________
+    | | | |                          |    | |...
+    | | | |                          |    | |... [More frames]
+    |_|_|_|__________________________|____|_|...
+     F A C       Payload              FCS  F
+
+     F = flag byte (0x7e, the ~ character)
+     A = address field
+     C = control field
+     FCS = frame check sequence (CRC-32)
+
+
 Encoding and sending data
 -------------------------
 This module first writes an initial frame delimiter byte (0x7E) to indicate the
 beginning of the frame. Before sending any of the payload data through serial,
-the special bytes are escaped accordingly:
+the special bytes are escaped:
 
-            +-----------------------+----------------------+
-            |Unescaped Special Bytes| Escaped Special Bytes|
-            +=======================+======================+
-            |       0x7E            |       0x7D5E         |
-            +-----------------------+----------------------+
-            |       0x7D            |       0x7D5D         |
-            +-----------------------+----------------------+
+            +-------------------------+-----------------------+
+            | Unescaped Special Bytes | Escaped Special Bytes |
+            +=========================+=======================+
+            |           7E            |        7D 5E          |
+            +-------------------------+-----------------------+
+            |           7D            |        7D 5D          |
+            +-------------------------+-----------------------+
 
 The bytes of the payload are escaped and written in a single pass. The
-CCITT-CRC16 value is calculated, escaped and written after. After this, a final
-frame delimiter byte (0x7E) is written to mark the end of the frame.
+frame check sequence is calculated, escaped, and written after. After this, a
+final frame delimiter byte (0x7E) is written to mark the end of the frame.
 
 Decoding received bytes
 -----------------------
-Packets may be received in multiple parts, so we need to store the received data
+Frames may be received in multiple parts, so we need to store the received data
 in a buffer until the ending frame delimiter (0x7E) is read. When the
-pw_hdlc_lite decoder receives data, it unescapes it and adds it to a buffer.
-When the frame is complete, it calculates and verifies the CCITT-CRC16 bytes and
-does the following:
+``pw_hdlc_lite`` decoder receives data, it unescapes it and adds it to a buffer.
+When the frame is complete, it calculates and verifies the frame check sequence
+and does the following:
 
-* If correctly verified, the decoder returns the decoded packet.
-* If the checksum verification fails, the data packet is discarded.
-
-During the decoding process, the decoder essentially transitions between 3
-states, where each state indicates the method of decoding that particular byte:
-
-NO_PACKET --> PACKET_ACTIVE --> (ESCAPE | NO_PACKET).
+* If correctly verified, the decoder returns the decoded frame.
+* If the checksum verification fails, the frame is discarded and an error is
+  reported.
 
 API Usage
 =========
+There are two primary functions of the ``pw_hdlc_lite`` module:
+
+  * **Encoding** data by constructing a frame with the escaped payload bytes and
+    frame check sequence.
+  * **Decoding** data by unescaping the received bytes, verifying the frame
+    check sequence, and returning successfully decoded frames.
 
 Encoder
 -------
-The Encoder API invloves a single function that encodes the data using the
-HDLC-Lite protocol and sends the data through the serial.
+The Encoder API provides a single function that encodes data as an HDLC
+information frame.
 
 C++
 ^^^
-In C++, this function is called ``EncodeAndWritePayload`` and it accepts a
-ConstByteSpan called payload and an object of type Writer& as arguments. It
-returns a Status object that indicates if the write was successful. This
-implementation uses the ``pw_checksum`` module to compute the CRC16 value. Since
-the function writes a starting and ending frame delimiter byte at the beginnning
-and the end of frames, it is safe to encode multiple spans. The usage of this
-function is as follows:
+.. cpp:namespace:: pw
+
+.. cpp:function:: Status hdlc_lite::WriteInformationFrame(uint8_t address, ConstByteSpan data, stream::Writer& writer)
+
+  Writes a span of data to a :ref:`pw::stream::Writer <chapter-pw-stream>` and
+  returns the status. This implementation uses the :ref:`chapter-pw-checksum`
+  module to compute the CRC-32 frame check sequence.
 
 .. code-block:: cpp
 
@@ -132,70 +119,57 @@
   #include "pw_hdlc_lite/sys_io_stream.h"
 
   int main() {
-      pw::stream::SerialWriter serial_writer;
-      constexpr std::array<byte, 1> test_array = { byte(0x41) };
-      auto status = EncodeAndWritePayload(test_array, serial_writer);
+    pw::stream::SysIoWriter serial_writer;
+    Status status = WriteInformationFrame(123 /* address */,
+                                          data,
+                                          serial_writer);
+    if (!status.ok()) {
+      PW_LOG_INFO("Writing frame failed! %s", status.str());
+    }
   }
 
-In the example above, we expect the encoder to send the following bytes:
-
-- **0x7E** - Initial Frame Delimiter
-- **0x41** - Payload
-- **0x15** - LSB of the CCITT-CRC16 value
-- **0xB9** - MSB of the CCITT-CRC16 value
-- **0x7E** - End Frame Delimiter
-
 Python
 ^^^^^^
-In Python, the function is called ``encode_and_write_payload`` and it accepts
-the payload as a byte object and a callable to which is used to write the data.
-This function does not return anything, and uses the binascii library function
-called crc_hqx to compute the CRC16 bytes. Like the C++ function, the Python
-function also writes a frame delimiter at the beginnning and the end of frames
-so it is safe to encode multiple spans consecutively. The usage of this function
-is as follows:
+.. automodule:: pw_hdlc_lite.encode
+  :members:
 
 .. code-block:: python
 
   import serial
-  from pw_hdlc_lite import encoder
+  from pw_hdlc_lite import encode
 
   ser = serial.Serial()
-  encoder.encode_and_write_payload(b'A', ser.write)
-
-We expect this example to give us the same result as the C++ example above since
-it encodes the same payload.
+  ser.write(encode.information_frame(b'your data here!'))
 
 Decoder
 -------
-The Decoder API involves a Decoder class whose main functionality is a function
-that unescapes the received bytes, adds them to a buffer and returns the
-successfully decoded packets. A class is used so that the user can call the
-decoder object's adding bytes functionality on the currently received bytes
-instead of waiting on the entire packet to arrive.
+The decoder class unescapes received bytes and adds them to a buffer. Complete,
+valid HDLC frames are yielded as they are received.
 
 C++
 ^^^
-The main functionality of the C++ ``Decoder`` class is the 'AddByte' function
-which accepts a single byte as an argument, unescapes it and adds it to the
-buffer. If the byte is the ending frame delimiter flag (0x7E) it attempts to
-decode the packet and returns a ``Result`` object indicating the success of the
-operation:
+.. cpp:class:: pw::hdlc_lite::Decoder
 
-  * The returned ``pw::Result`` object will have status ``Status::OK`` and
-    value ConstByteSpan containing the most recently decoded packet if it finds
-    the end of the data-frame and successfully verifies the CRC.
-  * The returned ``pw::Result`` object will have status ``Status::UNAVAILABLE``
-    if it doesnt find the end of the data-frame during that function call.
-  * The returned ``pw::Result`` object will have status ``Status::DATA_LOSS``
-    if it finds the end of the data-frame, but the CRC-verification fails. It
-    also returns this status if the packet in question does not have the
-    2-byte CRC in it.
-  * The returned ``pw::Result`` object will have status
-    ``Status::RESOURCE_EXHAUSTED`` if the decoder buffer runs out of space.
+  .. cpp:function:: pw::Result<Frame> Process(std::byte b)
 
-Here's a C++ example of reading individual bytes from serial and then using the
-decoder object to decode the received data:
+    Parses a single byte of an HDLC stream. Returns a Result with the complete
+    frame if the byte completes a frame. The status is the following:
+
+      - OK - A frame was successfully decoded. The Result contains the Frame,
+        which is invalidated by the next Process call.
+      - UNAVAILABLE - No frame is available.
+      - RESOURCE_EXHAUSTED - A frame completed, but it was too large to fit in
+        the decoder's buffer.
+      - DATA_LOSS - A frame completed, but it was invalid. The frame was
+        incomplete or the frame check sequence verification failed.
+
+  .. cpp:function:: void Process(pw::ConstByteSpan data, F&& callback, Args&&... args)
+
+    Processes a span of data and calls the provided callback with each frame or
+    error.
+
+This example demonstrates reading individual bytes from ``pw::sys_io`` and
+decoding HDLC frames:
 
 .. code-block:: cpp
 
@@ -203,66 +177,62 @@
   #include "pw_sys_io/sys_io.h"
 
   int main() {
-    byte data;
+    std::byte data;
     while (true) {
       if (!pw::sys_io::ReadByte(&data).ok()) {
         // Log serial reading error
       }
-      auto decoded_packet = decoder.AddByte(data);
+      Result<Frame> decoded_frame = decoder.Process(data);
 
-      if (decoded_packet.ok()) {
-       // Use decoded_packet to get access to the most recently decoded packet
+      if (decoded_frame.ok()) {
+        // Handle the decoded frame
       }
     }
   }
 
 Python
 ^^^^^^
-The main functionality of the Python ``Decoder`` class is the ``add_bytes``
-generator which unescapes the bytes object argument and adds them to a buffer
-until it encounters the ending frame delimiter (0x7E) flag. The generator yields
-the decoded packets as byte objects upon successfully verification of the CRC16
-value of the received bytes. If the CRC verification fails it raises a
-CrcMismatchError exception.
+.. autoclass:: pw_hdlc_lite.decode.FrameDecoder
+  :members:
 
-Below is an example of the usage of the decoder class to decode bytes read from
-serial:
+Below is an example using the decoder class to decode data read from serial:
 
 .. code-block:: python
 
   import serial
-  from pw_hdlc_lite import decoder
+  from pw_hdlc_lite import decode
 
   ser = serial.Serial()
-  decode = decoder.Decoder()
+  decoder = decode.FrameDecoder()
 
-  while true:
-    byte = ser.read(1)
+  while True:
+      for frame in decoder.process_valid_frames(ser.read()):
+          # Handle the decoded frame
 
-    for decoded_packet in decode.add_bytes(byte):
-      # Do something with the decoded packet
+Additional features
+===================
 
-Like the C++ example, this reads individual bytes and adds them to the decoder.
-
-Features
-========
-
-pw::stream::SerialWriter
+pw::stream::SysIoWriter
 ------------------------
-The ``SerialWriter`` class implements the ``Writer`` interface by using sys_io
-to write data over a serial connection. This Writer object is used by the C++
-encoder to send the encoded bytes to the device.
+The ``SysIoWriter`` C++ class implements the ``Writer`` interface with
+``pw::sys_io``. This Writer may be used by the C++ encoder to send HDLC frames
+over serial.
 
-Roadmap & Status
-================
+HdlcRpcClient
+-------------
+.. autoclass:: pw_hdlc_lite.rpc.HdlcRpcClient
+  :members:
 
-- **Additional fields** - As it currently stands, ``pw_hdlc_lite`` uses only
-  three fields of control bytes: starting frame delimiter, 2-byte CRC and an
-  ending frame delimiter. However, if we decided to send larger, more
-  complicated RPCs and if wanted to stream log debug and error messages, we will
-  require additional fields of data to ensure the different packets are sent
-  correctly. Thus, in the future, we plan to add additional channel and sequence
-  byte fields that could enable separate channels for pw_rpc, QoS etc.
+Roadmap
+=======
+- **Expanded protocol support** - ``pw_hdlc_lite`` currently only supports
+  information frames with a single address byte and control byte. Support for
+  different frame types and extended address or control fields may be added in
+  the future.
 
 - **Higher performance** - We plan to improve the overall performance of the
   decoder and encoder implementations by using SIMD/NEON.
+
+Compatibility
+=============
+C++17
diff --git a/pw_hdlc_lite/public/pw_hdlc_lite/decoder.h b/pw_hdlc_lite/public/pw_hdlc_lite/decoder.h
index 1e14066..d15cbe7 100644
--- a/pw_hdlc_lite/public/pw_hdlc_lite/decoder.h
+++ b/pw_hdlc_lite/public/pw_hdlc_lite/decoder.h
@@ -92,7 +92,8 @@
   //
   Result<Frame> Process(std::byte b);
 
-  // Calls the provided callback with each frame or error.
+  // Processes a span of data and calls the provided callback with each frame or
+  // error.
   template <typename F, typename... Args>
   void Process(ConstByteSpan data, F&& callback, Args&&... args) {
     for (std::byte b : data) {
diff --git a/pw_hdlc_lite/rpc_example/docs.rst b/pw_hdlc_lite/rpc_example/docs.rst
new file mode 100644
index 0000000..94bc3f6
--- /dev/null
+++ b/pw_hdlc_lite/rpc_example/docs.rst
@@ -0,0 +1,104 @@
+.. default-domain:: cpp
+
+.. highlight:: sh
+
+.. _chapter-pw-hdlc-rpc-example:
+
+=============================
+RPC over HDLC example project
+=============================
+The :ref:`chapter-pw-hdlc-lite` module includes an example of bringing up a
+:ref:`chapter-pw-rpc` server that can be used to invoke RPCs. The example code
+is located at ``pw_hdlc_lite/rpc_example``. This section walks through invoking
+RPCs interactively and with a script using the RPC over HDLC example.
+
+These instructions assume the STM32F429i Discovery board, but they work with
+any target with :ref:`pw::sys_io <chapter-pw-sys-io>` implemented.
+
+---------------------
+Getting started guide
+---------------------
+
+1. Set up your board
+====================
+Connect the board you'll be communicating with. For the Discovery board, connect
+the mini USB port, and note which serial device it appears as (e.g.
+``/dev/ttyACM0``).
+
+2. Build Pigweed
+================
+Activate the Pigweed environment and run the default build.
+
+.. code-block:: sh
+
+  source activate.sh
+  gn gen out
+  ninja -C out
+
+3. Flash the firmware image
+===========================
+After a successful build, the binary for the example will be located at
+``out/<toolchain>/obj/pw_hdlc_lite/rpc_example/bin/rpc_example.elf``.
+
+Flash this image to your board. If you are using the STM32F429i Discovery Board,
+you can flash the image with `OpenOCD <http://openocd.org>`_.
+
+.. code-block:: sh
+
+ openocd -f targets/stm32f429i-disc1/py/stm32f429i_disc1_utils/openocd_stm32f4xx.cfg \
+     -c "program out/stm32f429i_disc1_debug/obj/pw_hdlc_lite/rpc_example/bin/rpc_example.elf"
+
+4. Invoke RPCs from in an interactive console
+=============================================
+The RPC console uses `IPython <https://ipython.org>`_ to make a rich interactive
+console for working with pw_rpc. Run the RPC console with the following command,
+replacing ``/dev/ttyACM0`` with the correct serial device for your board.
+
+.. code-block:: text
+
+  $ python -m pw_hdlc_lite.rpc_console --device /dev/ttyACM0
+
+  Console for interacting with pw_rpc over HDLC.
+
+  To start the console, provide a serial port as the --device argument and paths
+  or globs for .proto files that define the RPC services to support:
+
+    python -m pw_hdlc_lite.rpc_console --device /dev/ttyUSB0 sample.proto
+
+  This starts an IPython console for communicating with the connected device. A
+  few variables are predefined in the interactive console. These include:
+
+      rpcs   - used to invoke RPCs
+      device - the serial device used for communication
+      client - the pw_rpc.Client
+
+  An example echo RPC command:
+
+    rpcs.pw.rpc.EchoService.Echo(msg="hello!")
+
+  In [1]:
+
+RPCs may be accessed through the predefined ``rpcs`` variable. RPCs are
+organized by their protocol buffer package and RPC service, as defined in a
+.proto file. To call the ``Echo`` method is part of the ``EchoService``, which
+is in the ``pw.rpc`` package. To invoke it synchronously, call
+``rpcs.pw.rpc.EchoService.Echo``:
+
+.. code-block:: python
+
+    In [1]: rpcs.pw.rpc.EchoService.Echo(msg="Your message here!")
+    Out[1]: (<Status.OK: 0>, msg: "Your message here!")
+
+5. Invoke RPCs with a script
+============================
+RPCs may also be invoked from Python scripts. Close the RPC console if it is
+running, and execute the example script. Set the --device argument to the
+serial port for your device.
+
+.. code-block:: text
+
+  $ pw_hdlc_lite/rpc_example/example_script.py --device /dev/ttyACM0
+  The status was Status.OK
+  The payload was msg: "Hello"
+
+  The device says: Goodbye!
diff --git a/pw_rpc/docs.rst b/pw_rpc/docs.rst
index 8076639..048627f 100644
--- a/pw_rpc/docs.rst
+++ b/pw_rpc/docs.rst
@@ -10,9 +10,150 @@
 The ``pw_rpc`` module provides a system for defining and invoking remote
 procedure calls (RPCs) on a device.
 
+.. admonition:: Try it out!
+
+  For a quick intro to ``pw_rpc``, see the :ref:`chapter-pw-hdlc-rpc-example` in
+  the :ref:`chapter-pw-hdlc-lite` module.
+
 .. attention::
 
-  Under construction.
+  This documentation is under construction.
+
+Creating an RPC
+===============
+
+1. RPC service declaration
+--------------------------
+Pigweed RPCs are declared in a protocol buffer service definition.
+
+* `Protocol Buffer service documentation
+  <https://developers.google.com/protocol-buffers/docs/proto3#services>`_
+* `gRPC service definition documentation
+  <https://grpc.io/docs/what-is-grpc/core-concepts/#service-definition>`_
+
+.. code-block:: protobuf
+
+  syntax = "proto3";
+
+  package foo.bar;
+
+  message Request {}
+
+  message Response {
+    int32 number = 1;
+  }
+
+  service TheService {
+    rpc MethodOne(Request) returns (Response) {}
+    rpc MethodTwo(Request) returns (stream Response) {}
+  }
+
+This protocol buffer is declared in a ``BUILD.gn`` file as follows:
+
+.. code-block:: python
+
+  import("//build_overrides/pigweed.gni")
+  import("$dir_pw_protobuf_compiler/proto.gni")
+
+  pw_proto_library("the_service_proto") {
+    sources = [ "foo_bar/the_service.proto" ]
+  }
+
+2. RPC service definition
+-------------------------
+``pw_rpc`` generates a C++ base class for each RPC service declared in a .proto
+file. The serivce class is implemented by inheriting from this generated base
+and defining a method for each RPC.
+
+A service named ``TheService`` in package ``foo.bar`` will generate the
+following class:
+
+.. cpp:class:: template <typename Implementation> foo::bar::generated::TheService
+
+A Nanopb implementation of this service would be as follows:
+
+.. code-block:: cpp
+
+  namespace foo::bar {
+
+  class TheService : public generated::TheService<TheService> {
+   public:
+    pw::Status MethodOne(ServerContext& ctx,
+                         const foo_bar_Request& request,
+                         foo_bar_Response& response) {
+      // implementation
+      return pw::Status::OK;
+    }
+
+    void MethodTwo(ServerContext& ctx,
+                   const foo_bar_Request& request,
+                   ServerWriter<foo_bar_Response>& response) {
+      // implementation
+      response.Write(foo_bar_Response{.number = 123});
+    }
+  };
+
+  }  // namespace foo::bar
+
+The Nanopb implementation would be declared in a ``BUILD.gn``:
+
+.. code-block:: python
+
+  import("//build_overrides/pigweed.gni")
+
+  import("$dir_pw_build/target_types.gni")
+
+  pw_source_set("the_service") {
+    public_configs = [ ":public" ]
+    public = [ "public/foo_bar/service.h" ]
+    public_deps = [ ":the_service_proto_nanopb_rpc" ]
+  }
+
+.. attention::
+
+  pw_rpc's generated classes will support using ``pw_protobuf`` or raw buffers
+  (no protobuf library) in the future.
+
+3. Register the service with a server
+-------------------------------------
+This example code sets up an RPC server with an
+:ref:`HDLC<chapter-pw-hdlc-lite>` channel output and the example service.
+
+.. code-block:: cpp
+
+  // Set up the output channel for the pw_rpc server to use. This configures the
+  // pw_rpc server to use HDLC over UART; projects not using UART and HDLC must
+  // adapt this as necessary.
+  pw::stream::SysIoWriter writer;
+  pw::rpc::RpcChannelOutput<kMaxTransmissionUnit> hdlc_channel_output(
+      writer, pw::hdlc_lite::kDefaultRpcAddress, "HDLC output");
+
+  pw::rpc::Channel channels[] = {
+      pw::rpc::Channel::Create<1>(&hdlc_channel_output)};
+
+  // Declare the pw_rpc server with the HDLC channel.
+  pw::rpc::Server server(channels);
+
+  pw::rpc::TheService the_service;
+
+  void RegisterServices() {
+    // Register the foo.bar.TheService example service.
+    server.Register(the_service);
+
+    // Register other services
+  }
+
+  int main() {
+    // Set up the server.
+    RegisterServices();
+
+    // Declare a buffer for decoding incoming HDLC frames.
+    std::array<std::byte, kMaxTransmissionUnit> input_buffer;
+
+    PW_LOG_INFO("Starting pw_rpc server");
+    pw::hdlc_lite::ReadAndProcessPackets(
+        server, hdlc_channel_output, input_buffer);
+  }
 
 Services
 ========
diff --git a/pw_stream/docs.rst b/pw_stream/docs.rst
index 9128070..b1edfa0 100644
--- a/pw_stream/docs.rst
+++ b/pw_stream/docs.rst
@@ -1,9 +1,9 @@
-.. _chapter-stream:
-
 .. default-domain:: cpp
 
 .. highlight:: sh
 
+.. _chapter-pw-stream:
+
 ---------
 pw_stream
 ---------