control: Add all fan functionality to JSON fan object

In transitioning to JSON based configuration support, the objects
created based on the JSON will eventually replace the similar objects
used for YAML based configurations. This adds all the functionality from
the current fan class used for YAML configs to the fan class used for
JSON configs.

Change-Id: I50bdd218fc964a4b75126643727f1c3f4d27966b
Signed-off-by: Matthew Barth <msbarth@us.ibm.com>
diff --git a/control/json/config_base.hpp b/control/json/config_base.hpp
index 6b060c1..2f282f8 100644
--- a/control/json/config_base.hpp
+++ b/control/json/config_base.hpp
@@ -20,6 +20,8 @@
 #include <nlohmann/json.hpp>
 #include <phosphor-logging/log.hpp>
 
+#include <vector>
+
 namespace phosphor::fan::control::json
 {
 
@@ -112,6 +114,9 @@
             "Unsupported data type for JSON object's value");
     }
 
+    /* Name of the configuration object */
+    std::string _name;
+
     /**
      * Profiles this configuration object belongs to (OPTIONAL).
      * Otherwise always include this object in the configuration
@@ -120,9 +125,6 @@
     std::vector<std::string> _profiles;
 
   private:
-    /* Name of the configuration object */
-    std::string _name;
-
     /**
      * @brief Sets the configuration object's name from the given JSON
      *
diff --git a/control/json/fan.cpp b/control/json/fan.cpp
index 7a65da1..008bca5 100644
--- a/control/json/fan.cpp
+++ b/control/json/fan.cpp
@@ -15,6 +15,10 @@
  */
 #include "fan.hpp"
 
+#include "sdbusplus.hpp"
+
+#include <fmt/format.h>
+
 #include <nlohmann/json.hpp>
 #include <phosphor-logging/log.hpp>
 #include <sdbusplus/bus.hpp>
@@ -25,7 +29,11 @@
 using json = nlohmann::json;
 using namespace phosphor::logging;
 
-Fan::Fan(sdbusplus::bus::bus& bus, const json& jsonObj) : ConfigBase(jsonObj)
+constexpr auto FAN_SENSOR_PATH = "/xyz/openbmc_project/sensors/fan_tach/";
+constexpr auto FAN_TARGET_PROPERTY = "Target";
+
+Fan::Fan(sdbusplus::bus::bus& bus, const json& jsonObj) :
+    ConfigBase(jsonObj), _bus(bus)
 {
     if (jsonObj.contains("profiles"))
     {
@@ -34,9 +42,45 @@
             _profiles.emplace_back(profile.get<std::string>());
         }
     }
-    setZone(jsonObj);
-    setSensors(jsonObj);
     setInterface(jsonObj);
+    setSensors(jsonObj);
+    setZone(jsonObj);
+}
+
+void Fan::setInterface(const json& jsonObj)
+{
+    if (!jsonObj.contains("target_interface"))
+    {
+        log<level::ERR>("Missing required fan sensor target interface",
+                        entry("JSON=%s", jsonObj.dump().c_str()));
+        throw std::runtime_error(
+            "Missing required fan sensor target interface");
+    }
+    _interface = jsonObj["target_interface"].get<std::string>();
+}
+
+void Fan::setSensors(const json& jsonObj)
+{
+    if (!jsonObj.contains("sensors"))
+    {
+        log<level::ERR>("Missing required fan sensors list",
+                        entry("JSON=%s", jsonObj.dump().c_str()));
+        throw std::runtime_error("Missing required fan sensors list");
+    }
+    std::string path;
+    for (const auto& sensor : jsonObj["sensors"])
+    {
+        path = FAN_SENSOR_PATH + sensor.get<std::string>();
+        auto service = util::SDBusPlus::getService(_bus, path, _interface);
+        _sensors[path] = service;
+    }
+    // All sensors associated with this fan are set to the same target speed,
+    // so only need to read target property from one of them
+    if (!path.empty())
+    {
+        _target = util::SDBusPlus::getProperty<uint64_t>(
+            _bus, _sensors.at(path), path, _interface, FAN_TARGET_PROPERTY);
+    }
 }
 
 void Fan::setZone(const json& jsonObj)
@@ -50,30 +94,25 @@
     _zone = jsonObj["zone"].get<std::string>();
 }
 
