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