1 #include "lamptest.hpp"
2 
3 #include <phosphor-logging/lg2.hpp>
4 
5 #include <algorithm>
6 
7 namespace phosphor
8 {
9 namespace led
10 {
11 
12 using Json = nlohmann::json;
13 
14 bool LampTest::processLEDUpdates(const ActionSet& ledsAssert,
15                                  const ActionSet& ledsDeAssert)
16 {
17     // If the physical LED status is updated during the lamp test, it should be
18     // saved to Queue, and the queue will be processed after the lamp test is
19     // stopped.
20     if (isLampTestRunning)
21     {
22         // Physical LEDs will be updated during lamp test
23         for (const auto& it : ledsDeAssert)
24         {
25             std::string path = std::string(PHY_LED_PATH) + it.name;
26             auto iter = std::find_if(
27                 forceUpdateLEDs.begin(), forceUpdateLEDs.end(),
28                 [&path](const auto& name) { return name == path; });
29 
30             if (iter != forceUpdateLEDs.end())
31             {
32                 manager.drivePhysicalLED(path, Layout::Action::Off, it.dutyOn,
33                                          it.period);
34             }
35         }
36 
37         for (const auto& it : ledsAssert)
38         {
39             std::string path = std::string(PHY_LED_PATH) + it.name;
40             auto iter = std::find_if(
41                 forceUpdateLEDs.begin(), forceUpdateLEDs.end(),
42                 [&path](const auto& name) { return name == path; });
43 
44             if (iter != forceUpdateLEDs.end())
45             {
46                 manager.drivePhysicalLED(path, it.action, it.dutyOn, it.period);
47             }
48         }
49 
50         updatedLEDsDuringLampTest.emplace(
51             std::make_pair(ledsAssert, ledsDeAssert));
52         return true;
53     }
54     return false;
55 }
56 
57 void LampTest::stop()
58 {
59     if (!isLampTestRunning)
60     {
61         return;
62     }
63 
64     timer.setEnabled(false);
65 
66     // Stop host lamp test
67     doHostLampTest(false);
68 
69     // Set all the Physical action to Off
70     for (const auto& path : physicalLEDPaths)
71     {
72         auto iter =
73             std::find_if(skipUpdateLEDs.begin(), skipUpdateLEDs.end(),
74                          [&path](const auto& skip) { return skip == path; });
75 
76         if (iter != skipUpdateLEDs.end())
77         {
78             // Skip update physical path
79             continue;
80         }
81 
82         manager.drivePhysicalLED(path, Layout::Action::Off, 0, 0);
83     }
84 
85     isLampTestRunning = false;
86     restorePhysicalLedStates();
87 }
88 
89 Layout::Action LampTest::getActionFromString(const std::string& str)
90 {
91     Layout::Action action = Layout::Action::Off;
92 
93     if (str == "xyz.openbmc_project.Led.Physical.Action.On")
94     {
95         action = Layout::Action::On;
96     }
97     else if (str == "xyz.openbmc_project.Led.Physical.Action.Blink")
98     {
99         action = Layout::Action::Blink;
100     }
101 
102     return action;
103 }
104 
105 void LampTest::storePhysicalLEDsStates()
106 {
107     physicalLEDStatesPriorToLampTest.clear();
108 
109     for (const auto& path : physicalLEDPaths)
110     {
111         auto iter = std::find_if(
112             skipUpdateLEDs.begin(), skipUpdateLEDs.end(),
113             [&path](const auto& skipLed) { return skipLed == path; });
114 
115         if (iter != skipUpdateLEDs.end())
116         {
117             // Physical LEDs will be skipped
118             continue;
119         }
120 
121         // Reverse intercept path, Get the name of each member of physical led
122         // e.g: path = /xyz/openbmc_project/led/physical/front_fan
123         //      name = front_fan
124         sdbusplus::message::object_path object_path(path);
125         auto name = object_path.filename();
126         if (name.empty())
127         {
128             lg2::error(
129                 "Failed to get the name of member of physical LED path, PATH = {PATH}, NAME = {NAME}",
130                 "PATH", path, "NAME", name);
131             continue;
132         }
133 
134         std::string state{};
135         uint16_t period{};
136         uint8_t dutyOn{};
137         try
138         {
139             auto properties = dBusHandler.getAllProperties(path, PHY_LED_IFACE);
140 
141             state = std::get<std::string>(properties["State"]);
142             period = std::get<uint16_t>(properties["Period"]);
143             dutyOn = std::get<uint8_t>(properties["DutyOn"]);
144         }
145         catch (const sdbusplus::exception::exception& e)
146         {
147             lg2::error(
148                 "Failed to get All properties, ERROR = {ERROR}, PATH = {PATH}",
149                 "ERROR", e, "PATH", path);
150             continue;
151         }
152 
153         phosphor::led::Layout::Action action = getActionFromString(state);
154         if (action != phosphor::led::Layout::Action::Off)
155         {
156             phosphor::led::Layout::LedAction ledAction{
157                 name, action, dutyOn, period,
158                 phosphor::led::Layout::Action::On};
159             physicalLEDStatesPriorToLampTest.emplace(ledAction);
160         }
161     }
162 }
163 
164 void LampTest::start()
165 {
166     if (isLampTestRunning)
167     {
168         // reset the timer and then return
169         timer.restart(std::chrono::seconds(LAMP_TEST_TIMEOUT_IN_SECS));
170         return;
171     }
172 
173     // Get paths of all the Physical LED objects
174     physicalLEDPaths = dBusHandler.getSubTreePaths(PHY_LED_PATH, PHY_LED_IFACE);
175 
176     // Get physical LEDs states before lamp test
177     storePhysicalLEDsStates();
178 
179     // restart lamp test, it contains initiate or reset the timer.
180     timer.restart(std::chrono::seconds(LAMP_TEST_TIMEOUT_IN_SECS));
181     isLampTestRunning = true;
182 
183     // Notify PHYP to start the lamp test
184     doHostLampTest(true);
185 
186     // Set all the Physical action to On for lamp test
187     for (const auto& path : physicalLEDPaths)
188     {
189         auto iter =
190             std::find_if(skipUpdateLEDs.begin(), skipUpdateLEDs.end(),
191                          [&path](const auto& skip) { return skip == path; });
192 
193         if (iter != skipUpdateLEDs.end())
194         {
195             // Skip update physical path
196             continue;
197         }
198 
199         manager.drivePhysicalLED(path, Layout::Action::On, 0, 0);
200     }
201 }
202 
203 void LampTest::timeOutHandler()
204 {
205     // set the Asserted property of lamp test to false
206     if (!groupObj)
207     {
208         lg2::error("the Group object is nullptr");
209         throw std::runtime_error("the Group object is nullptr");
210     }
211 
212     groupObj->asserted(false);
213 }
214 
215 void LampTest::requestHandler(Group* group, bool value)
216 {
217     if (groupObj == NULL)
218     {
219         groupObj = std::move(group);
220     }
221 
222     if (value)
223     {
224         start();
225     }
226     else
227     {
228         stop();
229     }
230 }
231 
232 void LampTest::restorePhysicalLedStates()
233 {
234     // restore physical LEDs states before lamp test
235     ActionSet ledsDeAssert{};
236     manager.driveLEDs(physicalLEDStatesPriorToLampTest, ledsDeAssert);
237     physicalLEDStatesPriorToLampTest.clear();
238 
239     // restore physical LEDs states during lamp test
240     while (!updatedLEDsDuringLampTest.empty())
241     {
242         auto& [ledsAssert, ledsDeAssert] = updatedLEDsDuringLampTest.front();
243         manager.driveLEDs(ledsAssert, ledsDeAssert);
244         updatedLEDsDuringLampTest.pop();
245     }
246 }
247 
248 void LampTest::doHostLampTest(bool value)
249 {
250     try
251     {
252         PropertyValue assertedValue{value};
253         dBusHandler.setProperty(HOST_LAMP_TEST_OBJECT,
254                                 "xyz.openbmc_project.Led.Group", "Asserted",
255                                 assertedValue);
256     }
257     catch (const sdbusplus::exception::exception& e)
258     {
259         lg2::error(
260             "Failed to set Asserted property, ERROR = {ERROR}, PATH = {PATH}",
261             "ERROR", e, "PATH", std::string(HOST_LAMP_TEST_OBJECT));
262     }
263 }
264 
265 void LampTest::getPhysicalLEDNamesFromJson(const fs::path& path)
266 {
267     if (!fs::exists(path) || fs::is_empty(path))
268     {
269         lg2::info("The file does not exist or is empty, FILE_PATH = {PATH}",
270                   "PATH", path);
271         return;
272     }
273 
274     try
275     {
276         std::ifstream jsonFile(path);
277         auto json = Json::parse(jsonFile);
278 
279         // define the default JSON as empty
280         const std::vector<std::string> empty{};
281         auto forceLEDs = json.value("forceLEDs", empty);
282         std::ranges::transform(forceLEDs, std::back_inserter(forceUpdateLEDs),
283                                [](const auto& i) { return PHY_LED_PATH + i; });
284 
285         auto skipLEDs = json.value("skipLEDs", empty);
286         std::ranges::transform(skipLEDs, std::back_inserter(skipUpdateLEDs),
287                                [](const auto& i) { return PHY_LED_PATH + i; });
288     }
289     catch (const std::exception& e)
290     {
291         lg2::error(
292             "Failed to parse config file, ERROR = {ERROR}, FILE_PATH = {PATH}",
293             "ERROR", e, "PATH", path);
294     }
295     return;
296 }
297 
298 } // namespace led
299 } // namespace phosphor
300