pw_rpc: Allow using enums for channel IDs

This adds a Channel::Create overload which accepts an enum ID value, to
allow cleaner organization of channel IDs.

Change-Id: Id29fba5c36133c1720a0f74e42e86770e8ad5366
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/40320
Reviewed-by: Wyatt Hepler <hepler@google.com>
Commit-Queue: Alexei Frolov <frolv@google.com>
diff --git a/pw_rpc/channel_test.cc b/pw_rpc/channel_test.cc
index 5e16ee6..7687a2d 100644
--- a/pw_rpc/channel_test.cc
+++ b/pw_rpc/channel_test.cc
@@ -42,6 +42,18 @@
                              5 /* method */ + 2 /* payload key */ +
                              2 /* status */;
 
+enum class ChannelId {
+  kOne = 1,
+  kTwo = 2,
+};
+
+TEST(Channel, Create_FromEnum) {
+  constexpr rpc::Channel one = Channel::Create<ChannelId::kOne>(nullptr);
+  constexpr rpc::Channel two = Channel::Create<ChannelId::kTwo>(nullptr);
+  static_assert(one.id() == 1);
+  static_assert(two.id() == 2);
+}
+
 TEST(Channel, TestPacket_ReservedSizeMatchesMinEncodedSizeBytes) {
   EXPECT_EQ(kReservedSize, kTestPacket.MinEncodedSizeBytes());
 }
diff --git a/pw_rpc/docs.rst b/pw_rpc/docs.rst
index eca6870..fc04165 100644
--- a/pw_rpc/docs.rst
+++ b/pw_rpc/docs.rst
@@ -238,6 +238,33 @@
         server, hdlc_channel_output, input_buffer);
   }
 
+Channels
+========
+``pw_rpc`` sends all of its packets over channels. These are logical,
+application-layer routes used to tell the RPC system where a packet should go.
+
+Channels over a client-server connection must all have a unique ID, which can be
+assigned statically at compile time or dynamically.
+
+.. code-block:: cpp
+
+  // Creating a channel with the static ID 3.
+  pw::rpc::Channel static_channel = pw::rpc::Channel::Create<3>(&output);
+
+  // Grouping channel IDs within an enum can lead to clearer code.
+  enum ChannelId {
+    kUartChannel = 1,
+    kSpiChannel = 2,
+  };
+
+  // Creating a channel with a static ID defined within an enum.
+  pw::rpc::Channel another_static_channel =
+      pw::rpc::Channel::Create<ChannelId::kUartChannel>(&output);
+
+  // Creating a channel with a dynamic ID (note that no output is provided; it
+  // will be set when the channel is used.
+  pw::rpc::Channel dynamic_channel;
+
 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 d17cd52..6d87020 100644
--- a/pw_rpc/public/pw_rpc/channel.h
+++ b/pw_rpc/public/pw_rpc/channel.h
@@ -15,6 +15,7 @@
 
 #include <cstdint>
 #include <span>
+#include <type_traits>
 
 #include "pw_assert/assert.h"
 #include "pw_status/status.h"
@@ -76,6 +77,19 @@
     return Channel(kId, output);
   }
 
+  // Creates a channel with a static ID from an enum value.
+  template <auto kId,
+            typename T = decltype(kId),
+            typename = std::enable_if_t<std::is_enum_v<T>>,
+            typename U = std::underlying_type_t<T>>
+  constexpr static Channel Create(ChannelOutput* output) {
+    constexpr U kIntId = static_cast<U>(kId);
+    static_assert(kIntId >= 0, "Channel ID cannot be negative");
+    static_assert(kIntId <= std::numeric_limits<uint32_t>::max(),
+                  "Channel ID must fit in a uint32");
+    return Create<static_cast<uint32_t>(kIntId)>(output);
+  }
+
   constexpr uint32_t id() const { return id_; }
   constexpr bool assigned() const { return id_ != kUnassignedChannelId; }