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