From e2764f16289f4a90ef6dfdb84652a293b70975d8 Mon Sep 17 00:00:00 2001
From: Dominik Smith <d.smith@gsi.de>
Date: Wed, 21 Jun 2023 12:41:11 +0000
Subject: [PATCH] Rich unpacker in cbm::algo

---
 algo/CMakeLists.txt                       |   2 +
 algo/detectors/rich/RichReadoutConfig.cxx | 355 ++++++++++++++++++++++
 algo/detectors/rich/RichReadoutConfig.h   |  89 ++++++
 algo/detectors/rich/UnpackRich.cxx        | 322 ++++++++++++++++++++
 algo/detectors/rich/UnpackRich.h          | 250 +++++++++++++++
 algo/unpack/Unpack.cxx                    |  22 ++
 algo/unpack/Unpack.h                      |   9 +
 7 files changed, 1049 insertions(+)
 create mode 100644 algo/detectors/rich/RichReadoutConfig.cxx
 create mode 100644 algo/detectors/rich/RichReadoutConfig.h
 create mode 100644 algo/detectors/rich/UnpackRich.cxx
 create mode 100644 algo/detectors/rich/UnpackRich.h

diff --git a/algo/CMakeLists.txt b/algo/CMakeLists.txt
index 78fa9029f8..9d8de75311 100644
--- a/algo/CMakeLists.txt
+++ b/algo/CMakeLists.txt
@@ -35,6 +35,8 @@ set(SRCS
   detectors/trd/UnpackTrd.cxx
   detectors/trd2d/Trd2dReadoutConfig.cxx
   detectors/trd2d/UnpackTrd2d.cxx
+  detectors/rich/RichReadoutConfig.cxx
+  detectors/rich/UnpackRich.cxx
   global/Reco.cxx
 )
 
