xref: /openbmc/bmcweb/redfish-core/lib/sensors.hpp (revision 25ce6206)
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 "dbus_singleton.hpp"
20 #include "dbus_utility.hpp"
21 #include "generated/enums/redundancy.hpp"
22 #include "generated/enums/resource.hpp"
23 #include "query.hpp"
24 #include "registries/privilege_registry.hpp"
25 #include "str_utility.hpp"
26 #include "utils/dbus_utils.hpp"
27 #include "utils/json_utils.hpp"
28 #include "utils/query_param.hpp"
29 #include "utils/sensor_utils.hpp"
30 
31 #include <boost/system/error_code.hpp>
32 #include <boost/url/format.hpp>
33 #include <sdbusplus/asio/property.hpp>
34 #include <sdbusplus/unpack_properties.hpp>
35 
36 #include <array>
37 #include <cmath>
38 #include <iterator>
39 #include <limits>
40 #include <map>
41 #include <ranges>
42 #include <set>
43 #include <string>
44 #include <string_view>
45 #include <utility>
46 #include <variant>
47 
48 namespace redfish
49 {
50 
51 namespace sensors
52 {
53 
54 // clang-format off
55 namespace dbus
56 {
57 constexpr auto powerPaths = std::to_array<std::string_view>({
58     "/xyz/openbmc_project/sensors/voltage",
59     "/xyz/openbmc_project/sensors/power"
60 });
61 
62 constexpr auto getSensorPaths(){
63     if constexpr(BMCWEB_REDFISH_NEW_POWERSUBSYSTEM_THERMALSUBSYSTEM){
64     return std::to_array<std::string_view>({
65         "/xyz/openbmc_project/sensors/power",
66         "/xyz/openbmc_project/sensors/current",
67         "/xyz/openbmc_project/sensors/airflow",
68         "/xyz/openbmc_project/sensors/humidity",
69         "/xyz/openbmc_project/sensors/voltage",
70         "/xyz/openbmc_project/sensors/fan_tach",
71         "/xyz/openbmc_project/sensors/temperature",
72         "/xyz/openbmc_project/sensors/fan_pwm",
73         "/xyz/openbmc_project/sensors/altitude",
74         "/xyz/openbmc_project/sensors/energy",
75         "/xyz/openbmc_project/sensors/utilization"});
76     } else {
77       return  std::to_array<std::string_view>({"/xyz/openbmc_project/sensors/power",
78         "/xyz/openbmc_project/sensors/current",
79         "/xyz/openbmc_project/sensors/airflow",
80         "/xyz/openbmc_project/sensors/humidity",
81         "/xyz/openbmc_project/sensors/utilization"});
82 }
83 }
84 
85 constexpr auto sensorPaths = getSensorPaths();
86 
87 constexpr auto thermalPaths = std::to_array<std::string_view>({
88     "/xyz/openbmc_project/sensors/fan_tach",
89     "/xyz/openbmc_project/sensors/temperature",
90     "/xyz/openbmc_project/sensors/fan_pwm"
91 });
92 
93 } // namespace dbus
94 // clang-format on
95 
96 constexpr std::string_view powerNodeStr = sensor_utils::chassisSubNodeToString(
97     sensor_utils::ChassisSubNode::powerNode);
98 constexpr std::string_view sensorsNodeStr =
99     sensor_utils::chassisSubNodeToString(
100         sensor_utils::ChassisSubNode::sensorsNode);
101 constexpr std::string_view thermalNodeStr =
102     sensor_utils::chassisSubNodeToString(
103         sensor_utils::ChassisSubNode::thermalNode);
104 
105 using sensorPair =
106     std::pair<std::string_view, std::span<const std::string_view>>;
107 static constexpr std::array<sensorPair, 3> paths = {
108     {{sensors::powerNodeStr, dbus::powerPaths},
109      {sensors::sensorsNodeStr, dbus::sensorPaths},
110      {sensors::thermalNodeStr, dbus::thermalPaths}}};
111 
112 } // namespace sensors
113 
114 /**
115  * SensorsAsyncResp
116  * Gathers data needed for response processing after async calls are done
117  */
118 class SensorsAsyncResp
119 {
120   public:
121     using DataCompleteCb = std::function<void(
122         const boost::beast::http::status status,
123         const std::map<std::string, std::string>& uriToDbus)>;
124 
125     struct SensorData
126     {
127         const std::string name;
128         std::string uri;
129         const std::string dbusPath;
130     };
131 
132     SensorsAsyncResp(const std::shared_ptr<bmcweb::AsyncResp>& asyncRespIn,
133                      const std::string& chassisIdIn,
134                      std::span<const std::string_view> typesIn,
135                      std::string_view subNode) :
136         asyncResp(asyncRespIn), chassisId(chassisIdIn), types(typesIn),
137         chassisSubNode(subNode), efficientExpand(false)
138     {}
139 
140     // Store extra data about sensor mapping and return it in callback
141     SensorsAsyncResp(const std::shared_ptr<bmcweb::AsyncResp>& asyncRespIn,
142                      const std::string& chassisIdIn,
143                      std::span<const std::string_view> typesIn,
144                      std::string_view subNode,
145                      DataCompleteCb&& creationComplete) :
146         asyncResp(asyncRespIn), chassisId(chassisIdIn), types(typesIn),
147         chassisSubNode(subNode), efficientExpand(false),
148         metadata{std::vector<SensorData>()},
149         dataComplete{std::move(creationComplete)}
150     {}
151 
152     // sensor collections expand
153     SensorsAsyncResp(const std::shared_ptr<bmcweb::AsyncResp>& asyncRespIn,
154                      const std::string& chassisIdIn,
155                      std::span<const std::string_view> typesIn,
156                      const std::string_view& subNode, bool efficientExpandIn) :
157         asyncResp(asyncRespIn), chassisId(chassisIdIn), types(typesIn),
158         chassisSubNode(subNode), efficientExpand(efficientExpandIn)
159     {}
160 
161     ~SensorsAsyncResp()
162     {
163         if (asyncResp->res.result() ==
164             boost::beast::http::status::internal_server_error)
165         {
166             // Reset the json object to clear out any data that made it in
167             // before the error happened todo(ed) handle error condition with
168             // proper code
169             asyncResp->res.jsonValue = nlohmann::json::object();
170         }
171 
172         if (dataComplete && metadata)
173         {
174             std::map<std::string, std::string> map;
175             if (asyncResp->res.result() == boost::beast::http::status::ok)
176             {
177                 for (auto& sensor : *metadata)
178                 {
179                     map.emplace(sensor.uri, sensor.dbusPath);
180                 }
181             }
182             dataComplete(asyncResp->res.result(), map);
183         }
184     }
185 
186     SensorsAsyncResp(const SensorsAsyncResp&) = delete;
187     SensorsAsyncResp(SensorsAsyncResp&&) = delete;
188     SensorsAsyncResp& operator=(const SensorsAsyncResp&) = delete;
189     SensorsAsyncResp& operator=(SensorsAsyncResp&&) = delete;
190 
191     void addMetadata(const nlohmann::json& sensorObject,
192                      const std::string& dbusPath)
193     {
194         if (metadata)
195         {
196             metadata->emplace_back(SensorData{
197                 sensorObject["Name"], sensorObject["@odata.id"], dbusPath});
198         }
199     }
200 
201     void updateUri(const std::string& name, const std::string& uri)
202     {
203         if (metadata)
204         {
205             for (auto& sensor : *metadata)
206             {
207                 if (sensor.name == name)
208                 {
209                     sensor.uri = uri;
210                 }
211             }
212         }
213     }
214 
215     const std::shared_ptr<bmcweb::AsyncResp> asyncResp;
216     const std::string chassisId;
217     const std::span<const std::string_view> types;
218     const std::string chassisSubNode;
219     const bool efficientExpand;
220 
221   private:
222     std::optional<std::vector<SensorData>> metadata;
223     DataCompleteCb dataComplete;
224 };
225 
226 using InventoryItem = sensor_utils::InventoryItem;
227 
228 /**
229  * @brief Get objects with connection necessary for sensors
230  * @param SensorsAsyncResp Pointer to object holding response data
231  * @param sensorNames Sensors retrieved from chassis
232  * @param callback Callback for processing gathered connections
233  */
234 template <typename Callback>
235 void getObjectsWithConnection(
236     const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp,
237     const std::shared_ptr<std::set<std::string>>& sensorNames,
238     Callback&& callback)
239 {
240     BMCWEB_LOG_DEBUG("getObjectsWithConnection enter");
241     const std::string path = "/xyz/openbmc_project/sensors";
242     constexpr std::array<std::string_view, 1> interfaces = {
243         "xyz.openbmc_project.Sensor.Value"};
244 
245     // Make call to ObjectMapper to find all sensors objects
246     dbus::utility::getSubTree(
247         path, 2, interfaces,
248         [callback = std::forward<Callback>(callback), sensorsAsyncResp,
249          sensorNames](const boost::system::error_code& ec,
250                       const dbus::utility::MapperGetSubTreeResponse& subtree) {
251             // Response handler for parsing objects subtree
252             BMCWEB_LOG_DEBUG("getObjectsWithConnection resp_handler enter");
253             if (ec)
254             {
255                 messages::internalError(sensorsAsyncResp->asyncResp->res);
256                 BMCWEB_LOG_ERROR(
257                     "getObjectsWithConnection resp_handler: Dbus error {}", ec);
258                 return;
259             }
260 
261             BMCWEB_LOG_DEBUG("Found {} subtrees", subtree.size());
262 
263             // Make unique list of connections only for requested sensor types
264             // and found in the chassis
265             std::set<std::string> connections;
266             std::set<std::pair<std::string, std::string>> objectsWithConnection;
267 
268             BMCWEB_LOG_DEBUG("sensorNames list count: {}", sensorNames->size());
269             for (const std::string& tsensor : *sensorNames)
270             {
271                 BMCWEB_LOG_DEBUG("Sensor to find: {}", tsensor);
272             }
273 
274             for (const std::pair<std::string,
275                                  std::vector<std::pair<
276                                      std::string, std::vector<std::string>>>>&
277                      object : subtree)
278             {
279                 if (sensorNames->find(object.first) != sensorNames->end())
280                 {
281                     for (const std::pair<std::string, std::vector<std::string>>&
282                              objData : object.second)
283                     {
284                         BMCWEB_LOG_DEBUG("Adding connection: {}",
285                                          objData.first);
286                         connections.insert(objData.first);
287                         objectsWithConnection.insert(
288                             std::make_pair(object.first, objData.first));
289                     }
290                 }
291             }
292             BMCWEB_LOG_DEBUG("Found {} connections", connections.size());
293             callback(std::move(connections), std::move(objectsWithConnection));
294             BMCWEB_LOG_DEBUG("getObjectsWithConnection resp_handler exit");
295         });
296     BMCWEB_LOG_DEBUG("getObjectsWithConnection exit");
297 }
298 
299 /**
300  * @brief Create connections necessary for sensors
301  * @param SensorsAsyncResp Pointer to object holding response data
302  * @param sensorNames Sensors retrieved from chassis
303  * @param callback Callback for processing gathered connections
304  */
305 template <typename Callback>
306 void getConnections(std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp,
307                     const std::shared_ptr<std::set<std::string>> sensorNames,
308                     Callback&& callback)
309 {
310     auto objectsWithConnectionCb =
311         [callback = std::forward<Callback>(callback)](
312             const std::set<std::string>& connections,
313             const std::set<std::pair<std::string, std::string>>&
314             /*objectsWithConnection*/) { callback(connections); };
315     getObjectsWithConnection(sensorsAsyncResp, sensorNames,
316                              std::move(objectsWithConnectionCb));
317 }
318 
319 /**
320  * @brief Shrinks the list of sensors for processing
321  * @param SensorsAysncResp  The class holding the Redfish response
322  * @param allSensors  A list of all the sensors associated to the
323  * chassis element (i.e. baseboard, front panel, etc...)
324  * @param activeSensors A list that is a reduction of the incoming
325  * allSensors list.  Eliminate Thermal sensors when a Power request is
326  * made, and eliminate Power sensors when a Thermal request is made.
327  */
328 inline void reduceSensorList(
329     crow::Response& res, std::string_view chassisSubNode,
330     std::span<const std::string_view> sensorTypes,
331     const std::vector<std::string>* allSensors,
332     const std::shared_ptr<std::set<std::string>>& activeSensors)
333 {
334     if ((allSensors == nullptr) || (activeSensors == nullptr))
335     {
336         messages::resourceNotFound(res, chassisSubNode,
337                                    chassisSubNode == sensors::thermalNodeStr
338                                        ? "Temperatures"
339                                        : "Voltages");
340 
341         return;
342     }
343     if (allSensors->empty())
344     {
345         // Nothing to do, the activeSensors object is also empty
346         return;
347     }
348 
349     for (std::string_view type : sensorTypes)
350     {
351         for (const std::string& sensor : *allSensors)
352         {
353             if (sensor.starts_with(type))
354             {
355                 activeSensors->emplace(sensor);
356             }
357         }
358     }
359 }
360 
361 /*
362  *Populates the top level collection for a given subnode.  Populates
363  *SensorCollection, Power, or Thermal schemas.
364  *
365  * */
366 inline void populateChassisNode(nlohmann::json& jsonValue,
367                                 std::string_view chassisSubNode)
368 {
369     if (chassisSubNode == sensors::powerNodeStr)
370     {
371         jsonValue["@odata.type"] = "#Power.v1_5_2.Power";
372     }
373     else if (chassisSubNode == sensors::thermalNodeStr)
374     {
375         jsonValue["@odata.type"] = "#Thermal.v1_4_0.Thermal";
376         jsonValue["Fans"] = nlohmann::json::array();
377         jsonValue["Temperatures"] = nlohmann::json::array();
378     }
379     else if (chassisSubNode == sensors::sensorsNodeStr)
380     {
381         jsonValue["@odata.type"] = "#SensorCollection.SensorCollection";
382         jsonValue["Description"] = "Collection of Sensors for this Chassis";
383         jsonValue["Members"] = nlohmann::json::array();
384         jsonValue["Members@odata.count"] = 0;
385     }
386 
387     if (chassisSubNode != sensors::sensorsNodeStr)
388     {
389         jsonValue["Id"] = chassisSubNode;
390     }
391     jsonValue["Name"] = chassisSubNode;
392 }
393 
394 /**
395  * @brief Retrieves requested chassis sensors and redundancy data from DBus .
396  * @param SensorsAsyncResp   Pointer to object holding response data
397  * @param callback  Callback for next step in gathered sensor processing
398  */
399 template <typename Callback>
400 void getChassis(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
401                 std::string_view chassisId, std::string_view chassisSubNode,
402                 std::span<const std::string_view> sensorTypes,
403                 Callback&& callback)
404 {
405     BMCWEB_LOG_DEBUG("getChassis enter");
406     constexpr std::array<std::string_view, 2> interfaces = {
407         "xyz.openbmc_project.Inventory.Item.Board",
408         "xyz.openbmc_project.Inventory.Item.Chassis"};
409 
410     // Get the Chassis Collection
411     dbus::utility::getSubTreePaths(
412         "/xyz/openbmc_project/inventory", 0, interfaces,
413         [callback = std::forward<Callback>(callback), asyncResp,
414          chassisIdStr{std::string(chassisId)},
415          chassisSubNode{std::string(chassisSubNode)},
416          sensorTypes](const boost::system::error_code& ec,
417                       const dbus::utility::MapperGetSubTreePathsResponse&
418                           chassisPaths) mutable {
419             BMCWEB_LOG_DEBUG("getChassis respHandler enter");
420             if (ec)
421             {
422                 BMCWEB_LOG_ERROR("getChassis respHandler DBUS error: {}", ec);
423                 messages::internalError(asyncResp->res);
424                 return;
425             }
426             const std::string* chassisPath = nullptr;
427             for (const std::string& chassis : chassisPaths)
428             {
429                 sdbusplus::message::object_path path(chassis);
430                 std::string chassisName = path.filename();
431                 if (chassisName.empty())
432                 {
433                     BMCWEB_LOG_ERROR("Failed to find '/' in {}", chassis);
434                     continue;
435                 }
436                 if (chassisName == chassisIdStr)
437                 {
438                     chassisPath = &chassis;
439                     break;
440                 }
441             }
442             if (chassisPath == nullptr)
443             {
444                 messages::resourceNotFound(asyncResp->res, "Chassis",
445                                            chassisIdStr);
446                 return;
447             }
448             populateChassisNode(asyncResp->res.jsonValue, chassisSubNode);
449 
450             asyncResp->res.jsonValue["@odata.id"] = boost::urls::format(
451                 "/redfish/v1/Chassis/{}/{}", chassisIdStr, chassisSubNode);
452 
453             // Get the list of all sensors for this Chassis element
454             std::string sensorPath = *chassisPath + "/all_sensors";
455             dbus::utility::getAssociationEndPoints(
456                 sensorPath, [asyncResp, chassisSubNode, sensorTypes,
457                              callback = std::forward<Callback>(callback)](
458                                 const boost::system::error_code& ec2,
459                                 const dbus::utility::MapperEndPoints&
460                                     nodeSensorList) mutable {
461                     if (ec2)
462                     {
463                         if (ec2.value() != EBADR)
464                         {
465                             messages::internalError(asyncResp->res);
466                             return;
467                         }
468                     }
469                     const std::shared_ptr<std::set<std::string>>
470                         culledSensorList =
471                             std::make_shared<std::set<std::string>>();
472                     reduceSensorList(asyncResp->res, chassisSubNode,
473                                      sensorTypes, &nodeSensorList,
474                                      culledSensorList);
475                     BMCWEB_LOG_DEBUG("Finishing with {}",
476                                      culledSensorList->size());
477                     callback(culledSensorList);
478                 });
479         });
480     BMCWEB_LOG_DEBUG("getChassis exit");
481 }
482 
483 /**
484  * @brief Builds a json sensor representation of a sensor.
485  * @param sensorName  The name of the sensor to be built
486  * @param sensorType  The type (temperature, fan_tach, etc) of the sensor to
487  * build
488  * @param chassisSubNode The subnode (thermal, sensor, etc) of the sensor
489  * @param interfacesDict  A dictionary of the interfaces and properties of said
490  * interfaces to be built from
491  * @param sensorJson  The json object to fill
492  * @param inventoryItem D-Bus inventory item associated with the sensor.  Will
493  * be nullptr if no associated inventory item was found.
494  */
495 inline void objectInterfacesToJson(
496     const std::string& sensorName, const std::string& sensorType,
497     const sensor_utils::ChassisSubNode chassisSubNode,
498     const dbus::utility::DBusInterfacesMap& interfacesDict,
499     nlohmann::json& sensorJson, InventoryItem* inventoryItem)
500 {
501     for (const auto& [interface, valuesDict] : interfacesDict)
502     {
503         sensor_utils::objectPropertiesToJson(
504             sensorName, sensorType, chassisSubNode, valuesDict, sensorJson,
505             inventoryItem);
506     }
507     BMCWEB_LOG_DEBUG("Added sensor {}", sensorName);
508 }
509 
510 inline void populateFanRedundancy(
511     const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp)
512 {
513     constexpr std::array<std::string_view, 1> interfaces = {
514         "xyz.openbmc_project.Control.FanRedundancy"};
515     dbus::utility::getSubTree(
516         "/xyz/openbmc_project/control", 2, interfaces,
517         [sensorsAsyncResp](
518             const boost::system::error_code& ec,
519             const dbus::utility::MapperGetSubTreeResponse& resp) {
520             if (ec)
521             {
522                 return; // don't have to have this interface
523             }
524             for (const std::pair<std::string, dbus::utility::MapperServiceMap>&
525                      pathPair : resp)
526             {
527                 const std::string& path = pathPair.first;
528                 const dbus::utility::MapperServiceMap& objDict =
529                     pathPair.second;
530                 if (objDict.empty())
531                 {
532                     continue; // this should be impossible
533                 }
534 
535                 const std::string& owner = objDict.begin()->first;
536                 dbus::utility::getAssociationEndPoints(
537                     path + "/chassis",
538                     [path, owner, sensorsAsyncResp](
539                         const boost::system::error_code& ec2,
540                         const dbus::utility::MapperEndPoints& endpoints) {
541                         if (ec2)
542                         {
543                             return; // if they don't have an association we
544                                     // can't tell what chassis is
545                         }
546                         auto found = std::ranges::find_if(
547                             endpoints,
548                             [sensorsAsyncResp](const std::string& entry) {
549                                 return entry.find(
550                                            sensorsAsyncResp->chassisId) !=
551                                        std::string::npos;
552                             });
553 
554                         if (found == endpoints.end())
555                         {
556                             return;
557                         }
558                         sdbusplus::asio::getAllProperties(
559                             *crow::connections::systemBus, owner, path,
560                             "xyz.openbmc_project.Control.FanRedundancy",
561                             [path, sensorsAsyncResp](
562                                 const boost::system::error_code& ec3,
563                                 const dbus::utility::DBusPropertiesMap& ret) {
564                                 if (ec3)
565                                 {
566                                     return; // don't have to have this
567                                             // interface
568                                 }
569 
570                                 const uint8_t* allowedFailures = nullptr;
571                                 const std::vector<std::string>* collection =
572                                     nullptr;
573                                 const std::string* status = nullptr;
574 
575                                 const bool success =
576                                     sdbusplus::unpackPropertiesNoThrow(
577                                         dbus_utils::UnpackErrorPrinter(), ret,
578                                         "AllowedFailures", allowedFailures,
579                                         "Collection", collection, "Status",
580                                         status);
581 
582                                 if (!success)
583                                 {
584                                     messages::internalError(
585                                         sensorsAsyncResp->asyncResp->res);
586                                     return;
587                                 }
588 
589                                 if (allowedFailures == nullptr ||
590                                     collection == nullptr || status == nullptr)
591                                 {
592                                     BMCWEB_LOG_ERROR(
593                                         "Invalid redundancy interface");
594                                     messages::internalError(
595                                         sensorsAsyncResp->asyncResp->res);
596                                     return;
597                                 }
598 
599                                 sdbusplus::message::object_path objectPath(
600                                     path);
601                                 std::string name = objectPath.filename();
602                                 if (name.empty())
603                                 {
604                                     // this should be impossible
605                                     messages::internalError(
606                                         sensorsAsyncResp->asyncResp->res);
607                                     return;
608                                 }
609                                 std::ranges::replace(name, '_', ' ');
610 
611                                 std::string health;
612 
613                                 if (status->ends_with("Full"))
614                                 {
615                                     health = "OK";
616                                 }
617                                 else if (status->ends_with("Degraded"))
618                                 {
619                                     health = "Warning";
620                                 }
621                                 else
622                                 {
623                                     health = "Critical";
624                                 }
625                                 nlohmann::json::array_t redfishCollection;
626                                 const auto& fanRedfish =
627                                     sensorsAsyncResp->asyncResp->res
628                                         .jsonValue["Fans"];
629                                 for (const std::string& item : *collection)
630                                 {
631                                     sdbusplus::message::object_path itemPath(
632                                         item);
633                                     std::string itemName = itemPath.filename();
634                                     if (itemName.empty())
635                                     {
636                                         continue;
637                                     }
638                                     /*
639                                     todo(ed): merge patch that fixes the names
640                                     std::replace(itemName.begin(),
641                                                  itemName.end(), '_', ' ');*/
642                                     auto schemaItem = std::ranges::find_if(
643                                         fanRedfish,
644                                         [itemName](const nlohmann::json& fan) {
645                                             return fan["Name"] == itemName;
646                                         });
647                                     if (schemaItem != fanRedfish.end())
648                                     {
649                                         nlohmann::json::object_t collectionId;
650                                         collectionId["@odata.id"] =
651                                             (*schemaItem)["@odata.id"];
652                                         redfishCollection.emplace_back(
653                                             std::move(collectionId));
654                                     }
655                                     else
656                                     {
657                                         BMCWEB_LOG_ERROR(
658                                             "failed to find fan in schema");
659                                         messages::internalError(
660                                             sensorsAsyncResp->asyncResp->res);
661                                         return;
662                                     }
663                                 }
664 
665                                 size_t minNumNeeded =
666                                     collection->empty()
667                                         ? 0
668                                         : collection->size() - *allowedFailures;
669                                 nlohmann::json& jResp =
670                                     sensorsAsyncResp->asyncResp->res
671                                         .jsonValue["Redundancy"];
672 
673                                 nlohmann::json::object_t redundancy;
674                                 boost::urls::url url = boost::urls::format(
675                                     "/redfish/v1/Chassis/{}/{}",
676                                     sensorsAsyncResp->chassisId,
677                                     sensorsAsyncResp->chassisSubNode);
678                                 url.set_fragment(
679                                     ("/Redundancy"_json_pointer / jResp.size())
680                                         .to_string());
681                                 redundancy["@odata.id"] = std::move(url);
682                                 redundancy["@odata.type"] =
683                                     "#Redundancy.v1_3_2.Redundancy";
684                                 redundancy["MinNumNeeded"] = minNumNeeded;
685                                 redundancy["Mode"] =
686                                     redundancy::RedundancyType::NPlusM;
687                                 redundancy["Name"] = name;
688                                 redundancy["RedundancySet"] = redfishCollection;
689                                 redundancy["Status"]["Health"] = health;
690                                 redundancy["Status"]["State"] =
691                                     resource::State::Enabled;
692 
693                                 jResp.emplace_back(std::move(redundancy));
694                             });
695                     });
696             }
697         });
698 }
699 
700 inline void
701     sortJSONResponse(const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp)
702 {
703     nlohmann::json& response = sensorsAsyncResp->asyncResp->res.jsonValue;
704     std::array<std::string, 2> sensorHeaders{"Temperatures", "Fans"};
705     if (sensorsAsyncResp->chassisSubNode == sensors::powerNodeStr)
706     {
707         sensorHeaders = {"Voltages", "PowerSupplies"};
708     }
709     for (const std::string& sensorGroup : sensorHeaders)
710     {
711         nlohmann::json::iterator entry = response.find(sensorGroup);
712         if (entry != response.end())
713         {
714             std::sort(entry->begin(), entry->end(),
715                       [](const nlohmann::json& c1, const nlohmann::json& c2) {
716                           return c1["Name"] < c2["Name"];
717                       });
718 
719             // add the index counts to the end of each entry
720             size_t count = 0;
721             for (nlohmann::json& sensorJson : *entry)
722             {
723                 nlohmann::json::iterator odata = sensorJson.find("@odata.id");
724                 if (odata == sensorJson.end())
725                 {
726                     continue;
727                 }
728                 std::string* value = odata->get_ptr<std::string*>();
729                 if (value != nullptr)
730                 {
731                     *value += "/" + std::to_string(count);
732                     sensorJson["MemberId"] = std::to_string(count);
733                     count++;
734                     sensorsAsyncResp->updateUri(sensorJson["Name"], *value);
735                 }
736             }
737         }
738     }
739 }
740 
741 /**
742  * @brief Finds the inventory item with the specified object path.
743  * @param inventoryItems D-Bus inventory items associated with sensors.
744  * @param invItemObjPath D-Bus object path of inventory item.
745  * @return Inventory item within vector, or nullptr if no match found.
746  */
747 inline InventoryItem* findInventoryItem(
748     const std::shared_ptr<std::vector<InventoryItem>>& inventoryItems,
749     const std::string& invItemObjPath)
750 {
751     for (InventoryItem& inventoryItem : *inventoryItems)
752     {
753         if (inventoryItem.objectPath == invItemObjPath)
754         {
755             return &inventoryItem;
756         }
757     }
758     return nullptr;
759 }
760 
761 /**
762  * @brief Finds the inventory item associated with the specified sensor.
763  * @param inventoryItems D-Bus inventory items associated with sensors.
764  * @param sensorObjPath D-Bus object path of sensor.
765  * @return Inventory item within vector, or nullptr if no match found.
766  */
767 inline InventoryItem* findInventoryItemForSensor(
768     const std::shared_ptr<std::vector<InventoryItem>>& inventoryItems,
769     const std::string& sensorObjPath)
770 {
771     for (InventoryItem& inventoryItem : *inventoryItems)
772     {
773         if (inventoryItem.sensors.contains(sensorObjPath))
774         {
775             return &inventoryItem;
776         }
777     }
778     return nullptr;
779 }
780 
781 /**
782  * @brief Finds the inventory item associated with the specified led path.
783  * @param inventoryItems D-Bus inventory items associated with sensors.
784  * @param ledObjPath D-Bus object path of led.
785  * @return Inventory item within vector, or nullptr if no match found.
786  */
787 inline InventoryItem* findInventoryItemForLed(
788     std::vector<InventoryItem>& inventoryItems, const std::string& ledObjPath)
789 {
790     for (InventoryItem& inventoryItem : inventoryItems)
791     {
792         if (inventoryItem.ledObjectPath == ledObjPath)
793         {
794             return &inventoryItem;
795         }
796     }
797     return nullptr;
798 }
799 
800 /**
801  * @brief Adds inventory item and associated sensor to specified vector.
802  *
803  * Adds a new InventoryItem to the vector if necessary.  Searches for an
804  * existing InventoryItem with the specified object path.  If not found, one is
805  * added to the vector.
806  *
807  * Next, the specified sensor is added to the set of sensors associated with the
808  * InventoryItem.
809  *
810  * @param inventoryItems D-Bus inventory items associated with sensors.
811  * @param invItemObjPath D-Bus object path of inventory item.
812  * @param sensorObjPath D-Bus object path of sensor
813  */
814 inline void addInventoryItem(
815     const std::shared_ptr<std::vector<InventoryItem>>& inventoryItems,
816     const std::string& invItemObjPath, const std::string& sensorObjPath)
817 {
818     // Look for inventory item in vector
819     InventoryItem* inventoryItem =
820         findInventoryItem(inventoryItems, invItemObjPath);
821 
822     // If inventory item doesn't exist in vector, add it
823     if (inventoryItem == nullptr)
824     {
825         inventoryItems->emplace_back(invItemObjPath);
826         inventoryItem = &(inventoryItems->back());
827     }
828 
829     // Add sensor to set of sensors associated with inventory item
830     inventoryItem->sensors.emplace(sensorObjPath);
831 }
832 
833 /**
834  * @brief Stores D-Bus data in the specified inventory item.
835  *
836  * Finds D-Bus data in the specified map of interfaces.  Stores the data in the
837  * specified InventoryItem.
838  *
839  * This data is later used to provide sensor property values in the JSON
840  * response.
841  *
842  * @param inventoryItem Inventory item where data will be stored.
843  * @param interfacesDict Map containing D-Bus interfaces and their properties
844  * for the specified inventory item.
845  */
846 inline void storeInventoryItemData(
847     InventoryItem& inventoryItem,
848     const dbus::utility::DBusInterfacesMap& interfacesDict)
849 {
850     // Get properties from Inventory.Item interface
851 
852     for (const auto& [interface, values] : interfacesDict)
853     {
854         if (interface == "xyz.openbmc_project.Inventory.Item")
855         {
856             for (const auto& [name, dbusValue] : values)
857             {
858                 if (name == "Present")
859                 {
860                     const bool* value = std::get_if<bool>(&dbusValue);
861                     if (value != nullptr)
862                     {
863                         inventoryItem.isPresent = *value;
864                     }
865                 }
866             }
867         }
868         // Check if Inventory.Item.PowerSupply interface is present
869 
870         if (interface == "xyz.openbmc_project.Inventory.Item.PowerSupply")
871         {
872             inventoryItem.isPowerSupply = true;
873         }
874 
875         // Get properties from Inventory.Decorator.Asset interface
876         if (interface == "xyz.openbmc_project.Inventory.Decorator.Asset")
877         {
878             for (const auto& [name, dbusValue] : values)
879             {
880                 if (name == "Manufacturer")
881                 {
882                     const std::string* value =
883                         std::get_if<std::string>(&dbusValue);
884                     if (value != nullptr)
885                     {
886                         inventoryItem.manufacturer = *value;
887                     }
888                 }
889                 if (name == "Model")
890                 {
891                     const std::string* value =
892                         std::get_if<std::string>(&dbusValue);
893                     if (value != nullptr)
894                     {
895                         inventoryItem.model = *value;
896                     }
897                 }
898                 if (name == "SerialNumber")
899                 {
900                     const std::string* value =
901                         std::get_if<std::string>(&dbusValue);
902                     if (value != nullptr)
903                     {
904                         inventoryItem.serialNumber = *value;
905                     }
906                 }
907                 if (name == "PartNumber")
908                 {
909                     const std::string* value =
910                         std::get_if<std::string>(&dbusValue);
911                     if (value != nullptr)
912                     {
913                         inventoryItem.partNumber = *value;
914                     }
915                 }
916             }
917         }
918 
919         if (interface ==
920             "xyz.openbmc_project.State.Decorator.OperationalStatus")
921         {
922             for (const auto& [name, dbusValue] : values)
923             {
924                 if (name == "Functional")
925                 {
926                     const bool* value = std::get_if<bool>(&dbusValue);
927                     if (value != nullptr)
928                     {
929                         inventoryItem.isFunctional = *value;
930                     }
931                 }
932             }
933         }
934     }
935 }
936 
937 /**
938  * @brief Gets D-Bus data for inventory items associated with sensors.
939  *
940  * Uses the specified connections (services) to obtain D-Bus data for inventory
941  * items associated with sensors.  Stores the resulting data in the
942  * inventoryItems vector.
943  *
944  * This data is later used to provide sensor property values in the JSON
945  * response.
946  *
947  * Finds the inventory item data asynchronously.  Invokes callback when data has
948  * been obtained.
949  *
950  * The callback must have the following signature:
951  *   @code
952  *   callback(void)
953  *   @endcode
954  *
955  * This function is called recursively, obtaining data asynchronously from one
956  * connection in each call.  This ensures the callback is not invoked until the
957  * last asynchronous function has completed.
958  *
959  * @param sensorsAsyncResp Pointer to object holding response data.
960  * @param inventoryItems D-Bus inventory items associated with sensors.
961  * @param invConnections Connections that provide data for the inventory items.
962  * implements ObjectManager.
963  * @param callback Callback to invoke when inventory data has been obtained.
964  * @param invConnectionsIndex Current index in invConnections.  Only specified
965  * in recursive calls to this function.
966  */
967 template <typename Callback>
968 static void getInventoryItemsData(
969     std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp,
970     std::shared_ptr<std::vector<InventoryItem>> inventoryItems,
971     std::shared_ptr<std::set<std::string>> invConnections, Callback&& callback,
972     size_t invConnectionsIndex = 0)
973 {
974     BMCWEB_LOG_DEBUG("getInventoryItemsData enter");
975 
976     // If no more connections left, call callback
977     if (invConnectionsIndex >= invConnections->size())
978     {
979         callback();
980         BMCWEB_LOG_DEBUG("getInventoryItemsData exit");
981         return;
982     }
983 
984     // Get inventory item data from current connection
985     auto it = invConnections->begin();
986     std::advance(it, invConnectionsIndex);
987     if (it != invConnections->end())
988     {
989         const std::string& invConnection = *it;
990 
991         // Get all object paths and their interfaces for current connection
992         sdbusplus::message::object_path path("/xyz/openbmc_project/inventory");
993         dbus::utility::getManagedObjects(
994             invConnection, path,
995             [sensorsAsyncResp, inventoryItems, invConnections,
996              callback = std::forward<Callback>(callback), invConnectionsIndex](
997                 const boost::system::error_code& ec,
998                 const dbus::utility::ManagedObjectType& resp) mutable {
999                 BMCWEB_LOG_DEBUG("getInventoryItemsData respHandler enter");
1000                 if (ec)
1001                 {
1002                     BMCWEB_LOG_ERROR(
1003                         "getInventoryItemsData respHandler DBus error {}", ec);
1004                     messages::internalError(sensorsAsyncResp->asyncResp->res);
1005                     return;
1006                 }
1007 
1008                 // Loop through returned object paths
1009                 for (const auto& objDictEntry : resp)
1010                 {
1011                     const std::string& objPath =
1012                         static_cast<const std::string&>(objDictEntry.first);
1013 
1014                     // If this object path is one of the specified inventory
1015                     // items
1016                     InventoryItem* inventoryItem =
1017                         findInventoryItem(inventoryItems, objPath);
1018                     if (inventoryItem != nullptr)
1019                     {
1020                         // Store inventory data in InventoryItem
1021                         storeInventoryItemData(*inventoryItem,
1022                                                objDictEntry.second);
1023                     }
1024                 }
1025 
1026                 // Recurse to get inventory item data from next connection
1027                 getInventoryItemsData(sensorsAsyncResp, inventoryItems,
1028                                       invConnections, std::move(callback),
1029                                       invConnectionsIndex + 1);
1030 
1031                 BMCWEB_LOG_DEBUG("getInventoryItemsData respHandler exit");
1032             });
1033     }
1034 
1035     BMCWEB_LOG_DEBUG("getInventoryItemsData exit");
1036 }
1037 
1038 /**
1039  * @brief Gets connections that provide D-Bus data for inventory items.
1040  *
1041  * Gets the D-Bus connections (services) that provide data for the inventory
1042  * items that are associated with sensors.
1043  *
1044  * Finds the connections asynchronously.  Invokes callback when information has
1045  * been obtained.
1046  *
1047  * The callback must have the following signature:
1048  *   @code
1049  *   callback(std::shared_ptr<std::set<std::string>> invConnections)
1050  *   @endcode
1051  *
1052  * @param sensorsAsyncResp Pointer to object holding response data.
1053  * @param inventoryItems D-Bus inventory items associated with sensors.
1054  * @param callback Callback to invoke when connections have been obtained.
1055  */
1056 template <typename Callback>
1057 static void getInventoryItemsConnections(
1058     const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp,
1059     const std::shared_ptr<std::vector<InventoryItem>>& inventoryItems,
1060     Callback&& callback)
1061 {
1062     BMCWEB_LOG_DEBUG("getInventoryItemsConnections enter");
1063 
1064     const std::string path = "/xyz/openbmc_project/inventory";
1065     constexpr std::array<std::string_view, 4> interfaces = {
1066         "xyz.openbmc_project.Inventory.Item",
1067         "xyz.openbmc_project.Inventory.Item.PowerSupply",
1068         "xyz.openbmc_project.Inventory.Decorator.Asset",
1069         "xyz.openbmc_project.State.Decorator.OperationalStatus"};
1070 
1071     // Make call to ObjectMapper to find all inventory items
1072     dbus::utility::getSubTree(
1073         path, 0, interfaces,
1074         [callback = std::forward<Callback>(callback), sensorsAsyncResp,
1075          inventoryItems](
1076             const boost::system::error_code& ec,
1077             const dbus::utility::MapperGetSubTreeResponse& subtree) mutable {
1078             // Response handler for parsing output from GetSubTree
1079             BMCWEB_LOG_DEBUG("getInventoryItemsConnections respHandler enter");
1080             if (ec)
1081             {
1082                 messages::internalError(sensorsAsyncResp->asyncResp->res);
1083                 BMCWEB_LOG_ERROR(
1084                     "getInventoryItemsConnections respHandler DBus error {}",
1085                     ec);
1086                 return;
1087             }
1088 
1089             // Make unique list of connections for desired inventory items
1090             std::shared_ptr<std::set<std::string>> invConnections =
1091                 std::make_shared<std::set<std::string>>();
1092 
1093             // Loop through objects from GetSubTree
1094             for (const std::pair<std::string,
1095                                  std::vector<std::pair<
1096                                      std::string, std::vector<std::string>>>>&
1097                      object : subtree)
1098             {
1099                 // Check if object path is one of the specified inventory items
1100                 const std::string& objPath = object.first;
1101                 if (findInventoryItem(inventoryItems, objPath) != nullptr)
1102                 {
1103                     // Store all connections to inventory item
1104                     for (const std::pair<std::string, std::vector<std::string>>&
1105                              objData : object.second)
1106                     {
1107                         const std::string& invConnection = objData.first;
1108                         invConnections->insert(invConnection);
1109                     }
1110                 }
1111             }
1112 
1113             callback(invConnections);
1114             BMCWEB_LOG_DEBUG("getInventoryItemsConnections respHandler exit");
1115         });
1116     BMCWEB_LOG_DEBUG("getInventoryItemsConnections exit");
1117 }
1118 
1119 /**
1120  * @brief Gets associations from sensors to inventory items.
1121  *
1122  * Looks for ObjectMapper associations from the specified sensors to related
1123  * inventory items. Then finds the associations from those inventory items to
1124  * their LEDs, if any.
1125  *
1126  * Finds the inventory items asynchronously.  Invokes callback when information
1127  * has been obtained.
1128  *
1129  * The callback must have the following signature:
1130  *   @code
1131  *   callback(std::shared_ptr<std::vector<InventoryItem>> inventoryItems)
1132  *   @endcode
1133  *
1134  * @param sensorsAsyncResp Pointer to object holding response data.
1135  * @param sensorNames All sensors within the current chassis.
1136  * implements ObjectManager.
1137  * @param callback Callback to invoke when inventory items have been obtained.
1138  */
1139 template <typename Callback>
1140 static void getInventoryItemAssociations(
1141     const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp,
1142     const std::shared_ptr<std::set<std::string>>& sensorNames,
1143     Callback&& callback)
1144 {
1145     BMCWEB_LOG_DEBUG("getInventoryItemAssociations enter");
1146 
1147     // Call GetManagedObjects on the ObjectMapper to get all associations
1148     sdbusplus::message::object_path path("/");
1149     dbus::utility::getManagedObjects(
1150         "xyz.openbmc_project.ObjectMapper", path,
1151         [callback = std::forward<Callback>(callback), sensorsAsyncResp,
1152          sensorNames](const boost::system::error_code& ec,
1153                       const dbus::utility::ManagedObjectType& resp) mutable {
1154             BMCWEB_LOG_DEBUG("getInventoryItemAssociations respHandler enter");
1155             if (ec)
1156             {
1157                 BMCWEB_LOG_ERROR(
1158                     "getInventoryItemAssociations respHandler DBus error {}",
1159                     ec);
1160                 messages::internalError(sensorsAsyncResp->asyncResp->res);
1161                 return;
1162             }
1163 
1164             // Create vector to hold list of inventory items
1165             std::shared_ptr<std::vector<InventoryItem>> inventoryItems =
1166                 std::make_shared<std::vector<InventoryItem>>();
1167 
1168             // Loop through returned object paths
1169             std::string sensorAssocPath;
1170             sensorAssocPath.reserve(128); // avoid memory allocations
1171             for (const auto& objDictEntry : resp)
1172             {
1173                 const std::string& objPath =
1174                     static_cast<const std::string&>(objDictEntry.first);
1175 
1176                 // If path is inventory association for one of the specified
1177                 // sensors
1178                 for (const std::string& sensorName : *sensorNames)
1179                 {
1180                     sensorAssocPath = sensorName;
1181                     sensorAssocPath += "/inventory";
1182                     if (objPath == sensorAssocPath)
1183                     {
1184                         // Get Association interface for object path
1185                         for (const auto& [interface, values] :
1186                              objDictEntry.second)
1187                         {
1188                             if (interface == "xyz.openbmc_project.Association")
1189                             {
1190                                 for (const auto& [valueName, value] : values)
1191                                 {
1192                                     if (valueName == "endpoints")
1193                                     {
1194                                         const std::vector<std::string>*
1195                                             endpoints = std::get_if<
1196                                                 std::vector<std::string>>(
1197                                                 &value);
1198                                         if ((endpoints != nullptr) &&
1199                                             !endpoints->empty())
1200                                         {
1201                                             // Add inventory item to vector
1202                                             const std::string& invItemPath =
1203                                                 endpoints->front();
1204                                             addInventoryItem(inventoryItems,
1205                                                              invItemPath,
1206                                                              sensorName);
1207                                         }
1208                                     }
1209                                 }
1210                             }
1211                         }
1212                         break;
1213                     }
1214                 }
1215             }
1216 
1217             // Now loop through the returned object paths again, this time to
1218             // find the leds associated with the inventory items we just found
1219             std::string inventoryAssocPath;
1220             inventoryAssocPath.reserve(128); // avoid memory allocations
1221             for (const auto& objDictEntry : resp)
1222             {
1223                 const std::string& objPath =
1224                     static_cast<const std::string&>(objDictEntry.first);
1225 
1226                 for (InventoryItem& inventoryItem : *inventoryItems)
1227                 {
1228                     inventoryAssocPath = inventoryItem.objectPath;
1229                     inventoryAssocPath += "/leds";
1230                     if (objPath == inventoryAssocPath)
1231                     {
1232                         for (const auto& [interface, values] :
1233                              objDictEntry.second)
1234                         {
1235                             if (interface == "xyz.openbmc_project.Association")
1236                             {
1237                                 for (const auto& [valueName, value] : values)
1238                                 {
1239                                     if (valueName == "endpoints")
1240                                     {
1241                                         const std::vector<std::string>*
1242                                             endpoints = std::get_if<
1243                                                 std::vector<std::string>>(
1244                                                 &value);
1245                                         if ((endpoints != nullptr) &&
1246                                             !endpoints->empty())
1247                                         {
1248                                             // Add inventory item to vector
1249                                             // Store LED path in inventory item
1250                                             const std::string& ledPath =
1251                                                 endpoints->front();
1252                                             inventoryItem.ledObjectPath =
1253                                                 ledPath;
1254                                         }
1255                                     }
1256                                 }
1257                             }
1258                         }
1259 
1260                         break;
1261                     }
1262                 }
1263             }
1264             callback(inventoryItems);
1265             BMCWEB_LOG_DEBUG("getInventoryItemAssociations respHandler exit");
1266         });
1267 
1268     BMCWEB_LOG_DEBUG("getInventoryItemAssociations exit");
1269 }
1270 
1271 /**
1272  * @brief Gets D-Bus data for inventory item leds associated with sensors.
1273  *
1274  * Uses the specified connections (services) to obtain D-Bus data for inventory
1275  * item leds associated with sensors.  Stores the resulting data in the
1276  * inventoryItems vector.
1277  *
1278  * This data is later used to provide sensor property values in the JSON
1279  * response.
1280  *
1281  * Finds the inventory item led data asynchronously.  Invokes callback when data
1282  * has been obtained.
1283  *
1284  * The callback must have the following signature:
1285  *   @code
1286  *   callback()
1287  *   @endcode
1288  *
1289  * This function is called recursively, obtaining data asynchronously from one
1290  * connection in each call.  This ensures the callback is not invoked until the
1291  * last asynchronous function has completed.
1292  *
1293  * @param sensorsAsyncResp Pointer to object holding response data.
1294  * @param inventoryItems D-Bus inventory items associated with sensors.
1295  * @param ledConnections Connections that provide data for the inventory leds.
1296  * @param callback Callback to invoke when inventory data has been obtained.
1297  * @param ledConnectionsIndex Current index in ledConnections.  Only specified
1298  * in recursive calls to this function.
1299  */
1300 template <typename Callback>
1301 void getInventoryLedData(
1302     std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp,
1303     std::shared_ptr<std::vector<InventoryItem>> inventoryItems,
1304     std::shared_ptr<std::map<std::string, std::string>> ledConnections,
1305     Callback&& callback, size_t ledConnectionsIndex = 0)
1306 {
1307     BMCWEB_LOG_DEBUG("getInventoryLedData enter");
1308 
1309     // If no more connections left, call callback
1310     if (ledConnectionsIndex >= ledConnections->size())
1311     {
1312         callback();
1313         BMCWEB_LOG_DEBUG("getInventoryLedData exit");
1314         return;
1315     }
1316 
1317     // Get inventory item data from current connection
1318     auto it = ledConnections->begin();
1319     std::advance(it, ledConnectionsIndex);
1320     if (it != ledConnections->end())
1321     {
1322         const std::string& ledPath = (*it).first;
1323         const std::string& ledConnection = (*it).second;
1324         // Response handler for Get State property
1325         auto respHandler =
1326             [sensorsAsyncResp, inventoryItems, ledConnections, ledPath,
1327              callback = std::forward<Callback>(callback),
1328              ledConnectionsIndex](const boost::system::error_code& ec,
1329                                   const std::string& state) mutable {
1330                 BMCWEB_LOG_DEBUG("getInventoryLedData respHandler enter");
1331                 if (ec)
1332                 {
1333                     BMCWEB_LOG_ERROR(
1334                         "getInventoryLedData respHandler DBus error {}", ec);
1335                     messages::internalError(sensorsAsyncResp->asyncResp->res);
1336                     return;
1337                 }
1338 
1339                 BMCWEB_LOG_DEBUG("Led state: {}", state);
1340                 // Find inventory item with this LED object path
1341                 InventoryItem* inventoryItem =
1342                     findInventoryItemForLed(*inventoryItems, ledPath);
1343                 if (inventoryItem != nullptr)
1344                 {
1345                     // Store LED state in InventoryItem
1346                     if (state.ends_with("On"))
1347                     {
1348                         inventoryItem->ledState = sensor_utils::LedState::ON;
1349                     }
1350                     else if (state.ends_with("Blink"))
1351                     {
1352                         inventoryItem->ledState = sensor_utils::LedState::BLINK;
1353                     }
1354                     else if (state.ends_with("Off"))
1355                     {
1356                         inventoryItem->ledState = sensor_utils::LedState::OFF;
1357                     }
1358                     else
1359                     {
1360                         inventoryItem->ledState =
1361                             sensor_utils::LedState::UNKNOWN;
1362                     }
1363                 }
1364 
1365                 // Recurse to get LED data from next connection
1366                 getInventoryLedData(sensorsAsyncResp, inventoryItems,
1367                                     ledConnections, std::move(callback),
1368                                     ledConnectionsIndex + 1);
1369 
1370                 BMCWEB_LOG_DEBUG("getInventoryLedData respHandler exit");
1371             };
1372 
1373         // Get the State property for the current LED
1374         sdbusplus::asio::getProperty<std::string>(
1375             *crow::connections::systemBus, ledConnection, ledPath,
1376             "xyz.openbmc_project.Led.Physical", "State",
1377             std::move(respHandler));
1378     }
1379 
1380     BMCWEB_LOG_DEBUG("getInventoryLedData exit");
1381 }
1382 
1383 /**
1384  * @brief Gets LED data for LEDs associated with given inventory items.
1385  *
1386  * Gets the D-Bus connections (services) that provide LED data for the LEDs
1387  * associated with the specified inventory items.  Then gets the LED data from
1388  * each connection and stores it in the inventory item.
1389  *
1390  * This data is later used to provide sensor property values in the JSON
1391  * response.
1392  *
1393  * Finds the LED data asynchronously.  Invokes callback when information has
1394  * been obtained.
1395  *
1396  * The callback must have the following signature:
1397  *   @code
1398  *   callback()
1399  *   @endcode
1400  *
1401  * @param sensorsAsyncResp Pointer to object holding response data.
1402  * @param inventoryItems D-Bus inventory items associated with sensors.
1403  * @param callback Callback to invoke when inventory items have been obtained.
1404  */
1405 template <typename Callback>
1406 void getInventoryLeds(
1407     std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp,
1408     std::shared_ptr<std::vector<InventoryItem>> inventoryItems,
1409     Callback&& callback)
1410 {
1411     BMCWEB_LOG_DEBUG("getInventoryLeds enter");
1412 
1413     const std::string path = "/xyz/openbmc_project";
1414     constexpr std::array<std::string_view, 1> interfaces = {
1415         "xyz.openbmc_project.Led.Physical"};
1416 
1417     // Make call to ObjectMapper to find all inventory items
1418     dbus::utility::getSubTree(
1419         path, 0, interfaces,
1420         [callback = std::forward<Callback>(callback), sensorsAsyncResp,
1421          inventoryItems](
1422             const boost::system::error_code& ec,
1423             const dbus::utility::MapperGetSubTreeResponse& subtree) mutable {
1424             // Response handler for parsing output from GetSubTree
1425             BMCWEB_LOG_DEBUG("getInventoryLeds respHandler enter");
1426             if (ec)
1427             {
1428                 messages::internalError(sensorsAsyncResp->asyncResp->res);
1429                 BMCWEB_LOG_ERROR("getInventoryLeds respHandler DBus error {}",
1430                                  ec);
1431                 return;
1432             }
1433 
1434             // Build map of LED object paths to connections
1435             std::shared_ptr<std::map<std::string, std::string>> ledConnections =
1436                 std::make_shared<std::map<std::string, std::string>>();
1437 
1438             // Loop through objects from GetSubTree
1439             for (const std::pair<std::string,
1440                                  std::vector<std::pair<
1441                                      std::string, std::vector<std::string>>>>&
1442                      object : subtree)
1443             {
1444                 // Check if object path is LED for one of the specified
1445                 // inventory items
1446                 const std::string& ledPath = object.first;
1447                 if (findInventoryItemForLed(*inventoryItems, ledPath) !=
1448                     nullptr)
1449                 {
1450                     // Add mapping from ledPath to connection
1451                     const std::string& connection =
1452                         object.second.begin()->first;
1453                     (*ledConnections)[ledPath] = connection;
1454                     BMCWEB_LOG_DEBUG("Added mapping {} -> {}", ledPath,
1455                                      connection);
1456                 }
1457             }
1458 
1459             getInventoryLedData(sensorsAsyncResp, inventoryItems,
1460                                 ledConnections, std::move(callback));
1461             BMCWEB_LOG_DEBUG("getInventoryLeds respHandler exit");
1462         });
1463     BMCWEB_LOG_DEBUG("getInventoryLeds exit");
1464 }
1465 
1466 /**
1467  * @brief Gets D-Bus data for Power Supply Attributes such as EfficiencyPercent
1468  *
1469  * Uses the specified connections (services) (currently assumes just one) to
1470  * obtain D-Bus data for Power Supply Attributes. Stores the resulting data in
1471  * the inventoryItems vector. Only stores data in Power Supply inventoryItems.
1472  *
1473  * This data is later used to provide sensor property values in the JSON
1474  * response.
1475  *
1476  * Finds the Power Supply Attributes data asynchronously.  Invokes callback
1477  * when data has been obtained.
1478  *
1479  * The callback must have the following signature:
1480  *   @code
1481  *   callback(std::shared_ptr<std::vector<InventoryItem>> inventoryItems)
1482  *   @endcode
1483  *
1484  * @param sensorsAsyncResp Pointer to object holding response data.
1485  * @param inventoryItems D-Bus inventory items associated with sensors.
1486  * @param psAttributesConnections Connections that provide data for the Power
1487  *        Supply Attributes
1488  * @param callback Callback to invoke when data has been obtained.
1489  */
1490 template <typename Callback>
1491 void getPowerSupplyAttributesData(
1492     const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp,
1493     std::shared_ptr<std::vector<InventoryItem>> inventoryItems,
1494     const std::map<std::string, std::string>& psAttributesConnections,
1495     Callback&& callback)
1496 {
1497     BMCWEB_LOG_DEBUG("getPowerSupplyAttributesData enter");
1498 
1499     if (psAttributesConnections.empty())
1500     {
1501         BMCWEB_LOG_DEBUG("Can't find PowerSupplyAttributes, no connections!");
1502         callback(inventoryItems);
1503         return;
1504     }
1505 
1506     // Assuming just one connection (service) for now
1507     auto it = psAttributesConnections.begin();
1508 
1509     const std::string& psAttributesPath = (*it).first;
1510     const std::string& psAttributesConnection = (*it).second;
1511 
1512     // Response handler for Get DeratingFactor property
1513     auto respHandler = [sensorsAsyncResp, inventoryItems,
1514                         callback = std::forward<Callback>(callback)](
1515                            const boost::system::error_code& ec,
1516                            uint32_t value) mutable {
1517         BMCWEB_LOG_DEBUG("getPowerSupplyAttributesData respHandler enter");
1518         if (ec)
1519         {
1520             BMCWEB_LOG_ERROR(
1521                 "getPowerSupplyAttributesData respHandler DBus error {}", ec);
1522             messages::internalError(sensorsAsyncResp->asyncResp->res);
1523             return;
1524         }
1525 
1526         BMCWEB_LOG_DEBUG("PS EfficiencyPercent value: {}", value);
1527         // Store value in Power Supply Inventory Items
1528         for (InventoryItem& inventoryItem : *inventoryItems)
1529         {
1530             if (inventoryItem.isPowerSupply)
1531             {
1532                 inventoryItem.powerSupplyEfficiencyPercent =
1533                     static_cast<int>(value);
1534             }
1535         }
1536 
1537         BMCWEB_LOG_DEBUG("getPowerSupplyAttributesData respHandler exit");
1538         callback(inventoryItems);
1539     };
1540 
1541     // Get the DeratingFactor property for the PowerSupplyAttributes
1542     // Currently only property on the interface/only one we care about
1543     sdbusplus::asio::getProperty<uint32_t>(
1544         *crow::connections::systemBus, psAttributesConnection, psAttributesPath,
1545         "xyz.openbmc_project.Control.PowerSupplyAttributes", "DeratingFactor",
1546         std::move(respHandler));
1547 
1548     BMCWEB_LOG_DEBUG("getPowerSupplyAttributesData exit");
1549 }
1550 
1551 /**
1552  * @brief Gets the Power Supply Attributes such as EfficiencyPercent
1553  *
1554  * Gets the D-Bus connection (service) that provides Power Supply Attributes
1555  * data. Then gets the Power Supply Attributes data from the connection
1556  * (currently just assumes 1 connection) and stores the data in the inventory
1557  * item.
1558  *
1559  * This data is later used to provide sensor property values in the JSON
1560  * response. DeratingFactor on D-Bus is mapped to EfficiencyPercent on Redfish.
1561  *
1562  * Finds the Power Supply Attributes data asynchronously. Invokes callback
1563  * when information has been obtained.
1564  *
1565  * The callback must have the following signature:
1566  *   @code
1567  *   callback(std::shared_ptr<std::vector<InventoryItem>> inventoryItems)
1568  *   @endcode
1569  *
1570  * @param sensorsAsyncResp Pointer to object holding response data.
1571  * @param inventoryItems D-Bus inventory items associated with sensors.
1572  * @param callback Callback to invoke when data has been obtained.
1573  */
1574 template <typename Callback>
1575 void getPowerSupplyAttributes(
1576     std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp,
1577     std::shared_ptr<std::vector<InventoryItem>> inventoryItems,
1578     Callback&& callback)
1579 {
1580     BMCWEB_LOG_DEBUG("getPowerSupplyAttributes enter");
1581 
1582     // Only need the power supply attributes when the Power Schema
1583     if (sensorsAsyncResp->chassisSubNode != sensors::powerNodeStr)
1584     {
1585         BMCWEB_LOG_DEBUG("getPowerSupplyAttributes exit since not Power");
1586         callback(inventoryItems);
1587         return;
1588     }
1589 
1590     constexpr std::array<std::string_view, 1> interfaces = {
1591         "xyz.openbmc_project.Control.PowerSupplyAttributes"};
1592 
1593     // Make call to ObjectMapper to find the PowerSupplyAttributes service
1594     dbus::utility::getSubTree(
1595         "/xyz/openbmc_project", 0, interfaces,
1596         [callback = std::forward<Callback>(callback), sensorsAsyncResp,
1597          inventoryItems](
1598             const boost::system::error_code& ec,
1599             const dbus::utility::MapperGetSubTreeResponse& subtree) mutable {
1600             // Response handler for parsing output from GetSubTree
1601             BMCWEB_LOG_DEBUG("getPowerSupplyAttributes respHandler enter");
1602             if (ec)
1603             {
1604                 messages::internalError(sensorsAsyncResp->asyncResp->res);
1605                 BMCWEB_LOG_ERROR(
1606                     "getPowerSupplyAttributes respHandler DBus error {}", ec);
1607                 return;
1608             }
1609             if (subtree.empty())
1610             {
1611                 BMCWEB_LOG_DEBUG("Can't find Power Supply Attributes!");
1612                 callback(inventoryItems);
1613                 return;
1614             }
1615 
1616             // Currently we only support 1 power supply attribute, use this for
1617             // all the power supplies. Build map of object path to connection.
1618             // Assume just 1 connection and 1 path for now.
1619             std::map<std::string, std::string> psAttributesConnections;
1620 
1621             if (subtree[0].first.empty() || subtree[0].second.empty())
1622             {
1623                 BMCWEB_LOG_DEBUG("Power Supply Attributes mapper error!");
1624                 callback(inventoryItems);
1625                 return;
1626             }
1627 
1628             const std::string& psAttributesPath = subtree[0].first;
1629             const std::string& connection = subtree[0].second.begin()->first;
1630 
1631             if (connection.empty())
1632             {
1633                 BMCWEB_LOG_DEBUG("Power Supply Attributes mapper error!");
1634                 callback(inventoryItems);
1635                 return;
1636             }
1637 
1638             psAttributesConnections[psAttributesPath] = connection;
1639             BMCWEB_LOG_DEBUG("Added mapping {} -> {}", psAttributesPath,
1640                              connection);
1641 
1642             getPowerSupplyAttributesData(sensorsAsyncResp, inventoryItems,
1643                                          psAttributesConnections,
1644                                          std::move(callback));
1645             BMCWEB_LOG_DEBUG("getPowerSupplyAttributes respHandler exit");
1646         });
1647     BMCWEB_LOG_DEBUG("getPowerSupplyAttributes exit");
1648 }
1649 
1650 /**
1651  * @brief Gets inventory items associated with sensors.
1652  *
1653  * Finds the inventory items that are associated with the specified sensors.
1654  * Then gets D-Bus data for the inventory items, such as presence and VPD.
1655  *
1656  * This data is later used to provide sensor property values in the JSON
1657  * response.
1658  *
1659  * Finds the inventory items asynchronously.  Invokes callback when the
1660  * inventory items have been obtained.
1661  *
1662  * The callback must have the following signature:
1663  *   @code
1664  *   callback(std::shared_ptr<std::vector<InventoryItem>> inventoryItems)
1665  *   @endcode
1666  *
1667  * @param sensorsAsyncResp Pointer to object holding response data.
1668  * @param sensorNames All sensors within the current chassis.
1669  * implements ObjectManager.
1670  * @param callback Callback to invoke when inventory items have been obtained.
1671  */
1672 template <typename Callback>
1673 static void
1674     getInventoryItems(std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp,
1675                       const std::shared_ptr<std::set<std::string>> sensorNames,
1676                       Callback&& callback)
1677 {
1678     BMCWEB_LOG_DEBUG("getInventoryItems enter");
1679     auto getInventoryItemAssociationsCb =
1680         [sensorsAsyncResp, callback = std::forward<Callback>(callback)](
1681             std::shared_ptr<std::vector<InventoryItem>>
1682                 inventoryItems) mutable {
1683             BMCWEB_LOG_DEBUG("getInventoryItemAssociationsCb enter");
1684             auto getInventoryItemsConnectionsCb =
1685                 [sensorsAsyncResp, inventoryItems,
1686                  callback = std::forward<Callback>(callback)](
1687                     std::shared_ptr<std::set<std::string>>
1688                         invConnections) mutable {
1689                     BMCWEB_LOG_DEBUG("getInventoryItemsConnectionsCb enter");
1690                     auto getInventoryItemsDataCb =
1691                         [sensorsAsyncResp, inventoryItems,
1692                          callback =
1693                              std::forward<Callback>(callback)]() mutable {
1694                             BMCWEB_LOG_DEBUG("getInventoryItemsDataCb enter");
1695 
1696                             auto getInventoryLedsCb =
1697                                 [sensorsAsyncResp, inventoryItems,
1698                                  callback = std::forward<Callback>(
1699                                      callback)]() mutable {
1700                                     BMCWEB_LOG_DEBUG(
1701                                         "getInventoryLedsCb enter");
1702                                     // Find Power Supply Attributes and get the
1703                                     // data
1704                                     getPowerSupplyAttributes(
1705                                         sensorsAsyncResp, inventoryItems,
1706                                         std::move(callback));
1707                                     BMCWEB_LOG_DEBUG("getInventoryLedsCb exit");
1708                                 };
1709 
1710                             // Find led connections and get the data
1711                             getInventoryLeds(sensorsAsyncResp, inventoryItems,
1712                                              std::move(getInventoryLedsCb));
1713                             BMCWEB_LOG_DEBUG("getInventoryItemsDataCb exit");
1714                         };
1715 
1716                     // Get inventory item data from connections
1717                     getInventoryItemsData(sensorsAsyncResp, inventoryItems,
1718                                           invConnections,
1719                                           std::move(getInventoryItemsDataCb));
1720                     BMCWEB_LOG_DEBUG("getInventoryItemsConnectionsCb exit");
1721                 };
1722 
1723             // Get connections that provide inventory item data
1724             getInventoryItemsConnections(
1725                 sensorsAsyncResp, inventoryItems,
1726                 std::move(getInventoryItemsConnectionsCb));
1727             BMCWEB_LOG_DEBUG("getInventoryItemAssociationsCb exit");
1728         };
1729 
1730     // Get associations from sensors to inventory items
1731     getInventoryItemAssociations(sensorsAsyncResp, sensorNames,
1732                                  std::move(getInventoryItemAssociationsCb));
1733     BMCWEB_LOG_DEBUG("getInventoryItems exit");
1734 }
1735 
1736 /**
1737  * @brief Returns JSON PowerSupply object for the specified inventory item.
1738  *
1739  * Searches for a JSON PowerSupply object that matches the specified inventory
1740  * item.  If one is not found, a new PowerSupply object is added to the JSON
1741  * array.
1742  *
1743  * Multiple sensors are often associated with one power supply inventory item.
1744  * As a result, multiple sensor values are stored in one JSON PowerSupply
1745  * object.
1746  *
1747  * @param powerSupplyArray JSON array containing Redfish PowerSupply objects.
1748  * @param inventoryItem Inventory item for the power supply.
1749  * @param chassisId Chassis that contains the power supply.
1750  * @return JSON PowerSupply object for the specified inventory item.
1751  */
1752 inline nlohmann::json& getPowerSupply(nlohmann::json& powerSupplyArray,
1753                                       const InventoryItem& inventoryItem,
1754                                       const std::string& chassisId)
1755 {
1756     std::string nameS;
1757     nameS.resize(inventoryItem.name.size());
1758     std::ranges::replace_copy(inventoryItem.name, nameS.begin(), '_', ' ');
1759     // Check if matching PowerSupply object already exists in JSON array
1760     for (nlohmann::json& powerSupply : powerSupplyArray)
1761     {
1762         nlohmann::json::iterator nameIt = powerSupply.find("Name");
1763         if (nameIt == powerSupply.end())
1764         {
1765             continue;
1766         }
1767         const std::string* name = nameIt->get_ptr<std::string*>();
1768         if (name == nullptr)
1769         {
1770             continue;
1771         }
1772         if (nameS == *name)
1773         {
1774             return powerSupply;
1775         }
1776     }
1777 
1778     // Add new PowerSupply object to JSON array
1779     powerSupplyArray.push_back({});
1780     nlohmann::json& powerSupply = powerSupplyArray.back();
1781     boost::urls::url url =
1782         boost::urls::format("/redfish/v1/Chassis/{}/Power", chassisId);
1783     url.set_fragment(("/PowerSupplies"_json_pointer).to_string());
1784     powerSupply["@odata.id"] = std::move(url);
1785     std::string escaped;
1786     escaped.resize(inventoryItem.name.size());
1787     std::ranges::replace_copy(inventoryItem.name, escaped.begin(), '_', ' ');
1788     powerSupply["Name"] = std::move(escaped);
1789     powerSupply["Manufacturer"] = inventoryItem.manufacturer;
1790     powerSupply["Model"] = inventoryItem.model;
1791     powerSupply["PartNumber"] = inventoryItem.partNumber;
1792     powerSupply["SerialNumber"] = inventoryItem.serialNumber;
1793     sensor_utils::setLedState(powerSupply, &inventoryItem);
1794 
1795     if (inventoryItem.powerSupplyEfficiencyPercent >= 0)
1796     {
1797         powerSupply["EfficiencyPercent"] =
1798             inventoryItem.powerSupplyEfficiencyPercent;
1799     }
1800 
1801     powerSupply["Status"]["State"] =
1802         sensor_utils::getState(&inventoryItem, true);
1803     const char* health = inventoryItem.isFunctional ? "OK" : "Critical";
1804     powerSupply["Status"]["Health"] = health;
1805 
1806     return powerSupply;
1807 }
1808 
1809 /**
1810  * @brief Gets the values of the specified sensors.
1811  *
1812  * Stores the results as JSON in the SensorsAsyncResp.
1813  *
1814  * Gets the sensor values asynchronously.  Stores the results later when the
1815  * information has been obtained.
1816  *
1817  * The sensorNames set contains all requested sensors for the current chassis.
1818  *
1819  * To minimize the number of DBus calls, the DBus method
1820  * org.freedesktop.DBus.ObjectManager.GetManagedObjects() is used to get the
1821  * values of all sensors provided by a connection (service).
1822  *
1823  * The connections set contains all the connections that provide sensor values.
1824  *
1825  * The InventoryItem vector contains D-Bus inventory items associated with the
1826  * sensors.  Inventory item data is needed for some Redfish sensor properties.
1827  *
1828  * @param SensorsAsyncResp Pointer to object holding response data.
1829  * @param sensorNames All requested sensors within the current chassis.
1830  * @param connections Connections that provide sensor values.
1831  * implements ObjectManager.
1832  * @param inventoryItems Inventory items associated with the sensors.
1833  */
1834 inline void getSensorData(
1835     const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp,
1836     const std::shared_ptr<std::set<std::string>>& sensorNames,
1837     const std::set<std::string>& connections,
1838     const std::shared_ptr<std::vector<InventoryItem>>& inventoryItems)
1839 {
1840     BMCWEB_LOG_DEBUG("getSensorData enter");
1841     // Get managed objects from all services exposing sensors
1842     for (const std::string& connection : connections)
1843     {
1844         sdbusplus::message::object_path sensorPath(
1845             "/xyz/openbmc_project/sensors");
1846         dbus::utility::getManagedObjects(
1847             connection, sensorPath,
1848             [sensorsAsyncResp, sensorNames,
1849              inventoryItems](const boost::system::error_code& ec,
1850                              const dbus::utility::ManagedObjectType& resp) {
1851                 BMCWEB_LOG_DEBUG("getManagedObjectsCb enter");
1852                 if (ec)
1853                 {
1854                     BMCWEB_LOG_ERROR("getManagedObjectsCb DBUS error: {}", ec);
1855                     messages::internalError(sensorsAsyncResp->asyncResp->res);
1856                     return;
1857                 }
1858                 auto chassisSubNode = sensor_utils::chassisSubNodeFromString(
1859                     sensorsAsyncResp->chassisSubNode);
1860                 // Go through all objects and update response with sensor data
1861                 for (const auto& objDictEntry : resp)
1862                 {
1863                     const std::string& objPath =
1864                         static_cast<const std::string&>(objDictEntry.first);
1865                     BMCWEB_LOG_DEBUG("getManagedObjectsCb parsing object {}",
1866                                      objPath);
1867 
1868                     std::vector<std::string> split;
1869                     // Reserve space for
1870                     // /xyz/openbmc_project/sensors/<name>/<subname>
1871                     split.reserve(6);
1872                     // NOLINTNEXTLINE
1873                     bmcweb::split(split, objPath, '/');
1874                     if (split.size() < 6)
1875                     {
1876                         BMCWEB_LOG_ERROR("Got path that isn't long enough {}",
1877                                          objPath);
1878                         continue;
1879                     }
1880                     // These indexes aren't intuitive, as split puts an empty
1881                     // string at the beginning
1882                     const std::string& sensorType = split[4];
1883                     const std::string& sensorName = split[5];
1884                     BMCWEB_LOG_DEBUG("sensorName {} sensorType {}", sensorName,
1885                                      sensorType);
1886                     if (sensorNames->find(objPath) == sensorNames->end())
1887                     {
1888                         BMCWEB_LOG_DEBUG("{} not in sensor list ", sensorName);
1889                         continue;
1890                     }
1891 
1892                     // Find inventory item (if any) associated with sensor
1893                     InventoryItem* inventoryItem =
1894                         findInventoryItemForSensor(inventoryItems, objPath);
1895 
1896                     const std::string& sensorSchema =
1897                         sensorsAsyncResp->chassisSubNode;
1898 
1899                     nlohmann::json* sensorJson = nullptr;
1900 
1901                     if (sensorSchema == sensors::sensorsNodeStr &&
1902                         !sensorsAsyncResp->efficientExpand)
1903                     {
1904                         std::string sensorId =
1905                             redfish::sensor_utils::getSensorId(sensorName,
1906                                                                sensorType);
1907 
1908                         sensorsAsyncResp->asyncResp->res
1909                             .jsonValue["@odata.id"] = boost::urls::format(
1910                             "/redfish/v1/Chassis/{}/{}/{}",
1911                             sensorsAsyncResp->chassisId,
1912                             sensorsAsyncResp->chassisSubNode, sensorId);
1913                         sensorJson =
1914                             &(sensorsAsyncResp->asyncResp->res.jsonValue);
1915                     }
1916                     else
1917                     {
1918                         std::string fieldName;
1919                         if (sensorsAsyncResp->efficientExpand)
1920                         {
1921                             fieldName = "Members";
1922                         }
1923                         else if (sensorType == "temperature")
1924                         {
1925                             fieldName = "Temperatures";
1926                         }
1927                         else if (sensorType == "fan" ||
1928                                  sensorType == "fan_tach" ||
1929                                  sensorType == "fan_pwm")
1930                         {
1931                             fieldName = "Fans";
1932                         }
1933                         else if (sensorType == "voltage")
1934                         {
1935                             fieldName = "Voltages";
1936                         }
1937                         else if (sensorType == "power")
1938                         {
1939                             if (sensorName == "total_power")
1940                             {
1941                                 fieldName = "PowerControl";
1942                             }
1943                             else if ((inventoryItem != nullptr) &&
1944                                      (inventoryItem->isPowerSupply))
1945                             {
1946                                 fieldName = "PowerSupplies";
1947                             }
1948                             else
1949                             {
1950                                 // Other power sensors are in SensorCollection
1951                                 continue;
1952                             }
1953                         }
1954                         else
1955                         {
1956                             BMCWEB_LOG_ERROR(
1957                                 "Unsure how to handle sensorType {}",
1958                                 sensorType);
1959                             continue;
1960                         }
1961 
1962                         nlohmann::json& tempArray =
1963                             sensorsAsyncResp->asyncResp->res
1964                                 .jsonValue[fieldName];
1965                         if (fieldName == "PowerControl")
1966                         {
1967                             if (tempArray.empty())
1968                             {
1969                                 // Put multiple "sensors" into a single
1970                                 // PowerControl. Follows MemberId naming and
1971                                 // naming in power.hpp.
1972                                 nlohmann::json::object_t power;
1973                                 boost::urls::url url = boost::urls::format(
1974                                     "/redfish/v1/Chassis/{}/{}",
1975                                     sensorsAsyncResp->chassisId,
1976                                     sensorsAsyncResp->chassisSubNode);
1977                                 url.set_fragment(
1978                                     (""_json_pointer / fieldName / "0")
1979                                         .to_string());
1980                                 power["@odata.id"] = std::move(url);
1981                                 tempArray.emplace_back(std::move(power));
1982                             }
1983                             sensorJson = &(tempArray.back());
1984                         }
1985                         else if (fieldName == "PowerSupplies")
1986                         {
1987                             if (inventoryItem != nullptr)
1988                             {
1989                                 sensorJson = &(getPowerSupply(
1990                                     tempArray, *inventoryItem,
1991                                     sensorsAsyncResp->chassisId));
1992                             }
1993                         }
1994                         else if (fieldName == "Members")
1995                         {
1996                             std::string sensorId =
1997                                 redfish::sensor_utils::getSensorId(sensorName,
1998                                                                    sensorType);
1999 
2000                             nlohmann::json::object_t member;
2001                             member["@odata.id"] = boost::urls::format(
2002                                 "/redfish/v1/Chassis/{}/{}/{}",
2003                                 sensorsAsyncResp->chassisId,
2004                                 sensorsAsyncResp->chassisSubNode, sensorId);
2005                             tempArray.emplace_back(std::move(member));
2006                             sensorJson = &(tempArray.back());
2007                         }
2008                         else
2009                         {
2010                             nlohmann::json::object_t member;
2011                             boost::urls::url url = boost::urls::format(
2012                                 "/redfish/v1/Chassis/{}/{}",
2013                                 sensorsAsyncResp->chassisId,
2014                                 sensorsAsyncResp->chassisSubNode);
2015                             url.set_fragment(
2016                                 (""_json_pointer / fieldName).to_string());
2017                             member["@odata.id"] = std::move(url);
2018                             tempArray.emplace_back(std::move(member));
2019                             sensorJson = &(tempArray.back());
2020                         }
2021                     }
2022 
2023                     if (sensorJson != nullptr)
2024                     {
2025                         objectInterfacesToJson(
2026                             sensorName, sensorType, chassisSubNode,
2027                             objDictEntry.second, *sensorJson, inventoryItem);
2028 
2029                         std::string path = "/xyz/openbmc_project/sensors/";
2030                         path += sensorType;
2031                         path += "/";
2032                         path += sensorName;
2033                         sensorsAsyncResp->addMetadata(*sensorJson, path);
2034                     }
2035                 }
2036                 if (sensorsAsyncResp.use_count() == 1)
2037                 {
2038                     sortJSONResponse(sensorsAsyncResp);
2039                     if (chassisSubNode ==
2040                             sensor_utils::ChassisSubNode::sensorsNode &&
2041                         sensorsAsyncResp->efficientExpand)
2042                     {
2043                         sensorsAsyncResp->asyncResp->res
2044                             .jsonValue["Members@odata.count"] =
2045                             sensorsAsyncResp->asyncResp->res
2046                                 .jsonValue["Members"]
2047                                 .size();
2048                     }
2049                     else if (chassisSubNode ==
2050                              sensor_utils::ChassisSubNode::thermalNode)
2051                     {
2052                         populateFanRedundancy(sensorsAsyncResp);
2053                     }
2054                 }
2055                 BMCWEB_LOG_DEBUG("getManagedObjectsCb exit");
2056             });
2057     }
2058     BMCWEB_LOG_DEBUG("getSensorData exit");
2059 }
2060 
2061 inline void
2062     processSensorList(const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp,
2063                       const std::shared_ptr<std::set<std::string>>& sensorNames)
2064 {
2065     auto getConnectionCb = [sensorsAsyncResp, sensorNames](
2066                                const std::set<std::string>& connections) {
2067         BMCWEB_LOG_DEBUG("getConnectionCb enter");
2068         auto getInventoryItemsCb =
2069             [sensorsAsyncResp, sensorNames, connections](
2070                 const std::shared_ptr<std::vector<InventoryItem>>&
2071                     inventoryItems) mutable {
2072                 BMCWEB_LOG_DEBUG("getInventoryItemsCb enter");
2073                 // Get sensor data and store results in JSON
2074                 getSensorData(sensorsAsyncResp, sensorNames, connections,
2075                               inventoryItems);
2076                 BMCWEB_LOG_DEBUG("getInventoryItemsCb exit");
2077             };
2078 
2079         // Get inventory items associated with sensors
2080         getInventoryItems(sensorsAsyncResp, sensorNames,
2081                           std::move(getInventoryItemsCb));
2082 
2083         BMCWEB_LOG_DEBUG("getConnectionCb exit");
2084     };
2085 
2086     // Get set of connections that provide sensor values
2087     getConnections(sensorsAsyncResp, sensorNames, std::move(getConnectionCb));
2088 }
2089 
2090 /**
2091  * @brief Entry point for retrieving sensors data related to requested
2092  *        chassis.
2093  * @param SensorsAsyncResp   Pointer to object holding response data
2094  */
2095 inline void
2096     getChassisData(const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp)
2097 {
2098     BMCWEB_LOG_DEBUG("getChassisData enter");
2099     auto getChassisCb =
2100         [sensorsAsyncResp](
2101             const std::shared_ptr<std::set<std::string>>& sensorNames) {
2102             BMCWEB_LOG_DEBUG("getChassisCb enter");
2103             processSensorList(sensorsAsyncResp, sensorNames);
2104             BMCWEB_LOG_DEBUG("getChassisCb exit");
2105         };
2106     // SensorCollection doesn't contain the Redundancy property
2107     if (sensorsAsyncResp->chassisSubNode != sensors::sensorsNodeStr)
2108     {
2109         sensorsAsyncResp->asyncResp->res.jsonValue["Redundancy"] =
2110             nlohmann::json::array();
2111     }
2112     // Get set of sensors in chassis
2113     getChassis(sensorsAsyncResp->asyncResp, sensorsAsyncResp->chassisId,
2114                sensorsAsyncResp->chassisSubNode, sensorsAsyncResp->types,
2115                std::move(getChassisCb));
2116     BMCWEB_LOG_DEBUG("getChassisData exit");
2117 }
2118 
2119 /**
2120  * @brief Find the requested sensorName in the list of all sensors supplied by
2121  * the chassis node
2122  *
2123  * @param sensorName   The sensor name supplied in the PATCH request
2124  * @param sensorsList  The list of sensors managed by the chassis node
2125  * @param sensorsModified  The list of sensors that were found as a result of
2126  *                         repeated calls to this function
2127  */
2128 inline bool findSensorNameUsingSensorPath(
2129     std::string_view sensorName, const std::set<std::string>& sensorsList,
2130     std::set<std::string>& sensorsModified)
2131 {
2132     for (const auto& chassisSensor : sensorsList)
2133     {
2134         sdbusplus::message::object_path path(chassisSensor);
2135         std::string thisSensorName = path.filename();
2136         if (thisSensorName.empty())
2137         {
2138             continue;
2139         }
2140         if (thisSensorName == sensorName)
2141         {
2142             sensorsModified.emplace(chassisSensor);
2143             return true;
2144         }
2145     }
2146     return false;
2147 }
2148 
2149 /**
2150  * @brief Entry point for overriding sensor values of given sensor
2151  *
2152  * @param sensorAsyncResp   response object
2153  * @param allCollections   Collections extract from sensors' request patch info
2154  * @param chassisSubNode   Chassis Node for which the query has to happen
2155  */
2156 inline void setSensorsOverride(
2157     const std::shared_ptr<SensorsAsyncResp>& sensorAsyncResp,
2158     std::unordered_map<std::string, std::vector<nlohmann::json::object_t>>&
2159         allCollections)
2160 {
2161     BMCWEB_LOG_INFO("setSensorsOverride for subNode{}",
2162                     sensorAsyncResp->chassisSubNode);
2163 
2164     std::string_view propertyValueName;
2165     std::unordered_map<std::string, std::pair<double, std::string>> overrideMap;
2166     std::string memberId;
2167     double value = 0.0;
2168     for (auto& collectionItems : allCollections)
2169     {
2170         if (collectionItems.first == "Temperatures")
2171         {
2172             propertyValueName = "ReadingCelsius";
2173         }
2174         else if (collectionItems.first == "Fans")
2175         {
2176             propertyValueName = "Reading";
2177         }
2178         else
2179         {
2180             propertyValueName = "ReadingVolts";
2181         }
2182         for (auto& item : collectionItems.second)
2183         {
2184             if (!json_util::readJsonObject(
2185                     item, sensorAsyncResp->asyncResp->res, "MemberId", memberId,
2186                     propertyValueName, value))
2187             {
2188                 return;
2189             }
2190             overrideMap.emplace(memberId,
2191                                 std::make_pair(value, collectionItems.first));
2192         }
2193     }
2194 
2195     auto getChassisSensorListCb = [sensorAsyncResp, overrideMap,
2196                                    propertyValueNameStr =
2197                                        std::string(propertyValueName)](
2198                                       const std::shared_ptr<
2199                                           std::set<std::string>>& sensorsList) {
2200         // Match sensor names in the PATCH request to those managed by the
2201         // chassis node
2202         const std::shared_ptr<std::set<std::string>> sensorNames =
2203             std::make_shared<std::set<std::string>>();
2204         for (const auto& item : overrideMap)
2205         {
2206             const auto& sensor = item.first;
2207             std::pair<std::string, std::string> sensorNameType =
2208                 redfish::sensor_utils::splitSensorNameAndType(sensor);
2209             if (!findSensorNameUsingSensorPath(sensorNameType.second,
2210                                                *sensorsList, *sensorNames))
2211             {
2212                 BMCWEB_LOG_INFO("Unable to find memberId {}", item.first);
2213                 messages::resourceNotFound(sensorAsyncResp->asyncResp->res,
2214                                            item.second.second, item.first);
2215                 return;
2216             }
2217         }
2218         // Get the connection to which the memberId belongs
2219         auto getObjectsWithConnectionCb = [sensorAsyncResp, overrideMap,
2220                                            propertyValueNameStr](
2221                                               const std::set<
2222                                                   std::string>& /*connections*/,
2223                                               const std::set<std::pair<
2224                                                   std::string, std::string>>&
2225                                                   objectsWithConnection) {
2226             if (objectsWithConnection.size() != overrideMap.size())
2227             {
2228                 BMCWEB_LOG_INFO(
2229                     "Unable to find all objects with proper connection {} requested {}",
2230                     objectsWithConnection.size(), overrideMap.size());
2231                 messages::resourceNotFound(
2232                     sensorAsyncResp->asyncResp->res,
2233                     sensorAsyncResp->chassisSubNode == sensors::thermalNodeStr
2234                         ? "Temperatures"
2235                         : "Voltages",
2236                     "Count");
2237                 return;
2238             }
2239             for (const auto& item : objectsWithConnection)
2240             {
2241                 sdbusplus::message::object_path path(item.first);
2242                 std::string sensorName = path.filename();
2243                 if (sensorName.empty())
2244                 {
2245                     messages::internalError(sensorAsyncResp->asyncResp->res);
2246                     return;
2247                 }
2248                 std::string id = redfish::sensor_utils::getSensorId(
2249                     sensorName, path.parent_path().filename());
2250 
2251                 const auto& iterator = overrideMap.find(id);
2252                 if (iterator == overrideMap.end())
2253                 {
2254                     BMCWEB_LOG_INFO("Unable to find sensor object{}",
2255                                     item.first);
2256                     messages::internalError(sensorAsyncResp->asyncResp->res);
2257                     return;
2258                 }
2259                 setDbusProperty(sensorAsyncResp->asyncResp,
2260                                 propertyValueNameStr, item.second, item.first,
2261                                 "xyz.openbmc_project.Sensor.Value", "Value",
2262                                 iterator->second.first);
2263             }
2264         };
2265         // Get object with connection for the given sensor name
2266         getObjectsWithConnection(sensorAsyncResp, sensorNames,
2267                                  std::move(getObjectsWithConnectionCb));
2268     };
2269     // get full sensor list for the given chassisId and cross verify the sensor.
2270     getChassis(sensorAsyncResp->asyncResp, sensorAsyncResp->chassisId,
2271                sensorAsyncResp->chassisSubNode, sensorAsyncResp->types,
2272                std::move(getChassisSensorListCb));
2273 }
2274 
2275 /**
2276  * @brief Retrieves mapping of Redfish URIs to sensor value property to D-Bus
2277  * path of the sensor.
2278  *
2279  * Function builds valid Redfish response for sensor query of given chassis and
2280  * node. It then builds metadata about Redfish<->D-Bus correlations and provides
2281  * it to caller in a callback.
2282  *
2283  * @param chassis   Chassis for which retrieval should be performed
2284  * @param node  Node (group) of sensors. See sensor_utils::node for supported
2285  * values
2286  * @param mapComplete   Callback to be called with retrieval result
2287  */
2288 template <typename Callback>
2289 inline void retrieveUriToDbusMap(
2290     const std::string& chassis, const std::string& node, Callback&& mapComplete)
2291 {
2292     decltype(sensors::paths)::const_iterator pathIt =
2293         std::find_if(sensors::paths.cbegin(), sensors::paths.cend(),
2294                      [&node](auto&& val) { return val.first == node; });
2295     if (pathIt == sensors::paths.cend())
2296     {
2297         BMCWEB_LOG_ERROR("Wrong node provided : {}", node);
2298         std::map<std::string, std::string> noop;
2299         mapComplete(boost::beast::http::status::bad_request, noop);
2300         return;
2301     }
2302 
2303     auto asyncResp = std::make_shared<bmcweb::AsyncResp>();
2304     auto callback =
2305         [asyncResp, mapCompleteCb = std::forward<Callback>(mapComplete)](
2306             const boost::beast::http::status status,
2307             const std::map<std::string, std::string>& uriToDbus) {
2308             mapCompleteCb(status, uriToDbus);
2309         };
2310 
2311     auto resp = std::make_shared<SensorsAsyncResp>(
2312         asyncResp, chassis, pathIt->second, node, std::move(callback));
2313     getChassisData(resp);
2314 }
2315 
2316 namespace sensors
2317 {
2318 
2319 inline void getChassisCallback(
2320     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2321     std::string_view chassisId, std::string_view chassisSubNode,
2322     const std::shared_ptr<std::set<std::string>>& sensorNames)
2323 {
2324     BMCWEB_LOG_DEBUG("getChassisCallback enter ");
2325 
2326     nlohmann::json& entriesArray = asyncResp->res.jsonValue["Members"];
2327     for (const std::string& sensor : *sensorNames)
2328     {
2329         BMCWEB_LOG_DEBUG("Adding sensor: {}", sensor);
2330 
2331         sdbusplus::message::object_path path(sensor);
2332         std::string sensorName = path.filename();
2333         if (sensorName.empty())
2334         {
2335             BMCWEB_LOG_ERROR("Invalid sensor path: {}", sensor);
2336             messages::internalError(asyncResp->res);
2337             return;
2338         }
2339         std::string type = path.parent_path().filename();
2340         std::string id = redfish::sensor_utils::getSensorId(sensorName, type);
2341 
2342         nlohmann::json::object_t member;
2343         member["@odata.id"] = boost::urls::format(
2344             "/redfish/v1/Chassis/{}/{}/{}", chassisId, chassisSubNode, id);
2345 
2346         entriesArray.emplace_back(std::move(member));
2347     }
2348 
2349     asyncResp->res.jsonValue["Members@odata.count"] = entriesArray.size();
2350     BMCWEB_LOG_DEBUG("getChassisCallback exit");
2351 }
2352 
2353 inline void handleSensorCollectionGet(
2354     App& app, const crow::Request& req,
2355     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2356     const std::string& chassisId)
2357 {
2358     query_param::QueryCapabilities capabilities = {
2359         .canDelegateExpandLevel = 1,
2360     };
2361     query_param::Query delegatedQuery;
2362     if (!redfish::setUpRedfishRouteWithDelegation(app, req, asyncResp,
2363                                                   delegatedQuery, capabilities))
2364     {
2365         return;
2366     }
2367 
2368     if (delegatedQuery.expandType != query_param::ExpandType::None)
2369     {
2370         // we perform efficient expand.
2371         auto sensorsAsyncResp = std::make_shared<SensorsAsyncResp>(
2372             asyncResp, chassisId, sensors::dbus::sensorPaths,
2373             sensors::sensorsNodeStr,
2374             /*efficientExpand=*/true);
2375         getChassisData(sensorsAsyncResp);
2376 
2377         BMCWEB_LOG_DEBUG(
2378             "SensorCollection doGet exit via efficient expand handler");
2379         return;
2380     }
2381 
2382     // We get all sensors as hyperlinkes in the chassis (this
2383     // implies we reply on the default query parameters handler)
2384     getChassis(asyncResp, chassisId, sensors::sensorsNodeStr, dbus::sensorPaths,
2385                std::bind_front(sensors::getChassisCallback, asyncResp,
2386                                chassisId, sensors::sensorsNodeStr));
2387 }
2388 
2389 inline void
2390     getSensorFromDbus(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2391                       const std::string& sensorPath,
2392                       const ::dbus::utility::MapperGetObject& mapperResponse)
2393 {
2394     if (mapperResponse.size() != 1)
2395     {
2396         messages::internalError(asyncResp->res);
2397         return;
2398     }
2399     const auto& valueIface = *mapperResponse.begin();
2400     const std::string& connectionName = valueIface.first;
2401     BMCWEB_LOG_DEBUG("Looking up {}", connectionName);
2402     BMCWEB_LOG_DEBUG("Path {}", sensorPath);
2403 
2404     sdbusplus::asio::getAllProperties(
2405         *crow::connections::systemBus, connectionName, sensorPath, "",
2406         [asyncResp,
2407          sensorPath](const boost::system::error_code& ec,
2408                      const ::dbus::utility::DBusPropertiesMap& valuesDict) {
2409             if (ec)
2410             {
2411                 messages::internalError(asyncResp->res);
2412                 return;
2413             }
2414             sdbusplus::message::object_path path(sensorPath);
2415             std::string name = path.filename();
2416             path = path.parent_path();
2417             std::string type = path.filename();
2418             sensor_utils::objectPropertiesToJson(
2419                 name, type, sensor_utils::ChassisSubNode::sensorsNode,
2420                 valuesDict, asyncResp->res.jsonValue, nullptr);
2421         });
2422 }
2423 
2424 inline void handleSensorGet(App& app, const crow::Request& req,
2425                             const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2426                             const std::string& chassisId,
2427                             const std::string& sensorId)
2428 {
2429     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2430     {
2431         return;
2432     }
2433     std::pair<std::string, std::string> nameType =
2434         redfish::sensor_utils::splitSensorNameAndType(sensorId);
2435     if (nameType.first.empty() || nameType.second.empty())
2436     {
2437         messages::resourceNotFound(asyncResp->res, sensorId, "Sensor");
2438         return;
2439     }
2440 
2441     asyncResp->res.jsonValue["@odata.id"] = boost::urls::format(
2442         "/redfish/v1/Chassis/{}/Sensors/{}", chassisId, sensorId);
2443 
2444     BMCWEB_LOG_DEBUG("Sensor doGet enter");
2445 
2446     constexpr std::array<std::string_view, 1> interfaces = {
2447         "xyz.openbmc_project.Sensor.Value"};
2448     std::string sensorPath = "/xyz/openbmc_project/sensors/" + nameType.first +
2449                              '/' + nameType.second;
2450     // Get a list of all of the sensors that implement Sensor.Value
2451     // and get the path and service name associated with the sensor
2452     ::dbus::utility::getDbusObject(
2453         sensorPath, interfaces,
2454         [asyncResp, sensorId,
2455          sensorPath](const boost::system::error_code& ec,
2456                      const ::dbus::utility::MapperGetObject& subtree) {
2457             BMCWEB_LOG_DEBUG("respHandler1 enter");
2458             if (ec == boost::system::errc::io_error)
2459             {
2460                 BMCWEB_LOG_WARNING("Sensor not found from getSensorPaths");
2461                 messages::resourceNotFound(asyncResp->res, sensorId, "Sensor");
2462                 return;
2463             }
2464             if (ec)
2465             {
2466                 messages::internalError(asyncResp->res);
2467                 BMCWEB_LOG_ERROR(
2468                     "Sensor getSensorPaths resp_handler: Dbus error {}", ec);
2469                 return;
2470             }
2471             getSensorFromDbus(asyncResp, sensorPath, subtree);
2472             BMCWEB_LOG_DEBUG("respHandler1 exit");
2473         });
2474 }
2475 
2476 } // namespace sensors
2477 
2478 inline void requestRoutesSensorCollection(App& app)
2479 {
2480     BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/Sensors/")
2481         .privileges(redfish::privileges::getSensorCollection)
2482         .methods(boost::beast::http::verb::get)(
2483             std::bind_front(sensors::handleSensorCollectionGet, std::ref(app)));
2484 }
2485 
2486 inline void requestRoutesSensor(App& app)
2487 {
2488     BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/Sensors/<str>/")
2489         .privileges(redfish::privileges::getSensor)
2490         .methods(boost::beast::http::verb::get)(
2491             std::bind_front(sensors::handleSensorGet, std::ref(app)));
2492 }
2493 
2494 } // namespace redfish
2495