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