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