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