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