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