manager: add timer for failed LED actions

The current manager performs LED ActionSet one-time only when LED group
asserted property changes, so if the physical LED object path is not
ready when it is set, the LED will be in an error state. Most of time
this error occurs at BMC booting due phospohr-led-manager starts before
LED driver is probed.

In order to correct LED state after physical LED is ready, add a timer
to repeatedly setting failed LED after 1 second.

Change-Id: I0bb46189c79c961cdaa501a7386346c2bb351252
Signed-off-by: Potin Lai <potin.lai@quantatw.com>
diff --git a/manager/manager.cpp b/manager/manager.cpp
index a9fd21e..6d223b8 100644
--- a/manager/manager.cpp
+++ b/manager/manager.cpp
@@ -118,34 +118,41 @@
         return;
     }
 #endif
-    // This order of LED operation is important.
-    if (ledsDeAssert.size())
+    ActionSet newReqChangedLeds;
+    std::vector<std::pair<ActionSet&, ActionSet&>> actionsVec = {
+        {reqLedsAssert, ledsAssert}, {reqLedsDeAssert, ledsDeAssert}};
+
+    timer.setEnabled(false);
+    std::set_union(ledsAssert.begin(), ledsAssert.end(), ledsDeAssert.begin(),
+                   ledsDeAssert.end(),
+                   std::inserter(newReqChangedLeds, newReqChangedLeds.begin()),
+                   ledLess);
+
+    // prepare reqLedsAssert & reqLedsDeAssert
+    for (auto pair : actionsVec)
     {
-        for (const auto& it : ledsDeAssert)
-        {
-            std::string objPath = std::string(phyLedPath) + it.name;
-            lg2::debug("De-Asserting LED, NAME = {NAME}", "NAME", it.name);
-            drivePhysicalLED(objPath, Layout::Action::Off, it.dutyOn,
-                             it.period);
-        }
+        ActionSet tmpSet;
+
+        // Discard current required LED actions, if these LEDs have new actions
+        // in newReqChangedLeds.
+        std::set_difference(pair.first.begin(), pair.first.end(),
+                            newReqChangedLeds.begin(), newReqChangedLeds.end(),
+                            std::inserter(tmpSet, tmpSet.begin()), ledLess);
+
+        // Union the remaining LED actions with new LED actions.
+        pair.first.clear();
+        std::set_union(tmpSet.begin(), tmpSet.end(), pair.second.begin(),
+                       pair.second.end(),
+                       std::inserter(pair.first, pair.first.begin()), ledLess);
     }
 
-    if (ledsAssert.size())
-    {
-        for (const auto& it : ledsAssert)
-        {
-            std::string objPath = std::string(phyLedPath) + it.name;
-            lg2::debug("Asserting LED, NAME = {NAME}", "NAME", it.name);
-            drivePhysicalLED(objPath, it.action, it.dutyOn, it.period);
-        }
-    }
+    driveLedsHandler();
     return;
 }
 
 // Calls into driving physical LED post choosing the action
-void Manager::drivePhysicalLED(const std::string& objPath,
-                               Layout::Action action, uint8_t dutyOn,
-                               const uint16_t period)
+int Manager::drivePhysicalLED(const std::string& objPath, Layout::Action action,
+                              uint8_t dutyOn, const uint16_t period)
 {
     try
     {
@@ -167,9 +174,10 @@
         lg2::error(
             "Error setting property for physical LED, ERROR = {ERROR}, OBJECT_PATH = {PATH}",
             "ERROR", e, "PATH", objPath);
+        return -1;
     }
 
-    return;
+    return 0;
 }
 
 /** @brief Returns action string based on enum */
@@ -194,5 +202,55 @@
     }
 }
 
+void Manager::driveLedsHandler(void)
+{
+    ActionSet failedLedsAssert;
+    ActionSet failedLedsDeAssert;
+
+    // This order of LED operation is important.
+    if (reqLedsDeAssert.size())
+    {
+        for (const auto& it : reqLedsDeAssert)
+        {
+            std::string objPath = std::string(phyLedPath) + it.name;
+            lg2::debug("De-Asserting LED, NAME = {NAME}, ACTION = {ACTION}",
+                       "NAME", it.name, "ACTION", it.action);
+            if (drivePhysicalLED(objPath, Layout::Action::Off, it.dutyOn,
+                                 it.period))
+            {
+                failedLedsDeAssert.insert(it);
+            }
+        }
+    }
+
+    if (reqLedsAssert.size())
+    {
+        for (const auto& it : reqLedsAssert)
+        {
+            std::string objPath = std::string(phyLedPath) + it.name;
+            lg2::debug("Asserting LED, NAME = {NAME}, ACTION = {ACTION}",
+                       "NAME", it.name, "ACTION", it.action);
+            if (drivePhysicalLED(objPath, it.action, it.dutyOn, it.period))
+            {
+                failedLedsAssert.insert(it);
+            }
+        }
+    }
+
+    reqLedsAssert = failedLedsAssert;
+    reqLedsDeAssert = failedLedsDeAssert;
+
+    if (reqLedsDeAssert.empty() && reqLedsAssert.empty())
+    {
+        timer.setEnabled(false);
+    }
+    else
+    {
+        timer.restartOnce(std::chrono::seconds(1));
+    }
+
+    return;
+}
+
 } // namespace led
 } // namespace phosphor