1 /* 2 // Copyright (c) 2018 Intel Corporation 3 // 4 // Licensed under the Apache License, Version 2.0 (the "License"); 5 // you may not use this file except in compliance with the License. 6 // You may obtain a copy of the License at 7 // 8 // http://www.apache.org/licenses/LICENSE-2.0 9 // 10 // Unless required by applicable law or agreed to in writing, software 11 // distributed under the License is distributed on an "AS IS" BASIS, 12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 // See the License for the specific language governing permissions and 14 // limitations under the License. 15 */ 16 #pragma once 17 18 #include "app.hpp" 19 #include "dbus_singleton.hpp" 20 #include "dbus_utility.hpp" 21 #include "generated/enums/redundancy.hpp" 22 #include "generated/enums/resource.hpp" 23 #include "generated/enums/sensor.hpp" 24 #include "generated/enums/thermal.hpp" 25 #include "query.hpp" 26 #include "registries/privilege_registry.hpp" 27 #include "str_utility.hpp" 28 #include "utils/dbus_utils.hpp" 29 #include "utils/json_utils.hpp" 30 #include "utils/query_param.hpp" 31 32 #include <boost/system/error_code.hpp> 33 #include <boost/url/format.hpp> 34 #include <sdbusplus/asio/property.hpp> 35 #include <sdbusplus/unpack_properties.hpp> 36 37 #include <array> 38 #include <cmath> 39 #include <iterator> 40 #include <limits> 41 #include <map> 42 #include <ranges> 43 #include <set> 44 #include <string> 45 #include <string_view> 46 #include <utility> 47 #include <variant> 48 49 namespace redfish 50 { 51 52 namespace sensors 53 { 54 namespace node 55 { 56 static constexpr std::string_view power = "Power"; 57 static constexpr std::string_view sensors = "Sensors"; 58 static constexpr std::string_view thermal = "Thermal"; 59 } // namespace node 60 61 // clang-format off 62 namespace dbus 63 { 64 constexpr auto powerPaths = std::to_array<std::string_view>({ 65 "/xyz/openbmc_project/sensors/voltage", 66 "/xyz/openbmc_project/sensors/power" 67 }); 68 69 constexpr auto getSensorPaths(){ 70 if constexpr(BMCWEB_REDFISH_NEW_POWERSUBSYSTEM_THERMALSUBSYSTEM){ 71 return std::to_array<std::string_view>({ 72 "/xyz/openbmc_project/sensors/power", 73 "/xyz/openbmc_project/sensors/current", 74 "/xyz/openbmc_project/sensors/airflow", 75 "/xyz/openbmc_project/sensors/humidity", 76 "/xyz/openbmc_project/sensors/voltage", 77 "/xyz/openbmc_project/sensors/fan_tach", 78 "/xyz/openbmc_project/sensors/temperature", 79 "/xyz/openbmc_project/sensors/fan_pwm", 80 "/xyz/openbmc_project/sensors/altitude", 81 "/xyz/openbmc_project/sensors/energy", 82 "/xyz/openbmc_project/sensors/utilization"}); 83 } else { 84 return std::to_array<std::string_view>({"/xyz/openbmc_project/sensors/power", 85 "/xyz/openbmc_project/sensors/current", 86 "/xyz/openbmc_project/sensors/airflow", 87 "/xyz/openbmc_project/sensors/humidity", 88 "/xyz/openbmc_project/sensors/utilization"}); 89 } 90 } 91 92 constexpr auto sensorPaths = getSensorPaths(); 93 94 constexpr auto thermalPaths = std::to_array<std::string_view>({ 95 "/xyz/openbmc_project/sensors/fan_tach", 96 "/xyz/openbmc_project/sensors/temperature", 97 "/xyz/openbmc_project/sensors/fan_pwm" 98 }); 99 100 } // namespace dbus 101 // clang-format on 102 103 using sensorPair = 104 std::pair<std::string_view, std::span<const std::string_view>>; 105 static constexpr std::array<sensorPair, 3> paths = { 106 {{node::power, dbus::powerPaths}, 107 {node::sensors, dbus::sensorPaths}, 108 {node::thermal, dbus::thermalPaths}}}; 109 110 inline sensor::ReadingType toReadingType(std::string_view sensorType) 111 { 112 if (sensorType == "voltage") 113 { 114 return sensor::ReadingType::Voltage; 115 } 116 if (sensorType == "power") 117 { 118 return sensor::ReadingType::Power; 119 } 120 if (sensorType == "current") 121 { 122 return sensor::ReadingType::Current; 123 } 124 if (sensorType == "fan_tach") 125 { 126 return sensor::ReadingType::Rotational; 127 } 128 if (sensorType == "temperature") 129 { 130 return sensor::ReadingType::Temperature; 131 } 132 if (sensorType == "fan_pwm" || sensorType == "utilization") 133 { 134 return sensor::ReadingType::Percent; 135 } 136 if (sensorType == "humidity") 137 { 138 return sensor::ReadingType::Humidity; 139 } 140 if (sensorType == "altitude") 141 { 142 return sensor::ReadingType::Altitude; 143 } 144 if (sensorType == "airflow") 145 { 146 return sensor::ReadingType::AirFlow; 147 } 148 if (sensorType == "energy") 149 { 150 return sensor::ReadingType::EnergyJoules; 151 } 152 return sensor::ReadingType::Invalid; 153 } 154 155 inline std::string_view toReadingUnits(std::string_view sensorType) 156 { 157 if (sensorType == "voltage") 158 { 159 return "V"; 160 } 161 if (sensorType == "power") 162 { 163 return "W"; 164 } 165 if (sensorType == "current") 166 { 167 return "A"; 168 } 169 if (sensorType == "fan_tach") 170 { 171 return "RPM"; 172 } 173 if (sensorType == "temperature") 174 { 175 return "Cel"; 176 } 177 if (sensorType == "fan_pwm" || sensorType == "utilization" || 178 sensorType == "humidity") 179 { 180 return "%"; 181 } 182 if (sensorType == "altitude") 183 { 184 return "m"; 185 } 186 if (sensorType == "airflow") 187 { 188 return "cft_i/min"; 189 } 190 if (sensorType == "energy") 191 { 192 return "J"; 193 } 194 return ""; 195 } 196 } // namespace sensors 197 198 /** 199 * SensorsAsyncResp 200 * Gathers data needed for response processing after async calls are done 201 */ 202 class SensorsAsyncResp 203 { 204 public: 205 using DataCompleteCb = std::function<void( 206 const boost::beast::http::status status, 207 const std::map<std::string, std::string>& uriToDbus)>; 208 209 struct SensorData 210 { 211 const std::string name; 212 std::string uri; 213 const std::string dbusPath; 214 }; 215 216 SensorsAsyncResp(const std::shared_ptr<bmcweb::AsyncResp>& asyncRespIn, 217 const std::string& chassisIdIn, 218 std::span<const std::string_view> typesIn, 219 std::string_view subNode) : 220 asyncResp(asyncRespIn), 221 chassisId(chassisIdIn), types(typesIn), chassisSubNode(subNode), 222 efficientExpand(false) 223 {} 224 225 // Store extra data about sensor mapping and return it in callback 226 SensorsAsyncResp(const std::shared_ptr<bmcweb::AsyncResp>& asyncRespIn, 227 const std::string& chassisIdIn, 228 std::span<const std::string_view> typesIn, 229 std::string_view subNode, 230 DataCompleteCb&& creationComplete) : 231 asyncResp(asyncRespIn), 232 chassisId(chassisIdIn), types(typesIn), chassisSubNode(subNode), 233 efficientExpand(false), metadata{std::vector<SensorData>()}, 234 dataComplete{std::move(creationComplete)} 235 {} 236 237 // sensor collections expand 238 SensorsAsyncResp(const std::shared_ptr<bmcweb::AsyncResp>& asyncRespIn, 239 const std::string& chassisIdIn, 240 std::span<const std::string_view> typesIn, 241 const std::string_view& subNode, bool efficientExpandIn) : 242 asyncResp(asyncRespIn), 243 chassisId(chassisIdIn), types(typesIn), chassisSubNode(subNode), 244 efficientExpand(efficientExpandIn) 245 {} 246 247 ~SensorsAsyncResp() 248 { 249 if (asyncResp->res.result() == 250 boost::beast::http::status::internal_server_error) 251 { 252 // Reset the json object to clear out any data that made it in 253 // before the error happened todo(ed) handle error condition with 254 // proper code 255 asyncResp->res.jsonValue = nlohmann::json::object(); 256 } 257 258 if (dataComplete && metadata) 259 { 260 std::map<std::string, std::string> map; 261 if (asyncResp->res.result() == boost::beast::http::status::ok) 262 { 263 for (auto& sensor : *metadata) 264 { 265 map.emplace(sensor.uri, sensor.dbusPath); 266 } 267 } 268 dataComplete(asyncResp->res.result(), map); 269 } 270 } 271 272 SensorsAsyncResp(const SensorsAsyncResp&) = delete; 273 SensorsAsyncResp(SensorsAsyncResp&&) = delete; 274 SensorsAsyncResp& operator=(const SensorsAsyncResp&) = delete; 275 SensorsAsyncResp& operator=(SensorsAsyncResp&&) = delete; 276 277 void addMetadata(const nlohmann::json& sensorObject, 278 const std::string& dbusPath) 279 { 280 if (metadata) 281 { 282 metadata->emplace_back(SensorData{ 283 sensorObject["Name"], sensorObject["@odata.id"], dbusPath}); 284 } 285 } 286 287 void updateUri(const std::string& name, const std::string& uri) 288 { 289 if (metadata) 290 { 291 for (auto& sensor : *metadata) 292 { 293 if (sensor.name == name) 294 { 295 sensor.uri = uri; 296 } 297 } 298 } 299 } 300 301 const std::shared_ptr<bmcweb::AsyncResp> asyncResp; 302 const std::string chassisId; 303 const std::span<const std::string_view> types; 304 const std::string chassisSubNode; 305 const bool efficientExpand; 306 307 private: 308 std::optional<std::vector<SensorData>> metadata; 309 DataCompleteCb dataComplete; 310 }; 311 312 /** 313 * Possible states for physical inventory leds 314 */ 315 enum class LedState 316 { 317 OFF, 318 ON, 319 BLINK, 320 UNKNOWN 321 }; 322 323 /** 324 * D-Bus inventory item associated with one or more sensors. 325 */ 326 class InventoryItem 327 { 328 public: 329 explicit InventoryItem(const std::string& objPath) : objectPath(objPath) 330 { 331 // Set inventory item name to last node of object path 332 sdbusplus::message::object_path path(objectPath); 333 name = path.filename(); 334 if (name.empty()) 335 { 336 BMCWEB_LOG_ERROR("Failed to find '/' in {}", objectPath); 337 } 338 } 339 340 std::string objectPath; 341 std::string name; 342 bool isPresent = true; 343 bool isFunctional = true; 344 bool isPowerSupply = false; 345 int powerSupplyEfficiencyPercent = -1; 346 std::string manufacturer; 347 std::string model; 348 std::string partNumber; 349 std::string serialNumber; 350 std::set<std::string> sensors; 351 std::string ledObjectPath; 352 LedState ledState = LedState::UNKNOWN; 353 }; 354 355 /** 356 * @brief Get objects with connection necessary for sensors 357 * @param SensorsAsyncResp Pointer to object holding response data 358 * @param sensorNames Sensors retrieved from chassis 359 * @param callback Callback for processing gathered connections 360 */ 361 template <typename Callback> 362 void getObjectsWithConnection( 363 const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp, 364 const std::shared_ptr<std::set<std::string>>& sensorNames, 365 Callback&& callback) 366 { 367 BMCWEB_LOG_DEBUG("getObjectsWithConnection enter"); 368 const std::string path = "/xyz/openbmc_project/sensors"; 369 constexpr std::array<std::string_view, 1> interfaces = { 370 "xyz.openbmc_project.Sensor.Value"}; 371 372 // Make call to ObjectMapper to find all sensors objects 373 dbus::utility::getSubTree( 374 path, 2, interfaces, 375 [callback = std::forward<Callback>(callback), sensorsAsyncResp, 376 sensorNames](const boost::system::error_code& ec, 377 const dbus::utility::MapperGetSubTreeResponse& subtree) { 378 // Response handler for parsing objects subtree 379 BMCWEB_LOG_DEBUG("getObjectsWithConnection resp_handler enter"); 380 if (ec) 381 { 382 messages::internalError(sensorsAsyncResp->asyncResp->res); 383 BMCWEB_LOG_ERROR( 384 "getObjectsWithConnection resp_handler: Dbus error {}", ec); 385 return; 386 } 387 388 BMCWEB_LOG_DEBUG("Found {} subtrees", subtree.size()); 389 390 // Make unique list of connections only for requested sensor types and 391 // found in the chassis 392 std::set<std::string> connections; 393 std::set<std::pair<std::string, std::string>> objectsWithConnection; 394 395 BMCWEB_LOG_DEBUG("sensorNames list count: {}", sensorNames->size()); 396 for (const std::string& tsensor : *sensorNames) 397 { 398 BMCWEB_LOG_DEBUG("Sensor to find: {}", tsensor); 399 } 400 401 for (const std::pair< 402 std::string, 403 std::vector<std::pair<std::string, std::vector<std::string>>>>& 404 object : subtree) 405 { 406 if (sensorNames->find(object.first) != sensorNames->end()) 407 { 408 for (const std::pair<std::string, std::vector<std::string>>& 409 objData : object.second) 410 { 411 BMCWEB_LOG_DEBUG("Adding connection: {}", objData.first); 412 connections.insert(objData.first); 413 objectsWithConnection.insert( 414 std::make_pair(object.first, objData.first)); 415 } 416 } 417 } 418 BMCWEB_LOG_DEBUG("Found {} connections", connections.size()); 419 callback(std::move(connections), std::move(objectsWithConnection)); 420 BMCWEB_LOG_DEBUG("getObjectsWithConnection resp_handler exit"); 421 }); 422 BMCWEB_LOG_DEBUG("getObjectsWithConnection exit"); 423 } 424 425 /** 426 * @brief Create connections necessary for sensors 427 * @param SensorsAsyncResp Pointer to object holding response data 428 * @param sensorNames Sensors retrieved from chassis 429 * @param callback Callback for processing gathered connections 430 */ 431 template <typename Callback> 432 void getConnections(std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp, 433 const std::shared_ptr<std::set<std::string>> sensorNames, 434 Callback&& callback) 435 { 436 auto objectsWithConnectionCb = 437 [callback = std::forward<Callback>(callback)]( 438 const std::set<std::string>& connections, 439 const std::set<std::pair<std::string, std::string>>& 440 /*objectsWithConnection*/) { callback(connections); }; 441 getObjectsWithConnection(sensorsAsyncResp, sensorNames, 442 std::move(objectsWithConnectionCb)); 443 } 444 445 /** 446 * @brief Shrinks the list of sensors for processing 447 * @param SensorsAysncResp The class holding the Redfish response 448 * @param allSensors A list of all the sensors associated to the 449 * chassis element (i.e. baseboard, front panel, etc...) 450 * @param activeSensors A list that is a reduction of the incoming 451 * allSensors list. Eliminate Thermal sensors when a Power request is 452 * made, and eliminate Power sensors when a Thermal request is made. 453 */ 454 inline void reduceSensorList( 455 crow::Response& res, std::string_view chassisSubNode, 456 std::span<const std::string_view> sensorTypes, 457 const std::vector<std::string>* allSensors, 458 const std::shared_ptr<std::set<std::string>>& activeSensors) 459 { 460 if ((allSensors == nullptr) || (activeSensors == nullptr)) 461 { 462 messages::resourceNotFound(res, chassisSubNode, 463 chassisSubNode == sensors::node::thermal 464 ? "Temperatures" 465 : "Voltages"); 466 467 return; 468 } 469 if (allSensors->empty()) 470 { 471 // Nothing to do, the activeSensors object is also empty 472 return; 473 } 474 475 for (std::string_view type : sensorTypes) 476 { 477 for (const std::string& sensor : *allSensors) 478 { 479 if (sensor.starts_with(type)) 480 { 481 activeSensors->emplace(sensor); 482 } 483 } 484 } 485 } 486 487 /* 488 *Populates the top level collection for a given subnode. Populates 489 *SensorCollection, Power, or Thermal schemas. 490 * 491 * */ 492 inline void populateChassisNode(nlohmann::json& jsonValue, 493 std::string_view chassisSubNode) 494 { 495 if (chassisSubNode == sensors::node::power) 496 { 497 jsonValue["@odata.type"] = "#Power.v1_5_2.Power"; 498 } 499 else if (chassisSubNode == sensors::node::thermal) 500 { 501 jsonValue["@odata.type"] = "#Thermal.v1_4_0.Thermal"; 502 jsonValue["Fans"] = nlohmann::json::array(); 503 jsonValue["Temperatures"] = nlohmann::json::array(); 504 } 505 else if (chassisSubNode == sensors::node::sensors) 506 { 507 jsonValue["@odata.type"] = "#SensorCollection.SensorCollection"; 508 jsonValue["Description"] = "Collection of Sensors for this Chassis"; 509 jsonValue["Members"] = nlohmann::json::array(); 510 jsonValue["Members@odata.count"] = 0; 511 } 512 513 if (chassisSubNode != sensors::node::sensors) 514 { 515 jsonValue["Id"] = chassisSubNode; 516 } 517 jsonValue["Name"] = chassisSubNode; 518 } 519 520 /** 521 * @brief Retrieves requested chassis sensors and redundancy data from DBus . 522 * @param SensorsAsyncResp Pointer to object holding response data 523 * @param callback Callback for next step in gathered sensor processing 524 */ 525 template <typename Callback> 526 void getChassis(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 527 std::string_view chassisId, std::string_view chassisSubNode, 528 std::span<const std::string_view> sensorTypes, 529 Callback&& callback) 530 { 531 BMCWEB_LOG_DEBUG("getChassis enter"); 532 constexpr std::array<std::string_view, 2> interfaces = { 533 "xyz.openbmc_project.Inventory.Item.Board", 534 "xyz.openbmc_project.Inventory.Item.Chassis"}; 535 536 // Get the Chassis Collection 537 dbus::utility::getSubTreePaths( 538 "/xyz/openbmc_project/inventory", 0, interfaces, 539 [callback = std::forward<Callback>(callback), asyncResp, 540 chassisIdStr{std::string(chassisId)}, 541 chassisSubNode{std::string(chassisSubNode)}, sensorTypes]( 542 const boost::system::error_code& ec, 543 const dbus::utility::MapperGetSubTreePathsResponse& chassisPaths) { 544 BMCWEB_LOG_DEBUG("getChassis respHandler enter"); 545 if (ec) 546 { 547 BMCWEB_LOG_ERROR("getChassis respHandler DBUS error: {}", ec); 548 messages::internalError(asyncResp->res); 549 return; 550 } 551 const std::string* chassisPath = nullptr; 552 for (const std::string& chassis : chassisPaths) 553 { 554 sdbusplus::message::object_path path(chassis); 555 std::string chassisName = path.filename(); 556 if (chassisName.empty()) 557 { 558 BMCWEB_LOG_ERROR("Failed to find '/' in {}", chassis); 559 continue; 560 } 561 if (chassisName == chassisIdStr) 562 { 563 chassisPath = &chassis; 564 break; 565 } 566 } 567 if (chassisPath == nullptr) 568 { 569 messages::resourceNotFound(asyncResp->res, "Chassis", chassisIdStr); 570 return; 571 } 572 populateChassisNode(asyncResp->res.jsonValue, chassisSubNode); 573 574 asyncResp->res.jsonValue["@odata.id"] = boost::urls::format( 575 "/redfish/v1/Chassis/{}/{}", chassisIdStr, chassisSubNode); 576 577 // Get the list of all sensors for this Chassis element 578 std::string sensorPath = *chassisPath + "/all_sensors"; 579 dbus::utility::getAssociationEndPoints( 580 sensorPath, 581 [asyncResp, chassisSubNode, sensorTypes, 582 callback = std::forward<const Callback>(callback)]( 583 const boost::system::error_code& ec2, 584 const dbus::utility::MapperEndPoints& nodeSensorList) { 585 if (ec2) 586 { 587 if (ec2.value() != EBADR) 588 { 589 messages::internalError(asyncResp->res); 590 return; 591 } 592 } 593 const std::shared_ptr<std::set<std::string>> culledSensorList = 594 std::make_shared<std::set<std::string>>(); 595 reduceSensorList(asyncResp->res, chassisSubNode, sensorTypes, 596 &nodeSensorList, culledSensorList); 597 BMCWEB_LOG_DEBUG("Finishing with {}", culledSensorList->size()); 598 callback(culledSensorList); 599 }); 600 }); 601 BMCWEB_LOG_DEBUG("getChassis exit"); 602 } 603 604 /** 605 * @brief Returns the Redfish State value for the specified inventory item. 606 * @param inventoryItem D-Bus inventory item associated with a sensor. 607 * @param sensorAvailable Boolean representing if D-Bus sensor is marked as 608 * available. 609 * @return State value for inventory item. 610 */ 611 inline resource::State getState(const InventoryItem* inventoryItem, 612 const bool sensorAvailable) 613 { 614 if ((inventoryItem != nullptr) && !(inventoryItem->isPresent)) 615 { 616 return resource::State::Absent; 617 } 618 619 if (!sensorAvailable) 620 { 621 return resource::State::UnavailableOffline; 622 } 623 624 return resource::State::Enabled; 625 } 626 627 /** 628 * @brief Returns the Redfish Health value for the specified sensor. 629 * @param sensorJson Sensor JSON object. 630 * @param valuesDict Map of all sensor DBus values. 631 * @param inventoryItem D-Bus inventory item associated with the sensor. Will 632 * be nullptr if no associated inventory item was found. 633 * @return Health value for sensor. 634 */ 635 inline std::string getHealth(nlohmann::json& sensorJson, 636 const dbus::utility::DBusPropertiesMap& valuesDict, 637 const InventoryItem* inventoryItem) 638 { 639 // Get current health value (if any) in the sensor JSON object. Some JSON 640 // objects contain multiple sensors (such as PowerSupplies). We want to set 641 // the overall health to be the most severe of any of the sensors. 642 std::string currentHealth; 643 auto statusIt = sensorJson.find("Status"); 644 if (statusIt != sensorJson.end()) 645 { 646 auto healthIt = statusIt->find("Health"); 647 if (healthIt != statusIt->end()) 648 { 649 std::string* health = healthIt->get_ptr<std::string*>(); 650 if (health != nullptr) 651 { 652 currentHealth = *health; 653 } 654 } 655 } 656 657 // If current health in JSON object is already Critical, return that. This 658 // should override the sensor health, which might be less severe. 659 if (currentHealth == "Critical") 660 { 661 return "Critical"; 662 } 663 664 const bool* criticalAlarmHigh = nullptr; 665 const bool* criticalAlarmLow = nullptr; 666 const bool* warningAlarmHigh = nullptr; 667 const bool* warningAlarmLow = nullptr; 668 669 const bool success = sdbusplus::unpackPropertiesNoThrow( 670 dbus_utils::UnpackErrorPrinter(), valuesDict, "CriticalAlarmHigh", 671 criticalAlarmHigh, "CriticalAlarmLow", criticalAlarmLow, 672 "WarningAlarmHigh", warningAlarmHigh, "WarningAlarmLow", 673 warningAlarmLow); 674 675 if (success) 676 { 677 // Check if sensor has critical threshold alarm 678 if ((criticalAlarmHigh != nullptr && *criticalAlarmHigh) || 679 (criticalAlarmLow != nullptr && *criticalAlarmLow)) 680 { 681 return "Critical"; 682 } 683 } 684 685 // Check if associated inventory item is not functional 686 if ((inventoryItem != nullptr) && !(inventoryItem->isFunctional)) 687 { 688 return "Critical"; 689 } 690 691 // If current health in JSON object is already Warning, return that. This 692 // should override the sensor status, which might be less severe. 693 if (currentHealth == "Warning") 694 { 695 return "Warning"; 696 } 697 698 if (success) 699 { 700 // Check if sensor has warning threshold alarm 701 if ((warningAlarmHigh != nullptr && *warningAlarmHigh) || 702 (warningAlarmLow != nullptr && *warningAlarmLow)) 703 { 704 return "Warning"; 705 } 706 } 707 708 return "OK"; 709 } 710 711 inline void setLedState(nlohmann::json& sensorJson, 712 const InventoryItem* inventoryItem) 713 { 714 if (inventoryItem != nullptr && !inventoryItem->ledObjectPath.empty()) 715 { 716 switch (inventoryItem->ledState) 717 { 718 case LedState::OFF: 719 sensorJson["IndicatorLED"] = resource::IndicatorLED::Off; 720 break; 721 case LedState::ON: 722 sensorJson["IndicatorLED"] = resource::IndicatorLED::Lit; 723 break; 724 case LedState::BLINK: 725 sensorJson["IndicatorLED"] = resource::IndicatorLED::Blinking; 726 break; 727 default: 728 break; 729 } 730 } 731 } 732 733 /** 734 * @brief Builds a json sensor representation of a sensor. 735 * @param sensorName The name of the sensor to be built 736 * @param sensorType The type (temperature, fan_tach, etc) of the sensor to 737 * build 738 * @param chassisSubNode The subnode (thermal, sensor, etc) of the sensor 739 * @param propertiesDict A dictionary of the properties to build the sensor 740 * from. 741 * @param sensorJson The json object to fill 742 * @param inventoryItem D-Bus inventory item associated with the sensor. Will 743 * be nullptr if no associated inventory item was found. 744 */ 745 inline void objectPropertiesToJson( 746 std::string_view sensorName, std::string_view sensorType, 747 std::string_view chassisSubNode, 748 const dbus::utility::DBusPropertiesMap& propertiesDict, 749 nlohmann::json& sensorJson, InventoryItem* inventoryItem) 750 { 751 if (chassisSubNode == sensors::node::sensors) 752 { 753 std::string subNodeEscaped(sensorType); 754 auto remove = std::ranges::remove(subNodeEscaped, '_'); 755 subNodeEscaped.erase(std::ranges::begin(remove), subNodeEscaped.end()); 756 757 // For sensors in SensorCollection we set Id instead of MemberId, 758 // including power sensors. 759 subNodeEscaped += '_'; 760 subNodeEscaped += sensorName; 761 sensorJson["Id"] = std::move(subNodeEscaped); 762 763 std::string sensorNameEs(sensorName); 764 std::replace(sensorNameEs.begin(), sensorNameEs.end(), '_', ' '); 765 sensorJson["Name"] = std::move(sensorNameEs); 766 } 767 else if (sensorType != "power") 768 { 769 // Set MemberId and Name for non-power sensors. For PowerSupplies and 770 // PowerControl, those properties have more general values because 771 // multiple sensors can be stored in the same JSON object. 772 std::string sensorNameEs(sensorName); 773 std::replace(sensorNameEs.begin(), sensorNameEs.end(), '_', ' '); 774 sensorJson["Name"] = std::move(sensorNameEs); 775 } 776 777 const bool* checkAvailable = nullptr; 778 bool available = true; 779 const bool success = sdbusplus::unpackPropertiesNoThrow( 780 dbus_utils::UnpackErrorPrinter(), propertiesDict, "Available", 781 checkAvailable); 782 if (!success) 783 { 784 messages::internalError(); 785 } 786 if (checkAvailable != nullptr) 787 { 788 available = *checkAvailable; 789 } 790 791 sensorJson["Status"]["State"] = getState(inventoryItem, available); 792 sensorJson["Status"]["Health"] = getHealth(sensorJson, propertiesDict, 793 inventoryItem); 794 795 // Parameter to set to override the type we get from dbus, and force it to 796 // int, regardless of what is available. This is used for schemas like fan, 797 // that require integers, not floats. 798 bool forceToInt = false; 799 800 nlohmann::json::json_pointer unit("/Reading"); 801 if (chassisSubNode == sensors::node::sensors) 802 { 803 sensorJson["@odata.type"] = "#Sensor.v1_2_0.Sensor"; 804 805 sensor::ReadingType readingType = sensors::toReadingType(sensorType); 806 if (readingType == sensor::ReadingType::Invalid) 807 { 808 BMCWEB_LOG_ERROR("Redfish cannot map reading type for {}", 809 sensorType); 810 } 811 else 812 { 813 sensorJson["ReadingType"] = readingType; 814 } 815 816 std::string_view readingUnits = sensors::toReadingUnits(sensorType); 817 if (readingUnits.empty()) 818 { 819 BMCWEB_LOG_ERROR("Redfish cannot map reading unit for {}", 820 sensorType); 821 } 822 else 823 { 824 sensorJson["ReadingUnits"] = readingUnits; 825 } 826 } 827 else if (sensorType == "temperature") 828 { 829 unit = "/ReadingCelsius"_json_pointer; 830 sensorJson["@odata.type"] = "#Thermal.v1_3_0.Temperature"; 831 // TODO(ed) Documentation says that path should be type fan_tach, 832 // implementation seems to implement fan 833 } 834 else if (sensorType == "fan" || sensorType == "fan_tach") 835 { 836 unit = "/Reading"_json_pointer; 837 sensorJson["ReadingUnits"] = thermal::ReadingUnits::RPM; 838 sensorJson["@odata.type"] = "#Thermal.v1_3_0.Fan"; 839 setLedState(sensorJson, inventoryItem); 840 forceToInt = true; 841 } 842 else if (sensorType == "fan_pwm") 843 { 844 unit = "/Reading"_json_pointer; 845 sensorJson["ReadingUnits"] = thermal::ReadingUnits::Percent; 846 sensorJson["@odata.type"] = "#Thermal.v1_3_0.Fan"; 847 setLedState(sensorJson, inventoryItem); 848 forceToInt = true; 849 } 850 else if (sensorType == "voltage") 851 { 852 unit = "/ReadingVolts"_json_pointer; 853 sensorJson["@odata.type"] = "#Power.v1_0_0.Voltage"; 854 } 855 else if (sensorType == "power") 856 { 857 std::string lower; 858 std::ranges::transform(sensorName, std::back_inserter(lower), 859 bmcweb::asciiToLower); 860 if (lower == "total_power") 861 { 862 sensorJson["@odata.type"] = "#Power.v1_0_0.PowerControl"; 863 // Put multiple "sensors" into a single PowerControl, so have 864 // generic names for MemberId and Name. Follows Redfish mockup. 865 sensorJson["MemberId"] = "0"; 866 sensorJson["Name"] = "Chassis Power Control"; 867 unit = "/PowerConsumedWatts"_json_pointer; 868 } 869 else if (lower.find("input") != std::string::npos) 870 { 871 unit = "/PowerInputWatts"_json_pointer; 872 } 873 else 874 { 875 unit = "/PowerOutputWatts"_json_pointer; 876 } 877 } 878 else 879 { 880 BMCWEB_LOG_ERROR("Redfish cannot map object type for {}", sensorName); 881 return; 882 } 883 // Map of dbus interface name, dbus property name and redfish property_name 884 std::vector< 885 std::tuple<const char*, const char*, nlohmann::json::json_pointer>> 886 properties; 887 properties.reserve(7); 888 889 properties.emplace_back("xyz.openbmc_project.Sensor.Value", "Value", unit); 890 891 if (chassisSubNode == sensors::node::sensors) 892 { 893 properties.emplace_back( 894 "xyz.openbmc_project.Sensor.Threshold.Warning", "WarningHigh", 895 "/Thresholds/UpperCaution/Reading"_json_pointer); 896 properties.emplace_back( 897 "xyz.openbmc_project.Sensor.Threshold.Warning", "WarningLow", 898 "/Thresholds/LowerCaution/Reading"_json_pointer); 899 properties.emplace_back( 900 "xyz.openbmc_project.Sensor.Threshold.Critical", "CriticalHigh", 901 "/Thresholds/UpperCritical/Reading"_json_pointer); 902 properties.emplace_back( 903 "xyz.openbmc_project.Sensor.Threshold.Critical", "CriticalLow", 904 "/Thresholds/LowerCritical/Reading"_json_pointer); 905 } 906 else if (sensorType != "power") 907 { 908 properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Warning", 909 "WarningHigh", 910 "/UpperThresholdNonCritical"_json_pointer); 911 properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Warning", 912 "WarningLow", 913 "/LowerThresholdNonCritical"_json_pointer); 914 properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Critical", 915 "CriticalHigh", 916 "/UpperThresholdCritical"_json_pointer); 917 properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Critical", 918 "CriticalLow", 919 "/LowerThresholdCritical"_json_pointer); 920 } 921 922 // TODO Need to get UpperThresholdFatal and LowerThresholdFatal 923 924 if (chassisSubNode == sensors::node::sensors) 925 { 926 properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MinValue", 927 "/ReadingRangeMin"_json_pointer); 928 properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MaxValue", 929 "/ReadingRangeMax"_json_pointer); 930 properties.emplace_back("xyz.openbmc_project.Sensor.Accuracy", 931 "Accuracy", "/Accuracy"_json_pointer); 932 } 933 else if (sensorType == "temperature") 934 { 935 properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MinValue", 936 "/MinReadingRangeTemp"_json_pointer); 937 properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MaxValue", 938 "/MaxReadingRangeTemp"_json_pointer); 939 } 940 else if (sensorType != "power") 941 { 942 properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MinValue", 943 "/MinReadingRange"_json_pointer); 944 properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MaxValue", 945 "/MaxReadingRange"_json_pointer); 946 } 947 948 for (const std::tuple<const char*, const char*, 949 nlohmann::json::json_pointer>& p : properties) 950 { 951 for (const auto& [valueName, valueVariant] : propertiesDict) 952 { 953 if (valueName != std::get<1>(p)) 954 { 955 continue; 956 } 957 958 // The property we want to set may be nested json, so use 959 // a json_pointer for easy indexing into the json structure. 960 const nlohmann::json::json_pointer& key = std::get<2>(p); 961 962 const double* doubleValue = std::get_if<double>(&valueVariant); 963 if (doubleValue == nullptr) 964 { 965 BMCWEB_LOG_ERROR("Got value interface that wasn't double"); 966 continue; 967 } 968 if (!std::isfinite(*doubleValue)) 969 { 970 if (valueName == "Value") 971 { 972 // Readings are allowed to be NAN for unavailable; coerce 973 // them to null in the json response. 974 sensorJson[key] = nullptr; 975 continue; 976 } 977 BMCWEB_LOG_WARNING("Sensor value for {} was unexpectedly {}", 978 valueName, *doubleValue); 979 continue; 980 } 981 if (forceToInt) 982 { 983 sensorJson[key] = static_cast<int64_t>(*doubleValue); 984 } 985 else 986 { 987 sensorJson[key] = *doubleValue; 988 } 989 } 990 } 991 } 992 993 /** 994 * @brief Builds a json sensor representation of a sensor. 995 * @param sensorName The name of the sensor to be built 996 * @param sensorType The type (temperature, fan_tach, etc) of the sensor to 997 * build 998 * @param chassisSubNode The subnode (thermal, sensor, etc) of the sensor 999 * @param interfacesDict A dictionary of the interfaces and properties of said 1000 * interfaces to be built from 1001 * @param sensorJson The json object to fill 1002 * @param inventoryItem D-Bus inventory item associated with the sensor. Will 1003 * be nullptr if no associated inventory item was found. 1004 */ 1005 inline void objectInterfacesToJson( 1006 const std::string& sensorName, const std::string& sensorType, 1007 const std::string& chassisSubNode, 1008 const dbus::utility::DBusInterfacesMap& interfacesDict, 1009 nlohmann::json& sensorJson, InventoryItem* inventoryItem) 1010 { 1011 for (const auto& [interface, valuesDict] : interfacesDict) 1012 { 1013 objectPropertiesToJson(sensorName, sensorType, chassisSubNode, 1014 valuesDict, sensorJson, inventoryItem); 1015 } 1016 BMCWEB_LOG_DEBUG("Added sensor {}", sensorName); 1017 } 1018 1019 inline void populateFanRedundancy( 1020 const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp) 1021 { 1022 constexpr std::array<std::string_view, 1> interfaces = { 1023 "xyz.openbmc_project.Control.FanRedundancy"}; 1024 dbus::utility::getSubTree( 1025 "/xyz/openbmc_project/control", 2, interfaces, 1026 [sensorsAsyncResp]( 1027 const boost::system::error_code& ec, 1028 const dbus::utility::MapperGetSubTreeResponse& resp) { 1029 if (ec) 1030 { 1031 return; // don't have to have this interface 1032 } 1033 for (const std::pair<std::string, dbus::utility::MapperServiceMap>& 1034 pathPair : resp) 1035 { 1036 const std::string& path = pathPair.first; 1037 const dbus::utility::MapperServiceMap& objDict = pathPair.second; 1038 if (objDict.empty()) 1039 { 1040 continue; // this should be impossible 1041 } 1042 1043 const std::string& owner = objDict.begin()->first; 1044 dbus::utility::getAssociationEndPoints( 1045 path + "/chassis", 1046 [path, owner, sensorsAsyncResp]( 1047 const boost::system::error_code& ec2, 1048 const dbus::utility::MapperEndPoints& endpoints) { 1049 if (ec2) 1050 { 1051 return; // if they don't have an association we 1052 // can't tell what chassis is 1053 } 1054 auto found = std::ranges::find_if( 1055 endpoints, [sensorsAsyncResp](const std::string& entry) { 1056 return entry.find(sensorsAsyncResp->chassisId) != 1057 std::string::npos; 1058 }); 1059 1060 if (found == endpoints.end()) 1061 { 1062 return; 1063 } 1064 sdbusplus::asio::getAllProperties( 1065 *crow::connections::systemBus, owner, path, 1066 "xyz.openbmc_project.Control.FanRedundancy", 1067 [path, sensorsAsyncResp]( 1068 const boost::system::error_code& ec3, 1069 const dbus::utility::DBusPropertiesMap& ret) { 1070 if (ec3) 1071 { 1072 return; // don't have to have this 1073 // interface 1074 } 1075 1076 const uint8_t* allowedFailures = nullptr; 1077 const std::vector<std::string>* collection = nullptr; 1078 const std::string* status = nullptr; 1079 1080 const bool success = sdbusplus::unpackPropertiesNoThrow( 1081 dbus_utils::UnpackErrorPrinter(), ret, 1082 "AllowedFailures", allowedFailures, "Collection", 1083 collection, "Status", status); 1084 1085 if (!success) 1086 { 1087 messages::internalError( 1088 sensorsAsyncResp->asyncResp->res); 1089 return; 1090 } 1091 1092 if (allowedFailures == nullptr || collection == nullptr || 1093 status == nullptr) 1094 { 1095 BMCWEB_LOG_ERROR("Invalid redundancy interface"); 1096 messages::internalError( 1097 sensorsAsyncResp->asyncResp->res); 1098 return; 1099 } 1100 1101 sdbusplus::message::object_path objectPath(path); 1102 std::string name = objectPath.filename(); 1103 if (name.empty()) 1104 { 1105 // this should be impossible 1106 messages::internalError( 1107 sensorsAsyncResp->asyncResp->res); 1108 return; 1109 } 1110 std::ranges::replace(name, '_', ' '); 1111 1112 std::string health; 1113 1114 if (status->ends_with("Full")) 1115 { 1116 health = "OK"; 1117 } 1118 else if (status->ends_with("Degraded")) 1119 { 1120 health = "Warning"; 1121 } 1122 else 1123 { 1124 health = "Critical"; 1125 } 1126 nlohmann::json::array_t redfishCollection; 1127 const auto& fanRedfish = 1128 sensorsAsyncResp->asyncResp->res.jsonValue["Fans"]; 1129 for (const std::string& item : *collection) 1130 { 1131 sdbusplus::message::object_path itemPath(item); 1132 std::string itemName = itemPath.filename(); 1133 if (itemName.empty()) 1134 { 1135 continue; 1136 } 1137 /* 1138 todo(ed): merge patch that fixes the names 1139 std::replace(itemName.begin(), 1140 itemName.end(), '_', ' ');*/ 1141 auto schemaItem = std::ranges::find_if( 1142 fanRedfish, [itemName](const nlohmann::json& fan) { 1143 return fan["Name"] == itemName; 1144 }); 1145 if (schemaItem != fanRedfish.end()) 1146 { 1147 nlohmann::json::object_t collectionId; 1148 collectionId["@odata.id"] = 1149 (*schemaItem)["@odata.id"]; 1150 redfishCollection.emplace_back( 1151 std::move(collectionId)); 1152 } 1153 else 1154 { 1155 BMCWEB_LOG_ERROR("failed to find fan in schema"); 1156 messages::internalError( 1157 sensorsAsyncResp->asyncResp->res); 1158 return; 1159 } 1160 } 1161 1162 size_t minNumNeeded = collection->empty() 1163 ? 0 1164 : collection->size() - 1165 *allowedFailures; 1166 nlohmann::json& jResp = sensorsAsyncResp->asyncResp->res 1167 .jsonValue["Redundancy"]; 1168 1169 nlohmann::json::object_t redundancy; 1170 boost::urls::url url = 1171 boost::urls::format("/redfish/v1/Chassis/{}/{}", 1172 sensorsAsyncResp->chassisId, 1173 sensorsAsyncResp->chassisSubNode); 1174 url.set_fragment(("/Redundancy"_json_pointer / jResp.size()) 1175 .to_string()); 1176 redundancy["@odata.id"] = std::move(url); 1177 redundancy["@odata.type"] = "#Redundancy.v1_3_2.Redundancy"; 1178 redundancy["MinNumNeeded"] = minNumNeeded; 1179 redundancy["Mode"] = redundancy::RedundancyType::NPlusM; 1180 redundancy["Name"] = name; 1181 redundancy["RedundancySet"] = redfishCollection; 1182 redundancy["Status"]["Health"] = health; 1183 redundancy["Status"]["State"] = resource::State::Enabled; 1184 1185 jResp.emplace_back(std::move(redundancy)); 1186 }); 1187 }); 1188 } 1189 }); 1190 } 1191 1192 inline void 1193 sortJSONResponse(const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp) 1194 { 1195 nlohmann::json& response = sensorsAsyncResp->asyncResp->res.jsonValue; 1196 std::array<std::string, 2> sensorHeaders{"Temperatures", "Fans"}; 1197 if (sensorsAsyncResp->chassisSubNode == sensors::node::power) 1198 { 1199 sensorHeaders = {"Voltages", "PowerSupplies"}; 1200 } 1201 for (const std::string& sensorGroup : sensorHeaders) 1202 { 1203 nlohmann::json::iterator entry = response.find(sensorGroup); 1204 if (entry != response.end()) 1205 { 1206 std::sort(entry->begin(), entry->end(), 1207 [](const nlohmann::json& c1, const nlohmann::json& c2) { 1208 return c1["Name"] < c2["Name"]; 1209 }); 1210 1211 // add the index counts to the end of each entry 1212 size_t count = 0; 1213 for (nlohmann::json& sensorJson : *entry) 1214 { 1215 nlohmann::json::iterator odata = sensorJson.find("@odata.id"); 1216 if (odata == sensorJson.end()) 1217 { 1218 continue; 1219 } 1220 std::string* value = odata->get_ptr<std::string*>(); 1221 if (value != nullptr) 1222 { 1223 *value += "/" + std::to_string(count); 1224 sensorJson["MemberId"] = std::to_string(count); 1225 count++; 1226 sensorsAsyncResp->updateUri(sensorJson["Name"], *value); 1227 } 1228 } 1229 } 1230 } 1231 } 1232 1233 /** 1234 * @brief Finds the inventory item with the specified object path. 1235 * @param inventoryItems D-Bus inventory items associated with sensors. 1236 * @param invItemObjPath D-Bus object path of inventory item. 1237 * @return Inventory item within vector, or nullptr if no match found. 1238 */ 1239 inline InventoryItem* findInventoryItem( 1240 const std::shared_ptr<std::vector<InventoryItem>>& inventoryItems, 1241 const std::string& invItemObjPath) 1242 { 1243 for (InventoryItem& inventoryItem : *inventoryItems) 1244 { 1245 if (inventoryItem.objectPath == invItemObjPath) 1246 { 1247 return &inventoryItem; 1248 } 1249 } 1250 return nullptr; 1251 } 1252 1253 /** 1254 * @brief Finds the inventory item associated with the specified sensor. 1255 * @param inventoryItems D-Bus inventory items associated with sensors. 1256 * @param sensorObjPath D-Bus object path of sensor. 1257 * @return Inventory item within vector, or nullptr if no match found. 1258 */ 1259 inline InventoryItem* findInventoryItemForSensor( 1260 const std::shared_ptr<std::vector<InventoryItem>>& inventoryItems, 1261 const std::string& sensorObjPath) 1262 { 1263 for (InventoryItem& inventoryItem : *inventoryItems) 1264 { 1265 if (inventoryItem.sensors.contains(sensorObjPath)) 1266 { 1267 return &inventoryItem; 1268 } 1269 } 1270 return nullptr; 1271 } 1272 1273 /** 1274 * @brief Finds the inventory item associated with the specified led path. 1275 * @param inventoryItems D-Bus inventory items associated with sensors. 1276 * @param ledObjPath D-Bus object path of led. 1277 * @return Inventory item within vector, or nullptr if no match found. 1278 */ 1279 inline InventoryItem* 1280 findInventoryItemForLed(std::vector<InventoryItem>& inventoryItems, 1281 const std::string& ledObjPath) 1282 { 1283 for (InventoryItem& inventoryItem : inventoryItems) 1284 { 1285 if (inventoryItem.ledObjectPath == ledObjPath) 1286 { 1287 return &inventoryItem; 1288 } 1289 } 1290 return nullptr; 1291 } 1292 1293 /** 1294 * @brief Adds inventory item and associated sensor to specified vector. 1295 * 1296 * Adds a new InventoryItem to the vector if necessary. Searches for an 1297 * existing InventoryItem with the specified object path. If not found, one is 1298 * added to the vector. 1299 * 1300 * Next, the specified sensor is added to the set of sensors associated with the 1301 * InventoryItem. 1302 * 1303 * @param inventoryItems D-Bus inventory items associated with sensors. 1304 * @param invItemObjPath D-Bus object path of inventory item. 1305 * @param sensorObjPath D-Bus object path of sensor 1306 */ 1307 inline void addInventoryItem( 1308 const std::shared_ptr<std::vector<InventoryItem>>& inventoryItems, 1309 const std::string& invItemObjPath, const std::string& sensorObjPath) 1310 { 1311 // Look for inventory item in vector 1312 InventoryItem* inventoryItem = findInventoryItem(inventoryItems, 1313 invItemObjPath); 1314 1315 // If inventory item doesn't exist in vector, add it 1316 if (inventoryItem == nullptr) 1317 { 1318 inventoryItems->emplace_back(invItemObjPath); 1319 inventoryItem = &(inventoryItems->back()); 1320 } 1321 1322 // Add sensor to set of sensors associated with inventory item 1323 inventoryItem->sensors.emplace(sensorObjPath); 1324 } 1325 1326 /** 1327 * @brief Stores D-Bus data in the specified inventory item. 1328 * 1329 * Finds D-Bus data in the specified map of interfaces. Stores the data in the 1330 * specified InventoryItem. 1331 * 1332 * This data is later used to provide sensor property values in the JSON 1333 * response. 1334 * 1335 * @param inventoryItem Inventory item where data will be stored. 1336 * @param interfacesDict Map containing D-Bus interfaces and their properties 1337 * for the specified inventory item. 1338 */ 1339 inline void storeInventoryItemData( 1340 InventoryItem& inventoryItem, 1341 const dbus::utility::DBusInterfacesMap& interfacesDict) 1342 { 1343 // Get properties from Inventory.Item interface 1344 1345 for (const auto& [interface, values] : interfacesDict) 1346 { 1347 if (interface == "xyz.openbmc_project.Inventory.Item") 1348 { 1349 for (const auto& [name, dbusValue] : values) 1350 { 1351 if (name == "Present") 1352 { 1353 const bool* value = std::get_if<bool>(&dbusValue); 1354 if (value != nullptr) 1355 { 1356 inventoryItem.isPresent = *value; 1357 } 1358 } 1359 } 1360 } 1361 // Check if Inventory.Item.PowerSupply interface is present 1362 1363 if (interface == "xyz.openbmc_project.Inventory.Item.PowerSupply") 1364 { 1365 inventoryItem.isPowerSupply = true; 1366 } 1367 1368 // Get properties from Inventory.Decorator.Asset interface 1369 if (interface == "xyz.openbmc_project.Inventory.Decorator.Asset") 1370 { 1371 for (const auto& [name, dbusValue] : values) 1372 { 1373 if (name == "Manufacturer") 1374 { 1375 const std::string* value = 1376 std::get_if<std::string>(&dbusValue); 1377 if (value != nullptr) 1378 { 1379 inventoryItem.manufacturer = *value; 1380 } 1381 } 1382 if (name == "Model") 1383 { 1384 const std::string* value = 1385 std::get_if<std::string>(&dbusValue); 1386 if (value != nullptr) 1387 { 1388 inventoryItem.model = *value; 1389 } 1390 } 1391 if (name == "SerialNumber") 1392 { 1393 const std::string* value = 1394 std::get_if<std::string>(&dbusValue); 1395 if (value != nullptr) 1396 { 1397 inventoryItem.serialNumber = *value; 1398 } 1399 } 1400 if (name == "PartNumber") 1401 { 1402 const std::string* value = 1403 std::get_if<std::string>(&dbusValue); 1404 if (value != nullptr) 1405 { 1406 inventoryItem.partNumber = *value; 1407 } 1408 } 1409 } 1410 } 1411 1412 if (interface == 1413 "xyz.openbmc_project.State.Decorator.OperationalStatus") 1414 { 1415 for (const auto& [name, dbusValue] : values) 1416 { 1417 if (name == "Functional") 1418 { 1419 const bool* value = std::get_if<bool>(&dbusValue); 1420 if (value != nullptr) 1421 { 1422 inventoryItem.isFunctional = *value; 1423 } 1424 } 1425 } 1426 } 1427 } 1428 } 1429 1430 /** 1431 * @brief Gets D-Bus data for inventory items associated with sensors. 1432 * 1433 * Uses the specified connections (services) to obtain D-Bus data for inventory 1434 * items associated with sensors. Stores the resulting data in the 1435 * inventoryItems vector. 1436 * 1437 * This data is later used to provide sensor property values in the JSON 1438 * response. 1439 * 1440 * Finds the inventory item data asynchronously. Invokes callback when data has 1441 * been obtained. 1442 * 1443 * The callback must have the following signature: 1444 * @code 1445 * callback(void) 1446 * @endcode 1447 * 1448 * This function is called recursively, obtaining data asynchronously from one 1449 * connection in each call. This ensures the callback is not invoked until the 1450 * last asynchronous function has completed. 1451 * 1452 * @param sensorsAsyncResp Pointer to object holding response data. 1453 * @param inventoryItems D-Bus inventory items associated with sensors. 1454 * @param invConnections Connections that provide data for the inventory items. 1455 * implements ObjectManager. 1456 * @param callback Callback to invoke when inventory data has been obtained. 1457 * @param invConnectionsIndex Current index in invConnections. Only specified 1458 * in recursive calls to this function. 1459 */ 1460 template <typename Callback> 1461 static void getInventoryItemsData( 1462 std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp, 1463 std::shared_ptr<std::vector<InventoryItem>> inventoryItems, 1464 std::shared_ptr<std::set<std::string>> invConnections, Callback&& callback, 1465 size_t invConnectionsIndex = 0) 1466 { 1467 BMCWEB_LOG_DEBUG("getInventoryItemsData enter"); 1468 1469 // If no more connections left, call callback 1470 if (invConnectionsIndex >= invConnections->size()) 1471 { 1472 callback(); 1473 BMCWEB_LOG_DEBUG("getInventoryItemsData exit"); 1474 return; 1475 } 1476 1477 // Get inventory item data from current connection 1478 auto it = invConnections->begin(); 1479 std::advance(it, invConnectionsIndex); 1480 if (it != invConnections->end()) 1481 { 1482 const std::string& invConnection = *it; 1483 1484 // Get all object paths and their interfaces for current connection 1485 sdbusplus::message::object_path path("/xyz/openbmc_project/inventory"); 1486 dbus::utility::getManagedObjects( 1487 invConnection, path, 1488 [sensorsAsyncResp, inventoryItems, invConnections, 1489 callback = std::forward<Callback>(callback), invConnectionsIndex]( 1490 const boost::system::error_code& ec, 1491 const dbus::utility::ManagedObjectType& resp) { 1492 BMCWEB_LOG_DEBUG("getInventoryItemsData respHandler enter"); 1493 if (ec) 1494 { 1495 BMCWEB_LOG_ERROR( 1496 "getInventoryItemsData respHandler DBus error {}", ec); 1497 messages::internalError(sensorsAsyncResp->asyncResp->res); 1498 return; 1499 } 1500 1501 // Loop through returned object paths 1502 for (const auto& objDictEntry : resp) 1503 { 1504 const std::string& objPath = 1505 static_cast<const std::string&>(objDictEntry.first); 1506 1507 // If this object path is one of the specified inventory items 1508 InventoryItem* inventoryItem = findInventoryItem(inventoryItems, 1509 objPath); 1510 if (inventoryItem != nullptr) 1511 { 1512 // Store inventory data in InventoryItem 1513 storeInventoryItemData(*inventoryItem, objDictEntry.second); 1514 } 1515 } 1516 1517 // Recurse to get inventory item data from next connection 1518 getInventoryItemsData(sensorsAsyncResp, inventoryItems, 1519 invConnections, std::move(callback), 1520 invConnectionsIndex + 1); 1521 1522 BMCWEB_LOG_DEBUG("getInventoryItemsData respHandler exit"); 1523 }); 1524 } 1525 1526 BMCWEB_LOG_DEBUG("getInventoryItemsData exit"); 1527 } 1528 1529 /** 1530 * @brief Gets connections that provide D-Bus data for inventory items. 1531 * 1532 * Gets the D-Bus connections (services) that provide data for the inventory 1533 * items that are associated with sensors. 1534 * 1535 * Finds the connections asynchronously. Invokes callback when information has 1536 * been obtained. 1537 * 1538 * The callback must have the following signature: 1539 * @code 1540 * callback(std::shared_ptr<std::set<std::string>> invConnections) 1541 * @endcode 1542 * 1543 * @param sensorsAsyncResp Pointer to object holding response data. 1544 * @param inventoryItems D-Bus inventory items associated with sensors. 1545 * @param callback Callback to invoke when connections have been obtained. 1546 */ 1547 template <typename Callback> 1548 static void getInventoryItemsConnections( 1549 const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp, 1550 const std::shared_ptr<std::vector<InventoryItem>>& inventoryItems, 1551 Callback&& callback) 1552 { 1553 BMCWEB_LOG_DEBUG("getInventoryItemsConnections enter"); 1554 1555 const std::string path = "/xyz/openbmc_project/inventory"; 1556 constexpr std::array<std::string_view, 4> interfaces = { 1557 "xyz.openbmc_project.Inventory.Item", 1558 "xyz.openbmc_project.Inventory.Item.PowerSupply", 1559 "xyz.openbmc_project.Inventory.Decorator.Asset", 1560 "xyz.openbmc_project.State.Decorator.OperationalStatus"}; 1561 1562 // Make call to ObjectMapper to find all inventory items 1563 dbus::utility::getSubTree( 1564 path, 0, interfaces, 1565 [callback = std::forward<Callback>(callback), sensorsAsyncResp, 1566 inventoryItems]( 1567 const boost::system::error_code& ec, 1568 const dbus::utility::MapperGetSubTreeResponse& subtree) { 1569 // Response handler for parsing output from GetSubTree 1570 BMCWEB_LOG_DEBUG("getInventoryItemsConnections respHandler enter"); 1571 if (ec) 1572 { 1573 messages::internalError(sensorsAsyncResp->asyncResp->res); 1574 BMCWEB_LOG_ERROR( 1575 "getInventoryItemsConnections respHandler DBus error {}", ec); 1576 return; 1577 } 1578 1579 // Make unique list of connections for desired inventory items 1580 std::shared_ptr<std::set<std::string>> invConnections = 1581 std::make_shared<std::set<std::string>>(); 1582 1583 // Loop through objects from GetSubTree 1584 for (const std::pair< 1585 std::string, 1586 std::vector<std::pair<std::string, std::vector<std::string>>>>& 1587 object : subtree) 1588 { 1589 // Check if object path is one of the specified inventory items 1590 const std::string& objPath = object.first; 1591 if (findInventoryItem(inventoryItems, objPath) != nullptr) 1592 { 1593 // Store all connections to inventory item 1594 for (const std::pair<std::string, std::vector<std::string>>& 1595 objData : object.second) 1596 { 1597 const std::string& invConnection = objData.first; 1598 invConnections->insert(invConnection); 1599 } 1600 } 1601 } 1602 1603 callback(invConnections); 1604 BMCWEB_LOG_DEBUG("getInventoryItemsConnections respHandler exit"); 1605 }); 1606 BMCWEB_LOG_DEBUG("getInventoryItemsConnections exit"); 1607 } 1608 1609 /** 1610 * @brief Gets associations from sensors to inventory items. 1611 * 1612 * Looks for ObjectMapper associations from the specified sensors to related 1613 * inventory items. Then finds the associations from those inventory items to 1614 * their LEDs, if any. 1615 * 1616 * Finds the inventory items asynchronously. Invokes callback when information 1617 * has been obtained. 1618 * 1619 * The callback must have the following signature: 1620 * @code 1621 * callback(std::shared_ptr<std::vector<InventoryItem>> inventoryItems) 1622 * @endcode 1623 * 1624 * @param sensorsAsyncResp Pointer to object holding response data. 1625 * @param sensorNames All sensors within the current chassis. 1626 * implements ObjectManager. 1627 * @param callback Callback to invoke when inventory items have been obtained. 1628 */ 1629 template <typename Callback> 1630 static void getInventoryItemAssociations( 1631 const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp, 1632 const std::shared_ptr<std::set<std::string>>& sensorNames, 1633 Callback&& callback) 1634 { 1635 BMCWEB_LOG_DEBUG("getInventoryItemAssociations enter"); 1636 1637 // Call GetManagedObjects on the ObjectMapper to get all associations 1638 sdbusplus::message::object_path path("/"); 1639 dbus::utility::getManagedObjects( 1640 "xyz.openbmc_project.ObjectMapper", path, 1641 [callback = std::forward<Callback>(callback), sensorsAsyncResp, 1642 sensorNames](const boost::system::error_code& ec, 1643 const dbus::utility::ManagedObjectType& resp) { 1644 BMCWEB_LOG_DEBUG("getInventoryItemAssociations respHandler enter"); 1645 if (ec) 1646 { 1647 BMCWEB_LOG_ERROR( 1648 "getInventoryItemAssociations respHandler DBus error {}", ec); 1649 messages::internalError(sensorsAsyncResp->asyncResp->res); 1650 return; 1651 } 1652 1653 // Create vector to hold list of inventory items 1654 std::shared_ptr<std::vector<InventoryItem>> inventoryItems = 1655 std::make_shared<std::vector<InventoryItem>>(); 1656 1657 // Loop through returned object paths 1658 std::string sensorAssocPath; 1659 sensorAssocPath.reserve(128); // avoid memory allocations 1660 for (const auto& objDictEntry : resp) 1661 { 1662 const std::string& objPath = 1663 static_cast<const std::string&>(objDictEntry.first); 1664 1665 // If path is inventory association for one of the specified sensors 1666 for (const std::string& sensorName : *sensorNames) 1667 { 1668 sensorAssocPath = sensorName; 1669 sensorAssocPath += "/inventory"; 1670 if (objPath == sensorAssocPath) 1671 { 1672 // Get Association interface for object path 1673 for (const auto& [interface, values] : objDictEntry.second) 1674 { 1675 if (interface == "xyz.openbmc_project.Association") 1676 { 1677 for (const auto& [valueName, value] : values) 1678 { 1679 if (valueName == "endpoints") 1680 { 1681 const std::vector<std::string>* endpoints = 1682 std::get_if<std::vector<std::string>>( 1683 &value); 1684 if ((endpoints != nullptr) && 1685 !endpoints->empty()) 1686 { 1687 // Add inventory item to vector 1688 const std::string& invItemPath = 1689 endpoints->front(); 1690 addInventoryItem(inventoryItems, 1691 invItemPath, 1692 sensorName); 1693 } 1694 } 1695 } 1696 } 1697 } 1698 break; 1699 } 1700 } 1701 } 1702 1703 // Now loop through the returned object paths again, this time to 1704 // find the leds associated with the inventory items we just found 1705 std::string inventoryAssocPath; 1706 inventoryAssocPath.reserve(128); // avoid memory allocations 1707 for (const auto& objDictEntry : resp) 1708 { 1709 const std::string& objPath = 1710 static_cast<const std::string&>(objDictEntry.first); 1711 1712 for (InventoryItem& inventoryItem : *inventoryItems) 1713 { 1714 inventoryAssocPath = inventoryItem.objectPath; 1715 inventoryAssocPath += "/leds"; 1716 if (objPath == inventoryAssocPath) 1717 { 1718 for (const auto& [interface, values] : objDictEntry.second) 1719 { 1720 if (interface == "xyz.openbmc_project.Association") 1721 { 1722 for (const auto& [valueName, value] : values) 1723 { 1724 if (valueName == "endpoints") 1725 { 1726 const std::vector<std::string>* endpoints = 1727 std::get_if<std::vector<std::string>>( 1728 &value); 1729 if ((endpoints != nullptr) && 1730 !endpoints->empty()) 1731 { 1732 // Add inventory item to vector 1733 // Store LED path in inventory item 1734 const std::string& ledPath = 1735 endpoints->front(); 1736 inventoryItem.ledObjectPath = ledPath; 1737 } 1738 } 1739 } 1740 } 1741 } 1742 1743 break; 1744 } 1745 } 1746 } 1747 callback(inventoryItems); 1748 BMCWEB_LOG_DEBUG("getInventoryItemAssociations respHandler exit"); 1749 }); 1750 1751 BMCWEB_LOG_DEBUG("getInventoryItemAssociations exit"); 1752 } 1753 1754 /** 1755 * @brief Gets D-Bus data for inventory item leds associated with sensors. 1756 * 1757 * Uses the specified connections (services) to obtain D-Bus data for inventory 1758 * item leds associated with sensors. Stores the resulting data in the 1759 * inventoryItems vector. 1760 * 1761 * This data is later used to provide sensor property values in the JSON 1762 * response. 1763 * 1764 * Finds the inventory item led data asynchronously. Invokes callback when data 1765 * has been obtained. 1766 * 1767 * The callback must have the following signature: 1768 * @code 1769 * callback() 1770 * @endcode 1771 * 1772 * This function is called recursively, obtaining data asynchronously from one 1773 * connection in each call. This ensures the callback is not invoked until the 1774 * last asynchronous function has completed. 1775 * 1776 * @param sensorsAsyncResp Pointer to object holding response data. 1777 * @param inventoryItems D-Bus inventory items associated with sensors. 1778 * @param ledConnections Connections that provide data for the inventory leds. 1779 * @param callback Callback to invoke when inventory data has been obtained. 1780 * @param ledConnectionsIndex Current index in ledConnections. Only specified 1781 * in recursive calls to this function. 1782 */ 1783 template <typename Callback> 1784 void getInventoryLedData( 1785 std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp, 1786 std::shared_ptr<std::vector<InventoryItem>> inventoryItems, 1787 std::shared_ptr<std::map<std::string, std::string>> ledConnections, 1788 Callback&& callback, size_t ledConnectionsIndex = 0) 1789 { 1790 BMCWEB_LOG_DEBUG("getInventoryLedData enter"); 1791 1792 // If no more connections left, call callback 1793 if (ledConnectionsIndex >= ledConnections->size()) 1794 { 1795 callback(); 1796 BMCWEB_LOG_DEBUG("getInventoryLedData exit"); 1797 return; 1798 } 1799 1800 // Get inventory item data from current connection 1801 auto it = ledConnections->begin(); 1802 std::advance(it, ledConnectionsIndex); 1803 if (it != ledConnections->end()) 1804 { 1805 const std::string& ledPath = (*it).first; 1806 const std::string& ledConnection = (*it).second; 1807 // Response handler for Get State property 1808 auto respHandler = 1809 [sensorsAsyncResp, inventoryItems, ledConnections, ledPath, 1810 callback = std::forward<Callback>(callback), ledConnectionsIndex]( 1811 const boost::system::error_code& ec, const std::string& state) { 1812 BMCWEB_LOG_DEBUG("getInventoryLedData respHandler enter"); 1813 if (ec) 1814 { 1815 BMCWEB_LOG_ERROR( 1816 "getInventoryLedData respHandler DBus error {}", ec); 1817 messages::internalError(sensorsAsyncResp->asyncResp->res); 1818 return; 1819 } 1820 1821 BMCWEB_LOG_DEBUG("Led state: {}", state); 1822 // Find inventory item with this LED object path 1823 InventoryItem* inventoryItem = 1824 findInventoryItemForLed(*inventoryItems, ledPath); 1825 if (inventoryItem != nullptr) 1826 { 1827 // Store LED state in InventoryItem 1828 if (state.ends_with("On")) 1829 { 1830 inventoryItem->ledState = LedState::ON; 1831 } 1832 else if (state.ends_with("Blink")) 1833 { 1834 inventoryItem->ledState = LedState::BLINK; 1835 } 1836 else if (state.ends_with("Off")) 1837 { 1838 inventoryItem->ledState = LedState::OFF; 1839 } 1840 else 1841 { 1842 inventoryItem->ledState = LedState::UNKNOWN; 1843 } 1844 } 1845 1846 // Recurse to get LED data from next connection 1847 getInventoryLedData(sensorsAsyncResp, inventoryItems, 1848 ledConnections, std::move(callback), 1849 ledConnectionsIndex + 1); 1850 1851 BMCWEB_LOG_DEBUG("getInventoryLedData respHandler exit"); 1852 }; 1853 1854 // Get the State property for the current LED 1855 sdbusplus::asio::getProperty<std::string>( 1856 *crow::connections::systemBus, ledConnection, ledPath, 1857 "xyz.openbmc_project.Led.Physical", "State", 1858 std::move(respHandler)); 1859 } 1860 1861 BMCWEB_LOG_DEBUG("getInventoryLedData exit"); 1862 } 1863 1864 /** 1865 * @brief Gets LED data for LEDs associated with given inventory items. 1866 * 1867 * Gets the D-Bus connections (services) that provide LED data for the LEDs 1868 * associated with the specified inventory items. Then gets the LED data from 1869 * each connection and stores it in the inventory item. 1870 * 1871 * This data is later used to provide sensor property values in the JSON 1872 * response. 1873 * 1874 * Finds the LED data asynchronously. Invokes callback when information has 1875 * been obtained. 1876 * 1877 * The callback must have the following signature: 1878 * @code 1879 * callback() 1880 * @endcode 1881 * 1882 * @param sensorsAsyncResp Pointer to object holding response data. 1883 * @param inventoryItems D-Bus inventory items associated with sensors. 1884 * @param callback Callback to invoke when inventory items have been obtained. 1885 */ 1886 template <typename Callback> 1887 void getInventoryLeds( 1888 std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp, 1889 std::shared_ptr<std::vector<InventoryItem>> inventoryItems, 1890 Callback&& callback) 1891 { 1892 BMCWEB_LOG_DEBUG("getInventoryLeds enter"); 1893 1894 const std::string path = "/xyz/openbmc_project"; 1895 constexpr std::array<std::string_view, 1> interfaces = { 1896 "xyz.openbmc_project.Led.Physical"}; 1897 1898 // Make call to ObjectMapper to find all inventory items 1899 dbus::utility::getSubTree( 1900 path, 0, interfaces, 1901 [callback = std::forward<Callback>(callback), sensorsAsyncResp, 1902 inventoryItems]( 1903 const boost::system::error_code& ec, 1904 const dbus::utility::MapperGetSubTreeResponse& subtree) { 1905 // Response handler for parsing output from GetSubTree 1906 BMCWEB_LOG_DEBUG("getInventoryLeds respHandler enter"); 1907 if (ec) 1908 { 1909 messages::internalError(sensorsAsyncResp->asyncResp->res); 1910 BMCWEB_LOG_ERROR("getInventoryLeds respHandler DBus error {}", ec); 1911 return; 1912 } 1913 1914 // Build map of LED object paths to connections 1915 std::shared_ptr<std::map<std::string, std::string>> ledConnections = 1916 std::make_shared<std::map<std::string, std::string>>(); 1917 1918 // Loop through objects from GetSubTree 1919 for (const std::pair< 1920 std::string, 1921 std::vector<std::pair<std::string, std::vector<std::string>>>>& 1922 object : subtree) 1923 { 1924 // Check if object path is LED for one of the specified inventory 1925 // items 1926 const std::string& ledPath = object.first; 1927 if (findInventoryItemForLed(*inventoryItems, ledPath) != nullptr) 1928 { 1929 // Add mapping from ledPath to connection 1930 const std::string& connection = object.second.begin()->first; 1931 (*ledConnections)[ledPath] = connection; 1932 BMCWEB_LOG_DEBUG("Added mapping {} -> {}", ledPath, connection); 1933 } 1934 } 1935 1936 getInventoryLedData(sensorsAsyncResp, inventoryItems, ledConnections, 1937 std::move(callback)); 1938 BMCWEB_LOG_DEBUG("getInventoryLeds respHandler exit"); 1939 }); 1940 BMCWEB_LOG_DEBUG("getInventoryLeds exit"); 1941 } 1942 1943 /** 1944 * @brief Gets D-Bus data for Power Supply Attributes such as EfficiencyPercent 1945 * 1946 * Uses the specified connections (services) (currently assumes just one) to 1947 * obtain D-Bus data for Power Supply Attributes. Stores the resulting data in 1948 * the inventoryItems vector. Only stores data in Power Supply inventoryItems. 1949 * 1950 * This data is later used to provide sensor property values in the JSON 1951 * response. 1952 * 1953 * Finds the Power Supply Attributes data asynchronously. Invokes callback 1954 * when data has been obtained. 1955 * 1956 * The callback must have the following signature: 1957 * @code 1958 * callback(std::shared_ptr<std::vector<InventoryItem>> inventoryItems) 1959 * @endcode 1960 * 1961 * @param sensorsAsyncResp Pointer to object holding response data. 1962 * @param inventoryItems D-Bus inventory items associated with sensors. 1963 * @param psAttributesConnections Connections that provide data for the Power 1964 * Supply Attributes 1965 * @param callback Callback to invoke when data has been obtained. 1966 */ 1967 template <typename Callback> 1968 void getPowerSupplyAttributesData( 1969 const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp, 1970 std::shared_ptr<std::vector<InventoryItem>> inventoryItems, 1971 const std::map<std::string, std::string>& psAttributesConnections, 1972 Callback&& callback) 1973 { 1974 BMCWEB_LOG_DEBUG("getPowerSupplyAttributesData enter"); 1975 1976 if (psAttributesConnections.empty()) 1977 { 1978 BMCWEB_LOG_DEBUG("Can't find PowerSupplyAttributes, no connections!"); 1979 callback(inventoryItems); 1980 return; 1981 } 1982 1983 // Assuming just one connection (service) for now 1984 auto it = psAttributesConnections.begin(); 1985 1986 const std::string& psAttributesPath = (*it).first; 1987 const std::string& psAttributesConnection = (*it).second; 1988 1989 // Response handler for Get DeratingFactor property 1990 auto respHandler = [sensorsAsyncResp, inventoryItems, 1991 callback = std::forward<Callback>(callback)]( 1992 const boost::system::error_code& ec, 1993 const uint32_t value) { 1994 BMCWEB_LOG_DEBUG("getPowerSupplyAttributesData respHandler enter"); 1995 if (ec) 1996 { 1997 BMCWEB_LOG_ERROR( 1998 "getPowerSupplyAttributesData respHandler DBus error {}", ec); 1999 messages::internalError(sensorsAsyncResp->asyncResp->res); 2000 return; 2001 } 2002 2003 BMCWEB_LOG_DEBUG("PS EfficiencyPercent value: {}", value); 2004 // Store value in Power Supply Inventory Items 2005 for (InventoryItem& inventoryItem : *inventoryItems) 2006 { 2007 if (inventoryItem.isPowerSupply) 2008 { 2009 inventoryItem.powerSupplyEfficiencyPercent = 2010 static_cast<int>(value); 2011 } 2012 } 2013 2014 BMCWEB_LOG_DEBUG("getPowerSupplyAttributesData respHandler exit"); 2015 callback(inventoryItems); 2016 }; 2017 2018 // Get the DeratingFactor property for the PowerSupplyAttributes 2019 // Currently only property on the interface/only one we care about 2020 sdbusplus::asio::getProperty<uint32_t>( 2021 *crow::connections::systemBus, psAttributesConnection, psAttributesPath, 2022 "xyz.openbmc_project.Control.PowerSupplyAttributes", "DeratingFactor", 2023 std::move(respHandler)); 2024 2025 BMCWEB_LOG_DEBUG("getPowerSupplyAttributesData exit"); 2026 } 2027 2028 /** 2029 * @brief Gets the Power Supply Attributes such as EfficiencyPercent 2030 * 2031 * Gets the D-Bus connection (service) that provides Power Supply Attributes 2032 * data. Then gets the Power Supply Attributes data from the connection 2033 * (currently just assumes 1 connection) and stores the data in the inventory 2034 * item. 2035 * 2036 * This data is later used to provide sensor property values in the JSON 2037 * response. DeratingFactor on D-Bus is mapped to EfficiencyPercent on Redfish. 2038 * 2039 * Finds the Power Supply Attributes data asynchronously. Invokes callback 2040 * when information has been obtained. 2041 * 2042 * The callback must have the following signature: 2043 * @code 2044 * callback(std::shared_ptr<std::vector<InventoryItem>> inventoryItems) 2045 * @endcode 2046 * 2047 * @param sensorsAsyncResp Pointer to object holding response data. 2048 * @param inventoryItems D-Bus inventory items associated with sensors. 2049 * @param callback Callback to invoke when data has been obtained. 2050 */ 2051 template <typename Callback> 2052 void getPowerSupplyAttributes( 2053 std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp, 2054 std::shared_ptr<std::vector<InventoryItem>> inventoryItems, 2055 Callback&& callback) 2056 { 2057 BMCWEB_LOG_DEBUG("getPowerSupplyAttributes enter"); 2058 2059 // Only need the power supply attributes when the Power Schema 2060 if (sensorsAsyncResp->chassisSubNode != sensors::node::power) 2061 { 2062 BMCWEB_LOG_DEBUG("getPowerSupplyAttributes exit since not Power"); 2063 callback(inventoryItems); 2064 return; 2065 } 2066 2067 constexpr std::array<std::string_view, 1> interfaces = { 2068 "xyz.openbmc_project.Control.PowerSupplyAttributes"}; 2069 2070 // Make call to ObjectMapper to find the PowerSupplyAttributes service 2071 dbus::utility::getSubTree( 2072 "/xyz/openbmc_project", 0, interfaces, 2073 [callback = std::forward<Callback>(callback), sensorsAsyncResp, 2074 inventoryItems]( 2075 const boost::system::error_code& ec, 2076 const dbus::utility::MapperGetSubTreeResponse& subtree) { 2077 // Response handler for parsing output from GetSubTree 2078 BMCWEB_LOG_DEBUG("getPowerSupplyAttributes respHandler enter"); 2079 if (ec) 2080 { 2081 messages::internalError(sensorsAsyncResp->asyncResp->res); 2082 BMCWEB_LOG_ERROR( 2083 "getPowerSupplyAttributes respHandler DBus error {}", ec); 2084 return; 2085 } 2086 if (subtree.empty()) 2087 { 2088 BMCWEB_LOG_DEBUG("Can't find Power Supply Attributes!"); 2089 callback(inventoryItems); 2090 return; 2091 } 2092 2093 // Currently we only support 1 power supply attribute, use this for 2094 // all the power supplies. Build map of object path to connection. 2095 // Assume just 1 connection and 1 path for now. 2096 std::map<std::string, std::string> psAttributesConnections; 2097 2098 if (subtree[0].first.empty() || subtree[0].second.empty()) 2099 { 2100 BMCWEB_LOG_DEBUG("Power Supply Attributes mapper error!"); 2101 callback(inventoryItems); 2102 return; 2103 } 2104 2105 const std::string& psAttributesPath = subtree[0].first; 2106 const std::string& connection = subtree[0].second.begin()->first; 2107 2108 if (connection.empty()) 2109 { 2110 BMCWEB_LOG_DEBUG("Power Supply Attributes mapper error!"); 2111 callback(inventoryItems); 2112 return; 2113 } 2114 2115 psAttributesConnections[psAttributesPath] = connection; 2116 BMCWEB_LOG_DEBUG("Added mapping {} -> {}", psAttributesPath, 2117 connection); 2118 2119 getPowerSupplyAttributesData(sensorsAsyncResp, inventoryItems, 2120 psAttributesConnections, 2121 std::move(callback)); 2122 BMCWEB_LOG_DEBUG("getPowerSupplyAttributes respHandler exit"); 2123 }); 2124 BMCWEB_LOG_DEBUG("getPowerSupplyAttributes exit"); 2125 } 2126 2127 /** 2128 * @brief Gets inventory items associated with sensors. 2129 * 2130 * Finds the inventory items that are associated with the specified sensors. 2131 * Then gets D-Bus data for the inventory items, such as presence and VPD. 2132 * 2133 * This data is later used to provide sensor property values in the JSON 2134 * response. 2135 * 2136 * Finds the inventory items asynchronously. Invokes callback when the 2137 * inventory items have been obtained. 2138 * 2139 * The callback must have the following signature: 2140 * @code 2141 * callback(std::shared_ptr<std::vector<InventoryItem>> inventoryItems) 2142 * @endcode 2143 * 2144 * @param sensorsAsyncResp Pointer to object holding response data. 2145 * @param sensorNames All sensors within the current chassis. 2146 * implements ObjectManager. 2147 * @param callback Callback to invoke when inventory items have been obtained. 2148 */ 2149 template <typename Callback> 2150 static void 2151 getInventoryItems(std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp, 2152 const std::shared_ptr<std::set<std::string>> sensorNames, 2153 Callback&& callback) 2154 { 2155 BMCWEB_LOG_DEBUG("getInventoryItems enter"); 2156 auto getInventoryItemAssociationsCb = 2157 [sensorsAsyncResp, callback = std::forward<Callback>(callback)]( 2158 std::shared_ptr<std::vector<InventoryItem>> inventoryItems) { 2159 BMCWEB_LOG_DEBUG("getInventoryItemAssociationsCb enter"); 2160 auto getInventoryItemsConnectionsCb = 2161 [sensorsAsyncResp, inventoryItems, 2162 callback = std::forward<const Callback>(callback)]( 2163 std::shared_ptr<std::set<std::string>> invConnections) { 2164 BMCWEB_LOG_DEBUG("getInventoryItemsConnectionsCb enter"); 2165 auto getInventoryItemsDataCb = [sensorsAsyncResp, inventoryItems, 2166 callback{std::move(callback)}]() { 2167 BMCWEB_LOG_DEBUG("getInventoryItemsDataCb enter"); 2168 2169 auto getInventoryLedsCb = [sensorsAsyncResp, inventoryItems, 2170 callback{std::move(callback)}]() { 2171 BMCWEB_LOG_DEBUG("getInventoryLedsCb enter"); 2172 // Find Power Supply Attributes and get the data 2173 getPowerSupplyAttributes(sensorsAsyncResp, inventoryItems, 2174 std::move(callback)); 2175 BMCWEB_LOG_DEBUG("getInventoryLedsCb exit"); 2176 }; 2177 2178 // Find led connections and get the data 2179 getInventoryLeds(sensorsAsyncResp, inventoryItems, 2180 std::move(getInventoryLedsCb)); 2181 BMCWEB_LOG_DEBUG("getInventoryItemsDataCb exit"); 2182 }; 2183 2184 // Get inventory item data from connections 2185 getInventoryItemsData(sensorsAsyncResp, inventoryItems, 2186 invConnections, 2187 std::move(getInventoryItemsDataCb)); 2188 BMCWEB_LOG_DEBUG("getInventoryItemsConnectionsCb exit"); 2189 }; 2190 2191 // Get connections that provide inventory item data 2192 getInventoryItemsConnections(sensorsAsyncResp, inventoryItems, 2193 std::move(getInventoryItemsConnectionsCb)); 2194 BMCWEB_LOG_DEBUG("getInventoryItemAssociationsCb exit"); 2195 }; 2196 2197 // Get associations from sensors to inventory items 2198 getInventoryItemAssociations(sensorsAsyncResp, sensorNames, 2199 std::move(getInventoryItemAssociationsCb)); 2200 BMCWEB_LOG_DEBUG("getInventoryItems exit"); 2201 } 2202 2203 /** 2204 * @brief Returns JSON PowerSupply object for the specified inventory item. 2205 * 2206 * Searches for a JSON PowerSupply object that matches the specified inventory 2207 * item. If one is not found, a new PowerSupply object is added to the JSON 2208 * array. 2209 * 2210 * Multiple sensors are often associated with one power supply inventory item. 2211 * As a result, multiple sensor values are stored in one JSON PowerSupply 2212 * object. 2213 * 2214 * @param powerSupplyArray JSON array containing Redfish PowerSupply objects. 2215 * @param inventoryItem Inventory item for the power supply. 2216 * @param chassisId Chassis that contains the power supply. 2217 * @return JSON PowerSupply object for the specified inventory item. 2218 */ 2219 inline nlohmann::json& getPowerSupply(nlohmann::json& powerSupplyArray, 2220 const InventoryItem& inventoryItem, 2221 const std::string& chassisId) 2222 { 2223 std::string nameS; 2224 nameS.resize(inventoryItem.name.size()); 2225 std::ranges::replace_copy(inventoryItem.name, nameS.begin(), '_', ' '); 2226 // Check if matching PowerSupply object already exists in JSON array 2227 for (nlohmann::json& powerSupply : powerSupplyArray) 2228 { 2229 nlohmann::json::iterator nameIt = powerSupply.find("Name"); 2230 if (nameIt == powerSupply.end()) 2231 { 2232 continue; 2233 } 2234 const std::string* name = nameIt->get_ptr<std::string*>(); 2235 if (name == nullptr) 2236 { 2237 continue; 2238 } 2239 if (nameS == *name) 2240 { 2241 return powerSupply; 2242 } 2243 } 2244 2245 // Add new PowerSupply object to JSON array 2246 powerSupplyArray.push_back({}); 2247 nlohmann::json& powerSupply = powerSupplyArray.back(); 2248 boost::urls::url url = boost::urls::format("/redfish/v1/Chassis/{}/Power", 2249 chassisId); 2250 url.set_fragment(("/PowerSupplies"_json_pointer).to_string()); 2251 powerSupply["@odata.id"] = std::move(url); 2252 std::string escaped; 2253 escaped.resize(inventoryItem.name.size()); 2254 std::ranges::replace_copy(inventoryItem.name, escaped.begin(), '_', ' '); 2255 powerSupply["Name"] = std::move(escaped); 2256 powerSupply["Manufacturer"] = inventoryItem.manufacturer; 2257 powerSupply["Model"] = inventoryItem.model; 2258 powerSupply["PartNumber"] = inventoryItem.partNumber; 2259 powerSupply["SerialNumber"] = inventoryItem.serialNumber; 2260 setLedState(powerSupply, &inventoryItem); 2261 2262 if (inventoryItem.powerSupplyEfficiencyPercent >= 0) 2263 { 2264 powerSupply["EfficiencyPercent"] = 2265 inventoryItem.powerSupplyEfficiencyPercent; 2266 } 2267 2268 powerSupply["Status"]["State"] = getState(&inventoryItem, true); 2269 const char* health = inventoryItem.isFunctional ? "OK" : "Critical"; 2270 powerSupply["Status"]["Health"] = health; 2271 2272 return powerSupply; 2273 } 2274 2275 /** 2276 * @brief Gets the values of the specified sensors. 2277 * 2278 * Stores the results as JSON in the SensorsAsyncResp. 2279 * 2280 * Gets the sensor values asynchronously. Stores the results later when the 2281 * information has been obtained. 2282 * 2283 * The sensorNames set contains all requested sensors for the current chassis. 2284 * 2285 * To minimize the number of DBus calls, the DBus method 2286 * org.freedesktop.DBus.ObjectManager.GetManagedObjects() is used to get the 2287 * values of all sensors provided by a connection (service). 2288 * 2289 * The connections set contains all the connections that provide sensor values. 2290 * 2291 * The InventoryItem vector contains D-Bus inventory items associated with the 2292 * sensors. Inventory item data is needed for some Redfish sensor properties. 2293 * 2294 * @param SensorsAsyncResp Pointer to object holding response data. 2295 * @param sensorNames All requested sensors within the current chassis. 2296 * @param connections Connections that provide sensor values. 2297 * implements ObjectManager. 2298 * @param inventoryItems Inventory items associated with the sensors. 2299 */ 2300 inline void getSensorData( 2301 const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp, 2302 const std::shared_ptr<std::set<std::string>>& sensorNames, 2303 const std::set<std::string>& connections, 2304 const std::shared_ptr<std::vector<InventoryItem>>& inventoryItems) 2305 { 2306 BMCWEB_LOG_DEBUG("getSensorData enter"); 2307 // Get managed objects from all services exposing sensors 2308 for (const std::string& connection : connections) 2309 { 2310 sdbusplus::message::object_path sensorPath( 2311 "/xyz/openbmc_project/sensors"); 2312 dbus::utility::getManagedObjects( 2313 connection, sensorPath, 2314 [sensorsAsyncResp, sensorNames, 2315 inventoryItems](const boost::system::error_code& ec, 2316 const dbus::utility::ManagedObjectType& resp) { 2317 BMCWEB_LOG_DEBUG("getManagedObjectsCb enter"); 2318 if (ec) 2319 { 2320 BMCWEB_LOG_ERROR("getManagedObjectsCb DBUS error: {}", ec); 2321 messages::internalError(sensorsAsyncResp->asyncResp->res); 2322 return; 2323 } 2324 // Go through all objects and update response with sensor data 2325 for (const auto& objDictEntry : resp) 2326 { 2327 const std::string& objPath = 2328 static_cast<const std::string&>(objDictEntry.first); 2329 BMCWEB_LOG_DEBUG("getManagedObjectsCb parsing object {}", 2330 objPath); 2331 2332 std::vector<std::string> split; 2333 // Reserve space for 2334 // /xyz/openbmc_project/sensors/<name>/<subname> 2335 split.reserve(6); 2336 // NOLINTNEXTLINE 2337 bmcweb::split(split, objPath, '/'); 2338 if (split.size() < 6) 2339 { 2340 BMCWEB_LOG_ERROR("Got path that isn't long enough {}", 2341 objPath); 2342 continue; 2343 } 2344 // These indexes aren't intuitive, as split puts an empty 2345 // string at the beginning 2346 const std::string& sensorType = split[4]; 2347 const std::string& sensorName = split[5]; 2348 BMCWEB_LOG_DEBUG("sensorName {} sensorType {}", sensorName, 2349 sensorType); 2350 if (sensorNames->find(objPath) == sensorNames->end()) 2351 { 2352 BMCWEB_LOG_DEBUG("{} not in sensor list ", sensorName); 2353 continue; 2354 } 2355 2356 // Find inventory item (if any) associated with sensor 2357 InventoryItem* inventoryItem = 2358 findInventoryItemForSensor(inventoryItems, objPath); 2359 2360 const std::string& sensorSchema = 2361 sensorsAsyncResp->chassisSubNode; 2362 2363 nlohmann::json* sensorJson = nullptr; 2364 2365 if (sensorSchema == sensors::node::sensors && 2366 !sensorsAsyncResp->efficientExpand) 2367 { 2368 std::string sensorTypeEscaped(sensorType); 2369 auto remove = std::ranges::remove(sensorTypeEscaped, '_'); 2370 2371 sensorTypeEscaped.erase(std::ranges::begin(remove), 2372 sensorTypeEscaped.end()); 2373 std::string sensorId(sensorTypeEscaped); 2374 sensorId += "_"; 2375 sensorId += sensorName; 2376 2377 sensorsAsyncResp->asyncResp->res.jsonValue["@odata.id"] = 2378 boost::urls::format("/redfish/v1/Chassis/{}/{}/{}", 2379 sensorsAsyncResp->chassisId, 2380 sensorsAsyncResp->chassisSubNode, 2381 sensorId); 2382 sensorJson = &(sensorsAsyncResp->asyncResp->res.jsonValue); 2383 } 2384 else 2385 { 2386 std::string fieldName; 2387 if (sensorsAsyncResp->efficientExpand) 2388 { 2389 fieldName = "Members"; 2390 } 2391 else if (sensorType == "temperature") 2392 { 2393 fieldName = "Temperatures"; 2394 } 2395 else if (sensorType == "fan" || sensorType == "fan_tach" || 2396 sensorType == "fan_pwm") 2397 { 2398 fieldName = "Fans"; 2399 } 2400 else if (sensorType == "voltage") 2401 { 2402 fieldName = "Voltages"; 2403 } 2404 else if (sensorType == "power") 2405 { 2406 if (sensorName == "total_power") 2407 { 2408 fieldName = "PowerControl"; 2409 } 2410 else if ((inventoryItem != nullptr) && 2411 (inventoryItem->isPowerSupply)) 2412 { 2413 fieldName = "PowerSupplies"; 2414 } 2415 else 2416 { 2417 // Other power sensors are in SensorCollection 2418 continue; 2419 } 2420 } 2421 else 2422 { 2423 BMCWEB_LOG_ERROR("Unsure how to handle sensorType {}", 2424 sensorType); 2425 continue; 2426 } 2427 2428 nlohmann::json& tempArray = 2429 sensorsAsyncResp->asyncResp->res.jsonValue[fieldName]; 2430 if (fieldName == "PowerControl") 2431 { 2432 if (tempArray.empty()) 2433 { 2434 // Put multiple "sensors" into a single 2435 // PowerControl. Follows MemberId naming and 2436 // naming in power.hpp. 2437 nlohmann::json::object_t power; 2438 boost::urls::url url = boost::urls::format( 2439 "/redfish/v1/Chassis/{}/{}", 2440 sensorsAsyncResp->chassisId, 2441 sensorsAsyncResp->chassisSubNode); 2442 url.set_fragment((""_json_pointer / fieldName / "0") 2443 .to_string()); 2444 power["@odata.id"] = std::move(url); 2445 tempArray.emplace_back(std::move(power)); 2446 } 2447 sensorJson = &(tempArray.back()); 2448 } 2449 else if (fieldName == "PowerSupplies") 2450 { 2451 if (inventoryItem != nullptr) 2452 { 2453 sensorJson = 2454 &(getPowerSupply(tempArray, *inventoryItem, 2455 sensorsAsyncResp->chassisId)); 2456 } 2457 } 2458 else if (fieldName == "Members") 2459 { 2460 std::string sensorTypeEscaped(sensorType); 2461 auto remove = std::ranges::remove(sensorTypeEscaped, 2462 '_'); 2463 sensorTypeEscaped.erase(std::ranges::begin(remove), 2464 sensorTypeEscaped.end()); 2465 std::string sensorId(sensorTypeEscaped); 2466 sensorId += "_"; 2467 sensorId += sensorName; 2468 2469 nlohmann::json::object_t member; 2470 member["@odata.id"] = boost::urls::format( 2471 "/redfish/v1/Chassis/{}/{}/{}", 2472 sensorsAsyncResp->chassisId, 2473 sensorsAsyncResp->chassisSubNode, sensorId); 2474 tempArray.emplace_back(std::move(member)); 2475 sensorJson = &(tempArray.back()); 2476 } 2477 else 2478 { 2479 nlohmann::json::object_t member; 2480 boost::urls::url url = boost::urls::format( 2481 "/redfish/v1/Chassis/{}/{}", 2482 sensorsAsyncResp->chassisId, 2483 sensorsAsyncResp->chassisSubNode); 2484 url.set_fragment( 2485 (""_json_pointer / fieldName).to_string()); 2486 member["@odata.id"] = std::move(url); 2487 tempArray.emplace_back(std::move(member)); 2488 sensorJson = &(tempArray.back()); 2489 } 2490 } 2491 2492 if (sensorJson != nullptr) 2493 { 2494 objectInterfacesToJson(sensorName, sensorType, 2495 sensorsAsyncResp->chassisSubNode, 2496 objDictEntry.second, *sensorJson, 2497 inventoryItem); 2498 2499 std::string path = "/xyz/openbmc_project/sensors/"; 2500 path += sensorType; 2501 path += "/"; 2502 path += sensorName; 2503 sensorsAsyncResp->addMetadata(*sensorJson, path); 2504 } 2505 } 2506 if (sensorsAsyncResp.use_count() == 1) 2507 { 2508 sortJSONResponse(sensorsAsyncResp); 2509 if (sensorsAsyncResp->chassisSubNode == 2510 sensors::node::sensors && 2511 sensorsAsyncResp->efficientExpand) 2512 { 2513 sensorsAsyncResp->asyncResp->res 2514 .jsonValue["Members@odata.count"] = 2515 sensorsAsyncResp->asyncResp->res.jsonValue["Members"] 2516 .size(); 2517 } 2518 else if (sensorsAsyncResp->chassisSubNode == 2519 sensors::node::thermal) 2520 { 2521 populateFanRedundancy(sensorsAsyncResp); 2522 } 2523 } 2524 BMCWEB_LOG_DEBUG("getManagedObjectsCb exit"); 2525 }); 2526 } 2527 BMCWEB_LOG_DEBUG("getSensorData exit"); 2528 } 2529 2530 inline void 2531 processSensorList(const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp, 2532 const std::shared_ptr<std::set<std::string>>& sensorNames) 2533 { 2534 auto getConnectionCb = [sensorsAsyncResp, sensorNames]( 2535 const std::set<std::string>& connections) { 2536 BMCWEB_LOG_DEBUG("getConnectionCb enter"); 2537 auto getInventoryItemsCb = 2538 [sensorsAsyncResp, sensorNames, 2539 connections](const std::shared_ptr<std::vector<InventoryItem>>& 2540 inventoryItems) { 2541 BMCWEB_LOG_DEBUG("getInventoryItemsCb enter"); 2542 // Get sensor data and store results in JSON 2543 getSensorData(sensorsAsyncResp, sensorNames, connections, 2544 inventoryItems); 2545 BMCWEB_LOG_DEBUG("getInventoryItemsCb exit"); 2546 }; 2547 2548 // Get inventory items associated with sensors 2549 getInventoryItems(sensorsAsyncResp, sensorNames, 2550 std::move(getInventoryItemsCb)); 2551 2552 BMCWEB_LOG_DEBUG("getConnectionCb exit"); 2553 }; 2554 2555 // Get set of connections that provide sensor values 2556 getConnections(sensorsAsyncResp, sensorNames, std::move(getConnectionCb)); 2557 } 2558 2559 /** 2560 * @brief Entry point for retrieving sensors data related to requested 2561 * chassis. 2562 * @param SensorsAsyncResp Pointer to object holding response data 2563 */ 2564 inline void 2565 getChassisData(const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp) 2566 { 2567 BMCWEB_LOG_DEBUG("getChassisData enter"); 2568 auto getChassisCb = 2569 [sensorsAsyncResp]( 2570 const std::shared_ptr<std::set<std::string>>& sensorNames) { 2571 BMCWEB_LOG_DEBUG("getChassisCb enter"); 2572 processSensorList(sensorsAsyncResp, sensorNames); 2573 BMCWEB_LOG_DEBUG("getChassisCb exit"); 2574 }; 2575 // SensorCollection doesn't contain the Redundancy property 2576 if (sensorsAsyncResp->chassisSubNode != sensors::node::sensors) 2577 { 2578 sensorsAsyncResp->asyncResp->res.jsonValue["Redundancy"] = 2579 nlohmann::json::array(); 2580 } 2581 // Get set of sensors in chassis 2582 getChassis(sensorsAsyncResp->asyncResp, sensorsAsyncResp->chassisId, 2583 sensorsAsyncResp->chassisSubNode, sensorsAsyncResp->types, 2584 std::move(getChassisCb)); 2585 BMCWEB_LOG_DEBUG("getChassisData exit"); 2586 } 2587 2588 /** 2589 * @brief Find the requested sensorName in the list of all sensors supplied by 2590 * the chassis node 2591 * 2592 * @param sensorName The sensor name supplied in the PATCH request 2593 * @param sensorsList The list of sensors managed by the chassis node 2594 * @param sensorsModified The list of sensors that were found as a result of 2595 * repeated calls to this function 2596 */ 2597 inline bool 2598 findSensorNameUsingSensorPath(std::string_view sensorName, 2599 const std::set<std::string>& sensorsList, 2600 std::set<std::string>& sensorsModified) 2601 { 2602 for (const auto& chassisSensor : sensorsList) 2603 { 2604 sdbusplus::message::object_path path(chassisSensor); 2605 std::string thisSensorName = path.filename(); 2606 if (thisSensorName.empty()) 2607 { 2608 continue; 2609 } 2610 if (thisSensorName == sensorName) 2611 { 2612 sensorsModified.emplace(chassisSensor); 2613 return true; 2614 } 2615 } 2616 return false; 2617 } 2618 2619 inline std::pair<std::string, std::string> 2620 splitSensorNameAndType(std::string_view sensorId) 2621 { 2622 size_t index = sensorId.find('_'); 2623 if (index == std::string::npos) 2624 { 2625 return std::make_pair<std::string, std::string>("", ""); 2626 } 2627 std::string sensorType{sensorId.substr(0, index)}; 2628 std::string sensorName{sensorId.substr(index + 1)}; 2629 // fan_pwm and fan_tach need special handling 2630 if (sensorType == "fantach" || sensorType == "fanpwm") 2631 { 2632 sensorType.insert(3, 1, '_'); 2633 } 2634 return std::make_pair(sensorType, sensorName); 2635 } 2636 2637 /** 2638 * @brief Entry point for overriding sensor values of given sensor 2639 * 2640 * @param sensorAsyncResp response object 2641 * @param allCollections Collections extract from sensors' request patch info 2642 * @param chassisSubNode Chassis Node for which the query has to happen 2643 */ 2644 inline void setSensorsOverride( 2645 const std::shared_ptr<SensorsAsyncResp>& sensorAsyncResp, 2646 std::unordered_map<std::string, std::vector<nlohmann::json::object_t>>& 2647 allCollections) 2648 { 2649 BMCWEB_LOG_INFO("setSensorsOverride for subNode{}", 2650 sensorAsyncResp->chassisSubNode); 2651 2652 std::string_view propertyValueName; 2653 std::unordered_map<std::string, std::pair<double, std::string>> overrideMap; 2654 std::string memberId; 2655 double value = 0.0; 2656 for (auto& collectionItems : allCollections) 2657 { 2658 if (collectionItems.first == "Temperatures") 2659 { 2660 propertyValueName = "ReadingCelsius"; 2661 } 2662 else if (collectionItems.first == "Fans") 2663 { 2664 propertyValueName = "Reading"; 2665 } 2666 else 2667 { 2668 propertyValueName = "ReadingVolts"; 2669 } 2670 for (auto& item : collectionItems.second) 2671 { 2672 if (!json_util::readJsonObject( 2673 item, sensorAsyncResp->asyncResp->res, "MemberId", memberId, 2674 propertyValueName, value)) 2675 { 2676 return; 2677 } 2678 overrideMap.emplace(memberId, 2679 std::make_pair(value, collectionItems.first)); 2680 } 2681 } 2682 2683 auto getChassisSensorListCb = 2684 [sensorAsyncResp, overrideMap, 2685 propertyValueNameStr = std::string(propertyValueName)]( 2686 const std::shared_ptr<std::set<std::string>>& sensorsList) { 2687 // Match sensor names in the PATCH request to those managed by the 2688 // chassis node 2689 const std::shared_ptr<std::set<std::string>> sensorNames = 2690 std::make_shared<std::set<std::string>>(); 2691 for (const auto& item : overrideMap) 2692 { 2693 const auto& sensor = item.first; 2694 std::pair<std::string, std::string> sensorNameType = 2695 splitSensorNameAndType(sensor); 2696 if (!findSensorNameUsingSensorPath(sensorNameType.second, 2697 *sensorsList, *sensorNames)) 2698 { 2699 BMCWEB_LOG_INFO("Unable to find memberId {}", item.first); 2700 messages::resourceNotFound(sensorAsyncResp->asyncResp->res, 2701 item.second.second, item.first); 2702 return; 2703 } 2704 } 2705 // Get the connection to which the memberId belongs 2706 auto getObjectsWithConnectionCb = 2707 [sensorAsyncResp, overrideMap, propertyValueNameStr]( 2708 const std::set<std::string>& /*connections*/, 2709 const std::set<std::pair<std::string, std::string>>& 2710 objectsWithConnection) { 2711 if (objectsWithConnection.size() != overrideMap.size()) 2712 { 2713 BMCWEB_LOG_INFO( 2714 "Unable to find all objects with proper connection {} requested {}", 2715 objectsWithConnection.size(), overrideMap.size()); 2716 messages::resourceNotFound(sensorAsyncResp->asyncResp->res, 2717 sensorAsyncResp->chassisSubNode == 2718 sensors::node::thermal 2719 ? "Temperatures" 2720 : "Voltages", 2721 "Count"); 2722 return; 2723 } 2724 for (const auto& item : objectsWithConnection) 2725 { 2726 sdbusplus::message::object_path path(item.first); 2727 std::string sensorName = path.filename(); 2728 if (sensorName.empty()) 2729 { 2730 messages::internalError(sensorAsyncResp->asyncResp->res); 2731 return; 2732 } 2733 std::string id = path.parent_path().filename(); 2734 auto remove = std::ranges::remove(id, '_'); 2735 id.erase(std::ranges::begin(remove), id.end()); 2736 id += "_"; 2737 id += sensorName; 2738 2739 const auto& iterator = overrideMap.find(id); 2740 if (iterator == overrideMap.end()) 2741 { 2742 BMCWEB_LOG_INFO("Unable to find sensor object{}", 2743 item.first); 2744 messages::internalError(sensorAsyncResp->asyncResp->res); 2745 return; 2746 } 2747 setDbusProperty(sensorAsyncResp->asyncResp, 2748 propertyValueNameStr, item.second, item.first, 2749 "xyz.openbmc_project.Sensor.Value", "Value", 2750 iterator->second.first); 2751 } 2752 }; 2753 // Get object with connection for the given sensor name 2754 getObjectsWithConnection(sensorAsyncResp, sensorNames, 2755 std::move(getObjectsWithConnectionCb)); 2756 }; 2757 // get full sensor list for the given chassisId and cross verify the sensor. 2758 getChassis(sensorAsyncResp->asyncResp, sensorAsyncResp->chassisId, 2759 sensorAsyncResp->chassisSubNode, sensorAsyncResp->types, 2760 std::move(getChassisSensorListCb)); 2761 } 2762 2763 /** 2764 * @brief Retrieves mapping of Redfish URIs to sensor value property to D-Bus 2765 * path of the sensor. 2766 * 2767 * Function builds valid Redfish response for sensor query of given chassis and 2768 * node. It then builds metadata about Redfish<->D-Bus correlations and provides 2769 * it to caller in a callback. 2770 * 2771 * @param chassis Chassis for which retrieval should be performed 2772 * @param node Node (group) of sensors. See sensors::node for supported values 2773 * @param mapComplete Callback to be called with retrieval result 2774 */ 2775 template <typename Callback> 2776 inline void retrieveUriToDbusMap(const std::string& chassis, 2777 const std::string& node, 2778 Callback&& mapComplete) 2779 { 2780 decltype(sensors::paths)::const_iterator pathIt = 2781 std::find_if(sensors::paths.cbegin(), sensors::paths.cend(), 2782 [&node](auto&& val) { return val.first == node; }); 2783 if (pathIt == sensors::paths.cend()) 2784 { 2785 BMCWEB_LOG_ERROR("Wrong node provided : {}", node); 2786 std::map<std::string, std::string> noop; 2787 mapComplete(boost::beast::http::status::bad_request, noop); 2788 return; 2789 } 2790 2791 auto asyncResp = std::make_shared<bmcweb::AsyncResp>(); 2792 auto callback = [asyncResp, 2793 mapCompleteCb = std::forward<Callback>(mapComplete)]( 2794 const boost::beast::http::status status, 2795 const std::map<std::string, std::string>& uriToDbus) { 2796 mapCompleteCb(status, uriToDbus); 2797 }; 2798 2799 auto resp = std::make_shared<SensorsAsyncResp>( 2800 asyncResp, chassis, pathIt->second, node, std::move(callback)); 2801 getChassisData(resp); 2802 } 2803 2804 namespace sensors 2805 { 2806 2807 inline void getChassisCallback( 2808 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2809 std::string_view chassisId, std::string_view chassisSubNode, 2810 const std::shared_ptr<std::set<std::string>>& sensorNames) 2811 { 2812 BMCWEB_LOG_DEBUG("getChassisCallback enter "); 2813 2814 nlohmann::json& entriesArray = asyncResp->res.jsonValue["Members"]; 2815 for (const std::string& sensor : *sensorNames) 2816 { 2817 BMCWEB_LOG_DEBUG("Adding sensor: {}", sensor); 2818 2819 sdbusplus::message::object_path path(sensor); 2820 std::string sensorName = path.filename(); 2821 if (sensorName.empty()) 2822 { 2823 BMCWEB_LOG_ERROR("Invalid sensor path: {}", sensor); 2824 messages::internalError(asyncResp->res); 2825 return; 2826 } 2827 std::string type = path.parent_path().filename(); 2828 // fan_tach has an underscore in it, so remove it to "normalize" the 2829 // type in the URI 2830 auto remove = std::ranges::remove(type, '_'); 2831 type.erase(std::ranges::begin(remove), type.end()); 2832 2833 nlohmann::json::object_t member; 2834 std::string id = type; 2835 id += "_"; 2836 id += sensorName; 2837 member["@odata.id"] = boost::urls::format( 2838 "/redfish/v1/Chassis/{}/{}/{}", chassisId, chassisSubNode, id); 2839 2840 entriesArray.emplace_back(std::move(member)); 2841 } 2842 2843 asyncResp->res.jsonValue["Members@odata.count"] = entriesArray.size(); 2844 BMCWEB_LOG_DEBUG("getChassisCallback exit"); 2845 } 2846 2847 inline void handleSensorCollectionGet( 2848 App& app, const crow::Request& req, 2849 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2850 const std::string& chassisId) 2851 { 2852 query_param::QueryCapabilities capabilities = { 2853 .canDelegateExpandLevel = 1, 2854 }; 2855 query_param::Query delegatedQuery; 2856 if (!redfish::setUpRedfishRouteWithDelegation(app, req, asyncResp, 2857 delegatedQuery, capabilities)) 2858 { 2859 return; 2860 } 2861 2862 if (delegatedQuery.expandType != query_param::ExpandType::None) 2863 { 2864 // we perform efficient expand. 2865 auto sensorsAsyncResp = std::make_shared<SensorsAsyncResp>( 2866 asyncResp, chassisId, sensors::dbus::sensorPaths, 2867 sensors::node::sensors, 2868 /*efficientExpand=*/true); 2869 getChassisData(sensorsAsyncResp); 2870 2871 BMCWEB_LOG_DEBUG( 2872 "SensorCollection doGet exit via efficient expand handler"); 2873 return; 2874 } 2875 2876 // We get all sensors as hyperlinkes in the chassis (this 2877 // implies we reply on the default query parameters handler) 2878 getChassis(asyncResp, chassisId, sensors::node::sensors, dbus::sensorPaths, 2879 std::bind_front(sensors::getChassisCallback, asyncResp, 2880 chassisId, sensors::node::sensors)); 2881 } 2882 2883 inline void 2884 getSensorFromDbus(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2885 const std::string& sensorPath, 2886 const ::dbus::utility::MapperGetObject& mapperResponse) 2887 { 2888 if (mapperResponse.size() != 1) 2889 { 2890 messages::internalError(asyncResp->res); 2891 return; 2892 } 2893 const auto& valueIface = *mapperResponse.begin(); 2894 const std::string& connectionName = valueIface.first; 2895 BMCWEB_LOG_DEBUG("Looking up {}", connectionName); 2896 BMCWEB_LOG_DEBUG("Path {}", sensorPath); 2897 2898 sdbusplus::asio::getAllProperties( 2899 *crow::connections::systemBus, connectionName, sensorPath, "", 2900 [asyncResp, 2901 sensorPath](const boost::system::error_code& ec, 2902 const ::dbus::utility::DBusPropertiesMap& valuesDict) { 2903 if (ec) 2904 { 2905 messages::internalError(asyncResp->res); 2906 return; 2907 } 2908 sdbusplus::message::object_path path(sensorPath); 2909 std::string name = path.filename(); 2910 path = path.parent_path(); 2911 std::string type = path.filename(); 2912 objectPropertiesToJson(name, type, sensors::node::sensors, valuesDict, 2913 asyncResp->res.jsonValue, nullptr); 2914 }); 2915 } 2916 2917 inline void handleSensorGet(App& app, const crow::Request& req, 2918 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2919 const std::string& chassisId, 2920 const std::string& sensorId) 2921 { 2922 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 2923 { 2924 return; 2925 } 2926 std::pair<std::string, std::string> nameType = 2927 splitSensorNameAndType(sensorId); 2928 if (nameType.first.empty() || nameType.second.empty()) 2929 { 2930 messages::resourceNotFound(asyncResp->res, sensorId, "Sensor"); 2931 return; 2932 } 2933 2934 asyncResp->res.jsonValue["@odata.id"] = boost::urls::format( 2935 "/redfish/v1/Chassis/{}/Sensors/{}", chassisId, sensorId); 2936 2937 BMCWEB_LOG_DEBUG("Sensor doGet enter"); 2938 2939 constexpr std::array<std::string_view, 1> interfaces = { 2940 "xyz.openbmc_project.Sensor.Value"}; 2941 std::string sensorPath = "/xyz/openbmc_project/sensors/" + nameType.first + 2942 '/' + nameType.second; 2943 // Get a list of all of the sensors that implement Sensor.Value 2944 // and get the path and service name associated with the sensor 2945 ::dbus::utility::getDbusObject( 2946 sensorPath, interfaces, 2947 [asyncResp, sensorId, 2948 sensorPath](const boost::system::error_code& ec, 2949 const ::dbus::utility::MapperGetObject& subtree) { 2950 BMCWEB_LOG_DEBUG("respHandler1 enter"); 2951 if (ec == boost::system::errc::io_error) 2952 { 2953 BMCWEB_LOG_WARNING("Sensor not found from getSensorPaths"); 2954 messages::resourceNotFound(asyncResp->res, sensorId, "Sensor"); 2955 return; 2956 } 2957 if (ec) 2958 { 2959 messages::internalError(asyncResp->res); 2960 BMCWEB_LOG_ERROR( 2961 "Sensor getSensorPaths resp_handler: Dbus error {}", ec); 2962 return; 2963 } 2964 getSensorFromDbus(asyncResp, sensorPath, subtree); 2965 BMCWEB_LOG_DEBUG("respHandler1 exit"); 2966 }); 2967 } 2968 2969 } // namespace sensors 2970 2971 inline void requestRoutesSensorCollection(App& app) 2972 { 2973 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/Sensors/") 2974 .privileges(redfish::privileges::getSensorCollection) 2975 .methods(boost::beast::http::verb::get)( 2976 std::bind_front(sensors::handleSensorCollectionGet, std::ref(app))); 2977 } 2978 2979 inline void requestRoutesSensor(App& app) 2980 { 2981 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/Sensors/<str>/") 2982 .privileges(redfish::privileges::getSensor) 2983 .methods(boost::beast::http::verb::get)( 2984 std::bind_front(sensors::handleSensorGet, std::ref(app))); 2985 } 2986 2987 } // namespace redfish 2988