diff --git a/src/common/kafka/AR51Serializer.cpp b/src/common/kafka/AR51Serializer.cpp
index f542a086e933d9611243933e70128918607572d3..4984d2fdcea93349404523274bd60970ad81112c 100644
--- a/src/common/kafka/AR51Serializer.cpp
+++ b/src/common/kafka/AR51Serializer.cpp
@@ -25,7 +25,7 @@ AR51Serializer::AR51Serializer(
 }
 
 
-nonstd::span<const uint8_t> & AR51Serializer::serialize(uint8_t * Data, int DataLength) {
+nonstd::span<const uint8_t> & AR51Serializer::serialize(uint8_t *Data, int DataLength) {
   FBBuilder.Reset();
   auto DataBuffer = FBBuilder.CreateVector(Data, DataLength);
 
diff --git a/src/common/kafka/EV44Serializer.h b/src/common/kafka/EV44Serializer.h
index 5c55cbc717751051b6489c1414e71d14d064b5eb..fb7601c95c9dcee667121f81c3b9cf90dc3aa763 100644
--- a/src/common/kafka/EV44Serializer.h
+++ b/src/common/kafka/EV44Serializer.h
@@ -84,6 +84,10 @@ public:
 
   TSCTimer ProduceTimer, DebugTimer;
 
+  // Counters for causes of calls to produce()
+  int64_t ProduceCausePulseChange;
+  int64_t ProduceCauseMaxEventsReached;
+
 private:
   /// \todo should this not be predefined in terms of jumbo frame?
   size_t MaxEvents{0};
diff --git a/src/common/time/CMakeLists.txt b/src/common/time/CMakeLists.txt
index 791e7acc56845bc2eaa4f161204e179e9187135a..4573fa489a1723f0992426ac3b00857b3d02a336 100644
--- a/src/common/time/CMakeLists.txt
+++ b/src/common/time/CMakeLists.txt
@@ -10,6 +10,7 @@ set(esstime_obj_SRC
   Timer.cpp
   TimeString.cpp
   TSCTimer.cpp
+  CheckTimer.cpp
 )
 
 set(esstime_obj_INC
@@ -17,6 +18,7 @@ ESSTime.h
 Timer.h
 TimeString.h
 TSCTimer.h
+CheckTimer.h
 )
 
 add_library(EssTimingLib OBJECT
diff --git a/src/common/time/CheckTimer.cpp b/src/common/time/CheckTimer.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..f6be2245d7ea916353ea91c26be6a89a2cbbf2f0
--- /dev/null
+++ b/src/common/time/CheckTimer.cpp
@@ -0,0 +1,40 @@
+// Copyright (C) 2024 European Spallation Source, ERIC. See LICENSE file
+//===----------------------------------------------------------------------===//
+///
+/// \file
+///
+/// \brief Implementation of the check timer class which uses the standard chrono
+/// library to provide a high resolution timer.
+//===----------------------------------------------------------------------===//
+
+#include <common/time/CheckTimer.h>
+
+///
+CheckTimer::CheckTimer(void) {
+  reset();
+}
+
+/// \param Timeout
+CheckTimer::CheckTimer(uint64_t Timeout) : TimeoutNS(Timeout) {
+  reset();
+}
+
+/// Determine if a timeout has occured and reset timer
+bool CheckTimer::timeout(void) {
+  if (timetsc() >= TimeoutNS) {
+    reset();
+    return true;
+  }
+  return false;
+}
+
+///
+void CheckTimer::reset(void) {
+  T0TimePoint = std::chrono::high_resolution_clock::now();
+}
+
+///
+uint64_t CheckTimer::timetsc(void) {
+  auto Now = std::chrono::high_resolution_clock::now();
+  return std::chrono::duration_cast<std::chrono::nanoseconds>(Now - T0TimePoint).count();
+}
diff --git a/src/common/time/CheckTimer.h b/src/common/time/CheckTimer.h
new file mode 100644
index 0000000000000000000000000000000000000000..09b3736fd50574f57457ead2bdebda6313b17130
--- /dev/null
+++ b/src/common/time/CheckTimer.h
@@ -0,0 +1,37 @@
+// Copyright (C) 2024 European Spallation Source, ERIC. See LICENSE file
+//===----------------------------------------------------------------------===//
+///
+/// \file
+///
+/// \brief platform independent equivalent replacement of wrapper for the cheap and fast time stamp counter (TSC)
+///
+/// Uses the standard chrono library to provide a high resolution timer.
+//===----------------------------------------------------------------------===//
+
+#pragma once
+
+#include <chrono>
+#include <cstdint>
+
+class CheckTimer {
+
+public:
+  /// Create a check timer without timeout value
+  CheckTimer(void);
+
+  /// Create a check timer with a timeout value in nanoseconds
+  CheckTimer(uint64_t TimeOut);
+
+  // Has timeout occurred? Then reset timer
+  bool timeout(void);
+
+  void reset(void); ///< record current time_point
+
+  uint64_t timetsc(void); ///< time since T0
+
+private:
+  std::chrono::high_resolution_clock::time_point T0TimePoint;
+
+  // If timeout not set use this 'infinite' value
+  uint64_t TimeoutNS{0xffffffffffffffff};
+};
diff --git a/src/modules/bifrost/geometry/BifrostGeometry.cpp b/src/modules/bifrost/geometry/BifrostGeometry.cpp
index 914729d746399a34e525d3c068afdf6658963d82..84a3ba4f2740986d5f56db7ab80d41c083aefd0b 100644
--- a/src/modules/bifrost/geometry/BifrostGeometry.cpp
+++ b/src/modules/bifrost/geometry/BifrostGeometry.cpp
@@ -16,7 +16,7 @@
 
 namespace Caen {
 
-BifrostGeometry::BifrostGeometry(Config &CaenConfiguration) {
+  BifrostGeometry::BifrostGeometry(Config &CaenConfiguration) {
   ESSGeom = new ESSGeometry(900, 15, 1, 1);
   setResolution(CaenConfiguration.Resolution);
   MaxRing = CaenConfiguration.MaxRing;
@@ -124,4 +124,22 @@ uint32_t BifrostGeometry::calcPixel(DataParser::CaenReadout &Data) {
   return pixel;
 }
 
+size_t BifrostGeometry::numSerializers() const {
+  return TripletsPerRing * (MaxRing + 1); // MaxRing is likely 2 (but [0, 1, 2] are all valid)
+}
+
+size_t BifrostGeometry::calcSerializer(DataParser::CaenReadout &Data) const {
+  // FiberID = _physical_ Ring (logical_ring/2)
+  // Group == triplet number
+  return Data.FiberId / 2 * TripletsPerRing + Data.Group;
+}
+
+std::string BifrostGeometry::serializerName(size_t Index) const {
+  auto ring_id = Index / TripletsPerRing;
+  auto tube_id = Index - ring_id * TripletsPerRing;
+  auto arc = tube_id / 3u;
+  auto triplet = (tube_id % 3u) + ring_id * 3u;
+  return fmt::format("arc={};triplet={}", arc, triplet);
+}
+
 } // namespace Caen
diff --git a/src/modules/bifrost/geometry/BifrostGeometry.h b/src/modules/bifrost/geometry/BifrostGeometry.h
index 1f7d552cc4a1141e40b14c1dd6ce6e1b9df52cc2..b0601b36d6f9601955a4a742aa5d6d10f29e443f 100644
--- a/src/modules/bifrost/geometry/BifrostGeometry.h
+++ b/src/modules/bifrost/geometry/BifrostGeometry.h
@@ -26,13 +26,13 @@
 namespace Caen {
 class BifrostGeometry : public Geometry {
 public:
-  BifrostGeometry(Config &CaenConfiguration);
+  explicit BifrostGeometry(Config &CaenConfiguration);
 
   ///\brief virtual method inherited from base class
-  uint32_t calcPixel(DataParser::CaenReadout &Data);
+  uint32_t calcPixel(DataParser::CaenReadout &Data) override;
 
   ///\brief virtual method inherited from base class
-  bool validateData(DataParser::CaenReadout &Data);
+  bool validateData(DataParser::CaenReadout &Data) override;
 
   /// \brief return the global x-offset for the given identifiers
   /// \param Ring logical ring as defined in the ICD
@@ -50,6 +50,10 @@ public:
   /// or (-1, -1.0) if invalid
   std::pair<int, double> calcUnitAndPos(int Group, int AmpA, int AmpB);
 
+  [[nodiscard]] size_t numSerializers() const override;
+  [[nodiscard]] size_t calcSerializer(DataParser::CaenReadout &Data) const override;
+  [[nodiscard]] std::string serializerName(size_t Index) const override;
+
   const int UnitsPerGroup{3};
   const int TripletsPerRing{15};
   int UnitPixellation{100}; ///< Number of pixels along a single He tube.
diff --git a/src/modules/caen/CaenBase.cpp b/src/modules/caen/CaenBase.cpp
index d294f45a25ca764a9ecb498c3961691ecdd90c9a..05a8bee1430b1eda95cee419084e5834ca0cd958 100644
--- a/src/modules/caen/CaenBase.cpp
+++ b/src/modules/caen/CaenBase.cpp
@@ -8,17 +8,14 @@
 
 #include <cinttypes>
 #include <common/RuntimeStat.h>
-#include <common/debug/Log.h>
-#include <common/debug/Trace.h>
 #include <common/detector/EFUArgs.h>
 #include <common/kafka/KafkaConfig.h>
-#include <common/system/Socket.h>
-#include <common/time/TSCTimer.h>
 #include <common/time/TimeString.h>
 #include <common/time/Timer.h>
 #include <modules/caen/CaenBase.h>
 #include <modules/caen/CaenInstrument.h>
 #include <unistd.h>
+#include <common/time/CheckTimer.h>
 
 // #undef TRC_LEVEL
 // #define TRC_LEVEL TRC_L_DEB
@@ -38,6 +35,7 @@ CaenBase::CaenBase(BaseSettings const &settings,
   Stats.create("receive.bytes", ITCounters.RxBytes);
   Stats.create("receive.dropped", ITCounters.FifoPushErrors);
   Stats.create("receive.fifo_seq_errors", Counters.FifoSeqErrors);
+  Stats.create("transmit.monitor_packets", Counters.TxRawReadoutPackets);
 
   // ESS Readout
   Stats.create("essheader.error_header", Counters.ErrorESSHeaders);
@@ -129,7 +127,7 @@ CaenBase::CaenBase(BaseSettings const &settings,
 ///
 /// \brief Normal processing thread
 void CaenBase::processingThread() {
-  if (EFUSettings.KafkaTopic == "") {
+  if (EFUSettings.KafkaTopic.empty()) {
     XTRACE(INIT, ERR, "No kafka topic set, using DetectorName + _detector");
     EFUSettings.KafkaTopic = EFUSettings.DetectorName + "_detector";
   }
@@ -142,32 +140,32 @@ void CaenBase::processingThread() {
     EventProducer.produce(DataBuffer, Timestamp);
   };
 
-  Serializer = new EV44Serializer(KafkaBufferSize, "caen", Produce);
-
-  Stats.create("produce.cause.pulse_change",
-               Serializer->stats().ProduceRefTimeTriggered);
-  Stats.create("produce.cause.max_events_reached",
-               Serializer->stats().ProduceTriggeredMaxEvents);
-
+  // Create the instrument
   CaenInstrument Caen(Counters, EFUSettings);
-  Caen.setSerializer(Serializer); // would rather have this in CaenInstrument
+  // and its serializers
+  Serializers.reserve(Caen.Geom->numSerializers());
+  for (size_t i=0; i < Caen.Geom->numSerializers(); ++i){
+    Serializers.emplace_back(std::make_shared<EV44Serializer>(KafkaBufferSize, Caen.Geom->serializerName(i), Produce));
+  }
+  // give the instrument shared pointers to the serializers
+  Caen.setSerializers(Serializers);
 
-  Producer EventProducerII(EFUSettings.KafkaBroker, "CAEN_debug",
+  // Create the raw-data monitor producer and serializer
+  Producer MonitorProducer(EFUSettings.KafkaBroker, EFUSettings.KafkaDebugTopic,
                            KafkaCfg.CfgParms);
-
-  auto ProduceII = [&EventProducerII](auto DataBuffer, auto Timestamp) {
-    EventProducerII.produce(DataBuffer, Timestamp);
+  auto ProduceMonitor = [&MonitorProducer](auto DataBuffer, auto Timestamp) {
+    MonitorProducer.produce(DataBuffer, Timestamp);
   };
 
-  SerializerII = new EV44Serializer(KafkaBufferSize, "caen", ProduceII);
-  Caen.setSerializerII(
-      SerializerII); // would rather have this in CaenInstrument
+  MonitorSerializer = std::make_shared<AR51Serializer>(EFUSettings.DetectorName, ProduceMonitor);
 
   unsigned int DataIndex;
-  TSCTimer ProduceTimer(EFUSettings.UpdateIntervalSec * 1000000 * TSC_MHZ);
 
   RuntimeStat RtStat({ITCounters.RxPackets, Counters.Events,
                       Counters.KafkaStats.produce_bytes_ok});
+  // Create the periodic timer for producing messages, in case of low event rate
+  // TSCTimer ProduceTimer(EFUSettings.UpdateIntervalSec * 1000000 * TSC_MHZ); // x86 specific
+  CheckTimer ProduceTimer(EFUSettings.UpdateIntervalSec * 1'000'000'000);
 
   while (runThreads) {
     if (InputFifo.pop(DataIndex)) { // There is data in the FIFO - do processing
@@ -202,6 +200,16 @@ void CaenBase::processingThread() {
       // Process readouts, generate (and produce) events
       Caen.processReadouts();
 
+      // send monitoring data
+      if (ITCounters.RxPackets % EFUSettings.MonitorPeriod <
+          EFUSettings.MonitorSamples) {
+        XTRACE(PROCESS, DEB, "Serialize and stream monitor data for packet %lu",
+               ITCounters.RxPackets);
+        MonitorSerializer->serialize((uint8_t *)DataPtr, DataLen);
+        MonitorSerializer->produce();
+        Counters.TxRawReadoutPackets++;
+      }
+
       /// \todo This could be moved and done less frequently
       Counters.Parser = Caen.CaenParser.Stats;
       Counters.TimeStats = Caen.ESSReadoutParser.Packet.Time.Stats;
@@ -213,21 +221,33 @@ void CaenBase::processingThread() {
       usleep(10);
     }
 
-    if (ProduceTimer.timeout()) {
+    if (ProduceTimer.timeout()){
       // XTRACE(DATA, DEB, "Serializer timer timed out, producing message now");
       RuntimeStatusMask =
           RtStat.getRuntimeStatusMask({ITCounters.RxPackets, Counters.Events,
                                        Counters.KafkaStats.produce_bytes_ok});
+      // Produce messages if there are events
+      for (auto &Serializer: Serializers) {
+        Serializer->produce();
+      }
 
-      Serializer->produce();
-      SerializerII->produce();
+      // update counter statistics
       Counters.ProduceCauseTimeout++;
+      Counters.ProduceCausePulseChange = std::transform_reduce(
+          Serializers.begin(), Serializers.end(), 0, std::plus<>(),
+          [](auto &Serializer) { return Serializer->ProduceCausePulseChange; }
+      );
+      Counters.ProduceCauseMaxEventsReached = std::transform_reduce(
+          Serializers.begin(), Serializers.end(), 0, std::plus<>(),
+          [](auto &Serializer) { return Serializer->ProduceCauseMaxEventsReached; }
+      );
     }
+
     /// Kafka stats update - common to all detectors
     /// don't increment as Producer & Serializer keep absolute count
     Counters.KafkaStats = EventProducer.stats;
   }
+
   XTRACE(INPUT, ALW, "Stopping processing thread.");
-  return;
 }
 } // namespace Caen
diff --git a/src/modules/caen/CaenBase.h b/src/modules/caen/CaenBase.h
index d18645d13d87fcd6129d98dec513b505013cc8fe..70bcea32e05d15c00957ed88ef4942421510d31b 100644
--- a/src/modules/caen/CaenBase.h
+++ b/src/modules/caen/CaenBase.h
@@ -11,6 +11,7 @@
 #include <caen/CaenCounters.h>
 #include <common/detector/Detector.h>
 #include <common/kafka/EV44Serializer.h>
+#include <common/kafka/AR51Serializer.h>
 
 namespace Caen {
 
@@ -26,8 +27,8 @@ public:
   struct CaenCounters Counters;
 
 protected:
-  EV44Serializer *Serializer;
-  EV44Serializer *SerializerII;
+  std::vector<std::shared_ptr<EV44Serializer>> Serializers;
+  std::shared_ptr<AR51Serializer> MonitorSerializer;
 };
 
 } // namespace Caen
diff --git a/src/modules/caen/CaenCounters.h b/src/modules/caen/CaenCounters.h
index cbe3aedb70d5ab0d5aa08d716283edd3fab89174..70b05bdade616fafcf57c7aebe867410ca7436c1 100644
--- a/src/modules/caen/CaenCounters.h
+++ b/src/modules/caen/CaenCounters.h
@@ -11,6 +11,7 @@
 
 #include <cinttypes>
 #include <common/kafka/Producer.h>
+#include <cstdint>
 #include <modules/caen/geometry/CDCalibration.h>
 #include <modules/caen/geometry/Geometry.h>
 #include <modules/caen/readout/DataParser.h>
@@ -21,12 +22,12 @@ struct CaenCounters {
   // Processing Counters - accessed in processing thread
 
   // System counters
-  int64_t FifoSeqErrors;
-  int64_t ProcessingIdle;
+  int64_t FifoSeqErrors{0};
+  int64_t ProcessingIdle{0};
 
   // ESSReadout parser
   struct ESSReadout::ESSHeaderStats ReadoutStats;
-  int64_t ErrorESSHeaders;
+  int64_t ErrorESSHeaders{0};
 
   // Caen DataParser
   struct DataParser::Stats Parser;
@@ -36,17 +37,21 @@ struct CaenCounters {
   struct Geometry::Stats Geom;
 
   // Events
-  int64_t Events;
-  int64_t PixelErrors;
-  int64_t EventsUdder;
+  int64_t Events{0};
+  int64_t PixelErrors{0};
+  int64_t EventsUdder{0};
+  int64_t TxRawReadoutPackets{0};
+  int64_t SerializerErrors{0};
 
   // Time
-  struct ESSReadout::ESSReferenceTime::Stats_t TimeStats;
+  struct ESSReadout::ESSReferenceTime::Stats_t TimeStats{};
 
   // Identification of the cause of produce calls
-  int64_t ProduceCauseTimeout;
+  int64_t ProduceCauseTimeout{0};
+  int64_t ProduceCausePulseChange{0};
+  int64_t ProduceCauseMaxEventsReached{0};
 
   // Kafka stats below are common to all detectors
-  struct Producer::ProducerStats KafkaStats;
+  struct Producer::ProducerStats KafkaStats{};
 } __attribute__((aligned(64)));
 } // namespace Caen
diff --git a/src/modules/caen/CaenInstrument.cpp b/src/modules/caen/CaenInstrument.cpp
index 0ec6fbb50f1f4afa8b2d4a0248552d5d2f1525d2..79433fab36071560d8d451e437bd7e7d9b673f90 100644
--- a/src/modules/caen/CaenInstrument.cpp
+++ b/src/modules/caen/CaenInstrument.cpp
@@ -118,10 +118,8 @@ void CaenInstrument::processReadouts() {
   XTRACE(DATA, DEB, "Reference time is %" PRIi64,
          ESSReadoutParser.Packet.Time.getRefTimeUInt64());
   /// \todo sometimes PrevPulseTime maybe?
-  Serializer->checkAndSetReferenceTime(
-      ESSReadoutParser.Packet.Time.getRefTimeUInt64());
-  SerializerII->checkAndSetReferenceTime(
-      ESSReadoutParser.Packet.Time.getRefTimeUInt64());
+  auto packet_ref_time = static_cast<int64_t>(ESSReadoutParser.Packet.Time.getRefTimeUInt64());
+  for (auto & Serializer: Serializers) Serializer->checkAndSetReferenceTime(packet_ref_time);
 
   /// Traverse readouts, calculate pixels
   for (auto &Data : CaenParser.Result) {
@@ -157,17 +155,22 @@ void CaenInstrument::processReadouts() {
            Data.TimeHigh, Data.TimeLow, TimeOfFlight, Data.DataSeqNum,
            Data.Group, Data.AmpA, Data.AmpB, Data.AmpC, Data.AmpD);
 
-    // Calculate pixelid and apply calibration
+    // Calculate pixel and apply calibration
     uint32_t PixelId = calcPixel(Data);
 
+    // Determine the correct serializer for this pixel
+    auto SerializerId = Geom->calcSerializer(Data);
+
     if (PixelId == 0) {
       XTRACE(EVENT, WAR, "Pixel error");
       counters.PixelErrors++;
+    } else if (SerializerId >= Serializers.size()) {
+      XTRACE(EVENT, WAR, "Serializer identification error");
+      counters.SerializerErrors++;
     } else {
       XTRACE(EVENT, DEB, "Pixel %u, TOF %u", PixelId, TimeOfFlight);
-      Serializer->addEvent(TimeOfFlight, PixelId);
+      Serializers[SerializerId]->addEvent(static_cast<int32_t>(TimeOfFlight), static_cast<int32_t>(PixelId));
       counters.Events++;
-      SerializerII->addEvent(Data.AmpA + Data.AmpB + Data.AmpC + Data.AmpD, 0);
     }
 
   } // for()
diff --git a/src/modules/caen/CaenInstrument.h b/src/modules/caen/CaenInstrument.h
index 2390204a0e724b561150e4bb1f78230531bb817b..9f099635795a1a8406281b90b3ea75e30a1b45b0 100644
--- a/src/modules/caen/CaenInstrument.h
+++ b/src/modules/caen/CaenInstrument.h
@@ -37,12 +37,9 @@ public:
   /// \brief Generates Events from Readouts, and adds them to a serializer
   void processReadouts();
 
-  /// \brief Sets the serializer to send events to
-  void setSerializer(EV44Serializer *serializer) { Serializer = serializer; }
-
-  /// \brief Sets the second serializer to send events to, recording Amp values
-  void setSerializerII(EV44Serializer *serializer) {
-    SerializerII = serializer;
+  /// \brief Set All serializers at once
+  void setSerializers(std::vector<std::shared_ptr<EV44Serializer>> &serializers) {
+    Serializers = serializers;
   }
 
   /// \brief Caen pixel calculations
@@ -61,8 +58,7 @@ public:
   ESSReadout::Parser ESSReadoutParser;
   DataParser CaenParser;
   Geometry *Geom;
-  EV44Serializer *Serializer;
-  EV44Serializer *SerializerII;
+  std::vector<std::shared_ptr<EV44Serializer>> Serializers;
   std::shared_ptr<ReadoutFile> DumpFile;
 };
 
diff --git a/src/modules/caen/geometry/Geometry.h b/src/modules/caen/geometry/Geometry.h
index d2f3404e0f44e0075b4e5fd5442ba7285c791953..cb76994e5a9699a130e21d6a39a888e53e219105 100644
--- a/src/modules/caen/geometry/Geometry.h
+++ b/src/modules/caen/geometry/Geometry.h
@@ -41,6 +41,16 @@ public:
   /// \param Data CaenReadout to check validity of.
   virtual bool validateData(DataParser::CaenReadout &Data) = 0;
 
+  /// \brief return the total number of serializers used by the geometry
+  [[nodiscard]] virtual size_t numSerializers() const = 0;
+
+  /// \brief calculate the serializer index for the given readout
+  /// \param Data CaenReadout to calculate serializer index for
+  [[nodiscard]] virtual size_t calcSerializer(DataParser::CaenReadout &Data) const = 0;
+
+  /// \brief return the name of the serializer at the given index
+  [[nodiscard]] virtual std::string serializerName(size_t Index) const = 0;
+
   struct Stats {
     int64_t RingErrors{0};
     int64_t RingMappingErrors{0};
diff --git a/src/modules/cspec/geometry/CspecGeometry.h b/src/modules/cspec/geometry/CspecGeometry.h
index 725184823cfe39dc41215f6f231cb5db3ecdd268..0aa19ee419667332e0b7fdc6daa73830a48d98dc 100644
--- a/src/modules/cspec/geometry/CspecGeometry.h
+++ b/src/modules/cspec/geometry/CspecGeometry.h
@@ -22,9 +22,9 @@
 namespace Caen {
 class CspecGeometry : public Geometry {
 public:
-  CspecGeometry(Config &CaenConfiguration);
-  uint32_t calcPixel(DataParser::CaenReadout &Data);
-  bool validateData(DataParser::CaenReadout &Data);
+  explicit CspecGeometry(Config &CaenConfiguration);
+  uint32_t calcPixel(DataParser::CaenReadout &Data) override;
+  bool validateData(DataParser::CaenReadout &Data) override;
 
   /// \brief return the global x-offset for the given identifiers
   int xOffset(int Ring, int Group);
@@ -34,5 +34,17 @@ public:
 
   /// \brief return the position along the unit (tube for CSPEC)
   int posAlongUnit(int AmpA, int AmpB);
+
+
+  /// \brief return the total number of serializers used by the geometry
+  [[nodiscard]] inline size_t numSerializers() const override {return 1;}
+
+  /// \brief calculate the serializer index for the given readout
+  /// \param Data CaenReadout to calculate serializer index for
+  [[nodiscard]] inline size_t calcSerializer(DataParser::CaenReadout &) const override {return 0;}
+
+  /// \brief return the name of the serializer at the given index
+  [[nodiscard]] inline std::string serializerName(size_t) const override {return "caen";}
+
 };
 } // namespace Caen
diff --git a/src/modules/loki/geometry/LokiGeometry.h b/src/modules/loki/geometry/LokiGeometry.h
index 36a6985423ee155e9857081c033dd5c2dade0f64..a0a0cc0aa95d2a614680c0146ba5e8dd6ce85bad 100644
--- a/src/modules/loki/geometry/LokiGeometry.h
+++ b/src/modules/loki/geometry/LokiGeometry.h
@@ -29,7 +29,7 @@ namespace Caen {
 
 class LokiGeometry : public Geometry {
 public:
-  LokiGeometry(Config &CaenConfiguration);
+  explicit LokiGeometry(Config &CaenConfiguration);
 
   /// \brief return the position along the tube
   /// \param AmpA amplitude A from readout data
@@ -54,13 +54,24 @@ public:
 
   void setCalibration(CDCalibration Calib) { CaenCDCalibration = Calib; }
 
-  uint32_t calcPixel(DataParser::CaenReadout &Data);
-  bool validateData(DataParser::CaenReadout &Data);
+  uint32_t calcPixel(DataParser::CaenReadout &Data) override;
+  bool validateData(DataParser::CaenReadout &Data) override;
 
   // Holds the parsed configuration
   Config Conf;
 
   const std::pair<int, float> InvalidPos{-1, -1.0};
+
+  /// \brief return the total number of serializers used by the geometry
+  [[nodiscard]] inline size_t numSerializers() const override {return 1;}
+
+  /// \brief calculate the serializer index for the given readout
+  /// \param Data CaenReadout to calculate serializer index for
+  [[nodiscard]] inline size_t calcSerializer(DataParser::CaenReadout &) const override {return 0;}
+
+  /// \brief return the name of the serializer at the given index
+  [[nodiscard]] inline std::string serializerName(size_t) const override {return "caen";}
+
 };
 
 } // namespace Caen
diff --git a/src/modules/miracles/geometry/MiraclesGeometry.h b/src/modules/miracles/geometry/MiraclesGeometry.h
index 00e957cfa25636a807a5bf7caec7da8a3504b60f..80fc69a35d145671484d7eacd9abedf1b2864e8a 100644
--- a/src/modules/miracles/geometry/MiraclesGeometry.h
+++ b/src/modules/miracles/geometry/MiraclesGeometry.h
@@ -23,9 +23,9 @@
 namespace Caen {
 class MiraclesGeometry : public Geometry {
 public:
-  MiraclesGeometry(Config &CaenConfiguration);
-  uint32_t calcPixel(DataParser::CaenReadout &Data);
-  bool validateData(DataParser::CaenReadout &Data);
+  explicit MiraclesGeometry(Config &CaenConfiguration);
+  uint32_t calcPixel(DataParser::CaenReadout &Data) override;
+  bool validateData(DataParser::CaenReadout &Data) override;
 
   /// \brief return local x-coordinate from amplitudes
   int xCoord(int Ring, int Tube, int AmpA, int AmpB);
@@ -37,5 +37,16 @@ public:
 
   /// \brief return the position along the tube
   int posAlongUnit(int AmpA, int AmpB);
+
+  /// \brief return the total number of serializers used by the geometry
+  [[nodiscard]] inline size_t numSerializers() const override {return 1;}
+
+  /// \brief calculate the serializer index for the given readout
+  /// \param Data CaenReadout to calculate serializer index for
+  [[nodiscard]] inline size_t calcSerializer(DataParser::CaenReadout &) const override {return 0;}
+
+  /// \brief return the name of the serializer at the given index
+  [[nodiscard]] inline std::string serializerName(size_t) const override {return "caen";}
+
 };
 } // namespace Caen