diff --git a/algo/detectors/rich/RichReadoutConfig.cxx b/algo/detectors/rich/RichReadoutConfig.cxx
new file mode 100644
index 0000000000..dd188e6b02
--- /dev/null
+++ b/algo/detectors/rich/RichReadoutConfig.cxx
@@ -0,0 +1,355 @@
+/* Copyright (C) 2022 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Volker Friese, Dominik Smith [committer] */
+
+#include "RichReadoutConfig.h"
+
+#include <cassert>
+#include <iomanip>
+
+using std::setw;
+
+namespace cbm::algo
+{
+
+
+  // ---  Constructor  ------------------------------------------------------------------
+  RichReadoutConfig::RichReadoutConfig() { Init(); }
+  // ------------------------------------------------------------------------------------
+
+
+  // ---   Destructor   -----------------------------------------------------------------
+  RichReadoutConfig::~RichReadoutConfig() {}
+  // ------------------------------------------------------------------------------------
+
+
+  // ---   Equipment IDs   --------------------------------------------------------------
+  std::vector<uint16_t> RichReadoutConfig::GetEquipmentIds()
+  {
+    std::vector<uint16_t> result;
+    for (auto& entry : fReadoutMap)
+      result.push_back(entry.first);
+    return result;
+  }
+  // ------------------------------------------------------------------------------------
+
+
+  // ---   Number of elinks for a component / equipment   -------------------------------
+  size_t RichReadoutConfig::GetNumElinks(uint16_t equipmentId)
+  {
+    size_t result = 0;
+    auto it       = fReadoutMap.find(equipmentId);
+    if (it != fReadoutMap.end()) result = fReadoutMap[equipmentId].size();
+    return result;
+  }
+  // ------------------------------------------------------------------------------------
+
+
+  // ---   Total number of elinks for STS   ---------------------------------------------
+  size_t RichReadoutConfig::GetNumElinks()
+  {
+    size_t result = 0;
+    for (auto& entry : fReadoutMap) {
+      result += entry.second.size();
+    }
+    return result;
+  }
+  // ------------------------------------------------------------------------------------
+
+
+  // ---  Initialise the mapping structure   --------------------------------------------
+  void RichReadoutConfig::Init()
+  {
+    // This here refers to the mCBM 2021 setup.
+    // Taken from CbmMcbm2018RichPar in combination with macro/beamtime/mcbm2021/mRich.par
+
+    // Constants
+    const uint16_t numComp          = 1;   // Number of components
+    const uint16_t numElinksPerComp = 74;  // Number of elinks per component
+    const uint16_t numChanPerElink  = 33;  // Number of channels per Elink
+
+    // Equipment IDs for each component
+    // This number is written to the data stream (MicrosliceDescriptor).
+    uint16_t eqId[numComp] = {12289};
+
+    uint32_t TRBaddresses[numElinksPerComp] = {
+      0xc000, 0xc001, 0x7000, 0x7001, 0x7010, 0x7011, 0x7020, 0x7021, 0x7030, 0x7031, 0x7040, 0x7041, 0x7050,
+      0x7051, 0x7060, 0x7061, 0x7070, 0x7071, 0x7080, 0x7081, 0x7100, 0x7101, 0x7110, 0x7111, 0x7120, 0x7121,
+      0x7130, 0x7131, 0x7140, 0x7141, 0x7150, 0x7151, 0x7160, 0x7161, 0x7170, 0x7171, 0x7180, 0x7181, 0x7200,
+      0x7201, 0x7210, 0x7211, 0x7220, 0x7221, 0x7230, 0x7231, 0x7240, 0x7241, 0x7250, 0x7251, 0x7260, 0x7261,
+      0x7270, 0x7271, 0x7280, 0x7281, 0x7300, 0x7301, 0x7310, 0x7311, 0x7320, 0x7321, 0x7330, 0x7331, 0x7340,
+      0x7341, 0x7350, 0x7351, 0x7360, 0x7361, 0x7370, 0x7371, 0x7380, 0x7381};
+
+    double ToTshifts[numElinksPerComp][numChanPerElink] = {
+      {0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00,
+       0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00},
+      {0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00,
+       0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00},
+      {0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00,
+       0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00},
+      {0.00,  15.05, 13.85, 14.15, 12.05, 15.05, 12.35, 11.35, 13.75, 13.85, 11.95,
+       14.75, 16.15, 16.35, 15.35, 8.15,  9.55,  6.05,  3.85,  5.65,  7.85,  8.65,
+       6.85,  7.15,  7.35,  8.05,  9.15,  7.25,  5.85,  7.15,  5.35,  6.95,  8.75},
+      {0.00,  13.85, 13.05, 13.25, 11.35, 14.75, 11.75, 10.85, 13.35, 13.25, 11.55,
+       13.85, 15.75, 15.75, 14.55, 7.65,  8.65,  5.65,  0.00,  5.65,  7.35,  0.00,
+       6.45,  6.85,  6.75,  7.35,  8.55,  6.35,  5.35,  6.35,  4.65,  5.85,  8.15},
+      {0.00,  14.15, 13.15, 13.65, 11.75, 14.65, 11.85, 10.35, 13.35, 13.15, 11.55,
+       14.15, 15.65, 15.65, 14.65, 7.45,  8.85,  5.75,  3.75,  0.00,  7.55,  8.45,
+       6.85,  6.75,  7.15,  7.75,  8.85,  6.85,  5.25,  6.55,  4.85,  6.55,  8.25},
+      {0.00,  15.95, 14.75, 15.05, 12.85, 16.45, 12.95, 11.85, 14.75, 14.55, 12.85,
+       15.45, 16.95, 16.85, 15.65, 9.15,  9.05,  0.00,  4.55,  6.65,  8.65,  9.55,
+       7.45,  7.75,  8.25,  8.85,  10.05, 8.05,  6.45,  7.85,  6.25,  7.75,  9.85},
+      {0.00,  17.35, 16.25, 16.35, 14.05, 18.15, 14.55, 13.55, 16.25, 16.05, 14.05,
+       17.05, 18.85, 19.25, 17.55, 10.15, 11.45, 7.75,  5.55,  7.45,  9.65,  10.75,
+       8.55,  9.15,  9.65,  9.65,  11.25, 9.15,  7.35,  8.55,  7.15,  8.55,  10.65},
+      {0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00,
+       0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00},
+      {0.00,  17.65, 16.15, 16.35, 14.15, 17.65, 14.45, 13.15, 16.05, 16.15, 13.85,
+       16.35, 18.05, 18.65, 17.05, 9.65,  10.75, 7.85,  5.65,  7.85,  9.55,  10.75,
+       8.65,  8.85,  9.35,  9.85,  10.65, 8.85,  7.15,  7.85,  6.15,  8.65,  10.65},
+      {0.00,  16.35, 15.25, 15.75, 13.25, 17.25, 13.55, 12.55, 15.25, 15.35, 13.55,
+       16.45, 17.65, 18.25, 16.85, 9.25,  10.55, 7.25,  4.35,  6.85,  8.75,  9.75,
+       8.35,  8.25,  8.65,  9.35,  10.05, 8.25,  6.85,  7.85,  6.35,  7.85,  9.65},
+      {0.00,  16.05, 14.85, 15.35, 13.05, 16.55, 13.45, 12.05, 14.75, 15.05, 13.15,
+       15.65, 17.35, 17.65, 16.35, 9.15,  10.45, 7.05,  4.65,  6.55,  8.55,  9.55,
+       7.75,  8.15,  8.05,  9.05,  10.25, 8.25,  6.55,  7.85,  6.15,  7.85,  9.65},
+      {0.00,  15.55, 14.55, 14.75, 12.65, 16.15, 12.85, 11.65, 14.35, 14.15, 12.65,
+       15.35, 16.95, 17.15, 15.85, 8.85,  9.75,  6.75,  4.35,  6.55,  8.55,  9.35,
+       7.65,  7.75,  8.15,  9.05,  10.05, 7.85,  6.35,  7.55,  5.35,  7.05,  9.05},
+      {0.00,  17.15, 16.15, 16.15, 14.05, 17.65, 14.25, 13.35, 16.15, 16.15, 14.35,
+       16.85, 18.05, 18.85, 17.15, 10.05, 11.15, 7.85,  5.55,  7.55,  9.85,  10.55,
+       8.75,  9.15,  9.65,  10.05, 11.05, 8.85,  7.35,  8.55,  7.35,  8.85,  10.75},
+      {0.00,  16.95, 15.85, 16.15, 14.15, 17.55, 14.05, 13.05, 15.85, 15.85, 13.85,
+       16.75, 18.35, 18.75, 17.55, 9.85,  11.35, 7.45,  5.05,  7.45,  9.45,  10.75,
+       8.45,  8.85,  9.35,  9.55,  10.75, 8.85,  6.95,  8.55,  6.65,  8.25,  10.05},
+      {0.00,  16.85, 15.95, 16.15, 14.05, 17.55, 14.35, 12.85, 15.65, 16.05, 14.05,
+       16.65, 18.25, 18.65, 17.35, 9.85,  11.05, 7.65,  5.25,  7.75,  9.55,  10.85,
+       8.85,  8.85,  9.45,  10.05, 11.05, 9.05,  7.15,  8.65,  7.05,  8.35,  10.35},
+      {0.00,  15.75, 14.25, 14.45, 12.65, 16.15, 13.05, 12.05, 14.55, 14.55, 12.85,
+       15.35, 16.85, 17.45, 15.85, 8.85,  9.85,  6.45,  4.15,  6.35,  8.35,  8.05,
+       7.55,  7.85,  8.35,  8.65,  9.85,  7.85,  6.35,  7.15,  5.55,  7.75,  9.15},
+      {0.00,  15.05, 13.85, 14.15, 12.05, 15.05, 12.55, 11.35, 13.75, 13.85, 11.85,
+       14.85, 16.35, 16.45, 15.35, 8.35,  9.55,  6.05,  4.05,  6.25,  8.15,  8.95,
+       7.15,  6.85,  7.65,  8.55,  9.35,  7.55,  6.05,  6.95,  5.35,  7.25,  8.95},
+      {0.00,  14.35, 13.35, 13.65, 11.35, 15.05, 12.15, 10.75, 13.55, 12.75, 11.65,
+       14.45, 15.75, 15.65, 15.25, 7.75,  8.85,  5.75,  3.55,  5.85,  7.35,  8.65,
+       6.95,  7.15,  7.35,  8.05,  8.75,  6.95,  5.55,  6.55,  5.35,  6.85,  8.05},
+      {0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00,
+       0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00},
+      {0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00,
+       0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00},
+      {0.00,  14.75, 13.45, 14.25, 11.85, 15.15, 12.35, 11.05, 13.85, 13.65, 12.05,
+       14.55, 16.25, 16.35, 15.55, 7.85,  9.55,  5.85,  3.75,  5.85,  7.95,  8.65,
+       0.00,  7.25,  7.85,  8.25,  9.05,  7.45,  5.85,  6.95,  5.65,  7.05,  9.05},
+      {0.00,  16.55, 15.05, 15.75, 13.55, 16.65, 13.35, 12.65, 15.05, 15.35, 13.25,
+       16.05, 17.55, 17.35, 16.65, 9.15,  10.65, 7.15,  4.55,  7.15,  8.75,  9.65,
+       8.25,  8.65,  8.85,  9.35,  10.35, 8.55,  6.85,  8.05,  6.65,  8.25,  10.05},
+      {0.00,  14.15, 13.05, 13.85, 11.55, 14.75, 11.85, 10.85, 13.25, 13.45, 11.35,
+       14.05, 15.85, 16.05, 14.85, 7.85,  8.85,  5.85,  3.55,  5.75,  7.55,  8.55,
+       7.05,  0.00,  7.35,  7.85,  9.05,  7.15,  5.35,  6.55,  5.35,  6.65,  8.65},
+      {0.00,  14.55, 13.65, 14.15, 12.35, 15.65, 12.75, 11.55, 14.05, 14.15, 12.05,
+       14.35, 16.05, 16.35, 15.35, 8.35,  9.15,  6.15,  4.05,  5.85,  7.55,  8.85,
+       0.00,  7.35,  7.75,  8.25,  9.15,  7.05,  5.75,  7.15,  5.75,  7.35,  8.65},
+      {0.00,  15.85, 14.85, 15.05, 13.05, 16.55, 13.25, 12.35, 15.05, 15.05, 13.15,
+       16.05, 17.35, 17.75, 16.55, 9.25,  10.35, 6.75,  4.65,  6.65,  8.85,  9.45,
+       7.85,  8.05,  8.55,  8.95,  10.05, 8.25,  6.35,  7.75,  6.25,  7.85,  9.55},
+      {0.00,  16.55, 15.25, 15.85, 13.55, 16.95, 14.05, 12.85, 15.35, 15.65, 13.65,
+       16.35, 17.95, 18.45, 17.05, 9.65,  10.85, 7.45,  5.15,  7.15,  9.35,  10.35,
+       8.55,  8.55,  9.15,  9.45,  10.65, 8.85,  6.85,  8.15,  6.55,  8.05,  10.15},
+      {0.00,  16.55, 15.45, 15.55, 13.65, 16.75, 14.05, 12.75, 15.55, 15.55, 13.75,
+       16.55, 17.95, 18.35, 17.15, 9.75,  10.55, 8.35,  5.05,  7.75,  9.15,  10.45,
+       8.65,  8.95,  9.15,  9.75,  10.85, 8.85,  7.05,  8.45,  6.55,  8.35,  10.35},
+      {0.00,  15.05, 13.35, 14.15, 12.25, 15.55, 12.35, 11.25, 14.15, 14.05, 12.35,
+       14.85, 16.15, 16.65, 15.65, 8.25,  9.35,  5.95,  3.55,  6.05,  8.05,  8.65,
+       7.15,  7.35,  8.05,  8.05,  9.35,  7.15,  5.45,  6.85,  5.25,  6.55,  8.55},
+      {0.00,  16.75, 15.55, 15.85, 13.65, 17.55, 14.05, 12.85, 15.65, 15.55, 13.65,
+       16.55, 17.85, 18.45, 17.45, 9.65,  10.65, 7.65,  5.15,  7.55,  9.45,  10.35,
+       8.65,  8.65,  9.35,  10.05, 10.85, 9.05,  7.15,  8.55,  6.95,  8.55,  10.55},
+      {0.00,  15.65, 14.55, 15.05, 12.75, 16.15, 12.85, 11.75, 14.65, 14.65, 12.85,
+       15.55, 17.15, 17.45, 15.95, 8.75,  9.85,  6.75,  4.35,  6.65,  8.35,  9.55,
+       7.95,  7.75,  8.35,  8.95,  9.85,  8.05,  6.55,  7.35,  6.05,  7.35,  9.05},
+      {0.00,  16.85, 15.65, 16.05, 14.05, 17.05, 14.25, 12.15, 15.55, 15.65, 13.85,
+       16.15, 17.55, 18.05, 16.95, 9.55,  10.55, 7.55,  4.75,  7.25,  9.15,  10.35,
+       8.35,  8.45,  9.15,  9.55,  10.85, 8.75,  7.05,  8.35,  7.05,  8.55,  10.35},
+      {0.00, 15.85, 14.75, 15.05, 13.15, 15.85, 12.65, 12.05, 14.75, 15.05, 12.45,
+       0.00, 16.55, 17.35, 16.05, 8.95,  9.65,  6.85,  4.65,  6.35,  8.15,  9.25,
+       7.35, 8.15,  8.55,  9.05,  9.65,  8.15,  6.55,  7.35,  5.55,  7.75,  9.65},
+      {0.00,  16.85, 16.15, 16.05, 14.15, 17.05, 14.25, 12.75, 16.05, 15.85, 13.95,
+       16.65, 18.35, 18.15, 16.85, 9.65,  10.55, 7.65,  5.65,  7.55,  9.55,  10.55,
+       8.85,  8.65,  9.15,  10.05, 10.75, 9.05,  6.85,  8.15,  6.15,  8.65,  10.75},
+      {0.00,  14.35, 13.25, 13.85, 11.55, 15.05, 11.85, 10.85, 13.35, 13.35, 11.15,
+       14.15, 15.65, 15.85, 14.25, 7.65,  8.85,  5.75,  4.05,  5.65,  7.85,  8.65,
+       6.65,  7.15,  7.55,  7.85,  8.65,  7.05,  5.35,  6.65,  5.05,  6.75,  8.35},
+      {0.00,  14.35, 13.15, 13.55, 11.75, 14.75, 11.95, 10.75, 13.05, 13.15, 11.65,
+       14.25, 15.75, 15.85, 14.55, 7.85,  8.85,  5.75,  3.75,  5.35,  7.15,  8.35,
+       6.85,  6.85,  7.25,  7.65,  8.65,  6.45,  5.15,  6.55,  4.85,  6.05,  8.35},
+      {0.00,  17.15, 15.95, 16.15, 13.85, 17.25, 14.35, 12.85, 15.55, 15.85, 13.55,
+       16.75, 18.45, 18.85, 16.65, 10.25, 11.15, 7.65,  5.35,  7.35,  9.25,  10.35,
+       8.25,  8.35,  9.05,  9.65,  10.65, 8.85,  7.15,  8.45,  5.95,  8.65,  10.05},
+      {0.00,  14.85, 13.85, 14.25, 11.65, 15.05, 12.05, 11.25, 13.85, 13.85, 11.55,
+       14.65, 16.05, 16.45, 15.25, 8.45,  9.05,  6.05,  4.15,  5.75,  7.95,  8.75,
+       6.85,  7.05,  7.45,  8.15,  9.15,  7.05,  5.65,  6.75,  5.35,  7.05,  8.95},
+      {0.00,  17.05, 15.85, 16.05, 14.15, 17.55, 14.35, 13.05, 15.55, 15.65, 13.95,
+       16.65, 18.35, 18.55, 17.05, 9.55,  10.55, 7.35,  5.35,  7.25,  9.35,  10.35,
+       8.65,  8.75,  9.15,  9.55,  10.75, 8.65,  7.25,  8.75,  6.85,  8.35,  10.55},
+      {0.00,  14.15, 13.15, 13.75, 11.55, 14.55, 11.55, 10.55, 13.35, 12.95, 11.45,
+       14.05, 15.65, 15.65, 14.85, 7.95,  8.75,  5.65,  3.45,  5.65,  7.25,  8.05,
+       6.45,  0.00,  7.25,  7.65,  8.55,  6.85,  5.15,  6.65,  5.05,  6.55,  0.00},
+      {0.00,  16.15, 15.05, 15.55, 13.25, 16.85, 13.55, 12.35, 15.35, 15.15, 13.05,
+       15.85, 17.95, 17.65, 16.65, 9.35,  10.55, 7.25,  4.85,  7.35,  9.15,  10.05,
+       7.95,  8.35,  8.85,  9.05,  10.35, 8.25,  6.65,  8.15,  6.55,  0.00,  9.85},
+      {0.00,  15.65, 14.55, 14.75, 13.05, 15.95, 12.45, 11.95, 14.25, 13.65, 12.05,
+       14.65, 16.35, 16.35, 15.25, 8.55,  9.85,  6.85,  4.15,  5.65,  7.55,  8.65,
+       7.55,  7.15,  7.35,  8.35,  9.15,  6.85,  6.25,  7.05,  6.15,  7.85,  8.35},
+      {0.00,  14.75, 13.55, 14.05, 12.05, 15.35, 12.05, 11.15, 13.95, 13.55, 11.65,
+       14.15, 15.85, 16.15, 15.25, 8.15,  9.45,  0.00,  3.95,  5.85,  7.85,  8.65,
+       7.05,  7.25,  7.55,  8.35,  9.25,  7.55,  5.75,  7.05,  5.65,  6.95,  8.75},
+      {0.00,  15.05, 13.55, 14.35, 11.95, 15.55, 12.05, 11.25, 13.85, 13.75, 12.15,
+       14.65, 16.35, 16.35, 15.35, 0.00,  9.55,  5.85,  3.55,  5.95,  7.75,  8.85,
+       7.05,  7.65,  7.65,  8.05,  9.05,  7.55,  5.85,  7.15,  5.65,  7.15,  9.05},
+      {0.00,  16.05, 14.65, 15.35, 13.05, 16.75, 13.55, 12.35, 14.85, 15.05, 12.95,
+       16.05, 17.55, 17.65, 16.45, 9.05,  10.15, 6.85,  4.85,  6.95,  9.15,  9.85,
+       7.95,  8.15,  8.85,  8.85,  10.05, 8.25,  6.55,  8.05,  6.15,  7.75,  9.65},
+      {0.00,  16.65, 15.55, 15.75, 13.75, 16.75, 13.65, 12.65, 15.05, 15.35, 13.35,
+       16.55, 17.85, 18.35, 16.85, 9.65,  10.75, 7.35,  5.05,  7.25,  9.45,  10.25,
+       8.65,  8.55,  9.25,  9.85,  10.75, 8.65,  7.15,  8.15,  6.15,  8.05,  10.25},
+      {0.00,  15.15, 13.65, 14.15, 12.25, 15.95, 12.65, 11.65, 14.05, 14.45, 12.25,
+       14.75, 16.15, 16.55, 15.25, 8.45,  9.35,  6.35,  4.25,  6.35,  8.25,  9.45,
+       8.05,  8.35,  7.95,  8.25,  10.05, 7.45,  6.65,  7.05,  5.05,  6.65,  8.45},
+      {0.00,  16.35, 14.15, 15.55, 12.65, 16.45, 13.65, 11.85, 15.05, 14.15, 12.85,
+       15.35, 17.15, 17.35, 15.85, 8.55,  9.65,  6.55,  4.35,  6.15,  8.65,  9.55,
+       7.65,  7.55,  8.25,  9.25,  9.75,  7.75,  6.05,  7.05,  5.35,  6.95,  8.85},
+      {0.00,  16.65, 15.45, 15.85, 13.65, 17.15, 14.05, 12.85, 15.55, 15.45, 13.85,
+       16.55, 17.85, 18.35, 17.05, 9.35,  10.55, 7.15,  5.05,  7.05,  9.05,  10.25,
+       8.15,  8.65,  9.05,  9.55,  0.00,  8.55,  7.05,  8.05,  6.55,  7.85,  10.15},
+      {0.00,  15.55, 14.55, 14.85, 12.75, 16.05, 12.95, 11.85, 14.55, 14.75, 12.75,
+       15.35, 16.85, 17.25, 15.75, 8.85,  9.85,  6.35,  4.25,  6.05,  8.25,  9.35,
+       7.55,  7.65,  8.05,  8.35,  9.55,  7.65,  5.75,  7.15,  5.55,  7.05,  8.85},
+      {0.00,  17.85, 16.75, 17.05, 14.65, 18.05, 14.85, 13.75, 16.65, 16.55, 14.75,
+       17.05, 18.55, 19.05, 17.55, 10.05, 11.05, 8.35,  6.05,  8.05,  10.35, 11.15,
+       9.05,  9.65,  9.85,  10.65, 11.35, 9.55,  7.85,  8.55,  7.55,  9.15,  10.05},
+      {0.00,  16.05, 15.05, 15.25, 13.55, 16.75, 13.75, 12.35, 15.15, 15.05, 13.35,
+       15.85, 17.55, 17.85, 16.55, 9.05,  10.15, 7.25,  4.55,  6.85,  8.85,  9.75,
+       8.05,  8.65,  8.75,  9.25,  10.15, 8.25,  6.35,  7.85,  6.25,  7.55,  9.35},
+      {0.00,  15.65, 14.15, 14.45, 12.45, 15.95, 12.85, 11.55, 14.15, 14.15, 12.75,
+       15.05, 16.65, 16.85, 15.65, 8.85,  9.85,  6.55,  4.35,  6.45,  8.35,  9.35,
+       7.65,  7.55,  8.05,  8.55,  9.55,  7.75,  6.15,  7.35,  5.65,  7.55,  9.05},
+      {0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00,
+       0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00},
+      {0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00,
+       0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00},
+      {0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00,
+       0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00},
+      {0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00,
+       0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00},
+      {0.00,  14.85, 13.05, 13.95, 11.65, 15.05, 12.15, 11.15, 13.85, 13.65, 12.15,
+       14.65, 15.95, 16.35, 15.15, 8.05,  9.35,  5.95,  3.55,  3.35,  7.55,  8.15,
+       6.65,  7.25,  7.65,  7.85,  8.75,  7.15,  5.45,  6.85,  5.45,  6.85,  8.75},
+      {0.00,  15.25, 14.25, 14.85, 12.55, 15.95, 12.65, 12.05, 14.35, 14.35, 12.55,
+       15.15, 16.65, 16.85, 16.05, 8.55,  9.75,  6.35,  -0.15, 6.15,  8.15,  9.15,
+       6.95,  7.65,  8.15,  8.65,  9.65,  7.25,  5.85,  7.05,  5.65,  6.75,  9.05},
+      {0.00,  14.35, 13.15, 13.55, 11.75, 14.65, 11.75, 10.95, 13.05, 13.25, 11.65,
+       13.85, 15.75, 16.05, 14.65, 7.65,  8.85,  5.75,  3.35,  0.00,  7.15,  8.15,
+       6.75,  6.55,  7.05,  7.85,  8.85,  6.75,  5.25,  6.55,  5.05,  6.55,  8.25},
+      {0.00,  13.45, 15.05, 15.35, 12.95, 16.25, 13.15, 12.05, 14.85, 14.85, 13.05,
+       15.65, 17.05, 17.35, 16.05, 9.05,  10.15, 7.05,  4.55,  6.85,  8.55,  9.75,
+       7.75,  0.00,  8.35,  9.05,  10.05, 8.05,  6.35,  7.85,  6.25,  7.65,  9.75},
+      {0.00,  15.55, 14.35, 14.35, 12.85, 15.75, 12.65, 11.55, 14.85, 14.15, 12.55,
+       15.35, 16.55, 16.75, 15.65, 8.95,  10.05, 6.05,  3.75,  5.95,  7.65,  9.05,
+       7.05,  7.25,  7.55,  8.05,  9.15,  7.55,  5.55,  6.95,  5.45,  6.55,  8.35},
+      {0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00,
+       0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00},
+      {0.00,  16.05, 15.05, 15.55, 13.65, 16.75, 13.45, 12.55, 15.55, 15.45, 13.35,
+       16.15, 17.55, 18.05, 16.65, 9.15,  10.55, 7.15,  4.45,  7.15,  8.65,  9.75,
+       8.15,  8.25,  8.65,  9.05,  10.35, 8.35,  6.45,  7.95,  6.25,  7.65,  9.65},
+      {0.00,  16.65, 14.35, 15.05, 12.65, 16.15, 13.15, 12.35, 15.05, 14.65, 12.85,
+       15.75, 17.35, 17.35, 16.05, 8.85,  10.05, 6.45,  4.65,  6.55,  8.65,  9.35,
+       7.95,  8.75,  8.35,  8.85,  10.05, 8.05,  6.05,  7.35,  5.75,  7.35,  8.95},
+      {0.00,  16.65, 15.35, 15.85, 13.85, 16.85, 13.15, 12.85, 14.65, 14.85, 13.05,
+       15.65, 17.25, 17.05, 16.05, 9.05,  10.05, 6.95,  4.65,  6.55,  8.55,  9.85,
+       7.65,  8.65,  8.25,  9.05,  10.05, 7.75,  6.35,  7.35,  5.65,  7.35,  9.55},
+      {0.00,  16.75, 15.35, 15.85, 13.85, 17.35, 14.25, 12.85, 15.85, 15.75, 13.75,
+       16.65, 18.25, 18.55, 17.15, 10.05, 11.05, 7.05,  4.85,  6.95,  9.05,  10.25,
+       8.15,  8.55,  8.85,  9.65,  10.55, 8.65,  6.65,  8.05,  6.25,  8.15,  10.05},
+      {0.00,  16.85, 15.75, 16.25, 13.85, 17.45, 13.95, 12.85, 15.65, 15.85, 13.65,
+       16.45, 17.75, 18.35, 16.55, 9.65,  10.85, 7.65,  5.25,  7.35,  9.15,  10.05,
+       7.95,  8.75,  9.05,  9.55,  10.55, 8.55,  6.75,  8.05,  5.85,  8.45,  10.35},
+      {0.00,  16.95, 15.95, 16.05, 14.05, 17.45, 14.35, 13.05, 15.65, 15.85, 14.15,
+       16.85, 18.35, 18.55, 17.45, 10.05, 11.35, 7.75,  5.15,  7.45,  9.65,  10.55,
+       8.65,  8.65,  9.15,  9.55,  10.85, 9.25,  7.25,  8.55,  7.15,  8.85,  10.65},
+      {0.00,  16.85, 15.55, 15.85, 13.95, 17.15, 14.35, 13.15, 15.75, 15.65, 13.85,
+       16.65, 18.35, 18.75, 17.55, 9.75,  10.85, 7.35,  5.15,  7.45,  9.35,  10.35,
+       8.35,  8.65,  9.15,  9.55,  11.05, 8.85,  7.05,  8.35,  6.85,  8.35,  10.05},
+      {0.00,  14.75, 13.65, 13.95, 12.15, 15.35, 12.45, 11.35, 13.85, 13.85, 12.05,
+       14.55, 15.85, 16.35, 15.25, 8.05,  9.55,  6.05,  3.95,  6.05,  8.25,  9.05,
+       7.35,  7.85,  8.05,  8.45,  9.45,  7.35,  5.85,  7.15,  5.45,  7.25,  8.65},
+      {0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00,
+       0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00},
+      {0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00,
+       0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00},
+      {0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00,
+       0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00}};
+
+    // Constructing the map (equipmentId, asic address, channel) -> (tot shift)
+    for (uint16_t comp = 0; comp < numComp; comp++) {
+      uint16_t equipment = eqId[comp];
+      for (uint16_t elink = 0; elink < numElinksPerComp; elink++) {
+        uint32_t address = TRBaddresses[elink];
+        fReadoutMap[equipment][address].resize(numChanPerElink);
+        for (uint16_t chan = 0; chan < numChanPerElink; chan++) {
+          fReadoutMap[equipment][address][chan] = ToTshifts[elink][chan];
+        }  //# channel
+      }    //# elink
+    }      //# component
+  }
+  // ------------------------------------------------------------------------------------
+
+
+  // ---  Mapping (equimentId, trb address, channel) -> (tot shift)  --------------------
+  double RichReadoutConfig::Map(uint16_t equipmentId, uint32_t address, uint16_t chan)
+  {
+    double result  = -1.;
+    auto equipIter = fReadoutMap.find(equipmentId);
+    if (equipIter != fReadoutMap.end()) {
+      auto elinkMap  = equipIter->second;
+      auto elinkIter = elinkMap.find(address);
+      if (elinkIter != elinkMap.end()) {
+        if (chan < elinkIter->second.size()) { result = elinkIter->second.at(chan); }
+      }
+    }
+    return result;
+  }
+  // ------------------------------------------------------------------------------------
+
+  // ---  Returns map (trb address, channel) -> (tot shift)  ----------------------------
+  std::map<uint32_t, std::vector<double>> RichReadoutConfig::Map(uint16_t equipmentId)
+  {
+    std::map<uint32_t, std::vector<double>> result;
+    auto equipIter = fReadoutMap.find(equipmentId);
+    if (equipIter != fReadoutMap.end()) { result = equipIter->second; }
+    return result;
+  }
+  // ------------------------------------------------------------------------------------
+
+
+  // -----   Print readout map   ------------------------------------------------
+  std::string RichReadoutConfig::PrintReadoutMap()
+  {
+    std::stringstream ss;
+    for (auto& equipment : fReadoutMap) {
+      uint16_t eqId = equipment.first;
+      for (auto& trb : equipment.second) {
+        uint32_t address            = trb.first;
+        std::vector<double>& shifts = trb.second;
+        for (size_t channel = 0; channel < shifts.size(); channel++) {
+          ss << "\n Equipment " << eqId << "  trb address " << setw(2) << address;
+          ss << "  channel " << setw(2) << channel << "  tot shift " << shifts.at(channel);
+        }
+      }
+    }
+    return ss.str();
+  }
+  // ----------------------------------------------------------------------------
+
+
+} /* namespace cbm::algo */
diff --git a/algo/detectors/rich/RichReadoutConfig.h b/algo/detectors/rich/RichReadoutConfig.h
new file mode 100644
index 0000000000..ea811d82a7
--- /dev/null
+++ b/algo/detectors/rich/RichReadoutConfig.h
@@ -0,0 +1,89 @@
+/* Copyright (C) 2022 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Volker Friese, Dominik Smith [committer] */
+
+#ifndef ALGO_DETECTORS_RICH_RICHREADOUTCONFIG_H
+#define ALGO_DETECTORS_RICH_RICHREADOUTCONFIG_H
+
+#include <map>
+#include <sstream>
+#include <utility>
+#include <vector>
+
+namespace cbm::algo
+{
+
+
+  /** @class RichReadoutConfig
+   ** @author Dominik Smith <d.smith@gsi.de>
+   ** @since 3 March 2022
+   ** @brief Provides the hardware-to-software address mapping for the CBM-RICH
+   **
+   ** The hardware address as provided in the raw data stream is specified in terms of the
+   ** equipment identifier (specific to one FLES component) and the trb address within
+   ** component. This is to be translated to a tot shift value. 
+   ** The mapping of the address spaces is hard-coded in this class.
+   **/
+
+  class RichReadoutConfig {
+
+  public:
+    /** @brief Constructor **/
+    RichReadoutConfig();
+
+
+    /** @brief Destructor **/
+    virtual ~RichReadoutConfig();
+
+
+    /** @brief Equipment in the configuration
+     ** @return Vector of equipment IDs
+     **/
+    std::vector<uint16_t> GetEquipmentIds();
+
+
+    /** @brief Number of elinks of a component
+     ** @param Equipment ID
+     ** @return Number of elinks
+     **/
+    size_t GetNumElinks(uint16_t equipmentId);
+
+
+    /** @brief Total number of elinks for RICH
+     ** @return Number of elinks
+     **/
+    size_t GetNumElinks();
+
+
+    /** @brief API: Mapping from component, address and channel to tot shift
+     ** @param equipId     Equipment identifier (component)
+     ** @param address     trb address within component
+     ** @param chan        channel within trb address 
+     ** @return tot shift
+     */
+    double Map(uint16_t equipmentId, uint32_t address, uint16_t chan);
+
+    /** @brief API: Returns map from address and channel to tot shift for component
+     ** @param equipId     Equipment identifier (component)
+     ** @return map: (address, channel) - > tot shift
+     */
+    std::map<uint32_t, std::vector<double>> Map(uint16_t equipmentId);
+
+
+    /** @brief Debug output of readout map **/
+    std::string PrintReadoutMap();
+
+
+  private:
+    // --- RICH readout map
+    // --- Map index: (equipment, trb address, channel), map value: (tot shift)
+    std::map<uint16_t, std::map<uint32_t, std::vector<double>>> fReadoutMap = {};  //!
+
+
+    /** @brief Initialisation of readout map **/
+    void Init();
+  };
+
+} /* namespace cbm::algo */
+
+#endif /* ALGO_DETECTORS_RICH_RICHREADOUTCONFIG_H_ */
diff --git a/algo/detectors/rich/UnpackRich.cxx b/algo/detectors/rich/UnpackRich.cxx
new file mode 100644
index 0000000000..471504f5f9
--- /dev/null
+++ b/algo/detectors/rich/UnpackRich.cxx
@@ -0,0 +1,322 @@
+/* Copyright (C) 2021 Goethe-University Frankfurt, Frankfurt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Pascal Raisig, Dominik Smith [committer] */
+
+#include "UnpackRich.h"
+
+#include <cstdint>
+
+#include "AlgoFairloggerCompat.h"
+
+namespace cbm::algo
+{
+  // ----   Algorithm execution   ---------------------------------------------
+  UnpackRich::resultType UnpackRich::operator()(const uint8_t* msContent, const fles::MicrosliceDescriptor& msDescr,
+                                                const uint64_t tTimeslice)
+  {
+    // --- Output data
+    resultType result = {};
+
+    fMonitor = UnpackRichMonitorData();
+    fOutputVec.clear();
+
+    // Clear CbmTime of MS. Used to get time offset of subtriggers to MS start
+    fCbmTimeMS = 0;
+
+    RichMicrosliceReader reader;
+    reader.SetData(msContent, msDescr.size);
+
+    const auto mstime = msDescr.idx;
+    fMsRefTime        = mstime - tTimeslice;
+
+    // There are a lot of MS  with 8 bytes size
+    // Does one need these MS?
+    if (reader.GetSize() <= 8) return result;
+
+    while (true) {
+      ProcessTrbPacket(reader);
+      ProcessHubBlock(reader);
+
+      // -4*2 for 2 last words which contain microslice index
+      if (reader.GetOffset() >= reader.GetSize() - 8) break;
+      // -4*3 for 0xffffffff padding and 2 last words which contain microslice index
+      if (reader.IsNextPadding() && reader.GetOffset() >= reader.GetSize() - 12) break;
+    }
+    uint32_t msIndexWord1 = reader.NextWord();
+    uint32_t msIndexWord2 = reader.NextWord();
+
+    result.first  = fOutputVec;
+    result.second = fMonitor;
+    return result;
+  }
+
+
+  void UnpackRich::ProcessTrbPacket(RichMicrosliceReader& reader)
+  {
+    //process CBM time
+    const uint32_t word_MSB = reader.NextWord();  // CBM 63:32
+    const uint32_t word_LSB = reader.NextWord();  // CBM 31: 0
+    fCbmTimePacket          = (uint64_t) word_MSB << 32 | word_LSB;
+    if (fCbmTimeMS == 0) fCbmTimeMS = fCbmTimePacket;
+
+    //discard unused words
+    for (auto l = 0; l < 10; ++l) {
+      reader.NextWord();
+    }
+
+    //process remaining words
+    for (auto l = 0; l < 14; ++l) {
+      const uint32_t wordEpoch = reader.NextWord();
+      const uint32_t epoch     = ProcessEpoch(wordEpoch);
+      const uint32_t wordTime  = reader.NextWord();
+      const RichTdcTimeData td = ProcessTimeData(wordTime);
+      const double fullTime    = CalculateTime(epoch, td.fCoarse, td.fFine);
+
+      if (l == 0) fPrevLastCh0ReTime[12] = fullTime;
+      if (l == 1) fMbsCorr = fullTime - fPrevLastCh0ReTime[12];  // = MbsPrevTimeCh1 - MbsPrevTimeCh0
+      if (l > 1) fPrevLastCh0ReTime[l - 2] = fullTime;
+    }
+
+    const uint32_t trbNum = reader.NextWord();  // TRB trigger number
+    //if (isLog()) L_(debug4) << getLogHeader(reader) << "TRB Num:" << reader.GetWordAsHexString(trbNum);
+  }
+
+
+  void UnpackRich::ProcessHubBlock(RichMicrosliceReader& reader)
+  {
+    uint32_t word          = reader.NextWord();
+    const uint32_t hubId   = word & 0xffff;          // 16 bits
+    const uint32_t hubSize = (word >> 16) & 0xffff;  // 16 bits
+
+    bool isLast         = false;  // if true then it is CTS sub-sub-event
+    size_t totalSize    = 0;
+    fCurrentSubSubEvent = 0;
+
+    uint32_t subSubEventId, subSubEventSize;
+
+    //loop over events in hub block
+    while (totalSize < hubSize) {
+      word            = reader.NextWord();
+      subSubEventId   = word & 0xffff;                              // 16 bits
+      subSubEventSize = (word >> 16) & 0xffff;                      // 16 bits
+      isLast          = reader.IsLastSubSubEvent(subSubEventSize);  // if true then it is CTS sub-sub-event
+      totalSize += (1 + subSubEventSize);
+
+      if (!isLast) {                                 // all except last are DiRICH events
+        if (((subSubEventId >> 12) & 0xF) != 0x7) {  // catch invalid ids
+          fMonitor.fNumErrInvalidHubId++;
+        }
+        if (totalSize == hubSize) { fMonitor.fNumErrInvalidHubSize++; }
+        ProcessSubSubEvent(reader, subSubEventSize, subSubEventId);
+        fCurrentSubSubEvent++;
+      }
+    }
+
+    //last event ist expected to be CTS
+    if (totalSize != hubSize || !isLast) { fMonitor.fNumErrInvalidHubSize++; }
+    subSubEventSize = ProcessCtsHeader(reader, subSubEventSize, subSubEventId);
+    ProcessSubSubEvent(reader, subSubEventSize, subSubEventId);
+
+    // read last words
+    int lastWordsCounter = 0;
+    while (true) {
+      lastWordsCounter++;
+      word = reader.NextWord();
+      if (word == 0x600dda7a) {
+        if (reader.IsNextPadding()) word = reader.NextWord();
+        break;
+      }
+      if (lastWordsCounter >= 7) { fMonitor.fNumErrExcessLastWords++; }
+    }
+  }
+
+  int UnpackRich::ProcessCtsHeader(RichMicrosliceReader& reader, uint32_t subSubEventSize, uint32_t subSubEventId)
+  {
+    const uint32_t word         = reader.NextWord();
+    const uint32_t ctsState     = word & 0xffff;                                                            // 16 bits
+    const uint32_t nofInputs    = (word >> 16) & 0xf;                                                       // 4 bits
+    const uint32_t nofTrigCh    = (word >> 20) & 0x1f;                                                      // 5 bits
+    const uint32_t inclLastIdle = (word >> 25) & 0x1;                                                       // 1 bit
+    const uint32_t inclTrigInfo = (word >> 26) & 0x1;                                                       // 1 bit
+    const uint32_t inclTime     = (word >> 27) & 0x1;                                                       // 1 bit
+    const uint32_t ETM          = (word >> 28) & 0x3;                                                       // 2 bits
+    uint32_t ctsInfoSize = 2 * nofInputs + 2 * nofTrigCh + 2 * inclLastIdle + 3 * inclTrigInfo + inclTime;  // in words
+    switch (ETM) {
+      case 0: break;
+      case 1: ctsInfoSize += 1; break;
+      case 2: ctsInfoSize += 4; break;
+      case 3: break;
+    }
+    for (uint32_t i = 0; i < ctsInfoSize; i++) {
+      reader.NextWord();  // do nothing?
+    }
+    const int nofTimeWords = subSubEventSize - ctsInfoSize - 1;
+    return nofTimeWords;
+  }
+
+  void UnpackRich::ProcessSubSubEvent(RichMicrosliceReader& reader, int nofTimeWords, uint32_t subSubEventId)
+  {
+    uint32_t epoch = 0;  // store last epoch obtained in sub-sub-event
+
+    // Store last raising edge time for each channel or -1. if no time
+    // this array is used to match raising and falling edges
+    std::vector<double> raisingTime(33, -1.);
+
+    // check if DiRICH (SubSubEvId) is masked and skip to end if so
+    if (CheckMaskedDiRICH(subSubEventId)) {
+      for (int i = 0; i < nofTimeWords; i++) {
+        reader.NextWord();
+      }
+      return;
+    }
+
+    // First word is expected to be of type "header"
+    if (GetTdcWordType(reader.NextWord()) != RichTdcWordType::Header) { fMonitor.fNumErrInvalidFirstMessage++; }
+
+    // Second word is expected to be of type "epoch"
+    uint32_t word = reader.NextWord();
+    if (GetTdcWordType(word) != RichTdcWordType::Epoch) { fMonitor.fNumErrInvalidSecondMessage++; }
+    else {
+      epoch = ProcessEpoch(word);
+    }
+
+    // Loop over words
+    for (int i = 2; i < nofTimeWords - 1; i++) {
+      word = reader.NextWord();
+      switch (GetTdcWordType(word)) {
+        case RichTdcWordType::TimeData: {
+          ProcessTimeDataWord(epoch, word, subSubEventId, raisingTime);
+          break;
+        }
+        case RichTdcWordType::Epoch: {
+          epoch = ProcessEpoch(word);
+          break;
+        }
+        case RichTdcWordType::Header: {
+          fMonitor.fNumErrWildHeaderMessage++;
+          break;
+        }
+        case RichTdcWordType::Trailer: {
+          fMonitor.fNumErrWildTrailerMessage++;
+          break;
+        }
+        case RichTdcWordType::Debug: {
+          break;
+          // for the moment do nothing
+          fMonitor.fNumDebugMessage++;
+          break;
+        }
+        case RichTdcWordType::Error: {
+          fMonitor.fNumErrTdcErrorWord++;
+          break;
+        }
+      }
+    }
+
+    // Last word is expected to be of type "trailer"
+    if (GetTdcWordType(reader.NextWord()) != RichTdcWordType::Trailer) { fMonitor.fNumErrInvalidLastMessage++; }
+  }
+
+  // ---- ProcessTimeDataWord ----
+  void UnpackRich::ProcessTimeDataWord(uint32_t epoch, uint32_t tdcWord, uint32_t subSubEventId,
+                                       std::vector<double>& raisingTime)
+  {
+    const RichTdcTimeData td = ProcessTimeData(tdcWord);
+    const double fullTime    = CalculateTime(epoch, td.fCoarse, td.fFine);
+
+    if (td.fChannel != 0) {
+      const double dT            = fullTime - fPrevLastCh0ReTime[fCurrentSubSubEvent];
+      const double subtrigOffset = (fCbmTimePacket - fCbmTimeMS) * 25.0;  // offset of SubTrigger to MS start in ns
+      const double fullTimeCorr  = dT - fMbsCorr + subtrigOffset;
+
+      if (td.fChannel < 1 || td.fChannel >= raisingTime.size()) { fMonitor.fNumErrChannelOutOfBounds++; }
+      if (td.fIsRisingEdge) {
+        // always store the latest raising edge. It means that in case RRFF situation only middle RF will be matched.
+        raisingTime[td.fChannel] = fullTimeCorr;
+      }
+      else {
+        if (raisingTime[td.fChannel] != -1.) {
+          // Matching was found, calculate ToT, if tot is in a good range -> create digi
+          const double ToT = fullTimeCorr - raisingTime[td.fChannel];
+          if (ToT >= fToTMin && ToT <= fToTMax) {
+            if (fullTimeCorr >= 0.0) { WriteOutputDigi(subSubEventId, td.fChannel, raisingTime[td.fChannel], ToT); }
+          }
+          // pair was created, set raising edge to -1.
+          raisingTime[td.fChannel] = -1.;
+        }
+      }
+    }
+  }
+
+  double UnpackRich::CalculateTime(uint32_t epoch, uint32_t coarse, uint32_t fine)
+  {
+    return ((double) epoch) * 2048. * 5. + ((double) coarse) * 5. - ((double) fine) * 0.005;
+  }
+
+  void UnpackRich::WriteOutputDigi(int32_t fpgaID, int32_t channel, double time, double tot)
+  {
+    double ToTcorr   = fbDoToTCorr ? fParams.fElinkParams[fpgaID].fToTshift[channel] : 0.;
+    int32_t pixelUID = GetPixelUID(fpgaID, channel);
+    //check ordering
+    double finalTime = time + (double) fMsRefTime - fSystemTimeOffset;
+
+    // Do not accept digis, where the MS und TS differs by more than 6 sec (mainly TS0)
+    if (6e9 < finalTime) return;
+
+    fOutputVec.emplace_back(pixelUID, finalTime, tot - ToTcorr);
+  }
+
+  bool UnpackRich::CheckMaskedDiRICH(int32_t subSubEventId)
+  {
+    for (unsigned int i = 0; i < fMaskedDiRICHes.size(); ++i) {
+      if (fMaskedDiRICHes.at(i) == subSubEventId) return true;
+    }
+    return false;
+  }
+
+  RichTdcWordType UnpackRich::GetTdcWordType(uint32_t tdcWord)
+  {
+    uint32_t tdcTimeDataMarker = (tdcWord >> 31) & 0x1;  // 1 bit
+    uint32_t tdcMarker         = (tdcWord >> 29) & 0x7;  // 3 bits
+
+    if (tdcTimeDataMarker == 0x1) {
+      // TODO: I also include tdcMarker == 0x5, some tdc time data words have this marker. Is it correct?
+      if (tdcMarker == 0x4 || tdcMarker == 0x5) { return RichTdcWordType::TimeData; }
+      else {
+        return RichTdcWordType::Error;
+      }
+    }
+    if (tdcMarker == 0x0) return RichTdcWordType::Trailer;
+    if (tdcMarker == 0x1) return RichTdcWordType::Header;
+    if (tdcMarker == 0x2) return RichTdcWordType::Debug;
+    if (tdcMarker == 0x3) return RichTdcWordType::Epoch;
+    return RichTdcWordType::Error;
+  }
+
+  int32_t UnpackRich::GetPixelUID(int32_t fpgaID, int32_t ch)
+  {
+    // First 16 bits are used for the FPGA ID, then
+    // 8 bits unused and then 8 bits are used for the channel
+    return ((fpgaID << 16) | (ch & 0x00FF));
+  }
+
+  RichTdcTimeData UnpackRich::ProcessTimeData(uint32_t tdcWord)
+  {
+    RichTdcTimeData out;
+    out.fCoarse       = static_cast<uint32_t>(tdcWord & 0x7ff);          // 11 bits
+    out.fIsRisingEdge = static_cast<uint32_t>((tdcWord >> 11) & 0x1);    // 1 bit
+    out.fFine         = static_cast<uint32_t>((tdcWord >> 12) & 0x3ff);  // 10 bits
+    out.fChannel      = static_cast<uint32_t>((tdcWord >> 22) & 0x7f);   // 7 bits
+    return out;
+  }
+
+  uint32_t UnpackRich::ProcessEpoch(uint32_t tdcWord) { return static_cast<uint32_t>(tdcWord & 0xfffffff); }
+
+  uint16_t UnpackRich::ProcessHeader(uint32_t tdcWord)
+  {
+    return static_cast<uint16_t>(tdcWord & 0xff);  //8 bits
+  }
+
+  uint16_t UnpackRich::ProcessTrailer(uint32_t tdcWord) { return static_cast<uint16_t>(tdcWord & 0xffff); }
+
+} /* namespace cbm::algo */
diff --git a/algo/detectors/rich/UnpackRich.h b/algo/detectors/rich/UnpackRich.h
new file mode 100644
index 0000000000..8987e926f3
--- /dev/null
+++ b/algo/detectors/rich/UnpackRich.h
@@ -0,0 +1,250 @@
+/* Copyright (C) 2021 Goethe-University Frankfurt, Frankfurt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Pascal Raisig, Dominik Smith [committer] */
+
+#ifndef UnpackRich_H
+#define UnpackRich_H
+
+#include "CbmRichDigi.h"
+
+#include "Timeslice.hpp"
+
+#include <cstddef>
+#include <cstdint>
+#include <iomanip>
+#include <memory>
+#include <sstream>
+#include <utility>
+
+namespace cbm::algo
+{
+  class RichMicrosliceReader;
+
+  enum class RichTdcWordType
+  {
+    TimeData,
+    Header,
+    Epoch,
+    Trailer,
+    Debug,
+    Error
+  };
+
+
+  struct RichTdcTimeData {
+    uint32_t fCoarse       = 0;  // 11 bits
+    uint32_t fIsRisingEdge = 0;  // 1 bit
+    uint32_t fFine         = 0;  // 10 bits
+    uint32_t fChannel      = 0;  // 7 bits
+  };
+
+
+  /** @struct UnpackRichElinkPar
+   ** @author Dominik Smith <d.smith@gsi.de>
+   ** @since 26 May 2023
+   ** @brief RICH Unpacking parameters for one eLink / ASIC
+   **/
+  struct UnpackRichElinkPar {
+    std::vector<double> fToTshift;  ///< TOT shift for different channels
+  };
+
+
+  /** @struct UnpackRichPar
+   ** @author Dominik Smith <d.smith@gsi.de>
+   ** @since 26 May 2023
+   ** @brief Parameters required for the RICH unpacking (specific to one component)
+   **/
+  struct UnpackRichPar {
+    std::map<uint32_t, UnpackRichElinkPar> fElinkParams = {};  ///< Map TRB address to elink params
+  };
+
+
+  /** @struct UnpackRichMoni
+   ** @author Dominik Smith <d.smith@gsi.de>
+   ** @since 26 May 2023
+   ** @brief Monitoring data for RICH unpacking
+   **/
+  struct UnpackRichMonitorData {
+    uint32_t fNumDebugMessage            = 0;  ///< Received debug messages
+    uint32_t fNumErrInvalidFirstMessage  = 0;  ///< First message is not HEADER
+    uint32_t fNumErrInvalidSecondMessage = 0;  ///< Second message is not EPOCH
+    uint32_t fNumErrInvalidLastMessage   = 0;  ///< Last message is not TRAILER
+    uint32_t fNumErrWildHeaderMessage    = 0;  ///< TDC header in invalid position
+    uint32_t fNumErrWildTrailerMessage   = 0;  ///< TDC trailer in invalid position
+    uint32_t fNumErrTdcErrorWord         = 0;  ///< TDC word of error type
+    uint32_t fNumErrChannelOutOfBounds   = 0;  ///< TDC channel out of bounds
+    uint32_t fNumErrInvalidHubId         = 0;  ///< "SubSubEvent" has invalid ID
+    uint32_t fNumErrInvalidHubSize       = 0;  ///< Premature end of hub block
+    uint32_t fNumErrExcessLastWords      = 0;  ///< More than exprected trailing words
+
+    bool HasErrors()
+    {
+      uint32_t numErrors = fNumDebugMessage + fNumErrInvalidFirstMessage + fNumErrInvalidSecondMessage
+                           + fNumErrInvalidLastMessage + fNumErrWildHeaderMessage + fNumErrWildTrailerMessage
+                           + fNumErrTdcErrorWord + fNumErrChannelOutOfBounds + fNumErrInvalidHubId
+                           + fNumErrInvalidHubSize + fNumErrExcessLastWords;
+      return (numErrors > 0 ? true : false);
+    }
+    std::string print()
+    {
+      std::stringstream ss;
+      ss << "errors " << fNumDebugMessage << " | " << fNumErrInvalidFirstMessage << " | " << fNumErrInvalidSecondMessage
+         << " | " << fNumErrInvalidLastMessage << " | " << fNumErrWildHeaderMessage << " | "
+         << fNumErrWildTrailerMessage << " | " << fNumErrTdcErrorWord << " | " << fNumErrChannelOutOfBounds << " | "
+         << fNumErrInvalidHubId << " | " << fNumErrInvalidHubSize << " | " << fNumErrExcessLastWords << " | ";
+      return ss.str();
+    }
+  };
+
+
+  class UnpackRich {
+  public:
+    typedef std::pair<std::vector<CbmRichDigi>, UnpackRichMonitorData> resultType;
+
+
+    /** @brief Default constructor **/
+    UnpackRich() {};
+
+
+    /** @brief Destructor **/
+    ~UnpackRich() {};
+
+
+    /** @brief Algorithm execution
+     ** @param  msContent  Microslice payload
+     ** @param  msDescr    Microslice descriptor
+     ** @param  tTimeslice Unix start time of timeslice [ns]
+     ** @return RICH digi data
+     **/
+    resultType operator()(const uint8_t* msContent, const fles::MicrosliceDescriptor& msDescr,
+                          const uint64_t tTimeslice);
+
+    /** @brief Set the parameter container
+     ** @param params Pointer to parameter container
+     **/
+    void SetParams(std::unique_ptr<UnpackRichPar> params) { fParams = *(std::move(params)); }
+
+  private:
+    void ProcessTrbPacket(RichMicrosliceReader& reader);
+
+    void ProcessHubBlock(RichMicrosliceReader& reader);
+
+    int ProcessCtsHeader(RichMicrosliceReader& reader, uint32_t subSubEventSize, uint32_t subSubEventId);
+
+    void ProcessSubSubEvent(RichMicrosliceReader& reader, int nofTimeWords, uint32_t subSubEventId);
+
+    void ProcessTimeDataWord(uint32_t epoch, uint32_t tdcWord, uint32_t subSubEventId,
+                             std::vector<double>& raisingTime);
+
+    RichTdcWordType GetTdcWordType(uint32_t tdcWord);
+
+    RichTdcTimeData ProcessTimeData(uint32_t tdcWord);
+
+    uint32_t ProcessEpoch(uint32_t tdcWord);
+
+    uint16_t ProcessHeader(uint32_t tdcWord);
+
+    uint16_t ProcessTrailer(uint32_t tdcWord);
+
+    void WriteOutputDigi(int32_t fpgaID, int32_t channel, double time, double tot);
+
+    double CalculateTime(uint32_t epoch, uint32_t coarse, uint32_t fine);
+
+    int32_t GetPixelUID(int32_t fpgaID, int32_t ch);
+
+    bool CheckMaskedDiRICH(int32_t subSubEventId);
+
+  private:
+    UnpackRichPar fParams = {};  ///< Parameter container
+
+    std::vector<int32_t> fMaskedDiRICHes;
+
+    bool fbDoToTCorr = true;  //activates ToT correction from parameter file
+
+    uint64_t fCbmTimeMS;
+    uint64_t fCbmTimePacket;
+
+    double fMbsCorr = 0.;
+    double fPrevLastCh0ReTime[13];  // 12 DiRICHes chnl0 + 1 CTS chnl0
+    uint16_t fCurrentSubSubEvent = 0;
+
+    uint64_t fMsRefTime = 0;
+
+    double fToTMin = -20.;
+    double fToTMax = 100.;
+
+    /** @brief Default return vector for Unpack function. */
+    std::vector<CbmRichDigi> fOutputVec = {};
+
+    /** @brief Storage of monitoring data. */
+    UnpackRichMonitorData fMonitor;
+
+    /**
+   * @brief Time offset for the system
+   * @todo This should be module and channel dependent and included into the asic parameters
+  */
+    std::int32_t fSystemTimeOffset = 0;
+  };
+
+
+  class RichMicrosliceReader {
+  private:
+    const uint8_t* fData = nullptr;
+    size_t fSize         = 0;
+    size_t fOffset       = 0;  // offset in bytes
+    size_t fWordCounter  = 0;
+    uint32_t fCurWord    = 0;
+
+  public:
+    void SetData(const uint8_t* data, size_t size)
+    {
+      fData        = data;
+      fSize        = size;
+      fOffset      = 0;
+      fWordCounter = 0;
+      fCurWord     = 0;
+    }
+
+    const uint8_t* GetData() { return fData; }
+    size_t GetSize() { return fSize; }
+    size_t GetOffset() { return fOffset; }
+    size_t GetWordCounter() { return fWordCounter; }
+    uint32_t GetCurWord() { return fCurWord; }
+
+    std::string GetWordAsHexString(uint32_t word)
+    {
+      std::stringstream stream;
+      stream << "0x" << std::setfill('0') << std::setw(sizeof(uint32_t) * 2) << std::hex << word;
+      return stream.str();
+    }
+
+    uint32_t NextWord()
+    {
+      uint32_t i = ((uint32_t*) (fData + fOffset))[0];
+      //swap bytes
+      i = (i >> 24) | ((i << 8) & 0x00FF0000) | ((i >> 8) & 0x0000FF00) | (i << 24);
+      fOffset += 4;
+      fWordCounter++;
+      fCurWord = i;
+      return i;
+    }
+
+    bool IsNextPadding()
+    {
+      uint32_t nextWord = ((uint32_t*) (fData + fOffset))[0];
+      if (nextWord == 0xffffffff) return true;
+      return false;
+    }
+
+    bool IsLastSubSubEvent(uint32_t subSubEventSize)
+    {
+      uint32_t i = ((uint32_t*) (fData + fOffset + 4 * subSubEventSize))[0];
+      i          = (i >> 24) | ((i << 8) & 0x00ff0000) | ((i >> 8) & 0x0000ff00) | (i << 24);
+      if (i == 0x00015555) return true;
+      return false;
+    }
+  };
+
+} /* namespace cbm::algo */
+
+#endif  // UnpackRich_H
diff --git a/algo/unpack/Unpack.cxx b/algo/unpack/Unpack.cxx
index db3a356ac7..bab391dcf8 100644
--- a/algo/unpack/Unpack.cxx
+++ b/algo/unpack/Unpack.cxx
@@ -57,6 +57,9 @@ namespace cbm::algo
       if (systemId == fles::SubsystemIdentifier::TRD2D) {
         MsLoop(timeslice, fAlgoTrd2d, comp, equipmentId, &digiTs.fData.fTrd2d.fDigis, monitor, &monitor.fTrd2d, 0x02);
       }
