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