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 // clang-format off 48 namespace dbus 49 { 50 static auto powerPaths = std::to_array<std::string_view>({ 51 "/xyz/openbmc_project/sensors/voltage", 52 "/xyz/openbmc_project/sensors/power" 53 }); 54 55 static auto sensorPaths = std::to_array<std::string_view>({ 56 "/xyz/openbmc_project/sensors/power", 57 "/xyz/openbmc_project/sensors/current", 58 "/xyz/openbmc_project/sensors/airflow", 59 "/xyz/openbmc_project/sensors/humidity", 60 #ifdef BMCWEB_NEW_POWERSUBSYSTEM_THERMALSUBSYSTEM 61 "/xyz/openbmc_project/sensors/voltage", 62 "/xyz/openbmc_project/sensors/fan_tach", 63 "/xyz/openbmc_project/sensors/temperature", 64 "/xyz/openbmc_project/sensors/fan_pwm", 65 "/xyz/openbmc_project/sensors/altitude", 66 "/xyz/openbmc_project/sensors/energy", 67 #endif 68 "/xyz/openbmc_project/sensors/utilization" 69 }); 70 71 static auto thermalPaths = std::to_array<std::string_view>({ 72 "/xyz/openbmc_project/sensors/fan_tach", 73 "/xyz/openbmc_project/sensors/temperature", 74 "/xyz/openbmc_project/sensors/fan_pwm" 75 }); 76 77 } // namespace dbus 78 // clang-format on 79 80 using sensorPair = std::pair<std::string_view, std::span<std::string_view>>; 81 static constexpr std::array<sensorPair, 3> paths = { 82 {{node::power, std::span<std::string_view>(dbus::powerPaths)}, 83 {node::sensors, std::span<std::string_view>(dbus::sensorPaths)}, 84 {node::thermal, std::span<std::string_view>(dbus::thermalPaths)}}}; 85 86 inline const char* toReadingType(const std::string& sensorType) 87 { 88 if (sensorType == "voltage") 89 { 90 return "Voltage"; 91 } 92 if (sensorType == "power") 93 { 94 return "Power"; 95 } 96 if (sensorType == "current") 97 { 98 return "Current"; 99 } 100 if (sensorType == "fan_tach") 101 { 102 return "Rotational"; 103 } 104 if (sensorType == "temperature") 105 { 106 return "Temperature"; 107 } 108 if (sensorType == "fan_pwm" || sensorType == "utilization") 109 { 110 return "Percent"; 111 } 112 if (sensorType == "humidity") 113 { 114 return "Humidity"; 115 } 116 if (sensorType == "altitude") 117 { 118 return "Altitude"; 119 } 120 if (sensorType == "airflow") 121 { 122 return "AirFlow"; 123 } 124 if (sensorType == "energy") 125 { 126 return "EnergyJoules"; 127 } 128 return ""; 129 } 130 131 inline const char* toReadingUnits(const std::string& sensorType) 132 { 133 if (sensorType == "voltage") 134 { 135 return "V"; 136 } 137 if (sensorType == "power") 138 { 139 return "W"; 140 } 141 if (sensorType == "current") 142 { 143 return "A"; 144 } 145 if (sensorType == "fan_tach") 146 { 147 return "RPM"; 148 } 149 if (sensorType == "temperature") 150 { 151 return "Cel"; 152 } 153 if (sensorType == "fan_pwm" || sensorType == "utilization" || 154 sensorType == "humidity") 155 { 156 return "%"; 157 } 158 if (sensorType == "altitude") 159 { 160 return "m"; 161 } 162 if (sensorType == "airflow") 163 { 164 return "cft_i/min"; 165 } 166 if (sensorType == "energy") 167 { 168 return "J"; 169 } 170 return ""; 171 } 172 } // namespace sensors 173 174 /** 175 * SensorsAsyncResp 176 * Gathers data needed for response processing after async calls are done 177 */ 178 class SensorsAsyncResp 179 { 180 public: 181 using DataCompleteCb = std::function<void( 182 const boost::beast::http::status status, 183 const boost::container::flat_map<std::string, std::string>& uriToDbus)>; 184 185 struct SensorData 186 { 187 const std::string name; 188 std::string uri; 189 const std::string valueKey; 190 const std::string dbusPath; 191 }; 192 193 SensorsAsyncResp(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 194 const std::string& chassisIdIn, 195 std::span<std::string_view> typesIn, 196 std::string_view subNode) : 197 asyncResp(asyncResp), 198 chassisId(chassisIdIn), types(typesIn), chassisSubNode(subNode), 199 efficientExpand(false) 200 {} 201 202 // Store extra data about sensor mapping and return it in callback 203 SensorsAsyncResp(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 204 const std::string& chassisIdIn, 205 std::span<std::string_view> typesIn, 206 std::string_view subNode, 207 DataCompleteCb&& creationComplete) : 208 asyncResp(asyncResp), 209 chassisId(chassisIdIn), types(typesIn), chassisSubNode(subNode), 210 efficientExpand(false), metadata{std::vector<SensorData>()}, 211 dataComplete{std::move(creationComplete)} 212 {} 213 214 // sensor collections expand 215 SensorsAsyncResp(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 216 const std::string& chassisIdIn, 217 const std::span<std::string_view> typesIn, 218 const std::string_view& subNode, bool efficientExpand) : 219 asyncResp(asyncResp), 220 chassisId(chassisIdIn), types(typesIn), chassisSubNode(subNode), 221 efficientExpand(efficientExpand) 222 {} 223 224 ~SensorsAsyncResp() 225 { 226 if (asyncResp->res.result() == 227 boost::beast::http::status::internal_server_error) 228 { 229 // Reset the json object to clear out any data that made it in 230 // before the error happened todo(ed) handle error condition with 231 // proper code 232 asyncResp->res.jsonValue = nlohmann::json::object(); 233 } 234 235 if (dataComplete && metadata) 236 { 237 boost::container::flat_map<std::string, std::string> map; 238 if (asyncResp->res.result() == boost::beast::http::status::ok) 239 { 240 for (auto& sensor : *metadata) 241 { 242 map.insert(std::make_pair(sensor.uri + sensor.valueKey, 243 sensor.dbusPath)); 244 } 245 } 246 dataComplete(asyncResp->res.result(), map); 247 } 248 } 249 250 SensorsAsyncResp(const SensorsAsyncResp&) = delete; 251 SensorsAsyncResp(SensorsAsyncResp&&) = delete; 252 SensorsAsyncResp& operator=(const SensorsAsyncResp&) = delete; 253 SensorsAsyncResp& operator=(SensorsAsyncResp&&) = delete; 254 255 void addMetadata(const nlohmann::json& sensorObject, 256 const std::string& valueKey, const std::string& dbusPath) 257 { 258 if (metadata) 259 { 260 metadata->emplace_back(SensorData{sensorObject["Name"], 261 sensorObject["@odata.id"], 262 valueKey, dbusPath}); 263 } 264 } 265 266 void updateUri(const std::string& name, const std::string& uri) 267 { 268 if (metadata) 269 { 270 for (auto& sensor : *metadata) 271 { 272 if (sensor.name == name) 273 { 274 sensor.uri = uri; 275 } 276 } 277 } 278 } 279 280 const std::shared_ptr<bmcweb::AsyncResp> asyncResp; 281 const std::string chassisId; 282 const std::span<std::string_view> types; 283 const std::string chassisSubNode; 284 const bool efficientExpand; 285 286 private: 287 std::optional<std::vector<SensorData>> metadata; 288 DataCompleteCb dataComplete; 289 }; 290 291 /** 292 * Possible states for physical inventory leds 293 */ 294 enum class LedState 295 { 296 OFF, 297 ON, 298 BLINK, 299 UNKNOWN 300 }; 301 302 /** 303 * D-Bus inventory item associated with one or more sensors. 304 */ 305 class InventoryItem 306 { 307 public: 308 InventoryItem(const std::string& objPath) : objectPath(objPath) 309 { 310 // Set inventory item name to last node of object path 311 sdbusplus::message::object_path path(objectPath); 312 name = path.filename(); 313 if (name.empty()) 314 { 315 BMCWEB_LOG_ERROR << "Failed to find '/' in " << objectPath; 316 } 317 } 318 319 std::string objectPath; 320 std::string name; 321 bool isPresent = true; 322 bool isFunctional = true; 323 bool isPowerSupply = false; 324 int powerSupplyEfficiencyPercent = -1; 325 std::string manufacturer; 326 std::string model; 327 std::string partNumber; 328 std::string serialNumber; 329 std::set<std::string> sensors; 330 std::string ledObjectPath; 331 LedState ledState = LedState::UNKNOWN; 332 }; 333 334 /** 335 * @brief Get objects with connection necessary for sensors 336 * @param SensorsAsyncResp Pointer to object holding response data 337 * @param sensorNames Sensors retrieved from chassis 338 * @param callback Callback for processing gathered connections 339 */ 340 template <typename Callback> 341 void getObjectsWithConnection( 342 const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp, 343 const std::shared_ptr<boost::container::flat_set<std::string>>& sensorNames, 344 Callback&& callback) 345 { 346 BMCWEB_LOG_DEBUG << "getObjectsWithConnection enter"; 347 const std::string path = "/xyz/openbmc_project/sensors"; 348 const std::array<std::string, 1> interfaces = { 349 "xyz.openbmc_project.Sensor.Value"}; 350 351 // Response handler for parsing objects subtree 352 auto respHandler = 353 [callback{std::forward<Callback>(callback)}, sensorsAsyncResp, 354 sensorNames](const boost::system::error_code ec, 355 const dbus::utility::MapperGetSubTreeResponse& subtree) { 356 BMCWEB_LOG_DEBUG << "getObjectsWithConnection resp_handler enter"; 357 if (ec) 358 { 359 messages::internalError(sensorsAsyncResp->asyncResp->res); 360 BMCWEB_LOG_ERROR 361 << "getObjectsWithConnection resp_handler: Dbus error " << ec; 362 return; 363 } 364 365 BMCWEB_LOG_DEBUG << "Found " << subtree.size() << " subtrees"; 366 367 // Make unique list of connections only for requested sensor types and 368 // found in the chassis 369 boost::container::flat_set<std::string> connections; 370 std::set<std::pair<std::string, std::string>> objectsWithConnection; 371 // Intrinsic to avoid malloc. Most systems will have < 8 sensor 372 // producers 373 connections.reserve(8); 374 375 BMCWEB_LOG_DEBUG << "sensorNames list count: " << sensorNames->size(); 376 for (const std::string& tsensor : *sensorNames) 377 { 378 BMCWEB_LOG_DEBUG << "Sensor to find: " << tsensor; 379 } 380 381 for (const std::pair< 382 std::string, 383 std::vector<std::pair<std::string, std::vector<std::string>>>>& 384 object : subtree) 385 { 386 if (sensorNames->find(object.first) != sensorNames->end()) 387 { 388 for (const std::pair<std::string, std::vector<std::string>>& 389 objData : object.second) 390 { 391 BMCWEB_LOG_DEBUG << "Adding connection: " << objData.first; 392 connections.insert(objData.first); 393 objectsWithConnection.insert( 394 std::make_pair(object.first, objData.first)); 395 } 396 } 397 } 398 BMCWEB_LOG_DEBUG << "Found " << connections.size() << " connections"; 399 callback(std::move(connections), std::move(objectsWithConnection)); 400 BMCWEB_LOG_DEBUG << "getObjectsWithConnection resp_handler exit"; 401 }; 402 // Make call to ObjectMapper to find all sensors objects 403 crow::connections::systemBus->async_method_call( 404 std::move(respHandler), "xyz.openbmc_project.ObjectMapper", 405 "/xyz/openbmc_project/object_mapper", 406 "xyz.openbmc_project.ObjectMapper", "GetSubTree", path, 2, interfaces); 407 BMCWEB_LOG_DEBUG << "getObjectsWithConnection exit"; 408 } 409 410 /** 411 * @brief Create connections necessary for sensors 412 * @param SensorsAsyncResp Pointer to object holding response data 413 * @param sensorNames Sensors retrieved from chassis 414 * @param callback Callback for processing gathered connections 415 */ 416 template <typename Callback> 417 void getConnections( 418 std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp, 419 const std::shared_ptr<boost::container::flat_set<std::string>> sensorNames, 420 Callback&& callback) 421 { 422 auto objectsWithConnectionCb = 423 [callback](const boost::container::flat_set<std::string>& connections, 424 const std::set<std::pair<std::string, std::string>>& 425 /*objectsWithConnection*/) { callback(connections); }; 426 getObjectsWithConnection(sensorsAsyncResp, sensorNames, 427 std::move(objectsWithConnectionCb)); 428 } 429 430 /** 431 * @brief Shrinks the list of sensors for processing 432 * @param SensorsAysncResp The class holding the Redfish response 433 * @param allSensors A list of all the sensors associated to the 434 * chassis element (i.e. baseboard, front panel, etc...) 435 * @param activeSensors A list that is a reduction of the incoming 436 * allSensors list. Eliminate Thermal sensors when a Power request is 437 * made, and eliminate Power sensors when a Thermal request is made. 438 */ 439 inline void reduceSensorList( 440 const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp, 441 const std::vector<std::string>* allSensors, 442 const std::shared_ptr<boost::container::flat_set<std::string>>& 443 activeSensors) 444 { 445 if (sensorsAsyncResp == nullptr) 446 { 447 return; 448 } 449 if ((allSensors == nullptr) || (activeSensors == nullptr)) 450 { 451 messages::resourceNotFound( 452 sensorsAsyncResp->asyncResp->res, sensorsAsyncResp->chassisSubNode, 453 sensorsAsyncResp->chassisSubNode == sensors::node::thermal 454 ? "Temperatures" 455 : "Voltages"); 456 457 return; 458 } 459 if (allSensors->empty()) 460 { 461 // Nothing to do, the activeSensors object is also empty 462 return; 463 } 464 465 for (std::string_view type : sensorsAsyncResp->types) 466 { 467 for (const std::string& sensor : *allSensors) 468 { 469 if (boost::starts_with(sensor, type)) 470 { 471 activeSensors->emplace(sensor); 472 } 473 } 474 } 475 } 476 477 /** 478 * @brief Retrieves valid chassis path 479 * @param asyncResp Pointer to object holding response data 480 * @param callback Callback for next step to get valid chassis path 481 */ 482 template <typename Callback> 483 void getValidChassisPath(const std::shared_ptr<SensorsAsyncResp>& asyncResp, 484 Callback&& callback) 485 { 486 BMCWEB_LOG_DEBUG << "checkChassisId enter"; 487 const std::array<const char*, 2> interfaces = { 488 "xyz.openbmc_project.Inventory.Item.Board", 489 "xyz.openbmc_project.Inventory.Item.Chassis"}; 490 491 auto respHandler = [callback{std::forward<Callback>(callback)}, asyncResp]( 492 const boost::system::error_code ec, 493 const dbus::utility::MapperGetSubTreePathsResponse& 494 chassisPaths) mutable { 495 BMCWEB_LOG_DEBUG << "getValidChassisPath respHandler enter"; 496 if (ec) 497 { 498 BMCWEB_LOG_ERROR << "getValidChassisPath respHandler DBUS error: " 499 << ec; 500 messages::internalError(asyncResp->asyncResp->res); 501 return; 502 } 503 504 std::optional<std::string> chassisPath; 505 std::string chassisName; 506 for (const std::string& chassis : chassisPaths) 507 { 508 sdbusplus::message::object_path path(chassis); 509 chassisName = path.filename(); 510 if (chassisName.empty()) 511 { 512 BMCWEB_LOG_ERROR << "Failed to find '/' in " << chassis; 513 continue; 514 } 515 if (chassisName == asyncResp->chassisId) 516 { 517 chassisPath = chassis; 518 break; 519 } 520 } 521 callback(chassisPath); 522 }; 523 524 // Get the Chassis Collection 525 crow::connections::systemBus->async_method_call( 526 respHandler, "xyz.openbmc_project.ObjectMapper", 527 "/xyz/openbmc_project/object_mapper", 528 "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", 529 "/xyz/openbmc_project/inventory", 0, interfaces); 530 BMCWEB_LOG_DEBUG << "checkChassisId exit"; 531 } 532 533 /** 534 * @brief Retrieves requested chassis sensors and redundancy data from DBus . 535 * @param SensorsAsyncResp Pointer to object holding response data 536 * @param callback Callback for next step in gathered sensor processing 537 */ 538 template <typename Callback> 539 void getChassis(const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp, 540 Callback&& callback) 541 { 542 BMCWEB_LOG_DEBUG << "getChassis enter"; 543 const std::array<const char*, 2> interfaces = { 544 "xyz.openbmc_project.Inventory.Item.Board", 545 "xyz.openbmc_project.Inventory.Item.Chassis"}; 546 auto respHandler = 547 [callback{std::forward<Callback>(callback)}, sensorsAsyncResp]( 548 const boost::system::error_code ec, 549 const dbus::utility::MapperGetSubTreePathsResponse& chassisPaths) { 550 BMCWEB_LOG_DEBUG << "getChassis respHandler enter"; 551 if (ec) 552 { 553 BMCWEB_LOG_ERROR << "getChassis respHandler DBUS error: " << ec; 554 messages::internalError(sensorsAsyncResp->asyncResp->res); 555 return; 556 } 557 558 const std::string* chassisPath = nullptr; 559 std::string chassisName; 560 for (const std::string& chassis : chassisPaths) 561 { 562 sdbusplus::message::object_path path(chassis); 563 chassisName = path.filename(); 564 if (chassisName.empty()) 565 { 566 BMCWEB_LOG_ERROR << "Failed to find '/' in " << chassis; 567 continue; 568 } 569 if (chassisName == sensorsAsyncResp->chassisId) 570 { 571 chassisPath = &chassis; 572 break; 573 } 574 } 575 if (chassisPath == nullptr) 576 { 577 messages::resourceNotFound(sensorsAsyncResp->asyncResp->res, 578 "Chassis", sensorsAsyncResp->chassisId); 579 return; 580 } 581 582 const std::string& chassisSubNode = sensorsAsyncResp->chassisSubNode; 583 if (chassisSubNode == sensors::node::power) 584 { 585 sensorsAsyncResp->asyncResp->res.jsonValue["@odata.type"] = 586 "#Power.v1_5_2.Power"; 587 } 588 else if (chassisSubNode == sensors::node::thermal) 589 { 590 sensorsAsyncResp->asyncResp->res.jsonValue["@odata.type"] = 591 "#Thermal.v1_4_0.Thermal"; 592 sensorsAsyncResp->asyncResp->res.jsonValue["Fans"] = 593 nlohmann::json::array(); 594 sensorsAsyncResp->asyncResp->res.jsonValue["Temperatures"] = 595 nlohmann::json::array(); 596 } 597 else if (chassisSubNode == sensors::node::sensors) 598 { 599 sensorsAsyncResp->asyncResp->res.jsonValue["@odata.type"] = 600 "#SensorCollection.SensorCollection"; 601 sensorsAsyncResp->asyncResp->res.jsonValue["Description"] = 602 "Collection of Sensors for this Chassis"; 603 sensorsAsyncResp->asyncResp->res.jsonValue["Members"] = 604 nlohmann::json::array(); 605 sensorsAsyncResp->asyncResp->res.jsonValue["Members@odata.count"] = 606 0; 607 } 608 609 if (chassisSubNode != sensors::node::sensors) 610 { 611 sensorsAsyncResp->asyncResp->res.jsonValue["Id"] = chassisSubNode; 612 } 613 614 sensorsAsyncResp->asyncResp->res.jsonValue["@odata.id"] = 615 "/redfish/v1/Chassis/" + sensorsAsyncResp->chassisId + "/" + 616 chassisSubNode; 617 sensorsAsyncResp->asyncResp->res.jsonValue["Name"] = chassisSubNode; 618 // Get the list of all sensors for this Chassis element 619 std::string sensorPath = *chassisPath + "/all_sensors"; 620 sdbusplus::asio::getProperty<std::vector<std::string>>( 621 *crow::connections::systemBus, "xyz.openbmc_project.ObjectMapper", 622 sensorPath, "xyz.openbmc_project.Association", "endpoints", 623 [sensorsAsyncResp, 624 callback{std::forward<const Callback>(callback)}]( 625 const boost::system::error_code& e, 626 const std::vector<std::string>& nodeSensorList) { 627 if (e) 628 { 629 if (e.value() != EBADR) 630 { 631 messages::internalError(sensorsAsyncResp->asyncResp->res); 632 return; 633 } 634 } 635 const std::shared_ptr<boost::container::flat_set<std::string>> 636 culledSensorList = 637 std::make_shared<boost::container::flat_set<std::string>>(); 638 reduceSensorList(sensorsAsyncResp, &nodeSensorList, 639 culledSensorList); 640 callback(culledSensorList); 641 }); 642 }; 643 644 // Get the Chassis Collection 645 crow::connections::systemBus->async_method_call( 646 respHandler, "xyz.openbmc_project.ObjectMapper", 647 "/xyz/openbmc_project/object_mapper", 648 "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", 649 "/xyz/openbmc_project/inventory", 0, interfaces); 650 BMCWEB_LOG_DEBUG << "getChassis exit"; 651 } 652 653 /** 654 * @brief Finds all DBus object paths that implement ObjectManager. 655 * 656 * Creates a mapping from the associated connection name to the object path. 657 * 658 * Finds the object paths asynchronously. Invokes callback when information has 659 * been obtained. 660 * 661 * The callback must have the following signature: 662 * @code 663 * callback(std::shared_ptr<boost::container::flat_map<std::string, 664 * std::string>> objectMgrPaths) 665 * @endcode 666 * 667 * @param sensorsAsyncResp Pointer to object holding response data. 668 * @param callback Callback to invoke when object paths obtained. 669 */ 670 template <typename Callback> 671 void getObjectManagerPaths( 672 const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp, 673 Callback&& callback) 674 { 675 BMCWEB_LOG_DEBUG << "getObjectManagerPaths enter"; 676 const std::array<std::string, 1> interfaces = { 677 "org.freedesktop.DBus.ObjectManager"}; 678 679 // Response handler for GetSubTree DBus method 680 auto respHandler = 681 [callback{std::forward<Callback>(callback)}, sensorsAsyncResp]( 682 const boost::system::error_code ec, 683 const dbus::utility::MapperGetSubTreeResponse& subtree) { 684 BMCWEB_LOG_DEBUG << "getObjectManagerPaths respHandler enter"; 685 if (ec) 686 { 687 messages::internalError(sensorsAsyncResp->asyncResp->res); 688 BMCWEB_LOG_ERROR << "getObjectManagerPaths respHandler: DBus error " 689 << ec; 690 return; 691 } 692 693 // Loop over returned object paths 694 std::shared_ptr<boost::container::flat_map<std::string, std::string>> 695 objectMgrPaths = std::make_shared< 696 boost::container::flat_map<std::string, std::string>>(); 697 for (const std::pair< 698 std::string, 699 std::vector<std::pair<std::string, std::vector<std::string>>>>& 700 object : subtree) 701 { 702 // Loop over connections for current object path 703 const std::string& objectPath = object.first; 704 for (const std::pair<std::string, std::vector<std::string>>& 705 objData : object.second) 706 { 707 // Add mapping from connection to object path 708 const std::string& connection = objData.first; 709 (*objectMgrPaths)[connection] = objectPath; 710 BMCWEB_LOG_DEBUG << "Added mapping " << connection << " -> " 711 << objectPath; 712 } 713 } 714 callback(objectMgrPaths); 715 BMCWEB_LOG_DEBUG << "getObjectManagerPaths respHandler exit"; 716 }; 717 718 // Query mapper for all DBus object paths that implement ObjectManager 719 crow::connections::systemBus->async_method_call( 720 std::move(respHandler), "xyz.openbmc_project.ObjectMapper", 721 "/xyz/openbmc_project/object_mapper", 722 "xyz.openbmc_project.ObjectMapper", "GetSubTree", "/", 0, interfaces); 723 BMCWEB_LOG_DEBUG << "getObjectManagerPaths exit"; 724 } 725 726 /** 727 * @brief Returns the Redfish State value for the specified inventory item. 728 * @param inventoryItem D-Bus inventory item associated with a sensor. 729 * @return State value for inventory item. 730 */ 731 inline std::string getState(const InventoryItem* inventoryItem) 732 { 733 if ((inventoryItem != nullptr) && !(inventoryItem->isPresent)) 734 { 735 return "Absent"; 736 } 737 738 return "Enabled"; 739 } 740 741 /** 742 * @brief Returns the Redfish Health value for the specified sensor. 743 * @param sensorJson Sensor JSON object. 744 * @param interfacesDict Map of all sensor interfaces. 745 * @param inventoryItem D-Bus inventory item associated with the sensor. Will 746 * be nullptr if no associated inventory item was found. 747 * @return Health value for sensor. 748 */ 749 inline std::string 750 getHealth(nlohmann::json& sensorJson, 751 const dbus::utility::DBusInteracesMap& interfacesDict, 752 const InventoryItem* inventoryItem) 753 { 754 // Get current health value (if any) in the sensor JSON object. Some JSON 755 // objects contain multiple sensors (such as PowerSupplies). We want to set 756 // the overall health to be the most severe of any of the sensors. 757 std::string currentHealth; 758 auto statusIt = sensorJson.find("Status"); 759 if (statusIt != sensorJson.end()) 760 { 761 auto healthIt = statusIt->find("Health"); 762 if (healthIt != statusIt->end()) 763 { 764 std::string* health = healthIt->get_ptr<std::string*>(); 765 if (health != nullptr) 766 { 767 currentHealth = *health; 768 } 769 } 770 } 771 772 // If current health in JSON object is already Critical, return that. This 773 // should override the sensor health, which might be less severe. 774 if (currentHealth == "Critical") 775 { 776 return "Critical"; 777 } 778 779 // Check if sensor has critical threshold alarm 780 781 for (const auto& [interface, values] : interfacesDict) 782 { 783 if (interface == "xyz.openbmc_project.Sensor.Threshold.Critical") 784 { 785 for (const auto& [valueName, value] : values) 786 { 787 if (valueName == "CriticalAlarmHigh" || 788 valueName == "CriticalAlarmLow") 789 { 790 const bool* asserted = std::get_if<bool>(&value); 791 if (asserted == nullptr) 792 { 793 BMCWEB_LOG_ERROR << "Illegal sensor threshold"; 794 } 795 else if (*asserted) 796 { 797 return "Critical"; 798 } 799 } 800 } 801 } 802 } 803 804 // Check if associated inventory item is not functional 805 if ((inventoryItem != nullptr) && !(inventoryItem->isFunctional)) 806 { 807 return "Critical"; 808 } 809 810 // If current health in JSON object is already Warning, return that. This 811 // should override the sensor status, which might be less severe. 812 if (currentHealth == "Warning") 813 { 814 return "Warning"; 815 } 816 817 // Check if sensor has warning threshold alarm 818 for (const auto& [interface, values] : interfacesDict) 819 { 820 if (interface == "xyz.openbmc_project.Sensor.Threshold.Warning") 821 { 822 for (const auto& [valueName, value] : values) 823 { 824 if (valueName == "WarningAlarmHigh" || 825 valueName == "WarningAlarmLow") 826 { 827 const bool* asserted = std::get_if<bool>(&value); 828 if (asserted == nullptr) 829 { 830 BMCWEB_LOG_ERROR << "Illegal sensor threshold"; 831 } 832 else if (*asserted) 833 { 834 return "Warning"; 835 } 836 } 837 } 838 } 839 } 840 841 return "OK"; 842 } 843 844 inline void setLedState(nlohmann::json& sensorJson, 845 const InventoryItem* inventoryItem) 846 { 847 if (inventoryItem != nullptr && !inventoryItem->ledObjectPath.empty()) 848 { 849 switch (inventoryItem->ledState) 850 { 851 case LedState::OFF: 852 sensorJson["IndicatorLED"] = "Off"; 853 break; 854 case LedState::ON: 855 sensorJson["IndicatorLED"] = "Lit"; 856 break; 857 case LedState::BLINK: 858 sensorJson["IndicatorLED"] = "Blinking"; 859 break; 860 case LedState::UNKNOWN: 861 break; 862 } 863 } 864 } 865 866 /** 867 * @brief Builds a json sensor representation of a sensor. 868 * @param sensorName The name of the sensor to be built 869 * @param sensorType The type (temperature, fan_tach, etc) of the sensor to 870 * build 871 * @param sensorsAsyncResp Sensor metadata 872 * @param interfacesDict A dictionary of the interfaces and properties of said 873 * interfaces to be built from 874 * @param sensor_json The json object to fill 875 * @param inventoryItem D-Bus inventory item associated with the sensor. Will 876 * be nullptr if no associated inventory item was found. 877 */ 878 inline void objectInterfacesToJson( 879 const std::string& sensorName, const std::string& sensorType, 880 const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp, 881 const dbus::utility::DBusInteracesMap& interfacesDict, 882 nlohmann::json& sensorJson, InventoryItem* inventoryItem) 883 { 884 // Assume values exist as is (10^0 == 1) if no scale exists 885 int64_t scaleMultiplier = 0; 886 for (const auto& [interface, values] : interfacesDict) 887 { 888 if (interface == "xyz.openbmc_project.Sensor.Value") 889 { 890 for (const auto& [valueName, value] : values) 891 { 892 if (valueName == "Scale") 893 { 894 const int64_t* int64Value = std::get_if<int64_t>(&value); 895 if (int64Value != nullptr) 896 { 897 scaleMultiplier = *int64Value; 898 } 899 } 900 } 901 } 902 } 903 904 if (sensorsAsyncResp->chassisSubNode == sensors::node::sensors) 905 { 906 // For sensors in SensorCollection we set Id instead of MemberId, 907 // including power sensors. 908 sensorJson["Id"] = sensorName; 909 sensorJson["Name"] = boost::replace_all_copy(sensorName, "_", " "); 910 } 911 else if (sensorType != "power") 912 { 913 // Set MemberId and Name for non-power sensors. For PowerSupplies and 914 // PowerControl, those properties have more general values because 915 // multiple sensors can be stored in the same JSON object. 916 sensorJson["MemberId"] = sensorName; 917 sensorJson["Name"] = boost::replace_all_copy(sensorName, "_", " "); 918 } 919 920 sensorJson["Status"]["State"] = getState(inventoryItem); 921 sensorJson["Status"]["Health"] = 922 getHealth(sensorJson, interfacesDict, inventoryItem); 923 924 // Parameter to set to override the type we get from dbus, and force it to 925 // int, regardless of what is available. This is used for schemas like fan, 926 // that require integers, not floats. 927 bool forceToInt = false; 928 929 nlohmann::json::json_pointer unit("/Reading"); 930 if (sensorsAsyncResp->chassisSubNode == sensors::node::sensors) 931 { 932 sensorJson["@odata.type"] = "#Sensor.v1_2_0.Sensor"; 933 934 const std::string& readingType = sensors::toReadingType(sensorType); 935 if (readingType.empty()) 936 { 937 BMCWEB_LOG_ERROR << "Redfish cannot map reading type for " 938 << sensorType; 939 } 940 else 941 { 942 sensorJson["ReadingType"] = readingType; 943 } 944 945 const std::string& readingUnits = sensors::toReadingUnits(sensorType); 946 if (readingUnits.empty()) 947 { 948 BMCWEB_LOG_ERROR << "Redfish cannot map reading unit for " 949 << sensorType; 950 } 951 else 952 { 953 sensorJson["ReadingUnits"] = readingUnits; 954 } 955 } 956 else if (sensorType == "temperature") 957 { 958 unit = "/ReadingCelsius"_json_pointer; 959 sensorJson["@odata.type"] = "#Thermal.v1_3_0.Temperature"; 960 // TODO(ed) Documentation says that path should be type fan_tach, 961 // implementation seems to implement fan 962 } 963 else if (sensorType == "fan" || sensorType == "fan_tach") 964 { 965 unit = "/Reading"_json_pointer; 966 sensorJson["ReadingUnits"] = "RPM"; 967 sensorJson["@odata.type"] = "#Thermal.v1_3_0.Fan"; 968 setLedState(sensorJson, inventoryItem); 969 forceToInt = true; 970 } 971 else if (sensorType == "fan_pwm") 972 { 973 unit = "/Reading"_json_pointer; 974 sensorJson["ReadingUnits"] = "Percent"; 975 sensorJson["@odata.type"] = "#Thermal.v1_3_0.Fan"; 976 setLedState(sensorJson, inventoryItem); 977 forceToInt = true; 978 } 979 else if (sensorType == "voltage") 980 { 981 unit = "/ReadingVolts"_json_pointer; 982 sensorJson["@odata.type"] = "#Power.v1_0_0.Voltage"; 983 } 984 else if (sensorType == "power") 985 { 986 std::string sensorNameLower = 987 boost::algorithm::to_lower_copy(sensorName); 988 989 if (sensorName == "total_power") 990 { 991 sensorJson["@odata.type"] = "#Power.v1_0_0.PowerControl"; 992 // Put multiple "sensors" into a single PowerControl, so have 993 // generic names for MemberId and Name. Follows Redfish mockup. 994 sensorJson["MemberId"] = "0"; 995 sensorJson["Name"] = "Chassis Power Control"; 996 unit = "/PowerConsumedWatts"_json_pointer; 997 } 998 else if (sensorNameLower.find("input") != std::string::npos) 999 { 1000 unit = "/PowerInputWatts"_json_pointer; 1001 } 1002 else 1003 { 1004 unit = "/PowerOutputWatts"_json_pointer; 1005 } 1006 } 1007 else 1008 { 1009 BMCWEB_LOG_ERROR << "Redfish cannot map object type for " << sensorName; 1010 return; 1011 } 1012 // Map of dbus interface name, dbus property name and redfish property_name 1013 std::vector< 1014 std::tuple<const char*, const char*, nlohmann::json::json_pointer>> 1015 properties; 1016 properties.reserve(7); 1017 1018 properties.emplace_back("xyz.openbmc_project.Sensor.Value", "Value", unit); 1019 1020 if (sensorsAsyncResp->chassisSubNode == sensors::node::sensors) 1021 { 1022 properties.emplace_back( 1023 "xyz.openbmc_project.Sensor.Threshold.Warning", "WarningHigh", 1024 "/Thresholds/UpperCaution/Reading"_json_pointer); 1025 properties.emplace_back( 1026 "xyz.openbmc_project.Sensor.Threshold.Warning", "WarningLow", 1027 "/Thresholds/LowerCaution/Reading"_json_pointer); 1028 properties.emplace_back( 1029 "xyz.openbmc_project.Sensor.Threshold.Critical", "CriticalHigh", 1030 "/Thresholds/UpperCritical/Reading"_json_pointer); 1031 properties.emplace_back( 1032 "xyz.openbmc_project.Sensor.Threshold.Critical", "CriticalLow", 1033 "/Thresholds/LowerCritical/Reading"_json_pointer); 1034 } 1035 else if (sensorType != "power") 1036 { 1037 properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Warning", 1038 "WarningHigh", 1039 "/UpperThresholdNonCritical"_json_pointer); 1040 properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Warning", 1041 "WarningLow", 1042 "/LowerThresholdNonCritical"_json_pointer); 1043 properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Critical", 1044 "CriticalHigh", 1045 "/UpperThresholdCritical"_json_pointer); 1046 properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Critical", 1047 "CriticalLow", 1048 "/LowerThresholdCritical"_json_pointer); 1049 } 1050 1051 // TODO Need to get UpperThresholdFatal and LowerThresholdFatal 1052 1053 if (sensorsAsyncResp->chassisSubNode == sensors::node::sensors) 1054 { 1055 properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MinValue", 1056 "/ReadingRangeMin"_json_pointer); 1057 properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MaxValue", 1058 "/ReadingRangeMax"_json_pointer); 1059 } 1060 else if (sensorType == "temperature") 1061 { 1062 properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MinValue", 1063 "/MinReadingRangeTemp"_json_pointer); 1064 properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MaxValue", 1065 "/MaxReadingRangeTemp"_json_pointer); 1066 } 1067 else if (sensorType != "power") 1068 { 1069 properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MinValue", 1070 "/MinReadingRange"_json_pointer); 1071 properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MaxValue", 1072 "/MaxReadingRange"_json_pointer); 1073 } 1074 1075 for (const std::tuple<const char*, const char*, 1076 nlohmann::json::json_pointer>& p : properties) 1077 { 1078 for (const auto& [interface, values] : interfacesDict) 1079 { 1080 if (interface != std::get<0>(p)) 1081 { 1082 continue; 1083 } 1084 for (const auto& [valueName, valueVariant] : values) 1085 { 1086 if (valueName != std::get<1>(p)) 1087 { 1088 continue; 1089 } 1090 1091 // The property we want to set may be nested json, so use 1092 // a json_pointer for easy indexing into the json structure. 1093 const nlohmann::json::json_pointer& key = std::get<2>(p); 1094 1095 // Attempt to pull the int64 directly 1096 const int64_t* int64Value = std::get_if<int64_t>(&valueVariant); 1097 1098 const double* doubleValue = std::get_if<double>(&valueVariant); 1099 const uint32_t* uValue = std::get_if<uint32_t>(&valueVariant); 1100 double temp = 0.0; 1101 if (int64Value != nullptr) 1102 { 1103 temp = static_cast<double>(*int64Value); 1104 } 1105 else if (doubleValue != nullptr) 1106 { 1107 temp = *doubleValue; 1108 } 1109 else if (uValue != nullptr) 1110 { 1111 temp = *uValue; 1112 } 1113 else 1114 { 1115 BMCWEB_LOG_ERROR 1116 << "Got value interface that wasn't int or double"; 1117 continue; 1118 } 1119 temp = temp * std::pow(10, scaleMultiplier); 1120 if (forceToInt) 1121 { 1122 sensorJson[key] = static_cast<int64_t>(temp); 1123 } 1124 else 1125 { 1126 sensorJson[key] = temp; 1127 } 1128 } 1129 } 1130 } 1131 1132 sensorsAsyncResp->addMetadata(sensorJson, unit.to_string(), 1133 "/xyz/openbmc_project/sensors/" + sensorType + 1134 "/" + sensorName); 1135 1136 BMCWEB_LOG_DEBUG << "Added sensor " << sensorName; 1137 } 1138 1139 inline void populateFanRedundancy( 1140 const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp) 1141 { 1142 crow::connections::systemBus->async_method_call( 1143 [sensorsAsyncResp]( 1144 const boost::system::error_code ec, 1145 const dbus::utility::MapperGetSubTreeResponse& resp) { 1146 if (ec) 1147 { 1148 return; // don't have to have this interface 1149 } 1150 for (const std::pair< 1151 std::string, 1152 std::vector<std::pair<std::string, std::vector<std::string>>>>& 1153 pathPair : resp) 1154 { 1155 const std::string& path = pathPair.first; 1156 const std::vector<std::pair<std::string, std::vector<std::string>>>& 1157 objDict = pathPair.second; 1158 if (objDict.empty()) 1159 { 1160 continue; // this should be impossible 1161 } 1162 1163 const std::string& owner = objDict.begin()->first; 1164 sdbusplus::asio::getProperty<std::vector<std::string>>( 1165 *crow::connections::systemBus, 1166 "xyz.openbmc_project.ObjectMapper", path + "/chassis", 1167 "xyz.openbmc_project.Association", "endpoints", 1168 [path, owner, 1169 sensorsAsyncResp](const boost::system::error_code e, 1170 const std::vector<std::string>& endpoints) { 1171 if (e) 1172 { 1173 return; // if they don't have an association we 1174 // can't tell what chassis is 1175 } 1176 auto found = 1177 std::find_if(endpoints.begin(), endpoints.end(), 1178 [sensorsAsyncResp](const std::string& entry) { 1179 return entry.find(sensorsAsyncResp->chassisId) != 1180 std::string::npos; 1181 }); 1182 1183 if (found == endpoints.end()) 1184 { 1185 return; 1186 } 1187 crow::connections::systemBus->async_method_call( 1188 [path, sensorsAsyncResp]( 1189 const boost::system::error_code& err, 1190 const boost::container::flat_map< 1191 std::string, dbus::utility::DbusVariantType>& ret) { 1192 if (err) 1193 { 1194 return; // don't have to have this 1195 // interface 1196 } 1197 auto findFailures = ret.find("AllowedFailures"); 1198 auto findCollection = ret.find("Collection"); 1199 auto findStatus = ret.find("Status"); 1200 1201 if (findFailures == ret.end() || 1202 findCollection == ret.end() || findStatus == ret.end()) 1203 { 1204 BMCWEB_LOG_ERROR << "Invalid redundancy interface"; 1205 messages::internalError( 1206 sensorsAsyncResp->asyncResp->res); 1207 return; 1208 } 1209 1210 const uint8_t* allowedFailures = 1211 std::get_if<uint8_t>(&(findFailures->second)); 1212 const std::vector<std::string>* collection = 1213 std::get_if<std::vector<std::string>>( 1214 &(findCollection->second)); 1215 const std::string* status = 1216 std::get_if<std::string>(&(findStatus->second)); 1217 1218 if (allowedFailures == nullptr || collection == nullptr || 1219 status == nullptr) 1220 { 1221 1222 BMCWEB_LOG_ERROR 1223 << "Invalid redundancy interface types"; 1224 messages::internalError( 1225 sensorsAsyncResp->asyncResp->res); 1226 return; 1227 } 1228 sdbusplus::message::object_path objectPath(path); 1229 std::string name = objectPath.filename(); 1230 if (name.empty()) 1231 { 1232 // this should be impossible 1233 messages::internalError( 1234 sensorsAsyncResp->asyncResp->res); 1235 return; 1236 } 1237 std::replace(name.begin(), name.end(), '_', ' '); 1238 1239 std::string health; 1240 1241 if (boost::ends_with(*status, "Full")) 1242 { 1243 health = "OK"; 1244 } 1245 else if (boost::ends_with(*status, "Degraded")) 1246 { 1247 health = "Warning"; 1248 } 1249 else 1250 { 1251 health = "Critical"; 1252 } 1253 nlohmann::json::array_t redfishCollection; 1254 const auto& fanRedfish = 1255 sensorsAsyncResp->asyncResp->res.jsonValue["Fans"]; 1256 for (const std::string& item : *collection) 1257 { 1258 sdbusplus::message::object_path path(item); 1259 std::string itemName = path.filename(); 1260 if (itemName.empty()) 1261 { 1262 continue; 1263 } 1264 /* 1265 todo(ed): merge patch that fixes the names 1266 std::replace(itemName.begin(), 1267 itemName.end(), '_', ' ');*/ 1268 auto schemaItem = 1269 std::find_if(fanRedfish.begin(), fanRedfish.end(), 1270 [itemName](const nlohmann::json& fan) { 1271 return fan["MemberId"] == itemName; 1272 }); 1273 if (schemaItem != fanRedfish.end()) 1274 { 1275 nlohmann::json::object_t collection; 1276 collection["@odata.id"] = 1277 (*schemaItem)["@odata.id"]; 1278 redfishCollection.emplace_back( 1279 std::move(collection)); 1280 } 1281 else 1282 { 1283 BMCWEB_LOG_ERROR << "failed to find fan in schema"; 1284 messages::internalError( 1285 sensorsAsyncResp->asyncResp->res); 1286 return; 1287 } 1288 } 1289 1290 size_t minNumNeeded = 1291 collection->empty() 1292 ? 0 1293 : collection->size() - *allowedFailures; 1294 nlohmann::json& jResp = sensorsAsyncResp->asyncResp->res 1295 .jsonValue["Redundancy"]; 1296 1297 nlohmann::json::object_t redundancy; 1298 redundancy["@odata.id"] = 1299 "/redfish/v1/Chassis/" + sensorsAsyncResp->chassisId + 1300 "/" + sensorsAsyncResp->chassisSubNode + 1301 "#/Redundancy/" + std::to_string(jResp.size()); 1302 redundancy["@odata.type"] = "#Redundancy.v1_3_2.Redundancy"; 1303 redundancy["MinNumNeeded"] = minNumNeeded; 1304 redundancy["MemberId"] = name; 1305 redundancy["Mode"] = "N+m"; 1306 redundancy["Name"] = name; 1307 redundancy["RedundancySet"] = redfishCollection; 1308 redundancy["Status"]["Health"] = health; 1309 redundancy["Status"]["State"] = "Enabled"; 1310 1311 jResp.push_back(std::move(redundancy)); 1312 }, 1313 owner, path, "org.freedesktop.DBus.Properties", "GetAll", 1314 "xyz.openbmc_project.Control.FanRedundancy"); 1315 }); 1316 } 1317 }, 1318 "xyz.openbmc_project.ObjectMapper", 1319 "/xyz/openbmc_project/object_mapper", 1320 "xyz.openbmc_project.ObjectMapper", "GetSubTree", 1321 "/xyz/openbmc_project/control", 2, 1322 std::array<const char*, 1>{ 1323 "xyz.openbmc_project.Control.FanRedundancy"}); 1324 } 1325 1326 inline void 1327 sortJSONResponse(const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp) 1328 { 1329 nlohmann::json& response = sensorsAsyncResp->asyncResp->res.jsonValue; 1330 std::array<std::string, 2> sensorHeaders{"Temperatures", "Fans"}; 1331 if (sensorsAsyncResp->chassisSubNode == sensors::node::power) 1332 { 1333 sensorHeaders = {"Voltages", "PowerSupplies"}; 1334 } 1335 for (const std::string& sensorGroup : sensorHeaders) 1336 { 1337 nlohmann::json::iterator entry = response.find(sensorGroup); 1338 if (entry != response.end()) 1339 { 1340 std::sort(entry->begin(), entry->end(), 1341 [](nlohmann::json& c1, nlohmann::json& c2) { 1342 return c1["Name"] < c2["Name"]; 1343 }); 1344 1345 // add the index counts to the end of each entry 1346 size_t count = 0; 1347 for (nlohmann::json& sensorJson : *entry) 1348 { 1349 nlohmann::json::iterator odata = sensorJson.find("@odata.id"); 1350 if (odata == sensorJson.end()) 1351 { 1352 continue; 1353 } 1354 std::string* value = odata->get_ptr<std::string*>(); 1355 if (value != nullptr) 1356 { 1357 *value += std::to_string(count); 1358 count++; 1359 sensorsAsyncResp->updateUri(sensorJson["Name"], *value); 1360 } 1361 } 1362 } 1363 } 1364 } 1365 1366 /** 1367 * @brief Finds the inventory item with the specified object path. 1368 * @param inventoryItems D-Bus inventory items associated with sensors. 1369 * @param invItemObjPath D-Bus object path of inventory item. 1370 * @return Inventory item within vector, or nullptr if no match found. 1371 */ 1372 inline InventoryItem* findInventoryItem( 1373 const std::shared_ptr<std::vector<InventoryItem>>& inventoryItems, 1374 const std::string& invItemObjPath) 1375 { 1376 for (InventoryItem& inventoryItem : *inventoryItems) 1377 { 1378 if (inventoryItem.objectPath == invItemObjPath) 1379 { 1380 return &inventoryItem; 1381 } 1382 } 1383 return nullptr; 1384 } 1385 1386 /** 1387 * @brief Finds the inventory item associated with the specified sensor. 1388 * @param inventoryItems D-Bus inventory items associated with sensors. 1389 * @param sensorObjPath D-Bus object path of sensor. 1390 * @return Inventory item within vector, or nullptr if no match found. 1391 */ 1392 inline InventoryItem* findInventoryItemForSensor( 1393 const std::shared_ptr<std::vector<InventoryItem>>& inventoryItems, 1394 const std::string& sensorObjPath) 1395 { 1396 for (InventoryItem& inventoryItem : *inventoryItems) 1397 { 1398 if (inventoryItem.sensors.count(sensorObjPath) > 0) 1399 { 1400 return &inventoryItem; 1401 } 1402 } 1403 return nullptr; 1404 } 1405 1406 /** 1407 * @brief Finds the inventory item associated with the specified led path. 1408 * @param inventoryItems D-Bus inventory items associated with sensors. 1409 * @param ledObjPath D-Bus object path of led. 1410 * @return Inventory item within vector, or nullptr if no match found. 1411 */ 1412 inline InventoryItem* 1413 findInventoryItemForLed(std::vector<InventoryItem>& inventoryItems, 1414 const std::string& ledObjPath) 1415 { 1416 for (InventoryItem& inventoryItem : inventoryItems) 1417 { 1418 if (inventoryItem.ledObjectPath == ledObjPath) 1419 { 1420 return &inventoryItem; 1421 } 1422 } 1423 return nullptr; 1424 } 1425 1426 /** 1427 * @brief Adds inventory item and associated sensor to specified vector. 1428 * 1429 * Adds a new InventoryItem to the vector if necessary. Searches for an 1430 * existing InventoryItem with the specified object path. If not found, one is 1431 * added to the vector. 1432 * 1433 * Next, the specified sensor is added to the set of sensors associated with the 1434 * InventoryItem. 1435 * 1436 * @param inventoryItems D-Bus inventory items associated with sensors. 1437 * @param invItemObjPath D-Bus object path of inventory item. 1438 * @param sensorObjPath D-Bus object path of sensor 1439 */ 1440 inline void addInventoryItem( 1441 const std::shared_ptr<std::vector<InventoryItem>>& inventoryItems, 1442 const std::string& invItemObjPath, const std::string& sensorObjPath) 1443 { 1444 // Look for inventory item in vector 1445 InventoryItem* inventoryItem = 1446 findInventoryItem(inventoryItems, invItemObjPath); 1447 1448 // If inventory item doesn't exist in vector, add it 1449 if (inventoryItem == nullptr) 1450 { 1451 inventoryItems->emplace_back(invItemObjPath); 1452 inventoryItem = &(inventoryItems->back()); 1453 } 1454 1455 // Add sensor to set of sensors associated with inventory item 1456 inventoryItem->sensors.emplace(sensorObjPath); 1457 } 1458 1459 /** 1460 * @brief Stores D-Bus data in the specified inventory item. 1461 * 1462 * Finds D-Bus data in the specified map of interfaces. Stores the data in the 1463 * specified InventoryItem. 1464 * 1465 * This data is later used to provide sensor property values in the JSON 1466 * response. 1467 * 1468 * @param inventoryItem Inventory item where data will be stored. 1469 * @param interfacesDict Map containing D-Bus interfaces and their properties 1470 * for the specified inventory item. 1471 */ 1472 inline void storeInventoryItemData( 1473 InventoryItem& inventoryItem, 1474 const dbus::utility::DBusInteracesMap& interfacesDict) 1475 { 1476 // Get properties from Inventory.Item interface 1477 1478 for (const auto& [interface, values] : interfacesDict) 1479 { 1480 if (interface == "xyz.openbmc_project.Inventory.Item") 1481 { 1482 for (const auto& [name, dbusValue] : values) 1483 { 1484 if (name == "Present") 1485 { 1486 const bool* value = std::get_if<bool>(&dbusValue); 1487 if (value != nullptr) 1488 { 1489 inventoryItem.isPresent = *value; 1490 } 1491 } 1492 } 1493 } 1494 // Check if Inventory.Item.PowerSupply interface is present 1495 1496 if (interface == "xyz.openbmc_project.Inventory.Item.PowerSupply") 1497 { 1498 inventoryItem.isPowerSupply = true; 1499 } 1500 1501 // Get properties from Inventory.Decorator.Asset interface 1502 if (interface == "xyz.openbmc_project.Inventory.Decorator.Asset") 1503 { 1504 for (const auto& [name, dbusValue] : values) 1505 { 1506 if (name == "Manufacturer") 1507 { 1508 const std::string* value = 1509 std::get_if<std::string>(&dbusValue); 1510 if (value != nullptr) 1511 { 1512 inventoryItem.manufacturer = *value; 1513 } 1514 } 1515 if (name == "Model") 1516 { 1517 const std::string* value = 1518 std::get_if<std::string>(&dbusValue); 1519 if (value != nullptr) 1520 { 1521 inventoryItem.model = *value; 1522 } 1523 } 1524 if (name == "SerialNumber") 1525 { 1526 const std::string* value = 1527 std::get_if<std::string>(&dbusValue); 1528 if (value != nullptr) 1529 { 1530 inventoryItem.serialNumber = *value; 1531 } 1532 } 1533 if (name == "PartNumber") 1534 { 1535 const std::string* value = 1536 std::get_if<std::string>(&dbusValue); 1537 if (value != nullptr) 1538 { 1539 inventoryItem.partNumber = *value; 1540 } 1541 } 1542 } 1543 } 1544 1545 if (interface == 1546 "xyz.openbmc_project.State.Decorator.OperationalStatus") 1547 { 1548 for (const auto& [name, dbusValue] : values) 1549 { 1550 if (name == "Functional") 1551 { 1552 const bool* value = std::get_if<bool>(&dbusValue); 1553 if (value != nullptr) 1554 { 1555 inventoryItem.isFunctional = *value; 1556 } 1557 } 1558 } 1559 } 1560 } 1561 } 1562 1563 /** 1564 * @brief Gets D-Bus data for inventory items associated with sensors. 1565 * 1566 * Uses the specified connections (services) to obtain D-Bus data for inventory 1567 * items associated with sensors. Stores the resulting data in the 1568 * inventoryItems vector. 1569 * 1570 * This data is later used to provide sensor property values in the JSON 1571 * response. 1572 * 1573 * Finds the inventory item data asynchronously. Invokes callback when data has 1574 * been obtained. 1575 * 1576 * The callback must have the following signature: 1577 * @code 1578 * callback(void) 1579 * @endcode 1580 * 1581 * This function is called recursively, obtaining data asynchronously from one 1582 * connection in each call. This ensures the callback is not invoked until the 1583 * last asynchronous function has completed. 1584 * 1585 * @param sensorsAsyncResp Pointer to object holding response data. 1586 * @param inventoryItems D-Bus inventory items associated with sensors. 1587 * @param invConnections Connections that provide data for the inventory items. 1588 * @param objectMgrPaths Mappings from connection name to DBus object path that 1589 * implements ObjectManager. 1590 * @param callback Callback to invoke when inventory data has been obtained. 1591 * @param invConnectionsIndex Current index in invConnections. Only specified 1592 * in recursive calls to this function. 1593 */ 1594 template <typename Callback> 1595 static void getInventoryItemsData( 1596 std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp, 1597 std::shared_ptr<std::vector<InventoryItem>> inventoryItems, 1598 std::shared_ptr<boost::container::flat_set<std::string>> invConnections, 1599 std::shared_ptr<boost::container::flat_map<std::string, std::string>> 1600 objectMgrPaths, 1601 Callback&& callback, size_t invConnectionsIndex = 0) 1602 { 1603 BMCWEB_LOG_DEBUG << "getInventoryItemsData enter"; 1604 1605 // If no more connections left, call callback 1606 if (invConnectionsIndex >= invConnections->size()) 1607 { 1608 callback(); 1609 BMCWEB_LOG_DEBUG << "getInventoryItemsData exit"; 1610 return; 1611 } 1612 1613 // Get inventory item data from current connection 1614 auto it = invConnections->nth(invConnectionsIndex); 1615 if (it != invConnections->end()) 1616 { 1617 const std::string& invConnection = *it; 1618 1619 // Response handler for GetManagedObjects 1620 auto respHandler = 1621 [sensorsAsyncResp, inventoryItems, invConnections, objectMgrPaths, 1622 callback{std::forward<Callback>(callback)}, 1623 invConnectionsIndex](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 = 1711 [callback{std::forward<Callback>(callback)}, sensorsAsyncResp, 1712 inventoryItems]( 1713 const boost::system::error_code ec, 1714 const dbus::utility::MapperGetSubTreeResponse& 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 = 2067 [callback{std::forward<Callback>(callback)}, sensorsAsyncResp, 2068 inventoryItems]( 2069 const boost::system::error_code ec, 2070 const dbus::utility::MapperGetSubTreeResponse& 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 = 2165 [sensorsAsyncResp, inventoryItems, 2166 callback{std::forward<Callback>(callback)}]( 2167 const boost::system::error_code ec, 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 = [sensorsAsyncResp, inventoryItems, 2350 callback{std::move(callback)}]() { 2351 BMCWEB_LOG_DEBUG << "getInventoryItemsDataCb enter"; 2352 2353 auto getInventoryLedsCb = [sensorsAsyncResp, inventoryItems, 2354 callback{std::move(callback)}]() { 2355 BMCWEB_LOG_DEBUG << "getInventoryLedsCb enter"; 2356 // Find Power Supply Attributes and get the data 2357 getPowerSupplyAttributes(sensorsAsyncResp, inventoryItems, 2358 std::move(callback)); 2359 BMCWEB_LOG_DEBUG << "getInventoryLedsCb exit"; 2360 }; 2361 2362 // Find led connections and get the data 2363 getInventoryLeds(sensorsAsyncResp, inventoryItems, 2364 std::move(getInventoryLedsCb)); 2365 BMCWEB_LOG_DEBUG << "getInventoryItemsDataCb exit"; 2366 }; 2367 2368 // Get inventory item data from connections 2369 getInventoryItemsData(sensorsAsyncResp, inventoryItems, 2370 invConnections, objectMgrPaths, 2371 std::move(getInventoryItemsDataCb)); 2372 BMCWEB_LOG_DEBUG << "getInventoryItemsConnectionsCb exit"; 2373 }; 2374 2375 // Get connections that provide inventory item data 2376 getInventoryItemsConnections(sensorsAsyncResp, inventoryItems, 2377 std::move(getInventoryItemsConnectionsCb)); 2378 BMCWEB_LOG_DEBUG << "getInventoryItemAssociationsCb exit"; 2379 }; 2380 2381 // Get associations from sensors to inventory items 2382 getInventoryItemAssociations(sensorsAsyncResp, sensorNames, objectMgrPaths, 2383 std::move(getInventoryItemAssociationsCb)); 2384 BMCWEB_LOG_DEBUG << "getInventoryItems exit"; 2385 } 2386 2387 /** 2388 * @brief Returns JSON PowerSupply object for the specified inventory item. 2389 * 2390 * Searches for a JSON PowerSupply object that matches the specified inventory 2391 * item. If one is not found, a new PowerSupply object is added to the JSON 2392 * array. 2393 * 2394 * Multiple sensors are often associated with one power supply inventory item. 2395 * As a result, multiple sensor values are stored in one JSON PowerSupply 2396 * object. 2397 * 2398 * @param powerSupplyArray JSON array containing Redfish PowerSupply objects. 2399 * @param inventoryItem Inventory item for the power supply. 2400 * @param chassisId Chassis that contains the power supply. 2401 * @return JSON PowerSupply object for the specified inventory item. 2402 */ 2403 inline nlohmann::json& getPowerSupply(nlohmann::json& powerSupplyArray, 2404 const InventoryItem& inventoryItem, 2405 const std::string& chassisId) 2406 { 2407 // Check if matching PowerSupply object already exists in JSON array 2408 for (nlohmann::json& powerSupply : powerSupplyArray) 2409 { 2410 if (powerSupply["MemberId"] == inventoryItem.name) 2411 { 2412 return powerSupply; 2413 } 2414 } 2415 2416 // Add new PowerSupply object to JSON array 2417 powerSupplyArray.push_back({}); 2418 nlohmann::json& powerSupply = powerSupplyArray.back(); 2419 powerSupply["@odata.id"] = 2420 "/redfish/v1/Chassis/" + chassisId + "/Power#/PowerSupplies/"; 2421 powerSupply["MemberId"] = inventoryItem.name; 2422 powerSupply["Name"] = boost::replace_all_copy(inventoryItem.name, "_", " "); 2423 powerSupply["Manufacturer"] = inventoryItem.manufacturer; 2424 powerSupply["Model"] = inventoryItem.model; 2425 powerSupply["PartNumber"] = inventoryItem.partNumber; 2426 powerSupply["SerialNumber"] = inventoryItem.serialNumber; 2427 setLedState(powerSupply, &inventoryItem); 2428 2429 if (inventoryItem.powerSupplyEfficiencyPercent >= 0) 2430 { 2431 powerSupply["EfficiencyPercent"] = 2432 inventoryItem.powerSupplyEfficiencyPercent; 2433 } 2434 2435 powerSupply["Status"]["State"] = getState(&inventoryItem); 2436 const char* health = inventoryItem.isFunctional ? "OK" : "Critical"; 2437 powerSupply["Status"]["Health"] = health; 2438 2439 return powerSupply; 2440 } 2441 2442 /** 2443 * @brief Gets the values of the specified sensors. 2444 * 2445 * Stores the results as JSON in the SensorsAsyncResp. 2446 * 2447 * Gets the sensor values asynchronously. Stores the results later when the 2448 * information has been obtained. 2449 * 2450 * The sensorNames set contains all requested sensors for the current chassis. 2451 * 2452 * To minimize the number of DBus calls, the DBus method 2453 * org.freedesktop.DBus.ObjectManager.GetManagedObjects() is used to get the 2454 * values of all sensors provided by a connection (service). 2455 * 2456 * The connections set contains all the connections that provide sensor values. 2457 * 2458 * The objectMgrPaths map contains mappings from a connection name to the 2459 * corresponding DBus object path that implements ObjectManager. 2460 * 2461 * The InventoryItem vector contains D-Bus inventory items associated with the 2462 * sensors. Inventory item data is needed for some Redfish sensor properties. 2463 * 2464 * @param SensorsAsyncResp Pointer to object holding response data. 2465 * @param sensorNames All requested sensors within the current chassis. 2466 * @param connections Connections that provide sensor values. 2467 * @param objectMgrPaths Mappings from connection name to DBus object path that 2468 * implements ObjectManager. 2469 * @param inventoryItems Inventory items associated with the sensors. 2470 */ 2471 inline void getSensorData( 2472 const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp, 2473 const std::shared_ptr<boost::container::flat_set<std::string>>& sensorNames, 2474 const boost::container::flat_set<std::string>& connections, 2475 const std::shared_ptr<boost::container::flat_map<std::string, std::string>>& 2476 objectMgrPaths, 2477 const std::shared_ptr<std::vector<InventoryItem>>& inventoryItems) 2478 { 2479 BMCWEB_LOG_DEBUG << "getSensorData enter"; 2480 // Get managed objects from all services exposing sensors 2481 for (const std::string& connection : connections) 2482 { 2483 // Response handler to process managed objects 2484 auto getManagedObjectsCb = 2485 [sensorsAsyncResp, sensorNames, 2486 inventoryItems](const boost::system::error_code ec, 2487 dbus::utility::ManagedObjectType& resp) { 2488 BMCWEB_LOG_DEBUG << "getManagedObjectsCb enter"; 2489 if (ec) 2490 { 2491 BMCWEB_LOG_ERROR << "getManagedObjectsCb DBUS error: " << ec; 2492 messages::internalError(sensorsAsyncResp->asyncResp->res); 2493 return; 2494 } 2495 // Go through all objects and update response with sensor data 2496 for (const auto& objDictEntry : resp) 2497 { 2498 const std::string& objPath = 2499 static_cast<const std::string&>(objDictEntry.first); 2500 BMCWEB_LOG_DEBUG << "getManagedObjectsCb parsing object " 2501 << objPath; 2502 2503 std::vector<std::string> split; 2504 // Reserve space for 2505 // /xyz/openbmc_project/sensors/<name>/<subname> 2506 split.reserve(6); 2507 boost::algorithm::split(split, objPath, boost::is_any_of("/")); 2508 if (split.size() < 6) 2509 { 2510 BMCWEB_LOG_ERROR << "Got path that isn't long enough " 2511 << objPath; 2512 continue; 2513 } 2514 // These indexes aren't intuitive, as boost::split puts an empty 2515 // string at the beginning 2516 const std::string& sensorType = split[4]; 2517 const std::string& sensorName = split[5]; 2518 BMCWEB_LOG_DEBUG << "sensorName " << sensorName 2519 << " sensorType " << sensorType; 2520 if (sensorNames->find(objPath) == sensorNames->end()) 2521 { 2522 BMCWEB_LOG_DEBUG << sensorName << " not in sensor list "; 2523 continue; 2524 } 2525 2526 // Find inventory item (if any) associated with sensor 2527 InventoryItem* inventoryItem = 2528 findInventoryItemForSensor(inventoryItems, objPath); 2529 2530 const std::string& sensorSchema = 2531 sensorsAsyncResp->chassisSubNode; 2532 2533 nlohmann::json* sensorJson = nullptr; 2534 2535 if (sensorSchema == sensors::node::sensors && 2536 !sensorsAsyncResp->efficientExpand) 2537 { 2538 sensorsAsyncResp->asyncResp->res.jsonValue["@odata.id"] = 2539 "/redfish/v1/Chassis/" + sensorsAsyncResp->chassisId + 2540 "/" + sensorsAsyncResp->chassisSubNode + "/" + 2541 sensorName; 2542 sensorJson = &(sensorsAsyncResp->asyncResp->res.jsonValue); 2543 } 2544 else 2545 { 2546 std::string fieldName; 2547 if (sensorsAsyncResp->efficientExpand) 2548 { 2549 fieldName = "Members"; 2550 } 2551 else if (sensorType == "temperature") 2552 { 2553 fieldName = "Temperatures"; 2554 } 2555 else if (sensorType == "fan" || sensorType == "fan_tach" || 2556 sensorType == "fan_pwm") 2557 { 2558 fieldName = "Fans"; 2559 } 2560 else if (sensorType == "voltage") 2561 { 2562 fieldName = "Voltages"; 2563 } 2564 else if (sensorType == "power") 2565 { 2566 if (sensorName == "total_power") 2567 { 2568 fieldName = "PowerControl"; 2569 } 2570 else if ((inventoryItem != nullptr) && 2571 (inventoryItem->isPowerSupply)) 2572 { 2573 fieldName = "PowerSupplies"; 2574 } 2575 else 2576 { 2577 // Other power sensors are in SensorCollection 2578 continue; 2579 } 2580 } 2581 else 2582 { 2583 BMCWEB_LOG_ERROR << "Unsure how to handle sensorType " 2584 << sensorType; 2585 continue; 2586 } 2587 2588 nlohmann::json& tempArray = 2589 sensorsAsyncResp->asyncResp->res.jsonValue[fieldName]; 2590 if (fieldName == "PowerControl") 2591 { 2592 if (tempArray.empty()) 2593 { 2594 // Put multiple "sensors" into a single 2595 // PowerControl. Follows MemberId naming and 2596 // naming in power.hpp. 2597 nlohmann::json::object_t power; 2598 power["@odata.id"] = 2599 "/redfish/v1/Chassis/" + 2600 sensorsAsyncResp->chassisId + "/" + 2601 sensorsAsyncResp->chassisSubNode + "#/" + 2602 fieldName + "/0"; 2603 tempArray.push_back(std::move(power)); 2604 } 2605 sensorJson = &(tempArray.back()); 2606 } 2607 else if (fieldName == "PowerSupplies") 2608 { 2609 if (inventoryItem != nullptr) 2610 { 2611 sensorJson = 2612 &(getPowerSupply(tempArray, *inventoryItem, 2613 sensorsAsyncResp->chassisId)); 2614 } 2615 } 2616 else if (fieldName == "Members") 2617 { 2618 nlohmann::json::object_t member; 2619 member["@odata.id"] = 2620 "/redfish/v1/Chassis/" + 2621 sensorsAsyncResp->chassisId + "/" + 2622 sensorsAsyncResp->chassisSubNode + "/" + sensorName; 2623 tempArray.push_back(std::move(member)); 2624 sensorJson = &(tempArray.back()); 2625 } 2626 else 2627 { 2628 nlohmann::json::object_t member; 2629 member["@odata.id"] = "/redfish/v1/Chassis/" + 2630 sensorsAsyncResp->chassisId + 2631 "/" + 2632 sensorsAsyncResp->chassisSubNode + 2633 "#/" + fieldName + "/"; 2634 tempArray.push_back(std::move(member)); 2635 sensorJson = &(tempArray.back()); 2636 } 2637 } 2638 2639 if (sensorJson != nullptr) 2640 { 2641 objectInterfacesToJson( 2642 sensorName, sensorType, sensorsAsyncResp, 2643 objDictEntry.second, *sensorJson, inventoryItem); 2644 } 2645 } 2646 if (sensorsAsyncResp.use_count() == 1) 2647 { 2648 sortJSONResponse(sensorsAsyncResp); 2649 if (sensorsAsyncResp->chassisSubNode == 2650 sensors::node::sensors && 2651 sensorsAsyncResp->efficientExpand) 2652 { 2653 sensorsAsyncResp->asyncResp->res 2654 .jsonValue["Members@odata.count"] = 2655 sensorsAsyncResp->asyncResp->res.jsonValue["Members"] 2656 .size(); 2657 } 2658 else if (sensorsAsyncResp->chassisSubNode == 2659 sensors::node::thermal) 2660 { 2661 populateFanRedundancy(sensorsAsyncResp); 2662 } 2663 } 2664 BMCWEB_LOG_DEBUG << "getManagedObjectsCb exit"; 2665 }; 2666 2667 // Find DBus object path that implements ObjectManager for the current 2668 // connection. If no mapping found, default to "/". 2669 auto iter = objectMgrPaths->find(connection); 2670 const std::string& objectMgrPath = 2671 (iter != objectMgrPaths->end()) ? iter->second : "/"; 2672 BMCWEB_LOG_DEBUG << "ObjectManager path for " << connection << " is " 2673 << objectMgrPath; 2674 2675 crow::connections::systemBus->async_method_call( 2676 getManagedObjectsCb, connection, objectMgrPath, 2677 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 2678 } 2679 BMCWEB_LOG_DEBUG << "getSensorData exit"; 2680 } 2681 2682 inline void processSensorList( 2683 const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp, 2684 const std::shared_ptr<boost::container::flat_set<std::string>>& sensorNames) 2685 { 2686 auto getConnectionCb = 2687 [sensorsAsyncResp, sensorNames]( 2688 const boost::container::flat_set<std::string>& connections) { 2689 BMCWEB_LOG_DEBUG << "getConnectionCb enter"; 2690 auto getObjectManagerPathsCb = 2691 [sensorsAsyncResp, sensorNames, 2692 connections](const std::shared_ptr<boost::container::flat_map< 2693 std::string, std::string>>& objectMgrPaths) { 2694 BMCWEB_LOG_DEBUG << "getObjectManagerPathsCb enter"; 2695 auto getInventoryItemsCb = 2696 [sensorsAsyncResp, sensorNames, connections, objectMgrPaths]( 2697 const std::shared_ptr<std::vector<InventoryItem>>& 2698 inventoryItems) { 2699 BMCWEB_LOG_DEBUG << "getInventoryItemsCb enter"; 2700 // Get sensor data and store results in JSON 2701 getSensorData(sensorsAsyncResp, sensorNames, connections, 2702 objectMgrPaths, inventoryItems); 2703 BMCWEB_LOG_DEBUG << "getInventoryItemsCb exit"; 2704 }; 2705 2706 // Get inventory items associated with sensors 2707 getInventoryItems(sensorsAsyncResp, sensorNames, objectMgrPaths, 2708 std::move(getInventoryItemsCb)); 2709 2710 BMCWEB_LOG_DEBUG << "getObjectManagerPathsCb exit"; 2711 }; 2712 2713 // Get mapping from connection names to the DBus object 2714 // paths that implement the ObjectManager interface 2715 getObjectManagerPaths(sensorsAsyncResp, 2716 std::move(getObjectManagerPathsCb)); 2717 BMCWEB_LOG_DEBUG << "getConnectionCb exit"; 2718 }; 2719 2720 // Get set of connections that provide sensor values 2721 getConnections(sensorsAsyncResp, sensorNames, std::move(getConnectionCb)); 2722 } 2723 2724 /** 2725 * @brief Entry point for retrieving sensors data related to requested 2726 * chassis. 2727 * @param SensorsAsyncResp Pointer to object holding response data 2728 */ 2729 inline void 2730 getChassisData(const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp) 2731 { 2732 BMCWEB_LOG_DEBUG << "getChassisData enter"; 2733 auto getChassisCb = 2734 [sensorsAsyncResp]( 2735 const std::shared_ptr<boost::container::flat_set<std::string>>& 2736 sensorNames) { 2737 BMCWEB_LOG_DEBUG << "getChassisCb enter"; 2738 processSensorList(sensorsAsyncResp, sensorNames); 2739 BMCWEB_LOG_DEBUG << "getChassisCb exit"; 2740 }; 2741 // SensorCollection doesn't contain the Redundancy property 2742 if (sensorsAsyncResp->chassisSubNode != sensors::node::sensors) 2743 { 2744 sensorsAsyncResp->asyncResp->res.jsonValue["Redundancy"] = 2745 nlohmann::json::array(); 2746 } 2747 // Get set of sensors in chassis 2748 getChassis(sensorsAsyncResp, std::move(getChassisCb)); 2749 BMCWEB_LOG_DEBUG << "getChassisData exit"; 2750 } 2751 2752 /** 2753 * @brief Find the requested sensorName in the list of all sensors supplied by 2754 * the chassis node 2755 * 2756 * @param sensorName The sensor name supplied in the PATCH request 2757 * @param sensorsList The list of sensors managed by the chassis node 2758 * @param sensorsModified The list of sensors that were found as a result of 2759 * repeated calls to this function 2760 */ 2761 inline bool findSensorNameUsingSensorPath( 2762 std::string_view sensorName, 2763 boost::container::flat_set<std::string>& sensorsList, 2764 boost::container::flat_set<std::string>& sensorsModified) 2765 { 2766 for (auto& chassisSensor : sensorsList) 2767 { 2768 sdbusplus::message::object_path path(chassisSensor); 2769 std::string thisSensorName = path.filename(); 2770 if (thisSensorName.empty()) 2771 { 2772 continue; 2773 } 2774 if (thisSensorName == sensorName) 2775 { 2776 sensorsModified.emplace(chassisSensor); 2777 return true; 2778 } 2779 } 2780 return false; 2781 } 2782 2783 /** 2784 * @brief Entry point for overriding sensor values of given sensor 2785 * 2786 * @param sensorAsyncResp response object 2787 * @param allCollections Collections extract from sensors' request patch info 2788 * @param chassisSubNode Chassis Node for which the query has to happen 2789 */ 2790 inline void setSensorsOverride( 2791 const std::shared_ptr<SensorsAsyncResp>& sensorAsyncResp, 2792 std::unordered_map<std::string, std::vector<nlohmann::json>>& 2793 allCollections) 2794 { 2795 BMCWEB_LOG_INFO << "setSensorsOverride for subNode" 2796 << sensorAsyncResp->chassisSubNode << "\n"; 2797 2798 const char* propertyValueName = nullptr; 2799 std::unordered_map<std::string, std::pair<double, std::string>> overrideMap; 2800 std::string memberId; 2801 double value = 0.0; 2802 for (auto& collectionItems : allCollections) 2803 { 2804 if (collectionItems.first == "Temperatures") 2805 { 2806 propertyValueName = "ReadingCelsius"; 2807 } 2808 else if (collectionItems.first == "Fans") 2809 { 2810 propertyValueName = "Reading"; 2811 } 2812 else 2813 { 2814 propertyValueName = "ReadingVolts"; 2815 } 2816 for (auto& item : collectionItems.second) 2817 { 2818 if (!json_util::readJson(item, sensorAsyncResp->asyncResp->res, 2819 "MemberId", memberId, propertyValueName, 2820 value)) 2821 { 2822 return; 2823 } 2824 overrideMap.emplace(memberId, 2825 std::make_pair(value, collectionItems.first)); 2826 } 2827 } 2828 2829 auto getChassisSensorListCb = 2830 [sensorAsyncResp, overrideMap]( 2831 const std::shared_ptr<boost::container::flat_set<std::string>>& 2832 sensorsList) { 2833 // Match sensor names in the PATCH request to those managed by the 2834 // chassis node 2835 const std::shared_ptr<boost::container::flat_set<std::string>> 2836 sensorNames = 2837 std::make_shared<boost::container::flat_set<std::string>>(); 2838 for (const auto& item : overrideMap) 2839 { 2840 const auto& sensor = item.first; 2841 if (!findSensorNameUsingSensorPath(sensor, *sensorsList, 2842 *sensorNames)) 2843 { 2844 BMCWEB_LOG_INFO << "Unable to find memberId " << item.first; 2845 messages::resourceNotFound(sensorAsyncResp->asyncResp->res, 2846 item.second.second, item.first); 2847 return; 2848 } 2849 } 2850 // Get the connection to which the memberId belongs 2851 auto getObjectsWithConnectionCb = 2852 [sensorAsyncResp, overrideMap]( 2853 const boost::container::flat_set<std::string>& /*connections*/, 2854 const std::set<std::pair<std::string, std::string>>& 2855 objectsWithConnection) { 2856 if (objectsWithConnection.size() != overrideMap.size()) 2857 { 2858 BMCWEB_LOG_INFO 2859 << "Unable to find all objects with proper connection " 2860 << objectsWithConnection.size() << " requested " 2861 << overrideMap.size() << "\n"; 2862 messages::resourceNotFound(sensorAsyncResp->asyncResp->res, 2863 sensorAsyncResp->chassisSubNode == 2864 sensors::node::thermal 2865 ? "Temperatures" 2866 : "Voltages", 2867 "Count"); 2868 return; 2869 } 2870 for (const auto& item : objectsWithConnection) 2871 { 2872 sdbusplus::message::object_path path(item.first); 2873 std::string sensorName = path.filename(); 2874 if (sensorName.empty()) 2875 { 2876 messages::internalError(sensorAsyncResp->asyncResp->res); 2877 return; 2878 } 2879 2880 const auto& iterator = overrideMap.find(sensorName); 2881 if (iterator == overrideMap.end()) 2882 { 2883 BMCWEB_LOG_INFO << "Unable to find sensor object" 2884 << item.first << "\n"; 2885 messages::internalError(sensorAsyncResp->asyncResp->res); 2886 return; 2887 } 2888 crow::connections::systemBus->async_method_call( 2889 [sensorAsyncResp](const boost::system::error_code ec) { 2890 if (ec) 2891 { 2892 if (ec.value() == 2893 boost::system::errc::permission_denied) 2894 { 2895 BMCWEB_LOG_WARNING 2896 << "Manufacturing mode is not Enabled...can't " 2897 "Override the sensor value. "; 2898 2899 messages::insufficientPrivilege( 2900 sensorAsyncResp->asyncResp->res); 2901 return; 2902 } 2903 BMCWEB_LOG_DEBUG 2904 << "setOverrideValueStatus DBUS error: " << ec; 2905 messages::internalError( 2906 sensorAsyncResp->asyncResp->res); 2907 } 2908 }, 2909 item.second, item.first, "org.freedesktop.DBus.Properties", 2910 "Set", "xyz.openbmc_project.Sensor.Value", "Value", 2911 dbus::utility::DbusVariantType(iterator->second.first)); 2912 } 2913 }; 2914 // Get object with connection for the given sensor name 2915 getObjectsWithConnection(sensorAsyncResp, sensorNames, 2916 std::move(getObjectsWithConnectionCb)); 2917 }; 2918 // get full sensor list for the given chassisId and cross verify the sensor. 2919 getChassis(sensorAsyncResp, std::move(getChassisSensorListCb)); 2920 } 2921 2922 /** 2923 * @brief Retrieves mapping of Redfish URIs to sensor value property to D-Bus 2924 * path of the sensor. 2925 * 2926 * Function builds valid Redfish response for sensor query of given chassis and 2927 * node. It then builds metadata about Redfish<->D-Bus correlations and provides 2928 * it to caller in a callback. 2929 * 2930 * @param chassis Chassis for which retrieval should be performed 2931 * @param node Node (group) of sensors. See sensors::node for supported values 2932 * @param mapComplete Callback to be called with retrieval result 2933 */ 2934 inline void retrieveUriToDbusMap(const std::string& chassis, 2935 const std::string& node, 2936 SensorsAsyncResp::DataCompleteCb&& mapComplete) 2937 { 2938 decltype(sensors::paths)::const_iterator pathIt = 2939 std::find_if(sensors::paths.cbegin(), sensors::paths.cend(), 2940 [&node](auto&& val) { return val.first == node; }); 2941 if (pathIt == sensors::paths.cend()) 2942 { 2943 BMCWEB_LOG_ERROR << "Wrong node provided : " << node; 2944 mapComplete(boost::beast::http::status::bad_request, {}); 2945 return; 2946 } 2947 2948 auto asyncResp = std::make_shared<bmcweb::AsyncResp>(); 2949 auto callback = 2950 [asyncResp, mapCompleteCb{std::move(mapComplete)}]( 2951 const boost::beast::http::status status, 2952 const boost::container::flat_map<std::string, std::string>& 2953 uriToDbus) { mapCompleteCb(status, uriToDbus); }; 2954 2955 auto resp = std::make_shared<SensorsAsyncResp>( 2956 asyncResp, chassis, pathIt->second, node, std::move(callback)); 2957 getChassisData(resp); 2958 } 2959 2960 namespace sensors 2961 { 2962 2963 inline void getChassisCallback( 2964 const std::shared_ptr<SensorsAsyncResp>& asyncResp, 2965 const std::shared_ptr<boost::container::flat_set<std::string>>& sensorNames) 2966 { 2967 BMCWEB_LOG_DEBUG << "getChassisCallback enter"; 2968 2969 nlohmann::json& entriesArray = 2970 asyncResp->asyncResp->res.jsonValue["Members"]; 2971 for (auto& sensor : *sensorNames) 2972 { 2973 BMCWEB_LOG_DEBUG << "Adding sensor: " << sensor; 2974 2975 sdbusplus::message::object_path path(sensor); 2976 std::string sensorName = path.filename(); 2977 if (sensorName.empty()) 2978 { 2979 BMCWEB_LOG_ERROR << "Invalid sensor path: " << sensor; 2980 messages::internalError(asyncResp->asyncResp->res); 2981 return; 2982 } 2983 nlohmann::json::object_t member; 2984 member["@odata.id"] = "/redfish/v1/Chassis/" + asyncResp->chassisId + 2985 "/" + asyncResp->chassisSubNode + "/" + 2986 sensorName; 2987 entriesArray.push_back(std::move(member)); 2988 } 2989 2990 asyncResp->asyncResp->res.jsonValue["Members@odata.count"] = 2991 entriesArray.size(); 2992 BMCWEB_LOG_DEBUG << "getChassisCallback exit"; 2993 } 2994 2995 inline void 2996 handleSensorCollectionGet(App& app, const crow::Request& req, 2997 const std::shared_ptr<bmcweb::AsyncResp>& aResp, 2998 const std::string& chassisId) 2999 { 3000 query_param::QueryCapabilities capabilities = { 3001 .canDelegateExpandLevel = 1, 3002 }; 3003 query_param::Query delegatedQuery; 3004 if (!redfish::setUpRedfishRouteWithDelegation(app, req, aResp, 3005 delegatedQuery, capabilities)) 3006 { 3007 return; 3008 } 3009 3010 if (delegatedQuery.expandType != query_param::ExpandType::None) 3011 { 3012 // we perform efficient expand. 3013 auto asyncResp = std::make_shared<SensorsAsyncResp>( 3014 aResp, chassisId, sensors::dbus::sensorPaths, 3015 sensors::node::sensors, 3016 /*efficientExpand=*/true); 3017 getChassisData(asyncResp); 3018 3019 BMCWEB_LOG_DEBUG 3020 << "SensorCollection doGet exit via efficient expand handler"; 3021 return; 3022 } 3023 3024 // if there's no efficient expand available, we use the default 3025 // Query Parameters route 3026 auto asyncResp = std::make_shared<SensorsAsyncResp>( 3027 aResp, chassisId, sensors::dbus::sensorPaths, sensors::node::sensors); 3028 3029 // We get all sensors as hyperlinkes in the chassis (this 3030 // implies we reply on the default query parameters handler) 3031 getChassis(asyncResp, 3032 std::bind_front(sensors::getChassisCallback, asyncResp)); 3033 BMCWEB_LOG_DEBUG << "SensorCollection doGet exit"; 3034 } 3035 3036 inline void handleSensorGet(App& app, const crow::Request& req, 3037 const std::shared_ptr<bmcweb::AsyncResp>& aResp, 3038 const std::string& chassisId, 3039 const std::string& sensorName) 3040 { 3041 if (!redfish::setUpRedfishRoute(app, req, aResp)) 3042 { 3043 return; 3044 } 3045 BMCWEB_LOG_DEBUG << "Sensor doGet enter"; 3046 std::shared_ptr<SensorsAsyncResp> asyncResp = 3047 std::make_shared<SensorsAsyncResp>(aResp, chassisId, 3048 std::span<std::string_view>(), 3049 sensors::node::sensors); 3050 3051 const std::array<const char*, 1> interfaces = { 3052 "xyz.openbmc_project.Sensor.Value"}; 3053 3054 // Get a list of all of the sensors that implement Sensor.Value 3055 // and get the path and service name associated with the sensor 3056 crow::connections::systemBus->async_method_call( 3057 [asyncResp, 3058 sensorName](const boost::system::error_code ec, 3059 const ::dbus::utility::MapperGetSubTreeResponse& subtree) { 3060 BMCWEB_LOG_DEBUG << "respHandler1 enter"; 3061 if (ec) 3062 { 3063 messages::internalError(asyncResp->asyncResp->res); 3064 BMCWEB_LOG_ERROR << "Sensor getSensorPaths resp_handler: " 3065 << "Dbus error " << ec; 3066 return; 3067 } 3068 3069 ::dbus::utility::MapperGetSubTreeResponse::const_iterator it = 3070 std::find_if( 3071 subtree.begin(), subtree.end(), 3072 [sensorName]( 3073 const std::pair< 3074 std::string, 3075 std::vector<std::pair< 3076 std::string, std::vector<std::string>>>>& object) { 3077 sdbusplus::message::object_path path(object.first); 3078 std::string name = path.filename(); 3079 if (name.empty()) 3080 { 3081 BMCWEB_LOG_ERROR << "Invalid sensor path: " << object.first; 3082 return false; 3083 } 3084 3085 return name == sensorName; 3086 }); 3087 3088 if (it == subtree.end()) 3089 { 3090 BMCWEB_LOG_ERROR << "Could not find path for sensor: " 3091 << sensorName; 3092 messages::resourceNotFound(asyncResp->asyncResp->res, "Sensor", 3093 sensorName); 3094 return; 3095 } 3096 std::string_view sensorPath = (*it).first; 3097 BMCWEB_LOG_DEBUG << "Found sensor path for sensor '" << sensorName 3098 << "': " << sensorPath; 3099 3100 const std::shared_ptr<boost::container::flat_set<std::string>> 3101 sensorList = 3102 std::make_shared<boost::container::flat_set<std::string>>(); 3103 3104 sensorList->emplace(sensorPath); 3105 processSensorList(asyncResp, sensorList); 3106 BMCWEB_LOG_DEBUG << "respHandler1 exit"; 3107 }, 3108 "xyz.openbmc_project.ObjectMapper", 3109 "/xyz/openbmc_project/object_mapper", 3110 "xyz.openbmc_project.ObjectMapper", "GetSubTree", 3111 "/xyz/openbmc_project/sensors", 2, interfaces); 3112 } 3113 3114 } // namespace sensors 3115 3116 inline void requestRoutesSensorCollection(App& app) 3117 { 3118 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/Sensors/") 3119 .privileges(redfish::privileges::getSensorCollection) 3120 .methods(boost::beast::http::verb::get)( 3121 std::bind_front(sensors::handleSensorCollectionGet, std::ref(app))); 3122 } 3123 3124 inline void requestRoutesSensor(App& app) 3125 { 3126 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/Sensors/<str>/") 3127 .privileges(redfish::privileges::getSensor) 3128 .methods(boost::beast::http::verb::get)( 3129 std::bind_front(sensors::handleSensorGet, std::ref(app))); 3130 } 3131 3132 } // namespace redfish 3133