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