xref: /openbmc/bmcweb/redfish-core/lib/sensors.hpp (revision 141d9431)
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 (const auto& [interface, values] : interfacesDict)
765     {
766         if (interface == "xyz.openbmc_project.Sensor.Threshold.Critical")
767         {
768             for (const 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 (const auto& [interface, values] : interfacesDict)
802     {
803         if (interface == "xyz.openbmc_project.Sensor.Threshold.Warning")
804         {
805             for (const 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 (const auto& [interface, values] : interfacesDict)
870     {
871         if (interface == "xyz.openbmc_project.Sensor.Value")
872         {
873             for (const 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                                 const uint8_t* allowedFailures =
1198                                     std::get_if<uint8_t>(
1199                                         &(findFailures->second));
1200                                 const std::vector<std::string>* collection =
1201                                     std::get_if<std::vector<std::string>>(
1202                                         &(findCollection->second));
1203                                 const std::string* status =
1204                                     std::get_if<std::string>(
1205                                         &(findStatus->second));
1206 
1207                                 if (allowedFailures == nullptr ||
1208                                     collection == nullptr || status == nullptr)
1209                                 {
1210 
1211                                     BMCWEB_LOG_ERROR
1212                                         << "Invalid redundancy interface types";
1213                                     messages::internalError(
1214                                         sensorsAsyncResp->asyncResp->res);
1215                                     return;
1216                                 }
1217                                 sdbusplus::message::object_path objectPath(
1218                                     path);
1219                                 std::string name = objectPath.filename();
1220                                 if (name.empty())
1221                                 {
1222                                     // this should be impossible
1223                                     messages::internalError(
1224                                         sensorsAsyncResp->asyncResp->res);
1225                                     return;
1226                                 }
1227                                 std::replace(name.begin(), name.end(), '_',
1228                                              ' ');
1229 
1230                                 std::string health;
1231 
1232                                 if (boost::ends_with(*status, "Full"))
1233                                 {
1234                                     health = "OK";
1235                                 }
1236                                 else if (boost::ends_with(*status, "Degraded"))
1237                                 {
1238                                     health = "Warning";
1239                                 }
1240                                 else
1241                                 {
1242                                     health = "Critical";
1243                                 }
1244                                 std::vector<nlohmann::json> redfishCollection;
1245                                 const auto& fanRedfish =
1246                                     sensorsAsyncResp->asyncResp->res
1247                                         .jsonValue["Fans"];
1248                                 for (const std::string& item : *collection)
1249                                 {
1250                                     sdbusplus::message::object_path path(item);
1251                                     std::string itemName = path.filename();
1252                                     if (itemName.empty())
1253                                     {
1254                                         continue;
1255                                     }
1256                                     /*
1257                                     todo(ed): merge patch that fixes the names
1258                                     std::replace(itemName.begin(),
1259                                                  itemName.end(), '_', ' ');*/
1260                                     auto schemaItem = std::find_if(
1261                                         fanRedfish.begin(), fanRedfish.end(),
1262                                         [itemName](const nlohmann::json& fan) {
1263                                             return fan["MemberId"] == itemName;
1264                                         });
1265                                     if (schemaItem != fanRedfish.end())
1266                                     {
1267                                         redfishCollection.push_back(
1268                                             {{"@odata.id",
1269                                               (*schemaItem)["@odata.id"]}});
1270                                     }
1271                                     else
1272                                     {
1273                                         BMCWEB_LOG_ERROR
1274                                             << "failed to find fan in schema";
1275                                         messages::internalError(
1276                                             sensorsAsyncResp->asyncResp->res);
1277                                         return;
1278                                     }
1279                                 }
1280 
1281                                 size_t minNumNeeded =
1282                                     collection->empty()
1283                                         ? 0
1284                                         : collection->size() - *allowedFailures;
1285                                 nlohmann::json& jResp =
1286                                     sensorsAsyncResp->asyncResp->res
1287                                         .jsonValue["Redundancy"];
1288                                 jResp.push_back(
1289                                     {{"@odata.id",
1290                                       "/redfish/v1/Chassis/" +
1291                                           sensorsAsyncResp->chassisId + "/" +
1292                                           sensorsAsyncResp->chassisSubNode +
1293                                           "#/Redundancy/" +
1294                                           std::to_string(jResp.size())},
1295                                      {"@odata.type",
1296                                       "#Redundancy.v1_3_2.Redundancy"},
1297                                      {"MinNumNeeded", minNumNeeded},
1298                                      {"MemberId", name},
1299                                      {"Mode", "N+m"},
1300                                      {"Name", name},
1301                                      {"RedundancySet", redfishCollection},
1302                                      {"Status",
1303                                       {{"Health", health},
1304                                        {"State", "Enabled"}}}});
1305                             },
1306                             owner, path, "org.freedesktop.DBus.Properties",
1307                             "GetAll",
1308                             "xyz.openbmc_project.Control.FanRedundancy");
1309                     });
1310             }
1311         },
1312         "xyz.openbmc_project.ObjectMapper",
1313         "/xyz/openbmc_project/object_mapper",
1314         "xyz.openbmc_project.ObjectMapper", "GetSubTree",
1315         "/xyz/openbmc_project/control", 2,
1316         std::array<const char*, 1>{
1317             "xyz.openbmc_project.Control.FanRedundancy"});
1318 }
1319 
1320 inline void
1321     sortJSONResponse(const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp)
1322 {
1323     nlohmann::json& response = sensorsAsyncResp->asyncResp->res.jsonValue;
1324     std::array<std::string, 2> sensorHeaders{"Temperatures", "Fans"};
1325     if (sensorsAsyncResp->chassisSubNode == sensors::node::power)
1326     {
1327         sensorHeaders = {"Voltages", "PowerSupplies"};
1328     }
1329     for (const std::string& sensorGroup : sensorHeaders)
1330     {
1331         nlohmann::json::iterator entry = response.find(sensorGroup);
1332         if (entry != response.end())
1333         {
1334             std::sort(entry->begin(), entry->end(),
1335                       [](nlohmann::json& c1, nlohmann::json& c2) {
1336                           return c1["Name"] < c2["Name"];
1337                       });
1338 
1339             // add the index counts to the end of each entry
1340             size_t count = 0;
1341             for (nlohmann::json& sensorJson : *entry)
1342             {
1343                 nlohmann::json::iterator odata = sensorJson.find("@odata.id");
1344                 if (odata == sensorJson.end())
1345                 {
1346                     continue;
1347                 }
1348                 std::string* value = odata->get_ptr<std::string*>();
1349                 if (value != nullptr)
1350                 {
1351                     *value += std::to_string(count);
1352                     count++;
1353                     sensorsAsyncResp->updateUri(sensorJson["Name"], *value);
1354                 }
1355             }
1356         }
1357     }
1358 }
1359 
1360 /**
1361  * @brief Finds the inventory item with the specified object path.
1362  * @param inventoryItems D-Bus inventory items associated with sensors.
1363  * @param invItemObjPath D-Bus object path of inventory item.
1364  * @return Inventory item within vector, or nullptr if no match found.
1365  */
1366 inline InventoryItem* findInventoryItem(
1367     const std::shared_ptr<std::vector<InventoryItem>>& inventoryItems,
1368     const std::string& invItemObjPath)
1369 {
1370     for (InventoryItem& inventoryItem : *inventoryItems)
1371     {
1372         if (inventoryItem.objectPath == invItemObjPath)
1373         {
1374             return &inventoryItem;
1375         }
1376     }
1377     return nullptr;
1378 }
1379 
1380 /**
1381  * @brief Finds the inventory item associated with the specified sensor.
1382  * @param inventoryItems D-Bus inventory items associated with sensors.
1383  * @param sensorObjPath D-Bus object path of sensor.
1384  * @return Inventory item within vector, or nullptr if no match found.
1385  */
1386 inline InventoryItem* findInventoryItemForSensor(
1387     const std::shared_ptr<std::vector<InventoryItem>>& inventoryItems,
1388     const std::string& sensorObjPath)
1389 {
1390     for (InventoryItem& inventoryItem : *inventoryItems)
1391     {
1392         if (inventoryItem.sensors.count(sensorObjPath) > 0)
1393         {
1394             return &inventoryItem;
1395         }
1396     }
1397     return nullptr;
1398 }
1399 
1400 /**
1401  * @brief Finds the inventory item associated with the specified led path.
1402  * @param inventoryItems D-Bus inventory items associated with sensors.
1403  * @param ledObjPath D-Bus object path of led.
1404  * @return Inventory item within vector, or nullptr if no match found.
1405  */
1406 inline InventoryItem*
1407     findInventoryItemForLed(std::vector<InventoryItem>& inventoryItems,
1408                             const std::string& ledObjPath)
1409 {
1410     for (InventoryItem& inventoryItem : inventoryItems)
1411     {
1412         if (inventoryItem.ledObjectPath == ledObjPath)
1413         {
1414             return &inventoryItem;
1415         }
1416     }
1417     return nullptr;
1418 }
1419 
1420 /**
1421  * @brief Adds inventory item and associated sensor to specified vector.
1422  *
1423  * Adds a new InventoryItem to the vector if necessary.  Searches for an
1424  * existing InventoryItem with the specified object path.  If not found, one is
1425  * added to the vector.
1426  *
1427  * Next, the specified sensor is added to the set of sensors associated with the
1428  * InventoryItem.
1429  *
1430  * @param inventoryItems D-Bus inventory items associated with sensors.
1431  * @param invItemObjPath D-Bus object path of inventory item.
1432  * @param sensorObjPath D-Bus object path of sensor
1433  */
1434 inline void addInventoryItem(
1435     const std::shared_ptr<std::vector<InventoryItem>>& inventoryItems,
1436     const std::string& invItemObjPath, const std::string& sensorObjPath)
1437 {
1438     // Look for inventory item in vector
1439     InventoryItem* inventoryItem =
1440         findInventoryItem(inventoryItems, invItemObjPath);
1441 
1442     // If inventory item doesn't exist in vector, add it
1443     if (inventoryItem == nullptr)
1444     {
1445         inventoryItems->emplace_back(invItemObjPath);
1446         inventoryItem = &(inventoryItems->back());
1447     }
1448 
1449     // Add sensor to set of sensors associated with inventory item
1450     inventoryItem->sensors.emplace(sensorObjPath);
1451 }
1452 
1453 /**
1454  * @brief Stores D-Bus data in the specified inventory item.
1455  *
1456  * Finds D-Bus data in the specified map of interfaces.  Stores the data in the
1457  * specified InventoryItem.
1458  *
1459  * This data is later used to provide sensor property values in the JSON
1460  * response.
1461  *
1462  * @param inventoryItem Inventory item where data will be stored.
1463  * @param interfacesDict Map containing D-Bus interfaces and their properties
1464  * for the specified inventory item.
1465  */
1466 inline void storeInventoryItemData(
1467     InventoryItem& inventoryItem,
1468     const dbus::utility::DBusInteracesMap& interfacesDict)
1469 {
1470     // Get properties from Inventory.Item interface
1471 
1472     for (const auto& [interface, values] : interfacesDict)
1473     {
1474         if (interface == "xyz.openbmc_project.Inventory.Item")
1475         {
1476             for (const auto& [name, dbusValue] : values)
1477             {
1478                 if (name == "Present")
1479                 {
1480                     const bool* value = std::get_if<bool>(&dbusValue);
1481                     if (value != nullptr)
1482                     {
1483                         inventoryItem.isPresent = *value;
1484                     }
1485                 }
1486             }
1487         }
1488         // Check if Inventory.Item.PowerSupply interface is present
1489 
1490         if (interface == "xyz.openbmc_project.Inventory.Item.PowerSupply")
1491         {
1492             inventoryItem.isPowerSupply = true;
1493         }
1494 
1495         // Get properties from Inventory.Decorator.Asset interface
1496         if (interface == "xyz.openbmc_project.Inventory.Decorator.Asset")
1497         {
1498             for (const auto& [name, dbusValue] : values)
1499             {
1500                 if (name == "Manufacturer")
1501                 {
1502                     const std::string* value =
1503                         std::get_if<std::string>(&dbusValue);
1504                     if (value != nullptr)
1505                     {
1506                         inventoryItem.manufacturer = *value;
1507                     }
1508                 }
1509                 if (name == "Model")
1510                 {
1511                     const std::string* value =
1512                         std::get_if<std::string>(&dbusValue);
1513                     if (value != nullptr)
1514                     {
1515                         inventoryItem.model = *value;
1516                     }
1517                 }
1518                 if (name == "SerialNumber")
1519                 {
1520                     const std::string* value =
1521                         std::get_if<std::string>(&dbusValue);
1522                     if (value != nullptr)
1523                     {
1524                         inventoryItem.serialNumber = *value;
1525                     }
1526                 }
1527                 if (name == "PartNumber")
1528                 {
1529                     const std::string* value =
1530                         std::get_if<std::string>(&dbusValue);
1531                     if (value != nullptr)
1532                     {
1533                         inventoryItem.partNumber = *value;
1534                     }
1535                 }
1536             }
1537         }
1538 
1539         if (interface ==
1540             "xyz.openbmc_project.State.Decorator.OperationalStatus")
1541         {
1542             for (const auto& [name, dbusValue] : values)
1543             {
1544                 if (name == "Functional")
1545                 {
1546                     const bool* value = std::get_if<bool>(&dbusValue);
1547                     if (value != nullptr)
1548                     {
1549                         inventoryItem.isFunctional = *value;
1550                     }
1551                 }
1552             }
1553         }
1554     }
1555 }
1556 
1557 /**
1558  * @brief Gets D-Bus data for inventory items associated with sensors.
1559  *
1560  * Uses the specified connections (services) to obtain D-Bus data for inventory
1561  * items associated with sensors.  Stores the resulting data in the
1562  * inventoryItems vector.
1563  *
1564  * This data is later used to provide sensor property values in the JSON
1565  * response.
1566  *
1567  * Finds the inventory item data asynchronously.  Invokes callback when data has
1568  * been obtained.
1569  *
1570  * The callback must have the following signature:
1571  *   @code
1572  *   callback(void)
1573  *   @endcode
1574  *
1575  * This function is called recursively, obtaining data asynchronously from one
1576  * connection in each call.  This ensures the callback is not invoked until the
1577  * last asynchronous function has completed.
1578  *
1579  * @param sensorsAsyncResp Pointer to object holding response data.
1580  * @param inventoryItems D-Bus inventory items associated with sensors.
1581  * @param invConnections Connections that provide data for the inventory items.
1582  * @param objectMgrPaths Mappings from connection name to DBus object path that
1583  * implements ObjectManager.
1584  * @param callback Callback to invoke when inventory data has been obtained.
1585  * @param invConnectionsIndex Current index in invConnections.  Only specified
1586  * in recursive calls to this function.
1587  */
1588 template <typename Callback>
1589 static void getInventoryItemsData(
1590     std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp,
1591     std::shared_ptr<std::vector<InventoryItem>> inventoryItems,
1592     std::shared_ptr<boost::container::flat_set<std::string>> invConnections,
1593     std::shared_ptr<boost::container::flat_map<std::string, std::string>>
1594         objectMgrPaths,
1595     Callback&& callback, size_t invConnectionsIndex = 0)
1596 {
1597     BMCWEB_LOG_DEBUG << "getInventoryItemsData enter";
1598 
1599     // If no more connections left, call callback
1600     if (invConnectionsIndex >= invConnections->size())
1601     {
1602         callback();
1603         BMCWEB_LOG_DEBUG << "getInventoryItemsData exit";
1604         return;
1605     }
1606 
1607     // Get inventory item data from current connection
1608     auto it = invConnections->nth(invConnectionsIndex);
1609     if (it != invConnections->end())
1610     {
1611         const std::string& invConnection = *it;
1612 
1613         // Response handler for GetManagedObjects
1614         auto respHandler = [sensorsAsyncResp, inventoryItems, invConnections,
1615                             objectMgrPaths,
1616                             callback{std::forward<Callback>(callback)},
1617                             invConnectionsIndex](
1618                                const boost::system::error_code ec,
1619                                dbus::utility::ManagedObjectType& resp) {
1620             BMCWEB_LOG_DEBUG << "getInventoryItemsData respHandler enter";
1621             if (ec)
1622             {
1623                 BMCWEB_LOG_ERROR
1624                     << "getInventoryItemsData respHandler DBus error " << ec;
1625                 messages::internalError(sensorsAsyncResp->asyncResp->res);
1626                 return;
1627             }
1628 
1629             // Loop through returned object paths
1630             for (const auto& objDictEntry : resp)
1631             {
1632                 const std::string& objPath =
1633                     static_cast<const std::string&>(objDictEntry.first);
1634 
1635                 // If this object path is one of the specified inventory items
1636                 InventoryItem* inventoryItem =
1637                     findInventoryItem(inventoryItems, objPath);
1638                 if (inventoryItem != nullptr)
1639                 {
1640                     // Store inventory data in InventoryItem
1641                     storeInventoryItemData(*inventoryItem, objDictEntry.second);
1642                 }
1643             }
1644 
1645             // Recurse to get inventory item data from next connection
1646             getInventoryItemsData(sensorsAsyncResp, inventoryItems,
1647                                   invConnections, objectMgrPaths,
1648                                   std::move(callback), invConnectionsIndex + 1);
1649 
1650             BMCWEB_LOG_DEBUG << "getInventoryItemsData respHandler exit";
1651         };
1652 
1653         // Find DBus object path that implements ObjectManager for the current
1654         // connection.  If no mapping found, default to "/".
1655         auto iter = objectMgrPaths->find(invConnection);
1656         const std::string& objectMgrPath =
1657             (iter != objectMgrPaths->end()) ? iter->second : "/";
1658         BMCWEB_LOG_DEBUG << "ObjectManager path for " << invConnection << " is "
1659                          << objectMgrPath;
1660 
1661         // Get all object paths and their interfaces for current connection
1662         crow::connections::systemBus->async_method_call(
1663             std::move(respHandler), invConnection, objectMgrPath,
1664             "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
1665     }
1666 
1667     BMCWEB_LOG_DEBUG << "getInventoryItemsData exit";
1668 }
1669 
1670 /**
1671  * @brief Gets connections that provide D-Bus data for inventory items.
1672  *
1673  * Gets the D-Bus connections (services) that provide data for the inventory
1674  * items that are associated with sensors.
1675  *
1676  * Finds the connections asynchronously.  Invokes callback when information has
1677  * been obtained.
1678  *
1679  * The callback must have the following signature:
1680  *   @code
1681  *   callback(std::shared_ptr<boost::container::flat_set<std::string>>
1682  *            invConnections)
1683  *   @endcode
1684  *
1685  * @param sensorsAsyncResp Pointer to object holding response data.
1686  * @param inventoryItems D-Bus inventory items associated with sensors.
1687  * @param callback Callback to invoke when connections have been obtained.
1688  */
1689 template <typename Callback>
1690 static void getInventoryItemsConnections(
1691     const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp,
1692     const std::shared_ptr<std::vector<InventoryItem>>& inventoryItems,
1693     Callback&& callback)
1694 {
1695     BMCWEB_LOG_DEBUG << "getInventoryItemsConnections enter";
1696 
1697     const std::string path = "/xyz/openbmc_project/inventory";
1698     const std::array<std::string, 4> interfaces = {
1699         "xyz.openbmc_project.Inventory.Item",
1700         "xyz.openbmc_project.Inventory.Item.PowerSupply",
1701         "xyz.openbmc_project.Inventory.Decorator.Asset",
1702         "xyz.openbmc_project.State.Decorator.OperationalStatus"};
1703 
1704     // Response handler for parsing output from GetSubTree
1705     auto respHandler = [callback{std::forward<Callback>(callback)},
1706                         sensorsAsyncResp,
1707                         inventoryItems](const boost::system::error_code ec,
1708                                         const GetSubTreeType& subtree) {
1709         BMCWEB_LOG_DEBUG << "getInventoryItemsConnections respHandler enter";
1710         if (ec)
1711         {
1712             messages::internalError(sensorsAsyncResp->asyncResp->res);
1713             BMCWEB_LOG_ERROR
1714                 << "getInventoryItemsConnections respHandler DBus error " << ec;
1715             return;
1716         }
1717 
1718         // Make unique list of connections for desired inventory items
1719         std::shared_ptr<boost::container::flat_set<std::string>>
1720             invConnections =
1721                 std::make_shared<boost::container::flat_set<std::string>>();
1722         invConnections->reserve(8);
1723 
1724         // Loop through objects from GetSubTree
1725         for (const std::pair<
1726                  std::string,
1727                  std::vector<std::pair<std::string, std::vector<std::string>>>>&
1728                  object : subtree)
1729         {
1730             // Check if object path is one of the specified inventory items
1731             const std::string& objPath = object.first;
1732             if (findInventoryItem(inventoryItems, objPath) != nullptr)
1733             {
1734                 // Store all connections to inventory item
1735                 for (const std::pair<std::string, std::vector<std::string>>&
1736                          objData : object.second)
1737                 {
1738                     const std::string& invConnection = objData.first;
1739                     invConnections->insert(invConnection);
1740                 }
1741             }
1742         }
1743 
1744         callback(invConnections);
1745         BMCWEB_LOG_DEBUG << "getInventoryItemsConnections respHandler exit";
1746     };
1747 
1748     // Make call to ObjectMapper to find all inventory items
1749     crow::connections::systemBus->async_method_call(
1750         std::move(respHandler), "xyz.openbmc_project.ObjectMapper",
1751         "/xyz/openbmc_project/object_mapper",
1752         "xyz.openbmc_project.ObjectMapper", "GetSubTree", path, 0, interfaces);
1753     BMCWEB_LOG_DEBUG << "getInventoryItemsConnections exit";
1754 }
1755 
1756 /**
1757  * @brief Gets associations from sensors to inventory items.
1758  *
1759  * Looks for ObjectMapper associations from the specified sensors to related
1760  * inventory items. Then finds the associations from those inventory items to
1761  * their LEDs, if any.
1762  *
1763  * Finds the inventory items asynchronously.  Invokes callback when information
1764  * has been obtained.
1765  *
1766  * The callback must have the following signature:
1767  *   @code
1768  *   callback(std::shared_ptr<std::vector<InventoryItem>> inventoryItems)
1769  *   @endcode
1770  *
1771  * @param sensorsAsyncResp Pointer to object holding response data.
1772  * @param sensorNames All sensors within the current chassis.
1773  * @param objectMgrPaths Mappings from connection name to DBus object path that
1774  * implements ObjectManager.
1775  * @param callback Callback to invoke when inventory items have been obtained.
1776  */
1777 template <typename Callback>
1778 static void getInventoryItemAssociations(
1779     const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp,
1780     const std::shared_ptr<boost::container::flat_set<std::string>>& sensorNames,
1781     const std::shared_ptr<boost::container::flat_map<std::string, std::string>>&
1782         objectMgrPaths,
1783     Callback&& callback)
1784 {
1785     BMCWEB_LOG_DEBUG << "getInventoryItemAssociations enter";
1786 
1787     // Response handler for GetManagedObjects
1788     auto respHandler = [callback{std::forward<Callback>(callback)},
1789                         sensorsAsyncResp,
1790                         sensorNames](const boost::system::error_code ec,
1791                                      dbus::utility::ManagedObjectType& resp) {
1792         BMCWEB_LOG_DEBUG << "getInventoryItemAssociations respHandler enter";
1793         if (ec)
1794         {
1795             BMCWEB_LOG_ERROR
1796                 << "getInventoryItemAssociations respHandler DBus error " << ec;
1797             messages::internalError(sensorsAsyncResp->asyncResp->res);
1798             return;
1799         }
1800 
1801         // Create vector to hold list of inventory items
1802         std::shared_ptr<std::vector<InventoryItem>> inventoryItems =
1803             std::make_shared<std::vector<InventoryItem>>();
1804 
1805         // Loop through returned object paths
1806         std::string sensorAssocPath;
1807         sensorAssocPath.reserve(128); // avoid memory allocations
1808         for (const auto& objDictEntry : resp)
1809         {
1810             const std::string& objPath =
1811                 static_cast<const std::string&>(objDictEntry.first);
1812 
1813             // If path is inventory association for one of the specified sensors
1814             for (const std::string& sensorName : *sensorNames)
1815             {
1816                 sensorAssocPath = sensorName;
1817                 sensorAssocPath += "/inventory";
1818                 if (objPath == sensorAssocPath)
1819                 {
1820                     // Get Association interface for object path
1821                     for (const auto& [interface, values] : objDictEntry.second)
1822                     {
1823                         if (interface == "xyz.openbmc_project.Association")
1824                         {
1825                             for (const auto& [valueName, value] : values)
1826                             {
1827                                 if (valueName == "endpoints")
1828                                 {
1829                                     const std::vector<std::string>* endpoints =
1830                                         std::get_if<std::vector<std::string>>(
1831                                             &value);
1832                                     if ((endpoints != nullptr) &&
1833                                         !endpoints->empty())
1834                                     {
1835                                         // Add inventory item to vector
1836                                         const std::string& invItemPath =
1837                                             endpoints->front();
1838                                         addInventoryItem(inventoryItems,
1839                                                          invItemPath,
1840                                                          sensorName);
1841                                     }
1842                                 }
1843                             }
1844                         }
1845                     }
1846                     break;
1847                 }
1848             }
1849         }
1850 
1851         // Now loop through the returned object paths again, this time to
1852         // find the leds associated with the inventory items we just found
1853         std::string inventoryAssocPath;
1854         inventoryAssocPath.reserve(128); // avoid memory allocations
1855         for (const auto& objDictEntry : resp)
1856         {
1857             const std::string& objPath =
1858                 static_cast<const std::string&>(objDictEntry.first);
1859 
1860             for (InventoryItem& inventoryItem : *inventoryItems)
1861             {
1862                 inventoryAssocPath = inventoryItem.objectPath;
1863                 inventoryAssocPath += "/leds";
1864                 if (objPath == inventoryAssocPath)
1865                 {
1866                     for (const auto& [interface, values] : objDictEntry.second)
1867                     {
1868                         if (interface == "xyz.openbmc_project.Association")
1869                         {
1870                             for (const auto& [valueName, value] : values)
1871                             {
1872                                 if (valueName == "endpoints")
1873                                 {
1874                                     const std::vector<std::string>* endpoints =
1875                                         std::get_if<std::vector<std::string>>(
1876                                             &value);
1877                                     if ((endpoints != nullptr) &&
1878                                         !endpoints->empty())
1879                                     {
1880                                         // Add inventory item to vector
1881                                         // Store LED path in inventory item
1882                                         const std::string& ledPath =
1883                                             endpoints->front();
1884                                         inventoryItem.ledObjectPath = ledPath;
1885                                     }
1886                                 }
1887                             }
1888                         }
1889                     }
1890 
1891                     break;
1892                 }
1893             }
1894         }
1895         callback(inventoryItems);
1896         BMCWEB_LOG_DEBUG << "getInventoryItemAssociations respHandler exit";
1897     };
1898 
1899     // Find DBus object path that implements ObjectManager for ObjectMapper
1900     std::string connection = "xyz.openbmc_project.ObjectMapper";
1901     auto iter = objectMgrPaths->find(connection);
1902     const std::string& objectMgrPath =
1903         (iter != objectMgrPaths->end()) ? iter->second : "/";
1904     BMCWEB_LOG_DEBUG << "ObjectManager path for " << connection << " is "
1905                      << objectMgrPath;
1906 
1907     // Call GetManagedObjects on the ObjectMapper to get all associations
1908     crow::connections::systemBus->async_method_call(
1909         std::move(respHandler), connection, objectMgrPath,
1910         "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
1911 
1912     BMCWEB_LOG_DEBUG << "getInventoryItemAssociations exit";
1913 }
1914 
1915 /**
1916  * @brief Gets D-Bus data for inventory item leds associated with sensors.
1917  *
1918  * Uses the specified connections (services) to obtain D-Bus data for inventory
1919  * item leds associated with sensors.  Stores the resulting data in the
1920  * inventoryItems vector.
1921  *
1922  * This data is later used to provide sensor property values in the JSON
1923  * response.
1924  *
1925  * Finds the inventory item led data asynchronously.  Invokes callback when data
1926  * has been obtained.
1927  *
1928  * The callback must have the following signature:
1929  *   @code
1930  *   callback()
1931  *   @endcode
1932  *
1933  * This function is called recursively, obtaining data asynchronously from one
1934  * connection in each call.  This ensures the callback is not invoked until the
1935  * last asynchronous function has completed.
1936  *
1937  * @param sensorsAsyncResp Pointer to object holding response data.
1938  * @param inventoryItems D-Bus inventory items associated with sensors.
1939  * @param ledConnections Connections that provide data for the inventory leds.
1940  * @param callback Callback to invoke when inventory data has been obtained.
1941  * @param ledConnectionsIndex Current index in ledConnections.  Only specified
1942  * in recursive calls to this function.
1943  */
1944 template <typename Callback>
1945 void getInventoryLedData(
1946     std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp,
1947     std::shared_ptr<std::vector<InventoryItem>> inventoryItems,
1948     std::shared_ptr<boost::container::flat_map<std::string, std::string>>
1949         ledConnections,
1950     Callback&& callback, size_t ledConnectionsIndex = 0)
1951 {
1952     BMCWEB_LOG_DEBUG << "getInventoryLedData enter";
1953 
1954     // If no more connections left, call callback
1955     if (ledConnectionsIndex >= ledConnections->size())
1956     {
1957         callback();
1958         BMCWEB_LOG_DEBUG << "getInventoryLedData exit";
1959         return;
1960     }
1961 
1962     // Get inventory item data from current connection
1963     auto it = ledConnections->nth(ledConnectionsIndex);
1964     if (it != ledConnections->end())
1965     {
1966         const std::string& ledPath = (*it).first;
1967         const std::string& ledConnection = (*it).second;
1968         // Response handler for Get State property
1969         auto respHandler =
1970             [sensorsAsyncResp, inventoryItems, ledConnections, ledPath,
1971              callback{std::forward<Callback>(callback)}, ledConnectionsIndex](
1972                 const boost::system::error_code ec, const std::string& state) {
1973                 BMCWEB_LOG_DEBUG << "getInventoryLedData respHandler enter";
1974                 if (ec)
1975                 {
1976                     BMCWEB_LOG_ERROR
1977                         << "getInventoryLedData respHandler DBus error " << ec;
1978                     messages::internalError(sensorsAsyncResp->asyncResp->res);
1979                     return;
1980                 }
1981 
1982                 BMCWEB_LOG_DEBUG << "Led state: " << state;
1983                 // Find inventory item with this LED object path
1984                 InventoryItem* inventoryItem =
1985                     findInventoryItemForLed(*inventoryItems, ledPath);
1986                 if (inventoryItem != nullptr)
1987                 {
1988                     // Store LED state in InventoryItem
1989                     if (boost::ends_with(state, "On"))
1990                     {
1991                         inventoryItem->ledState = LedState::ON;
1992                     }
1993                     else if (boost::ends_with(state, "Blink"))
1994                     {
1995                         inventoryItem->ledState = LedState::BLINK;
1996                     }
1997                     else if (boost::ends_with(state, "Off"))
1998                     {
1999                         inventoryItem->ledState = LedState::OFF;
2000                     }
2001                     else
2002                     {
2003                         inventoryItem->ledState = LedState::UNKNOWN;
2004                     }
2005                 }
2006 
2007                 // Recurse to get LED data from next connection
2008                 getInventoryLedData(sensorsAsyncResp, inventoryItems,
2009                                     ledConnections, std::move(callback),
2010                                     ledConnectionsIndex + 1);
2011 
2012                 BMCWEB_LOG_DEBUG << "getInventoryLedData respHandler exit";
2013             };
2014 
2015         // Get the State property for the current LED
2016         sdbusplus::asio::getProperty<std::string>(
2017             *crow::connections::systemBus, ledConnection, ledPath,
2018             "xyz.openbmc_project.Led.Physical", "State",
2019             std::move(respHandler));
2020     }
2021 
2022     BMCWEB_LOG_DEBUG << "getInventoryLedData exit";
2023 }
2024 
2025 /**
2026  * @brief Gets LED data for LEDs associated with given inventory items.
2027  *
2028  * Gets the D-Bus connections (services) that provide LED data for the LEDs
2029  * associated with the specified inventory items.  Then gets the LED data from
2030  * each connection and stores it in the inventory item.
2031  *
2032  * This data is later used to provide sensor property values in the JSON
2033  * response.
2034  *
2035  * Finds the LED data asynchronously.  Invokes callback when information has
2036  * been obtained.
2037  *
2038  * The callback must have the following signature:
2039  *   @code
2040  *   callback()
2041  *   @endcode
2042  *
2043  * @param sensorsAsyncResp Pointer to object holding response data.
2044  * @param inventoryItems D-Bus inventory items associated with sensors.
2045  * @param callback Callback to invoke when inventory items have been obtained.
2046  */
2047 template <typename Callback>
2048 void getInventoryLeds(
2049     std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp,
2050     std::shared_ptr<std::vector<InventoryItem>> inventoryItems,
2051     Callback&& callback)
2052 {
2053     BMCWEB_LOG_DEBUG << "getInventoryLeds enter";
2054 
2055     const std::string path = "/xyz/openbmc_project";
2056     const std::array<std::string, 1> interfaces = {
2057         "xyz.openbmc_project.Led.Physical"};
2058 
2059     // Response handler for parsing output from GetSubTree
2060     auto respHandler = [callback{std::forward<Callback>(callback)},
2061                         sensorsAsyncResp,
2062                         inventoryItems](const boost::system::error_code ec,
2063                                         const GetSubTreeType& subtree) {
2064         BMCWEB_LOG_DEBUG << "getInventoryLeds respHandler enter";
2065         if (ec)
2066         {
2067             messages::internalError(sensorsAsyncResp->asyncResp->res);
2068             BMCWEB_LOG_ERROR << "getInventoryLeds respHandler DBus error "
2069                              << ec;
2070             return;
2071         }
2072 
2073         // Build map of LED object paths to connections
2074         std::shared_ptr<boost::container::flat_map<std::string, std::string>>
2075             ledConnections = std::make_shared<
2076                 boost::container::flat_map<std::string, std::string>>();
2077 
2078         // Loop through objects from GetSubTree
2079         for (const std::pair<
2080                  std::string,
2081                  std::vector<std::pair<std::string, std::vector<std::string>>>>&
2082                  object : subtree)
2083         {
2084             // Check if object path is LED for one of the specified inventory
2085             // items
2086             const std::string& ledPath = object.first;
2087             if (findInventoryItemForLed(*inventoryItems, ledPath) != nullptr)
2088             {
2089                 // Add mapping from ledPath to connection
2090                 const std::string& connection = object.second.begin()->first;
2091                 (*ledConnections)[ledPath] = connection;
2092                 BMCWEB_LOG_DEBUG << "Added mapping " << ledPath << " -> "
2093                                  << connection;
2094             }
2095         }
2096 
2097         getInventoryLedData(sensorsAsyncResp, inventoryItems, ledConnections,
2098                             std::move(callback));
2099         BMCWEB_LOG_DEBUG << "getInventoryLeds respHandler exit";
2100     };
2101     // Make call to ObjectMapper to find all inventory items
2102     crow::connections::systemBus->async_method_call(
2103         std::move(respHandler), "xyz.openbmc_project.ObjectMapper",
2104         "/xyz/openbmc_project/object_mapper",
2105         "xyz.openbmc_project.ObjectMapper", "GetSubTree", path, 0, interfaces);
2106     BMCWEB_LOG_DEBUG << "getInventoryLeds exit";
2107 }
2108 
2109 /**
2110  * @brief Gets D-Bus data for Power Supply Attributes such as EfficiencyPercent
2111  *
2112  * Uses the specified connections (services) (currently assumes just one) to
2113  * obtain D-Bus data for Power Supply Attributes. Stores the resulting data in
2114  * the inventoryItems vector. Only stores data in Power Supply inventoryItems.
2115  *
2116  * This data is later used to provide sensor property values in the JSON
2117  * response.
2118  *
2119  * Finds the Power Supply Attributes data asynchronously.  Invokes callback
2120  * when data has been obtained.
2121  *
2122  * The callback must have the following signature:
2123  *   @code
2124  *   callback(std::shared_ptr<std::vector<InventoryItem>> inventoryItems)
2125  *   @endcode
2126  *
2127  * @param sensorsAsyncResp Pointer to object holding response data.
2128  * @param inventoryItems D-Bus inventory items associated with sensors.
2129  * @param psAttributesConnections Connections that provide data for the Power
2130  *        Supply Attributes
2131  * @param callback Callback to invoke when data has been obtained.
2132  */
2133 template <typename Callback>
2134 void getPowerSupplyAttributesData(
2135     const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp,
2136     std::shared_ptr<std::vector<InventoryItem>> inventoryItems,
2137     const boost::container::flat_map<std::string, std::string>&
2138         psAttributesConnections,
2139     Callback&& callback)
2140 {
2141     BMCWEB_LOG_DEBUG << "getPowerSupplyAttributesData enter";
2142 
2143     if (psAttributesConnections.empty())
2144     {
2145         BMCWEB_LOG_DEBUG << "Can't find PowerSupplyAttributes, no connections!";
2146         callback(inventoryItems);
2147         return;
2148     }
2149 
2150     // Assuming just one connection (service) for now
2151     auto it = psAttributesConnections.nth(0);
2152 
2153     const std::string& psAttributesPath = (*it).first;
2154     const std::string& psAttributesConnection = (*it).second;
2155 
2156     // Response handler for Get DeratingFactor property
2157     auto respHandler = [sensorsAsyncResp, inventoryItems,
2158                         callback{std::forward<Callback>(callback)}](
2159                            const boost::system::error_code ec,
2160                            const uint32_t value) {
2161         BMCWEB_LOG_DEBUG << "getPowerSupplyAttributesData respHandler enter";
2162         if (ec)
2163         {
2164             BMCWEB_LOG_ERROR
2165                 << "getPowerSupplyAttributesData respHandler DBus error " << ec;
2166             messages::internalError(sensorsAsyncResp->asyncResp->res);
2167             return;
2168         }
2169 
2170         BMCWEB_LOG_DEBUG << "PS EfficiencyPercent value: " << value;
2171         // Store value in Power Supply Inventory Items
2172         for (InventoryItem& inventoryItem : *inventoryItems)
2173         {
2174             if (inventoryItem.isPowerSupply == true)
2175             {
2176                 inventoryItem.powerSupplyEfficiencyPercent =
2177                     static_cast<int>(value);
2178             }
2179         }
2180 
2181         BMCWEB_LOG_DEBUG << "getPowerSupplyAttributesData respHandler exit";
2182         callback(inventoryItems);
2183     };
2184 
2185     // Get the DeratingFactor property for the PowerSupplyAttributes
2186     // Currently only property on the interface/only one we care about
2187     sdbusplus::asio::getProperty<uint32_t>(
2188         *crow::connections::systemBus, psAttributesConnection, psAttributesPath,
2189         "xyz.openbmc_project.Control.PowerSupplyAttributes", "DeratingFactor",
2190         std::move(respHandler));
2191 
2192     BMCWEB_LOG_DEBUG << "getPowerSupplyAttributesData exit";
2193 }
2194 
2195 /**
2196  * @brief Gets the Power Supply Attributes such as EfficiencyPercent
2197  *
2198  * Gets the D-Bus connection (service) that provides Power Supply Attributes
2199  * data. Then gets the Power Supply Attributes data from the connection
2200  * (currently just assumes 1 connection) and stores the data in the inventory
2201  * item.
2202  *
2203  * This data is later used to provide sensor property values in the JSON
2204  * response. DeratingFactor on D-Bus is mapped to EfficiencyPercent on Redfish.
2205  *
2206  * Finds the Power Supply Attributes data asynchronously. Invokes callback
2207  * when information has been obtained.
2208  *
2209  * The callback must have the following signature:
2210  *   @code
2211  *   callback(std::shared_ptr<std::vector<InventoryItem>> inventoryItems)
2212  *   @endcode
2213  *
2214  * @param sensorsAsyncResp Pointer to object holding response data.
2215  * @param inventoryItems D-Bus inventory items associated with sensors.
2216  * @param callback Callback to invoke when data has been obtained.
2217  */
2218 template <typename Callback>
2219 void getPowerSupplyAttributes(
2220     std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp,
2221     std::shared_ptr<std::vector<InventoryItem>> inventoryItems,
2222     Callback&& callback)
2223 {
2224     BMCWEB_LOG_DEBUG << "getPowerSupplyAttributes enter";
2225 
2226     // Only need the power supply attributes when the Power Schema
2227     if (sensorsAsyncResp->chassisSubNode != sensors::node::power)
2228     {
2229         BMCWEB_LOG_DEBUG << "getPowerSupplyAttributes exit since not Power";
2230         callback(inventoryItems);
2231         return;
2232     }
2233 
2234     const std::array<std::string, 1> interfaces = {
2235         "xyz.openbmc_project.Control.PowerSupplyAttributes"};
2236 
2237     // Response handler for parsing output from GetSubTree
2238     auto respHandler = [callback{std::forward<Callback>(callback)},
2239                         sensorsAsyncResp,
2240                         inventoryItems](const boost::system::error_code ec,
2241                                         const GetSubTreeType& subtree) {
2242         BMCWEB_LOG_DEBUG << "getPowerSupplyAttributes respHandler enter";
2243         if (ec)
2244         {
2245             messages::internalError(sensorsAsyncResp->asyncResp->res);
2246             BMCWEB_LOG_ERROR
2247                 << "getPowerSupplyAttributes respHandler DBus error " << ec;
2248             return;
2249         }
2250         if (subtree.empty())
2251         {
2252             BMCWEB_LOG_DEBUG << "Can't find Power Supply Attributes!";
2253             callback(inventoryItems);
2254             return;
2255         }
2256 
2257         // Currently we only support 1 power supply attribute, use this for
2258         // all the power supplies. Build map of object path to connection.
2259         // Assume just 1 connection and 1 path for now.
2260         boost::container::flat_map<std::string, std::string>
2261             psAttributesConnections;
2262 
2263         if (subtree[0].first.empty() || subtree[0].second.empty())
2264         {
2265             BMCWEB_LOG_DEBUG << "Power Supply Attributes mapper error!";
2266             callback(inventoryItems);
2267             return;
2268         }
2269 
2270         const std::string& psAttributesPath = subtree[0].first;
2271         const std::string& connection = subtree[0].second.begin()->first;
2272 
2273         if (connection.empty())
2274         {
2275             BMCWEB_LOG_DEBUG << "Power Supply Attributes mapper error!";
2276             callback(inventoryItems);
2277             return;
2278         }
2279 
2280         psAttributesConnections[psAttributesPath] = connection;
2281         BMCWEB_LOG_DEBUG << "Added mapping " << psAttributesPath << " -> "
2282                          << connection;
2283 
2284         getPowerSupplyAttributesData(sensorsAsyncResp, inventoryItems,
2285                                      psAttributesConnections,
2286                                      std::move(callback));
2287         BMCWEB_LOG_DEBUG << "getPowerSupplyAttributes respHandler exit";
2288     };
2289     // Make call to ObjectMapper to find the PowerSupplyAttributes service
2290     crow::connections::systemBus->async_method_call(
2291         std::move(respHandler), "xyz.openbmc_project.ObjectMapper",
2292         "/xyz/openbmc_project/object_mapper",
2293         "xyz.openbmc_project.ObjectMapper", "GetSubTree",
2294         "/xyz/openbmc_project", 0, interfaces);
2295     BMCWEB_LOG_DEBUG << "getPowerSupplyAttributes exit";
2296 }
2297 
2298 /**
2299  * @brief Gets inventory items associated with sensors.
2300  *
2301  * Finds the inventory items that are associated with the specified sensors.
2302  * Then gets D-Bus data for the inventory items, such as presence and VPD.
2303  *
2304  * This data is later used to provide sensor property values in the JSON
2305  * response.
2306  *
2307  * Finds the inventory items asynchronously.  Invokes callback when the
2308  * inventory items have been obtained.
2309  *
2310  * The callback must have the following signature:
2311  *   @code
2312  *   callback(std::shared_ptr<std::vector<InventoryItem>> inventoryItems)
2313  *   @endcode
2314  *
2315  * @param sensorsAsyncResp Pointer to object holding response data.
2316  * @param sensorNames All sensors within the current chassis.
2317  * @param objectMgrPaths Mappings from connection name to DBus object path that
2318  * implements ObjectManager.
2319  * @param callback Callback to invoke when inventory items have been obtained.
2320  */
2321 template <typename Callback>
2322 static void getInventoryItems(
2323     std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp,
2324     const std::shared_ptr<boost::container::flat_set<std::string>> sensorNames,
2325     std::shared_ptr<boost::container::flat_map<std::string, std::string>>
2326         objectMgrPaths,
2327     Callback&& callback)
2328 {
2329     BMCWEB_LOG_DEBUG << "getInventoryItems enter";
2330     auto getInventoryItemAssociationsCb =
2331         [sensorsAsyncResp, objectMgrPaths,
2332          callback{std::forward<Callback>(callback)}](
2333             std::shared_ptr<std::vector<InventoryItem>> inventoryItems) {
2334             BMCWEB_LOG_DEBUG << "getInventoryItemAssociationsCb enter";
2335             auto getInventoryItemsConnectionsCb =
2336                 [sensorsAsyncResp, inventoryItems, objectMgrPaths,
2337                  callback{std::forward<const Callback>(callback)}](
2338                     std::shared_ptr<boost::container::flat_set<std::string>>
2339                         invConnections) {
2340                     BMCWEB_LOG_DEBUG << "getInventoryItemsConnectionsCb enter";
2341                     auto getInventoryItemsDataCb =
2342                         [sensorsAsyncResp, inventoryItems,
2343                          callback{std::move(callback)}]() {
2344                             BMCWEB_LOG_DEBUG << "getInventoryItemsDataCb enter";
2345 
2346                             auto getInventoryLedsCb = [sensorsAsyncResp,
2347                                                        inventoryItems,
2348                                                        callback{std::move(
2349                                                            callback)}]() {
2350                                 BMCWEB_LOG_DEBUG << "getInventoryLedsCb enter";
2351                                 // Find Power Supply Attributes and get the data
2352                                 getPowerSupplyAttributes(sensorsAsyncResp,
2353                                                          inventoryItems,
2354                                                          std::move(callback));
2355                                 BMCWEB_LOG_DEBUG << "getInventoryLedsCb exit";
2356                             };
2357 
2358                             // Find led connections and get the data
2359                             getInventoryLeds(sensorsAsyncResp, inventoryItems,
2360                                              std::move(getInventoryLedsCb));
2361                             BMCWEB_LOG_DEBUG << "getInventoryItemsDataCb exit";
2362                         };
2363 
2364                     // Get inventory item data from connections
2365                     getInventoryItemsData(sensorsAsyncResp, inventoryItems,
2366                                           invConnections, objectMgrPaths,
2367                                           std::move(getInventoryItemsDataCb));
2368                     BMCWEB_LOG_DEBUG << "getInventoryItemsConnectionsCb exit";
2369                 };
2370 
2371             // Get connections that provide inventory item data
2372             getInventoryItemsConnections(
2373                 sensorsAsyncResp, inventoryItems,
2374                 std::move(getInventoryItemsConnectionsCb));
2375             BMCWEB_LOG_DEBUG << "getInventoryItemAssociationsCb exit";
2376         };
2377 
2378     // Get associations from sensors to inventory items
2379     getInventoryItemAssociations(sensorsAsyncResp, sensorNames, objectMgrPaths,
2380                                  std::move(getInventoryItemAssociationsCb));
2381     BMCWEB_LOG_DEBUG << "getInventoryItems exit";
2382 }
2383 
2384 /**
2385  * @brief Returns JSON PowerSupply object for the specified inventory item.
2386  *
2387  * Searches for a JSON PowerSupply object that matches the specified inventory
2388  * item.  If one is not found, a new PowerSupply object is added to the JSON
2389  * array.
2390  *
2391  * Multiple sensors are often associated with one power supply inventory item.
2392  * As a result, multiple sensor values are stored in one JSON PowerSupply
2393  * object.
2394  *
2395  * @param powerSupplyArray JSON array containing Redfish PowerSupply objects.
2396  * @param inventoryItem Inventory item for the power supply.
2397  * @param chassisId Chassis that contains the power supply.
2398  * @return JSON PowerSupply object for the specified inventory item.
2399  */
2400 inline nlohmann::json& getPowerSupply(nlohmann::json& powerSupplyArray,
2401                                       const InventoryItem& inventoryItem,
2402                                       const std::string& chassisId)
2403 {
2404     // Check if matching PowerSupply object already exists in JSON array
2405     for (nlohmann::json& powerSupply : powerSupplyArray)
2406     {
2407         if (powerSupply["MemberId"] == inventoryItem.name)
2408         {
2409             return powerSupply;
2410         }
2411     }
2412 
2413     // Add new PowerSupply object to JSON array
2414     powerSupplyArray.push_back({});
2415     nlohmann::json& powerSupply = powerSupplyArray.back();
2416     powerSupply["@odata.id"] =
2417         "/redfish/v1/Chassis/" + chassisId + "/Power#/PowerSupplies/";
2418     powerSupply["MemberId"] = inventoryItem.name;
2419     powerSupply["Name"] = boost::replace_all_copy(inventoryItem.name, "_", " ");
2420     powerSupply["Manufacturer"] = inventoryItem.manufacturer;
2421     powerSupply["Model"] = inventoryItem.model;
2422     powerSupply["PartNumber"] = inventoryItem.partNumber;
2423     powerSupply["SerialNumber"] = inventoryItem.serialNumber;
2424     setLedState(powerSupply, &inventoryItem);
2425 
2426     if (inventoryItem.powerSupplyEfficiencyPercent >= 0)
2427     {
2428         powerSupply["EfficiencyPercent"] =
2429             inventoryItem.powerSupplyEfficiencyPercent;
2430     }
2431 
2432     powerSupply["Status"]["State"] = getState(&inventoryItem);
2433     const char* health = inventoryItem.isFunctional ? "OK" : "Critical";
2434     powerSupply["Status"]["Health"] = health;
2435 
2436     return powerSupply;
2437 }
2438 
2439 /**
2440  * @brief Gets the values of the specified sensors.
2441  *
2442  * Stores the results as JSON in the SensorsAsyncResp.
2443  *
2444  * Gets the sensor values asynchronously.  Stores the results later when the
2445  * information has been obtained.
2446  *
2447  * The sensorNames set contains all requested sensors for the current chassis.
2448  *
2449  * To minimize the number of DBus calls, the DBus method
2450  * org.freedesktop.DBus.ObjectManager.GetManagedObjects() is used to get the
2451  * values of all sensors provided by a connection (service).
2452  *
2453  * The connections set contains all the connections that provide sensor values.
2454  *
2455  * The objectMgrPaths map contains mappings from a connection name to the
2456  * corresponding DBus object path that implements ObjectManager.
2457  *
2458  * The InventoryItem vector contains D-Bus inventory items associated with the
2459  * sensors.  Inventory item data is needed for some Redfish sensor properties.
2460  *
2461  * @param SensorsAsyncResp Pointer to object holding response data.
2462  * @param sensorNames All requested sensors within the current chassis.
2463  * @param connections Connections that provide sensor values.
2464  * @param objectMgrPaths Mappings from connection name to DBus object path that
2465  * implements ObjectManager.
2466  * @param inventoryItems Inventory items associated with the sensors.
2467  */
2468 inline void getSensorData(
2469     const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp,
2470     const std::shared_ptr<boost::container::flat_set<std::string>>& sensorNames,
2471     const boost::container::flat_set<std::string>& connections,
2472     const std::shared_ptr<boost::container::flat_map<std::string, std::string>>&
2473         objectMgrPaths,
2474     const std::shared_ptr<std::vector<InventoryItem>>& inventoryItems)
2475 {
2476     BMCWEB_LOG_DEBUG << "getSensorData enter";
2477     // Get managed objects from all services exposing sensors
2478     for (const std::string& connection : connections)
2479     {
2480         // Response handler to process managed objects
2481         auto getManagedObjectsCb = [sensorsAsyncResp, sensorNames,
2482                                     inventoryItems](
2483                                        const boost::system::error_code ec,
2484                                        dbus::utility::ManagedObjectType& resp) {
2485             BMCWEB_LOG_DEBUG << "getManagedObjectsCb enter";
2486             if (ec)
2487             {
2488                 BMCWEB_LOG_ERROR << "getManagedObjectsCb DBUS error: " << ec;
2489                 messages::internalError(sensorsAsyncResp->asyncResp->res);
2490                 return;
2491             }
2492             // Go through all objects and update response with sensor data
2493             for (const auto& objDictEntry : resp)
2494             {
2495                 const std::string& objPath =
2496                     static_cast<const std::string&>(objDictEntry.first);
2497                 BMCWEB_LOG_DEBUG << "getManagedObjectsCb parsing object "
2498                                  << objPath;
2499 
2500                 std::vector<std::string> split;
2501                 // Reserve space for
2502                 // /xyz/openbmc_project/sensors/<name>/<subname>
2503                 split.reserve(6);
2504                 boost::algorithm::split(split, objPath, boost::is_any_of("/"));
2505                 if (split.size() < 6)
2506                 {
2507                     BMCWEB_LOG_ERROR << "Got path that isn't long enough "
2508                                      << objPath;
2509                     continue;
2510                 }
2511                 // These indexes aren't intuitive, as boost::split puts an empty
2512                 // string at the beginning
2513                 const std::string& sensorType = split[4];
2514                 const std::string& sensorName = split[5];
2515                 BMCWEB_LOG_DEBUG << "sensorName " << sensorName
2516                                  << " sensorType " << sensorType;
2517                 if (sensorNames->find(objPath) == sensorNames->end())
2518                 {
2519                     BMCWEB_LOG_DEBUG << sensorName << " not in sensor list ";
2520                     continue;
2521                 }
2522 
2523                 // Find inventory item (if any) associated with sensor
2524                 InventoryItem* inventoryItem =
2525                     findInventoryItemForSensor(inventoryItems, objPath);
2526 
2527                 const std::string& sensorSchema =
2528                     sensorsAsyncResp->chassisSubNode;
2529 
2530                 nlohmann::json* sensorJson = nullptr;
2531 
2532                 if (sensorSchema == sensors::node::sensors)
2533                 {
2534                     sensorsAsyncResp->asyncResp->res.jsonValue["@odata.id"] =
2535                         "/redfish/v1/Chassis/" + sensorsAsyncResp->chassisId +
2536                         "/" + sensorsAsyncResp->chassisSubNode + "/" +
2537                         sensorName;
2538                     sensorJson = &(sensorsAsyncResp->asyncResp->res.jsonValue);
2539                 }
2540                 else
2541                 {
2542                     std::string fieldName;
2543                     if (sensorType == "temperature")
2544                     {
2545                         fieldName = "Temperatures";
2546                     }
2547                     else if (sensorType == "fan" || sensorType == "fan_tach" ||
2548                              sensorType == "fan_pwm")
2549                     {
2550                         fieldName = "Fans";
2551                     }
2552                     else if (sensorType == "voltage")
2553                     {
2554                         fieldName = "Voltages";
2555                     }
2556                     else if (sensorType == "power")
2557                     {
2558                         if (!sensorName.compare("total_power"))
2559                         {
2560                             fieldName = "PowerControl";
2561                         }
2562                         else if ((inventoryItem != nullptr) &&
2563                                  (inventoryItem->isPowerSupply))
2564                         {
2565                             fieldName = "PowerSupplies";
2566                         }
2567                         else
2568                         {
2569                             // Other power sensors are in SensorCollection
2570                             continue;
2571                         }
2572                     }
2573                     else
2574                     {
2575                         BMCWEB_LOG_ERROR << "Unsure how to handle sensorType "
2576                                          << sensorType;
2577                         continue;
2578                     }
2579 
2580                     nlohmann::json& tempArray =
2581                         sensorsAsyncResp->asyncResp->res.jsonValue[fieldName];
2582                     if (fieldName == "PowerControl")
2583                     {
2584                         if (tempArray.empty())
2585                         {
2586                             // Put multiple "sensors" into a single
2587                             // PowerControl. Follows MemberId naming and
2588                             // naming in power.hpp.
2589                             tempArray.push_back(
2590                                 {{"@odata.id",
2591                                   "/redfish/v1/Chassis/" +
2592                                       sensorsAsyncResp->chassisId + "/" +
2593                                       sensorsAsyncResp->chassisSubNode + "#/" +
2594                                       fieldName + "/0"}});
2595                         }
2596                         sensorJson = &(tempArray.back());
2597                     }
2598                     else if (fieldName == "PowerSupplies")
2599                     {
2600                         if (inventoryItem != nullptr)
2601                         {
2602                             sensorJson =
2603                                 &(getPowerSupply(tempArray, *inventoryItem,
2604                                                  sensorsAsyncResp->chassisId));
2605                         }
2606                     }
2607                     else
2608                     {
2609                         tempArray.push_back(
2610                             {{"@odata.id",
2611                               "/redfish/v1/Chassis/" +
2612                                   sensorsAsyncResp->chassisId + "/" +
2613                                   sensorsAsyncResp->chassisSubNode + "#/" +
2614                                   fieldName + "/"}});
2615                         sensorJson = &(tempArray.back());
2616                     }
2617                 }
2618 
2619                 if (sensorJson != nullptr)
2620                 {
2621                     objectInterfacesToJson(
2622                         sensorName, sensorType, sensorsAsyncResp,
2623                         objDictEntry.second, *sensorJson, inventoryItem);
2624                 }
2625             }
2626             if (sensorsAsyncResp.use_count() == 1)
2627             {
2628                 sortJSONResponse(sensorsAsyncResp);
2629                 if (sensorsAsyncResp->chassisSubNode == sensors::node::thermal)
2630                 {
2631                     populateFanRedundancy(sensorsAsyncResp);
2632                 }
2633             }
2634             BMCWEB_LOG_DEBUG << "getManagedObjectsCb exit";
2635         };
2636 
2637         // Find DBus object path that implements ObjectManager for the current
2638         // connection.  If no mapping found, default to "/".
2639         auto iter = objectMgrPaths->find(connection);
2640         const std::string& objectMgrPath =
2641             (iter != objectMgrPaths->end()) ? iter->second : "/";
2642         BMCWEB_LOG_DEBUG << "ObjectManager path for " << connection << " is "
2643                          << objectMgrPath;
2644 
2645         crow::connections::systemBus->async_method_call(
2646             getManagedObjectsCb, connection, objectMgrPath,
2647             "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
2648     }
2649     BMCWEB_LOG_DEBUG << "getSensorData exit";
2650 }
2651 
2652 inline void processSensorList(
2653     const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp,
2654     const std::shared_ptr<boost::container::flat_set<std::string>>& sensorNames)
2655 {
2656     auto getConnectionCb =
2657         [sensorsAsyncResp, sensorNames](
2658             const boost::container::flat_set<std::string>& connections) {
2659             BMCWEB_LOG_DEBUG << "getConnectionCb enter";
2660             auto getObjectManagerPathsCb =
2661                 [sensorsAsyncResp, sensorNames,
2662                  connections](const std::shared_ptr<boost::container::flat_map<
2663                                   std::string, std::string>>& objectMgrPaths) {
2664                     BMCWEB_LOG_DEBUG << "getObjectManagerPathsCb enter";
2665                     auto getInventoryItemsCb =
2666                         [sensorsAsyncResp, sensorNames, connections,
2667                          objectMgrPaths](
2668                             const std::shared_ptr<std::vector<InventoryItem>>&
2669                                 inventoryItems) {
2670                             BMCWEB_LOG_DEBUG << "getInventoryItemsCb enter";
2671                             // Get sensor data and store results in JSON
2672                             getSensorData(sensorsAsyncResp, sensorNames,
2673                                           connections, objectMgrPaths,
2674                                           inventoryItems);
2675                             BMCWEB_LOG_DEBUG << "getInventoryItemsCb exit";
2676                         };
2677 
2678                     // Get inventory items associated with sensors
2679                     getInventoryItems(sensorsAsyncResp, sensorNames,
2680                                       objectMgrPaths,
2681                                       std::move(getInventoryItemsCb));
2682 
2683                     BMCWEB_LOG_DEBUG << "getObjectManagerPathsCb exit";
2684                 };
2685 
2686             // Get mapping from connection names to the DBus object
2687             // paths that implement the ObjectManager interface
2688             getObjectManagerPaths(sensorsAsyncResp,
2689                                   std::move(getObjectManagerPathsCb));
2690             BMCWEB_LOG_DEBUG << "getConnectionCb exit";
2691         };
2692 
2693     // Get set of connections that provide sensor values
2694     getConnections(sensorsAsyncResp, sensorNames, std::move(getConnectionCb));
2695 }
2696 
2697 /**
2698  * @brief Entry point for retrieving sensors data related to requested
2699  *        chassis.
2700  * @param SensorsAsyncResp   Pointer to object holding response data
2701  */
2702 inline void
2703     getChassisData(const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp)
2704 {
2705     BMCWEB_LOG_DEBUG << "getChassisData enter";
2706     auto getChassisCb =
2707         [sensorsAsyncResp](
2708             const std::shared_ptr<boost::container::flat_set<std::string>>&
2709                 sensorNames) {
2710             BMCWEB_LOG_DEBUG << "getChassisCb enter";
2711             processSensorList(sensorsAsyncResp, sensorNames);
2712             BMCWEB_LOG_DEBUG << "getChassisCb exit";
2713         };
2714     sensorsAsyncResp->asyncResp->res.jsonValue["Redundancy"] =
2715         nlohmann::json::array();
2716 
2717     // Get set of sensors in chassis
2718     getChassis(sensorsAsyncResp, std::move(getChassisCb));
2719     BMCWEB_LOG_DEBUG << "getChassisData exit";
2720 }
2721 
2722 /**
2723  * @brief Find the requested sensorName in the list of all sensors supplied by
2724  * the chassis node
2725  *
2726  * @param sensorName   The sensor name supplied in the PATCH request
2727  * @param sensorsList  The list of sensors managed by the chassis node
2728  * @param sensorsModified  The list of sensors that were found as a result of
2729  *                         repeated calls to this function
2730  */
2731 inline bool findSensorNameUsingSensorPath(
2732     std::string_view sensorName,
2733     boost::container::flat_set<std::string>& sensorsList,
2734     boost::container::flat_set<std::string>& sensorsModified)
2735 {
2736     for (auto& chassisSensor : sensorsList)
2737     {
2738         sdbusplus::message::object_path path(chassisSensor);
2739         std::string thisSensorName = path.filename();
2740         if (thisSensorName.empty())
2741         {
2742             continue;
2743         }
2744         if (thisSensorName == sensorName)
2745         {
2746             sensorsModified.emplace(chassisSensor);
2747             return true;
2748         }
2749     }
2750     return false;
2751 }
2752 
2753 /**
2754  * @brief Entry point for overriding sensor values of given sensor
2755  *
2756  * @param sensorAsyncResp   response object
2757  * @param allCollections   Collections extract from sensors' request patch info
2758  * @param chassisSubNode   Chassis Node for which the query has to happen
2759  */
2760 inline void setSensorsOverride(
2761     const std::shared_ptr<SensorsAsyncResp>& sensorAsyncResp,
2762     std::unordered_map<std::string, std::vector<nlohmann::json>>&
2763         allCollections)
2764 {
2765     BMCWEB_LOG_INFO << "setSensorsOverride for subNode"
2766                     << sensorAsyncResp->chassisSubNode << "\n";
2767 
2768     const char* propertyValueName = nullptr;
2769     std::unordered_map<std::string, std::pair<double, std::string>> overrideMap;
2770     std::string memberId;
2771     double value = 0.0;
2772     for (auto& collectionItems : allCollections)
2773     {
2774         if (collectionItems.first == "Temperatures")
2775         {
2776             propertyValueName = "ReadingCelsius";
2777         }
2778         else if (collectionItems.first == "Fans")
2779         {
2780             propertyValueName = "Reading";
2781         }
2782         else
2783         {
2784             propertyValueName = "ReadingVolts";
2785         }
2786         for (auto& item : collectionItems.second)
2787         {
2788             if (!json_util::readJson(item, sensorAsyncResp->asyncResp->res,
2789                                      "MemberId", memberId, propertyValueName,
2790                                      value))
2791             {
2792                 return;
2793             }
2794             overrideMap.emplace(memberId,
2795                                 std::make_pair(value, collectionItems.first));
2796         }
2797     }
2798 
2799     auto getChassisSensorListCb = [sensorAsyncResp, overrideMap](
2800                                       const std::shared_ptr<
2801                                           boost::container::flat_set<
2802                                               std::string>>& sensorsList) {
2803         // Match sensor names in the PATCH request to those managed by the
2804         // chassis node
2805         const std::shared_ptr<boost::container::flat_set<std::string>>
2806             sensorNames =
2807                 std::make_shared<boost::container::flat_set<std::string>>();
2808         for (const auto& item : overrideMap)
2809         {
2810             const auto& sensor = item.first;
2811             if (!findSensorNameUsingSensorPath(sensor, *sensorsList,
2812                                                *sensorNames))
2813             {
2814                 BMCWEB_LOG_INFO << "Unable to find memberId " << item.first;
2815                 messages::resourceNotFound(sensorAsyncResp->asyncResp->res,
2816                                            item.second.second, item.first);
2817                 return;
2818             }
2819         }
2820         // Get the connection to which the memberId belongs
2821         auto getObjectsWithConnectionCb = [sensorAsyncResp, overrideMap](
2822                                               const boost::container::flat_set<
2823                                                   std::string>& /*connections*/,
2824                                               const std::set<std::pair<
2825                                                   std::string, std::string>>&
2826                                                   objectsWithConnection) {
2827             if (objectsWithConnection.size() != overrideMap.size())
2828             {
2829                 BMCWEB_LOG_INFO
2830                     << "Unable to find all objects with proper connection "
2831                     << objectsWithConnection.size() << " requested "
2832                     << overrideMap.size() << "\n";
2833                 messages::resourceNotFound(sensorAsyncResp->asyncResp->res,
2834                                            sensorAsyncResp->chassisSubNode ==
2835                                                    sensors::node::thermal
2836                                                ? "Temperatures"
2837                                                : "Voltages",
2838                                            "Count");
2839                 return;
2840             }
2841             for (const auto& item : objectsWithConnection)
2842             {
2843                 sdbusplus::message::object_path path(item.first);
2844                 std::string sensorName = path.filename();
2845                 if (sensorName.empty())
2846                 {
2847                     messages::internalError(sensorAsyncResp->asyncResp->res);
2848                     return;
2849                 }
2850 
2851                 const auto& iterator = overrideMap.find(sensorName);
2852                 if (iterator == overrideMap.end())
2853                 {
2854                     BMCWEB_LOG_INFO << "Unable to find sensor object"
2855                                     << item.first << "\n";
2856                     messages::internalError(sensorAsyncResp->asyncResp->res);
2857                     return;
2858                 }
2859                 crow::connections::systemBus->async_method_call(
2860                     [sensorAsyncResp](const boost::system::error_code ec) {
2861                         if (ec)
2862                         {
2863                             if (ec.value() ==
2864                                 boost::system::errc::permission_denied)
2865                             {
2866                                 BMCWEB_LOG_WARNING
2867                                     << "Manufacturing mode is not Enabled...can't "
2868                                        "Override the sensor value. ";
2869 
2870                                 messages::insufficientPrivilege(
2871                                     sensorAsyncResp->asyncResp->res);
2872                                 return;
2873                             }
2874                             BMCWEB_LOG_DEBUG
2875                                 << "setOverrideValueStatus DBUS error: " << ec;
2876                             messages::internalError(
2877                                 sensorAsyncResp->asyncResp->res);
2878                         }
2879                     },
2880                     item.second, item.first, "org.freedesktop.DBus.Properties",
2881                     "Set", "xyz.openbmc_project.Sensor.Value", "Value",
2882                     dbus::utility::DbusVariantType(iterator->second.first));
2883             }
2884         };
2885         // Get object with connection for the given sensor name
2886         getObjectsWithConnection(sensorAsyncResp, sensorNames,
2887                                  std::move(getObjectsWithConnectionCb));
2888     };
2889     // get full sensor list for the given chassisId and cross verify the sensor.
2890     getChassis(sensorAsyncResp, std::move(getChassisSensorListCb));
2891 }
2892 
2893 /**
2894  * @brief Retrieves mapping of Redfish URIs to sensor value property to D-Bus
2895  * path of the sensor.
2896  *
2897  * Function builds valid Redfish response for sensor query of given chassis and
2898  * node. It then builds metadata about Redfish<->D-Bus correlations and provides
2899  * it to caller in a callback.
2900  *
2901  * @param chassis   Chassis for which retrieval should be performed
2902  * @param node  Node (group) of sensors. See sensors::node for supported values
2903  * @param mapComplete   Callback to be called with retrieval result
2904  */
2905 inline void retrieveUriToDbusMap(const std::string& chassis,
2906                                  const std::string& node,
2907                                  SensorsAsyncResp::DataCompleteCb&& mapComplete)
2908 {
2909     auto pathIt = sensors::dbus::paths.find(node);
2910     if (pathIt == sensors::dbus::paths.end())
2911     {
2912         BMCWEB_LOG_ERROR << "Wrong node provided : " << node;
2913         mapComplete(boost::beast::http::status::bad_request, {});
2914         return;
2915     }
2916 
2917     auto res = std::make_shared<crow::Response>();
2918     auto asyncResp = std::make_shared<bmcweb::AsyncResp>(*res);
2919     auto callback =
2920         [res, asyncResp, mapCompleteCb{std::move(mapComplete)}](
2921             const boost::beast::http::status status,
2922             const boost::container::flat_map<std::string, std::string>&
2923                 uriToDbus) { mapCompleteCb(status, uriToDbus); };
2924 
2925     auto resp = std::make_shared<SensorsAsyncResp>(
2926         asyncResp, chassis, pathIt->second, node, std::move(callback));
2927     getChassisData(resp);
2928 }
2929 
2930 inline void requestRoutesSensorCollection(App& app)
2931 {
2932     BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/Sensors/")
2933         .privileges(redfish::privileges::getSensorCollection)
2934         .methods(
2935             boost::beast::http::verb::get)([](const crow::Request&,
2936                                               const std::shared_ptr<
2937                                                   bmcweb::AsyncResp>& aResp,
2938                                               const std::string& chassisId) {
2939             BMCWEB_LOG_DEBUG << "SensorCollection doGet enter";
2940 
2941             std::shared_ptr<SensorsAsyncResp> asyncResp =
2942                 std::make_shared<SensorsAsyncResp>(
2943                     aResp, chassisId,
2944                     sensors::dbus::paths.at(sensors::node::sensors),
2945                     sensors::node::sensors);
2946 
2947             auto getChassisCb =
2948                 [asyncResp](
2949                     const std::shared_ptr<
2950                         boost::container::flat_set<std::string>>& sensorNames) {
2951                     BMCWEB_LOG_DEBUG << "getChassisCb enter";
2952 
2953                     nlohmann::json& entriesArray =
2954                         asyncResp->asyncResp->res.jsonValue["Members"];
2955                     for (auto& sensor : *sensorNames)
2956                     {
2957                         BMCWEB_LOG_DEBUG << "Adding sensor: " << sensor;
2958 
2959                         sdbusplus::message::object_path path(sensor);
2960                         std::string sensorName = path.filename();
2961                         if (sensorName.empty())
2962                         {
2963                             BMCWEB_LOG_ERROR << "Invalid sensor path: "
2964                                              << sensor;
2965                             messages::internalError(asyncResp->asyncResp->res);
2966                             return;
2967                         }
2968                         entriesArray.push_back(
2969                             {{"@odata.id", "/redfish/v1/Chassis/" +
2970                                                asyncResp->chassisId + "/" +
2971                                                asyncResp->chassisSubNode + "/" +
2972                                                sensorName}});
2973                     }
2974 
2975                     asyncResp->asyncResp->res.jsonValue["Members@odata.count"] =
2976                         entriesArray.size();
2977                     BMCWEB_LOG_DEBUG << "getChassisCb exit";
2978                 };
2979 
2980             // Get set of sensors in chassis
2981             getChassis(asyncResp, std::move(getChassisCb));
2982             BMCWEB_LOG_DEBUG << "SensorCollection doGet exit";
2983         });
2984 }
2985 
2986 inline void requestRoutesSensor(App& app)
2987 {
2988     BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/Sensors/<str>/")
2989         .privileges(redfish::privileges::getSensor)
2990         .methods(
2991             boost::beast::http::verb::get)([](const crow::Request&,
2992                                               const std::shared_ptr<
2993                                                   bmcweb::AsyncResp>& aResp,
2994                                               const std::string& chassisId,
2995                                               const std::string& sensorName) {
2996             BMCWEB_LOG_DEBUG << "Sensor doGet enter";
2997             std::shared_ptr<SensorsAsyncResp> asyncResp =
2998                 std::make_shared<SensorsAsyncResp>(aResp, chassisId,
2999                                                    std::vector<const char*>(),
3000                                                    sensors::node::sensors);
3001 
3002             const std::array<const char*, 1> interfaces = {
3003                 "xyz.openbmc_project.Sensor.Value"};
3004 
3005             // Get a list of all of the sensors that implement Sensor.Value
3006             // and get the path and service name associated with the sensor
3007             crow::connections::systemBus->async_method_call(
3008                 [asyncResp, sensorName](const boost::system::error_code ec,
3009                                         const GetSubTreeType& subtree) {
3010                     BMCWEB_LOG_DEBUG << "respHandler1 enter";
3011                     if (ec)
3012                     {
3013                         messages::internalError(asyncResp->asyncResp->res);
3014                         BMCWEB_LOG_ERROR
3015                             << "Sensor getSensorPaths resp_handler: "
3016                             << "Dbus error " << ec;
3017                         return;
3018                     }
3019 
3020                     GetSubTreeType::const_iterator it = std::find_if(
3021                         subtree.begin(), subtree.end(),
3022                         [sensorName](
3023                             const std::pair<
3024                                 std::string,
3025                                 std::vector<std::pair<
3026                                     std::string, std::vector<std::string>>>>&
3027                                 object) {
3028                             sdbusplus::message::object_path path(object.first);
3029                             std::string name = path.filename();
3030                             if (name.empty())
3031                             {
3032                                 BMCWEB_LOG_ERROR << "Invalid sensor path: "
3033                                                  << object.first;
3034                                 return false;
3035                             }
3036 
3037                             return name == sensorName;
3038                         });
3039 
3040                     if (it == subtree.end())
3041                     {
3042                         BMCWEB_LOG_ERROR << "Could not find path for sensor: "
3043                                          << sensorName;
3044                         messages::resourceNotFound(asyncResp->asyncResp->res,
3045                                                    "Sensor", sensorName);
3046                         return;
3047                     }
3048                     std::string_view sensorPath = (*it).first;
3049                     BMCWEB_LOG_DEBUG << "Found sensor path for sensor '"
3050                                      << sensorName << "': " << sensorPath;
3051 
3052                     const std::shared_ptr<
3053                         boost::container::flat_set<std::string>>
3054                         sensorList = std::make_shared<
3055                             boost::container::flat_set<std::string>>();
3056 
3057                     sensorList->emplace(sensorPath);
3058                     processSensorList(asyncResp, sensorList);
3059                     BMCWEB_LOG_DEBUG << "respHandler1 exit";
3060                 },
3061                 "xyz.openbmc_project.ObjectMapper",
3062                 "/xyz/openbmc_project/object_mapper",
3063                 "xyz.openbmc_project.ObjectMapper", "GetSubTree",
3064                 "/xyz/openbmc_project/sensors", 2, interfaces);
3065         });
3066 }
3067 
3068 } // namespace redfish
3069