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