hardware: data readers can set up sensor thresholds

We are monitoring some hardware sensors. Some sensor values might be
considered dangerous (e.g. high temperatures). This commit introduces a
way of storing thresholds for data readers and validating measured
sensor values against those thresholds.

This is implemented in a way that each data reader must also implement a
method called thresholds() that returns a mapping of sensor XPath to
a Threshold object. These will be collected in the future by
IETFHardware and used to check obtained sensor values in order to raise
alarms.

Change-Id: I2d3854603f2ec04613906290633151f4d7b034f1
diff --git a/src/ietf-hardware/IETFHardware.h b/src/ietf-hardware/IETFHardware.h
index d807fe8..c1265b3 100644
--- a/src/ietf-hardware/IETFHardware.h
+++ b/src/ietf-hardware/IETFHardware.h
@@ -13,6 +13,7 @@
 #include <utility>
 #include "ietf-hardware/sysfs/EMMC.h"
 #include "ietf-hardware/sysfs/HWMon.h"
+#include "ietf-hardware/thresholds.h"
 #include "utils/log-fwd.h"
 
 using namespace std::literals;
@@ -20,6 +21,7 @@
 namespace velia::ietf_hardware {
 
 using DataTree = std::map<std::string, std::string>;
+using ThresholdsBySensorPath = std::map<std::string, Thresholds<int64_t>>;
 
 /**
  * @brief Readout of hardware-state related data according to RFC 8348 (App. A)
@@ -49,7 +51,12 @@
 
     IETFHardware();
     ~IETFHardware();
-    void registerDataReader(const DataReader& callable);
+
+    template <class DataReaderType>
+    void registerDataReader(const DataReaderType& callable)
+    {
+        m_callbacks.push_back(callable);
+    }
 
     DataTree process();
 
@@ -89,6 +96,7 @@
 struct StaticData : private DataReader {
     StaticData(std::string propertyPrefix, std::optional<std::string> parent, DataTree tree);
     DataTree operator()() const;
+    ThresholdsBySensorPath thresholds() const;
 };
 
 /** @brief Manages fans component. Data is provided by a sysfs::HWMon object. */
@@ -96,10 +104,12 @@
 private:
     std::shared_ptr<sysfs::HWMon> m_hwmon;
     unsigned m_fanChannelsCount;
+    Thresholds<int64_t> m_thresholds;
 
 public:
-    Fans(std::string propertyPrefix, std::optional<std::string> parent, std::shared_ptr<sysfs::HWMon> hwmon, unsigned fanChannelsCount);
+    Fans(std::string propertyPrefix, std::optional<std::string> parent, std::shared_ptr<sysfs::HWMon> hwmon, unsigned fanChannelsCount, Thresholds<int64_t> thresholds = {});
     DataTree operator()() const;
+    ThresholdsBySensorPath thresholds() const;
 };
 
 enum class SensorType {
@@ -117,20 +127,24 @@
 private:
     std::shared_ptr<sysfs::HWMon> m_hwmon;
     std::string m_sysfsFile;
+    Thresholds<int64_t> m_thresholds;
 
 public:
-    SysfsValue(std::string propertyPrefix, std::optional<std::string> parent, std::shared_ptr<sysfs::HWMon> hwmon, int sysfsChannelNr);
+    SysfsValue(std::string propertyPrefix, std::optional<std::string> parent, std::shared_ptr<sysfs::HWMon> hwmon, int sysfsChannelNr, Thresholds<int64_t> thresholds = {});
     DataTree operator()() const;
+    ThresholdsBySensorPath thresholds() const;
 };
 
 /** @brief Manages a single eMMC block device hardware component. Data is provided by a sysfs::EMMC object. */
 struct EMMC : private DataReader {
 private:
     std::shared_ptr<sysfs::EMMC> m_emmc;
+    Thresholds<int64_t> m_thresholds;
 
 public:
-    EMMC(std::string propertyPrefix, std::optional<std::string> parent, std::shared_ptr<sysfs::EMMC> emmc);
+    EMMC(std::string propertyPrefix, std::optional<std::string> parent, std::shared_ptr<sysfs::EMMC> emmc, Thresholds<int64_t> thresholds = {});
     DataTree operator()() const;
+    ThresholdsBySensorPath thresholds() const;
 };
 
 /** @brief Use this when you want to wrap reading several properties in one go and still use it as a single DataReader instance            (e.g. in on thread)
@@ -138,10 +152,18 @@
 struct Group {
 private:
     std::vector<IETFHardware::DataReader> m_readers;
+    ThresholdsBySensorPath m_thresholds;
 
 public:
-    void registerDataReader(const IETFHardware::DataReader& callable);
     DataTree operator()() const;
+    ThresholdsBySensorPath thresholds() const;
+
+    template <class DataReaderType>
+    void registerDataReader(const DataReaderType& callable)
+    {
+        m_readers.push_back(callable);
+        m_thresholds.merge(callable.thresholds());
+    }
 };
 
 }