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