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