xref: /openbmc/bmcweb/redfish-core/lib/sensors.hpp (revision 4491419217d426791003facc749f600692d9b2a6)
1 // SPDX-License-Identifier: Apache-2.0
2 // SPDX-FileCopyrightText: Copyright OpenBMC Authors
3 // SPDX-FileCopyrightText: Copyright 2018 Intel Corporation
4 #pragma once
5 
6 #include "bmcweb_config.h"
7 
8 #include "app.hpp"
9 #include "async_resp.hpp"
10 #include "dbus_singleton.hpp"
11 #include "dbus_utility.hpp"
12 #include "error_messages.hpp"
13 #include "generated/enums/redundancy.hpp"
14 #include "generated/enums/resource.hpp"
15 #include "http_request.hpp"
16 #include "logging.hpp"
17 #include "query.hpp"
18 #include "registries/privilege_registry.hpp"
19 #include "str_utility.hpp"
20 #include "utils/dbus_utils.hpp"
21 #include "utils/json_utils.hpp"
22 #include "utils/query_param.hpp"
23 #include "utils/sensor_utils.hpp"
24 
25 #include <asm-generic/errno.h>
26 
27 #include <boost/beast/http/status.hpp>
28 #include <boost/beast/http/verb.hpp>
29 #include <boost/system/error_code.hpp>
30 #include <boost/url/format.hpp>
31 #include <boost/url/url.hpp>
32 #include <nlohmann/json.hpp>
33 #include <sdbusplus/message/native_types.hpp>
34 #include <sdbusplus/unpack_properties.hpp>
35 
36 #include <algorithm>
37 #include <array>
38 #include <cmath>
39 #include <cstddef>
40 #include <cstdint>
41 #include <functional>
42 #include <iterator>
43 #include <map>
44 #include <memory>
45 #include <optional>
46 #include <ranges>
47 #include <set>
48 #include <span>
49 #include <string>
50 #include <string_view>
51 #include <unordered_map>
52 #include <utility>
53 #include <variant>
54 #include <vector>
55 
56 namespace redfish
57 {
58 
59 namespace sensors
60 {
61 
62 // clang-format off
63 namespace dbus
64 {
65 constexpr auto powerPaths = std::to_array<std::string_view>({
66     "/xyz/openbmc_project/sensors/voltage",
67     "/xyz/openbmc_project/sensors/power"
68 });
69 
getSensorPaths()70 constexpr auto getSensorPaths(){
71     if constexpr(BMCWEB_REDFISH_NEW_POWERSUBSYSTEM_THERMALSUBSYSTEM){
72     return std::to_array<std::string_view>({
73         "/xyz/openbmc_project/sensors/power",
74         "/xyz/openbmc_project/sensors/current",
75         "/xyz/openbmc_project/sensors/airflow",
76         "/xyz/openbmc_project/sensors/humidity",
77         "/xyz/openbmc_project/sensors/voltage",
78         "/xyz/openbmc_project/sensors/fan_tach",
79         "/xyz/openbmc_project/sensors/temperature",
80         "/xyz/openbmc_project/sensors/fan_pwm",
81         "/xyz/openbmc_project/sensors/altitude",
82         "/xyz/openbmc_project/sensors/energy",
83         "/xyz/openbmc_project/sensors/liquidflow",
84         "/xyz/openbmc_project/sensors/pressure",
85         "/xyz/openbmc_project/sensors/utilization"});
86     } else {
87       return  std::to_array<std::string_view>({"/xyz/openbmc_project/sensors/power",
88         "/xyz/openbmc_project/sensors/current",
89         "/xyz/openbmc_project/sensors/airflow",
90         "/xyz/openbmc_project/sensors/humidity",
91         "/xyz/openbmc_project/sensors/utilization"});
92 }
93 }
94 
95 constexpr auto sensorPaths = getSensorPaths();
96 
97 constexpr auto thermalPaths = std::to_array<std::string_view>({
98     "/xyz/openbmc_project/sensors/fan_tach",
99     "/xyz/openbmc_project/sensors/temperature",
100     "/xyz/openbmc_project/sensors/fan_pwm"
101 });
102 
103 } // namespace dbus
104 // clang-format on
105 
106 constexpr std::string_view powerNodeStr = sensor_utils::chassisSubNodeToString(
107     sensor_utils::ChassisSubNode::powerNode);
108 constexpr std::string_view sensorsNodeStr =
109     sensor_utils::chassisSubNodeToString(
110         sensor_utils::ChassisSubNode::sensorsNode);
111 constexpr std::string_view thermalNodeStr =
112     sensor_utils::chassisSubNodeToString(
113         sensor_utils::ChassisSubNode::thermalNode);
114 
115 using sensorPair =
116     std::pair<std::string_view, std::span<const std::string_view>>;
117 static constexpr std::array<sensorPair, 3> paths = {
118     {{sensors::powerNodeStr, dbus::powerPaths},
119      {sensors::sensorsNodeStr, dbus::sensorPaths},
120      {sensors::thermalNodeStr, dbus::thermalPaths}}};
121 
122 } // namespace sensors
123 
124 /**
125  * SensorsAsyncResp
126  * Gathers data needed for response processing after async calls are done
127  */
128 class SensorsAsyncResp
129 {
130   public:
131     using DataCompleteCb = std::function<void(
132         const boost::beast::http::status status,
133         const std::map<std::string, std::string>& uriToDbus)>;
134 
135     struct SensorData
136     {
137         std::string name;
138         std::string uri;
139         std::string dbusPath;
140     };
141 
SensorsAsyncResp(const std::shared_ptr<bmcweb::AsyncResp> & asyncRespIn,const std::string & chassisIdIn,std::span<const std::string_view> typesIn,std::string_view subNode)142     SensorsAsyncResp(const std::shared_ptr<bmcweb::AsyncResp>& asyncRespIn,
143                      const std::string& chassisIdIn,
144                      std::span<const std::string_view> typesIn,
145                      std::string_view subNode) :
146         asyncResp(asyncRespIn), chassisId(chassisIdIn), types(typesIn),
147         chassisSubNode(subNode), efficientExpand(false)
148     {}
149 
150     // Store extra data about sensor mapping and return it in callback
SensorsAsyncResp(const std::shared_ptr<bmcweb::AsyncResp> & asyncRespIn,const std::string & chassisIdIn,std::span<const std::string_view> typesIn,std::string_view subNode,DataCompleteCb && creationComplete)151     SensorsAsyncResp(const std::shared_ptr<bmcweb::AsyncResp>& asyncRespIn,
152                      const std::string& chassisIdIn,
153                      std::span<const std::string_view> typesIn,
154                      std::string_view subNode,
155                      DataCompleteCb&& creationComplete) :
156         asyncResp(asyncRespIn), chassisId(chassisIdIn), types(typesIn),
157         chassisSubNode(subNode), efficientExpand(false),
158         metadata{std::vector<SensorData>()},
159         dataComplete{std::move(creationComplete)}
160     {}
161 
162     // sensor collections expand
SensorsAsyncResp(const std::shared_ptr<bmcweb::AsyncResp> & asyncRespIn,const std::string & chassisIdIn,std::span<const std::string_view> typesIn,const std::string_view & subNode,bool efficientExpandIn)163     SensorsAsyncResp(const std::shared_ptr<bmcweb::AsyncResp>& asyncRespIn,
164                      const std::string& chassisIdIn,
165                      std::span<const std::string_view> typesIn,
166                      const std::string_view& subNode, bool efficientExpandIn) :
167         asyncResp(asyncRespIn), chassisId(chassisIdIn), types(typesIn),
168         chassisSubNode(subNode), efficientExpand(efficientExpandIn)
169     {}
170 
~SensorsAsyncResp()171     ~SensorsAsyncResp()
172     {
173         if (asyncResp->res.result() ==
174             boost::beast::http::status::internal_server_error)
175         {
176             // Reset the json object to clear out any data that made it in
177             // before the error happened todo(ed) handle error condition with
178             // proper code
179             asyncResp->res.jsonValue = nlohmann::json::object();
180         }
181 
182         if (dataComplete && metadata)
183         {
184             std::map<std::string, std::string> map;
185             if (asyncResp->res.result() == boost::beast::http::status::ok)
186             {
187                 for (auto& sensor : *metadata)
188                 {
189                     map.emplace(sensor.uri, sensor.dbusPath);
190                 }
191             }
192             dataComplete(asyncResp->res.result(), map);
193         }
194     }
195 
196     SensorsAsyncResp(const SensorsAsyncResp&) = delete;
197     SensorsAsyncResp(SensorsAsyncResp&&) = delete;
198     SensorsAsyncResp& operator=(const SensorsAsyncResp&) = delete;
199     SensorsAsyncResp& operator=(SensorsAsyncResp&&) = delete;
200 
addMetadata(const nlohmann::json & sensorObject,const std::string & dbusPath)201     void addMetadata(const nlohmann::json& sensorObject,
202                      const std::string& dbusPath)
203     {
204         if (metadata)
205         {
206             metadata->emplace_back(SensorData{
207                 sensorObject["Name"], sensorObject["@odata.id"], dbusPath});
208         }
209     }
210 
updateUri(const std::string & name,const std::string & uri)211     void updateUri(const std::string& name, const std::string& uri)
212     {
213         if (metadata)
214         {
215             for (auto& sensor : *metadata)
216             {
217                 if (sensor.name == name)
218                 {
219                     sensor.uri = uri;
220                 }
221             }
222         }
223     }
224 
225     const std::shared_ptr<bmcweb::AsyncResp> asyncResp;
226     const std::string chassisId;
227     const std::span<const std::string_view> types;
228     const std::string chassisSubNode;
229     const bool efficientExpand;
230 
231   private:
232     std::optional<std::vector<SensorData>> metadata;
233     DataCompleteCb dataComplete;
234 };
235 
236 using InventoryItem = sensor_utils::InventoryItem;
237 
238 /**
239  * @brief Get objects with connection necessary for sensors
240  * @param SensorsAsyncResp Pointer to object holding response data
241  * @param sensorNames Sensors retrieved from chassis
242  * @param callback Callback for processing gathered connections
243  */
244 template <typename Callback>
getObjectsWithConnection(const std::shared_ptr<SensorsAsyncResp> & sensorsAsyncResp,const std::shared_ptr<std::set<std::string>> & sensorNames,Callback && callback)245 void getObjectsWithConnection(
246     const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp,
247     const std::shared_ptr<std::set<std::string>>& sensorNames,
248     Callback&& callback)
249 {
250     BMCWEB_LOG_DEBUG("getObjectsWithConnection enter");
251     const std::string path = "/xyz/openbmc_project/sensors";
252     constexpr std::array<std::string_view, 1> interfaces = {
253         "xyz.openbmc_project.Sensor.Value"};
254 
255     // Make call to ObjectMapper to find all sensors objects
256     dbus::utility::getSubTree(
257         path, 2, interfaces,
258         [callback = std::forward<Callback>(callback), sensorsAsyncResp,
259          sensorNames](const boost::system::error_code& ec,
260                       const dbus::utility::MapperGetSubTreeResponse& subtree) {
261             // Response handler for parsing objects subtree
262             BMCWEB_LOG_DEBUG("getObjectsWithConnection resp_handler enter");
263             if (ec)
264             {
265                 messages::internalError(sensorsAsyncResp->asyncResp->res);
266                 BMCWEB_LOG_ERROR(
267                     "getObjectsWithConnection resp_handler: Dbus error {}", ec);
268                 return;
269             }
270 
271             BMCWEB_LOG_DEBUG("Found {} subtrees", subtree.size());
272 
273             // Make unique list of connections only for requested sensor types
274             // and found in the chassis
275             std::set<std::string> connections;
276             std::set<std::pair<std::string, std::string>> objectsWithConnection;
277 
278             BMCWEB_LOG_DEBUG("sensorNames list count: {}", sensorNames->size());
279             for (const std::string& tsensor : *sensorNames)
280             {
281                 BMCWEB_LOG_DEBUG("Sensor to find: {}", tsensor);
282             }
283 
284             for (const std::pair<std::string,
285                                  std::vector<std::pair<
286                                      std::string, std::vector<std::string>>>>&
287                      object : subtree)
288             {
289                 if (sensorNames->find(object.first) != sensorNames->end())
290                 {
291                     for (const std::pair<std::string, std::vector<std::string>>&
292                              objData : object.second)
293                     {
294                         BMCWEB_LOG_DEBUG("Adding connection: {}",
295                                          objData.first);
296                         connections.insert(objData.first);
297                         objectsWithConnection.insert(
298                             std::make_pair(object.first, objData.first));
299                     }
300                 }
301             }
302             BMCWEB_LOG_DEBUG("Found {} connections", connections.size());
303             callback(std::move(connections), std::move(objectsWithConnection));
304             BMCWEB_LOG_DEBUG("getObjectsWithConnection resp_handler exit");
305         });
306     BMCWEB_LOG_DEBUG("getObjectsWithConnection exit");
307 }
308 
309 /**
310  * @brief Create connections necessary for sensors
311  * @param SensorsAsyncResp Pointer to object holding response data
312  * @param sensorNames Sensors retrieved from chassis
313  * @param callback Callback for processing gathered connections
314  */
315 template <typename Callback>
getConnections(const std::shared_ptr<SensorsAsyncResp> & sensorsAsyncResp,const std::shared_ptr<std::set<std::string>> & sensorNames,Callback && callback)316 void getConnections(const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp,
317                     const std::shared_ptr<std::set<std::string>>& sensorNames,
318                     Callback&& callback)
319 {
320     auto objectsWithConnectionCb =
321         [callback = std::forward<Callback>(callback)](
322             const std::set<std::string>& connections,
323             const std::set<std::pair<std::string, std::string>>&
324             /*objectsWithConnection*/) { callback(connections); };
325     getObjectsWithConnection(sensorsAsyncResp, sensorNames,
326                              std::move(objectsWithConnectionCb));
327 }
328 
329 /**
330  * @brief Shrinks the list of sensors for processing
331  * @param SensorsAysncResp  The class holding the Redfish response
332  * @param allSensors  A list of all the sensors associated to the
333  * chassis element (i.e. baseboard, front panel, etc...)
334  * @param activeSensors A list that is a reduction of the incoming
335  * allSensors list.  Eliminate Thermal sensors when a Power request is
336  * made, and eliminate Power sensors when a Thermal request is made.
337  */
reduceSensorList(crow::Response & res,std::string_view chassisSubNode,std::span<const std::string_view> sensorTypes,const std::vector<std::string> * allSensors,const std::shared_ptr<std::set<std::string>> & activeSensors)338 inline void reduceSensorList(
339     crow::Response& res, std::string_view chassisSubNode,
340     std::span<const std::string_view> sensorTypes,
341     const std::vector<std::string>* allSensors,
342     const std::shared_ptr<std::set<std::string>>& activeSensors)
343 {
344     if ((allSensors == nullptr) || (activeSensors == nullptr))
345     {
346         messages::resourceNotFound(res, chassisSubNode,
347                                    chassisSubNode == sensors::thermalNodeStr
348                                        ? "Temperatures"
349                                        : "Voltages");
350 
351         return;
352     }
353     if (allSensors->empty())
354     {
355         // Nothing to do, the activeSensors object is also empty
356         return;
357     }
358 
359     for (std::string_view type : sensorTypes)
360     {
361         for (const std::string& sensor : *allSensors)
362         {
363             if (sensor.starts_with(type))
364             {
365                 activeSensors->emplace(sensor);
366             }
367         }
368     }
369 }
370 
371 /*
372  *Populates the top level collection for a given subnode.  Populates
373  *SensorCollection, Power, or Thermal schemas.
374  *
375  * */
populateChassisNode(nlohmann::json & jsonValue,std::string_view chassisSubNode)376 inline void populateChassisNode(nlohmann::json& jsonValue,
377                                 std::string_view chassisSubNode)
378 {
379     if (chassisSubNode == sensors::powerNodeStr)
380     {
381         jsonValue["@odata.type"] = "#Power.v1_5_2.Power";
382     }
383     else if (chassisSubNode == sensors::thermalNodeStr)
384     {
385         jsonValue["@odata.type"] = "#Thermal.v1_4_0.Thermal";
386         jsonValue["Fans"] = nlohmann::json::array();
387         jsonValue["Temperatures"] = nlohmann::json::array();
388     }
389     else if (chassisSubNode == sensors::sensorsNodeStr)
390     {
391         jsonValue["@odata.type"] = "#SensorCollection.SensorCollection";
392         jsonValue["Description"] = "Collection of Sensors for this Chassis";
393         jsonValue["Members"] = nlohmann::json::array();
394         jsonValue["Members@odata.count"] = 0;
395     }
396 
397     if (chassisSubNode != sensors::sensorsNodeStr)
398     {
399         jsonValue["Id"] = chassisSubNode;
400     }
401     jsonValue["Name"] = chassisSubNode;
402 }
403 
404 /**
405  * @brief Retrieves requested chassis sensors and redundancy data from DBus .
406  * @param SensorsAsyncResp   Pointer to object holding response data
407  * @param callback  Callback for next step in gathered sensor processing
408  */
409 template <typename Callback>
getChassis(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,std::string_view chassisId,std::string_view chassisSubNode,std::span<const std::string_view> sensorTypes,Callback && callback)410 void getChassis(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
411                 std::string_view chassisId, std::string_view chassisSubNode,
412                 std::span<const std::string_view> sensorTypes,
413                 Callback&& callback)
414 {
415     BMCWEB_LOG_DEBUG("getChassis enter");
416     constexpr std::array<std::string_view, 2> interfaces = {
417         "xyz.openbmc_project.Inventory.Item.Board",
418         "xyz.openbmc_project.Inventory.Item.Chassis"};
419 
420     // Get the Chassis Collection
421     dbus::utility::getSubTreePaths(
422         "/xyz/openbmc_project/inventory", 0, interfaces,
423         [callback = std::forward<Callback>(callback), asyncResp,
424          chassisIdStr{std::string(chassisId)},
425          chassisSubNode{std::string(chassisSubNode)},
426          sensorTypes](const boost::system::error_code& ec,
427                       const dbus::utility::MapperGetSubTreePathsResponse&
428                           chassisPaths) mutable {
429             BMCWEB_LOG_DEBUG("getChassis respHandler enter");
430             if (ec)
431             {
432                 BMCWEB_LOG_ERROR("getChassis respHandler DBUS error: {}", ec);
433                 messages::internalError(asyncResp->res);
434                 return;
435             }
436             const std::string* chassisPath = nullptr;
437             for (const std::string& chassis : chassisPaths)
438             {
439                 sdbusplus::message::object_path path(chassis);
440                 std::string chassisName = path.filename();
441                 if (chassisName.empty())
442                 {
443                     BMCWEB_LOG_ERROR("Failed to find '/' in {}", chassis);
444                     continue;
445                 }
446                 if (chassisName == chassisIdStr)
447                 {
448                     chassisPath = &chassis;
449                     break;
450                 }
451             }
452             if (chassisPath == nullptr)
453             {
454                 messages::resourceNotFound(asyncResp->res, "Chassis",
455                                            chassisIdStr);
456                 return;
457             }
458             populateChassisNode(asyncResp->res.jsonValue, chassisSubNode);
459 
460             asyncResp->res.jsonValue["@odata.id"] = boost::urls::format(
461                 "/redfish/v1/Chassis/{}/{}", chassisIdStr, chassisSubNode);
462 
463             // Get the list of all sensors for this Chassis element
464             std::string sensorPath = *chassisPath + "/all_sensors";
465             dbus::utility::getAssociationEndPoints(
466                 sensorPath, [asyncResp, chassisSubNode, sensorTypes,
467                              callback = std::forward<Callback>(callback)](
468                                 const boost::system::error_code& ec2,
469                                 const dbus::utility::MapperEndPoints&
470                                     nodeSensorList) mutable {
471                     if (ec2)
472                     {
473                         if (ec2.value() != EBADR)
474                         {
475                             messages::internalError(asyncResp->res);
476                             return;
477                         }
478                     }
479                     const std::shared_ptr<std::set<std::string>>
480                         culledSensorList =
481                             std::make_shared<std::set<std::string>>();
482                     reduceSensorList(asyncResp->res, chassisSubNode,
483                                      sensorTypes, &nodeSensorList,
484                                      culledSensorList);
485                     BMCWEB_LOG_DEBUG("Finishing with {}",
486                                      culledSensorList->size());
487                     callback(culledSensorList);
488                 });
489         });
490     BMCWEB_LOG_DEBUG("getChassis exit");
491 }
492 
493 /**
494  * @brief Builds a json sensor representation of a sensor.
495  * @param sensorName  The name of the sensor to be built
496  * @param sensorType  The type (temperature, fan_tach, etc) of the sensor to
497  * build
498  * @param chassisSubNode The subnode (thermal, sensor, etc) of the sensor
499  * @param interfacesDict  A dictionary of the interfaces and properties of said
500  * interfaces to be built from
501  * @param sensorJson  The json object to fill
502  * @param inventoryItem D-Bus inventory item associated with the sensor.  Will
503  * be nullptr if no associated inventory item was found.
504  */
objectInterfacesToJson(const std::string & sensorName,const std::string & sensorType,const sensor_utils::ChassisSubNode chassisSubNode,const dbus::utility::DBusInterfacesMap & interfacesDict,nlohmann::json & sensorJson,InventoryItem * inventoryItem)505 inline void objectInterfacesToJson(
506     const std::string& sensorName, const std::string& sensorType,
507     const sensor_utils::ChassisSubNode chassisSubNode,
508     const dbus::utility::DBusInterfacesMap& interfacesDict,
509     nlohmann::json& sensorJson, InventoryItem* inventoryItem)
510 {
511     for (const auto& [interface, valuesDict] : interfacesDict)
512     {
513         sensor_utils::objectPropertiesToJson(
514             sensorName, sensorType, chassisSubNode, valuesDict, sensorJson,
515             inventoryItem);
516     }
517     BMCWEB_LOG_DEBUG("Added sensor {}", sensorName);
518 }
519 
populateFanRedundancy(const std::shared_ptr<SensorsAsyncResp> & sensorsAsyncResp)520 inline void populateFanRedundancy(
521     const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp)
522 {
523     constexpr std::array<std::string_view, 1> interfaces = {
524         "xyz.openbmc_project.Control.FanRedundancy"};
525     dbus::utility::getSubTree(
526         "/xyz/openbmc_project/control", 2, interfaces,
527         [sensorsAsyncResp](
528             const boost::system::error_code& ec,
529             const dbus::utility::MapperGetSubTreeResponse& resp) {
530             if (ec)
531             {
532                 return; // don't have to have this interface
533             }
534             for (const std::pair<std::string, dbus::utility::MapperServiceMap>&
535                      pathPair : resp)
536             {
537                 const std::string& path = pathPair.first;
538                 const dbus::utility::MapperServiceMap& objDict =
539                     pathPair.second;
540                 if (objDict.empty())
541                 {
542                     continue; // this should be impossible
543                 }
544 
545                 const std::string& owner = objDict.begin()->first;
546                 dbus::utility::getAssociationEndPoints(
547                     path + "/chassis",
548                     [path, owner, sensorsAsyncResp](
549                         const boost::system::error_code& ec2,
550                         const dbus::utility::MapperEndPoints& endpoints) {
551                         if (ec2)
552                         {
553                             return; // if they don't have an association we
554                                     // can't tell what chassis is
555                         }
556                         auto found = std::ranges::find_if(
557                             endpoints,
558                             [sensorsAsyncResp](const std::string& entry) {
559                                 return entry.find(
560                                            sensorsAsyncResp->chassisId) !=
561                                        std::string::npos;
562                             });
563 
564                         if (found == endpoints.end())
565                         {
566                             return;
567                         }
568                         dbus::utility::getAllProperties(
569                             *crow::connections::systemBus, owner, path,
570                             "xyz.openbmc_project.Control.FanRedundancy",
571                             [path, sensorsAsyncResp](
572                                 const boost::system::error_code& ec3,
573                                 const dbus::utility::DBusPropertiesMap& ret) {
574                                 if (ec3)
575                                 {
576                                     return; // don't have to have this
577                                             // interface
578                                 }
579 
580                                 const uint8_t* allowedFailures = nullptr;
581                                 const std::vector<std::string>* collection =
582                                     nullptr;
583                                 const std::string* status = nullptr;
584 
585                                 const bool success =
586                                     sdbusplus::unpackPropertiesNoThrow(
587                                         dbus_utils::UnpackErrorPrinter(), ret,
588                                         "AllowedFailures", allowedFailures,
589                                         "Collection", collection, "Status",
590                                         status);
591 
592                                 if (!success)
593                                 {
594                                     messages::internalError(
595                                         sensorsAsyncResp->asyncResp->res);
596                                     return;
597                                 }
598 
599                                 if (allowedFailures == nullptr ||
600                                     collection == nullptr || status == nullptr)
601                                 {
602                                     BMCWEB_LOG_ERROR(
603                                         "Invalid redundancy interface");
604                                     messages::internalError(
605                                         sensorsAsyncResp->asyncResp->res);
606                                     return;
607                                 }
608 
609                                 sdbusplus::message::object_path objectPath(
610                                     path);
611                                 std::string name = objectPath.filename();
612                                 if (name.empty())
613                                 {
614                                     // this should be impossible
615                                     messages::internalError(
616                                         sensorsAsyncResp->asyncResp->res);
617                                     return;
618                                 }
619                                 std::ranges::replace(name, '_', ' ');
620 
621                                 std::string health;
622 
623                                 if (status->ends_with("Full"))
624                                 {
625                                     health = "OK";
626                                 }
627                                 else if (status->ends_with("Degraded"))
628                                 {
629                                     health = "Warning";
630                                 }
631                                 else
632                                 {
633                                     health = "Critical";
634                                 }
635                                 nlohmann::json::array_t redfishCollection;
636                                 const auto& fanRedfish =
637                                     sensorsAsyncResp->asyncResp->res
638                                         .jsonValue["Fans"];
639                                 for (const std::string& item : *collection)
640                                 {
641                                     sdbusplus::message::object_path itemPath(
642                                         item);
643                                     std::string itemName = itemPath.filename();
644                                     if (itemName.empty())
645                                     {
646                                         continue;
647                                     }
648                                     /*
649                                     todo(ed): merge patch that fixes the names
650                                     std::replace(itemName.begin(),
651                                                  itemName.end(), '_', ' ');*/
652                                     auto schemaItem = std::ranges::find_if(
653                                         fanRedfish,
654                                         [itemName](const nlohmann::json& fan) {
655                                             return fan["Name"] == itemName;
656                                         });
657                                     if (schemaItem != fanRedfish.end())
658                                     {
659                                         nlohmann::json::object_t collectionId;
660                                         collectionId["@odata.id"] =
661                                             (*schemaItem)["@odata.id"];
662                                         redfishCollection.emplace_back(
663                                             std::move(collectionId));
664                                     }
665                                     else
666                                     {
667                                         BMCWEB_LOG_ERROR(
668                                             "failed to find fan in schema");
669                                         messages::internalError(
670                                             sensorsAsyncResp->asyncResp->res);
671                                         return;
672                                     }
673                                 }
674 
675                                 size_t minNumNeeded =
676                                     collection->empty()
677                                         ? 0
678                                         : collection->size() - *allowedFailures;
679                                 nlohmann::json& jResp =
680                                     sensorsAsyncResp->asyncResp->res
681                                         .jsonValue["Redundancy"];
682 
683                                 nlohmann::json::object_t redundancy;
684                                 boost::urls::url url = boost::urls::format(
685                                     "/redfish/v1/Chassis/{}/{}",
686                                     sensorsAsyncResp->chassisId,
687                                     sensorsAsyncResp->chassisSubNode);
688                                 url.set_fragment(
689                                     ("/Redundancy"_json_pointer / jResp.size())
690                                         .to_string());
691                                 redundancy["@odata.id"] = std::move(url);
692                                 redundancy["@odata.type"] =
693                                     "#Redundancy.v1_3_2.Redundancy";
694                                 redundancy["MinNumNeeded"] = minNumNeeded;
695                                 redundancy["Mode"] =
696                                     redundancy::RedundancyType::NPlusM;
697                                 redundancy["Name"] = name;
698                                 redundancy["RedundancySet"] = redfishCollection;
699                                 redundancy["Status"]["Health"] = health;
700                                 redundancy["Status"]["State"] =
701                                     resource::State::Enabled;
702 
703                                 jResp.emplace_back(std::move(redundancy));
704                             });
705                     });
706             }
707         });
708 }
709 
sortJSONResponse(const std::shared_ptr<SensorsAsyncResp> & sensorsAsyncResp)710 inline void sortJSONResponse(
711     const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp)
712 {
713     nlohmann::json& response = sensorsAsyncResp->asyncResp->res.jsonValue;
714     std::array<std::string, 2> sensorHeaders{"Temperatures", "Fans"};
715     if (sensorsAsyncResp->chassisSubNode == sensors::powerNodeStr)
716     {
717         sensorHeaders = {"Voltages", "PowerSupplies"};
718     }
719     for (const std::string& sensorGroup : sensorHeaders)
720     {
721         nlohmann::json::iterator entry = response.find(sensorGroup);
722         if (entry == response.end())
723         {
724             continue;
725         }
726         nlohmann::json::array_t* arr =
727             entry->get_ptr<nlohmann::json::array_t*>();
728         if (arr == nullptr)
729         {
730             continue;
731         }
732         json_util::sortJsonArrayByKey(*arr, "Name");
733 
734         // add the index counts to the end of each entry
735         size_t count = 0;
736         for (nlohmann::json& sensorJson : *entry)
737         {
738             nlohmann::json::iterator odata = sensorJson.find("@odata.id");
739             if (odata == sensorJson.end())
740             {
741                 continue;
742             }
743             std::string* value = odata->get_ptr<std::string*>();
744             if (value != nullptr)
745             {
746                 *value += "/" + std::to_string(count);
747                 sensorJson["MemberId"] = std::to_string(count);
748                 count++;
749                 sensorsAsyncResp->updateUri(sensorJson["Name"], *value);
750             }
751         }
752     }
753 }
754 
755 /**
756  * @brief Finds the inventory item with the specified object path.
757  * @param inventoryItems D-Bus inventory items associated with sensors.
758  * @param invItemObjPath D-Bus object path of inventory item.
759  * @return Inventory item within vector, or nullptr if no match found.
760  */
findInventoryItem(const std::shared_ptr<std::vector<InventoryItem>> & inventoryItems,const std::string & invItemObjPath)761 inline InventoryItem* findInventoryItem(
762     const std::shared_ptr<std::vector<InventoryItem>>& inventoryItems,
763     const std::string& invItemObjPath)
764 {
765     for (InventoryItem& inventoryItem : *inventoryItems)
766     {
767         if (inventoryItem.objectPath == invItemObjPath)
768         {
769             return &inventoryItem;
770         }
771     }
772     return nullptr;
773 }
774 
775 /**
776  * @brief Finds the inventory item associated with the specified sensor.
777  * @param inventoryItems D-Bus inventory items associated with sensors.
778  * @param sensorObjPath D-Bus object path of sensor.
779  * @return Inventory item within vector, or nullptr if no match found.
780  */
findInventoryItemForSensor(const std::shared_ptr<std::vector<InventoryItem>> & inventoryItems,const std::string & sensorObjPath)781 inline InventoryItem* findInventoryItemForSensor(
782     const std::shared_ptr<std::vector<InventoryItem>>& inventoryItems,
783     const std::string& sensorObjPath)
784 {
785     for (InventoryItem& inventoryItem : *inventoryItems)
786     {
787         if (inventoryItem.sensors.contains(sensorObjPath))
788         {
789             return &inventoryItem;
790         }
791     }
792     return nullptr;
793 }
794 
795 /**
796  * @brief Finds the inventory item associated with the specified led path.
797  * @param inventoryItems D-Bus inventory items associated with sensors.
798  * @param ledObjPath D-Bus object path of led.
799  * @return Inventory item within vector, or nullptr if no match found.
800  */
findInventoryItemForLed(std::vector<InventoryItem> & inventoryItems,const std::string & ledObjPath)801 inline InventoryItem* findInventoryItemForLed(
802     std::vector<InventoryItem>& inventoryItems, const std::string& ledObjPath)
803 {
804     for (InventoryItem& inventoryItem : inventoryItems)
805     {
806         if (inventoryItem.ledObjectPath == ledObjPath)
807         {
808             return &inventoryItem;
809         }
810     }
811     return nullptr;
812 }
813 
814 /**
815  * @brief Adds inventory item and associated sensor to specified vector.
816  *
817  * Adds a new InventoryItem to the vector if necessary.  Searches for an
818  * existing InventoryItem with the specified object path.  If not found, one is
819  * added to the vector.
820  *
821  * Next, the specified sensor is added to the set of sensors associated with the
822  * InventoryItem.
823  *
824  * @param inventoryItems D-Bus inventory items associated with sensors.
825  * @param invItemObjPath D-Bus object path of inventory item.
826  * @param sensorObjPath D-Bus object path of sensor
827  */
addInventoryItem(const std::shared_ptr<std::vector<InventoryItem>> & inventoryItems,const std::string & invItemObjPath,const std::string & sensorObjPath)828 inline void addInventoryItem(
829     const std::shared_ptr<std::vector<InventoryItem>>& inventoryItems,
830     const std::string& invItemObjPath, const std::string& sensorObjPath)
831 {
832     // Look for inventory item in vector
833     InventoryItem* inventoryItem =
834         findInventoryItem(inventoryItems, invItemObjPath);
835 
836     // If inventory item doesn't exist in vector, add it
837     if (inventoryItem == nullptr)
838     {
839         inventoryItems->emplace_back(invItemObjPath);
840         inventoryItem = &(inventoryItems->back());
841     }
842 
843     // Add sensor to set of sensors associated with inventory item
844     inventoryItem->sensors.emplace(sensorObjPath);
845 }
846 
847 /**
848  * @brief Stores D-Bus data in the specified inventory item.
849  *
850  * Finds D-Bus data in the specified map of interfaces.  Stores the data in the
851  * specified InventoryItem.
852  *
853  * This data is later used to provide sensor property values in the JSON
854  * response.
855  *
856  * @param inventoryItem Inventory item where data will be stored.
857  * @param interfacesDict Map containing D-Bus interfaces and their properties
858  * for the specified inventory item.
859  */
storeInventoryItemData(InventoryItem & inventoryItem,const dbus::utility::DBusInterfacesMap & interfacesDict)860 inline void storeInventoryItemData(
861     InventoryItem& inventoryItem,
862     const dbus::utility::DBusInterfacesMap& interfacesDict)
863 {
864     // Get properties from Inventory.Item interface
865 
866     for (const auto& [interface, values] : interfacesDict)
867     {
868         if (interface == "xyz.openbmc_project.Inventory.Item")
869         {
870             for (const auto& [name, dbusValue] : values)
871             {
872                 if (name == "Present")
873                 {
874                     const bool* value = std::get_if<bool>(&dbusValue);
875                     if (value != nullptr)
876                     {
877                         inventoryItem.isPresent = *value;
878                     }
879                 }
880             }
881         }
882         // Check if Inventory.Item.PowerSupply interface is present
883 
884         if (interface == "xyz.openbmc_project.Inventory.Item.PowerSupply")
885         {
886             inventoryItem.isPowerSupply = true;
887         }
888 
889         // Get properties from Inventory.Decorator.Asset interface
890         if (interface == "xyz.openbmc_project.Inventory.Decorator.Asset")
891         {
892             for (const auto& [name, dbusValue] : values)
893             {
894                 if (name == "Manufacturer")
895                 {
896                     const std::string* value =
897                         std::get_if<std::string>(&dbusValue);
898                     if (value != nullptr)
899                     {
900                         inventoryItem.manufacturer = *value;
901                     }
902                 }
903                 if (name == "Model")
904                 {
905                     const std::string* value =
906                         std::get_if<std::string>(&dbusValue);
907                     if (value != nullptr)
908                     {
909                         inventoryItem.model = *value;
910                     }
911                 }
912                 if (name == "SerialNumber")
913                 {
914                     const std::string* value =
915                         std::get_if<std::string>(&dbusValue);
916                     if (value != nullptr)
917                     {
918                         inventoryItem.serialNumber = *value;
919                     }
920                 }
921                 if (name == "PartNumber")
922                 {
923                     const std::string* value =
924                         std::get_if<std::string>(&dbusValue);
925                     if (value != nullptr)
926                     {
927                         inventoryItem.partNumber = *value;
928                     }
929                 }
930             }
931         }
932 
933         if (interface ==
934             "xyz.openbmc_project.State.Decorator.OperationalStatus")
935         {
936             for (const auto& [name, dbusValue] : values)
937             {
938                 if (name == "Functional")
939                 {
940                     const bool* value = std::get_if<bool>(&dbusValue);
941                     if (value != nullptr)
942                     {
943                         inventoryItem.isFunctional = *value;
944                     }
945                 }
946             }
947         }
948     }
949 }
950 
951 /**
952  * @brief Gets D-Bus data for inventory items associated with sensors.
953  *
954  * Uses the specified connections (services) to obtain D-Bus data for inventory
955  * items associated with sensors.  Stores the resulting data in the
956  * inventoryItems vector.
957  *
958  * This data is later used to provide sensor property values in the JSON
959  * response.
960  *
961  * Finds the inventory item data asynchronously.  Invokes callback when data has
962  * been obtained.
963  *
964  * The callback must have the following signature:
965  *   @code
966  *   callback(void)
967  *   @endcode
968  *
969  * This function is called recursively, obtaining data asynchronously from one
970  * connection in each call.  This ensures the callback is not invoked until the
971  * last asynchronous function has completed.
972  *
973  * @param sensorsAsyncResp Pointer to object holding response data.
974  * @param inventoryItems D-Bus inventory items associated with sensors.
975  * @param invConnections Connections that provide data for the inventory items.
976  * implements ObjectManager.
977  * @param callback Callback to invoke when inventory data has been obtained.
978  * @param invConnectionsIndex Current index in invConnections.  Only specified
979  * in recursive calls to this function.
980  */
981 template <typename Callback>
getInventoryItemsData(const std::shared_ptr<SensorsAsyncResp> & sensorsAsyncResp,const std::shared_ptr<std::vector<InventoryItem>> & inventoryItems,const std::shared_ptr<std::set<std::string>> & invConnections,Callback && callback,size_t invConnectionsIndex=0)982 void getInventoryItemsData(
983     const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp,
984     const std::shared_ptr<std::vector<InventoryItem>>& inventoryItems,
985     const std::shared_ptr<std::set<std::string>>& invConnections,
986     Callback&& callback, size_t invConnectionsIndex = 0)
987 {
988     BMCWEB_LOG_DEBUG("getInventoryItemsData enter");
989 
990     // If no more connections left, call callback
991     if (invConnectionsIndex >= invConnections->size())
992     {
993         callback();
994         BMCWEB_LOG_DEBUG("getInventoryItemsData exit");
995         return;
996     }
997 
998     // Get inventory item data from current connection
999     auto it = invConnections->begin();
1000     std::advance(it, invConnectionsIndex);
1001     if (it != invConnections->end())
1002     {
1003         const std::string& invConnection = *it;
1004 
1005         // Get all object paths and their interfaces for current connection
1006         sdbusplus::message::object_path path("/xyz/openbmc_project/inventory");
1007         dbus::utility::getManagedObjects(
1008             invConnection, path,
1009             [sensorsAsyncResp, inventoryItems, invConnections,
1010              callback = std::forward<Callback>(callback), invConnectionsIndex](
1011                 const boost::system::error_code& ec,
1012                 const dbus::utility::ManagedObjectType& resp) mutable {
1013                 BMCWEB_LOG_DEBUG("getInventoryItemsData respHandler enter");
1014                 if (ec)
1015                 {
1016                     BMCWEB_LOG_ERROR(
1017                         "getInventoryItemsData respHandler DBus error {}", ec);
1018                     messages::internalError(sensorsAsyncResp->asyncResp->res);
1019                     return;
1020                 }
1021 
1022                 // Loop through returned object paths
1023                 for (const auto& objDictEntry : resp)
1024                 {
1025                     const std::string& objPath =
1026                         static_cast<const std::string&>(objDictEntry.first);
1027 
1028                     // If this object path is one of the specified inventory
1029                     // items
1030                     InventoryItem* inventoryItem =
1031                         findInventoryItem(inventoryItems, objPath);
1032                     if (inventoryItem != nullptr)
1033                     {
1034                         // Store inventory data in InventoryItem
1035                         storeInventoryItemData(*inventoryItem,
1036                                                objDictEntry.second);
1037                     }
1038                 }
1039 
1040                 // Recurse to get inventory item data from next connection
1041                 getInventoryItemsData(sensorsAsyncResp, inventoryItems,
1042                                       invConnections, std::move(callback),
1043                                       invConnectionsIndex + 1);
1044 
1045                 BMCWEB_LOG_DEBUG("getInventoryItemsData respHandler exit");
1046             });
1047     }
1048 
1049     BMCWEB_LOG_DEBUG("getInventoryItemsData exit");
1050 }
1051 
1052 /**
1053  * @brief Gets connections that provide D-Bus data for inventory items.
1054  *
1055  * Gets the D-Bus connections (services) that provide data for the inventory
1056  * items that are associated with sensors.
1057  *
1058  * Finds the connections asynchronously.  Invokes callback when information has
1059  * been obtained.
1060  *
1061  * The callback must have the following signature:
1062  *   @code
1063  *   callback(std::shared_ptr<std::set<std::string>> invConnections)
1064  *   @endcode
1065  *
1066  * @param sensorsAsyncResp Pointer to object holding response data.
1067  * @param inventoryItems D-Bus inventory items associated with sensors.
1068  * @param callback Callback to invoke when connections have been obtained.
1069  */
1070 template <typename Callback>
getInventoryItemsConnections(const std::shared_ptr<SensorsAsyncResp> & sensorsAsyncResp,const std::shared_ptr<std::vector<InventoryItem>> & inventoryItems,Callback && callback)1071 void getInventoryItemsConnections(
1072     const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp,
1073     const std::shared_ptr<std::vector<InventoryItem>>& inventoryItems,
1074     Callback&& callback)
1075 {
1076     BMCWEB_LOG_DEBUG("getInventoryItemsConnections enter");
1077 
1078     const std::string path = "/xyz/openbmc_project/inventory";
1079     constexpr std::array<std::string_view, 4> interfaces = {
1080         "xyz.openbmc_project.Inventory.Item",
1081         "xyz.openbmc_project.Inventory.Item.PowerSupply",
1082         "xyz.openbmc_project.Inventory.Decorator.Asset",
1083         "xyz.openbmc_project.State.Decorator.OperationalStatus"};
1084 
1085     // Make call to ObjectMapper to find all inventory items
1086     dbus::utility::getSubTree(
1087         path, 0, interfaces,
1088         [callback = std::forward<Callback>(callback), sensorsAsyncResp,
1089          inventoryItems](
1090             const boost::system::error_code& ec,
1091             const dbus::utility::MapperGetSubTreeResponse& subtree) mutable {
1092             // Response handler for parsing output from GetSubTree
1093             BMCWEB_LOG_DEBUG("getInventoryItemsConnections respHandler enter");
1094             if (ec)
1095             {
1096                 messages::internalError(sensorsAsyncResp->asyncResp->res);
1097                 BMCWEB_LOG_ERROR(
1098                     "getInventoryItemsConnections respHandler DBus error {}",
1099                     ec);
1100                 return;
1101             }
1102 
1103             // Make unique list of connections for desired inventory items
1104             std::shared_ptr<std::set<std::string>> invConnections =
1105                 std::make_shared<std::set<std::string>>();
1106 
1107             // Loop through objects from GetSubTree
1108             for (const std::pair<std::string,
1109                                  std::vector<std::pair<
1110                                      std::string, std::vector<std::string>>>>&
1111                      object : subtree)
1112             {
1113                 // Check if object path is one of the specified inventory items
1114                 const std::string& objPath = object.first;
1115                 if (findInventoryItem(inventoryItems, objPath) != nullptr)
1116                 {
1117                     // Store all connections to inventory item
1118                     for (const std::pair<std::string, std::vector<std::string>>&
1119                              objData : object.second)
1120                     {
1121                         const std::string& invConnection = objData.first;
1122                         invConnections->insert(invConnection);
1123                     }
1124                 }
1125             }
1126 
1127             callback(invConnections);
1128             BMCWEB_LOG_DEBUG("getInventoryItemsConnections respHandler exit");
1129         });
1130     BMCWEB_LOG_DEBUG("getInventoryItemsConnections exit");
1131 }
1132 
1133 /**
1134  * @brief Gets associations from sensors to inventory items.
1135  *
1136  * Looks for ObjectMapper associations from the specified sensors to related
1137  * inventory items. Then finds the associations from those inventory items to
1138  * their LEDs, if any.
1139  *
1140  * Finds the inventory items asynchronously.  Invokes callback when information
1141  * has been obtained.
1142  *
1143  * The callback must have the following signature:
1144  *   @code
1145  *   callback(std::shared_ptr<std::vector<InventoryItem>> inventoryItems)
1146  *   @endcode
1147  *
1148  * @param sensorsAsyncResp Pointer to object holding response data.
1149  * @param sensorNames All sensors within the current chassis.
1150  * implements ObjectManager.
1151  * @param callback Callback to invoke when inventory items have been obtained.
1152  */
1153 template <typename Callback>
getInventoryItemAssociations(const std::shared_ptr<SensorsAsyncResp> & sensorsAsyncResp,const std::shared_ptr<std::set<std::string>> & sensorNames,Callback && callback)1154 void getInventoryItemAssociations(
1155     const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp,
1156     const std::shared_ptr<std::set<std::string>>& sensorNames,
1157     Callback&& callback)
1158 {
1159     BMCWEB_LOG_DEBUG("getInventoryItemAssociations enter");
1160 
1161     // Call GetManagedObjects on the ObjectMapper to get all associations
1162     sdbusplus::message::object_path path("/");
1163     dbus::utility::getManagedObjects(
1164         "xyz.openbmc_project.ObjectMapper", path,
1165         [callback = std::forward<Callback>(callback), sensorsAsyncResp,
1166          sensorNames](const boost::system::error_code& ec,
1167                       const dbus::utility::ManagedObjectType& resp) mutable {
1168             BMCWEB_LOG_DEBUG("getInventoryItemAssociations respHandler enter");
1169             if (ec)
1170             {
1171                 BMCWEB_LOG_ERROR(
1172                     "getInventoryItemAssociations respHandler DBus error {}",
1173                     ec);
1174                 messages::internalError(sensorsAsyncResp->asyncResp->res);
1175                 return;
1176             }
1177 
1178             // Create vector to hold list of inventory items
1179             std::shared_ptr<std::vector<InventoryItem>> inventoryItems =
1180                 std::make_shared<std::vector<InventoryItem>>();
1181 
1182             // Loop through returned object paths
1183             std::string sensorAssocPath;
1184             sensorAssocPath.reserve(128); // avoid memory allocations
1185             for (const auto& objDictEntry : resp)
1186             {
1187                 const std::string& objPath =
1188                     static_cast<const std::string&>(objDictEntry.first);
1189 
1190                 // If path is inventory association for one of the specified
1191                 // sensors
1192                 for (const std::string& sensorName : *sensorNames)
1193                 {
1194                     sensorAssocPath = sensorName;
1195                     sensorAssocPath += "/inventory";
1196                     if (objPath == sensorAssocPath)
1197                     {
1198                         // Get Association interface for object path
1199                         for (const auto& [interface, values] :
1200                              objDictEntry.second)
1201                         {
1202                             if (interface == "xyz.openbmc_project.Association")
1203                             {
1204                                 for (const auto& [valueName, value] : values)
1205                                 {
1206                                     if (valueName == "endpoints")
1207                                     {
1208                                         const std::vector<std::string>*
1209                                             endpoints = std::get_if<
1210                                                 std::vector<std::string>>(
1211                                                 &value);
1212                                         if ((endpoints != nullptr) &&
1213                                             !endpoints->empty())
1214                                         {
1215                                             // Add inventory item to vector
1216                                             const std::string& invItemPath =
1217                                                 endpoints->front();
1218                                             addInventoryItem(inventoryItems,
1219                                                              invItemPath,
1220                                                              sensorName);
1221                                         }
1222                                     }
1223                                 }
1224                             }
1225                         }
1226                         break;
1227                     }
1228                 }
1229             }
1230 
1231             // Now loop through the returned object paths again, this time to
1232             // find the leds associated with the inventory items we just found
1233             std::string inventoryAssocPath;
1234             inventoryAssocPath.reserve(128); // avoid memory allocations
1235             for (const auto& objDictEntry : resp)
1236             {
1237                 const std::string& objPath =
1238                     static_cast<const std::string&>(objDictEntry.first);
1239 
1240                 for (InventoryItem& inventoryItem : *inventoryItems)
1241                 {
1242                     inventoryAssocPath = inventoryItem.objectPath;
1243                     inventoryAssocPath += "/leds";
1244                     if (objPath == inventoryAssocPath)
1245                     {
1246                         for (const auto& [interface, values] :
1247                              objDictEntry.second)
1248                         {
1249                             if (interface == "xyz.openbmc_project.Association")
1250                             {
1251                                 for (const auto& [valueName, value] : values)
1252                                 {
1253                                     if (valueName == "endpoints")
1254                                     {
1255                                         const std::vector<std::string>*
1256                                             endpoints = std::get_if<
1257                                                 std::vector<std::string>>(
1258                                                 &value);
1259                                         if ((endpoints != nullptr) &&
1260                                             !endpoints->empty())
1261                                         {
1262                                             // Add inventory item to vector
1263                                             // Store LED path in inventory item
1264                                             const std::string& ledPath =
1265                                                 endpoints->front();
1266                                             inventoryItem.ledObjectPath =
1267                                                 ledPath;
1268                                         }
1269                                     }
1270                                 }
1271                             }
1272                         }
1273 
1274                         break;
1275                     }
1276                 }
1277             }
1278             callback(inventoryItems);
1279             BMCWEB_LOG_DEBUG("getInventoryItemAssociations respHandler exit");
1280         });
1281 
1282     BMCWEB_LOG_DEBUG("getInventoryItemAssociations exit");
1283 }
1284 
1285 /**
1286  * @brief Gets D-Bus data for inventory item leds associated with sensors.
1287  *
1288  * Uses the specified connections (services) to obtain D-Bus data for inventory
1289  * item leds associated with sensors.  Stores the resulting data in the
1290  * inventoryItems vector.
1291  *
1292  * This data is later used to provide sensor property values in the JSON
1293  * response.
1294  *
1295  * Finds the inventory item led data asynchronously.  Invokes callback when data
1296  * has been obtained.
1297  *
1298  * The callback must have the following signature:
1299  *   @code
1300  *   callback()
1301  *   @endcode
1302  *
1303  * This function is called recursively, obtaining data asynchronously from one
1304  * connection in each call.  This ensures the callback is not invoked until the
1305  * last asynchronous function has completed.
1306  *
1307  * @param sensorsAsyncResp Pointer to object holding response data.
1308  * @param inventoryItems D-Bus inventory items associated with sensors.
1309  * @param ledConnections Connections that provide data for the inventory leds.
1310  * @param callback Callback to invoke when inventory data has been obtained.
1311  * @param ledConnectionsIndex Current index in ledConnections.  Only specified
1312  * in recursive calls to this function.
1313  */
1314 template <typename Callback>
getInventoryLedData(const std::shared_ptr<SensorsAsyncResp> & sensorsAsyncResp,const std::shared_ptr<std::vector<InventoryItem>> & inventoryItems,const std::shared_ptr<std::map<std::string,std::string>> & ledConnections,Callback && callback,size_t ledConnectionsIndex=0)1315 void getInventoryLedData(
1316     const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp,
1317     const std::shared_ptr<std::vector<InventoryItem>>& inventoryItems,
1318     const std::shared_ptr<std::map<std::string, std::string>>& ledConnections,
1319     Callback&& callback, size_t ledConnectionsIndex = 0)
1320 {
1321     BMCWEB_LOG_DEBUG("getInventoryLedData enter");
1322 
1323     // If no more connections left, call callback
1324     if (ledConnectionsIndex >= ledConnections->size())
1325     {
1326         callback();
1327         BMCWEB_LOG_DEBUG("getInventoryLedData exit");
1328         return;
1329     }
1330 
1331     // Get inventory item data from current connection
1332     auto it = ledConnections->begin();
1333     std::advance(it, ledConnectionsIndex);
1334     if (it != ledConnections->end())
1335     {
1336         const std::string& ledPath = (*it).first;
1337         const std::string& ledConnection = (*it).second;
1338         // Response handler for Get State property
1339         auto respHandler =
1340             [sensorsAsyncResp, inventoryItems, ledConnections, ledPath,
1341              callback = std::forward<Callback>(callback),
1342              ledConnectionsIndex](const boost::system::error_code& ec,
1343                                   const std::string& state) mutable {
1344                 BMCWEB_LOG_DEBUG("getInventoryLedData respHandler enter");
1345                 if (ec)
1346                 {
1347                     BMCWEB_LOG_ERROR(
1348                         "getInventoryLedData respHandler DBus error {}", ec);
1349                     messages::internalError(sensorsAsyncResp->asyncResp->res);
1350                     return;
1351                 }
1352 
1353                 BMCWEB_LOG_DEBUG("Led state: {}", state);
1354                 // Find inventory item with this LED object path
1355                 InventoryItem* inventoryItem =
1356                     findInventoryItemForLed(*inventoryItems, ledPath);
1357                 if (inventoryItem != nullptr)
1358                 {
1359                     // Store LED state in InventoryItem
1360                     if (state.ends_with("On"))
1361                     {
1362                         inventoryItem->ledState = sensor_utils::LedState::ON;
1363                     }
1364                     else if (state.ends_with("Blink"))
1365                     {
1366                         inventoryItem->ledState = sensor_utils::LedState::BLINK;
1367                     }
1368                     else if (state.ends_with("Off"))
1369                     {
1370                         inventoryItem->ledState = sensor_utils::LedState::OFF;
1371                     }
1372                     else
1373                     {
1374                         inventoryItem->ledState =
1375                             sensor_utils::LedState::UNKNOWN;
1376                     }
1377                 }
1378 
1379                 // Recurse to get LED data from next connection
1380                 getInventoryLedData(sensorsAsyncResp, inventoryItems,
1381                                     ledConnections, std::move(callback),
1382                                     ledConnectionsIndex + 1);
1383 
1384                 BMCWEB_LOG_DEBUG("getInventoryLedData respHandler exit");
1385             };
1386 
1387         // Get the State property for the current LED
1388         dbus::utility::getProperty<std::string>(
1389             ledConnection, ledPath, "xyz.openbmc_project.Led.Physical", "State",
1390             std::move(respHandler));
1391     }
1392 
1393     BMCWEB_LOG_DEBUG("getInventoryLedData exit");
1394 }
1395 
1396 /**
1397  * @brief Gets LED data for LEDs associated with given inventory items.
1398  *
1399  * Gets the D-Bus connections (services) that provide LED data for the LEDs
1400  * associated with the specified inventory items.  Then gets the LED data from
1401  * each connection and stores it in the inventory item.
1402  *
1403  * This data is later used to provide sensor property values in the JSON
1404  * response.
1405  *
1406  * Finds the LED data asynchronously.  Invokes callback when information has
1407  * been obtained.
1408  *
1409  * The callback must have the following signature:
1410  *   @code
1411  *   callback()
1412  *   @endcode
1413  *
1414  * @param sensorsAsyncResp Pointer to object holding response data.
1415  * @param inventoryItems D-Bus inventory items associated with sensors.
1416  * @param callback Callback to invoke when inventory items have been obtained.
1417  */
1418 template <typename Callback>
getInventoryLeds(const std::shared_ptr<SensorsAsyncResp> & sensorsAsyncResp,const std::shared_ptr<std::vector<InventoryItem>> & inventoryItems,Callback && callback)1419 void getInventoryLeds(
1420     const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp,
1421     const std::shared_ptr<std::vector<InventoryItem>>& inventoryItems,
1422     Callback&& callback)
1423 {
1424     BMCWEB_LOG_DEBUG("getInventoryLeds enter");
1425 
1426     const std::string path = "/xyz/openbmc_project";
1427     constexpr std::array<std::string_view, 1> interfaces = {
1428         "xyz.openbmc_project.Led.Physical"};
1429 
1430     // Make call to ObjectMapper to find all inventory items
1431     dbus::utility::getSubTree(
1432         path, 0, interfaces,
1433         [callback = std::forward<Callback>(callback), sensorsAsyncResp,
1434          inventoryItems](
1435             const boost::system::error_code& ec,
1436             const dbus::utility::MapperGetSubTreeResponse& subtree) mutable {
1437             // Response handler for parsing output from GetSubTree
1438             BMCWEB_LOG_DEBUG("getInventoryLeds respHandler enter");
1439             if (ec)
1440             {
1441                 messages::internalError(sensorsAsyncResp->asyncResp->res);
1442                 BMCWEB_LOG_ERROR("getInventoryLeds respHandler DBus error {}",
1443                                  ec);
1444                 return;
1445             }
1446 
1447             // Build map of LED object paths to connections
1448             std::shared_ptr<std::map<std::string, std::string>> ledConnections =
1449                 std::make_shared<std::map<std::string, std::string>>();
1450 
1451             // Loop through objects from GetSubTree
1452             for (const std::pair<std::string,
1453                                  std::vector<std::pair<
1454                                      std::string, std::vector<std::string>>>>&
1455                      object : subtree)
1456             {
1457                 // Check if object path is LED for one of the specified
1458                 // inventory items
1459                 const std::string& ledPath = object.first;
1460                 if (findInventoryItemForLed(*inventoryItems, ledPath) !=
1461                     nullptr)
1462                 {
1463                     // Add mapping from ledPath to connection
1464                     const std::string& connection =
1465                         object.second.begin()->first;
1466                     (*ledConnections)[ledPath] = connection;
1467                     BMCWEB_LOG_DEBUG("Added mapping {} -> {}", ledPath,
1468                                      connection);
1469                 }
1470             }
1471 
1472             getInventoryLedData(sensorsAsyncResp, inventoryItems,
1473                                 ledConnections, std::move(callback));
1474             BMCWEB_LOG_DEBUG("getInventoryLeds respHandler exit");
1475         });
1476     BMCWEB_LOG_DEBUG("getInventoryLeds exit");
1477 }
1478 
1479 /**
1480  * @brief Gets D-Bus data for Power Supply Attributes such as EfficiencyPercent
1481  *
1482  * Uses the specified connections (services) (currently assumes just one) to
1483  * obtain D-Bus data for Power Supply Attributes. Stores the resulting data in
1484  * the inventoryItems vector. Only stores data in Power Supply inventoryItems.
1485  *
1486  * This data is later used to provide sensor property values in the JSON
1487  * response.
1488  *
1489  * Finds the Power Supply Attributes data asynchronously.  Invokes callback
1490  * when data has been obtained.
1491  *
1492  * The callback must have the following signature:
1493  *   @code
1494  *   callback(std::shared_ptr<std::vector<InventoryItem>> inventoryItems)
1495  *   @endcode
1496  *
1497  * @param sensorsAsyncResp Pointer to object holding response data.
1498  * @param inventoryItems D-Bus inventory items associated with sensors.
1499  * @param psAttributesConnections Connections that provide data for the Power
1500  *        Supply Attributes
1501  * @param callback Callback to invoke when data has been obtained.
1502  */
1503 template <typename Callback>
getPowerSupplyAttributesData(const std::shared_ptr<SensorsAsyncResp> & sensorsAsyncResp,const std::shared_ptr<std::vector<InventoryItem>> & inventoryItems,const std::map<std::string,std::string> & psAttributesConnections,Callback && callback)1504 void getPowerSupplyAttributesData(
1505     const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp,
1506     const std::shared_ptr<std::vector<InventoryItem>>& inventoryItems,
1507     const std::map<std::string, std::string>& psAttributesConnections,
1508     Callback&& callback)
1509 {
1510     BMCWEB_LOG_DEBUG("getPowerSupplyAttributesData enter");
1511 
1512     if (psAttributesConnections.empty())
1513     {
1514         BMCWEB_LOG_DEBUG("Can't find PowerSupplyAttributes, no connections!");
1515         callback(inventoryItems);
1516         return;
1517     }
1518 
1519     // Assuming just one connection (service) for now
1520     auto it = psAttributesConnections.begin();
1521 
1522     const std::string& psAttributesPath = (*it).first;
1523     const std::string& psAttributesConnection = (*it).second;
1524 
1525     // Response handler for Get DeratingFactor property
1526     auto respHandler = [sensorsAsyncResp, inventoryItems,
1527                         callback = std::forward<Callback>(callback)](
1528                            const boost::system::error_code& ec,
1529                            uint32_t value) mutable {
1530         BMCWEB_LOG_DEBUG("getPowerSupplyAttributesData respHandler enter");
1531         if (ec)
1532         {
1533             BMCWEB_LOG_ERROR(
1534                 "getPowerSupplyAttributesData respHandler DBus error {}", ec);
1535             messages::internalError(sensorsAsyncResp->asyncResp->res);
1536             return;
1537         }
1538 
1539         BMCWEB_LOG_DEBUG("PS EfficiencyPercent value: {}", value);
1540         // Store value in Power Supply Inventory Items
1541         for (InventoryItem& inventoryItem : *inventoryItems)
1542         {
1543             if (inventoryItem.isPowerSupply)
1544             {
1545                 inventoryItem.powerSupplyEfficiencyPercent =
1546                     static_cast<int>(value);
1547             }
1548         }
1549 
1550         BMCWEB_LOG_DEBUG("getPowerSupplyAttributesData respHandler exit");
1551         callback(inventoryItems);
1552     };
1553 
1554     // Get the DeratingFactor property for the PowerSupplyAttributes
1555     // Currently only property on the interface/only one we care about
1556     dbus::utility::getProperty<uint32_t>(
1557         psAttributesConnection, psAttributesPath,
1558         "xyz.openbmc_project.Control.PowerSupplyAttributes", "DeratingFactor",
1559         std::move(respHandler));
1560 
1561     BMCWEB_LOG_DEBUG("getPowerSupplyAttributesData exit");
1562 }
1563 
1564 /**
1565  * @brief Gets the Power Supply Attributes such as EfficiencyPercent
1566  *
1567  * Gets the D-Bus connection (service) that provides Power Supply Attributes
1568  * data. Then gets the Power Supply Attributes data from the connection
1569  * (currently just assumes 1 connection) and stores the data in the inventory
1570  * item.
1571  *
1572  * This data is later used to provide sensor property values in the JSON
1573  * response. DeratingFactor on D-Bus is mapped to EfficiencyPercent on Redfish.
1574  *
1575  * Finds the Power Supply Attributes data asynchronously. Invokes callback
1576  * when information has been obtained.
1577  *
1578  * The callback must have the following signature:
1579  *   @code
1580  *   callback(std::shared_ptr<std::vector<InventoryItem>> inventoryItems)
1581  *   @endcode
1582  *
1583  * @param sensorsAsyncResp Pointer to object holding response data.
1584  * @param inventoryItems D-Bus inventory items associated with sensors.
1585  * @param callback Callback to invoke when data has been obtained.
1586  */
1587 template <typename Callback>
getPowerSupplyAttributes(const std::shared_ptr<SensorsAsyncResp> & sensorsAsyncResp,const std::shared_ptr<std::vector<InventoryItem>> & inventoryItems,Callback && callback)1588 void getPowerSupplyAttributes(
1589     const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp,
1590     const std::shared_ptr<std::vector<InventoryItem>>& inventoryItems,
1591     Callback&& callback)
1592 {
1593     BMCWEB_LOG_DEBUG("getPowerSupplyAttributes enter");
1594 
1595     // Only need the power supply attributes when the Power Schema
1596     if (sensorsAsyncResp->chassisSubNode != sensors::powerNodeStr)
1597     {
1598         BMCWEB_LOG_DEBUG("getPowerSupplyAttributes exit since not Power");
1599         callback(inventoryItems);
1600         return;
1601     }
1602 
1603     constexpr std::array<std::string_view, 1> interfaces = {
1604         "xyz.openbmc_project.Control.PowerSupplyAttributes"};
1605 
1606     // Make call to ObjectMapper to find the PowerSupplyAttributes service
1607     dbus::utility::getSubTree(
1608         "/xyz/openbmc_project", 0, interfaces,
1609         [callback = std::forward<Callback>(callback), sensorsAsyncResp,
1610          inventoryItems](
1611             const boost::system::error_code& ec,
1612             const dbus::utility::MapperGetSubTreeResponse& subtree) mutable {
1613             // Response handler for parsing output from GetSubTree
1614             BMCWEB_LOG_DEBUG("getPowerSupplyAttributes respHandler enter");
1615             if (ec)
1616             {
1617                 messages::internalError(sensorsAsyncResp->asyncResp->res);
1618                 BMCWEB_LOG_ERROR(
1619                     "getPowerSupplyAttributes respHandler DBus error {}", ec);
1620                 return;
1621             }
1622             if (subtree.empty())
1623             {
1624                 BMCWEB_LOG_DEBUG("Can't find Power Supply Attributes!");
1625                 callback(inventoryItems);
1626                 return;
1627             }
1628 
1629             // Currently we only support 1 power supply attribute, use this for
1630             // all the power supplies. Build map of object path to connection.
1631             // Assume just 1 connection and 1 path for now.
1632             std::map<std::string, std::string> psAttributesConnections;
1633 
1634             if (subtree[0].first.empty() || subtree[0].second.empty())
1635             {
1636                 BMCWEB_LOG_DEBUG("Power Supply Attributes mapper error!");
1637                 callback(inventoryItems);
1638                 return;
1639             }
1640 
1641             const std::string& psAttributesPath = subtree[0].first;
1642             const std::string& connection = subtree[0].second.begin()->first;
1643 
1644             if (connection.empty())
1645             {
1646                 BMCWEB_LOG_DEBUG("Power Supply Attributes mapper error!");
1647                 callback(inventoryItems);
1648                 return;
1649             }
1650 
1651             psAttributesConnections[psAttributesPath] = connection;
1652             BMCWEB_LOG_DEBUG("Added mapping {} -> {}", psAttributesPath,
1653                              connection);
1654 
1655             getPowerSupplyAttributesData(sensorsAsyncResp, inventoryItems,
1656                                          psAttributesConnections,
1657                                          std::move(callback));
1658             BMCWEB_LOG_DEBUG("getPowerSupplyAttributes respHandler exit");
1659         });
1660     BMCWEB_LOG_DEBUG("getPowerSupplyAttributes exit");
1661 }
1662 
1663 /**
1664  * @brief Gets inventory items associated with sensors.
1665  *
1666  * Finds the inventory items that are associated with the specified sensors.
1667  * Then gets D-Bus data for the inventory items, such as presence and VPD.
1668  *
1669  * This data is later used to provide sensor property values in the JSON
1670  * response.
1671  *
1672  * Finds the inventory items asynchronously.  Invokes callback when the
1673  * inventory items have been obtained.
1674  *
1675  * The callback must have the following signature:
1676  *   @code
1677  *   callback(std::shared_ptr<std::vector<InventoryItem>> inventoryItems)
1678  *   @endcode
1679  *
1680  * @param sensorsAsyncResp Pointer to object holding response data.
1681  * @param sensorNames All sensors within the current chassis.
1682  * implements ObjectManager.
1683  * @param callback Callback to invoke when inventory items have been obtained.
1684  */
1685 template <typename Callback>
getInventoryItems(const std::shared_ptr<SensorsAsyncResp> & sensorsAsyncResp,const std::shared_ptr<std::set<std::string>> & sensorNames,Callback && callback)1686 inline void getInventoryItems(
1687     const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp,
1688     const std::shared_ptr<std::set<std::string>>& sensorNames,
1689     Callback&& callback)
1690 {
1691     BMCWEB_LOG_DEBUG("getInventoryItems enter");
1692     auto getInventoryItemAssociationsCb =
1693         [sensorsAsyncResp, callback = std::forward<Callback>(callback)](
1694             const std::shared_ptr<std::vector<InventoryItem>>&
1695                 inventoryItems) mutable {
1696             BMCWEB_LOG_DEBUG("getInventoryItemAssociationsCb enter");
1697             auto getInventoryItemsConnectionsCb =
1698                 [sensorsAsyncResp, inventoryItems,
1699                  callback = std::forward<Callback>(callback)](
1700                     const std::shared_ptr<std::set<std::string>>&
1701                         invConnections) mutable {
1702                     BMCWEB_LOG_DEBUG("getInventoryItemsConnectionsCb enter");
1703                     auto getInventoryItemsDataCb =
1704                         [sensorsAsyncResp, inventoryItems,
1705                          callback =
1706                              std::forward<Callback>(callback)]() mutable {
1707                             BMCWEB_LOG_DEBUG("getInventoryItemsDataCb enter");
1708 
1709                             auto getInventoryLedsCb =
1710                                 [sensorsAsyncResp, inventoryItems,
1711                                  callback = std::forward<Callback>(
1712                                      callback)]() mutable {
1713                                     BMCWEB_LOG_DEBUG(
1714                                         "getInventoryLedsCb enter");
1715                                     // Find Power Supply Attributes and get the
1716                                     // data
1717                                     getPowerSupplyAttributes(
1718                                         sensorsAsyncResp, inventoryItems,
1719                                         std::move(callback));
1720                                     BMCWEB_LOG_DEBUG("getInventoryLedsCb exit");
1721                                 };
1722 
1723                             // Find led connections and get the data
1724                             getInventoryLeds(sensorsAsyncResp, inventoryItems,
1725                                              std::move(getInventoryLedsCb));
1726                             BMCWEB_LOG_DEBUG("getInventoryItemsDataCb exit");
1727                         };
1728 
1729                     // Get inventory item data from connections
1730                     getInventoryItemsData(sensorsAsyncResp, inventoryItems,
1731                                           invConnections,
1732                                           std::move(getInventoryItemsDataCb));
1733                     BMCWEB_LOG_DEBUG("getInventoryItemsConnectionsCb exit");
1734                 };
1735 
1736             // Get connections that provide inventory item data
1737             getInventoryItemsConnections(
1738                 sensorsAsyncResp, inventoryItems,
1739                 std::move(getInventoryItemsConnectionsCb));
1740             BMCWEB_LOG_DEBUG("getInventoryItemAssociationsCb exit");
1741         };
1742 
1743     // Get associations from sensors to inventory items
1744     getInventoryItemAssociations(sensorsAsyncResp, sensorNames,
1745                                  std::move(getInventoryItemAssociationsCb));
1746     BMCWEB_LOG_DEBUG("getInventoryItems exit");
1747 }
1748 
1749 /**
1750  * @brief Returns JSON PowerSupply object for the specified inventory item.
1751  *
1752  * Searches for a JSON PowerSupply object that matches the specified inventory
1753  * item.  If one is not found, a new PowerSupply object is added to the JSON
1754  * array.
1755  *
1756  * Multiple sensors are often associated with one power supply inventory item.
1757  * As a result, multiple sensor values are stored in one JSON PowerSupply
1758  * object.
1759  *
1760  * @param powerSupplyArray JSON array containing Redfish PowerSupply objects.
1761  * @param inventoryItem Inventory item for the power supply.
1762  * @param chassisId Chassis that contains the power supply.
1763  * @return JSON PowerSupply object for the specified inventory item.
1764  */
getPowerSupply(nlohmann::json & powerSupplyArray,const InventoryItem & inventoryItem,const std::string & chassisId)1765 inline nlohmann::json& getPowerSupply(nlohmann::json& powerSupplyArray,
1766                                       const InventoryItem& inventoryItem,
1767                                       const std::string& chassisId)
1768 {
1769     std::string nameS;
1770     nameS.resize(inventoryItem.name.size());
1771     std::ranges::replace_copy(inventoryItem.name, nameS.begin(), '_', ' ');
1772     // Check if matching PowerSupply object already exists in JSON array
1773     for (nlohmann::json& powerSupply : powerSupplyArray)
1774     {
1775         nlohmann::json::iterator nameIt = powerSupply.find("Name");
1776         if (nameIt == powerSupply.end())
1777         {
1778             continue;
1779         }
1780         const std::string* name = nameIt->get_ptr<std::string*>();
1781         if (name == nullptr)
1782         {
1783             continue;
1784         }
1785         if (nameS == *name)
1786         {
1787             return powerSupply;
1788         }
1789     }
1790 
1791     // Add new PowerSupply object to JSON array
1792     powerSupplyArray.push_back({});
1793     nlohmann::json& powerSupply = powerSupplyArray.back();
1794     boost::urls::url url =
1795         boost::urls::format("/redfish/v1/Chassis/{}/Power", chassisId);
1796     url.set_fragment(("/PowerSupplies"_json_pointer).to_string());
1797     powerSupply["@odata.id"] = std::move(url);
1798     std::string escaped;
1799     escaped.resize(inventoryItem.name.size());
1800     std::ranges::replace_copy(inventoryItem.name, escaped.begin(), '_', ' ');
1801     powerSupply["Name"] = std::move(escaped);
1802     powerSupply["Manufacturer"] = inventoryItem.manufacturer;
1803     powerSupply["Model"] = inventoryItem.model;
1804     powerSupply["PartNumber"] = inventoryItem.partNumber;
1805     powerSupply["SerialNumber"] = inventoryItem.serialNumber;
1806     sensor_utils::setLedState(powerSupply, &inventoryItem);
1807 
1808     if (inventoryItem.powerSupplyEfficiencyPercent >= 0)
1809     {
1810         powerSupply["EfficiencyPercent"] =
1811             inventoryItem.powerSupplyEfficiencyPercent;
1812     }
1813 
1814     powerSupply["Status"]["State"] =
1815         sensor_utils::getState(&inventoryItem, true);
1816     const char* health = inventoryItem.isFunctional ? "OK" : "Critical";
1817     powerSupply["Status"]["Health"] = health;
1818 
1819     return powerSupply;
1820 }
1821 
1822 /**
1823  * @brief Gets the values of the specified sensors.
1824  *
1825  * Stores the results as JSON in the SensorsAsyncResp.
1826  *
1827  * Gets the sensor values asynchronously.  Stores the results later when the
1828  * information has been obtained.
1829  *
1830  * The sensorNames set contains all requested sensors for the current chassis.
1831  *
1832  * To minimize the number of DBus calls, the DBus method
1833  * org.freedesktop.DBus.ObjectManager.GetManagedObjects() is used to get the
1834  * values of all sensors provided by a connection (service).
1835  *
1836  * The connections set contains all the connections that provide sensor values.
1837  *
1838  * The InventoryItem vector contains D-Bus inventory items associated with the
1839  * sensors.  Inventory item data is needed for some Redfish sensor properties.
1840  *
1841  * @param SensorsAsyncResp Pointer to object holding response data.
1842  * @param sensorNames All requested sensors within the current chassis.
1843  * @param connections Connections that provide sensor values.
1844  * implements ObjectManager.
1845  * @param inventoryItems Inventory items associated with the sensors.
1846  */
getSensorData(const std::shared_ptr<SensorsAsyncResp> & sensorsAsyncResp,const std::shared_ptr<std::set<std::string>> & sensorNames,const std::set<std::string> & connections,const std::shared_ptr<std::vector<InventoryItem>> & inventoryItems)1847 inline void getSensorData(
1848     const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp,
1849     const std::shared_ptr<std::set<std::string>>& sensorNames,
1850     const std::set<std::string>& connections,
1851     const std::shared_ptr<std::vector<InventoryItem>>& inventoryItems)
1852 {
1853     BMCWEB_LOG_DEBUG("getSensorData enter");
1854     // Get managed objects from all services exposing sensors
1855     for (const std::string& connection : connections)
1856     {
1857         sdbusplus::message::object_path sensorPath(
1858             "/xyz/openbmc_project/sensors");
1859         dbus::utility::getManagedObjects(
1860             connection, sensorPath,
1861             [sensorsAsyncResp, sensorNames,
1862              inventoryItems](const boost::system::error_code& ec,
1863                              const dbus::utility::ManagedObjectType& resp) {
1864                 BMCWEB_LOG_DEBUG("getManagedObjectsCb enter");
1865                 if (ec)
1866                 {
1867                     BMCWEB_LOG_ERROR("getManagedObjectsCb DBUS error: {}", ec);
1868                     messages::internalError(sensorsAsyncResp->asyncResp->res);
1869                     return;
1870                 }
1871                 auto chassisSubNode = sensor_utils::chassisSubNodeFromString(
1872                     sensorsAsyncResp->chassisSubNode);
1873                 // Go through all objects and update response with sensor data
1874                 for (const auto& objDictEntry : resp)
1875                 {
1876                     const std::string& objPath =
1877                         static_cast<const std::string&>(objDictEntry.first);
1878                     BMCWEB_LOG_DEBUG("getManagedObjectsCb parsing object {}",
1879                                      objPath);
1880 
1881                     std::vector<std::string> split;
1882                     // Reserve space for
1883                     // /xyz/openbmc_project/sensors/<name>/<subname>
1884                     split.reserve(6);
1885                     // NOLINTNEXTLINE
1886                     bmcweb::split(split, objPath, '/');
1887                     if (split.size() < 6)
1888                     {
1889                         BMCWEB_LOG_ERROR("Got path that isn't long enough {}",
1890                                          objPath);
1891                         continue;
1892                     }
1893                     // These indexes aren't intuitive, as split puts an empty
1894                     // string at the beginning
1895                     const std::string& sensorType = split[4];
1896                     const std::string& sensorName = split[5];
1897                     BMCWEB_LOG_DEBUG("sensorName {} sensorType {}", sensorName,
1898                                      sensorType);
1899                     if (sensorNames->find(objPath) == sensorNames->end())
1900                     {
1901                         BMCWEB_LOG_DEBUG("{} not in sensor list ", sensorName);
1902                         continue;
1903                     }
1904 
1905                     // Find inventory item (if any) associated with sensor
1906                     InventoryItem* inventoryItem =
1907                         findInventoryItemForSensor(inventoryItems, objPath);
1908 
1909                     const std::string& sensorSchema =
1910                         sensorsAsyncResp->chassisSubNode;
1911 
1912                     nlohmann::json* sensorJson = nullptr;
1913 
1914                     if (sensorSchema == sensors::sensorsNodeStr &&
1915                         !sensorsAsyncResp->efficientExpand)
1916                     {
1917                         std::string sensorId =
1918                             redfish::sensor_utils::getSensorId(sensorName,
1919                                                                sensorType);
1920 
1921                         sensorsAsyncResp->asyncResp->res
1922                             .jsonValue["@odata.id"] = boost::urls::format(
1923                             "/redfish/v1/Chassis/{}/{}/{}",
1924                             sensorsAsyncResp->chassisId,
1925                             sensorsAsyncResp->chassisSubNode, sensorId);
1926                         sensorJson =
1927                             &(sensorsAsyncResp->asyncResp->res.jsonValue);
1928                     }
1929                     else
1930                     {
1931                         std::string fieldName;
1932                         if (sensorsAsyncResp->efficientExpand)
1933                         {
1934                             fieldName = "Members";
1935                         }
1936                         else if (sensorType == "temperature")
1937                         {
1938                             fieldName = "Temperatures";
1939                         }
1940                         else if (sensorType == "fan" ||
1941                                  sensorType == "fan_tach" ||
1942                                  sensorType == "fan_pwm")
1943                         {
1944                             fieldName = "Fans";
1945                         }
1946                         else if (sensorType == "voltage")
1947                         {
1948                             fieldName = "Voltages";
1949                         }
1950                         else if (sensorType == "power")
1951                         {
1952                             if (sensorName == "total_power")
1953                             {
1954                                 fieldName = "PowerControl";
1955                             }
1956                             else if ((inventoryItem != nullptr) &&
1957                                      (inventoryItem->isPowerSupply))
1958                             {
1959                                 fieldName = "PowerSupplies";
1960                             }
1961                             else
1962                             {
1963                                 // Other power sensors are in SensorCollection
1964                                 continue;
1965                             }
1966                         }
1967                         else
1968                         {
1969                             BMCWEB_LOG_ERROR(
1970                                 "Unsure how to handle sensorType {}",
1971                                 sensorType);
1972                             continue;
1973                         }
1974 
1975                         nlohmann::json& tempArray =
1976                             sensorsAsyncResp->asyncResp->res
1977                                 .jsonValue[fieldName];
1978                         if (fieldName == "PowerControl")
1979                         {
1980                             if (tempArray.empty())
1981                             {
1982                                 // Put multiple "sensors" into a single
1983                                 // PowerControl. Follows MemberId naming and
1984                                 // naming in power.hpp.
1985                                 nlohmann::json::object_t power;
1986                                 boost::urls::url url = boost::urls::format(
1987                                     "/redfish/v1/Chassis/{}/{}",
1988                                     sensorsAsyncResp->chassisId,
1989                                     sensorsAsyncResp->chassisSubNode);
1990                                 url.set_fragment(
1991                                     (""_json_pointer / fieldName / "0")
1992                                         .to_string());
1993                                 power["@odata.id"] = std::move(url);
1994                                 tempArray.emplace_back(std::move(power));
1995                             }
1996                             sensorJson = &(tempArray.back());
1997                         }
1998                         else if (fieldName == "PowerSupplies")
1999                         {
2000                             if (inventoryItem != nullptr)
2001                             {
2002                                 sensorJson = &(getPowerSupply(
2003                                     tempArray, *inventoryItem,
2004                                     sensorsAsyncResp->chassisId));
2005                             }
2006                         }
2007                         else if (fieldName == "Members")
2008                         {
2009                             std::string sensorId =
2010                                 redfish::sensor_utils::getSensorId(sensorName,
2011                                                                    sensorType);
2012 
2013                             nlohmann::json::object_t member;
2014                             member["@odata.id"] = boost::urls::format(
2015                                 "/redfish/v1/Chassis/{}/{}/{}",
2016                                 sensorsAsyncResp->chassisId,
2017                                 sensorsAsyncResp->chassisSubNode, sensorId);
2018                             tempArray.emplace_back(std::move(member));
2019                             sensorJson = &(tempArray.back());
2020                         }
2021                         else
2022                         {
2023                             nlohmann::json::object_t member;
2024                             boost::urls::url url = boost::urls::format(
2025                                 "/redfish/v1/Chassis/{}/{}",
2026                                 sensorsAsyncResp->chassisId,
2027                                 sensorsAsyncResp->chassisSubNode);
2028                             url.set_fragment(
2029                                 (""_json_pointer / fieldName).to_string());
2030                             member["@odata.id"] = std::move(url);
2031                             tempArray.emplace_back(std::move(member));
2032                             sensorJson = &(tempArray.back());
2033                         }
2034                     }
2035 
2036                     if (sensorJson != nullptr)
2037                     {
2038                         objectInterfacesToJson(
2039                             sensorName, sensorType, chassisSubNode,
2040                             objDictEntry.second, *sensorJson, inventoryItem);
2041 
2042                         std::string path = "/xyz/openbmc_project/sensors/";
2043                         path += sensorType;
2044                         path += "/";
2045                         path += sensorName;
2046                         sensorsAsyncResp->addMetadata(*sensorJson, path);
2047                     }
2048                 }
2049                 if (sensorsAsyncResp.use_count() == 1)
2050                 {
2051                     sortJSONResponse(sensorsAsyncResp);
2052                     if (chassisSubNode ==
2053                             sensor_utils::ChassisSubNode::sensorsNode &&
2054                         sensorsAsyncResp->efficientExpand)
2055                     {
2056                         sensorsAsyncResp->asyncResp->res
2057                             .jsonValue["Members@odata.count"] =
2058                             sensorsAsyncResp->asyncResp->res
2059                                 .jsonValue["Members"]
2060                                 .size();
2061                     }
2062                     else if (chassisSubNode ==
2063                              sensor_utils::ChassisSubNode::thermalNode)
2064                     {
2065                         populateFanRedundancy(sensorsAsyncResp);
2066                     }
2067                 }
2068                 BMCWEB_LOG_DEBUG("getManagedObjectsCb exit");
2069             });
2070     }
2071     BMCWEB_LOG_DEBUG("getSensorData exit");
2072 }
2073 
processSensorList(const std::shared_ptr<SensorsAsyncResp> & sensorsAsyncResp,const std::shared_ptr<std::set<std::string>> & sensorNames)2074 inline void processSensorList(
2075     const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp,
2076     const std::shared_ptr<std::set<std::string>>& sensorNames)
2077 {
2078     auto getConnectionCb = [sensorsAsyncResp, sensorNames](
2079                                const std::set<std::string>& connections) {
2080         BMCWEB_LOG_DEBUG("getConnectionCb enter");
2081         auto getInventoryItemsCb =
2082             [sensorsAsyncResp, sensorNames, connections](
2083                 const std::shared_ptr<std::vector<InventoryItem>>&
2084                     inventoryItems) mutable {
2085                 BMCWEB_LOG_DEBUG("getInventoryItemsCb enter");
2086                 // Get sensor data and store results in JSON
2087                 getSensorData(sensorsAsyncResp, sensorNames, connections,
2088                               inventoryItems);
2089                 BMCWEB_LOG_DEBUG("getInventoryItemsCb exit");
2090             };
2091 
2092         // Get inventory items associated with sensors
2093         getInventoryItems(sensorsAsyncResp, sensorNames,
2094                           std::move(getInventoryItemsCb));
2095 
2096         BMCWEB_LOG_DEBUG("getConnectionCb exit");
2097     };
2098 
2099     // Get set of connections that provide sensor values
2100     getConnections(sensorsAsyncResp, sensorNames, std::move(getConnectionCb));
2101 }
2102 
2103 /**
2104  * @brief Entry point for retrieving sensors data related to requested
2105  *        chassis.
2106  * @param SensorsAsyncResp   Pointer to object holding response data
2107  */
getChassisData(const std::shared_ptr<SensorsAsyncResp> & sensorsAsyncResp)2108 inline void getChassisData(
2109     const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp)
2110 {
2111     BMCWEB_LOG_DEBUG("getChassisData enter");
2112     auto getChassisCb =
2113         [sensorsAsyncResp](
2114             const std::shared_ptr<std::set<std::string>>& sensorNames) {
2115             BMCWEB_LOG_DEBUG("getChassisCb enter");
2116             processSensorList(sensorsAsyncResp, sensorNames);
2117             BMCWEB_LOG_DEBUG("getChassisCb exit");
2118         };
2119     // SensorCollection doesn't contain the Redundancy property
2120     if (sensorsAsyncResp->chassisSubNode != sensors::sensorsNodeStr)
2121     {
2122         sensorsAsyncResp->asyncResp->res.jsonValue["Redundancy"] =
2123             nlohmann::json::array();
2124     }
2125     // Get set of sensors in chassis
2126     getChassis(sensorsAsyncResp->asyncResp, sensorsAsyncResp->chassisId,
2127                sensorsAsyncResp->chassisSubNode, sensorsAsyncResp->types,
2128                std::move(getChassisCb));
2129     BMCWEB_LOG_DEBUG("getChassisData exit");
2130 }
2131 
2132 /**
2133  * @brief Find the requested sensorName in the list of all sensors supplied by
2134  * the chassis node
2135  *
2136  * @param sensorName   The sensor name supplied in the PATCH request
2137  * @param sensorsList  The list of sensors managed by the chassis node
2138  * @param sensorsModified  The list of sensors that were found as a result of
2139  *                         repeated calls to this function
2140  */
findSensorNameUsingSensorPath(std::string_view sensorName,const std::set<std::string> & sensorsList,std::set<std::string> & sensorsModified)2141 inline bool findSensorNameUsingSensorPath(
2142     std::string_view sensorName, const std::set<std::string>& sensorsList,
2143     std::set<std::string>& sensorsModified)
2144 {
2145     for (const auto& chassisSensor : sensorsList)
2146     {
2147         sdbusplus::message::object_path path(chassisSensor);
2148         std::string thisSensorName = path.filename();
2149         if (thisSensorName.empty())
2150         {
2151             continue;
2152         }
2153         if (thisSensorName == sensorName)
2154         {
2155             sensorsModified.emplace(chassisSensor);
2156             return true;
2157         }
2158     }
2159     return false;
2160 }
2161 
2162 /**
2163  * @brief Entry point for overriding sensor values of given sensor
2164  *
2165  * @param sensorAsyncResp   response object
2166  * @param allCollections   Collections extract from sensors' request patch info
2167  * @param chassisSubNode   Chassis Node for which the query has to happen
2168  */
setSensorsOverride(const std::shared_ptr<SensorsAsyncResp> & sensorAsyncResp,std::unordered_map<std::string,std::vector<nlohmann::json::object_t>> & allCollections)2169 inline void setSensorsOverride(
2170     const std::shared_ptr<SensorsAsyncResp>& sensorAsyncResp,
2171     std::unordered_map<std::string, std::vector<nlohmann::json::object_t>>&
2172         allCollections)
2173 {
2174     BMCWEB_LOG_INFO("setSensorsOverride for subNode{}",
2175                     sensorAsyncResp->chassisSubNode);
2176 
2177     std::string_view propertyValueName;
2178     std::unordered_map<std::string, std::pair<double, std::string>> overrideMap;
2179     std::string memberId;
2180     double value = 0.0;
2181     for (auto& collectionItems : allCollections)
2182     {
2183         if (collectionItems.first == "Temperatures")
2184         {
2185             propertyValueName = "ReadingCelsius";
2186         }
2187         else if (collectionItems.first == "Fans")
2188         {
2189             propertyValueName = "Reading";
2190         }
2191         else
2192         {
2193             propertyValueName = "ReadingVolts";
2194         }
2195         for (auto& item : collectionItems.second)
2196         {
2197             if (!json_util::readJsonObject(                //
2198                     item, sensorAsyncResp->asyncResp->res, //
2199                     "MemberId", memberId,                  //
2200                     propertyValueName, value               //
2201                     ))
2202             {
2203                 return;
2204             }
2205             overrideMap.emplace(memberId,
2206                                 std::make_pair(value, collectionItems.first));
2207         }
2208     }
2209 
2210     auto getChassisSensorListCb = [sensorAsyncResp, overrideMap,
2211                                    propertyValueNameStr =
2212                                        std::string(propertyValueName)](
2213                                       const std::shared_ptr<
2214                                           std::set<std::string>>& sensorsList) {
2215         // Match sensor names in the PATCH request to those managed by the
2216         // chassis node
2217         const std::shared_ptr<std::set<std::string>> sensorNames =
2218             std::make_shared<std::set<std::string>>();
2219         for (const auto& item : overrideMap)
2220         {
2221             const auto& sensor = item.first;
2222             std::pair<std::string, std::string> sensorNameType =
2223                 redfish::sensor_utils::splitSensorNameAndType(sensor);
2224             if (!findSensorNameUsingSensorPath(sensorNameType.second,
2225                                                *sensorsList, *sensorNames))
2226             {
2227                 BMCWEB_LOG_INFO("Unable to find memberId {}", item.first);
2228                 messages::resourceNotFound(sensorAsyncResp->asyncResp->res,
2229                                            item.second.second, item.first);
2230                 return;
2231             }
2232         }
2233         // Get the connection to which the memberId belongs
2234         auto getObjectsWithConnectionCb = [sensorAsyncResp, overrideMap,
2235                                            propertyValueNameStr](
2236                                               const std::set<
2237                                                   std::string>& /*connections*/,
2238                                               const std::set<std::pair<
2239                                                   std::string, std::string>>&
2240                                                   objectsWithConnection) {
2241             if (objectsWithConnection.size() != overrideMap.size())
2242             {
2243                 BMCWEB_LOG_INFO(
2244                     "Unable to find all objects with proper connection {} requested {}",
2245                     objectsWithConnection.size(), overrideMap.size());
2246                 messages::resourceNotFound(
2247                     sensorAsyncResp->asyncResp->res,
2248                     sensorAsyncResp->chassisSubNode == sensors::thermalNodeStr
2249                         ? "Temperatures"
2250                         : "Voltages",
2251                     "Count");
2252                 return;
2253             }
2254             for (const auto& item : objectsWithConnection)
2255             {
2256                 sdbusplus::message::object_path path(item.first);
2257                 std::string sensorName = path.filename();
2258                 if (sensorName.empty())
2259                 {
2260                     messages::internalError(sensorAsyncResp->asyncResp->res);
2261                     return;
2262                 }
2263                 std::string id = redfish::sensor_utils::getSensorId(
2264                     sensorName, path.parent_path().filename());
2265 
2266                 const auto& iterator = overrideMap.find(id);
2267                 if (iterator == overrideMap.end())
2268                 {
2269                     BMCWEB_LOG_INFO("Unable to find sensor object{}",
2270                                     item.first);
2271                     messages::internalError(sensorAsyncResp->asyncResp->res);
2272                     return;
2273                 }
2274                 setDbusProperty(sensorAsyncResp->asyncResp,
2275                                 propertyValueNameStr, item.second, item.first,
2276                                 "xyz.openbmc_project.Sensor.Value", "Value",
2277                                 iterator->second.first);
2278             }
2279         };
2280         // Get object with connection for the given sensor name
2281         getObjectsWithConnection(sensorAsyncResp, sensorNames,
2282                                  std::move(getObjectsWithConnectionCb));
2283     };
2284     // get full sensor list for the given chassisId and cross verify the sensor.
2285     getChassis(sensorAsyncResp->asyncResp, sensorAsyncResp->chassisId,
2286                sensorAsyncResp->chassisSubNode, sensorAsyncResp->types,
2287                std::move(getChassisSensorListCb));
2288 }
2289 
2290 /**
2291  * @brief Retrieves mapping of Redfish URIs to sensor value property to D-Bus
2292  * path of the sensor.
2293  *
2294  * Function builds valid Redfish response for sensor query of given chassis and
2295  * node. It then builds metadata about Redfish<->D-Bus correlations and provides
2296  * it to caller in a callback.
2297  *
2298  * @param chassis   Chassis for which retrieval should be performed
2299  * @param node  Node (group) of sensors. See sensor_utils::node for supported
2300  * values
2301  * @param mapComplete   Callback to be called with retrieval result
2302  */
2303 template <typename Callback>
retrieveUriToDbusMap(const std::string & chassis,const std::string & node,Callback && mapComplete)2304 inline void retrieveUriToDbusMap(
2305     const std::string& chassis, const std::string& node, Callback&& mapComplete)
2306 {
2307     decltype(sensors::paths)::const_iterator pathIt =
2308         std::find_if(sensors::paths.cbegin(), sensors::paths.cend(),
2309                      [&node](auto&& val) { return val.first == node; });
2310     if (pathIt == sensors::paths.cend())
2311     {
2312         BMCWEB_LOG_ERROR("Wrong node provided : {}", node);
2313         std::map<std::string, std::string> noop;
2314         mapComplete(boost::beast::http::status::bad_request, noop);
2315         return;
2316     }
2317 
2318     auto asyncResp = std::make_shared<bmcweb::AsyncResp>();
2319     auto callback =
2320         [asyncResp, mapCompleteCb = std::forward<Callback>(mapComplete)](
2321             const boost::beast::http::status status,
2322             const std::map<std::string, std::string>& uriToDbus) {
2323             mapCompleteCb(status, uriToDbus);
2324         };
2325 
2326     auto resp = std::make_shared<SensorsAsyncResp>(
2327         asyncResp, chassis, pathIt->second, node, std::move(callback));
2328     getChassisData(resp);
2329 }
2330 
2331 namespace sensors
2332 {
2333 
getChassisCallback(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,std::string_view chassisId,std::string_view chassisSubNode,const std::shared_ptr<std::set<std::string>> & sensorNames)2334 inline void getChassisCallback(
2335     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2336     std::string_view chassisId, std::string_view chassisSubNode,
2337     const std::shared_ptr<std::set<std::string>>& sensorNames)
2338 {
2339     BMCWEB_LOG_DEBUG("getChassisCallback enter ");
2340 
2341     nlohmann::json& entriesArray = asyncResp->res.jsonValue["Members"];
2342     for (const std::string& sensor : *sensorNames)
2343     {
2344         BMCWEB_LOG_DEBUG("Adding sensor: {}", sensor);
2345 
2346         sdbusplus::message::object_path path(sensor);
2347         std::string sensorName = path.filename();
2348         if (sensorName.empty())
2349         {
2350             BMCWEB_LOG_ERROR("Invalid sensor path: {}", sensor);
2351             messages::internalError(asyncResp->res);
2352             return;
2353         }
2354         std::string type = path.parent_path().filename();
2355         std::string id = redfish::sensor_utils::getSensorId(sensorName, type);
2356 
2357         nlohmann::json::object_t member;
2358         member["@odata.id"] = boost::urls::format(
2359             "/redfish/v1/Chassis/{}/{}/{}", chassisId, chassisSubNode, id);
2360 
2361         entriesArray.emplace_back(std::move(member));
2362     }
2363 
2364     asyncResp->res.jsonValue["Members@odata.count"] = entriesArray.size();
2365     BMCWEB_LOG_DEBUG("getChassisCallback exit");
2366 }
2367 
handleSensorCollectionGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & chassisId)2368 inline void handleSensorCollectionGet(
2369     App& app, const crow::Request& req,
2370     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2371     const std::string& chassisId)
2372 {
2373     query_param::QueryCapabilities capabilities = {
2374         .canDelegateExpandLevel = 1,
2375     };
2376     query_param::Query delegatedQuery;
2377     if (!redfish::setUpRedfishRouteWithDelegation(app, req, asyncResp,
2378                                                   delegatedQuery, capabilities))
2379     {
2380         return;
2381     }
2382 
2383     if (delegatedQuery.expandType != query_param::ExpandType::None)
2384     {
2385         // we perform efficient expand.
2386         auto sensorsAsyncResp = std::make_shared<SensorsAsyncResp>(
2387             asyncResp, chassisId, sensors::dbus::sensorPaths,
2388             sensors::sensorsNodeStr,
2389             /*efficientExpand=*/true);
2390         getChassisData(sensorsAsyncResp);
2391 
2392         BMCWEB_LOG_DEBUG(
2393             "SensorCollection doGet exit via efficient expand handler");
2394         return;
2395     }
2396 
2397     // We get all sensors as hyperlinkes in the chassis (this
2398     // implies we reply on the default query parameters handler)
2399     getChassis(asyncResp, chassisId, sensors::sensorsNodeStr, dbus::sensorPaths,
2400                std::bind_front(sensors::getChassisCallback, asyncResp,
2401                                chassisId, sensors::sensorsNodeStr));
2402 }
2403 
getSensorFromDbus(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & sensorPath,const::dbus::utility::MapperGetObject & mapperResponse)2404 inline void getSensorFromDbus(
2405     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2406     const std::string& sensorPath,
2407     const ::dbus::utility::MapperGetObject& mapperResponse)
2408 {
2409     if (mapperResponse.size() != 1)
2410     {
2411         messages::internalError(asyncResp->res);
2412         return;
2413     }
2414     const auto& valueIface = *mapperResponse.begin();
2415     const std::string& connectionName = valueIface.first;
2416     BMCWEB_LOG_DEBUG("Looking up {}", connectionName);
2417     BMCWEB_LOG_DEBUG("Path {}", sensorPath);
2418 
2419     ::dbus::utility::getAllProperties(
2420         *crow::connections::systemBus, connectionName, sensorPath, "",
2421         [asyncResp,
2422          sensorPath](const boost::system::error_code& ec,
2423                      const ::dbus::utility::DBusPropertiesMap& valuesDict) {
2424             if (ec)
2425             {
2426                 messages::internalError(asyncResp->res);
2427                 return;
2428             }
2429             sdbusplus::message::object_path path(sensorPath);
2430             std::string name = path.filename();
2431             path = path.parent_path();
2432             std::string type = path.filename();
2433             sensor_utils::objectPropertiesToJson(
2434                 name, type, sensor_utils::ChassisSubNode::sensorsNode,
2435                 valuesDict, asyncResp->res.jsonValue, nullptr);
2436         });
2437 }
2438 
handleSensorGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & chassisId,const std::string & sensorId)2439 inline void handleSensorGet(App& app, const crow::Request& req,
2440                             const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2441                             const std::string& chassisId,
2442                             const std::string& sensorId)
2443 {
2444     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2445     {
2446         return;
2447     }
2448     std::pair<std::string, std::string> nameType =
2449         redfish::sensor_utils::splitSensorNameAndType(sensorId);
2450     if (nameType.first.empty() || nameType.second.empty())
2451     {
2452         messages::resourceNotFound(asyncResp->res, sensorId, "Sensor");
2453         return;
2454     }
2455 
2456     asyncResp->res.jsonValue["@odata.id"] = boost::urls::format(
2457         "/redfish/v1/Chassis/{}/Sensors/{}", chassisId, sensorId);
2458 
2459     BMCWEB_LOG_DEBUG("Sensor doGet enter");
2460 
2461     constexpr std::array<std::string_view, 1> interfaces = {
2462         "xyz.openbmc_project.Sensor.Value"};
2463     std::string sensorPath = "/xyz/openbmc_project/sensors/" + nameType.first +
2464                              '/' + nameType.second;
2465     // Get a list of all of the sensors that implement Sensor.Value
2466     // and get the path and service name associated with the sensor
2467     ::dbus::utility::getDbusObject(
2468         sensorPath, interfaces,
2469         [asyncResp, sensorId,
2470          sensorPath](const boost::system::error_code& ec,
2471                      const ::dbus::utility::MapperGetObject& subtree) {
2472             BMCWEB_LOG_DEBUG("respHandler1 enter");
2473             if (ec == boost::system::errc::io_error)
2474             {
2475                 BMCWEB_LOG_WARNING("Sensor not found from getSensorPaths");
2476                 messages::resourceNotFound(asyncResp->res, sensorId, "Sensor");
2477                 return;
2478             }
2479             if (ec)
2480             {
2481                 messages::internalError(asyncResp->res);
2482                 BMCWEB_LOG_ERROR(
2483                     "Sensor getSensorPaths resp_handler: Dbus error {}", ec);
2484                 return;
2485             }
2486             getSensorFromDbus(asyncResp, sensorPath, subtree);
2487             BMCWEB_LOG_DEBUG("respHandler1 exit");
2488         });
2489 }
2490 
2491 } // namespace sensors
2492 
requestRoutesSensorCollection(App & app)2493 inline void requestRoutesSensorCollection(App& app)
2494 {
2495     BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/Sensors/")
2496         .privileges(redfish::privileges::getSensorCollection)
2497         .methods(boost::beast::http::verb::get)(
2498             std::bind_front(sensors::handleSensorCollectionGet, std::ref(app)));
2499 }
2500 
requestRoutesSensor(App & app)2501 inline void requestRoutesSensor(App& app)
2502 {
2503     BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/Sensors/<str>/")
2504         .privileges(redfish::privileges::getSensor)
2505         .methods(boost::beast::http::verb::get)(
2506             std::bind_front(sensors::handleSensorGet, std::ref(app)));
2507 }
2508 
2509 } // namespace redfish
2510