Start RPC example

Change-Id: Ie39a707573c615ceeb3650c036331683757cbc50
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/quickstart/cmake/+/422273
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..378eac2
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+build
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..f7242af
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,102 @@
+cmake_minimum_required(VERSION 3.20)
+project(rpc-demo C CXX)
+
+add_compile_definitions(PW_SYSTEM_ENABLE_TRACE_SERVICE=0)
+
+# 1. Fetch Nanopb
+include(FetchContent)
+FetchContent_Declare(
+  nanopb
+  GIT_REPOSITORY https://github.com/nanopb/nanopb.git
+  GIT_TAG nanopb-0.4.9.1
+)
+FetchContent_MakeAvailable(nanopb)
+
+set(dir_pw_third_party_nanopb "${nanopb_SOURCE_DIR}" CACHE PATH "" FORCE)
+
+FetchContent_Declare(
+  pigweed
+  GIT_REPOSITORY https://pigweed.googlesource.com/pigweed/pigweed
+  GIT_TAG 05195263e8e0b43c692152538f0554c16874012e
+)
+
+# 3. Configure Pigweed backends.
+# We must set these backend variables in the CMake CACHE before calling
+# FetchContent_MakeAvailable(pigweed). This is because MakeAvailable automatically
+# calls add_subdirectory(), which defines Pigweed targets that depend on these
+# backends. Setting them here avoids the need to use the deprecated
+# FetchContent_Populate() pattern to retrieve the source path before adding the
+# subdirectory.
+set(pw_log_BACKEND "pw_log_basic" CACHE STRING "" FORCE)
+set(pw_assert.check_BACKEND "pw_assert_basic.check_backend" CACHE STRING "" FORCE)
+set(pw_assert.assert_BACKEND "pw_assert.print_and_abort_assert_backend" CACHE STRING "" FORCE)
+set(pw_assert_basic.handler_BACKEND "pw_assert_basic.basic_handler" CACHE STRING "" FORCE)
+set(pw_sys_io_BACKEND "pw_sys_io_stdio" CACHE STRING "" FORCE)
+set(pw_sync.mutex_BACKEND "pw_sync_stl.mutex_backend" CACHE STRING "" FORCE)
+set(pw_sync.interrupt_spin_lock_BACKEND "pw_sync_stl.interrupt_spin_lock" CACHE STRING "" FORCE)
+set(pw_chrono.system_clock_BACKEND "pw_chrono_stl.system_clock" CACHE STRING "" FORCE)
+set(pw_chrono.system_timer_BACKEND "pw_chrono_stl.system_timer" CACHE STRING "" FORCE)
+set(pw_thread.sleep_BACKEND "pw_thread_stl.sleep" CACHE STRING "" FORCE)
+set(pw_thread.id_BACKEND "pw_thread_stl.id" CACHE STRING "" FORCE)
+set(pw_thread.thread_BACKEND "pw_thread_stl.thread" CACHE STRING "" FORCE)
+set(pw_thread.yield_BACKEND "pw_thread_stl.yield" CACHE STRING "" FORCE)
+set(pw_thread.thread_iteration_BACKEND "pw_thread_stl.thread_iteration" CACHE STRING "" FORCE)
+
+# pw_system backends
+set(pw_system.rpc_server_BACKEND "pw_system.hdlc_rpc_server" CACHE STRING "" FORCE)
+set(pw_system.io_BACKEND "pw_system.socket_target_io" CACHE STRING "" FORCE)
+set(pw_system.target_hooks_BACKEND "pw_system.stl_target_hooks" CACHE STRING "" FORCE)
+
+# pw_sync backends needed by pw_system
+set(pw_sync.binary_semaphore_BACKEND "pw_sync_stl.binary_semaphore_backend" CACHE STRING "" FORCE)
+set(pw_sync.thread_notification_BACKEND "pw_sync.binary_semaphore_thread_notification_backend" CACHE STRING "" FORCE)
+set(pw_sync.timed_thread_notification_BACKEND "pw_sync.binary_semaphore_timed_thread_notification_backend" CACHE STRING "" FORCE)
+
+# Disable experimental editions globally for Pigweed proto compiler
+set(pw_protobuf_compiler_GENERATE_PROTOS_ARGS "--no-experimental-editions" CACHE STRING "" FORCE)
+
+set(pw_protobuf_compiler_PYTHON_OUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/generated_python" CACHE PATH "" FORCE)
+
+# 4. Make Pigweed available
+FetchContent_MakeAvailable(pigweed)
+
+# 5. Set PIGWEED_DIR for our own use
+set(PIGWEED_DIR "${pigweed_SOURCE_DIR}")
+set(ENV{PW_ROOT} "${PIGWEED_DIR}")
+
+# Include Pigweed CMake helper
+include(${PIGWEED_DIR}/pw_build/pigweed.cmake)
+include(${PIGWEED_DIR}/pw_protobuf_compiler/proto.cmake)
+
+# Define a target to group all python protos needed for the console.
+add_custom_target(generate_python_protos
+  DEPENDS
+    math_service.python
+    pw_protobuf.codegen_protos.python
+    pw_protobuf.common_proto.python
+    pw_protobuf.status_proto.python
+    pw_protobuf.field_options_proto.python
+    pw_rpc.protos.python
+)
+
+# 6. Declare the proto library.
+pw_proto_library(math_service
+  SOURCES
+    math_service.proto
+)
+
+# 8. Define Demo Executable
+add_executable(rpc_demo main.cc)
+target_link_libraries(rpc_demo
+  PRIVATE
+    math_service.nanopb_rpc
+    pw_rpc.nanopb.echo_service
+    pw_system.init
+    pw_system.io
+    pw_system.log
+    pw_system.rpc_server
+    pw_system.work_queue
+    pw_system.target_hooks
+    pw_log
+)
+add_dependencies(rpc_demo generate_python_protos)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 677be08..80e3307 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -17,13 +17,23 @@
 
 ## Contributing changes and submitting code reviews
 
-All changes require review, including changes by project members.
+1. Clone this repo:
 
-For detailed instructions on how to contribute changes,
-see [the Gerrit docs](https://gerrit-review.googlesource.com/Documentation/intro-user.html).
+   ```
+   git clone https://pigweed.googlesource.com/pigweed/quickstart/cmake
+   ```
+
+2. Complete the [Gerrit setup](https://pigweed.dev/contributing/#gerrit-setup)
+   part of Pigweed's upstream contributor process.
+
+3. [Create a change](https://pigweed.dev/contributing/#create-your-change)
+   and get it [reviewed](https://pigweed.dev/contributing/#code-review).
+
+If you're unfamiliar with Gerrit (our code review tool) see [User
+Guide](https://gerrit-review.googlesource.com/Documentation/intro-user.html).
 
 ## Community guidelines
 
 This project observes the following community guidelines:
 
-  * [Google's Open Source Community Guidelines](https://opensource.google/conduct/)
+* [Google's Open Source Community Guidelines](https://opensource.google/conduct/)
diff --git a/OWNERS b/OWNERS
index 528c611..7a3a52d 100644
--- a/OWNERS
+++ b/OWNERS
@@ -4,4 +4,5 @@
 #
 # Syntax for OWNERS files can be found at:
 # https://pigweed-review.googlesource.com/plugins/code-owners/Documentation/backend-find-owners.html#syntax
-*
+kayce@google.com
+frolv@google.com
diff --git a/README.md b/README.md
index c5ae3d3..8cc190c 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,164 @@
-Pigweed Open Source Template Repository
-=======================================
+# Pigweed: minimal CMake example
 
-This repository is a template that we will use when creating new open source
-repositories for Pigweed.
+A minimal, complete example of integrating [Pigweed](https://pigweed.dev)
+into a CMake project.
+
+## Overview
+
+`main.cc` creates an RPC server that listens on a local TCP socket for RPC
+requests. Using [`pw_console`](https://pigweed.dev/pw_console) as an RPC client
+you will send an RPC request to this server. Communication over the socket uses
+HDLC framing. All RPC requests and responses are structured as protobufs.
+All protobuf generation is configured inside of `CMakeLists.txt`.
+
+## Limitations
+
+* This example has only been verified to work on Debian.
+
+* The example is only set up to build a host-side simulation of the firmware.
+  It does not set up a cross-compilation toolchain or provide flashing tools.
+
+* The firmware build intentionally avoids a
+  [bootstrap](https://pigweed.dev/pw_env_setup) to demonstrate manual setup
+  of Pigweed modules in a CMake project. Most of the toolchain must be
+  installed globally and available on the system path. We use a bootstrap
+  to speed up the manual testing (i.e. bootstrapping in order to spin up
+  the `pw_console` RPC client quickly) but it's not required on the
+  client-side, either.
+
+## Quickstart
+
+### 1. Install dependencies
+
+Linux:
+
+```
+sudo apt-get install -y build-essential cmake ninja-build \
+  protobuf-compiler python3 python3-protobuf python3-serial
+```
+
+Note: Pigweed and Nanopb (on-device protobuf codegen library) are managed in
+`CMakeLists.txt` via
+[`FetchContent`](https://cmake.org/cmake/help/latest/module/FetchContent.html).
+
+### 2. Build
+
+Configure the build:
+
+```
+cmake -B build -S . -G Ninja
+```
+
+Run the build:
+
+```
+cmake --build build --target rpc_demo
+```
+
+Note: The Python protobuf codegen also runs as part of this build.
+The `pw_console` client will use this generated Python to communicate
+with the simulated firmware device.
+
+### 3. Run
+
+1. Open a console tab and run `./build/rpc_demo`.
+
+   The simulated firmware device boots up and starts listening on
+   `localhost:33000`.
+
+2. Open another console tab and `cd` into the root of the Pigweed repo.
+
+   ```
+   cd third_party/pigweed
+   ```
+
+3. Bootstrap an environment for `pw_console`.
+
+   ```
+   . bootstrap.sh
+   ```
+
+   Note: Bootstrap is only used here to simplify and speed up the
+   manual testing part of the example. It's possible to use `pw_console`
+   without a bootstrap.
+
+4. Start a `pw_console` session.
+
+   ```
+   python3 ../../../run_console.py --socket-addr localhost:33000
+   ```
+
+5. In the Python REPL of `pw_console` invoke the `Echo` RPC.
+   You should see the server respond with a tuple containing `Status.OK`
+   followed by a response containing the same message that you sent.
+
+   ```
+   >>> device.rpcs.pw.rpc.EchoService.Echo(msg="hello")
+   (Status.OK, pw.rpc.EchoMessage(msg="hello"))
+   ```
+
+6. Now try invoking the `Add` RPC. You should see the server respond
+   with a tuple containing `Status.OK` again followed by the correct sum.
+
+   ```
+   >>> device.rpcs.rpc.math.MathService.Add(a=3, b=5)
+   (Status.OK, rpc.math.AddResponse(result=8))
+   ```
+
+7. Go back to the tab running the simulated firmware device. You
+   should see the server logging messages.
+
+   ```
+   Awaiting connection on port 33000
+   Client connected
+   INF  pw_system initialized, main thread sleeping...
+   INF  System init
+   INF  Registering RPC services
+   INF  Starting threads
+   INF  Running RPC server
+   INF  Registering custom MathService
+   INF  Server received Add: 5 + 3
+   INF  Simulated device is still alive
+   ```
+
+## Next steps
+
+* Talk to us in the `#cmake-build` channel of our
+  [Discord](https://pigweed.dev/community/).
+
+* Learn more about [Pigweed's CMake
+  support](https://pigweed.dev/build/cmake/).
+
+## Appendix: Files
+
+Source files:
+
+*   `//CMakeLists.txt`: Defines the CMake build system. It fetches dependencies
+    (Nanopb, Pigweed), configures Pigweed backends, sets up C++ and Python
+    protobuf codegen, and defines the `rpc_demo` executable target.
+
+*   `//main.cc`: The entry point for the C++ application. It initializes
+    `pw_system` and implements `UserAppInit` to register the custom `MathService`.
+
+*   `//math_service.proto`: Defines the protobuf service `MathService` with an
+    `Add` method.
+
+*   `//math_service.h`: Implements the `MathService` defined in `math_service.proto`.
+
+*   `//run_console.py`: Python script to launch `pw_console` configured to talk
+    to the running application. It sets up the Python path to include generated
+    protos.
+
+Generated files:
+
+*   `//build/math_service/nanopb/math_service.pb.h` and `//build/math_service/nanopb/math_service.pb.c`: Nanopb
+    generated C code for the `math_service.proto` messages.
+
+*   `//build/math_service/nanopb_rpc/math_service.rpc.pb.h`: Pigweed RPC generated C++ header for the
+    `math_service.proto` service.
+
+*   `//build/generated_python/python/math_service_pb2.py`: Python protobuf module generated
+    from `math_service.proto`, used by `run_console.py`.
+
+*   `//build/python_packages/`: Contains Python protobuf modules generated from
+    Pigweed internal protos, required by Pigweed RPC plugins.
diff --git a/main.cc b/main.cc
new file mode 100644
index 0000000..d23237d
--- /dev/null
+++ b/main.cc
@@ -0,0 +1,34 @@
+#include "pw_system/init.h"
+#include "pw_system/rpc_server.h"
+#include "math_service.h"
+#include "pw_rpc/echo_service_nanopb.h"
+#include "pw_log/log.h"
+#include "pw_thread/sleep.h"
+#include <chrono>
+
+MathService math_service;
+pw::rpc::EchoService echo_service;
+
+namespace pw::system {
+
+// This will run once after pw::system::Init() completes. This callback must
+// return or it will block the work queue.
+void UserAppInit() {
+  PW_LOG_INFO("Registering custom MathService");
+  GetRpcServer().RegisterService(math_service);
+  PW_LOG_INFO("Registering EchoService");
+  GetRpcServer().RegisterService(echo_service);
+}
+
+}  // namespace pw::system
+
+int main() {
+  pw::system::Init();
+
+  PW_LOG_INFO("pw_system initialized, main thread sleeping...");
+  while (true) {
+    pw::this_thread::sleep_for(std::chrono::seconds(10));
+    PW_LOG_INFO("Simulated device is still alive");
+  }
+  return 0;
+}
diff --git a/math_service.h b/math_service.h
new file mode 100644
index 0000000..d16b2ae
--- /dev/null
+++ b/math_service.h
@@ -0,0 +1,16 @@
+#pragma once
+
+#include "math_service.rpc.pb.h"
+#include "pw_log/log.h"
+#include "pw_status/status.h"
+
+class MathService final
+    : public rpc::math::pw_rpc::nanopb::MathService::Service<MathService> {
+ public:
+  pw::Status Add(const rpc_math_AddRequest& request,
+                 rpc_math_AddResponse& response) {
+    PW_LOG_INFO("Server received Add: %d + %d", static_cast<int>(request.a), static_cast<int>(request.b));
+    response.result = request.a + request.b;
+    return pw::OkStatus();
+  }
+};
diff --git a/math_service.proto b/math_service.proto
new file mode 100644
index 0000000..ab031e5
--- /dev/null
+++ b/math_service.proto
@@ -0,0 +1,16 @@
+syntax = "proto3";
+
+package rpc.math;
+
+message AddRequest {
+  int32 a = 1;
+  int32 b = 2;
+}
+
+message AddResponse {
+  int32 result = 1;
+}
+
+service MathService {
+  rpc Add(AddRequest) returns (AddResponse);
+}
diff --git a/run_console.py b/run_console.py
new file mode 100755
index 0000000..c42d144
--- /dev/null
+++ b/run_console.py
@@ -0,0 +1,53 @@
+#!/usr/bin/env python3
+import sys
+from pathlib import Path
+
+# Calculate paths relative to this script
+SCRIPT_DIR = Path(__file__).resolve().parent
+GENERATED_PYTHON_DIR = SCRIPT_DIR / 'build' / 'generated_python' / 'python'
+
+# Ensure the generated python protos are importable
+if GENERATED_PYTHON_DIR.exists():
+    sys.path.append(str(GENERATED_PYTHON_DIR))
+else:
+    print(f"Error: Generated python directory not found at {GENERATED_PYTHON_DIR}")
+    print("Please build the project first (./build.sh)")
+    sys.exit(1)
+
+try:
+    import math_service_pb2
+except ImportError as e:
+    print(f"Error: Failed to import math_service_pb2: {e}")
+    print("Ensure you have built the project.")
+    sys.exit(1)
+
+try:
+    import pw_system.console
+except ImportError:
+    print("Error: Failed to import pw_system.console.")
+    print("Please run this script from a Pigweed bootstrapped environment:")
+    print("  cd path/to/pigweed")
+    print("  . ./bootstrap.sh")
+    print("  python path/to/this/project/run_console.py --socket-addr default")
+    sys.exit(1)
+
+def main():
+    # If no connection arguments are provided, default to socket
+    if not any(arg in sys.argv for arg in ['-s', '--socket-addr', '-d', '--device']):
+        sys.argv.extend(['--socket-addr', 'default'])
+        print("Defaulting to --socket-addr default")
+
+    # Force --no-rpc-logging since the demo server doesn't support RPC logging
+    if '--rpc-logging' in sys.argv:
+        print("Warning: RPC logging is not supported by this demo server. Forcing --no-rpc-logging.")
+        sys.argv.remove('--rpc-logging')
+    if '--no-rpc-logging' not in sys.argv:
+        sys.argv.append('--no-rpc-logging')
+
+    # Pass our compiled proto to the console
+    sys.exit(pw_system.console.main(compiled_protos=[math_service_pb2]))
+
+
+
+if __name__ == '__main__':
+    main()