xref: /openbmc/bmcweb/redfish-core/lib/sensors.hpp (revision 340d74c8)
1 /*
2 Copyright (c) 2018 Intel Corporation
3 
4 Licensed under the Apache License, Version 2.0 (the "License");
5 you may not use this file except in compliance with the License.
6 You may obtain a copy of the License at
7 
8       http://www.apache.org/licenses/LICENSE-2.0
9 
10 Unless required by applicable law or agreed to in writing, software
11 distributed under the License is distributed on an "AS IS" BASIS,
12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 See the License for the specific language governing permissions and
14 limitations under the License.
15 */
16 #pragma once
17 
18 #include "app.hpp"
19 #include "dbus_singleton.hpp"
20 #include "dbus_utility.hpp"
21 #include "generated/enums/redundancy.hpp"
22 #include "generated/enums/resource.hpp"
23 #include "query.hpp"
24 #include "registries/privilege_registry.hpp"
25 #include "str_utility.hpp"
26 #include "utils/dbus_utils.hpp"
27 #include "utils/json_utils.hpp"
28 #include "utils/query_param.hpp"
29 #include "utils/sensor_utils.hpp"
30 
31 #include <boost/system/error_code.hpp>
32 #include <boost/url/format.hpp>
33 #include <sdbusplus/asio/property.hpp>
34 #include <sdbusplus/unpack_properties.hpp>
35 
36 #include <array>
37 #include <cmath>
38 #include <iterator>
39 #include <limits>
40 #include <map>
41 #include <ranges>
42 #include <set>
43 #include <string>
44 #include <string_view>
45 #include <utility>
46 #include <variant>
47 
48 namespace redfish
49 {
50 
51 namespace sensors
52 {
53 
54 // clang-format off
55 namespace dbus
56 {
57 constexpr auto powerPaths = std::to_array<std::string_view>({
58     "/xyz/openbmc_project/sensors/voltage",
59     "/xyz/openbmc_project/sensors/power"
60 });
61 
62 constexpr auto getSensorPaths(){
63     if constexpr(BMCWEB_REDFISH_NEW_POWERSUBSYSTEM_THERMALSUBSYSTEM){
64     return std::to_array<std::string_view>({
65         "/xyz/openbmc_project/sensors/power",
66         "/xyz/openbmc_project/sensors/current",
67         "/xyz/openbmc_project/sensors/airflow",
68         "/xyz/openbmc_project/sensors/humidity",
69         "/xyz/openbmc_project/sensors/voltage",
70         "/xyz/openbmc_project/sensors/fan_tach",
71         "/xyz/openbmc_project/sensors/temperature",
72         "/xyz/openbmc_project/sensors/fan_pwm",
73         "/xyz/openbmc_project/sensors/altitude",
74         "/xyz/openbmc_project/sensors/energy",
75         "/xyz/openbmc_project/sensors/utilization"});
76     } else {
77       return  std::to_array<std::string_view>({"/xyz/openbmc_project/sensors/power",
78         "/xyz/openbmc_project/sensors/current",
79         "/xyz/openbmc_project/sensors/airflow",
80         "/xyz/openbmc_project/sensors/humidity",
81         "/xyz/openbmc_project/sensors/utilization"});
82 }
83 }
84 
85 constexpr auto sensorPaths = getSensorPaths();
86 
87 constexpr auto thermalPaths = std::to_array<std::string_view>({
88     "/xyz/openbmc_project/sensors/fan_tach",
89     "/xyz/openbmc_project/sensors/temperature",
90     "/xyz/openbmc_project/sensors/fan_pwm"
91 });
92 
93 } // namespace dbus
94 // clang-format on
95 
96 constexpr std::string_view powerNodeStr = sensor_utils::chassisSubNodeToString(
97     sensor_utils::ChassisSubNode::powerNode);
98 constexpr std::string_view sensorsNodeStr =
99     sensor_utils::chassisSubNodeToString(
100         sensor_utils::ChassisSubNode::sensorsNode);
101 constexpr std::string_view thermalNodeStr =
102     sensor_utils::chassisSubNodeToString(
103         sensor_utils::ChassisSubNode::thermalNode);
104 
105 using sensorPair =
106     std::pair<std::string_view, std::span<const std::string_view>>;
107 static constexpr std::array<sensorPair, 3> paths = {
108     {{sensors::powerNodeStr, dbus::powerPaths},
109      {sensors::sensorsNodeStr, dbus::sensorPaths},
110      {sensors::thermalNodeStr, dbus::thermalPaths}}};
111 
112 } // namespace sensors
113 
114 /**
115  * SensorsAsyncResp
116  * Gathers data needed for response processing after async calls are done
117  */
118 class SensorsAsyncResp
119 {
120   public:
121     using DataCompleteCb = std::function<void(
122         const boost::beast::http::status status,
123         const std::map<std::string, std::string>& uriToDbus)>;
124 
125     struct SensorData
126     {
127         std::string name;
128         std::string uri;
129         std::string dbusPath;
130     };
131 
132     SensorsAsyncResp(const std::shared_ptr<bmcweb::AsyncResp>& asyncRespIn,
133                      const std::string& chassisIdIn,
134                      std::span<const std::string_view> typesIn,
135                      std::string_view subNode) :
136         asyncResp(asyncRespIn), chassisId(chassisIdIn), types(typesIn),
137         chassisSubNode(subNode), efficientExpand(false)
138     {}
139 
140     // Store extra data about sensor mapping and return it in callback
141     SensorsAsyncResp(const std::shared_ptr<bmcweb::AsyncResp>& asyncRespIn,
142                      const std::string& chassisIdIn,
143                      std::span<const std::string_view> typesIn,
144                      std::string_view subNode,
145                      DataCompleteCb&& creationComplete) :
146         asyncResp(asyncRespIn), chassisId(chassisIdIn), types(typesIn),
147         chassisSubNode(subNode), efficientExpand(false),
148         metadata{std::vector<SensorData>()},
149         dataComplete{std::move(creationComplete)}
150     {}
151 
152     // sensor collections expand
153     SensorsAsyncResp(const std::shared_ptr<bmcweb::AsyncResp>& asyncRespIn,
154                      const std::string& chassisIdIn,
155                      std::span<const std::string_view> typesIn,
156                      const std::string_view& subNode, bool efficientExpandIn) :
157         asyncResp(asyncRespIn), chassisId(chassisIdIn), types(typesIn),
158         chassisSubNode(subNode), efficientExpand(efficientExpandIn)
159     {}
160 
161     ~SensorsAsyncResp()
162     {
163         if (asyncResp->res.result() ==
164             boost::beast::http::status::internal_server_error)
165         {
166             // Reset the json object to clear out any data that made it in
167             // before the error happened todo(ed) handle error condition with
168             // proper code
169             asyncResp->res.jsonValue = nlohmann::json::object();
170         }
171 
172         if (dataComplete && metadata)
173         {
174             std::map<std::string, std::string> map;
175             if (asyncResp->res.result() == boost::beast::http::status::ok)
176             {
177                 for (auto& sensor : *metadata)
178                 {
179                     map.emplace(sensor.uri, sensor.dbusPath);
180                 }
181             }
182             dataComplete(asyncResp->res.result(), map);
183         }
184     }
185 
186     SensorsAsyncResp(const SensorsAsyncResp&) = delete;
187     SensorsAsyncResp(SensorsAsyncResp&&) = delete;
188     SensorsAsyncResp& operator=(const SensorsAsyncResp&) = delete;
189     SensorsAsyncResp& operator=(SensorsAsyncResp&&) = delete;
190 
191     void addMetadata(const nlohmann::json& sensorObject,
192                      const std::string& dbusPath)
193     {
194         if (metadata)
195         {
196             metadata->emplace_back(SensorData{
197                 sensorObject["Name"], sensorObject["@odata.id"], dbusPath});
198         }
199     }
200 
201     void updateUri(const std::string& name, const std::string& uri)
202     {
203         if (metadata)
204         {
205             for (auto& sensor : *metadata)
206             {
207                 if (sensor.name == name)
208                 {
209                     sensor.uri = uri;
210                 }
211             }
212         }
213     }
214 
215     const std::shared_ptr<bmcweb::AsyncResp> asyncResp;
216     const std::string chassisId;
217     const std::span<const std::string_view> types;
218     const std::string chassisSubNode;
219     const bool efficientExpand;
220 
221   private:
222     std::optional<std::vector<SensorData>> metadata;
223     DataCompleteCb dataComplete;
224 };
225 
226 using InventoryItem = sensor_utils::InventoryItem;
227 
228 /**
229  * @brief Get objects with connection necessary for sensors
230  * @param SensorsAsyncResp Pointer to object holding response data
231  * @param sensorNames Sensors retrieved from chassis
232  * @param callback Callback for processing gathered connections
233  */
234 template <typename Callback>
235 void getObjectsWithConnection(
236     const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp,
237     const std::shared_ptr<std::set<std::string>>& sensorNames,
238     Callback&& callback)
239 {
240     BMCWEB_LOG_DEBUG("getObjectsWithConnection enter");
241     const std::string path = "/xyz/openbmc_project/sensors";
242     constexpr std::array<std::string_view, 1> interfaces = {
243         "xyz.openbmc_project.Sensor.Value"};
244 
245     // Make call to ObjectMapper to find all sensors objects
246     dbus::utility::getSubTree(
247         path, 2, interfaces,
248         [callback = std::forward<Callback>(callback), sensorsAsyncResp,
249          sensorNames](const boost::system::error_code& ec,
250                       const dbus::utility::MapperGetSubTreeResponse& subtree) {
251             // Response handler for parsing objects subtree
252             BMCWEB_LOG_DEBUG("getObjectsWithConnection resp_handler enter");
253             if (ec)
254             {
255                 messages::internalError(sensorsAsyncResp->asyncResp->res);
256                 BMCWEB_LOG_ERROR(
257                     "getObjectsWithConnection resp_handler: Dbus error {}", ec);
258                 return;
259             }
260 
261             BMCWEB_LOG_DEBUG("Found {} subtrees", subtree.size());
262 
263             // Make unique list of connections only for requested sensor types
264             // and found in the chassis
265             std::set<std::string> connections;
266             std::set<std::pair<std::string, std::string>> objectsWithConnection;
267 
268             BMCWEB_LOG_DEBUG("sensorNames list count: {}", sensorNames->size());
269             for (const std::string& tsensor : *sensorNames)
270             {
271                 BMCWEB_LOG_DEBUG("Sensor to find: {}", tsensor);
272             }
273 
274             for (const std::pair<std::string,
275                                  std::vector<std::pair<
276                                      std::string, std::vector<std::string>>>>&
277                      object : subtree)
278             {
279                 if (sensorNames->find(object.first) != sensorNames->end())
280                 {
281                     for (const std::pair<std::string, std::vector<std::string>>&
282                              objData : object.second)
283                     {
284                         BMCWEB_LOG_DEBUG("Adding connection: {}",
285                                          objData.first);
286                         connections.insert(objData.first);
287                         objectsWithConnection.insert(
288                             std::make_pair(object.first, objData.first));
289                     }
290                 }
291             }
292             BMCWEB_LOG_DEBUG("Found {} connections", connections.size());
293             callback(std::move(connections), std::move(objectsWithConnection));
294             BMCWEB_LOG_DEBUG("getObjectsWithConnection resp_handler exit");
295         });
296     BMCWEB_LOG_DEBUG("getObjectsWithConnection exit");
297 }
298 
299 /**
300  * @brief Create connections necessary for sensors
301  * @param SensorsAsyncResp Pointer to object holding response data
302  * @param sensorNames Sensors retrieved from chassis
303  * @param callback Callback for processing gathered connections
304  */
305 template <typename Callback>
306 void getConnections(std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp,
307                     const std::shared_ptr<std::set<std::string>> sensorNames,
308                     Callback&& callback)
309 {
310     auto objectsWithConnectionCb =
311         [callback = std::forward<Callback>(callback)](
312             const std::set<std::string>& connections,
313             const std::set<std::pair<std::string, std::string>>&
314             /*objectsWithConnection*/) { callback(connections); };
315     getObjectsWithConnection(sensorsAsyncResp, sensorNames,
316                              std::move(objectsWithConnectionCb));
317 }
318 
319 /**
320  * @brief Shrinks the list of sensors for processing
321  * @param SensorsAysncResp  The class holding the Redfish response
322  * @param allSensors  A list of all the sensors associated to the
323  * chassis element (i.e. baseboard, front panel, etc...)
324  * @param activeSensors A list that is a reduction of the incoming
325  * allSensors list.  Eliminate Thermal sensors when a Power request is
326  * made, and eliminate Power sensors when a Thermal request is made.
327  */
328 inline void reduceSensorList(
329     crow::Response& res, std::string_view chassisSubNode,
330     std::span<const std::string_view> sensorTypes,
331     const std::vector<std::string>* allSensors,
332     const std::shared_ptr<std::set<std::string>>& activeSensors)
333 {
334     if ((allSensors == nullptr) || (activeSensors == nullptr))
335     {
336         messages::resourceNotFound(res, chassisSubNode,
337                                    chassisSubNode == sensors::thermalNodeStr
338                                        ? "Temperatures"
339                                        : "Voltages");
340 
341         return;
342     }
343     if (allSensors->empty())
344     {
345         // Nothing to do, the activeSensors object is also empty
346         return;
347     }
348 
349     for (std::string_view type : sensorTypes)
350     {
351         for (const std::string& sensor : *allSensors)
352         {
353             if (sensor.starts_with(type))
354             {
355                 activeSensors->emplace(sensor);
356             }
357         }
358     }
359 }
360 
361 /*
362  *Populates the top level collection for a given subnode.  Populates
363  *SensorCollection, Power, or Thermal schemas.
364  *
365  * */
366 inline void populateChassisNode(nlohmann::json& jsonValue,
367                                 std::string_view chassisSubNode)
368 {
369     if (chassisSubNode == sensors::powerNodeStr)
370     {
371         jsonValue["@odata.type"] = "#Power.v1_5_2.Power";
372     }
373     else if (chassisSubNode == sensors::thermalNodeStr)
374     {
375         jsonValue["@odata.type"] = "#Thermal.v1_4_0.Thermal";
376         jsonValue["Fans"] = nlohmann::json::array();
377         jsonValue["Temperatures"] = nlohmann::json::array();
378     }
379     else if (chassisSubNode == sensors::sensorsNodeStr)
380     {
381         jsonValue["@odata.type"] = "#SensorCollection.SensorCollection";
382         jsonValue["Description"] = "Collection of Sensors for this Chassis";
383         jsonValue["Members"] = nlohmann::json::array();
384         jsonValue["Members@odata.count"] = 0;
385     }
386 
387     if (chassisSubNode != sensors::sensorsNodeStr)
388     {
389         jsonValue["Id"] = chassisSubNode;
390     }
391     jsonValue["Name"] = chassisSubNode;
392 }
393 
394 /**
395  * @brief Retrieves requested chassis sensors and redundancy data from DBus .
396  * @param SensorsAsyncResp   Pointer to object holding response data
397  * @param callback  Callback for next step in gathered sensor processing
398  */
399 template <typename Callback>
400 void getChassis(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
401                 std::string_view chassisId, std::string_view chassisSubNode,
402                 std::span<const std::string_view> sensorTypes,
403                 Callback&& callback)
404 {
405     BMCWEB_LOG_DEBUG("getChassis enter");
406     constexpr std::array<std::string_view, 2> interfaces = {
407         "xyz.openbmc_project.Inventory.Item.Board",
408         "xyz.openbmc_project.Inventory.Item.Chassis"};
409 
410     // Get the Chassis Collection
411     dbus::utility::getSubTreePaths(
412         "/xyz/openbmc_project/inventory", 0, interfaces,
413         [callback = std::forward<Callback>(callback), asyncResp,
414          chassisIdStr{std::string(chassisId)},
415          chassisSubNode{std::string(chassisSubNode)},
416          sensorTypes](const boost::system::error_code& ec,
417                       const dbus::utility::MapperGetSubTreePathsResponse&
418                           chassisPaths) mutable {
419             BMCWEB_LOG_DEBUG("getChassis respHandler enter");
420             if (ec)
421             {
422                 BMCWEB_LOG_ERROR("getChassis respHandler DBUS error: {}", ec);
423                 messages::internalError(asyncResp->res);
424                 return;
425             }
426             const std::string* chassisPath = nullptr;
427             for (const std::string& chassis : chassisPaths)
428             {
429                 sdbusplus::message::object_path path(chassis);
430                 std::string chassisName = path.filename();
431                 if (chassisName.empty())
432                 {
433                     BMCWEB_LOG_ERROR("Failed to find '/' in {}", chassis);
434                     continue;
435                 }
436                 if (chassisName == chassisIdStr)
437                 {
438                     chassisPath = &chassis;
439                     break;
440                 }
441             }
442             if (chassisPath == nullptr)
443             {
444                 messages::resourceNotFound(asyncResp->res, "Chassis",
445                                            chassisIdStr);
446                 return;
447             }
448             populateChassisNode(asyncResp->res.jsonValue, chassisSubNode);
449 
450             asyncResp->res.jsonValue["@odata.id"] = boost::urls::format(
451                 "/redfish/v1/Chassis/{}/{}", chassisIdStr, chassisSubNode);
452 
453             // Get the list of all sensors for this Chassis element
454             std::string sensorPath = *chassisPath + "/all_sensors";
455             dbus::utility::getAssociationEndPoints(
456                 sensorPath, [asyncResp, chassisSubNode, sensorTypes,
457                              callback = std::forward<Callback>(callback)](
458                                 const boost::system::error_code& ec2,
459                                 const dbus::utility::MapperEndPoints&
460                                     nodeSensorList) mutable {
461                     if (ec2)
462                     {
463                         if (ec2.value() != EBADR)
464                         {
465                             messages::internalError(asyncResp->res);
466                             return;
467                         }
468                     }
469                     const std::shared_ptr<std::set<std::string>>
470                         culledSensorList =
471                             std::make_shared<std::set<std::string>>();
472                     reduceSensorList(asyncResp->res, chassisSubNode,
473                                      sensorTypes, &nodeSensorList,
474                                      culledSensorList);
475                     BMCWEB_LOG_DEBUG("Finishing with {}",
476                                      culledSensorList->size());
477                     callback(culledSensorList);
478                 });
479         });
480     BMCWEB_LOG_DEBUG("getChassis exit");
481 }
482 
483 /**
484  * @brief Builds a json sensor representation of a sensor.
485  * @param sensorName  The name of the sensor to be built
486  * @param sensorType  The type (temperature, fan_tach, etc) of the sensor to
487  * build
488  * @param chassisSubNode The subnode (thermal, sensor, etc) of the sensor
489  * @param interfacesDict  A dictionary of the interfaces and properties of said
490  * interfaces to be built from
491  * @param sensorJson  The json object to fill
492  * @param inventoryItem D-Bus inventory item associated with the sensor.  Will
493  * be nullptr if no associated inventory item was found.
494  */
495 inline void objectInterfacesToJson(
496     const std::string& sensorName, const std::string& sensorType,
497     const sensor_utils::ChassisSubNode chassisSubNode,
498     const dbus::utility::DBusInterfacesMap& interfacesDict,
499     nlohmann::json& sensorJson, InventoryItem* inventoryItem)
500 {
501     for (const auto& [interface, valuesDict] : interfacesDict)
502     {
503         sensor_utils::objectPropertiesToJson(
504             sensorName, sensorType, chassisSubNode, valuesDict, sensorJson,
505             inventoryItem);
506     }
507     BMCWEB_LOG_DEBUG("Added sensor {}", sensorName);
508 }
509 
510 inline void populateFanRedundancy(
511     const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp)
512 {
513     constexpr std::array<std::string_view, 1> interfaces = {
514         "xyz.openbmc_project.Control.FanRedundancy"};
515     dbus::utility::getSubTree(
516         "/xyz/openbmc_project/control", 2, interfaces,
517         [sensorsAsyncResp](
518             const boost::system::error_code& ec,
519             const dbus::utility::MapperGetSubTreeResponse& resp) {
520             if (ec)
521             {
522                 return; // don't have to have this interface
523             }
524             for (const std::pair<std::string, dbus::utility::MapperServiceMap>&
525                      pathPair : resp)
526             {
527                 const std::string& path = pathPair.first;
528                 const dbus::utility::MapperServiceMap& objDict =
529                     pathPair.second;
530                 if (objDict.empty())
531                 {
532                     continue; // this should be impossible
533                 }
534 
535                 const std::string& owner = objDict.begin()->first;
536                 dbus::utility::getAssociationEndPoints(
537                     path + "/chassis",
538                     [path, owner, sensorsAsyncResp](
539                         const boost::system::error_code& ec2,
540                         const dbus::utility::MapperEndPoints& endpoints) {
541                         if (ec2)
542                         {
543                             return; // if they don't have an association we
544                                     // can't tell what chassis is
545                         }
546                         auto found = std::ranges::find_if(
547                             endpoints,
548                             [sensorsAsyncResp](const std::string& entry) {
549                                 return entry.find(
550                                            sensorsAsyncResp->chassisId) !=
551                                        std::string::npos;
552                             });
553 
554                         if (found == endpoints.end())
555                         {
556                             return;
557                         }
558                         sdbusplus::asio::getAllProperties(
559                             *crow::connections::systemBus, owner, path,
560                             "xyz.openbmc_project.Control.FanRedundancy",
561                             [path, sensorsAsyncResp](
562                                 const boost::system::error_code& ec3,
563                                 const dbus::utility::DBusPropertiesMap& ret) {
564                                 if (ec3)
565                                 {
566                                     return; // don't have to have this
567                                             // interface
568                                 }
569 
570                                 const uint8_t* allowedFailures = nullptr;
571                                 const std::vector<std::string>* collection =
572                                     nullptr;
573                                 const std::string* status = nullptr;
574 
575                                 const bool success =
576                                     sdbusplus::unpackPropertiesNoThrow(
577                                         dbus_utils::UnpackErrorPrinter(), ret,
578                                         "AllowedFailures", allowedFailures,
579                                         "Collection", collection, "Status",
580                                         status);
581 
582                                 if (!success)
583                                 {
584                                     messages::internalError(
585                                         sensorsAsyncResp->asyncResp->res);
586                                     return;
587                                 }
588 
589                                 if (allowedFailures == nullptr ||
590                                     collection == nullptr || status == nullptr)
591                                 {
592                                     BMCWEB_LOG_ERROR(
593                                         "Invalid redundancy interface");
594                                     messages::internalError(
595                                         sensorsAsyncResp->asyncResp->res);
596                                     return;
597                                 }
598 
599                                 sdbusplus::message::object_path objectPath(
600                                     path);
601                                 std::string name = objectPath.filename();
602                                 if (name.empty())
603                                 {
604                                     // this should be impossible
605                                     messages::internalError(
606                                         sensorsAsyncResp->asyncResp->res);
607                                     return;
608                                 }
609                                 std::ranges::replace(name, '_', ' ');
610 
611                                 std::string health;
612 
613                                 if (status->ends_with("Full"))
614                                 {
615                                     health = "OK";
616                                 }
617                                 else if (status->ends_with("Degraded"))
618                                 {
619                                     health = "Warning";
620                                 }
621                                 else
622                                 {
623                                     health = "Critical";
624                                 }
625                                 nlohmann::json::array_t redfishCollection;
626                                 const auto& fanRedfish =
627                                     sensorsAsyncResp->asyncResp->res
628                                         .jsonValue["Fans"];
629                                 for (const std::string& item : *collection)
630                                 {
631                                     sdbusplus::message::object_path itemPath(
632                                         item);
633                                     std::string itemName = itemPath.filename();
634                                     if (itemName.empty())
635                                     {
636                                         continue;
637                                     }
638                                     /*
639                                     todo(ed): merge patch that fixes the names
640                                     std::replace(itemName.begin(),
641                                                  itemName.end(), '_', ' ');*/
642                                     auto schemaItem = std::ranges::find_if(
643                                         fanRedfish,
644                                         [itemName](const nlohmann::json& fan) {
645                                             return fan["Name"] == itemName;
646                                         });
647                                     if (schemaItem != fanRedfish.end())
648                                     {
649                                         nlohmann::json::object_t collectionId;
650                                         collectionId["@odata.id"] =
651                                             (*schemaItem)["@odata.id"];
652                                         redfishCollection.emplace_back(
653                                             std::move(collectionId));
654                                     }
655                                     else
656                                     {
657                                         BMCWEB_LOG_ERROR(
658                                             "failed to find fan in schema");
659                                         messages::internalError(
660                                             sensorsAsyncResp->asyncResp->res);
661                                         return;
662                                     }
663                                 }
664 
665                                 size_t minNumNeeded =
666                                     collection->empty()
667                                         ? 0
668                                         : collection->size() - *allowedFailures;
669                                 nlohmann::json& jResp =
670                                     sensorsAsyncResp->asyncResp->res
671                                         .jsonValue["Redundancy"];
672 
673                                 nlohmann::json::object_t redundancy;
674                                 boost::urls::url url = boost::urls::format(
675                                     "/redfish/v1/Chassis/{}/{}",
676                                     sensorsAsyncResp->chassisId,
677                                     sensorsAsyncResp->chassisSubNode);
678                                 url.set_fragment(
679                                     ("/Redundancy"_json_pointer / jResp.size())
680                                         .to_string());
681                                 redundancy["@odata.id"] = std::move(url);
682                                 redundancy["@odata.type"] =
683                                     "#Redundancy.v1_3_2.Redundancy";
684                                 redundancy["MinNumNeeded"] = minNumNeeded;
685                                 redundancy["Mode"] =
686                                     redundancy::RedundancyType::NPlusM;
687                                 redundancy["Name"] = name;
688                                 redundancy["RedundancySet"] = redfishCollection;
689                                 redundancy["Status"]["Health"] = health;
690                                 redundancy["Status"]["State"] =
691                                     resource::State::Enabled;
692 
693                                 jResp.emplace_back(std::move(redundancy));
694                             });
695                     });
696             }
697         });
698 }
699 
700 inline void
701     sortJSONResponse(const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp)
702 {
703     nlohmann::json& response = sensorsAsyncResp->asyncResp->res.jsonValue;
704     std::array<std::string, 2> sensorHeaders{"Temperatures", "Fans"};
705     if (sensorsAsyncResp->chassisSubNode == sensors::powerNodeStr)
706     {
707         sensorHeaders = {"Voltages", "PowerSupplies"};
708     }
709     for (const std::string& sensorGroup : sensorHeaders)
710     {
711         nlohmann::json::iterator entry = response.find(sensorGroup);
712         if (entry == response.end())
713         {
714             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         sdbusplus::asio::getProperty<std::string>(
1379             *crow::connections::systemBus, ledConnection, ledPath,
1380             "xyz.openbmc_project.Led.Physical", "State",
1381             std::move(respHandler));
1382     }
1383 
1384     BMCWEB_LOG_DEBUG("getInventoryLedData exit");
1385 }
1386 
1387 /**
1388  * @brief Gets LED data for LEDs associated with given inventory items.
1389  *
1390  * Gets the D-Bus connections (services) that provide LED data for the LEDs
1391  * associated with the specified inventory items.  Then gets the LED data from
1392  * each connection and stores it in the inventory item.
1393  *
1394  * This data is later used to provide sensor property values in the JSON
1395  * response.
1396  *
1397  * Finds the LED data asynchronously.  Invokes callback when information has
1398  * been obtained.
1399  *
1400  * The callback must have the following signature:
1401  *   @code
1402  *   callback()
1403  *   @endcode
1404  *
1405  * @param sensorsAsyncResp Pointer to object holding response data.
1406  * @param inventoryItems D-Bus inventory items associated with sensors.
1407  * @param callback Callback to invoke when inventory items have been obtained.
1408  */
1409 template <typename Callback>
1410 void getInventoryLeds(
1411     std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp,
1412     std::shared_ptr<std::vector<InventoryItem>> inventoryItems,
1413     Callback&& callback)
1414 {
1415     BMCWEB_LOG_DEBUG("getInventoryLeds enter");
1416 
1417     const std::string path = "/xyz/openbmc_project";
1418     constexpr std::array<std::string_view, 1> interfaces = {
1419         "xyz.openbmc_project.Led.Physical"};
1420 
1421     // Make call to ObjectMapper to find all inventory items
1422     dbus::utility::getSubTree(
1423         path, 0, interfaces,
1424         [callback = std::forward<Callback>(callback), sensorsAsyncResp,
1425          inventoryItems](
1426             const boost::system::error_code& ec,
1427             const dbus::utility::MapperGetSubTreeResponse& subtree) mutable {
1428             // Response handler for parsing output from GetSubTree
1429             BMCWEB_LOG_DEBUG("getInventoryLeds respHandler enter");
1430             if (ec)
1431             {
1432                 messages::internalError(sensorsAsyncResp->asyncResp->res);
1433                 BMCWEB_LOG_ERROR("getInventoryLeds respHandler DBus error {}",
1434                                  ec);
1435                 return;
1436             }
1437 
1438             // Build map of LED object paths to connections
1439             std::shared_ptr<std::map<std::string, std::string>> ledConnections =
1440                 std::make_shared<std::map<std::string, std::string>>();
1441 
1442             // Loop through objects from GetSubTree
1443             for (const std::pair<std::string,
1444                                  std::vector<std::pair<
1445                                      std::string, std::vector<std::string>>>>&
1446                      object : subtree)
1447             {
1448                 // Check if object path is LED for one of the specified
1449                 // inventory items
1450                 const std::string& ledPath = object.first;
1451                 if (findInventoryItemForLed(*inventoryItems, ledPath) !=
1452                     nullptr)
1453                 {
1454                     // Add mapping from ledPath to connection
1455                     const std::string& connection =
1456                         object.second.begin()->first;
1457                     (*ledConnections)[ledPath] = connection;
1458                     BMCWEB_LOG_DEBUG("Added mapping {} -> {}", ledPath,
1459                                      connection);
1460                 }
1461             }
1462 
1463             getInventoryLedData(sensorsAsyncResp, inventoryItems,
1464                                 ledConnections, std::move(callback));
1465             BMCWEB_LOG_DEBUG("getInventoryLeds respHandler exit");
1466         });
1467     BMCWEB_LOG_DEBUG("getInventoryLeds exit");
1468 }
1469 
1470 /**
1471  * @brief Gets D-Bus data for Power Supply Attributes such as EfficiencyPercent
1472  *
1473  * Uses the specified connections (services) (currently assumes just one) to
1474  * obtain D-Bus data for Power Supply Attributes. Stores the resulting data in
1475  * the inventoryItems vector. Only stores data in Power Supply inventoryItems.
1476  *
1477  * This data is later used to provide sensor property values in the JSON
1478  * response.
1479  *
1480  * Finds the Power Supply Attributes data asynchronously.  Invokes callback
1481  * when data has been obtained.
1482  *
1483  * The callback must have the following signature:
1484  *   @code
1485  *   callback(std::shared_ptr<std::vector<InventoryItem>> inventoryItems)
1486  *   @endcode
1487  *
1488  * @param sensorsAsyncResp Pointer to object holding response data.
1489  * @param inventoryItems D-Bus inventory items associated with sensors.
1490  * @param psAttributesConnections Connections that provide data for the Power
1491  *        Supply Attributes
1492  * @param callback Callback to invoke when data has been obtained.
1493  */
1494 template <typename Callback>
1495 void getPowerSupplyAttributesData(
1496     const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp,
1497     std::shared_ptr<std::vector<InventoryItem>> inventoryItems,
1498     const std::map<std::string, std::string>& psAttributesConnections,
1499     Callback&& callback)
1500 {
1501     BMCWEB_LOG_DEBUG("getPowerSupplyAttributesData enter");
1502 
1503     if (psAttributesConnections.empty())
1504     {
1505         BMCWEB_LOG_DEBUG("Can't find PowerSupplyAttributes, no connections!");
1506         callback(inventoryItems);
1507         return;
1508     }
1509 
1510     // Assuming just one connection (service) for now
1511     auto it = psAttributesConnections.begin();
1512 
1513     const std::string& psAttributesPath = (*it).first;
1514     const std::string& psAttributesConnection = (*it).second;
1515 
1516     // Response handler for Get DeratingFactor property
1517     auto respHandler = [sensorsAsyncResp, inventoryItems,
1518                         callback = std::forward<Callback>(callback)](
1519                            const boost::system::error_code& ec,
1520                            uint32_t value) mutable {
1521         BMCWEB_LOG_DEBUG("getPowerSupplyAttributesData respHandler enter");
1522         if (ec)
1523         {
1524             BMCWEB_LOG_ERROR(
1525                 "getPowerSupplyAttributesData respHandler DBus error {}", ec);
1526             messages::internalError(sensorsAsyncResp->asyncResp->res);
1527             return;
1528         }
1529 
1530         BMCWEB_LOG_DEBUG("PS EfficiencyPercent value: {}", value);
1531         // Store value in Power Supply Inventory Items
1532         for (InventoryItem& inventoryItem : *inventoryItems)
1533         {
1534             if (inventoryItem.isPowerSupply)
1535             {
1536                 inventoryItem.powerSupplyEfficiencyPercent =
1537                     static_cast<int>(value);
1538             }
1539         }
1540 
1541         BMCWEB_LOG_DEBUG("getPowerSupplyAttributesData respHandler exit");
1542         callback(inventoryItems);
1543     };
1544 
1545     // Get the DeratingFactor property for the PowerSupplyAttributes
1546     // Currently only property on the interface/only one we care about
1547     sdbusplus::asio::getProperty<uint32_t>(
1548         *crow::connections::systemBus, psAttributesConnection, psAttributesPath,
1549         "xyz.openbmc_project.Control.PowerSupplyAttributes", "DeratingFactor",
1550         std::move(respHandler));
1551 
1552     BMCWEB_LOG_DEBUG("getPowerSupplyAttributesData exit");
1553 }
1554 
1555 /**
1556  * @brief Gets the Power Supply Attributes such as EfficiencyPercent
1557  *
1558  * Gets the D-Bus connection (service) that provides Power Supply Attributes
1559  * data. Then gets the Power Supply Attributes data from the connection
1560  * (currently just assumes 1 connection) and stores the data in the inventory
1561  * item.
1562  *
1563  * This data is later used to provide sensor property values in the JSON
1564  * response. DeratingFactor on D-Bus is mapped to EfficiencyPercent on Redfish.
1565  *
1566  * Finds the Power Supply Attributes data asynchronously. Invokes callback
1567  * when information has been obtained.
1568  *
1569  * The callback must have the following signature:
1570  *   @code
1571  *   callback(std::shared_ptr<std::vector<InventoryItem>> inventoryItems)
1572  *   @endcode
1573  *
1574  * @param sensorsAsyncResp Pointer to object holding response data.
1575  * @param inventoryItems D-Bus inventory items associated with sensors.
1576  * @param callback Callback to invoke when data has been obtained.
1577  */
1578 template <typename Callback>
1579 void getPowerSupplyAttributes(
1580     std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp,
1581     std::shared_ptr<std::vector<InventoryItem>> inventoryItems,
1582     Callback&& callback)
1583 {
1584     BMCWEB_LOG_DEBUG("getPowerSupplyAttributes enter");
1585 
1586     // Only need the power supply attributes when the Power Schema
1587     if (sensorsAsyncResp->chassisSubNode != sensors::powerNodeStr)
1588     {
1589         BMCWEB_LOG_DEBUG("getPowerSupplyAttributes exit since not Power");
1590         callback(inventoryItems);
1591         return;
1592     }
1593 
1594     constexpr std::array<std::string_view, 1> interfaces = {
1595         "xyz.openbmc_project.Control.PowerSupplyAttributes"};
1596 
1597     // Make call to ObjectMapper to find the PowerSupplyAttributes service
1598     dbus::utility::getSubTree(
1599         "/xyz/openbmc_project", 0, interfaces,
1600         [callback = std::forward<Callback>(callback), sensorsAsyncResp,
1601          inventoryItems](
1602             const boost::system::error_code& ec,
1603             const dbus::utility::MapperGetSubTreeResponse& subtree) mutable {
1604             // Response handler for parsing output from GetSubTree
1605             BMCWEB_LOG_DEBUG("getPowerSupplyAttributes respHandler enter");
1606             if (ec)
1607             {
1608                 messages::internalError(sensorsAsyncResp->asyncResp->res);
1609                 BMCWEB_LOG_ERROR(
1610                     "getPowerSupplyAttributes respHandler DBus error {}", ec);
1611                 return;
1612             }
1613             if (subtree.empty())
1614             {
1615                 BMCWEB_LOG_DEBUG("Can't find Power Supply Attributes!");
1616                 callback(inventoryItems);
1617                 return;
1618             }
1619 
1620             // Currently we only support 1 power supply attribute, use this for
1621             // all the power supplies. Build map of object path to connection.
1622             // Assume just 1 connection and 1 path for now.
1623             std::map<std::string, std::string> psAttributesConnections;
1624 
1625             if (subtree[0].first.empty() || subtree[0].second.empty())
1626             {
1627                 BMCWEB_LOG_DEBUG("Power Supply Attributes mapper error!");
1628                 callback(inventoryItems);
1629                 return;
1630             }
1631 
1632             const std::string& psAttributesPath = subtree[0].first;
1633             const std::string& connection = subtree[0].second.begin()->first;
1634 
1635             if (connection.empty())
1636             {
1637                 BMCWEB_LOG_DEBUG("Power Supply Attributes mapper error!");
1638                 callback(inventoryItems);
1639                 return;
1640             }
1641 
1642             psAttributesConnections[psAttributesPath] = connection;
1643             BMCWEB_LOG_DEBUG("Added mapping {} -> {}", psAttributesPath,
1644                              connection);
1645 
1646             getPowerSupplyAttributesData(sensorsAsyncResp, inventoryItems,
1647                                          psAttributesConnections,
1648                                          std::move(callback));
1649             BMCWEB_LOG_DEBUG("getPowerSupplyAttributes respHandler exit");
1650         });
1651     BMCWEB_LOG_DEBUG("getPowerSupplyAttributes exit");
1652 }
1653 
1654 /**
1655  * @brief Gets inventory items associated with sensors.
1656  *
1657  * Finds the inventory items that are associated with the specified sensors.
1658  * Then gets D-Bus data for the inventory items, such as presence and VPD.
1659  *
1660  * This data is later used to provide sensor property values in the JSON
1661  * response.
1662  *
1663  * Finds the inventory items asynchronously.  Invokes callback when the
1664  * inventory items have been obtained.
1665  *
1666  * The callback must have the following signature:
1667  *   @code
1668  *   callback(std::shared_ptr<std::vector<InventoryItem>> inventoryItems)
1669  *   @endcode
1670  *
1671  * @param sensorsAsyncResp Pointer to object holding response data.
1672  * @param sensorNames All sensors within the current chassis.
1673  * implements ObjectManager.
1674  * @param callback Callback to invoke when inventory items have been obtained.
1675  */
1676 template <typename Callback>
1677 inline void
1678     getInventoryItems(std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp,
1679                       const std::shared_ptr<std::set<std::string>> sensorNames,
1680                       Callback&& callback)
1681 {
1682     BMCWEB_LOG_DEBUG("getInventoryItems enter");
1683     auto getInventoryItemAssociationsCb =
1684         [sensorsAsyncResp, callback = std::forward<Callback>(callback)](
1685             std::shared_ptr<std::vector<InventoryItem>>
1686                 inventoryItems) mutable {
1687             BMCWEB_LOG_DEBUG("getInventoryItemAssociationsCb enter");
1688             auto getInventoryItemsConnectionsCb =
1689                 [sensorsAsyncResp, inventoryItems,
1690                  callback = std::forward<Callback>(callback)](
1691                     std::shared_ptr<std::set<std::string>>
1692                         invConnections) mutable {
1693                     BMCWEB_LOG_DEBUG("getInventoryItemsConnectionsCb enter");
1694                     auto getInventoryItemsDataCb =
1695                         [sensorsAsyncResp, inventoryItems,
1696                          callback =
1697                              std::forward<Callback>(callback)]() mutable {
1698                             BMCWEB_LOG_DEBUG("getInventoryItemsDataCb enter");
1699 
1700                             auto getInventoryLedsCb =
1701                                 [sensorsAsyncResp, inventoryItems,
1702                                  callback = std::forward<Callback>(
1703                                      callback)]() mutable {
1704                                     BMCWEB_LOG_DEBUG(
1705                                         "getInventoryLedsCb enter");
1706                                     // Find Power Supply Attributes and get the
1707                                     // data
1708                                     getPowerSupplyAttributes(
1709                                         sensorsAsyncResp, inventoryItems,
1710                                         std::move(callback));
1711                                     BMCWEB_LOG_DEBUG("getInventoryLedsCb exit");
1712                                 };
1713 
1714                             // Find led connections and get the data
1715                             getInventoryLeds(sensorsAsyncResp, inventoryItems,
1716                                              std::move(getInventoryLedsCb));
1717                             BMCWEB_LOG_DEBUG("getInventoryItemsDataCb exit");
1718                         };
1719 
1720                     // Get inventory item data from connections
1721                     getInventoryItemsData(sensorsAsyncResp, inventoryItems,
1722                                           invConnections,
1723                                           std::move(getInventoryItemsDataCb));
1724                     BMCWEB_LOG_DEBUG("getInventoryItemsConnectionsCb exit");
1725                 };
1726 
1727             // Get connections that provide inventory item data
1728             getInventoryItemsConnections(
1729                 sensorsAsyncResp, inventoryItems,
1730                 std::move(getInventoryItemsConnectionsCb));
1731             BMCWEB_LOG_DEBUG("getInventoryItemAssociationsCb exit");
1732         };
1733 
1734     // Get associations from sensors to inventory items
1735     getInventoryItemAssociations(sensorsAsyncResp, sensorNames,
1736                                  std::move(getInventoryItemAssociationsCb));
1737     BMCWEB_LOG_DEBUG("getInventoryItems exit");
1738 }
1739 
1740 /**
1741  * @brief Returns JSON PowerSupply object for the specified inventory item.
1742  *
1743  * Searches for a JSON PowerSupply object that matches the specified inventory
1744  * item.  If one is not found, a new PowerSupply object is added to the JSON
1745  * array.
1746  *
1747  * Multiple sensors are often associated with one power supply inventory item.
1748  * As a result, multiple sensor values are stored in one JSON PowerSupply
1749  * object.
1750  *
1751  * @param powerSupplyArray JSON array containing Redfish PowerSupply objects.
1752  * @param inventoryItem Inventory item for the power supply.
1753  * @param chassisId Chassis that contains the power supply.
1754  * @return JSON PowerSupply object for the specified inventory item.
1755  */
1756 inline nlohmann::json& getPowerSupply(nlohmann::json& powerSupplyArray,
1757                                       const InventoryItem& inventoryItem,
1758                                       const std::string& chassisId)
1759 {
1760     std::string nameS;
1761     nameS.resize(inventoryItem.name.size());
1762     std::ranges::replace_copy(inventoryItem.name, nameS.begin(), '_', ' ');
1763     // Check if matching PowerSupply object already exists in JSON array
1764     for (nlohmann::json& powerSupply : powerSupplyArray)
1765     {
1766         nlohmann::json::iterator nameIt = powerSupply.find("Name");
1767         if (nameIt == powerSupply.end())
1768         {
1769             continue;
1770         }
1771         const std::string* name = nameIt->get_ptr<std::string*>();
1772         if (name == nullptr)
1773         {
1774             continue;
1775         }
1776         if (nameS == *name)
1777         {
1778             return powerSupply;
1779         }
1780     }
1781 
1782     // Add new PowerSupply object to JSON array
1783     powerSupplyArray.push_back({});
1784     nlohmann::json& powerSupply = powerSupplyArray.back();
1785     boost::urls::url url =
1786         boost::urls::format("/redfish/v1/Chassis/{}/Power", chassisId);
1787     url.set_fragment(("/PowerSupplies"_json_pointer).to_string());
1788     powerSupply["@odata.id"] = std::move(url);
1789     std::string escaped;
1790     escaped.resize(inventoryItem.name.size());
1791     std::ranges::replace_copy(inventoryItem.name, escaped.begin(), '_', ' ');
1792     powerSupply["Name"] = std::move(escaped);
1793     powerSupply["Manufacturer"] = inventoryItem.manufacturer;
1794     powerSupply["Model"] = inventoryItem.model;
1795     powerSupply["PartNumber"] = inventoryItem.partNumber;
1796     powerSupply["SerialNumber"] = inventoryItem.serialNumber;
1797     sensor_utils::setLedState(powerSupply, &inventoryItem);
1798 
1799     if (inventoryItem.powerSupplyEfficiencyPercent >= 0)
1800     {
1801         powerSupply["EfficiencyPercent"] =
1802             inventoryItem.powerSupplyEfficiencyPercent;
1803     }
1804 
1805     powerSupply["Status"]["State"] =
1806         sensor_utils::getState(&inventoryItem, true);
1807     const char* health = inventoryItem.isFunctional ? "OK" : "Critical";
1808     powerSupply["Status"]["Health"] = health;
1809 
1810     return powerSupply;
1811 }
1812 
1813 /**
1814  * @brief Gets the values of the specified sensors.
1815  *
1816  * Stores the results as JSON in the SensorsAsyncResp.
1817  *
1818  * Gets the sensor values asynchronously.  Stores the results later when the
1819  * information has been obtained.
1820  *
1821  * The sensorNames set contains all requested sensors for the current chassis.
1822  *
1823  * To minimize the number of DBus calls, the DBus method
1824  * org.freedesktop.DBus.ObjectManager.GetManagedObjects() is used to get the
1825  * values of all sensors provided by a connection (service).
1826  *
1827  * The connections set contains all the connections that provide sensor values.
1828  *
1829  * The InventoryItem vector contains D-Bus inventory items associated with the
1830  * sensors.  Inventory item data is needed for some Redfish sensor properties.
1831  *
1832  * @param SensorsAsyncResp Pointer to object holding response data.
1833  * @param sensorNames All requested sensors within the current chassis.
1834  * @param connections Connections that provide sensor values.
1835  * implements ObjectManager.
1836  * @param inventoryItems Inventory items associated with the sensors.
1837  */
1838 inline void getSensorData(
1839     const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp,
1840     const std::shared_ptr<std::set<std::string>>& sensorNames,
1841     const std::set<std::string>& connections,
1842     const std::shared_ptr<std::vector<InventoryItem>>& inventoryItems)
1843 {
1844     BMCWEB_LOG_DEBUG("getSensorData enter");
1845     // Get managed objects from all services exposing sensors
1846     for (const std::string& connection : connections)
1847     {
1848         sdbusplus::message::object_path sensorPath(
1849             "/xyz/openbmc_project/sensors");
1850         dbus::utility::getManagedObjects(
1851             connection, sensorPath,
1852             [sensorsAsyncResp, sensorNames,
1853              inventoryItems](const boost::system::error_code& ec,
1854                              const dbus::utility::ManagedObjectType& resp) {
1855                 BMCWEB_LOG_DEBUG("getManagedObjectsCb enter");
1856                 if (ec)
1857                 {
1858                     BMCWEB_LOG_ERROR("getManagedObjectsCb DBUS error: {}", ec);
1859                     messages::internalError(sensorsAsyncResp->asyncResp->res);
1860                     return;
1861                 }
1862                 auto chassisSubNode = sensor_utils::chassisSubNodeFromString(
1863                     sensorsAsyncResp->chassisSubNode);
1864                 // Go through all objects and update response with sensor data
1865                 for (const auto& objDictEntry : resp)
1866                 {
1867                     const std::string& objPath =
1868                         static_cast<const std::string&>(objDictEntry.first);
1869                     BMCWEB_LOG_DEBUG("getManagedObjectsCb parsing object {}",
1870                                      objPath);
1871 
1872                     std::vector<std::string> split;
1873                     // Reserve space for
1874                     // /xyz/openbmc_project/sensors/<name>/<subname>
1875                     split.reserve(6);
1876                     // NOLINTNEXTLINE
1877                     bmcweb::split(split, objPath, '/');
1878                     if (split.size() < 6)
1879                     {
1880                         BMCWEB_LOG_ERROR("Got path that isn't long enough {}",
1881                                          objPath);
1882                         continue;
1883                     }
1884                     // These indexes aren't intuitive, as split puts an empty
1885                     // string at the beginning
1886                     const std::string& sensorType = split[4];
1887                     const std::string& sensorName = split[5];
1888                     BMCWEB_LOG_DEBUG("sensorName {} sensorType {}", sensorName,
1889                                      sensorType);
1890                     if (sensorNames->find(objPath) == sensorNames->end())
1891                     {
1892                         BMCWEB_LOG_DEBUG("{} not in sensor list ", sensorName);
1893                         continue;
1894                     }
1895 
1896                     // Find inventory item (if any) associated with sensor
1897                     InventoryItem* inventoryItem =
1898                         findInventoryItemForSensor(inventoryItems, objPath);
1899 
1900                     const std::string& sensorSchema =
1901                         sensorsAsyncResp->chassisSubNode;
1902 
1903                     nlohmann::json* sensorJson = nullptr;
1904 
1905                     if (sensorSchema == sensors::sensorsNodeStr &&
1906                         !sensorsAsyncResp->efficientExpand)
1907                     {
1908                         std::string sensorId =
1909                             redfish::sensor_utils::getSensorId(sensorName,
1910                                                                sensorType);
1911 
1912                         sensorsAsyncResp->asyncResp->res
1913                             .jsonValue["@odata.id"] = boost::urls::format(
1914                             "/redfish/v1/Chassis/{}/{}/{}",
1915                             sensorsAsyncResp->chassisId,
1916                             sensorsAsyncResp->chassisSubNode, sensorId);
1917                         sensorJson =
1918                             &(sensorsAsyncResp->asyncResp->res.jsonValue);
1919                     }
1920                     else
1921                     {
1922                         std::string fieldName;
1923                         if (sensorsAsyncResp->efficientExpand)
1924                         {
1925                             fieldName = "Members";
1926                         }
1927                         else if (sensorType == "temperature")
1928                         {
1929                             fieldName = "Temperatures";
1930                         }
1931                         else if (sensorType == "fan" ||
1932                                  sensorType == "fan_tach" ||
1933                                  sensorType == "fan_pwm")
1934                         {
1935                             fieldName = "Fans";
1936                         }
1937                         else if (sensorType == "voltage")
1938                         {
1939                             fieldName = "Voltages";
1940                         }
1941                         else if (sensorType == "power")
1942                         {
1943                             if (sensorName == "total_power")
1944                             {
1945                                 fieldName = "PowerControl";
1946                             }
1947                             else if ((inventoryItem != nullptr) &&
1948                                      (inventoryItem->isPowerSupply))
1949                             {
1950                                 fieldName = "PowerSupplies";
1951                             }
1952                             else
1953                             {
1954                                 // Other power sensors are in SensorCollection
1955                                 continue;
1956                             }
1957                         }
1958                         else
1959                         {
1960                             BMCWEB_LOG_ERROR(
1961                                 "Unsure how to handle sensorType {}",
1962                                 sensorType);
1963                             continue;
1964                         }
1965 
1966                         nlohmann::json& tempArray =
1967                             sensorsAsyncResp->asyncResp->res
1968                                 .jsonValue[fieldName];
1969                         if (fieldName == "PowerControl")
1970                         {
1971                             if (tempArray.empty())
1972                             {
1973                                 // Put multiple "sensors" into a single
1974                                 // PowerControl. Follows MemberId naming and
1975                                 // naming in power.hpp.
1976                                 nlohmann::json::object_t power;
1977                                 boost::urls::url url = boost::urls::format(
1978                                     "/redfish/v1/Chassis/{}/{}",
1979                                     sensorsAsyncResp->chassisId,
1980                                     sensorsAsyncResp->chassisSubNode);
1981                                 url.set_fragment(
1982                                     (""_json_pointer / fieldName / "0")
1983                                         .to_string());
1984                                 power["@odata.id"] = std::move(url);
1985                                 tempArray.emplace_back(std::move(power));
1986                             }
1987                             sensorJson = &(tempArray.back());
1988                         }
1989                         else if (fieldName == "PowerSupplies")
1990                         {
1991                             if (inventoryItem != nullptr)
1992                             {
1993                                 sensorJson = &(getPowerSupply(
1994                                     tempArray, *inventoryItem,
1995                                     sensorsAsyncResp->chassisId));
1996                             }
1997                         }
1998                         else if (fieldName == "Members")
1999                         {
2000                             std::string sensorId =
2001                                 redfish::sensor_utils::getSensorId(sensorName,
2002                                                                    sensorType);
2003 
2004                             nlohmann::json::object_t member;
2005                             member["@odata.id"] = boost::urls::format(
2006                                 "/redfish/v1/Chassis/{}/{}/{}",
2007                                 sensorsAsyncResp->chassisId,
2008                                 sensorsAsyncResp->chassisSubNode, sensorId);
2009                             tempArray.emplace_back(std::move(member));
2010                             sensorJson = &(tempArray.back());
2011                         }
2012                         else
2013                         {
2014                             nlohmann::json::object_t member;
2015                             boost::urls::url url = boost::urls::format(
2016                                 "/redfish/v1/Chassis/{}/{}",
2017                                 sensorsAsyncResp->chassisId,
2018                                 sensorsAsyncResp->chassisSubNode);
2019                             url.set_fragment(
2020                                 (""_json_pointer / fieldName).to_string());
2021                             member["@odata.id"] = std::move(url);
2022                             tempArray.emplace_back(std::move(member));
2023                             sensorJson = &(tempArray.back());
2024                         }
2025                     }
2026 
2027                     if (sensorJson != nullptr)
2028                     {
2029                         objectInterfacesToJson(
2030                             sensorName, sensorType, chassisSubNode,
2031                             objDictEntry.second, *sensorJson, inventoryItem);
2032 
2033                         std::string path = "/xyz/openbmc_project/sensors/";
2034                         path += sensorType;
2035                         path += "/";
2036                         path += sensorName;
2037                         sensorsAsyncResp->addMetadata(*sensorJson, path);
2038                     }
2039                 }
2040                 if (sensorsAsyncResp.use_count() == 1)
2041                 {
2042                     sortJSONResponse(sensorsAsyncResp);
2043                     if (chassisSubNode ==
2044                             sensor_utils::ChassisSubNode::sensorsNode &&
2045                         sensorsAsyncResp->efficientExpand)
2046                     {
2047                         sensorsAsyncResp->asyncResp->res
2048                             .jsonValue["Members@odata.count"] =
2049                             sensorsAsyncResp->asyncResp->res
2050                                 .jsonValue["Members"]
2051                                 .size();
2052                     }
2053                     else if (chassisSubNode ==
2054                              sensor_utils::ChassisSubNode::thermalNode)
2055                     {
2056                         populateFanRedundancy(sensorsAsyncResp);
2057                     }
2058                 }
2059                 BMCWEB_LOG_DEBUG("getManagedObjectsCb exit");
2060             });
2061     }
2062     BMCWEB_LOG_DEBUG("getSensorData exit");
2063 }
2064 
2065 inline void
2066     processSensorList(const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp,
2067                       const std::shared_ptr<std::set<std::string>>& sensorNames)
2068 {
2069     auto getConnectionCb = [sensorsAsyncResp, sensorNames](
2070                                const std::set<std::string>& connections) {
2071         BMCWEB_LOG_DEBUG("getConnectionCb enter");
2072         auto getInventoryItemsCb =
2073             [sensorsAsyncResp, sensorNames, connections](
2074                 const std::shared_ptr<std::vector<InventoryItem>>&
2075                     inventoryItems) mutable {
2076                 BMCWEB_LOG_DEBUG("getInventoryItemsCb enter");
2077                 // Get sensor data and store results in JSON
2078                 getSensorData(sensorsAsyncResp, sensorNames, connections,
2079                               inventoryItems);
2080                 BMCWEB_LOG_DEBUG("getInventoryItemsCb exit");
2081             };
2082 
2083         // Get inventory items associated with sensors
2084         getInventoryItems(sensorsAsyncResp, sensorNames,
2085                           std::move(getInventoryItemsCb));
2086 
2087         BMCWEB_LOG_DEBUG("getConnectionCb exit");
2088     };
2089 
2090     // Get set of connections that provide sensor values
2091     getConnections(sensorsAsyncResp, sensorNames, std::move(getConnectionCb));
2092 }
2093 
2094 /**
2095  * @brief Entry point for retrieving sensors data related to requested
2096  *        chassis.
2097  * @param SensorsAsyncResp   Pointer to object holding response data
2098  */
2099 inline void
2100     getChassisData(const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp)
2101 {
2102     BMCWEB_LOG_DEBUG("getChassisData enter");
2103     auto getChassisCb =
2104         [sensorsAsyncResp](
2105             const std::shared_ptr<std::set<std::string>>& sensorNames) {
2106             BMCWEB_LOG_DEBUG("getChassisCb enter");
2107             processSensorList(sensorsAsyncResp, sensorNames);
2108             BMCWEB_LOG_DEBUG("getChassisCb exit");
2109         };
2110     // SensorCollection doesn't contain the Redundancy property
2111     if (sensorsAsyncResp->chassisSubNode != sensors::sensorsNodeStr)
2112     {
2113         sensorsAsyncResp->asyncResp->res.jsonValue["Redundancy"] =
2114             nlohmann::json::array();
2115     }
2116     // Get set of sensors in chassis
2117     getChassis(sensorsAsyncResp->asyncResp, sensorsAsyncResp->chassisId,
2118                sensorsAsyncResp->chassisSubNode, sensorsAsyncResp->types,
2119                std::move(getChassisCb));
2120     BMCWEB_LOG_DEBUG("getChassisData exit");
2121 }
2122 
2123 /**
2124  * @brief Find the requested sensorName in the list of all sensors supplied by
2125  * the chassis node
2126  *
2127  * @param sensorName   The sensor name supplied in the PATCH request
2128  * @param sensorsList  The list of sensors managed by the chassis node
2129  * @param sensorsModified  The list of sensors that were found as a result of
2130  *                         repeated calls to this function
2131  */
2132 inline bool findSensorNameUsingSensorPath(
2133     std::string_view sensorName, const std::set<std::string>& sensorsList,
2134     std::set<std::string>& sensorsModified)
2135 {
2136     for (const auto& chassisSensor : sensorsList)
2137     {
2138         sdbusplus::message::object_path path(chassisSensor);
2139         std::string thisSensorName = path.filename();
2140         if (thisSensorName.empty())
2141         {
2142             continue;
2143         }
2144         if (thisSensorName == sensorName)
2145         {
2146             sensorsModified.emplace(chassisSensor);
2147             return true;
2148         }
2149     }
2150     return false;
2151 }
2152 
2153 /**
2154  * @brief Entry point for overriding sensor values of given sensor
2155  *
2156  * @param sensorAsyncResp   response object
2157  * @param allCollections   Collections extract from sensors' request patch info
2158  * @param chassisSubNode   Chassis Node for which the query has to happen
2159  */
2160 inline void setSensorsOverride(
2161     const std::shared_ptr<SensorsAsyncResp>& sensorAsyncResp,
2162     std::unordered_map<std::string, std::vector<nlohmann::json::object_t>>&
2163         allCollections)
2164 {
2165     BMCWEB_LOG_INFO("setSensorsOverride for subNode{}",
2166                     sensorAsyncResp->chassisSubNode);
2167 
2168     std::string_view propertyValueName;
2169     std::unordered_map<std::string, std::pair<double, std::string>> overrideMap;
2170     std::string memberId;
2171     double value = 0.0;
2172     for (auto& collectionItems : allCollections)
2173     {
2174         if (collectionItems.first == "Temperatures")
2175         {
2176             propertyValueName = "ReadingCelsius";
2177         }
2178         else if (collectionItems.first == "Fans")
2179         {
2180             propertyValueName = "Reading";
2181         }
2182         else
2183         {
2184             propertyValueName = "ReadingVolts";
2185         }
2186         for (auto& item : collectionItems.second)
2187         {
2188             if (!json_util::readJsonObject( //
2189                     item, sensorAsyncResp->asyncResp->res, //
2190                     "MemberId", memberId, //
2191                     propertyValueName, value //
2192                     ))
2193             {
2194                 return;
2195             }
2196             overrideMap.emplace(memberId,
2197                                 std::make_pair(value, collectionItems.first));
2198         }
2199     }
2200 
2201     auto getChassisSensorListCb = [sensorAsyncResp, overrideMap,
2202                                    propertyValueNameStr =
2203                                        std::string(propertyValueName)](
2204                                       const std::shared_ptr<
2205                                           std::set<std::string>>& sensorsList) {
2206         // Match sensor names in the PATCH request to those managed by the
2207         // chassis node
2208         const std::shared_ptr<std::set<std::string>> sensorNames =
2209             std::make_shared<std::set<std::string>>();
2210         for (const auto& item : overrideMap)
2211         {
2212             const auto& sensor = item.first;
2213             std::pair<std::string, std::string> sensorNameType =
2214                 redfish::sensor_utils::splitSensorNameAndType(sensor);
2215             if (!findSensorNameUsingSensorPath(sensorNameType.second,
2216                                                *sensorsList, *sensorNames))
2217             {
2218                 BMCWEB_LOG_INFO("Unable to find memberId {}", item.first);
2219                 messages::resourceNotFound(sensorAsyncResp->asyncResp->res,
2220                                            item.second.second, item.first);
2221                 return;
2222             }
2223         }
2224         // Get the connection to which the memberId belongs
2225         auto getObjectsWithConnectionCb = [sensorAsyncResp, overrideMap,
2226                                            propertyValueNameStr](
2227                                               const std::set<
2228                                                   std::string>& /*connections*/,
2229                                               const std::set<std::pair<
2230                                                   std::string, std::string>>&
2231                                                   objectsWithConnection) {
2232             if (objectsWithConnection.size() != overrideMap.size())
2233             {
2234                 BMCWEB_LOG_INFO(
2235                     "Unable to find all objects with proper connection {} requested {}",
2236                     objectsWithConnection.size(), overrideMap.size());
2237                 messages::resourceNotFound(
2238                     sensorAsyncResp->asyncResp->res,
2239                     sensorAsyncResp->chassisSubNode == sensors::thermalNodeStr
2240                         ? "Temperatures"
2241                         : "Voltages",
2242                     "Count");
2243                 return;
2244             }
2245             for (const auto& item : objectsWithConnection)
2246             {
2247                 sdbusplus::message::object_path path(item.first);
2248                 std::string sensorName = path.filename();
2249                 if (sensorName.empty())
2250                 {
2251                     messages::internalError(sensorAsyncResp->asyncResp->res);
2252                     return;
2253                 }
2254                 std::string id = redfish::sensor_utils::getSensorId(
2255                     sensorName, path.parent_path().filename());
2256 
2257                 const auto& iterator = overrideMap.find(id);
2258                 if (iterator == overrideMap.end())
2259                 {
2260                     BMCWEB_LOG_INFO("Unable to find sensor object{}",
2261                                     item.first);
2262                     messages::internalError(sensorAsyncResp->asyncResp->res);
2263                     return;
2264                 }
2265                 setDbusProperty(sensorAsyncResp->asyncResp,
2266                                 propertyValueNameStr, item.second, item.first,
2267                                 "xyz.openbmc_project.Sensor.Value", "Value",
2268                                 iterator->second.first);
2269             }
2270         };
2271         // Get object with connection for the given sensor name
2272         getObjectsWithConnection(sensorAsyncResp, sensorNames,
2273                                  std::move(getObjectsWithConnectionCb));
2274     };
2275     // get full sensor list for the given chassisId and cross verify the sensor.
2276     getChassis(sensorAsyncResp->asyncResp, sensorAsyncResp->chassisId,
2277                sensorAsyncResp->chassisSubNode, sensorAsyncResp->types,
2278                std::move(getChassisSensorListCb));
2279 }
2280 
2281 /**
2282  * @brief Retrieves mapping of Redfish URIs to sensor value property to D-Bus
2283  * path of the sensor.
2284  *
2285  * Function builds valid Redfish response for sensor query of given chassis and
2286  * node. It then builds metadata about Redfish<->D-Bus correlations and provides
2287  * it to caller in a callback.
2288  *
2289  * @param chassis   Chassis for which retrieval should be performed
2290  * @param node  Node (group) of sensors. See sensor_utils::node for supported
2291  * values
2292  * @param mapComplete   Callback to be called with retrieval result
2293  */
2294 template <typename Callback>
2295 inline void retrieveUriToDbusMap(
2296     const std::string& chassis, const std::string& node, Callback&& mapComplete)
2297 {
2298     decltype(sensors::paths)::const_iterator pathIt =
2299         std::find_if(sensors::paths.cbegin(), sensors::paths.cend(),
2300                      [&node](auto&& val) { return val.first == node; });
2301     if (pathIt == sensors::paths.cend())
2302     {
2303         BMCWEB_LOG_ERROR("Wrong node provided : {}", node);
2304         std::map<std::string, std::string> noop;
2305         mapComplete(boost::beast::http::status::bad_request, noop);
2306         return;
2307     }
2308 
2309     auto asyncResp = std::make_shared<bmcweb::AsyncResp>();
2310     auto callback =
2311         [asyncResp, mapCompleteCb = std::forward<Callback>(mapComplete)](
2312             const boost::beast::http::status status,
2313             const std::map<std::string, std::string>& uriToDbus) {
2314             mapCompleteCb(status, uriToDbus);
2315         };
2316 
2317     auto resp = std::make_shared<SensorsAsyncResp>(
2318         asyncResp, chassis, pathIt->second, node, std::move(callback));
2319     getChassisData(resp);
2320 }
2321 
2322 namespace sensors
2323 {
2324 
2325 inline void getChassisCallback(
2326     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2327     std::string_view chassisId, std::string_view chassisSubNode,
2328     const std::shared_ptr<std::set<std::string>>& sensorNames)
2329 {
2330     BMCWEB_LOG_DEBUG("getChassisCallback enter ");
2331 
2332     nlohmann::json& entriesArray = asyncResp->res.jsonValue["Members"];
2333     for (const std::string& sensor : *sensorNames)
2334     {
2335         BMCWEB_LOG_DEBUG("Adding sensor: {}", sensor);
2336 
2337         sdbusplus::message::object_path path(sensor);
2338         std::string sensorName = path.filename();
2339         if (sensorName.empty())
2340         {
2341             BMCWEB_LOG_ERROR("Invalid sensor path: {}", sensor);
2342             messages::internalError(asyncResp->res);
2343             return;
2344         }
2345         std::string type = path.parent_path().filename();
2346         std::string id = redfish::sensor_utils::getSensorId(sensorName, type);
2347 
2348         nlohmann::json::object_t member;
2349         member["@odata.id"] = boost::urls::format(
2350             "/redfish/v1/Chassis/{}/{}/{}", chassisId, chassisSubNode, id);
2351 
2352         entriesArray.emplace_back(std::move(member));
2353     }
2354 
2355     asyncResp->res.jsonValue["Members@odata.count"] = entriesArray.size();
2356     BMCWEB_LOG_DEBUG("getChassisCallback exit");
2357 }
2358 
2359 inline void handleSensorCollectionGet(
2360     App& app, const crow::Request& req,
2361     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2362     const std::string& chassisId)
2363 {
2364     query_param::QueryCapabilities capabilities = {
2365         .canDelegateExpandLevel = 1,
2366     };
2367     query_param::Query delegatedQuery;
2368     if (!redfish::setUpRedfishRouteWithDelegation(app, req, asyncResp,
2369                                                   delegatedQuery, capabilities))
2370     {
2371         return;
2372     }
2373 
2374     if (delegatedQuery.expandType != query_param::ExpandType::None)
2375     {
2376         // we perform efficient expand.
2377         auto sensorsAsyncResp = std::make_shared<SensorsAsyncResp>(
2378             asyncResp, chassisId, sensors::dbus::sensorPaths,
2379             sensors::sensorsNodeStr,
2380             /*efficientExpand=*/true);
2381         getChassisData(sensorsAsyncResp);
2382 
2383         BMCWEB_LOG_DEBUG(
2384             "SensorCollection doGet exit via efficient expand handler");
2385         return;
2386     }
2387 
2388     // We get all sensors as hyperlinkes in the chassis (this
2389     // implies we reply on the default query parameters handler)
2390     getChassis(asyncResp, chassisId, sensors::sensorsNodeStr, dbus::sensorPaths,
2391                std::bind_front(sensors::getChassisCallback, asyncResp,
2392                                chassisId, sensors::sensorsNodeStr));
2393 }
2394 
2395 inline void
2396     getSensorFromDbus(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2397                       const std::string& sensorPath,
2398                       const ::dbus::utility::MapperGetObject& mapperResponse)
2399 {
2400     if (mapperResponse.size() != 1)
2401     {
2402         messages::internalError(asyncResp->res);
2403         return;
2404     }
2405     const auto& valueIface = *mapperResponse.begin();
2406     const std::string& connectionName = valueIface.first;
2407     BMCWEB_LOG_DEBUG("Looking up {}", connectionName);
2408     BMCWEB_LOG_DEBUG("Path {}", sensorPath);
2409 
2410     sdbusplus::asio::getAllProperties(
2411         *crow::connections::systemBus, connectionName, sensorPath, "",
2412         [asyncResp,
2413          sensorPath](const boost::system::error_code& ec,
2414                      const ::dbus::utility::DBusPropertiesMap& valuesDict) {
2415             if (ec)
2416             {
2417                 messages::internalError(asyncResp->res);
2418                 return;
2419             }
2420             sdbusplus::message::object_path path(sensorPath);
2421             std::string name = path.filename();
2422             path = path.parent_path();
2423             std::string type = path.filename();
2424             sensor_utils::objectPropertiesToJson(
2425                 name, type, sensor_utils::ChassisSubNode::sensorsNode,
2426                 valuesDict, asyncResp->res.jsonValue, nullptr);
2427         });
2428 }
2429 
2430 inline void handleSensorGet(App& app, const crow::Request& req,
2431                             const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2432                             const std::string& chassisId,
2433                             const std::string& sensorId)
2434 {
2435     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2436     {
2437         return;
2438     }
2439     std::pair<std::string, std::string> nameType =
2440         redfish::sensor_utils::splitSensorNameAndType(sensorId);
2441     if (nameType.first.empty() || nameType.second.empty())
2442     {
2443         messages::resourceNotFound(asyncResp->res, sensorId, "Sensor");
2444         return;
2445     }
2446 
2447     asyncResp->res.jsonValue["@odata.id"] = boost::urls::format(
2448         "/redfish/v1/Chassis/{}/Sensors/{}", chassisId, sensorId);
2449 
2450     BMCWEB_LOG_DEBUG("Sensor doGet enter");
2451 
2452     constexpr std::array<std::string_view, 1> interfaces = {
2453         "xyz.openbmc_project.Sensor.Value"};
2454     std::string sensorPath = "/xyz/openbmc_project/sensors/" + nameType.first +
2455                              '/' + nameType.second;
2456     // Get a list of all of the sensors that implement Sensor.Value
2457     // and get the path and service name associated with the sensor
2458     ::dbus::utility::getDbusObject(
2459         sensorPath, interfaces,
2460         [asyncResp, sensorId,
2461          sensorPath](const boost::system::error_code& ec,
2462                      const ::dbus::utility::MapperGetObject& subtree) {
2463             BMCWEB_LOG_DEBUG("respHandler1 enter");
2464             if (ec == boost::system::errc::io_error)
2465             {
2466                 BMCWEB_LOG_WARNING("Sensor not found from getSensorPaths");
2467                 messages::resourceNotFound(asyncResp->res, sensorId, "Sensor");
2468                 return;
2469             }
2470             if (ec)
2471             {
2472                 messages::internalError(asyncResp->res);
2473                 BMCWEB_LOG_ERROR(
2474                     "Sensor getSensorPaths resp_handler: Dbus error {}", ec);
2475                 return;
2476             }
2477             getSensorFromDbus(asyncResp, sensorPath, subtree);
2478             BMCWEB_LOG_DEBUG("respHandler1 exit");
2479         });
2480 }
2481 
2482 } // namespace sensors
2483 
2484 inline void requestRoutesSensorCollection(App& app)
2485 {
2486     BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/Sensors/")
2487         .privileges(redfish::privileges::getSensorCollection)
2488         .methods(boost::beast::http::verb::get)(
2489             std::bind_front(sensors::handleSensorCollectionGet, std::ref(app)));
2490 }
2491 
2492 inline void requestRoutesSensor(App& app)
2493 {
2494     BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/Sensors/<str>/")
2495         .privileges(redfish::privileges::getSensor)
2496         .methods(boost::beast::http::verb::get)(
2497             std::bind_front(sensors::handleSensorGet, std::ref(app)));
2498 }
2499 
2500 } // namespace redfish
2501