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