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