control: add command line tool to retrieve fan status (get function)

This is part two of a multipart commit to create a fan-control command
line utility to report service status, show target/actual RPM/PWM info,
and manually control single fans. This commit implements the "get"
command. Further functionality will come in subsequent commits.

Sample output: /tmp/fanctl get
TARGET SENSOR    TARGET(RPM)   FEEDBACK SENSOR   FEEDBACK(RPMS)
===============================================================
fan0_0                 10000            fan0_0            10000
                                        fan0_1            13591
fan1_0                 10000            fan1_0            10000
                                        fan1_1            13591
fan2_0                 10000            fan2_0            10000
                                        fan2_1            13591
fan3_0                 10000            fan3_0            10000
                                        fan3_1            13591
fan4_0                 10000            fan4_0            10000
                                        fan4_1            13591
fan5_0                 10000            fan5_0            10000
                                        fan5_1            13591

Signed-off-by: Mike Capps <mikepcapps@gmail.com>
Signed-off-by: Matthew Barth <msbarth@us.ibm.com>
Change-Id: I5a918b34b807fa284c0072db2141aa572ae0f0da
diff --git a/control/fanctl.cpp b/control/fanctl.cpp
index f5e77f2..07ae84b 100644
--- a/control/fanctl.cpp
+++ b/control/fanctl.cpp
@@ -24,26 +24,6 @@
 
 using SDBusPlus = phosphor::fan::util::SDBusPlus;
 
-constexpr auto phosphorServiceName = "phosphor-fan-control@0.service";
-constexpr auto systemdMgrIface = "org.freedesktop.systemd1.Manager";
-constexpr auto systemdPath = "/org/freedesktop/systemd1";
-constexpr auto systemdService = "org.freedesktop.systemd1";
-
-std::map<std::string, std::string> interfaces{
-    {"FanSpeed", "xyz.openbmc_project.Control.FanSpeed"},
-    {"FanPwm", "xyz.openbmc_project.Control.FanPwm"},
-    {"SensorValue", "xyz.openbmc_project.Sensor.Value"},
-    {"Item", "xyz.openbmc_project.Inventory.Item"},
-    {"OpStatus", "xyz.openbmc_project.State.Decorator.OperationalStatus"}};
-
-std::map<std::string, std::string> paths{
-    {"motherboard",
-     "/xyz/openbmc_project/inventory/system/chassis/motherboard"},
-    {"tach", "/xyz/openbmc_project/sensors/fan_tach"}};
-
-// paths by D-bus interface,fan name
-std::map<std::string, std::map<std::string, std::vector<std::string>>> pathMap;
-
 /**
  * @function extracts fan name from dbus path string (last token where
  * delimiter is the / character), with proper bounds checking.
@@ -103,6 +83,73 @@
 }
 
 /**
+ * @function consolidated function to load dbus paths and fan names
+ */
+auto loadDBusData()
+{
+    auto& bus{SDBusPlus::getBus()};
+
+    std::vector<std::string> fanNames;
+
+    // paths by D-bus interface,fan name
+    std::map<std::string, std::map<std::string, std::vector<std::string>>>
+        pathMap;
+
+    std::string method("RPM");
+
+    std::map<const std::string, const std::string> interfaces{
+        {"FanSpeed", "xyz.openbmc_project.Control.FanSpeed"},
+        {"FanPwm", "xyz.openbmc_project.Control.FanPwm"},
+        {"SensorValue", "xyz.openbmc_project.Sensor.Value"},
+        {"Item", "xyz.openbmc_project.Inventory.Item"},
+        {"OpStatus", "xyz.openbmc_project.State.Decorator.OperationalStatus"}};
+
+    std::map<const std::string, const std::string> paths{
+        {"motherboard",
+         "/xyz/openbmc_project/inventory/system/chassis/motherboard"},
+        {"tach", "/xyz/openbmc_project/sensors/fan_tach"}};
+
+    // build a list of all fans
+    for (auto& path : SDBusPlus::getSubTreePathsRaw(bus, paths["tach"],
+                                                    interfaces["FanSpeed"], 0))
+    {
+        // special case where we build the list of fans
+        auto fan = justFanName(path);
+        fan = fan.substr(0, fan.rfind("_"));
+        fanNames.push_back(fan);
+    }
+
+    // retry with PWM mode if none found
+    if (0 == fanNames.size())
+    {
+        method = "PWM";
+
+        for (auto& path : SDBusPlus::getSubTreePathsRaw(
+                 bus, paths["tach"], interfaces["FanPwm"], 0))
+        {
+            // special case where we build the list of fans
+            auto fan = justFanName(path);
+            fan = fan.substr(0, fan.rfind("_"));
+            fanNames.push_back(fan);
+        }
+    }
+
+    // load tach sensor paths for each fan
+    pathMap["tach"] =
+        getPathsFromIface(paths["tach"], interfaces["SensorValue"], fanNames);
+
+    // load inventory Item data for each fan
+    pathMap["inventory"] = getPathsFromIface(
+        paths["motherboard"], interfaces["Item"], fanNames, true);
+
+    // load operational status data for each fan
+    pathMap["opstatus"] = getPathsFromIface(
+        paths["motherboard"], interfaces["OpStatus"], fanNames, true);
+
+    return std::make_tuple(fanNames, pathMap, interfaces, method);
+}
+
+/**
  * @function gets the states of phosphor-fanctl. equivalent to
  * "systemctl status phosphor-fan-control@0"
  * @return a list of several (sub)states of fanctl (loaded,
@@ -117,9 +164,13 @@
                    std::string, std::string, sdbusplus::message::object_path,
                    uint32_t, std::string, sdbusplus::message::object_path>;
 
+    constexpr auto systemdMgrIface = "org.freedesktop.systemd1.Manager";
+    constexpr auto systemdPath = "/org/freedesktop/systemd1";
+    constexpr auto systemdService = "org.freedesktop.systemd1";
+
     std::array<std::string, 6> ret;
 
-    std::vector<std::string> services{phosphorServiceName};
+    std::vector<std::string> services{"phosphor-fan-control@0.service"};
 
     try
     {
@@ -139,7 +190,7 @@
                       << std::endl;
         }
     }
-    catch (std::exception& e)
+    catch (const std::exception& e)
     {
         std::cerr << "Failure retrieving phosphor-fan-control states: "
                   << e.what() << std::endl;
@@ -164,6 +215,14 @@
 }
 
 /**
+ * @function helper to determine interface type from a given control method
+ */
+std::string ifaceTypeFromMethod(const std::string& method)
+{
+    return (method == "RPM" ? "FanSpeed" : "FanPwm");
+}
+
+/**
  * @function performs the "status" command from the cmdline.
  * get states and sensor data and output to the console
  */
