presence: Track missing fans with timers

Fill in the ErrorReporter class to track fan presence status using Timer
objects.  The timers will be started if power is on when fans are
removed.  If a fan is replaced before the timer expires, or if the
system powers off before the timer expires, the timer will be stopped.

The function called when a timer expires is currently stubbed, but
eventually it will create an event log.

The class watches presence changes by watching the Present property for
the fans in the inventory.  Technically, it could watch an internal
status, but this method was chosen because
a) It makes testing easier, so presence changes can be forced using
   busctl as opposed to physically removing hardware.
b) There wasn't really a good place to hook in presence state watches.
c) The application would already crash anyway if the inventory service
   wasn't working for some reason.

Signed-off-by: Matt Spinler <spinler@us.ibm.com>
Change-Id: Ib0524b9b8335414de6b52ce159771eba95248441
diff --git a/presence/error_reporter.hpp b/presence/error_reporter.hpp
index 251aa7e..398cd1f 100644
--- a/presence/error_reporter.hpp
+++ b/presence/error_reporter.hpp
@@ -1,9 +1,13 @@
 #pragma once
 
 #include "fan.hpp"
+#include "power_state.hpp"
 
 #include <nlohmann/json.hpp>
 #include <sdbusplus/bus.hpp>
+#include <sdbusplus/bus/match.hpp>
+#include <sdeventplus/clock.hpp>
+#include <sdeventplus/utility/timer.hpp>
 
 namespace phosphor::fan::presence
 {
@@ -53,15 +57,80 @@
     void loadConfig(const nlohmann::json& jsonConf);
 
     /**
+     * @brief The propertiesChanged callback for the interface that
+     *        contains the Present property of a fan.
+     *
+     * Will start the timer to create an event log if power is on.
+     *
+     * @param[in] msg - The payload of the propertiesChanged signal
+     */
+    void presenceChanged(sdbusplus::message::message& msg);
+
+    /**
+     * @brief The callback function called by the PowerState class
+     *        when the power state changes.
+     *
+     * Will start timers for missing fans if power is on, and stop
+     * them when power changes to off.
+     *
+     * @param[in] powerState - The new power state
+     */
+    void powerStateChanged(bool powerState);
+
+    /**
+     * @brief Called when the fan missing timer expires to create
+     *        an event log for a missing fan.
+     *
+     * @param[in] fanPath - The D-Bus path of the missing fan.
+     */
+    void fanMissingTimerExpired(const std::string& fanPath);
+
+    /**
+     * @brief Checks if the fan missing timer for a fan needs to
+     *        either be started or stopped based on the power and
+     *        presence states.
+     *
+     * @param[in] fanPath - The D-Bus path of the fan
+     */
+    void checkFan(const std::string& fanPath);
+
+    /**
      * @brief Reference to the D-Bus connection object.
      */
     sdbusplus::bus::bus& _bus;
 
     /**
+     * @brief The connection to the event loop for the timer.
+     */
+    sdeventplus::Event _event;
+
+    /**
+     * @brief The propertiesChanged match objects.
+     */
+    std::vector<sdbusplus::bus::match::match> _matches;
+
+    /**
+     * @brief Base class pointer to the power state implementation.
+     */
+    std::unique_ptr<PowerState> _powerState;
+
+    /**
      * @brief The amount of time in seconds that a fan must be missing
      *        before an event log is created for it.
      */
     std::chrono::seconds _fanMissingErrorTime;
+
+    /**
+     * @brief The map of fan paths to their presence states.
+     */
+    std::map<std::string, bool> _fanStates;
+
+    /**
+     * @brief The map of fan paths to their Timer objects.
+     */
+    std::map<std::string, std::unique_ptr<sdeventplus::utility::Timer<
+                              sdeventplus::ClockId::Monotonic>>>
+        _fanMissingTimers;
 };
 
 } // namespace phosphor::fan::presence