diff --git a/algo/CMakeLists.txt b/algo/CMakeLists.txt
index 09ee075af5715bab37f3bb866c7d4f7bb488f38b..a2a052fa4026f62246d4be605c6a50c2217bf65a 100644
--- a/algo/CMakeLists.txt
+++ b/algo/CMakeLists.txt
@@ -95,6 +95,12 @@ set(SRCS
   evselector/DigiEventSelector.cxx
   evselector/DigiEventSelectorConfig.cxx
   unpack/CommonUnpacker.cxx
+  detectors/bmon/Calibrate.cxx
+  detectors/bmon/Clusterizer.cxx
+  detectors/bmon/Hitfind.cxx
+  detectors/bmon/ReadoutConfig.cxx
+  detectors/bmon/Unpack.cxx
+  detectors/bmon/UnpackMS.cxx
   detectors/sts/ChannelMaskSet.cxx
   detectors/sts/ReadoutConfig.cxx
   detectors/sts/HitfinderChain.cxx
@@ -113,9 +119,6 @@ set(SRCS
   detectors/tof/UnpackMS.cxx
   detectors/tof/Hitfind.cxx
   detectors/tof/TrackingInterface.cxx
-  detectors/bmon/ReadoutConfig.cxx
-  detectors/bmon/Unpack.cxx
-  detectors/bmon/UnpackMS.cxx
   detectors/trd/Cluster.cxx
   detectors/trd/Clusterizer.cxx
   detectors/trd/Cluster2D.cxx
@@ -151,6 +154,8 @@ set(SRCS
   qa/QaData.cxx
   qa/RecoGeneralQa.cxx
   qa/QaManager.cxx
+  qa/hitfind/BmonHitfindQa.cxx
+  qa/hitfind/BmonHitfindQaParameters.cxx
   qa/unpack/StsDigiQa.cxx
   ca/TrackingSetup.cxx
   ca/TrackingChain.cxx
@@ -188,6 +193,7 @@ target_include_directories(Algo
          ${CMAKE_CURRENT_SOURCE_DIR}/detectors
          ${CMAKE_CURRENT_SOURCE_DIR}/qa
          ${CMAKE_CURRENT_SOURCE_DIR}/qa/unpack
+         ${CMAKE_CURRENT_SOURCE_DIR}/qa/hitfind
          ${CMAKE_CURRENT_SOURCE_DIR}/kf
          ${CMAKE_CURRENT_SOURCE_DIR}/kf/core
          ${CMAKE_CURRENT_SOURCE_DIR}/kf/core/utils
diff --git a/algo/ca/qa/CaQa.h b/algo/ca/qa/CaQa.h
index 655d6fa50b16a9dac1133223ebafb56abeef08e7..1c42b60562f19c5adf5274c045971fe9bda330bf 100644
--- a/algo/ca/qa/CaQa.h
+++ b/algo/ca/qa/CaQa.h
@@ -54,7 +54,7 @@ namespace cbm::algo::ca
     static constexpr HitSetArray_t<EHitSet> kHitSets = {EHitSet::Input, EHitSet::Used};
 
    public:
-    /// \brief Default destructor
+    /// \brief Constructor
     /// \param pManager  Pointer to the QA manager
     /// \param name      Name of the QA (directory)
     Qa(const std::unique_ptr<qa::Manager>& pManager, std::string_view name) : qa::TaskHeader(pManager, name) {}
diff --git a/algo/detectors/bmon/Calibrate.cxx b/algo/detectors/bmon/Calibrate.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..d5519854902605e3c84e77f584324d3811c88d04
--- /dev/null
+++ b/algo/detectors/bmon/Calibrate.cxx
@@ -0,0 +1,168 @@
+/* Copyright (C) 2025 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergei Zharko [committer] */
+
+/// \file   Calibrate.h
+/// \brief  Calibrator for the BMON digis (implementation)
+/// \since  04.02.2025
+/// \author Sergei Zharko <s.zharko@gsi.de>
+
+#include "Calibrate.h"
+
+#include "AlgoFairloggerCompat.h"
+#include "CbmTofAddress.h"
+#include "util/TimingsFormat.h"
+
+#include <bitset>
+#include <chrono>
+
+using cbm::algo::bmon::Calibrate;
+using cbm::algo::bmon::CalibrateSetup;
+using fles::Subsystem;
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+Calibrate::Calibrate(CalibrateSetup setup) : fSetup(setup), fSelectionBitsOffset(0)
+{
+  // Initialize offset arrays for channel deadtime check
+  int nDiamondsInSetup = fSetup.diamonds.size();
+  if (nDiamondsInSetup == 0) {
+    throw std::runtime_error("No diamonds found in the BMON calibration config");
+  }
+  if (!(fSetup.selectionMask) != (nDiamondsInSetup == 1)) {
+    throw std::runtime_error("Wrong diamond selection mask: for a single diamond it must be zero, and for multiple"
+                             " diamonds it must be non-zero");
+  }
+  if (nDiamondsInSetup > 1) {
+    // Define the selection bit offset
+    while (!((fSetup.selectionMask >> fSelectionBitsOffset) % 2)) {
+      ++fSelectionBitsOffset;
+    }
+
+    // Sort the diamonds in the setup by their SM or Side or other distinguishing index
+    std::sort(fSetup.diamonds.begin(), fSetup.diamonds.end(), [&](const auto& lhs, const auto& rhs) {
+      return GetDiamondIndex(lhs.refAddress) < GetDiamondIndex(rhs.refAddress);
+    });
+  }
+
+  // Remove the channel information from the reference address
+  for (auto& diamond : fSetup.diamonds) {
+    diamond.refAddress &= ~CbmTofAddress::GetChannelIdBitmask();
+  }
+
+  // Calculate the channel offsets, needed for the dead time
+  fChannelOffset = std::vector<size_t>(nDiamondsInSetup + 1, 0);  // the last element -- total number of channels
+  for (int32_t iD = 0; iD < nDiamondsInSetup; ++iD) {
+    fChannelOffset[iD + 1] = fChannelOffset[iD] + fSetup.diamonds[iD].chanPar.size();
+  }
+  fChannelDeadTime = std::vector<double>(fChannelOffset.back(), std::numeric_limits<double>::quiet_NaN());
+
+  // **** DEBUG: BEGIN
+  for (size_t iO = 0; iO < fChannelOffset.size(); ++iO) {
+    L_(info) << "Channel offset: diamond=" << iO << ", offset=" << fChannelOffset[iO];
+  }
+  L_(info) << "Selection mask: 0b" << std::bitset<32>(fSetup.selectionMask);
+  L_(info) << "Bits offset: " << static_cast<uint32_t>(fSelectionBitsOffset);
+  L_(info) << "Size of the channel dead time vector: " << fChannelDeadTime.size();
+  // **** DEBUG: END
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+Calibrate::resultType Calibrate::operator()(gsl::span<const CbmBmonDigi> digiIn)
+{
+  xpu::push_timer("BmonCalibrate");
+  xpu::t_add_bytes(digiIn.size_bytes());
+
+  // --- Output data
+  resultType result = {};
+
+  auto& calDigiOut = std::get<0>(result);
+  auto& monitor    = std::get<1>(result);
+  calDigiOut.reserve(digiIn.size());
+
+  // Reset the channel dead time
+  std::fill(fChannelDeadTime.begin(), fChannelDeadTime.end(), std::numeric_limits<double>::quiet_NaN());
+
+  const auto& diamonds = fSetup.diamonds;
+  for (const auto& digi : digiIn) {
+    uint32_t address = static_cast<uint32_t>(digi.GetAddress());
+    int32_t iChannel = CbmTofAddress::GetChannelId(address);
+    size_t iDiamond  = GetDiamondIndex(address);
+    if (iDiamond >= diamonds.size()
+        || (address & ~CbmTofAddress::GetChannelIdBitmask()) != diamonds[iDiamond].refAddress) {
+      monitor.fDigiCalibUnknownRPC++;
+      L_(error) << "Unknown BMON digi address: " << CbmTofAddress::ToString(address) << ", iDiamond = " << iDiamond;
+      continue;
+    }
+
+    const auto& diamondPar = diamonds[iDiamond];
+    const auto& channelPar = diamondPar.chanPar[iChannel];
+
+    // Check dead time
+    const size_t iChannelGlb = fChannelOffset[iDiamond] + iChannel;
+    const double deadTime    = fChannelDeadTime[iChannelGlb];
+
+    if (!std::isnan(deadTime) && digi.GetTime() <= deadTime) {
+      fChannelDeadTime[iChannelGlb] = digi.GetTime() + diamondPar.channelDeadtime;
+      monitor.fDigiDeadTimeCount++;
+      continue;
+    }
+    fChannelDeadTime[iChannelGlb] = digi.GetTime() + diamondPar.channelDeadtime;
+
+    // Create calibrated digi
+    CbmBmonDigi& pCalDigi = calDigiOut.emplace_back(digi);
+    pCalDigi.SetAddress(address);
+
+    // calibrate Digi Time
+    pCalDigi.SetTime(pCalDigi.GetTime() - channelPar.vCPTOff);
+
+    // subtract Offset
+    const double dTot = std::max(pCalDigi.GetCharge() - channelPar.vCPTotOff, 0.001);
+
+    // calibrate Digi charge (corresponds to TOF ToT)
+    pCalDigi.SetCharge(dTot * channelPar.vCPTotGain);
+
+    // walk correction
+    const std::vector<double>& walk = channelPar.vCPWalk;
+    const double dTotBinSize        = (diamondPar.TOTMax - diamondPar.TOTMin) / diamondPar.numClWalkBinX;
+    int32_t iWx                     = std::max((int32_t)((pCalDigi.GetCharge() - diamondPar.TOTMin) / dTotBinSize), 0);
+    iWx                             = std::min(iWx, diamondPar.numClWalkBinX - 1);
+
+    const double dDTot = (pCalDigi.GetCharge() - diamondPar.TOTMin) / dTotBinSize - (double) iWx - 0.5;
+    double dWT         = walk[iWx];
+
+    // linear interpolation to next bin
+    if (dDTot > 0) {
+      if (iWx < diamondPar.numClWalkBinX - 1) {
+        dWT += dDTot * (walk[iWx + 1] - walk[iWx]);
+      }
+    }
+    else {
+      if (0 < iWx) {
+        dWT -= dDTot * (walk[iWx - 1] - walk[iWx]);
+      }
+    }
+    pCalDigi.SetTime(pCalDigi.GetTime() - dWT);  // calibrate Digi Time
+  }
+
+  /// Sort the buffers of hits due to the time offsets applied
+  // (insert-sort faster than std::sort due to pre-sorting)
+  for (std::size_t i = 1; i < calDigiOut.size(); ++i) {
+    CbmTofDigi digi = calDigiOut[i];
+    std::size_t j   = i;
+    while (j > 0 && calDigiOut[j - 1].GetTime() > digi.GetTime()) {
+      calDigiOut[j] = calDigiOut[j - 1];
+      --j;
+    }
+    calDigiOut[j] = digi;
+  }
+
+  //  Kept for possible unsorted input
+  //  std::sort(calDigiOut.begin(), calDigiOut.end(),
+  //  [](const CbmTofDigi& a, const CbmTofDigi& b) -> bool { return a.GetTime() < b.GetTime(); });
+
+  monitor.fTime     = xpu::pop_timer();
+  monitor.fNumDigis = digiIn.size();
+  return result;
+}
diff --git a/algo/detectors/bmon/Calibrate.h b/algo/detectors/bmon/Calibrate.h
new file mode 100644
index 0000000000000000000000000000000000000000..644fd203200dbceeac0402248b5cf3a2943be3c3
--- /dev/null
+++ b/algo/detectors/bmon/Calibrate.h
@@ -0,0 +1,56 @@
+/* Copyright (C) 2025 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergei Zharko [committer] */
+
+/// \file   Calibrate.h
+/// \brief  Calibratior for the BMON digis
+/// \since  04.02.2025
+/// \author Sergei Zharko <s.zharko@gsi.de>
+
+#pragma once
+
+#include "CbmBmonDigi.h"
+#include "PartitionedVector.h"
+#include "bmon/CalibrateSetup.h"
+#include "tof/Calibrate.h"  // for the monitor data
+
+#include <gsl/span>
+#include <optional>
+#include <sstream>
+#include <vector>
+
+#include <xpu/host.h>
+
+namespace cbm::algo::bmon
+{
+  // NOTE: reusing TOF monitor
+  using CalibrateMonitorData = tof::CalibrateMonitorData;
+
+  /// \class Calibrate
+  /// \brief Algorithm to calibrate BMon digis
+  class Calibrate {
+   public:
+    using resultType = std::tuple<std::vector<CbmBmonDigi>, CalibrateMonitorData>;
+
+    /// \brief Constructor
+    /// \param params  Calibration parameters
+    explicit Calibrate(CalibrateSetup params);
+
+    /// \brief Calibrates a portion of digis
+    /// \param digiIn  A portion of digis to calibrate
+    resultType operator()(gsl::span<const CbmBmonDigi> digiIn);
+
+   private:
+    /// \brief Returns an index of the diamond by the address
+    /// \param address  A hardware address of the digi
+    size_t GetDiamondIndex(uint32_t address) const
+    {
+      return ((fSetup.selectionMask & address) >> fSelectionBitsOffset);
+    }
+
+    CalibrateSetup fSetup;                 ///< Parameters of calibrator
+    std::vector<size_t> fChannelOffset;    ///< Channel offset: offset for the channel index of each diamond
+    std::vector<double> fChannelDeadTime;  ///< Dead time, stored for a channel
+    uint32_t fSelectionBitsOffset;         ///< Number of bits to ther right from the first bit in the selection mask
+  };
+}  // namespace cbm::algo::bmon
diff --git a/algo/detectors/bmon/CalibrateSetup.h b/algo/detectors/bmon/CalibrateSetup.h
new file mode 100644
index 0000000000000000000000000000000000000000..eeeb58e1784c7ff8896bb5050157171aff162e79
--- /dev/null
+++ b/algo/detectors/bmon/CalibrateSetup.h
@@ -0,0 +1,64 @@
+/* Copyright (C) 2025 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergei Zharko [committer] */
+
+/// \file   CalibrateSetup.h
+/// \brief  Configuration of the calibrator for the BMON digis
+/// \since  04.02.2025
+/// \author Sergei Zharko <s.zharko@gsi.de>
+
+#pragma once
+
+#include "Definitions.h"
+#include "yaml/Property.h"
+
+#include <array>
+#include <map>
+#include <string>
+#include <vector>
+
+namespace cbm::algo::bmon
+{
+  /// \struct  CalibrateSetup
+  /// \brief   BMON calibration per channel
+  struct CalibrateSetup {
+    // FIXME: remove 'v' from non-vector variable names
+    struct Channel {
+      double vCPTOff;
+      double vCPTotGain;
+      double vCPTotOff;
+      std::vector<double> vCPWalk;
+
+      CBM_YAML_PROPERTIES(yaml::Property(&Channel::vCPTOff, "vCPTOff", "CPT offset"),
+                        yaml::Property(&Channel::vCPTotGain, "vCPTotGain", "CP time over threshold gain"),
+                        yaml::Property(&Channel::vCPTotOff, "vCPTotOff", "CP time over threshold offset"),
+                        yaml::Property(&Channel::vCPWalk, "vCPWalk", "CP walk correction", YAML::Block, YAML::Flow));
+    };
+
+    struct Diamond {
+      uint32_t refAddress;
+      int32_t numClWalkBinX;
+      double TOTMax;
+      double TOTMin;
+      double channelDeadtime;
+      std::vector<Channel> chanPar;
+
+      CBM_YAML_PROPERTIES(
+                        yaml::Property(&Diamond::refAddress, "refAddress", "reference HW address to distinguish this BMON"),
+                        yaml::Property(&Diamond::numClWalkBinX, "numClWalkBinX", "number of walk correction bins"),
+                        yaml::Property(&Diamond::TOTMax, "TOTMax", "maximum time over threshold"),
+                        yaml::Property(&Diamond::TOTMin, "TOTMin", "minimum time over threshold"),
+                        yaml::Property(&Diamond::channelDeadtime, "channelDeadtime", "channel dead time"),
+                        yaml::Property(&Diamond::chanPar, "chanPar", "channel parameters"));
+    };
+
+    /* Members */
+    uint32_t selectionMask;
+    std::vector<Diamond> diamonds;
+
+    CBM_YAML_PROPERTIES(
+      yaml::Property(&CalibrateSetup::selectionMask, "selectionMask", "A bit mask to distinguish between different diamonds"),
+      yaml::Property(&CalibrateSetup::diamonds, "diamonds", "Parameters of each diamond"));
+  };
+
+}  // namespace cbm::algo::bmon
diff --git a/algo/detectors/bmon/Clusterizer.cxx b/algo/detectors/bmon/Clusterizer.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..d3a8e3d4abf18a8a9e1e727606fd08a187afdf88
--- /dev/null
+++ b/algo/detectors/bmon/Clusterizer.cxx
@@ -0,0 +1,87 @@
+/* Copyright (C) 2025 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergei Zharko [committer] */
+
+/// \file   Clusterizer.cxx
+/// \brief  A clusterizer algorithm for BMON (implementation)
+/// \since  07.02.2025
+/// \author Sergei Zharko <s.zharko@gsi.de>
+
+
+#include "bmon/Clusterizer.h"
+
+#include "AlgoFairloggerCompat.h"
+#include "CbmBmonDigi.h"
+#include "CbmTofAddress.h"
+
+#include <algorithm>
+#include <iomanip>
+#include <iostream>
+#include <sstream>
+
+using cbm::algo::bmon::Clusterizer;
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+Clusterizer::Output_t Clusterizer::operator()(const Clusterizer::Input_t& digis)
+{
+  // Description:
+  // The input array of digis is traced through until the last element. If the current digi and the next digi are
+  // close in time and have neighboring channels, a single hit is produced. Otherwise a hit is produced from a
+  // single digi. The complexity of the algorithm is O(nDigis). Requirements: digis must be sorted in time
+  Output_t res;
+  if (digis.empty()) {
+    return res;
+  }
+
+  auto& hits        = std::get<0>(res);
+  auto& digiIndices = std::get<1>(res);
+  hits.reserve(digis.size());
+  digiIndices.reserve(digis.size());
+  auto itLast = std::prev(digis.end());  // iterator pointing to the last digi
+  bool bUsedWithPrevious{false};         // A flag: if the current digi was used together with the previous one
+  for (auto it = digis.begin(), in = it; it != itLast; ++it) {
+    if (bUsedWithPrevious) {
+      // skip a digi, if it was already used
+      bUsedWithPrevious = false;
+      continue;
+    }
+    in                = std::next(it);
+    const auto& digiT = it->first;
+    const auto& digiN = in->first;
+    if (digiN.GetTime() - digiT.GetTime() < fParams.fdMaxTimeDist
+        && abs(digiN.GetChannel() - digiT.GetChannel()) == 1) {
+      // A hit consisting from two digis is found
+      hits.emplace_back(fParams.fAddress, digiT, digiN);
+      digiIndices.emplace_back(it->second);
+      bUsedWithPrevious = true;
+    }
+    else {
+      // A hit consisting from a single digi
+      hits.emplace_back(fParams.fAddress, digiT);
+      digiIndices.emplace_back(it->second);
+    }
+  }
+  if (!bUsedWithPrevious) {
+    // Create a hit from the last digi
+    hits.emplace_back(fParams.fAddress, itLast->first);
+    digiIndices.emplace_back(itLast->second);
+  }
+
+  return res;
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+bool Clusterizer::SelectDigi(const CbmBmonDigi& digi) const
+{
+  // Dead channel cut
+  if (fParams.fDeadStrips & (1 << CbmTofAddress::GetChannelId(digi.GetAddress()))) {
+    return false;
+  }
+
+  // ??? Other cuts ??? Charge threshold? Also dead strips can be accounted already on the calibrator level, together
+  //     with the cuts
+
+  return true;
+}
diff --git a/algo/detectors/bmon/Clusterizer.h b/algo/detectors/bmon/Clusterizer.h
new file mode 100644
index 0000000000000000000000000000000000000000..86b3c76e3ba74d1595e7ae0282ac1f10e0e22a09
--- /dev/null
+++ b/algo/detectors/bmon/Clusterizer.h
@@ -0,0 +1,57 @@
+/* Copyright (C) 2025 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergei Zharko [committer] */
+
+/// \file   Clusterizer.h
+/// \brief  A clusterizer algorithm for BMON
+/// \since  07.02.2025
+/// \author Sergei Zharko <s.zharko@gsi.de>
+
+#pragma once
+
+#include "base/PODVector.h"
+#include "bmon/ClusterizerPars.h"
+#include "bmon/Hit.h"
+
+#include <cmath>
+#include <cstdint>
+#include <memory>
+#include <utility>
+#include <vector>
+
+class CbmBmonDigi;
+
+namespace cbm::algo::bmon
+{
+  /// \class Clusterizer
+  /// \brief A clusterizer algorithm for a BMON
+  ///
+  /// The algorithm is executed on a single hardware module
+  class Clusterizer {
+   public:
+    using Input_t  = std::vector<std::pair<CbmBmonDigi, int32_t>>;     ///< Input type
+    using Output_t = std::pair<std::vector<Hit>, PODVector<int32_t>>;  ///< Output type
+
+    /// \brief Constructor
+    /// \param params RPC parameters
+    explicit Clusterizer(ClusterizerPars params) : fParams(params) {}
+
+    /// \brief Hit building function
+    Output_t operator()(const Input_t& digisInput);
+
+    /// \brief  Applies selection on a digis
+    /// \return true   Digi is selected
+    /// \return false  Digi is cut out
+    bool SelectDigi(const CbmBmonDigi& digi) const;
+
+   private:
+    /// \brief Creates a hit from a single digi
+    Hit CreateHit(const CbmBmonDigi& digi) const;
+
+    /// \brief Creates a hit from two digis
+    Hit CreateHit(const CbmBmonDigi& digiL, const CbmBmonDigi& digiR) const;
+
+
+    ClusterizerPars fParams;  ///< parameters container
+  };
+}  // namespace cbm::algo::bmon
diff --git a/algo/detectors/bmon/ClusterizerPars.h b/algo/detectors/bmon/ClusterizerPars.h
new file mode 100644
index 0000000000000000000000000000000000000000..0e2e65ecf98814f9650e17f9d0c54298441e347d
--- /dev/null
+++ b/algo/detectors/bmon/ClusterizerPars.h
@@ -0,0 +1,25 @@
+/* Copyright (C) 2025 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergei Zharko [committer] */
+
+/// \file   ClusterizerPars.h
+/// \brief  BMON clusterizer parameters
+/// \since  07.02.2025
+/// \author Sergei Zharko <s.zharko@gsi.de>
+
+#pragma once
+
+#include <cstdint>
+#include <vector>
+
+namespace cbm::algo::bmon
+{
+  /// \struct ClusterizerPars
+  /// \brief  Clusterizer parameters for Diamond
+  struct ClusterizerPars {
+    uint32_t fAddress;     ///< Address of the diamond (the channel bit field is 0)
+    uint32_t fDeadStrips;  ///< Dead strip bitmask
+    double fdMaxTimeDist;  ///< Maximum time difference between two consecutive digis to form a single hit
+    double fTimeRes;       ///< Time resolution
+  };
+}  // namespace cbm::algo::bmon
diff --git a/algo/detectors/bmon/Hit.h b/algo/detectors/bmon/Hit.h
new file mode 100644
index 0000000000000000000000000000000000000000..e4e1aef243769e409fc0b8c06466b78548a80461
--- /dev/null
+++ b/algo/detectors/bmon/Hit.h
@@ -0,0 +1,103 @@
+/* Copyright (C) 2025 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergei Zharko [committer] */
+
+/// \file   Hitfind.h
+/// \brief  A BMON hit class
+/// \since  07.02.2025
+/// \author Sergei Zharko <s.zharko@gsi.de>
+
+#pragma once
+
+#include "CbmBmonDigi.h"
+
+#include <boost/serialization/access.hpp>
+
+#include <cstdint>
+#include <vector>
+
+namespace cbm::algo::bmon
+{
+  /// \class  Hit
+  /// \brief  A BMON hit
+  class Hit {
+   public:
+    /// \brief Constructor from a single digi
+    /// \param address  Address of the diamond
+    /// \param digi     A digi
+    Hit(uint32_t address, const CbmBmonDigi& digi) : fAddress(address), fNofChannels(1)
+    {
+      fTime      = digi.GetTime();
+      fTimeError = 0.;  // FIXME: provide a rule to set an error
+    }
+
+    /// \brief Constructor from two digis
+    /// \param address  Address of the diamond
+    /// \param digiL    First digi
+    /// \param digiR    Second digi
+    Hit(uint32_t address, const CbmBmonDigi& digiL, const CbmBmonDigi& digiR) : fAddress(address), fNofChannels(2)
+    {
+      double weightSum = digiL.GetCharge() + digiR.GetCharge();
+      fTime            = (digiL.GetTime() * digiL.GetCharge() + digiR.GetTime() * digiR.GetCharge()) / weightSum;
+      fTimeError       = 0.;  // FIXME: provide a rule to set an error
+    }
+
+    /// \brief Constructor
+    /// \param address   Address of diamond (the channel is not stored)
+    /// \param time      Time of the hit [ns]
+    /// \param timeError Time error of the hit [ns]
+    /// \param nChannels Number of channels used (either one or two)
+    Hit(uint32_t address, double time, double timeError, uint8_t nChannels)
+      : fTime(time)
+      , fTimeError(timeError)
+      , fAddress(address)
+      , fNofChannels(nChannels)
+    {
+    }
+
+    /// \brief Gets hardware address
+    uint32_t GetAddress() const { return fAddress; }
+
+    /// \brief Gets number of channels
+    uint8_t GetNofChannels() const { return fNofChannels; }
+
+    /// \brief Gets time [ns]
+    double GetTime() const { return fTime; }
+
+    /// \brief Gets time error [ns]
+    double GetTimeError() const { return fTimeError; }
+
+    /// \brief Sets address
+    /// \param address Hardware address
+    void SetAddress(uint32_t address) { fAddress = address; }
+
+    /// \brief Sets number of channels
+    /// \param nofChannels Number of channels (digis), used to create a hit
+    void SetNofChannels(uint8_t nofChannels) { fNofChannels = nofChannels; }
+
+    /// \brief Sets time
+    /// \param  time  Hit time [ns]
+    void SetTime(double time) { fTime = time; }
+
+    /// \brief Sets time error
+    /// \param  timeError  Hit time error [ns]
+    void SetTimeError(double timeError) { fTimeError = timeError; }
+
+   private:
+    double fTime{0.};         ///< Time [ns]
+    double fTimeError{0.};    ///< Time error [ns]
+    uint32_t fAddress{0};     ///< Assigned hit address
+    uint8_t fNofChannels{0};  ///< Number of channels used
+
+    /// \brief Boost serialization function
+    friend class boost::serialization::access;
+    template<class Archive>
+    void serialize(Archive& ar, unsigned int /*version*/)
+    {
+      ar& fTime;
+      ar& fTimeError;
+      ar& fAddress;
+      ar& fNofChannels;
+    }
+  };
+}  // namespace cbm::algo::bmon
diff --git a/algo/detectors/bmon/Hitfind.cxx b/algo/detectors/bmon/Hitfind.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..d39a46f6c4bf9faf4bfbe2425557ef85ba02664a
--- /dev/null
+++ b/algo/detectors/bmon/Hitfind.cxx
@@ -0,0 +1,113 @@
+/* Copyright (C) 2025 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergei Zharko [committer] */
+
+/// \file   Hitfind.cxx
+/// \brief  A BMON hitfinder steering class (implementation)
+/// \since  07.02.2025
+/// \author Sergei Zharko <s.zharko@gsi.de>
+
+
+#include "Hitfind.h"
+
+#include "AlgoFairloggerCompat.h"
+#include "compat/OpenMP.h"
+#include "util/TimingsFormat.h"
+
+#include <chrono>
+
+using cbm::algo::bmon::ClusterizerPars;
+using cbm::algo::bmon::Hitfind;
+using cbm::algo::bmon::HitfindSetup;
+using fles::Subsystem;
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+Hitfind::Hitfind(HitfindSetup setup, uint32_t nThreads) : fNofThreads(nThreads)
+{
+  // Create one algorithm per diamond and per thread
+  size_t nDiamondsInSetup{setup.diamonds.size()};
+  if (nDiamondsInSetup == 0) {
+    throw std::runtime_error("No diamonds found in the BMON calibration config");
+  }
+  if (!(setup.selectionMask) != (nDiamondsInSetup == 1)) {
+    throw std::runtime_error("Wrong diamond selection mask: for a single diamond it must be zero, and for multiple"
+                             " diamonds it must be non-zero");
+  }
+
+  if (nDiamondsInSetup > 1) {
+    // Define the selection bit offset
+    while (!((setup.selectionMask >> fSelectionBitsOffset) % 2)) {
+      ++fSelectionBitsOffset;
+    }
+
+    // Sort the diamonds in the setup by their SM or Side or other distinguishing index
+    std::sort(setup.diamonds.begin(), setup.diamonds.end(), [&](const auto& lhs, const auto& rhs) {
+      return GetDiamondIndex(lhs.refAddress) < GetDiamondIndex(rhs.refAddress);
+    });
+  }
+
+  fSelectionBitmask = setup.selectionMask;
+  fDiamondAddress   = PODVector<uint32_t>(nDiamondsInSetup, 0);
+
+  // Store diamond address
+  for (size_t iDiamond = 0; iDiamond < nDiamondsInSetup; ++iDiamond) {
+    fDiamondAddress[iDiamond] = setup.diamonds[iDiamond].refAddress & ~CbmTofAddress::GetChannelIdBitmask();
+  }
+
+  // Create and configure clusterizer algorithms per thread and per diamond
+  fAlgo = std::vector<std::vector<Clusterizer>>(fNofThreads, std::vector<Clusterizer>());
+  for (auto& algoPerThread : fAlgo) {
+    algoPerThread.reserve(nDiamondsInSetup);
+    for (size_t iDiamond = 0; iDiamond < nDiamondsInSetup; ++iDiamond) {
+      auto par               = std::make_unique<ClusterizerPars>();
+      const auto& diamondPar = setup.diamonds[iDiamond];
+      par->fAddress          = diamondPar.refAddress;
+      par->fDeadStrips       = diamondPar.deadStrips;
+      par->fdMaxTimeDist     = diamondPar.maxTimeDist;
+      par->fTimeRes          = diamondPar.timeRes;
+      algoPerThread.emplace_back(std::move(*par));
+    }
+  }
+  L_(info) << "--- Configured hitfinder algorithms for BMON.";
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+Hitfind::Output_t Hitfind::operator()(gsl::span<CbmBmonDigi> digisIn, uint32_t iThread)
+{
+  Output_t res     = {};
+  auto& resHits    = std::get<0>(res);
+  auto& resMoni    = std::get<1>(res);
+  auto& resDigiIds = std::get<2>(res);
+
+  auto& algoPerThread = fAlgo[iThread];
+
+  // Distribute digis over diamonds, apply cuts on this level (maybe the Calibrator is a more proper place for it)
+  size_t nDiamonds  = algoPerThread.size();
+  auto vDigiStorage = std::vector<Clusterizer::Input_t>(nDiamonds, Clusterizer::Input_t(0));
+
+  for (int32_t iDigi = 0; iDigi < static_cast<int32_t>(digisIn.size()); ++iDigi) {
+    const auto& digi = digisIn[iDigi];
+    size_t iDiamond  = GetDiamondIndex(digi.GetAddress());
+    if (algoPerThread[iDiamond].SelectDigi(digi)) {
+      vDigiStorage[iDiamond].emplace_back(digi, iDigi);
+    }
+  }
+
+  // NOTE: I see no sense in storing a full channel address for different BMON hits,
+  //       so for each hit the diamond address will be assigned: address & ~CbmTofAddress::GetChannelIdBitmask()
+  PODVector<Hit> vHitsFlat;                                    // storage for clusters
+  PODVector<size_t> vNhitsPerDiamond(fDiamondAddress.size());  // number of hits per diamond
+
+  for (size_t iDiamond = 0; iDiamond < algoPerThread.size(); ++iDiamond) {
+    auto [hits, digiIds]       = algoPerThread[iDiamond](vDigiStorage[iDiamond]);
+    vNhitsPerDiamond[iDiamond] = hits.size();
+    vHitsFlat.insert(vHitsFlat.end(), std::make_move_iterator(hits.begin()), std::make_move_iterator(hits.end()));
+    resDigiIds.insert(resDigiIds.end(), std::make_move_iterator(digiIds.begin()),
+                      std::make_move_iterator(digiIds.end()));
+  }
+
+  resHits = PartitionedVector(std::move(vHitsFlat), vNhitsPerDiamond, fDiamondAddress);
+  return res;
+}
diff --git a/algo/detectors/bmon/Hitfind.h b/algo/detectors/bmon/Hitfind.h
new file mode 100644
index 0000000000000000000000000000000000000000..68d7d3a9b57e80099a6a4ce3854445622cca5384
--- /dev/null
+++ b/algo/detectors/bmon/Hitfind.h
@@ -0,0 +1,60 @@
+/* Copyright (C) 2025 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergei Zharko [committer] */
+
+/// \file   Hitfind.h
+/// \brief  BMON hitfinder steering class
+/// \since  07.02.2025
+/// \author Sergei Zharko <s.zharko@gsi.de>
+
+#pragma once
+
+#include "CbmBmonDigi.h"
+#include "PODVector.h"
+#include "PartitionedVector.h"
+#include "bmon/Clusterizer.h"
+#include "bmon/HitfindSetup.h"
+#include "tof/Hitfind.h"  // for tof::HitfindMonitorData
+
+#include <gsl/span>
+#include <optional>
+#include <sstream>
+#include <vector>
+
+#include <xpu/host.h>
+
+namespace cbm::algo::bmon
+{
+  /// \brief TOF hit-finder monitor, re-used for BMON
+  using HitfindMonitorData = tof::HitfindMonitorData;
+
+  /// \class Hitfind
+  /// \brief Hit-finder steering class for BMON
+  class Hitfind {
+   public:
+    /// \brief Output format
+    using Output_t = std::tuple<PartitionedVector<Hit>, HitfindMonitorData, PODVector<int32_t>>;
+
+    /// \brief Constructor
+    /// \param  setup     Setup configuration
+    /// \param  nThreads  Number of threads (for event-based mode)
+    explicit Hitfind(HitfindSetup setup, uint32_t nThreads = 1);
+
+    /// \brief Algorithm execution operator
+    /// \param digiIn   A portion of digis in TS/event
+    /// \param iThread  Index of thread
+    Output_t operator()(gsl::span<CbmBmonDigi> digisIn, uint32_t iThread = 0);
+
+   private:  // members
+    /// \brief Returns an index of the diamond by the address
+    /// \param address  A hardware address of the digi
+    size_t GetDiamondIndex(uint32_t address) const { return ((fSelectionBitmask & address) >> fSelectionBitsOffset); }
+
+    uint32_t fNofThreads;           ///< Number of threads
+    uint32_t fSelectionBitsOffset;  ///< Number of bits to ther right from the first bit in the selection mask
+    uint32_t fSelectionBitmask;     ///< Selection bitmask
+
+    std::vector<std::vector<Clusterizer>> fAlgo;  ///< Clusterizer algorithms [thread][diamond]
+    PODVector<uint32_t> fDiamondAddress;          ///< Diamond address
+  };
+}  // namespace cbm::algo::bmon
diff --git a/algo/detectors/bmon/HitfindSetup.h b/algo/detectors/bmon/HitfindSetup.h
new file mode 100644
index 0000000000000000000000000000000000000000..c958cbc17ca2ffcdc2f5027a42f3e6f8f09b1e22
--- /dev/null
+++ b/algo/detectors/bmon/HitfindSetup.h
@@ -0,0 +1,43 @@
+/* Copyright (C) 2025 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergei Zharko [committer] */
+
+/// \file   HitfindSetup.h
+/// \brief  Parameters of the BMON hitfinder
+/// \since  06.02.2025
+/// \author Sergei Zharko <s.zharko@gsi.de>
+
+#pragma once
+
+#include "Definitions.h"
+#include "yaml/Property.h"
+
+#include <string>
+#include <vector>
+
+namespace cbm::algo::bmon
+{
+  /// \struct HitfindSetup
+  /// \brief  Parameters for the BMON hitfinder
+  struct HitfindSetup {
+    struct Diamond {
+      u32 refAddress;
+      u32 deadStrips;
+      double maxTimeDist;
+      double timeRes;
+
+      CBM_YAML_PROPERTIES(
+                        yaml::Property(&Diamond::refAddress, "refAddress", "reference address of the diamond"),
+                        yaml::Property(&Diamond::deadStrips, "deadStrips", "bit mask for dead strips"),
+                        yaml::Property(&Diamond::maxTimeDist, "maxTimeDist", "maximum time distance"),
+                        yaml::Property(&Diamond::timeRes, "timeRes", "time resolution"));
+    };
+
+    uint32_t selectionMask;
+    std::vector<Diamond> diamonds;
+
+    CBM_YAML_PROPERTIES(
+      yaml::Property(&HitfindSetup::selectionMask, "selectionMask", "A bit mask to distinguish between different diamonds"),
+      yaml::Property(&HitfindSetup::diamonds, "diamonds", "Parameters of diamonds"));
+  };
+}  // namespace cbm::algo::bmon
diff --git a/algo/detectors/bmon/UnpackMS.cxx b/algo/detectors/bmon/UnpackMS.cxx
index b4fbf123e176991049b236c672beec4fa2a8e26a..22d56e90d1aee148e917555fa5a3bd1019f3d950 100644
--- a/algo/detectors/bmon/UnpackMS.cxx
+++ b/algo/detectors/bmon/UnpackMS.cxx
@@ -5,6 +5,7 @@
 #include "UnpackMS.h"
 
 #include "AlgoFairloggerCompat.h"
+#include "CbmTofAddress.h"
 
 #include <cassert>
 #include <cmath>
diff --git a/algo/global/ParFiles.cxx b/algo/global/ParFiles.cxx
index 8eb07742a6669fc291bbfef84548f8b232de9206..eabec37b4b13cdcbcf8e25e137d37fdfa6b384ba 100644
--- a/algo/global/ParFiles.cxx
+++ b/algo/global/ParFiles.cxx
@@ -25,7 +25,9 @@ ParFiles::ParFiles(uint32_t runId)
   switch (setup) {
 
     case Setup::mCBM2022:
-      bmon.readout = "BmonReadout_mcbm2022.yaml";
+      bmon.readout   = "BmonReadout_mcbm2022.yaml";
+      bmon.calibrate = "BmonCalibratePar_mcbm2022.yaml";
+      bmon.hitfinder = "BmonHitfinderPar_mcbm2022.yaml";
 
       sts.readout   = "StsReadout_mcbm2022.yaml";
       sts.chanMask  = "StsChannelMaskSet_mcbm2022.yaml";
@@ -46,7 +48,9 @@ ParFiles::ParFiles(uint32_t runId)
       break;
 
     case Setup::mCBM2024_03:
-      bmon.readout = "BmonReadout_mcbm2024.yaml";
+      bmon.readout   = "BmonReadout_mcbm2024.yaml";
+      bmon.calibrate = "BmonCalibratePar_mcbm2024.yaml";
+      bmon.hitfinder = "BmonHitfinderPar_mcbm2024.yaml";
 
       sts.readout   = "StsReadout_mcbm2024.yaml";
       sts.chanMask  = "StsChannelMaskSet_mcbm2024.yaml";
@@ -67,7 +71,9 @@ ParFiles::ParFiles(uint32_t runId)
       break;
 
     case Setup::mCBM2024_05:
-      bmon.readout = "BmonReadout_mcbm2024.yaml";
+      bmon.readout   = "BmonReadout_mcbm2024.yaml";
+      bmon.calibrate = "mcbm2024_05/BmonCalibratePar.yaml";
+      bmon.hitfinder = "mcbm2024_05/BmonHitfinderPar.yaml";
 
       sts.readout   = "StsReadout_mcbm2024.yaml";
       sts.chanMask  = "StsChannelMaskSet_mcbm2024.yaml";
diff --git a/algo/global/ParFiles.h b/algo/global/ParFiles.h
index 9c966ac6370f04b830fe0fc17a384800e39d13d1..691e27b39bf8af9b64b89cc209422f786048e1e7 100644
--- a/algo/global/ParFiles.h
+++ b/algo/global/ParFiles.h
@@ -26,6 +26,8 @@ namespace cbm::algo
 
     struct {
       fs::path readout;
+      fs::path calibrate;
+      fs::path hitfinder;
     } bmon;
 
     struct {
diff --git a/algo/global/Reco.cxx b/algo/global/Reco.cxx
index e9a0a7a9a0e0ff8e1cdb27b051c52f149ebc702b..8460ea28bf34fa71071601370839602433199058 100644
--- a/algo/global/Reco.cxx
+++ b/algo/global/Reco.cxx
@@ -14,6 +14,8 @@
 #include "RecoGeneralQa.h"
 #include "StsDigiQa.h"
 #include "TrackingSetup.h"
+#include "bmon/Calibrate.h"
+#include "bmon/Hitfind.h"
 #include "bmon/ReadoutConfig.h"
 #include "bmon/Unpack.h"
 #include "ca/TrackingChain.h"
@@ -22,6 +24,7 @@
 #include "evbuild/Config.h"
 #include "much/Unpack.h"
 #include "qa/QaManager.h"
+#include "qa/hitfind/BmonHitfindQa.h"
 #include "rich/Unpack.h"
 #include "sts/ChannelMaskSet.h"
 #include "sts/HitfinderChain.h"
@@ -41,6 +44,10 @@
 
 #include <xpu/host.h>
 
+// DEBUG: BEGIN
+#include <set>
+// DEBUG: END
+
 using namespace cbm::algo;
 using fles::Subsystem;
 
@@ -204,6 +211,20 @@ void Reco::Init(const Options& opts)
     fStsHitFinder->SetParameters(hitFinderPars);
   }
 
+  // BMON Hitfinder
+  if (Opts().Has(fles::Subsystem::BMON) && Opts().Has(Step::LocalReco)) {
+    auto calibSetup = yaml::ReadFromFile<bmon::CalibrateSetup>(opts.ParamsDir() / parFiles.bmon.calibrate);
+    fBmonCalibrator = std::make_unique<bmon::Calibrate>(calibSetup);
+
+    auto hitfindSetup = yaml::ReadFromFile<bmon::HitfindSetup>(opts.ParamsDir() / parFiles.bmon.hitfinder);
+    fBmonHitFinder    = std::make_unique<bmon::Hitfind>(hitfindSetup);
+
+    if (fQaManager != nullptr && Opts().Has(QaStep::RecoBmon)) {
+      fBmonHitFinderQa = std::make_unique<bmon::HitfindQa>(fQaManager, "BmonHitfindEvent");
+      fBmonHitFinderQa->InitParameters(calibSetup, hitfindSetup);
+      fBmonHitFinderQa->Init();
+    }
+  }
 
   // TOF Hitfinder
   if (Opts().Has(fles::Subsystem::TOF) && Opts().Has(Step::LocalReco)) {
@@ -222,7 +243,7 @@ void Reco::Init(const Options& opts)
 
   // Tracking
   if (Opts().Has(Step::Tracking)) {
-    if (Opts().Has(QaStep::Tracking)) {
+    if (fQaManager != nullptr && Opts().Has(QaStep::Tracking)) {
       fTracking = std::make_unique<TrackingChain>(fQaManager, "CaTimeslice");
     }
     else {
@@ -372,6 +393,24 @@ RecoResults Reco::Run(const fles::Timeslice& ts)
       QueueEvbuildMetrics(evbuildMonitor);
     }
 
+    // ***** DEBUG: BEGIN
+    if constexpr (0) {
+      int nEvents = events.size();
+      for (int iE = 0; iE < nEvents; ++iE) {
+        const auto& event = events[iE];
+        // Calibrate TOF digis:
+        auto [bmonDigis, bmonCalMonitor]         = (*fBmonCalibrator)(event.fBmon);
+        auto [bmonHits, hitmonitor, digiIndices] = (*fBmonHitFinder)(bmonDigis);
+        if (fBmonHitFinderQa != nullptr) {
+          fBmonHitFinderQa->RegisterDigis(&bmonDigis);
+          fBmonHitFinderQa->RegisterHits(&bmonHits);
+          fBmonHitFinderQa->RegisterDigiIndices(&digiIndices);
+          fBmonHitFinderQa->Exec();
+        }
+      }
+    }
+    // ***** DEBUG: END
+
     // --- Filter data for output
     if (Opts().HasOutput(RecoData::DigiTimeslice)) {
       results.bmonDigis  = std::move(digis.fBmon);
diff --git a/algo/global/Reco.h b/algo/global/Reco.h
index 8e26d35271857feebcc89776c800c5abb4511ca2..6182decdad9d289e7a654aabbb6b5db8650efe29 100644
--- a/algo/global/Reco.h
+++ b/algo/global/Reco.h
@@ -27,6 +27,9 @@ namespace cbm::algo
   namespace bmon
   {
     class Unpack;
+    class Calibrate;
+    class Hitfind;
+    class HitfindQa;
   }
 
   namespace much
@@ -146,6 +149,9 @@ namespace cbm::algo
 
     // BMON
     std::unique_ptr<bmon::Unpack> fBmonUnpack;
+    std::unique_ptr<bmon::Calibrate> fBmonCalibrator;
+    std::unique_ptr<bmon::Hitfind> fBmonHitFinder;
+    std::unique_ptr<bmon::HitfindQa> fBmonHitFinderQa;
 
     // MUCH
     std::unique_ptr<much::Unpack> fMuchUnpack;
diff --git a/algo/qa/PadConfig.h b/algo/qa/PadConfig.h
index 4d6ac5376c121a618601111ff3aa1d6d07deba09..104b2144263b06834952557d14d3b30d21e6197a 100644
--- a/algo/qa/PadConfig.h
+++ b/algo/qa/PadConfig.h
@@ -43,6 +43,15 @@ namespace cbm::algo::qa
     {
     }
 
+    /// \brief  Constructor from a single histogram
+    /// \tparam Hist  Histogram class
+    /// \param  hist  Histogram object
+    /// \param  opt   Draw options for the histogram
+    template<class Hist>
+    PadConfig(const Hist* hist, std::string_view opt)
+    {
+      RegisterHistogram(hist, opt);
+    }
 
     /// \brief Copy constructor
     PadConfig(const PadConfig&) = default;
@@ -104,11 +113,11 @@ namespace cbm::algo::qa
     std::string ToString() const;
 
    private:
-    bool fbGridX = false;  ///< Grid flag for x-axis
-    bool fbGridY = false;  ///< Grid flag for y-axis
-    bool fbLogX  = false;  ///< Log flag for x-axis
-    bool fbLogY  = false;  ///< Log flag for y-axis
-    bool fbLogZ  = false;  ///< Log flag for z-axis
+    bool fbGridX{false};  ///< Grid flag for x-axis
+    bool fbGridY{false};  ///< Grid flag for y-axis
+    bool fbLogX{false};   ///< Log flag for x-axis
+    bool fbLogY{false};   ///< Log flag for y-axis
+    bool fbLogZ{false};   ///< Log flag for z-axis
 
     std::vector<std::pair<std::string, std::string>> fvObjectList;  ///< List of objects on the pad
   };
diff --git a/algo/qa/QaTaskHeader.h b/algo/qa/QaTaskHeader.h
index ca41383152a066fd98b79293f874428cd53fb350..ceb03e5ad457c9b6ceecb1145c7a5f4c15f262b9 100644
--- a/algo/qa/QaTaskHeader.h
+++ b/algo/qa/QaTaskHeader.h
@@ -24,7 +24,8 @@ namespace cbm::algo::qa
     /// \param pManager a QA-manager
     /// \param name A name of the task (histograms directory)
     TaskHeader(const std::unique_ptr<Manager>& pManager, std::string_view name)
-      : fpData(pManager != nullptr ? pManager->GetData() : nullptr)
+      : fsName(name)
+      , fpData(pManager != nullptr ? pManager->GetData() : nullptr)
     {
       if (fpData != nullptr) {
         fpData->RegisterNewTask(name);
@@ -52,6 +53,9 @@ namespace cbm::algo::qa
     /// the fpData instance is not defined, and no actions on the task should be performed
     bool IsActive() const { return static_cast<bool>(fpData != nullptr); }
 
+    /// \brief Gets name of the task
+    const std::string& GetTaskName() { return fsName; }
+
    protected:
     /// \brief Adds a canvas configuration
     /// \param canvas  A CanvasConfig object
@@ -68,6 +72,7 @@ namespace cbm::algo::qa
     }
 
    private:
+    std::string fsName{};                   ///< Name of the task
     std::shared_ptr<Data> fpData{nullptr};  ///< An instance of the QA data (shared between different tasks)
   };
 }  // namespace cbm::algo::qa
diff --git a/algo/qa/hitfind/BmonHitfindQa.cxx b/algo/qa/hitfind/BmonHitfindQa.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..0bf088900f35ef7f55b6ce59f311bda818dee011
--- /dev/null
+++ b/algo/qa/hitfind/BmonHitfindQa.cxx
@@ -0,0 +1,112 @@
+/* Copyright (C) 2025 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergei Zharko [committer] */
+
+/// \file   BmonHitfindQa.cxx
+/// \brief  A BMON hitfinder QA (implementation)
+/// \since  09.02.2025
+/// \author Sergei Zharko <s.zharko@gsi.de>
+
+#include "qa/hitfind/BmonHitfindQa.h"
+
+#include "CbmBmonDigi.h"
+#include "CbmTofAddress.h"
+#include "bmon/Hit.h"
+#include "qa/Histogram.h"
+
+#include <fmt/format.h>
+
+using cbm::algo::bmon::HitfindQa;
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+void HitfindQa::Init()
+try {
+  using fmt::format;
+  if (!IsActive()) {
+    return;
+  }
+
+  size_t nDiamonds = fParameters.diamonds.size();
+  if (nDiamonds < 1) {
+    throw std::runtime_error("parameters were not initialized. Please, provide the configuration using the function "
+                             "HitfindQa::InitParameters(calSetup, hitSetup)");
+  }
+
+  fvphDigiOccupVsChan.resize(nDiamonds, nullptr);
+  fvphDigiChargeVsChan.resize(nDiamonds, nullptr);
+  fvphHitNofChan.resize(nDiamonds, nullptr);
+  fvphHitTimeDiff.resize(nDiamonds, nullptr);
+
+  for (size_t iD = 0; iD < nDiamonds; ++iD) {
+    const auto& diamondPar = fParameters.diamonds[iD];
+    int nCh                = diamondPar.nChannels;
+    auto sDN               = format("_diamond_{:#08x}", diamondPar.address);  // diamond suffix
+
+    // Histograms initialisation
+    /* clang-format off */
+    fvphDigiOccupVsChan[iD] = MakeObj<qa::H1D>(
+      format("bmon_digi_occup_channel{}", sDN),
+      format("BMON-{} digi occupancy vs. channel;channel;counts", iD), 
+      nCh, -0.5, nCh - 0.5);
+    fvphDigiChargeVsChan[iD] = MakeObj<qa::H2D>(
+      format("bmon_digi_charge_channel{}", sDN),
+      format("BMON-{} digi charge vs. channel;channel;charge;counts", iD),
+      nCh, -0.5, nCh - 0.5, kChrgB, kChrgL, kChrgU);
+    fvphHitNofChan[iD]       = MakeObj<qa::H1D>(
+      format("bmon_hit_nChannels{}", sDN),
+      format("BMON-{} hit number of channels;N_{{chan}};counts", iD), 
+      2, 0.5, 2.5);
+    fvphHitTimeDiff[iD]      = MakeObj<qa::H1D>(
+      format("bmon_hit_time_diff{}", sDN),
+      format("BMON-{} digi time difference in a hit formed from two digis;#Delta t_{{digi}} [ns];counts", iD), 
+      kDtimeB, kDtimeL, kDtimeU);
+    /* clang-format on */
+
+    // Canvas initialization
+    auto cName = format("{}/bmon{}", GetTaskName(), sDN);
+    auto cTitl = format("BMON-{}", iD);
+    auto canv  = qa::CanvasConfig(cName, cTitl, 2, 2);
+    canv.AddPadConfig(qa::PadConfig(fvphDigiOccupVsChan[iD], "hist"));   // (0,0)
+    canv.AddPadConfig(qa::PadConfig(fvphDigiChargeVsChan[iD], "colz"));  // (1,0)
+    canv.AddPadConfig(qa::PadConfig(fvphHitNofChan[iD], "hist"));        // (0,1)
+    canv.AddPadConfig(qa::PadConfig(fvphHitTimeDiff[iD], "hist"));       // (1,1)
+    AddCanvasConfig(canv);
+  }
+}
+catch (const std::exception& err) {
+  L_(fatal) << "bmon::HitfindQa: initialization failed. Reason: " << err.what();
+  throw std::runtime_error("bmon::HitfindQa initialization failure");
+}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+void HitfindQa::Exec()
+{
+  if (!IsActive()) {
+    return;
+  }
+
+  // Fill digi distributions
+  for (const auto& digi : *fpDigis) {
+    size_t iDiamond = fParameters.GetDiamondIndex(digi.GetAddress());
+    int32_t chan    = digi.GetChannel();
+    fvphDigiOccupVsChan[iDiamond]->Fill(chan);
+    fvphDigiChargeVsChan[iDiamond]->Fill(chan, digi.GetCharge());
+  }
+
+  // Fill hit distributions
+  const auto& hits = fpHits->Data();
+  for (size_t iH = 0; iH < hits.size(); ++iH) {
+    const auto& hit = hits[iH];
+    size_t iDiamond = fParameters.GetDiamondIndex(hit.GetAddress());
+    int nChannels   = hit.GetNofChannels();
+    fvphHitNofChan[iDiamond]->Fill(nChannels);
+    if (nChannels == 2) {
+      int32_t iDigi     = (*fpDigiIndices)[iH];
+      const auto& digiF = (*fpDigis)[iDigi];
+      const auto& digiS = (*fpDigis)[iDigi + 1];
+      fvphHitTimeDiff[iDiamond]->Fill(digiS.GetTime() - digiF.GetTime());
+    }
+  }
+}
diff --git a/algo/qa/hitfind/BmonHitfindQa.h b/algo/qa/hitfind/BmonHitfindQa.h
new file mode 100644
index 0000000000000000000000000000000000000000..020f62fa821a2c8ec298c8aa758684aee023cc21
--- /dev/null
+++ b/algo/qa/hitfind/BmonHitfindQa.h
@@ -0,0 +1,112 @@
+/* Copyright (C) 2025 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergei Zharko [committer] */
+
+/// \file   BmonHitfindQa.h
+/// \brief  A BMON hitfinder QA
+/// \since  07.02.2025
+/// \author Sergei Zharko <s.zharko@gsi.de>
+
+#pragma once
+
+#include "PODVector.h"
+#include "PartitionedVector.h"
+#include "qa/QaTaskHeader.h"
+#include "qa/hitfind/BmonHitfindQaParameters.h"
+
+class CbmBmonDigi;
+
+namespace cbm::algo
+{
+  namespace qa
+  {
+    class H1D;
+    class H2D;
+  }  // namespace qa
+
+  namespace bmon
+  {
+    class Hit;
+  }
+}  // namespace cbm::algo
+
+namespace cbm::algo::bmon
+{
+  /// \class HitfindQa
+  /// \brief A QA module for the BMON hit-finder
+  /// \param pManager  Pointer to the QA manager
+  /// \param name      Name of the QA (directory)
+  class HitfindQa : public qa::TaskHeader {
+   public:
+    /// \brief Constructor
+    /// \param pManager  Pointer to the QA manager
+    /// \param name      Name of the QA (directory)
+    HitfindQa(const std::unique_ptr<qa::Manager>& pManager, std::string_view name) : qa::TaskHeader(pManager, name) {}
+
+    /// \brief Constructor from the configuration object
+    /// \param config  QA configuration object
+    HitfindQa() = default;
+
+    /// \brief Copy constructor
+    HitfindQa(const HitfindQa&) = delete;
+
+    /// \brief Move constructor
+    HitfindQa(HitfindQa&&) = delete;
+
+    /// \brief Destructor
+    ~HitfindQa() = default;
+
+    /// \brief Copy assignment operator
+    HitfindQa& operator=(const HitfindQa&) = delete;
+
+    /// \brief Move assignment operator
+    HitfindQa& operator=(HitfindQa&&) = delete;
+
+    /// \brief Executes the task, fills the histograms
+    void Exec();
+
+    /// \brief Initialized the task
+    void Init();
+
+    /// \brief Initialisation of the parameters
+    void InitParameters(const CalibrateSetup& calSetup, const HitfindSetup& hitSetup)
+    {
+      fParameters = std::move(HitfindQaParameters(calSetup, hitSetup));
+    }
+
+    /// \brief Registers a sample of digis
+    /// \param pDigis  A pointer to a vector of digis
+    void RegisterDigis(const std::vector<CbmBmonDigi>* pDigis) { fpDigis = pDigis; }
+
+    /// \brief Registers a sample of hits
+    /// \param pHits  A pointer to a vector of hits
+    void RegisterHits(const PartitionedVector<bmon::Hit>* pHits) { fpHits = pHits; }
+
+    /// \brief Registers a sample of digi indices, used by hits
+    /// \param pDigiIndices  A pointer to a vector of digi indices
+    void RegisterDigiIndices(const PODVector<int32_t>* pDigiIndices) { fpDigiIndices = pDigiIndices; }
+
+   private:
+    //* Constants
+    static constexpr int kChrgB     = 150;   ///< charge scale: number of bins
+    static constexpr double kChrgL  = 0.;    ///< charge scale: lower bound
+    static constexpr double kChrgU  = 150.;  ///< charge scale: upper bound
+    static constexpr int kDtimeB    = 40;    ///< digi time difference: number of bins
+    static constexpr double kDtimeL = 0.;    ///< digi time difference: lower bound [ns]
+    static constexpr double kDtimeU = 2.0;   ///< digi time difference: upper bound [ns]
+
+    //* Parameters
+    HitfindQaParameters fParameters;  ///< Parameters of the hit finder QA
+
+    //* Data samples
+    const std::vector<CbmBmonDigi>* fpDigis{nullptr};     ///< Pointer to BMON digi sample
+    const PartitionedVector<bmon::Hit>* fpHits{nullptr};  ///< Pointer to BMON hit sample
+    const PODVector<int32_t>* fpDigiIndices{nullptr};     ///< Pointer to BMON digi indices, used by hits
+
+    //* Histograms
+    std::vector<qa::H1D*> fvphDigiOccupVsChan;   ///< Digi occupancy vs. channel [diamond]
+    std::vector<qa::H2D*> fvphDigiChargeVsChan;  ///< Digi charge vs channel [diamond]
+    std::vector<qa::H1D*> fvphHitNofChan;        ///< Hit  number of channels [diamond]
+    std::vector<qa::H1D*> fvphHitTimeDiff;       ///< Time difference of two digis in a hit [diamond]
+  };
+}  // namespace cbm::algo::bmon
diff --git a/algo/qa/hitfind/BmonHitfindQaParameters.cxx b/algo/qa/hitfind/BmonHitfindQaParameters.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..aa7f4aadce0f8e7cad631de165f789177a02c4c9
--- /dev/null
+++ b/algo/qa/hitfind/BmonHitfindQaParameters.cxx
@@ -0,0 +1,58 @@
+/* Copyright (C) 2025 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergei Zharko [committer] */
+
+/// \file   BmonHitfindQaParameters.cxx
+/// \brief  A BMON hitfinder QA parameter configuration (implementation)
+/// \since  10.02.2025
+/// \author Sergei Zharko <s.zharko@gsi.de>
+
+#include "qa/hitfind/BmonHitfindQaParameters.h"
+
+#include "AlgoFairloggerCompat.h"
+#include "CbmTofAddress.h"
+
+using cbm::algo::bmon::CalibrateSetup;
+using cbm::algo::bmon::HitfindQaParameters;
+using cbm::algo::bmon::HitfindSetup;
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+HitfindQaParameters::HitfindQaParameters(const CalibrateSetup& calSetup, const HitfindSetup& hitSetup)
+{
+
+  this->selectionMask = calSetup.selectionMask;
+  if (this->selectionMask != hitSetup.selectionMask) {
+    throw std::runtime_error("Mismatch of the selection bitmask in the BMON CalibrateSetup and HitfindSetup configs");
+  }
+
+  auto nDiamonds = calSetup.diamonds.size();
+  if (nDiamonds != hitSetup.diamonds.size()) {
+    throw std::runtime_error("Mismatch of number of diamonds in the BMON CalibrateSetup and HitfindSetup configs");
+  }
+
+  if (nDiamonds > 1) {
+    while (!((this->selectionMask >> fSelectionBitsOffset) % 2)) {
+      ++fSelectionBitsOffset;
+    }
+  }
+
+  this->diamonds.resize(nDiamonds);
+  for (const auto& calDiamond : calSetup.diamonds) {
+    uint32_t address      = calDiamond.refAddress & ~CbmTofAddress::GetChannelIdBitmask();
+    auto& thisDiamond     = this->diamonds[GetDiamondIndex(address)];
+    thisDiamond.address   = address;
+    thisDiamond.nChannels = calDiamond.chanPar.size();
+  }
+
+  for (const auto& hitDiamond : hitSetup.diamonds) {
+    int32_t address   = hitDiamond.refAddress & ~CbmTofAddress::GetChannelIdBitmask();
+    auto& thisDiamond = this->diamonds[GetDiamondIndex(address)];
+    if (thisDiamond.address != address) {
+      throw std::runtime_error("Mismatch between diamond addresses in BMON CalibrateSetup and HitfindSetup configs");
+    }
+    thisDiamond.deadStrips  = hitDiamond.deadStrips;
+    thisDiamond.timeRes     = hitDiamond.timeRes;
+    thisDiamond.maxTimeDist = hitDiamond.maxTimeDist;
+  }
+}
diff --git a/algo/qa/hitfind/BmonHitfindQaParameters.h b/algo/qa/hitfind/BmonHitfindQaParameters.h
new file mode 100644
index 0000000000000000000000000000000000000000..25f524ba87945024350ed277757e8856f1eb6de6
--- /dev/null
+++ b/algo/qa/hitfind/BmonHitfindQaParameters.h
@@ -0,0 +1,50 @@
+/* Copyright (C) 2025 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergei Zharko [committer] */
+
+/// \file   BmonHitfindQaParameters.h
+/// \brief  A BMON hitfinder QA parameter configuration
+/// \since  10.02.2025
+/// \author Sergei Zharko <s.zharko@gsi.de>
+
+#pragma once
+
+#include "bmon/CalibrateSetup.h"
+#include "bmon/HitfindSetup.h"
+
+#include <vector>
+
+namespace cbm::algo::bmon
+{
+  /// \struct HitfindQaParameters
+  /// \brief  A structure to handle BMON QA parameters
+  struct HitfindQaParameters {
+    /// \struct Diamond
+    /// \brief  A diamond representation
+    struct Diamond {
+      double timeRes{0.};      ///< Time resolution [ns]
+      double maxTimeDist{0.};  ///< Max time distance between digis in a hit [ns]
+      int32_t address{0};      ///< Address of a diamond
+      int32_t nChannels{0};    ///< Number of channels in a diamond
+      uint32_t deadStrips{0};  ///< A bit mask of dead strips
+    };
+
+    uint32_t selectionMask{0};  ///< A bitmask to distinguish different diamonds
+    std::vector<Diamond> diamonds{};
+
+    /// \brief Default constructor
+    HitfindQaParameters() = default;
+
+    /// \brief Constructor
+    /// \param calSetup  Calibration parameters
+    /// \param hitSetup  Hitfinder parameters
+    HitfindQaParameters(const CalibrateSetup& calSetup, const HitfindSetup& hitSetup);
+
+    /// \brief Returns an index of the diamond by the address
+    /// \param address  A hardware address of the digi
+    size_t GetDiamondIndex(uint32_t address) const { return ((selectionMask & address) >> fSelectionBitsOffset); }
+
+   private:
+    uint32_t fSelectionBitsOffset{0};  ///< Number of bits to the right from the first bit in the selection mask
+  };
+}  // namespace cbm::algo::bmon
diff --git a/core/data/bmon/CbmBmonDigi.h b/core/data/bmon/CbmBmonDigi.h
index 200e72d0cec551bb7ee3c15e4aac8364fab5aab4..fffed5ca6118e8ff51e2c3bdfbc9d619392094e6 100644
--- a/core/data/bmon/CbmBmonDigi.h
+++ b/core/data/bmon/CbmBmonDigi.h
@@ -1,12 +1,13 @@
-/* Copyright (C) 2022 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+/* Copyright (C) 2022-2025 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
    SPDX-License-Identifier: GPL-3.0-only
-   Authors: Pierre-Alain Loizeau, Volker Friese [committer] */
+   Authors: Pierre-Alain Loizeau, Volker Friese [committer], Sergei Zharko */
 
 
 #ifndef CBMBMONDIGI_H
 #define CBMBMONDIGI_H 1
 
 #include "CbmDefs.h"
+#include "CbmTofAddress.h"
 
 #ifndef NO_ROOT
 #include <Rtypes.h>  // for ClassDef
@@ -85,6 +86,11 @@ public:
    **/
   double GetTime() const { return fTime; }
 
+  /**
+   **  @brief Gets channel ID
+   **  @return Channel ID
+   **/
+  int32_t GetChannel() const { return CbmTofAddress::GetChannelId(fAddress); }
 
   /** @brief Charge
    ** @return Charge
@@ -111,19 +117,20 @@ public:
 
 
 private:
-  int32_t fAddress = ToIntegralType<ECbmModuleId>(ECbmModuleId::kBmon);  ///< Unique CBM address
-  double fTime     = -1.;                                                ///< Time of signal in BMON [ns]
-  float fCharge    = -1.;                                                ///< Charge
-
-  friend class boost::serialization::access;
-
-  template<class Archive>
-  void serialize(Archive& ar, const unsigned int /*version*/)
-  {
-    ar& fAddress;
-    ar& fTime;
-    ar& fCharge;
-  }
+ // FIXME: SZh 5.2.2025: change address type int32_t -> uint32_t
+ int32_t fAddress = ToIntegralType<ECbmModuleId>(ECbmModuleId::kBmon);  ///< Unique CBM address
+ double fTime     = -1.;                                                ///< Time of signal in BMON [ns]
+ float fCharge    = -1.;                                                ///< Charge
+
+ friend class boost::serialization::access;
+
+ template<class Archive>
+ void serialize(Archive& ar, const unsigned int /*version*/)
+ {
+   ar& fAddress;
+   ar& fTime;
+   ar& fCharge;
+ }
 
 #ifndef NO_ROOT
   ClassDefNV(CbmBmonDigi, 1);
diff --git a/core/data/tof/CbmTofAddress.cxx b/core/data/tof/CbmTofAddress.cxx
index 0ed83293f27f2576da1de3ffdb02ab3330afa363..29c1573452a1713f962b5a4347e47577000aca0e 100644
--- a/core/data/tof/CbmTofAddress.cxx
+++ b/core/data/tof/CbmTofAddress.cxx
@@ -1,6 +1,6 @@
-/* Copyright (C) 2013-2020 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+/* Copyright (C) 2013-2025 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
    SPDX-License-Identifier: GPL-3.0-only
-   Authors: Pierre-Alain Loizeau, Florian Uhlig [committer], Norbert Herrmann */
+   Authors: Pierre-Alain Loizeau, Florian Uhlig [committer], Norbert Herrmann, Segei Zharko */
 
 /** @file CbmTofAddress.cxx
  ** @author Pierre-Alain Loizeau <loizeau@physi.uni-heidelberg.de>
@@ -11,6 +11,9 @@
 
 #include "CbmTofAddress.h"
 
+#include <iomanip>
+#include <sstream>
+
 // It seems C++ standard force the initialization to be in cxx/cpp file (outside of class definition)
 // When not trivial constant values => To check if it should apply also to simple values maybe?
 /** Offset in bits for Super Module Id in the address field  **/
@@ -40,3 +43,26 @@ const int32_t CbmTofAddress::fgkiStripFullIdMask =
   (((1 << fgkSystemBits) - 1)) + (((1 << fgkSmIdBits) - 1) << fgkSmIdOffset)
   + (((1 << fgkSmTypeBits) - 1) << fgkSmTypeOffset) + (((1 << fgkRpcIdBits) - 1) << fgkRpcIdOffset)
   + (((1 << fgkChannelIdBits) - 1) << fgkChannelIdOffset);
+
+const int32_t CbmTofAddress::fgkSystemIdBitmask    = (1 << CbmAddress::fgkSystemBits) - 1;
+const int32_t CbmTofAddress::fgkSmIdBitmask        = ((1 << fgkSmIdBits) - 1) << fgkSmIdOffset;
+const int32_t CbmTofAddress::fgkSmTypeBitmask      = ((1 << fgkSmTypeBits) - 1) << fgkSmTypeOffset;
+const int32_t CbmTofAddress::fgkRpcIdBitmask       = ((1 << fgkRpcIdBits) - 1) << fgkRpcIdOffset;
+const int32_t CbmTofAddress::fgkChannelSideBitmask = ((1 << fgkChannelSideBits) - 1) << fgkChannelSideOffset;
+const int32_t CbmTofAddress::fgkChannelIdBitmask   = ((1 << fgkChannelIdBits) - 1) << fgkChannelIdOffset;
+const int32_t CbmTofAddress::fgkRpcTypeBitmask     = ((1 << fgkRpcTypeBits) - 1) << fgkRpcTypeOffset;
+
+std::string CbmTofAddress::ToString(int32_t address)
+{
+  using std::setfill;
+  using std::setw;
+  std::stringstream msg;
+  msg << std::hex << "0x" << setw(8) << setfill('0') << address << std::dec << setfill(' ');
+  msg << ": SmType=" << setw(3) << CbmTofAddress::GetSmType(address);
+  msg << ", Sm=" << setw(2) << CbmTofAddress::GetSmId(address);
+  msg << ", Rpc=" << setw(2) << CbmTofAddress::GetRpcId(address);
+  msg << ", Ch=" << setw(2) << CbmTofAddress::GetChannelId(address);
+  msg << ", Side=" << setw(1) << CbmTofAddress::GetChannelSide(address);
+  msg << ", RpcType=" << setw(2) << CbmTofAddress::GetRpcType(address);
+  return msg.str();
+}
diff --git a/core/data/tof/CbmTofAddress.h b/core/data/tof/CbmTofAddress.h
index f8954d532e759804afcb121c715e7852627fb345..dfa1a18e89a74bd6037206079cf23e60011c956d 100644
--- a/core/data/tof/CbmTofAddress.h
+++ b/core/data/tof/CbmTofAddress.h
@@ -1,6 +1,6 @@
-/* Copyright (C) 2013-2020 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+/* Copyright (C) 2013-2025 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
    SPDX-License-Identifier: GPL-3.0-only
-   Authors: Pierre-Alain Loizeau, Florian Uhlig [committer], Norbert Herrmann */
+   Authors: Pierre-Alain Loizeau, Florian Uhlig [committer], Norbert Herrmann, Sergei Zharko */
 
 /** @file CbmTofAddress.h
  ** @author Pierre-Alain Loizeau <loizeau@physi.uni-heidelberg.de>
@@ -44,6 +44,7 @@
 #include "CbmTofDetectorId_v12b.h"  // for CbmTofDetectorId_v12b
 
 #include <cstdint>
+#include <string>
 
 class CbmTofAddress : public CbmAddress {
 public:
@@ -53,6 +54,37 @@ public:
   /** Destructor  **/
   virtual ~CbmTofAddress() {};
 
+  /** Component bitmasks **/
+  /** Gets a bitmask for the System bit field
+   ** @return a bitmask with the bits set only for the System bit field
+   **/
+  static int32_t GetSystemIdBitmask() { return fgkSystemIdBitmask; }
+  /** Gets a bitmask for the Super Module ID bit field
+   ** @return a bitmask with the bits set only for the Super Module ID bit field
+   **/
+  static int32_t GetSmIdBitmask() { return fgkSmIdBitmask; }
+  /** Gets a bitmask for the Super Module Type bit field
+   ** @return a bitmask with the bits set only for the Super Module Type bit field
+   **/
+  static int32_t GetSmTypeBitmask() { return fgkSmTypeBitmask; }
+  /** Gets a bitmask for the RPC ID bit field
+   ** @return a bitmask with the bits set only for the RPC ID bit field
+   **/
+  static int32_t GetRpcIdBitmask() { return fgkRpcIdBitmask; }
+  /** Gets a bitmask for the Channel Side bit field
+   ** @return a bitmask with the bits set only for the Channel Side bit field
+   **/
+  static int32_t GetChannelSideBitmask() { return fgkChannelSideBitmask; }
+  /** Gets a bitmask for the Channel ID bit field
+   ** @return a bitmask with the bits set only for the Channel ID bit field
+   **/
+  static int32_t GetChannelIdBitmask() { return fgkChannelIdBitmask; }
+  /** Gets a bitmask for the RPC Type bit field
+   ** @return a bitmask with the bits set only for the RPC Type bit field
+   **/
+  static int32_t GetRpcTypeBitmask() { return fgkRpcTypeBitmask; }
+
+
   /** Field size accessors **/
   /** Number of bits for Super Module Id in the address field
    ** @return Number of bits
@@ -75,6 +107,33 @@ public:
    **/
   static int32_t GetNofChSideBits() { return fgkChannelSideBits; };
 
+
+  /** Bit field offsets **/
+  /** Gets and offset for the System bit field
+   ** @return and offset with the bits set only for the System bit field
+   **/
+  static int32_t GetSmIdOffset() { return fgkSmIdOffset; }
+  /** Gets and offset for the Super Module Type bit field
+   ** @return and offset with the bits set only for the Super Module Type bit field
+   **/
+  static int32_t GetSmTypeOffset() { return fgkSmTypeOffset; }
+  /** Gets and offset for the RPC ID bit field
+   ** @return and offset with the bits set only for the RPC ID bit field
+   **/
+  static int32_t GetRpcIdOffset() { return fgkRpcIdOffset; }
+  /** Gets and offset for the Channel Side bit field
+   ** @return and offset with the bits set only for the Channel Side bit field
+   **/
+  static int32_t GetChannelSideOffset() { return fgkChannelSideOffset; }
+  /** Gets and offset for the Channel ID bit field
+   ** @return and offset with the bits set only for the Channel ID bit field
+   **/
+  static int32_t GetChannelIdOffset() { return fgkChannelIdOffset; }
+  /** Gets and offset for the RPC Type bit field
+   ** @return and offset with the bits set only for the RPC Type bit field
+   **/
+  static int32_t GetRpcTypeOffset() { return fgkRpcTypeOffset; }
+
   /** Maskers **/
   /** Get the Super Module Id from the address
    ** @param address  Unique address
@@ -91,6 +150,11 @@ public:
    ** @return  systemId
    **/
   static int32_t GetRpcId(uint32_t address) { return ((address >> fgkRpcIdOffset) & ((1 << fgkRpcIdBits) - 1)); };
+  /** Get the Rpc Type from the address
+   ** @param address  Unique address
+   ** @return  systemId
+   **/
+  static int32_t GetRpcType(uint32_t address) { return ((address >> fgkRpcTypeOffset) & ((1 << fgkRpcTypeBits) - 1)); };
   /** Get the Channel Id from the address
    ** @param address  Unique address
    ** @return  systemId
@@ -148,7 +212,9 @@ public:
   {
     return (GetModFullId(addressA) == GetModFullId(addressB)) ? true : false;
   };
-
+  /**
+   ** @brief Sets the channel ID to the address 
+   **/
   static uint32_t ConvertCbmTofDetectorInfo(CbmTofDetectorInfo infoInput)
   {
     // For now assume that the system ID will always be correct
@@ -169,8 +235,14 @@ public:
     return GetUniqueAddress(detId.GetSModule(detIdInput), detId.GetCounter(detIdInput), detId.GetCell(detIdInput), 0,
                             detId.GetSMType(detIdInput));
   };
+  /** String representation of the address
+   ** @param address  Unique address
+   ** @return String representation of the address
+   **/
+  static std::string ToString(int32_t address);
 
-private:
+ private:
+  // FIXME: SZh 7.2.2025: Make these fields constexpr
   /**
    ** To adapt the address sub-fields repartition in size,
    ** you just need to change number of bits of the two sub-fields changing length.
@@ -239,6 +311,16 @@ private:
    ** For the detector strip Id determination (ignore RPC type and side)
    **/
   static const int32_t fgkiStripFullIdMask;
+
+
+  /** Individual bitmasks for each bit field **/
+  static const int32_t fgkSystemIdBitmask;     //<  A bitmask for System ID
+  static const int32_t fgkSmIdBitmask;         //<  A bitmask for SM
+  static const int32_t fgkSmTypeBitmask;       //<  A bitmask for SM Type
+  static const int32_t fgkRpcIdBitmask;        //<  A bitmask for RPC ID
+  static const int32_t fgkChannelSideBitmask;  //<  A bitmask for Channel Side
+  static const int32_t fgkChannelIdBitmask;    //<  A bitmask for Channel ID
+  static const int32_t fgkRpcTypeBitmask;      //<  A bitmask for RPC Type
 };
 
 #endif  // CBMTOFADDRESS_H
diff --git a/core/data/tof/CbmTofDigi.h b/core/data/tof/CbmTofDigi.h
index 942c6edb189dd38b27476ec982c3849a339a81c4..7448ad1a72d204728b1434704ab183a852700938 100644
--- a/core/data/tof/CbmTofDigi.h
+++ b/core/data/tof/CbmTofDigi.h
@@ -109,6 +109,7 @@ public:
   /**
           ** @brief Inherited from CbmDigi.
           **/
+  // FIXME: SZh 5.2.2025: change address type int32_t -> uint32_t
   int32_t GetAddress() const { return fuAddress; };
 
 
@@ -160,6 +161,8 @@ public:
   double GetSide() const { return CbmTofAddress::GetChannelSide(GetAddress()); };
 
   /** Modifiers **/
+
+  // FIXME: SZh 5.2.2025: change address type int32_t -> uint32_t
   void SetAddress(int32_t address) { fuAddress = address; };
   void SetAddress(uint32_t Sm, uint32_t Rpc, uint32_t Channel, uint32_t Side = 0, uint32_t SmType = 0);
   void SetTime(double time) { fdTime = time; };
@@ -169,19 +172,19 @@ public:
 
 
 private:
-  double fdTime;       ///< Absolute time [ps]
-  double fdTot;        ///< Tot [ps]
-  uint32_t fuAddress;  ///< Unique channel address
-
-  friend class boost::serialization::access;
-
-  template<class Archive>
-  void serialize(Archive& ar, const unsigned int /*version*/)
-  {
-    ar& fuAddress;
-    ar& fdTime;
-    ar& fdTot;
-  }
+ double fdTime;       ///< Absolute time [ns]
+ double fdTot;        ///< Tot [ns?]
+ uint32_t fuAddress;  ///< Unique channel address
+
+ friend class boost::serialization::access;
+
+ template<class Archive>
+ void serialize(Archive& ar, const unsigned int /*version*/)
+ {
+   ar& fuAddress;
+   ar& fdTime;
+   ar& fdTot;
+ }
 
 #ifndef NO_ROOT
   ClassDefNV(CbmTofDigi, 3);