+      if (systemId == fles::SubsystemIdentifier::RICH) {
+        MsLoop(timeslice, fAlgoRich, comp, equipmentId, &digiTs.fData.fRich.fDigis, monitor, &monitor.fRich, 0x03);
+      }
     }  //# component
 
     // --- Sorting of output digis. Is required by both digi trigger and event builder.
@@ -73,6 +76,8 @@ namespace cbm::algo
               [](CbmTrdDigi digi1, CbmTrdDigi digi2) { return digi1.GetTime() < digi2.GetTime(); });
     std::sort(std::execution::par_unseq, digiTs.fData.fTrd2d.fDigis.begin(), digiTs.fData.fTrd2d.fDigis.end(),
               [](CbmTrdDigi digi1, CbmTrdDigi digi2) { return digi1.GetTime() < digi2.GetTime(); });
+    std::sort(std::execution::par_unseq, digiTs.fData.fRich.fDigis.begin(), digiTs.fData.fRich.fDigis.end(),
+              [](CbmRichDigi digi1, CbmRichDigi digi2) { return digi1.GetTime() < digi2.GetTime(); });
 #else
     std::sort(digiTs.fData.fSts.fDigis.begin(), digiTs.fData.fSts.fDigis.end(),
               [](CbmStsDigi digi1, CbmStsDigi digi2) { return digi1.GetTime() < digi2.GetTime(); });
