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 // Get all object paths and their interfaces for current connection 1453 sdbusplus::message::object_path path("/xyz/openbmc_project/inventory"); 1454 dbus::utility::getManagedObjects( 1455 invConnection, path, 1456 [sensorsAsyncResp, inventoryItems, invConnections, 1457 callback{std::forward<Callback>(callback)}, invConnectionsIndex]( 1458 const boost::system::error_code& ec, 1459 const dbus::utility::ManagedObjectType& resp) { 1460 BMCWEB_LOG_DEBUG << "getInventoryItemsData respHandler enter"; 1461 if (ec) 1462 { 1463 BMCWEB_LOG_ERROR 1464 << "getInventoryItemsData respHandler DBus error " << ec; 1465 messages::internalError(sensorsAsyncResp->asyncResp->res); 1466 return; 1467 } 1468 1469 // Loop through returned object paths 1470 for (const auto& objDictEntry : resp) 1471 { 1472 const std::string& objPath = 1473 static_cast<const std::string&>(objDictEntry.first); 1474 1475 // If this object path is one of the specified inventory items 1476 InventoryItem* inventoryItem = findInventoryItem(inventoryItems, 1477 objPath); 1478 if (inventoryItem != nullptr) 1479 { 1480 // Store inventory data in InventoryItem 1481 storeInventoryItemData(*inventoryItem, objDictEntry.second); 1482 } 1483 } 1484 1485 // Recurse to get inventory item data from next connection 1486 getInventoryItemsData(sensorsAsyncResp, inventoryItems, 1487 invConnections, std::move(callback), 1488 invConnectionsIndex + 1); 1489 1490 BMCWEB_LOG_DEBUG << "getInventoryItemsData respHandler exit"; 1491 }); 1492 } 1493 1494 BMCWEB_LOG_DEBUG << "getInventoryItemsData exit"; 1495 } 1496 1497 /** 1498 * @brief Gets connections that provide D-Bus data for inventory items. 1499 * 1500 * Gets the D-Bus connections (services) that provide data for the inventory 1501 * items that are associated with sensors. 1502 * 1503 * Finds the connections asynchronously. Invokes callback when information has 1504 * been obtained. 1505 * 1506 * The callback must have the following signature: 1507 * @code 1508 * callback(std::shared_ptr<std::set<std::string>> invConnections) 1509 * @endcode 1510 * 1511 * @param sensorsAsyncResp Pointer to object holding response data. 1512 * @param inventoryItems D-Bus inventory items associated with sensors. 1513 * @param callback Callback to invoke when connections have been obtained. 1514 */ 1515 template <typename Callback> 1516 static void getInventoryItemsConnections( 1517 const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp, 1518 const std::shared_ptr<std::vector<InventoryItem>>& inventoryItems, 1519 Callback&& callback) 1520 { 1521 BMCWEB_LOG_DEBUG << "getInventoryItemsConnections enter"; 1522 1523 const std::string path = "/xyz/openbmc_project/inventory"; 1524 constexpr std::array<std::string_view, 4> interfaces = { 1525 "xyz.openbmc_project.Inventory.Item", 1526 "xyz.openbmc_project.Inventory.Item.PowerSupply", 1527 "xyz.openbmc_project.Inventory.Decorator.Asset", 1528 "xyz.openbmc_project.State.Decorator.OperationalStatus"}; 1529 1530 // Make call to ObjectMapper to find all inventory items 1531 dbus::utility::getSubTree( 1532 path, 0, interfaces, 1533 [callback{std::forward<Callback>(callback)}, sensorsAsyncResp, 1534 inventoryItems]( 1535 const boost::system::error_code& ec, 1536 const dbus::utility::MapperGetSubTreeResponse& subtree) { 1537 // Response handler for parsing output from GetSubTree 1538 BMCWEB_LOG_DEBUG << "getInventoryItemsConnections respHandler enter"; 1539 if (ec) 1540 { 1541 messages::internalError(sensorsAsyncResp->asyncResp->res); 1542 BMCWEB_LOG_ERROR 1543 << "getInventoryItemsConnections respHandler DBus error " << ec; 1544 return; 1545 } 1546 1547 // Make unique list of connections for desired inventory items 1548 std::shared_ptr<std::set<std::string>> invConnections = 1549 std::make_shared<std::set<std::string>>(); 1550 1551 // Loop through objects from GetSubTree 1552 for (const std::pair< 1553 std::string, 1554 std::vector<std::pair<std::string, std::vector<std::string>>>>& 1555 object : subtree) 1556 { 1557 // Check if object path is one of the specified inventory items 1558 const std::string& objPath = object.first; 1559 if (findInventoryItem(inventoryItems, objPath) != nullptr) 1560 { 1561 // Store all connections to inventory item 1562 for (const std::pair<std::string, std::vector<std::string>>& 1563 objData : object.second) 1564 { 1565 const std::string& invConnection = objData.first; 1566 invConnections->insert(invConnection); 1567 } 1568 } 1569 } 1570 1571 callback(invConnections); 1572 BMCWEB_LOG_DEBUG << "getInventoryItemsConnections respHandler exit"; 1573 }); 1574 BMCWEB_LOG_DEBUG << "getInventoryItemsConnections exit"; 1575 } 1576 1577 /** 1578 * @brief Gets associations from sensors to inventory items. 1579 * 1580 * Looks for ObjectMapper associations from the specified sensors to related 1581 * inventory items. Then finds the associations from those inventory items to 1582 * their LEDs, if any. 1583 * 1584 * Finds the inventory items asynchronously. Invokes callback when information 1585 * has been obtained. 1586 * 1587 * The callback must have the following signature: 1588 * @code 1589 * callback(std::shared_ptr<std::vector<InventoryItem>> inventoryItems) 1590 * @endcode 1591 * 1592 * @param sensorsAsyncResp Pointer to object holding response data. 1593 * @param sensorNames All sensors within the current chassis. 1594 * implements ObjectManager. 1595 * @param callback Callback to invoke when inventory items have been obtained. 1596 */ 1597 template <typename Callback> 1598 static void getInventoryItemAssociations( 1599 const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp, 1600 const std::shared_ptr<std::set<std::string>>& sensorNames, 1601 Callback&& callback) 1602 { 1603 BMCWEB_LOG_DEBUG << "getInventoryItemAssociations enter"; 1604 1605 // Call GetManagedObjects on the ObjectMapper to get all associations 1606 sdbusplus::message::object_path path("/"); 1607 dbus::utility::getManagedObjects( 1608 "xyz.openbmc_project.ObjectMapper", path, 1609 [callback{std::forward<Callback>(callback)}, sensorsAsyncResp, 1610 sensorNames](const boost::system::error_code& ec, 1611 const dbus::utility::ManagedObjectType& resp) { 1612 BMCWEB_LOG_DEBUG << "getInventoryItemAssociations respHandler enter"; 1613 if (ec) 1614 { 1615 BMCWEB_LOG_ERROR 1616 << "getInventoryItemAssociations respHandler DBus error " << ec; 1617 messages::internalError(sensorsAsyncResp->asyncResp->res); 1618 return; 1619 } 1620 1621 // Create vector to hold list of inventory items 1622 std::shared_ptr<std::vector<InventoryItem>> inventoryItems = 1623 std::make_shared<std::vector<InventoryItem>>(); 1624 1625 // Loop through returned object paths 1626 std::string sensorAssocPath; 1627 sensorAssocPath.reserve(128); // avoid memory allocations 1628 for (const auto& objDictEntry : resp) 1629 { 1630 const std::string& objPath = 1631 static_cast<const std::string&>(objDictEntry.first); 1632 1633 // If path is inventory association for one of the specified sensors 1634 for (const std::string& sensorName : *sensorNames) 1635 { 1636 sensorAssocPath = sensorName; 1637 sensorAssocPath += "/inventory"; 1638 if (objPath == sensorAssocPath) 1639 { 1640 // Get Association interface for object path 1641 for (const auto& [interface, values] : objDictEntry.second) 1642 { 1643 if (interface == "xyz.openbmc_project.Association") 1644 { 1645 for (const auto& [valueName, value] : values) 1646 { 1647 if (valueName == "endpoints") 1648 { 1649 const std::vector<std::string>* endpoints = 1650 std::get_if<std::vector<std::string>>( 1651 &value); 1652 if ((endpoints != nullptr) && 1653 !endpoints->empty()) 1654 { 1655 // Add inventory item to vector 1656 const std::string& invItemPath = 1657 endpoints->front(); 1658 addInventoryItem(inventoryItems, 1659 invItemPath, 1660 sensorName); 1661 } 1662 } 1663 } 1664 } 1665 } 1666 break; 1667 } 1668 } 1669 } 1670 1671 // Now loop through the returned object paths again, this time to 1672 // find the leds associated with the inventory items we just found 1673 std::string inventoryAssocPath; 1674 inventoryAssocPath.reserve(128); // avoid memory allocations 1675 for (const auto& objDictEntry : resp) 1676 { 1677 const std::string& objPath = 1678 static_cast<const std::string&>(objDictEntry.first); 1679 1680 for (InventoryItem& inventoryItem : *inventoryItems) 1681 { 1682 inventoryAssocPath = inventoryItem.objectPath; 1683 inventoryAssocPath += "/leds"; 1684 if (objPath == inventoryAssocPath) 1685 { 1686 for (const auto& [interface, values] : objDictEntry.second) 1687 { 1688 if (interface == "xyz.openbmc_project.Association") 1689 { 1690 for (const auto& [valueName, value] : values) 1691 { 1692 if (valueName == "endpoints") 1693 { 1694 const std::vector<std::string>* endpoints = 1695 std::get_if<std::vector<std::string>>( 1696 &value); 1697 if ((endpoints != nullptr) && 1698 !endpoints->empty()) 1699 { 1700 // Add inventory item to vector 1701 // Store LED path in inventory item 1702 const std::string& ledPath = 1703 endpoints->front(); 1704 inventoryItem.ledObjectPath = ledPath; 1705 } 1706 } 1707 } 1708 } 1709 } 1710 1711 break; 1712 } 1713 } 1714 } 1715 callback(inventoryItems); 1716 BMCWEB_LOG_DEBUG << "getInventoryItemAssociations respHandler exit"; 1717 }); 1718 1719 BMCWEB_LOG_DEBUG << "getInventoryItemAssociations exit"; 1720 } 1721 1722 /** 1723 * @brief Gets D-Bus data for inventory item leds associated with sensors. 1724 * 1725 * Uses the specified connections (services) to obtain D-Bus data for inventory 1726 * item leds associated with sensors. Stores the resulting data in the 1727 * inventoryItems vector. 1728 * 1729 * This data is later used to provide sensor property values in the JSON 1730 * response. 1731 * 1732 * Finds the inventory item led data asynchronously. Invokes callback when data 1733 * has been obtained. 1734 * 1735 * The callback must have the following signature: 1736 * @code 1737 * callback() 1738 * @endcode 1739 * 1740 * This function is called recursively, obtaining data asynchronously from one 1741 * connection in each call. This ensures the callback is not invoked until the 1742 * last asynchronous function has completed. 1743 * 1744 * @param sensorsAsyncResp Pointer to object holding response data. 1745 * @param inventoryItems D-Bus inventory items associated with sensors. 1746 * @param ledConnections Connections that provide data for the inventory leds. 1747 * @param callback Callback to invoke when inventory data has been obtained. 1748 * @param ledConnectionsIndex Current index in ledConnections. Only specified 1749 * in recursive calls to this function. 1750 */ 1751 template <typename Callback> 1752 void getInventoryLedData( 1753 std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp, 1754 std::shared_ptr<std::vector<InventoryItem>> inventoryItems, 1755 std::shared_ptr<std::map<std::string, std::string>> ledConnections, 1756 Callback&& callback, size_t ledConnectionsIndex = 0) 1757 { 1758 BMCWEB_LOG_DEBUG << "getInventoryLedData enter"; 1759 1760 // If no more connections left, call callback 1761 if (ledConnectionsIndex >= ledConnections->size()) 1762 { 1763 callback(); 1764 BMCWEB_LOG_DEBUG << "getInventoryLedData exit"; 1765 return; 1766 } 1767 1768 // Get inventory item data from current connection 1769 auto it = ledConnections->begin(); 1770 std::advance(it, ledConnectionsIndex); 1771 if (it != ledConnections->end()) 1772 { 1773 const std::string& ledPath = (*it).first; 1774 const std::string& ledConnection = (*it).second; 1775 // Response handler for Get State property 1776 auto respHandler = 1777 [sensorsAsyncResp, inventoryItems, ledConnections, ledPath, 1778 callback{std::forward<Callback>(callback)}, ledConnectionsIndex]( 1779 const boost::system::error_code& ec, const std::string& state) { 1780 BMCWEB_LOG_DEBUG << "getInventoryLedData respHandler enter"; 1781 if (ec) 1782 { 1783 BMCWEB_LOG_ERROR 1784 << "getInventoryLedData respHandler DBus error " << ec; 1785 messages::internalError(sensorsAsyncResp->asyncResp->res); 1786 return; 1787 } 1788 1789 BMCWEB_LOG_DEBUG << "Led state: " << state; 1790 // Find inventory item with this LED object path 1791 InventoryItem* inventoryItem = 1792 findInventoryItemForLed(*inventoryItems, ledPath); 1793 if (inventoryItem != nullptr) 1794 { 1795 // Store LED state in InventoryItem 1796 if (state.ends_with("On")) 1797 { 1798 inventoryItem->ledState = LedState::ON; 1799 } 1800 else if (state.ends_with("Blink")) 1801 { 1802 inventoryItem->ledState = LedState::BLINK; 1803 } 1804 else if (state.ends_with("Off")) 1805 { 1806 inventoryItem->ledState = LedState::OFF; 1807 } 1808 else 1809 { 1810 inventoryItem->ledState = LedState::UNKNOWN; 1811 } 1812 } 1813 1814 // Recurse to get LED data from next connection 1815 getInventoryLedData(sensorsAsyncResp, inventoryItems, 1816 ledConnections, std::move(callback), 1817 ledConnectionsIndex + 1); 1818 1819 BMCWEB_LOG_DEBUG << "getInventoryLedData respHandler exit"; 1820 }; 1821 1822 // Get the State property for the current LED 1823 sdbusplus::asio::getProperty<std::string>( 1824 *crow::connections::systemBus, ledConnection, ledPath, 1825 "xyz.openbmc_project.Led.Physical", "State", 1826 std::move(respHandler)); 1827 } 1828 1829 BMCWEB_LOG_DEBUG << "getInventoryLedData exit"; 1830 } 1831 1832 /** 1833 * @brief Gets LED data for LEDs associated with given inventory items. 1834 * 1835 * Gets the D-Bus connections (services) that provide LED data for the LEDs 1836 * associated with the specified inventory items. Then gets the LED data from 1837 * each connection and stores it in the inventory item. 1838 * 1839 * This data is later used to provide sensor property values in the JSON 1840 * response. 1841 * 1842 * Finds the LED data asynchronously. Invokes callback when information has 1843 * been obtained. 1844 * 1845 * The callback must have the following signature: 1846 * @code 1847 * callback() 1848 * @endcode 1849 * 1850 * @param sensorsAsyncResp Pointer to object holding response data. 1851 * @param inventoryItems D-Bus inventory items associated with sensors. 1852 * @param callback Callback to invoke when inventory items have been obtained. 1853 */ 1854 template <typename Callback> 1855 void getInventoryLeds( 1856 std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp, 1857 std::shared_ptr<std::vector<InventoryItem>> inventoryItems, 1858 Callback&& callback) 1859 { 1860 BMCWEB_LOG_DEBUG << "getInventoryLeds enter"; 1861 1862 const std::string path = "/xyz/openbmc_project"; 1863 constexpr std::array<std::string_view, 1> interfaces = { 1864 "xyz.openbmc_project.Led.Physical"}; 1865 1866 // Make call to ObjectMapper to find all inventory items 1867 dbus::utility::getSubTree( 1868 path, 0, interfaces, 1869 [callback{std::forward<Callback>(callback)}, sensorsAsyncResp, 1870 inventoryItems]( 1871 const boost::system::error_code& ec, 1872 const dbus::utility::MapperGetSubTreeResponse& subtree) { 1873 // Response handler for parsing output from GetSubTree 1874 BMCWEB_LOG_DEBUG << "getInventoryLeds respHandler enter"; 1875 if (ec) 1876 { 1877 messages::internalError(sensorsAsyncResp->asyncResp->res); 1878 BMCWEB_LOG_ERROR << "getInventoryLeds respHandler DBus error " 1879 << ec; 1880 return; 1881 } 1882 1883 // Build map of LED object paths to connections 1884 std::shared_ptr<std::map<std::string, std::string>> ledConnections = 1885 std::make_shared<std::map<std::string, std::string>>(); 1886 1887 // Loop through objects from GetSubTree 1888 for (const std::pair< 1889 std::string, 1890 std::vector<std::pair<std::string, std::vector<std::string>>>>& 1891 object : subtree) 1892 { 1893 // Check if object path is LED for one of the specified inventory 1894 // items 1895 const std::string& ledPath = object.first; 1896 if (findInventoryItemForLed(*inventoryItems, ledPath) != nullptr) 1897 { 1898 // Add mapping from ledPath to connection 1899 const std::string& connection = object.second.begin()->first; 1900 (*ledConnections)[ledPath] = connection; 1901 BMCWEB_LOG_DEBUG << "Added mapping " << ledPath << " -> " 1902 << connection; 1903 } 1904 } 1905 1906 getInventoryLedData(sensorsAsyncResp, inventoryItems, ledConnections, 1907 std::move(callback)); 1908 BMCWEB_LOG_DEBUG << "getInventoryLeds respHandler exit"; 1909 }); 1910 BMCWEB_LOG_DEBUG << "getInventoryLeds exit"; 1911 } 1912 1913 /** 1914 * @brief Gets D-Bus data for Power Supply Attributes such as EfficiencyPercent 1915 * 1916 * Uses the specified connections (services) (currently assumes just one) to 1917 * obtain D-Bus data for Power Supply Attributes. Stores the resulting data in 1918 * the inventoryItems vector. Only stores data in Power Supply inventoryItems. 1919 * 1920 * This data is later used to provide sensor property values in the JSON 1921 * response. 1922 * 1923 * Finds the Power Supply Attributes data asynchronously. Invokes callback 1924 * when data has been obtained. 1925 * 1926 * The callback must have the following signature: 1927 * @code 1928 * callback(std::shared_ptr<std::vector<InventoryItem>> inventoryItems) 1929 * @endcode 1930 * 1931 * @param sensorsAsyncResp Pointer to object holding response data. 1932 * @param inventoryItems D-Bus inventory items associated with sensors. 1933 * @param psAttributesConnections Connections that provide data for the Power 1934 * Supply Attributes 1935 * @param callback Callback to invoke when data has been obtained. 1936 */ 1937 template <typename Callback> 1938 void getPowerSupplyAttributesData( 1939 const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp, 1940 std::shared_ptr<std::vector<InventoryItem>> inventoryItems, 1941 const std::map<std::string, std::string>& psAttributesConnections, 1942 Callback&& callback) 1943 { 1944 BMCWEB_LOG_DEBUG << "getPowerSupplyAttributesData enter"; 1945 1946 if (psAttributesConnections.empty()) 1947 { 1948 BMCWEB_LOG_DEBUG << "Can't find PowerSupplyAttributes, no connections!"; 1949 callback(inventoryItems); 1950 return; 1951 } 1952 1953 // Assuming just one connection (service) for now 1954 auto it = psAttributesConnections.begin(); 1955 1956 const std::string& psAttributesPath = (*it).first; 1957 const std::string& psAttributesConnection = (*it).second; 1958 1959 // Response handler for Get DeratingFactor property 1960 auto respHandler = 1961 [sensorsAsyncResp, inventoryItems, 1962 callback{std::forward<Callback>(callback)}]( 1963 const boost::system::error_code& ec, const uint32_t value) { 1964 BMCWEB_LOG_DEBUG << "getPowerSupplyAttributesData respHandler enter"; 1965 if (ec) 1966 { 1967 BMCWEB_LOG_ERROR 1968 << "getPowerSupplyAttributesData respHandler DBus error " << ec; 1969 messages::internalError(sensorsAsyncResp->asyncResp->res); 1970 return; 1971 } 1972 1973 BMCWEB_LOG_DEBUG << "PS EfficiencyPercent value: " << value; 1974 // Store value in Power Supply Inventory Items 1975 for (InventoryItem& inventoryItem : *inventoryItems) 1976 { 1977 if (inventoryItem.isPowerSupply) 1978 { 1979 inventoryItem.powerSupplyEfficiencyPercent = 1980 static_cast<int>(value); 1981 } 1982 } 1983 1984 BMCWEB_LOG_DEBUG << "getPowerSupplyAttributesData respHandler exit"; 1985 callback(inventoryItems); 1986 }; 1987 1988 // Get the DeratingFactor property for the PowerSupplyAttributes 1989 // Currently only property on the interface/only one we care about 1990 sdbusplus::asio::getProperty<uint32_t>( 1991 *crow::connections::systemBus, psAttributesConnection, psAttributesPath, 1992 "xyz.openbmc_project.Control.PowerSupplyAttributes", "DeratingFactor", 1993 std::move(respHandler)); 1994 1995 BMCWEB_LOG_DEBUG << "getPowerSupplyAttributesData exit"; 1996 } 1997 1998 /** 1999 * @brief Gets the Power Supply Attributes such as EfficiencyPercent 2000 * 2001 * Gets the D-Bus connection (service) that provides Power Supply Attributes 2002 * data. Then gets the Power Supply Attributes data from the connection 2003 * (currently just assumes 1 connection) and stores the data in the inventory 2004 * item. 2005 * 2006 * This data is later used to provide sensor property values in the JSON 2007 * response. DeratingFactor on D-Bus is mapped to EfficiencyPercent on Redfish. 2008 * 2009 * Finds the Power Supply Attributes data asynchronously. Invokes callback 2010 * when information has been obtained. 2011 * 2012 * The callback must have the following signature: 2013 * @code 2014 * callback(std::shared_ptr<std::vector<InventoryItem>> inventoryItems) 2015 * @endcode 2016 * 2017 * @param sensorsAsyncResp Pointer to object holding response data. 2018 * @param inventoryItems D-Bus inventory items associated with sensors. 2019 * @param callback Callback to invoke when data has been obtained. 2020 */ 2021 template <typename Callback> 2022 void getPowerSupplyAttributes( 2023 std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp, 2024 std::shared_ptr<std::vector<InventoryItem>> inventoryItems, 2025 Callback&& callback) 2026 { 2027 BMCWEB_LOG_DEBUG << "getPowerSupplyAttributes enter"; 2028 2029 // Only need the power supply attributes when the Power Schema 2030 if (sensorsAsyncResp->chassisSubNode != sensors::node::power) 2031 { 2032 BMCWEB_LOG_DEBUG << "getPowerSupplyAttributes exit since not Power"; 2033 callback(inventoryItems); 2034 return; 2035 } 2036 2037 constexpr std::array<std::string_view, 1> interfaces = { 2038 "xyz.openbmc_project.Control.PowerSupplyAttributes"}; 2039 2040 // Make call to ObjectMapper to find the PowerSupplyAttributes service 2041 dbus::utility::getSubTree( 2042 "/xyz/openbmc_project", 0, interfaces, 2043 [callback{std::forward<Callback>(callback)}, sensorsAsyncResp, 2044 inventoryItems]( 2045 const boost::system::error_code& ec, 2046 const dbus::utility::MapperGetSubTreeResponse& subtree) { 2047 // Response handler for parsing output from GetSubTree 2048 BMCWEB_LOG_DEBUG << "getPowerSupplyAttributes respHandler enter"; 2049 if (ec) 2050 { 2051 messages::internalError(sensorsAsyncResp->asyncResp->res); 2052 BMCWEB_LOG_ERROR 2053 << "getPowerSupplyAttributes respHandler DBus error " << ec; 2054 return; 2055 } 2056 if (subtree.empty()) 2057 { 2058 BMCWEB_LOG_DEBUG << "Can't find Power Supply Attributes!"; 2059 callback(inventoryItems); 2060 return; 2061 } 2062 2063 // Currently we only support 1 power supply attribute, use this for 2064 // all the power supplies. Build map of object path to connection. 2065 // Assume just 1 connection and 1 path for now. 2066 std::map<std::string, std::string> psAttributesConnections; 2067 2068 if (subtree[0].first.empty() || subtree[0].second.empty()) 2069 { 2070 BMCWEB_LOG_DEBUG << "Power Supply Attributes mapper error!"; 2071 callback(inventoryItems); 2072 return; 2073 } 2074 2075 const std::string& psAttributesPath = subtree[0].first; 2076 const std::string& connection = subtree[0].second.begin()->first; 2077 2078 if (connection.empty()) 2079 { 2080 BMCWEB_LOG_DEBUG << "Power Supply Attributes mapper error!"; 2081 callback(inventoryItems); 2082 return; 2083 } 2084 2085 psAttributesConnections[psAttributesPath] = connection; 2086 BMCWEB_LOG_DEBUG << "Added mapping " << psAttributesPath << " -> " 2087 << connection; 2088 2089 getPowerSupplyAttributesData(sensorsAsyncResp, inventoryItems, 2090 psAttributesConnections, 2091 std::move(callback)); 2092 BMCWEB_LOG_DEBUG << "getPowerSupplyAttributes respHandler exit"; 2093 }); 2094 BMCWEB_LOG_DEBUG << "getPowerSupplyAttributes exit"; 2095 } 2096 2097 /** 2098 * @brief Gets inventory items associated with sensors. 2099 * 2100 * Finds the inventory items that are associated with the specified sensors. 2101 * Then gets D-Bus data for the inventory items, such as presence and VPD. 2102 * 2103 * This data is later used to provide sensor property values in the JSON 2104 * response. 2105 * 2106 * Finds the inventory items asynchronously. Invokes callback when the 2107 * inventory items have been obtained. 2108 * 2109 * The callback must have the following signature: 2110 * @code 2111 * callback(std::shared_ptr<std::vector<InventoryItem>> inventoryItems) 2112 * @endcode 2113 * 2114 * @param sensorsAsyncResp Pointer to object holding response data. 2115 * @param sensorNames All sensors within the current chassis. 2116 * implements ObjectManager. 2117 * @param callback Callback to invoke when inventory items have been obtained. 2118 */ 2119 template <typename Callback> 2120 static void 2121 getInventoryItems(std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp, 2122 const std::shared_ptr<std::set<std::string>> sensorNames, 2123 Callback&& callback) 2124 { 2125 BMCWEB_LOG_DEBUG << "getInventoryItems enter"; 2126 auto getInventoryItemAssociationsCb = 2127 [sensorsAsyncResp, callback{std::forward<Callback>(callback)}]( 2128 std::shared_ptr<std::vector<InventoryItem>> inventoryItems) { 2129 BMCWEB_LOG_DEBUG << "getInventoryItemAssociationsCb enter"; 2130 auto getInventoryItemsConnectionsCb = 2131 [sensorsAsyncResp, inventoryItems, 2132 callback{std::forward<const Callback>(callback)}]( 2133 std::shared_ptr<std::set<std::string>> invConnections) { 2134 BMCWEB_LOG_DEBUG << "getInventoryItemsConnectionsCb enter"; 2135 auto getInventoryItemsDataCb = [sensorsAsyncResp, inventoryItems, 2136 callback{std::move(callback)}]() { 2137 BMCWEB_LOG_DEBUG << "getInventoryItemsDataCb enter"; 2138 2139 auto getInventoryLedsCb = [sensorsAsyncResp, inventoryItems, 2140 callback{std::move(callback)}]() { 2141 BMCWEB_LOG_DEBUG << "getInventoryLedsCb enter"; 2142 // Find Power Supply Attributes and get the data 2143 getPowerSupplyAttributes(sensorsAsyncResp, inventoryItems, 2144 std::move(callback)); 2145 BMCWEB_LOG_DEBUG << "getInventoryLedsCb exit"; 2146 }; 2147 2148 // Find led connections and get the data 2149 getInventoryLeds(sensorsAsyncResp, inventoryItems, 2150 std::move(getInventoryLedsCb)); 2151 BMCWEB_LOG_DEBUG << "getInventoryItemsDataCb exit"; 2152 }; 2153 2154 // Get inventory item data from connections 2155 getInventoryItemsData(sensorsAsyncResp, inventoryItems, 2156 invConnections, 2157 std::move(getInventoryItemsDataCb)); 2158 BMCWEB_LOG_DEBUG << "getInventoryItemsConnectionsCb exit"; 2159 }; 2160 2161 // Get connections that provide inventory item data 2162 getInventoryItemsConnections(sensorsAsyncResp, inventoryItems, 2163 std::move(getInventoryItemsConnectionsCb)); 2164 BMCWEB_LOG_DEBUG << "getInventoryItemAssociationsCb exit"; 2165 }; 2166 2167 // Get associations from sensors to inventory items 2168 getInventoryItemAssociations(sensorsAsyncResp, sensorNames, 2169 std::move(getInventoryItemAssociationsCb)); 2170 BMCWEB_LOG_DEBUG << "getInventoryItems exit"; 2171 } 2172 2173 /** 2174 * @brief Returns JSON PowerSupply object for the specified inventory item. 2175 * 2176 * Searches for a JSON PowerSupply object that matches the specified inventory 2177 * item. If one is not found, a new PowerSupply object is added to the JSON 2178 * array. 2179 * 2180 * Multiple sensors are often associated with one power supply inventory item. 2181 * As a result, multiple sensor values are stored in one JSON PowerSupply 2182 * object. 2183 * 2184 * @param powerSupplyArray JSON array containing Redfish PowerSupply objects. 2185 * @param inventoryItem Inventory item for the power supply. 2186 * @param chassisId Chassis that contains the power supply. 2187 * @return JSON PowerSupply object for the specified inventory item. 2188 */ 2189 inline nlohmann::json& getPowerSupply(nlohmann::json& powerSupplyArray, 2190 const InventoryItem& inventoryItem, 2191 const std::string& chassisId) 2192 { 2193 // Check if matching PowerSupply object already exists in JSON array 2194 for (nlohmann::json& powerSupply : powerSupplyArray) 2195 { 2196 if (powerSupply["Name"] == 2197 boost::replace_all_copy(inventoryItem.name, "_", " ")) 2198 { 2199 return powerSupply; 2200 } 2201 } 2202 2203 // Add new PowerSupply object to JSON array 2204 powerSupplyArray.push_back({}); 2205 nlohmann::json& powerSupply = powerSupplyArray.back(); 2206 boost::urls::url url = boost::urls::format("/redfish/v1/Chassis/{}/Power", 2207 chassisId); 2208 url.set_fragment(("/PowerSupplies"_json_pointer).to_string()); 2209 powerSupply["@odata.id"] = std::move(url); 2210 powerSupply["Name"] = boost::replace_all_copy(inventoryItem.name, "_", " "); 2211 powerSupply["Manufacturer"] = inventoryItem.manufacturer; 2212 powerSupply["Model"] = inventoryItem.model; 2213 powerSupply["PartNumber"] = inventoryItem.partNumber; 2214 powerSupply["SerialNumber"] = inventoryItem.serialNumber; 2215 setLedState(powerSupply, &inventoryItem); 2216 2217 if (inventoryItem.powerSupplyEfficiencyPercent >= 0) 2218 { 2219 powerSupply["EfficiencyPercent"] = 2220 inventoryItem.powerSupplyEfficiencyPercent; 2221 } 2222 2223 powerSupply["Status"]["State"] = getState(&inventoryItem); 2224 const char* health = inventoryItem.isFunctional ? "OK" : "Critical"; 2225 powerSupply["Status"]["Health"] = health; 2226 2227 return powerSupply; 2228 } 2229 2230 /** 2231 * @brief Gets the values of the specified sensors. 2232 * 2233 * Stores the results as JSON in the SensorsAsyncResp. 2234 * 2235 * Gets the sensor values asynchronously. Stores the results later when the 2236 * information has been obtained. 2237 * 2238 * The sensorNames set contains all requested sensors for the current chassis. 2239 * 2240 * To minimize the number of DBus calls, the DBus method 2241 * org.freedesktop.DBus.ObjectManager.GetManagedObjects() is used to get the 2242 * values of all sensors provided by a connection (service). 2243 * 2244 * The connections set contains all the connections that provide sensor values. 2245 * 2246 * The InventoryItem vector contains D-Bus inventory items associated with the 2247 * sensors. Inventory item data is needed for some Redfish sensor properties. 2248 * 2249 * @param SensorsAsyncResp Pointer to object holding response data. 2250 * @param sensorNames All requested sensors within the current chassis. 2251 * @param connections Connections that provide sensor values. 2252 * implements ObjectManager. 2253 * @param inventoryItems Inventory items associated with the sensors. 2254 */ 2255 inline void getSensorData( 2256 const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp, 2257 const std::shared_ptr<std::set<std::string>>& sensorNames, 2258 const std::set<std::string>& connections, 2259 const std::shared_ptr<std::vector<InventoryItem>>& inventoryItems) 2260 { 2261 BMCWEB_LOG_DEBUG << "getSensorData enter"; 2262 // Get managed objects from all services exposing sensors 2263 for (const std::string& connection : connections) 2264 { 2265 sdbusplus::message::object_path sensorPath( 2266 "/xyz/openbmc_project/sensors"); 2267 dbus::utility::getManagedObjects( 2268 connection, sensorPath, 2269 [sensorsAsyncResp, sensorNames, 2270 inventoryItems](const boost::system::error_code& ec, 2271 const dbus::utility::ManagedObjectType& resp) { 2272 BMCWEB_LOG_DEBUG << "getManagedObjectsCb enter"; 2273 if (ec) 2274 { 2275 BMCWEB_LOG_ERROR << "getManagedObjectsCb DBUS error: " << ec; 2276 messages::internalError(sensorsAsyncResp->asyncResp->res); 2277 return; 2278 } 2279 // Go through all objects and update response with sensor data 2280 for (const auto& objDictEntry : resp) 2281 { 2282 const std::string& objPath = 2283 static_cast<const std::string&>(objDictEntry.first); 2284 BMCWEB_LOG_DEBUG << "getManagedObjectsCb parsing object " 2285 << objPath; 2286 2287 std::vector<std::string> split; 2288 // Reserve space for 2289 // /xyz/openbmc_project/sensors/<name>/<subname> 2290 split.reserve(6); 2291 // NOLINTNEXTLINE 2292 bmcweb::split(split, objPath, '/'); 2293 if (split.size() < 6) 2294 { 2295 BMCWEB_LOG_ERROR << "Got path that isn't long enough " 2296 << objPath; 2297 continue; 2298 } 2299 // These indexes aren't intuitive, as split puts an empty 2300 // string at the beginning 2301 const std::string& sensorType = split[4]; 2302 const std::string& sensorName = split[5]; 2303 BMCWEB_LOG_DEBUG << "sensorName " << sensorName 2304 << " sensorType " << sensorType; 2305 if (sensorNames->find(objPath) == sensorNames->end()) 2306 { 2307 BMCWEB_LOG_DEBUG << sensorName << " not in sensor list "; 2308 continue; 2309 } 2310 2311 // Find inventory item (if any) associated with sensor 2312 InventoryItem* inventoryItem = 2313 findInventoryItemForSensor(inventoryItems, objPath); 2314 2315 const std::string& sensorSchema = 2316 sensorsAsyncResp->chassisSubNode; 2317 2318 nlohmann::json* sensorJson = nullptr; 2319 2320 if (sensorSchema == sensors::node::sensors && 2321 !sensorsAsyncResp->efficientExpand) 2322 { 2323 std::string sensorTypeEscaped(sensorType); 2324 sensorTypeEscaped.erase( 2325 std::remove(sensorTypeEscaped.begin(), 2326 sensorTypeEscaped.end(), '_'), 2327 sensorTypeEscaped.end()); 2328 std::string sensorId(sensorTypeEscaped); 2329 sensorId += "_"; 2330 sensorId += sensorName; 2331 2332 sensorsAsyncResp->asyncResp->res.jsonValue["@odata.id"] = 2333 boost::urls::format("/redfish/v1/Chassis/{}/{}/{}", 2334 sensorsAsyncResp->chassisId, 2335 sensorsAsyncResp->chassisSubNode, 2336 sensorId); 2337 sensorJson = &(sensorsAsyncResp->asyncResp->res.jsonValue); 2338 } 2339 else 2340 { 2341 std::string fieldName; 2342 if (sensorsAsyncResp->efficientExpand) 2343 { 2344 fieldName = "Members"; 2345 } 2346 else if (sensorType == "temperature") 2347 { 2348 fieldName = "Temperatures"; 2349 } 2350 else if (sensorType == "fan" || sensorType == "fan_tach" || 2351 sensorType == "fan_pwm") 2352 { 2353 fieldName = "Fans"; 2354 } 2355 else if (sensorType == "voltage") 2356 { 2357 fieldName = "Voltages"; 2358 } 2359 else if (sensorType == "power") 2360 { 2361 if (sensorName == "total_power") 2362 { 2363 fieldName = "PowerControl"; 2364 } 2365 else if ((inventoryItem != nullptr) && 2366 (inventoryItem->isPowerSupply)) 2367 { 2368 fieldName = "PowerSupplies"; 2369 } 2370 else 2371 { 2372 // Other power sensors are in SensorCollection 2373 continue; 2374 } 2375 } 2376 else 2377 { 2378 BMCWEB_LOG_ERROR << "Unsure how to handle sensorType " 2379 << sensorType; 2380 continue; 2381 } 2382 2383 nlohmann::json& tempArray = 2384 sensorsAsyncResp->asyncResp->res.jsonValue[fieldName]; 2385 if (fieldName == "PowerControl") 2386 { 2387 if (tempArray.empty()) 2388 { 2389 // Put multiple "sensors" into a single 2390 // PowerControl. Follows MemberId naming and 2391 // naming in power.hpp. 2392 nlohmann::json::object_t power; 2393 boost::urls::url url = boost::urls::format( 2394 "/redfish/v1/Chassis/{}/{}", 2395 sensorsAsyncResp->chassisId, 2396 sensorsAsyncResp->chassisSubNode); 2397 url.set_fragment((""_json_pointer / fieldName / "0") 2398 .to_string()); 2399 power["@odata.id"] = std::move(url); 2400 tempArray.emplace_back(std::move(power)); 2401 } 2402 sensorJson = &(tempArray.back()); 2403 } 2404 else if (fieldName == "PowerSupplies") 2405 { 2406 if (inventoryItem != nullptr) 2407 { 2408 sensorJson = 2409 &(getPowerSupply(tempArray, *inventoryItem, 2410 sensorsAsyncResp->chassisId)); 2411 } 2412 } 2413 else if (fieldName == "Members") 2414 { 2415 std::string sensorTypeEscaped(sensorType); 2416 sensorTypeEscaped.erase( 2417 std::remove(sensorTypeEscaped.begin(), 2418 sensorTypeEscaped.end(), '_'), 2419 sensorTypeEscaped.end()); 2420 std::string sensorId(sensorTypeEscaped); 2421 sensorId += "_"; 2422 sensorId += sensorName; 2423 2424 nlohmann::json::object_t member; 2425 member["@odata.id"] = boost::urls::format( 2426 "/redfish/v1/Chassis/{}/{}/{}", 2427 sensorsAsyncResp->chassisId, 2428 sensorsAsyncResp->chassisSubNode, sensorId); 2429 tempArray.emplace_back(std::move(member)); 2430 sensorJson = &(tempArray.back()); 2431 } 2432 else 2433 { 2434 nlohmann::json::object_t member; 2435 boost::urls::url url = boost::urls::format( 2436 "/redfish/v1/Chassis/{}/{}", 2437 sensorsAsyncResp->chassisId, 2438 sensorsAsyncResp->chassisSubNode); 2439 url.set_fragment( 2440 (""_json_pointer / fieldName).to_string()); 2441 member["@odata.id"] = std::move(url); 2442 tempArray.emplace_back(std::move(member)); 2443 sensorJson = &(tempArray.back()); 2444 } 2445 } 2446 2447 if (sensorJson != nullptr) 2448 { 2449 objectInterfacesToJson(sensorName, sensorType, 2450 sensorsAsyncResp->chassisSubNode, 2451 objDictEntry.second, *sensorJson, 2452 inventoryItem); 2453 2454 std::string path = "/xyz/openbmc_project/sensors/"; 2455 path += sensorType; 2456 path += "/"; 2457 path += sensorName; 2458 sensorsAsyncResp->addMetadata(*sensorJson, path); 2459 } 2460 } 2461 if (sensorsAsyncResp.use_count() == 1) 2462 { 2463 sortJSONResponse(sensorsAsyncResp); 2464 if (sensorsAsyncResp->chassisSubNode == 2465 sensors::node::sensors && 2466 sensorsAsyncResp->efficientExpand) 2467 { 2468 sensorsAsyncResp->asyncResp->res 2469 .jsonValue["Members@odata.count"] = 2470 sensorsAsyncResp->asyncResp->res.jsonValue["Members"] 2471 .size(); 2472 } 2473 else if (sensorsAsyncResp->chassisSubNode == 2474 sensors::node::thermal) 2475 { 2476 populateFanRedundancy(sensorsAsyncResp); 2477 } 2478 } 2479 BMCWEB_LOG_DEBUG << "getManagedObjectsCb exit"; 2480 }); 2481 } 2482 BMCWEB_LOG_DEBUG << "getSensorData exit"; 2483 } 2484 2485 inline void 2486 processSensorList(const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp, 2487 const std::shared_ptr<std::set<std::string>>& sensorNames) 2488 { 2489 auto getConnectionCb = [sensorsAsyncResp, sensorNames]( 2490 const std::set<std::string>& connections) { 2491 BMCWEB_LOG_DEBUG << "getConnectionCb enter"; 2492 auto getInventoryItemsCb = 2493 [sensorsAsyncResp, sensorNames, 2494 connections](const std::shared_ptr<std::vector<InventoryItem>>& 2495 inventoryItems) { 2496 BMCWEB_LOG_DEBUG << "getInventoryItemsCb enter"; 2497 // Get sensor data and store results in JSON 2498 getSensorData(sensorsAsyncResp, sensorNames, connections, 2499 inventoryItems); 2500 BMCWEB_LOG_DEBUG << "getInventoryItemsCb exit"; 2501 }; 2502 2503 // Get inventory items associated with sensors 2504 getInventoryItems(sensorsAsyncResp, sensorNames, 2505 std::move(getInventoryItemsCb)); 2506 2507 BMCWEB_LOG_DEBUG << "getConnectionCb exit"; 2508 }; 2509 2510 // Get set of connections that provide sensor values 2511 getConnections(sensorsAsyncResp, sensorNames, std::move(getConnectionCb)); 2512 } 2513 2514 /** 2515 * @brief Entry point for retrieving sensors data related to requested 2516 * chassis. 2517 * @param SensorsAsyncResp Pointer to object holding response data 2518 */ 2519 inline void 2520 getChassisData(const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp) 2521 { 2522 BMCWEB_LOG_DEBUG << "getChassisData enter"; 2523 auto getChassisCb = 2524 [sensorsAsyncResp]( 2525 const std::shared_ptr<std::set<std::string>>& sensorNames) { 2526 BMCWEB_LOG_DEBUG << "getChassisCb enter"; 2527 processSensorList(sensorsAsyncResp, sensorNames); 2528 BMCWEB_LOG_DEBUG << "getChassisCb exit"; 2529 }; 2530 // SensorCollection doesn't contain the Redundancy property 2531 if (sensorsAsyncResp->chassisSubNode != sensors::node::sensors) 2532 { 2533 sensorsAsyncResp->asyncResp->res.jsonValue["Redundancy"] = 2534 nlohmann::json::array(); 2535 } 2536 // Get set of sensors in chassis 2537 getChassis(sensorsAsyncResp->asyncResp, sensorsAsyncResp->chassisId, 2538 sensorsAsyncResp->chassisSubNode, sensorsAsyncResp->types, 2539 std::move(getChassisCb)); 2540 BMCWEB_LOG_DEBUG << "getChassisData exit"; 2541 } 2542 2543 /** 2544 * @brief Find the requested sensorName in the list of all sensors supplied by 2545 * the chassis node 2546 * 2547 * @param sensorName The sensor name supplied in the PATCH request 2548 * @param sensorsList The list of sensors managed by the chassis node 2549 * @param sensorsModified The list of sensors that were found as a result of 2550 * repeated calls to this function 2551 */ 2552 inline bool 2553 findSensorNameUsingSensorPath(std::string_view sensorName, 2554 const std::set<std::string>& sensorsList, 2555 std::set<std::string>& sensorsModified) 2556 { 2557 for (const auto& chassisSensor : sensorsList) 2558 { 2559 sdbusplus::message::object_path path(chassisSensor); 2560 std::string thisSensorName = path.filename(); 2561 if (thisSensorName.empty()) 2562 { 2563 continue; 2564 } 2565 if (thisSensorName == sensorName) 2566 { 2567 sensorsModified.emplace(chassisSensor); 2568 return true; 2569 } 2570 } 2571 return false; 2572 } 2573 2574 inline std::pair<std::string, std::string> 2575 splitSensorNameAndType(std::string_view sensorId) 2576 { 2577 size_t index = sensorId.find('_'); 2578 if (index == std::string::npos) 2579 { 2580 return std::make_pair<std::string, std::string>("", ""); 2581 } 2582 std::string sensorType{sensorId.substr(0, index)}; 2583 std::string sensorName{sensorId.substr(index + 1)}; 2584 // fan_pwm and fan_tach need special handling 2585 if (sensorType == "fantach" || sensorType == "fanpwm") 2586 { 2587 sensorType.insert(3, 1, '_'); 2588 } 2589 return std::make_pair(sensorType, sensorName); 2590 } 2591 2592 /** 2593 * @brief Entry point for overriding sensor values of given sensor 2594 * 2595 * @param sensorAsyncResp response object 2596 * @param allCollections Collections extract from sensors' request patch info 2597 * @param chassisSubNode Chassis Node for which the query has to happen 2598 */ 2599 inline void setSensorsOverride( 2600 const std::shared_ptr<SensorsAsyncResp>& sensorAsyncResp, 2601 std::unordered_map<std::string, std::vector<nlohmann::json>>& 2602 allCollections) 2603 { 2604 BMCWEB_LOG_INFO << "setSensorsOverride for subNode" 2605 << sensorAsyncResp->chassisSubNode << "\n"; 2606 2607 const char* propertyValueName = nullptr; 2608 std::unordered_map<std::string, std::pair<double, std::string>> overrideMap; 2609 std::string memberId; 2610 double value = 0.0; 2611 for (auto& collectionItems : allCollections) 2612 { 2613 if (collectionItems.first == "Temperatures") 2614 { 2615 propertyValueName = "ReadingCelsius"; 2616 } 2617 else if (collectionItems.first == "Fans") 2618 { 2619 propertyValueName = "Reading"; 2620 } 2621 else 2622 { 2623 propertyValueName = "ReadingVolts"; 2624 } 2625 for (auto& item : collectionItems.second) 2626 { 2627 if (!json_util::readJson(item, sensorAsyncResp->asyncResp->res, 2628 "MemberId", memberId, propertyValueName, 2629 value)) 2630 { 2631 return; 2632 } 2633 overrideMap.emplace(memberId, 2634 std::make_pair(value, collectionItems.first)); 2635 } 2636 } 2637 2638 auto getChassisSensorListCb = 2639 [sensorAsyncResp, overrideMap]( 2640 const std::shared_ptr<std::set<std::string>>& sensorsList) { 2641 // Match sensor names in the PATCH request to those managed by the 2642 // chassis node 2643 const std::shared_ptr<std::set<std::string>> sensorNames = 2644 std::make_shared<std::set<std::string>>(); 2645 for (const auto& item : overrideMap) 2646 { 2647 const auto& sensor = item.first; 2648 std::pair<std::string, std::string> sensorNameType = 2649 splitSensorNameAndType(sensor); 2650 if (!findSensorNameUsingSensorPath(sensorNameType.second, 2651 *sensorsList, *sensorNames)) 2652 { 2653 BMCWEB_LOG_INFO << "Unable to find memberId " << item.first; 2654 messages::resourceNotFound(sensorAsyncResp->asyncResp->res, 2655 item.second.second, item.first); 2656 return; 2657 } 2658 } 2659 // Get the connection to which the memberId belongs 2660 auto getObjectsWithConnectionCb = 2661 [sensorAsyncResp, 2662 overrideMap](const std::set<std::string>& /*connections*/, 2663 const std::set<std::pair<std::string, std::string>>& 2664 objectsWithConnection) { 2665 if (objectsWithConnection.size() != overrideMap.size()) 2666 { 2667 BMCWEB_LOG_INFO 2668 << "Unable to find all objects with proper connection " 2669 << objectsWithConnection.size() << " requested " 2670 << overrideMap.size() << "\n"; 2671 messages::resourceNotFound(sensorAsyncResp->asyncResp->res, 2672 sensorAsyncResp->chassisSubNode == 2673 sensors::node::thermal 2674 ? "Temperatures" 2675 : "Voltages", 2676 "Count"); 2677 return; 2678 } 2679 for (const auto& item : objectsWithConnection) 2680 { 2681 sdbusplus::message::object_path path(item.first); 2682 std::string sensorName = path.filename(); 2683 if (sensorName.empty()) 2684 { 2685 messages::internalError(sensorAsyncResp->asyncResp->res); 2686 return; 2687 } 2688 2689 const auto& iterator = overrideMap.find(sensorName); 2690 if (iterator == overrideMap.end()) 2691 { 2692 BMCWEB_LOG_INFO << "Unable to find sensor object" 2693 << item.first << "\n"; 2694 messages::internalError(sensorAsyncResp->asyncResp->res); 2695 return; 2696 } 2697 crow::connections::systemBus->async_method_call( 2698 [sensorAsyncResp](const boost::system::error_code& ec) { 2699 if (ec) 2700 { 2701 if (ec.value() == 2702 boost::system::errc::permission_denied) 2703 { 2704 BMCWEB_LOG_WARNING 2705 << "Manufacturing mode is not Enabled...can't " 2706 "Override the sensor value. "; 2707 2708 messages::insufficientPrivilege( 2709 sensorAsyncResp->asyncResp->res); 2710 return; 2711 } 2712 BMCWEB_LOG_DEBUG 2713 << "setOverrideValueStatus DBUS error: " << ec; 2714 messages::internalError( 2715 sensorAsyncResp->asyncResp->res); 2716 } 2717 }, 2718 item.second, item.first, "org.freedesktop.DBus.Properties", 2719 "Set", "xyz.openbmc_project.Sensor.Value", "Value", 2720 dbus::utility::DbusVariantType(iterator->second.first)); 2721 } 2722 }; 2723 // Get object with connection for the given sensor name 2724 getObjectsWithConnection(sensorAsyncResp, sensorNames, 2725 std::move(getObjectsWithConnectionCb)); 2726 }; 2727 // get full sensor list for the given chassisId and cross verify the sensor. 2728 getChassis(sensorAsyncResp->asyncResp, sensorAsyncResp->chassisId, 2729 sensorAsyncResp->chassisSubNode, sensorAsyncResp->types, 2730 std::move(getChassisSensorListCb)); 2731 } 2732 2733 /** 2734 * @brief Retrieves mapping of Redfish URIs to sensor value property to D-Bus 2735 * path of the sensor. 2736 * 2737 * Function builds valid Redfish response for sensor query of given chassis and 2738 * node. It then builds metadata about Redfish<->D-Bus correlations and provides 2739 * it to caller in a callback. 2740 * 2741 * @param chassis Chassis for which retrieval should be performed 2742 * @param node Node (group) of sensors. See sensors::node for supported values 2743 * @param mapComplete Callback to be called with retrieval result 2744 */ 2745 inline void retrieveUriToDbusMap(const std::string& chassis, 2746 const std::string& node, 2747 SensorsAsyncResp::DataCompleteCb&& mapComplete) 2748 { 2749 decltype(sensors::paths)::const_iterator pathIt = 2750 std::find_if(sensors::paths.cbegin(), sensors::paths.cend(), 2751 [&node](auto&& val) { return val.first == node; }); 2752 if (pathIt == sensors::paths.cend()) 2753 { 2754 BMCWEB_LOG_ERROR << "Wrong node provided : " << node; 2755 mapComplete(boost::beast::http::status::bad_request, {}); 2756 return; 2757 } 2758 2759 auto asyncResp = std::make_shared<bmcweb::AsyncResp>(); 2760 auto callback = [asyncResp, mapCompleteCb{std::move(mapComplete)}]( 2761 const boost::beast::http::status status, 2762 const std::map<std::string, std::string>& uriToDbus) { 2763 mapCompleteCb(status, uriToDbus); 2764 }; 2765 2766 auto resp = std::make_shared<SensorsAsyncResp>( 2767 asyncResp, chassis, pathIt->second, node, std::move(callback)); 2768 getChassisData(resp); 2769 } 2770 2771 namespace sensors 2772 { 2773 2774 inline void getChassisCallback( 2775 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2776 std::string_view chassisId, std::string_view chassisSubNode, 2777 const std::shared_ptr<std::set<std::string>>& sensorNames) 2778 { 2779 BMCWEB_LOG_DEBUG << "getChassisCallback enter "; 2780 2781 nlohmann::json& entriesArray = asyncResp->res.jsonValue["Members"]; 2782 for (const std::string& sensor : *sensorNames) 2783 { 2784 BMCWEB_LOG_DEBUG << "Adding sensor: " << sensor; 2785 2786 sdbusplus::message::object_path path(sensor); 2787 std::string sensorName = path.filename(); 2788 if (sensorName.empty()) 2789 { 2790 BMCWEB_LOG_ERROR << "Invalid sensor path: " << sensor; 2791 messages::internalError(asyncResp->res); 2792 return; 2793 } 2794 std::string type = path.parent_path().filename(); 2795 // fan_tach has an underscore in it, so remove it to "normalize" the 2796 // type in the URI 2797 type.erase(std::remove(type.begin(), type.end(), '_'), type.end()); 2798 2799 nlohmann::json::object_t member; 2800 std::string id = type; 2801 id += "_"; 2802 id += sensorName; 2803 member["@odata.id"] = boost::urls::format( 2804 "/redfish/v1/Chassis/{}/{}/{}", chassisId, chassisSubNode, id); 2805 2806 entriesArray.emplace_back(std::move(member)); 2807 } 2808 2809 asyncResp->res.jsonValue["Members@odata.count"] = entriesArray.size(); 2810 BMCWEB_LOG_DEBUG << "getChassisCallback exit"; 2811 } 2812 2813 inline void handleSensorCollectionGet( 2814 App& app, const crow::Request& req, 2815 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2816 const std::string& chassisId) 2817 { 2818 query_param::QueryCapabilities capabilities = { 2819 .canDelegateExpandLevel = 1, 2820 }; 2821 query_param::Query delegatedQuery; 2822 if (!redfish::setUpRedfishRouteWithDelegation(app, req, asyncResp, 2823 delegatedQuery, capabilities)) 2824 { 2825 return; 2826 } 2827 2828 if (delegatedQuery.expandType != query_param::ExpandType::None) 2829 { 2830 // we perform efficient expand. 2831 auto sensorsAsyncResp = std::make_shared<SensorsAsyncResp>( 2832 asyncResp, chassisId, sensors::dbus::sensorPaths, 2833 sensors::node::sensors, 2834 /*efficientExpand=*/true); 2835 getChassisData(sensorsAsyncResp); 2836 2837 BMCWEB_LOG_DEBUG 2838 << "SensorCollection doGet exit via efficient expand handler"; 2839 return; 2840 } 2841 2842 // We get all sensors as hyperlinkes in the chassis (this 2843 // implies we reply on the default query parameters handler) 2844 getChassis(asyncResp, chassisId, sensors::node::sensors, dbus::sensorPaths, 2845 std::bind_front(sensors::getChassisCallback, asyncResp, 2846 chassisId, sensors::node::sensors)); 2847 } 2848 2849 inline void 2850 getSensorFromDbus(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2851 const std::string& sensorPath, 2852 const ::dbus::utility::MapperGetObject& mapperResponse) 2853 { 2854 if (mapperResponse.size() != 1) 2855 { 2856 messages::internalError(asyncResp->res); 2857 return; 2858 } 2859 const auto& valueIface = *mapperResponse.begin(); 2860 const std::string& connectionName = valueIface.first; 2861 BMCWEB_LOG_DEBUG << "Looking up " << connectionName; 2862 BMCWEB_LOG_DEBUG << "Path " << sensorPath; 2863 2864 sdbusplus::asio::getAllProperties( 2865 *crow::connections::systemBus, connectionName, sensorPath, "", 2866 [asyncResp, 2867 sensorPath](const boost::system::error_code& ec, 2868 const ::dbus::utility::DBusPropertiesMap& valuesDict) { 2869 if (ec) 2870 { 2871 messages::internalError(asyncResp->res); 2872 return; 2873 } 2874 sdbusplus::message::object_path path(sensorPath); 2875 std::string name = path.filename(); 2876 path = path.parent_path(); 2877 std::string type = path.filename(); 2878 objectPropertiesToJson(name, type, sensors::node::sensors, valuesDict, 2879 asyncResp->res.jsonValue, nullptr); 2880 }); 2881 } 2882 2883 inline void handleSensorGet(App& app, const crow::Request& req, 2884 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2885 const std::string& chassisId, 2886 const std::string& sensorId) 2887 { 2888 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 2889 { 2890 return; 2891 } 2892 std::pair<std::string, std::string> nameType = 2893 splitSensorNameAndType(sensorId); 2894 if (nameType.first.empty() || nameType.second.empty()) 2895 { 2896 messages::resourceNotFound(asyncResp->res, sensorId, "Sensor"); 2897 return; 2898 } 2899 2900 asyncResp->res.jsonValue["@odata.id"] = boost::urls::format( 2901 "/redfish/v1/Chassis/{}/Sensors/{}", chassisId, sensorId); 2902 2903 BMCWEB_LOG_DEBUG << "Sensor doGet enter"; 2904 2905 constexpr std::array<std::string_view, 1> interfaces = { 2906 "xyz.openbmc_project.Sensor.Value"}; 2907 std::string sensorPath = "/xyz/openbmc_project/sensors/" + nameType.first + 2908 '/' + nameType.second; 2909 // Get a list of all of the sensors that implement Sensor.Value 2910 // and get the path and service name associated with the sensor 2911 ::dbus::utility::getDbusObject( 2912 sensorPath, interfaces, 2913 [asyncResp, sensorId, 2914 sensorPath](const boost::system::error_code& ec, 2915 const ::dbus::utility::MapperGetObject& subtree) { 2916 BMCWEB_LOG_DEBUG << "respHandler1 enter"; 2917 if (ec == boost::system::errc::io_error) 2918 { 2919 BMCWEB_LOG_WARNING << "Sensor not found from getSensorPaths"; 2920 messages::resourceNotFound(asyncResp->res, sensorId, "Sensor"); 2921 return; 2922 } 2923 if (ec) 2924 { 2925 messages::internalError(asyncResp->res); 2926 BMCWEB_LOG_ERROR << "Sensor getSensorPaths resp_handler: " 2927 << "Dbus error " << ec; 2928 return; 2929 } 2930 getSensorFromDbus(asyncResp, sensorPath, subtree); 2931 BMCWEB_LOG_DEBUG << "respHandler1 exit"; 2932 }); 2933 } 2934 2935 } // namespace sensors 2936 2937 inline void requestRoutesSensorCollection(App& app) 2938 { 2939 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/Sensors/") 2940 .privileges(redfish::privileges::getSensorCollection) 2941 .methods(boost::beast::http::verb::get)( 2942 std::bind_front(sensors::handleSensorCollectionGet, std::ref(app))); 2943 } 2944 2945 inline void requestRoutesSensor(App& app) 2946 { 2947 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/Sensors/<str>/") 2948 .privileges(redfish::privileges::getSensor) 2949 .methods(boost::beast::http::verb::get)( 2950 std::bind_front(sensors::handleSensorGet, std::ref(app))); 2951 } 2952 2953 } // namespace redfish 2954