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