presence: Make error time be per fan

Previously there was a global value for the amount of time a fan needed
to be missing before an event log was created.  This commit changes it
so that instead the value is specified per fan in the JSON.

This way, the times can be different or left off completely on a fan to
fan basis.  The ErrorReporter object will only be created if there is at
least one fan with a time value.

Signed-off-by: Matt Spinler <spinler@us.ibm.com>
Change-Id: I53bb91c88ec1b0352df11b2e988064c2ec02af45
diff --git a/README.md b/README.md
index 360fdea..4afe741 100755
--- a/README.md
+++ b/README.md
@@ -23,6 +23,7 @@
 *Note: The following fan applications support the use of a JSON configuration
 file.*
 * Fan presence detection(presence)
+    * The error logging feature is only available via the JSON configuration.
 * Fan monitoring(monitor)
 
 To clean the repository run `./bootstrap.sh clean`.
diff --git a/presence/error_reporter.cpp b/presence/error_reporter.cpp
index 1f7e5bc..4644535 100644
--- a/presence/error_reporter.cpp
+++ b/presence/error_reporter.cpp
@@ -33,6 +33,7 @@
 using namespace phosphor::logging;
 using namespace sdbusplus::bus::match;
 using namespace std::literals::string_literals;
+using namespace std::chrono;
 namespace fs = std::filesystem;
 
 const auto itemIface = "xyz.openbmc_project.Inventory.Item"s;
@@ -41,14 +42,12 @@
 const auto loggingCreateIface = "xyz.openbmc_project.Logging.Create";
 
 ErrorReporter::ErrorReporter(
-    sdbusplus::bus::bus& bus, const json& jsonConf,
+    sdbusplus::bus::bus& bus,
     const std::vector<
         std::tuple<Fan, std::vector<std::unique_ptr<PresenceSensor>>>>& fans) :
     _bus(bus),
     _event(sdeventplus::Event::get_default())
 {
-    loadConfig(jsonConf);
-
     // If different methods to check the power state are needed across the
     // various platforms, the method/class to use could be read out of JSON
     // or set with a compilation flag.
@@ -58,25 +57,34 @@
 
     for (const auto& fan : fans)
     {
-        auto path = invPrefix + std::get<1>(std::get<0>(fan));
+        const auto& fanData = std::get<0>(fan);
 
-        // Register for fan presence changes, get their initial states,
-        // and create the fan missing timers for each fan.
+        // Only deal with fans that have an error time defined.
+        if (std::get<std::optional<size_t>>(fanData))
+        {
+            auto path = invPrefix + std::get<1>(fanData);
 
-        _matches.emplace_back(
-            _bus, rules::propertiesChanged(path, itemIface),
-            std::bind(std::mem_fn(&ErrorReporter::presenceChanged), this,
-                      std::placeholders::_1));
+            // Register for fan presence changes, get their initial states,
+            // and create the fan missing timers.
 
-        _fanStates.emplace(path, getPresence(std::get<0>(fan)));
+            _matches.emplace_back(
+                _bus, rules::propertiesChanged(path, itemIface),
+                std::bind(std::mem_fn(&ErrorReporter::presenceChanged), this,
+                          std::placeholders::_1));
 
-        auto timer = std::make_unique<
-            sdeventplus::utility::Timer<sdeventplus::ClockId::Monotonic>>(
-            _event,
-            std::bind(std::mem_fn(&ErrorReporter::fanMissingTimerExpired), this,
-                      path));
+            _fanStates.emplace(path, getPresence(fanData));
 
-        _fanMissingTimers.emplace(path, std::move(timer));
+            auto timer = std::make_unique<
+                sdeventplus::utility::Timer<sdeventplus::ClockId::Monotonic>>(
+                _event,
+                std::bind(std::mem_fn(&ErrorReporter::fanMissingTimerExpired),
+                          this, path));
+
+            seconds errorTime{std::get<std::optional<size_t>>(fanData).value()};
+
+            _fanMissingTimers.emplace(
+                path, std::make_tuple(std::move(timer), std::move(errorTime)));
+        }
     }
 
     // If power is already on, check for currently missing fans.
@@ -86,20 +94,6 @@
     }
 }
 
