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