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