Add the restoreLedsAssert method for LED manager

- Logically, Since the physical LEDs states should not be operated
  during the lamp test, all the physical LEDs states before and
  during the lamp test should be saved and restored after the lamp
  test.

Tested:
- Store the state of all the physical LEDs before the lamp test,
  and keep the state of all the physical LEDs consistent after
  the lamp test.

Signed-off-by: George Liu <liuxiwei@inspur.com>
Change-Id: I8b321bae2e38001ea33a80bda8badcf4c8a55040
diff --git a/lamptest.cpp b/lamptest.cpp
index 04db6fd..572ef68 100644
--- a/lamptest.cpp
+++ b/lamptest.cpp
@@ -9,8 +9,30 @@
 namespace led
 {
 
+using namespace phosphor::logging;
+
+bool LampTest::processLEDUpdates(const Manager::group& ledsAssert,
+                                 const Manager::group& ledsDeAssert)
+{
+    // If the physical LED status is updated during the lamp test, it should be
+    // saved to Queue, and the queue will be processed after the lamp test is
+    // stopped.
+    if (isLampTestRunning)
+    {
+        updatedLEDsDuringLampTest.emplace(
+            std::make_pair(ledsAssert, ledsDeAssert));
+        return true;
+    }
+    return false;
+}
+
 void LampTest::stop()
 {
+    if (!isLampTestRunning)
+    {
+        return;
+    }
+
     timer.setEnabled(false);
 
     // Set all the Physical action to Off
@@ -18,15 +40,93 @@
     {
         manager.drivePhysicalLED(path, Layout::Action::Off, 0, 0);
     }
+
+    isLampTestRunning = false;
+    restorePhysicalLedStates();
+}
+
+Layout::Action LampTest::getActionFromString(const std::string& str)
+{
+    Layout::Action action = Layout::Off;
+
+    if (str == "xyz.openbmc_project.Led.Physical.Action.On")
+    {
+        action = Layout::On;
+    }
+    else if (str == "xyz.openbmc_project.Led.Physical.Action.Blink")
+    {
+        action = Layout::Blink;
+    }
+
+    return action;
+}
+
+void LampTest::storePhysicalLEDsStates()
+{
+    physicalLEDStatesPriorToLampTest.clear();
+
+    for (const auto& path : physicalLEDPaths)
+    {
+        // Reverse intercept path, Get the name of each member of physical led
+        // e.g: path = /xyz/openbmc_project/led/physical/front_fan
+        //      name = front_fan
+        sdbusplus::message::object_path object_path(path);
+        auto name = object_path.filename();
+        if (name.empty())
+        {
+            log<level::ERR>(
+                "Failed to get the name of member of physical LED path",
+                entry("PATH=%s", path.c_str()), entry("NAME=%s", name.c_str()));
+            continue;
+        }
+
+        std::string state{};
+        uint16_t period{};
+        uint8_t dutyOn{};
+        try
+        {
+            auto properties = dBusHandler.getAllProperties(path, PHY_LED_IFACE);
+
+            state = std::get<std::string>(properties["State"]);
+            period = std::get<uint16_t>(properties["Period"]);
+            dutyOn = std::get<uint8_t>(properties["DutyOn"]);
+        }
+        catch (const sdbusplus::exception::SdBusError& e)
+        {
+            log<level::ERR>("Failed to get All properties",
+                            entry("ERROR=%s", e.what()),
+                            entry("PATH=%s", path.c_str()));
+            continue;
+        }
+
+        phosphor::led::Layout::Action action = getActionFromString(state);
+        if (action != phosphor::led::Layout::Off)
+        {
+            phosphor::led::Layout::LedAction ledAction{
+                name, action, dutyOn, period, phosphor::led::Layout::On};
+            physicalLEDStatesPriorToLampTest.emplace(ledAction);
+        }
+    }
 }
 
 void LampTest::start()
 {
+    if (isLampTestRunning)
+    {
+        // reset the timer and then return
+        timer.restart(std::chrono::seconds(LAMP_TEST_TIMEOUT_IN_SECS));
+        return;
+    }
+
     // Get paths of all the Physical LED objects
     physicalLEDPaths = dBusHandler.getSubTreePaths(PHY_LED_PATH, PHY_LED_IFACE);
 
+    // Get physical LEDs states before lamp test
+    storePhysicalLEDsStates();
+
     // restart lamp test, it contains initiate or reset the timer.
     timer.restart(std::chrono::seconds(LAMP_TEST_TIMEOUT_IN_SECS));
+    isLampTestRunning = true;
 
     // Set all the Physical action to On for lamp test
     for (const auto& path : physicalLEDPaths)
@@ -37,8 +137,6 @@
 
 void LampTest::timeOutHandler()
 {
-    using namespace phosphor::logging;
-
     // set the Asserted property of lamp test to false
     if (!groupObj)
     {
@@ -66,5 +164,21 @@
     }
 }
 
+void LampTest::restorePhysicalLedStates()
+{
+    // restore physical LEDs states before lamp test
+    Manager::group savedLEDStatesDeAssert{};
+    manager.driveLEDs(physicalLEDStatesPriorToLampTest, savedLEDStatesDeAssert);
+    physicalLEDStatesPriorToLampTest.clear();
+
+    // restore physical LEDs states during lamp test
+    while (!updatedLEDsDuringLampTest.empty())
+    {
+        auto& [ledsAssert, ledsDeAssert] = updatedLEDsDuringLampTest.front();
+        manager.driveLEDs(ledsAssert, ledsDeAssert);
+        updatedLEDsDuringLampTest.pop();
+    }
+}
+
 } // namespace led
 } // namespace phosphor
