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