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