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