xref: /openbmc/bmcweb/features/redfish/include/utils/sensor_utils.hpp (revision f664fd8abeba11c7aa06d5119423a27b9d40d045)
140e9b92eSEd Tanous // SPDX-License-Identifier: Apache-2.0
240e9b92eSEd Tanous // SPDX-FileCopyrightText: Copyright OpenBMC Authors
31516c21bSJanet Adkins #pragma once
41516c21bSJanet Adkins 
5c9563608SJanet Adkins #include "dbus_utility.hpp"
6d7857201SEd Tanous #include "error_messages.hpp"
7c9563608SJanet Adkins #include "generated/enums/resource.hpp"
8c9563608SJanet Adkins #include "generated/enums/sensor.hpp"
9c9563608SJanet Adkins #include "generated/enums/thermal.hpp"
10d7857201SEd Tanous #include "logging.hpp"
11c9563608SJanet Adkins #include "str_utility.hpp"
12c9563608SJanet Adkins #include "utils/dbus_utils.hpp"
13c9563608SJanet Adkins 
146fe8751cSGeorge Liu #include <boost/url/format.hpp>
15d7857201SEd Tanous #include <nlohmann/json.hpp>
16d7857201SEd Tanous #include <sdbusplus/message/native_types.hpp>
17c9563608SJanet Adkins #include <sdbusplus/unpack_properties.hpp>
18c9563608SJanet Adkins 
191516c21bSJanet Adkins #include <algorithm>
20d7857201SEd Tanous #include <cmath>
21d7857201SEd Tanous #include <cstddef>
22d7857201SEd Tanous #include <cstdint>
231516c21bSJanet Adkins #include <format>
246fe8751cSGeorge Liu #include <functional>
25d7857201SEd Tanous #include <iterator>
266fe8751cSGeorge Liu #include <optional>
271516c21bSJanet Adkins #include <ranges>
28d7857201SEd Tanous #include <set>
29d7857201SEd Tanous #include <span>
301516c21bSJanet Adkins #include <string>
311516c21bSJanet Adkins #include <string_view>
32c9563608SJanet Adkins #include <tuple>
331516c21bSJanet Adkins #include <utility>
34d7857201SEd Tanous #include <variant>
351516c21bSJanet Adkins #include <vector>
361516c21bSJanet Adkins 
371516c21bSJanet Adkins namespace redfish
381516c21bSJanet Adkins {
391516c21bSJanet Adkins namespace sensor_utils
401516c21bSJanet Adkins {
411516c21bSJanet Adkins 
420c728b42SJanet Adkins enum class ChassisSubNode
430c728b42SJanet Adkins {
440c728b42SJanet Adkins     powerNode,
450c728b42SJanet Adkins     sensorsNode,
460c728b42SJanet Adkins     thermalNode,
476fe8751cSGeorge Liu     thermalMetricsNode,
480c728b42SJanet Adkins     unknownNode,
490c728b42SJanet Adkins };
500c728b42SJanet Adkins 
510c728b42SJanet Adkins constexpr std::string_view chassisSubNodeToString(ChassisSubNode subNode)
520c728b42SJanet Adkins {
530c728b42SJanet Adkins     switch (subNode)
540c728b42SJanet Adkins     {
550c728b42SJanet Adkins         case ChassisSubNode::powerNode:
560c728b42SJanet Adkins             return "Power";
570c728b42SJanet Adkins         case ChassisSubNode::sensorsNode:
580c728b42SJanet Adkins             return "Sensors";
590c728b42SJanet Adkins         case ChassisSubNode::thermalNode:
600c728b42SJanet Adkins             return "Thermal";
616fe8751cSGeorge Liu         case ChassisSubNode::thermalMetricsNode:
626fe8751cSGeorge Liu             return "ThermalMetrics";
630c728b42SJanet Adkins         case ChassisSubNode::unknownNode:
640c728b42SJanet Adkins         default:
650c728b42SJanet Adkins             return "";
660c728b42SJanet Adkins     }
670c728b42SJanet Adkins }
680c728b42SJanet Adkins 
690c728b42SJanet Adkins inline ChassisSubNode chassisSubNodeFromString(const std::string& subNodeStr)
700c728b42SJanet Adkins {
710c728b42SJanet Adkins     // If none match unknownNode is returned
720c728b42SJanet Adkins     ChassisSubNode subNode = ChassisSubNode::unknownNode;
730c728b42SJanet Adkins 
740c728b42SJanet Adkins     if (subNodeStr == "Power")
750c728b42SJanet Adkins     {
760c728b42SJanet Adkins         subNode = ChassisSubNode::powerNode;
770c728b42SJanet Adkins     }
780c728b42SJanet Adkins     else if (subNodeStr == "Sensors")
790c728b42SJanet Adkins     {
800c728b42SJanet Adkins         subNode = ChassisSubNode::sensorsNode;
810c728b42SJanet Adkins     }
820c728b42SJanet Adkins     else if (subNodeStr == "Thermal")
830c728b42SJanet Adkins     {
840c728b42SJanet Adkins         subNode = ChassisSubNode::thermalNode;
850c728b42SJanet Adkins     }
866fe8751cSGeorge Liu     else if (subNodeStr == "ThermalMetrics")
876fe8751cSGeorge Liu     {
886fe8751cSGeorge Liu         subNode = ChassisSubNode::thermalMetricsNode;
896fe8751cSGeorge Liu     }
900c728b42SJanet Adkins 
910c728b42SJanet Adkins     return subNode;
920c728b42SJanet Adkins }
93c9563608SJanet Adkins 
946fe8751cSGeorge Liu inline bool isExcerptNode(const ChassisSubNode subNode)
956fe8751cSGeorge Liu {
966fe8751cSGeorge Liu     return (subNode == ChassisSubNode::thermalMetricsNode);
976fe8751cSGeorge Liu }
986fe8751cSGeorge Liu 
99c9563608SJanet Adkins /**
100c9563608SJanet Adkins  * Possible states for physical inventory leds
101c9563608SJanet Adkins  */
102c9563608SJanet Adkins enum class LedState
103c9563608SJanet Adkins {
104c9563608SJanet Adkins     OFF,
105c9563608SJanet Adkins     ON,
106c9563608SJanet Adkins     BLINK,
107c9563608SJanet Adkins     UNKNOWN
108c9563608SJanet Adkins };
109c9563608SJanet Adkins 
110c9563608SJanet Adkins /**
111c9563608SJanet Adkins  * D-Bus inventory item associated with one or more sensors.
112c9563608SJanet Adkins  */
113c9563608SJanet Adkins class InventoryItem
114c9563608SJanet Adkins {
115c9563608SJanet Adkins   public:
116c9563608SJanet Adkins     explicit InventoryItem(const std::string& objPath) : objectPath(objPath)
117c9563608SJanet Adkins     {
118c9563608SJanet Adkins         // Set inventory item name to last node of object path
119c9563608SJanet Adkins         sdbusplus::message::object_path path(objectPath);
120c9563608SJanet Adkins         name = path.filename();
121c9563608SJanet Adkins         if (name.empty())
122c9563608SJanet Adkins         {
123c9563608SJanet Adkins             BMCWEB_LOG_ERROR("Failed to find '/' in {}", objectPath);
124c9563608SJanet Adkins         }
125c9563608SJanet Adkins     }
126c9563608SJanet Adkins 
127c9563608SJanet Adkins     std::string objectPath;
128c9563608SJanet Adkins     std::string name;
129c9563608SJanet Adkins     bool isPresent = true;
130c9563608SJanet Adkins     bool isFunctional = true;
131c9563608SJanet Adkins     bool isPowerSupply = false;
132c9563608SJanet Adkins     int powerSupplyEfficiencyPercent = -1;
133c9563608SJanet Adkins     std::string manufacturer;
134c9563608SJanet Adkins     std::string model;
135c9563608SJanet Adkins     std::string partNumber;
136c9563608SJanet Adkins     std::string serialNumber;
137c9563608SJanet Adkins     std::set<std::string> sensors;
138c9563608SJanet Adkins     std::string ledObjectPath;
139c9563608SJanet Adkins     LedState ledState = LedState::UNKNOWN;
140c9563608SJanet Adkins };
141c9563608SJanet Adkins 
1421516c21bSJanet Adkins inline std::string getSensorId(std::string_view sensorName,
1431516c21bSJanet Adkins                                std::string_view sensorType)
1441516c21bSJanet Adkins {
1451516c21bSJanet Adkins     std::string normalizedType(sensorType);
1461516c21bSJanet Adkins     auto remove = std::ranges::remove(normalizedType, '_');
1471516c21bSJanet Adkins     normalizedType.erase(std::ranges::begin(remove), normalizedType.end());
1481516c21bSJanet Adkins 
1491516c21bSJanet Adkins     return std::format("{}_{}", normalizedType, sensorName);
1501516c21bSJanet Adkins }
1511516c21bSJanet Adkins 
152504af5a0SPatrick Williams inline std::pair<std::string, std::string> splitSensorNameAndType(
153504af5a0SPatrick Williams     std::string_view sensorId)
1541516c21bSJanet Adkins {
1551516c21bSJanet Adkins     size_t index = sensorId.find('_');
1561516c21bSJanet Adkins     if (index == std::string::npos)
1571516c21bSJanet Adkins     {
1581516c21bSJanet Adkins         return std::make_pair<std::string, std::string>("", "");
1591516c21bSJanet Adkins     }
1601516c21bSJanet Adkins     std::string sensorType{sensorId.substr(0, index)};
1611516c21bSJanet Adkins     std::string sensorName{sensorId.substr(index + 1)};
1621516c21bSJanet Adkins     // fan_pwm and fan_tach need special handling
1631516c21bSJanet Adkins     if (sensorType == "fantach" || sensorType == "fanpwm")
1641516c21bSJanet Adkins     {
1651516c21bSJanet Adkins         sensorType.insert(3, 1, '_');
1661516c21bSJanet Adkins     }
1671516c21bSJanet Adkins     return std::make_pair(sensorType, sensorName);
1681516c21bSJanet Adkins }
1691516c21bSJanet Adkins 
170c9563608SJanet Adkins namespace sensors
171c9563608SJanet Adkins {
172c9563608SJanet Adkins inline std::string_view toReadingUnits(std::string_view sensorType)
173c9563608SJanet Adkins {
174c9563608SJanet Adkins     if (sensorType == "voltage")
175c9563608SJanet Adkins     {
176c9563608SJanet Adkins         return "V";
177c9563608SJanet Adkins     }
178c9563608SJanet Adkins     if (sensorType == "power")
179c9563608SJanet Adkins     {
180c9563608SJanet Adkins         return "W";
181c9563608SJanet Adkins     }
182c9563608SJanet Adkins     if (sensorType == "current")
183c9563608SJanet Adkins     {
184c9563608SJanet Adkins         return "A";
185c9563608SJanet Adkins     }
186c9563608SJanet Adkins     if (sensorType == "fan_tach")
187c9563608SJanet Adkins     {
188c9563608SJanet Adkins         return "RPM";
189c9563608SJanet Adkins     }
190c9563608SJanet Adkins     if (sensorType == "temperature")
191c9563608SJanet Adkins     {
192c9563608SJanet Adkins         return "Cel";
193c9563608SJanet Adkins     }
194c9563608SJanet Adkins     if (sensorType == "fan_pwm" || sensorType == "utilization" ||
195c9563608SJanet Adkins         sensorType == "humidity")
196c9563608SJanet Adkins     {
197c9563608SJanet Adkins         return "%";
198c9563608SJanet Adkins     }
199c9563608SJanet Adkins     if (sensorType == "altitude")
200c9563608SJanet Adkins     {
201c9563608SJanet Adkins         return "m";
202c9563608SJanet Adkins     }
203c9563608SJanet Adkins     if (sensorType == "airflow")
204c9563608SJanet Adkins     {
205c9563608SJanet Adkins         return "cft_i/min";
206c9563608SJanet Adkins     }
207c9563608SJanet Adkins     if (sensorType == "energy")
208c9563608SJanet Adkins     {
209c9563608SJanet Adkins         return "J";
210c9563608SJanet Adkins     }
21144914192SZev Weiss     if (sensorType == "liquidflow")
21244914192SZev Weiss     {
21344914192SZev Weiss         return "L/min";
21444914192SZev Weiss     }
21544914192SZev Weiss     if (sensorType == "pressure")
21644914192SZev Weiss     {
21744914192SZev Weiss         return "Pa";
21844914192SZev Weiss     }
219c9563608SJanet Adkins     return "";
220c9563608SJanet Adkins }
221c9563608SJanet Adkins 
222c9563608SJanet Adkins inline sensor::ReadingType toReadingType(std::string_view sensorType)
223c9563608SJanet Adkins {
224c9563608SJanet Adkins     if (sensorType == "voltage")
225c9563608SJanet Adkins     {
226c9563608SJanet Adkins         return sensor::ReadingType::Voltage;
227c9563608SJanet Adkins     }
228c9563608SJanet Adkins     if (sensorType == "power")
229c9563608SJanet Adkins     {
230c9563608SJanet Adkins         return sensor::ReadingType::Power;
231c9563608SJanet Adkins     }
232c9563608SJanet Adkins     if (sensorType == "current")
233c9563608SJanet Adkins     {
234c9563608SJanet Adkins         return sensor::ReadingType::Current;
235c9563608SJanet Adkins     }
236c9563608SJanet Adkins     if (sensorType == "fan_tach")
237c9563608SJanet Adkins     {
238c9563608SJanet Adkins         return sensor::ReadingType::Rotational;
239c9563608SJanet Adkins     }
240c9563608SJanet Adkins     if (sensorType == "temperature")
241c9563608SJanet Adkins     {
242c9563608SJanet Adkins         return sensor::ReadingType::Temperature;
243c9563608SJanet Adkins     }
244c9563608SJanet Adkins     if (sensorType == "fan_pwm" || sensorType == "utilization")
245c9563608SJanet Adkins     {
246c9563608SJanet Adkins         return sensor::ReadingType::Percent;
247c9563608SJanet Adkins     }
248c9563608SJanet Adkins     if (sensorType == "humidity")
249c9563608SJanet Adkins     {
250c9563608SJanet Adkins         return sensor::ReadingType::Humidity;
251c9563608SJanet Adkins     }
252c9563608SJanet Adkins     if (sensorType == "altitude")
253c9563608SJanet Adkins     {
254c9563608SJanet Adkins         return sensor::ReadingType::Altitude;
255c9563608SJanet Adkins     }
256c9563608SJanet Adkins     if (sensorType == "airflow")
257c9563608SJanet Adkins     {
258c9563608SJanet Adkins         return sensor::ReadingType::AirFlow;
259c9563608SJanet Adkins     }
260c9563608SJanet Adkins     if (sensorType == "energy")
261c9563608SJanet Adkins     {
262c9563608SJanet Adkins         return sensor::ReadingType::EnergyJoules;
263c9563608SJanet Adkins     }
26444914192SZev Weiss     if (sensorType == "liquidflow")
26544914192SZev Weiss     {
26644914192SZev Weiss         return sensor::ReadingType::LiquidFlowLPM;
26744914192SZev Weiss     }
26844914192SZev Weiss     if (sensorType == "pressure")
26944914192SZev Weiss     {
27044914192SZev Weiss         return sensor::ReadingType::PressurePa;
27144914192SZev Weiss     }
272c9563608SJanet Adkins     return sensor::ReadingType::Invalid;
273c9563608SJanet Adkins }
274c9563608SJanet Adkins 
275c9563608SJanet Adkins } // namespace sensors
276c9563608SJanet Adkins 
277c9563608SJanet Adkins /**
278c9563608SJanet Adkins  * @brief Returns the Redfish State value for the specified inventory item.
279c9563608SJanet Adkins  * @param inventoryItem D-Bus inventory item associated with a sensor.
280c9563608SJanet Adkins  * @param sensorAvailable Boolean representing if D-Bus sensor is marked as
281c9563608SJanet Adkins  * available.
282c9563608SJanet Adkins  * @return State value for inventory item.
283c9563608SJanet Adkins  */
284c9563608SJanet Adkins inline resource::State getState(const InventoryItem* inventoryItem,
285c9563608SJanet Adkins                                 const bool sensorAvailable)
286c9563608SJanet Adkins {
287c9563608SJanet Adkins     if ((inventoryItem != nullptr) && !(inventoryItem->isPresent))
288c9563608SJanet Adkins     {
289c9563608SJanet Adkins         return resource::State::Absent;
290c9563608SJanet Adkins     }
291c9563608SJanet Adkins 
292c9563608SJanet Adkins     if (!sensorAvailable)
293c9563608SJanet Adkins     {
294c9563608SJanet Adkins         return resource::State::UnavailableOffline;
295c9563608SJanet Adkins     }
296c9563608SJanet Adkins 
297c9563608SJanet Adkins     return resource::State::Enabled;
298c9563608SJanet Adkins }
299c9563608SJanet Adkins 
300c9563608SJanet Adkins /**
301c9563608SJanet Adkins  * @brief Returns the Redfish Health value for the specified sensor.
302c9563608SJanet Adkins  * @param sensorJson Sensor JSON object.
303c9563608SJanet Adkins  * @param valuesDict Map of all sensor DBus values.
304c9563608SJanet Adkins  * @param inventoryItem D-Bus inventory item associated with the sensor.  Will
305c9563608SJanet Adkins  * be nullptr if no associated inventory item was found.
306c9563608SJanet Adkins  * @return Health value for sensor.
307c9563608SJanet Adkins  */
308c9563608SJanet Adkins inline std::string getHealth(nlohmann::json& sensorJson,
309c9563608SJanet Adkins                              const dbus::utility::DBusPropertiesMap& valuesDict,
310c9563608SJanet Adkins                              const InventoryItem* inventoryItem)
311c9563608SJanet Adkins {
312c9563608SJanet Adkins     // Get current health value (if any) in the sensor JSON object.  Some JSON
313c9563608SJanet Adkins     // objects contain multiple sensors (such as PowerSupplies).  We want to set
314c9563608SJanet Adkins     // the overall health to be the most severe of any of the sensors.
315c9563608SJanet Adkins     std::string currentHealth;
316c9563608SJanet Adkins     auto statusIt = sensorJson.find("Status");
317c9563608SJanet Adkins     if (statusIt != sensorJson.end())
318c9563608SJanet Adkins     {
319c9563608SJanet Adkins         auto healthIt = statusIt->find("Health");
320c9563608SJanet Adkins         if (healthIt != statusIt->end())
321c9563608SJanet Adkins         {
322c9563608SJanet Adkins             std::string* health = healthIt->get_ptr<std::string*>();
323c9563608SJanet Adkins             if (health != nullptr)
324c9563608SJanet Adkins             {
325c9563608SJanet Adkins                 currentHealth = *health;
326c9563608SJanet Adkins             }
327c9563608SJanet Adkins         }
328c9563608SJanet Adkins     }
329c9563608SJanet Adkins 
330c9563608SJanet Adkins     // If current health in JSON object is already Critical, return that.  This
331c9563608SJanet Adkins     // should override the sensor health, which might be less severe.
332c9563608SJanet Adkins     if (currentHealth == "Critical")
333c9563608SJanet Adkins     {
334c9563608SJanet Adkins         return "Critical";
335c9563608SJanet Adkins     }
336c9563608SJanet Adkins 
337c9563608SJanet Adkins     const bool* criticalAlarmHigh = nullptr;
338c9563608SJanet Adkins     const bool* criticalAlarmLow = nullptr;
339c9563608SJanet Adkins     const bool* warningAlarmHigh = nullptr;
340c9563608SJanet Adkins     const bool* warningAlarmLow = nullptr;
341c9563608SJanet Adkins 
342c9563608SJanet Adkins     const bool success = sdbusplus::unpackPropertiesNoThrow(
343c9563608SJanet Adkins         dbus_utils::UnpackErrorPrinter(), valuesDict, "CriticalAlarmHigh",
344c9563608SJanet Adkins         criticalAlarmHigh, "CriticalAlarmLow", criticalAlarmLow,
345c9563608SJanet Adkins         "WarningAlarmHigh", warningAlarmHigh, "WarningAlarmLow",
346c9563608SJanet Adkins         warningAlarmLow);
347c9563608SJanet Adkins 
348c9563608SJanet Adkins     if (success)
349c9563608SJanet Adkins     {
350c9563608SJanet Adkins         // Check if sensor has critical threshold alarm
351c9563608SJanet Adkins         if ((criticalAlarmHigh != nullptr && *criticalAlarmHigh) ||
352c9563608SJanet Adkins             (criticalAlarmLow != nullptr && *criticalAlarmLow))
353c9563608SJanet Adkins         {
354c9563608SJanet Adkins             return "Critical";
355c9563608SJanet Adkins         }
356c9563608SJanet Adkins     }
357c9563608SJanet Adkins 
358c9563608SJanet Adkins     // Check if associated inventory item is not functional
359c9563608SJanet Adkins     if ((inventoryItem != nullptr) && !(inventoryItem->isFunctional))
360c9563608SJanet Adkins     {
361c9563608SJanet Adkins         return "Critical";
362c9563608SJanet Adkins     }
363c9563608SJanet Adkins 
364c9563608SJanet Adkins     // If current health in JSON object is already Warning, return that. This
365c9563608SJanet Adkins     // should override the sensor status, which might be less severe.
366c9563608SJanet Adkins     if (currentHealth == "Warning")
367c9563608SJanet Adkins     {
368c9563608SJanet Adkins         return "Warning";
369c9563608SJanet Adkins     }
370c9563608SJanet Adkins 
371c9563608SJanet Adkins     if (success)
372c9563608SJanet Adkins     {
373c9563608SJanet Adkins         // Check if sensor has warning threshold alarm
374c9563608SJanet Adkins         if ((warningAlarmHigh != nullptr && *warningAlarmHigh) ||
375c9563608SJanet Adkins             (warningAlarmLow != nullptr && *warningAlarmLow))
376c9563608SJanet Adkins         {
377c9563608SJanet Adkins             return "Warning";
378c9563608SJanet Adkins         }
379c9563608SJanet Adkins     }
380c9563608SJanet Adkins 
381c9563608SJanet Adkins     return "OK";
382c9563608SJanet Adkins }
383c9563608SJanet Adkins 
384c9563608SJanet Adkins inline void setLedState(nlohmann::json& sensorJson,
385c9563608SJanet Adkins                         const InventoryItem* inventoryItem)
386c9563608SJanet Adkins {
387c9563608SJanet Adkins     if (inventoryItem != nullptr && !inventoryItem->ledObjectPath.empty())
388c9563608SJanet Adkins     {
389c9563608SJanet Adkins         switch (inventoryItem->ledState)
390c9563608SJanet Adkins         {
391c9563608SJanet Adkins             case LedState::OFF:
392c9563608SJanet Adkins                 sensorJson["IndicatorLED"] = resource::IndicatorLED::Off;
393c9563608SJanet Adkins                 break;
394c9563608SJanet Adkins             case LedState::ON:
395c9563608SJanet Adkins                 sensorJson["IndicatorLED"] = resource::IndicatorLED::Lit;
396c9563608SJanet Adkins                 break;
397c9563608SJanet Adkins             case LedState::BLINK:
398c9563608SJanet Adkins                 sensorJson["IndicatorLED"] = resource::IndicatorLED::Blinking;
399c9563608SJanet Adkins                 break;
400c9563608SJanet Adkins             default:
401c9563608SJanet Adkins                 break;
402c9563608SJanet Adkins         }
403c9563608SJanet Adkins     }
404c9563608SJanet Adkins }
405c9563608SJanet Adkins 
406c9563608SJanet Adkins /**
407c9563608SJanet Adkins  * @brief Builds a json sensor representation of a sensor.
408c9563608SJanet Adkins  * @param sensorName  The name of the sensor to be built
409c9563608SJanet Adkins  * @param sensorType  The type (temperature, fan_tach, etc) of the sensor to
410c9563608SJanet Adkins  * build
411c9563608SJanet Adkins  * @param chassisSubNode The subnode (thermal, sensor, etc) of the sensor
412c9563608SJanet Adkins  * @param propertiesDict A dictionary of the properties to build the sensor
413c9563608SJanet Adkins  * from.
414c9563608SJanet Adkins  * @param sensorJson  The json object to fill
415c9563608SJanet Adkins  * @param inventoryItem D-Bus inventory item associated with the sensor.  Will
416c9563608SJanet Adkins  * be nullptr if no associated inventory item was found.
417c9563608SJanet Adkins  */
418c9563608SJanet Adkins inline void objectPropertiesToJson(
419c9563608SJanet Adkins     std::string_view sensorName, std::string_view sensorType,
4200c728b42SJanet Adkins     ChassisSubNode chassisSubNode,
421c9563608SJanet Adkins     const dbus::utility::DBusPropertiesMap& propertiesDict,
422c9563608SJanet Adkins     nlohmann::json& sensorJson, InventoryItem* inventoryItem)
423c9563608SJanet Adkins {
4246fe8751cSGeorge Liu     // Parameter to set to override the type we get from dbus, and force it to
4256fe8751cSGeorge Liu     // int, regardless of what is available.  This is used for schemas like fan,
4266fe8751cSGeorge Liu     // that require integers, not floats.
4276fe8751cSGeorge Liu     bool forceToInt = false;
4286fe8751cSGeorge Liu 
4296fe8751cSGeorge Liu     nlohmann::json::json_pointer unit("/Reading");
4306fe8751cSGeorge Liu 
4316fe8751cSGeorge Liu     // This ChassisSubNode builds sensor excerpts
4326fe8751cSGeorge Liu     bool isExcerpt = isExcerptNode(chassisSubNode);
4336fe8751cSGeorge Liu 
4346fe8751cSGeorge Liu     /* Sensor excerpts use different keys to reference the sensor. These are
4356fe8751cSGeorge Liu      * built by the caller.
4366fe8751cSGeorge Liu      * Additionally they don't include these additional properties.
4376fe8751cSGeorge Liu      */
4386fe8751cSGeorge Liu     if (!isExcerpt)
4396fe8751cSGeorge Liu     {
4400c728b42SJanet Adkins         if (chassisSubNode == ChassisSubNode::sensorsNode)
441c9563608SJanet Adkins         {
442c9563608SJanet Adkins             std::string subNodeEscaped = getSensorId(sensorName, sensorType);
443c9563608SJanet Adkins             // For sensors in SensorCollection we set Id instead of MemberId,
444c9563608SJanet Adkins             // including power sensors.
445c9563608SJanet Adkins             sensorJson["Id"] = std::move(subNodeEscaped);
446c9563608SJanet Adkins 
447c9563608SJanet Adkins             std::string sensorNameEs(sensorName);
448c9563608SJanet Adkins             std::replace(sensorNameEs.begin(), sensorNameEs.end(), '_', ' ');
449c9563608SJanet Adkins             sensorJson["Name"] = std::move(sensorNameEs);
450c9563608SJanet Adkins         }
451c9563608SJanet Adkins         else if (sensorType != "power")
452c9563608SJanet Adkins         {
4536fe8751cSGeorge Liu             // Set MemberId and Name for non-power sensors.  For PowerSupplies
4546fe8751cSGeorge Liu             // and PowerControl, those properties have more general values
4556fe8751cSGeorge Liu             // because multiple sensors can be stored in the same JSON object.
456c9563608SJanet Adkins             std::string sensorNameEs(sensorName);
457c9563608SJanet Adkins             std::replace(sensorNameEs.begin(), sensorNameEs.end(), '_', ' ');
458c9563608SJanet Adkins             sensorJson["Name"] = std::move(sensorNameEs);
459c9563608SJanet Adkins         }
460c9563608SJanet Adkins 
461c9563608SJanet Adkins         const bool* checkAvailable = nullptr;
462c9563608SJanet Adkins         bool available = true;
463c9563608SJanet Adkins         const bool success = sdbusplus::unpackPropertiesNoThrow(
464c9563608SJanet Adkins             dbus_utils::UnpackErrorPrinter(), propertiesDict, "Available",
465c9563608SJanet Adkins             checkAvailable);
466c9563608SJanet Adkins         if (!success)
467c9563608SJanet Adkins         {
468c9563608SJanet Adkins             messages::internalError();
469c9563608SJanet Adkins         }
470c9563608SJanet Adkins         if (checkAvailable != nullptr)
471c9563608SJanet Adkins         {
472c9563608SJanet Adkins             available = *checkAvailable;
473c9563608SJanet Adkins         }
474c9563608SJanet Adkins 
475c9563608SJanet Adkins         sensorJson["Status"]["State"] = getState(inventoryItem, available);
476c9563608SJanet Adkins         sensorJson["Status"]["Health"] =
477c9563608SJanet Adkins             getHealth(sensorJson, propertiesDict, inventoryItem);
478c9563608SJanet Adkins 
4790c728b42SJanet Adkins         if (chassisSubNode == ChassisSubNode::sensorsNode)
480c9563608SJanet Adkins         {
481c9563608SJanet Adkins             sensorJson["@odata.type"] = "#Sensor.v1_2_0.Sensor";
482c9563608SJanet Adkins 
4836fe8751cSGeorge Liu             sensor::ReadingType readingType =
4846fe8751cSGeorge Liu                 sensors::toReadingType(sensorType);
485c9563608SJanet Adkins             if (readingType == sensor::ReadingType::Invalid)
486c9563608SJanet Adkins             {
487c9563608SJanet Adkins                 BMCWEB_LOG_ERROR("Redfish cannot map reading type for {}",
488c9563608SJanet Adkins                                  sensorType);
489c9563608SJanet Adkins             }
490c9563608SJanet Adkins             else
491c9563608SJanet Adkins             {
492c9563608SJanet Adkins                 sensorJson["ReadingType"] = readingType;
493c9563608SJanet Adkins             }
494c9563608SJanet Adkins 
495c9563608SJanet Adkins             std::string_view readingUnits = sensors::toReadingUnits(sensorType);
496c9563608SJanet Adkins             if (readingUnits.empty())
497c9563608SJanet Adkins             {
498c9563608SJanet Adkins                 BMCWEB_LOG_ERROR("Redfish cannot map reading unit for {}",
499c9563608SJanet Adkins                                  sensorType);
500c9563608SJanet Adkins             }
501c9563608SJanet Adkins             else
502c9563608SJanet Adkins             {
503c9563608SJanet Adkins                 sensorJson["ReadingUnits"] = readingUnits;
504c9563608SJanet Adkins             }
505c9563608SJanet Adkins         }
506c9563608SJanet Adkins         else if (sensorType == "temperature")
507c9563608SJanet Adkins         {
508c9563608SJanet Adkins             unit = "/ReadingCelsius"_json_pointer;
509c9563608SJanet Adkins             sensorJson["@odata.type"] = "#Thermal.v1_3_0.Temperature";
510c9563608SJanet Adkins             // TODO(ed) Documentation says that path should be type fan_tach,
511c9563608SJanet Adkins             // implementation seems to implement fan
512c9563608SJanet Adkins         }
513c9563608SJanet Adkins         else if (sensorType == "fan" || sensorType == "fan_tach")
514c9563608SJanet Adkins         {
515c9563608SJanet Adkins             unit = "/Reading"_json_pointer;
516c9563608SJanet Adkins             sensorJson["ReadingUnits"] = thermal::ReadingUnits::RPM;
517c9563608SJanet Adkins             sensorJson["@odata.type"] = "#Thermal.v1_3_0.Fan";
518*f664fd8aSJanet Adkins             if constexpr (BMCWEB_REDFISH_ALLOW_DEPRECATED_INDICATORLED)
519*f664fd8aSJanet Adkins             {
520c9563608SJanet Adkins                 setLedState(sensorJson, inventoryItem);
521*f664fd8aSJanet Adkins             }
522c9563608SJanet Adkins             forceToInt = true;
523c9563608SJanet Adkins         }
524c9563608SJanet Adkins         else if (sensorType == "fan_pwm")
525c9563608SJanet Adkins         {
526c9563608SJanet Adkins             unit = "/Reading"_json_pointer;
527c9563608SJanet Adkins             sensorJson["ReadingUnits"] = thermal::ReadingUnits::Percent;
528c9563608SJanet Adkins             sensorJson["@odata.type"] = "#Thermal.v1_3_0.Fan";
529*f664fd8aSJanet Adkins             if constexpr (BMCWEB_REDFISH_ALLOW_DEPRECATED_INDICATORLED)
530*f664fd8aSJanet Adkins             {
531c9563608SJanet Adkins                 setLedState(sensorJson, inventoryItem);
532*f664fd8aSJanet Adkins             }
533c9563608SJanet Adkins             forceToInt = true;
534c9563608SJanet Adkins         }
535c9563608SJanet Adkins         else if (sensorType == "voltage")
536c9563608SJanet Adkins         {
537c9563608SJanet Adkins             unit = "/ReadingVolts"_json_pointer;
538c9563608SJanet Adkins             sensorJson["@odata.type"] = "#Power.v1_0_0.Voltage";
539c9563608SJanet Adkins         }
540c9563608SJanet Adkins         else if (sensorType == "power")
541c9563608SJanet Adkins         {
542c9563608SJanet Adkins             std::string lower;
543c9563608SJanet Adkins             std::ranges::transform(sensorName, std::back_inserter(lower),
544c9563608SJanet Adkins                                    bmcweb::asciiToLower);
545c9563608SJanet Adkins             if (lower == "total_power")
546c9563608SJanet Adkins             {
547c9563608SJanet Adkins                 sensorJson["@odata.type"] = "#Power.v1_0_0.PowerControl";
548c9563608SJanet Adkins                 // Put multiple "sensors" into a single PowerControl, so have
549c9563608SJanet Adkins                 // generic names for MemberId and Name. Follows Redfish mockup.
550c9563608SJanet Adkins                 sensorJson["MemberId"] = "0";
551c9563608SJanet Adkins                 sensorJson["Name"] = "Chassis Power Control";
552c9563608SJanet Adkins                 unit = "/PowerConsumedWatts"_json_pointer;
553c9563608SJanet Adkins             }
554c9563608SJanet Adkins             else if (lower.find("input") != std::string::npos)
555c9563608SJanet Adkins             {
556c9563608SJanet Adkins                 unit = "/PowerInputWatts"_json_pointer;
557c9563608SJanet Adkins             }
558c9563608SJanet Adkins             else
559c9563608SJanet Adkins             {
560c9563608SJanet Adkins                 unit = "/PowerOutputWatts"_json_pointer;
561c9563608SJanet Adkins             }
562c9563608SJanet Adkins         }
563c9563608SJanet Adkins         else
564c9563608SJanet Adkins         {
5656fe8751cSGeorge Liu             BMCWEB_LOG_ERROR("Redfish cannot map object type for {}",
5666fe8751cSGeorge Liu                              sensorName);
567c9563608SJanet Adkins             return;
568c9563608SJanet Adkins         }
5696fe8751cSGeorge Liu     }
5706fe8751cSGeorge Liu 
571c9563608SJanet Adkins     // Map of dbus interface name, dbus property name and redfish property_name
572c9563608SJanet Adkins     std::vector<
573c9563608SJanet Adkins         std::tuple<const char*, const char*, nlohmann::json::json_pointer>>
574c9563608SJanet Adkins         properties;
575c9563608SJanet Adkins 
576c9563608SJanet Adkins     properties.emplace_back("xyz.openbmc_project.Sensor.Value", "Value", unit);
577c9563608SJanet Adkins 
5786fe8751cSGeorge Liu     if (!isExcerpt)
5796fe8751cSGeorge Liu     {
5800c728b42SJanet Adkins         if (chassisSubNode == ChassisSubNode::sensorsNode)
581c9563608SJanet Adkins         {
582c9563608SJanet Adkins             properties.emplace_back(
583c9563608SJanet Adkins                 "xyz.openbmc_project.Sensor.Threshold.Warning", "WarningHigh",
584c9563608SJanet Adkins                 "/Thresholds/UpperCaution/Reading"_json_pointer);
585c9563608SJanet Adkins             properties.emplace_back(
586c9563608SJanet Adkins                 "xyz.openbmc_project.Sensor.Threshold.Warning", "WarningLow",
587c9563608SJanet Adkins                 "/Thresholds/LowerCaution/Reading"_json_pointer);
588c9563608SJanet Adkins             properties.emplace_back(
589c9563608SJanet Adkins                 "xyz.openbmc_project.Sensor.Threshold.Critical", "CriticalHigh",
590c9563608SJanet Adkins                 "/Thresholds/UpperCritical/Reading"_json_pointer);
591c9563608SJanet Adkins             properties.emplace_back(
592c9563608SJanet Adkins                 "xyz.openbmc_project.Sensor.Threshold.Critical", "CriticalLow",
593c9563608SJanet Adkins                 "/Thresholds/LowerCritical/Reading"_json_pointer);
5946f5be432SPotin Lai             properties.emplace_back(
5956f5be432SPotin Lai                 "xyz.openbmc_project.Sensor.Threshold.HardShutdown",
5966f5be432SPotin Lai                 "HardShutdownHigh",
5976f5be432SPotin Lai                 "/Thresholds/UpperFatal/Reading"_json_pointer);
5986f5be432SPotin Lai             properties.emplace_back(
5996f5be432SPotin Lai                 "xyz.openbmc_project.Sensor.Threshold.HardShutdown",
6006f5be432SPotin Lai                 "HardShutdownLow",
6016f5be432SPotin Lai                 "/Thresholds/LowerFatal/Reading"_json_pointer);
602cd5a898fSJanet Adkins 
603cd5a898fSJanet Adkins             /* Add additional properties specific to sensorType */
604cd5a898fSJanet Adkins             if (sensorType == "fan_tach")
605cd5a898fSJanet Adkins             {
6066fe8751cSGeorge Liu                 properties.emplace_back("xyz.openbmc_project.Sensor.Value",
6076fe8751cSGeorge Liu                                         "Value", "/SpeedRPM"_json_pointer);
608cd5a898fSJanet Adkins             }
609c9563608SJanet Adkins         }
610c9563608SJanet Adkins         else if (sensorType != "power")
611c9563608SJanet Adkins         {
6126fe8751cSGeorge Liu             properties.emplace_back(
6136fe8751cSGeorge Liu                 "xyz.openbmc_project.Sensor.Threshold.Warning", "WarningHigh",
614c9563608SJanet Adkins                 "/UpperThresholdNonCritical"_json_pointer);
6156fe8751cSGeorge Liu             properties.emplace_back(
6166fe8751cSGeorge Liu                 "xyz.openbmc_project.Sensor.Threshold.Warning", "WarningLow",
617c9563608SJanet Adkins                 "/LowerThresholdNonCritical"_json_pointer);
6186fe8751cSGeorge Liu             properties.emplace_back(
6196fe8751cSGeorge Liu                 "xyz.openbmc_project.Sensor.Threshold.Critical", "CriticalHigh",
620c9563608SJanet Adkins                 "/UpperThresholdCritical"_json_pointer);
6216fe8751cSGeorge Liu             properties.emplace_back(
6226fe8751cSGeorge Liu                 "xyz.openbmc_project.Sensor.Threshold.Critical", "CriticalLow",
623c9563608SJanet Adkins                 "/LowerThresholdCritical"_json_pointer);
624c9563608SJanet Adkins         }
625c9563608SJanet Adkins 
626c9563608SJanet Adkins         // TODO Need to get UpperThresholdFatal and LowerThresholdFatal
627c9563608SJanet Adkins 
6280c728b42SJanet Adkins         if (chassisSubNode == ChassisSubNode::sensorsNode)
629c9563608SJanet Adkins         {
6306fe8751cSGeorge Liu             properties.emplace_back("xyz.openbmc_project.Sensor.Value",
6316fe8751cSGeorge Liu                                     "MinValue",
632c9563608SJanet Adkins                                     "/ReadingRangeMin"_json_pointer);
6336fe8751cSGeorge Liu             properties.emplace_back("xyz.openbmc_project.Sensor.Value",
6346fe8751cSGeorge Liu                                     "MaxValue",
635c9563608SJanet Adkins                                     "/ReadingRangeMax"_json_pointer);
636c9563608SJanet Adkins             properties.emplace_back("xyz.openbmc_project.Sensor.Accuracy",
637c9563608SJanet Adkins                                     "Accuracy", "/Accuracy"_json_pointer);
638c9563608SJanet Adkins         }
639c9563608SJanet Adkins         else if (sensorType == "temperature")
640c9563608SJanet Adkins         {
6416fe8751cSGeorge Liu             properties.emplace_back("xyz.openbmc_project.Sensor.Value",
6426fe8751cSGeorge Liu                                     "MinValue",
643c9563608SJanet Adkins                                     "/MinReadingRangeTemp"_json_pointer);
6446fe8751cSGeorge Liu             properties.emplace_back("xyz.openbmc_project.Sensor.Value",
6456fe8751cSGeorge Liu                                     "MaxValue",
646c9563608SJanet Adkins                                     "/MaxReadingRangeTemp"_json_pointer);
647c9563608SJanet Adkins         }
648c9563608SJanet Adkins         else if (sensorType != "power")
649c9563608SJanet Adkins         {
6506fe8751cSGeorge Liu             properties.emplace_back("xyz.openbmc_project.Sensor.Value",
6516fe8751cSGeorge Liu                                     "MinValue",
652c9563608SJanet Adkins                                     "/MinReadingRange"_json_pointer);
6536fe8751cSGeorge Liu             properties.emplace_back("xyz.openbmc_project.Sensor.Value",
6546fe8751cSGeorge Liu                                     "MaxValue",
655c9563608SJanet Adkins                                     "/MaxReadingRange"_json_pointer);
656c9563608SJanet Adkins         }
6576fe8751cSGeorge Liu     }
658c9563608SJanet Adkins 
659c9563608SJanet Adkins     for (const std::tuple<const char*, const char*,
660c9563608SJanet Adkins                           nlohmann::json::json_pointer>& p : properties)
661c9563608SJanet Adkins     {
662c9563608SJanet Adkins         for (const auto& [valueName, valueVariant] : propertiesDict)
663c9563608SJanet Adkins         {
664c9563608SJanet Adkins             if (valueName != std::get<1>(p))
665c9563608SJanet Adkins             {
666c9563608SJanet Adkins                 continue;
667c9563608SJanet Adkins             }
668c9563608SJanet Adkins 
669c9563608SJanet Adkins             // The property we want to set may be nested json, so use
670c9563608SJanet Adkins             // a json_pointer for easy indexing into the json structure.
671c9563608SJanet Adkins             const nlohmann::json::json_pointer& key = std::get<2>(p);
672c9563608SJanet Adkins 
673c9563608SJanet Adkins             const double* doubleValue = std::get_if<double>(&valueVariant);
674c9563608SJanet Adkins             if (doubleValue == nullptr)
675c9563608SJanet Adkins             {
676c9563608SJanet Adkins                 BMCWEB_LOG_ERROR("Got value interface that wasn't double");
677c9563608SJanet Adkins                 continue;
678c9563608SJanet Adkins             }
679c9563608SJanet Adkins             if (!std::isfinite(*doubleValue))
680c9563608SJanet Adkins             {
681c9563608SJanet Adkins                 if (valueName == "Value")
682c9563608SJanet Adkins                 {
683c9563608SJanet Adkins                     // Readings are allowed to be NAN for unavailable;  coerce
684c9563608SJanet Adkins                     // them to null in the json response.
685c9563608SJanet Adkins                     sensorJson[key] = nullptr;
686c9563608SJanet Adkins                     continue;
687c9563608SJanet Adkins                 }
688c9563608SJanet Adkins                 BMCWEB_LOG_WARNING("Sensor value for {} was unexpectedly {}",
689c9563608SJanet Adkins                                    valueName, *doubleValue);
690c9563608SJanet Adkins                 continue;
691c9563608SJanet Adkins             }
692c9563608SJanet Adkins             if (forceToInt)
693c9563608SJanet Adkins             {
694c9563608SJanet Adkins                 sensorJson[key] = static_cast<int64_t>(*doubleValue);
695c9563608SJanet Adkins             }
696c9563608SJanet Adkins             else
697c9563608SJanet Adkins             {
698c9563608SJanet Adkins                 sensorJson[key] = *doubleValue;
699c9563608SJanet Adkins             }
700c9563608SJanet Adkins         }
701c9563608SJanet Adkins     }
702c9563608SJanet Adkins }
703c9563608SJanet Adkins 
7046fe8751cSGeorge Liu /**
7056fe8751cSGeorge Liu  * @brief Builds a json sensor excerpt representation of a sensor.
7066fe8751cSGeorge Liu  *
7076fe8751cSGeorge Liu  * @details This is a wrapper function to provide consistent setting of
7086fe8751cSGeorge Liu  * "DataSourceUri" for sensor excerpts and filling of properties. Since sensor
7096fe8751cSGeorge Liu  * excerpts usually have just the D-Bus path for the sensor that is accepted
7106fe8751cSGeorge Liu  * and used to build "DataSourceUri".
7116fe8751cSGeorge Liu 
7126fe8751cSGeorge Liu  * @param path The D-Bus path to the sensor to be built
7136fe8751cSGeorge Liu  * @param chassisId The Chassis Id for the sensor
7146fe8751cSGeorge Liu  * @param chassisSubNode The subnode (e.g. ThermalMetrics) of the sensor
7156fe8751cSGeorge Liu  * @param sensorTypeExpected The expected type of the sensor
7166fe8751cSGeorge Liu  * @param propertiesDict A dictionary of the properties to build the sensor
7176fe8751cSGeorge Liu  * from.
7186fe8751cSGeorge Liu  * @param sensorJson  The json object to fill
7196fe8751cSGeorge Liu  * @returns True if sensorJson object filled. False on any error.
7206fe8751cSGeorge Liu  * Caller is responsible for handling error.
7216fe8751cSGeorge Liu  */
7226fe8751cSGeorge Liu inline bool objectExcerptToJson(
7236fe8751cSGeorge Liu     const std::string& path, const std::string_view chassisId,
7246fe8751cSGeorge Liu     ChassisSubNode chassisSubNode,
7256fe8751cSGeorge Liu     const std::optional<std::string>& sensorTypeExpected,
7266fe8751cSGeorge Liu     const dbus::utility::DBusPropertiesMap& propertiesDict,
7276fe8751cSGeorge Liu     nlohmann::json& sensorJson)
7286fe8751cSGeorge Liu {
7296fe8751cSGeorge Liu     if (!isExcerptNode(chassisSubNode))
7306fe8751cSGeorge Liu     {
7316fe8751cSGeorge Liu         BMCWEB_LOG_DEBUG("{} is not a sensor excerpt",
7326fe8751cSGeorge Liu                          chassisSubNodeToString(chassisSubNode));
7336fe8751cSGeorge Liu         return false;
7346fe8751cSGeorge Liu     }
7356fe8751cSGeorge Liu 
7366fe8751cSGeorge Liu     sdbusplus::message::object_path sensorPath(path);
7376fe8751cSGeorge Liu     std::string sensorName = sensorPath.filename();
7386fe8751cSGeorge Liu     std::string sensorType = sensorPath.parent_path().filename();
7396fe8751cSGeorge Liu     if (sensorName.empty() || sensorType.empty())
7406fe8751cSGeorge Liu     {
7416fe8751cSGeorge Liu         BMCWEB_LOG_DEBUG("Invalid sensor path {}", path);
7426fe8751cSGeorge Liu         return false;
7436fe8751cSGeorge Liu     }
7446fe8751cSGeorge Liu 
7456fe8751cSGeorge Liu     if (sensorTypeExpected && (sensorType != *sensorTypeExpected))
7466fe8751cSGeorge Liu     {
7476fe8751cSGeorge Liu         BMCWEB_LOG_DEBUG("{} is not expected type {}", path,
7486fe8751cSGeorge Liu                          *sensorTypeExpected);
7496fe8751cSGeorge Liu         return false;
7506fe8751cSGeorge Liu     }
7516fe8751cSGeorge Liu 
7526fe8751cSGeorge Liu     // Sensor excerpts use DataSourceUri to reference full sensor Redfish path
7536fe8751cSGeorge Liu     sensorJson["DataSourceUri"] =
7546fe8751cSGeorge Liu         boost::urls::format("/redfish/v1/Chassis/{}/Sensors/{}", chassisId,
7556fe8751cSGeorge Liu                             getSensorId(sensorName, sensorType));
7566fe8751cSGeorge Liu 
7576fe8751cSGeorge Liu     // Fill in sensor excerpt properties
7586fe8751cSGeorge Liu     objectPropertiesToJson(sensorName, sensorType, chassisSubNode,
7596fe8751cSGeorge Liu                            propertiesDict, sensorJson, nullptr);
7606fe8751cSGeorge Liu 
7616fe8751cSGeorge Liu     return true;
7626fe8751cSGeorge Liu }
7636fe8751cSGeorge Liu 
7646fe8751cSGeorge Liu // Maps D-Bus: Service, SensorPath
7656fe8751cSGeorge Liu using SensorServicePathMap = std::pair<std::string, std::string>;
7666fe8751cSGeorge Liu using SensorServicePathList = std::vector<SensorServicePathMap>;
7676fe8751cSGeorge Liu 
7686fe8751cSGeorge Liu inline void getAllSensorObjects(
7696fe8751cSGeorge Liu     const std::string& associatedPath, const std::string& path,
7706fe8751cSGeorge Liu     std::span<const std::string_view> interfaces, const int32_t depth,
7716fe8751cSGeorge Liu     std::function<void(const boost::system::error_code& ec,
7726fe8751cSGeorge Liu                        SensorServicePathList&)>&& callback)
7736fe8751cSGeorge Liu {
7746fe8751cSGeorge Liu     sdbusplus::message::object_path endpointPath{associatedPath};
7756fe8751cSGeorge Liu     endpointPath /= "all_sensors";
7766fe8751cSGeorge Liu 
7776fe8751cSGeorge Liu     dbus::utility::getAssociatedSubTree(
7786fe8751cSGeorge Liu         endpointPath, sdbusplus::message::object_path(path), depth, interfaces,
7796fe8751cSGeorge Liu         [callback = std::move(callback)](
7806fe8751cSGeorge Liu             const boost::system::error_code& ec,
7816fe8751cSGeorge Liu             const dbus::utility::MapperGetSubTreeResponse& subtree) {
7826fe8751cSGeorge Liu             SensorServicePathList sensorsServiceAndPath;
7836fe8751cSGeorge Liu 
7846fe8751cSGeorge Liu             if (ec)
7856fe8751cSGeorge Liu             {
7866fe8751cSGeorge Liu                 callback(ec, sensorsServiceAndPath);
7876fe8751cSGeorge Liu                 return;
7886fe8751cSGeorge Liu             }
7896fe8751cSGeorge Liu 
7906fe8751cSGeorge Liu             for (const auto& [sensorPath, serviceMaps] : subtree)
7916fe8751cSGeorge Liu             {
7926fe8751cSGeorge Liu                 for (const auto& [service, mapInterfaces] : serviceMaps)
7936fe8751cSGeorge Liu                 {
7946fe8751cSGeorge Liu                     sensorsServiceAndPath.emplace_back(service, sensorPath);
7956fe8751cSGeorge Liu                 }
7966fe8751cSGeorge Liu             }
7976fe8751cSGeorge Liu 
7986fe8751cSGeorge Liu             callback(ec, sensorsServiceAndPath);
7996fe8751cSGeorge Liu         });
8006fe8751cSGeorge Liu }
8016fe8751cSGeorge Liu 
8021516c21bSJanet Adkins } // namespace sensor_utils
8031516c21bSJanet Adkins } // namespace redfish
804