xref: /openbmc/bmcweb/features/redfish/lib/sensors.hpp (revision 7ab06f49454378a9b5c1934f5e155a4f3c352fdb)
1 /*
2 // Copyright (c) 2018 Intel Corporation
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 //      http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 */
16 #pragma once
17 
18 #include <math.h>
19 
20 #include <boost/algorithm/string/predicate.hpp>
21 #include <boost/algorithm/string/split.hpp>
22 #include <boost/container/flat_map.hpp>
23 #include <boost/range/algorithm/replace_copy_if.hpp>
24 #include <dbus_singleton.hpp>
25 #include <utils/json_utils.hpp>
26 #include <variant>
27 
28 namespace redfish
29 {
30 
31 using GetSubTreeType = std::vector<
32     std::pair<std::string,
33               std::vector<std::pair<std::string, std::vector<std::string>>>>>;
34 
35 using SensorVariant = std::variant<int64_t, double, uint32_t, bool>;
36 
37 using ManagedObjectsVectorType = std::vector<std::pair<
38     sdbusplus::message::object_path,
39     boost::container::flat_map<
40         std::string, boost::container::flat_map<std::string, SensorVariant>>>>;
41 
42 /**
43  * SensorsAsyncResp
44  * Gathers data needed for response processing after async calls are done
45  */
46 class SensorsAsyncResp
47 {
48   public:
49     SensorsAsyncResp(crow::Response& response, const std::string& chassisId,
50                      const std::vector<const char*> types,
51                      const std::string& subNode) :
52         res(response),
53         chassisId(chassisId), types(types), chassisSubNode(subNode)
54     {
55     }
56 
57     ~SensorsAsyncResp()
58     {
59         if (res.result() == boost::beast::http::status::internal_server_error)
60         {
61             // Reset the json object to clear out any data that made it in
62             // before the error happened todo(ed) handle error condition with
63             // proper code
64             res.jsonValue = nlohmann::json::object();
65         }
66         res.end();
67     }
68 
69     crow::Response& res;
70     std::string chassisId{};
71     const std::vector<const char*> types;
72     std::string chassisSubNode{};
73 };
74 
75 /**
76  * @brief Get objects with connection necessary for sensors
77  * @param SensorsAsyncResp Pointer to object holding response data
78  * @param sensorNames Sensors retrieved from chassis
79  * @param callback Callback for processing gathered connections
80  */
81 template <typename Callback>
82 void getObjectsWithConnection(
83     std::shared_ptr<SensorsAsyncResp> SensorsAsyncResp,
84     const std::shared_ptr<boost::container::flat_set<std::string>> sensorNames,
85     Callback&& callback)
86 {
87     BMCWEB_LOG_DEBUG << "getObjectsWithConnection enter";
88     const std::string path = "/xyz/openbmc_project/sensors";
89     const std::array<std::string, 1> interfaces = {
90         "xyz.openbmc_project.Sensor.Value"};
91 
92     // Response handler for parsing objects subtree
93     auto respHandler = [callback{std::move(callback)}, SensorsAsyncResp,
94                         sensorNames](const boost::system::error_code ec,
95                                      const GetSubTreeType& subtree) {
96         BMCWEB_LOG_DEBUG << "getObjectsWithConnection resp_handler enter";
97         if (ec)
98         {
99             messages::internalError(SensorsAsyncResp->res);
100             BMCWEB_LOG_ERROR
101                 << "getObjectsWithConnection resp_handler: Dbus error " << ec;
102             return;
103         }
104 
105         BMCWEB_LOG_DEBUG << "Found " << subtree.size() << " subtrees";
106 
107         // Make unique list of connections only for requested sensor types and
108         // found in the chassis
109         boost::container::flat_set<std::string> connections;
110         std::set<std::pair<std::string, std::string>> objectsWithConnection;
111         // Intrinsic to avoid malloc.  Most systems will have < 8 sensor
112         // producers
113         connections.reserve(8);
114 
115         BMCWEB_LOG_DEBUG << "sensorNames list count: " << sensorNames->size();
116         for (const std::string& tsensor : *sensorNames)
117         {
118             BMCWEB_LOG_DEBUG << "Sensor to find: " << tsensor;
119         }
120 
121         for (const std::pair<
122                  std::string,
123                  std::vector<std::pair<std::string, std::vector<std::string>>>>&
124                  object : subtree)
125         {
126             if (sensorNames->find(object.first) != sensorNames->end())
127             {
128                 for (const std::pair<std::string, std::vector<std::string>>&
129                          objData : object.second)
130                 {
131                     BMCWEB_LOG_DEBUG << "Adding connection: " << objData.first;
132                     connections.insert(objData.first);
133                     objectsWithConnection.insert(
134                         std::make_pair(object.first, objData.first));
135                 }
136             }
137         }
138         BMCWEB_LOG_DEBUG << "Found " << connections.size() << " connections";
139         callback(std::move(connections), std::move(objectsWithConnection));
140         BMCWEB_LOG_DEBUG << "getObjectsWithConnection resp_handler exit";
141     };
142     // Make call to ObjectMapper to find all sensors objects
143     crow::connections::systemBus->async_method_call(
144         std::move(respHandler), "xyz.openbmc_project.ObjectMapper",
145         "/xyz/openbmc_project/object_mapper",
146         "xyz.openbmc_project.ObjectMapper", "GetSubTree", path, 2, interfaces);
147     BMCWEB_LOG_DEBUG << "getObjectsWithConnection exit";
148 }
149 
150 /**
151  * @brief Create connections necessary for sensors
152  * @param SensorsAsyncResp Pointer to object holding response data
153  * @param sensorNames Sensors retrieved from chassis
154  * @param callback Callback for processing gathered connections
155  */
156 template <typename Callback>
157 void getConnections(
158     std::shared_ptr<SensorsAsyncResp> SensorsAsyncResp,
159     const std::shared_ptr<boost::container::flat_set<std::string>> sensorNames,
160     Callback&& callback)
161 {
162     auto objectsWithConnectionCb =
163         [callback](const boost::container::flat_set<std::string>& connections,
164                    const std::set<std::pair<std::string, std::string>>&
165                        objectsWithConnection) {
166             callback(std::move(connections));
167         };
168     getObjectsWithConnection(SensorsAsyncResp, sensorNames,
169                              std::move(objectsWithConnectionCb));
170 }
171 
172 /**
173  * @brief Shrinks the list of sensors for processing
174  * @param SensorsAysncResp  The class holding the Redfish response
175  * @param allSensors  A list of all the sensors associated to the
176  * chassis element (i.e. baseboard, front panel, etc...)
177  * @param activeSensors A list that is a reduction of the incoming
178  * allSensors list.  Eliminate Thermal sensors when a Power request is
179  * made, and eliminate Power sensors when a Thermal request is made.
180  */
181 void reduceSensorList(
182     std::shared_ptr<SensorsAsyncResp> SensorsAsyncResp,
183     const std::vector<std::string>* allSensors,
184     std::shared_ptr<boost::container::flat_set<std::string>> activeSensors)
185 {
186     if (SensorsAsyncResp == nullptr)
187     {
188         return;
189     }
190     if ((allSensors == nullptr) || (activeSensors == nullptr))
191     {
192         messages::resourceNotFound(
193             SensorsAsyncResp->res, SensorsAsyncResp->chassisSubNode,
194             SensorsAsyncResp->chassisSubNode == "Thermal" ? "Temperatures"
195                                                           : "Voltages");
196 
197         return;
198     }
199     if (allSensors->empty())
200     {
201         // Nothing to do, the activeSensors object is also empty
202         return;
203     }
204 
205     for (const char* type : SensorsAsyncResp->types)
206     {
207         for (const std::string& sensor : *allSensors)
208         {
209             if (boost::starts_with(sensor, type))
210             {
211                 activeSensors->emplace(sensor);
212             }
213         }
214     }
215 }
216 
217 /**
218  * @brief Retrieves requested chassis sensors and redundancy data from DBus .
219  * @param SensorsAsyncResp   Pointer to object holding response data
220  * @param callback  Callback for next step in gathered sensor processing
221  */
222 template <typename Callback>
223 void getChassis(std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp,
224                 Callback&& callback)
225 {
226     BMCWEB_LOG_DEBUG << "getChassis enter";
227     const std::array<const char*, 3> interfaces = {
228         "xyz.openbmc_project.Inventory.Item.Board",
229         "xyz.openbmc_project.Inventory.Item.Chassis",
230         "xyz.openbmc_project.Inventory.Item.PowerSupply"};
231     auto respHandler = [callback{std::move(callback)}, sensorsAsyncResp](
232                            const boost::system::error_code ec,
233                            const std::vector<std::string>& chassisPaths) {
234         BMCWEB_LOG_DEBUG << "getChassis respHandler enter";
235         if (ec)
236         {
237             BMCWEB_LOG_ERROR << "getChassis respHandler DBUS error: " << ec;
238             messages::internalError(sensorsAsyncResp->res);
239             return;
240         }
241 
242         const std::string* chassisPath = nullptr;
243         std::string chassisName;
244         for (const std::string& chassis : chassisPaths)
245         {
246             std::size_t lastPos = chassis.rfind("/");
247             if (lastPos == std::string::npos)
248             {
249                 BMCWEB_LOG_ERROR << "Failed to find '/' in " << chassis;
250                 continue;
251             }
252             chassisName = chassis.substr(lastPos + 1);
253             if (chassisName == sensorsAsyncResp->chassisId)
254             {
255                 chassisPath = &chassis;
256                 break;
257             }
258         }
259         if (chassisPath == nullptr)
260         {
261             messages::resourceNotFound(sensorsAsyncResp->res, "Chassis",
262                                        sensorsAsyncResp->chassisId);
263             return;
264         }
265 
266         const std::string& chassisSubNode = sensorsAsyncResp->chassisSubNode;
267         if (chassisSubNode == "Power")
268         {
269             sensorsAsyncResp->res.jsonValue["@odata.type"] =
270                 "#Power.v1_5_2.Power";
271         }
272         else if (chassisSubNode == "Thermal")
273         {
274             sensorsAsyncResp->res.jsonValue["@odata.type"] =
275                 "#Thermal.v1_4_0.Thermal";
276             sensorsAsyncResp->res.jsonValue["Fans"] = nlohmann::json::array();
277             sensorsAsyncResp->res.jsonValue["Temperatures"] =
278                 nlohmann::json::array();
279         }
280         sensorsAsyncResp->res.jsonValue["@odata.id"] =
281             "/redfish/v1/Chassis/" + sensorsAsyncResp->chassisId + "/" +
282             chassisSubNode;
283 
284         sensorsAsyncResp->res.jsonValue["@odata.context"] =
285             "/redfish/v1/$metadata#" + chassisSubNode + "." + chassisSubNode;
286         sensorsAsyncResp->res.jsonValue["Id"] = chassisSubNode;
287         sensorsAsyncResp->res.jsonValue["Name"] = chassisSubNode;
288 
289         // Get the list of all sensors for this Chassis element
290         std::string sensorPath = *chassisPath + "/all_sensors";
291         crow::connections::systemBus->async_method_call(
292             [sensorsAsyncResp, callback{std::move(callback)}](
293                 const boost::system::error_code ec,
294                 const std::variant<std::vector<std::string>>&
295                     variantEndpoints) {
296                 if (ec)
297                 {
298                     if (ec.value() != EBADR)
299                     {
300                         messages::internalError(sensorsAsyncResp->res);
301                         return;
302                     }
303                 }
304                 const std::vector<std::string>* nodeSensorList =
305                     std::get_if<std::vector<std::string>>(&(variantEndpoints));
306                 if (nodeSensorList == nullptr)
307                 {
308                     messages::resourceNotFound(
309                         sensorsAsyncResp->res, sensorsAsyncResp->chassisSubNode,
310                         sensorsAsyncResp->chassisSubNode == "Thermal"
311                             ? "Temperatures"
312                             : "Voltages");
313                     return;
314                 }
315                 const std::shared_ptr<boost::container::flat_set<std::string>>
316                     culledSensorList = std::make_shared<
317                         boost::container::flat_set<std::string>>();
318                 reduceSensorList(sensorsAsyncResp, nodeSensorList,
319                                  culledSensorList);
320                 callback(culledSensorList);
321             },
322             "xyz.openbmc_project.ObjectMapper", sensorPath,
323             "org.freedesktop.DBus.Properties", "Get",
324             "xyz.openbmc_project.Association", "endpoints");
325     };
326 
327     // Get the Chassis Collection
328     crow::connections::systemBus->async_method_call(
329         respHandler, "xyz.openbmc_project.ObjectMapper",
330         "/xyz/openbmc_project/object_mapper",
331         "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths",
332         "/xyz/openbmc_project/inventory", int32_t(0), interfaces);
333     BMCWEB_LOG_DEBUG << "getChassis exit";
334 }
335 
336 /**
337  * @brief Finds all DBus object paths that implement ObjectManager.
338  *
339  * Creates a mapping from the associated connection name to the object path.
340  *
341  * Finds the object paths asynchronously.  Invokes callback when information has
342  * been obtained.
343  *
344  * The callback must have the following signature:
345  *   @code
346  *   callback(std::shared_ptr<boost::container::flat_map<std::string,
347  *                std::string>> objectMgrPaths)
348  *   @endcode
349  *
350  * @param sensorsAsyncResp Pointer to object holding response data.
351  * @param callback Callback to invoke when object paths obtained.
352  */
353 template <typename Callback>
354 void getObjectManagerPaths(std::shared_ptr<SensorsAsyncResp> SensorsAsyncResp,
355                            Callback&& callback)
356 {
357     BMCWEB_LOG_DEBUG << "getObjectManagerPaths enter";
358     const std::array<std::string, 1> interfaces = {
359         "org.freedesktop.DBus.ObjectManager"};
360 
361     // Response handler for GetSubTree DBus method
362     auto respHandler = [callback{std::move(callback)},
363                         SensorsAsyncResp](const boost::system::error_code ec,
364                                           const GetSubTreeType& subtree) {
365         BMCWEB_LOG_DEBUG << "getObjectManagerPaths respHandler enter";
366         if (ec)
367         {
368             messages::internalError(SensorsAsyncResp->res);
369             BMCWEB_LOG_ERROR << "getObjectManagerPaths respHandler: DBus error "
370                              << ec;
371             return;
372         }
373 
374         // Loop over returned object paths
375         std::shared_ptr<boost::container::flat_map<std::string, std::string>>
376             objectMgrPaths = std::make_shared<
377                 boost::container::flat_map<std::string, std::string>>();
378         for (const std::pair<
379                  std::string,
380                  std::vector<std::pair<std::string, std::vector<std::string>>>>&
381                  object : subtree)
382         {
383             // Loop over connections for current object path
384             const std::string& objectPath = object.first;
385             for (const std::pair<std::string, std::vector<std::string>>&
386                      objData : object.second)
387             {
388                 // Add mapping from connection to object path
389                 const std::string& connection = objData.first;
390                 (*objectMgrPaths)[connection] = objectPath;
391                 BMCWEB_LOG_DEBUG << "Added mapping " << connection << " -> "
392                                  << objectPath;
393             }
394         }
395         callback(objectMgrPaths);
396         BMCWEB_LOG_DEBUG << "getObjectManagerPaths respHandler exit";
397     };
398 
399     // Query mapper for all DBus object paths that implement ObjectManager
400     crow::connections::systemBus->async_method_call(
401         std::move(respHandler), "xyz.openbmc_project.ObjectMapper",
402         "/xyz/openbmc_project/object_mapper",
403         "xyz.openbmc_project.ObjectMapper", "GetSubTree", "/", int32_t(0),
404         interfaces);
405     BMCWEB_LOG_DEBUG << "getObjectManagerPaths exit";
406 }
407 
408 /**
409  * @brief Retrieves the health from a sensor .
410  * @param interfacesDict   Map of all sensor interfaces
411  */
412 
413 static std::string getHealth(
414     const boost::container::flat_map<
415         std::string, boost::container::flat_map<std::string, SensorVariant>>&
416         interfacesDict)
417 {
418     auto criticalThresholdIt =
419         interfacesDict.find("xyz.openbmc_project.Sensor.Threshold.Critical");
420     if (criticalThresholdIt != interfacesDict.end())
421     {
422         auto thresholdHighIt =
423             criticalThresholdIt->second.find("CriticalAlarmHigh");
424         auto thresholdLowIt =
425             criticalThresholdIt->second.find("CriticalAlarmLow");
426         if (thresholdHighIt != criticalThresholdIt->second.end())
427         {
428             const bool* asserted = std::get_if<bool>(&thresholdHighIt->second);
429             if (asserted == nullptr)
430             {
431                 BMCWEB_LOG_ERROR << "Illegal sensor threshold";
432             }
433             else if (*asserted)
434             {
435                 return "Critical";
436             }
437         }
438         if (thresholdLowIt != criticalThresholdIt->second.end())
439         {
440             const bool* asserted = std::get_if<bool>(&thresholdLowIt->second);
441             if (asserted == nullptr)
442             {
443                 BMCWEB_LOG_ERROR << "Illegal sensor threshold";
444             }
445             else if (*asserted)
446             {
447                 return "Critical";
448             }
449         }
450     }
451 
452     auto warningThresholdIt =
453         interfacesDict.find("xyz.openbmc_project.Sensor.Threshold.Warning");
454     if (warningThresholdIt != interfacesDict.end())
455     {
456         auto thresholdHighIt =
457             warningThresholdIt->second.find("WarningAlarmHigh");
458         auto thresholdLowIt =
459             warningThresholdIt->second.find("WarningAlarmLow");
460         if (thresholdHighIt != warningThresholdIt->second.end())
461         {
462             const bool* asserted = std::get_if<bool>(&thresholdHighIt->second);
463             if (asserted == nullptr)
464             {
465                 BMCWEB_LOG_ERROR << "Illegal sensor threshold";
466             }
467             else if (*asserted)
468             {
469                 return "Warning";
470             }
471         }
472         if (thresholdLowIt != warningThresholdIt->second.end())
473         {
474             const bool* asserted = std::get_if<bool>(&thresholdLowIt->second);
475             if (asserted == nullptr)
476             {
477                 BMCWEB_LOG_ERROR << "Illegal sensor threshold";
478             }
479             else if (*asserted)
480             {
481                 return "Warning";
482             }
483         }
484     }
485     return "OK";
486 }
487 
488 /**
489  * @brief Builds a json sensor representation of a sensor.
490  * @param sensorName  The name of the sensor to be built
491  * @param sensorType  The type (temperature, fan_tach, etc) of the sensor to
492  * build
493  * @param interfacesDict  A dictionary of the interfaces and properties of said
494  * interfaces to be built from
495  * @param sensor_json  The json object to fill
496  */
497 void objectInterfacesToJson(
498     const std::string& sensorName, const std::string& sensorType,
499     const boost::container::flat_map<
500         std::string, boost::container::flat_map<std::string, SensorVariant>>&
501         interfacesDict,
502     nlohmann::json& sensor_json)
503 {
504     // We need a value interface before we can do anything with it
505     auto valueIt = interfacesDict.find("xyz.openbmc_project.Sensor.Value");
506     if (valueIt == interfacesDict.end())
507     {
508         BMCWEB_LOG_ERROR << "Sensor doesn't have a value interface";
509         return;
510     }
511 
512     // Assume values exist as is (10^0 == 1) if no scale exists
513     int64_t scaleMultiplier = 0;
514 
515     auto scaleIt = valueIt->second.find("Scale");
516     // If a scale exists, pull value as int64, and use the scaling.
517     if (scaleIt != valueIt->second.end())
518     {
519         const int64_t* int64Value = std::get_if<int64_t>(&scaleIt->second);
520         if (int64Value != nullptr)
521         {
522             scaleMultiplier = *int64Value;
523         }
524     }
525 
526     sensor_json["MemberId"] = sensorName;
527     sensor_json["Name"] = boost::replace_all_copy(sensorName, "_", " ");
528 
529     sensor_json["Status"]["State"] = "Enabled";
530     sensor_json["Status"]["Health"] = getHealth(interfacesDict);
531 
532     // Parameter to set to override the type we get from dbus, and force it to
533     // int, regardless of what is available.  This is used for schemas like fan,
534     // that require integers, not floats.
535     bool forceToInt = false;
536 
537     const char* unit = "Reading";
538     if (sensorType == "temperature")
539     {
540         unit = "ReadingCelsius";
541         sensor_json["@odata.type"] = "#Thermal.v1_3_0.Temperature";
542         // TODO(ed) Documentation says that path should be type fan_tach,
543         // implementation seems to implement fan
544     }
545     else if (sensorType == "fan" || sensorType == "fan_tach")
546     {
547         unit = "Reading";
548         sensor_json["ReadingUnits"] = "RPM";
549         sensor_json["@odata.type"] = "#Thermal.v1_3_0.Fan";
550         forceToInt = true;
551     }
552     else if (sensorType == "fan_pwm")
553     {
554         unit = "Reading";
555         sensor_json["ReadingUnits"] = "Percent";
556         sensor_json["@odata.type"] = "#Thermal.v1_3_0.Fan";
557         forceToInt = true;
558     }
559     else if (sensorType == "voltage")
560     {
561         unit = "ReadingVolts";
562         sensor_json["@odata.type"] = "#Power.v1_0_0.Voltage";
563     }
564     else if (sensorType == "power")
565     {
566         std::string sensorNameLower =
567             boost::algorithm::to_lower_copy(sensorName);
568 
569         if (!sensorName.compare("total_power"))
570         {
571             sensor_json["@odata.type"] = "#Power.v1_0_0.PowerControl";
572             // Put multiple "sensors" into a single PowerControl, so have
573             // generic names for MemberId and Name. Follows Redfish mockup.
574             sensor_json["MemberId"] = "0";
575             sensor_json["Name"] = "Chassis Power Control";
576             unit = "PowerConsumedWatts";
577         }
578         else if (sensorNameLower.find("input") != std::string::npos)
579         {
580             unit = "PowerInputWatts";
581         }
582         else
583         {
584             unit = "PowerOutputWatts";
585         }
586     }
587     else
588     {
589         BMCWEB_LOG_ERROR << "Redfish cannot map object type for " << sensorName;
590         return;
591     }
592     // Map of dbus interface name, dbus property name and redfish property_name
593     std::vector<std::tuple<const char*, const char*, const char*>> properties;
594     properties.reserve(7);
595 
596     properties.emplace_back("xyz.openbmc_project.Sensor.Value", "Value", unit);
597 
598     // If sensor type doesn't map to Redfish PowerSupply, add threshold props
599     if ((sensorType != "current") && (sensorType != "power"))
600     {
601         properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Warning",
602                                 "WarningHigh", "UpperThresholdNonCritical");
603         properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Warning",
604                                 "WarningLow", "LowerThresholdNonCritical");
605         properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Critical",
606                                 "CriticalHigh", "UpperThresholdCritical");
607         properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Critical",
608                                 "CriticalLow", "LowerThresholdCritical");
609     }
610 
611     // TODO Need to get UpperThresholdFatal and LowerThresholdFatal
612 
613     if (sensorType == "temperature")
614     {
615         properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MinValue",
616                                 "MinReadingRangeTemp");
617         properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MaxValue",
618                                 "MaxReadingRangeTemp");
619     }
620     else if ((sensorType != "current") && (sensorType != "power"))
621     {
622         // Sensor type doesn't map to Redfish PowerSupply; add min/max props
623         properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MinValue",
624                                 "MinReadingRange");
625         properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MaxValue",
626                                 "MaxReadingRange");
627     }
628 
629     for (const std::tuple<const char*, const char*, const char*>& p :
630          properties)
631     {
632         auto interfaceProperties = interfacesDict.find(std::get<0>(p));
633         if (interfaceProperties != interfacesDict.end())
634         {
635             auto valueIt = interfaceProperties->second.find(std::get<1>(p));
636             if (valueIt != interfaceProperties->second.end())
637             {
638                 const SensorVariant& valueVariant = valueIt->second;
639                 nlohmann::json& valueIt = sensor_json[std::get<2>(p)];
640                 // Attempt to pull the int64 directly
641                 const int64_t* int64Value = std::get_if<int64_t>(&valueVariant);
642 
643                 const double* doubleValue = std::get_if<double>(&valueVariant);
644                 const uint32_t* uValue = std::get_if<uint32_t>(&valueVariant);
645                 double temp = 0.0;
646                 if (int64Value != nullptr)
647                 {
648                     temp = *int64Value;
649                 }
650                 else if (doubleValue != nullptr)
651                 {
652                     temp = *doubleValue;
653                 }
654                 else if (uValue != nullptr)
655                 {
656                     temp = *uValue;
657                 }
658                 else
659                 {
660                     BMCWEB_LOG_ERROR
661                         << "Got value interface that wasn't int or double";
662                     continue;
663                 }
664                 temp = temp * std::pow(10, scaleMultiplier);
665                 if (forceToInt)
666                 {
667                     valueIt = static_cast<int64_t>(temp);
668                 }
669                 else
670                 {
671                     valueIt = temp;
672                 }
673             }
674         }
675     }
676     BMCWEB_LOG_DEBUG << "Added sensor " << sensorName;
677 }
678 
679 static void
680     populateFanRedundancy(std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp)
681 {
682     crow::connections::systemBus->async_method_call(
683         [sensorsAsyncResp](const boost::system::error_code ec,
684                            const GetSubTreeType& resp) {
685             if (ec)
686             {
687                 return; // don't have to have this interface
688             }
689             for (const std::pair<std::string,
690                                  std::vector<std::pair<
691                                      std::string, std::vector<std::string>>>>&
692                      pathPair : resp)
693             {
694                 const std::string& path = pathPair.first;
695                 const std::vector<
696                     std::pair<std::string, std::vector<std::string>>>& objDict =
697                     pathPair.second;
698                 if (objDict.empty())
699                 {
700                     continue; // this should be impossible
701                 }
702 
703                 const std::string& owner = objDict.begin()->first;
704                 crow::connections::systemBus->async_method_call(
705                     [path, owner,
706                      sensorsAsyncResp](const boost::system::error_code ec,
707                                        std::variant<std::vector<std::string>>
708                                            variantEndpoints) {
709                         if (ec)
710                         {
711                             return; // if they don't have an association we
712                                     // can't tell what chassis is
713                         }
714                         // verify part of the right chassis
715                         auto endpoints = std::get_if<std::vector<std::string>>(
716                             &variantEndpoints);
717 
718                         if (endpoints == nullptr)
719                         {
720                             BMCWEB_LOG_ERROR << "Invalid association interface";
721                             messages::internalError(sensorsAsyncResp->res);
722                             return;
723                         }
724 
725                         auto found = std::find_if(
726                             endpoints->begin(), endpoints->end(),
727                             [sensorsAsyncResp](const std::string& entry) {
728                                 return entry.find(
729                                            sensorsAsyncResp->chassisId) !=
730                                        std::string::npos;
731                             });
732 
733                         if (found == endpoints->end())
734                         {
735                             return;
736                         }
737                         crow::connections::systemBus->async_method_call(
738                             [path, sensorsAsyncResp](
739                                 const boost::system::error_code ec,
740                                 const boost::container::flat_map<
741                                     std::string,
742                                     std::variant<uint8_t,
743                                                  std::vector<std::string>,
744                                                  std::string>>& ret) {
745                                 if (ec)
746                                 {
747                                     return; // don't have to have this
748                                             // interface
749                                 }
750                                 auto findFailures = ret.find("AllowedFailures");
751                                 auto findCollection = ret.find("Collection");
752                                 auto findStatus = ret.find("Status");
753 
754                                 if (findFailures == ret.end() ||
755                                     findCollection == ret.end() ||
756                                     findStatus == ret.end())
757                                 {
758                                     BMCWEB_LOG_ERROR
759                                         << "Invalid redundancy interface";
760                                     messages::internalError(
761                                         sensorsAsyncResp->res);
762                                     return;
763                                 }
764 
765                                 auto allowedFailures = std::get_if<uint8_t>(
766                                     &(findFailures->second));
767                                 auto collection =
768                                     std::get_if<std::vector<std::string>>(
769                                         &(findCollection->second));
770                                 auto status = std::get_if<std::string>(
771                                     &(findStatus->second));
772 
773                                 if (allowedFailures == nullptr ||
774                                     collection == nullptr || status == nullptr)
775                                 {
776 
777                                     BMCWEB_LOG_ERROR
778                                         << "Invalid redundancy interface "
779                                            "types";
780                                     messages::internalError(
781                                         sensorsAsyncResp->res);
782                                     return;
783                                 }
784                                 size_t lastSlash = path.rfind("/");
785                                 if (lastSlash == std::string::npos)
786                                 {
787                                     // this should be impossible
788                                     messages::internalError(
789                                         sensorsAsyncResp->res);
790                                     return;
791                                 }
792                                 std::string name = path.substr(lastSlash + 1);
793                                 std::replace(name.begin(), name.end(), '_',
794                                              ' ');
795 
796                                 std::string health;
797 
798                                 if (boost::ends_with(*status, "Full"))
799                                 {
800                                     health = "OK";
801                                 }
802                                 else if (boost::ends_with(*status, "Degraded"))
803                                 {
804                                     health = "Warning";
805                                 }
806                                 else
807                                 {
808                                     health = "Critical";
809                                 }
810                                 std::vector<nlohmann::json> redfishCollection;
811                                 const auto& fanRedfish =
812                                     sensorsAsyncResp->res.jsonValue["Fans"];
813                                 for (const std::string& item : *collection)
814                                 {
815                                     lastSlash = item.rfind("/");
816                                     // make a copy as collection is const
817                                     std::string itemName =
818                                         item.substr(lastSlash + 1);
819                                     /*
820                                     todo(ed): merge patch that fixes the names
821                                     std::replace(itemName.begin(),
822                                                  itemName.end(), '_', ' ');*/
823                                     auto schemaItem = std::find_if(
824                                         fanRedfish.begin(), fanRedfish.end(),
825                                         [itemName](const nlohmann::json& fan) {
826                                             return fan["MemberId"] == itemName;
827                                         });
828                                     if (schemaItem != fanRedfish.end())
829                                     {
830                                         redfishCollection.push_back(
831                                             {{"@odata.id",
832                                               (*schemaItem)["@odata.id"]}});
833                                     }
834                                     else
835                                     {
836                                         BMCWEB_LOG_ERROR
837                                             << "failed to find fan in schema";
838                                         messages::internalError(
839                                             sensorsAsyncResp->res);
840                                         return;
841                                     }
842                                 }
843 
844                                 auto& resp = sensorsAsyncResp->res
845                                                  .jsonValue["Redundancy"];
846                                 resp.push_back(
847                                     {{"@odata.id",
848                                       "/refish/v1/Chassis/" +
849                                           sensorsAsyncResp->chassisId + "/" +
850                                           sensorsAsyncResp->chassisSubNode +
851                                           "#/Redundancy/" +
852                                           std::to_string(resp.size())},
853                                      {"@odata.type",
854                                       "#Redundancy.v1_3_2.Redundancy"},
855                                      {"MinNumNeeded",
856                                       collection->size() - *allowedFailures},
857                                      {"MemberId", name},
858                                      {"Mode", "N+m"},
859                                      {"Name", name},
860                                      {"RedundancySet", redfishCollection},
861                                      {"Status",
862                                       {{"Health", health},
863                                        {"State", "Enabled"}}}});
864                             },
865                             owner, path, "org.freedesktop.DBus.Properties",
866                             "GetAll",
867                             "xyz.openbmc_project.Control.FanRedundancy");
868                     },
869                     "xyz.openbmc_project.ObjectMapper", path + "/chassis",
870                     "org.freedesktop.DBus.Properties", "Get",
871                     "xyz.openbmc_project.Association", "endpoints");
872             }
873         },
874         "xyz.openbmc_project.ObjectMapper",
875         "/xyz/openbmc_project/object_mapper",
876         "xyz.openbmc_project.ObjectMapper", "GetSubTree",
877         "/xyz/openbmc_project/control", 2,
878         std::array<const char*, 1>{
879             "xyz.openbmc_project.Control.FanRedundancy"});
880 }
881 
882 void sortJSONResponse(std::shared_ptr<SensorsAsyncResp> SensorsAsyncResp)
883 {
884     nlohmann::json& response = SensorsAsyncResp->res.jsonValue;
885     std::array<std::string, 2> sensorHeaders{"Temperatures", "Fans"};
886     if (SensorsAsyncResp->chassisSubNode == "Power")
887     {
888         sensorHeaders = {"Voltages", "PowerSupplies"};
889     }
890     for (const std::string& sensorGroup : sensorHeaders)
891     {
892         nlohmann::json::iterator entry = response.find(sensorGroup);
893         if (entry != response.end())
894         {
895             std::sort(entry->begin(), entry->end(),
896                       [](nlohmann::json& c1, nlohmann::json& c2) {
897                           return c1["Name"] < c2["Name"];
898                       });
899 
900             // add the index counts to the end of each entry
901             size_t count = 0;
902             for (nlohmann::json& sensorJson : *entry)
903             {
904                 nlohmann::json::iterator odata = sensorJson.find("@odata.id");
905                 if (odata == sensorJson.end())
906                 {
907                     continue;
908                 }
909                 std::string* value = odata->get_ptr<std::string*>();
910                 if (value != nullptr)
911                 {
912                     *value += std::to_string(count);
913                     count++;
914                 }
915             }
916         }
917     }
918 }
919 
920 /**
921  * @brief Finds the JSON object for the specified sensor.
922  *
923  * Searches the JSON response in sensorsAsyncResp for an object corresponding to
924  * the specified sensor.
925  *
926  * @param sensorsAsyncResp Pointer to object holding response data.
927  * @param sensorName DBus object path to the sensor.
928  * @return Pointer to JSON object, or nullptr if object not found.
929  */
930 static nlohmann::json*
931     findSensorJson(std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp,
932                    const std::string& sensorName)
933 {
934     // Get base name of sensor
935     std::size_t lastSlash = sensorName.rfind('/');
936     if (lastSlash != std::string::npos)
937     {
938         std::string baseSensorName = sensorName.substr(lastSlash + 1);
939 
940         // Loop through JSON sensor groups that could contain sensor
941         nlohmann::json& response = sensorsAsyncResp->res.jsonValue;
942         std::array<std::string, 4> sensorGroups{"Temperatures", "Fans",
943                                                 "Voltages", "PowerSupplies"};
944         for (const std::string& sensorGroup : sensorGroups)
945         {
946             nlohmann::json::iterator groupIt = response.find(sensorGroup);
947             if (groupIt != response.end())
948             {
949                 // Loop through sensors in current group
950                 for (nlohmann::json& sensorJson : *groupIt)
951                 {
952                     // Check if this is the sensor we are looking for
953                     nlohmann::json::iterator memberIdIt =
954                         sensorJson.find("MemberId");
955                     if (memberIdIt != sensorJson.end())
956                     {
957                         std::string* memberId =
958                             memberIdIt->get_ptr<std::string*>();
959                         if ((memberId != nullptr) &&
960                             (*memberId == baseSensorName))
961                         {
962                             return &sensorJson;
963                         }
964                     }
965                 }
966             }
967         }
968     }
969 
970     // Unable to find JSON object for specified sensor
971     return nullptr;
972 }
973 
974 /**
975  * @brief Updates sensor status in JSON response based on inventory item status.
976  *
977  * Updates the status of the specified sensor based on the status of a related
978  * inventory item.
979  *
980  * Modifies the Redfish Status property in the JSON response if the inventory
981  * item indicates the hardware is not present or not functional.
982  *
983  * The D-Bus Present and Functional properties are typically on the inventory
984  * item rather than the sensor.
985  *
986  * @param sensorsAsyncResp Pointer to object holding response data.
987  * @param sensorName DBus object path to the sensor.
988  * @param interfacesDict Map containing the interfaces and properties of the
989  * inventory item associated with this sensor.
990  */
991 static void updateSensorStatus(
992     std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp,
993     const std::string& sensorName,
994     const boost::container::flat_map<
995         std::string, boost::container::flat_map<std::string, SensorVariant>>&
996         interfacesDict)
997 {
998     // Find the JSON object in the response for this sensor
999     nlohmann::json* sensorJson = findSensorJson(sensorsAsyncResp, sensorName);
1000     if (sensorJson != nullptr)
1001     {
1002         // Get Inventory.Item.Present property of inventory item
1003         auto itemIt = interfacesDict.find("xyz.openbmc_project.Inventory.Item");
1004         if (itemIt != interfacesDict.end())
1005         {
1006             auto presentIt = itemIt->second.find("Present");
1007             if (presentIt != itemIt->second.end())
1008             {
1009                 const bool* present = std::get_if<bool>(&presentIt->second);
1010                 if ((present != nullptr) && (*present == false))
1011                 {
1012                     // Inventory item is not present; update sensor State
1013                     (*sensorJson)["Status"]["State"] = "Absent";
1014                 }
1015             }
1016         }
1017 
1018         // Get OperationalStatus.Functional property of inventory item
1019         auto opStatusIt = interfacesDict.find(
1020             "xyz.openbmc_project.State.Decorator.OperationalStatus");
1021         if (opStatusIt != interfacesDict.end())
1022         {
1023             auto functionalIt = opStatusIt->second.find("Functional");
1024             if (functionalIt != opStatusIt->second.end())
1025             {
1026                 const bool* functional =
1027                     std::get_if<bool>(&functionalIt->second);
1028                 if ((functional != nullptr) && (*functional == false))
1029                 {
1030                     // Inventory item is not functional; update sensor Health
1031                     (*sensorJson)["Status"]["Health"] = "Critical";
1032                 }
1033             }
1034         }
1035     }
1036 }
1037 
1038 /**
1039  * @brief Gets status of inventory items associated with sensors.
1040  *
1041  * Gets the D-Bus status properties for the inventory items associated with
1042  * sensors.
1043  *
1044  * Updates the Redfish sensors status in the JSON response, if needed, based on
1045  * the inventory items status.
1046  *
1047  * @param sensorsAsyncResp Pointer to object holding response data.
1048  * @param sensorToInvMap Mappings from sensor object path to the associated
1049  * inventory object path.
1050  * @param invConnections Connections that provide the status
1051  * interfaces/properties for the inventory items.
1052  * @param objectMgrPaths Mappings from connection name to DBus object path that
1053  * implements ObjectManager.
1054  */
1055 static void getInventoryItemsStatus(
1056     std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp,
1057     std::shared_ptr<boost::container::flat_map<std::string, std::string>>
1058         sensorToInvMap,
1059     std::shared_ptr<boost::container::flat_set<std::string>> invConnections,
1060     std::shared_ptr<boost::container::flat_map<std::string, std::string>>
1061         objectMgrPaths)
1062 {
1063     BMCWEB_LOG_DEBUG << "getInventoryItemsStatus enter";
1064 
1065     // Loop through all connections providing inventory item status
1066     for (const std::string& invConnection : *invConnections)
1067     {
1068         // Response handler for GetManagedObjects
1069         auto respHandler = [sensorsAsyncResp,
1070                             sensorToInvMap](const boost::system::error_code ec,
1071                                             ManagedObjectsVectorType& resp) {
1072             BMCWEB_LOG_DEBUG << "getInventoryItemsStatus respHandler enter";
1073             if (ec)
1074             {
1075                 BMCWEB_LOG_ERROR
1076                     << "getInventoryItemsStatus respHandler DBus error " << ec;
1077                 messages::internalError(sensorsAsyncResp->res);
1078                 return;
1079             }
1080 
1081             // Loop through returned object paths
1082             for (const auto& objDictEntry : resp)
1083             {
1084                 const std::string& objPath =
1085                     static_cast<const std::string&>(objDictEntry.first);
1086 
1087                 // Find all sensors associated with this inventory item
1088                 for (const std::pair<std::string, std::string>& pair :
1089                      *sensorToInvMap)
1090                 {
1091                     if (pair.second == objPath)
1092                     {
1093                         // Update sensor status based on inventory item status
1094                         updateSensorStatus(sensorsAsyncResp, pair.first,
1095                                            objDictEntry.second);
1096                     }
1097                 }
1098             }
1099 
1100             BMCWEB_LOG_DEBUG << "getInventoryItemsStatus respHandler exit";
1101         };
1102 
1103         // Find DBus object path that implements ObjectManager for the current
1104         // connection.  If no mapping found, default to "/".
1105         auto iter = objectMgrPaths->find(invConnection);
1106         const std::string& objectMgrPath =
1107             (iter != objectMgrPaths->end()) ? iter->second : "/";
1108         BMCWEB_LOG_DEBUG << "ObjectManager path for " << invConnection << " is "
1109                          << objectMgrPath;
1110 
1111         // Get all object paths and their interfaces for current connection
1112         crow::connections::systemBus->async_method_call(
1113             std::move(respHandler), invConnection, objectMgrPath,
1114             "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
1115     }
1116 
1117     BMCWEB_LOG_DEBUG << "getInventoryItemsStatus exit";
1118 }
1119 
1120 /**
1121  * @brief Gets connections that provide status information on inventory items.
1122  *
1123  * Gets the D-Bus connections (services) that provide the interfaces and
1124  * properties containing status information for the inventory items.
1125  *
1126  * Finds the connections asynchronously.  Invokes callback when information has
1127  * been obtained.
1128  *
1129  * The callback must have the following signature:
1130  *   @code
1131  *   callback(std::shared_ptr<boost::container::flat_set<std::string>>
1132  *            invConnections)
1133  *   @endcode
1134  *
1135  * @param sensorsAsyncResp Pointer to object holding response data.
1136  * @param sensorToInvMap Mappings from sensor object path to the associated
1137  * inventory object path.
1138  * @param callback Callback to invoke when connections have been obtained.
1139  */
1140 template <typename Callback>
1141 static void getInventoryItemsConnections(
1142     std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp,
1143     std::shared_ptr<boost::container::flat_map<std::string, std::string>>
1144         sensorToInvMap,
1145     Callback&& callback)
1146 {
1147     BMCWEB_LOG_DEBUG << "getInventoryItemsConnections enter";
1148 
1149     const std::string path = "/xyz/openbmc_project/inventory";
1150     const std::array<std::string, 2> interfaces = {
1151         "xyz.openbmc_project.Inventory.Item",
1152         "xyz.openbmc_project.State.Decorator.OperationalStatus"};
1153 
1154     // Response handler for parsing output from GetSubTree
1155     auto respHandler = [callback{std::move(callback)}, sensorsAsyncResp,
1156                         sensorToInvMap](const boost::system::error_code ec,
1157                                         const GetSubTreeType& subtree) {
1158         BMCWEB_LOG_DEBUG << "getInventoryItemsConnections respHandler enter";
1159         if (ec)
1160         {
1161             messages::internalError(sensorsAsyncResp->res);
1162             BMCWEB_LOG_ERROR
1163                 << "getInventoryItemsConnections respHandler DBus error " << ec;
1164             return;
1165         }
1166 
1167         // Make unique list of connections for desired inventory items
1168         std::shared_ptr<boost::container::flat_set<std::string>>
1169             invConnections =
1170                 std::make_shared<boost::container::flat_set<std::string>>();
1171         invConnections->reserve(8);
1172 
1173         // Loop through objects from GetSubTree
1174         for (const std::pair<
1175                  std::string,
1176                  std::vector<std::pair<std::string, std::vector<std::string>>>>&
1177                  object : subtree)
1178         {
1179             // Look for inventory item object path in the sensor->inventory map
1180             const std::string& objPath = object.first;
1181             for (const std::pair<std::string, std::string>& pair :
1182                  *sensorToInvMap)
1183             {
1184                 if (pair.second == objPath)
1185                 {
1186                     // Store all connections to inventory item
1187                     for (const std::pair<std::string, std::vector<std::string>>&
1188                              objData : object.second)
1189                     {
1190                         const std::string& invConnection = objData.first;
1191                         invConnections->insert(invConnection);
1192                     }
1193                     break;
1194                 }
1195             }
1196         }
1197         callback(invConnections);
1198         BMCWEB_LOG_DEBUG << "getInventoryItemsConnections respHandler exit";
1199     };
1200 
1201     // Make call to ObjectMapper to find all inventory items
1202     crow::connections::systemBus->async_method_call(
1203         std::move(respHandler), "xyz.openbmc_project.ObjectMapper",
1204         "/xyz/openbmc_project/object_mapper",
1205         "xyz.openbmc_project.ObjectMapper", "GetSubTree", path, 0, interfaces);
1206     BMCWEB_LOG_DEBUG << "getInventoryItemsConnections exit";
1207 }
1208 
1209 /**
1210  * @brief Gets inventory items associated with the specified sensors.
1211  *
1212  * Looks for ObjectMapper associations from the specified sensors to related
1213  * inventory items.  Builds map where key is sensor object path and value is
1214  * inventory item object path.
1215  *
1216  * Finds the inventory items asynchronously.  Invokes callback when information
1217  * has been obtained.
1218  *
1219  * The callback must have the following signature:
1220  *   @code
1221  *   callback(std::shared_ptr<boost::container::flat_map<
1222                   std::string, std::string>> sensorToInvMap)
1223  *   @endcode
1224  *
1225  * @param sensorsAsyncResp Pointer to object holding response data.
1226  * @param sensorNames All sensors within the current chassis.
1227  * @param objectMgrPaths Mappings from connection name to DBus object path that
1228  * implements ObjectManager.
1229  * @param callback Callback to invoke when inventory items have been obtained.
1230  */
1231 template <typename Callback>
1232 static void getInventoryItems(
1233     std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp,
1234     const std::shared_ptr<boost::container::flat_set<std::string>> sensorNames,
1235     std::shared_ptr<boost::container::flat_map<std::string, std::string>>
1236         objectMgrPaths,
1237     Callback&& callback)
1238 {
1239     BMCWEB_LOG_DEBUG << "getInventoryItems enter";
1240 
1241     // Response handler for GetManagedObjects
1242     auto respHandler = [callback{std::move(callback)}, sensorsAsyncResp,
1243                         sensorNames](const boost::system::error_code ec,
1244                                      dbus::utility::ManagedObjectType& resp) {
1245         BMCWEB_LOG_DEBUG << "getInventoryItems respHandler enter";
1246         if (ec)
1247         {
1248             BMCWEB_LOG_ERROR << "getInventoryItems respHandler DBus error "
1249                              << ec;
1250             messages::internalError(sensorsAsyncResp->res);
1251             return;
1252         }
1253 
1254         // Loop through returned object paths
1255         std::shared_ptr<boost::container::flat_map<std::string, std::string>>
1256             sensorToInvMap = std::make_shared<
1257                 boost::container::flat_map<std::string, std::string>>();
1258         std::string sensorAssocPath;
1259         sensorAssocPath.reserve(128); // avoid memory allocations
1260         for (const auto& objDictEntry : resp)
1261         {
1262             const std::string& objPath =
1263                 static_cast<const std::string&>(objDictEntry.first);
1264             const boost::container::flat_map<
1265                 std::string, boost::container::flat_map<
1266                                  std::string, dbus::utility::DbusVariantType>>&
1267                 interfacesDict = objDictEntry.second;
1268 
1269             // If path is inventory association for one of the specified sensors
1270             for (const std::string& sensorName : *sensorNames)
1271             {
1272                 sensorAssocPath = sensorName;
1273                 sensorAssocPath += "/inventory";
1274                 if (objPath == sensorAssocPath)
1275                 {
1276                     // Get Association interface for object path
1277                     auto assocIt =
1278                         interfacesDict.find("xyz.openbmc_project.Association");
1279                     if (assocIt != interfacesDict.end())
1280                     {
1281                         // Get inventory item from end point
1282                         auto endpointsIt = assocIt->second.find("endpoints");
1283                         if (endpointsIt != assocIt->second.end())
1284                         {
1285                             const std::vector<std::string>* endpoints =
1286                                 std::get_if<std::vector<std::string>>(
1287                                     &endpointsIt->second);
1288                             if ((endpoints != nullptr) && !endpoints->empty())
1289                             {
1290                                 // Store sensor -> inventory item mapping
1291                                 const std::string& invItem = endpoints->front();
1292                                 (*sensorToInvMap)[sensorName] = invItem;
1293                             }
1294                         }
1295                     }
1296                     break;
1297                 }
1298             }
1299         }
1300 
1301         // Call callback if at least one inventory item was found
1302         if (!sensorToInvMap->empty())
1303         {
1304             callback(sensorToInvMap);
1305         }
1306         BMCWEB_LOG_DEBUG << "getInventoryItems respHandler exit";
1307     };
1308 
1309     // Find DBus object path that implements ObjectManager for ObjectMapper
1310     std::string connection = "xyz.openbmc_project.ObjectMapper";
1311     auto iter = objectMgrPaths->find(connection);
1312     const std::string& objectMgrPath =
1313         (iter != objectMgrPaths->end()) ? iter->second : "/";
1314     BMCWEB_LOG_DEBUG << "ObjectManager path for " << connection << " is "
1315                      << objectMgrPath;
1316 
1317     // Call GetManagedObjects on the ObjectMapper to get all associations
1318     crow::connections::systemBus->async_method_call(
1319         std::move(respHandler), connection, objectMgrPath,
1320         "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
1321 
1322     BMCWEB_LOG_DEBUG << "getInventoryItems exit";
1323 }
1324 
1325 /**
1326  * @brief Checks the status of inventory items associated with sensors.
1327  *
1328  * Finds the inventory items that are associated with the specified sensors.
1329  * Gets the status of those inventory items.
1330  *
1331  * If the inventory items are not present or functional, the sensor status is
1332  * updated in the JSON response.
1333  *
1334  * In D-Bus, the hardware present and functional properties are typically on the
1335  * inventory item rather than the sensor.
1336  *
1337  * @param sensorsAsyncResp Pointer to object holding response data.
1338  * @param sensorNames All sensors within the current chassis.
1339  * @param objectMgrPaths Mappings from connection name to DBus object path that
1340  * implements ObjectManager.
1341  */
1342 static void checkInventoryItemsStatus(
1343     std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp,
1344     const std::shared_ptr<boost::container::flat_set<std::string>> sensorNames,
1345     std::shared_ptr<boost::container::flat_map<std::string, std::string>>
1346         objectMgrPaths)
1347 {
1348     BMCWEB_LOG_DEBUG << "checkInventoryItemsStatus enter";
1349     auto getInventoryItemsCb =
1350         [sensorsAsyncResp,
1351          objectMgrPaths](std::shared_ptr<
1352                          boost::container::flat_map<std::string, std::string>>
1353                              sensorToInvMap) {
1354             BMCWEB_LOG_DEBUG << "getInventoryItemsCb enter";
1355             auto getInventoryItemsConnectionsCb =
1356                 [sensorsAsyncResp, sensorToInvMap, objectMgrPaths](
1357                     std::shared_ptr<boost::container::flat_set<std::string>>
1358                         invConnections) {
1359                     BMCWEB_LOG_DEBUG << "getInventoryItemsConnectionsCb enter";
1360 
1361                     // Get status of inventory items and update sensors
1362                     getInventoryItemsStatus(sensorsAsyncResp, sensorToInvMap,
1363                                             invConnections, objectMgrPaths);
1364 
1365                     BMCWEB_LOG_DEBUG << "getInventoryItemsConnectionsCb exit";
1366                 };
1367 
1368             // Get connections that provide status of inventory items
1369             getInventoryItemsConnections(
1370                 sensorsAsyncResp, sensorToInvMap,
1371                 std::move(getInventoryItemsConnectionsCb));
1372             BMCWEB_LOG_DEBUG << "getInventoryItemsCb exit";
1373         };
1374 
1375     // Get inventory items that are associated with specified sensors
1376     getInventoryItems(sensorsAsyncResp, sensorNames, objectMgrPaths,
1377                       std::move(getInventoryItemsCb));
1378     BMCWEB_LOG_DEBUG << "checkInventoryItemsStatus exit";
1379 }
1380 
1381 /**
1382  * @brief Gets the values of the specified sensors.
1383  *
1384  * Stores the results as JSON in the SensorsAsyncResp.
1385  *
1386  * Gets the sensor values asynchronously.  Stores the results later when the
1387  * information has been obtained.
1388  *
1389  * The sensorNames set contains all sensors for the current chassis.
1390  * SensorsAsyncResp contains the requested sensor types.  Only sensors of a
1391  * requested type are included in the JSON output.
1392  *
1393  * To minimize the number of DBus calls, the DBus method
1394  * org.freedesktop.DBus.ObjectManager.GetManagedObjects() is used to get the
1395  * values of all sensors provided by a connection (service).
1396  *
1397  * The connections set contains all the connections that provide sensor values.
1398  *
1399  * The objectMgrPaths map contains mappings from a connection name to the
1400  * corresponding DBus object path that implements ObjectManager.
1401  *
1402  * @param SensorsAsyncResp Pointer to object holding response data.
1403  * @param sensorNames All sensors within the current chassis.
1404  * @param connections Connections that provide sensor values.
1405  * @param objectMgrPaths Mappings from connection name to DBus object path that
1406  * implements ObjectManager.
1407  */
1408 void getSensorData(
1409     std::shared_ptr<SensorsAsyncResp> SensorsAsyncResp,
1410     const std::shared_ptr<boost::container::flat_set<std::string>> sensorNames,
1411     const boost::container::flat_set<std::string>& connections,
1412     std::shared_ptr<boost::container::flat_map<std::string, std::string>>
1413         objectMgrPaths)
1414 {
1415     BMCWEB_LOG_DEBUG << "getSensorData enter";
1416     // Get managed objects from all services exposing sensors
1417     for (const std::string& connection : connections)
1418     {
1419         // Response handler to process managed objects
1420         auto getManagedObjectsCb = [SensorsAsyncResp, sensorNames,
1421                                     objectMgrPaths](
1422                                        const boost::system::error_code ec,
1423                                        ManagedObjectsVectorType& resp) {
1424             BMCWEB_LOG_DEBUG << "getManagedObjectsCb enter";
1425             if (ec)
1426             {
1427                 BMCWEB_LOG_ERROR << "getManagedObjectsCb DBUS error: " << ec;
1428                 messages::internalError(SensorsAsyncResp->res);
1429                 return;
1430             }
1431             // Go through all objects and update response with sensor data
1432             for (const auto& objDictEntry : resp)
1433             {
1434                 const std::string& objPath =
1435                     static_cast<const std::string&>(objDictEntry.first);
1436                 BMCWEB_LOG_DEBUG << "getManagedObjectsCb parsing object "
1437                                  << objPath;
1438 
1439                 std::vector<std::string> split;
1440                 // Reserve space for
1441                 // /xyz/openbmc_project/sensors/<name>/<subname>
1442                 split.reserve(6);
1443                 boost::algorithm::split(split, objPath, boost::is_any_of("/"));
1444                 if (split.size() < 6)
1445                 {
1446                     BMCWEB_LOG_ERROR << "Got path that isn't long enough "
1447                                      << objPath;
1448                     continue;
1449                 }
1450                 // These indexes aren't intuitive, as boost::split puts an empty
1451                 // string at the beginning
1452                 const std::string& sensorType = split[4];
1453                 const std::string& sensorName = split[5];
1454                 BMCWEB_LOG_DEBUG << "sensorName " << sensorName
1455                                  << " sensorType " << sensorType;
1456                 if (sensorNames->find(objPath) == sensorNames->end())
1457                 {
1458                     BMCWEB_LOG_ERROR << sensorName << " not in sensor list ";
1459                     continue;
1460                 }
1461 
1462                 const char* fieldName = nullptr;
1463                 if (sensorType == "temperature")
1464                 {
1465                     fieldName = "Temperatures";
1466                 }
1467                 else if (sensorType == "fan" || sensorType == "fan_tach" ||
1468                          sensorType == "fan_pwm")
1469                 {
1470                     fieldName = "Fans";
1471                 }
1472                 else if (sensorType == "voltage")
1473                 {
1474                     fieldName = "Voltages";
1475                 }
1476                 else if (sensorType == "current")
1477                 {
1478                     fieldName = "PowerSupplies";
1479                 }
1480                 else if (sensorType == "power")
1481                 {
1482                     if (!sensorName.compare("total_power"))
1483                     {
1484                         fieldName = "PowerControl";
1485                     }
1486                     else
1487                     {
1488                         fieldName = "PowerSupplies";
1489                     }
1490                 }
1491                 else
1492                 {
1493                     BMCWEB_LOG_ERROR << "Unsure how to handle sensorType "
1494                                      << sensorType;
1495                     continue;
1496                 }
1497 
1498                 nlohmann::json& tempArray =
1499                     SensorsAsyncResp->res.jsonValue[fieldName];
1500 
1501                 if ((fieldName == "PowerSupplies" ||
1502                      fieldName == "PowerControl") &&
1503                     !tempArray.empty())
1504                 {
1505                     // For power supplies and power control put multiple
1506                     // "sensors" into a single power supply or power control
1507                     // entry, so only create the first one
1508                 }
1509                 else if (fieldName == "PowerControl")
1510                 {
1511                     // Put multiple "sensors" into a single PowerControl.
1512                     // Follows MemberId naming and naming in power.hpp.
1513                     tempArray.push_back(
1514                         {{"@odata.id", "/redfish/v1/Chassis/" +
1515                                            SensorsAsyncResp->chassisId + "/" +
1516                                            SensorsAsyncResp->chassisSubNode +
1517                                            "#/" + fieldName + "/0"}});
1518                 }
1519                 else
1520                 {
1521                     tempArray.push_back(
1522                         {{"@odata.id", "/redfish/v1/Chassis/" +
1523                                            SensorsAsyncResp->chassisId + "/" +
1524                                            SensorsAsyncResp->chassisSubNode +
1525                                            "#/" + fieldName + "/"}});
1526                 }
1527                 nlohmann::json& sensorJson = tempArray.back();
1528 
1529                 objectInterfacesToJson(sensorName, sensorType,
1530                                        objDictEntry.second, sensorJson);
1531             }
1532             if (SensorsAsyncResp.use_count() == 1)
1533             {
1534                 sortJSONResponse(SensorsAsyncResp);
1535                 checkInventoryItemsStatus(SensorsAsyncResp, sensorNames,
1536                                           objectMgrPaths);
1537                 if (SensorsAsyncResp->chassisSubNode == "Thermal")
1538                 {
1539                     populateFanRedundancy(SensorsAsyncResp);
1540                 }
1541             }
1542             BMCWEB_LOG_DEBUG << "getManagedObjectsCb exit";
1543         };
1544 
1545         // Find DBus object path that implements ObjectManager for the current
1546         // connection.  If no mapping found, default to "/".
1547         auto iter = objectMgrPaths->find(connection);
1548         const std::string& objectMgrPath =
1549             (iter != objectMgrPaths->end()) ? iter->second : "/";
1550         BMCWEB_LOG_DEBUG << "ObjectManager path for " << connection << " is "
1551                          << objectMgrPath;
1552 
1553         crow::connections::systemBus->async_method_call(
1554             getManagedObjectsCb, connection, objectMgrPath,
1555             "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
1556     };
1557     BMCWEB_LOG_DEBUG << "getSensorData exit";
1558 }
1559 
1560 /**
1561  * @brief Entry point for retrieving sensors data related to requested
1562  *        chassis.
1563  * @param SensorsAsyncResp   Pointer to object holding response data
1564  */
1565 void getChassisData(std::shared_ptr<SensorsAsyncResp> SensorsAsyncResp)
1566 {
1567     BMCWEB_LOG_DEBUG << "getChassisData enter";
1568     auto getChassisCb =
1569         [SensorsAsyncResp](
1570             std::shared_ptr<boost::container::flat_set<std::string>>
1571                 sensorNames) {
1572             BMCWEB_LOG_DEBUG << "getChassisCb enter";
1573             auto getConnectionCb = [SensorsAsyncResp, sensorNames](
1574                                        const boost::container::flat_set<
1575                                            std::string>& connections) {
1576                 BMCWEB_LOG_DEBUG << "getConnectionCb enter";
1577                 auto getObjectManagerPathsCb =
1578                     [SensorsAsyncResp, sensorNames, connections](
1579                         std::shared_ptr<boost::container::flat_map<std::string,
1580                                                                    std::string>>
1581                             objectMgrPaths) {
1582                         BMCWEB_LOG_DEBUG << "getObjectManagerPathsCb enter";
1583                         // Get sensor data and store results in JSON
1584                         // response
1585                         getSensorData(SensorsAsyncResp, sensorNames,
1586                                       connections, objectMgrPaths);
1587                         BMCWEB_LOG_DEBUG << "getObjectManagerPathsCb exit";
1588                     };
1589 
1590                 // Get mapping from connection names to the DBus object
1591                 // paths that implement the ObjectManager interface
1592                 getObjectManagerPaths(SensorsAsyncResp,
1593                                       std::move(getObjectManagerPathsCb));
1594                 BMCWEB_LOG_DEBUG << "getConnectionCb exit";
1595             };
1596 
1597             // Get set of connections that provide sensor values
1598             getConnections(SensorsAsyncResp, sensorNames,
1599                            std::move(getConnectionCb));
1600             BMCWEB_LOG_DEBUG << "getChassisCb exit";
1601         };
1602     SensorsAsyncResp->res.jsonValue["Redundancy"] = nlohmann::json::array();
1603 
1604     // Get set of sensors in chassis
1605     getChassis(SensorsAsyncResp, std::move(getChassisCb));
1606     BMCWEB_LOG_DEBUG << "getChassisData exit";
1607 };
1608 
1609 /**
1610  * @brief Find the requested sensorName in the list of all sensors supplied by
1611  * the chassis node
1612  *
1613  * @param sensorName   The sensor name supplied in the PATCH request
1614  * @param sensorsList  The list of sensors managed by the chassis node
1615  * @param sensorsModified  The list of sensors that were found as a result of
1616  *                         repeated calls to this function
1617  */
1618 bool findSensorNameUsingSensorPath(
1619     std::string_view sensorName,
1620     boost::container::flat_set<std::string>& sensorsList,
1621     boost::container::flat_set<std::string>& sensorsModified)
1622 {
1623     for (std::string_view chassisSensor : sensorsList)
1624     {
1625         std::size_t pos = chassisSensor.rfind("/");
1626         if (pos >= (chassisSensor.size() - 1))
1627         {
1628             continue;
1629         }
1630         std::string_view thisSensorName = chassisSensor.substr(pos + 1);
1631         if (thisSensorName == sensorName)
1632         {
1633             sensorsModified.emplace(chassisSensor);
1634             return true;
1635         }
1636     }
1637     return false;
1638 }
1639 
1640 /**
1641  * @brief Entry point for overriding sensor values of given sensor
1642  *
1643  * @param res   response object
1644  * @param req   request object
1645  * @param params   parameter passed for CRUD
1646  * @param typeList   TypeList of sensors for the resource queried
1647  * @param chassisSubNode   Chassis Node for which the query has to happen
1648  */
1649 void setSensorOverride(crow::Response& res, const crow::Request& req,
1650                        const std::vector<std::string>& params,
1651                        const std::vector<const char*> typeList,
1652                        const std::string& chassisSubNode)
1653 {
1654 
1655     // TODO: Need to figure out dynamic way to restrict patch (Set Sensor
1656     // override) based on another d-bus announcement to be more generic.
1657     if (params.size() != 1)
1658     {
1659         messages::internalError(res);
1660         res.end();
1661         return;
1662     }
1663 
1664     std::unordered_map<std::string, std::vector<nlohmann::json>> allCollections;
1665     std::optional<std::vector<nlohmann::json>> temperatureCollections;
1666     std::optional<std::vector<nlohmann::json>> fanCollections;
1667     std::vector<nlohmann::json> voltageCollections;
1668     BMCWEB_LOG_INFO << "setSensorOverride for subNode" << chassisSubNode
1669                     << "\n";
1670 
1671     if (chassisSubNode == "Thermal")
1672     {
1673         if (!json_util::readJson(req, res, "Temperatures",
1674                                  temperatureCollections, "Fans",
1675                                  fanCollections))
1676         {
1677             return;
1678         }
1679         if (!temperatureCollections && !fanCollections)
1680         {
1681             messages::resourceNotFound(res, "Thermal",
1682                                        "Temperatures / Voltages");
1683             res.end();
1684             return;
1685         }
1686         if (temperatureCollections)
1687         {
1688             allCollections.emplace("Temperatures",
1689                                    *std::move(temperatureCollections));
1690         }
1691         if (fanCollections)
1692         {
1693             allCollections.emplace("Fans", *std::move(fanCollections));
1694         }
1695     }
1696     else if (chassisSubNode == "Power")
1697     {
1698         if (!json_util::readJson(req, res, "Voltages", voltageCollections))
1699         {
1700             return;
1701         }
1702         allCollections.emplace("Voltages", std::move(voltageCollections));
1703     }
1704     else
1705     {
1706         res.result(boost::beast::http::status::not_found);
1707         res.end();
1708         return;
1709     }
1710 
1711     const char* propertyValueName;
1712     std::unordered_map<std::string, std::pair<double, std::string>> overrideMap;
1713     std::string memberId;
1714     double value;
1715     for (auto& collectionItems : allCollections)
1716     {
1717         if (collectionItems.first == "Temperatures")
1718         {
1719             propertyValueName = "ReadingCelsius";
1720         }
1721         else if (collectionItems.first == "Fans")
1722         {
1723             propertyValueName = "Reading";
1724         }
1725         else
1726         {
1727             propertyValueName = "ReadingVolts";
1728         }
1729         for (auto& item : collectionItems.second)
1730         {
1731             if (!json_util::readJson(item, res, "MemberId", memberId,
1732                                      propertyValueName, value))
1733             {
1734                 return;
1735             }
1736             overrideMap.emplace(memberId,
1737                                 std::make_pair(value, collectionItems.first));
1738         }
1739     }
1740     const std::string& chassisName = params[0];
1741     auto sensorAsyncResp = std::make_shared<SensorsAsyncResp>(
1742         res, chassisName, typeList, chassisSubNode);
1743     auto getChassisSensorListCb = [sensorAsyncResp,
1744                                    overrideMap](const std::shared_ptr<
1745                                                 boost::container::flat_set<
1746                                                     std::string>>
1747                                                     sensorsList) {
1748         // Match sensor names in the PATCH request to those managed by the
1749         // chassis node
1750         const std::shared_ptr<boost::container::flat_set<std::string>>
1751             sensorNames =
1752                 std::make_shared<boost::container::flat_set<std::string>>();
1753         for (const auto& item : overrideMap)
1754         {
1755             const auto& sensor = item.first;
1756             if (!findSensorNameUsingSensorPath(sensor, *sensorsList,
1757                                                *sensorNames))
1758             {
1759                 BMCWEB_LOG_INFO << "Unable to find memberId " << item.first;
1760                 messages::resourceNotFound(sensorAsyncResp->res,
1761                                            item.second.second, item.first);
1762                 return;
1763             }
1764         }
1765         // Get the connection to which the memberId belongs
1766         auto getObjectsWithConnectionCb =
1767             [sensorAsyncResp, overrideMap](
1768                 const boost::container::flat_set<std::string>& connections,
1769                 const std::set<std::pair<std::string, std::string>>&
1770                     objectsWithConnection) {
1771                 if (objectsWithConnection.size() != overrideMap.size())
1772                 {
1773                     BMCWEB_LOG_INFO
1774                         << "Unable to find all objects with proper connection "
1775                         << objectsWithConnection.size() << " requested "
1776                         << overrideMap.size() << "\n";
1777                     messages::resourceNotFound(
1778                         sensorAsyncResp->res,
1779                         sensorAsyncResp->chassisSubNode == "Thermal"
1780                             ? "Temperatures"
1781                             : "Voltages",
1782                         "Count");
1783                     return;
1784                 }
1785                 for (const auto& item : objectsWithConnection)
1786                 {
1787 
1788                     auto lastPos = item.first.rfind('/');
1789                     if (lastPos == std::string::npos)
1790                     {
1791                         messages::internalError(sensorAsyncResp->res);
1792                         return;
1793                     }
1794                     std::string sensorName = item.first.substr(lastPos + 1);
1795 
1796                     const auto& iterator = overrideMap.find(sensorName);
1797                     if (iterator == overrideMap.end())
1798                     {
1799                         BMCWEB_LOG_INFO << "Unable to find sensor object"
1800                                         << item.first << "\n";
1801                         messages::internalError(sensorAsyncResp->res);
1802                         return;
1803                     }
1804                     crow::connections::systemBus->async_method_call(
1805                         [sensorAsyncResp](const boost::system::error_code ec) {
1806                             if (ec)
1807                             {
1808                                 BMCWEB_LOG_DEBUG
1809                                     << "setOverrideValueStatus DBUS error: "
1810                                     << ec;
1811                                 messages::internalError(sensorAsyncResp->res);
1812                                 return;
1813                             }
1814                         },
1815                         item.second, item.first,
1816                         "org.freedesktop.DBus.Properties", "Set",
1817                         "xyz.openbmc_project.Sensor.Value", "Value",
1818                         sdbusplus::message::variant<double>(
1819                             iterator->second.first));
1820                 }
1821             };
1822         // Get object with connection for the given sensor name
1823         getObjectsWithConnection(sensorAsyncResp, sensorNames,
1824                                  std::move(getObjectsWithConnectionCb));
1825     };
1826     // get full sensor list for the given chassisId and cross verify the sensor.
1827     getChassis(sensorAsyncResp, std::move(getChassisSensorListCb));
1828 }
1829 
1830 } // namespace redfish
1831