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