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> splitSensorNameAndType( 153 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 if (sensorType == "liquidflow") 212 { 213 return "L/min"; 214 } 215 if (sensorType == "pressure") 216 { 217 return "Pa"; 218 } 219 return ""; 220 } 221 222 inline sensor::ReadingType toReadingType(std::string_view sensorType) 223 { 224 if (sensorType == "voltage") 225 { 226 return sensor::ReadingType::Voltage; 227 } 228 if (sensorType == "power") 229 { 230 return sensor::ReadingType::Power; 231 } 232 if (sensorType == "current") 233 { 234 return sensor::ReadingType::Current; 235 } 236 if (sensorType == "fan_tach") 237 { 238 return sensor::ReadingType::Rotational; 239 } 240 if (sensorType == "temperature") 241 { 242 return sensor::ReadingType::Temperature; 243 } 244 if (sensorType == "fan_pwm" || sensorType == "utilization") 245 { 246 return sensor::ReadingType::Percent; 247 } 248 if (sensorType == "humidity") 249 { 250 return sensor::ReadingType::Humidity; 251 } 252 if (sensorType == "altitude") 253 { 254 return sensor::ReadingType::Altitude; 255 } 256 if (sensorType == "airflow") 257 { 258 return sensor::ReadingType::AirFlow; 259 } 260 if (sensorType == "energy") 261 { 262 return sensor::ReadingType::EnergyJoules; 263 } 264 if (sensorType == "liquidflow") 265 { 266 return sensor::ReadingType::LiquidFlowLPM; 267 } 268 if (sensorType == "pressure") 269 { 270 return sensor::ReadingType::PressurePa; 271 } 272 return sensor::ReadingType::Invalid; 273 } 274 275 } // namespace sensors 276 277 /** 278 * @brief Returns the Redfish State value for the specified inventory item. 279 * @param inventoryItem D-Bus inventory item associated with a sensor. 280 * @param sensorAvailable Boolean representing if D-Bus sensor is marked as 281 * available. 282 * @return State value for inventory item. 283 */ 284 inline resource::State getState(const InventoryItem* inventoryItem, 285 const bool sensorAvailable) 286 { 287 if ((inventoryItem != nullptr) && !(inventoryItem->isPresent)) 288 { 289 return resource::State::Absent; 290 } 291 292 if (!sensorAvailable) 293 { 294 return resource::State::UnavailableOffline; 295 } 296 297 return resource::State::Enabled; 298 } 299 300 /** 301 * @brief Returns the Redfish Health value for the specified sensor. 302 * @param sensorJson Sensor JSON object. 303 * @param valuesDict Map of all sensor DBus values. 304 * @param inventoryItem D-Bus inventory item associated with the sensor. Will 305 * be nullptr if no associated inventory item was found. 306 * @return Health value for sensor. 307 */ 308 inline std::string getHealth(nlohmann::json& sensorJson, 309 const dbus::utility::DBusPropertiesMap& valuesDict, 310 const InventoryItem* inventoryItem) 311 { 312 // Get current health value (if any) in the sensor JSON object. Some JSON 313 // objects contain multiple sensors (such as PowerSupplies). We want to set 314 // the overall health to be the most severe of any of the sensors. 315 std::string currentHealth; 316 auto statusIt = sensorJson.find("Status"); 317 if (statusIt != sensorJson.end()) 318 { 319 auto healthIt = statusIt->find("Health"); 320 if (healthIt != statusIt->end()) 321 { 322 std::string* health = healthIt->get_ptr<std::string*>(); 323 if (health != nullptr) 324 { 325 currentHealth = *health; 326 } 327 } 328 } 329 330 // If current health in JSON object is already Critical, return that. This 331 // should override the sensor health, which might be less severe. 332 if (currentHealth == "Critical") 333 { 334 return "Critical"; 335 } 336 337 const bool* criticalAlarmHigh = nullptr; 338 const bool* criticalAlarmLow = nullptr; 339 const bool* warningAlarmHigh = nullptr; 340 const bool* warningAlarmLow = nullptr; 341 342 const bool success = sdbusplus::unpackPropertiesNoThrow( 343 dbus_utils::UnpackErrorPrinter(), valuesDict, "CriticalAlarmHigh", 344 criticalAlarmHigh, "CriticalAlarmLow", criticalAlarmLow, 345 "WarningAlarmHigh", warningAlarmHigh, "WarningAlarmLow", 346 warningAlarmLow); 347 348 if (success) 349 { 350 // Check if sensor has critical threshold alarm 351 if ((criticalAlarmHigh != nullptr && *criticalAlarmHigh) || 352 (criticalAlarmLow != nullptr && *criticalAlarmLow)) 353 { 354 return "Critical"; 355 } 356 } 357 358 // Check if associated inventory item is not functional 359 if ((inventoryItem != nullptr) && !(inventoryItem->isFunctional)) 360 { 361 return "Critical"; 362 } 363 364 // If current health in JSON object is already Warning, return that. This 365 // should override the sensor status, which might be less severe. 366 if (currentHealth == "Warning") 367 { 368 return "Warning"; 369 } 370 371 if (success) 372 { 373 // Check if sensor has warning threshold alarm 374 if ((warningAlarmHigh != nullptr && *warningAlarmHigh) || 375 (warningAlarmLow != nullptr && *warningAlarmLow)) 376 { 377 return "Warning"; 378 } 379 } 380 381 return "OK"; 382 } 383 384 inline void setLedState(nlohmann::json& sensorJson, 385 const InventoryItem* inventoryItem) 386 { 387 if (inventoryItem != nullptr && !inventoryItem->ledObjectPath.empty()) 388 { 389 switch (inventoryItem->ledState) 390 { 391 case LedState::OFF: 392 sensorJson["IndicatorLED"] = resource::IndicatorLED::Off; 393 break; 394 case LedState::ON: 395 sensorJson["IndicatorLED"] = resource::IndicatorLED::Lit; 396 break; 397 case LedState::BLINK: 398 sensorJson["IndicatorLED"] = resource::IndicatorLED::Blinking; 399 break; 400 default: 401 break; 402 } 403 } 404 } 405 406 /** 407 * @brief Builds a json sensor representation of a sensor. 408 * @param sensorName The name of the sensor to be built 409 * @param sensorType The type (temperature, fan_tach, etc) of the sensor to 410 * build 411 * @param chassisSubNode The subnode (thermal, sensor, etc) of the sensor 412 * @param propertiesDict A dictionary of the properties to build the sensor 413 * from. 414 * @param sensorJson The json object to fill 415 * @param inventoryItem D-Bus inventory item associated with the sensor. Will 416 * be nullptr if no associated inventory item was found. 417 */ 418 inline void objectPropertiesToJson( 419 std::string_view sensorName, std::string_view sensorType, 420 ChassisSubNode chassisSubNode, 421 const dbus::utility::DBusPropertiesMap& propertiesDict, 422 nlohmann::json& sensorJson, InventoryItem* inventoryItem) 423 { 424 // Parameter to set to override the type we get from dbus, and force it to 425 // int, regardless of what is available. This is used for schemas like fan, 426 // that require integers, not floats. 427 bool forceToInt = false; 428 429 nlohmann::json::json_pointer unit("/Reading"); 430 431 // This ChassisSubNode builds sensor excerpts 432 bool isExcerpt = isExcerptNode(chassisSubNode); 433 434 /* Sensor excerpts use different keys to reference the sensor. These are 435 * built by the caller. 436 * Additionally they don't include these additional properties. 437 */ 438 if (!isExcerpt) 439 { 440 if (chassisSubNode == ChassisSubNode::sensorsNode) 441 { 442 std::string subNodeEscaped = getSensorId(sensorName, sensorType); 443 // For sensors in SensorCollection we set Id instead of MemberId, 444 // including power sensors. 445 sensorJson["Id"] = std::move(subNodeEscaped); 446 447 std::string sensorNameEs(sensorName); 448 std::replace(sensorNameEs.begin(), sensorNameEs.end(), '_', ' '); 449 sensorJson["Name"] = std::move(sensorNameEs); 450 } 451 else if (sensorType != "power") 452 { 453 // Set MemberId and Name for non-power sensors. For PowerSupplies 454 // and PowerControl, those properties have more general values 455 // because multiple sensors can be stored in the same JSON object. 456 std::string sensorNameEs(sensorName); 457 std::replace(sensorNameEs.begin(), sensorNameEs.end(), '_', ' '); 458 sensorJson["Name"] = std::move(sensorNameEs); 459 } 460 461 const bool* checkAvailable = nullptr; 462 bool available = true; 463 const bool success = sdbusplus::unpackPropertiesNoThrow( 464 dbus_utils::UnpackErrorPrinter(), propertiesDict, "Available", 465 checkAvailable); 466 if (!success) 467 { 468 messages::internalError(); 469 } 470 if (checkAvailable != nullptr) 471 { 472 available = *checkAvailable; 473 } 474 475 sensorJson["Status"]["State"] = getState(inventoryItem, available); 476 sensorJson["Status"]["Health"] = 477 getHealth(sensorJson, propertiesDict, inventoryItem); 478 479 if (chassisSubNode == ChassisSubNode::sensorsNode) 480 { 481 sensorJson["@odata.type"] = "#Sensor.v1_2_0.Sensor"; 482 483 sensor::ReadingType readingType = 484 sensors::toReadingType(sensorType); 485 if (readingType == sensor::ReadingType::Invalid) 486 { 487 BMCWEB_LOG_ERROR("Redfish cannot map reading type for {}", 488 sensorType); 489 } 490 else 491 { 492 sensorJson["ReadingType"] = readingType; 493 } 494 495 std::string_view readingUnits = sensors::toReadingUnits(sensorType); 496 if (readingUnits.empty()) 497 { 498 BMCWEB_LOG_ERROR("Redfish cannot map reading unit for {}", 499 sensorType); 500 } 501 else 502 { 503 sensorJson["ReadingUnits"] = readingUnits; 504 } 505 } 506 else if (sensorType == "temperature") 507 { 508 unit = "/ReadingCelsius"_json_pointer; 509 sensorJson["@odata.type"] = "#Thermal.v1_3_0.Temperature"; 510 // TODO(ed) Documentation says that path should be type fan_tach, 511 // implementation seems to implement fan 512 } 513 else if (sensorType == "fan" || sensorType == "fan_tach") 514 { 515 unit = "/Reading"_json_pointer; 516 sensorJson["ReadingUnits"] = thermal::ReadingUnits::RPM; 517 sensorJson["@odata.type"] = "#Thermal.v1_3_0.Fan"; 518 setLedState(sensorJson, inventoryItem); 519 forceToInt = true; 520 } 521 else if (sensorType == "fan_pwm") 522 { 523 unit = "/Reading"_json_pointer; 524 sensorJson["ReadingUnits"] = thermal::ReadingUnits::Percent; 525 sensorJson["@odata.type"] = "#Thermal.v1_3_0.Fan"; 526 setLedState(sensorJson, inventoryItem); 527 forceToInt = true; 528 } 529 else if (sensorType == "voltage") 530 { 531 unit = "/ReadingVolts"_json_pointer; 532 sensorJson["@odata.type"] = "#Power.v1_0_0.Voltage"; 533 } 534 else if (sensorType == "power") 535 { 536 std::string lower; 537 std::ranges::transform(sensorName, std::back_inserter(lower), 538 bmcweb::asciiToLower); 539 if (lower == "total_power") 540 { 541 sensorJson["@odata.type"] = "#Power.v1_0_0.PowerControl"; 542 // Put multiple "sensors" into a single PowerControl, so have 543 // generic names for MemberId and Name. Follows Redfish mockup. 544 sensorJson["MemberId"] = "0"; 545 sensorJson["Name"] = "Chassis Power Control"; 546 unit = "/PowerConsumedWatts"_json_pointer; 547 } 548 else if (lower.find("input") != std::string::npos) 549 { 550 unit = "/PowerInputWatts"_json_pointer; 551 } 552 else 553 { 554 unit = "/PowerOutputWatts"_json_pointer; 555 } 556 } 557 else 558 { 559 BMCWEB_LOG_ERROR("Redfish cannot map object type for {}", 560 sensorName); 561 return; 562 } 563 } 564 565 // Map of dbus interface name, dbus property name and redfish property_name 566 std::vector< 567 std::tuple<const char*, const char*, nlohmann::json::json_pointer>> 568 properties; 569 570 properties.emplace_back("xyz.openbmc_project.Sensor.Value", "Value", unit); 571 572 if (!isExcerpt) 573 { 574 if (chassisSubNode == ChassisSubNode::sensorsNode) 575 { 576 properties.emplace_back( 577 "xyz.openbmc_project.Sensor.Threshold.Warning", "WarningHigh", 578 "/Thresholds/UpperCaution/Reading"_json_pointer); 579 properties.emplace_back( 580 "xyz.openbmc_project.Sensor.Threshold.Warning", "WarningLow", 581 "/Thresholds/LowerCaution/Reading"_json_pointer); 582 properties.emplace_back( 583 "xyz.openbmc_project.Sensor.Threshold.Critical", "CriticalHigh", 584 "/Thresholds/UpperCritical/Reading"_json_pointer); 585 properties.emplace_back( 586 "xyz.openbmc_project.Sensor.Threshold.Critical", "CriticalLow", 587 "/Thresholds/LowerCritical/Reading"_json_pointer); 588 589 /* Add additional properties specific to sensorType */ 590 if (sensorType == "fan_tach") 591 { 592 properties.emplace_back("xyz.openbmc_project.Sensor.Value", 593 "Value", "/SpeedRPM"_json_pointer); 594 } 595 } 596 else if (sensorType != "power") 597 { 598 properties.emplace_back( 599 "xyz.openbmc_project.Sensor.Threshold.Warning", "WarningHigh", 600 "/UpperThresholdNonCritical"_json_pointer); 601 properties.emplace_back( 602 "xyz.openbmc_project.Sensor.Threshold.Warning", "WarningLow", 603 "/LowerThresholdNonCritical"_json_pointer); 604 properties.emplace_back( 605 "xyz.openbmc_project.Sensor.Threshold.Critical", "CriticalHigh", 606 "/UpperThresholdCritical"_json_pointer); 607 properties.emplace_back( 608 "xyz.openbmc_project.Sensor.Threshold.Critical", "CriticalLow", 609 "/LowerThresholdCritical"_json_pointer); 610 } 611 612 // TODO Need to get UpperThresholdFatal and LowerThresholdFatal 613 614 if (chassisSubNode == ChassisSubNode::sensorsNode) 615 { 616 properties.emplace_back("xyz.openbmc_project.Sensor.Value", 617 "MinValue", 618 "/ReadingRangeMin"_json_pointer); 619 properties.emplace_back("xyz.openbmc_project.Sensor.Value", 620 "MaxValue", 621 "/ReadingRangeMax"_json_pointer); 622 properties.emplace_back("xyz.openbmc_project.Sensor.Accuracy", 623 "Accuracy", "/Accuracy"_json_pointer); 624 } 625 else if (sensorType == "temperature") 626 { 627 properties.emplace_back("xyz.openbmc_project.Sensor.Value", 628 "MinValue", 629 "/MinReadingRangeTemp"_json_pointer); 630 properties.emplace_back("xyz.openbmc_project.Sensor.Value", 631 "MaxValue", 632 "/MaxReadingRangeTemp"_json_pointer); 633 } 634 else if (sensorType != "power") 635 { 636 properties.emplace_back("xyz.openbmc_project.Sensor.Value", 637 "MinValue", 638 "/MinReadingRange"_json_pointer); 639 properties.emplace_back("xyz.openbmc_project.Sensor.Value", 640 "MaxValue", 641 "/MaxReadingRange"_json_pointer); 642 } 643 } 644 645 for (const std::tuple<const char*, const char*, 646 nlohmann::json::json_pointer>& p : properties) 647 { 648 for (const auto& [valueName, valueVariant] : propertiesDict) 649 { 650 if (valueName != std::get<1>(p)) 651 { 652 continue; 653 } 654 655 // The property we want to set may be nested json, so use 656 // a json_pointer for easy indexing into the json structure. 657 const nlohmann::json::json_pointer& key = std::get<2>(p); 658 659 const double* doubleValue = std::get_if<double>(&valueVariant); 660 if (doubleValue == nullptr) 661 { 662 BMCWEB_LOG_ERROR("Got value interface that wasn't double"); 663 continue; 664 } 665 if (!std::isfinite(*doubleValue)) 666 { 667 if (valueName == "Value") 668 { 669 // Readings are allowed to be NAN for unavailable; coerce 670 // them to null in the json response. 671 sensorJson[key] = nullptr; 672 continue; 673 } 674 BMCWEB_LOG_WARNING("Sensor value for {} was unexpectedly {}", 675 valueName, *doubleValue); 676 continue; 677 } 678 if (forceToInt) 679 { 680 sensorJson[key] = static_cast<int64_t>(*doubleValue); 681 } 682 else 683 { 684 sensorJson[key] = *doubleValue; 685 } 686 } 687 } 688 } 689 690 /** 691 * @brief Builds a json sensor excerpt representation of a sensor. 692 * 693 * @details This is a wrapper function to provide consistent setting of 694 * "DataSourceUri" for sensor excerpts and filling of properties. Since sensor 695 * excerpts usually have just the D-Bus path for the sensor that is accepted 696 * and used to build "DataSourceUri". 697 698 * @param path The D-Bus path to the sensor to be built 699 * @param chassisId The Chassis Id for the sensor 700 * @param chassisSubNode The subnode (e.g. ThermalMetrics) of the sensor 701 * @param sensorTypeExpected The expected type of the sensor 702 * @param propertiesDict A dictionary of the properties to build the sensor 703 * from. 704 * @param sensorJson The json object to fill 705 * @returns True if sensorJson object filled. False on any error. 706 * Caller is responsible for handling error. 707 */ 708 inline bool objectExcerptToJson( 709 const std::string& path, const std::string_view chassisId, 710 ChassisSubNode chassisSubNode, 711 const std::optional<std::string>& sensorTypeExpected, 712 const dbus::utility::DBusPropertiesMap& propertiesDict, 713 nlohmann::json& sensorJson) 714 { 715 if (!isExcerptNode(chassisSubNode)) 716 { 717 BMCWEB_LOG_DEBUG("{} is not a sensor excerpt", 718 chassisSubNodeToString(chassisSubNode)); 719 return false; 720 } 721 722 sdbusplus::message::object_path sensorPath(path); 723 std::string sensorName = sensorPath.filename(); 724 std::string sensorType = sensorPath.parent_path().filename(); 725 if (sensorName.empty() || sensorType.empty()) 726 { 727 BMCWEB_LOG_DEBUG("Invalid sensor path {}", path); 728 return false; 729 } 730 731 if (sensorTypeExpected && (sensorType != *sensorTypeExpected)) 732 { 733 BMCWEB_LOG_DEBUG("{} is not expected type {}", path, 734 *sensorTypeExpected); 735 return false; 736 } 737 738 // Sensor excerpts use DataSourceUri to reference full sensor Redfish path 739 sensorJson["DataSourceUri"] = 740 boost::urls::format("/redfish/v1/Chassis/{}/Sensors/{}", chassisId, 741 getSensorId(sensorName, sensorType)); 742 743 // Fill in sensor excerpt properties 744 objectPropertiesToJson(sensorName, sensorType, chassisSubNode, 745 propertiesDict, sensorJson, nullptr); 746 747 return true; 748 } 749 750 // Maps D-Bus: Service, SensorPath 751 using SensorServicePathMap = std::pair<std::string, std::string>; 752 using SensorServicePathList = std::vector<SensorServicePathMap>; 753 754 inline void getAllSensorObjects( 755 const std::string& associatedPath, const std::string& path, 756 std::span<const std::string_view> interfaces, const int32_t depth, 757 std::function<void(const boost::system::error_code& ec, 758 SensorServicePathList&)>&& callback) 759 { 760 sdbusplus::message::object_path endpointPath{associatedPath}; 761 endpointPath /= "all_sensors"; 762 763 dbus::utility::getAssociatedSubTree( 764 endpointPath, sdbusplus::message::object_path(path), depth, interfaces, 765 [callback = std::move(callback)]( 766 const boost::system::error_code& ec, 767 const dbus::utility::MapperGetSubTreeResponse& subtree) { 768 SensorServicePathList sensorsServiceAndPath; 769 770 if (ec) 771 { 772 callback(ec, sensorsServiceAndPath); 773 return; 774 } 775 776 for (const auto& [sensorPath, serviceMaps] : subtree) 777 { 778 for (const auto& [service, mapInterfaces] : serviceMaps) 779 { 780 sensorsServiceAndPath.emplace_back(service, sensorPath); 781 } 782 } 783 784 callback(ec, sensorsServiceAndPath); 785 }); 786 } 787 788 } // namespace sensor_utils 789 } // namespace redfish 790