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