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