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