pw_rpc: Allow configuring a channel at runtime

This adds a method to the RPC channel which can be called once to
configure it with an ID and output at runtime, to support use cases
where the channel's parameters are not known at compile time.

Change-Id: Ic1a9dcb3e70b8247b9aa178be1fcaf337a7b4f94
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/40760
Reviewed-by: Wyatt Hepler <hepler@google.com>
Commit-Queue: Alexei Frolov <frolv@google.com>
diff --git a/pw_rpc/docs.rst b/pw_rpc/docs.rst
index daece79..ae17d5a 100644
--- a/pw_rpc/docs.rst
+++ b/pw_rpc/docs.rst
@@ -265,6 +265,22 @@
   // will be set when the channel is used.
   pw::rpc::Channel dynamic_channel;
 
+Sometimes, the ID and output of a channel are not known at compile time as they
+depend on information stored on the physical device. To support this use case, a
+dynamically-assignable channel can be configured once at runtime with an ID and
+output.
+
+.. code-block:: cpp
+
+  // Create a dynamic channel without a compile-time ID or output.
+  pw::rpc::Channel dynamic_channel;
+
+  void Init() {
+    // Called during boot to pull the channel configuration from the system.
+    dynamic_channel.Configure(GetChannelId(), some_output);
+  }
+
+
 Services
 ========
 A service is a logical grouping of RPCs defined within a .proto file. ``pw_rpc``
diff --git a/pw_rpc/public/pw_rpc/channel.h b/pw_rpc/public/pw_rpc/channel.h
index a8823ea..7776ca8 100644
--- a/pw_rpc/public/pw_rpc/channel.h
+++ b/pw_rpc/public/pw_rpc/channel.h
@@ -90,6 +90,27 @@
     return Create<static_cast<uint32_t>(kIntId)>(output);
   }
 
+  // Manually configures a dynamically-assignable channel with a specified ID
+  // and output. This is useful when a channels parameters are not known until
+  // runtime. This can only be called once per channel.
+  constexpr void Configure(uint32_t id, ChannelOutput& output) {
+    PW_ASSERT(id_ == kUnassignedChannelId);
+    PW_ASSERT(id != kUnassignedChannelId);
+    id_ = id;
+    output_ = &output;
+  }
+
+  // Configure using an enum value channel ID.
+  template <typename T,
+            typename = std::enable_if_t<std::is_enum_v<T>>,
+            typename U = std::underlying_type_t<T>>
+  constexpr void Configure(T id, ChannelOutput& output) {
+    static_assert(sizeof(U) <= sizeof(uint32_t));
+    const U kIntId = static_cast<U>(id);
+    PW_ASSERT(kIntId > 0);
+    return Configure(static_cast<uint32_t>(kIntId), output);
+  }
+
   constexpr uint32_t id() const { return id_; }
   constexpr bool assigned() const { return id_ != kUnassignedChannelId; }