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