@@ -86,6 +91,8 @@ namespace cbm::algo
               [](CbmTrdDigi digi1, CbmTrdDigi digi2) { return digi1.GetTime() < digi2.GetTime(); });
     std::sort(digiTs.fData.fTrd2d.fDigis.begin(), digiTs.fData.fTrd2d.fDigis.end(),
               [](CbmTrdDigi digi1, CbmTrdDigi digi2) { return digi1.GetTime() < digi2.GetTime(); });
+    std::sort(digiTs.fData.fRich.fDigis.begin(), digiTs.fData.fRich.fDigis.end(),
+              [](CbmRichDigi digi1, CbmRichDigi digi2) { return digi1.GetTime() < digi2.GetTime(); });
 #endif
     return result;
   }
@@ -213,6 +220,19 @@ namespace cbm::algo
       L_(info) << "--- Configured equipment " << equip << " with " << numElinks << " elinks";
     }
 
+    // Create one algorithm per component and configure it with parameters
+    auto equipIdsRich = fRichConfig.GetEquipmentIds();
+    for (auto& equip : equipIdsRich) {
+      std::unique_ptr<UnpackRichPar> par(new UnpackRichPar());
+      std::map<uint32_t, std::vector<double>> compMap = fRichConfig.Map(equip);
+      for (auto const& val : compMap) {
+        uint32_t address                     = val.first;
+        par->fElinkParams[address].fToTshift = val.second;
+      }
+      fAlgoRich[equip].SetParams(std::move(par));
+      L_(info) << "--- Configured equipment " << equip << " with " << fRichConfig.GetNumElinks(equip) << " elinks";
+    }
+
     // Create one algorithm per component for TRD and configure it with parameters
     auto equipIdsTrd = fTrdConfig.GetEquipmentIds();
     for (auto& equip : equipIdsTrd) {
@@ -268,6 +288,8 @@ namespace cbm::algo
     L_(info) << "--- Configured " << fAlgoSts.size() << " unpacker algorithms for STS.";
     L_(debug) << "Readout map:" << fStsConfig.PrintReadoutMap();
     L_(info) << "--- Configured " << fAlgoMuch.size() << " unpacker algorithms for MUCH.";
+    L_(info) << "--- Configured " << fAlgoRich.size() << " unpacker algorithms for RICH.";
+    L_(debug) << "Readout map:" << fRichConfig.PrintReadoutMap();
     L_(info) << "--- Configured " << fAlgoTof.size() << " unpacker algorithms for TOF.";
     L_(info) << "--- Configured " << fAlgoTrd.size() << " unpacker algorithms for TRD.";
     L_(info) << "--- Configured " << fAlgoTrd2d.size() << " unpacker algorithms for TRD2D.";
diff --git a/algo/unpack/Unpack.h b/algo/unpack/Unpack.h
index 5a0d6dbed4..57d72cb8d2 100644
--- a/algo/unpack/Unpack.h
+++ b/algo/unpack/Unpack.h
@@ -22,6 +22,8 @@
 #include "bmon/UnpackBmon.h"
 #include "much/MuchReadoutConfig.h"
 #include "much/UnpackMuch.h"
+#include "rich/RichReadoutConfig.h"
+#include "rich/UnpackRich.h"
 #include "sts/StsReadoutConfigLegacy.h"
 #include "sts/UnpackSts.h"
 
@@ -40,6 +42,7 @@ namespace cbm::algo
     std::vector<UnpackBmonMonitorData> fBmon;    ///< Monitoring data for T0
     std::vector<UnpackTrdMonitorData> fTrd;      ///< Monitoring data for TRD
     std::vector<UnpackTrd2dMonitorData> fTrd2d;  ///< Monitoring data for TRD2D
+    std::vector<UnpackRichMonitorData> fRich;    ///< Monitoring data for RICH
     size_t fNumMs       = 0;
     size_t fNumBytes    = 0;
     size_t fNumDigis    = 0;
@@ -94,6 +97,9 @@ namespace cbm::algo
     /** @brief Parameters for TRD2D unpackers **/
     Trd2dReadoutConfig fTrd2dConfig {};
 
+    /** @brief Parameters for RICH unpackers **/
+    RichReadoutConfig fRichConfig {};
+
     /** @brief Initialize unpackers and fill parameters from config objects **/
     bool Init();
 
@@ -123,6 +129,9 @@ namespace cbm::algo
 
     /** @brief TRD2D unpackers **/
     std::map<uint16_t, UnpackTrd2d> fAlgoTrd2d = {};
+
+    /** @brief RICH unpackers **/
+    std::map<uint16_t, UnpackRich> fAlgoRich = {};
   };
 }  // namespace cbm::algo
 
-- 
GitLab