C++ with raw RPC#

pw_rpc: Efficient, low-code-size RPC system for embedded devices

Raw RPC allows C++ service methods and clients to send and receive raw, unparsed byte buffers (pw::ConstByteSpan / pw::span<const std::byte>) directly, without passing them through generated protobuf structs or classes.

When to use raw RPC#

  • Zero-copy serialization: Encode fields directly into wire buffers in-place using pw_protobuf stream encoders.

  • Large or dynamic payloads: Handle variable-length binary payloads, chunked firmware images, or pass-through proxy data without intermediate buffer copies.

  • Low-overhead benchmarking: Implement echo and throughput test services with minimal CPU cycles (see Benchmarking).

  • Mixing with Nanopb or pw_protobuf: Fall back to raw methods on individual performance-critical RPCs within a Nanopb or pw_protobuf service.

Usage#

In your build file, depend on the raw_rpc variant of your proto library:

# Bazel: raw_rpc_proto_library
# GN: :my_protos.raw_rpc

Include the generated header "my_project/sensor_service.raw_rpc.pb.h":

#include "my_project/sensor_service.raw_rpc.pb.h"
#include "pw_bytes/span.h"
#include "pw_rpc/raw/server_reader_writer.h"

Server-side#

Implement services by inheriting from the generated pw_rpc::raw service base:

class RawSensorService final
    : public ::pw::rpc::examples::pw_rpc::raw::SensorService::Service<
          RawSensorService> {
 public:
  // 1. Unary RPC: accepts raw request bytes and completes with responder
  void GetReading(pw::ConstByteSpan request_bytes,
                  pw::rpc::RawUnaryResponder& responder) {
    PW_LOG_INFO("Received %u raw request bytes",
                static_cast<unsigned>(request_bytes.size()));
    std::byte response_buffer[64]{};
    static_cast<void>(responder.Finish(response_buffer, pw::OkStatus()));
  }

  // 2. Server Streaming RPC
  void StreamReadings(pw::ConstByteSpan request_bytes,
                      pw::rpc::RawServerWriter& writer) {
    PW_LOG_INFO("Streaming readings for request of size %u",
                static_cast<unsigned>(request_bytes.size()));
    std::byte chunk[32]{};
    static_cast<void>(writer.Write(chunk));
    static_cast<void>(writer.Finish(pw::OkStatus()));
  }

  // 3. Bidirectional Streaming RPC
  void Calibrate(pw::rpc::RawServerReaderWriter& stream) {
    stream_ = std::move(stream);

    stream_.set_on_next([this](pw::ConstByteSpan payload) {
      // Echo back raw calibration payload
      static_cast<void>(stream_.Write(payload));
    });
  }

 private:
  pw::rpc::RawServerReaderWriter stream_;
};

Mixing raw methods into services#

You do not need to declare an entire service as raw. You can implement individual methods as raw inside a Nanopb or pw_protobuf service:

class MixedSensorService final
    : public ::pw::rpc::examples::pw_rpc::nanopb::SensorService::Service<
          MixedSensorService> {
 public:
  // Standard Nanopb unary method:
  pw::Status GetReading(const pw_rpc_examples_SensorRequest& request,
                        pw_rpc_examples_SensorResponse& response) {
    PW_LOG_INFO("Reading sensor %u", static_cast<unsigned>(request.sensor_id));
    response.temperature = 22.5f;
    response.humidity = 45.0f;
    return pw::OkStatus();
  }

  // Raw server streaming method fallback:
  void StreamReadings([[maybe_unused]] pw::ConstByteSpan request_bytes,
                      pw::rpc::RawServerWriter& writer) {
    std::byte payload[32]{};
    static_cast<void>(writer.Write(payload));
    static_cast<void>(writer.Finish(pw::OkStatus()));
  }
};

Client-side#

Raw clients allow sending and receiving raw byte spans directly:

using RawSensorClient = ::pw::rpc::examples::pw_rpc::raw::SensorService::Client;

[[maybe_unused]] void InvokeRawRpc() {
  RawSensorClient raw_client(client, 1);

  std::byte request_payload[16]{};
  auto call = raw_client.GetReading(
      request_payload, [](pw::ConstByteSpan response, pw::Status status) {
        if (status.ok()) {
          PW_LOG_INFO("Received %u raw bytes",
                      static_cast<unsigned>(response.size()));
        }
      });
}