pw_rpc: Add a sample project
Add a sample application that uses the current PW RPC module (and many
more). The application can also be run as a test using Zephyr's
twister command.
Bug: b/236263182
Change-Id: I0a5e35e8c5427886a12fb0640d6d1b586ec8b763
Signed-off-by: Yuval Peress <peress@google.com>
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/zephyr-integration/+/108833
Reviewed-by: Wyatt Hepler <hepler@google.com>
Commit-Queue: Auto-Submit <auto-submit@pigweed.google.com.iam.gserviceaccount.com>
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..000a9af
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,12 @@
+# Default build directories
+build/
+twister-out*/
+
+# West modules and install directories
+bootloader/
+modules/
+tools/
+.west
+
+# IDE specific directories
+.idea/
diff --git a/README.rst b/README.rst
new file mode 100644
index 0000000..382053e
--- /dev/null
+++ b/README.rst
@@ -0,0 +1,23 @@
+Pigweed samples and tests
+##################
+
+Setting up:
+
+.. code-block:: console
+
+ $ pip install pigweed
+ $ west init -l zephyr
+ $ west update
+ $ pip install -r zephyr/scripts/requirements.txt
+
+Running the RPC sample:
+
+.. code-block:: console
+
+ $ west build -b native_posix -p -t run samples/pw_rpc/
+
+Test everything under samples/:
+
+.. code-block:: console
+
+ $ ./zephyr/scripts/twister -v -c -p native_posix --coverage -T samples/
diff --git a/samples/pw_rpc/CMakeLists.txt b/samples/pw_rpc/CMakeLists.txt
new file mode 100644
index 0000000..184248f
--- /dev/null
+++ b/samples/pw_rpc/CMakeLists.txt
@@ -0,0 +1,60 @@
+# Copyright 2022 The Pigweed Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+
+cmake_minimum_required(VERSION 3.20)
+
+set(BOARD native_posix)
+set(NO_BUILD_TYPE_WARNING ON)
+set(CMAKE_VERBOSE_MAKEFILE ON)
+
+# Use this copy of Pigweed
+get_filename_component(PW_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/../../pigweed" ABSOLUTE)
+set(ENV{PW_ROOT} ${PW_ROOT})
+
+set(pw_third_party_nanopb_ADD_SUBDIRECTORY ON CACHE BOOL "" FORCE)
+
+list(APPEND ZEPHYR_EXTRA_MODULES
+ ${PW_ROOT}
+)
+
+find_package(Zephyr REQUIRED PATHS $ENV{ZEPHYR_BASE})
+project(rpc_demo)
+
+add_definitions(-DVERSION=5)
+add_definitions(-DCONFIG_RPC_BUFFER_SIZE=1024)
+add_definitions(-DPW_LOG_LEVEL=PW_LOG_LEVEL_INFO)
+add_definitions(-DPW_LOG_SHOW_MODULE=1)
+
+# Define the proto library
+include($ENV{PW_ROOT}/pw_protobuf_compiler/proto.cmake)
+pw_proto_library(
+ rpc_demo.protos
+ SOURCES proto/demo.proto
+)
+
+file(GLOB app_sources src/*.cc)
+target_sources(app
+ PRIVATE
+ ${app_sources}
+)
+zephyr_include_directories(include)
+zephyr_link_libraries(
+ rpc_demo.protos.nanopb
+ rpc_demo.protos.nanopb_rpc
+)
+target_link_libraries(app
+ PRIVATE
+ rpc_demo.protos.nanopb
+ rpc_demo.protos.nanopb_rpc
+)
diff --git a/samples/pw_rpc/include/rpc_demo/client/client_reader.h b/samples/pw_rpc/include/rpc_demo/client/client_reader.h
new file mode 100644
index 0000000..b9b9b64
--- /dev/null
+++ b/samples/pw_rpc/include/rpc_demo/client/client_reader.h
@@ -0,0 +1,38 @@
+// Copyright 2022 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+#pragma once
+
+#include <pw_hdlc/rpc_channel.h>
+#include <pw_hdlc/rpc_packets.h>
+#include <pw_rpc/client.h>
+#include <pw_stream/stream.h>
+
+#include <array>
+#include <atomic>
+
+namespace rpc_demo {
+
+class ClientReader {
+ public:
+ ClientReader(pw::stream::Reader& rx, pw::rpc::Client* rpc_client);
+ bool ParsePacket();
+
+ private:
+ pw::stream::Reader& rx_;
+ pw::rpc::Client* rpc_client_;
+ std::array<std::byte, CONFIG_RPC_BUFFER_SIZE> decode_buffer_;
+ pw::hdlc::Decoder decoder_;
+};
+
+} // namespace rpc_demo
diff --git a/samples/pw_rpc/include/rpc_demo/deque_stream.h b/samples/pw_rpc/include/rpc_demo/deque_stream.h
new file mode 100644
index 0000000..f34d07c
--- /dev/null
+++ b/samples/pw_rpc/include/rpc_demo/deque_stream.h
@@ -0,0 +1,32 @@
+// Copyright 2022 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+#pragma once
+
+#include <pw_stream/stream.h>
+#include <pw_sync/mutex.h>
+
+#include <deque>
+
+class DequeReadWriter : public pw::stream::NonSeekableReaderWriter {
+ public:
+ DequeReadWriter() = default;
+
+ protected:
+ pw::StatusWithSize DoRead(pw::ByteSpan destination) override;
+ pw::Status DoWrite(pw::ConstByteSpan data) override;
+
+ private:
+ std::deque<std::byte> buff_;
+ pw::sync::Mutex mutex_;
+};
diff --git a/samples/pw_rpc/include/rpc_demo/rxtx.h b/samples/pw_rpc/include/rpc_demo/rxtx.h
new file mode 100644
index 0000000..f20db35
--- /dev/null
+++ b/samples/pw_rpc/include/rpc_demo/rxtx.h
@@ -0,0 +1,26 @@
+// Copyright 2022 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+#pragma once
+
+#include <pw_stream/stream.h>
+
+/**
+ * @return The ReaderWriter stream used for client-to-service communication.
+ */
+pw::stream::ReaderWriter& ClientToServiceStream();
+
+/**
+ * @return The ReaderWriter stream used for service-to-client communication.
+ */
+pw::stream::ReaderWriter& ServiceToClientStream();
diff --git a/samples/pw_rpc/include/rpc_demo/service/demo_service.h b/samples/pw_rpc/include/rpc_demo/service/demo_service.h
new file mode 100644
index 0000000..377981d
--- /dev/null
+++ b/samples/pw_rpc/include/rpc_demo/service/demo_service.h
@@ -0,0 +1,37 @@
+// Copyright 2022 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+#pragma once
+
+#include "proto/demo.rpc.pb.h"
+
+namespace rpc_demo {
+
+class DemoService : public pw_rpc::nanopb::DemoService::Service<DemoService> {
+ public:
+ ::pw::Status GetVersion(const ::rpc_demo_Empty& request,
+ ::rpc_demo_GetVersionResponse& response);
+
+ ::pw::Status GetSensorList(const ::rpc_demo_Empty& request,
+ ::rpc_demo_GetSensorListResponse& response);
+
+ ::pw::Status UpdateSensorFrequency(
+ const ::rpc_demo_UpdateSensorFrequencyRequest& request,
+ ::rpc_demo_UpdateSensorFrequencyResponse& response);
+
+ ::pw::Status GetSensorSamples(
+ const ::rpc_demo_GetSensorSamplesRequest& request,
+ ::rpc_demo_GetSensorSamplesResponse& response);
+};
+
+} // namespace rpc_demo
\ No newline at end of file
diff --git a/samples/pw_rpc/include/rpc_demo/service/service_reader.h b/samples/pw_rpc/include/rpc_demo/service/service_reader.h
new file mode 100644
index 0000000..2f9835b
--- /dev/null
+++ b/samples/pw_rpc/include/rpc_demo/service/service_reader.h
@@ -0,0 +1,43 @@
+// Copyright 2022 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+#pragma once
+
+#include <pw_hdlc/rpc_channel.h>
+#include <pw_span/span.h>
+#include <pw_stream/stream.h>
+
+#include <atomic>
+#include <thread>
+
+#include "rpc_demo/service/demo_service.h"
+
+namespace rpc_demo {
+
+class ServiceReader {
+ public:
+ ServiceReader(pw::stream::Reader* reader,
+ pw::stream::Writer* writer,
+ pw::hdlc::RpcChannelOutput* output,
+ pw::span<pw::rpc::Channel> channels);
+
+ bool ParsePacket();
+
+ private:
+ pw::stream::Reader* reader_;
+ pw::stream::Writer* writer_;
+ pw::hdlc::RpcChannelOutput* output_;
+ pw::span<pw::rpc::Channel> channels_;
+};
+
+} // namespace rpc_demo
diff --git a/samples/pw_rpc/prj.conf b/samples/pw_rpc/prj.conf
new file mode 100644
index 0000000..2beca2c
--- /dev/null
+++ b/samples/pw_rpc/prj.conf
@@ -0,0 +1,46 @@
+# Copyright 2022 The Pigweed Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+
+CONFIG_ASSERT=y
+CONFIG_NANOPB=y
+CONFIG_BOARD_NATIVE_POSIX_64BIT=y
+CONFIG_LOG=y
+CONFIG_LOG_DEFAULT_LEVEL=4
+
+CONFIG_PIGWEED_ASSERT=y
+CONFIG_PIGWEED_BYTES=y
+CONFIG_PIGWEED_CHECKSUM=y
+CONFIG_PIGWEED_CHRONO_SYSTEM_CLOCK=y
+CONFIG_PIGWEED_CONTAINERS=y
+CONFIG_PIGWEED_FUNCTION=y
+CONFIG_PIGWEED_HDLC=y
+CONFIG_PIGWEED_INTERRUPT_CONTEXT=y
+CONFIG_PIGWEED_LOG=y
+CONFIG_PIGWEED_POLYFILL=y
+CONFIG_PIGWEED_POLYFILL_OVERRIDES=y
+CONFIG_PIGWEED_PREPROCESSOR=y
+CONFIG_PIGWEED_RESULT=y
+CONFIG_PIGWEED_ROUTER_PACKET_PARSER=y
+CONFIG_PIGWEED_RPC_COMMON=y
+CONFIG_PIGWEED_SPAN=y
+CONFIG_PIGWEED_STATUS=y
+CONFIG_PIGWEED_STREAM=y
+CONFIG_PIGWEED_STRING=y
+CONFIG_PIGWEED_SYNC_MUTEX=y
+CONFIG_PIGWEED_SYS_IO=y
+CONFIG_PIGWEED_VARINT=y
+
+CONFIG_CPLUSPLUS=y
+CONFIG_STD_CPP17=y
+CONFIG_LIB_CPLUSPLUS=y
diff --git a/samples/pw_rpc/proto/demo.proto b/samples/pw_rpc/proto/demo.proto
new file mode 100644
index 0000000..122427f
--- /dev/null
+++ b/samples/pw_rpc/proto/demo.proto
@@ -0,0 +1,74 @@
+// Copyright 2022 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+syntax = "proto3";
+
+package rpc_demo;
+
+message GetVersionResponse {
+ uint32 version_number = 1;
+}
+
+message ThreeAxisData {
+ float x = 1;
+ float y = 2;
+ float z = 3;
+}
+
+message SensorInfo {
+ string name = 1;
+ enum Type {
+ ACCEL = 0;
+ GYRO = 1;
+ MAG = 2;
+ }
+ Type type = 2;
+ uint32 sample_frequency_hz = 3;
+}
+
+message GetSensorListResponse {
+ repeated SensorInfo sensors = 1;
+}
+
+message UpdateSensorFrequencyRequest {
+ uint32 sensor_index = 1;
+ uint32 sample_frequency_hz = 2;
+}
+
+message UpdateSensorFrequencyResponse {
+ uint32 sample_frequency_hz = 1;
+}
+
+message GetSensorSamplesRequest {
+ uint32 sensor_index = 1;
+}
+
+message GetSensorSamplesResponse {
+ repeated ThreeAxisData data = 1;
+}
+
+message Empty {}
+
+enum RpcClientChannelId {
+ UNASSIGNED = 0;
+ DEFAULT = 1;
+}
+
+service DemoService {
+ rpc GetVersion(Empty) returns (GetVersionResponse) {}
+ rpc GetSensorList(Empty) returns (GetSensorListResponse) {}
+ rpc UpdateSensorFrequency(UpdateSensorFrequencyRequest)
+ returns (UpdateSensorFrequencyResponse) {}
+ rpc GetSensorSamples(GetSensorSamplesRequest)
+ returns (GetSensorSamplesResponse) {}
+}
diff --git a/samples/pw_rpc/sample.yaml b/samples/pw_rpc/sample.yaml
new file mode 100644
index 0000000..acf3ad4
--- /dev/null
+++ b/samples/pw_rpc/sample.yaml
@@ -0,0 +1,20 @@
+sample:
+ name: pw_rpc sample
+tests:
+ sample.pw_rpc:
+ tags: pw_rpc
+ platform_allow: native_posix
+ timeout: 10
+ harness: console
+ harness_config:
+ type: multi_line
+ ordered: true
+ regex:
+ - "(.*)Starting RPC demo(.*)"
+ - "(.*)Service waiting for data(.*)"
+ - "(.*)Starting pw_rpc service(.*)"
+ - "(.*)Service got packet!(.*)"
+ - "(.*)Client waiting for response(.*)"
+ - "(.*)Reading bytes(.*)"
+ - "(.*)Client got packet!(.*)"
+ - "(.*)Received version #5(.*)"
diff --git a/samples/pw_rpc/src/client_reader.cc b/samples/pw_rpc/src/client_reader.cc
new file mode 100644
index 0000000..0f4cbff
--- /dev/null
+++ b/samples/pw_rpc/src/client_reader.cc
@@ -0,0 +1,57 @@
+// Copyright 2022 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+#define PW_LOG_MODULE_NAME "ClientReader"
+
+#include "rpc_demo/client/client_reader.h"
+
+#include <pw_hdlc/decoder.h>
+#include <pw_hdlc/rpc_packets.h>
+#include <pw_log/log.h>
+#include <pw_status/status.h>
+
+namespace rpc_demo {
+
+ClientReader::ClientReader(pw::stream::Reader& rx, pw::rpc::Client* rpc_client)
+ : rx_(rx), rpc_client_(rpc_client), decoder_(decode_buffer_) {}
+
+bool ClientReader::ParsePacket() {
+ PW_LOG_INFO("Reading bytes...");
+ while (true) {
+ std::byte data;
+ if (auto result = rx_.Read(&data, 1); !result.ok()) {
+ PW_LOG_ERROR("Failed to read data");
+ continue;
+ }
+ PW_LOG_DEBUG("0x%02x", static_cast<uint8_t>(data));
+
+ pw::Result<pw::hdlc::Frame> result = decoder_.Process(data);
+ if (!result.ok()) {
+ continue;
+ }
+
+ pw::hdlc::Frame& frame = result.value();
+ if (frame.address() != pw::hdlc::kDefaultRpcAddress) {
+ return false;
+ }
+
+ PW_LOG_INFO("Client got packet!");
+ auto client_result = rpc_client_->ProcessPacket(frame.data());
+ PW_LOG_DEBUG("Client ProcessPacket status (%s)",
+ pw_StatusString(client_result));
+ decoder_.Clear();
+ return true;
+ }
+}
+
+} // namespace rpc_demo
diff --git a/samples/pw_rpc/src/demo_service.cc b/samples/pw_rpc/src/demo_service.cc
new file mode 100644
index 0000000..1707965
--- /dev/null
+++ b/samples/pw_rpc/src/demo_service.cc
@@ -0,0 +1,55 @@
+// Copyright 2022 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+#include "rpc_demo/service/demo_service.h"
+
+namespace rpc_demo {
+
+// Method definitions for pw_demo.DemoHostService.
+::pw::Status DemoService::GetVersion(const rpc_demo_Empty& request,
+ rpc_demo_GetVersionResponse& response) {
+ static_cast<void>(request);
+ response.version_number = VERSION;
+ return ::pw::OkStatus();
+}
+
+::pw::Status DemoService::GetSensorList(
+ const rpc_demo_Empty& request, rpc_demo_GetSensorListResponse& response) {
+ // TODO: Read the request as appropriate for your application
+ static_cast<void>(request);
+ // TODO: Fill in the response as appropriate for your application
+ static_cast<void>(response);
+ return ::pw::Status::Unimplemented();
+}
+
+::pw::Status DemoService::UpdateSensorFrequency(
+ const rpc_demo_UpdateSensorFrequencyRequest& request,
+ rpc_demo_UpdateSensorFrequencyResponse& response) {
+ // TODO: Read the request as appropriate for your application
+ static_cast<void>(request);
+ // TODO: Fill in the response as appropriate for your application
+ static_cast<void>(response);
+ return ::pw::Status::Unimplemented();
+}
+
+::pw::Status DemoService::GetSensorSamples(
+ const rpc_demo_GetSensorSamplesRequest& request,
+ rpc_demo_GetSensorSamplesResponse& response) {
+ // TODO: Read the request as appropriate for your application
+ static_cast<void>(request);
+ // TODO: Fill in the response as appropriate for your application
+ static_cast<void>(response);
+ return ::pw::Status::Unimplemented();
+}
+
+} // namespace rpc_demo
diff --git a/samples/pw_rpc/src/deque_stream.cc b/samples/pw_rpc/src/deque_stream.cc
new file mode 100644
index 0000000..e05df96
--- /dev/null
+++ b/samples/pw_rpc/src/deque_stream.cc
@@ -0,0 +1,37 @@
+// Copyright 2022 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+#include <mutex>
+#include "rpc_demo/deque_stream.h"
+
+pw::StatusWithSize DequeReadWriter::DoRead(pw::ByteSpan destination) {
+ size_t count = 0;
+ std::lock_guard lock(mutex_);
+
+ while (!buff_.empty() && count < destination.size_bytes()) {
+ destination[count++] = buff_.front();
+ buff_.pop_front();
+ }
+
+ auto status = (count == 0) ? pw::Status::OutOfRange() : pw::OkStatus();
+ return pw::StatusWithSize(status, count);
+}
+
+pw::Status DequeReadWriter::DoWrite(pw::ConstByteSpan data) {
+ std::lock_guard lock(mutex_);
+ for (size_t i = 0; i < data.size_bytes(); ++i) {
+ buff_.push_back(data[i]);
+ }
+
+ return pw::OkStatus();
+}
diff --git a/samples/pw_rpc/src/main.cc b/samples/pw_rpc/src/main.cc
new file mode 100644
index 0000000..25789a1
--- /dev/null
+++ b/samples/pw_rpc/src/main.cc
@@ -0,0 +1,83 @@
+// Copyright 2022 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+#define PW_LOG_MODULE_NAME "MAIN"
+
+#include <pw_hdlc/rpc_channel.h>
+#include <pw_hdlc/rpc_packets.h>
+#include <pw_log/log.h>
+#include <pw_status/status.h>
+
+#include "proto/demo.pb.h"
+#include "rpc_demo/client/client_reader.h"
+#include "rpc_demo/service/service_reader.h"
+#include "rpc_demo/rxtx.h"
+
+
+namespace {
+// Create the client channel output with a built-in buffer.
+pw::hdlc::RpcChannelOutput
+ client_to_service_channel_output(ClientToServiceStream().as_writer(),
+ pw::hdlc::kDefaultRpcAddress,
+ "HDLC client output");
+pw::hdlc::RpcChannelOutput
+ service_to_client_channel_output(ServiceToClientStream().as_writer(),
+ pw::hdlc::kDefaultRpcAddress,
+ "HDLC service output");
+
+// Configure the channel. Is this an output channel only?
+pw::rpc::Channel client_channels[] = {
+ pw::rpc::Channel::Create<rpc_demo_RpcClientChannelId_DEFAULT,
+ rpc_demo_RpcClientChannelId>(
+ &client_to_service_channel_output),
+};
+pw::rpc::Channel service_channels[] = {
+ pw::rpc::Channel::Create<rpc_demo_RpcClientChannelId_DEFAULT,
+ rpc_demo_RpcClientChannelId>(
+ &service_to_client_channel_output),
+};
+
+pw::rpc::Client rpc_client(client_channels);
+rpc_demo::pw_rpc::nanopb::DemoService::Client
+ service_client(rpc_client, rpc_demo_RpcClientChannelId_DEFAULT);
+
+void GetVersionResponse(const rpc_demo_GetVersionResponse &response,
+ pw::Status status) {
+ if (status.ok()) {
+ PW_LOG_INFO("Received version #%u", response.version_number);
+ } else {
+ PW_LOG_ERROR("Failed to get version number, status: %s",
+ pw_StatusString(status));
+ }
+}
+
+} // namespace
+
+void main(void) {
+ PW_LOG_INFO("Starting RPC demo...");
+ rpc_demo::ServiceReader ec_service(
+ &ClientToServiceStream().as_reader(),
+ &ServiceToClientStream().as_writer(),
+ reinterpret_cast<pw::hdlc::RpcChannelOutput*>(
+ &service_to_client_channel_output),
+ service_channels);
+ rpc_demo::ClientReader ap_client(ServiceToClientStream().as_reader(), &rpc_client);
+
+ auto response = service_client.GetVersion(rpc_demo_Empty_init_default,
+ GetVersionResponse);
+ PW_ASSERT(response.active());
+ PW_LOG_INFO("Service waiting for data...");
+ PW_ASSERT(ec_service.ParsePacket());
+ PW_LOG_INFO("Client waiting for response...");
+ PW_ASSERT(ap_client.ParsePacket());
+}
diff --git a/samples/pw_rpc/src/rxtx.cc b/samples/pw_rpc/src/rxtx.cc
new file mode 100644
index 0000000..52c1982
--- /dev/null
+++ b/samples/pw_rpc/src/rxtx.cc
@@ -0,0 +1,28 @@
+// Copyright 2022 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+#include "rpc_demo/rxtx.h"
+#include "rpc_demo/deque_stream.h"
+
+namespace {
+DequeReadWriter client_to_service_stream;
+DequeReadWriter service_to_client_stream;
+} // namespace
+
+pw::stream::ReaderWriter &ClientToServiceStream() {
+ return client_to_service_stream;
+}
+pw::stream::ReaderWriter &ServiceToClientStream() {
+ return service_to_client_stream;
+}
diff --git a/samples/pw_rpc/src/service_reader.cc b/samples/pw_rpc/src/service_reader.cc
new file mode 100644
index 0000000..aaf888d
--- /dev/null
+++ b/samples/pw_rpc/src/service_reader.cc
@@ -0,0 +1,70 @@
+// Copyright 2022 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+#define PW_LOG_MODULE_NAME "ServiceReader"
+
+#include "rpc_demo/service/service_reader.h"
+
+#include <pw_hdlc/rpc_channel.h>
+#include <pw_hdlc/rpc_packets.h>
+#include <pw_log/log.h>
+#include <pw_span/span.h>
+
+namespace rpc_demo {
+
+ServiceReader::ServiceReader(pw::stream::Reader* reader,
+ pw::stream::Writer* writer,
+ pw::hdlc::RpcChannelOutput* output,
+ pw::span<pw::rpc::Channel> channels)
+ : reader_(reader), writer_(writer), output_(output), channels_(channels) {}
+
+bool ServiceReader::ParsePacket() {
+ std::array<std::byte, CONFIG_RPC_BUFFER_SIZE> decode_buffer{};
+ pw::rpc::Server server(channels_);
+ pw::hdlc::Decoder decoder(decode_buffer);
+ DemoService service;
+
+ server.RegisterService(service);
+
+ // Input buffer
+ PW_LOG_INFO("Starting pw_rpc service...");
+ while (true) {
+ std::byte data;
+
+ if (auto result = reader_->Read(&data, 1); !result.ok()) {
+ std::this_thread::sleep_for(std::chrono::milliseconds(50));
+ continue;
+ }
+
+ PW_LOG_DEBUG("0x%02x", static_cast<uint8_t>(data));
+ auto result = decoder.Process(data);
+ if (!result.ok()) {
+ continue;
+ }
+
+ auto& frame = result.value();
+ if (frame.address() != pw::hdlc::kDefaultRpcAddress) {
+ decoder.Clear();
+ return false;
+ }
+
+ PW_LOG_INFO("Service got packet!");
+ auto server_result = server.ProcessPacket(frame.data());
+ PW_LOG_DEBUG("Client ProcessPacket status (%s)",
+ pw_StatusString(server_result));
+ decoder.Clear();
+ return true;
+ }
+}
+
+} // namespace rpc_demo