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