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