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