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(phyLedPath) + 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(phyLedPath) + 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, phyLedIntf);
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_t& 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     try
175     {
176         physicalLEDPaths = dBusHandler.getSubTreePaths(phyLedPath, phyLedIntf);
177     }
178     catch (const sdbusplus::exception_t& e)
179     {
180         lg2::error(
181             "Failed to call the SubTreePaths method: {ERROR}, ledPath: {PATH}, ledInterface: {INTERFACE}",
182             "ERROR", e, "PATH", phyLedPath, "INTERFACE", phyLedIntf);
183         return;
184     }
185 
186     // Get physical LEDs states before lamp test
187     storePhysicalLEDsStates();
188 
189     // restart lamp test, it contains initiate or reset the timer.
190     timer.restart(std::chrono::seconds(LAMP_TEST_TIMEOUT_IN_SECS));
191     isLampTestRunning = true;
192 
193     // Notify PHYP to start the lamp test
194     doHostLampTest(true);
195 
196     // Set all the Physical action to On for lamp test
197     for (const auto& path : physicalLEDPaths)
198     {
199         auto iter =
200             std::find_if(skipUpdateLEDs.begin(), skipUpdateLEDs.end(),
201                          [&path](const auto& skip) { return skip == path; });
202 
203         if (iter != skipUpdateLEDs.end())
204         {
205             // Skip update physical path
206             continue;
207         }
208 
209         manager.drivePhysicalLED(path, Layout::Action::On, 0, 0);
210     }
211 }
212 
213 void LampTest::timeOutHandler()
214 {
215     // set the Asserted property of lamp test to false
216     if (!groupObj)
217     {
218         lg2::error("the Group object is nullptr");
219         throw std::runtime_error("the Group object is nullptr");
220     }
221 
222     groupObj->asserted(false);
223 }
224 
225 void LampTest::requestHandler(Group* group, bool value)
226 {
227     if (groupObj == NULL)
228     {
229         groupObj = std::move(group);
230     }
231 
232     if (value)
233     {
234         start();
235     }
236     else
237     {
238         stop();
239     }
240 }
241 
242 void LampTest::restorePhysicalLedStates()
243 {
244     // restore physical LEDs states before lamp test
245     ActionSet ledsDeAssert{};
246     manager.driveLEDs(physicalLEDStatesPriorToLampTest, ledsDeAssert);
247     physicalLEDStatesPriorToLampTest.clear();
248 
249     // restore physical LEDs states during lamp test
250     while (!updatedLEDsDuringLampTest.empty())
251     {
252         auto& [ledsAssert, ledsDeAssert] = updatedLEDsDuringLampTest.front();
253         manager.driveLEDs(ledsAssert, ledsDeAssert);
254         updatedLEDsDuringLampTest.pop();
255     }
256 }
257 
258 void LampTest::doHostLampTest(bool value)
259 {
260     try
261     {
262         PropertyValue assertedValue{value};
263         dBusHandler.setProperty(HOST_LAMP_TEST_OBJECT,
264                                 "xyz.openbmc_project.Led.Group", "Asserted",
265                                 assertedValue);
266     }
267     catch (const sdbusplus::exception_t& e)
268     {
269         lg2::error(
270             "Failed to set Asserted property, ERROR = {ERROR}, PATH = {PATH}",
271             "ERROR", e, "PATH", std::string(HOST_LAMP_TEST_OBJECT));
272     }
273 }
274 
275 void LampTest::getPhysicalLEDNamesFromJson(const fs::path& path)
276 {
277     if (!fs::exists(path) || fs::is_empty(path))
278     {
279         lg2::info("The file does not exist or is empty, FILE_PATH = {PATH}",
280                   "PATH", path);
281         return;
282     }
283 
284     try
285     {
286         std::ifstream jsonFile(path);
287         auto json = Json::parse(jsonFile);
288 
289         // define the default JSON as empty
290         const std::vector<std::string> empty{};
291         auto forceLEDs = json.value("forceLEDs", empty);
292         std::ranges::transform(forceLEDs, std::back_inserter(forceUpdateLEDs),
293                                [](const auto& i) { return phyLedPath + i; });
294 
295         auto skipLEDs = json.value("skipLEDs", empty);
296         std::ranges::transform(skipLEDs, std::back_inserter(skipUpdateLEDs),
297                                [](const auto& i) { return phyLedPath + i; });
298     }
299     catch (const std::exception& e)
300     {
301         lg2::error(
302             "Failed to parse config file, ERROR = {ERROR}, FILE_PATH = {PATH}",
303             "ERROR", e, "PATH", path);
304     }
305     return;
306 }
307 
308 } // namespace led
309 } // namespace phosphor
310