-void Fan::setSensors(const json& jsonObj)
+void Fan::setTarget(uint64_t target)
 {
-    if (!jsonObj.contains("sensors"))
+    for (const auto& sensor : _sensors)
     {
-        log<level::ERR>("Missing required fan sensors list",
-                        entry("JSON=%s", jsonObj.dump().c_str()));
-        throw std::runtime_error("Missing required fan sensors list");
+        auto value = target;
+        try
+        {
+            util::SDBusPlus::setProperty<uint64_t>(
+                _bus, sensor.second, sensor.first, _interface,
+                FAN_TARGET_PROPERTY, std::move(value));
+        }
+        catch (const sdbusplus::exception::SdBusError&)
+        {
+            throw util::DBusPropertyError{
+                fmt::format("Failed to set target for fan {}", _name).c_str(),
+                sensor.second, sensor.first, _interface, FAN_TARGET_PROPERTY};
+        }
     }
-    for (const auto& sensor : jsonObj["sensors"])
-    {
-        _sensors.emplace_back(sensor.get<std::string>());
-    }
-}
-
-void Fan::setInterface(const json& jsonObj)
-{
-    if (!jsonObj.contains("target_interface"))
-    {
-        log<level::ERR>("Missing required fan sensor target interface",
-                        entry("JSON=%s", jsonObj.dump().c_str()));
-        throw std::runtime_error(
-            "Missing required fan sensor target interface");
-    }
-    _interface = jsonObj["target_interface"].get<std::string>();
+    _target = target;
 }
 
 } // namespace phosphor::fan::control::json
diff --git a/control/json/fan.hpp b/control/json/fan.hpp
index d0f7a06..90bb13a 100644
--- a/control/json/fan.hpp
+++ b/control/json/fan.hpp
@@ -20,6 +20,8 @@
 #include <nlohmann/json.hpp>
 #include <sdbusplus/bus.hpp>
 
+#include <map>
+
 namespace phosphor::fan::control::json
 {
 
@@ -90,15 +92,26 @@
         return _interface;
     }
 
-  private:
-    /* The zone this fan belongs to */
-    std::string _zone;
+    /**
+     * @brief Get the current fan target
+     *
+     * @return - The current target of the fan
+     */
+    inline auto getTarget() const
+    {
+        return _target;
+    }
 
     /**
-     * Sensors containing the `Target` property on
-     * dbus that make up the fan
+     * Sets the target value on all contained sensors
+     *
+     * @param[in] target - The value to set
      */
-    std::vector<std::string> _sensors;
+    void setTarget(uint64_t target);
+
+  private:
+    /* The sdbusplus bus object */
+    sdbusplus::bus::bus& _bus;
 
     /**
      * Interface containing the `Target` property
@@ -106,14 +119,26 @@
      */
     std::string _interface;
 
+    /* Target for this fan */
+    uint64_t _target;
+
     /**
-     * @brief Parse and set the fan's zone
+     * Map of sensors containing the `Target` property on
+     * dbus to the service providing them that make up the fan
+     */
+    std::map<std::string, std::string> _sensors;
+
+    /* The zone this fan belongs to */
+    std::string _zone;
+
+    /**
+     * @brief Parse and set the fan's sensor interface
      *
      * @param[in] jsonObj - JSON object for the fan
      *
-     * Sets the zone this fan is included in.
+     * Sets the sensor interface to use when setting the `Target` property
      */
-    void setZone(const json& jsonObj);
+    void setInterface(const json& jsonObj);
 
     /**
      * @brief Parse and set the fan's sensor list
@@ -126,13 +151,13 @@
     void setSensors(const json& jsonObj);
 
     /**
-     * @brief Parse and set the fan's sensor interface
+     * @brief Parse and set the fan's zone
      *
      * @param[in] jsonObj - JSON object for the fan
      *
-     * Sets the sensor interface to use when setting the `Target` property
+     * Sets the zone this fan is included in.
      */
-    void setInterface(const json& jsonObj);
+    void setZone(const json& jsonObj);
 };
 
 } // namespace phosphor::fan::control::json
diff --git a/control/json_parser.cpp b/control/json_parser.cpp
index c074736..de7bc26 100644
--- a/control/json_parser.cpp
+++ b/control/json_parser.cpp
@@ -27,6 +27,7 @@
 
 #include <algorithm>
 #include <cstdlib>
+#include <string>
 #include <tuple>
 #include <vector>
 
@@ -106,9 +107,18 @@
                 if (checkEntry(activeProfiles, fan.second->getProfiles()) &&
                     fan.second->getZone() == zoneName)
                 {
-                    fanDefs.emplace_back(std::make_tuple(
-                        fan.second->getName(), fan.second->getSensors(),
-                        fan.second->getInterface()));
+                    // Adjust fan object sensor list to be included in the
+                    // YAML fan definitions structure
+                    std::vector<std::string> fanSensorList;
+                    for (const auto& sensorMap : fan.second->getSensors())
+                    {
+                        auto sensor = sensorMap.first;
+                        sensor.erase(0, 38);
+                        fanSensorList.emplace_back(sensor);
+                    }
+                    fanDefs.emplace_back(
+                        std::make_tuple(fan.second->getName(), fanSensorList,
+                                        fan.second->getInterface()));
                 }
             }