xref: /openbmc/bmcweb/redfish-core/lib/sensors.hpp (revision 4cee35e7)
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                                 nlohmann::json::array_t 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                                         nlohmann::json::object_t collection;
1290                                         collection["@odata.id"] =
1291                                             (*schemaItem)["@odata.id"];
1292                                         redfishCollection.emplace_back(
1293                                             std::move(collection));
1294                                     }
1295                                     else
1296                                     {
1297                                         BMCWEB_LOG_ERROR
1298                                             << "failed to find fan in schema";
1299                                         messages::internalError(
1300                                             sensorsAsyncResp->asyncResp->res);
1301                                         return;
1302                                     }
1303                                 }
1304 
1305                                 size_t minNumNeeded =
1306                                     collection->empty()
1307                                         ? 0
1308                                         : collection->size() - *allowedFailures;
1309                                 nlohmann::json& jResp =
1310                                     sensorsAsyncResp->asyncResp->res
1311                                         .jsonValue["Redundancy"];
1312 
1313                                 nlohmann::json::object_t redundancy;
1314                                 redundancy["@odata.id"] =
1315                                     "/redfish/v1/Chassis/" +
1316                                     sensorsAsyncResp->chassisId + "/" +
1317                                     sensorsAsyncResp->chassisSubNode +
1318                                     "#/Redundancy/" +
1319                                     std::to_string(jResp.size());
1320                                 redundancy["@odata.type"] =
1321                                     "#Redundancy.v1_3_2.Redundancy";
1322                                 redundancy["MinNumNeeded"] = minNumNeeded;
1323                                 redundancy["MemberId"] = name;
1324                                 redundancy["Mode"] = "N+m";
1325                                 redundancy["Name"] = name;
1326                                 redundancy["RedundancySet"] = redfishCollection;
1327                                 redundancy["Status"]["Health"] = health;
1328                                 redundancy["Status"]["State"] = "Enabled";
1329 
1330                                 jResp.push_back(std::move(redundancy));
1331                             },
1332                             owner, path, "org.freedesktop.DBus.Properties",
1333                             "GetAll",
1334                             "xyz.openbmc_project.Control.FanRedundancy");
1335                     });
1336             }
1337         },
1338         "xyz.openbmc_project.ObjectMapper",
1339         "/xyz/openbmc_project/object_mapper",
1340         "xyz.openbmc_project.ObjectMapper", "GetSubTree",
1341         "/xyz/openbmc_project/control", 2,
1342         std::array<const char*, 1>{
1343             "xyz.openbmc_project.Control.FanRedundancy"});
1344 }
1345 
1346 inline void
1347     sortJSONResponse(const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp)
1348 {
1349     nlohmann::json& response = sensorsAsyncResp->asyncResp->res.jsonValue;
1350     std::array<std::string, 2> sensorHeaders{"Temperatures", "Fans"};
1351     if (sensorsAsyncResp->chassisSubNode == sensors::node::power)
1352     {
1353         sensorHeaders = {"Voltages", "PowerSupplies"};
1354     }
1355     for (const std::string& sensorGroup : sensorHeaders)
1356     {
1357         nlohmann::json::iterator entry = response.find(sensorGroup);
1358         if (entry != response.end())
1359         {
1360             std::sort(entry->begin(), entry->end(),
1361                       [](nlohmann::json& c1, nlohmann::json& c2) {
1362                           return c1["Name"] < c2["Name"];
1363                       });
1364 
1365             // add the index counts to the end of each entry
1366             size_t count = 0;
1367             for (nlohmann::json& sensorJson : *entry)
1368             {
1369                 nlohmann::json::iterator odata = sensorJson.find("@odata.id");
1370                 if (odata == sensorJson.end())
1371                 {
1372                     continue;
1373                 }
1374                 std::string* value = odata->get_ptr<std::string*>();
1375                 if (value != nullptr)
1376                 {
1377                     *value += std::to_string(count);
1378                     count++;
1379                     sensorsAsyncResp->updateUri(sensorJson["Name"], *value);
1380                 }
1381             }
1382         }
1383     }
1384 }
1385 
1386 /**
1387  * @brief Finds the inventory item with the specified object path.
1388  * @param inventoryItems D-Bus inventory items associated with sensors.
1389  * @param invItemObjPath D-Bus object path of inventory item.
1390  * @return Inventory item within vector, or nullptr if no match found.
1391  */
1392 inline InventoryItem* findInventoryItem(
1393     const std::shared_ptr<std::vector<InventoryItem>>& inventoryItems,
1394     const std::string& invItemObjPath)
1395 {
1396     for (InventoryItem& inventoryItem : *inventoryItems)
1397     {
1398         if (inventoryItem.objectPath == invItemObjPath)
1399         {
1400             return &inventoryItem;
1401         }
1402     }
1403     return nullptr;
1404 }
1405 
1406 /**
1407  * @brief Finds the inventory item associated with the specified sensor.
1408  * @param inventoryItems D-Bus inventory items associated with sensors.
1409  * @param sensorObjPath D-Bus object path of sensor.
1410  * @return Inventory item within vector, or nullptr if no match found.
1411  */
1412 inline InventoryItem* findInventoryItemForSensor(
1413     const std::shared_ptr<std::vector<InventoryItem>>& inventoryItems,
1414     const std::string& sensorObjPath)
1415 {
1416     for (InventoryItem& inventoryItem : *inventoryItems)
1417     {
1418         if (inventoryItem.sensors.count(sensorObjPath) > 0)
1419         {
1420             return &inventoryItem;
1421         }
1422     }
1423     return nullptr;
1424 }
1425 
1426 /**
1427  * @brief Finds the inventory item associated with the specified led path.
1428  * @param inventoryItems D-Bus inventory items associated with sensors.
1429  * @param ledObjPath D-Bus object path of led.
1430  * @return Inventory item within vector, or nullptr if no match found.
1431  */
1432 inline InventoryItem*
1433     findInventoryItemForLed(std::vector<InventoryItem>& inventoryItems,
1434                             const std::string& ledObjPath)
1435 {
1436     for (InventoryItem& inventoryItem : inventoryItems)
1437     {
1438         if (inventoryItem.ledObjectPath == ledObjPath)
1439         {
1440             return &inventoryItem;
1441         }
1442     }
1443     return nullptr;
1444 }
1445 
1446 /**
1447  * @brief Adds inventory item and associated sensor to specified vector.
1448  *
1449  * Adds a new InventoryItem to the vector if necessary.  Searches for an
1450  * existing InventoryItem with the specified object path.  If not found, one is
1451  * added to the vector.
1452  *
1453  * Next, the specified sensor is added to the set of sensors associated with the
1454  * InventoryItem.
1455  *
1456  * @param inventoryItems D-Bus inventory items associated with sensors.
1457  * @param invItemObjPath D-Bus object path of inventory item.
1458  * @param sensorObjPath D-Bus object path of sensor
1459  */
1460 inline void addInventoryItem(
1461     const std::shared_ptr<std::vector<InventoryItem>>& inventoryItems,
1462     const std::string& invItemObjPath, const std::string& sensorObjPath)
1463 {
1464     // Look for inventory item in vector
1465     InventoryItem* inventoryItem =
1466         findInventoryItem(inventoryItems, invItemObjPath);
1467 
1468     // If inventory item doesn't exist in vector, add it
1469     if (inventoryItem == nullptr)
1470     {
1471         inventoryItems->emplace_back(invItemObjPath);
1472         inventoryItem = &(inventoryItems->back());
1473     }
1474 
1475     // Add sensor to set of sensors associated with inventory item
1476     inventoryItem->sensors.emplace(sensorObjPath);
1477 }
1478 
1479 /**
1480  * @brief Stores D-Bus data in the specified inventory item.
1481  *
1482  * Finds D-Bus data in the specified map of interfaces.  Stores the data in the
1483  * specified InventoryItem.
1484  *
1485  * This data is later used to provide sensor property values in the JSON
1486  * response.
1487  *
1488  * @param inventoryItem Inventory item where data will be stored.
1489  * @param interfacesDict Map containing D-Bus interfaces and their properties
1490  * for the specified inventory item.
1491  */
1492 inline void storeInventoryItemData(
1493     InventoryItem& inventoryItem,
1494     const dbus::utility::DBusInteracesMap& interfacesDict)
1495 {
1496     // Get properties from Inventory.Item interface
1497 
1498     for (const auto& [interface, values] : interfacesDict)
1499     {
1500         if (interface == "xyz.openbmc_project.Inventory.Item")
1501         {
1502             for (const auto& [name, dbusValue] : values)
1503             {
1504                 if (name == "Present")
1505                 {
1506                     const bool* value = std::get_if<bool>(&dbusValue);
1507                     if (value != nullptr)
1508                     {
1509                         inventoryItem.isPresent = *value;
1510                     }
1511                 }
1512             }
1513         }
1514         // Check if Inventory.Item.PowerSupply interface is present
1515 
1516         if (interface == "xyz.openbmc_project.Inventory.Item.PowerSupply")
1517         {
1518             inventoryItem.isPowerSupply = true;
1519         }
1520 
1521         // Get properties from Inventory.Decorator.Asset interface
1522         if (interface == "xyz.openbmc_project.Inventory.Decorator.Asset")
1523         {
1524             for (const auto& [name, dbusValue] : values)
1525             {
1526                 if (name == "Manufacturer")
1527                 {
1528                     const std::string* value =
1529                         std::get_if<std::string>(&dbusValue);
1530                     if (value != nullptr)
1531                     {
1532                         inventoryItem.manufacturer = *value;
1533                     }
1534                 }
1535                 if (name == "Model")
1536                 {
1537                     const std::string* value =
1538                         std::get_if<std::string>(&dbusValue);
1539                     if (value != nullptr)
1540                     {
1541                         inventoryItem.model = *value;
1542                     }
1543                 }
1544                 if (name == "SerialNumber")
1545                 {
1546                     const std::string* value =
1547                         std::get_if<std::string>(&dbusValue);
1548                     if (value != nullptr)
1549                     {
1550                         inventoryItem.serialNumber = *value;
1551                     }
1552                 }
1553                 if (name == "PartNumber")
1554                 {
1555                     const std::string* value =
1556                         std::get_if<std::string>(&dbusValue);
1557                     if (value != nullptr)
1558                     {
1559                         inventoryItem.partNumber = *value;
1560                     }
1561                 }
1562             }
1563         }
1564 
1565         if (interface ==
1566             "xyz.openbmc_project.State.Decorator.OperationalStatus")
1567         {
1568             for (const auto& [name, dbusValue] : values)
1569             {
1570                 if (name == "Functional")
1571                 {
1572                     const bool* value = std::get_if<bool>(&dbusValue);
1573                     if (value != nullptr)
1574                     {
1575                         inventoryItem.isFunctional = *value;
1576                     }
1577                 }
1578             }
1579         }
1580     }
1581 }
1582 
1583 /**
1584  * @brief Gets D-Bus data for inventory items associated with sensors.
1585  *
1586  * Uses the specified connections (services) to obtain D-Bus data for inventory
1587  * items associated with sensors.  Stores the resulting data in the
1588  * inventoryItems vector.
1589  *
1590  * This data is later used to provide sensor property values in the JSON
1591  * response.
1592  *
1593  * Finds the inventory item data asynchronously.  Invokes callback when data has
1594  * been obtained.
1595  *
1596  * The callback must have the following signature:
1597  *   @code
1598  *   callback(void)
1599  *   @endcode
1600  *
1601  * This function is called recursively, obtaining data asynchronously from one
1602  * connection in each call.  This ensures the callback is not invoked until the
1603  * last asynchronous function has completed.
1604  *
1605  * @param sensorsAsyncResp Pointer to object holding response data.
1606  * @param inventoryItems D-Bus inventory items associated with sensors.
1607  * @param invConnections Connections that provide data for the inventory items.
1608  * @param objectMgrPaths Mappings from connection name to DBus object path that
1609  * implements ObjectManager.
1610  * @param callback Callback to invoke when inventory data has been obtained.
1611  * @param invConnectionsIndex Current index in invConnections.  Only specified
1612  * in recursive calls to this function.
1613  */
1614 template <typename Callback>
1615 static void getInventoryItemsData(
1616     std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp,
1617     std::shared_ptr<std::vector<InventoryItem>> inventoryItems,
1618     std::shared_ptr<boost::container::flat_set<std::string>> invConnections,
1619     std::shared_ptr<boost::container::flat_map<std::string, std::string>>
1620         objectMgrPaths,
1621     Callback&& callback, size_t invConnectionsIndex = 0)
1622 {
1623     BMCWEB_LOG_DEBUG << "getInventoryItemsData enter";
1624 
1625     // If no more connections left, call callback
1626     if (invConnectionsIndex >= invConnections->size())
1627     {
1628         callback();
1629         BMCWEB_LOG_DEBUG << "getInventoryItemsData exit";
1630         return;
1631     }
1632 
1633     // Get inventory item data from current connection
1634     auto it = invConnections->nth(invConnectionsIndex);
1635     if (it != invConnections->end())
1636     {
1637         const std::string& invConnection = *it;
1638 
1639         // Response handler for GetManagedObjects
1640         auto respHandler = [sensorsAsyncResp, inventoryItems, invConnections,
1641                             objectMgrPaths,
1642                             callback{std::forward<Callback>(callback)},
1643                             invConnectionsIndex](
1644                                const boost::system::error_code ec,
1645                                dbus::utility::ManagedObjectType& resp) {
1646             BMCWEB_LOG_DEBUG << "getInventoryItemsData respHandler enter";
1647             if (ec)
1648             {
1649                 BMCWEB_LOG_ERROR
1650                     << "getInventoryItemsData respHandler DBus error " << ec;
1651                 messages::internalError(sensorsAsyncResp->asyncResp->res);
1652                 return;
1653             }
1654 
1655             // Loop through returned object paths
1656             for (const auto& objDictEntry : resp)
1657             {
1658                 const std::string& objPath =
1659                     static_cast<const std::string&>(objDictEntry.first);
1660 
1661                 // If this object path is one of the specified inventory items
1662                 InventoryItem* inventoryItem =
1663                     findInventoryItem(inventoryItems, objPath);
1664                 if (inventoryItem != nullptr)
1665                 {
1666                     // Store inventory data in InventoryItem
1667                     storeInventoryItemData(*inventoryItem, objDictEntry.second);
1668                 }
1669             }
1670 
1671             // Recurse to get inventory item data from next connection
1672             getInventoryItemsData(sensorsAsyncResp, inventoryItems,
1673                                   invConnections, objectMgrPaths,
1674                                   std::move(callback), invConnectionsIndex + 1);
1675 
1676             BMCWEB_LOG_DEBUG << "getInventoryItemsData respHandler exit";
1677         };
1678 
1679         // Find DBus object path that implements ObjectManager for the current
1680         // connection.  If no mapping found, default to "/".
1681         auto iter = objectMgrPaths->find(invConnection);
1682         const std::string& objectMgrPath =
1683             (iter != objectMgrPaths->end()) ? iter->second : "/";
1684         BMCWEB_LOG_DEBUG << "ObjectManager path for " << invConnection << " is "
1685                          << objectMgrPath;
1686 
1687         // Get all object paths and their interfaces for current connection
1688         crow::connections::systemBus->async_method_call(
1689             std::move(respHandler), invConnection, objectMgrPath,
1690             "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
1691     }
1692 
1693     BMCWEB_LOG_DEBUG << "getInventoryItemsData exit";
1694 }
1695 
1696 /**
1697  * @brief Gets connections that provide D-Bus data for inventory items.
1698  *
1699  * Gets the D-Bus connections (services) that provide data for the inventory
1700  * items that are associated with sensors.
1701  *
1702  * Finds the connections asynchronously.  Invokes callback when information has
1703  * been obtained.
1704  *
1705  * The callback must have the following signature:
1706  *   @code
1707  *   callback(std::shared_ptr<boost::container::flat_set<std::string>>
1708  *            invConnections)
1709  *   @endcode
1710  *
1711  * @param sensorsAsyncResp Pointer to object holding response data.
1712  * @param inventoryItems D-Bus inventory items associated with sensors.
1713  * @param callback Callback to invoke when connections have been obtained.
1714  */
1715 template <typename Callback>
1716 static void getInventoryItemsConnections(
1717     const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp,
1718     const std::shared_ptr<std::vector<InventoryItem>>& inventoryItems,
1719     Callback&& callback)
1720 {
1721     BMCWEB_LOG_DEBUG << "getInventoryItemsConnections enter";
1722 
1723     const std::string path = "/xyz/openbmc_project/inventory";
1724     const std::array<std::string, 4> interfaces = {
1725         "xyz.openbmc_project.Inventory.Item",
1726         "xyz.openbmc_project.Inventory.Item.PowerSupply",
1727         "xyz.openbmc_project.Inventory.Decorator.Asset",
1728         "xyz.openbmc_project.State.Decorator.OperationalStatus"};
1729 
1730     // Response handler for parsing output from GetSubTree
1731     auto respHandler = [callback{std::forward<Callback>(callback)},
1732                         sensorsAsyncResp, inventoryItems](
1733                            const boost::system::error_code ec,
1734                            const dbus::utility::MapperGetSubTreeResponse&
1735                                subtree) {
1736         BMCWEB_LOG_DEBUG << "getInventoryItemsConnections respHandler enter";
1737         if (ec)
1738         {
1739             messages::internalError(sensorsAsyncResp->asyncResp->res);
1740             BMCWEB_LOG_ERROR
1741                 << "getInventoryItemsConnections respHandler DBus error " << ec;
1742             return;
1743         }
1744 
1745         // Make unique list of connections for desired inventory items
1746         std::shared_ptr<boost::container::flat_set<std::string>>
1747             invConnections =
1748                 std::make_shared<boost::container::flat_set<std::string>>();
1749         invConnections->reserve(8);
1750 
1751         // Loop through objects from GetSubTree
1752         for (const std::pair<
1753                  std::string,
1754                  std::vector<std::pair<std::string, std::vector<std::string>>>>&
1755                  object : subtree)
1756         {
1757             // Check if object path is one of the specified inventory items
1758             const std::string& objPath = object.first;
1759             if (findInventoryItem(inventoryItems, objPath) != nullptr)
1760             {
1761                 // Store all connections to inventory item
1762                 for (const std::pair<std::string, std::vector<std::string>>&
1763                          objData : object.second)
1764                 {
1765                     const std::string& invConnection = objData.first;
1766                     invConnections->insert(invConnection);
1767                 }
1768             }
1769         }
1770 
1771         callback(invConnections);
1772         BMCWEB_LOG_DEBUG << "getInventoryItemsConnections respHandler exit";
1773     };
1774 
1775     // Make call to ObjectMapper to find all inventory items
1776     crow::connections::systemBus->async_method_call(
1777         std::move(respHandler), "xyz.openbmc_project.ObjectMapper",
1778         "/xyz/openbmc_project/object_mapper",
1779         "xyz.openbmc_project.ObjectMapper", "GetSubTree", path, 0, interfaces);
1780     BMCWEB_LOG_DEBUG << "getInventoryItemsConnections exit";
1781 }
1782 
1783 /**
1784  * @brief Gets associations from sensors to inventory items.
1785  *
1786  * Looks for ObjectMapper associations from the specified sensors to related
1787  * inventory items. Then finds the associations from those inventory items to
1788  * their LEDs, if any.
1789  *
1790  * Finds the inventory items asynchronously.  Invokes callback when information
1791  * has been obtained.
1792  *
1793  * The callback must have the following signature:
1794  *   @code
1795  *   callback(std::shared_ptr<std::vector<InventoryItem>> inventoryItems)
1796  *   @endcode
1797  *
1798  * @param sensorsAsyncResp Pointer to object holding response data.
1799  * @param sensorNames All sensors within the current chassis.
1800  * @param objectMgrPaths Mappings from connection name to DBus object path that
1801  * implements ObjectManager.
1802  * @param callback Callback to invoke when inventory items have been obtained.
1803  */
1804 template <typename Callback>
1805 static void getInventoryItemAssociations(
1806     const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp,
1807     const std::shared_ptr<boost::container::flat_set<std::string>>& sensorNames,
1808     const std::shared_ptr<boost::container::flat_map<std::string, std::string>>&
1809         objectMgrPaths,
1810     Callback&& callback)
1811 {
1812     BMCWEB_LOG_DEBUG << "getInventoryItemAssociations enter";
1813 
1814     // Response handler for GetManagedObjects
1815     auto respHandler = [callback{std::forward<Callback>(callback)},
1816                         sensorsAsyncResp,
1817                         sensorNames](const boost::system::error_code ec,
1818                                      dbus::utility::ManagedObjectType& resp) {
1819         BMCWEB_LOG_DEBUG << "getInventoryItemAssociations respHandler enter";
1820         if (ec)
1821         {
1822             BMCWEB_LOG_ERROR
1823                 << "getInventoryItemAssociations respHandler DBus error " << ec;
1824             messages::internalError(sensorsAsyncResp->asyncResp->res);
1825             return;
1826         }
1827 
1828         // Create vector to hold list of inventory items
1829         std::shared_ptr<std::vector<InventoryItem>> inventoryItems =
1830             std::make_shared<std::vector<InventoryItem>>();
1831 
1832         // Loop through returned object paths
1833         std::string sensorAssocPath;
1834         sensorAssocPath.reserve(128); // avoid memory allocations
1835         for (const auto& objDictEntry : resp)
1836         {
1837             const std::string& objPath =
1838                 static_cast<const std::string&>(objDictEntry.first);
1839 
1840             // If path is inventory association for one of the specified sensors
1841             for (const std::string& sensorName : *sensorNames)
1842             {
1843                 sensorAssocPath = sensorName;
1844                 sensorAssocPath += "/inventory";
1845                 if (objPath == sensorAssocPath)
1846                 {
1847                     // Get Association interface for object path
1848                     for (const auto& [interface, values] : objDictEntry.second)
1849                     {
1850                         if (interface == "xyz.openbmc_project.Association")
1851                         {
1852                             for (const auto& [valueName, value] : values)
1853                             {
1854                                 if (valueName == "endpoints")
1855                                 {
1856                                     const std::vector<std::string>* endpoints =
1857                                         std::get_if<std::vector<std::string>>(
1858                                             &value);
1859                                     if ((endpoints != nullptr) &&
1860                                         !endpoints->empty())
1861                                     {
1862                                         // Add inventory item to vector
1863                                         const std::string& invItemPath =
1864                                             endpoints->front();
1865                                         addInventoryItem(inventoryItems,
1866                                                          invItemPath,
1867                                                          sensorName);
1868                                     }
1869                                 }
1870                             }
1871                         }
1872                     }
1873                     break;
1874                 }
1875             }
1876         }
1877 
1878         // Now loop through the returned object paths again, this time to
1879         // find the leds associated with the inventory items we just found
1880         std::string inventoryAssocPath;
1881         inventoryAssocPath.reserve(128); // avoid memory allocations
1882         for (const auto& objDictEntry : resp)
1883         {
1884             const std::string& objPath =
1885                 static_cast<const std::string&>(objDictEntry.first);
1886 
1887             for (InventoryItem& inventoryItem : *inventoryItems)
1888             {
1889                 inventoryAssocPath = inventoryItem.objectPath;
1890                 inventoryAssocPath += "/leds";
1891                 if (objPath == inventoryAssocPath)
1892                 {
1893                     for (const auto& [interface, values] : objDictEntry.second)
1894                     {
1895                         if (interface == "xyz.openbmc_project.Association")
1896                         {
1897                             for (const auto& [valueName, value] : values)
1898                             {
1899                                 if (valueName == "endpoints")
1900                                 {
1901                                     const std::vector<std::string>* endpoints =
1902                                         std::get_if<std::vector<std::string>>(
1903                                             &value);
1904                                     if ((endpoints != nullptr) &&
1905                                         !endpoints->empty())
1906                                     {
1907                                         // Add inventory item to vector
1908                                         // Store LED path in inventory item
1909                                         const std::string& ledPath =
1910                                             endpoints->front();
1911                                         inventoryItem.ledObjectPath = ledPath;
1912                                     }
1913                                 }
1914                             }
1915                         }
1916                     }
1917 
1918                     break;
1919                 }
1920             }
1921         }
1922         callback(inventoryItems);
1923         BMCWEB_LOG_DEBUG << "getInventoryItemAssociations respHandler exit";
1924     };
1925 
1926     // Find DBus object path that implements ObjectManager for ObjectMapper
1927     std::string connection = "xyz.openbmc_project.ObjectMapper";
1928     auto iter = objectMgrPaths->find(connection);
1929     const std::string& objectMgrPath =
1930         (iter != objectMgrPaths->end()) ? iter->second : "/";
1931     BMCWEB_LOG_DEBUG << "ObjectManager path for " << connection << " is "
1932                      << objectMgrPath;
1933 
1934     // Call GetManagedObjects on the ObjectMapper to get all associations
1935     crow::connections::systemBus->async_method_call(
1936         std::move(respHandler), connection, objectMgrPath,
1937         "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
1938 
1939     BMCWEB_LOG_DEBUG << "getInventoryItemAssociations exit";
1940 }
1941 
1942 /**
1943  * @brief Gets D-Bus data for inventory item leds associated with sensors.
1944  *
1945  * Uses the specified connections (services) to obtain D-Bus data for inventory
1946  * item leds associated with sensors.  Stores the resulting data in the
1947  * inventoryItems vector.
1948  *
1949  * This data is later used to provide sensor property values in the JSON
1950  * response.
1951  *
1952  * Finds the inventory item led data asynchronously.  Invokes callback when data
1953  * has been obtained.
1954  *
1955  * The callback must have the following signature:
1956  *   @code
1957  *   callback()
1958  *   @endcode
1959  *
1960  * This function is called recursively, obtaining data asynchronously from one
1961  * connection in each call.  This ensures the callback is not invoked until the
1962  * last asynchronous function has completed.
1963  *
1964  * @param sensorsAsyncResp Pointer to object holding response data.
1965  * @param inventoryItems D-Bus inventory items associated with sensors.
1966  * @param ledConnections Connections that provide data for the inventory leds.
1967  * @param callback Callback to invoke when inventory data has been obtained.
1968  * @param ledConnectionsIndex Current index in ledConnections.  Only specified
1969  * in recursive calls to this function.
1970  */
1971 template <typename Callback>
1972 void getInventoryLedData(
1973     std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp,
1974     std::shared_ptr<std::vector<InventoryItem>> inventoryItems,
1975     std::shared_ptr<boost::container::flat_map<std::string, std::string>>
1976         ledConnections,
1977     Callback&& callback, size_t ledConnectionsIndex = 0)
1978 {
1979     BMCWEB_LOG_DEBUG << "getInventoryLedData enter";
1980 
1981     // If no more connections left, call callback
1982     if (ledConnectionsIndex >= ledConnections->size())
1983     {
1984         callback();
1985         BMCWEB_LOG_DEBUG << "getInventoryLedData exit";
1986         return;
1987     }
1988 
1989     // Get inventory item data from current connection
1990     auto it = ledConnections->nth(ledConnectionsIndex);
1991     if (it != ledConnections->end())
1992     {
1993         const std::string& ledPath = (*it).first;
1994         const std::string& ledConnection = (*it).second;
1995         // Response handler for Get State property
1996         auto respHandler =
1997             [sensorsAsyncResp, inventoryItems, ledConnections, ledPath,
1998              callback{std::forward<Callback>(callback)}, ledConnectionsIndex](
1999                 const boost::system::error_code ec, const std::string& state) {
2000                 BMCWEB_LOG_DEBUG << "getInventoryLedData respHandler enter";
2001                 if (ec)
2002                 {
2003                     BMCWEB_LOG_ERROR
2004                         << "getInventoryLedData respHandler DBus error " << ec;
2005                     messages::internalError(sensorsAsyncResp->asyncResp->res);
2006                     return;
2007                 }
2008 
2009                 BMCWEB_LOG_DEBUG << "Led state: " << state;
2010                 // Find inventory item with this LED object path
2011                 InventoryItem* inventoryItem =
2012                     findInventoryItemForLed(*inventoryItems, ledPath);
2013                 if (inventoryItem != nullptr)
2014                 {
2015                     // Store LED state in InventoryItem
2016                     if (boost::ends_with(state, "On"))
2017                     {
2018                         inventoryItem->ledState = LedState::ON;
2019                     }
2020                     else if (boost::ends_with(state, "Blink"))
2021                     {
2022                         inventoryItem->ledState = LedState::BLINK;
2023                     }
2024                     else if (boost::ends_with(state, "Off"))
2025                     {
2026                         inventoryItem->ledState = LedState::OFF;
2027                     }
2028                     else
2029                     {
2030                         inventoryItem->ledState = LedState::UNKNOWN;
2031                     }
2032                 }
2033 
2034                 // Recurse to get LED data from next connection
2035                 getInventoryLedData(sensorsAsyncResp, inventoryItems,
2036                                     ledConnections, std::move(callback),
2037                                     ledConnectionsIndex + 1);
2038 
2039                 BMCWEB_LOG_DEBUG << "getInventoryLedData respHandler exit";
2040             };
2041 
2042         // Get the State property for the current LED
2043         sdbusplus::asio::getProperty<std::string>(
2044             *crow::connections::systemBus, ledConnection, ledPath,
2045             "xyz.openbmc_project.Led.Physical", "State",
2046             std::move(respHandler));
2047     }
2048 
2049     BMCWEB_LOG_DEBUG << "getInventoryLedData exit";
2050 }
2051 
2052 /**
2053  * @brief Gets LED data for LEDs associated with given inventory items.
2054  *
2055  * Gets the D-Bus connections (services) that provide LED data for the LEDs
2056  * associated with the specified inventory items.  Then gets the LED data from
2057  * each connection and stores it in the inventory item.
2058  *
2059  * This data is later used to provide sensor property values in the JSON
2060  * response.
2061  *
2062  * Finds the LED data asynchronously.  Invokes callback when information has
2063  * been obtained.
2064  *
2065  * The callback must have the following signature:
2066  *   @code
2067  *   callback()
2068  *   @endcode
2069  *
2070  * @param sensorsAsyncResp Pointer to object holding response data.
2071  * @param inventoryItems D-Bus inventory items associated with sensors.
2072  * @param callback Callback to invoke when inventory items have been obtained.
2073  */
2074 template <typename Callback>
2075 void getInventoryLeds(
2076     std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp,
2077     std::shared_ptr<std::vector<InventoryItem>> inventoryItems,
2078     Callback&& callback)
2079 {
2080     BMCWEB_LOG_DEBUG << "getInventoryLeds enter";
2081 
2082     const std::string path = "/xyz/openbmc_project";
2083     const std::array<std::string, 1> interfaces = {
2084         "xyz.openbmc_project.Led.Physical"};
2085 
2086     // Response handler for parsing output from GetSubTree
2087     auto respHandler = [callback{std::forward<Callback>(callback)},
2088                         sensorsAsyncResp, inventoryItems](
2089                            const boost::system::error_code ec,
2090                            const dbus::utility::MapperGetSubTreeResponse&
2091                                subtree) {
2092         BMCWEB_LOG_DEBUG << "getInventoryLeds respHandler enter";
2093         if (ec)
2094         {
2095             messages::internalError(sensorsAsyncResp->asyncResp->res);
2096             BMCWEB_LOG_ERROR << "getInventoryLeds respHandler DBus error "
2097                              << ec;
2098             return;
2099         }
2100 
2101         // Build map of LED object paths to connections
2102         std::shared_ptr<boost::container::flat_map<std::string, std::string>>
2103             ledConnections = std::make_shared<
2104                 boost::container::flat_map<std::string, std::string>>();
2105 
2106         // Loop through objects from GetSubTree
2107         for (const std::pair<
2108                  std::string,
2109                  std::vector<std::pair<std::string, std::vector<std::string>>>>&
2110                  object : subtree)
2111         {
2112             // Check if object path is LED for one of the specified inventory
2113             // items
2114             const std::string& ledPath = object.first;
2115             if (findInventoryItemForLed(*inventoryItems, ledPath) != nullptr)
2116             {
2117                 // Add mapping from ledPath to connection
2118                 const std::string& connection = object.second.begin()->first;
2119                 (*ledConnections)[ledPath] = connection;
2120                 BMCWEB_LOG_DEBUG << "Added mapping " << ledPath << " -> "
2121                                  << connection;
2122             }
2123         }
2124 
2125         getInventoryLedData(sensorsAsyncResp, inventoryItems, ledConnections,
2126                             std::move(callback));
2127         BMCWEB_LOG_DEBUG << "getInventoryLeds respHandler exit";
2128     };
2129     // Make call to ObjectMapper to find all inventory items
2130     crow::connections::systemBus->async_method_call(
2131         std::move(respHandler), "xyz.openbmc_project.ObjectMapper",
2132         "/xyz/openbmc_project/object_mapper",
2133         "xyz.openbmc_project.ObjectMapper", "GetSubTree", path, 0, interfaces);
2134     BMCWEB_LOG_DEBUG << "getInventoryLeds exit";
2135 }
2136 
2137 /**
2138  * @brief Gets D-Bus data for Power Supply Attributes such as EfficiencyPercent
2139  *
2140  * Uses the specified connections (services) (currently assumes just one) to
2141  * obtain D-Bus data for Power Supply Attributes. Stores the resulting data in
2142  * the inventoryItems vector. Only stores data in Power Supply inventoryItems.
2143  *
2144  * This data is later used to provide sensor property values in the JSON
2145  * response.
2146  *
2147  * Finds the Power Supply Attributes data asynchronously.  Invokes callback
2148  * when data has been obtained.
2149  *
2150  * The callback must have the following signature:
2151  *   @code
2152  *   callback(std::shared_ptr<std::vector<InventoryItem>> inventoryItems)
2153  *   @endcode
2154  *
2155  * @param sensorsAsyncResp Pointer to object holding response data.
2156  * @param inventoryItems D-Bus inventory items associated with sensors.
2157  * @param psAttributesConnections Connections that provide data for the Power
2158  *        Supply Attributes
2159  * @param callback Callback to invoke when data has been obtained.
2160  */
2161 template <typename Callback>
2162 void getPowerSupplyAttributesData(
2163     const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp,
2164     std::shared_ptr<std::vector<InventoryItem>> inventoryItems,
2165     const boost::container::flat_map<std::string, std::string>&
2166         psAttributesConnections,
2167     Callback&& callback)
2168 {
2169     BMCWEB_LOG_DEBUG << "getPowerSupplyAttributesData enter";
2170 
2171     if (psAttributesConnections.empty())
2172     {
2173         BMCWEB_LOG_DEBUG << "Can't find PowerSupplyAttributes, no connections!";
2174         callback(inventoryItems);
2175         return;
2176     }
2177 
2178     // Assuming just one connection (service) for now
2179     auto it = psAttributesConnections.nth(0);
2180 
2181     const std::string& psAttributesPath = (*it).first;
2182     const std::string& psAttributesConnection = (*it).second;
2183 
2184     // Response handler for Get DeratingFactor property
2185     auto respHandler = [sensorsAsyncResp, inventoryItems,
2186                         callback{std::forward<Callback>(callback)}](
2187                            const boost::system::error_code ec,
2188                            const uint32_t value) {
2189         BMCWEB_LOG_DEBUG << "getPowerSupplyAttributesData respHandler enter";
2190         if (ec)
2191         {
2192             BMCWEB_LOG_ERROR
2193                 << "getPowerSupplyAttributesData respHandler DBus error " << ec;
2194             messages::internalError(sensorsAsyncResp->asyncResp->res);
2195             return;
2196         }
2197 
2198         BMCWEB_LOG_DEBUG << "PS EfficiencyPercent value: " << value;
2199         // Store value in Power Supply Inventory Items
2200         for (InventoryItem& inventoryItem : *inventoryItems)
2201         {
2202             if (inventoryItem.isPowerSupply)
2203             {
2204                 inventoryItem.powerSupplyEfficiencyPercent =
2205                     static_cast<int>(value);
2206             }
2207         }
2208 
2209         BMCWEB_LOG_DEBUG << "getPowerSupplyAttributesData respHandler exit";
2210         callback(inventoryItems);
2211     };
2212 
2213     // Get the DeratingFactor property for the PowerSupplyAttributes
2214     // Currently only property on the interface/only one we care about
2215     sdbusplus::asio::getProperty<uint32_t>(
2216         *crow::connections::systemBus, psAttributesConnection, psAttributesPath,
2217         "xyz.openbmc_project.Control.PowerSupplyAttributes", "DeratingFactor",
2218         std::move(respHandler));
2219 
2220     BMCWEB_LOG_DEBUG << "getPowerSupplyAttributesData exit";
2221 }
2222 
2223 /**
2224  * @brief Gets the Power Supply Attributes such as EfficiencyPercent
2225  *
2226  * Gets the D-Bus connection (service) that provides Power Supply Attributes
2227  * data. Then gets the Power Supply Attributes data from the connection
2228  * (currently just assumes 1 connection) and stores the data in the inventory
2229  * item.
2230  *
2231  * This data is later used to provide sensor property values in the JSON
2232  * response. DeratingFactor on D-Bus is mapped to EfficiencyPercent on Redfish.
2233  *
2234  * Finds the Power Supply Attributes data asynchronously. Invokes callback
2235  * when information has been obtained.
2236  *
2237  * The callback must have the following signature:
2238  *   @code
2239  *   callback(std::shared_ptr<std::vector<InventoryItem>> inventoryItems)
2240  *   @endcode
2241  *
2242  * @param sensorsAsyncResp Pointer to object holding response data.
2243  * @param inventoryItems D-Bus inventory items associated with sensors.
2244  * @param callback Callback to invoke when data has been obtained.
2245  */
2246 template <typename Callback>
2247 void getPowerSupplyAttributes(
2248     std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp,
2249     std::shared_ptr<std::vector<InventoryItem>> inventoryItems,
2250     Callback&& callback)
2251 {
2252     BMCWEB_LOG_DEBUG << "getPowerSupplyAttributes enter";
2253 
2254     // Only need the power supply attributes when the Power Schema
2255     if (sensorsAsyncResp->chassisSubNode != sensors::node::power)
2256     {
2257         BMCWEB_LOG_DEBUG << "getPowerSupplyAttributes exit since not Power";
2258         callback(inventoryItems);
2259         return;
2260     }
2261 
2262     const std::array<std::string, 1> interfaces = {
2263         "xyz.openbmc_project.Control.PowerSupplyAttributes"};
2264 
2265     // Response handler for parsing output from GetSubTree
2266     auto respHandler =
2267         [callback{std::forward<Callback>(callback)}, sensorsAsyncResp,
2268          inventoryItems](
2269             const boost::system::error_code ec,
2270             const dbus::utility::MapperGetSubTreeResponse& subtree) {
2271             BMCWEB_LOG_DEBUG << "getPowerSupplyAttributes respHandler enter";
2272             if (ec)
2273             {
2274                 messages::internalError(sensorsAsyncResp->asyncResp->res);
2275                 BMCWEB_LOG_ERROR
2276                     << "getPowerSupplyAttributes respHandler DBus error " << ec;
2277                 return;
2278             }
2279             if (subtree.empty())
2280             {
2281                 BMCWEB_LOG_DEBUG << "Can't find Power Supply Attributes!";
2282                 callback(inventoryItems);
2283                 return;
2284             }
2285 
2286             // Currently we only support 1 power supply attribute, use this for
2287             // all the power supplies. Build map of object path to connection.
2288             // Assume just 1 connection and 1 path for now.
2289             boost::container::flat_map<std::string, std::string>
2290                 psAttributesConnections;
2291 
2292             if (subtree[0].first.empty() || subtree[0].second.empty())
2293             {
2294                 BMCWEB_LOG_DEBUG << "Power Supply Attributes mapper error!";
2295                 callback(inventoryItems);
2296                 return;
2297             }
2298 
2299             const std::string& psAttributesPath = subtree[0].first;
2300             const std::string& connection = subtree[0].second.begin()->first;
2301 
2302             if (connection.empty())
2303             {
2304                 BMCWEB_LOG_DEBUG << "Power Supply Attributes mapper error!";
2305                 callback(inventoryItems);
2306                 return;
2307             }
2308 
2309             psAttributesConnections[psAttributesPath] = connection;
2310             BMCWEB_LOG_DEBUG << "Added mapping " << psAttributesPath << " -> "
2311                              << connection;
2312 
2313             getPowerSupplyAttributesData(sensorsAsyncResp, inventoryItems,
2314                                          psAttributesConnections,
2315                                          std::move(callback));
2316             BMCWEB_LOG_DEBUG << "getPowerSupplyAttributes respHandler exit";
2317         };
2318     // Make call to ObjectMapper to find the PowerSupplyAttributes service
2319     crow::connections::systemBus->async_method_call(
2320         std::move(respHandler), "xyz.openbmc_project.ObjectMapper",
2321         "/xyz/openbmc_project/object_mapper",
2322         "xyz.openbmc_project.ObjectMapper", "GetSubTree",
2323         "/xyz/openbmc_project", 0, interfaces);
2324     BMCWEB_LOG_DEBUG << "getPowerSupplyAttributes exit";
2325 }
2326 
2327 /**
2328  * @brief Gets inventory items associated with sensors.
2329  *
2330  * Finds the inventory items that are associated with the specified sensors.
2331  * Then gets D-Bus data for the inventory items, such as presence and VPD.
2332  *
2333  * This data is later used to provide sensor property values in the JSON
2334  * response.
2335  *
2336  * Finds the inventory items asynchronously.  Invokes callback when the
2337  * inventory items have been obtained.
2338  *
2339  * The callback must have the following signature:
2340  *   @code
2341  *   callback(std::shared_ptr<std::vector<InventoryItem>> inventoryItems)
2342  *   @endcode
2343  *
2344  * @param sensorsAsyncResp Pointer to object holding response data.
2345  * @param sensorNames All sensors within the current chassis.
2346  * @param objectMgrPaths Mappings from connection name to DBus object path that
2347  * implements ObjectManager.
2348  * @param callback Callback to invoke when inventory items have been obtained.
2349  */
2350 template <typename Callback>
2351 static void getInventoryItems(
2352     std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp,
2353     const std::shared_ptr<boost::container::flat_set<std::string>> sensorNames,
2354     std::shared_ptr<boost::container::flat_map<std::string, std::string>>
2355         objectMgrPaths,
2356     Callback&& callback)
2357 {
2358     BMCWEB_LOG_DEBUG << "getInventoryItems enter";
2359     auto getInventoryItemAssociationsCb =
2360         [sensorsAsyncResp, objectMgrPaths,
2361          callback{std::forward<Callback>(callback)}](
2362             std::shared_ptr<std::vector<InventoryItem>> inventoryItems) {
2363             BMCWEB_LOG_DEBUG << "getInventoryItemAssociationsCb enter";
2364             auto getInventoryItemsConnectionsCb =
2365                 [sensorsAsyncResp, inventoryItems, objectMgrPaths,
2366                  callback{std::forward<const Callback>(callback)}](
2367                     std::shared_ptr<boost::container::flat_set<std::string>>
2368                         invConnections) {
2369                     BMCWEB_LOG_DEBUG << "getInventoryItemsConnectionsCb enter";
2370                     auto getInventoryItemsDataCb =
2371                         [sensorsAsyncResp, inventoryItems,
2372                          callback{std::move(callback)}]() {
2373                             BMCWEB_LOG_DEBUG << "getInventoryItemsDataCb enter";
2374 
2375                             auto getInventoryLedsCb = [sensorsAsyncResp,
2376                                                        inventoryItems,
2377                                                        callback{std::move(
2378                                                            callback)}]() {
2379                                 BMCWEB_LOG_DEBUG << "getInventoryLedsCb enter";
2380                                 // Find Power Supply Attributes and get the data
2381                                 getPowerSupplyAttributes(sensorsAsyncResp,
2382                                                          inventoryItems,
2383                                                          std::move(callback));
2384                                 BMCWEB_LOG_DEBUG << "getInventoryLedsCb exit";
2385                             };
2386 
2387                             // Find led connections and get the data
2388                             getInventoryLeds(sensorsAsyncResp, inventoryItems,
2389                                              std::move(getInventoryLedsCb));
2390                             BMCWEB_LOG_DEBUG << "getInventoryItemsDataCb exit";
2391                         };
2392 
2393                     // Get inventory item data from connections
2394                     getInventoryItemsData(sensorsAsyncResp, inventoryItems,
2395                                           invConnections, objectMgrPaths,
2396                                           std::move(getInventoryItemsDataCb));
2397                     BMCWEB_LOG_DEBUG << "getInventoryItemsConnectionsCb exit";
2398                 };
2399 
2400             // Get connections that provide inventory item data
2401             getInventoryItemsConnections(
2402                 sensorsAsyncResp, inventoryItems,
2403                 std::move(getInventoryItemsConnectionsCb));
2404             BMCWEB_LOG_DEBUG << "getInventoryItemAssociationsCb exit";
2405         };
2406 
2407     // Get associations from sensors to inventory items
2408     getInventoryItemAssociations(sensorsAsyncResp, sensorNames, objectMgrPaths,
2409                                  std::move(getInventoryItemAssociationsCb));
2410     BMCWEB_LOG_DEBUG << "getInventoryItems exit";
2411 }
2412 
2413 /**
2414  * @brief Returns JSON PowerSupply object for the specified inventory item.
2415  *
2416  * Searches for a JSON PowerSupply object that matches the specified inventory
2417  * item.  If one is not found, a new PowerSupply object is added to the JSON
2418  * array.
2419  *
2420  * Multiple sensors are often associated with one power supply inventory item.
2421  * As a result, multiple sensor values are stored in one JSON PowerSupply
2422  * object.
2423  *
2424  * @param powerSupplyArray JSON array containing Redfish PowerSupply objects.
2425  * @param inventoryItem Inventory item for the power supply.
2426  * @param chassisId Chassis that contains the power supply.
2427  * @return JSON PowerSupply object for the specified inventory item.
2428  */
2429 inline nlohmann::json& getPowerSupply(nlohmann::json& powerSupplyArray,
2430                                       const InventoryItem& inventoryItem,
2431                                       const std::string& chassisId)
2432 {
2433     // Check if matching PowerSupply object already exists in JSON array
2434     for (nlohmann::json& powerSupply : powerSupplyArray)
2435     {
2436         if (powerSupply["MemberId"] == inventoryItem.name)
2437         {
2438             return powerSupply;
2439         }
2440     }
2441 
2442     // Add new PowerSupply object to JSON array
2443     powerSupplyArray.push_back({});
2444     nlohmann::json& powerSupply = powerSupplyArray.back();
2445     powerSupply["@odata.id"] =
2446         "/redfish/v1/Chassis/" + chassisId + "/Power#/PowerSupplies/";
2447     powerSupply["MemberId"] = inventoryItem.name;
2448     powerSupply["Name"] = boost::replace_all_copy(inventoryItem.name, "_", " ");
2449     powerSupply["Manufacturer"] = inventoryItem.manufacturer;
2450     powerSupply["Model"] = inventoryItem.model;
2451     powerSupply["PartNumber"] = inventoryItem.partNumber;
2452     powerSupply["SerialNumber"] = inventoryItem.serialNumber;
2453     setLedState(powerSupply, &inventoryItem);
2454 
2455     if (inventoryItem.powerSupplyEfficiencyPercent >= 0)
2456     {
2457         powerSupply["EfficiencyPercent"] =
2458             inventoryItem.powerSupplyEfficiencyPercent;
2459     }
2460 
2461     powerSupply["Status"]["State"] = getState(&inventoryItem);
2462     const char* health = inventoryItem.isFunctional ? "OK" : "Critical";
2463     powerSupply["Status"]["Health"] = health;
2464 
2465     return powerSupply;
2466 }
2467 
2468 /**
2469  * @brief Gets the values of the specified sensors.
2470  *
2471  * Stores the results as JSON in the SensorsAsyncResp.
2472  *
2473  * Gets the sensor values asynchronously.  Stores the results later when the
2474  * information has been obtained.
2475  *
2476  * The sensorNames set contains all requested sensors for the current chassis.
2477  *
2478  * To minimize the number of DBus calls, the DBus method
2479  * org.freedesktop.DBus.ObjectManager.GetManagedObjects() is used to get the
2480  * values of all sensors provided by a connection (service).
2481  *
2482  * The connections set contains all the connections that provide sensor values.
2483  *
2484  * The objectMgrPaths map contains mappings from a connection name to the
2485  * corresponding DBus object path that implements ObjectManager.
2486  *
2487  * The InventoryItem vector contains D-Bus inventory items associated with the
2488  * sensors.  Inventory item data is needed for some Redfish sensor properties.
2489  *
2490  * @param SensorsAsyncResp Pointer to object holding response data.
2491  * @param sensorNames All requested sensors within the current chassis.
2492  * @param connections Connections that provide sensor values.
2493  * @param objectMgrPaths Mappings from connection name to DBus object path that
2494  * implements ObjectManager.
2495  * @param inventoryItems Inventory items associated with the sensors.
2496  */
2497 inline void getSensorData(
2498     const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp,
2499     const std::shared_ptr<boost::container::flat_set<std::string>>& sensorNames,
2500     const boost::container::flat_set<std::string>& connections,
2501     const std::shared_ptr<boost::container::flat_map<std::string, std::string>>&
2502         objectMgrPaths,
2503     const std::shared_ptr<std::vector<InventoryItem>>& inventoryItems)
2504 {
2505     BMCWEB_LOG_DEBUG << "getSensorData enter";
2506     // Get managed objects from all services exposing sensors
2507     for (const std::string& connection : connections)
2508     {
2509         // Response handler to process managed objects
2510         auto getManagedObjectsCb = [sensorsAsyncResp, sensorNames,
2511                                     inventoryItems](
2512                                        const boost::system::error_code ec,
2513                                        dbus::utility::ManagedObjectType& resp) {
2514             BMCWEB_LOG_DEBUG << "getManagedObjectsCb enter";
2515             if (ec)
2516             {
2517                 BMCWEB_LOG_ERROR << "getManagedObjectsCb DBUS error: " << ec;
2518                 messages::internalError(sensorsAsyncResp->asyncResp->res);
2519                 return;
2520             }
2521             // Go through all objects and update response with sensor data
2522             for (const auto& objDictEntry : resp)
2523             {
2524                 const std::string& objPath =
2525                     static_cast<const std::string&>(objDictEntry.first);
2526                 BMCWEB_LOG_DEBUG << "getManagedObjectsCb parsing object "
2527                                  << objPath;
2528 
2529                 std::vector<std::string> split;
2530                 // Reserve space for
2531                 // /xyz/openbmc_project/sensors/<name>/<subname>
2532                 split.reserve(6);
2533                 boost::algorithm::split(split, objPath, boost::is_any_of("/"));
2534                 if (split.size() < 6)
2535                 {
2536                     BMCWEB_LOG_ERROR << "Got path that isn't long enough "
2537                                      << objPath;
2538                     continue;
2539                 }
2540                 // These indexes aren't intuitive, as boost::split puts an empty
2541                 // string at the beginning
2542                 const std::string& sensorType = split[4];
2543                 const std::string& sensorName = split[5];
2544                 BMCWEB_LOG_DEBUG << "sensorName " << sensorName
2545                                  << " sensorType " << sensorType;
2546                 if (sensorNames->find(objPath) == sensorNames->end())
2547                 {
2548                     BMCWEB_LOG_DEBUG << sensorName << " not in sensor list ";
2549                     continue;
2550                 }
2551 
2552                 // Find inventory item (if any) associated with sensor
2553                 InventoryItem* inventoryItem =
2554                     findInventoryItemForSensor(inventoryItems, objPath);
2555 
2556                 const std::string& sensorSchema =
2557                     sensorsAsyncResp->chassisSubNode;
2558 
2559                 nlohmann::json* sensorJson = nullptr;
2560 
2561                 if (sensorSchema == sensors::node::sensors &&
2562                     !sensorsAsyncResp->efficientExpand)
2563                 {
2564                     sensorsAsyncResp->asyncResp->res.jsonValue["@odata.id"] =
2565                         "/redfish/v1/Chassis/" + sensorsAsyncResp->chassisId +
2566                         "/" + sensorsAsyncResp->chassisSubNode + "/" +
2567                         sensorName;
2568                     sensorJson = &(sensorsAsyncResp->asyncResp->res.jsonValue);
2569                 }
2570                 else
2571                 {
2572                     std::string fieldName;
2573                     if (sensorsAsyncResp->efficientExpand)
2574                     {
2575                         fieldName = "Members";
2576                     }
2577                     else if (sensorType == "temperature")
2578                     {
2579                         fieldName = "Temperatures";
2580                     }
2581                     else if (sensorType == "fan" || sensorType == "fan_tach" ||
2582                              sensorType == "fan_pwm")
2583                     {
2584                         fieldName = "Fans";
2585                     }
2586                     else if (sensorType == "voltage")
2587                     {
2588                         fieldName = "Voltages";
2589                     }
2590                     else if (sensorType == "power")
2591                     {
2592                         if (sensorName == "total_power")
2593                         {
2594                             fieldName = "PowerControl";
2595                         }
2596                         else if ((inventoryItem != nullptr) &&
2597                                  (inventoryItem->isPowerSupply))
2598                         {
2599                             fieldName = "PowerSupplies";
2600                         }
2601                         else
2602                         {
2603                             // Other power sensors are in SensorCollection
2604                             continue;
2605                         }
2606                     }
2607                     else
2608                     {
2609                         BMCWEB_LOG_ERROR << "Unsure how to handle sensorType "
2610                                          << sensorType;
2611                         continue;
2612                     }
2613 
2614                     nlohmann::json& tempArray =
2615                         sensorsAsyncResp->asyncResp->res.jsonValue[fieldName];
2616                     if (fieldName == "PowerControl")
2617                     {
2618                         if (tempArray.empty())
2619                         {
2620                             // Put multiple "sensors" into a single
2621                             // PowerControl. Follows MemberId naming and
2622                             // naming in power.hpp.
2623                             nlohmann::json::object_t power;
2624                             power["@odata.id"] =
2625                                 "/redfish/v1/Chassis/" +
2626                                 sensorsAsyncResp->chassisId + "/" +
2627                                 sensorsAsyncResp->chassisSubNode + "#/" +
2628                                 fieldName + "/0";
2629                             tempArray.push_back(std::move(power));
2630                         }
2631                         sensorJson = &(tempArray.back());
2632                     }
2633                     else if (fieldName == "PowerSupplies")
2634                     {
2635                         if (inventoryItem != nullptr)
2636                         {
2637                             sensorJson =
2638                                 &(getPowerSupply(tempArray, *inventoryItem,
2639                                                  sensorsAsyncResp->chassisId));
2640                         }
2641                     }
2642                     else if (fieldName == "Members")
2643                     {
2644                         nlohmann::json::object_t member;
2645                         member["@odata.id"] =
2646                             "/redfish/v1/Chassis/" +
2647                             sensorsAsyncResp->chassisId + "/" +
2648                             sensorsAsyncResp->chassisSubNode + "/" + sensorName;
2649                         tempArray.push_back(std::move(member));
2650                         sensorJson = &(tempArray.back());
2651                     }
2652                     else
2653                     {
2654                         nlohmann::json::object_t member;
2655                         member["@odata.id"] = "/redfish/v1/Chassis/" +
2656                                               sensorsAsyncResp->chassisId +
2657                                               "/" +
2658                                               sensorsAsyncResp->chassisSubNode +
2659                                               "#/" + fieldName + "/";
2660                         tempArray.push_back(std::move(member));
2661                         sensorJson = &(tempArray.back());
2662                     }
2663                 }
2664 
2665                 if (sensorJson != nullptr)
2666                 {
2667                     objectInterfacesToJson(
2668                         sensorName, sensorType, sensorsAsyncResp,
2669                         objDictEntry.second, *sensorJson, inventoryItem);
2670                 }
2671             }
2672             if (sensorsAsyncResp.use_count() == 1)
2673             {
2674                 sortJSONResponse(sensorsAsyncResp);
2675                 if (sensorsAsyncResp->chassisSubNode ==
2676                         sensors::node::sensors &&
2677                     sensorsAsyncResp->efficientExpand)
2678                 {
2679                     sensorsAsyncResp->asyncResp->res
2680                         .jsonValue["Members@odata.count"] =
2681                         sensorsAsyncResp->asyncResp->res.jsonValue["Members"]
2682                             .size();
2683                 }
2684                 else if (sensorsAsyncResp->chassisSubNode ==
2685                          sensors::node::thermal)
2686                 {
2687                     populateFanRedundancy(sensorsAsyncResp);
2688                 }
2689             }
2690             BMCWEB_LOG_DEBUG << "getManagedObjectsCb exit";
2691         };
2692 
2693         // Find DBus object path that implements ObjectManager for the current
2694         // connection.  If no mapping found, default to "/".
2695         auto iter = objectMgrPaths->find(connection);
2696         const std::string& objectMgrPath =
2697             (iter != objectMgrPaths->end()) ? iter->second : "/";
2698         BMCWEB_LOG_DEBUG << "ObjectManager path for " << connection << " is "
2699                          << objectMgrPath;
2700 
2701         crow::connections::systemBus->async_method_call(
2702             getManagedObjectsCb, connection, objectMgrPath,
2703             "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
2704     }
2705     BMCWEB_LOG_DEBUG << "getSensorData exit";
2706 }
2707 
2708 inline void processSensorList(
2709     const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp,
2710     const std::shared_ptr<boost::container::flat_set<std::string>>& sensorNames)
2711 {
2712     auto getConnectionCb =
2713         [sensorsAsyncResp, sensorNames](
2714             const boost::container::flat_set<std::string>& connections) {
2715             BMCWEB_LOG_DEBUG << "getConnectionCb enter";
2716             auto getObjectManagerPathsCb =
2717                 [sensorsAsyncResp, sensorNames,
2718                  connections](const std::shared_ptr<boost::container::flat_map<
2719                                   std::string, std::string>>& objectMgrPaths) {
2720                     BMCWEB_LOG_DEBUG << "getObjectManagerPathsCb enter";
2721                     auto getInventoryItemsCb =
2722                         [sensorsAsyncResp, sensorNames, connections,
2723                          objectMgrPaths](
2724                             const std::shared_ptr<std::vector<InventoryItem>>&
2725                                 inventoryItems) {
2726                             BMCWEB_LOG_DEBUG << "getInventoryItemsCb enter";
2727                             // Get sensor data and store results in JSON
2728                             getSensorData(sensorsAsyncResp, sensorNames,
2729                                           connections, objectMgrPaths,
2730                                           inventoryItems);
2731                             BMCWEB_LOG_DEBUG << "getInventoryItemsCb exit";
2732                         };
2733 
2734                     // Get inventory items associated with sensors
2735                     getInventoryItems(sensorsAsyncResp, sensorNames,
2736                                       objectMgrPaths,
2737                                       std::move(getInventoryItemsCb));
2738 
2739                     BMCWEB_LOG_DEBUG << "getObjectManagerPathsCb exit";
2740                 };
2741 
2742             // Get mapping from connection names to the DBus object
2743             // paths that implement the ObjectManager interface
2744             getObjectManagerPaths(sensorsAsyncResp,
2745                                   std::move(getObjectManagerPathsCb));
2746             BMCWEB_LOG_DEBUG << "getConnectionCb exit";
2747         };
2748 
2749     // Get set of connections that provide sensor values
2750     getConnections(sensorsAsyncResp, sensorNames, std::move(getConnectionCb));
2751 }
2752 
2753 /**
2754  * @brief Entry point for retrieving sensors data related to requested
2755  *        chassis.
2756  * @param SensorsAsyncResp   Pointer to object holding response data
2757  */
2758 inline void
2759     getChassisData(const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp)
2760 {
2761     BMCWEB_LOG_DEBUG << "getChassisData enter";
2762     auto getChassisCb =
2763         [sensorsAsyncResp](
2764             const std::shared_ptr<boost::container::flat_set<std::string>>&
2765                 sensorNames) {
2766             BMCWEB_LOG_DEBUG << "getChassisCb enter";
2767             processSensorList(sensorsAsyncResp, sensorNames);
2768             BMCWEB_LOG_DEBUG << "getChassisCb exit";
2769         };
2770     // SensorCollection doesn't contain the Redundancy property
2771     if (sensorsAsyncResp->chassisSubNode != sensors::node::sensors)
2772     {
2773         sensorsAsyncResp->asyncResp->res.jsonValue["Redundancy"] =
2774             nlohmann::json::array();
2775     }
2776     // Get set of sensors in chassis
2777     getChassis(sensorsAsyncResp, std::move(getChassisCb));
2778     BMCWEB_LOG_DEBUG << "getChassisData exit";
2779 }
2780 
2781 /**
2782  * @brief Find the requested sensorName in the list of all sensors supplied by
2783  * the chassis node
2784  *
2785  * @param sensorName   The sensor name supplied in the PATCH request
2786  * @param sensorsList  The list of sensors managed by the chassis node
2787  * @param sensorsModified  The list of sensors that were found as a result of
2788  *                         repeated calls to this function
2789  */
2790 inline bool findSensorNameUsingSensorPath(
2791     std::string_view sensorName,
2792     boost::container::flat_set<std::string>& sensorsList,
2793     boost::container::flat_set<std::string>& sensorsModified)
2794 {
2795     for (auto& chassisSensor : sensorsList)
2796     {
2797         sdbusplus::message::object_path path(chassisSensor);
2798         std::string thisSensorName = path.filename();
2799         if (thisSensorName.empty())
2800         {
2801             continue;
2802         }
2803         if (thisSensorName == sensorName)
2804         {
2805             sensorsModified.emplace(chassisSensor);
2806             return true;
2807         }
2808     }
2809     return false;
2810 }
2811 
2812 /**
2813  * @brief Entry point for overriding sensor values of given sensor
2814  *
2815  * @param sensorAsyncResp   response object
2816  * @param allCollections   Collections extract from sensors' request patch info
2817  * @param chassisSubNode   Chassis Node for which the query has to happen
2818  */
2819 inline void setSensorsOverride(
2820     const std::shared_ptr<SensorsAsyncResp>& sensorAsyncResp,
2821     std::unordered_map<std::string, std::vector<nlohmann::json>>&
2822         allCollections)
2823 {
2824     BMCWEB_LOG_INFO << "setSensorsOverride for subNode"
2825                     << sensorAsyncResp->chassisSubNode << "\n";
2826 
2827     const char* propertyValueName = nullptr;
2828     std::unordered_map<std::string, std::pair<double, std::string>> overrideMap;
2829     std::string memberId;
2830     double value = 0.0;
2831     for (auto& collectionItems : allCollections)
2832     {
2833         if (collectionItems.first == "Temperatures")
2834         {
2835             propertyValueName = "ReadingCelsius";
2836         }
2837         else if (collectionItems.first == "Fans")
2838         {
2839             propertyValueName = "Reading";
2840         }
2841         else
2842         {
2843             propertyValueName = "ReadingVolts";
2844         }
2845         for (auto& item : collectionItems.second)
2846         {
2847             if (!json_util::readJson(item, sensorAsyncResp->asyncResp->res,
2848                                      "MemberId", memberId, propertyValueName,
2849                                      value))
2850             {
2851                 return;
2852             }
2853             overrideMap.emplace(memberId,
2854                                 std::make_pair(value, collectionItems.first));
2855         }
2856     }
2857 
2858     auto getChassisSensorListCb = [sensorAsyncResp, overrideMap](
2859                                       const std::shared_ptr<
2860                                           boost::container::flat_set<
2861                                               std::string>>& sensorsList) {
2862         // Match sensor names in the PATCH request to those managed by the
2863         // chassis node
2864         const std::shared_ptr<boost::container::flat_set<std::string>>
2865             sensorNames =
2866                 std::make_shared<boost::container::flat_set<std::string>>();
2867         for (const auto& item : overrideMap)
2868         {
2869             const auto& sensor = item.first;
2870             if (!findSensorNameUsingSensorPath(sensor, *sensorsList,
2871                                                *sensorNames))
2872             {
2873                 BMCWEB_LOG_INFO << "Unable to find memberId " << item.first;
2874                 messages::resourceNotFound(sensorAsyncResp->asyncResp->res,
2875                                            item.second.second, item.first);
2876                 return;
2877             }
2878         }
2879         // Get the connection to which the memberId belongs
2880         auto getObjectsWithConnectionCb = [sensorAsyncResp, overrideMap](
2881                                               const boost::container::flat_set<
2882                                                   std::string>& /*connections*/,
2883                                               const std::set<std::pair<
2884                                                   std::string, std::string>>&
2885                                                   objectsWithConnection) {
2886             if (objectsWithConnection.size() != overrideMap.size())
2887             {
2888                 BMCWEB_LOG_INFO
2889                     << "Unable to find all objects with proper connection "
2890                     << objectsWithConnection.size() << " requested "
2891                     << overrideMap.size() << "\n";
2892                 messages::resourceNotFound(sensorAsyncResp->asyncResp->res,
2893                                            sensorAsyncResp->chassisSubNode ==
2894                                                    sensors::node::thermal
2895                                                ? "Temperatures"
2896                                                : "Voltages",
2897                                            "Count");
2898                 return;
2899             }
2900             for (const auto& item : objectsWithConnection)
2901             {
2902                 sdbusplus::message::object_path path(item.first);
2903                 std::string sensorName = path.filename();
2904                 if (sensorName.empty())
2905                 {
2906                     messages::internalError(sensorAsyncResp->asyncResp->res);
2907                     return;
2908                 }
2909 
2910                 const auto& iterator = overrideMap.find(sensorName);
2911                 if (iterator == overrideMap.end())
2912                 {
2913                     BMCWEB_LOG_INFO << "Unable to find sensor object"
2914                                     << item.first << "\n";
2915                     messages::internalError(sensorAsyncResp->asyncResp->res);
2916                     return;
2917                 }
2918                 crow::connections::systemBus->async_method_call(
2919                     [sensorAsyncResp](const boost::system::error_code ec) {
2920                         if (ec)
2921                         {
2922                             if (ec.value() ==
2923                                 boost::system::errc::permission_denied)
2924                             {
2925                                 BMCWEB_LOG_WARNING
2926                                     << "Manufacturing mode is not Enabled...can't "
2927                                        "Override the sensor value. ";
2928 
2929                                 messages::insufficientPrivilege(
2930                                     sensorAsyncResp->asyncResp->res);
2931                                 return;
2932                             }
2933                             BMCWEB_LOG_DEBUG
2934                                 << "setOverrideValueStatus DBUS error: " << ec;
2935                             messages::internalError(
2936                                 sensorAsyncResp->asyncResp->res);
2937                         }
2938                     },
2939                     item.second, item.first, "org.freedesktop.DBus.Properties",
2940                     "Set", "xyz.openbmc_project.Sensor.Value", "Value",
2941                     dbus::utility::DbusVariantType(iterator->second.first));
2942             }
2943         };
2944         // Get object with connection for the given sensor name
2945         getObjectsWithConnection(sensorAsyncResp, sensorNames,
2946                                  std::move(getObjectsWithConnectionCb));
2947     };
2948     // get full sensor list for the given chassisId and cross verify the sensor.
2949     getChassis(sensorAsyncResp, std::move(getChassisSensorListCb));
2950 }
2951 
2952 /**
2953  * @brief Retrieves mapping of Redfish URIs to sensor value property to D-Bus
2954  * path of the sensor.
2955  *
2956  * Function builds valid Redfish response for sensor query of given chassis and
2957  * node. It then builds metadata about Redfish<->D-Bus correlations and provides
2958  * it to caller in a callback.
2959  *
2960  * @param chassis   Chassis for which retrieval should be performed
2961  * @param node  Node (group) of sensors. See sensors::node for supported values
2962  * @param mapComplete   Callback to be called with retrieval result
2963  */
2964 inline void retrieveUriToDbusMap(const std::string& chassis,
2965                                  const std::string& node,
2966                                  SensorsAsyncResp::DataCompleteCb&& mapComplete)
2967 {
2968     decltype(sensors::paths)::const_iterator pathIt =
2969         std::find_if(sensors::paths.cbegin(), sensors::paths.cend(),
2970                      [&node](auto&& val) { return val.first == node; });
2971     if (pathIt == sensors::paths.cend())
2972     {
2973         BMCWEB_LOG_ERROR << "Wrong node provided : " << node;
2974         mapComplete(boost::beast::http::status::bad_request, {});
2975         return;
2976     }
2977 
2978     auto asyncResp = std::make_shared<bmcweb::AsyncResp>();
2979     auto callback =
2980         [asyncResp, mapCompleteCb{std::move(mapComplete)}](
2981             const boost::beast::http::status status,
2982             const boost::container::flat_map<std::string, std::string>&
2983                 uriToDbus) { mapCompleteCb(status, uriToDbus); };
2984 
2985     auto resp = std::make_shared<SensorsAsyncResp>(
2986         asyncResp, chassis, pathIt->second, node, std::move(callback));
2987     getChassisData(resp);
2988 }
2989 
2990 namespace sensors
2991 {
2992 
2993 inline void getChassisCallback(
2994     const std::shared_ptr<SensorsAsyncResp>& asyncResp,
2995     const std::shared_ptr<boost::container::flat_set<std::string>>& sensorNames)
2996 {
2997     BMCWEB_LOG_DEBUG << "getChassisCallback enter";
2998 
2999     nlohmann::json& entriesArray =
3000         asyncResp->asyncResp->res.jsonValue["Members"];
3001     for (auto& sensor : *sensorNames)
3002     {
3003         BMCWEB_LOG_DEBUG << "Adding sensor: " << sensor;
3004 
3005         sdbusplus::message::object_path path(sensor);
3006         std::string sensorName = path.filename();
3007         if (sensorName.empty())
3008         {
3009             BMCWEB_LOG_ERROR << "Invalid sensor path: " << sensor;
3010             messages::internalError(asyncResp->asyncResp->res);
3011             return;
3012         }
3013         nlohmann::json::object_t member;
3014         member["@odata.id"] = "/redfish/v1/Chassis/" + asyncResp->chassisId +
3015                               "/" + asyncResp->chassisSubNode + "/" +
3016                               sensorName;
3017         entriesArray.push_back(std::move(member));
3018     }
3019 
3020     asyncResp->asyncResp->res.jsonValue["Members@odata.count"] =
3021         entriesArray.size();
3022     BMCWEB_LOG_DEBUG << "getChassisCallback exit";
3023 }
3024 } // namespace sensors
3025 
3026 inline void requestRoutesSensorCollection(App& app)
3027 {
3028     BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/Sensors/")
3029         .privileges(redfish::privileges::getSensorCollection)
3030         .methods(
3031             boost::beast::http::verb::get)([&app](
3032                                                const crow::Request& req,
3033                                                const std::shared_ptr<
3034                                                    bmcweb::AsyncResp>& aResp,
3035                                                const std::string& chassisId) {
3036             query_param::QueryCapabilities capabilities = {
3037                 .canDelegateExpandLevel = 1,
3038             };
3039             query_param::Query delegatedQuery;
3040             if (!redfish::setUpRedfishRouteWithDelegation(
3041                     app, req, aResp->res, delegatedQuery, capabilities))
3042             {
3043                 return;
3044             }
3045 
3046             if (delegatedQuery.expandType != query_param::ExpandType::None)
3047             {
3048                 // we perform efficient expand.
3049                 auto asyncResp = std::make_shared<SensorsAsyncResp>(
3050                     aResp, chassisId, sensors::dbus::sensorPaths,
3051                     sensors::node::sensors,
3052                     /*efficientExpand=*/true);
3053                 getChassisData(asyncResp);
3054 
3055                 BMCWEB_LOG_DEBUG
3056                     << "SensorCollection doGet exit via efficient expand handler";
3057                 return;
3058             };
3059 
3060             // if there's no efficient expand available, we use the default
3061             // Query Parameters route
3062             auto asyncResp = std::make_shared<SensorsAsyncResp>(
3063                 aResp, chassisId, sensors::dbus::sensorPaths,
3064                 sensors::node::sensors);
3065 
3066             // We get all sensors as hyperlinkes in the chassis (this
3067             // implies we reply on the default query parameters handler)
3068             getChassis(asyncResp,
3069                        std::bind_front(sensors::getChassisCallback, asyncResp));
3070             BMCWEB_LOG_DEBUG << "SensorCollection doGet exit";
3071         });
3072 }
3073 
3074 inline void requestRoutesSensor(App& app)
3075 {
3076     BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/Sensors/<str>/")
3077         .privileges(redfish::privileges::getSensor)
3078         .methods(
3079             boost::beast::http::verb::get)([&app](
3080                                                const crow::Request& req,
3081                                                const std::shared_ptr<
3082                                                    bmcweb::AsyncResp>& aResp,
3083                                                const std::string& chassisId,
3084                                                const std::string& sensorName) {
3085             if (!redfish::setUpRedfishRoute(app, req, aResp->res))
3086             {
3087                 return;
3088             }
3089             BMCWEB_LOG_DEBUG << "Sensor doGet enter";
3090             std::shared_ptr<SensorsAsyncResp> asyncResp =
3091                 std::make_shared<SensorsAsyncResp>(
3092                     aResp, chassisId, std::span<std::string_view>(),
3093                     sensors::node::sensors);
3094 
3095             const std::array<const char*, 1> interfaces = {
3096                 "xyz.openbmc_project.Sensor.Value"};
3097 
3098             // Get a list of all of the sensors that implement Sensor.Value
3099             // and get the path and service name associated with the sensor
3100             crow::connections::systemBus->async_method_call(
3101                 [asyncResp, sensorName](
3102                     const boost::system::error_code ec,
3103                     const dbus::utility::MapperGetSubTreeResponse& subtree) {
3104                     BMCWEB_LOG_DEBUG << "respHandler1 enter";
3105                     if (ec)
3106                     {
3107                         messages::internalError(asyncResp->asyncResp->res);
3108                         BMCWEB_LOG_ERROR
3109                             << "Sensor getSensorPaths resp_handler: "
3110                             << "Dbus error " << ec;
3111                         return;
3112                     }
3113 
3114                     dbus::utility::MapperGetSubTreeResponse::const_iterator it =
3115                         std::find_if(
3116                             subtree.begin(), subtree.end(),
3117                             [sensorName](
3118                                 const std::pair<std::string,
3119                                                 std::vector<std::pair<
3120                                                     std::string,
3121                                                     std::vector<std::string>>>>&
3122                                     object) {
3123                                 sdbusplus::message::object_path path(
3124                                     object.first);
3125                                 std::string name = path.filename();
3126                                 if (name.empty())
3127                                 {
3128                                     BMCWEB_LOG_ERROR << "Invalid sensor path: "
3129                                                      << object.first;
3130                                     return false;
3131                                 }
3132 
3133                                 return name == sensorName;
3134                             });
3135 
3136                     if (it == subtree.end())
3137                     {
3138                         BMCWEB_LOG_ERROR << "Could not find path for sensor: "
3139                                          << sensorName;
3140                         messages::resourceNotFound(asyncResp->asyncResp->res,
3141                                                    "Sensor", sensorName);
3142                         return;
3143                     }
3144                     std::string_view sensorPath = (*it).first;
3145                     BMCWEB_LOG_DEBUG << "Found sensor path for sensor '"
3146                                      << sensorName << "': " << sensorPath;
3147 
3148                     const std::shared_ptr<
3149                         boost::container::flat_set<std::string>>
3150                         sensorList = std::make_shared<
3151                             boost::container::flat_set<std::string>>();
3152 
3153                     sensorList->emplace(sensorPath);
3154                     processSensorList(asyncResp, sensorList);
3155                     BMCWEB_LOG_DEBUG << "respHandler1 exit";
3156                 },
3157                 "xyz.openbmc_project.ObjectMapper",
3158                 "/xyz/openbmc_project/object_mapper",
3159                 "xyz.openbmc_project.ObjectMapper", "GetSubTree",
3160                 "/xyz/openbmc_project/sensors", 2, interfaces);
3161         });
3162 }
3163 
3164 } // namespace redfish
3165