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