diff --git a/lamptest.hpp b/lamptest.hpp
index ffce377..ef0d7b0 100644
--- a/lamptest.hpp
+++ b/lamptest.hpp
@@ -5,6 +5,7 @@
 
 #include <sdeventplus/utility/timer.hpp>
 
+#include <queue>
 #include <vector>
 
 namespace phosphor
@@ -47,6 +48,18 @@
      */
     void requestHandler(Group* group, bool value);
 
+    /** @brief Update physical LEDs states during lamp test and the lamp test is
+     *         running
+     *
+     *  @param[in]  ledsAssert    -  LEDs that are to be asserted newly or to a
+     *                               different state
+     *  @param[in]  ledsDeAssert  -  LEDs that are to be Deasserted
+     *
+     *  @return Is running lamp test, true running
+     */
+    bool processLEDUpdates(const Manager::group& ledsAssert,
+                           const Manager::group& ledsDeAssert);
+
   private:
     /** @brief Timer used for LEDs lamp test period */
     sdeventplus::utility::Timer<sdeventplus::ClockId::Monotonic> timer;
@@ -63,6 +76,16 @@
     /** all the Physical paths */
     std::vector<std::string> physicalLEDPaths;
 
+    /** @brief Queue to save LED states during lamp test */
+    std::queue<std::pair<Manager::group, Manager::group>>
+        updatedLEDsDuringLampTest;
+
+    /** @brief Get state of the lamp test operation */
+    bool isLampTestRunning{false};
+
+    /** @brief Physical LED states prior to lamp test */
+    Manager::group physicalLEDStatesPriorToLampTest;
+
     /** @brief Start and restart lamp test depending on what is the current
      *         state. */
     void start();
@@ -73,6 +96,20 @@
     /** @brief This method gets called when the lamp test procedure is done as
      *         part of timeout. */
     void timeOutHandler();
+
+    /** @brief Restore the physical LEDs states after the lamp test finishes */
+    void restorePhysicalLedStates();
+
+    /** @brief Store the physical LEDs states before the lamp test start */
+    void storePhysicalLEDsStates();
+
+    /** @brief Returns action enum based on string
+     *
+     *  @param[in]  str - Action string
+     *
+     *  @return enumeration equivalent of the passed in string
+     */
+    Layout::Action getActionFromString(const std::string& str);
 };
 
 } // namespace led
diff --git a/led-main.cpp b/led-main.cpp
index c1e1a67..df057a5 100644
--- a/led-main.cpp
+++ b/led-main.cpp
@@ -49,6 +49,12 @@
         bus, LAMP_TEST_OBJECT, manager, serialize,
         std::bind(std::mem_fn(&phosphor::led::LampTest::requestHandler),
                   &lampTest, std::placeholders::_1, std::placeholders::_2)));
+
+    // Register a lamp test method in the manager class, and call this method
+    // when the lamp test is started
+    manager.setLampTestCallBack(
+        std::bind(std::mem_fn(&phosphor::led::LampTest::processLEDUpdates),
+                  &lampTest, std::placeholders::_1, std::placeholders::_2));
 #endif
 
     /** Now create so many dbus objects as there are groups */
