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