xref: /openbmc/bmcweb/features/redfish/lib/sensors.hpp (revision 42cbe53889b5f2d358d1174245df51a23efcb3f8)
1 /*
2 // Copyright (c) 2018 Intel Corporation
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 //      http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 */
16 #pragma once
17 
18 #include "node.hpp"
19 
20 #include <boost/algorithm/string/predicate.hpp>
21 #include <boost/algorithm/string/split.hpp>
22 #include <boost/container/flat_map.hpp>
23 #include <boost/range/algorithm/replace_copy_if.hpp>
24 #include <cmath>
25 #include <dbus_singleton.hpp>
26 #include <utils/json_utils.hpp>
27 #include <variant>
28 
29 namespace redfish
30 {
31 
32 using GetSubTreeType = std::vector<
33     std::pair<std::string,
34               std::vector<std::pair<std::string, std::vector<std::string>>>>>;
35 
36 using SensorVariant =
37     std::variant<int64_t, double, uint32_t, bool, std::string>;
38 
39 using ManagedObjectsVectorType = std::vector<std::pair<
40     sdbusplus::message::object_path,
41     boost::container::flat_map<
42         std::string, boost::container::flat_map<std::string, SensorVariant>>>>;
43 
44 /**
45  * SensorsAsyncResp
46  * Gathers data needed for response processing after async calls are done
47  */
48 class SensorsAsyncResp
49 {
50   public:
51     SensorsAsyncResp(crow::Response& response, const std::string& chassisIdIn,
52                      const std::vector<const char*> typesIn,
53                      const std::string& subNode) :
54         res(response),
55         chassisId(chassisIdIn), types(typesIn), chassisSubNode(subNode)
56     {
57     }
58 
59     ~SensorsAsyncResp()
60     {
61         if (res.result() == boost::beast::http::status::internal_server_error)
62         {
63             // Reset the json object to clear out any data that made it in
64             // before the error happened todo(ed) handle error condition with
65             // proper code
66             res.jsonValue = nlohmann::json::object();
67         }
68         res.end();
69     }
70 
71     crow::Response& res;
72     std::string chassisId{};
73     const std::vector<const char*> types;
74     std::string chassisSubNode{};
75 };
76 
77 /**
78  * Possible states for physical inventory leds
79  */
80 enum class LedState
81 {
82     OFF,
83     ON,
84     BLINK,
85     UNKNOWN
86 };
87 
88 /**
89  * D-Bus inventory item associated with one or more sensors.
90  */
91 class InventoryItem
92 {
93   public:
94     InventoryItem(const std::string& objPath) :
95         objectPath(objPath), name(), isPresent(true), isFunctional(true),
96         isPowerSupply(false), powerSupplyEfficiencyPercent(-1), manufacturer(),
97         model(), partNumber(), serialNumber(), sensors(), ledObjectPath(""),
98         ledState(LedState::UNKNOWN)
99     {
100         // Set inventory item name to last node of object path
101         auto pos = objectPath.rfind('/');
102         if ((pos != std::string::npos) && ((pos + 1) < objectPath.size()))
103         {
104             name = objectPath.substr(pos + 1);
105         }
106     }
107 
108     std::string objectPath;
109     std::string name;
110     bool isPresent;
111     bool isFunctional;
112     bool isPowerSupply;
113     int powerSupplyEfficiencyPercent;
114     std::string manufacturer;
115     std::string model;
116     std::string partNumber;
117     std::string serialNumber;
118     std::set<std::string> sensors;
119     std::string ledObjectPath;
120     LedState ledState;
121 };
122 
123 /**
124  * @brief Get objects with connection necessary for sensors
125  * @param SensorsAsyncResp Pointer to object holding response data
126  * @param sensorNames Sensors retrieved from chassis
127  * @param callback Callback for processing gathered connections
128  */
129 template <typename Callback>
130 void getObjectsWithConnection(
131     std::shared_ptr<SensorsAsyncResp> SensorsAsyncResp,
132     const std::shared_ptr<boost::container::flat_set<std::string>> sensorNames,
133     Callback&& callback)
134 {
135     BMCWEB_LOG_DEBUG << "getObjectsWithConnection enter";
136     const std::string path = "/xyz/openbmc_project/sensors";
137     const std::array<std::string, 1> interfaces = {
138         "xyz.openbmc_project.Sensor.Value"};
139 
140     // Response handler for parsing objects subtree
141     auto respHandler = [callback{std::move(callback)}, SensorsAsyncResp,
142                         sensorNames](const boost::system::error_code ec,
143                                      const GetSubTreeType& subtree) {
144         BMCWEB_LOG_DEBUG << "getObjectsWithConnection resp_handler enter";
145         if (ec)
146         {
147             messages::internalError(SensorsAsyncResp->res);
148             BMCWEB_LOG_ERROR
149                 << "getObjectsWithConnection resp_handler: Dbus error " << ec;
150             return;
151         }
152 
153         BMCWEB_LOG_DEBUG << "Found " << subtree.size() << " subtrees";
154 
155         // Make unique list of connections only for requested sensor types and
156         // found in the chassis
157         boost::container::flat_set<std::string> connections;
158         std::set<std::pair<std::string, std::string>> objectsWithConnection;
159         // Intrinsic to avoid malloc.  Most systems will have < 8 sensor
160         // producers
161         connections.reserve(8);
162 
163         BMCWEB_LOG_DEBUG << "sensorNames list count: " << sensorNames->size();
164         for (const std::string& tsensor : *sensorNames)
165         {
166             BMCWEB_LOG_DEBUG << "Sensor to find: " << tsensor;
167         }
168 
169         for (const std::pair<
170                  std::string,
171                  std::vector<std::pair<std::string, std::vector<std::string>>>>&
172                  object : subtree)
173         {
174             if (sensorNames->find(object.first) != sensorNames->end())
175             {
176                 for (const std::pair<std::string, std::vector<std::string>>&
177                          objData : object.second)
178                 {
179                     BMCWEB_LOG_DEBUG << "Adding connection: " << objData.first;
180                     connections.insert(objData.first);
181                     objectsWithConnection.insert(
182                         std::make_pair(object.first, objData.first));
183                 }
184             }
185         }
186         BMCWEB_LOG_DEBUG << "Found " << connections.size() << " connections";
187         callback(std::move(connections), std::move(objectsWithConnection));
188         BMCWEB_LOG_DEBUG << "getObjectsWithConnection resp_handler exit";
189     };
190     // Make call to ObjectMapper to find all sensors objects
191     crow::connections::systemBus->async_method_call(
192         std::move(respHandler), "xyz.openbmc_project.ObjectMapper",
193         "/xyz/openbmc_project/object_mapper",
194         "xyz.openbmc_project.ObjectMapper", "GetSubTree", path, 2, interfaces);
195     BMCWEB_LOG_DEBUG << "getObjectsWithConnection exit";
196 }
197 
198 /**
199  * @brief Create connections necessary for sensors
200  * @param SensorsAsyncResp Pointer to object holding response data
201  * @param sensorNames Sensors retrieved from chassis
202  * @param callback Callback for processing gathered connections
203  */
204 template <typename Callback>
205 void getConnections(
206     std::shared_ptr<SensorsAsyncResp> SensorsAsyncResp,
207     const std::shared_ptr<boost::container::flat_set<std::string>> sensorNames,
208     Callback&& callback)
209 {
210     auto objectsWithConnectionCb =
211         [callback](const boost::container::flat_set<std::string>& connections,
212                    const std::set<std::pair<std::string, std::string>>&
213                        objectsWithConnection) {
214             callback(std::move(connections));
215         };
216     getObjectsWithConnection(SensorsAsyncResp, sensorNames,
217                              std::move(objectsWithConnectionCb));
218 }
219 
220 /**
221  * @brief Shrinks the list of sensors for processing
222  * @param SensorsAysncResp  The class holding the Redfish response
223  * @param allSensors  A list of all the sensors associated to the
224  * chassis element (i.e. baseboard, front panel, etc...)
225  * @param activeSensors A list that is a reduction of the incoming
226  * allSensors list.  Eliminate Thermal sensors when a Power request is
227  * made, and eliminate Power sensors when a Thermal request is made.
228  */
229 void reduceSensorList(
230     std::shared_ptr<SensorsAsyncResp> SensorsAsyncResp,
231     const std::vector<std::string>* allSensors,
232     std::shared_ptr<boost::container::flat_set<std::string>> activeSensors)
233 {
234     if (SensorsAsyncResp == nullptr)
235     {
236         return;
237     }
238     if ((allSensors == nullptr) || (activeSensors == nullptr))
239     {
240         messages::resourceNotFound(
241             SensorsAsyncResp->res, SensorsAsyncResp->chassisSubNode,
242             SensorsAsyncResp->chassisSubNode == "Thermal" ? "Temperatures"
243                                                           : "Voltages");
244 
245         return;
246     }
247     if (allSensors->empty())
248     {
249         // Nothing to do, the activeSensors object is also empty
250         return;
251     }
252 
253     for (const char* type : SensorsAsyncResp->types)
254     {
255         for (const std::string& sensor : *allSensors)
256         {
257             if (boost::starts_with(sensor, type))
258             {
259                 activeSensors->emplace(sensor);
260             }
261         }
262     }
263 }
264 
265 /**
266  * @brief Retrieves valid chassis path
267  * @param asyncResp   Pointer to object holding response data
268  * @param callback  Callback for next step to get valid chassis path
269  */
270 template <typename Callback>
271 void getValidChassisPath(std::shared_ptr<SensorsAsyncResp> asyncResp,
272                          Callback&& callback)
273 {
274     BMCWEB_LOG_DEBUG << "checkChassisId enter";
275     const std::array<const char*, 2> interfaces = {
276         "xyz.openbmc_project.Inventory.Item.Board",
277         "xyz.openbmc_project.Inventory.Item.Chassis"};
278 
279     auto respHandler =
280         [callback{std::move(callback)},
281          asyncResp](const boost::system::error_code ec,
282                     const std::vector<std::string>& chassisPaths) mutable {
283             BMCWEB_LOG_DEBUG << "getValidChassisPath respHandler enter";
284             if (ec)
285             {
286                 BMCWEB_LOG_ERROR
287                     << "getValidChassisPath respHandler DBUS error: " << ec;
288                 messages::internalError(asyncResp->res);
289                 return;
290             }
291 
292             std::optional<std::string> chassisPath;
293             std::string chassisName;
294             for (const std::string& chassis : chassisPaths)
295             {
296                 std::size_t lastPos = chassis.rfind("/");
297                 if (lastPos == std::string::npos)
298                 {
299                     BMCWEB_LOG_ERROR << "Failed to find '/' in " << chassis;
300                     continue;
301                 }
302                 chassisName = chassis.substr(lastPos + 1);
303                 if (chassisName == asyncResp->chassisId)
304                 {
305                     chassisPath = chassis;
306                     break;
307                 }
308             }
309             callback(chassisPath);
310         };
311 
312     // Get the Chassis Collection
313     crow::connections::systemBus->async_method_call(
314         respHandler, "xyz.openbmc_project.ObjectMapper",
315         "/xyz/openbmc_project/object_mapper",
316         "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths",
317         "/xyz/openbmc_project/inventory", 0, interfaces);
318     BMCWEB_LOG_DEBUG << "checkChassisId exit";
319 }
320 
321 /**
322  * @brief Retrieves requested chassis sensors and redundancy data from DBus .
323  * @param SensorsAsyncResp   Pointer to object holding response data
324  * @param callback  Callback for next step in gathered sensor processing
325  */
326 template <typename Callback>
327 void getChassis(std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp,
328                 Callback&& callback)
329 {
330     BMCWEB_LOG_DEBUG << "getChassis enter";
331     const std::array<const char*, 2> interfaces = {
332         "xyz.openbmc_project.Inventory.Item.Board",
333         "xyz.openbmc_project.Inventory.Item.Chassis"};
334     auto respHandler = [callback{std::move(callback)}, sensorsAsyncResp](
335                            const boost::system::error_code ec,
336                            const std::vector<std::string>& chassisPaths) {
337         BMCWEB_LOG_DEBUG << "getChassis respHandler enter";
338         if (ec)
339         {
340             BMCWEB_LOG_ERROR << "getChassis respHandler DBUS error: " << ec;
341             messages::internalError(sensorsAsyncResp->res);
342             return;
343         }
344 
345         const std::string* chassisPath = nullptr;
346         std::string chassisName;
347         for (const std::string& chassis : chassisPaths)
348         {
349             std::size_t lastPos = chassis.rfind("/");
350             if (lastPos == std::string::npos)
351             {
352                 BMCWEB_LOG_ERROR << "Failed to find '/' in " << chassis;
353                 continue;
354             }
355             chassisName = chassis.substr(lastPos + 1);
356             if (chassisName == sensorsAsyncResp->chassisId)
357             {
358                 chassisPath = &chassis;
359                 break;
360             }
361         }
362         if (chassisPath == nullptr)
363         {
364             messages::resourceNotFound(sensorsAsyncResp->res, "Chassis",
365                                        sensorsAsyncResp->chassisId);
366             return;
367         }
368 
369         const std::string& chassisSubNode = sensorsAsyncResp->chassisSubNode;
370         if (chassisSubNode == "Power")
371         {
372             sensorsAsyncResp->res.jsonValue["@odata.type"] =
373                 "#Power.v1_5_2.Power";
374         }
375         else if (chassisSubNode == "Thermal")
376         {
377             sensorsAsyncResp->res.jsonValue["@odata.type"] =
378                 "#Thermal.v1_4_0.Thermal";
379             sensorsAsyncResp->res.jsonValue["Fans"] = nlohmann::json::array();
380             sensorsAsyncResp->res.jsonValue["Temperatures"] =
381                 nlohmann::json::array();
382         }
383         else if (chassisSubNode == "Sensors")
384         {
385             sensorsAsyncResp->res.jsonValue["@odata.type"] =
386                 "#SensorCollection.SensorCollection";
387             sensorsAsyncResp->res.jsonValue["@odata.context"] =
388                 "/redfish/v1/$metadata#SensorCollection.SensorCollection";
389             sensorsAsyncResp->res.jsonValue["Description"] =
390                 "Collection of Sensors for this Chassis";
391             sensorsAsyncResp->res.jsonValue["Members"] =
392                 nlohmann::json::array();
393             sensorsAsyncResp->res.jsonValue["Members@odata.count"] = 0;
394         }
395 
396         if (chassisSubNode != "Sensors")
397         {
398             sensorsAsyncResp->res.jsonValue["Id"] = chassisSubNode;
399             sensorsAsyncResp->res.jsonValue["@odata.context"] =
400                 "/redfish/v1/$metadata#" + chassisSubNode + "." +
401                 chassisSubNode;
402         }
403 
404         sensorsAsyncResp->res.jsonValue["@odata.id"] =
405             "/redfish/v1/Chassis/" + sensorsAsyncResp->chassisId + "/" +
406             chassisSubNode;
407         sensorsAsyncResp->res.jsonValue["Name"] = chassisSubNode;
408 
409         // Get the list of all sensors for this Chassis element
410         std::string sensorPath = *chassisPath + "/all_sensors";
411         crow::connections::systemBus->async_method_call(
412             [sensorsAsyncResp, callback{std::move(callback)}](
413                 const boost::system::error_code& e,
414                 const std::variant<std::vector<std::string>>&
415                     variantEndpoints) {
416                 if (e)
417                 {
418                     if (e.value() != EBADR)
419                     {
420                         messages::internalError(sensorsAsyncResp->res);
421                         return;
422                     }
423                 }
424                 const std::vector<std::string>* nodeSensorList =
425                     std::get_if<std::vector<std::string>>(&(variantEndpoints));
426                 if (nodeSensorList == nullptr)
427                 {
428                     messages::resourceNotFound(
429                         sensorsAsyncResp->res, sensorsAsyncResp->chassisSubNode,
430                         sensorsAsyncResp->chassisSubNode == "Thermal"
431                             ? "Temperatures"
432                             : sensorsAsyncResp->chassisSubNode == "Power"
433                                   ? "Voltages"
434                                   : "Sensors");
435                     return;
436                 }
437                 const std::shared_ptr<boost::container::flat_set<std::string>>
438                     culledSensorList = std::make_shared<
439                         boost::container::flat_set<std::string>>();
440                 reduceSensorList(sensorsAsyncResp, nodeSensorList,
441                                  culledSensorList);
442                 callback(culledSensorList);
443             },
444             "xyz.openbmc_project.ObjectMapper", sensorPath,
445             "org.freedesktop.DBus.Properties", "Get",
446             "xyz.openbmc_project.Association", "endpoints");
447     };
448 
449     // Get the Chassis Collection
450     crow::connections::systemBus->async_method_call(
451         respHandler, "xyz.openbmc_project.ObjectMapper",
452         "/xyz/openbmc_project/object_mapper",
453         "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths",
454         "/xyz/openbmc_project/inventory", 0, interfaces);
455     BMCWEB_LOG_DEBUG << "getChassis exit";
456 }
457 
458 /**
459  * @brief Finds all DBus object paths that implement ObjectManager.
460  *
461  * Creates a mapping from the associated connection name to the object path.
462  *
463  * Finds the object paths asynchronously.  Invokes callback when information has
464  * been obtained.
465  *
466  * The callback must have the following signature:
467  *   @code
468  *   callback(std::shared_ptr<boost::container::flat_map<std::string,
469  *                std::string>> objectMgrPaths)
470  *   @endcode
471  *
472  * @param sensorsAsyncResp Pointer to object holding response data.
473  * @param callback Callback to invoke when object paths obtained.
474  */
475 template <typename Callback>
476 void getObjectManagerPaths(std::shared_ptr<SensorsAsyncResp> SensorsAsyncResp,
477                            Callback&& callback)
478 {
479     BMCWEB_LOG_DEBUG << "getObjectManagerPaths enter";
480     const std::array<std::string, 1> interfaces = {
481         "org.freedesktop.DBus.ObjectManager"};
482 
483     // Response handler for GetSubTree DBus method
484     auto respHandler = [callback{std::move(callback)},
485                         SensorsAsyncResp](const boost::system::error_code ec,
486                                           const GetSubTreeType& subtree) {
487         BMCWEB_LOG_DEBUG << "getObjectManagerPaths respHandler enter";
488         if (ec)
489         {
490             messages::internalError(SensorsAsyncResp->res);
491             BMCWEB_LOG_ERROR << "getObjectManagerPaths respHandler: DBus error "
492                              << ec;
493             return;
494         }
495 
496         // Loop over returned object paths
497         std::shared_ptr<boost::container::flat_map<std::string, std::string>>
498             objectMgrPaths = std::make_shared<
499                 boost::container::flat_map<std::string, std::string>>();
500         for (const std::pair<
501                  std::string,
502                  std::vector<std::pair<std::string, std::vector<std::string>>>>&
503                  object : subtree)
504         {
505             // Loop over connections for current object path
506             const std::string& objectPath = object.first;
507             for (const std::pair<std::string, std::vector<std::string>>&
508                      objData : object.second)
509             {
510                 // Add mapping from connection to object path
511                 const std::string& connection = objData.first;
512                 (*objectMgrPaths)[connection] = objectPath;
513                 BMCWEB_LOG_DEBUG << "Added mapping " << connection << " -> "
514                                  << objectPath;
515             }
516         }
517         callback(objectMgrPaths);
518         BMCWEB_LOG_DEBUG << "getObjectManagerPaths respHandler exit";
519     };
520 
521     // Query mapper for all DBus object paths that implement ObjectManager
522     crow::connections::systemBus->async_method_call(
523         std::move(respHandler), "xyz.openbmc_project.ObjectMapper",
524         "/xyz/openbmc_project/object_mapper",
525         "xyz.openbmc_project.ObjectMapper", "GetSubTree", "/", 0, interfaces);
526     BMCWEB_LOG_DEBUG << "getObjectManagerPaths exit";
527 }
528 
529 /**
530  * @brief Returns the Redfish State value for the specified inventory item.
531  * @param inventoryItem D-Bus inventory item associated with a sensor.
532  * @return State value for inventory item.
533  */
534 static std::string getState(const InventoryItem* inventoryItem)
535 {
536     if ((inventoryItem != nullptr) && !(inventoryItem->isPresent))
537     {
538         return "Absent";
539     }
540 
541     return "Enabled";
542 }
543 
544 /**
545  * @brief Returns the Redfish Health value for the specified sensor.
546  * @param sensorJson Sensor JSON object.
547  * @param interfacesDict Map of all sensor interfaces.
548  * @param inventoryItem D-Bus inventory item associated with the sensor.  Will
549  * be nullptr if no associated inventory item was found.
550  * @return Health value for sensor.
551  */
552 static std::string getHealth(
553     nlohmann::json& sensorJson,
554     const boost::container::flat_map<
555         std::string, boost::container::flat_map<std::string, SensorVariant>>&
556         interfacesDict,
557     const InventoryItem* inventoryItem)
558 {
559     // Get current health value (if any) in the sensor JSON object.  Some JSON
560     // objects contain multiple sensors (such as PowerSupplies).  We want to set
561     // the overall health to be the most severe of any of the sensors.
562     std::string currentHealth;
563     auto statusIt = sensorJson.find("Status");
564     if (statusIt != sensorJson.end())
565     {
566         auto healthIt = statusIt->find("Health");
567         if (healthIt != statusIt->end())
568         {
569             std::string* health = healthIt->get_ptr<std::string*>();
570             if (health != nullptr)
571             {
572                 currentHealth = *health;
573             }
574         }
575     }
576 
577     // If current health in JSON object is already Critical, return that.  This
578     // should override the sensor health, which might be less severe.
579     if (currentHealth == "Critical")
580     {
581         return "Critical";
582     }
583 
584     // Check if sensor has critical threshold alarm
585     auto criticalThresholdIt =
586         interfacesDict.find("xyz.openbmc_project.Sensor.Threshold.Critical");
587     if (criticalThresholdIt != interfacesDict.end())
588     {
589         auto thresholdHighIt =
590             criticalThresholdIt->second.find("CriticalAlarmHigh");
591         auto thresholdLowIt =
592             criticalThresholdIt->second.find("CriticalAlarmLow");
593         if (thresholdHighIt != criticalThresholdIt->second.end())
594         {
595             const bool* asserted = std::get_if<bool>(&thresholdHighIt->second);
596             if (asserted == nullptr)
597             {
598                 BMCWEB_LOG_ERROR << "Illegal sensor threshold";
599             }
600             else if (*asserted)
601             {
602                 return "Critical";
603             }
604         }
605         if (thresholdLowIt != criticalThresholdIt->second.end())
606         {
607             const bool* asserted = std::get_if<bool>(&thresholdLowIt->second);
608             if (asserted == nullptr)
609             {
610                 BMCWEB_LOG_ERROR << "Illegal sensor threshold";
611             }
612             else if (*asserted)
613             {
614                 return "Critical";
615             }
616         }
617     }
618 
619     // Check if associated inventory item is not functional
620     if ((inventoryItem != nullptr) && !(inventoryItem->isFunctional))
621     {
622         return "Critical";
623     }
624 
625     // If current health in JSON object is already Warning, return that.  This
626     // should override the sensor status, which might be less severe.
627     if (currentHealth == "Warning")
628     {
629         return "Warning";
630     }
631 
632     // Check if sensor has warning threshold alarm
633     auto warningThresholdIt =
634         interfacesDict.find("xyz.openbmc_project.Sensor.Threshold.Warning");
635     if (warningThresholdIt != interfacesDict.end())
636     {
637         auto thresholdHighIt =
638             warningThresholdIt->second.find("WarningAlarmHigh");
639         auto thresholdLowIt =
640             warningThresholdIt->second.find("WarningAlarmLow");
641         if (thresholdHighIt != warningThresholdIt->second.end())
642         {
643             const bool* asserted = std::get_if<bool>(&thresholdHighIt->second);
644             if (asserted == nullptr)
645             {
646                 BMCWEB_LOG_ERROR << "Illegal sensor threshold";
647             }
648             else if (*asserted)
649             {
650                 return "Warning";
651             }
652         }
653         if (thresholdLowIt != warningThresholdIt->second.end())
654         {
655             const bool* asserted = std::get_if<bool>(&thresholdLowIt->second);
656             if (asserted == nullptr)
657             {
658                 BMCWEB_LOG_ERROR << "Illegal sensor threshold";
659             }
660             else if (*asserted)
661             {
662                 return "Warning";
663             }
664         }
665     }
666 
667     return "OK";
668 }
669 
670 static void setLedState(nlohmann::json& sensorJson,
671                         const InventoryItem* inventoryItem)
672 {
673     if (inventoryItem != nullptr && !inventoryItem->ledObjectPath.empty())
674     {
675         switch (inventoryItem->ledState)
676         {
677             case LedState::OFF:
678                 sensorJson["IndicatorLED"] = "Off";
679                 break;
680             case LedState::ON:
681                 sensorJson["IndicatorLED"] = "Lit";
682                 break;
683             case LedState::BLINK:
684                 sensorJson["IndicatorLED"] = "Blinking";
685                 break;
686             default:
687                 break;
688         }
689     }
690 }
691 
692 /**
693  * @brief Builds a json sensor representation of a sensor.
694  * @param sensorName  The name of the sensor to be built
695  * @param sensorType  The type (temperature, fan_tach, etc) of the sensor to
696  * build
697  * @param sensorSchema  The schema (Power, Thermal, etc) being associated with
698  * the sensor to build
699  * @param interfacesDict  A dictionary of the interfaces and properties of said
700  * interfaces to be built from
701  * @param sensor_json  The json object to fill
702  * @param inventoryItem D-Bus inventory item associated with the sensor.  Will
703  * be nullptr if no associated inventory item was found.
704  */
705 void objectInterfacesToJson(
706     const std::string& sensorName, const std::string& sensorType,
707     const std::string& sensorSchema,
708     const boost::container::flat_map<
709         std::string, boost::container::flat_map<std::string, SensorVariant>>&
710         interfacesDict,
711     nlohmann::json& sensor_json, InventoryItem* inventoryItem)
712 {
713     // We need a value interface before we can do anything with it
714     auto valueIt = interfacesDict.find("xyz.openbmc_project.Sensor.Value");
715     if (valueIt == interfacesDict.end())
716     {
717         BMCWEB_LOG_ERROR << "Sensor doesn't have a value interface";
718         return;
719     }
720 
721     // Assume values exist as is (10^0 == 1) if no scale exists
722     int64_t scaleMultiplier = 0;
723 
724     auto scaleIt = valueIt->second.find("Scale");
725     // If a scale exists, pull value as int64, and use the scaling.
726     if (scaleIt != valueIt->second.end())
727     {
728         const int64_t* int64Value = std::get_if<int64_t>(&scaleIt->second);
729         if (int64Value != nullptr)
730         {
731             scaleMultiplier = *int64Value;
732         }
733     }
734 
735     if (sensorSchema == "Sensors")
736     {
737         // For sensors in SensorCollection we set Id instead of MemberId,
738         // including power sensors.
739         sensor_json["Id"] = sensorName;
740         sensor_json["Name"] = boost::replace_all_copy(sensorName, "_", " ");
741     }
742     else if (sensorType != "power")
743     {
744         // Set MemberId and Name for non-power sensors.  For PowerSupplies and
745         // PowerControl, those properties have more general values because
746         // multiple sensors can be stored in the same JSON object.
747         sensor_json["MemberId"] = sensorName;
748         sensor_json["Name"] = boost::replace_all_copy(sensorName, "_", " ");
749     }
750 
751     sensor_json["Status"]["State"] = getState(inventoryItem);
752     sensor_json["Status"]["Health"] =
753         getHealth(sensor_json, interfacesDict, inventoryItem);
754 
755     // Parameter to set to override the type we get from dbus, and force it to
756     // int, regardless of what is available.  This is used for schemas like fan,
757     // that require integers, not floats.
758     bool forceToInt = false;
759 
760     nlohmann::json::json_pointer unit("/Reading");
761     if (sensorSchema == "Sensors")
762     {
763         sensor_json["@odata.type"] = "#Sensor.v1_0_0.Sensor";
764         sensor_json["@odata.context"] = "/redfish/v1/$metadata#Sensor.Sensor";
765         if (sensorType == "power")
766         {
767             sensor_json["ReadingUnits"] = "Watts";
768         }
769         else if (sensorType == "current")
770         {
771             sensor_json["ReadingUnits"] = "Amperes";
772         }
773     }
774     else if (sensorType == "temperature")
775     {
776         unit = "/ReadingCelsius"_json_pointer;
777         sensor_json["@odata.type"] = "#Thermal.v1_3_0.Temperature";
778         // TODO(ed) Documentation says that path should be type fan_tach,
779         // implementation seems to implement fan
780     }
781     else if (sensorType == "fan" || sensorType == "fan_tach")
782     {
783         unit = "/Reading"_json_pointer;
784         sensor_json["ReadingUnits"] = "RPM";
785         sensor_json["@odata.type"] = "#Thermal.v1_3_0.Fan";
786         setLedState(sensor_json, inventoryItem);
787         forceToInt = true;
788     }
789     else if (sensorType == "fan_pwm")
790     {
791         unit = "/Reading"_json_pointer;
792         sensor_json["ReadingUnits"] = "Percent";
793         sensor_json["@odata.type"] = "#Thermal.v1_3_0.Fan";
794         setLedState(sensor_json, inventoryItem);
795         forceToInt = true;
796     }
797     else if (sensorType == "voltage")
798     {
799         unit = "/ReadingVolts"_json_pointer;
800         sensor_json["@odata.type"] = "#Power.v1_0_0.Voltage";
801     }
802     else if (sensorType == "power")
803     {
804         std::string sensorNameLower =
805             boost::algorithm::to_lower_copy(sensorName);
806 
807         if (!sensorName.compare("total_power"))
808         {
809             sensor_json["@odata.type"] = "#Power.v1_0_0.PowerControl";
810             // Put multiple "sensors" into a single PowerControl, so have
811             // generic names for MemberId and Name. Follows Redfish mockup.
812             sensor_json["MemberId"] = "0";
813             sensor_json["Name"] = "Chassis Power Control";
814             unit = "/PowerConsumedWatts"_json_pointer;
815         }
816         else if (sensorNameLower.find("input") != std::string::npos)
817         {
818             unit = "/PowerInputWatts"_json_pointer;
819         }
820         else
821         {
822             unit = "/PowerOutputWatts"_json_pointer;
823         }
824     }
825     else
826     {
827         BMCWEB_LOG_ERROR << "Redfish cannot map object type for " << sensorName;
828         return;
829     }
830     // Map of dbus interface name, dbus property name and redfish property_name
831     std::vector<
832         std::tuple<const char*, const char*, nlohmann::json::json_pointer>>
833         properties;
834     properties.reserve(7);
835 
836     properties.emplace_back("xyz.openbmc_project.Sensor.Value", "Value", unit);
837 
838     if (sensorSchema == "Sensors")
839     {
840         properties.emplace_back(
841             "xyz.openbmc_project.Sensor.Threshold.Warning", "WarningHigh",
842             "/Thresholds/UpperCaution/Reading"_json_pointer);
843         properties.emplace_back(
844             "xyz.openbmc_project.Sensor.Threshold.Warning", "WarningLow",
845             "/Thresholds/LowerCaution/Reading"_json_pointer);
846         properties.emplace_back(
847             "xyz.openbmc_project.Sensor.Threshold.Critical", "CriticalHigh",
848             "/Thresholds/UpperCritical/Reading"_json_pointer);
849         properties.emplace_back(
850             "xyz.openbmc_project.Sensor.Threshold.Critical", "CriticalLow",
851             "/Thresholds/LowerCritical/Reading"_json_pointer);
852     }
853     else if (sensorType != "power")
854     {
855         properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Warning",
856                                 "WarningHigh",
857                                 "/UpperThresholdNonCritical"_json_pointer);
858         properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Warning",
859                                 "WarningLow",
860                                 "/LowerThresholdNonCritical"_json_pointer);
861         properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Critical",
862                                 "CriticalHigh",
863                                 "/UpperThresholdCritical"_json_pointer);
864         properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Critical",
865                                 "CriticalLow",
866                                 "/LowerThresholdCritical"_json_pointer);
867     }
868 
869     // TODO Need to get UpperThresholdFatal and LowerThresholdFatal
870 
871     if (sensorSchema == "Sensors")
872     {
873         properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MinValue",
874                                 "/ReadingRangeMin"_json_pointer);
875         properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MaxValue",
876                                 "/ReadingRangeMax"_json_pointer);
877     }
878     else if (sensorType == "temperature")
879     {
880         properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MinValue",
881                                 "/MinReadingRangeTemp"_json_pointer);
882         properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MaxValue",
883                                 "/MaxReadingRangeTemp"_json_pointer);
884     }
885     else if (sensorType != "power")
886     {
887         properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MinValue",
888                                 "/MinReadingRange"_json_pointer);
889         properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MaxValue",
890                                 "/MaxReadingRange"_json_pointer);
891     }
892 
893     for (const std::tuple<const char*, const char*,
894                           nlohmann::json::json_pointer>& p : properties)
895     {
896         auto interfaceProperties = interfacesDict.find(std::get<0>(p));
897         if (interfaceProperties != interfacesDict.end())
898         {
899             auto thisValueIt = interfaceProperties->second.find(std::get<1>(p));
900             if (thisValueIt != interfaceProperties->second.end())
901             {
902                 const SensorVariant& valueVariant = thisValueIt->second;
903 
904                 // The property we want to set may be nested json, so use
905                 // a json_pointer for easy indexing into the json structure.
906                 const nlohmann::json::json_pointer& key = std::get<2>(p);
907 
908                 // Attempt to pull the int64 directly
909                 const int64_t* int64Value = std::get_if<int64_t>(&valueVariant);
910 
911                 const double* doubleValue = std::get_if<double>(&valueVariant);
912                 const uint32_t* uValue = std::get_if<uint32_t>(&valueVariant);
913                 double temp = 0.0;
914                 if (int64Value != nullptr)
915                 {
916                     temp = static_cast<double>(*int64Value);
917                 }
918                 else if (doubleValue != nullptr)
919                 {
920                     temp = *doubleValue;
921                 }
922                 else if (uValue != nullptr)
923                 {
924                     temp = *uValue;
925                 }
926                 else
927                 {
928                     BMCWEB_LOG_ERROR
929                         << "Got value interface that wasn't int or double";
930                     continue;
931                 }
932                 temp = temp * std::pow(10, scaleMultiplier);
933                 if (forceToInt)
934                 {
935                     sensor_json[key] = static_cast<int64_t>(temp);
936                 }
937                 else
938                 {
939                     sensor_json[key] = temp;
940                 }
941             }
942         }
943     }
944     BMCWEB_LOG_DEBUG << "Added sensor " << sensorName;
945 }
946 
947 static void
948     populateFanRedundancy(std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp)
949 {
950     crow::connections::systemBus->async_method_call(
951         [sensorsAsyncResp](const boost::system::error_code ec,
952                            const GetSubTreeType& resp) {
953             if (ec)
954             {
955                 return; // don't have to have this interface
956             }
957             for (const std::pair<std::string,
958                                  std::vector<std::pair<
959                                      std::string, std::vector<std::string>>>>&
960                      pathPair : resp)
961             {
962                 const std::string& path = pathPair.first;
963                 const std::vector<
964                     std::pair<std::string, std::vector<std::string>>>& objDict =
965                     pathPair.second;
966                 if (objDict.empty())
967                 {
968                     continue; // this should be impossible
969                 }
970 
971                 const std::string& owner = objDict.begin()->first;
972                 crow::connections::systemBus->async_method_call(
973                     [path, owner,
974                      sensorsAsyncResp](const boost::system::error_code e,
975                                        std::variant<std::vector<std::string>>
976                                            variantEndpoints) {
977                         if (e)
978                         {
979                             return; // if they don't have an association we
980                                     // can't tell what chassis is
981                         }
982                         // verify part of the right chassis
983                         auto endpoints = std::get_if<std::vector<std::string>>(
984                             &variantEndpoints);
985 
986                         if (endpoints == nullptr)
987                         {
988                             BMCWEB_LOG_ERROR << "Invalid association interface";
989                             messages::internalError(sensorsAsyncResp->res);
990                             return;
991                         }
992 
993                         auto found = std::find_if(
994                             endpoints->begin(), endpoints->end(),
995                             [sensorsAsyncResp](const std::string& entry) {
996                                 return entry.find(
997                                            sensorsAsyncResp->chassisId) !=
998                                        std::string::npos;
999                             });
1000 
1001                         if (found == endpoints->end())
1002                         {
1003                             return;
1004                         }
1005                         crow::connections::systemBus->async_method_call(
1006                             [path, sensorsAsyncResp](
1007                                 const boost::system::error_code& err,
1008                                 const boost::container::flat_map<
1009                                     std::string,
1010                                     std::variant<uint8_t,
1011                                                  std::vector<std::string>,
1012                                                  std::string>>& ret) {
1013                                 if (err)
1014                                 {
1015                                     return; // don't have to have this
1016                                             // interface
1017                                 }
1018                                 auto findFailures = ret.find("AllowedFailures");
1019                                 auto findCollection = ret.find("Collection");
1020                                 auto findStatus = ret.find("Status");
1021 
1022                                 if (findFailures == ret.end() ||
1023                                     findCollection == ret.end() ||
1024                                     findStatus == ret.end())
1025                                 {
1026                                     BMCWEB_LOG_ERROR
1027                                         << "Invalid redundancy interface";
1028                                     messages::internalError(
1029                                         sensorsAsyncResp->res);
1030                                     return;
1031                                 }
1032 
1033                                 auto allowedFailures = std::get_if<uint8_t>(
1034                                     &(findFailures->second));
1035                                 auto collection =
1036                                     std::get_if<std::vector<std::string>>(
1037                                         &(findCollection->second));
1038                                 auto status = std::get_if<std::string>(
1039                                     &(findStatus->second));
1040 
1041                                 if (allowedFailures == nullptr ||
1042                                     collection == nullptr || status == nullptr)
1043                                 {
1044 
1045                                     BMCWEB_LOG_ERROR
1046                                         << "Invalid redundancy interface "
1047                                            "types";
1048                                     messages::internalError(
1049                                         sensorsAsyncResp->res);
1050                                     return;
1051                                 }
1052                                 size_t lastSlash = path.rfind("/");
1053                                 if (lastSlash == std::string::npos)
1054                                 {
1055                                     // this should be impossible
1056                                     messages::internalError(
1057                                         sensorsAsyncResp->res);
1058                                     return;
1059                                 }
1060                                 std::string name = path.substr(lastSlash + 1);
1061                                 std::replace(name.begin(), name.end(), '_',
1062                                              ' ');
1063 
1064                                 std::string health;
1065 
1066                                 if (boost::ends_with(*status, "Full"))
1067                                 {
1068                                     health = "OK";
1069                                 }
1070                                 else if (boost::ends_with(*status, "Degraded"))
1071                                 {
1072                                     health = "Warning";
1073                                 }
1074                                 else
1075                                 {
1076                                     health = "Critical";
1077                                 }
1078                                 std::vector<nlohmann::json> redfishCollection;
1079                                 const auto& fanRedfish =
1080                                     sensorsAsyncResp->res.jsonValue["Fans"];
1081                                 for (const std::string& item : *collection)
1082                                 {
1083                                     lastSlash = item.rfind("/");
1084                                     // make a copy as collection is const
1085                                     std::string itemName =
1086                                         item.substr(lastSlash + 1);
1087                                     /*
1088                                     todo(ed): merge patch that fixes the names
1089                                     std::replace(itemName.begin(),
1090                                                  itemName.end(), '_', ' ');*/
1091                                     auto schemaItem = std::find_if(
1092                                         fanRedfish.begin(), fanRedfish.end(),
1093                                         [itemName](const nlohmann::json& fan) {
1094                                             return fan["MemberId"] == itemName;
1095                                         });
1096                                     if (schemaItem != fanRedfish.end())
1097                                     {
1098                                         redfishCollection.push_back(
1099                                             {{"@odata.id",
1100                                               (*schemaItem)["@odata.id"]}});
1101                                     }
1102                                     else
1103                                     {
1104                                         BMCWEB_LOG_ERROR
1105                                             << "failed to find fan in schema";
1106                                         messages::internalError(
1107                                             sensorsAsyncResp->res);
1108                                         return;
1109                                     }
1110                                 }
1111 
1112                                 nlohmann::json& jResp =
1113                                     sensorsAsyncResp->res
1114                                         .jsonValue["Redundancy"];
1115                                 jResp.push_back(
1116                                     {{"@odata.id",
1117                                       "/redfish/v1/Chassis/" +
1118                                           sensorsAsyncResp->chassisId + "/" +
1119                                           sensorsAsyncResp->chassisSubNode +
1120                                           "#/Redundancy/" +
1121                                           std::to_string(jResp.size())},
1122                                      {"@odata.type",
1123                                       "#Redundancy.v1_3_2.Redundancy"},
1124                                      {"MinNumNeeded",
1125                                       collection->size() - *allowedFailures},
1126                                      {"MemberId", name},
1127                                      {"Mode", "N+m"},
1128                                      {"Name", name},
1129                                      {"RedundancySet", redfishCollection},
1130                                      {"Status",
1131                                       {{"Health", health},
1132                                        {"State", "Enabled"}}}});
1133                             },
1134                             owner, path, "org.freedesktop.DBus.Properties",
1135                             "GetAll",
1136                             "xyz.openbmc_project.Control.FanRedundancy");
1137                     },
1138                     "xyz.openbmc_project.ObjectMapper", path + "/chassis",
1139                     "org.freedesktop.DBus.Properties", "Get",
1140                     "xyz.openbmc_project.Association", "endpoints");
1141             }
1142         },
1143         "xyz.openbmc_project.ObjectMapper",
1144         "/xyz/openbmc_project/object_mapper",
1145         "xyz.openbmc_project.ObjectMapper", "GetSubTree",
1146         "/xyz/openbmc_project/control", 2,
1147         std::array<const char*, 1>{
1148             "xyz.openbmc_project.Control.FanRedundancy"});
1149 }
1150 
1151 void sortJSONResponse(std::shared_ptr<SensorsAsyncResp> SensorsAsyncResp)
1152 {
1153     nlohmann::json& response = SensorsAsyncResp->res.jsonValue;
1154     std::array<std::string, 2> sensorHeaders{"Temperatures", "Fans"};
1155     if (SensorsAsyncResp->chassisSubNode == "Power")
1156     {
1157         sensorHeaders = {"Voltages", "PowerSupplies"};
1158     }
1159     for (const std::string& sensorGroup : sensorHeaders)
1160     {
1161         nlohmann::json::iterator entry = response.find(sensorGroup);
1162         if (entry != response.end())
1163         {
1164             std::sort(entry->begin(), entry->end(),
1165                       [](nlohmann::json& c1, nlohmann::json& c2) {
1166                           return c1["Name"] < c2["Name"];
1167                       });
1168 
1169             // add the index counts to the end of each entry
1170             size_t count = 0;
1171             for (nlohmann::json& sensorJson : *entry)
1172             {
1173                 nlohmann::json::iterator odata = sensorJson.find("@odata.id");
1174                 if (odata == sensorJson.end())
1175                 {
1176                     continue;
1177                 }
1178                 std::string* value = odata->get_ptr<std::string*>();
1179                 if (value != nullptr)
1180                 {
1181                     *value += std::to_string(count);
1182                     count++;
1183                 }
1184             }
1185         }
1186     }
1187 }
1188 
1189 /**
1190  * @brief Finds the inventory item with the specified object path.
1191  * @param inventoryItems D-Bus inventory items associated with sensors.
1192  * @param invItemObjPath D-Bus object path of inventory item.
1193  * @return Inventory item within vector, or nullptr if no match found.
1194  */
1195 static InventoryItem* findInventoryItem(
1196     std::shared_ptr<std::vector<InventoryItem>> inventoryItems,
1197     const std::string& invItemObjPath)
1198 {
1199     for (InventoryItem& inventoryItem : *inventoryItems)
1200     {
1201         if (inventoryItem.objectPath == invItemObjPath)
1202         {
1203             return &inventoryItem;
1204         }
1205     }
1206     return nullptr;
1207 }
1208 
1209 /**
1210  * @brief Finds the inventory item associated with the specified sensor.
1211  * @param inventoryItems D-Bus inventory items associated with sensors.
1212  * @param sensorObjPath D-Bus object path of sensor.
1213  * @return Inventory item within vector, or nullptr if no match found.
1214  */
1215 static InventoryItem* findInventoryItemForSensor(
1216     std::shared_ptr<std::vector<InventoryItem>> inventoryItems,
1217     const std::string& sensorObjPath)
1218 {
1219     for (InventoryItem& inventoryItem : *inventoryItems)
1220     {
1221         if (inventoryItem.sensors.count(sensorObjPath) > 0)
1222         {
1223             return &inventoryItem;
1224         }
1225     }
1226     return nullptr;
1227 }
1228 
1229 /**
1230  * @brief Finds the inventory item associated with the specified led path.
1231  * @param inventoryItems D-Bus inventory items associated with sensors.
1232  * @param ledObjPath D-Bus object path of led.
1233  * @return Inventory item within vector, or nullptr if no match found.
1234  */
1235 inline InventoryItem*
1236     findInventoryItemForLed(std::vector<InventoryItem>& inventoryItems,
1237                             const std::string& ledObjPath)
1238 {
1239     for (InventoryItem& inventoryItem : inventoryItems)
1240     {
1241         if (inventoryItem.ledObjectPath == ledObjPath)
1242         {
1243             return &inventoryItem;
1244         }
1245     }
1246     return nullptr;
1247 }
1248 
1249 /**
1250  * @brief Adds inventory item and associated sensor to specified vector.
1251  *
1252  * Adds a new InventoryItem to the vector if necessary.  Searches for an
1253  * existing InventoryItem with the specified object path.  If not found, one is
1254  * added to the vector.
1255  *
1256  * Next, the specified sensor is added to the set of sensors associated with the
1257  * InventoryItem.
1258  *
1259  * @param inventoryItems D-Bus inventory items associated with sensors.
1260  * @param invItemObjPath D-Bus object path of inventory item.
1261  * @param sensorObjPath D-Bus object path of sensor
1262  */
1263 static void
1264     addInventoryItem(std::shared_ptr<std::vector<InventoryItem>> inventoryItems,
1265                      const std::string& invItemObjPath,
1266                      const std::string& sensorObjPath)
1267 {
1268     // Look for inventory item in vector
1269     InventoryItem* inventoryItem =
1270         findInventoryItem(inventoryItems, invItemObjPath);
1271 
1272     // If inventory item doesn't exist in vector, add it
1273     if (inventoryItem == nullptr)
1274     {
1275         inventoryItems->emplace_back(invItemObjPath);
1276         inventoryItem = &(inventoryItems->back());
1277     }
1278 
1279     // Add sensor to set of sensors associated with inventory item
1280     inventoryItem->sensors.emplace(sensorObjPath);
1281 }
1282 
1283 /**
1284  * @brief Stores D-Bus data in the specified inventory item.
1285  *
1286  * Finds D-Bus data in the specified map of interfaces.  Stores the data in the
1287  * specified InventoryItem.
1288  *
1289  * This data is later used to provide sensor property values in the JSON
1290  * response.
1291  *
1292  * @param inventoryItem Inventory item where data will be stored.
1293  * @param interfacesDict Map containing D-Bus interfaces and their properties
1294  * for the specified inventory item.
1295  */
1296 static void storeInventoryItemData(
1297     InventoryItem& inventoryItem,
1298     const boost::container::flat_map<
1299         std::string, boost::container::flat_map<std::string, SensorVariant>>&
1300         interfacesDict)
1301 {
1302     // Get properties from Inventory.Item interface
1303     auto interfaceIt =
1304         interfacesDict.find("xyz.openbmc_project.Inventory.Item");
1305     if (interfaceIt != interfacesDict.end())
1306     {
1307         auto propertyIt = interfaceIt->second.find("Present");
1308         if (propertyIt != interfaceIt->second.end())
1309         {
1310             const bool* value = std::get_if<bool>(&propertyIt->second);
1311             if (value != nullptr)
1312             {
1313                 inventoryItem.isPresent = *value;
1314             }
1315         }
1316     }
1317 
1318     // Check if Inventory.Item.PowerSupply interface is present
1319     interfaceIt =
1320         interfacesDict.find("xyz.openbmc_project.Inventory.Item.PowerSupply");
1321     if (interfaceIt != interfacesDict.end())
1322     {
1323         inventoryItem.isPowerSupply = true;
1324     }
1325 
1326     // Get properties from Inventory.Decorator.Asset interface
1327     interfaceIt =
1328         interfacesDict.find("xyz.openbmc_project.Inventory.Decorator.Asset");
1329     if (interfaceIt != interfacesDict.end())
1330     {
1331         auto propertyIt = interfaceIt->second.find("Manufacturer");
1332         if (propertyIt != interfaceIt->second.end())
1333         {
1334             const std::string* value =
1335                 std::get_if<std::string>(&propertyIt->second);
1336             if (value != nullptr)
1337             {
1338                 inventoryItem.manufacturer = *value;
1339             }
1340         }
1341 
1342         propertyIt = interfaceIt->second.find("Model");
1343         if (propertyIt != interfaceIt->second.end())
1344         {
1345             const std::string* value =
1346                 std::get_if<std::string>(&propertyIt->second);
1347             if (value != nullptr)
1348             {
1349                 inventoryItem.model = *value;
1350             }
1351         }
1352 
1353         propertyIt = interfaceIt->second.find("PartNumber");
1354         if (propertyIt != interfaceIt->second.end())
1355         {
1356             const std::string* value =
1357                 std::get_if<std::string>(&propertyIt->second);
1358             if (value != nullptr)
1359             {
1360                 inventoryItem.partNumber = *value;
1361             }
1362         }
1363 
1364         propertyIt = interfaceIt->second.find("SerialNumber");
1365         if (propertyIt != interfaceIt->second.end())
1366         {
1367             const std::string* value =
1368                 std::get_if<std::string>(&propertyIt->second);
1369             if (value != nullptr)
1370             {
1371                 inventoryItem.serialNumber = *value;
1372             }
1373         }
1374     }
1375 
1376     // Get properties from State.Decorator.OperationalStatus interface
1377     interfaceIt = interfacesDict.find(
1378         "xyz.openbmc_project.State.Decorator.OperationalStatus");
1379     if (interfaceIt != interfacesDict.end())
1380     {
1381         auto propertyIt = interfaceIt->second.find("Functional");
1382         if (propertyIt != interfaceIt->second.end())
1383         {
1384             const bool* value = std::get_if<bool>(&propertyIt->second);
1385             if (value != nullptr)
1386             {
1387                 inventoryItem.isFunctional = *value;
1388             }
1389         }
1390     }
1391 }
1392 
1393 /**
1394  * @brief Gets D-Bus data for inventory items associated with sensors.
1395  *
1396  * Uses the specified connections (services) to obtain D-Bus data for inventory
1397  * items associated with sensors.  Stores the resulting data in the
1398  * inventoryItems vector.
1399  *
1400  * This data is later used to provide sensor property values in the JSON
1401  * response.
1402  *
1403  * Finds the inventory item data asynchronously.  Invokes callback when data has
1404  * been obtained.
1405  *
1406  * The callback must have the following signature:
1407  *   @code
1408  *   callback(void)
1409  *   @endcode
1410  *
1411  * This function is called recursively, obtaining data asynchronously from one
1412  * connection in each call.  This ensures the callback is not invoked until the
1413  * last asynchronous function has completed.
1414  *
1415  * @param sensorsAsyncResp Pointer to object holding response data.
1416  * @param inventoryItems D-Bus inventory items associated with sensors.
1417  * @param invConnections Connections that provide data for the inventory items.
1418  * @param objectMgrPaths Mappings from connection name to DBus object path that
1419  * implements ObjectManager.
1420  * @param callback Callback to invoke when inventory data has been obtained.
1421  * @param invConnectionsIndex Current index in invConnections.  Only specified
1422  * in recursive calls to this function.
1423  */
1424 template <typename Callback>
1425 static void getInventoryItemsData(
1426     std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp,
1427     std::shared_ptr<std::vector<InventoryItem>> inventoryItems,
1428     std::shared_ptr<boost::container::flat_set<std::string>> invConnections,
1429     std::shared_ptr<boost::container::flat_map<std::string, std::string>>
1430         objectMgrPaths,
1431     Callback&& callback, size_t invConnectionsIndex = 0)
1432 {
1433     BMCWEB_LOG_DEBUG << "getInventoryItemsData enter";
1434 
1435     // If no more connections left, call callback
1436     if (invConnectionsIndex >= invConnections->size())
1437     {
1438         callback();
1439         BMCWEB_LOG_DEBUG << "getInventoryItemsData exit";
1440         return;
1441     }
1442 
1443     // Get inventory item data from current connection
1444     auto it = invConnections->nth(invConnectionsIndex);
1445     if (it != invConnections->end())
1446     {
1447         const std::string& invConnection = *it;
1448 
1449         // Response handler for GetManagedObjects
1450         auto respHandler = [sensorsAsyncResp, inventoryItems, invConnections,
1451                             objectMgrPaths, callback{std::move(callback)},
1452                             invConnectionsIndex](
1453                                const boost::system::error_code ec,
1454                                ManagedObjectsVectorType& resp) {
1455             BMCWEB_LOG_DEBUG << "getInventoryItemsData respHandler enter";
1456             if (ec)
1457             {
1458                 BMCWEB_LOG_ERROR
1459                     << "getInventoryItemsData respHandler DBus error " << ec;
1460                 messages::internalError(sensorsAsyncResp->res);
1461                 return;
1462             }
1463 
1464             // Loop through returned object paths
1465             for (const auto& objDictEntry : resp)
1466             {
1467                 const std::string& objPath =
1468                     static_cast<const std::string&>(objDictEntry.first);
1469 
1470                 // If this object path is one of the specified inventory items
1471                 InventoryItem* inventoryItem =
1472                     findInventoryItem(inventoryItems, objPath);
1473                 if (inventoryItem != nullptr)
1474                 {
1475                     // Store inventory data in InventoryItem
1476                     storeInventoryItemData(*inventoryItem, objDictEntry.second);
1477                 }
1478             }
1479 
1480             // Recurse to get inventory item data from next connection
1481             getInventoryItemsData(sensorsAsyncResp, inventoryItems,
1482                                   invConnections, objectMgrPaths,
1483                                   std::move(callback), invConnectionsIndex + 1);
1484 
1485             BMCWEB_LOG_DEBUG << "getInventoryItemsData respHandler exit";
1486         };
1487 
1488         // Find DBus object path that implements ObjectManager for the current
1489         // connection.  If no mapping found, default to "/".
1490         auto iter = objectMgrPaths->find(invConnection);
1491         const std::string& objectMgrPath =
1492             (iter != objectMgrPaths->end()) ? iter->second : "/";
1493         BMCWEB_LOG_DEBUG << "ObjectManager path for " << invConnection << " is "
1494                          << objectMgrPath;
1495 
1496         // Get all object paths and their interfaces for current connection
1497         crow::connections::systemBus->async_method_call(
1498             std::move(respHandler), invConnection, objectMgrPath,
1499             "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
1500     }
1501 
1502     BMCWEB_LOG_DEBUG << "getInventoryItemsData exit";
1503 }
1504 
1505 /**
1506  * @brief Gets connections that provide D-Bus data for inventory items.
1507  *
1508  * Gets the D-Bus connections (services) that provide data for the inventory
1509  * items that are associated with sensors.
1510  *
1511  * Finds the connections asynchronously.  Invokes callback when information has
1512  * been obtained.
1513  *
1514  * The callback must have the following signature:
1515  *   @code
1516  *   callback(std::shared_ptr<boost::container::flat_set<std::string>>
1517  *            invConnections)
1518  *   @endcode
1519  *
1520  * @param sensorsAsyncResp Pointer to object holding response data.
1521  * @param inventoryItems D-Bus inventory items associated with sensors.
1522  * @param callback Callback to invoke when connections have been obtained.
1523  */
1524 template <typename Callback>
1525 static void getInventoryItemsConnections(
1526     std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp,
1527     std::shared_ptr<std::vector<InventoryItem>> inventoryItems,
1528     Callback&& callback)
1529 {
1530     BMCWEB_LOG_DEBUG << "getInventoryItemsConnections enter";
1531 
1532     const std::string path = "/xyz/openbmc_project/inventory";
1533     const std::array<std::string, 4> interfaces = {
1534         "xyz.openbmc_project.Inventory.Item",
1535         "xyz.openbmc_project.Inventory.Item.PowerSupply",
1536         "xyz.openbmc_project.Inventory.Decorator.Asset",
1537         "xyz.openbmc_project.State.Decorator.OperationalStatus"};
1538 
1539     // Response handler for parsing output from GetSubTree
1540     auto respHandler = [callback{std::move(callback)}, sensorsAsyncResp,
1541                         inventoryItems](const boost::system::error_code ec,
1542                                         const GetSubTreeType& subtree) {
1543         BMCWEB_LOG_DEBUG << "getInventoryItemsConnections respHandler enter";
1544         if (ec)
1545         {
1546             messages::internalError(sensorsAsyncResp->res);
1547             BMCWEB_LOG_ERROR
1548                 << "getInventoryItemsConnections respHandler DBus error " << ec;
1549             return;
1550         }
1551 
1552         // Make unique list of connections for desired inventory items
1553         std::shared_ptr<boost::container::flat_set<std::string>>
1554             invConnections =
1555                 std::make_shared<boost::container::flat_set<std::string>>();
1556         invConnections->reserve(8);
1557 
1558         // Loop through objects from GetSubTree
1559         for (const std::pair<
1560                  std::string,
1561                  std::vector<std::pair<std::string, std::vector<std::string>>>>&
1562                  object : subtree)
1563         {
1564             // Check if object path is one of the specified inventory items
1565             const std::string& objPath = object.first;
1566             if (findInventoryItem(inventoryItems, objPath) != nullptr)
1567             {
1568                 // Store all connections to inventory item
1569                 for (const std::pair<std::string, std::vector<std::string>>&
1570                          objData : object.second)
1571                 {
1572                     const std::string& invConnection = objData.first;
1573                     invConnections->insert(invConnection);
1574                 }
1575             }
1576         }
1577 
1578         callback(invConnections);
1579         BMCWEB_LOG_DEBUG << "getInventoryItemsConnections respHandler exit";
1580     };
1581 
1582     // Make call to ObjectMapper to find all inventory items
1583     crow::connections::systemBus->async_method_call(
1584         std::move(respHandler), "xyz.openbmc_project.ObjectMapper",
1585         "/xyz/openbmc_project/object_mapper",
1586         "xyz.openbmc_project.ObjectMapper", "GetSubTree", path, 0, interfaces);
1587     BMCWEB_LOG_DEBUG << "getInventoryItemsConnections exit";
1588 }
1589 
1590 /**
1591  * @brief Gets associations from sensors to inventory items.
1592  *
1593  * Looks for ObjectMapper associations from the specified sensors to related
1594  * inventory items. Then finds the associations from those inventory items to
1595  * their LEDs, if any.
1596  *
1597  * Finds the inventory items asynchronously.  Invokes callback when information
1598  * has been obtained.
1599  *
1600  * The callback must have the following signature:
1601  *   @code
1602  *   callback(std::shared_ptr<std::vector<InventoryItem>> inventoryItems)
1603  *   @endcode
1604  *
1605  * @param sensorsAsyncResp Pointer to object holding response data.
1606  * @param sensorNames All sensors within the current chassis.
1607  * @param objectMgrPaths Mappings from connection name to DBus object path that
1608  * implements ObjectManager.
1609  * @param callback Callback to invoke when inventory items have been obtained.
1610  */
1611 template <typename Callback>
1612 static void getInventoryItemAssociations(
1613     std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp,
1614     const std::shared_ptr<boost::container::flat_set<std::string>> sensorNames,
1615     std::shared_ptr<boost::container::flat_map<std::string, std::string>>
1616         objectMgrPaths,
1617     Callback&& callback)
1618 {
1619     BMCWEB_LOG_DEBUG << "getInventoryItemAssociations enter";
1620 
1621     // Response handler for GetManagedObjects
1622     auto respHandler = [callback{std::move(callback)}, sensorsAsyncResp,
1623                         sensorNames](const boost::system::error_code ec,
1624                                      dbus::utility::ManagedObjectType& resp) {
1625         BMCWEB_LOG_DEBUG << "getInventoryItemAssociations respHandler enter";
1626         if (ec)
1627         {
1628             BMCWEB_LOG_ERROR
1629                 << "getInventoryItemAssociations respHandler DBus error " << ec;
1630             messages::internalError(sensorsAsyncResp->res);
1631             return;
1632         }
1633 
1634         // Create vector to hold list of inventory items
1635         std::shared_ptr<std::vector<InventoryItem>> inventoryItems =
1636             std::make_shared<std::vector<InventoryItem>>();
1637 
1638         // Loop through returned object paths
1639         std::string sensorAssocPath;
1640         sensorAssocPath.reserve(128); // avoid memory allocations
1641         for (const auto& objDictEntry : resp)
1642         {
1643             const std::string& objPath =
1644                 static_cast<const std::string&>(objDictEntry.first);
1645             const boost::container::flat_map<
1646                 std::string, boost::container::flat_map<
1647                                  std::string, dbus::utility::DbusVariantType>>&
1648                 interfacesDict = objDictEntry.second;
1649 
1650             // If path is inventory association for one of the specified sensors
1651             for (const std::string& sensorName : *sensorNames)
1652             {
1653                 sensorAssocPath = sensorName;
1654                 sensorAssocPath += "/inventory";
1655                 if (objPath == sensorAssocPath)
1656                 {
1657                     // Get Association interface for object path
1658                     auto assocIt =
1659                         interfacesDict.find("xyz.openbmc_project.Association");
1660                     if (assocIt != interfacesDict.end())
1661                     {
1662                         // Get inventory item from end point
1663                         auto endpointsIt = assocIt->second.find("endpoints");
1664                         if (endpointsIt != assocIt->second.end())
1665                         {
1666                             const std::vector<std::string>* endpoints =
1667                                 std::get_if<std::vector<std::string>>(
1668                                     &endpointsIt->second);
1669                             if ((endpoints != nullptr) && !endpoints->empty())
1670                             {
1671                                 // Add inventory item to vector
1672                                 const std::string& invItemPath =
1673                                     endpoints->front();
1674                                 addInventoryItem(inventoryItems, invItemPath,
1675                                                  sensorName);
1676                             }
1677                         }
1678                     }
1679                     break;
1680                 }
1681             }
1682         }
1683 
1684         // Now loop through the returned object paths again, this time to
1685         // find the leds associated with the inventory items we just found
1686         std::string inventoryAssocPath;
1687         inventoryAssocPath.reserve(128); // avoid memory allocations
1688         for (const auto& objDictEntry : resp)
1689         {
1690             const std::string& objPath =
1691                 static_cast<const std::string&>(objDictEntry.first);
1692             const boost::container::flat_map<
1693                 std::string, boost::container::flat_map<
1694                                  std::string, dbus::utility::DbusVariantType>>&
1695                 interfacesDict = objDictEntry.second;
1696 
1697             for (InventoryItem& inventoryItem : *inventoryItems)
1698             {
1699                 inventoryAssocPath = inventoryItem.objectPath;
1700                 inventoryAssocPath += "/leds";
1701                 if (objPath == inventoryAssocPath)
1702                 {
1703                     // Get Association interface for object path
1704                     auto assocIt =
1705                         interfacesDict.find("xyz.openbmc_project.Association");
1706                     if (assocIt != interfacesDict.end())
1707                     {
1708                         // Get inventory item from end point
1709                         auto endpointsIt = assocIt->second.find("endpoints");
1710                         if (endpointsIt != assocIt->second.end())
1711                         {
1712                             const std::vector<std::string>* endpoints =
1713                                 std::get_if<std::vector<std::string>>(
1714                                     &endpointsIt->second);
1715                             if ((endpoints != nullptr) && !endpoints->empty())
1716                             {
1717                                 // Store LED path in inventory item
1718                                 const std::string& ledPath = endpoints->front();
1719                                 inventoryItem.ledObjectPath = ledPath;
1720                             }
1721                         }
1722                     }
1723                     break;
1724                 }
1725             }
1726         }
1727         callback(inventoryItems);
1728         BMCWEB_LOG_DEBUG << "getInventoryItemAssociations respHandler exit";
1729     };
1730 
1731     // Find DBus object path that implements ObjectManager for ObjectMapper
1732     std::string connection = "xyz.openbmc_project.ObjectMapper";
1733     auto iter = objectMgrPaths->find(connection);
1734     const std::string& objectMgrPath =
1735         (iter != objectMgrPaths->end()) ? iter->second : "/";
1736     BMCWEB_LOG_DEBUG << "ObjectManager path for " << connection << " is "
1737                      << objectMgrPath;
1738 
1739     // Call GetManagedObjects on the ObjectMapper to get all associations
1740     crow::connections::systemBus->async_method_call(
1741         std::move(respHandler), connection, objectMgrPath,
1742         "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
1743 
1744     BMCWEB_LOG_DEBUG << "getInventoryItemAssociations exit";
1745 }
1746 
1747 /**
1748  * @brief Gets D-Bus data for inventory item leds associated with sensors.
1749  *
1750  * Uses the specified connections (services) to obtain D-Bus data for inventory
1751  * item leds associated with sensors.  Stores the resulting data in the
1752  * inventoryItems vector.
1753  *
1754  * This data is later used to provide sensor property values in the JSON
1755  * response.
1756  *
1757  * Finds the inventory item led data asynchronously.  Invokes callback when data
1758  * has been obtained.
1759  *
1760  * The callback must have the following signature:
1761  *   @code
1762  *   callback()
1763  *   @endcode
1764  *
1765  * This function is called recursively, obtaining data asynchronously from one
1766  * connection in each call.  This ensures the callback is not invoked until the
1767  * last asynchronous function has completed.
1768  *
1769  * @param sensorsAsyncResp Pointer to object holding response data.
1770  * @param inventoryItems D-Bus inventory items associated with sensors.
1771  * @param ledConnections Connections that provide data for the inventory leds.
1772  * @param callback Callback to invoke when inventory data has been obtained.
1773  * @param ledConnectionsIndex Current index in ledConnections.  Only specified
1774  * in recursive calls to this function.
1775  */
1776 template <typename Callback>
1777 void getInventoryLedData(
1778     std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp,
1779     std::shared_ptr<std::vector<InventoryItem>> inventoryItems,
1780     std::shared_ptr<boost::container::flat_map<std::string, std::string>>
1781         ledConnections,
1782     Callback&& callback, size_t ledConnectionsIndex = 0)
1783 {
1784     BMCWEB_LOG_DEBUG << "getInventoryLedData enter";
1785 
1786     // If no more connections left, call callback
1787     if (ledConnectionsIndex >= ledConnections->size())
1788     {
1789         callback();
1790         BMCWEB_LOG_DEBUG << "getInventoryLedData exit";
1791         return;
1792     }
1793 
1794     // Get inventory item data from current connection
1795     auto it = ledConnections->nth(ledConnectionsIndex);
1796     if (it != ledConnections->end())
1797     {
1798         const std::string& ledPath = (*it).first;
1799         const std::string& ledConnection = (*it).second;
1800         // Response handler for Get State property
1801         auto respHandler =
1802             [sensorsAsyncResp, inventoryItems, ledConnections, ledPath,
1803              callback{std::move(callback)},
1804              ledConnectionsIndex](const boost::system::error_code ec,
1805                                   const std::variant<std::string>& ledState) {
1806                 BMCWEB_LOG_DEBUG << "getInventoryLedData respHandler enter";
1807                 if (ec)
1808                 {
1809                     BMCWEB_LOG_ERROR
1810                         << "getInventoryLedData respHandler DBus error " << ec;
1811                     messages::internalError(sensorsAsyncResp->res);
1812                     return;
1813                 }
1814 
1815                 const std::string* state = std::get_if<std::string>(&ledState);
1816                 if (state != nullptr)
1817                 {
1818                     BMCWEB_LOG_DEBUG << "Led state: " << *state;
1819                     // Find inventory item with this LED object path
1820                     InventoryItem* inventoryItem =
1821                         findInventoryItemForLed(*inventoryItems, ledPath);
1822                     if (inventoryItem != nullptr)
1823                     {
1824                         // Store LED state in InventoryItem
1825                         if (boost::ends_with(*state, "On"))
1826                         {
1827                             inventoryItem->ledState = LedState::ON;
1828                         }
1829                         else if (boost::ends_with(*state, "Blink"))
1830                         {
1831                             inventoryItem->ledState = LedState::BLINK;
1832                         }
1833                         else if (boost::ends_with(*state, "Off"))
1834                         {
1835                             inventoryItem->ledState = LedState::OFF;
1836                         }
1837                         else
1838                         {
1839                             inventoryItem->ledState = LedState::UNKNOWN;
1840                         }
1841                     }
1842                 }
1843                 else
1844                 {
1845                     BMCWEB_LOG_DEBUG << "Failed to find State data for LED: "
1846                                      << ledPath;
1847                 }
1848 
1849                 // Recurse to get LED data from next connection
1850                 getInventoryLedData(sensorsAsyncResp, inventoryItems,
1851                                     ledConnections, std::move(callback),
1852                                     ledConnectionsIndex + 1);
1853 
1854                 BMCWEB_LOG_DEBUG << "getInventoryLedData respHandler exit";
1855             };
1856 
1857         // Get the State property for the current LED
1858         crow::connections::systemBus->async_method_call(
1859             std::move(respHandler), ledConnection, ledPath,
1860             "org.freedesktop.DBus.Properties", "Get",
1861             "xyz.openbmc_project.Led.Physical", "State");
1862     }
1863 
1864     BMCWEB_LOG_DEBUG << "getInventoryLedData exit";
1865 }
1866 
1867 /**
1868  * @brief Gets LED data for LEDs associated with given inventory items.
1869  *
1870  * Gets the D-Bus connections (services) that provide LED data for the LEDs
1871  * associated with the specified inventory items.  Then gets the LED data from
1872  * each connection and stores it in the inventory item.
1873  *
1874  * This data is later used to provide sensor property values in the JSON
1875  * response.
1876  *
1877  * Finds the LED data asynchronously.  Invokes callback when information has
1878  * been obtained.
1879  *
1880  * The callback must have the following signature:
1881  *   @code
1882  *   callback()
1883  *   @endcode
1884  *
1885  * @param sensorsAsyncResp Pointer to object holding response data.
1886  * @param inventoryItems D-Bus inventory items associated with sensors.
1887  * @param callback Callback to invoke when inventory items have been obtained.
1888  */
1889 template <typename Callback>
1890 void getInventoryLeds(
1891     std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp,
1892     std::shared_ptr<std::vector<InventoryItem>> inventoryItems,
1893     Callback&& callback)
1894 {
1895     BMCWEB_LOG_DEBUG << "getInventoryLeds enter";
1896 
1897     const std::string path = "/xyz/openbmc_project";
1898     const std::array<std::string, 1> interfaces = {
1899         "xyz.openbmc_project.Led.Physical"};
1900 
1901     // Response handler for parsing output from GetSubTree
1902     auto respHandler = [callback{std::move(callback)}, sensorsAsyncResp,
1903                         inventoryItems](const boost::system::error_code ec,
1904                                         const GetSubTreeType& subtree) {
1905         BMCWEB_LOG_DEBUG << "getInventoryLeds respHandler enter";
1906         if (ec)
1907         {
1908             messages::internalError(sensorsAsyncResp->res);
1909             BMCWEB_LOG_ERROR << "getInventoryLeds respHandler DBus error "
1910                              << ec;
1911             return;
1912         }
1913 
1914         // Build map of LED object paths to connections
1915         std::shared_ptr<boost::container::flat_map<std::string, std::string>>
1916             ledConnections = std::make_shared<
1917                 boost::container::flat_map<std::string, std::string>>();
1918 
1919         // Loop through objects from GetSubTree
1920         for (const std::pair<
1921                  std::string,
1922                  std::vector<std::pair<std::string, std::vector<std::string>>>>&
1923                  object : subtree)
1924         {
1925             // Check if object path is LED for one of the specified inventory
1926             // items
1927             const std::string& ledPath = object.first;
1928             if (findInventoryItemForLed(*inventoryItems, ledPath) != nullptr)
1929             {
1930                 // Add mapping from ledPath to connection
1931                 const std::string& connection = object.second.begin()->first;
1932                 (*ledConnections)[ledPath] = connection;
1933                 BMCWEB_LOG_DEBUG << "Added mapping " << ledPath << " -> "
1934                                  << connection;
1935             }
1936         }
1937 
1938         getInventoryLedData(sensorsAsyncResp, inventoryItems, ledConnections,
1939                             std::move(callback));
1940         BMCWEB_LOG_DEBUG << "getInventoryLeds respHandler exit";
1941     };
1942     // Make call to ObjectMapper to find all inventory items
1943     crow::connections::systemBus->async_method_call(
1944         std::move(respHandler), "xyz.openbmc_project.ObjectMapper",
1945         "/xyz/openbmc_project/object_mapper",
1946         "xyz.openbmc_project.ObjectMapper", "GetSubTree", path, 0, interfaces);
1947     BMCWEB_LOG_DEBUG << "getInventoryLeds exit";
1948 }
1949 
1950 /**
1951  * @brief Gets D-Bus data for Power Supply Attributes such as EfficiencyPercent
1952  *
1953  * Uses the specified connections (services) (currently assumes just one) to
1954  * obtain D-Bus data for Power Supply Attributes. Stores the resulting data in
1955  * the inventoryItems vector. Only stores data in Power Supply inventoryItems.
1956  *
1957  * This data is later used to provide sensor property values in the JSON
1958  * response.
1959  *
1960  * Finds the Power Supply Attributes data asynchronously.  Invokes callback
1961  * when data has been obtained.
1962  *
1963  * The callback must have the following signature:
1964  *   @code
1965  *   callback(std::shared_ptr<std::vector<InventoryItem>> inventoryItems)
1966  *   @endcode
1967  *
1968  * @param sensorsAsyncResp Pointer to object holding response data.
1969  * @param inventoryItems D-Bus inventory items associated with sensors.
1970  * @param psAttributesConnections Connections that provide data for the Power
1971  *        Supply Attributes
1972  * @param callback Callback to invoke when data has been obtained.
1973  */
1974 template <typename Callback>
1975 void getPowerSupplyAttributesData(
1976     std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp,
1977     std::shared_ptr<std::vector<InventoryItem>> inventoryItems,
1978     const boost::container::flat_map<std::string, std::string>&
1979         psAttributesConnections,
1980     Callback&& callback)
1981 {
1982     BMCWEB_LOG_DEBUG << "getPowerSupplyAttributesData enter";
1983 
1984     if (psAttributesConnections.empty())
1985     {
1986         BMCWEB_LOG_DEBUG << "Can't find PowerSupplyAttributes, no connections!";
1987         callback(inventoryItems);
1988         return;
1989     }
1990 
1991     // Assuming just one connection (service) for now
1992     auto it = psAttributesConnections.nth(0);
1993 
1994     const std::string& psAttributesPath = (*it).first;
1995     const std::string& psAttributesConnection = (*it).second;
1996 
1997     // Response handler for Get DeratingFactor property
1998     auto respHandler = [sensorsAsyncResp, inventoryItems,
1999                         callback{std::move(callback)}](
2000                            const boost::system::error_code ec,
2001                            const std::variant<uint32_t>& deratingFactor) {
2002         BMCWEB_LOG_DEBUG << "getPowerSupplyAttributesData respHandler enter";
2003         if (ec)
2004         {
2005             BMCWEB_LOG_ERROR
2006                 << "getPowerSupplyAttributesData respHandler DBus error " << ec;
2007             messages::internalError(sensorsAsyncResp->res);
2008             return;
2009         }
2010 
2011         const uint32_t* value = std::get_if<uint32_t>(&deratingFactor);
2012         if (value != nullptr)
2013         {
2014             BMCWEB_LOG_DEBUG << "PS EfficiencyPercent value: " << *value;
2015             // Store value in Power Supply Inventory Items
2016             for (InventoryItem& inventoryItem : *inventoryItems)
2017             {
2018                 if (inventoryItem.isPowerSupply == true)
2019                 {
2020                     inventoryItem.powerSupplyEfficiencyPercent =
2021                         static_cast<int>(*value);
2022                 }
2023             }
2024         }
2025         else
2026         {
2027             BMCWEB_LOG_DEBUG
2028                 << "Failed to find EfficiencyPercent value for PowerSupplies";
2029         }
2030 
2031         BMCWEB_LOG_DEBUG << "getPowerSupplyAttributesData respHandler exit";
2032         callback(inventoryItems);
2033     };
2034 
2035     // Get the DeratingFactor property for the PowerSupplyAttributes
2036     // Currently only property on the interface/only one we care about
2037     crow::connections::systemBus->async_method_call(
2038         std::move(respHandler), psAttributesConnection, psAttributesPath,
2039         "org.freedesktop.DBus.Properties", "Get",
2040         "xyz.openbmc_project.Control.PowerSupplyAttributes", "DeratingFactor");
2041 
2042     BMCWEB_LOG_DEBUG << "getPowerSupplyAttributesData exit";
2043 }
2044 
2045 /**
2046  * @brief Gets the Power Supply Attributes such as EfficiencyPercent
2047  *
2048  * Gets the D-Bus connection (service) that provides Power Supply Attributes
2049  * data. Then gets the Power Supply Attributes data from the connection
2050  * (currently just assumes 1 connection) and stores the data in the inventory
2051  * item.
2052  *
2053  * This data is later used to provide sensor property values in the JSON
2054  * response. DeratingFactor on D-Bus is mapped to EfficiencyPercent on Redfish.
2055  *
2056  * Finds the Power Supply Attributes data asynchronously. Invokes callback
2057  * when information has been obtained.
2058  *
2059  * The callback must have the following signature:
2060  *   @code
2061  *   callback(std::shared_ptr<std::vector<InventoryItem>> inventoryItems)
2062  *   @endcode
2063  *
2064  * @param sensorsAsyncResp Pointer to object holding response data.
2065  * @param inventoryItems D-Bus inventory items associated with sensors.
2066  * @param callback Callback to invoke when data has been obtained.
2067  */
2068 template <typename Callback>
2069 void getPowerSupplyAttributes(
2070     std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp,
2071     std::shared_ptr<std::vector<InventoryItem>> inventoryItems,
2072     Callback&& callback)
2073 {
2074     BMCWEB_LOG_DEBUG << "getPowerSupplyAttributes enter";
2075 
2076     // Only need the power supply attributes when the Power Schema
2077     if (sensorsAsyncResp->chassisSubNode != "Power")
2078     {
2079         BMCWEB_LOG_DEBUG << "getPowerSupplyAttributes exit since not Power";
2080         callback(inventoryItems);
2081         return;
2082     }
2083 
2084     const std::array<std::string, 1> interfaces = {
2085         "xyz.openbmc_project.Control.PowerSupplyAttributes"};
2086 
2087     // Response handler for parsing output from GetSubTree
2088     auto respHandler = [callback{std::move(callback)}, sensorsAsyncResp,
2089                         inventoryItems](const boost::system::error_code ec,
2090                                         const GetSubTreeType& subtree) {
2091         BMCWEB_LOG_DEBUG << "getPowerSupplyAttributes respHandler enter";
2092         if (ec)
2093         {
2094             messages::internalError(sensorsAsyncResp->res);
2095             BMCWEB_LOG_ERROR
2096                 << "getPowerSupplyAttributes respHandler DBus error " << ec;
2097             return;
2098         }
2099         if (subtree.size() == 0)
2100         {
2101             BMCWEB_LOG_DEBUG << "Can't find Power Supply Attributes!";
2102             callback(inventoryItems);
2103             return;
2104         }
2105 
2106         // Currently we only support 1 power supply attribute, use this for
2107         // all the power supplies. Build map of object path to connection.
2108         // Assume just 1 connection and 1 path for now.
2109         boost::container::flat_map<std::string, std::string>
2110             psAttributesConnections;
2111 
2112         if (subtree[0].first.empty() || subtree[0].second.empty())
2113         {
2114             BMCWEB_LOG_DEBUG << "Power Supply Attributes mapper error!";
2115             callback(inventoryItems);
2116             return;
2117         }
2118 
2119         const std::string& psAttributesPath = subtree[0].first;
2120         const std::string& connection = subtree[0].second.begin()->first;
2121 
2122         if (connection.empty())
2123         {
2124             BMCWEB_LOG_DEBUG << "Power Supply Attributes mapper error!";
2125             callback(inventoryItems);
2126             return;
2127         }
2128 
2129         psAttributesConnections[psAttributesPath] = connection;
2130         BMCWEB_LOG_DEBUG << "Added mapping " << psAttributesPath << " -> "
2131                          << connection;
2132 
2133         getPowerSupplyAttributesData(sensorsAsyncResp, inventoryItems,
2134                                      psAttributesConnections,
2135                                      std::move(callback));
2136         BMCWEB_LOG_DEBUG << "getPowerSupplyAttributes respHandler exit";
2137     };
2138     // Make call to ObjectMapper to find the PowerSupplyAttributes service
2139     crow::connections::systemBus->async_method_call(
2140         std::move(respHandler), "xyz.openbmc_project.ObjectMapper",
2141         "/xyz/openbmc_project/object_mapper",
2142         "xyz.openbmc_project.ObjectMapper", "GetSubTree",
2143         "/xyz/openbmc_project", 0, interfaces);
2144     BMCWEB_LOG_DEBUG << "getPowerSupplyAttributes exit";
2145 }
2146 
2147 /**
2148  * @brief Gets inventory items associated with sensors.
2149  *
2150  * Finds the inventory items that are associated with the specified sensors.
2151  * Then gets D-Bus data for the inventory items, such as presence and VPD.
2152  *
2153  * This data is later used to provide sensor property values in the JSON
2154  * response.
2155  *
2156  * Finds the inventory items asynchronously.  Invokes callback when the
2157  * inventory items have been obtained.
2158  *
2159  * The callback must have the following signature:
2160  *   @code
2161  *   callback(std::shared_ptr<std::vector<InventoryItem>> inventoryItems)
2162  *   @endcode
2163  *
2164  * @param sensorsAsyncResp Pointer to object holding response data.
2165  * @param sensorNames All sensors within the current chassis.
2166  * @param objectMgrPaths Mappings from connection name to DBus object path that
2167  * implements ObjectManager.
2168  * @param callback Callback to invoke when inventory items have been obtained.
2169  */
2170 template <typename Callback>
2171 static void getInventoryItems(
2172     std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp,
2173     const std::shared_ptr<boost::container::flat_set<std::string>> sensorNames,
2174     std::shared_ptr<boost::container::flat_map<std::string, std::string>>
2175         objectMgrPaths,
2176     Callback&& callback)
2177 {
2178     BMCWEB_LOG_DEBUG << "getInventoryItems enter";
2179     auto getInventoryItemAssociationsCb =
2180         [sensorsAsyncResp, objectMgrPaths, callback{std::move(callback)}](
2181             std::shared_ptr<std::vector<InventoryItem>> inventoryItems) {
2182             BMCWEB_LOG_DEBUG << "getInventoryItemAssociationsCb enter";
2183             auto getInventoryItemsConnectionsCb =
2184                 [sensorsAsyncResp, inventoryItems, objectMgrPaths,
2185                  callback{std::move(callback)}](
2186                     std::shared_ptr<boost::container::flat_set<std::string>>
2187                         invConnections) {
2188                     BMCWEB_LOG_DEBUG << "getInventoryItemsConnectionsCb enter";
2189                     auto getInventoryItemsDataCb =
2190                         [sensorsAsyncResp, inventoryItems,
2191                          callback{std::move(callback)}]() {
2192                             BMCWEB_LOG_DEBUG << "getInventoryItemsDataCb enter";
2193 
2194                             auto getInventoryLedsCb = [sensorsAsyncResp,
2195                                                        inventoryItems,
2196                                                        callback{std::move(
2197                                                            callback)}]() {
2198                                 BMCWEB_LOG_DEBUG << "getInventoryLedsCb enter";
2199                                 // Find Power Supply Attributes and get the data
2200                                 getPowerSupplyAttributes(sensorsAsyncResp,
2201                                                          inventoryItems,
2202                                                          std::move(callback));
2203                                 BMCWEB_LOG_DEBUG << "getInventoryLedsCb exit";
2204                             };
2205 
2206                             // Find led connections and get the data
2207                             getInventoryLeds(sensorsAsyncResp, inventoryItems,
2208                                              std::move(getInventoryLedsCb));
2209                             BMCWEB_LOG_DEBUG << "getInventoryItemsDataCb exit";
2210                         };
2211 
2212                     // Get inventory item data from connections
2213                     getInventoryItemsData(sensorsAsyncResp, inventoryItems,
2214                                           invConnections, objectMgrPaths,
2215                                           std::move(getInventoryItemsDataCb));
2216                     BMCWEB_LOG_DEBUG << "getInventoryItemsConnectionsCb exit";
2217                 };
2218 
2219             // Get connections that provide inventory item data
2220             getInventoryItemsConnections(
2221                 sensorsAsyncResp, inventoryItems,
2222                 std::move(getInventoryItemsConnectionsCb));
2223             BMCWEB_LOG_DEBUG << "getInventoryItemAssociationsCb exit";
2224         };
2225 
2226     // Get associations from sensors to inventory items
2227     getInventoryItemAssociations(sensorsAsyncResp, sensorNames, objectMgrPaths,
2228                                  std::move(getInventoryItemAssociationsCb));
2229     BMCWEB_LOG_DEBUG << "getInventoryItems exit";
2230 }
2231 
2232 /**
2233  * @brief Returns JSON PowerSupply object for the specified inventory item.
2234  *
2235  * Searches for a JSON PowerSupply object that matches the specified inventory
2236  * item.  If one is not found, a new PowerSupply object is added to the JSON
2237  * array.
2238  *
2239  * Multiple sensors are often associated with one power supply inventory item.
2240  * As a result, multiple sensor values are stored in one JSON PowerSupply
2241  * object.
2242  *
2243  * @param powerSupplyArray JSON array containing Redfish PowerSupply objects.
2244  * @param inventoryItem Inventory item for the power supply.
2245  * @param chassisId Chassis that contains the power supply.
2246  * @return JSON PowerSupply object for the specified inventory item.
2247  */
2248 static nlohmann::json& getPowerSupply(nlohmann::json& powerSupplyArray,
2249                                       const InventoryItem& inventoryItem,
2250                                       const std::string& chassisId)
2251 {
2252     // Check if matching PowerSupply object already exists in JSON array
2253     for (nlohmann::json& powerSupply : powerSupplyArray)
2254     {
2255         if (powerSupply["MemberId"] == inventoryItem.name)
2256         {
2257             return powerSupply;
2258         }
2259     }
2260 
2261     // Add new PowerSupply object to JSON array
2262     powerSupplyArray.push_back({});
2263     nlohmann::json& powerSupply = powerSupplyArray.back();
2264     powerSupply["@odata.id"] =
2265         "/redfish/v1/Chassis/" + chassisId + "/Power#/PowerSupplies/";
2266     powerSupply["MemberId"] = inventoryItem.name;
2267     powerSupply["Name"] = boost::replace_all_copy(inventoryItem.name, "_", " ");
2268     powerSupply["Manufacturer"] = inventoryItem.manufacturer;
2269     powerSupply["Model"] = inventoryItem.model;
2270     powerSupply["PartNumber"] = inventoryItem.partNumber;
2271     powerSupply["SerialNumber"] = inventoryItem.serialNumber;
2272     setLedState(powerSupply, &inventoryItem);
2273 
2274     if (inventoryItem.powerSupplyEfficiencyPercent >= 0)
2275     {
2276         powerSupply["EfficiencyPercent"] =
2277             inventoryItem.powerSupplyEfficiencyPercent;
2278     }
2279 
2280     powerSupply["Status"]["State"] = getState(&inventoryItem);
2281     const char* health = inventoryItem.isFunctional ? "OK" : "Critical";
2282     powerSupply["Status"]["Health"] = health;
2283 
2284     return powerSupply;
2285 }
2286 
2287 /**
2288  * @brief Gets the values of the specified sensors.
2289  *
2290  * Stores the results as JSON in the SensorsAsyncResp.
2291  *
2292  * Gets the sensor values asynchronously.  Stores the results later when the
2293  * information has been obtained.
2294  *
2295  * The sensorNames set contains all requested sensors for the current chassis.
2296  *
2297  * To minimize the number of DBus calls, the DBus method
2298  * org.freedesktop.DBus.ObjectManager.GetManagedObjects() is used to get the
2299  * values of all sensors provided by a connection (service).
2300  *
2301  * The connections set contains all the connections that provide sensor values.
2302  *
2303  * The objectMgrPaths map contains mappings from a connection name to the
2304  * corresponding DBus object path that implements ObjectManager.
2305  *
2306  * The InventoryItem vector contains D-Bus inventory items associated with the
2307  * sensors.  Inventory item data is needed for some Redfish sensor properties.
2308  *
2309  * @param SensorsAsyncResp Pointer to object holding response data.
2310  * @param sensorNames All requested sensors within the current chassis.
2311  * @param connections Connections that provide sensor values.
2312  * @param objectMgrPaths Mappings from connection name to DBus object path that
2313  * implements ObjectManager.
2314  * @param inventoryItems Inventory items associated with the sensors.
2315  */
2316 void getSensorData(
2317     std::shared_ptr<SensorsAsyncResp> SensorsAsyncResp,
2318     const std::shared_ptr<boost::container::flat_set<std::string>> sensorNames,
2319     const boost::container::flat_set<std::string>& connections,
2320     std::shared_ptr<boost::container::flat_map<std::string, std::string>>
2321         objectMgrPaths,
2322     std::shared_ptr<std::vector<InventoryItem>> inventoryItems)
2323 {
2324     BMCWEB_LOG_DEBUG << "getSensorData enter";
2325     // Get managed objects from all services exposing sensors
2326     for (const std::string& connection : connections)
2327     {
2328         // Response handler to process managed objects
2329         auto getManagedObjectsCb = [SensorsAsyncResp, sensorNames,
2330                                     inventoryItems](
2331                                        const boost::system::error_code ec,
2332                                        ManagedObjectsVectorType& resp) {
2333             BMCWEB_LOG_DEBUG << "getManagedObjectsCb enter";
2334             if (ec)
2335             {
2336                 BMCWEB_LOG_ERROR << "getManagedObjectsCb DBUS error: " << ec;
2337                 messages::internalError(SensorsAsyncResp->res);
2338                 return;
2339             }
2340             // Go through all objects and update response with sensor data
2341             for (const auto& objDictEntry : resp)
2342             {
2343                 const std::string& objPath =
2344                     static_cast<const std::string&>(objDictEntry.first);
2345                 BMCWEB_LOG_DEBUG << "getManagedObjectsCb parsing object "
2346                                  << objPath;
2347 
2348                 std::vector<std::string> split;
2349                 // Reserve space for
2350                 // /xyz/openbmc_project/sensors/<name>/<subname>
2351                 split.reserve(6);
2352                 boost::algorithm::split(split, objPath, boost::is_any_of("/"));
2353                 if (split.size() < 6)
2354                 {
2355                     BMCWEB_LOG_ERROR << "Got path that isn't long enough "
2356                                      << objPath;
2357                     continue;
2358                 }
2359                 // These indexes aren't intuitive, as boost::split puts an empty
2360                 // string at the beginning
2361                 const std::string& sensorType = split[4];
2362                 const std::string& sensorName = split[5];
2363                 BMCWEB_LOG_DEBUG << "sensorName " << sensorName
2364                                  << " sensorType " << sensorType;
2365                 if (sensorNames->find(objPath) == sensorNames->end())
2366                 {
2367                     BMCWEB_LOG_ERROR << sensorName << " not in sensor list ";
2368                     continue;
2369                 }
2370 
2371                 // Find inventory item (if any) associated with sensor
2372                 InventoryItem* inventoryItem =
2373                     findInventoryItemForSensor(inventoryItems, objPath);
2374 
2375                 const std::string& sensorSchema =
2376                     SensorsAsyncResp->chassisSubNode;
2377 
2378                 nlohmann::json* sensorJson = nullptr;
2379 
2380                 if (sensorSchema == "Sensors")
2381                 {
2382                     SensorsAsyncResp->res.jsonValue["@odata.id"] =
2383                         "/redfish/v1/Chassis/" + SensorsAsyncResp->chassisId +
2384                         "/" + SensorsAsyncResp->chassisSubNode + "/" +
2385                         sensorName;
2386                     sensorJson = &(SensorsAsyncResp->res.jsonValue);
2387                 }
2388                 else
2389                 {
2390                     std::string fieldName;
2391                     if (sensorType == "temperature")
2392                     {
2393                         fieldName = "Temperatures";
2394                     }
2395                     else if (sensorType == "fan" || sensorType == "fan_tach" ||
2396                              sensorType == "fan_pwm")
2397                     {
2398                         fieldName = "Fans";
2399                     }
2400                     else if (sensorType == "voltage")
2401                     {
2402                         fieldName = "Voltages";
2403                     }
2404                     else if (sensorType == "power")
2405                     {
2406                         if (!sensorName.compare("total_power"))
2407                         {
2408                             fieldName = "PowerControl";
2409                         }
2410                         else if ((inventoryItem != nullptr) &&
2411                                  (inventoryItem->isPowerSupply))
2412                         {
2413                             fieldName = "PowerSupplies";
2414                         }
2415                         else
2416                         {
2417                             // Other power sensors are in SensorCollection
2418                             continue;
2419                         }
2420                     }
2421                     else
2422                     {
2423                         BMCWEB_LOG_ERROR << "Unsure how to handle sensorType "
2424                                          << sensorType;
2425                         continue;
2426                     }
2427 
2428                     nlohmann::json& tempArray =
2429                         SensorsAsyncResp->res.jsonValue[fieldName];
2430                     if (fieldName == "PowerControl")
2431                     {
2432                         if (tempArray.empty())
2433                         {
2434                             // Put multiple "sensors" into a single
2435                             // PowerControl. Follows MemberId naming and
2436                             // naming in power.hpp.
2437                             tempArray.push_back(
2438                                 {{"@odata.id",
2439                                   "/redfish/v1/Chassis/" +
2440                                       SensorsAsyncResp->chassisId + "/" +
2441                                       SensorsAsyncResp->chassisSubNode + "#/" +
2442                                       fieldName + "/0"}});
2443                         }
2444                         sensorJson = &(tempArray.back());
2445                     }
2446                     else if (fieldName == "PowerSupplies")
2447                     {
2448                         if (inventoryItem != nullptr)
2449                         {
2450                             sensorJson =
2451                                 &(getPowerSupply(tempArray, *inventoryItem,
2452                                                  SensorsAsyncResp->chassisId));
2453                         }
2454                     }
2455                     else
2456                     {
2457                         tempArray.push_back(
2458                             {{"@odata.id",
2459                               "/redfish/v1/Chassis/" +
2460                                   SensorsAsyncResp->chassisId + "/" +
2461                                   SensorsAsyncResp->chassisSubNode + "#/" +
2462                                   fieldName + "/"}});
2463                         sensorJson = &(tempArray.back());
2464                     }
2465                 }
2466 
2467                 if (sensorJson != nullptr)
2468                 {
2469                     objectInterfacesToJson(sensorName, sensorType,
2470                                            SensorsAsyncResp->chassisSubNode,
2471                                            objDictEntry.second, *sensorJson,
2472                                            inventoryItem);
2473                 }
2474             }
2475             if (SensorsAsyncResp.use_count() == 1)
2476             {
2477                 sortJSONResponse(SensorsAsyncResp);
2478                 if (SensorsAsyncResp->chassisSubNode == "Thermal")
2479                 {
2480                     populateFanRedundancy(SensorsAsyncResp);
2481                 }
2482             }
2483             BMCWEB_LOG_DEBUG << "getManagedObjectsCb exit";
2484         };
2485 
2486         // Find DBus object path that implements ObjectManager for the current
2487         // connection.  If no mapping found, default to "/".
2488         auto iter = objectMgrPaths->find(connection);
2489         const std::string& objectMgrPath =
2490             (iter != objectMgrPaths->end()) ? iter->second : "/";
2491         BMCWEB_LOG_DEBUG << "ObjectManager path for " << connection << " is "
2492                          << objectMgrPath;
2493 
2494         crow::connections::systemBus->async_method_call(
2495             getManagedObjectsCb, connection, objectMgrPath,
2496             "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
2497     };
2498     BMCWEB_LOG_DEBUG << "getSensorData exit";
2499 }
2500 
2501 void processSensorList(
2502     std::shared_ptr<SensorsAsyncResp> SensorsAsyncResp,
2503     std::shared_ptr<boost::container::flat_set<std::string>> sensorNames)
2504 {
2505     auto getConnectionCb =
2506         [SensorsAsyncResp, sensorNames](
2507             const boost::container::flat_set<std::string>& connections) {
2508             BMCWEB_LOG_DEBUG << "getConnectionCb enter";
2509             auto getObjectManagerPathsCb =
2510                 [SensorsAsyncResp, sensorNames, connections](
2511                     std::shared_ptr<
2512                         boost::container::flat_map<std::string, std::string>>
2513                         objectMgrPaths) {
2514                     BMCWEB_LOG_DEBUG << "getObjectManagerPathsCb enter";
2515                     auto getInventoryItemsCb =
2516                         [SensorsAsyncResp, sensorNames, connections,
2517                          objectMgrPaths](
2518                             std::shared_ptr<std::vector<InventoryItem>>
2519                                 inventoryItems) {
2520                             BMCWEB_LOG_DEBUG << "getInventoryItemsCb enter";
2521                             // Get sensor data and store results in JSON
2522                             getSensorData(SensorsAsyncResp, sensorNames,
2523                                           connections, objectMgrPaths,
2524                                           inventoryItems);
2525                             BMCWEB_LOG_DEBUG << "getInventoryItemsCb exit";
2526                         };
2527 
2528                     // Get inventory items associated with sensors
2529                     getInventoryItems(SensorsAsyncResp, sensorNames,
2530                                       objectMgrPaths,
2531                                       std::move(getInventoryItemsCb));
2532 
2533                     BMCWEB_LOG_DEBUG << "getObjectManagerPathsCb exit";
2534                 };
2535 
2536             // Get mapping from connection names to the DBus object
2537             // paths that implement the ObjectManager interface
2538             getObjectManagerPaths(SensorsAsyncResp,
2539                                   std::move(getObjectManagerPathsCb));
2540             BMCWEB_LOG_DEBUG << "getConnectionCb exit";
2541         };
2542 
2543     // Get set of connections that provide sensor values
2544     getConnections(SensorsAsyncResp, sensorNames, std::move(getConnectionCb));
2545 }
2546 
2547 /**
2548  * @brief Entry point for retrieving sensors data related to requested
2549  *        chassis.
2550  * @param SensorsAsyncResp   Pointer to object holding response data
2551  */
2552 void getChassisData(std::shared_ptr<SensorsAsyncResp> SensorsAsyncResp)
2553 {
2554     BMCWEB_LOG_DEBUG << "getChassisData enter";
2555     auto getChassisCb =
2556         [SensorsAsyncResp](
2557             std::shared_ptr<boost::container::flat_set<std::string>>
2558                 sensorNames) {
2559             BMCWEB_LOG_DEBUG << "getChassisCb enter";
2560             processSensorList(SensorsAsyncResp, sensorNames);
2561             BMCWEB_LOG_DEBUG << "getChassisCb exit";
2562         };
2563     SensorsAsyncResp->res.jsonValue["Redundancy"] = nlohmann::json::array();
2564 
2565     // Get set of sensors in chassis
2566     getChassis(SensorsAsyncResp, std::move(getChassisCb));
2567     BMCWEB_LOG_DEBUG << "getChassisData exit";
2568 }
2569 
2570 /**
2571  * @brief Find the requested sensorName in the list of all sensors supplied by
2572  * the chassis node
2573  *
2574  * @param sensorName   The sensor name supplied in the PATCH request
2575  * @param sensorsList  The list of sensors managed by the chassis node
2576  * @param sensorsModified  The list of sensors that were found as a result of
2577  *                         repeated calls to this function
2578  */
2579 bool findSensorNameUsingSensorPath(
2580     std::string_view sensorName,
2581     boost::container::flat_set<std::string>& sensorsList,
2582     boost::container::flat_set<std::string>& sensorsModified)
2583 {
2584     for (std::string_view chassisSensor : sensorsList)
2585     {
2586         std::size_t pos = chassisSensor.rfind("/");
2587         if (pos >= (chassisSensor.size() - 1))
2588         {
2589             continue;
2590         }
2591         std::string_view thisSensorName = chassisSensor.substr(pos + 1);
2592         if (thisSensorName == sensorName)
2593         {
2594             sensorsModified.emplace(chassisSensor);
2595             return true;
2596         }
2597     }
2598     return false;
2599 }
2600 
2601 /**
2602  * @brief Entry point for overriding sensor values of given sensor
2603  *
2604  * @param res   response object
2605  * @param allCollections   Collections extract from sensors' request patch info
2606  * @param typeList   TypeList of sensors for the resource queried
2607  * @param chassisSubNode   Chassis Node for which the query has to happen
2608  */
2609 void setSensorOverride(
2610     std::shared_ptr<SensorsAsyncResp> sensorAsyncResp,
2611     std::unordered_map<std::string, std::vector<nlohmann::json>>&
2612         allCollections,
2613     const std::string& chassisName, const std::vector<const char*> typeList)
2614 {
2615     BMCWEB_LOG_INFO << "setSensorOverride for subNode"
2616                     << sensorAsyncResp->chassisSubNode << "\n";
2617 
2618     const char* propertyValueName;
2619     std::unordered_map<std::string, std::pair<double, std::string>> overrideMap;
2620     std::string memberId;
2621     double value;
2622     for (auto& collectionItems : allCollections)
2623     {
2624         if (collectionItems.first == "Temperatures")
2625         {
2626             propertyValueName = "ReadingCelsius";
2627         }
2628         else if (collectionItems.first == "Fans")
2629         {
2630             propertyValueName = "Reading";
2631         }
2632         else
2633         {
2634             propertyValueName = "ReadingVolts";
2635         }
2636         for (auto& item : collectionItems.second)
2637         {
2638             if (!json_util::readJson(item, sensorAsyncResp->res, "MemberId",
2639                                      memberId, propertyValueName, value))
2640             {
2641                 return;
2642             }
2643             overrideMap.emplace(memberId,
2644                                 std::make_pair(value, collectionItems.first));
2645         }
2646     }
2647 
2648     auto getChassisSensorListCb = [sensorAsyncResp,
2649                                    overrideMap](const std::shared_ptr<
2650                                                 boost::container::flat_set<
2651                                                     std::string>>
2652                                                     sensorsList) {
2653         // Match sensor names in the PATCH request to those managed by the
2654         // chassis node
2655         const std::shared_ptr<boost::container::flat_set<std::string>>
2656             sensorNames =
2657                 std::make_shared<boost::container::flat_set<std::string>>();
2658         for (const auto& item : overrideMap)
2659         {
2660             const auto& sensor = item.first;
2661             if (!findSensorNameUsingSensorPath(sensor, *sensorsList,
2662                                                *sensorNames))
2663             {
2664                 BMCWEB_LOG_INFO << "Unable to find memberId " << item.first;
2665                 messages::resourceNotFound(sensorAsyncResp->res,
2666                                            item.second.second, item.first);
2667                 return;
2668             }
2669         }
2670         // Get the connection to which the memberId belongs
2671         auto getObjectsWithConnectionCb =
2672             [sensorAsyncResp, overrideMap](
2673                 const boost::container::flat_set<std::string>& connections,
2674                 const std::set<std::pair<std::string, std::string>>&
2675                     objectsWithConnection) {
2676                 if (objectsWithConnection.size() != overrideMap.size())
2677                 {
2678                     BMCWEB_LOG_INFO
2679                         << "Unable to find all objects with proper connection "
2680                         << objectsWithConnection.size() << " requested "
2681                         << overrideMap.size() << "\n";
2682                     messages::resourceNotFound(
2683                         sensorAsyncResp->res,
2684                         sensorAsyncResp->chassisSubNode == "Thermal"
2685                             ? "Temperatures"
2686                             : "Voltages",
2687                         "Count");
2688                     return;
2689                 }
2690                 for (const auto& item : objectsWithConnection)
2691                 {
2692 
2693                     auto lastPos = item.first.rfind('/');
2694                     if (lastPos == std::string::npos)
2695                     {
2696                         messages::internalError(sensorAsyncResp->res);
2697                         return;
2698                     }
2699                     std::string sensorName = item.first.substr(lastPos + 1);
2700 
2701                     const auto& iterator = overrideMap.find(sensorName);
2702                     if (iterator == overrideMap.end())
2703                     {
2704                         BMCWEB_LOG_INFO << "Unable to find sensor object"
2705                                         << item.first << "\n";
2706                         messages::internalError(sensorAsyncResp->res);
2707                         return;
2708                     }
2709                     crow::connections::systemBus->async_method_call(
2710                         [sensorAsyncResp](const boost::system::error_code ec) {
2711                             if (ec)
2712                             {
2713                                 BMCWEB_LOG_DEBUG
2714                                     << "setOverrideValueStatus DBUS error: "
2715                                     << ec;
2716                                 messages::internalError(sensorAsyncResp->res);
2717                                 return;
2718                             }
2719                         },
2720                         item.second, item.first,
2721                         "org.freedesktop.DBus.Properties", "Set",
2722                         "xyz.openbmc_project.Sensor.Value", "Value",
2723                         sdbusplus::message::variant<double>(
2724                             iterator->second.first));
2725                 }
2726             };
2727         // Get object with connection for the given sensor name
2728         getObjectsWithConnection(sensorAsyncResp, sensorNames,
2729                                  std::move(getObjectsWithConnectionCb));
2730     };
2731     // get full sensor list for the given chassisId and cross verify the sensor.
2732     getChassis(sensorAsyncResp, std::move(getChassisSensorListCb));
2733 }
2734 
2735 class SensorCollection : public Node
2736 {
2737   public:
2738     SensorCollection(CrowApp& app) :
2739         Node(app, "/redfish/v1/Chassis/<str>/Sensors", std::string())
2740     {
2741         entityPrivileges = {
2742             {boost::beast::http::verb::get, {{"Login"}}},
2743             {boost::beast::http::verb::head, {{"Login"}}},
2744             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
2745             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
2746             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
2747             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
2748     }
2749 
2750   private:
2751     std::vector<const char*> typeList = {
2752         "/xyz/openbmc_project/sensors/power",
2753         "/xyz/openbmc_project/sensors/current"};
2754     void doGet(crow::Response& res, const crow::Request& req,
2755                const std::vector<std::string>& params) override
2756     {
2757         BMCWEB_LOG_DEBUG << "SensorCollection doGet enter";
2758         if (params.size() != 1)
2759         {
2760             BMCWEB_LOG_DEBUG << "SensorCollection doGet param size < 1";
2761             messages::internalError(res);
2762             res.end();
2763             return;
2764         }
2765 
2766         const std::string& chassisId = params[0];
2767         std::shared_ptr<SensorsAsyncResp> asyncResp =
2768             std::make_shared<SensorsAsyncResp>(res, chassisId, typeList,
2769                                                "Sensors");
2770 
2771         auto getChassisCb =
2772             [asyncResp](std::shared_ptr<boost::container::flat_set<std::string>>
2773                             sensorNames) {
2774                 BMCWEB_LOG_DEBUG << "getChassisCb enter";
2775 
2776                 nlohmann::json& entriesArray =
2777                     asyncResp->res.jsonValue["Members"];
2778                 for (auto& sensor : *sensorNames)
2779                 {
2780                     BMCWEB_LOG_DEBUG << "Adding sensor: " << sensor;
2781 
2782                     std::size_t lastPos = sensor.rfind("/");
2783                     if (lastPos == std::string::npos ||
2784                         lastPos + 1 >= sensor.size())
2785                     {
2786                         BMCWEB_LOG_ERROR << "Invalid sensor path: " << sensor;
2787                         messages::internalError(asyncResp->res);
2788                         return;
2789                     }
2790                     std::string sensorName = sensor.substr(lastPos + 1);
2791                     entriesArray.push_back(
2792                         {{"@odata.id",
2793                           "/redfish/v1/Chassis/" + asyncResp->chassisId + "/" +
2794                               asyncResp->chassisSubNode + "/" + sensorName}});
2795                 }
2796 
2797                 asyncResp->res.jsonValue["Members@odata.count"] =
2798                     entriesArray.size();
2799                 BMCWEB_LOG_DEBUG << "getChassisCb exit";
2800             };
2801 
2802         // Get set of sensors in chassis
2803         getChassis(asyncResp, std::move(getChassisCb));
2804         BMCWEB_LOG_DEBUG << "SensorCollection doGet exit";
2805     }
2806 };
2807 
2808 class Sensor : public Node
2809 {
2810   public:
2811     Sensor(CrowApp& app) :
2812         Node(app, "/redfish/v1/Chassis/<str>/Sensors/<str>/", std::string(),
2813              std::string())
2814     {
2815         entityPrivileges = {
2816             {boost::beast::http::verb::get, {{"Login"}}},
2817             {boost::beast::http::verb::head, {{"Login"}}},
2818             {boost::beast::http::verb::patch, {{"ConfigureManager"}}},
2819             {boost::beast::http::verb::put, {{"ConfigureManager"}}},
2820             {boost::beast::http::verb::delete_, {{"ConfigureManager"}}},
2821             {boost::beast::http::verb::post, {{"ConfigureManager"}}}};
2822     }
2823 
2824   private:
2825     void doGet(crow::Response& res, const crow::Request& req,
2826                const std::vector<std::string>& params) override
2827     {
2828         BMCWEB_LOG_DEBUG << "Sensor doGet enter";
2829         if (params.size() != 2)
2830         {
2831             BMCWEB_LOG_DEBUG << "Sensor doGet param size < 2";
2832             messages::internalError(res);
2833             res.end();
2834             return;
2835         }
2836         const std::string& chassisId = params[0];
2837         std::shared_ptr<SensorsAsyncResp> asyncResp =
2838             std::make_shared<SensorsAsyncResp>(
2839                 res, chassisId, std::vector<const char*>(), "Sensors");
2840 
2841         const std::string& sensorName = params[1];
2842         const std::array<const char*, 1> interfaces = {
2843             "xyz.openbmc_project.Sensor.Value"};
2844 
2845         // Get a list of all of the sensors that implement Sensor.Value
2846         // and get the path and service name associated with the sensor
2847         crow::connections::systemBus->async_method_call(
2848             [asyncResp, sensorName](const boost::system::error_code ec,
2849                                     const GetSubTreeType& subtree) {
2850                 BMCWEB_LOG_DEBUG << "respHandler1 enter";
2851                 if (ec)
2852                 {
2853                     messages::internalError(asyncResp->res);
2854                     BMCWEB_LOG_ERROR << "Sensor getSensorPaths resp_handler: "
2855                                      << "Dbus error " << ec;
2856                     return;
2857                 }
2858 
2859                 GetSubTreeType::const_iterator it = std::find_if(
2860                     subtree.begin(), subtree.end(),
2861                     [sensorName](
2862                         const std::pair<
2863                             std::string,
2864                             std::vector<std::pair<std::string,
2865                                                   std::vector<std::string>>>>&
2866                             object) {
2867                         std::string_view sensor = object.first;
2868                         std::size_t lastPos = sensor.rfind("/");
2869                         if (lastPos == std::string::npos ||
2870                             lastPos + 1 >= sensor.size())
2871                         {
2872                             BMCWEB_LOG_ERROR << "Invalid sensor path: "
2873                                              << sensor;
2874                             return false;
2875                         }
2876                         std::string_view name = sensor.substr(lastPos + 1);
2877 
2878                         return name == sensorName;
2879                     });
2880 
2881                 if (it == subtree.end())
2882                 {
2883                     BMCWEB_LOG_ERROR << "Could not find path for sensor: "
2884                                      << sensorName;
2885                     messages::resourceNotFound(asyncResp->res, "Sensor",
2886                                                sensorName);
2887                     return;
2888                 }
2889                 std::string_view sensorPath = (*it).first;
2890                 BMCWEB_LOG_DEBUG << "Found sensor path for sensor '"
2891                                  << sensorName << "': " << sensorPath;
2892 
2893                 const std::shared_ptr<boost::container::flat_set<std::string>>
2894                     sensorList = std::make_shared<
2895                         boost::container::flat_set<std::string>>();
2896 
2897                 sensorList->emplace(sensorPath);
2898                 processSensorList(asyncResp, sensorList);
2899                 BMCWEB_LOG_DEBUG << "respHandler1 exit";
2900             },
2901             "xyz.openbmc_project.ObjectMapper",
2902             "/xyz/openbmc_project/object_mapper",
2903             "xyz.openbmc_project.ObjectMapper", "GetSubTree",
2904             "/xyz/openbmc_project/sensors", 2, interfaces);
2905     }
2906 };
2907 
2908 } // namespace redfish
2909