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