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