diff --git a/CLC-qthing/SiliconTorch/CyanBus.cpp b/CLC-qthing/SiliconTorch/CyanBus.cpp
index b53af7da117711e9e22f0f4e334845ac9aa76c64..d3b6b1cb03d4905dc75465ecd481c331ba37425e 100644
--- a/CLC-qthing/SiliconTorch/CyanBus.cpp
+++ b/CLC-qthing/SiliconTorch/CyanBus.cpp
@@ -1,6 +1,7 @@
 #include "CyanBus.hpp"
 
 // C++ system level
+#include <vector>
 #include <cstring>     // memset, strncmp
 #include <cstdlib>     // TODO: is this for memcpy?
 #include <functional>
@@ -31,6 +32,13 @@ static u16 getLength(const u8* buffer) {
 }
 
 
+// TODO: do some locking for thread-safety (or build some generalized solution for this kind of counter 😉)
+static u8 nextUartChannel() {
+  static u8 uartChannel = 1;
+  return uartChannel++;
+}
+
+
 namespace SiliconTorch {
 
   namespace CyanBus {
@@ -39,7 +47,46 @@ namespace SiliconTorch {
     const char* const HEADER = "fxCyan";
 
 
-    CyanBus::CyanBus(u8 tx, u8 rx, u8 de, u8 re, u32 baudRate, u8 uartChannel) : tx(tx), rx(rx), de(de), re(re), uartChannel(uartChannel) {
+    static std::vector<CyanBus*> interfaces;
+
+    u32 registerCyanBusInterface(CyanBus* iface) {
+      if (iface == NULL) return 0xFFFFFFFF;
+
+      u32 number = 0;
+
+      for (const auto& cb : interfaces) {
+        if (cb == iface) {
+          ESP_LOGW(TAG, "equality of (obj*)& and (obj*): ✅  Please remove this line from CyanBus.cpp!");
+          return number;
+        }
+        number++;
+      }
+
+      interfaces.push_back(iface);
+
+      return number;
+    }
+
+    u32 countCyanBusInterfaces() {
+      return interfaces.size();
+    }
+
+    CyanBus* getInterface(u32 number) {
+      if (number >= countCyanBusInterfaces()) return NULL;
+      else                                    return interfaces[number];
+    }
+
+    u32 createCyanBusInterface(u8 tx, u8 rx, u8 de, u8 re, u32 baudRate) {
+      return registerCyanBusInterface (
+        new CyanBus(tx, rx, de, re, baudRate)
+      );
+    }
+
+
+    CyanBus::CyanBus(u8 tx, u8 rx, u8 de, u8 re, u32 baudRate, u8 uartChannel) : tx(tx), rx(rx), de(de), re(re) {
+
+      if (uartChannel == UART_CHANNEL_AUTO) this->uartChannel = nextUartChannel();
+      else                                  this->uartChannel = uartChannel;
 
       u64 bitMask = 0;
 
diff --git a/CLC-qthing/SiliconTorch/CyanBus.hpp b/CLC-qthing/SiliconTorch/CyanBus.hpp
index f6c8d003e0a50635ad84128c2074b283b5b470cd..2bd8e895a90b64999dc688ba3bdc794684f56e02 100644
--- a/CLC-qthing/SiliconTorch/CyanBus.hpp
+++ b/CLC-qthing/SiliconTorch/CyanBus.hpp
@@ -27,6 +27,8 @@ namespace SiliconTorch {
     // CyanBus envelope header
     extern const char* const HEADER;
 
+    constexpr u8 UART_CHANNEL_AUTO = 0xFF;
+
 
     typedef struct {
       const u8* payload;
@@ -39,9 +41,17 @@ namespace SiliconTorch {
     } PacketMetrics;
 
 
+    class CyanBus;
+
+    u32 createCyanBusInterface(u8 tx, u8 rx, u8 de, u8 re, u32 baudRate = 115200);
+    u32 registerCyanBusInterface(CyanBus* iface);
+    u32 countCyanBusInterfaces();
+    CyanBus* getInterface(u32 number = 0);
+
+
     class CyanBus {
       public:
-        CyanBus(u8 tx, u8 rx, u8 de, u8 re, u32 baudRate = 115200, u8 uartChannel = 1);
+        CyanBus(u8 tx, u8 rx, u8 de, u8 re, u32 baudRate = 115200, u8 uartChannel = UART_CHANNEL_AUTO);
 
         u32 getBaudRate();
         void setBaudRate(u32 baudRate);
diff --git a/CLC-qthing/SiliconTorch/Service/CyanBus.cpp b/CLC-qthing/SiliconTorch/Service/CyanBus.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..5c0ffa8fb3831e66c150a8106cd932dfbdbe2758
--- /dev/null
+++ b/CLC-qthing/SiliconTorch/Service/CyanBus.cpp
@@ -0,0 +1,90 @@
+#include "CyanBus.hpp"
+
+// C++ system level
+#include <cstdio>     // sprintf
+// #include <functional>
+
+// ESP32 specific
+#include "esp_log.h"
+#include "driver/gpio.h"
+
+// project specific
+#include <Types.hpp>
+#include "SiliconTorch/NVSExplorer.hpp"
+
+// qthing stuff
+#include <qthing>
+#include "SiliconTorch/CyanBus.hpp"
+
+
+
+
+namespace SiliconTorch {
+
+  namespace Service {
+
+    void CyanBus::init() {
+      setName("CyanBus");
+      setNameSpace("CyanBus");
+    }
+
+    void CyanBus::start() {
+
+      for (u8 idx = 0; idx < MAX_INTERFACES; idx++) {
+
+        InterfaceCfg cfg = readInterfaceData(idx);
+
+        if (!cfg.valid) break;
+
+        ESP_LOGI(getName().c_str(), "Configuring: CyanBus{ tx[ %d ] rx[ %d ] de[ %d ] re[ %d ] baud[ %d ] interface[ cb%d ] }",
+          cfg.tx,
+          cfg.rx,
+          cfg.de,
+          cfg.re,
+          cfg.baud,
+          idx
+        );
+
+        SiliconTorch::CyanBus::createCyanBusInterface(cfg.tx, cfg.rx, cfg.de, cfg.re, cfg.baud);
+
+      }
+
+    }
+
+
+    CyanBus::InterfaceCfg CyanBus::readInterfaceData(u32 number) const {
+
+      NVSExplorer::NVSExplorer& nvs = NVSExplorer::NVSExplorer::instance();
+
+      InterfaceCfg cfg;
+
+      char tmp[32];
+
+      std::snprintf(tmp, sizeof(tmp), "cb%d_tx", number);
+      cfg.tx = nvs.getUnsignedInt(getNameSpace(), std::string(tmp), cfg.tx);
+
+      std::snprintf(tmp, sizeof(tmp), "cb%d_rx", number);
+      cfg.rx = nvs.getUnsignedInt(getNameSpace(), std::string(tmp), cfg.rx);
+
+      std::snprintf(tmp, sizeof(tmp), "cb%d_de", number);
+      cfg.de = nvs.getUnsignedInt(getNameSpace(), std::string(tmp), cfg.de);
+
+      std::snprintf(tmp, sizeof(tmp), "cb%d_re", number);
+      cfg.re = nvs.getUnsignedInt(getNameSpace(), std::string(tmp), cfg.re);
+
+      std::snprintf(tmp, sizeof(tmp), "cb%d_baud", number);
+      cfg.baud = nvs.getUnsignedInt(getNameSpace(), std::string(tmp), cfg.baud);
+
+
+      cfg.valid = cfg.tx < 0xFF
+                && cfg.rx < 0xFF
+                && cfg.de < 0xFF
+                && cfg.re < 0xFF;
+
+      return cfg;
+    }
+
+  }
+}
+
+
diff --git a/CLC-qthing/SiliconTorch/Service/CyanBus.hpp b/CLC-qthing/SiliconTorch/Service/CyanBus.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..93746fe71509b34279f98328ee854c792c4dc90e
--- /dev/null
+++ b/CLC-qthing/SiliconTorch/Service/CyanBus.hpp
@@ -0,0 +1,61 @@
+#pragma once
+
+// C++ system level
+// #include <cstring>     // memset, strncmp
+// #include <cstdlib>     // TODO: is this for memcpy?
+// #include <functional>
+
+// ESP32 specific
+#include "esp_log.h"
+
+// project specific
+#include <Types.hpp>
+#include "Service.hpp"
+
+// qthing stuff
+#include "SiliconTorch/FxCyanRGB8.hpp"
+// #include <qthing>
+
+
+namespace SiliconTorch {
+
+  namespace Service {
+
+    class CyanBus : public ServiceManager::Service {
+
+      public:
+
+
+        void init();
+
+        void start();
+
+
+        static const u8 MAX_INTERFACES = 2;  // Limitted to the available UART HW units
+
+
+        typedef struct {
+          u8   tx    = 0xFF;
+          u8   rx    = 0xFF;
+          u8   de    = 0xFF;
+          u8   re    = 0xFF;
+          u32  baud  = 115200;
+          bool valid = false;
+        } InterfaceCfg;
+
+
+
+        InterfaceCfg readInterfaceData(u32 number) const;
+
+
+        // TODO: should we block copy/assignment by default…?
+        CyanBus() {};
+        CyanBus(const CyanBus&) = delete;
+        CyanBus& operator=(CyanBus const&) = delete;
+
+      private:
+
+    };
+
+  }
+}
diff --git a/CLC-qthing/SiliconTorch/Service/__services__.cpp b/CLC-qthing/SiliconTorch/Service/__services__.cpp
index def22a373d801058aebd3c935e8b2b118a8211fa..1c4dd7aab4f1ceafc7f83f71e64b4c100a6a109c 100644
--- a/CLC-qthing/SiliconTorch/Service/__services__.cpp
+++ b/CLC-qthing/SiliconTorch/Service/__services__.cpp
@@ -2,6 +2,7 @@
 
 // our services
 #include "FxCyanF.hpp"
+#include "CyanBus.hpp"
 #include "FxPublish.hpp"
 #include "CyanStripe.hpp"
 
@@ -15,6 +16,7 @@ namespace SiliconTorch {
     void registerSiliconTorchServices(ServiceManager* mgr) {
 
       mgr->registerService(new SiliconTorch::Service::FxCyanF());
+      mgr->registerService(new SiliconTorch::Service::CyanBus());
       mgr->registerService(new SiliconTorch::Service::FxPublish());
       mgr->registerService(new SiliconTorch::Service::CyanStripe::CyanStripe());