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