control: Handle adding timers and when they expire

Timers are added to the manager object with a package of data that's
used when the timer expires and runs a set of actions. Each timer added
creates a new timer instance and `oneshot` timers are removed when they
expire.

Change-Id: I1c47878fd3f68a1121b0fd0dfdfe3e66f9ff783f
Signed-off-by: Matthew Barth <msbarth@us.ibm.com>
diff --git a/control/json/actions/action.hpp b/control/json/actions/action.hpp
index 1525175..06759cc 100644
--- a/control/json/actions/action.hpp
+++ b/control/json/actions/action.hpp
@@ -15,9 +15,9 @@
  */
 #pragma once
 
+#include "../zone.hpp"
 #include "config_base.hpp"
 #include "group.hpp"
-#include "zone.hpp"
 
 #include <fmt/format.h>
 
diff --git a/control/json/manager.cpp b/control/json/manager.cpp
index 3508307..a072f49 100644
--- a/control/json/manager.cpp
+++ b/control/json/manager.cpp
@@ -17,7 +17,9 @@
 
 #include "manager.hpp"
 
+#include "action.hpp"
 #include "fan.hpp"
+#include "group.hpp"
 #include "json_config.hpp"
 #include "profile.hpp"
 #include "zone.hpp"
@@ -25,9 +27,16 @@
 #include <nlohmann/json.hpp>
 #include <sdbusplus/bus.hpp>
 #include <sdeventplus/event.hpp>
+#include <sdeventplus/utility/timer.hpp>
 
 #include <algorithm>
+#include <chrono>
 #include <filesystem>
+#include <functional>
+#include <map>
+#include <memory>
+#include <tuple>
+#include <utility>
 #include <vector>
 
 namespace phosphor::fan::control::json
@@ -106,6 +115,55 @@
     return false;
 }
 
+void Manager::addTimer(const TimerType type,
+                       const std::chrono::microseconds interval,
+                       std::unique_ptr<TimerPkg> pkg)
+{
+    auto dataPtr =
+        std::make_unique<TimerData>(std::make_pair(type, std::move(*pkg)));
+    Timer timer(_event,
+                std::bind(&Manager::timerExpired, this, std::ref(*dataPtr)));
+    if (type == TimerType::repeating)
+    {
+        timer.restart(interval);
+    }
+    else if (type == TimerType::oneshot)
+    {
+        timer.restartOnce(interval);
+    }
+    else
+    {
+        throw std::invalid_argument("Invalid Timer Type");
+    }
+    _timers.emplace_back(std::move(dataPtr), std::move(timer));
+}
+
+void Manager::timerExpired(TimerData& data)
+{
+    auto& actions =
+        std::get<std::vector<std::unique_ptr<ActionBase>>&>(data.second);
+    // Perform the actions in the timer data
+    std::for_each(actions.begin(), actions.end(),
+                  [zone = std::ref(std::get<Zone&>(data.second)),
+                   group = std::cref(std::get<const Group&>(data.second))](
+                      auto& action) { action->run(zone, group); });
+
+    // Remove oneshot timers after they expired
+    if (data.first == TimerType::oneshot)
+    {
+        auto itTimer = std::find_if(
+            _timers.begin(), _timers.end(), [&data](const auto& timer) {
+                return (data.first == timer.first->first &&
+                        (std::get<std::string>(data.second) ==
+                         std::get<std::string>(timer.first->second)));
+            });
+        if (itTimer != std::end(_timers))
+        {
+            _timers.erase(itTimer);
+        }
+    }
+}
+
 unsigned int Manager::getPowerOnDelay()
 {
     auto powerOnDelay = 0;
diff --git a/control/json/manager.hpp b/control/json/manager.hpp
index eeedfd3..c2a0904 100644
--- a/control/json/manager.hpp
+++ b/control/json/manager.hpp
@@ -15,6 +15,8 @@
  */
 #pragma once
 
+#include "action.hpp"
+#include "group.hpp"
 #include "json_config.hpp"
 #include "profile.hpp"
 #include "zone.hpp"
@@ -22,6 +24,15 @@
 #include <nlohmann/json.hpp>
 #include <sdbusplus/bus.hpp>
 #include <sdeventplus/event.hpp>
+#include <sdeventplus/utility/timer.hpp>
+
+#include <chrono>
+#include <map>
+#include <memory>
+#include <optional>
+#include <tuple>
+#include <utility>
+#include <vector>
 
 namespace phosphor::fan::control::json
 {
@@ -40,6 +51,33 @@
  */
 using configKey = std::pair<std::string, std::vector<std::string>>;
 
+/* Type of timers supported */
+enum class TimerType
+{
+    oneshot,
+    repeating,
+};
+/**
+ * Package of data required when a timer expires
+ * Tuple constructed of:
+ *      std::string = Timer package unique identifier
+ *      Zone = Zone object to run the actions against
+ *      Group = Group of dbus objects for the actions
+ *      std::vector<std::unique_ptr<ActionBase>> = List of pointers to actions
+ * that run when the timer expires
+ */
+using TimerPkg = std::tuple<std::string, Zone&, const Group&,
+                            std::vector<std::unique_ptr<ActionBase>>&>;
+/**
+ * Data associated with a running timer that's used when it expires
+ * Pair constructed of:
+ *      TimerType = Type of timer to manage expired timer instances
+ *      TimerPkg = Package of data required when the timer expires
+ */
+using TimerData = std::pair<TimerType, TimerPkg>;
+/* Dbus event timer */
+using Timer = sdeventplus::utility::Timer<sdeventplus::ClockId::Monotonic>;
+
 /**
  * @class Manager - Represents the fan control manager's configuration
  *
@@ -162,6 +200,24 @@
     };
 
     /**
+     * @brief Add a dbus timer
+     *
+     * @param[in] type - Type of timer
+     * @param[in] interval - Timer interval in microseconds
+     * @param[in] pkg - Packaged data for when timer expires
+     */
+    void addTimer(const TimerType type,
+                  const std::chrono::microseconds interval,
+                  std::unique_ptr<TimerPkg> pkg);
+
+    /**
+     * @brief Callback when a timer expires
+     *
+     * @param[in] data - Data to be used when the timer expired
+     */
+    void timerExpired(TimerData& data);
+
+    /**
      * @brief Get the configured power on delay(OPTIONAL)
      *
      * @return Power on delay in seconds
@@ -203,6 +259,9 @@
         std::map<std::string, std::map<std::string, PropertyVariantType>>>
         _objects;
 
+    /* List of timers and their data to be processed when expired */
+    std::vector<std::pair<std::unique_ptr<TimerData>, Timer>> _timers;
+
     /* List of zones configured */
     std::map<configKey, std::unique_ptr<Zone>> _zones;