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