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