1 #pragma once 2 3 #include "dbus_utility.hpp" 4 #include "generated/enums/resource.hpp" 5 #include "generated/enums/sensor.hpp" 6 #include "generated/enums/thermal.hpp" 7 #include "str_utility.hpp" 8 #include "utils/dbus_utils.hpp" 9 #include "utils/json_utils.hpp" 10 11 #include <boost/url/format.hpp> 12 #include <sdbusplus/unpack_properties.hpp> 13 14 #include <algorithm> 15 #include <format> 16 #include <functional> 17 #include <optional> 18 #include <ranges> 19 #include <string> 20 #include <string_view> 21 #include <tuple> 22 #include <utility> 23 #include <vector> 24 25 namespace redfish 26 { 27 namespace sensor_utils 28 { 29 30 enum class ChassisSubNode 31 { 32 powerNode, 33 sensorsNode, 34 thermalNode, 35 thermalMetricsNode, 36 unknownNode, 37 }; 38 39 constexpr std::string_view chassisSubNodeToString(ChassisSubNode subNode) 40 { 41 switch (subNode) 42 { 43 case ChassisSubNode::powerNode: 44 return "Power"; 45 case ChassisSubNode::sensorsNode: 46 return "Sensors"; 47 case ChassisSubNode::thermalNode: 48 return "Thermal"; 49 case ChassisSubNode::thermalMetricsNode: 50 return "ThermalMetrics"; 51 case ChassisSubNode::unknownNode: 52 default: 53 return ""; 54 } 55 } 56 57 inline ChassisSubNode chassisSubNodeFromString(const std::string& subNodeStr) 58 { 59 // If none match unknownNode is returned 60 ChassisSubNode subNode = ChassisSubNode::unknownNode; 61 62 if (subNodeStr == "Power") 63 { 64 subNode = ChassisSubNode::powerNode; 65 } 66 else if (subNodeStr == "Sensors") 67 { 68 subNode = ChassisSubNode::sensorsNode; 69 } 70 else if (subNodeStr == "Thermal") 71 { 72 subNode = ChassisSubNode::thermalNode; 73 } 74 else if (subNodeStr == "ThermalMetrics") 75 { 76 subNode = ChassisSubNode::thermalMetricsNode; 77 } 78 79 return subNode; 80 } 81 82 inline bool isExcerptNode(const ChassisSubNode subNode) 83 { 84 return (subNode == ChassisSubNode::thermalMetricsNode); 85 } 86 87 /** 88 * Possible states for physical inventory leds 89 */ 90 enum class LedState 91 { 92 OFF, 93 ON, 94 BLINK, 95 UNKNOWN 96 }; 97 98 /** 99 * D-Bus inventory item associated with one or more sensors. 100 */ 101 class InventoryItem 102 { 103 public: 104 explicit InventoryItem(const std::string& objPath) : objectPath(objPath) 105 { 106 // Set inventory item name to last node of object path 107 sdbusplus::message::object_path path(objectPath); 108 name = path.filename(); 109 if (name.empty()) 110 { 111 BMCWEB_LOG_ERROR("Failed to find '/' in {}", objectPath); 112 } 113 } 114 115 std::string objectPath; 116 std::string name; 117 bool isPresent = true; 118 bool isFunctional = true; 119 bool isPowerSupply = false; 120 int powerSupplyEfficiencyPercent = -1; 121 std::string manufacturer; 122 std::string model; 123 std::string partNumber; 124 std::string serialNumber; 125 std::set<std::string> sensors; 126 std::string ledObjectPath; 127 LedState ledState = LedState::UNKNOWN; 128 }; 129 130 inline std::string getSensorId(std::string_view sensorName, 131 std::string_view sensorType) 132 { 133 std::string normalizedType(sensorType); 134 auto remove = std::ranges::remove(normalizedType, '_'); 135 normalizedType.erase(std::ranges::begin(remove), normalizedType.end()); 136 137 return std::format("{}_{}", normalizedType, sensorName); 138 } 139 140 inline std::pair<std::string, std::string> 141 splitSensorNameAndType(std::string_view sensorId) 142 { 143 size_t index = sensorId.find('_'); 144 if (index == std::string::npos) 145 { 146 return std::make_pair<std::string, std::string>("", ""); 147 } 148 std::string sensorType{sensorId.substr(0, index)}; 149 std::string sensorName{sensorId.substr(index + 1)}; 150 // fan_pwm and fan_tach need special handling 151 if (sensorType == "fantach" || sensorType == "fanpwm") 152 { 153 sensorType.insert(3, 1, '_'); 154 } 155 return std::make_pair(sensorType, sensorName); 156 } 157 158 namespace sensors 159 { 160 inline std::string_view toReadingUnits(std::string_view sensorType) 161 { 162 if (sensorType == "voltage") 163 { 164 return "V"; 165 } 166 if (sensorType == "power") 167 { 168 return "W"; 169 } 170 if (sensorType == "current") 171 { 172 return "A"; 173 } 174 if (sensorType == "fan_tach") 175 { 176 return "RPM"; 177 } 178 if (sensorType == "temperature") 179 { 180 return "Cel"; 181 } 182 if (sensorType == "fan_pwm" || sensorType == "utilization" || 183 sensorType == "humidity") 184 { 185 return "%"; 186 } 187 if (sensorType == "altitude") 188 { 189 return "m"; 190 } 191 if (sensorType == "airflow") 192 { 193 return "cft_i/min"; 194 } 195 if (sensorType == "energy") 196 { 197 return "J"; 198 } 199 return ""; 200 } 201 202 inline sensor::ReadingType toReadingType(std::string_view sensorType) 203 { 204 if (sensorType == "voltage") 205 { 206 return sensor::ReadingType::Voltage; 207 } 208 if (sensorType == "power") 209 { 210 return sensor::ReadingType::Power; 211 } 212 if (sensorType == "current") 213 { 214 return sensor::ReadingType::Current; 215 } 216 if (sensorType == "fan_tach") 217 { 218 return sensor::ReadingType::Rotational; 219 } 220 if (sensorType == "temperature") 221 { 222 return sensor::ReadingType::Temperature; 223 } 224 if (sensorType == "fan_pwm" || sensorType == "utilization") 225 { 226 return sensor::ReadingType::Percent; 227 } 228 if (sensorType == "humidity") 229 { 230 return sensor::ReadingType::Humidity; 231 } 232 if (sensorType == "altitude") 233 { 234 return sensor::ReadingType::Altitude; 235 } 236 if (sensorType == "airflow") 237 { 238 return sensor::ReadingType::AirFlow; 239 } 240 if (sensorType == "energy") 241 { 242 return sensor::ReadingType::EnergyJoules; 243 } 244 return sensor::ReadingType::Invalid; 245 } 246 247 } // namespace sensors 248 249 /** 250 * @brief Returns the Redfish State value for the specified inventory item. 251 * @param inventoryItem D-Bus inventory item associated with a sensor. 252 * @param sensorAvailable Boolean representing if D-Bus sensor is marked as 253 * available. 254 * @return State value for inventory item. 255 */ 256 inline resource::State getState(const InventoryItem* inventoryItem, 257 const bool sensorAvailable) 258 { 259 if ((inventoryItem != nullptr) && !(inventoryItem->isPresent)) 260 { 261 return resource::State::Absent; 262 } 263 264 if (!sensorAvailable) 265 { 266 return resource::State::UnavailableOffline; 267 } 268 269 return resource::State::Enabled; 270 } 271 272 /** 273 * @brief Returns the Redfish Health value for the specified sensor. 274 * @param sensorJson Sensor JSON object. 275 * @param valuesDict Map of all sensor DBus values. 276 * @param inventoryItem D-Bus inventory item associated with the sensor. Will 277 * be nullptr if no associated inventory item was found. 278 * @return Health value for sensor. 279 */ 280 inline std::string getHealth(nlohmann::json& sensorJson, 281 const dbus::utility::DBusPropertiesMap& valuesDict, 282 const InventoryItem* inventoryItem) 283 { 284 // Get current health value (if any) in the sensor JSON object. Some JSON 285 // objects contain multiple sensors (such as PowerSupplies). We want to set 286 // the overall health to be the most severe of any of the sensors. 287 std::string currentHealth; 288 auto statusIt = sensorJson.find("Status"); 289 if (statusIt != sensorJson.end()) 290 { 291 auto healthIt = statusIt->find("Health"); 292 if (healthIt != statusIt->end()) 293 { 294 std::string* health = healthIt->get_ptr<std::string*>(); 295 if (health != nullptr) 296 { 297 currentHealth = *health; 298 } 299 } 300 } 301 302 // If current health in JSON object is already Critical, return that. This 303 // should override the sensor health, which might be less severe. 304 if (currentHealth == "Critical") 305 { 306 return "Critical"; 307 } 308 309 const bool* criticalAlarmHigh = nullptr; 310 const bool* criticalAlarmLow = nullptr; 311 const bool* warningAlarmHigh = nullptr; 312 const bool* warningAlarmLow = nullptr; 313 314 const bool success = sdbusplus::unpackPropertiesNoThrow( 315 dbus_utils::UnpackErrorPrinter(), valuesDict, "CriticalAlarmHigh", 316 criticalAlarmHigh, "CriticalAlarmLow", criticalAlarmLow, 317 "WarningAlarmHigh", warningAlarmHigh, "WarningAlarmLow", 318 warningAlarmLow); 319 320 if (success) 321 { 322 // Check if sensor has critical threshold alarm 323 if ((criticalAlarmHigh != nullptr && *criticalAlarmHigh) || 324 (criticalAlarmLow != nullptr && *criticalAlarmLow)) 325 { 326 return "Critical"; 327 } 328 } 329 330 // Check if associated inventory item is not functional 331 if ((inventoryItem != nullptr) && !(inventoryItem->isFunctional)) 332 { 333 return "Critical"; 334 } 335 336 // If current health in JSON object is already Warning, return that. This 337 // should override the sensor status, which might be less severe. 338 if (currentHealth == "Warning") 339 { 340 return "Warning"; 341 } 342 343 if (success) 344 { 345 // Check if sensor has warning threshold alarm 346 if ((warningAlarmHigh != nullptr && *warningAlarmHigh) || 347 (warningAlarmLow != nullptr && *warningAlarmLow)) 348 { 349 return "Warning"; 350 } 351 } 352 353 return "OK"; 354 } 355 356 inline void setLedState(nlohmann::json& sensorJson, 357 const InventoryItem* inventoryItem) 358 { 359 if (inventoryItem != nullptr && !inventoryItem->ledObjectPath.empty()) 360 { 361 switch (inventoryItem->ledState) 362 { 363 case LedState::OFF: 364 sensorJson["IndicatorLED"] = resource::IndicatorLED::Off; 365 break; 366 case LedState::ON: 367 sensorJson["IndicatorLED"] = resource::IndicatorLED::Lit; 368 break; 369 case LedState::BLINK: 370 sensorJson["IndicatorLED"] = resource::IndicatorLED::Blinking; 371 break; 372 default: 373 break; 374 } 375 } 376 } 377 378 /** 379 * @brief Builds a json sensor representation of a sensor. 380 * @param sensorName The name of the sensor to be built 381 * @param sensorType The type (temperature, fan_tach, etc) of the sensor to 382 * build 383 * @param chassisSubNode The subnode (thermal, sensor, etc) of the sensor 384 * @param propertiesDict A dictionary of the properties to build the sensor 385 * from. 386 * @param sensorJson The json object to fill 387 * @param inventoryItem D-Bus inventory item associated with the sensor. Will 388 * be nullptr if no associated inventory item was found. 389 */ 390 inline void objectPropertiesToJson( 391 std::string_view sensorName, std::string_view sensorType, 392 ChassisSubNode chassisSubNode, 393 const dbus::utility::DBusPropertiesMap& propertiesDict, 394 nlohmann::json& sensorJson, InventoryItem* inventoryItem) 395 { 396 // Parameter to set to override the type we get from dbus, and force it to 397 // int, regardless of what is available. This is used for schemas like fan, 398 // that require integers, not floats. 399 bool forceToInt = false; 400 401 nlohmann::json::json_pointer unit("/Reading"); 402 403 // This ChassisSubNode builds sensor excerpts 404 bool isExcerpt = isExcerptNode(chassisSubNode); 405 406 /* Sensor excerpts use different keys to reference the sensor. These are 407 * built by the caller. 408 * Additionally they don't include these additional properties. 409 */ 410 if (!isExcerpt) 411 { 412 if (chassisSubNode == ChassisSubNode::sensorsNode) 413 { 414 std::string subNodeEscaped = getSensorId(sensorName, sensorType); 415 // For sensors in SensorCollection we set Id instead of MemberId, 416 // including power sensors. 417 sensorJson["Id"] = std::move(subNodeEscaped); 418 419 std::string sensorNameEs(sensorName); 420 std::replace(sensorNameEs.begin(), sensorNameEs.end(), '_', ' '); 421 sensorJson["Name"] = std::move(sensorNameEs); 422 } 423 else if (sensorType != "power") 424 { 425 // Set MemberId and Name for non-power sensors. For PowerSupplies 426 // and PowerControl, those properties have more general values 427 // because multiple sensors can be stored in the same JSON object. 428 std::string sensorNameEs(sensorName); 429 std::replace(sensorNameEs.begin(), sensorNameEs.end(), '_', ' '); 430 sensorJson["Name"] = std::move(sensorNameEs); 431 } 432 433 const bool* checkAvailable = nullptr; 434 bool available = true; 435 const bool success = sdbusplus::unpackPropertiesNoThrow( 436 dbus_utils::UnpackErrorPrinter(), propertiesDict, "Available", 437 checkAvailable); 438 if (!success) 439 { 440 messages::internalError(); 441 } 442 if (checkAvailable != nullptr) 443 { 444 available = *checkAvailable; 445 } 446 447 sensorJson["Status"]["State"] = getState(inventoryItem, available); 448 sensorJson["Status"]["Health"] = 449 getHealth(sensorJson, propertiesDict, inventoryItem); 450 451 if (chassisSubNode == ChassisSubNode::sensorsNode) 452 { 453 sensorJson["@odata.type"] = "#Sensor.v1_2_0.Sensor"; 454 455 sensor::ReadingType readingType = 456 sensors::toReadingType(sensorType); 457 if (readingType == sensor::ReadingType::Invalid) 458 { 459 BMCWEB_LOG_ERROR("Redfish cannot map reading type for {}", 460 sensorType); 461 } 462 else 463 { 464 sensorJson["ReadingType"] = readingType; 465 } 466 467 std::string_view readingUnits = sensors::toReadingUnits(sensorType); 468 if (readingUnits.empty()) 469 { 470 BMCWEB_LOG_ERROR("Redfish cannot map reading unit for {}", 471 sensorType); 472 } 473 else 474 { 475 sensorJson["ReadingUnits"] = readingUnits; 476 } 477 } 478 else if (sensorType == "temperature") 479 { 480 unit = "/ReadingCelsius"_json_pointer; 481 sensorJson["@odata.type"] = "#Thermal.v1_3_0.Temperature"; 482 // TODO(ed) Documentation says that path should be type fan_tach, 483 // implementation seems to implement fan 484 } 485 else if (sensorType == "fan" || sensorType == "fan_tach") 486 { 487 unit = "/Reading"_json_pointer; 488 sensorJson["ReadingUnits"] = thermal::ReadingUnits::RPM; 489 sensorJson["@odata.type"] = "#Thermal.v1_3_0.Fan"; 490 setLedState(sensorJson, inventoryItem); 491 forceToInt = true; 492 } 493 else if (sensorType == "fan_pwm") 494 { 495 unit = "/Reading"_json_pointer; 496 sensorJson["ReadingUnits"] = thermal::ReadingUnits::Percent; 497 sensorJson["@odata.type"] = "#Thermal.v1_3_0.Fan"; 498 setLedState(sensorJson, inventoryItem); 499 forceToInt = true; 500 } 501 else if (sensorType == "voltage") 502 { 503 unit = "/ReadingVolts"_json_pointer; 504 sensorJson["@odata.type"] = "#Power.v1_0_0.Voltage"; 505 } 506 else if (sensorType == "power") 507 { 508 std::string lower; 509 std::ranges::transform(sensorName, std::back_inserter(lower), 510 bmcweb::asciiToLower); 511 if (lower == "total_power") 512 { 513 sensorJson["@odata.type"] = "#Power.v1_0_0.PowerControl"; 514 // Put multiple "sensors" into a single PowerControl, so have 515 // generic names for MemberId and Name. Follows Redfish mockup. 516 sensorJson["MemberId"] = "0"; 517 sensorJson["Name"] = "Chassis Power Control"; 518 unit = "/PowerConsumedWatts"_json_pointer; 519 } 520 else if (lower.find("input") != std::string::npos) 521 { 522 unit = "/PowerInputWatts"_json_pointer; 523 } 524 else 525 { 526 unit = "/PowerOutputWatts"_json_pointer; 527 } 528 } 529 else 530 { 531 BMCWEB_LOG_ERROR("Redfish cannot map object type for {}", 532 sensorName); 533 return; 534 } 535 } 536 537 // Map of dbus interface name, dbus property name and redfish property_name 538 std::vector< 539 std::tuple<const char*, const char*, nlohmann::json::json_pointer>> 540 properties; 541 542 properties.emplace_back("xyz.openbmc_project.Sensor.Value", "Value", unit); 543 544 if (!isExcerpt) 545 { 546 if (chassisSubNode == ChassisSubNode::sensorsNode) 547 { 548 properties.emplace_back( 549 "xyz.openbmc_project.Sensor.Threshold.Warning", "WarningHigh", 550 "/Thresholds/UpperCaution/Reading"_json_pointer); 551 properties.emplace_back( 552 "xyz.openbmc_project.Sensor.Threshold.Warning", "WarningLow", 553 "/Thresholds/LowerCaution/Reading"_json_pointer); 554 properties.emplace_back( 555 "xyz.openbmc_project.Sensor.Threshold.Critical", "CriticalHigh", 556 "/Thresholds/UpperCritical/Reading"_json_pointer); 557 properties.emplace_back( 558 "xyz.openbmc_project.Sensor.Threshold.Critical", "CriticalLow", 559 "/Thresholds/LowerCritical/Reading"_json_pointer); 560 561 /* Add additional properties specific to sensorType */ 562 if (sensorType == "fan_tach") 563 { 564 properties.emplace_back("xyz.openbmc_project.Sensor.Value", 565 "Value", "/SpeedRPM"_json_pointer); 566 } 567 } 568 else if (sensorType != "power") 569 { 570 properties.emplace_back( 571 "xyz.openbmc_project.Sensor.Threshold.Warning", "WarningHigh", 572 "/UpperThresholdNonCritical"_json_pointer); 573 properties.emplace_back( 574 "xyz.openbmc_project.Sensor.Threshold.Warning", "WarningLow", 575 "/LowerThresholdNonCritical"_json_pointer); 576 properties.emplace_back( 577 "xyz.openbmc_project.Sensor.Threshold.Critical", "CriticalHigh", 578 "/UpperThresholdCritical"_json_pointer); 579 properties.emplace_back( 580 "xyz.openbmc_project.Sensor.Threshold.Critical", "CriticalLow", 581 "/LowerThresholdCritical"_json_pointer); 582 } 583 584 // TODO Need to get UpperThresholdFatal and LowerThresholdFatal 585 586 if (chassisSubNode == ChassisSubNode::sensorsNode) 587 { 588 properties.emplace_back("xyz.openbmc_project.Sensor.Value", 589 "MinValue", 590 "/ReadingRangeMin"_json_pointer); 591 properties.emplace_back("xyz.openbmc_project.Sensor.Value", 592 "MaxValue", 593 "/ReadingRangeMax"_json_pointer); 594 properties.emplace_back("xyz.openbmc_project.Sensor.Accuracy", 595 "Accuracy", "/Accuracy"_json_pointer); 596 } 597 else if (sensorType == "temperature") 598 { 599 properties.emplace_back("xyz.openbmc_project.Sensor.Value", 600 "MinValue", 601 "/MinReadingRangeTemp"_json_pointer); 602 properties.emplace_back("xyz.openbmc_project.Sensor.Value", 603 "MaxValue", 604 "/MaxReadingRangeTemp"_json_pointer); 605 } 606 else if (sensorType != "power") 607 { 608 properties.emplace_back("xyz.openbmc_project.Sensor.Value", 609 "MinValue", 610 "/MinReadingRange"_json_pointer); 611 properties.emplace_back("xyz.openbmc_project.Sensor.Value", 612 "MaxValue", 613 "/MaxReadingRange"_json_pointer); 614 } 615 } 616 617 for (const std::tuple<const char*, const char*, 618 nlohmann::json::json_pointer>& p : properties) 619 { 620 for (const auto& [valueName, valueVariant] : propertiesDict) 621 { 622 if (valueName != std::get<1>(p)) 623 { 624 continue; 625 } 626 627 // The property we want to set may be nested json, so use 628 // a json_pointer for easy indexing into the json structure. 629 const nlohmann::json::json_pointer& key = std::get<2>(p); 630 631 const double* doubleValue = std::get_if<double>(&valueVariant); 632 if (doubleValue == nullptr) 633 { 634 BMCWEB_LOG_ERROR("Got value interface that wasn't double"); 635 continue; 636 } 637 if (!std::isfinite(*doubleValue)) 638 { 639 if (valueName == "Value") 640 { 641 // Readings are allowed to be NAN for unavailable; coerce 642 // them to null in the json response. 643 sensorJson[key] = nullptr; 644 continue; 645 } 646 BMCWEB_LOG_WARNING("Sensor value for {} was unexpectedly {}", 647 valueName, *doubleValue); 648 continue; 649 } 650 if (forceToInt) 651 { 652 sensorJson[key] = static_cast<int64_t>(*doubleValue); 653 } 654 else 655 { 656 sensorJson[key] = *doubleValue; 657 } 658 } 659 } 660 } 661 662 /** 663 * @brief Builds a json sensor excerpt representation of a sensor. 664 * 665 * @details This is a wrapper function to provide consistent setting of 666 * "DataSourceUri" for sensor excerpts and filling of properties. Since sensor 667 * excerpts usually have just the D-Bus path for the sensor that is accepted 668 * and used to build "DataSourceUri". 669 670 * @param path The D-Bus path to the sensor to be built 671 * @param chassisId The Chassis Id for the sensor 672 * @param chassisSubNode The subnode (e.g. ThermalMetrics) of the sensor 673 * @param sensorTypeExpected The expected type of the sensor 674 * @param propertiesDict A dictionary of the properties to build the sensor 675 * from. 676 * @param sensorJson The json object to fill 677 * @returns True if sensorJson object filled. False on any error. 678 * Caller is responsible for handling error. 679 */ 680 inline bool objectExcerptToJson( 681 const std::string& path, const std::string_view chassisId, 682 ChassisSubNode chassisSubNode, 683 const std::optional<std::string>& sensorTypeExpected, 684 const dbus::utility::DBusPropertiesMap& propertiesDict, 685 nlohmann::json& sensorJson) 686 { 687 if (!isExcerptNode(chassisSubNode)) 688 { 689 BMCWEB_LOG_DEBUG("{} is not a sensor excerpt", 690 chassisSubNodeToString(chassisSubNode)); 691 return false; 692 } 693 694 sdbusplus::message::object_path sensorPath(path); 695 std::string sensorName = sensorPath.filename(); 696 std::string sensorType = sensorPath.parent_path().filename(); 697 if (sensorName.empty() || sensorType.empty()) 698 { 699 BMCWEB_LOG_DEBUG("Invalid sensor path {}", path); 700 return false; 701 } 702 703 if (sensorTypeExpected && (sensorType != *sensorTypeExpected)) 704 { 705 BMCWEB_LOG_DEBUG("{} is not expected type {}", path, 706 *sensorTypeExpected); 707 return false; 708 } 709 710 // Sensor excerpts use DataSourceUri to reference full sensor Redfish path 711 sensorJson["DataSourceUri"] = 712 boost::urls::format("/redfish/v1/Chassis/{}/Sensors/{}", chassisId, 713 getSensorId(sensorName, sensorType)); 714 715 // Fill in sensor excerpt properties 716 objectPropertiesToJson(sensorName, sensorType, chassisSubNode, 717 propertiesDict, sensorJson, nullptr); 718 719 return true; 720 } 721 722 // Maps D-Bus: Service, SensorPath 723 using SensorServicePathMap = std::pair<std::string, std::string>; 724 using SensorServicePathList = std::vector<SensorServicePathMap>; 725 726 inline void getAllSensorObjects( 727 const std::string& associatedPath, const std::string& path, 728 std::span<const std::string_view> interfaces, const int32_t depth, 729 std::function<void(const boost::system::error_code& ec, 730 SensorServicePathList&)>&& callback) 731 { 732 sdbusplus::message::object_path endpointPath{associatedPath}; 733 endpointPath /= "all_sensors"; 734 735 dbus::utility::getAssociatedSubTree( 736 endpointPath, sdbusplus::message::object_path(path), depth, interfaces, 737 [callback = std::move(callback)]( 738 const boost::system::error_code& ec, 739 const dbus::utility::MapperGetSubTreeResponse& subtree) { 740 SensorServicePathList sensorsServiceAndPath; 741 742 if (ec) 743 { 744 callback(ec, sensorsServiceAndPath); 745 return; 746 } 747 748 for (const auto& [sensorPath, serviceMaps] : subtree) 749 { 750 for (const auto& [service, mapInterfaces] : serviceMaps) 751 { 752 sensorsServiceAndPath.emplace_back(service, sensorPath); 753 } 754 } 755 756 callback(ec, sensorsServiceAndPath); 757 }); 758 } 759 760 } // namespace sensor_utils 761 } // namespace redfish 762