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