manager: move code to subdirectory

The root has become pretty cluttered.  Move code for the primary
manager application to a subdirectory.

Signed-off-by: Patrick Williams <patrick@stwcx.xyz>
Change-Id: I8503cf08c72750c88a5ce94a4d94671d6b59aafb
diff --git a/manager/group.cpp b/manager/group.cpp
new file mode 100644
index 0000000..33c64f6
--- /dev/null
+++ b/manager/group.cpp
@@ -0,0 +1,53 @@
+#include "config.h"
+
+#include "group.hpp"
+
+#include <sdbusplus/message.hpp>
+namespace phosphor
+{
+namespace led
+{
+
+/** @brief Overloaded Property Setter function */
+bool Group::asserted(bool value)
+{
+    // If the value is already what is before, return right away
+    if (value ==
+        sdbusplus::xyz::openbmc_project::Led::server::Group::asserted())
+    {
+        return value;
+    }
+
+    if (customCallBack != nullptr)
+    {
+        // Call the custom callback method
+        customCallBack(this, value);
+
+        return sdbusplus::xyz::openbmc_project::Led::server::Group::asserted(
+            value);
+    }
+
+    // Introducing these to enable gtest.
+    Manager::group ledsAssert{};
+    Manager::group ledsDeAssert{};
+
+    // Group management is handled by Manager. The populated leds* sets are not
+    // really used by production code. They are there to enable gtest for
+    // validation.
+    auto result = manager.setGroupState(path, value, ledsAssert, ledsDeAssert);
+
+    // Store asserted state
+    serialize.storeGroups(path, result);
+
+    // If something does not go right here, then there should be an sdbusplus
+    // exception thrown.
+    manager.driveLEDs(ledsAssert, ledsDeAssert);
+
+    // Set the base class's asserted to 'true' since the getter
+    // operation is handled there.
+    return sdbusplus::xyz::openbmc_project::Led::server::Group::asserted(
+        result);
+}
+
+} // namespace led
+} // namespace phosphor
diff --git a/manager/group.hpp b/manager/group.hpp
new file mode 100644
index 0000000..57c33d9
--- /dev/null
+++ b/manager/group.hpp
@@ -0,0 +1,83 @@
+#pragma once
+
+#include "manager.hpp"
+#include "serialize.hpp"
+
+#include <sdbusplus/bus.hpp>
+#include <sdbusplus/server/object.hpp>
+#include <xyz/openbmc_project/Led/Group/server.hpp>
+
+#include <string>
+
+namespace phosphor
+{
+namespace led
+{
+
+/** @class Group
+ *  @brief Manages group of LEDs and applies action on the elements of group
+ */
+class Group :
+    sdbusplus::server::object::object<
+        sdbusplus::xyz::openbmc_project::Led::server::Group>
+{
+  public:
+    Group() = delete;
+    ~Group() = default;
+    Group(const Group&) = delete;
+    Group& operator=(const Group&) = delete;
+    Group(Group&&) = default;
+    Group& operator=(Group&&) = default;
+
+    /** @brief Constructs LED Group
+     *
+     * @param[in] bus       - Handle to system dbus
+     * @param[in] objPath   - The D-Bus path that hosts LED group
+     * @param[in] manager   - Reference to Manager
+     * @param[in] serialize - Serialize object
+     * @param[in] callBack  - Custom callback when LED group is asserted
+     */
+    Group(sdbusplus::bus::bus& bus, const std::string& objPath,
+          Manager& manager, Serialize& serialize,
+          std::function<void(Group*, bool)> callBack = nullptr) :
+
+        sdbusplus::server::object::object<
+            sdbusplus::xyz::openbmc_project::Led::server::Group>(
+            bus, objPath.c_str(), true),
+        path(objPath), manager(manager), serialize(serialize),
+        customCallBack(callBack)
+    {
+        // Initialize Asserted property value
+        if (serialize.getGroupSavedState(objPath))
+        {
+            asserted(true);
+        }
+
+        // Emit deferred signal.
+        emit_object_added();
+    }
+
+    /** @brief Property SET Override function
+     *
+     *  @param[in]  value   -  True or False
+     *  @return             -  Success or exception thrown
+     */
+    bool asserted(bool value) override;
+
+  private:
+    /** @brief Path of the group instance */
+    std::string path;
+
+    /** @brief Reference to Manager object */
+    Manager& manager;
+
+    /** @brief The serialize class for storing and restoring groups of LEDs */
+    Serialize& serialize;
+
+    /** @brief Custom callback when LED group is asserted
+     */
+    std::function<void(Group*, bool)> customCallBack;
+};
+
+} // namespace led
+} // namespace phosphor
diff --git a/manager/json-config.hpp b/manager/json-config.hpp
new file mode 100644
index 0000000..4a07175
--- /dev/null
+++ b/manager/json-config.hpp
@@ -0,0 +1,226 @@
+#include "utils.hpp"
+
+#include <phosphor-logging/lg2.hpp>
+#include <sdbusplus/exception.hpp>
+#include <sdeventplus/event.hpp>
+
+#include <filesystem>
+#include <fstream>
+
+namespace fs = std::filesystem;
+
+namespace phosphor
+{
+namespace led
+{
+
+static constexpr auto confFileName = "led-group-config.json";
+static constexpr auto confOverridePath = "/etc/phosphor-led-manager";
+static constexpr auto confBasePath = "/usr/share/phosphor-led-manager";
+static constexpr auto confCompatibleInterface =
+    "xyz.openbmc_project.Configuration.IBMCompatibleSystem";
+static constexpr auto confCompatibleProperty = "Names";
+
+class JsonConfig
+{
+  public:
+    /**
+     * @brief Constructor
+     *
+     * Looks for the JSON config file.  If it can't find one, then it
+     * will watch entity-manager for the IBMCompatibleSystem interface
+     * to show up.
+     *
+     * @param[in] bus       - The D-Bus object
+     * @param[in] event     - sd event handler
+     */
+    JsonConfig(sdbusplus::bus::bus& bus, sdeventplus::Event& event) :
+        event(event)
+    {
+        match = std::make_unique<sdbusplus::bus::match_t>(
+            bus,
+            sdbusplus::bus::match::rules::interfacesAdded() +
+                sdbusplus::bus::match::rules::sender(
+                    "xyz.openbmc_project.EntityManager"),
+            std::bind(&JsonConfig::ifacesAddedCallback, this,
+                      std::placeholders::_1));
+        getFilePath();
+
+        if (!confFile.empty())
+        {
+            match.reset();
+        }
+    }
+
+    /**
+     * @brief Get the configuration file
+     *
+     * @return filesystem path
+     */
+    inline const fs::path& getConfFile() const
+    {
+        return confFile;
+    }
+
+  private:
+    /** @brief Check the file path exists
+     *
+     *  @param[in]  names   -  Vector of the confCompatible Property
+     *
+     *  @return             -  True or False
+     */
+    bool filePathExists(const std::vector<std::string>& names)
+    {
+        auto it =
+            std::find_if(names.begin(), names.end(), [this](const auto& name) {
+                auto tempConfFile =
+                    fs::path{confBasePath} / name / confFileName;
+                if (fs::exists(tempConfFile))
+                {
+                    confFile = tempConfFile;
+                    return true;
+                }
+                return false;
+            });
+        return it == names.end() ? false : true;
+    }
+
+    /**
+     * @brief The interfacesAdded callback function that looks for
+     *        the IBMCompatibleSystem interface.  If it finds it,
+     *        it uses the Names property in the interface to find
+     *        the JSON config file to use.
+     *
+     * @param[in] msg - The D-Bus message contents
+     */
+    void ifacesAddedCallback(sdbusplus::message::message& msg)
+    {
+        sdbusplus::message::object_path path;
+        std::map<std::string,
+                 std::map<std::string, std::variant<std::vector<std::string>>>>
+            interfaces;
+
+        msg.read(path, interfaces);
+
+        if (!interfaces.contains(confCompatibleInterface))
+        {
+            return;
+        }
+
+        // Get the "Name" property value of the
+        // "xyz.openbmc_project.Configuration.IBMCompatibleSystem" interface
+        const auto& properties = interfaces.at(confCompatibleInterface);
+
+        if (!properties.contains(confCompatibleProperty))
+        {
+            return;
+        }
+        auto names = std::get<std::vector<std::string>>(
+            properties.at(confCompatibleProperty));
+
+        if (filePathExists(names))
+        {
+            match.reset();
+
+            // This results in event.loop() exiting in getSystemLedMap
+            event.exit(0);
+        }
+    }
+
+    /**
+     * Get the json configuration file. The first location found to contain the
+     * json config file from the following locations in order.
+     * confOverridePath: /etc/phosphor-led-manager/led-group-config.json
+     * confBasePath: /usr/shard/phosphor-led-manager/led-group-config.json
+     * the name property of the confCompatibleInterface:
+     * /usr/shard/phosphor-led-manager/${Name}/led-group-config.json
+     *
+     * @brief Get the configuration file to be used
+     *
+     * @return
+     */
+    void getFilePath()
+    {
+        // Check override location
+        confFile = fs::path{confOverridePath} / confFileName;
+        if (fs::exists(confFile))
+        {
+            return;
+        }
+
+        // If the default file is there, use it
+        confFile = fs::path{confBasePath} / confFileName;
+        if (fs::exists(confFile))
+        {
+            return;
+        }
+        confFile.clear();
+
+        try
+        {
+            // Get all objects implementing the compatible interface
+            auto objects =
+                dBusHandler.getSubTreePaths("/", confCompatibleInterface);
+            for (const auto& path : objects)
+            {
+                try
+                {
+                    // Retrieve json config compatible relative path locations
+                    auto value = dBusHandler.getProperty(
+                        path, confCompatibleInterface, confCompatibleProperty);
+
+                    auto confCompatValues =
+                        std::get<std::vector<std::string>>(value);
+
+                    // Look for a config file at each name relative to the base
+                    // path and use the first one found
+                    if (filePathExists(confCompatValues))
+                    {
+                        // Use the first config file found at a listed location
+                        break;
+                    }
+                    confFile.clear();
+                }
+                catch (const sdbusplus::exception::exception& e)
+                {
+                    // Property unavailable on object.
+                    lg2::error(
+                        "Failed to get Names property, ERROR = {ERROR}, INTERFACES = {INTERFACES}, PATH = {PATH}",
+                        "ERROR", e, "INTERFACE", confCompatibleInterface,
+                        "PATH", path);
+
+                    confFile.clear();
+                }
+            }
+        }
+        catch (const sdbusplus::exception::exception& e)
+        {
+            lg2::error(
+                "Failed to call the SubTreePaths method, ERROR = {ERROR}, INTERFACE = {INTERFACE}",
+                "ERROR", e, "INTERFACE", confCompatibleInterface);
+        }
+        return;
+    }
+
+  private:
+    /**
+     * @brief sd event handler.
+     */
+    sdeventplus::Event& event;
+
+    /**
+     * @brief The JSON config file
+     */
+    fs::path confFile;
+
+    /**
+     * @brief The interfacesAdded match that is used to wait
+     *        for the IBMCompatibleSystem interface to show up.
+     */
+    std::unique_ptr<sdbusplus::bus::match_t> match;
+
+    /** DBusHandler class handles the D-Bus operations */
+    utils::DBusHandler dBusHandler;
+};
+} // namespace led
+} // namespace phosphor
diff --git a/manager/json-parser.hpp b/manager/json-parser.hpp
new file mode 100644
index 0000000..a18db5d
--- /dev/null
+++ b/manager/json-parser.hpp
@@ -0,0 +1,177 @@
+#include "config.h"
+
+#include "json-config.hpp"
+#include "ledlayout.hpp"
+
+#include <nlohmann/json.hpp>
+#include <phosphor-logging/lg2.hpp>
+#include <sdbusplus/bus.hpp>
+#include <sdeventplus/event.hpp>
+
+#include <filesystem>
+#include <fstream>
+#include <iostream>
+
+namespace fs = std::filesystem;
+
+using Json = nlohmann::json;
+using LedAction = std::set<phosphor::led::Layout::LedAction>;
+using LedMap = std::map<std::string, LedAction>;
+
+// Priority for a particular LED needs to stay SAME across all groups
+// phosphor::led::Layout::Action can only be one of `Blink` and `On`
+using PriorityMap = std::map<std::string, phosphor::led::Layout::Action>;
+
+/** @brief Parse LED JSON file and output Json object
+ *
+ *  @param[in] path - path of LED JSON file
+ *
+ *  @return const Json - Json object
+ */
+const Json readJson(const fs::path& path)
+{
+
+    if (!fs::exists(path) || fs::is_empty(path))
+    {
+        lg2::error("Incorrect File Path or empty file, FILE_PATH = {PATH}",
+                   "PATH", path);
+        throw std::runtime_error("Incorrect File Path or empty file");
+    }
+
+    try
+    {
+        std::ifstream jsonFile(path);
+        return Json::parse(jsonFile);
+    }
+    catch (const std::exception& e)
+    {
+        lg2::error(
+            "Failed to parse config file, ERROR = {ERROR}, FILE_PATH = {PATH}",
+            "ERROR", e, "PATH", path);
+        throw std::runtime_error("Failed to parse config file");
+    }
+}
+
+/** @brief Returns action enum based on string
+ *
+ *  @param[in] action - action string
+ *
+ *  @return Action - action enum (On/Blink)
+ */
+phosphor::led::Layout::Action getAction(const std::string& action)
+{
+    assert(action == "On" || action == "Blink");
+
+    return action == "Blink" ? phosphor::led::Layout::Blink
+                             : phosphor::led::Layout::On;
+}
+
+/** @brief Validate the Priority of an LED is same across ALL groups
+ *
+ *  @param[in] name - led name member of each group
+ *  @param[in] priority - member priority of each group
+ *  @param[out] priorityMap - std::map, key:name, value:priority
+ *
+ *  @return
+ */
+void validatePriority(const std::string& name,
+                      const phosphor::led::Layout::Action& priority,
+                      PriorityMap& priorityMap)
+{
+
+    auto iter = priorityMap.find(name);
+    if (iter == priorityMap.end())
+    {
+        priorityMap.emplace(name, priority);
+        return;
+    }
+
+    if (iter->second != priority)
+    {
+        lg2::error(
+            "Priority of LED is not same across all, Name = {NAME}, Old Priority = {OLD_PRIO}, New Priority = {NEW_PRIO}",
+            "NAME", name, "OLD_PRIO", int(iter->second), "NEW_PRIO",
+            int(priority));
+
+        throw std::runtime_error(
+            "Priority of at least one LED is not same across groups");
+    }
+}
+
+/** @brief Load JSON config and return led map
+ *
+ *  @return LedMap - Generated an std::map of LedAction
+ */
+const LedMap loadJsonConfig(const fs::path& path)
+{
+    LedMap ledMap{};
+    PriorityMap priorityMap{};
+
+    // define the default JSON as empty
+    const Json empty{};
+    auto json = readJson(path);
+    auto leds = json.value("leds", empty);
+
+    for (const auto& entry : leds)
+    {
+        fs::path tmpPath(std::string{OBJPATH});
+        tmpPath /= entry.value("group", "");
+        auto objpath = tmpPath.string();
+        auto members = entry.value("members", empty);
+
+        LedAction ledActions{};
+        for (const auto& member : members)
+        {
+            auto name = member.value("Name", "");
+            auto action = getAction(member.value("Action", ""));
+            uint8_t dutyOn = member.value("DutyOn", 50);
+            uint16_t period = member.value("Period", 0);
+
+            // Since only have Blink/On and default priority is Blink
+            auto priority = getAction(member.value("Priority", "Blink"));
+
+            // Same LEDs can be part of multiple groups. However, their
+            // priorities across groups need to match.
+            validatePriority(name, priority, priorityMap);
+
+            phosphor::led::Layout::LedAction ledAction{name, action, dutyOn,
+                                                       period, priority};
+            ledActions.emplace(ledAction);
+        }
+
+        // Generated an std::map of LedGroupNames to std::set of LEDs
+        // containing the name and properties.
+        ledMap.emplace(objpath, ledActions);
+    }
+
+    return ledMap;
+}
+
+/** @brief Get led map from LED groups JSON config
+ *
+ *  @return LedMap - Generated an std::map of LedAction
+ */
+const LedMap getSystemLedMap()
+{
+    // Get a new Dbus
+    auto bus = sdbusplus::bus::new_bus();
+
+    // Get a new event loop
+    auto event = sdeventplus::Event::get_new();
+
+    // Attach the bus to sd_event to service user requests
+    bus.attach_event(event.get(), SD_EVENT_PRIORITY_IMPORTANT);
+    phosphor::led::JsonConfig jsonConfig(bus, event);
+
+    // The event loop will be terminated from inside of a function in JsonConfig
+    // after finding the configuration file
+    if (jsonConfig.getConfFile().empty())
+    {
+        event.loop();
+    }
+
+    // Detach the bus from its sd_event event loop object
+    bus.detach_event();
+
+    return loadJsonConfig(jsonConfig.getConfFile());
+}
diff --git a/manager/lamptest/lamptest.cpp b/manager/lamptest/lamptest.cpp
new file mode 100644
index 0000000..cd87bc2
--- /dev/null
+++ b/manager/lamptest/lamptest.cpp
@@ -0,0 +1,298 @@
+#include "lamptest.hpp"
+
+#include <phosphor-logging/lg2.hpp>
+
+#include <algorithm>
+
+namespace phosphor
+{
+namespace led
+{
+
+using Json = nlohmann::json;
+
+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)
+    {
+        // Physical LEDs will be updated during lamp test
+        for (const auto& it : ledsDeAssert)
+        {
+            std::string path = std::string(PHY_LED_PATH) + it.name;
+            auto iter = std::find_if(
+                forceUpdateLEDs.begin(), forceUpdateLEDs.end(),
+                [&path](const auto& name) { return name == path; });
+
+            if (iter != forceUpdateLEDs.end())
+            {
+                manager.drivePhysicalLED(path, Layout::Action::Off, it.dutyOn,
+                                         it.period);
+            }
+        }
+
+        for (const auto& it : ledsAssert)
+        {
+            std::string path = std::string(PHY_LED_PATH) + it.name;
+            auto iter = std::find_if(
+                forceUpdateLEDs.begin(), forceUpdateLEDs.end(),
+                [&path](const auto& name) { return name == path; });
+
+            if (iter != forceUpdateLEDs.end())
+            {
+                manager.drivePhysicalLED(path, it.action, it.dutyOn, it.period);
+            }
+        }
+
+        updatedLEDsDuringLampTest.emplace(
+            std::make_pair(ledsAssert, ledsDeAssert));
+        return true;
+    }
+    return false;
+}
+
+void LampTest::stop()
+{
+    if (!isLampTestRunning)
+    {
+        return;
+    }
+
+    timer.setEnabled(false);
+
+    // Stop host lamp test
+    doHostLampTest(false);
+
+    // Set all the Physical action to Off
+    for (const auto& path : physicalLEDPaths)
+    {
+        auto iter =
+            std::find_if(skipUpdateLEDs.begin(), skipUpdateLEDs.end(),
+                         [&path](const auto& skip) { return skip == path; });
+
+        if (iter != skipUpdateLEDs.end())
+        {
+            // Skip update physical path
+            continue;
+        }
+
+        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)
+    {
+        auto iter = std::find_if(
+            skipUpdateLEDs.begin(), skipUpdateLEDs.end(),
+            [&path](const auto& skipLed) { return skipLed == path; });
+
+        if (iter != skipUpdateLEDs.end())
+        {
+            // Physical LEDs will be skipped
+            continue;
+        }
+
+        // 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())
+        {
+            lg2::error(
+                "Failed to get the name of member of physical LED path, PATH = {PATH}, NAME = {NAME}",
+                "PATH", path, "NAME", name);
+            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::exception& e)
+        {
+            lg2::error(
+                "Failed to get All properties, ERROR = {ERROR}, PATH = {PATH}",
+                "ERROR", e, "PATH", path);
+            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;
+
+    // Notify PHYP to start the lamp test
+    doHostLampTest(true);
+
+    // Set all the Physical action to On for lamp test
+    for (const auto& path : physicalLEDPaths)
+    {
+        auto iter =
+            std::find_if(skipUpdateLEDs.begin(), skipUpdateLEDs.end(),
+                         [&path](const auto& skip) { return skip == path; });
+
+        if (iter != skipUpdateLEDs.end())
+        {
+            // Skip update physical path
+            continue;
+        }
+
+        manager.drivePhysicalLED(path, Layout::Action::On, 0, 0);
+    }
+}
+
+void LampTest::timeOutHandler()
+{
+    // set the Asserted property of lamp test to false
+    if (!groupObj)
+    {
+        lg2::error("the Group object is nullptr");
+        throw std::runtime_error("the Group object is nullptr");
+    }
+
+    groupObj->asserted(false);
+}
+
+void LampTest::requestHandler(Group* group, bool value)
+{
+    if (groupObj == NULL)
+    {
+        groupObj = std::move(group);
+    }
+
+    if (value)
+    {
+        start();
+    }
+    else
+    {
+        stop();
+    }
+}
+
+void LampTest::restorePhysicalLedStates()
+{
+    // restore physical LEDs states before lamp test
+    Manager::group ledsDeAssert{};
+    manager.driveLEDs(physicalLEDStatesPriorToLampTest, ledsDeAssert);
+    physicalLEDStatesPriorToLampTest.clear();
+
+    // restore physical LEDs states during lamp test
+    while (!updatedLEDsDuringLampTest.empty())
+    {
+        auto& [ledsAssert, ledsDeAssert] = updatedLEDsDuringLampTest.front();
+        manager.driveLEDs(ledsAssert, ledsDeAssert);
+        updatedLEDsDuringLampTest.pop();
+    }
+}
+
+void LampTest::doHostLampTest(bool value)
+{
+    try
+    {
+        PropertyValue assertedValue{value};
+        dBusHandler.setProperty(HOST_LAMP_TEST_OBJECT,
+                                "xyz.openbmc_project.Led.Group", "Asserted",
+                                assertedValue);
+    }
+    catch (const sdbusplus::exception::exception& e)
+    {
+        lg2::error(
+            "Failed to set Asserted property, ERROR = {ERROR}, PATH = {PATH}",
+            "ERROR", e, "PATH", std::string(HOST_LAMP_TEST_OBJECT));
+    }
+}
+
+void LampTest::getPhysicalLEDNamesFromJson(const fs::path& path)
+{
+    if (!fs::exists(path) || fs::is_empty(path))
+    {
+        lg2::info("The file does not exist or is empty, FILE_PATH = {PATH}",
+                  "PATH", path);
+        return;
+    }
+
+    try
+    {
+        std::ifstream jsonFile(path);
+        auto json = Json::parse(jsonFile);
+
+        // define the default JSON as empty
+        const std::vector<std::string> empty{};
+        auto forceLEDs = json.value("forceLEDs", empty);
+        std::ranges::transform(forceLEDs, std::back_inserter(forceUpdateLEDs),
+                               [](const auto& i) { return PHY_LED_PATH + i; });
+
+        auto skipLEDs = json.value("skipLEDs", empty);
+        std::ranges::transform(skipLEDs, std::back_inserter(skipUpdateLEDs),
+                               [](const auto& i) { return PHY_LED_PATH + i; });
+    }
+    catch (const std::exception& e)
+    {
+        lg2::error(
+            "Failed to parse config file, ERROR = {ERROR}, FILE_PATH = {PATH}",
+            "ERROR", e, "PATH", path);
+    }
+    return;
+}
+
+} // namespace led
+} // namespace phosphor
diff --git a/manager/lamptest/lamptest.hpp b/manager/lamptest/lamptest.hpp
new file mode 100644
index 0000000..d69667a
--- /dev/null
+++ b/manager/lamptest/lamptest.hpp
@@ -0,0 +1,145 @@
+#pragma once
+
+#include "config.h"
+
+#include "group.hpp"
+#include "manager.hpp"
+
+#include <nlohmann/json.hpp>
+#include <sdeventplus/utility/timer.hpp>
+
+#include <queue>
+#include <vector>
+
+namespace phosphor
+{
+namespace led
+{
+
+/** @class LampTest
+ *  @brief Manager LampTest feature
+ */
+class LampTest
+{
+  public:
+    LampTest() = delete;
+    ~LampTest() = default;
+    LampTest(const LampTest&) = delete;
+    LampTest& operator=(const LampTest&) = delete;
+    LampTest(LampTest&&) = default;
+    LampTest& operator=(LampTest&&) = default;
+
+    /** @brief Constructs LED LampTest
+     *
+     * Constructs timer and when the timeout occurs, the stop method is called
+     * back to stop timer and also end the lamp test.
+     *
+     * @param[in] event   - sd event handler
+     * @param[in] manager - reference to manager instance
+     */
+    LampTest(const sdeventplus::Event& event, Manager& manager) :
+        timer(event, std::bind(std::mem_fn(&LampTest::timeOutHandler), this)),
+        manager(manager), groupObj(NULL)
+    {
+        // Get the force update and/or skipped physical LEDs names from the
+        // lamp-test-led-overrides.json file during lamp
+        getPhysicalLEDNamesFromJson(LAMP_TEST_LED_OVERRIDES_JSON);
+    }
+
+    /** @brief the lamp test request handler
+     *
+     *  @param[in]  group    -  Pointer to Group object
+     *  @param[in]  value    -  true: start lamptest
+     *                          false: stop lamptest
+     *  @return
+     */
+    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;
+
+    /** @brief Reference to Manager object */
+    Manager& manager;
+
+    /** DBusHandler class handles the D-Bus operations */
+    DBusHandler dBusHandler;
+
+    /** @brief Pointer to Group object */
+    Group* groupObj;
+
+    /** 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 Vector of names of physical LEDs, whose changes will be forcibly
+     *         updated even during lamp test. */
+    std::vector<std::string> forceUpdateLEDs;
+
+    /** @brief Vector of names of physical LEDs, that will be exempted from lamp
+     *         test */
+    std::vector<std::string> skipUpdateLEDs;
+
+    /** @brief Start and restart lamp test depending on what is the current
+     *         state. */
+    void start();
+
+    /** @brief Stop lamp test. */
+    void stop();
+
+    /** @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);
+
+    /** @brief Notify PHYP to start / stop the lamp test
+     *
+     *  @param[in]  value   -  the Asserted property value
+     */
+    void doHostLampTest(bool value);
+
+    /** @brief Get physical LED names from lamp test JSON config file
+     *
+     *  @param[in]  path - path of LED JSON file
+     *
+     *  return
+     */
+    void getPhysicalLEDNamesFromJson(const fs::path& path);
+};
+
+} // namespace led
+} // namespace phosphor
diff --git a/manager/led-main.cpp b/manager/led-main.cpp
new file mode 100644
index 0000000..c3b2cea
--- /dev/null
+++ b/manager/led-main.cpp
@@ -0,0 +1,79 @@
+#include "config.h"
+
+#include "group.hpp"
+#ifdef LED_USE_JSON
+#include "json-parser.hpp"
+#else
+#include "led-gen.hpp"
+#endif
+#include "ledlayout.hpp"
+#include "manager.hpp"
+#include "serialize.hpp"
+#include "utils.hpp"
+#ifdef USE_LAMP_TEST
+#include "lamptest/lamptest.hpp"
+#endif
+
+#include <sdeventplus/event.hpp>
+
+#include <algorithm>
+#include <iostream>
+
+int main(void)
+{
+    // Get a default event loop
+    auto event = sdeventplus::Event::get_default();
+
+    /** @brief Dbus constructs used by LED Group manager */
+    auto& bus = phosphor::led::utils::DBusHandler::getBus();
+
+#ifdef LED_USE_JSON
+    auto systemLedMap = getSystemLedMap();
+#endif
+
+    /** @brief Group manager object */
+    phosphor::led::Manager manager(bus, systemLedMap);
+
+    /** @brief sd_bus object manager */
+    sdbusplus::server::manager::manager objManager(bus, OBJPATH);
+
+    /** @brief vector of led groups */
+    std::vector<std::unique_ptr<phosphor::led::Group>> groups;
+
+    /** @brief store and re-store Group */
+    phosphor::led::Serialize serialize(SAVED_GROUPS_FILE);
+
+#ifdef USE_LAMP_TEST
+    phosphor::led::LampTest lampTest(event, manager);
+
+    groups.emplace_back(std::make_unique<phosphor::led::Group>(
+        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 */
+    std::ranges::transform(
+        systemLedMap, std::back_inserter(groups),
+        [&bus, &manager, &serialize](
+            const std::pair<std::string,
+                            std::set<phosphor::led::Layout::LedAction>>& grp) {
+            return std::make_unique<phosphor::led::Group>(bus, grp.first,
+                                                          manager, serialize);
+        });
+
+    // Attach the bus to sd_event to service user requests
+    bus.attach_event(event.get(), SD_EVENT_PRIORITY_NORMAL);
+
+    /** @brief Claim the bus */
+    bus.request_name(BUSNAME);
+    event.loop();
+
+    return 0;
+}
diff --git a/manager/ledlayout.hpp b/manager/ledlayout.hpp
new file mode 100644
index 0000000..f12e0b5
--- /dev/null
+++ b/manager/ledlayout.hpp
@@ -0,0 +1,58 @@
+#pragma once
+
+#include <map>
+#include <set>
+#include <string>
+
+namespace phosphor
+{
+namespace led
+{
+/** @namespace Layout
+ *  @brief Depicts the LED and their mappings and group actions
+ */
+namespace Layout
+{
+/** @brief Define possible actions on a given LED.
+ *  For the BLINK operation, follow 50-50 duty cycle
+ */
+enum Action
+{
+    Off,
+    On,
+    Blink,
+};
+
+/** @brief Name of the LED and it's proposed action.
+ *  This structure is supplied as configuration at build time
+ */
+struct LedAction
+{
+    std::string name;
+    Action action;
+    uint8_t dutyOn;
+    uint16_t period;
+    Action priority;
+
+    // Order LEDs such that same LEDs are grouped next to
+    // each other and the same LEDs are in priority order
+    // with the highest priority coming first
+    bool operator<(const LedAction& right) const
+    {
+        if (name == right.name)
+        {
+            if (action == right.action)
+            {
+                return false;
+            }
+            else if (action == priority)
+            {
+                return true;
+            }
+        }
+        return name < right.name;
+    }
+};
+} // namespace Layout
+} // namespace led
+} // namespace phosphor
diff --git a/manager/manager.cpp b/manager/manager.cpp
new file mode 100644
index 0000000..998f22d
--- /dev/null
+++ b/manager/manager.cpp
@@ -0,0 +1,199 @@
+#include "config.h"
+
+#include "manager.hpp"
+
+#include <phosphor-logging/lg2.hpp>
+#include <sdbusplus/exception.hpp>
+#include <xyz/openbmc_project/Led/Physical/server.hpp>
+
+#include <algorithm>
+#include <iostream>
+#include <string>
+namespace phosphor
+{
+namespace led
+{
+
+// Assert -or- De-assert
+bool Manager::setGroupState(const std::string& path, bool assert,
+                            group& ledsAssert, group& ledsDeAssert)
+{
+    if (assert)
+    {
+        assertedGroups.insert(&ledMap.at(path));
+    }
+    else
+    {
+        if (assertedGroups.contains(&ledMap.at(path)))
+        {
+            assertedGroups.erase(&ledMap.at(path));
+        }
+    }
+
+    // This will contain the union of what's already in the asserted group
+    group desiredState{};
+    for (const auto& grp : assertedGroups)
+    {
+        desiredState.insert(grp->cbegin(), grp->cend());
+    }
+
+    // Find difference between Combined and Desired to identify
+    // which LEDs are getting altered
+    group transient{};
+    std::set_difference(combinedState.begin(), combinedState.end(),
+                        desiredState.begin(), desiredState.end(),
+                        std::inserter(transient, transient.begin()), ledComp);
+    if (transient.size())
+    {
+        // Find common LEDs between transient and Desired to know if some LEDs
+        // are changing state and not really getting DeAsserted
+        group ledsTransient{};
+        std::set_intersection(
+            transient.begin(), transient.end(), desiredState.begin(),
+            desiredState.end(),
+            std::inserter(ledsTransient, ledsTransient.begin()), ledLess);
+
+        // Find difference between above 2 to identify those LEDs which are
+        // really getting DeAsserted
+        std::set_difference(transient.begin(), transient.end(),
+                            ledsTransient.begin(), ledsTransient.end(),
+                            std::inserter(ledsDeAssert, ledsDeAssert.begin()),
+                            ledLess);
+
+        // Remove the elements from Current that are being DeAsserted.
+        if (ledsDeAssert.size())
+        {
+            // Power off LEDs that are to be really DeAsserted
+            for (auto& it : ledsDeAssert)
+            {
+                // Update LEDs in "physically asserted" set by removing those
+                // LEDs which are De-Asserted
+                auto found = currentState.find(it);
+                if (found != currentState.end())
+                {
+                    currentState.erase(found);
+                }
+            }
+        }
+    }
+
+    // Now LEDs that are to be Asserted. These could either be fresh asserts
+    // -or- change between [On]<-->[Blink]
+    group temp{};
+    std::unique_copy(desiredState.begin(), desiredState.end(),
+                     std::inserter(temp, temp.begin()), ledEqual);
+    if (temp.size())
+    {
+        // Find difference between [desired to be Asserted] and those LEDs
+        // that are physically asserted currently.
+        std::set_difference(
+            temp.begin(), temp.end(), currentState.begin(), currentState.end(),
+            std::inserter(ledsAssert, ledsAssert.begin()), ledComp);
+    }
+
+    // Update the current actual and desired(the virtual actual)
+    currentState = std::move(temp);
+    combinedState = std::move(desiredState);
+
+    // If we survive, then set the state accordingly.
+    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())
+    {
+        for (const auto& it : ledsDeAssert)
+        {
+            std::string objPath = std::string(PHY_LED_PATH) + it.name;
+            lg2::debug("De-Asserting LED, NAME = {NAME}", "NAME", it.name);
+            drivePhysicalLED(objPath, Layout::Action::Off, it.dutyOn,
+                             it.period);
+        }
+    }
+
+    if (ledsAssert.size())
+    {
+        for (const auto& it : ledsAssert)
+        {
+            std::string objPath = std::string(PHY_LED_PATH) + it.name;
+            lg2::debug("Asserting LED, NAME = {NAME}", "NAME", it.name);
+            drivePhysicalLED(objPath, it.action, it.dutyOn, it.period);
+        }
+    }
+    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)
+{
+    try
+    {
+        // If Blink, set its property
+        if (action == Layout::Action::Blink)
+        {
+            PropertyValue dutyOnValue{dutyOn};
+            PropertyValue periodValue{period};
+
+            dBusHandler.setProperty(objPath, PHY_LED_IFACE, "DutyOn",
+                                    dutyOnValue);
+            dBusHandler.setProperty(objPath, PHY_LED_IFACE, "Period",
+                                    periodValue);
+        }
+
+        PropertyValue actionValue{getPhysicalAction(action)};
+        dBusHandler.setProperty(objPath, PHY_LED_IFACE, "State", actionValue);
+    }
+    catch (const std::exception& e)
+    {
+        lg2::error(
+            "Error setting property for physical LED, ERROR = {ERROR}, OBJECT_PATH = {PATH}",
+            "ERROR", e, "PATH", objPath);
+    }
+
+    return;
+}
+
+/** @brief Returns action string based on enum */
+std::string Manager::getPhysicalAction(Layout::Action action)
+{
+    namespace server = sdbusplus::xyz::openbmc_project::Led::server;
+
+    // TODO: openbmc/phosphor-led-manager#5
+    //    Somehow need to use the generated Action enum than giving one
+    //    in ledlayout.
+    if (action == Layout::Action::On)
+    {
+        return server::convertForMessage(server::Physical::Action::On);
+    }
+    else if (action == Layout::Action::Blink)
+    {
+        return server::convertForMessage(server::Physical::Action::Blink);
+    }
+    else
+    {
+        return server::convertForMessage(server::Physical::Action::Off);
+    }
+}
+
+} // namespace led
+} // namespace phosphor
diff --git a/manager/manager.hpp b/manager/manager.hpp
new file mode 100644
index 0000000..20f8aaa
--- /dev/null
+++ b/manager/manager.hpp
@@ -0,0 +1,164 @@
+#pragma once
+
+#include "ledlayout.hpp"
+#include "utils.hpp"
+
+#include <map>
+#include <set>
+#include <string>
+
+namespace phosphor
+{
+namespace led
+{
+using namespace phosphor::led::utils;
+
+static constexpr auto PHY_LED_PATH = "/xyz/openbmc_project/led/physical/";
+static constexpr auto PHY_LED_IFACE = "xyz.openbmc_project.Led.Physical";
+
+/** @class Manager
+ *  @brief Manages group of LEDs and applies action on the elements of group
+ */
+class Manager
+{
+  public:
+    /** @brief Only need the default Manager */
+    Manager() = delete;
+    ~Manager() = default;
+    Manager(const Manager&) = delete;
+    Manager& operator=(const Manager&) = delete;
+    Manager(Manager&&) = delete;
+    Manager& operator=(Manager&&) = delete;
+
+    /** @brief Special comparator for finding set difference */
+    static bool ledComp(const phosphor::led::Layout::LedAction& left,
+                        const phosphor::led::Layout::LedAction& right)
+    {
+        // Example :
+        // If FIRST_1 is {fan0, 1, 1} and FIRST_2 is {fan0, 2, 2},
+        // with default priority of Blink, this comparator would return
+        // false. But considering the priority, this comparator would need
+        // to return true so that we consider appropriate set and in
+        // this case its {fan0, 1, 1}
+        if (left.name == right.name)
+        {
+            if (left.action == right.action)
+            {
+                return false;
+            }
+            else
+            {
+                return true;
+            }
+        }
+        return left.name < right.name;
+    }
+
+    /** @brief Comparator for finding LEDs to be DeAsserted */
+    static bool ledLess(const phosphor::led::Layout::LedAction& left,
+                        const phosphor::led::Layout::LedAction& right)
+    {
+        return left.name < right.name;
+    }
+
+    /** @brief Comparator for helping unique_copy */
+    static bool ledEqual(const phosphor::led::Layout::LedAction& left,
+                         const phosphor::led::Layout::LedAction& right)
+    {
+        return left.name == right.name;
+    }
+
+    using group = std::set<phosphor::led::Layout::LedAction>;
+    using LedLayout = std::map<std::string, group>;
+
+    /** @brief static global map constructed at compile time */
+    const LedLayout& ledMap;
+
+    /** @brief Refer the user supplied LED layout and sdbusplus handler
+     *
+     *  @param [in] bus       - sdbusplus handler
+     *  @param [in] LedLayout - LEDs group layout
+     */
+    Manager(sdbusplus::bus::bus& bus, const LedLayout& ledLayout) :
+        ledMap(ledLayout), bus(bus)
+    {
+        // Nothing here
+    }
+
+    /** @brief Given a group name, applies the action on the group
+     *
+     *  @param[in]  path          -  dbus path of group
+     *  @param[in]  assert        -  Could be true or false
+     *  @param[in]  ledsAssert    -  LEDs that are to be asserted new
+     *                               or to a different state
+     *  @param[in]  ledsDeAssert  -  LEDs that are to be Deasserted
+     *
+     *  @return                   -  Success or exception thrown
+     */
+    bool setGroupState(const std::string& path, bool assert, group& ledsAssert,
+                       group& ledsDeAssert);
+
+    /** @brief Finds the set of LEDs to operate on and executes action
+     *
+     *  @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: None
+     */
+    void driveLEDs(group& ledsAssert, group& ledsDeAssert);
+
+    /** @brief Chooses appropriate action to be triggered on physical LED
+     *  and calls into function that applies the actual action.
+     *
+     *  @param[in]  objPath   -  D-Bus object path
+     *  @param[in]  action    -  Intended action to be triggered
+     *  @param[in]  dutyOn    -  Duty Cycle ON percentage
+     *  @param[in]  period    -  Time taken for one blink cycle
+     */
+    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;
+
+    /** Map of physical LED path to service name */
+    std::map<std::string, std::string> phyLeds{};
+
+    /** DBusHandler class handles the D-Bus operations */
+    DBusHandler dBusHandler;
+
+    /** @brief Pointers to groups that are in asserted state */
+    std::set<const group*> assertedGroups;
+
+    /** @brief Contains the highest priority actions for all
+     *         asserted LEDs.
+     */
+    group currentState;
+
+    /** @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
+     *
+     *  @return string equivalent of the passed in enumeration
+     */
+    static std::string getPhysicalAction(Layout::Action action);
+};
+
+} // namespace led
+} // namespace phosphor
diff --git a/manager/meson.build b/manager/meson.build
new file mode 100644
index 0000000..b3eddeb
--- /dev/null
+++ b/manager/meson.build
@@ -0,0 +1,38 @@
+sources = [
+    'group.cpp',
+    'led-main.cpp',
+    'manager.cpp',
+    'serialize.cpp',
+    '../utils.cpp',
+]
+
+if get_option('use-json').disabled()
+    led_gen_hpp = custom_target(
+        'led-gen.hpp',
+        command : [
+            prog_python,
+            meson.project_source_root() + '/scripts/parse_led.py',
+            '-i', meson.project_source_root(),
+            '-o', meson.current_build_dir(),
+        ],
+        output : 'led-gen.hpp')
+    sources += [led_gen_hpp]
+endif
+
+if get_option('use-lamp-test').enabled()
+    conf_data.set_quoted('LAMP_TEST_OBJECT', '/xyz/openbmc_project/led/groups/lamp_test')
+    conf_data.set_quoted('HOST_LAMP_TEST_OBJECT', '/xyz/openbmc_project/led/groups/host_lamp_test')
+    conf_data.set_quoted('LAMP_TEST_LED_OVERRIDES_JSON', '/usr/share/phosphor-led-manager/lamp-test-led-overrides.json')
+    conf_data.set('LAMP_TEST_TIMEOUT_IN_SECS', 240)
+
+    sources += ['lamptest/lamptest.cpp']
+endif
+
+executable(
+    'phosphor-ledmanager',
+    sources,
+    include_directories: ['..'],
+    dependencies: deps,
+    install: true,
+    install_dir: get_option('bindir')
+)
diff --git a/manager/serialize.cpp b/manager/serialize.cpp
new file mode 100644
index 0000000..5982093
--- /dev/null
+++ b/manager/serialize.cpp
@@ -0,0 +1,79 @@
+#include "config.h"
+
+#include "serialize.hpp"
+
+#include <cereal/archives/json.hpp>
+#include <cereal/types/set.hpp>
+#include <cereal/types/string.hpp>
+#include <phosphor-logging/lg2.hpp>
+
+#include <filesystem>
+#include <fstream>
+
+// Register class version with Cereal
+CEREAL_CLASS_VERSION(phosphor::led::Serialize, CLASS_VERSION)
+
+namespace phosphor
+{
+namespace led
+{
+
+namespace fs = std::filesystem;
+
+bool Serialize::getGroupSavedState(const std::string& objPath) const
+{
+    return savedGroups.contains(objPath);
+}
+
+void Serialize::storeGroups(const std::string& group, bool asserted)
+{
+    // If the name of asserted group does not exist in the archive and the
+    // Asserted property is true, it is inserted into archive.
+    // If the name of asserted group exist in the archive and the Asserted
+    // property is false, entry is removed from the archive.
+    auto iter = savedGroups.find(group);
+    if (iter != savedGroups.end() && asserted == false)
+    {
+        savedGroups.erase(iter);
+    }
+
+    if (iter == savedGroups.end() && asserted)
+    {
+        savedGroups.emplace(group);
+    }
+
+    auto dir = path.parent_path();
+    if (!fs::exists(dir))
+    {
+        fs::create_directories(dir);
+    }
+
+    std::ofstream os(path.c_str(), std::ios::binary);
+    cereal::JSONOutputArchive oarchive(os);
+    oarchive(savedGroups);
+}
+
+void Serialize::restoreGroups()
+{
+
+    if (!fs::exists(path))
+    {
+        lg2::info("File does not exist, FILE_PATH = {PATH}", "PATH", path);
+        return;
+    }
+
+    try
+    {
+        std::ifstream is(path.c_str(), std::ios::in | std::ios::binary);
+        cereal::JSONInputArchive iarchive(is);
+        iarchive(savedGroups);
+    }
+    catch (const cereal::Exception& e)
+    {
+        lg2::error("Failed to restore groups, ERROR = {ERROR}", "ERROR", e);
+        fs::remove(path);
+    }
+}
+
+} // namespace led
+} // namespace phosphor
diff --git a/manager/serialize.hpp b/manager/serialize.hpp
new file mode 100644
index 0000000..71de987
--- /dev/null
+++ b/manager/serialize.hpp
@@ -0,0 +1,57 @@
+#pragma once
+
+#include <filesystem>
+#include <fstream>
+#include <set>
+#include <string>
+
+namespace phosphor
+{
+namespace led
+{
+
+namespace fs = std::filesystem;
+
+// the set of names of asserted groups, which contains the D-Bus Object path
+using SavedGroups = std::set<std::string>;
+
+/** @class Serialize
+ *  @brief Store and restore groups of LEDs
+ */
+class Serialize
+{
+  public:
+    explicit Serialize(const fs::path& path) : path(path)
+    {
+        restoreGroups();
+    }
+
+    /** @brief Store asserted group names to SAVED_GROUPS_FILE
+     *
+     *  @param [in] group     - name of the group
+     *  @param [in] asserted  - asserted state, true or false
+     */
+    void storeGroups(const std::string& group, bool asserted);
+
+    /** @brief Is the group in asserted state stored in SAVED_GROUPS_FILE
+     *
+     *  @param [in] objPath - The D-Bus path that hosts LED group
+     *
+     *  @return             - true: exist, false: does not exist
+     */
+    bool getGroupSavedState(const std::string& objPath) const;
+
+  private:
+    /** @brief restore asserted group names from SAVED_GROUPS_FILE
+     */
+    void restoreGroups();
+
+    /** @brief the set of names of asserted groups */
+    SavedGroups savedGroups;
+
+    /** @brief the path of file for storing the names of asserted groups */
+    fs::path path;
+};
+
+} // namespace led
+} // namespace phosphor