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