diff --git a/algo/CMakeLists.txt b/algo/CMakeLists.txt
index 8ffefffa5c93ebd8a83485c48e4c340fca6038a2..a0eb6be5633551fee8da1855602ee4477ab41a2f 100644
--- a/algo/CMakeLists.txt
+++ b/algo/CMakeLists.txt
@@ -6,6 +6,7 @@ add_subdirectory (test)
 set(SRCS
   evbuild/EventBuilder.cxx
   trigger/TimeClusterTrigger.cxx
+  detectors/sts/StsReadoutConfig.cxx
   detectors/sts/UnpackSts.cxx
  )
 
diff --git a/algo/detectors/sts/StsReadoutConfig.cxx b/algo/detectors/sts/StsReadoutConfig.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..4a2c74655d86ba4a56b050cec5c12ea80f820c74
--- /dev/null
+++ b/algo/detectors/sts/StsReadoutConfig.cxx
@@ -0,0 +1,212 @@
+/* Copyright (C) 2022 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Volker Friese [committer] */
+
+#include "StsReadoutConfig.h"
+
+#include "CbmStsAddress.h"
+
+#include <iomanip>
+
+using std::pair;
+using std::setw;
+
+namespace cbm::algo
+{
+
+
+  // ---  Constructor  ------------------------------------------------------------------
+  StsReadoutConfig::StsReadoutConfig() { Init(); }
+  // ------------------------------------------------------------------------------------
+
+
+  // ---   Destructor   -----------------------------------------------------------------
+  StsReadoutConfig::~StsReadoutConfig() {}
+  // ------------------------------------------------------------------------------------
+
+
+  // ---   Equipment IDs   --------------------------------------------------------------
+  std::vector<uint16_t> StsReadoutConfig::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 StsReadoutConfig::GetNumElinks(uint16_t equipmentId)
+  {
+    size_t result = 0;
+    auto it       = fReadoutMap.find(equipmentId);
+    if (it != fReadoutMap.end()) result = fReadoutMap[equipmentId].size();
+    return result;
+  }
+  // ------------------------------------------------------------------------------------
+
+
+  // ---  Initialise the mapping structure   --------------------------------------------
+  void StsReadoutConfig::Init()
+  {
+
+    // This here refers to the mCBM 2021 setup.
+    // Taken from CbmMcbm2018StsPar in combination with macro/beamtime/mcbm2021/mSts.par
+
+    // The readout hierarchy is: component - CROB - FEB - ASIC (elink). Each elink
+    // connects one ASIC. One FEB comprises 8 ASICs and reads out one side of a module (sensor).
+    // In this setup, there is only one CROB per component. The code below is formulate such
+    // as to support also multiple CROBs per component. In that case, the elinks are numbered
+    // consecutively within one component.
+
+    // Constants
+    const uint16_t numModules       = 13;  // Number of modules in the setup
+    const uint16_t numComp          = 5;   // Number of components
+    const uint16_t numCrobPerComp   = 1;   // Number of CROBs per component
+    const uint16_t numFebsPerCrob   = 5;   // Number of FEBs per CROB
+    const uint16_t numAsicsPerFeb   = 8;   // Number of ASICs per FEB
+    const uint16_t numElinksPerCrob = 42;  // Number of elinks per CROB
+
+    // Equipment IDs for each component
+    // This number is written to the data stream (MicrosliceDescriptor).
+    uint16_t eqId[numComp] = {0x1003, 0x1002, 0x1006, 0x1005, 0x1004};
+
+    // Mapping of eLink to FEB number within CROB. If -1, elink not used.
+    // This mapping is the same for each component.
+    const int16_t elink2Feb[numElinksPerCrob] = {4, 4, 4, 4, 4, 3, 3, 3, 4, 4, -1, -1, 4, 3, 2, 2, 3, 3, 3, 1, 2,
+                                                 2, 1, 2, 2, 2, 3, 2, 1, 1, 1, 1,  1,  0, 0, 0, 1, 0, 0, 0, 0, 0};
+
+    // Mapping of FEB within CROB to module index (-1 = inactive)
+    int16_t feb2module[numComp][numCrobPerComp][numFebsPerCrob] = {
+      {{-1, 1, 1, 0, 0}},     // component 0
+      {{4, 3, 3, 2, 2}},      // component 1
+      {{7, 6, 6, 5, 5}},      // component 2
+      {{10, 9, 9, 8, 8}},     // component 3
+      {{12, 12, 11, 11, 10}}  // component 4
+    };
+
+    // Mapping of FEB to module side (0 = p side, 1 = n side, -1 = inactive)
+    int16_t feb2moduleSide[numComp][numCrobPerComp][numFebsPerCrob] = {
+      {{-1, 1, 0, 1, 0}},  // component 0
+      {{1, 1, 0, 1, 0}},   // component 1
+      {{1, 1, 0, 1, 0}},   // component 2
+      {{0, 1, 0, 1, 0}},   // component 3
+      {{1, 0, 1, 0, 1}}    // component 4
+    };
+
+    // Mapping of eLink to ASIC number for FEB Type A
+    // The ASIC number is a running number over all FEBs.
+    const uint32_t elink2AsicFebA[numElinksPerCrob] = {
+      0x0021, 0x0023, 0x0025, 0x0020, 0x0022, 0x0018, 0x001A, 0x001C, 0x0024, 0x0027, 0xFFFF, 0xFFFF, 0x0026, 0x001E,
+      0x0010, 0x0012, 0x0019, 0x001B, 0x001F, 0x000E, 0x0011, 0x0013, 0x000C, 0x0015, 0x0017, 0x0016, 0x001D, 0x0014,
+      0x0009, 0x000D, 0x000F, 0x0008, 0x000A, 0x0002, 0x0004, 0x0006, 0x000B, 0x0005, 0x0000, 0x0003, 0x0007, 0x0001,
+    };
+
+    // Mapping of eLink to ASIC for FEB Type B
+    // The ASIC number is a running number over all FEBs.
+    const uint32_t elink2AsicFebB[numElinksPerCrob] = {
+      0x0027, 0x0025, 0x0023, 0x0026, 0x0024, 0x001E, 0x001C, 0x001A, 0x0022, 0x0021, 0xFFFF, 0xFFFF, 0x0020, 0x0018,
+      0x0016, 0x0014, 0x001F, 0x001D, 0x0019, 0x0008, 0x0017, 0x0015, 0x000A, 0x0013, 0x0011, 0x0010, 0x001B, 0x0012,
+      0x000F, 0x000B, 0x0009, 0x000E, 0x000C, 0x0004, 0x0002, 0x0000, 0x000D, 0x0003, 0x0006, 0x0005, 0x0001, 0x0007};
+
+    // Module types
+    // Type 0 had the connector at the right side, type 1 at the left side.
+    // For type 0, the mapping of FEB to module side as above applies,
+    // for type 1, it has to be inverted.
+    bool modType[numModules] = {0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1};
+
+    // Module addresses (CbmStsAddress)
+    int32_t modAddress[numModules];
+    modAddress[0]  = 0x10008002;
+    modAddress[1]  = 0x10018002;
+    modAddress[2]  = 0x10008402;
+    modAddress[3]  = 0x10018402;
+    modAddress[4]  = 0x10107C02;
+    modAddress[5]  = 0x10008412;
+    modAddress[6]  = 0x10018412;
+    modAddress[7]  = 0x101FFC02;
+    modAddress[8]  = 0x10008012;
+    modAddress[9]  = 0x10018012;
+    modAddress[10] = 0x10008812;
+    modAddress[11] = 0x10018812;
+    modAddress[12] = 0x10028812;
+
+
+    // Constructing the map (equipmentId, eLink) -> (module, ASIC within module)
+    uint16_t numElinksPerComp = numCrobPerComp * numElinksPerCrob;
+    for (uint16_t comp = 0; comp < numComp; comp++) {
+      uint16_t equipment = eqId[comp];
+      fReadoutMap[equipment].resize(numElinksPerComp);
+      for (uint16_t crob = 0; crob < numCrobPerComp; crob++) {
+        for (uint16_t elink = 0; elink < numElinksPerCrob; elink++) {
+
+          int32_t moduleAddress = -1;
+          uint16_t asicInModule = 0;
+
+          uint16_t elinkId = numElinksPerCrob * crob + elink;  // elink within component
+          int16_t feb      = elink2Feb[elinkId];               // FEB within CROB
+          if (feb != -1) {
+            int16_t module = feb2module[comp][crob][feb];  // Module index
+            if (module != -1) {
+              assert(module < numModules);
+              bool moduleType    = modType[module];                               // 0 or 1
+              int16_t moduleSide = feb2moduleSide[comp][crob][feb];               // 0 or 1, -1 is inactive
+              int16_t febType    = (moduleType == 0 ? moduleSide : !moduleSide);  // 0 = FEB A, 1 = FEB B
+              uint32_t asicIndex = (febType == 0 ? elink2AsicFebA[elinkId] : elink2AsicFebB[elinkId]);
+              uint32_t asicInFeb = asicIndex % numAsicsPerFeb;  // ASIC number within FEB
+
+              moduleAddress = modAddress[module];
+              asicInModule  = (moduleSide == 0 ? asicInFeb : asicInFeb + numAsicsPerFeb);
+            }
+          }
+          fReadoutMap[equipment][elink] = std::make_pair(moduleAddress, asicInModule);
+
+        }  //# elink
+      }    //# CROB
+    }      //# component
+  }
+  // ------------------------------------------------------------------------------------
+
+
+  // ---  Mapping (equimentId, elink) -> (address, ASIC)  --------------------------------
+  pair<int32_t, uint16_t> StsReadoutConfig::Map(uint16_t equipmentId, uint16_t elinkId)
+  {
+    std::pair<int32_t, uint16_t> result(-1, 0);
+    auto equipIter = fReadoutMap.find(equipmentId);
+    if (equipIter != fReadoutMap.end()) {
+      if (elinkId < equipIter->second.size()) { result = equipIter->second.at(elinkId); }
+    }
+    return result;
+  }
+  // ------------------------------------------------------------------------------------
+
+
+  // -----   Print readout map   ------------------------------------------------
+  std::string StsReadoutConfig::PrintReadoutMap()
+  {
+
+    std::stringstream ss;
+    for (auto& equipment : fReadoutMap) {
+      auto eqId = equipment.first;
+      for (size_t elink = 0; elink < equipment.second.size(); elink++) {
+        auto address = equipment.second.at(elink).first;
+        auto asicNr  = equipment.second.at(elink).second;
+        ss << "\n Equipment " << eqId << "  elink " << setw(2) << elink;
+        ss << "  ASIC " << setw(2) << asicNr << "  module " << address;
+        if (address != -1) {
+          ss << "  Unit " << setw(2) << CbmStsAddress::GetElementId(address, kStsUnit);
+          ss << "  Ladd " << setw(2) << CbmStsAddress::GetElementId(address, kStsLadder);
+          ss << "  Hlad " << setw(2) << CbmStsAddress::GetElementId(address, kStsHalfLadder);
+          ss << "  Modu " << setw(2) << CbmStsAddress::GetElementId(address, kStsModule);
+        }
+        else
+          ss << "  Inactive";
+      }  //# elink
+    }    //# component
+    return ss.str();
+  }
+  // ----------------------------------------------------------------------------
+
+
+} /* namespace cbm::algo */
diff --git a/algo/detectors/sts/StsReadoutConfig.h b/algo/detectors/sts/StsReadoutConfig.h
new file mode 100644
index 0000000000000000000000000000000000000000..03302aec8c5148e2af0dd58002c20f60c0de87d1
--- /dev/null
+++ b/algo/detectors/sts/StsReadoutConfig.h
@@ -0,0 +1,77 @@
+/* Copyright (C) 2022 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Volker Friese [committer] */
+
+#ifndef ALGO_DETECTORS_STS_STSREADOUTCONFIG_H
+#define ALGO_DETECTORS_STS_STSREADOUTCONFIG_H
+
+#include <map>
+#include <sstream>
+#include <utility>
+#include <vector>
+
+namespace cbm::algo
+{
+
+
+  /** @class StsReadoutConfig
+   ** @author Volker Friese <v.friese@gsi.de>
+   ** @since 3 March 2022
+   ** @brief Provides the hardware-to-software address mapping for the CBM-STS
+   **
+   ** 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 elink number with in
+   ** component. This is to be translated into the module address and the ASIC number within
+   ** the module.
+   ** The mapping of the two address spaces is hard-coded in this class.
+   **/
+
+  class StsReadoutConfig {
+
+  public:
+    /** @brief Constructor **/
+    StsReadoutConfig();
+
+
+    /** @brief Destructor **/
+    virtual ~StsReadoutConfig();
+
+
+    /** @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 address and ASIC number
+     ** @param equipId     Equipment identifier (component)
+     ** @param elink       Elink number within component
+     ** @return (module address, ASIC number within module)
+     */
+    std::pair<int32_t, uint16_t> Map(uint16_t equipId, uint16_t elink);
+
+
+    /** @brief Debug output of readout map **/
+    std::string PrintReadoutMap();
+
+
+  private:
+    // --- STS readout map
+    // --- Map index: (equipment, elink), map value: (module address, ASIC number in module)
+    std::map<uint16_t, std::vector<std::pair<int32_t, uint16_t>>> fReadoutMap = {};  //!
+
+
+    /** @brief Initialisation of readout map **/
+    void Init();
+  };
+
+} /* namespace cbm::algo */
+
+#endif /* ALGO_DETECTORS_STS_STSREADOUTCONFIG_H_ */
diff --git a/algo/detectors/sts/UnpackSts.cxx b/algo/detectors/sts/UnpackSts.cxx
index ec8389ce1775c78f37cd47be61525468831c0c2c..a58a132c5594e96511ed2e4f0ba2727b91dbf00d 100644
--- a/algo/detectors/sts/UnpackSts.cxx
+++ b/algo/detectors/sts/UnpackSts.cxx
@@ -23,9 +23,6 @@ namespace cbm::algo
                                               const uint64_t tTimeslice)
   {
 
-    // --- Assert that parameters are set
-    assert(fParams);
-
     // --- Output data
     vector<CbmStsDigi> digiVec = {};
     UnpackStsMonitorData moni  = {};
@@ -38,12 +35,20 @@ namespace cbm::algo
     // --- Interpret MS content as sequence of SMX messages
     auto message = reinterpret_cast<const stsxyter::Message*>(msContent);
 
-    // --- Get first TS_MSB (first message in microslice must be of type ts_msb)
-    if (message[0].GetMessType() != stsxyter::MessType::TsMsb) {
+    // --- The first message in the MS is expected to be of type EPOCH and can be ignored
+    if (message[0].GetMessType() != stsxyter::MessType::Epoch) {
+      std::cout << "Error: UnpackSts: First message type is " << uint16_t(message[0].GetMessType()) << std::endl;
+      moni.fNumErrInvalidFirstMessage++;
+      return std::make_pair(digiVec, moni);
+    }
+
+    // --- The second message must be of type ts_msb
+    if (message[1].GetMessType() != stsxyter::MessType::TsMsb) {
+      std::cout << "Error: UnpackSts: Second message type is " << uint16_t(message[0].GetMessType()) << std::endl;
       moni.fNumErrInvalidFirstMessage++;
       return std::make_pair(digiVec, moni);
     }
-    ProcessTsmsbMessage(message[0]);
+    ProcessTsmsbMessage(message[1]);
 
     // --- Number of messages in microslice
     auto msSize = msDescr.size;
@@ -54,7 +59,7 @@ namespace cbm::algo
     const uint32_t numMessages = msSize / sizeof(stsxyter::Message);
 
     // --- Message loop
-    for (uint32_t messageNr = 1; messageNr < numMessages; messageNr++) {
+    for (uint32_t messageNr = 2; messageNr < numMessages; messageNr++) {
 
       // --- Action depending on message type
       switch (message[messageNr].GetMessType()) {
@@ -88,19 +93,19 @@ namespace cbm::algo
 
     // --- Check eLink and get parameters
     uint16_t elink = message.GetLinkIndexHitBinning();
-    if (elink >= fParams->fElinkParams.size()) {
+    if (elink >= fParams.fElinkParams.size()) {
       moni.fNumErrElinkOutOfRange++;
       return;
     }
-    const UnpackStsElinkPar& elinkPar = fParams->fElinkParams.at(elink);
+    const UnpackStsElinkPar& elinkPar = fParams.fElinkParams.at(elink);
     uint32_t asicNr                   = elinkPar.fAsicNr;
 
     // --- Hardware-to-software address
-    uint32_t numChansPerModule = fParams->fNumAsicsPerModule * fParams->fNumChansPerAsic;
+    uint32_t numChansPerModule = fParams.fNumAsicsPerModule * fParams.fNumChansPerAsic;
     uint32_t address           = elinkPar.fAddress;
     uint32_t channel           = 0;
-    if (asicNr < fParams->fNumAsicsPerModule / 2) {  // front side (n side)
-      channel = message.GetHitChannel() + fParams->fNumChansPerAsic * asicNr;
+    if (asicNr < fParams.fNumAsicsPerModule / 2) {  // front side (n side)
+      channel = message.GetHitChannel() + fParams.fNumChansPerAsic * asicNr;
     }
     else {  // back side (p side)
       channel = numChansPerModule - message.GetHitChannel() + 1;
diff --git a/algo/detectors/sts/UnpackSts.h b/algo/detectors/sts/UnpackSts.h
index 855844104db53b5633952b5245323a66d70f6939..c5e5aba6e2f11c4ebb28ab3254d73708c566e5ee 100644
--- a/algo/detectors/sts/UnpackSts.h
+++ b/algo/detectors/sts/UnpackSts.h
@@ -29,7 +29,7 @@ namespace cbm::algo
    ** @brief STS Unpacking parameters for one eLink / ASIC
    **/
   struct UnpackStsElinkPar {
-    uint32_t fAddress    = 0;   ///< CbmStsAddress for the connected module
+    int32_t fAddress     = 0;   ///< CbmStsAddress for the connected module
     uint32_t fAsicNr     = 0;   ///< Number of connected ASIC within the module
     uint64_t fTimeOffset = 0.;  ///< Time calibration parameter
     double fAdcOffset    = 0.;  ///< Charge calibration parameter
@@ -60,6 +60,12 @@ namespace cbm::algo
     uint32_t fNumErrInvalidFirstMessage = 0;  ///< First message is not TS_MSB
     uint32_t fNumErrInvalidMsSize       = 0;  ///< Microslice size is not multiple of message size
     uint32_t fNumErrTimestampOverflow   = 0;  ///< Overflow in 64 bit time stamp
+    bool HasErrors()
+    {
+      uint32_t numErrors = fNumNonHitOrTsbMessage + fNumErrElinkOutOfRange + fNumErrInvalidFirstMessage
+                           + fNumErrInvalidMsSize + fNumErrTimestampOverflow;
+      return (numErrors > 0 ? true : false);
+    }
   };
 
 
@@ -95,7 +101,7 @@ namespace cbm::algo
     /** @brief Set the parameter container
      ** @param params Pointer to parameter container
      **/
-    void SetParams(std::unique_ptr<UnpackStsPar> params) { fParams = std::move(params); }
+    void SetParams(std::unique_ptr<UnpackStsPar> params) { fParams = *(std::move(params)); }
 
 
   private:  // methods
@@ -127,7 +133,8 @@ namespace cbm::algo
     static constexpr uint64_t fkCycleLength =
       (fkEpochsPerCycle * fkEpochLength * fkClockCycleNom) / fkClockCycleDen;  ///< Epoch cycle length in ns
 
-    std::unique_ptr<UnpackStsPar> fParams = nullptr;  ///< Parameter container
+    //std::unique_ptr<UnpackStsPar> fParams = nullptr;  ///< Parameter container
+    UnpackStsPar fParams = {};  ///< Parameter container
   };
 
 
diff --git a/core/data/CMakeLists.txt b/core/data/CMakeLists.txt
index 1ee4b62b81a43d2aff4967b96909e43f8ea1e232..cc3b4c4631702c63848691894f3a8833836bcfd0 100644
--- a/core/data/CMakeLists.txt
+++ b/core/data/CMakeLists.txt
@@ -138,17 +138,12 @@ Set(NO_DICT_SRCS
 SET_SOURCE_FILES_PROPERTIES(tof/etof/star_rhicf.c PROPERTIES COMPILE_FLAGS -Wno-pointer-sign)
 
 CHANGE_FILE_EXTENSION(*.cxx *.h HEADERS "${SRCS}")
-list(APPEND HEADERS base/CbmDigiData.h global/CbmDigiEvent.h)
+list(APPEND HEADERS base/CbmDigiData.h global/CbmDigiEvent.h global/CbmDigiTimeslice.h)
 
 set(LINKDEF DataLinkDef.h)
 set(LIBRARY_NAME CbmData)
 set(DEPENDENCIES Base)
 
-# Generate list of headers from the list of source files and add some extra header files 
-CHANGE_FILE_EXTENSION(*.cxx *.h HEADERS "${SRCS}")
-list(APPEND HEADERS global/CbmDigiEvent.h)
-
-
 GENERATE_LIBRARY()
 
 # Install file which has no corresponding source file
diff --git a/core/data/DataLinkDef.h b/core/data/DataLinkDef.h
index 66e9f013d61da20813feaa769fe99b82e2303b2d..781ad0795c9cf79bfc908abad0f3f88b85a64881 100644
--- a/core/data/DataLinkDef.h
+++ b/core/data/DataLinkDef.h
@@ -133,7 +133,7 @@
 #pragma link C++ class CbmDigiVector < CbmPsdDsp> + ;
 #pragma link C++ class vector < CbmEventStore> + ;
 
-#pragma link C++ class std::vector < CbmEvent > + ;
+#pragma link C++ class std::vector < CbmEvent> + ;
 #pragma link C++ class StsDigiData + ;
 #pragma link C++ class MuchDigiData + ;
 #pragma link C++ class RichDigiData + ;
@@ -142,7 +142,8 @@
 #pragma link C++ class PsdDigiData + ;
 #pragma link C++ class CbmDigiData + ;
 #pragma link C++ class CbmDigiEvent + ;
-#pragma link C++ class std::vector < CbmDigiEvent > + ;
+#pragma link C++ class std::vector < CbmDigiEvent> + ;
+#pragma link C++ class CbmDigiTimeslice + ;
 
 /* clang-format off */
 #pragma read sourceClass="CbmStsDigi" version="[7]" targetClass="CbmStsDigi" \
diff --git a/core/data/base/CbmDigiData.h b/core/data/base/CbmDigiData.h
index 01d6444aa8a4166d8bd23aa01dc79737818b40be..28448e246602411581c0baddb4290dafe5530fca 100644
--- a/core/data/base/CbmDigiData.h
+++ b/core/data/base/CbmDigiData.h
@@ -35,6 +35,7 @@ struct DigiVec {
   {
     ar& fDigis;
   }
+  void Clear() { fDigis.clear(); }
 };
 
 /** Unless a detector-specific implementation for the digi data is present, the
@@ -73,6 +74,16 @@ struct CbmDigiData {
     ar& fPsd;
     ar& fRich;
   }
+  void Clear()
+  {
+    fT0.Clear();
+    fSts.Clear();
+    fMuch.Clear();
+    fTrd.Clear();
+    fTof.Clear();
+    fPsd.Clear();
+    fRich.Clear();
+  }
 };
 
 
diff --git a/core/data/global/CbmDigiTimeslice.h b/core/data/global/CbmDigiTimeslice.h
index c818a4577dedfc073113d2991b842ee8ddc7d317..b6b9e0f29b97686b1aab3f67645fa44c4cadd1a2 100644
--- a/core/data/global/CbmDigiTimeslice.h
+++ b/core/data/global/CbmDigiTimeslice.h
@@ -25,6 +25,11 @@ struct CbmDigiTimeslice {
     ar& fData;
     ar& fDesc;
   }
+  void Clear()
+  {
+    fData.Clear();
+    fDesc = fles::TimesliceDescriptor();
+  }
 };
 
 #endif /* CBMDIGITIMESLICE_H */
diff --git a/macro/reco/reco_unpack.C b/macro/reco/reco_unpack.C
new file mode 100644
index 0000000000000000000000000000000000000000..8e9af237530c34209d2af8bad660ec7bdb74dfdb
--- /dev/null
+++ b/macro/reco/reco_unpack.C
@@ -0,0 +1,102 @@
+/* Copyright (C) 2021 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Volker Friese [committer] */
+
+
+// --- Includes needed for IDE code analyser
+#if !defined(__CLING__)
+#include "CbmSourceTs.h"
+#include "CbmTaskUnpack.h"
+#include "CbmTsEventHeader.h"
+
+#include <FairRootFileSink.h>
+#include <FairRunOnline.h>
+
+#include <TStopwatch.h>
+#include <TSystem.h>
+
+#include <iostream>
+#endif
+
+using std::string;
+
+void reco_unpack(TString tsaFile = "", TString outFile = "")
+{
+
+  // ========================================================================
+  //          Adjust this part according to your requirements
+
+  // --- Logger settings ----------------------------------------------------
+  TString logLevel     = "INFO";
+  TString logVerbosity = "LOW";
+  // ------------------------------------------------------------------------
+
+  // -----   Environment   --------------------------------------------------
+  TString myName = "reco_unpack";                  // this macro's name for screen output
+  TString srcDir = gSystem->Getenv("VMCWORKDIR");  // top source directory
+  // ------------------------------------------------------------------------
+
+  // In general, the following parts need not be touched
+  // ========================================================================
+
+  //tsaFile = "/Users/vfriese/Cbmroot/data/1588_node8_1_0000";   Used for testing
+  //outFile = "test";
+
+  // ----- Default file names   ---------------------------------------------
+  if (tsaFile.IsNull()) tsaFile = srcDir + "/input/mcbm_run399_first20Ts";
+  TString inFile = tsaFile + ".tsa";
+  if (outFile.IsNull()) outFile = tsaFile;
+  outFile += ".digi.root";
+  // ------------------------------------------------------------------------
+
+
+  // -----   Timer   --------------------------------------------------------
+  TStopwatch timer;
+  timer.Start();
+  // ------------------------------------------------------------------------
+
+
+  // ----   Run configuration   ---------------------------------------------
+  auto source = new CbmSourceTs(inFile.Data());
+  auto run    = new FairRunOnline(source);
+  auto sink   = new FairRootFileSink(outFile.Data());
+  run->SetSink(sink);
+  auto eventheader = new CbmTsEventHeader();
+  run->SetEventHeader(new CbmTsEventHeader());
+  // ------------------------------------------------------------------------
+
+
+  // -----   Unpacking task   -----------------------------------------------
+  auto unpack = new CbmTaskUnpack();
+  run->AddTask(unpack);
+  // ------------------------------------------------------------------------
+
+
+  // -----   Logger settings   ----------------------------------------------
+  FairLogger::GetLogger()->SetLogScreenLevel(logLevel.Data());
+  FairLogger::GetLogger()->SetLogVerbosityLevel(logVerbosity.Data());
+  // ------------------------------------------------------------------------
+
+
+  // -----   Run initialisation   -------------------------------------------
+  std::cout << std::endl;
+  std::cout << "-I- " << myName << ": Initialise run" << std::endl;
+  run->Init();
+  // ------------------------------------------------------------------------
+
+
+  // -----   Start run   ----------------------------------------------------
+  std::cout << std::endl << std::endl;
+  std::cout << "-I- " << myName << ": Starting run" << std::endl;
+  run->Run(-1, 0);
+  // ------------------------------------------------------------------------
+
+
+  // -----   Finish   -------------------------------------------------------
+  timer.Stop();
+  std::cout << "\nMacro finished successfully." << std::endl;
+  std::cout << "Output file: " << outFile << std::endl;
+  std::cout << "CPU time = " << timer.CpuTime() << " s, real time = " << timer.RealTime() << " s." << std::endl;
+  // ------------------------------------------------------------------------
+
+}  // End of main macro function
diff --git a/reco/tasks/CMakeLists.txt b/reco/tasks/CMakeLists.txt
index 5896b2ef13b6d74adf299d40305ba3432886537a..4113b15c9dff9b8be32a442ce2d7abaafe592554 100644
--- a/reco/tasks/CMakeLists.txt
+++ b/reco/tasks/CMakeLists.txt
@@ -12,6 +12,8 @@ set(SRCS
 CbmTaskBuildEvents.cxx
 CbmTaskMakeRecoEvents.cxx
 CbmTaskTriggerDigi.cxx
+CbmTaskUnpack.cxx
+CbmSourceTs.cxx
 )
 # ---------------------------------------------------------
 
@@ -31,9 +33,11 @@ ${CBMROOT_SOURCE_DIR}/core/data/much
 ${CBMROOT_SOURCE_DIR}/core/data/trd
 ${CBMROOT_SOURCE_DIR}/core/data/tof
 ${CBMROOT_SOURCE_DIR}/core/data/psd
+${CBMROOT_SOURCE_DIR}/core/data/raw
 
 ${CBMROOT_SOURCE_DIR}/algo/evbuild
 ${CBMROOT_SOURCE_DIR}/algo/trigger
+${CBMROOT_SOURCE_DIR}/algo/detectors/sts
 
 )
 
diff --git a/reco/tasks/CbmRecoTasksLinkDef.h b/reco/tasks/CbmRecoTasksLinkDef.h
index 0e54302a7ceed2b3cc1550550afe335416271de0..5525af94d54845f4099eb5c8d262ca1ec5b388cf 100644
--- a/reco/tasks/CbmRecoTasksLinkDef.h
+++ b/reco/tasks/CbmRecoTasksLinkDef.h
@@ -14,5 +14,7 @@
 #pragma link C++ class CbmTaskBuildEvents + ;
 #pragma link C++ class CbmTaskMakeRecoEvents + ;
 #pragma link C++ class CbmTaskTriggerDigi + ;
+#pragma link C++ class CbmTaskUnpack + ;
+#pragma link C++ class CbmSourceTs + ;
 
 #endif /* __CINT__ */
diff --git a/reco/tasks/CbmSourceTs.cxx b/reco/tasks/CbmSourceTs.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..ccc6ba59513589c53793923a477e9e6f28e1fe8f
--- /dev/null
+++ b/reco/tasks/CbmSourceTs.cxx
@@ -0,0 +1,67 @@
+/* Copyright (C) 2021 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Volker Friese[committer] */
+
+#include "CbmSourceTs.h"
+
+#include <TimesliceAutoSource.hpp>
+
+#include <FairSource.h>
+#include <Logger.h>
+
+
+using fles::Timeslice;
+using std::string;
+using std::vector;
+
+
+// -----   Constructor   ------------------------------------------------------
+CbmSourceTs::CbmSourceTs(const char* fileName) { AddInputFile(fileName); }
+// ----------------------------------------------------------------------------
+
+// -----   Constructor   ------------------------------------------------------
+CbmSourceTs::CbmSourceTs(vector<string> fileNames) : fFileNames(fileNames) {}
+// ----------------------------------------------------------------------------
+
+
+// -----   Add an input file   ------------------------------------------------
+size_t CbmSourceTs::AddInputFile(const char* fileName)
+{
+  string sFile(fileName);
+  if (sFile.size()) fFileNames.push_back(sFile);
+  return fFileNames.size();
+}
+// ----------------------------------------------------------------------------
+
+
+// -----   Close   -----------------------------------------------------------
+void CbmSourceTs::Close() {}
+// ----------------------------------------------------------------------------
+
+
+// -----   Initialisation   ---------------------------------------------------
+Bool_t CbmSourceTs::Init()
+{
+  LOG(info) << "SourceTs: Creating TimesliceSource with " << fFileNames.size() << " input files.";
+  fFlesSource = new fles::TimesliceAutoSource(fFileNames);
+  return kTRUE;
+}
+// ----------------------------------------------------------------------------
+
+
+// -----   Read one time slice from archive   ---------------------------------
+Int_t CbmSourceTs::ReadEvent(UInt_t)
+{
+  fFlesTs = fFlesSource->get();
+  if (!fFlesTs) {
+    LOG(info) << "SourceTs: End of archive reached; stopping run.";
+    return 1;
+  }
+  LOG(info) << "SourceTs: Reading time slice " << fNumTs << " (index " << fFlesTs->index() << ")";
+  fNumTs++;
+  return 0;
+}
+// ----------------------------------------------------------------------------
+
+
+ClassImp(CbmSourceTs)
diff --git a/reco/tasks/CbmSourceTs.h b/reco/tasks/CbmSourceTs.h
new file mode 100644
index 0000000000000000000000000000000000000000..b5c48a6205aca7ea8872fc29e2f600acca8b1e58
--- /dev/null
+++ b/reco/tasks/CbmSourceTs.h
@@ -0,0 +1,122 @@
+/* Copyright (C) 2021 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Volker Friese[committer] */
+
+#ifndef CBMSOURCETS_H
+#define CBMSOURCETS_H 1
+
+
+#include <TimesliceSource.hpp>
+
+#include <FairSource.h>
+
+#include <cstdint>
+#include <memory>
+#include <string>
+
+
+/** @class CbmSourceTs
+ ** @brief Source class for reading from archived time slice data
+ ** @author Volker Friese
+ ** @since 14 February 2022
+ **
+ ** This class allows to read time-slice data from file(s).
+ ** It interfaces fles::TimesliceAutoSource to cbmroot.
+ **/
+class CbmSourceTs : public FairSource {
+
+public:
+  /** @brief Constructor
+   ** @param fileName  Name of (single) input file.
+   **
+   ** More input files can be added by the method AddInputFile.
+   */
+  CbmSourceTs(const char* fileName = "");
+
+  /** @brief Constructor
+   ** @param fileName  Vector with name(s) of input file(s).
+   **
+   ** More input files can be added by the method AddInputFile.
+   */
+  CbmSourceTs(std::vector<std::string> fileNames);
+
+
+  /** @brief Destructor **/
+  virtual ~CbmSourceTs() {};
+
+
+  /** @brief Copy constructor - not implemented **/
+  CbmSourceTs(const CbmSourceTs&) = delete;
+
+
+  /** @brief Assignment operator - not implemented **/
+  CbmSourceTs& operator=(const CbmSourceTs&) = delete;
+
+
+  /** @brief Add an input file
+   ** @param fileName  Input file name
+   ** @return Number of input files after adding this one
+   **/
+  size_t AddInputFile(const char* fileName);
+
+
+  /** @brief Demanded by base class **/
+  virtual void Close();
+
+
+  /** @brief Demanded by base class **/
+  virtual Source_Type GetSourceType() { return kFILE; }
+
+
+  /** @brief Pointer to current FLES timeslice **/
+  //std::shared_ptr<fles::Timeslice> GetTimeslice() { return fFlesTs; }
+  fles::Timeslice* GetTimeslice() { return fFlesTs.get(); }
+
+
+  /** @brief Initialisation **/
+  virtual Bool_t Init();
+
+
+  /** @brief Demanded by base class **/
+  virtual Bool_t InitUnpackers() { return kTRUE; }
+
+
+  /** @brief Read one time slice from file **/
+  virtual Int_t ReadEvent(UInt_t = 0);
+
+
+  /** @brief Demanded by base class **/
+  virtual Bool_t ReInitUnpackers() { return kTRUE; }
+
+
+  /** @brief Demanded by base class **/
+  virtual void Reset() {}
+
+
+  /** @brief Demanded by base class **/
+  virtual void SetParUnpackers() {}
+
+
+  /** @brief Demanded by base class **/
+  virtual Bool_t SpecifyRunId() { return kTRUE; }
+
+
+private:  // member variables
+  /** List of input file names **/
+  std::vector<std::string> fFileNames = {};
+
+  /** FLES interface **/
+  fles::TimesliceSource* fFlesSource = nullptr;  //!
+
+  /** Pointer to current time slice **/
+  std::unique_ptr<fles::Timeslice> fFlesTs = nullptr;  //!
+
+  /** Time-slice counter **/
+  size_t fNumTs = 0;
+
+
+  ClassDef(CbmSourceTs, 1)
+};
+
+
+#endif
diff --git a/reco/tasks/CbmTaskUnpack.cxx b/reco/tasks/CbmTaskUnpack.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..4927255e38ecd2c03983011c85305cde1633337c
--- /dev/null
+++ b/reco/tasks/CbmTaskUnpack.cxx
@@ -0,0 +1,356 @@
+/* Copyright (C) 2022 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Volker Friese [committer] */
+
+
+#include "CbmTaskUnpack.h"
+
+#include "CbmDefs.h"
+#include "CbmDigiBranchBase.h"
+#include "CbmDigiEvent.h"
+#include "CbmDigiManager.h"
+#include "CbmDigiTimeslice.h"
+#include "CbmSourceTs.h"
+
+#include "MicrosliceDescriptor.hpp"
+
+#include <FairLogger.h>
+#include <FairRunOnline.h>
+
+#include <TStopwatch.h>
+
+#include <algorithm>
+#include <cassert>
+#include <cstdint>
+#include <iomanip>
+#include <iostream>
+#include <memory>
+#include <sstream>
+#include <vector>
+
+#include "UnpackSts.h"
+
+
+using namespace std;
+using cbm::algo::UnpackStsElinkPar;
+using cbm::algo::UnpackStsPar;
+
+
+// -----   Constructor   -----------------------------------------------------
+CbmTaskUnpack::CbmTaskUnpack() : FairTask("Unpack") {}
+// ---------------------------------------------------------------------------
+
+
+// -----   Destructor   ------------------------------------------------------
+CbmTaskUnpack::~CbmTaskUnpack()
+{
+  if (fTimeslice) delete fTimeslice;
+}
+// ---------------------------------------------------------------------------
+
+
+// -----   Execution   -------------------------------------------------------
+void CbmTaskUnpack::Exec(Option_t*)
+{
+
+  // --- Reset output branch (CbmDigiTimeslice)
+  fTimeslice->Clear();
+
+  // --- Get FLES timeslice
+  assert(fSource);
+  fles::Timeslice* timeslice = fSource->GetTimeslice();
+  assert(timeslice);
+
+  // --- Timer and counters
+  TStopwatch timer;
+  TStopwatch compTimer;
+  timer.Start();
+  size_t numMs    = 0;
+  size_t numDigis = 0;
+
+  // --- Timeslice properties
+  uint64_t tsIndex     = timeslice->index();
+  uint64_t tsTime      = timeslice->start_time();
+  uint64_t numComp     = timeslice->num_components();
+  uint64_t numCompUsed = 0;
+
+  LOG(info) << GetName() << ": TS " << tsIndex << " at t = " << tsTime << ", components " << numComp;
+
+  // ---  Component loop
+  for (uint64_t comp = 0; comp < numComp; comp++) {
+
+    uint8_t systemId = timeslice->descriptor(comp, 0).sys_id;
+    if (systemId == static_cast<uint8_t>(fles::SubsystemIdentifier::STS)) {
+
+      // --- Component log
+      size_t numDigisInComp = 0;
+      compTimer.Start();
+
+      // --- Microslice loop
+      uint64_t numMsInComp = timeslice->num_microslices(comp);
+      for (uint64_t mslice = 0; mslice < numMsInComp; mslice++) {
+        auto msDescriptor = timeslice->descriptor(comp, mslice);
+        auto msContent    = timeslice->content(comp, mslice);
+        auto result       = fAlgoSts[comp](msContent, msDescriptor, tsTime);
+        LOG(debug1) << GetName() << ": Component " << comp << ", microslice " << mslice << ", digis "
+                    << result.first.size() << ", errors " << result.second.fNumNonHitOrTsbMessage << " | "
+                    << result.second.fNumErrElinkOutOfRange << " | " << result.second.fNumErrInvalidFirstMessage
+                    << " | " << result.second.fNumErrInvalidMsSize << " | " << result.second.fNumErrTimestampOverflow
+                    << " | ";
+        //std::move(result.first.begin(), result.first.end(), fTimeslice->fData.fSts.fDigis.end());
+        // TODO: The above usage of std::move does not work (seg. fault). Would need advice.
+        auto it = fTimeslice->fData.fSts.fDigis.end();
+        fTimeslice->fData.fSts.fDigis.insert(it, result.first.begin(), result.first.end());
+        numDigisInComp += result.first.size();
+      }  //# microslice
+
+      compTimer.Stop();
+      LOG(info) << GetName() << ": Component " << comp << ", microslices " << numMsInComp << ", digis "
+                << numDigisInComp << ", CPU time " << compTimer.CpuTime() * 1000. << " ms";
+      numCompUsed++;
+      numDigis += numDigisInComp;
+      numMs += numMsInComp;
+
+    }  //? system (only STS)
+
+  }  //# component
+
+
+  // --- Timeslice log
+  timer.Stop();
+  stringstream logOut;
+  logOut << setw(20) << left << GetName() << " [";
+  logOut << fixed << setw(8) << setprecision(1) << right << timer.RealTime() * 1000. << " ms] ";
+  logOut << "TS " << fNumTs << " (index " << tsIndex << ")";
+  logOut << ", components " << numCompUsed << " / " << numComp << ", microslices " << numMs;
+  logOut << ", digis " << numDigis;
+  LOG(info) << logOut.str();
+
+  // --- Run statistics
+  fNumTs++;
+  fNumMs += numMs;
+  fNumDigis += numDigis;
+  fTime += timer.RealTime();
+}
+// ----------------------------------------------------------------------------
+
+
+// -----   End-of-run action   ------------------------------------------------
+void CbmTaskUnpack::Finish()
+{
+
+  std::cout << std::endl;
+  LOG(info) << "=====================================";
+  LOG(info) << GetName() << ": Run summary";
+  LOG(info) << "Timeslices         : " << fNumTs;
+  LOG(info) << "Microslices        : " << fNumMs;
+  LOG(info) << "Digis              : " << fNumDigis;
+  LOG(info) << "Time  / TS         : " << fixed << setprecision(2) << 1000. * fTime / double(fNumTs) << " ms";
+  LOG(info) << "=====================================";
+}
+// ----------------------------------------------------------------------------
+
+
+// -----   Initialisation   ---------------------------------------------------
+InitStatus CbmTaskUnpack::Init()
+{
+
+  std::cout << std::endl;
+  LOG(info) << "==================================================";
+  LOG(info) << GetName() << ": Init";
+
+  // --- Get source instance
+  fSource = dynamic_cast<CbmSourceTs*>(FairRunOnline::Instance()->GetSource());
+  if (fSource == nullptr) {
+    LOG(error) << GetName() << ": No valid source class registered!";
+    return kFATAL;
+  }
+  LOG(info) << GetName() << ": Found CbmSourceTs instance";
+
+  // --- Get FairRootManager instance
+  FairRootManager* ioman = FairRootManager::Instance();
+  assert(ioman);
+
+  // --- Register output array (CbmDigiTimeslice)
+  if (ioman->GetObject("DigiTimeslice")) {
+    LOG(fatal) << GetName() << ": Branch DigiTimeslice already exists!";
+    return kFATAL;
+  }
+  fTimeslice = new CbmDigiTimeslice();
+  ioman->RegisterAny("DigiTimeslice.", fTimeslice, kTRUE);
+  LOG(info) << GetName() << ": Registered branch DigiTimeslice.";
+
+  // --- Initialise STS readout configuration
+  //InitStsConfig();
+
+  // --- Common parameters for all components
+  uint32_t numChansPerAsic   = 128;  // R/O channels per ASIC
+  uint32_t numAsicsPerModule = 16;   // Number of ASICs per module
+
+  // Create one algorithm per component and configure it with parameters
+  auto equipIds = fStsConfig.GetEquipmentIds();
+  for (auto& equip : equipIds) {
+    std::unique_ptr<UnpackStsPar> par(new UnpackStsPar());
+    par->fNumChansPerAsic   = numChansPerAsic;
+    par->fNumAsicsPerModule = numAsicsPerModule;
+    size_t numElinks        = fStsConfig.GetNumElinks(equip);
+    for (size_t elink = 0; elink < numElinks; elink++) {
+      UnpackStsElinkPar elinkPar;
+      auto mapEntry        = fStsConfig.Map(equip, elink);
+      elinkPar.fAddress    = mapEntry.first;   // Module address for this elink
+      elinkPar.fAsicNr     = mapEntry.second;  // ASIC number within module
+      elinkPar.fTimeOffset = 0.;
+      elinkPar.fAdcOffset  = 0.;
+      elinkPar.fAdcGain    = 0.;
+      // TODO: Add parameters for time and ADC calibration
+      par->fElinkParams.push_back(elinkPar);
+    }
+    cbm::algo::UnpackSts algo;
+    algo.SetParams(std::move(par));
+    fAlgoSts.push_back(algo);
+    LOG(info) << GetName() << ": configured equipment " << equip << " with " << numElinks << " elinks";
+  }  //# equipments
+
+  LOG(info) << GetName() << ": configured " << fAlgoSts.size() << " unpacker algorithms for STS.";
+  LOG(info) << "==================================================";
+  std::cout << std::endl;
+  LOG(info) << "Readout map:" << fStsConfig.PrintReadoutMap();
+
+  return kSUCCESS;
+}
+// ----------------------------------------------------------------------------
+
+
+// -----   Set the STS readout configuration   --------------------------------
+void CbmTaskUnpack::InitStsConfig()
+{
+
+  // This here refers to the mCBM 2021 setup. (from macro/beamtime/mcbm2021/mStsPar.par)
+  // I have to confess that this is among the weirdest things I ever came across.
+
+  // Constants
+  const uint16_t numModules       = 13;  // Number of modules in the setup
+  const uint16_t numElinksPerCrob = 42;  // Number of elinks per CROB
+  const uint16_t numAsicsPerFeb   = 8;   // Number of ASICs per FEB
+
+  // Module addresses and types
+  // Type 0 means connector at the right side, type 1 has connector at the left
+  int32_t modAddress[numModules];
+  modAddress[0]            = 0x10008002;
+  modAddress[1]            = 0x10018002;
+  modAddress[2]            = 0x10008402;
+  modAddress[3]            = 0x10018402;
+  modAddress[4]            = 0x10107C02;
+  modAddress[5]            = 0x10008412;
+  modAddress[6]            = 0x10018412;
+  modAddress[7]            = 0x101FFC02;
+  modAddress[8]            = 0x10008012;
+  modAddress[9]            = 0x10018012;
+  modAddress[10]           = 0x10008812;
+  modAddress[11]           = 0x10018812;
+  modAddress[12]           = 0x10028812;
+  bool modType[numModules] = {0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1};
+
+  // Mapping of eLink to ASIC for FEB A
+  const uint32_t elink2AsicFebA[numElinksPerCrob] = {
+    0x0021, 0x0023, 0x0025, 0x0020, 0x0022, 0x0018, 0x001A, 0x001C, 0x0024, 0x0027, 0xFFFF, 0xFFFF, 0x0026, 0x001E,
+    0x0010, 0x0012, 0x0019, 0x001B, 0x001F, 0x000E, 0x0011, 0x0013, 0x000C, 0x0015, 0x0017, 0x0016, 0x001D, 0x0014,
+    0x0009, 0x000D, 0x000F, 0x0008, 0x000A, 0x0002, 0x0004, 0x0006, 0x000B, 0x0005, 0x0000, 0x0003, 0x0007, 0x0001,
+  };
+
+  // Mapping of eLink to ASIC for FEB B
+  const uint32_t elink2AsicFebB[numElinksPerCrob] = {
+    0x0027, 0x0025, 0x0023, 0x0026, 0x0024, 0x001E, 0x001C, 0x001A, 0x0022, 0x0021, 0xFFFF, 0xFFFF, 0x0020, 0x0018,
+    0x0016, 0x0014, 0x001F, 0x001D, 0x0019, 0x0008, 0x0017, 0x0015, 0x000A, 0x0013, 0x0011, 0x0010, 0x001B, 0x0012,
+    0x000F, 0x000B, 0x0009, 0x000E, 0x000C, 0x0004, 0x0002, 0x0000, 0x000D, 0x0003, 0x0006, 0x0005, 0x0001, 0x0007};
+
+  // Mapping of eLink to FEB number within CROB. If -1, elink not used.
+  const int16_t elink2Feb[numElinksPerCrob] = {4, 4, 4, 4, 4, 3, 3, 3, 4, 4, -1, -1, 4, 3, 2, 2, 3, 3, 3, 1, 2,
+                                               2, 1, 2, 2, 2, 3, 2, 1, 1, 1, 1,  1,  0, 0, 0, 1, 0, 0, 0, 0, 0};
+
+
+  // The readout hierarchy is: DPB (component) - CROB - FEB
+  const uint16_t numComp = 5;  // Number of components
+  const uint16_t numCrob = 1;  // Number of CROBs per component
+  const uint16_t numFebs = 5;  // FEBs per CROB
+
+  // Mapping of component to equipment ID
+  uint16_t eqId[numComp] = {0x1003, 0x1002, 0x1006, 0x1005, 0x1004};
+
+  // Mapping of FEB to module index (-1 = inactive)
+  int16_t feb2module[numComp][numCrob][numFebs] = {
+    {{-1, 1, 1, 0, 0}},     // component 0
+    {{4, 3, 3, 2, 2}},      // component 1
+    {{7, 6, 6, 5, 5}},      // component 2
+    {{10, 9, 9, 8, 8}},     // component 3
+    {{12, 12, 11, 11, 10}}  // component 4
+  };
+
+  // Mapping of FEB to module side (0 = p side, 1 = n side, -1 = inactive)
+  int16_t feb2moduleSide[numComp][numCrob][numFebs] = {
+    {{-1, 1, 0, 1, 0}},  // component 0
+    {{1, 1, 0, 1, 0}},   // component 1
+    {{1, 1, 0, 1, 0}},   // component 2
+    {{0, 1, 0, 1, 0}},   // component 3
+    {{1, 0, 1, 0, 1}}    // component 4
+  };
+
+  // Map (component, CROB, eLink) -> (module, ASIC within module)
+  LOG(info) << "STS readout mapping: ";
+  for (uint16_t comp = 0; comp < numComp; comp++) {
+    uint16_t equipment = eqId[comp];
+    for (uint16_t crob = 0; crob < numCrob; crob++) {
+      for (uint16_t elink = 0; elink < numElinksPerCrob; elink++) {
+
+        int32_t moduleAddress = -1;
+        uint16_t asicInModule = 0;
+
+        uint16_t elinkId = numElinksPerCrob * crob + elink;  // elink within component
+        int16_t feb      = elink2Feb[elinkId];               // FEB within CROB
+        if (feb != -1) {
+          int16_t module = feb2module[comp][crob][feb];  // Module index
+          if (module != -1) {
+            assert(module < numModules);
+            bool moduleType    = modType[module];                               // 0 or 1
+            int16_t moduleSide = feb2moduleSide[comp][crob][feb];               // 0 or 1, -1 is inactive
+            int16_t febType    = (moduleType == 0 ? moduleSide : !moduleSide);  // 0 = FEB A, 1 = FEB B
+            uint32_t asicIndex = (febType == 0 ? elink2AsicFebA[elinkId] : elink2AsicFebB[elinkId]);
+            uint32_t asicInFeb = asicIndex % numAsicsPerFeb;  // ASIC number within FEB
+
+            moduleAddress = modAddress[module];
+            asicInModule  = (moduleSide == 0 ? asicInFeb : asicInFeb + numAsicsPerFeb);
+          }
+        }
+        fStsMap[equipment][elink] = std::make_pair(moduleAddress, asicInModule);
+
+      }  //# elink
+    }    //# CROB
+  }      //# component
+}
+// ----------------------------------------------------------------------------
+
+
+// -----   Print readout map   ------------------------------------------------
+std::string CbmTaskUnpack::PrintReadoutMap()
+{
+
+  std::stringstream ss;
+  for (auto& stsComp : fStsMap) {
+    for (auto& stsElink : stsComp.second) {
+      auto comp    = stsComp.first;
+      auto elink   = stsElink.first;
+      auto address = stsElink.second.first;
+      auto asic    = stsElink.second.second;
+      ss << "\n Component " << comp << "  elink " << setw(2) << elink;
+      ss << "  ASIC " << setw(2) << asic << "  module " << address;
+      ss << "  Unit " << setw(2) << CbmStsAddress::GetElementId(address, kStsUnit);
+      ss << "  Ladd " << setw(2) << CbmStsAddress::GetElementId(address, kStsLadder);
+      ss << "  Hlad " << setw(2) << CbmStsAddress::GetElementId(address, kStsHalfLadder);
+      ss << "  Modu " << setw(2) << CbmStsAddress::GetElementId(address, kStsModule);
+    }  //# elink
+  }    //# component
+  return ss.str();
+}
+// ----------------------------------------------------------------------------
+
+ClassImp(CbmTaskUnpack)
diff --git a/reco/tasks/CbmTaskUnpack.h b/reco/tasks/CbmTaskUnpack.h
new file mode 100644
index 0000000000000000000000000000000000000000..5a673735fcba7690d8fc741eeb027573f09847d0
--- /dev/null
+++ b/reco/tasks/CbmTaskUnpack.h
@@ -0,0 +1,93 @@
+/* Copyright (C) 2022 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Volker Friese [committer] */
+
+
+#ifndef CBMTASKUNPACK_H
+#define CBMTASKUNPACK_H 1
+
+#include "CbmDefs.h"
+#include "CbmDigiTimeslice.h"
+
+#include <FairTask.h>
+
+#include <sstream>
+#include <vector>
+
+#include "EventBuilder.h"
+#include "StsReadoutConfig.h"
+#include "UnpackSts.h"
+
+class CbmDigiManager;
+class CbmSourceTs;
+
+
+/** @class CbmTaskUnpack
+ ** @brief Task class for associating digis to events
+ ** @author Volker Friese <v.friese@gsi.de>
+ ** @since 15.11.2021
+ **
+ ** Creates objects of class CbmDigiEvent and fills them with digi objects,
+ ** using the algorithm EventBuilder.
+ **
+ ** TOFO: The current implementation is for STS only and with a dummy trigger list
+ ** just to establish the framework integration of algorithm and data interfaces.
+ **/
+class CbmTaskUnpack : public FairTask {
+
+
+public:
+  /** @brief Constructor **/
+  CbmTaskUnpack();
+
+
+  /** @brief Copy constructor (disabled) **/
+  CbmTaskUnpack(const CbmTaskUnpack&) = delete;
+
+
+  /** @brief Destructor **/
+  virtual ~CbmTaskUnpack();
+
+
+  /** @brief Task execution **/
+  virtual void Exec(Option_t* opt);
+
+
+  /** @brief Finish timeslice **/
+  virtual void Finish();
+
+
+  /** @brief Assignment operator (disabled) **/
+  CbmTaskUnpack& operator=(const CbmTaskUnpack&) = delete;
+
+
+private:  // methods
+  /** @brief Task initialisation **/
+  virtual InitStatus Init();
+
+
+  void InitStsConfig();
+
+
+private:  // members
+  CbmSourceTs* fSource                       = nullptr;
+  std::vector<cbm::algo::UnpackSts> fAlgoSts = {};  //!
+  cbm::algo::StsReadoutConfig fStsConfig {};
+  size_t fNumTs                = 0;
+  size_t fNumMs                = 0;
+  size_t fNumDigis             = 0;
+  double fTime                 = 0.;
+  CbmDigiTimeslice* fTimeslice = nullptr;  ///< Output data
+
+  // --- STS readout configuration; temporary in this class, will be moved later.
+  // --- Map index: (component, elink), map value: (module address, ASIC number in module
+  std::map<uint16_t, std::map<uint16_t, std::pair<int32_t, uint16_t>>> fStsMap = {};  //!
+
+private:  // methods
+  std::string PrintReadoutMap();
+
+
+  ClassDef(CbmTaskUnpack, 1);
+};
+
+#endif /* CBMTASKUNPACK_H */