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(skipUpdateLEDs.begin(), skipUpdateLEDs.end(),
112                                  [&path](const auto& skipLed) {
113             return skipLed == path;
114         });
115 
116         if (iter != skipUpdateLEDs.end())
117         {
118             // Physical LEDs will be skipped
119             continue;
120         }
121 
122         // Reverse intercept path, Get the name of each member of physical led
123         // e.g: path = /xyz/openbmc_project/led/physical/front_fan
124         //      name = front_fan
125         sdbusplus::message::object_path object_path(path);
126         auto name = object_path.filename();
127         if (name.empty())
128         {
129             lg2::error(
130                 "Failed to get the name of member of physical LED path, PATH = {PATH}, NAME = {NAME}",
131                 "PATH", path, "NAME", name);
132             continue;
133         }
134 
135         std::string state{};
136         uint16_t period{};
137         uint8_t dutyOn{};
138         try
139         {
140             auto properties = dBusHandler.getAllProperties(path, PHY_LED_IFACE);
141 
142             state = std::get<std::string>(properties["State"]);
143             period = std::get<uint16_t>(properties["Period"]);
144             dutyOn = std::get<uint8_t>(properties["DutyOn"]);
145         }
146         catch (const sdbusplus::exception_t& e)
147         {
148             lg2::error(
149                 "Failed to get All properties, ERROR = {ERROR}, PATH = {PATH}",
150                 "ERROR", e, "PATH", path);
151             continue;
152         }
153 
154         phosphor::led::Layout::Action action = getActionFromString(state);
155         if (action != phosphor::led::Layout::Action::Off)
156         {
157             phosphor::led::Layout::LedAction ledAction{
158                 name, action, dutyOn, period,
159                 phosphor::led::Layout::Action::On};
160             physicalLEDStatesPriorToLampTest.emplace(ledAction);
161         }
162     }
163 }
164 
165 void LampTest::start()
166 {
167     if (isLampTestRunning)
168     {
169         // reset the timer and then return
170         timer.restart(std::chrono::seconds(LAMP_TEST_TIMEOUT_IN_SECS));
171         return;
172     }
173 
174     // Get paths of all the Physical LED objects
175     physicalLEDPaths = dBusHandler.getSubTreePaths(PHY_LED_PATH, PHY_LED_IFACE);
176 
177     // Get physical LEDs states before lamp test
178     storePhysicalLEDsStates();
179 
180     // restart lamp test, it contains initiate or reset the timer.
181     timer.restart(std::chrono::seconds(LAMP_TEST_TIMEOUT_IN_SECS));
182     isLampTestRunning = true;
183 
184     // Notify PHYP to start the lamp test
185     doHostLampTest(true);
186 
187     // Set all the Physical action to On for lamp test
188     for (const auto& path : physicalLEDPaths)
189     {
190         auto iter =
191             std::find_if(skipUpdateLEDs.begin(), skipUpdateLEDs.end(),
192                          [&path](const auto& skip) { return skip == path; });
193 
194         if (iter != skipUpdateLEDs.end())
195         {
196             // Skip update physical path
197             continue;
198         }
199 
200         manager.drivePhysicalLED(path, Layout::Action::On, 0, 0);
201     }
202 }
203 
204 void LampTest::timeOutHandler()
205 {
206     // set the Asserted property of lamp test to false
207     if (!groupObj)
208     {
209         lg2::error("the Group object is nullptr");
210         throw std::runtime_error("the Group object is nullptr");
211     }
212 
213     groupObj->asserted(false);
214 }
215 
216 void LampTest::requestHandler(Group* group, bool value)
217 {
218     if (groupObj == NULL)
219     {
220         groupObj = std::move(group);
221     }
222 
223     if (value)
224     {
225         start();
226     }
227     else
228     {
229         stop();
230     }
231 }
232 
233 void LampTest::restorePhysicalLedStates()
234 {
235     // restore physical LEDs states before lamp test
236     ActionSet ledsDeAssert{};
237     manager.driveLEDs(physicalLEDStatesPriorToLampTest, ledsDeAssert);
238     physicalLEDStatesPriorToLampTest.clear();
239 
240     // restore physical LEDs states during lamp test
241     while (!updatedLEDsDuringLampTest.empty())
242     {
243         auto& [ledsAssert, ledsDeAssert] = updatedLEDsDuringLampTest.front();
244         manager.driveLEDs(ledsAssert, ledsDeAssert);
245         updatedLEDsDuringLampTest.pop();
246     }
247 }
248 
249 void LampTest::doHostLampTest(bool value)
250 {
251     try
252     {
253         PropertyValue assertedValue{value};
254         dBusHandler.setProperty(HOST_LAMP_TEST_OBJECT,
255                                 "xyz.openbmc_project.Led.Group", "Asserted",
256                                 assertedValue);
257     }
258     catch (const sdbusplus::exception_t& e)
259     {
260         lg2::error(
261             "Failed to set Asserted property, ERROR = {ERROR}, PATH = {PATH}",
262             "ERROR", e, "PATH", std::string(HOST_LAMP_TEST_OBJECT));
263     }
264 }
265 
266 void LampTest::getPhysicalLEDNamesFromJson(const fs::path& path)
267 {
268     if (!fs::exists(path) || fs::is_empty(path))
269     {
270         lg2::info("The file does not exist or is empty, FILE_PATH = {PATH}",
271                   "PATH", path);
272         return;
273     }
274 
275     try
276     {
277         std::ifstream jsonFile(path);
278         auto json = Json::parse(jsonFile);
279 
280         // define the default JSON as empty
281         const std::vector<std::string> empty{};
282         auto forceLEDs = json.value("forceLEDs", empty);
283         std::ranges::transform(forceLEDs, std::back_inserter(forceUpdateLEDs),
284                                [](const auto& i) { return PHY_LED_PATH + i; });
285 
286         auto skipLEDs = json.value("skipLEDs", empty);
287         std::ranges::transform(skipLEDs, std::back_inserter(skipUpdateLEDs),
288                                [](const auto& i) { return PHY_LED_PATH + i; });
289     }
290     catch (const std::exception& e)
291     {
292         lg2::error(
293             "Failed to parse config file, ERROR = {ERROR}, FILE_PATH = {PATH}",
294             "ERROR", e, "PATH", path);
295     }
296     return;
297 }
298 
299 } // namespace led
300 } // namespace phosphor
301