xref: /openbmc/bmcweb/redfish-core/lib/sensors.hpp (revision a14c9113)
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         std::string name;
128         std::string uri;
129         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 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 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 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 inline 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