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