-void ErrorReporter::loadConfig(const json& jsonConf)
-{
-    if (!jsonConf.contains("fan_missing_error_time"))
-    {
-        log<level::ERR>("Missing 'fan_missing_error_time' entry in JSON "
-                        "'reporting' section");
-
-        throw std::runtime_error("Missing fan missing time entry in JSON");
-    }
-
-    _fanMissingErrorTime = std::chrono::seconds{
-        jsonConf.at("fan_missing_error_time").get<std::size_t>()};
-}
-
 void ErrorReporter::presenceChanged(sdbusplus::message::message& msg)
 {
     bool present;
@@ -126,25 +120,27 @@
 
 void ErrorReporter::checkFan(const std::string& fanPath)
 {
+    auto& timer = std::get<0>(_fanMissingTimers[fanPath]);
+
     if (!_fanStates[fanPath])
     {
         // Fan is missing. If power is on, start the timer.
         // If power is off, stop a running timer.
         if (_powerState->isPowerOn())
         {
-            _fanMissingTimers[fanPath]->restartOnce(_fanMissingErrorTime);
+            timer->restartOnce(std::get<seconds>(_fanMissingTimers[fanPath]));
         }
-        else if (_fanMissingTimers[fanPath]->isEnabled())
+        else if (timer->isEnabled())
         {
-            _fanMissingTimers[fanPath]->setEnabled(false);
+            timer->setEnabled(false);
         }
     }
     else
     {
         // Fan is present. Stop a running timer.
-        if (_fanMissingTimers[fanPath]->isEnabled())
+        if (timer->isEnabled())
         {
-            _fanMissingTimers[fanPath]->setEnabled(false);
+            timer->setEnabled(false);
         }
     }
 }
diff --git a/presence/error_reporter.hpp b/presence/error_reporter.hpp
index 398cd1f..a6a86fe 100644
--- a/presence/error_reporter.hpp
+++ b/presence/error_reporter.hpp
@@ -39,24 +39,16 @@
      * @brief Constructor
      *
      * @param[in] bus - The sdbusplus bus object
-     * @param[in] jsonConf - The 'reporting' section of the JSON config file
      * @param[in] fans - The fans for this configuration
      */
     ErrorReporter(
-        sdbusplus::bus::bus& bus, const nlohmann::json& jsonConf,
+        sdbusplus::bus::bus& bus,
         const std::vector<
             std::tuple<Fan, std::vector<std::unique_ptr<PresenceSensor>>>>&
             fans);
 
   private:
     /**
-     * @brief Reads in the configuration from the JSON section
-     *
-     * @param[in] jsonConf - The 'reporting' section of the JSON
-     */
-    void loadConfig(const nlohmann::json& jsonConf);
-
-    /**
      * @brief The propertiesChanged callback for the interface that
      *        contains the Present property of a fan.
      *
@@ -115,21 +107,18 @@
     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.
+     * @brief The map of fan paths to their Timer objects with
+     *        the timer expiration time.
      */
-    std::map<std::string, std::unique_ptr<sdeventplus::utility::Timer<
-                              sdeventplus::ClockId::Monotonic>>>
+    std::map<std::string,
+             std::tuple<std::unique_ptr<sdeventplus::utility::Timer<
+                            sdeventplus::ClockId::Monotonic>>,
+                        std::chrono::seconds>>
         _fanMissingTimers;
 };
 
