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