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