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