@@ -173,51 +232,10 @@
     using std::endl;
     using std::setw;
 
-    auto& bus{SDBusPlus::getBus()};
+    auto busData = loadDBusData();
+    auto& method = std::get<3>(busData);
 
-    std::string tmethod("RPM"), fmethod("RPMS"), property;
-
-    std::vector<std::string> sensorPaths, fanNames;
-
-    // build a list of all fans
-    for (auto& path : SDBusPlus::getSubTreePathsRaw(bus, paths["tach"],
-                                                    interfaces["FanSpeed"], 0))
-    {
-        // special case where we build the list of fans
-        auto fan = justFanName(path);
-        fan = fan.substr(0, fan.rfind("_"));
-        fanNames.push_back(fan);
-    }
-
-    // retry if none found
-    if (0 == fanNames.size())
-    {
-        tmethod = "PWM";
-        fmethod = "PWM";
-
-        for (auto& path : SDBusPlus::getSubTreePathsRaw(
-                 bus, paths["tach"], interfaces["FanPwm"], 0))
-        {
-            auto fan = justFanName(path);
-            fan = fan.substr(0, fan.rfind("_"));
-            fanNames.push_back(fan);
-        }
-    }
-
-    // load tach sensor paths for each fan
-    pathMap["tach"] =
-        getPathsFromIface(paths["tach"], interfaces["SensorValue"], fanNames);
-
-    // load speed sensor paths for each fan
-    pathMap["speed"] = pathMap["tach"];
-
-    // load inventory Item data for each fan
-    pathMap["inventory"] = getPathsFromIface(
-        paths["motherboard"], interfaces["Item"], fanNames, true);
-
-    // load operational status data for each fan
-    pathMap["opstatus"] = getPathsFromIface(
-        paths["motherboard"], interfaces["OpStatus"], fanNames, true);
+    std::string property;
 
     // get the state,substate of fan-control and obmc
     auto states = getStates();
