Add LED Support to Fan Sensor

This allows the fan sensor to trip an LED when a critical
threshold is crossed.

Tested: Saw LED d-bus object in Group manager get set
using sensor override

Change-Id: Iab5a69ded20de6e3ac99e9ac687c60605d5763d1
Signed-off-by: James Feist <james.feist@linux.intel.com>
diff --git a/include/TachSensor.hpp b/include/TachSensor.hpp
index af39d0c..1e5347f 100644
--- a/include/TachSensor.hpp
+++ b/include/TachSensor.hpp
@@ -73,7 +73,8 @@
                std::vector<thresholds::Threshold>&& thresholds,
                const std::string& sensorConfiguration,
                const std::pair<size_t, size_t>& limits,
-               const PowerState& powerState = PowerState::on);
+               const PowerState& powerState,
+               const std::optional<std::string>& led);
     ~TachSensor();
 
   private:
@@ -86,6 +87,8 @@
     boost::asio::deadline_timer waitTimer;
     boost::asio::streambuf readBuf;
     std::string path;
+    std::optional<std::string> led;
+    bool ledState = false;
     size_t errCount;
     void setupRead(void);
     void handleResponse(const boost::system::error_code& err);
diff --git a/include/Utils.hpp b/include/Utils.hpp
index 2c6ce9e..7f529e1 100644
--- a/include/Utils.hpp
+++ b/include/Utils.hpp
@@ -81,6 +81,7 @@
 {
 constexpr const char* interface = "org.freedesktop.DBus.Properties";
 constexpr const char* get = "Get";
+constexpr const char* set = "Set";
 } // namespace properties
 
 namespace power
@@ -152,6 +153,22 @@
     }
 }
 
+inline void setLed(std::shared_ptr<sdbusplus::asio::connection> conn,
+                   const std::string& name, bool on)
+{
+    conn->async_method_call(
+        [name](const boost::system::error_code ec) {
+            if (ec)
+            {
+                std::cerr << "Failed to set LED " << name << "\n";
+            }
+        },
+        "xyz.openbmc_project.LED.GroupManager",
+        "/xyz/openbmc_project/led/groups/" + name, properties::interface,
+        properties::set, "xyz.openbmc_project.Led.Group", "Asserted",
+        std::variant<bool>(on));
+}
+
 void createInventoryAssoc(
     std::shared_ptr<sdbusplus::asio::connection> conn,
     std::shared_ptr<sdbusplus::asio::dbus_interface> association,
diff --git a/src/FanMain.cpp b/src/FanMain.cpp
index 0454706..232144f 100644
--- a/src/FanMain.cpp
+++ b/src/FanMain.cpp
@@ -366,40 +366,63 @@
                 auto limits =
                     std::make_pair(defaultMinReading, defaultMaxReading);
 
+                auto connector =
+                    sensorData->find(baseType + std::string(".Connector"));
+
+                std::optional<std::string> led;
+
+                if (connector != sensorData->end())
+                {
+                    auto findPwm = connector->second.find("Pwm");
+                    if (findPwm != connector->second.end())
+                    {
+
+                        size_t pwm = std::visit(VariantToUnsignedIntVisitor(),
+                                                findPwm->second);
+                        /* use pwm name override if found in configuration else
+                         * use default */
+                        auto findOverride = connector->second.find("PwmName");
+                        std::string pwmName;
+                        if (findOverride != connector->second.end())
+                        {
+                            pwmName = std::visit(VariantToStringVisitor(),
+                                                 findOverride->second);
+                        }
+                        else
+                        {
+                            pwmName = "Pwm_" + std::to_string(pwm + 1);
+                        }
+                        pwmNumbers.emplace_back(pwm, *interfacePath, pwmName);
+                    }
+                    else
+                    {
+                        std::cerr << "Connector for " << sensorName
+                                  << " missing pwm!\n";
+                    }
+
+                    auto findLED = connector->second.find("LED");
+                    if (findLED != connector->second.end())
+                    {
+                        auto ledName =
+                            std::get_if<std::string>(&(findLED->second));
+                        if (ledName == nullptr)
+                        {
+                            std::cerr << "Wrong format for LED of "
+                                      << sensorName << "\n";
+                        }
+                        else
+                        {
+                            led = *ledName;
+                        }
+                    }
+                }
+
                 findLimits(limits, baseConfiguration);
                 tachSensors[sensorName] = std::make_unique<TachSensor>(
                     path.string(), baseType, objectServer, dbusConnection,
                     std::move(presenceSensor), redundancy, io, sensorName,
                     std::move(sensorThresholds), *interfacePath, limits,
-                    powerState);
-
-                auto connector =
-                    sensorData->find(baseType + std::string(".Connector"));
-                if (connector != sensorData->end())
-                {
-                    auto findPwm = connector->second.find("Pwm");
-                    if (findPwm == connector->second.end())
-                    {
-                        std::cerr << "Connector Missing PWM!\n";
-                        continue;
-                    }
-                    size_t pwm = std::visit(VariantToUnsignedIntVisitor(),
-                                            findPwm->second);
-                    /* use pwm name override if found in configuration else use
-                     * default */
-                    auto findOverride = connector->second.find("PwmName");
-                    std::string pwmName;
-                    if (findOverride != connector->second.end())
-                    {
-                        pwmName = std::visit(VariantToStringVisitor(),
-                                             findOverride->second);
-                    }
-                    else
-                    {
-                        pwmName = "Pwm_" + std::to_string(pwm + 1);
-                    }
-                    pwmNumbers.emplace_back(pwm, *interfacePath, pwmName);
-                }
+                    powerState, led);
             }
             createRedundancySensor(tachSensors, dbusConnection, objectServer);
             std::vector<fs::path> pwms;
diff --git a/src/TachSensor.cpp b/src/TachSensor.cpp
index d39e92c..0490135 100644
--- a/src/TachSensor.cpp
+++ b/src/TachSensor.cpp
@@ -51,13 +51,15 @@
                        std::vector<thresholds::Threshold>&& _thresholds,
                        const std::string& sensorConfiguration,
                        const std::pair<size_t, size_t>& limits,
-                       const PowerState& powerState) :
+                       const PowerState& powerState,
+                       const std::optional<std::string>& ledIn) :
     Sensor(boost::replace_all_copy(fanName, " ", "_"), std::move(_thresholds),
            sensorConfiguration, objectType, limits.second, limits.first, conn,
            powerState),
     objServer(objectServer), redundancy(redundancy),
     presence(std::move(presenceSensor)),
-    inputDev(io, open(path.c_str(), O_RDONLY)), waitTimer(io), path(path)
+    inputDev(io, open(path.c_str(), O_RDONLY)), waitTimer(io), path(path),
+    led(ledIn)
 {
     sensorInterface = objectServer.add_interface(
         "/xyz/openbmc_project/sensors/fan_tach/" + name,
@@ -192,6 +194,13 @@
         (*redundancy)
             ->update("/xyz/openbmc_project/sensors/fan_tach/" + name, !status);
     }
+
+    bool curLed = !status;
+    if (led && ledState != curLed)
+    {
+        ledState = curLed;
+        setLed(dbusConnection, *led, curLed);
+    }
 }
 
 PresenceSensor::PresenceSensor(const std::string& gpioName, bool inverted,