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 if constexpr (BMCWEB_REDFISH_ALLOW_DEPRECATED_INDICATORLED) 519 { 520 setLedState(sensorJson, inventoryItem); 521 } 522 forceToInt = true; 523 } 524 else if (sensorType == "fan_pwm") 525 { 526 unit = "/Reading"_json_pointer; 527 sensorJson["ReadingUnits"] = thermal::ReadingUnits::Percent; 528 sensorJson["@odata.type"] = "#Thermal.v1_3_0.Fan"; 529 if constexpr (BMCWEB_REDFISH_ALLOW_DEPRECATED_INDICATORLED) 530 { 531 setLedState(sensorJson, inventoryItem); 532 } 533 forceToInt = true; 534 } 535 else if (sensorType == "voltage") 536 { 537 unit = "/ReadingVolts"_json_pointer; 538 sensorJson["@odata.type"] = "#Power.v1_0_0.Voltage"; 539 } 540 else if (sensorType == "power") 541 { 542 std::string lower; 543 std::ranges::transform(sensorName, std::back_inserter(lower), 544 bmcweb::asciiToLower); 545 if (lower == "total_power") 546 { 547 sensorJson["@odata.type"] = "#Power.v1_0_0.PowerControl"; 548 // Put multiple "sensors" into a single PowerControl, so have 549 // generic names for MemberId and Name. Follows Redfish mockup. 550 sensorJson["MemberId"] = "0"; 551 sensorJson["Name"] = "Chassis Power Control"; 552 unit = "/PowerConsumedWatts"_json_pointer; 553 } 554 else if (lower.find("input") != std::string::npos) 555 { 556 unit = "/PowerInputWatts"_json_pointer; 557 } 558 else 559 { 560 unit = "/PowerOutputWatts"_json_pointer; 561 } 562 } 563 else 564 { 565 BMCWEB_LOG_ERROR("Redfish cannot map object type for {}", 566 sensorName); 567 return; 568 } 569 } 570 571 // Map of dbus interface name, dbus property name and redfish property_name 572 std::vector< 573 std::tuple<const char*, const char*, nlohmann::json::json_pointer>> 574 properties; 575 576 properties.emplace_back("xyz.openbmc_project.Sensor.Value", "Value", unit); 577 578 if (!isExcerpt) 579 { 580 if (chassisSubNode == ChassisSubNode::sensorsNode) 581 { 582 properties.emplace_back( 583 "xyz.openbmc_project.Sensor.Threshold.Warning", "WarningHigh", 584 "/Thresholds/UpperCaution/Reading"_json_pointer); 585 properties.emplace_back( 586 "xyz.openbmc_project.Sensor.Threshold.Warning", "WarningLow", 587 "/Thresholds/LowerCaution/Reading"_json_pointer); 588 properties.emplace_back( 589 "xyz.openbmc_project.Sensor.Threshold.Critical", "CriticalHigh", 590 "/Thresholds/UpperCritical/Reading"_json_pointer); 591 properties.emplace_back( 592 "xyz.openbmc_project.Sensor.Threshold.Critical", "CriticalLow", 593 "/Thresholds/LowerCritical/Reading"_json_pointer); 594 properties.emplace_back( 595 "xyz.openbmc_project.Sensor.Threshold.HardShutdown", 596 "HardShutdownHigh", 597 "/Thresholds/UpperFatal/Reading"_json_pointer); 598 properties.emplace_back( 599 "xyz.openbmc_project.Sensor.Threshold.HardShutdown", 600 "HardShutdownLow", 601 "/Thresholds/LowerFatal/Reading"_json_pointer); 602 603 /* Add additional properties specific to sensorType */ 604 if (sensorType == "fan_tach") 605 { 606 properties.emplace_back("xyz.openbmc_project.Sensor.Value", 607 "Value", "/SpeedRPM"_json_pointer); 608 } 609 } 610 else if (sensorType != "power") 611 { 612 properties.emplace_back( 613 "xyz.openbmc_project.Sensor.Threshold.Warning", "WarningHigh", 614 "/UpperThresholdNonCritical"_json_pointer); 615 properties.emplace_back( 616 "xyz.openbmc_project.Sensor.Threshold.Warning", "WarningLow", 617 "/LowerThresholdNonCritical"_json_pointer); 618 properties.emplace_back( 619 "xyz.openbmc_project.Sensor.Threshold.Critical", "CriticalHigh", 620 "/UpperThresholdCritical"_json_pointer); 621 properties.emplace_back( 622 "xyz.openbmc_project.Sensor.Threshold.Critical", "CriticalLow", 623 "/LowerThresholdCritical"_json_pointer); 624 } 625 626 // TODO Need to get UpperThresholdFatal and LowerThresholdFatal 627 628 if (chassisSubNode == ChassisSubNode::sensorsNode) 629 { 630 properties.emplace_back("xyz.openbmc_project.Sensor.Value", 631 "MinValue", 632 "/ReadingRangeMin"_json_pointer); 633 properties.emplace_back("xyz.openbmc_project.Sensor.Value", 634 "MaxValue", 635 "/ReadingRangeMax"_json_pointer); 636 properties.emplace_back("xyz.openbmc_project.Sensor.Accuracy", 637 "Accuracy", "/Accuracy"_json_pointer); 638 } 639 else if (sensorType == "temperature") 640 { 641 properties.emplace_back("xyz.openbmc_project.Sensor.Value", 642 "MinValue", 643 "/MinReadingRangeTemp"_json_pointer); 644 properties.emplace_back("xyz.openbmc_project.Sensor.Value", 645 "MaxValue", 646 "/MaxReadingRangeTemp"_json_pointer); 647 } 648 else if (sensorType != "power") 649 { 650 properties.emplace_back("xyz.openbmc_project.Sensor.Value", 651 "MinValue", 652 "/MinReadingRange"_json_pointer); 653 properties.emplace_back("xyz.openbmc_project.Sensor.Value", 654 "MaxValue", 655 "/MaxReadingRange"_json_pointer); 656 } 657 } 658 659 for (const std::tuple<const char*, const char*, 660 nlohmann::json::json_pointer>& p : properties) 661 { 662 for (const auto& [valueName, valueVariant] : propertiesDict) 663 { 664 if (valueName != std::get<1>(p)) 665 { 666 continue; 667 } 668 669 // The property we want to set may be nested json, so use 670 // a json_pointer for easy indexing into the json structure. 671 const nlohmann::json::json_pointer& key = std::get<2>(p); 672 673 const double* doubleValue = std::get_if<double>(&valueVariant); 674 if (doubleValue == nullptr) 675 { 676 BMCWEB_LOG_ERROR("Got value interface that wasn't double"); 677 continue; 678 } 679 if (!std::isfinite(*doubleValue)) 680 { 681 if (valueName == "Value") 682 { 683 // Readings are allowed to be NAN for unavailable; coerce 684 // them to null in the json response. 685 sensorJson[key] = nullptr; 686 continue; 687 } 688 BMCWEB_LOG_WARNING("Sensor value for {} was unexpectedly {}", 689 valueName, *doubleValue); 690 continue; 691 } 692 if (forceToInt) 693 { 694 sensorJson[key] = static_cast<int64_t>(*doubleValue); 695 } 696 else 697 { 698 sensorJson[key] = *doubleValue; 699 } 700 } 701 } 702 } 703 704 /** 705 * @brief Builds a json sensor excerpt representation of a sensor. 706 * 707 * @details This is a wrapper function to provide consistent setting of 708 * "DataSourceUri" for sensor excerpts and filling of properties. Since sensor 709 * excerpts usually have just the D-Bus path for the sensor that is accepted 710 * and used to build "DataSourceUri". 711 712 * @param path The D-Bus path to the sensor to be built 713 * @param chassisId The Chassis Id for the sensor 714 * @param chassisSubNode The subnode (e.g. ThermalMetrics) of the sensor 715 * @param sensorTypeExpected The expected type of the sensor 716 * @param propertiesDict A dictionary of the properties to build the sensor 717 * from. 718 * @param sensorJson The json object to fill 719 * @returns True if sensorJson object filled. False on any error. 720 * Caller is responsible for handling error. 721 */ 722 inline bool objectExcerptToJson( 723 const std::string& path, const std::string_view chassisId, 724 ChassisSubNode chassisSubNode, 725 const std::optional<std::string>& sensorTypeExpected, 726 const dbus::utility::DBusPropertiesMap& propertiesDict, 727 nlohmann::json& sensorJson) 728 { 729 if (!isExcerptNode(chassisSubNode)) 730 { 731 BMCWEB_LOG_DEBUG("{} is not a sensor excerpt", 732 chassisSubNodeToString(chassisSubNode)); 733 return false; 734 } 735 736 sdbusplus::message::object_path sensorPath(path); 737 std::string sensorName = sensorPath.filename(); 738 std::string sensorType = sensorPath.parent_path().filename(); 739 if (sensorName.empty() || sensorType.empty()) 740 { 741 BMCWEB_LOG_DEBUG("Invalid sensor path {}", path); 742 return false; 743 } 744 745 if (sensorTypeExpected && (sensorType != *sensorTypeExpected)) 746 { 747 BMCWEB_LOG_DEBUG("{} is not expected type {}", path, 748 *sensorTypeExpected); 749 return false; 750 } 751 752 // Sensor excerpts use DataSourceUri to reference full sensor Redfish path 753 sensorJson["DataSourceUri"] = 754 boost::urls::format("/redfish/v1/Chassis/{}/Sensors/{}", chassisId, 755 getSensorId(sensorName, sensorType)); 756 757 // Fill in sensor excerpt properties 758 objectPropertiesToJson(sensorName, sensorType, chassisSubNode, 759 propertiesDict, sensorJson, nullptr); 760 761 return true; 762 } 763 764 // Maps D-Bus: Service, SensorPath 765 using SensorServicePathMap = std::pair<std::string, std::string>; 766 using SensorServicePathList = std::vector<SensorServicePathMap>; 767 768 inline void getAllSensorObjects( 769 const std::string& associatedPath, const std::string& path, 770 std::span<const std::string_view> interfaces, const int32_t depth, 771 std::function<void(const boost::system::error_code& ec, 772 SensorServicePathList&)>&& callback) 773 { 774 sdbusplus::message::object_path endpointPath{associatedPath}; 775 endpointPath /= "all_sensors"; 776 777 dbus::utility::getAssociatedSubTree( 778 endpointPath, sdbusplus::message::object_path(path), depth, interfaces, 779 [callback = std::move(callback)]( 780 const boost::system::error_code& ec, 781 const dbus::utility::MapperGetSubTreeResponse& subtree) { 782 SensorServicePathList sensorsServiceAndPath; 783 784 if (ec) 785 { 786 callback(ec, sensorsServiceAndPath); 787 return; 788 } 789 790 for (const auto& [sensorPath, serviceMaps] : subtree) 791 { 792 for (const auto& [service, mapInterfaces] : serviceMaps) 793 { 794 sensorsServiceAndPath.emplace_back(service, sensorPath); 795 } 796 } 797 798 callback(ec, sensorsServiceAndPath); 799 }); 800 } 801 802 } // namespace sensor_utils 803 } // namespace redfish 804