hardware: add new sensors to alarm-inventory

After previous commits we saw failures on live boxes that nonoperational
alarm is not registered in alarm-inventory for some components.
And indeed. We fill alarm-inventory right after the Hardware classes
are constructed. However, when the pluggable components are not present
at that time, then its thresholds would not get passed to IETFHardware.
And we read active sensors based on the keys of sensor XPath to
Thresholds mapping.

The simple solution was to load all the active sensors and their
thresholds with every HW state poll. Then, we'd sync the
alarm-inventory with the current state of the active sensors. However,
this could not work. If the sensor is unplugged, we'd delete all
alarm-inventory entries for this sensor but then we can't publish the
missing sensor alarm.

Therefore, let's do it like in health tracking alarms for systemd
services: Publish new alarm-inventory entries but do not delete them.

Change-Id: I8a9b85e3b11f3ea4d66a48241d39b808a8b5dffa
diff --git a/src/ietf-hardware/IETFHardware.h b/src/ietf-hardware/IETFHardware.h
index c226e96..abbd457 100644
--- a/src/ietf-hardware/IETFHardware.h
+++ b/src/ietf-hardware/IETFHardware.h
@@ -10,6 +10,7 @@
 #include <functional>
 #include <map>
 #include <optional>
+#include <set>
 #include <utility>
 #include "ietf-hardware/sysfs/EMMC.h"
 #include "ietf-hardware/sysfs/HWMon.h"
@@ -26,6 +27,13 @@
 struct HardwareInfo {
     DataTree dataTree;
     std::map<std::string, State> updatedTresholdCrossing;
+    std::set<std::string> activeSensors;
+};
+
+struct SensorPollData {
+    DataTree data;
+    ThresholdsBySensorPath thresholds;
+    void merge(SensorPollData&& other);
 };
 
 /**
@@ -52,23 +60,13 @@
 
 public:
     /** @brief The component */
-    using DataReader = std::function<DataTree()>;
+    using DataReader = std::function<SensorPollData()>;
 
     IETFHardware();
     ~IETFHardware();
 
-    template <class DataReaderType>
-    void registerDataReader(const DataReaderType& callable)
-    {
-        m_callbacks.push_back(callable);
-
-        for (const auto& [sensorPath, thresholds] : callable.thresholds()) {
-            m_thresholdsWatchers.emplace(sensorPath, thresholds);
-        }
-    }
-
+    void registerDataReader(const DataReader& callable);
     HardwareInfo process();
-    std::vector<std::string> sensorsXPaths() const;
 
 private:
     velia::Log m_log;
@@ -112,8 +110,7 @@
 /** @brief Manages any component composing of static data only. The static data are provided as a DataTree in construction time. */
 struct StaticData : private DataReader {
     StaticData(std::string propertyPrefix, std::optional<std::string> parent, DataTree tree);
-    DataTree operator()() const;
-    ThresholdsBySensorPath thresholds() const;
+    SensorPollData operator()() const;
 };
 
 /** @brief Manages fans component. Data is provided by a sysfs::HWMon object. */
@@ -125,8 +122,7 @@
 
 public:
     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;
+    SensorPollData operator()() const;
 };
 
 enum class SensorType {
@@ -148,8 +144,7 @@
 
 public:
     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;
+    SensorPollData operator()() const;
 };
 
 /** @brief Manages a single eMMC block device hardware component. Data is provided by a sysfs::EMMC object. */
@@ -160,8 +155,7 @@
 
 public:
     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;
+    SensorPollData operator()() 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)
@@ -169,18 +163,10 @@
 struct Group {
 private:
     std::vector<IETFHardware::DataReader> m_readers;
-    ThresholdsBySensorPath m_thresholds;
 
 public:
-    DataTree operator()() const;
-    ThresholdsBySensorPath thresholds() const;
-
-    template <class DataReaderType>
-    void registerDataReader(const DataReaderType& callable)
-    {
-        m_readers.push_back(callable);
-        m_thresholds.merge(callable.thresholds());
-    }
+    SensorPollData operator()() const;
+    void registerDataReader(const IETFHardware::DataReader& callable);
 };
 
 }