diff --git a/presence/example/example.json b/presence/example/example.json
index 25c77cc..fd1d657 100644
--- a/presence/example/example.json
+++ b/presence/example/example.json
@@ -1,81 +1,76 @@
-{
-    "fans":
-    [
-        {
-            "name": "Example Fan0",
-            "description": "'Example fan with tach feedback detection method. Fans without any special presence detection hardware can use one or more tach speed sensor feedbacks as an indicator of presence.  Listed sensors are expected to be found in the /xyz/openbmc_project/sensors/fan_tach namespace as required by the OpenBMC DBus API. Supported policy types are fallback or anyof.'",
-            "path": "/system/chassis/motherboard/fan0",
-            "methods": [
-                {
-                    "type": "tach",
-                    "sensors": [
-                        "fan0"
-                    ]
-                }
-            ]
-        },
-        {
-            "name": "Example Fan1",
-            "description": "'Example fan with gpio detection method. Fans with dedicated gpios can use the gpio detection method.  The gpio detection uses Linux gpio-keys: the event number must be provided via the key property.'",
-            "path": "/system/chassis/motherboard/fan1",
-            "methods": [
-                {
-                    "type": "gpio",
-                    "key": 123,
-                    "physpath": "/sys/devices/foo/bar",
-                    "devpath": "/dev/input/by-path/platform-gpio-keys-event"
-                }
-            ]
-        },
-        {
-            "name": "Example Fan2",
-            "description": "'Example fan with fallback redundancy policy. Multiple detection methods for a single fan are allowed. When multiple detection methods are provided a redundancy algorithm must be specified with the rpolicy attribute. Note that the redundancy policy algorithm may or may not factor the order the detection methods are listed into its logic. The fallback algorithm falls back to subsequently listed detection methods when the first method does not detect a fan and the second method does.'",
-            "path": "/system/chassis/motherboard/fan2",
-            "methods": [
-                {
-                    "type": "gpio",
-                    "key": 124,
-                    "physpath": "/sys/devices/foo/bar",
-                    "devpath": "/dev/input/by-path/platform-gpio-keys-polled-event"
-                },
-                {
-                    "type": "tach",
-                    "sensors": [
-                        "fan2"
-                    ]
-                }
-            ],
-            "rpolicy": {
-                "type": "fallback"
-            }
-        },
-        {
-            "name": "Example Fan3",
-            "description": "'Example fan with anyof redundancy policy. The anyof algorithm reports true if any redundancy set component sensors report true.'",
-            "path": "/system/chassis/motherboard/fan3",
-            "methods": [
-                {
-                    "type": "gpio",
-                    "key": 125,
-                    "physpath": "/sys/devices/foo/bar",
-                    "devpath": "/dev/input/by-path/platform-gpio-keys-polled-event"
-                },
-                {
-                    "type": "tach",
-                    "sensors": [
-                        "fan3"
-                    ]
-                }
-            ],
-            "rpolicy": {
-                "type": "anyof"
-            }
-        }
-    ],
-
-    "reporting":
+[
     {
-        "description": "'The amount of time, in seconds, that a fan must be missing before an event log is created.'",
-        "fan_missing_error_time": 10
+        "name": "Example Fan0",
+        "description": "'Example fan with tach feedback detection method. Fans without any special presence detection hardware can use one or more tach speed sensor feedbacks as an indicator of presence.  Listed sensors are expected to be found in the /xyz/openbmc_project/sensors/fan_tach namespace as required by the OpenBMC DBus API. Supported policy types are fallback or anyof.  The fan_missing_error_time value is the amount of time in seconds that a fan must be missing before an event log is created.'",
+        "path": "/system/chassis/motherboard/fan0",
+        "fan_missing_error_time": 10,
+        "methods": [
+            {
+                "type": "tach",
+                "sensors": [
+                    "fan0"
+                ]
+            }
+        ]
+    },
+    {
+        "name": "Example Fan1",
+        "description": "'Example fan with gpio detection method. Fans with dedicated gpios can use the gpio detection method.  The gpio detection uses Linux gpio-keys: the event number must be provided via the key property.'",
+        "path": "/system/chassis/motherboard/fan1",
+        "fan_missing_error_time": 10,
+        "methods": [
+            {
+                "type": "gpio",
+                "key": 123,
+                "physpath": "/sys/devices/foo/bar",
+                "devpath": "/dev/input/by-path/platform-gpio-keys-event"
+            }
+        ]
+    },
+    {
+        "name": "Example Fan2",
+        "description": "'Example fan with fallback redundancy policy. Multiple detection methods for a single fan are allowed. When multiple detection methods are provided a redundancy algorithm must be specified with the rpolicy attribute. Note that the redundancy policy algorithm may or may not factor the order the detection methods are listed into its logic. The fallback algorithm falls back to subsequently listed detection methods when the first method does not detect a fan and the second method does.'",
+        "path": "/system/chassis/motherboard/fan2",
+        "fan_missing_error_time": 10,
+        "methods": [
+            {
+                "type": "gpio",
+                "key": 124,
+                "physpath": "/sys/devices/foo/bar",
+                "devpath": "/dev/input/by-path/platform-gpio-keys-polled-event"
+            },
+            {
+                "type": "tach",
+                "sensors": [
+                    "fan2"
+                ]
+            }
+        ],
+        "rpolicy": {
+            "type": "fallback"
+        }
+    },
+    {
+        "name": "Example Fan3",
+        "description": "'Example fan with anyof redundancy policy. The anyof algorithm reports true if any redundancy set component sensors report true.'",
+        "path": "/system/chassis/motherboard/fan3",
+        "fan_missing_error_time": 10,
+        "methods": [
+            {
+                "type": "gpio",
+                "key": 125,
+                "physpath": "/sys/devices/foo/bar",
+                "devpath": "/dev/input/by-path/platform-gpio-keys-polled-event"
+            },
+            {
+                "type": "tach",
+                "sensors": [
+                    "fan3"
+                ]
+            }
+        ],
+        "rpolicy": {
+            "type": "anyof"
+        }
     }
-}
+]
diff --git a/presence/fan.hpp b/presence/fan.hpp
index c743e3e..3eab814 100644
--- a/presence/fan.hpp
+++ b/presence/fan.hpp
@@ -1,5 +1,6 @@
 #pragma once
 