diff --git a/manager.cpp b/manager.cpp
index c3c142f..6219091 100644
--- a/manager.cpp
+++ b/manager.cpp
@@ -1,3 +1,5 @@
+#include "config.h"
+
 #include "manager.hpp"
 
 #include <phosphor-logging/log.hpp>
@@ -100,9 +102,25 @@
     return assert;
 }
 
+void Manager::setLampTestCallBack(
+    std::function<bool(group& ledsAssert, group& ledsDeAssert)> callBack)
+{
+    lampTestCallBack = callBack;
+}
+
 /** @brief Run through the map and apply action on the LEDs */
 void Manager::driveLEDs(group& ledsAssert, group& ledsDeAssert)
 {
+#ifdef USE_LAMP_TEST
+    // Use the lampTestCallBack method and trigger the callback method in the
+    // lamp test(processLEDUpdates), in this way, all lamp test operations
+    // are performed in the lamp test class.
+    if (lampTestCallBack(ledsAssert, ledsDeAssert))
+    {
+        return;
+    }
+#endif
+
     // This order of LED operation is important.
     if (ledsDeAssert.size())
     {
diff --git a/manager.hpp b/manager.hpp
index f75a81b..6e6ef48 100644
--- a/manager.hpp
+++ b/manager.hpp
@@ -129,6 +129,13 @@
     void drivePhysicalLED(const std::string& objPath, Layout::Action action,
                           uint8_t dutyOn, const uint16_t period);
 
+    /** @brief Set lamp test callback when enabled lamp test.
+     *
+     *  @param[in]  callBack   -  Custom callback when enabled lamp test
+     */
+    void setLampTestCallBack(
+        std::function<bool(group& ledsAssert, group& ledsDeAssert)> callBack);
+
   private:
     /** @brief sdbusplus handler */
     sdbusplus::bus::bus& bus;
@@ -150,6 +157,10 @@
     /** @brief Contains the set of all actions for asserted LEDs */
     group combinedState;
 
+    /** @brief Custom callback when enabled lamp test */
+    std::function<bool(group& ledsAssert, group& ledsDeAssert)>
+        lampTestCallBack;
+
     /** @brief Returns action string based on enum
      *
      *  @param[in]  action - Action enum
diff --git a/utils.cpp b/utils.cpp
index 116f0c3..41b78b9 100644
--- a/utils.cpp
+++ b/utils.cpp
@@ -47,6 +47,30 @@
     return mapperResponse.cbegin()->first;
 }
 
+// Get all properties
+const PropertyMap
+    DBusHandler::getAllProperties(const std::string& objectPath,
+                                  const std::string& interface) const
+{
+    PropertyMap properties;
+
+    auto& bus = DBusHandler::getBus();
+    auto service = getService(objectPath, interface);
+    if (service.empty())
+    {
+        return properties;
+    }
+
+    auto method = bus.new_method_call(service.c_str(), objectPath.c_str(),
+                                      DBUS_PROPERTY_IFACE, "GetAll");
+    method.append(interface);
+
+    auto reply = bus.call(method);
+    reply.read(properties);
+
+    return properties;
+}
+
 // Get the property name
 const PropertyValue
     DBusHandler::getProperty(const std::string& objectPath,
diff --git a/utils.hpp b/utils.hpp
index ef56f5f..8499b2a 100644
--- a/utils.hpp
+++ b/utils.hpp
@@ -20,6 +20,12 @@
 using PropertyValue = std::variant<uint8_t, uint16_t, std::string,
                                    std::vector<std::string>, bool>;
 
+// The name of the property
+using DbusProperty = std::string;
+
+// The Map to constructs all properties values of the interface
+using PropertyMap = std::map<DbusProperty, PropertyValue>;
+
 /**
  *  @class DBusHandler
  *
@@ -49,6 +55,18 @@
     const std::string getService(const std::string& path,
                                  const std::string& interface) const;
 
+    /** @brief Get All properties
+     *
+     *  @param[in] objectPath       -   D-Bus object path
+     *  @param[in] interface        -   D-Bus interface
+     *
+     *  @return The Map to constructs all properties values
+     *
+     *  @throw sdbusplus::exception::SdBusError when it fails
+     */
+    const PropertyMap getAllProperties(const std::string& objectPath,
+                                       const std::string& interface) const;
+
     /** @brief Get property(type: variant)
      *
      *  @param[in] objectPath       -   D-Bus object path