@@ -231,11 +249,15 @@
     cout << "CurrentHostState    : " << states[5] << endl;
     cout << endl;
     cout << " FAN        "
-         << "TARGET(" << tmethod << ")  FEEDBACKS(RPMS)   PRESENT"
+         << "TARGET(" << method << ")  FEEDBACKS(RPMS)   PRESENT"
          << "   FUNCTIONAL" << endl;
     cout << "==============================================================="
          << endl;
 
+    auto& fanNames{std::get<0>(busData)};
+    auto& pathMap{std::get<1>(busData)};
+    auto& interfaces{std::get<2>(busData)};
+
     for (auto& fan : fanNames)
     {
         cout << " " << fan << setw(18);
@@ -243,11 +265,13 @@
         // get the target RPM
         property = "Target";
         cout << SDBusPlus::getProperty<uint64_t>(
-                    pathMap["speed"][fan][0], interfaces["FanSpeed"], property)
+                    pathMap["tach"][fan][0],
+                    interfaces[ifaceTypeFromMethod(method)], property)
              << setw(11);
 
         // get the sensor RPM
         property = "Value";
+
         int numRotors = pathMap["tach"][fan].size();
         // print tach readings for each rotor
         for (auto& path : pathMap["tach"][fan])
@@ -261,7 +285,7 @@
         }
         cout << setw(10);
 
-        // get the present property
+        // print the Present property
         property = "Present";
         std::string val;
         for (auto& path : pathMap["inventory"][fan])
@@ -287,7 +311,7 @@
 
         cout << setw(13);
 
-        // get the functional property
+        // and the functional property
         property = "Functional";
         for (auto& path : pathMap["opstatus"][fan])
         {
@@ -315,6 +339,65 @@
 }
 
 /**
+ * @function print target RPM/PWM and tach readings from each fan
+ */
+void get()
+{
+    using std::cout;
+    using std::endl;
+    using std::setw;
+
+    auto busData = loadDBusData();
+
+    auto& fanNames{std::get<0>(busData)};
+    auto& pathMap{std::get<1>(busData)};
+    auto& interfaces{std::get<2>(busData)};
+    auto& method = std::get<3>(busData);
+
+    std::string property;
+
+    // print the header
+    cout << "TARGET SENSOR" << setw(11) << "TARGET(" << method
+         << ")   FEEDBACK SENSOR   ";
+    cout << "FEEDBACK(" << method << ")" << endl;
+    cout << "==============================================================="
+         << endl;
+
+    for (auto& fan : fanNames)
+    {
+        if (pathMap["tach"][fan].size() == 0)
+            continue;
+        // print just the sensor name
+        auto shortPath = pathMap["tach"][fan][0];
+        shortPath = justFanName(shortPath);
+        cout << shortPath << setw(22);
+
+        // print its target RPM/PWM
+        property = "Target";
+        cout << SDBusPlus::getProperty<uint64_t>(
+                    pathMap["tach"][fan][0],
+                    interfaces[ifaceTypeFromMethod(method)], property)
+             << setw(12) << " ";
+
+        // print readings for each rotor
+        property = "Value";
+
+        auto indent = 0U;
+        for (auto& path : pathMap["tach"][fan])
+        {
+            cout << setw(indent);
+            cout << justFanName(path) << setw(17)
+                 << SDBusPlus::getProperty<double>(
+                        path, interfaces["SensorValue"], property)
+                 << endl;
+
+            if (0 == indent)
+                indent = 46;
+        }
+    }
+}
+
+/**
  * @function main entry point for the application
  */
 int main(int argc, char* argv[])
@@ -344,12 +427,25 @@
                           "states, and fan-monitor/BMC/Power service status");
         cmdStatus->require_option(0);
 
+        // Get method
+        auto cmdGet = commands->add_subcommand(
+            "get",
+            "Get the current fan target and feedback speeds for all rotors");
+        cmdGet->set_help_flag(
+            "-h, --help",
+            "Get the current fan target and feedback speeds for all rotors");
+        cmdGet->require_option(0);
+
         CLI11_PARSE(app, argc, argv);
 
         if (app.got_subcommand("status"))
         {
             status();
         }
+        else if (app.got_subcommand("get"))
+        {
+            get();
+        }
     }
     catch (const std::exception& e)
     {