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