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