diff --git a/algo/detectors/t0/T0ReadoutConfig.cxx b/algo/detectors/t0/T0ReadoutConfig.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..662ba589db43cc995db49a6e510f9e3810480a20
--- /dev/null
+++ b/algo/detectors/t0/T0ReadoutConfig.cxx
@@ -0,0 +1,180 @@
+/* Copyright (C) 2022 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Dominik Smith [committer] */
+
+#include "T0ReadoutConfig.h"
+
+#include "CbmTofAddress.h"
+
+#include <Logger.h>
+
+#include <bitset>
+#include <iomanip>
+
+#include "gDpbMessv100.h"
+
+using namespace std;
+
+namespace cbm::algo
+{
+  // ---  Constructor  ------------------------------------------------------------------
+  T0ReadoutConfig::T0ReadoutConfig() { Init(); }
+  // ------------------------------------------------------------------------------------
+
+  // ---   Destructor   -----------------------------------------------------------------
+  T0ReadoutConfig::~T0ReadoutConfig() {}
+  // ------------------------------------------------------------------------------------
+
+  // ---   Equipment IDs   --------------------------------------------------------------
+  std::vector<uint16_t> T0ReadoutConfig::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 T0ReadoutConfig::GetNumElinks(uint16_t equipmentId)
+  {
+    size_t result = 0;
+    auto it       = fReadoutMap.find(equipmentId);
+    if (it != fReadoutMap.end()) result = fReadoutMap[equipmentId].size();
+    return result;
+  }
+  // ------------------------------------------------------------------------------------
+
+
+  // ---  Mapping (equimentId, elink) -> address[channel]  ------------------------------
+  std::vector<uint32_t> T0ReadoutConfig::Map(uint16_t equipmentId, uint16_t elinkId)
+  {
+    std::vector<uint32_t> result;
+    auto equipIter = fReadoutMap.find(equipmentId);
+    if (equipIter != fReadoutMap.end()) {
+      if (elinkId < equipIter->second.size()) { result = equipIter->second.at(elinkId); }
+    }
+    return result;
+  }
+  // ------------------------------------------------------------------------------------
+
+  void T0ReadoutConfig::Init()
+  {
+    // This here refers to the mCBM 2022 setup.
+    // Taken from CbmMcbm2018TofPar in combination with macro/beamtime/mcbm2022/mBmonCriPar.par
+
+    // Array to hold the unique IDs (equipment ID) for all TOF DPBs
+    uint16_t eqId[numComp] = {0xabf3, 0xabf2, 0xabf1, 0xabf0};
+
+    // Constructing the map (equipmentId, eLink, channel) -> (TOF address)
+    const uint32_t numElinksPerComp = numFebsPerComp * numAsicsPerFeb;
+    const uint32_t numChanPerComp   = numChanPerAsic * numElinksPerComp;
+
+    // Constructs the fviRpcChUId array
+    BuildChannelsUidMap();
+
+    for (uint16_t comp = 0; comp < numComp; comp++) {
+      uint16_t equipment = eqId[comp];
+      fReadoutMap[equipment].resize(numElinksPerComp);
+
+      for (uint16_t elink = 0; elink < numElinksPerComp; elink++) {
+        fReadoutMap[equipment][elink].resize(numChanPerAsic);
+
+        for (uint16_t channel = 0; channel < numChanPerAsic; channel++) {
+
+          const uint32_t chanInComp = elink * numChanPerAsic + channel;
+          uint32_t chanInSys        = comp * numChanPerComp + chanInComp;
+
+          {  // hack? perhaps can be removed
+            const int numFullFlims = 8;
+            if (comp > numFullFlims) { chanInSys -= (comp - numFullFlims) * numChanPerComp / 2; }
+          }
+
+          const uint32_t chanUId                 = fviRpcChUId[chanInSys];
+          fReadoutMap[equipment][elink][channel] = chanUId;
+        }  //# channel
+      }    //# elink
+    }      //# component
+  }
+
+  // -------------------------------------------------------------------------
+  void T0ReadoutConfig::BuildChannelsUidMap()
+  {
+    const uint32_t numAsics = numComp * numFebsPerComp * numAsicsPerFeb;
+    const uint32_t numChan  = numAsics * numChanPerAsic;
+    fviRpcChUId.resize(numChan);
+
+    uint32_t uCh = 0;
+    for (uint32_t uGbtx = 0; uGbtx < numCrob; ++uGbtx) {
+      const uint32_t uCh0 = uCh;
+      switch (rpcType[uGbtx]) {
+        case 5: {
+          /// Special Treatment for the T0 diamond
+          BuildChannelsUidMapT0(uCh, uGbtx);
+          break;
+        }
+        case 99: {
+          /// Special Treatment for the 2022 T0 diamond, keep past behavior for older data!
+          BuildChannelsUidMapT0_2022(uCh, uGbtx);
+          break;
+        }
+        case -1: {
+          LOG(info) << " Found unused GBTX link at uCh = " << uCh;
+          uCh += 160;
+          break;
+        }
+        default: {
+          LOG(error) << "Invalid T0 Type specifier for GBTx " << std::setw(2) << uGbtx << ": " << rpcType[uGbtx];
+        }
+      }  // switch (rpcType[uGbtx])
+      if ((int32_t)(uCh - uCh0) != numFebsPerComp * numAsicsPerFeb * numChanPerAsic / 2) {
+        LOG(fatal) << "T0 mapping error for Gbtx " << uGbtx << ",  diff = " << uCh - uCh0;
+      }
+    }
+  }
+
+  // -------------------------------------------------------------------------
+  void T0ReadoutConfig::BuildChannelsUidMapT0(uint32_t& uCh, uint32_t uGbtx)
+  {
+    LOG(info) << " Map diamond " << moduleId[uGbtx] << " at GBTX " << uGbtx << " -  uCh = " << uCh;
+    for (uint32_t uGet4 = 0; uGet4 < numElinksPerCrob; ++uGet4) {
+      for (uint32_t uGet4Ch = 0; uGet4Ch < numChanPerAsic; ++uGet4Ch) {
+        /// Mapping for the 2022 beamtime
+        if (uGet4 < 32 && 0 == uGet4Ch && -1 < moduleId[uGbtx]) {
+          uint32_t uChannelT0 = uGet4 + 32 * rpcSide[uGbtx];
+          uChannelT0 /= 8;
+          fviRpcChUId[uCh] = CbmTofAddress::GetUniqueAddress(moduleId[uGbtx], 0, uChannelT0, 0, rpcType[uGbtx]);
+          printf("  T0 channel: %u from GBTx %2u, indx %d address %08x", uChannelT0, uGbtx, uCh, fviRpcChUId[uCh]);
+        }  // Valid T0 channel
+        else {
+          fviRpcChUId[uCh] = 0;
+        }  // Invalid T0 channel
+        uCh++;
+      }
+    }
+  }
+
+  // -------------------------------------------------------------------------
+  void T0ReadoutConfig::BuildChannelsUidMapT0_2022(uint32_t& uCh, uint32_t uGbtx)
+  {
+    LOG(info) << " Map 2022 diamond " << moduleId[uGbtx] << " at GBTX " << uGbtx << " -  uCh = " << uCh;
+    for (uint32_t uGet4 = 0; uGet4 < numElinksPerCrob; ++uGet4) {
+      for (uint32_t uGet4Ch = 0; uGet4Ch < numChanPerAsic; ++uGet4Ch) {
+        /// Mapping for the 2022 beamtime
+        if (-1 < moduleId[uGbtx] && uGet4 < 32 && 0 == uGet4 % 4 && 0 == uGet4Ch) {
+          /// 1 channel per physical GET4, 2 links per physical GET4, 4 physical GET4s per GBTx, 1 GBTx per comp.
+          /// 16 channels for one side, 16 for the other
+          uint32_t uChannelT0 = (uGet4 / 8 + 4 * (uGbtx / 2)) % 16;
+          /// Type hard-coded to allow different parameter values to separate 2022 T0 and pre-2022 T0
+          fviRpcChUId[uCh] = CbmTofAddress::GetUniqueAddress(moduleId[uGbtx], 0, uChannelT0, rpcSide[uGbtx], 5);
+          printf("  Bmon channel: %u side %u from GBTx %2u, indx %d address %08x \n", uChannelT0, rpcSide[uGbtx], uGbtx,
+                 uCh, fviRpcChUId[uCh]);
+        }  // Valid T0 channel
+        else {
+          fviRpcChUId[uCh] = 0;
+        }  // Invalid T0 channel
+        uCh++;
+      }
+    }
+  }
+} /* namespace cbm::algo */
diff --git a/algo/detectors/t0/T0ReadoutConfig.h b/algo/detectors/t0/T0ReadoutConfig.h
new file mode 100644
index 0000000000000000000000000000000000000000..a12f3a396a8f5959b2c93aaf10a5c5d19a3da6ad
--- /dev/null
+++ b/algo/detectors/t0/T0ReadoutConfig.h
@@ -0,0 +1,85 @@
+/* Copyright (C) 2022 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Dominik Smith [committer] */
+
+// -------------------------------------------------------------------------
+// -----            T0ReadoutConfig header file                       -----
+// -----            Created 22/02/22  by P.-A. Loizeau                 -----
+// -----            Modified 07/12/18  by A Kumar                      -----
+// -------------------------------------------------------------------------
+
+#ifndef ALGO_DETECTORS_T0_T0READOUTCONFIG_H
+#define ALGO_DETECTORS_T0_T0READOUTCONFIG_H
+
+#include <cstdint>
+#include <map>
+#include <vector>
+
+namespace cbm::algo
+{
+  class T0ReadoutConfig {
+
+  public:
+    /** @brief Constructor **/
+    T0ReadoutConfig();
+
+    /** @brief Destructor **/
+    virtual ~T0ReadoutConfig();
+
+    /** @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 API: Mapping from component and elink to addresses per channel
+     ** @param equipId     Equipment identifier (component)
+     ** @param elink       Elink number within component
+     ** @return Vector of TOF addresses, indexed via channel number
+     */
+    std::vector<uint32_t> Map(uint16_t equipId, uint16_t elink);
+
+  private:
+    // --- T0 readout map
+    // --- Map index: (equipment, elink, channel), map value: (TOF address)
+    std::map<uint16_t, std::vector<std::vector<uint32_t>>> fReadoutMap = {};
+
+    /** @brief Initialisation of readout map **/
+    void Init();
+
+    /// Constants
+    /// Taken from mBmonCriPar.par
+    static const uint16_t numComp        = 4;   // Total number of TOF DPBs in system
+    static const uint32_t numFebsPerComp = 10;  // Number of FEEs which are connected to one GDPB
+    static const uint32_t numAsicsPerFeb = 8;   // Number of ASICs connected in each FEB for TOF
+    static const uint32_t numChanPerAsic = 4;   // Number of channels in each ASIC
+    static const uint32_t numCrob        = 8;   // Total number of Gbtx links
+
+    /// Taken from CbmMcbm2018TofPar.h
+    static const uint32_t numFebsPerCrob   = 5;  // Number of FEBs  connected to each CROB for mTof 2019
+    static const uint32_t numElinksPerCrob = numAsicsPerFeb * numFebsPerCrob;
+
+    // Module Identifier connected to Gbtx link, has to match geometry
+    const int32_t moduleId[numCrob] = {0, -1, 0, -1, 0, -1, 0, -1};
+
+    // type of Rpcs connected to Gbtx link
+    const int32_t rpcType[numCrob] = {99, -1, 99, -1, 99, -1, 99, -1};
+
+    // side of Rpcs connected to Gbtx link, i.e. 0 or 1
+    const int32_t rpcSide[numCrob] = {0, 0, 0, 0, 0, 0, 0, 0};
+
+    std::vector<int32_t> fviRpcChUId = {};  // UID/address for each channel, build from type, side and module
+
+    void BuildChannelsUidMap();
+    void BuildChannelsUidMapT0(uint32_t& uCh, uint32_t uGbtx);
+    void BuildChannelsUidMapT0_2022(uint32_t& uCh, uint32_t uGbtx);
+  };
+
+} /* namespace cbm::algo */
+
+#endif  //ALGO_DETECTORS_T0_T0READOUTCONFIG_H
diff --git a/algo/detectors/t0/UnpackT0.cxx b/algo/detectors/t0/UnpackT0.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..f8fc5a25d00a7b935c67f801f2add8e840308859
--- /dev/null
+++ b/algo/detectors/t0/UnpackT0.cxx
@@ -0,0 +1,156 @@
+/* Copyright (C) 2022 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Dominik Smith [committer] */
+
+#include "UnpackT0.h"
+
+#include <cassert>
+#include <sstream>
+#include <utility>
+#include <vector>
+
+#include <cmath>
+
+using std::unique_ptr;
+using std::vector;
+
+namespace cbm::algo
+{
+
+  // ----   Algorithm execution   ---------------------------------------------
+  UnpackT0::resultType UnpackT0::operator()(const uint8_t* msContent, const fles::MicrosliceDescriptor& msDescr,
+                                            const uint64_t tTimeslice)
+  {
+
+    // --- Output data
+    resultType result = {};
+
+    // --- Current Timeslice start time in epoch units. Note that it is always a multiple of epochs
+    // --- and the epoch is a multiple of ns.
+    fCurrentTsTime = static_cast<uint64_t>(tTimeslice / critof001::kuEpochInNs) % critof001::kulEpochCycleEp;
+
+    // --- Number of messages in microslice
+    auto msSize = msDescr.size;
+    if (msSize % sizeof(critof001::Message) != 0) {
+      result.second.fNumErrInvalidMsSize++;
+      return result;
+    }
+    const uint32_t numMessages = msSize / sizeof(critof001::Message);
+    if (numMessages < 2) {
+      result.second.fNumErrInvalidMsSize++;
+      return result;
+    }
+
+    // --- Interpret MS content as sequence of SMX messages
+    auto message = reinterpret_cast<const critof001::Message*>(msContent);
+
+    // --- The first message in the MS is expected to be of type EPOCH.
+    if (message[0].getMessageType() != critof001::MSG_EPOCH) {
+      result.second.fNumErrInvalidFirstMessage++;
+      return result;
+    }
+
+    {  // --- Check that first epoch matches with the microslice index
+      const uint64_t msStartEpoch =
+        static_cast<uint64_t>(msDescr.idx / critof001::kuEpochInNs) % critof001::kulEpochCycleEp;
+      if (message[0].getGdpbEpEpochNb() != msStartEpoch) {
+        result.second.fNumErrInvalidStartEpoch++;
+        return result;
+      }
+    }
+
+    // --- The last message in the MS is expected to be EndOfMs.
+    if (!message[numMessages - 1].isEndOfMs()) {
+      result.second.fNumErrInvalidLastMessage++;
+      return result;
+    }
+    //Check if last message is "EndOfMs"!! Maybe loop to messageNr < numMessages - 1
+
+    // --- Message loop
+    for (uint32_t messageNr = 0; messageNr < numMessages; messageNr++) {
+
+      // --- Action depending on message type
+      switch (message[messageNr].getMessageType()) {
+
+        case critof001::MSG_HIT: {
+          ProcessHitMessage(message[messageNr], result.first, result.second);
+          break;
+        }
+        case critof001::MSG_EPOCH: {
+          ProcessEpochMessage(message[messageNr]);
+          break;
+        }
+        case critof001::MSG_SLOWC: {
+          // Fill error
+          break;
+        }
+        case critof001::MSG_SYST: {
+          // Fill error
+          break;
+        }
+        default: {
+          result.second.fNumNonHitOrTsbMessage++;
+          break;
+        }
+      }  //? Message type
+    }    //# Messages
+
+    return result;
+  }
+  // --------------------------------------------------------------------------
+
+
+  // -----   Process hit message   --------------------------------------------
+  inline void UnpackT0::ProcessHitMessage(const critof001::Message& message, vector<CbmTofDigi>& digiVec,
+                                          UnpackT0MonitorData& monitor) const
+  {
+    // IGNORES:
+    // - Duplicate messages
+    // - (0 == uChanUId)
+    // - (fviRpcChUId.size() < uRemappedChannelNrInSys)
+    // - successive digis with same time
+    // (these are filtered in original version but not here)
+    // also: does not apply new "remap digis" hack, and always includes timeslice overlap
+
+    // --- Check eLink and get parameters
+    const uint32_t elink = message.getGet4Idx();
+    if (elink >= fParams.fElinkParams.size()) {
+      monitor.fNumErrElinkOutOfRange++;
+      return;
+    }
+    const UnpackT0ElinkPar& elinkPar = fParams.fElinkParams.at(elink);
+
+    const uint32_t channel    = message.getGdpbHitChanId();
+    const uint32_t channelUId = (elinkPar.fChannelUId)[channel];
+
+    double messageTime  = message.getMsgFullTimeD(fCurrentEpochInTs) - elinkPar.fTimeOffset;
+    const double charge = (double) message.getGdpbHit32Tot();  //cast from uint32_t
+
+    /*
+    std::stringstream ss;
+    ss << " elink " << elink << " channel " << channel << " channelUId " << channelUId << " charge " << charge << " time " << message.getGdpbHitFullTs() << '\n';
+    std::cout << ss.str();
+    exit(0);
+*/
+    // --- Create output digi
+    digiVec.emplace_back(channelUId, messageTime, charge);
+  }
+  // --------------------------------------------------------------------------
+
+
+  // -----   Process an epoch message   ---------------------------------------
+  inline void UnpackT0::ProcessEpochMessage(const critof001::Message& message)
+  {
+    const uint64_t epoch = message.getGdpbEpEpochNb();
+
+    // --- Calculate epoch relative to timeslice start time; correct for epoch cycles
+    if (fCurrentTsTime <= epoch) { fCurrentEpochInTs = epoch - fCurrentTsTime; }
+    else {
+      fCurrentEpochInTs = epoch + critof001::kulEpochCycleEp - fCurrentTsTime;
+    }
+    //Problem if MS spans multiple epoch cycles?
+  }
+  // --------------------------------------------------------------------------
+
+
+} /* namespace cbm::algo */
diff --git a/algo/detectors/t0/UnpackT0.h b/algo/detectors/t0/UnpackT0.h
new file mode 100644
index 0000000000000000000000000000000000000000..6ade5e58166b88637b7e57c5f15e224b965be49b
--- /dev/null
+++ b/algo/detectors/t0/UnpackT0.h
@@ -0,0 +1,130 @@
+/* Copyright (C) 2022 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Dominik Smith [committer] */
+
+#ifndef CBM_ALGO_UNPACKT0_H
+#define CBM_ALGO_UNPACKT0_H 1
+
+
+#include "CbmTofDigi.h"
+
+#include "MicrosliceDescriptor.hpp"
+#include "Timeslice.hpp"
+
+#include <cassert>
+#include <cstddef>
+#include <cstdint>
+#include <memory>
+#include <vector>
+
+#include "CriGet4Mess001.h"
+
+namespace cbm::algo
+{
+
+
+  /** @struct UnpackT0ElinkPar
+   ** @author Volker Friese <v.friese@gsi.de>
+   ** @since 25 November 2021
+   ** @brief T0 Unpacking parameters for one eLink / ASIC
+   **/
+  struct UnpackT0ElinkPar {
+    std::vector<uint32_t> fChannelUId;  ///< CbmT0Address for different channels
+    uint64_t fTimeOffset = 0.;          ///< Time calibration parameter
+  };
+
+
+  /** @struct UnpackT0Par
+   ** @author Volker Friese <v.friese@gsi.de>
+   ** @since 25 November 2021
+   ** @brief Parameters required for the STS unpacking (specific to one component)
+   **/
+  struct UnpackT0Par {
+    uint32_t fNumChansPerAsic                  = 0;   ///< Number of channels per ASIC
+    uint32_t fNumAsicsPerModule                = 0;   ///< Number of ASICS per module
+    std::vector<UnpackT0ElinkPar> fElinkParams = {};  ///< Parameters for each eLink
+  };
+
+
+  /** @struct UnpackT0Moni
+   ** @author Volker Friese <v.friese@gsi.de>
+   ** @since 2 December 2021
+   ** @brief Monitoring data for STS unpacking
+   **/
+  struct UnpackT0MonitorData {
+    uint32_t fNumNonHitOrTsbMessage     = 0;
+    uint32_t fNumErrElinkOutOfRange     = 0;  ///< Elink not contained in parameters
+    uint32_t fNumErrInvalidFirstMessage = 0;  ///< First message is not EPOCH
+    uint32_t fNumErrInvalidLastMessage  = 0;  ///< Last message is not EndOfMs
+    uint32_t fNumErrInvalidMsSize       = 0;  ///< Microslice size is not multiple of message size
+    uint32_t fNumErrTimestampOverflow   = 0;  ///< Overflow in 64 bit time stamp
+    uint32_t fNumErrInvalidStartEpoch   = 0;  ///< Microslice index doesn't match first epoch
+    bool HasErrors()
+    {
+      uint32_t numErrors = fNumNonHitOrTsbMessage + fNumErrElinkOutOfRange + fNumErrInvalidFirstMessage
+                           + fNumErrInvalidMsSize + fNumErrTimestampOverflow;
+      return (numErrors > 0 ? true : false);
+    }
+  };
+
+
+  /** @class UnpackT0
+   ** @author Pierre-Alain Loizeau <p.-a.loizeau@gsi.de>
+   ** @author Volker Friese <v.friese@gsi.de>
+   ** @since 25 November 2021
+   ** @brief Unpack algorithm for STS
+   **/
+  class UnpackT0 {
+
+  public:
+    typedef std::pair<std::vector<CbmTofDigi>, UnpackT0MonitorData> resultType;
+
+
+    /** @brief Default constructor **/
+    UnpackT0() {};
+
+
+    /** @brief Destructor **/
+    ~UnpackT0() {};
+
+
+    /** @brief Algorithm execution
+     ** @param  msContent  Microslice payload
+     ** @param  msDescr    Microslice descriptor
+     ** @param  tTimeslice Unix start time of timeslice [ns]
+     ** @return STS 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<UnpackT0Par> params) { fParams = *(std::move(params)); }
+
+
+  private:  // methods
+    /** @brief Process a hit message
+     ** @param message SMX message (32-bit word)
+     ** @param digiVec Vector to append the created digi to
+     ** @param monitor Reference to monitor object
+     **/
+    void ProcessHitMessage(const critof001::Message& message, std::vector<CbmTofDigi>& digiVec,
+                           UnpackT0MonitorData& monitor) const;
+
+    /** @brief Process an epoch message (TS_MSB)
+     ** @param message SMX message (32-bit word)
+     **/
+    void ProcessEpochMessage(const critof001::Message& message);
+
+
+  private:                            // members
+    uint64_t fCurrentTsTime    = 0;   ///< Unix time of timeslice in units of epoch length
+    uint32_t fCurrentEpochInTs = 0;   ///< Current epoch number relative to timeslice start epoch
+    UnpackT0Par fParams        = {};  ///< Parameter container
+  };
+
+
+} /* namespace cbm::algo */
+
+#endif /* CBM_ALGO_UNPACKT0_H */