+#include <optional>
 #include <string>
 #include <tuple>
 
@@ -10,8 +11,8 @@
 namespace presence
 {
 
-/** @brief PrettyName and inventory path. */
-using Fan = std::tuple<std::string, std::string>;
+/** @brief PrettyName, inventory path and time until error. */
+using Fan = std::tuple<std::string, std::string, std::optional<size_t>>;
 
 /**
  * @brief Update the presence state.
diff --git a/presence/json_parser.cpp b/presence/json_parser.cpp
index 23420b6..d850a95 100644
--- a/presence/json_parser.cpp
+++ b/presence/json_parser.cpp
@@ -67,6 +67,8 @@
     {
         using config = fan::JsonConfig;
 
+        _reporter.reset();
+
         // Load and process the json configuration
         process(
             config::load(config::getConfFile(_bus, confAppName, confFileName)));
@@ -88,25 +90,11 @@
 {
     policies policies;
     std::vector<fanPolicy> fans;
-    const json* fanJSON;
-
-    // The original JSON had the fan array at the root, but the new JSON
-    // has it under a 'fans' element.  Support both.
-    // This can be removed after the new JSON is in the image.
-    if (jsonConf.is_array())
-    {
-        fanJSON = &jsonConf;
-    }
-    else
-    {
-        fanJSON = &jsonConf["fans"];
-    }
-
     // Set the expected number of fan entries
     // to be size of the list of fan json config entries
     // (Must be done to eliminate vector reallocation of fan references)
-    fans.reserve(fanJSON->size());
-    for (auto& member : *fanJSON)
+    fans.reserve(jsonConf.size());
+    for (auto& member : jsonConf)
     {
         if (!member.contains("name") || !member.contains("path") ||
             !member.contains("methods") || !member.contains("rpolicy"))
@@ -155,7 +143,17 @@
                 throw std::runtime_error("Invalid fan presence method type");
             }
         }
-        auto fan = std::make_tuple(member["name"], member["path"]);
+
+        // Get the amount of time a fan must be not present before
+        // creating an error.
+        std::optional<size_t> timeUntilError;
+        if (member.contains("fan_missing_error_time"))
+        {
+            timeUntilError = member["fan_missing_error_time"].get<size_t>();
+        }
+
+        auto fan =
+            std::make_tuple(member["name"], member["path"], timeUntilError);
         // Create a fan object
         fans.emplace_back(std::make_tuple(fan, std::move(sensors)));
 
@@ -175,10 +173,12 @@
     _policies.swap(policies);
 
     // Create the error reporter class if necessary
-    if (jsonConf.contains("reporting"))
+    if (std::any_of(_fans.begin(), _fans.end(), [](const auto& fan) {
+            return std::get<std::optional<size_t>>(std::get<Fan>(fan)) !=
+                   std::nullopt;
+        }))
     {
-        _reporter = std::make_unique<ErrorReporter>(
-            _bus, jsonConf.at("reporting"), _fans);
+        _reporter = std::make_unique<ErrorReporter>(_bus, _fans);
     }
 }
 
diff --git a/presence/templates/generated.mako.hpp b/presence/templates/generated.mako.hpp
index 6ba18dc..6395eac 100644
--- a/presence/templates/generated.mako.hpp
+++ b/presence/templates/generated.mako.hpp
@@ -4,6 +4,7 @@
 
 #include <array>
 #include <memory>
+#include <optional>
 #include <string>
 #include "anyof.hpp"
 #include "fallback.hpp"
@@ -51,6 +52,7 @@
                 Fans::value_type{
                     "${f.name}"s,
                     "${f.path}"s,
+                    std::nullopt
                 },
 % endfor
             }