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 nlohmann::json::array_t 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 nlohmann::json::object_t collection; 1290 collection["@odata.id"] = 1291 (*schemaItem)["@odata.id"]; 1292 redfishCollection.emplace_back( 1293 std::move(collection)); 1294 } 1295 else 1296 { 1297 BMCWEB_LOG_ERROR 1298 << "failed to find fan in schema"; 1299 messages::internalError( 1300 sensorsAsyncResp->asyncResp->res); 1301 return; 1302 } 1303 } 1304 1305 size_t minNumNeeded = 1306 collection->empty() 1307 ? 0 1308 : collection->size() - *allowedFailures; 1309 nlohmann::json& jResp = 1310 sensorsAsyncResp->asyncResp->res 1311 .jsonValue["Redundancy"]; 1312 1313 nlohmann::json::object_t redundancy; 1314 redundancy["@odata.id"] = 1315 "/redfish/v1/Chassis/" + 1316 sensorsAsyncResp->chassisId + "/" + 1317 sensorsAsyncResp->chassisSubNode + 1318 "#/Redundancy/" + 1319 std::to_string(jResp.size()); 1320 redundancy["@odata.type"] = 1321 "#Redundancy.v1_3_2.Redundancy"; 1322 redundancy["MinNumNeeded"] = minNumNeeded; 1323 redundancy["MemberId"] = name; 1324 redundancy["Mode"] = "N+m"; 1325 redundancy["Name"] = name; 1326 redundancy["RedundancySet"] = redfishCollection; 1327 redundancy["Status"]["Health"] = health; 1328 redundancy["Status"]["State"] = "Enabled"; 1329 1330 jResp.push_back(std::move(redundancy)); 1331 }, 1332 owner, path, "org.freedesktop.DBus.Properties", 1333 "GetAll", 1334 "xyz.openbmc_project.Control.FanRedundancy"); 1335 }); 1336 } 1337 }, 1338 "xyz.openbmc_project.ObjectMapper", 1339 "/xyz/openbmc_project/object_mapper", 1340 "xyz.openbmc_project.ObjectMapper", "GetSubTree", 1341 "/xyz/openbmc_project/control", 2, 1342 std::array<const char*, 1>{ 1343 "xyz.openbmc_project.Control.FanRedundancy"}); 1344 } 1345 1346 inline void 1347 sortJSONResponse(const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp) 1348 { 1349 nlohmann::json& response = sensorsAsyncResp->asyncResp->res.jsonValue; 1350 std::array<std::string, 2> sensorHeaders{"Temperatures", "Fans"}; 1351 if (sensorsAsyncResp->chassisSubNode == sensors::node::power) 1352 { 1353 sensorHeaders = {"Voltages", "PowerSupplies"}; 1354 } 1355 for (const std::string& sensorGroup : sensorHeaders) 1356 { 1357 nlohmann::json::iterator entry = response.find(sensorGroup); 1358 if (entry != response.end()) 1359 { 1360 std::sort(entry->begin(), entry->end(), 1361 [](nlohmann::json& c1, nlohmann::json& c2) { 1362 return c1["Name"] < c2["Name"]; 1363 }); 1364 1365 // add the index counts to the end of each entry 1366 size_t count = 0; 1367 for (nlohmann::json& sensorJson : *entry) 1368 { 1369 nlohmann::json::iterator odata = sensorJson.find("@odata.id"); 1370 if (odata == sensorJson.end()) 1371 { 1372 continue; 1373 } 1374 std::string* value = odata->get_ptr<std::string*>(); 1375 if (value != nullptr) 1376 { 1377 *value += std::to_string(count); 1378 count++; 1379 sensorsAsyncResp->updateUri(sensorJson["Name"], *value); 1380 } 1381 } 1382 } 1383 } 1384 } 1385 1386 /** 1387 * @brief Finds the inventory item with the specified object path. 1388 * @param inventoryItems D-Bus inventory items associated with sensors. 1389 * @param invItemObjPath D-Bus object path of inventory item. 1390 * @return Inventory item within vector, or nullptr if no match found. 1391 */ 1392 inline InventoryItem* findInventoryItem( 1393 const std::shared_ptr<std::vector<InventoryItem>>& inventoryItems, 1394 const std::string& invItemObjPath) 1395 { 1396 for (InventoryItem& inventoryItem : *inventoryItems) 1397 { 1398 if (inventoryItem.objectPath == invItemObjPath) 1399 { 1400 return &inventoryItem; 1401 } 1402 } 1403 return nullptr; 1404 } 1405 1406 /** 1407 * @brief Finds the inventory item associated with the specified sensor. 1408 * @param inventoryItems D-Bus inventory items associated with sensors. 1409 * @param sensorObjPath D-Bus object path of sensor. 1410 * @return Inventory item within vector, or nullptr if no match found. 1411 */ 1412 inline InventoryItem* findInventoryItemForSensor( 1413 const std::shared_ptr<std::vector<InventoryItem>>& inventoryItems, 1414 const std::string& sensorObjPath) 1415 { 1416 for (InventoryItem& inventoryItem : *inventoryItems) 1417 { 1418 if (inventoryItem.sensors.count(sensorObjPath) > 0) 1419 { 1420 return &inventoryItem; 1421 } 1422 } 1423 return nullptr; 1424 } 1425 1426 /** 1427 * @brief Finds the inventory item associated with the specified led path. 1428 * @param inventoryItems D-Bus inventory items associated with sensors. 1429 * @param ledObjPath D-Bus object path of led. 1430 * @return Inventory item within vector, or nullptr if no match found. 1431 */ 1432 inline InventoryItem* 1433 findInventoryItemForLed(std::vector<InventoryItem>& inventoryItems, 1434 const std::string& ledObjPath) 1435 { 1436 for (InventoryItem& inventoryItem : inventoryItems) 1437 { 1438 if (inventoryItem.ledObjectPath == ledObjPath) 1439 { 1440 return &inventoryItem; 1441 } 1442 } 1443 return nullptr; 1444 } 1445 1446 /** 1447 * @brief Adds inventory item and associated sensor to specified vector. 1448 * 1449 * Adds a new InventoryItem to the vector if necessary. Searches for an 1450 * existing InventoryItem with the specified object path. If not found, one is 1451 * added to the vector. 1452 * 1453 * Next, the specified sensor is added to the set of sensors associated with the 1454 * InventoryItem. 1455 * 1456 * @param inventoryItems D-Bus inventory items associated with sensors. 1457 * @param invItemObjPath D-Bus object path of inventory item. 1458 * @param sensorObjPath D-Bus object path of sensor 1459 */ 1460 inline void addInventoryItem( 1461 const std::shared_ptr<std::vector<InventoryItem>>& inventoryItems, 1462 const std::string& invItemObjPath, const std::string& sensorObjPath) 1463 { 1464 // Look for inventory item in vector 1465 InventoryItem* inventoryItem = 1466 findInventoryItem(inventoryItems, invItemObjPath); 1467 1468 // If inventory item doesn't exist in vector, add it 1469 if (inventoryItem == nullptr) 1470 { 1471 inventoryItems->emplace_back(invItemObjPath); 1472 inventoryItem = &(inventoryItems->back()); 1473 } 1474 1475 // Add sensor to set of sensors associated with inventory item 1476 inventoryItem->sensors.emplace(sensorObjPath); 1477 } 1478 1479 /** 1480 * @brief Stores D-Bus data in the specified inventory item. 1481 * 1482 * Finds D-Bus data in the specified map of interfaces. Stores the data in the 1483 * specified InventoryItem. 1484 * 1485 * This data is later used to provide sensor property values in the JSON 1486 * response. 1487 * 1488 * @param inventoryItem Inventory item where data will be stored. 1489 * @param interfacesDict Map containing D-Bus interfaces and their properties 1490 * for the specified inventory item. 1491 */ 1492 inline void storeInventoryItemData( 1493 InventoryItem& inventoryItem, 1494 const dbus::utility::DBusInteracesMap& interfacesDict) 1495 { 1496 // Get properties from Inventory.Item interface 1497 1498 for (const auto& [interface, values] : interfacesDict) 1499 { 1500 if (interface == "xyz.openbmc_project.Inventory.Item") 1501 { 1502 for (const auto& [name, dbusValue] : values) 1503 { 1504 if (name == "Present") 1505 { 1506 const bool* value = std::get_if<bool>(&dbusValue); 1507 if (value != nullptr) 1508 { 1509 inventoryItem.isPresent = *value; 1510 } 1511 } 1512 } 1513 } 1514 // Check if Inventory.Item.PowerSupply interface is present 1515 1516 if (interface == "xyz.openbmc_project.Inventory.Item.PowerSupply") 1517 { 1518 inventoryItem.isPowerSupply = true; 1519 } 1520 1521 // Get properties from Inventory.Decorator.Asset interface 1522 if (interface == "xyz.openbmc_project.Inventory.Decorator.Asset") 1523 { 1524 for (const auto& [name, dbusValue] : values) 1525 { 1526 if (name == "Manufacturer") 1527 { 1528 const std::string* value = 1529 std::get_if<std::string>(&dbusValue); 1530 if (value != nullptr) 1531 { 1532 inventoryItem.manufacturer = *value; 1533 } 1534 } 1535 if (name == "Model") 1536 { 1537 const std::string* value = 1538 std::get_if<std::string>(&dbusValue); 1539 if (value != nullptr) 1540 { 1541 inventoryItem.model = *value; 1542 } 1543 } 1544 if (name == "SerialNumber") 1545 { 1546 const std::string* value = 1547 std::get_if<std::string>(&dbusValue); 1548 if (value != nullptr) 1549 { 1550 inventoryItem.serialNumber = *value; 1551 } 1552 } 1553 if (name == "PartNumber") 1554 { 1555 const std::string* value = 1556 std::get_if<std::string>(&dbusValue); 1557 if (value != nullptr) 1558 { 1559 inventoryItem.partNumber = *value; 1560 } 1561 } 1562 } 1563 } 1564 1565 if (interface == 1566 "xyz.openbmc_project.State.Decorator.OperationalStatus") 1567 { 1568 for (const auto& [name, dbusValue] : values) 1569 { 1570 if (name == "Functional") 1571 { 1572 const bool* value = std::get_if<bool>(&dbusValue); 1573 if (value != nullptr) 1574 { 1575 inventoryItem.isFunctional = *value; 1576 } 1577 } 1578 } 1579 } 1580 } 1581 } 1582 1583 /** 1584 * @brief Gets D-Bus data for inventory items associated with sensors. 1585 * 1586 * Uses the specified connections (services) to obtain D-Bus data for inventory 1587 * items associated with sensors. Stores the resulting data in the 1588 * inventoryItems vector. 1589 * 1590 * This data is later used to provide sensor property values in the JSON 1591 * response. 1592 * 1593 * Finds the inventory item data asynchronously. Invokes callback when data has 1594 * been obtained. 1595 * 1596 * The callback must have the following signature: 1597 * @code 1598 * callback(void) 1599 * @endcode 1600 * 1601 * This function is called recursively, obtaining data asynchronously from one 1602 * connection in each call. This ensures the callback is not invoked until the 1603 * last asynchronous function has completed. 1604 * 1605 * @param sensorsAsyncResp Pointer to object holding response data. 1606 * @param inventoryItems D-Bus inventory items associated with sensors. 1607 * @param invConnections Connections that provide data for the inventory items. 1608 * @param objectMgrPaths Mappings from connection name to DBus object path that 1609 * implements ObjectManager. 1610 * @param callback Callback to invoke when inventory data has been obtained. 1611 * @param invConnectionsIndex Current index in invConnections. Only specified 1612 * in recursive calls to this function. 1613 */ 1614 template <typename Callback> 1615 static void getInventoryItemsData( 1616 std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp, 1617 std::shared_ptr<std::vector<InventoryItem>> inventoryItems, 1618 std::shared_ptr<boost::container::flat_set<std::string>> invConnections, 1619 std::shared_ptr<boost::container::flat_map<std::string, std::string>> 1620 objectMgrPaths, 1621 Callback&& callback, size_t invConnectionsIndex = 0) 1622 { 1623 BMCWEB_LOG_DEBUG << "getInventoryItemsData enter"; 1624 1625 // If no more connections left, call callback 1626 if (invConnectionsIndex >= invConnections->size()) 1627 { 1628 callback(); 1629 BMCWEB_LOG_DEBUG << "getInventoryItemsData exit"; 1630 return; 1631 } 1632 1633 // Get inventory item data from current connection 1634 auto it = invConnections->nth(invConnectionsIndex); 1635 if (it != invConnections->end()) 1636 { 1637 const std::string& invConnection = *it; 1638 1639 // Response handler for GetManagedObjects 1640 auto respHandler = [sensorsAsyncResp, inventoryItems, invConnections, 1641 objectMgrPaths, 1642 callback{std::forward<Callback>(callback)}, 1643 invConnectionsIndex]( 1644 const boost::system::error_code ec, 1645 dbus::utility::ManagedObjectType& resp) { 1646 BMCWEB_LOG_DEBUG << "getInventoryItemsData respHandler enter"; 1647 if (ec) 1648 { 1649 BMCWEB_LOG_ERROR 1650 << "getInventoryItemsData respHandler DBus error " << ec; 1651 messages::internalError(sensorsAsyncResp->asyncResp->res); 1652 return; 1653 } 1654 1655 // Loop through returned object paths 1656 for (const auto& objDictEntry : resp) 1657 { 1658 const std::string& objPath = 1659 static_cast<const std::string&>(objDictEntry.first); 1660 1661 // If this object path is one of the specified inventory items 1662 InventoryItem* inventoryItem = 1663 findInventoryItem(inventoryItems, objPath); 1664 if (inventoryItem != nullptr) 1665 { 1666 // Store inventory data in InventoryItem 1667 storeInventoryItemData(*inventoryItem, objDictEntry.second); 1668 } 1669 } 1670 1671 // Recurse to get inventory item data from next connection 1672 getInventoryItemsData(sensorsAsyncResp, inventoryItems, 1673 invConnections, objectMgrPaths, 1674 std::move(callback), invConnectionsIndex + 1); 1675 1676 BMCWEB_LOG_DEBUG << "getInventoryItemsData respHandler exit"; 1677 }; 1678 1679 // Find DBus object path that implements ObjectManager for the current 1680 // connection. If no mapping found, default to "/". 1681 auto iter = objectMgrPaths->find(invConnection); 1682 const std::string& objectMgrPath = 1683 (iter != objectMgrPaths->end()) ? iter->second : "/"; 1684 BMCWEB_LOG_DEBUG << "ObjectManager path for " << invConnection << " is " 1685 << objectMgrPath; 1686 1687 // Get all object paths and their interfaces for current connection 1688 crow::connections::systemBus->async_method_call( 1689 std::move(respHandler), invConnection, objectMgrPath, 1690 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 1691 } 1692 1693 BMCWEB_LOG_DEBUG << "getInventoryItemsData exit"; 1694 } 1695 1696 /** 1697 * @brief Gets connections that provide D-Bus data for inventory items. 1698 * 1699 * Gets the D-Bus connections (services) that provide data for the inventory 1700 * items that are associated with sensors. 1701 * 1702 * Finds the connections asynchronously. Invokes callback when information has 1703 * been obtained. 1704 * 1705 * The callback must have the following signature: 1706 * @code 1707 * callback(std::shared_ptr<boost::container::flat_set<std::string>> 1708 * invConnections) 1709 * @endcode 1710 * 1711 * @param sensorsAsyncResp Pointer to object holding response data. 1712 * @param inventoryItems D-Bus inventory items associated with sensors. 1713 * @param callback Callback to invoke when connections have been obtained. 1714 */ 1715 template <typename Callback> 1716 static void getInventoryItemsConnections( 1717 const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp, 1718 const std::shared_ptr<std::vector<InventoryItem>>& inventoryItems, 1719 Callback&& callback) 1720 { 1721 BMCWEB_LOG_DEBUG << "getInventoryItemsConnections enter"; 1722 1723 const std::string path = "/xyz/openbmc_project/inventory"; 1724 const std::array<std::string, 4> interfaces = { 1725 "xyz.openbmc_project.Inventory.Item", 1726 "xyz.openbmc_project.Inventory.Item.PowerSupply", 1727 "xyz.openbmc_project.Inventory.Decorator.Asset", 1728 "xyz.openbmc_project.State.Decorator.OperationalStatus"}; 1729 1730 // Response handler for parsing output from GetSubTree 1731 auto respHandler = [callback{std::forward<Callback>(callback)}, 1732 sensorsAsyncResp, inventoryItems]( 1733 const boost::system::error_code ec, 1734 const dbus::utility::MapperGetSubTreeResponse& 1735 subtree) { 1736 BMCWEB_LOG_DEBUG << "getInventoryItemsConnections respHandler enter"; 1737 if (ec) 1738 { 1739 messages::internalError(sensorsAsyncResp->asyncResp->res); 1740 BMCWEB_LOG_ERROR 1741 << "getInventoryItemsConnections respHandler DBus error " << ec; 1742 return; 1743 } 1744 1745 // Make unique list of connections for desired inventory items 1746 std::shared_ptr<boost::container::flat_set<std::string>> 1747 invConnections = 1748 std::make_shared<boost::container::flat_set<std::string>>(); 1749 invConnections->reserve(8); 1750 1751 // Loop through objects from GetSubTree 1752 for (const std::pair< 1753 std::string, 1754 std::vector<std::pair<std::string, std::vector<std::string>>>>& 1755 object : subtree) 1756 { 1757 // Check if object path is one of the specified inventory items 1758 const std::string& objPath = object.first; 1759 if (findInventoryItem(inventoryItems, objPath) != nullptr) 1760 { 1761 // Store all connections to inventory item 1762 for (const std::pair<std::string, std::vector<std::string>>& 1763 objData : object.second) 1764 { 1765 const std::string& invConnection = objData.first; 1766 invConnections->insert(invConnection); 1767 } 1768 } 1769 } 1770 1771 callback(invConnections); 1772 BMCWEB_LOG_DEBUG << "getInventoryItemsConnections respHandler exit"; 1773 }; 1774 1775 // Make call to ObjectMapper to find all inventory items 1776 crow::connections::systemBus->async_method_call( 1777 std::move(respHandler), "xyz.openbmc_project.ObjectMapper", 1778 "/xyz/openbmc_project/object_mapper", 1779 "xyz.openbmc_project.ObjectMapper", "GetSubTree", path, 0, interfaces); 1780 BMCWEB_LOG_DEBUG << "getInventoryItemsConnections exit"; 1781 } 1782 1783 /** 1784 * @brief Gets associations from sensors to inventory items. 1785 * 1786 * Looks for ObjectMapper associations from the specified sensors to related 1787 * inventory items. Then finds the associations from those inventory items to 1788 * their LEDs, if any. 1789 * 1790 * Finds the inventory items asynchronously. Invokes callback when information 1791 * has been obtained. 1792 * 1793 * The callback must have the following signature: 1794 * @code 1795 * callback(std::shared_ptr<std::vector<InventoryItem>> inventoryItems) 1796 * @endcode 1797 * 1798 * @param sensorsAsyncResp Pointer to object holding response data. 1799 * @param sensorNames All sensors within the current chassis. 1800 * @param objectMgrPaths Mappings from connection name to DBus object path that 1801 * implements ObjectManager. 1802 * @param callback Callback to invoke when inventory items have been obtained. 1803 */ 1804 template <typename Callback> 1805 static void getInventoryItemAssociations( 1806 const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp, 1807 const std::shared_ptr<boost::container::flat_set<std::string>>& sensorNames, 1808 const std::shared_ptr<boost::container::flat_map<std::string, std::string>>& 1809 objectMgrPaths, 1810 Callback&& callback) 1811 { 1812 BMCWEB_LOG_DEBUG << "getInventoryItemAssociations enter"; 1813 1814 // Response handler for GetManagedObjects 1815 auto respHandler = [callback{std::forward<Callback>(callback)}, 1816 sensorsAsyncResp, 1817 sensorNames](const boost::system::error_code ec, 1818 dbus::utility::ManagedObjectType& resp) { 1819 BMCWEB_LOG_DEBUG << "getInventoryItemAssociations respHandler enter"; 1820 if (ec) 1821 { 1822 BMCWEB_LOG_ERROR 1823 << "getInventoryItemAssociations respHandler DBus error " << ec; 1824 messages::internalError(sensorsAsyncResp->asyncResp->res); 1825 return; 1826 } 1827 1828 // Create vector to hold list of inventory items 1829 std::shared_ptr<std::vector<InventoryItem>> inventoryItems = 1830 std::make_shared<std::vector<InventoryItem>>(); 1831 1832 // Loop through returned object paths 1833 std::string sensorAssocPath; 1834 sensorAssocPath.reserve(128); // avoid memory allocations 1835 for (const auto& objDictEntry : resp) 1836 { 1837 const std::string& objPath = 1838 static_cast<const std::string&>(objDictEntry.first); 1839 1840 // If path is inventory association for one of the specified sensors 1841 for (const std::string& sensorName : *sensorNames) 1842 { 1843 sensorAssocPath = sensorName; 1844 sensorAssocPath += "/inventory"; 1845 if (objPath == sensorAssocPath) 1846 { 1847 // Get Association interface for object path 1848 for (const auto& [interface, values] : objDictEntry.second) 1849 { 1850 if (interface == "xyz.openbmc_project.Association") 1851 { 1852 for (const auto& [valueName, value] : values) 1853 { 1854 if (valueName == "endpoints") 1855 { 1856 const std::vector<std::string>* endpoints = 1857 std::get_if<std::vector<std::string>>( 1858 &value); 1859 if ((endpoints != nullptr) && 1860 !endpoints->empty()) 1861 { 1862 // Add inventory item to vector 1863 const std::string& invItemPath = 1864 endpoints->front(); 1865 addInventoryItem(inventoryItems, 1866 invItemPath, 1867 sensorName); 1868 } 1869 } 1870 } 1871 } 1872 } 1873 break; 1874 } 1875 } 1876 } 1877 1878 // Now loop through the returned object paths again, this time to 1879 // find the leds associated with the inventory items we just found 1880 std::string inventoryAssocPath; 1881 inventoryAssocPath.reserve(128); // avoid memory allocations 1882 for (const auto& objDictEntry : resp) 1883 { 1884 const std::string& objPath = 1885 static_cast<const std::string&>(objDictEntry.first); 1886 1887 for (InventoryItem& inventoryItem : *inventoryItems) 1888 { 1889 inventoryAssocPath = inventoryItem.objectPath; 1890 inventoryAssocPath += "/leds"; 1891 if (objPath == inventoryAssocPath) 1892 { 1893 for (const auto& [interface, values] : objDictEntry.second) 1894 { 1895 if (interface == "xyz.openbmc_project.Association") 1896 { 1897 for (const auto& [valueName, value] : values) 1898 { 1899 if (valueName == "endpoints") 1900 { 1901 const std::vector<std::string>* endpoints = 1902 std::get_if<std::vector<std::string>>( 1903 &value); 1904 if ((endpoints != nullptr) && 1905 !endpoints->empty()) 1906 { 1907 // Add inventory item to vector 1908 // Store LED path in inventory item 1909 const std::string& ledPath = 1910 endpoints->front(); 1911 inventoryItem.ledObjectPath = ledPath; 1912 } 1913 } 1914 } 1915 } 1916 } 1917 1918 break; 1919 } 1920 } 1921 } 1922 callback(inventoryItems); 1923 BMCWEB_LOG_DEBUG << "getInventoryItemAssociations respHandler exit"; 1924 }; 1925 1926 // Find DBus object path that implements ObjectManager for ObjectMapper 1927 std::string connection = "xyz.openbmc_project.ObjectMapper"; 1928 auto iter = objectMgrPaths->find(connection); 1929 const std::string& objectMgrPath = 1930 (iter != objectMgrPaths->end()) ? iter->second : "/"; 1931 BMCWEB_LOG_DEBUG << "ObjectManager path for " << connection << " is " 1932 << objectMgrPath; 1933 1934 // Call GetManagedObjects on the ObjectMapper to get all associations 1935 crow::connections::systemBus->async_method_call( 1936 std::move(respHandler), connection, objectMgrPath, 1937 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 1938 1939 BMCWEB_LOG_DEBUG << "getInventoryItemAssociations exit"; 1940 } 1941 1942 /** 1943 * @brief Gets D-Bus data for inventory item leds associated with sensors. 1944 * 1945 * Uses the specified connections (services) to obtain D-Bus data for inventory 1946 * item leds associated with sensors. Stores the resulting data in the 1947 * inventoryItems vector. 1948 * 1949 * This data is later used to provide sensor property values in the JSON 1950 * response. 1951 * 1952 * Finds the inventory item led data asynchronously. Invokes callback when data 1953 * has been obtained. 1954 * 1955 * The callback must have the following signature: 1956 * @code 1957 * callback() 1958 * @endcode 1959 * 1960 * This function is called recursively, obtaining data asynchronously from one 1961 * connection in each call. This ensures the callback is not invoked until the 1962 * last asynchronous function has completed. 1963 * 1964 * @param sensorsAsyncResp Pointer to object holding response data. 1965 * @param inventoryItems D-Bus inventory items associated with sensors. 1966 * @param ledConnections Connections that provide data for the inventory leds. 1967 * @param callback Callback to invoke when inventory data has been obtained. 1968 * @param ledConnectionsIndex Current index in ledConnections. Only specified 1969 * in recursive calls to this function. 1970 */ 1971 template <typename Callback> 1972 void getInventoryLedData( 1973 std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp, 1974 std::shared_ptr<std::vector<InventoryItem>> inventoryItems, 1975 std::shared_ptr<boost::container::flat_map<std::string, std::string>> 1976 ledConnections, 1977 Callback&& callback, size_t ledConnectionsIndex = 0) 1978 { 1979 BMCWEB_LOG_DEBUG << "getInventoryLedData enter"; 1980 1981 // If no more connections left, call callback 1982 if (ledConnectionsIndex >= ledConnections->size()) 1983 { 1984 callback(); 1985 BMCWEB_LOG_DEBUG << "getInventoryLedData exit"; 1986 return; 1987 } 1988 1989 // Get inventory item data from current connection 1990 auto it = ledConnections->nth(ledConnectionsIndex); 1991 if (it != ledConnections->end()) 1992 { 1993 const std::string& ledPath = (*it).first; 1994 const std::string& ledConnection = (*it).second; 1995 // Response handler for Get State property 1996 auto respHandler = 1997 [sensorsAsyncResp, inventoryItems, ledConnections, ledPath, 1998 callback{std::forward<Callback>(callback)}, ledConnectionsIndex]( 1999 const boost::system::error_code ec, const std::string& state) { 2000 BMCWEB_LOG_DEBUG << "getInventoryLedData respHandler enter"; 2001 if (ec) 2002 { 2003 BMCWEB_LOG_ERROR 2004 << "getInventoryLedData respHandler DBus error " << ec; 2005 messages::internalError(sensorsAsyncResp->asyncResp->res); 2006 return; 2007 } 2008 2009 BMCWEB_LOG_DEBUG << "Led state: " << state; 2010 // Find inventory item with this LED object path 2011 InventoryItem* inventoryItem = 2012 findInventoryItemForLed(*inventoryItems, ledPath); 2013 if (inventoryItem != nullptr) 2014 { 2015 // Store LED state in InventoryItem 2016 if (boost::ends_with(state, "On")) 2017 { 2018 inventoryItem->ledState = LedState::ON; 2019 } 2020 else if (boost::ends_with(state, "Blink")) 2021 { 2022 inventoryItem->ledState = LedState::BLINK; 2023 } 2024 else if (boost::ends_with(state, "Off")) 2025 { 2026 inventoryItem->ledState = LedState::OFF; 2027 } 2028 else 2029 { 2030 inventoryItem->ledState = LedState::UNKNOWN; 2031 } 2032 } 2033 2034 // Recurse to get LED data from next connection 2035 getInventoryLedData(sensorsAsyncResp, inventoryItems, 2036 ledConnections, std::move(callback), 2037 ledConnectionsIndex + 1); 2038 2039 BMCWEB_LOG_DEBUG << "getInventoryLedData respHandler exit"; 2040 }; 2041 2042 // Get the State property for the current LED 2043 sdbusplus::asio::getProperty<std::string>( 2044 *crow::connections::systemBus, ledConnection, ledPath, 2045 "xyz.openbmc_project.Led.Physical", "State", 2046 std::move(respHandler)); 2047 } 2048 2049 BMCWEB_LOG_DEBUG << "getInventoryLedData exit"; 2050 } 2051 2052 /** 2053 * @brief Gets LED data for LEDs associated with given inventory items. 2054 * 2055 * Gets the D-Bus connections (services) that provide LED data for the LEDs 2056 * associated with the specified inventory items. Then gets the LED data from 2057 * each connection and stores it in the inventory item. 2058 * 2059 * This data is later used to provide sensor property values in the JSON 2060 * response. 2061 * 2062 * Finds the LED data asynchronously. Invokes callback when information has 2063 * been obtained. 2064 * 2065 * The callback must have the following signature: 2066 * @code 2067 * callback() 2068 * @endcode 2069 * 2070 * @param sensorsAsyncResp Pointer to object holding response data. 2071 * @param inventoryItems D-Bus inventory items associated with sensors. 2072 * @param callback Callback to invoke when inventory items have been obtained. 2073 */ 2074 template <typename Callback> 2075 void getInventoryLeds( 2076 std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp, 2077 std::shared_ptr<std::vector<InventoryItem>> inventoryItems, 2078 Callback&& callback) 2079 { 2080 BMCWEB_LOG_DEBUG << "getInventoryLeds enter"; 2081 2082 const std::string path = "/xyz/openbmc_project"; 2083 const std::array<std::string, 1> interfaces = { 2084 "xyz.openbmc_project.Led.Physical"}; 2085 2086 // Response handler for parsing output from GetSubTree 2087 auto respHandler = [callback{std::forward<Callback>(callback)}, 2088 sensorsAsyncResp, inventoryItems]( 2089 const boost::system::error_code ec, 2090 const dbus::utility::MapperGetSubTreeResponse& 2091 subtree) { 2092 BMCWEB_LOG_DEBUG << "getInventoryLeds respHandler enter"; 2093 if (ec) 2094 { 2095 messages::internalError(sensorsAsyncResp->asyncResp->res); 2096 BMCWEB_LOG_ERROR << "getInventoryLeds respHandler DBus error " 2097 << ec; 2098 return; 2099 } 2100 2101 // Build map of LED object paths to connections 2102 std::shared_ptr<boost::container::flat_map<std::string, std::string>> 2103 ledConnections = std::make_shared< 2104 boost::container::flat_map<std::string, std::string>>(); 2105 2106 // Loop through objects from GetSubTree 2107 for (const std::pair< 2108 std::string, 2109 std::vector<std::pair<std::string, std::vector<std::string>>>>& 2110 object : subtree) 2111 { 2112 // Check if object path is LED for one of the specified inventory 2113 // items 2114 const std::string& ledPath = object.first; 2115 if (findInventoryItemForLed(*inventoryItems, ledPath) != nullptr) 2116 { 2117 // Add mapping from ledPath to connection 2118 const std::string& connection = object.second.begin()->first; 2119 (*ledConnections)[ledPath] = connection; 2120 BMCWEB_LOG_DEBUG << "Added mapping " << ledPath << " -> " 2121 << connection; 2122 } 2123 } 2124 2125 getInventoryLedData(sensorsAsyncResp, inventoryItems, ledConnections, 2126 std::move(callback)); 2127 BMCWEB_LOG_DEBUG << "getInventoryLeds respHandler exit"; 2128 }; 2129 // Make call to ObjectMapper to find all inventory items 2130 crow::connections::systemBus->async_method_call( 2131 std::move(respHandler), "xyz.openbmc_project.ObjectMapper", 2132 "/xyz/openbmc_project/object_mapper", 2133 "xyz.openbmc_project.ObjectMapper", "GetSubTree", path, 0, interfaces); 2134 BMCWEB_LOG_DEBUG << "getInventoryLeds exit"; 2135 } 2136 2137 /** 2138 * @brief Gets D-Bus data for Power Supply Attributes such as EfficiencyPercent 2139 * 2140 * Uses the specified connections (services) (currently assumes just one) to 2141 * obtain D-Bus data for Power Supply Attributes. Stores the resulting data in 2142 * the inventoryItems vector. Only stores data in Power Supply inventoryItems. 2143 * 2144 * This data is later used to provide sensor property values in the JSON 2145 * response. 2146 * 2147 * Finds the Power Supply Attributes data asynchronously. Invokes callback 2148 * when data has been obtained. 2149 * 2150 * The callback must have the following signature: 2151 * @code 2152 * callback(std::shared_ptr<std::vector<InventoryItem>> inventoryItems) 2153 * @endcode 2154 * 2155 * @param sensorsAsyncResp Pointer to object holding response data. 2156 * @param inventoryItems D-Bus inventory items associated with sensors. 2157 * @param psAttributesConnections Connections that provide data for the Power 2158 * Supply Attributes 2159 * @param callback Callback to invoke when data has been obtained. 2160 */ 2161 template <typename Callback> 2162 void getPowerSupplyAttributesData( 2163 const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp, 2164 std::shared_ptr<std::vector<InventoryItem>> inventoryItems, 2165 const boost::container::flat_map<std::string, std::string>& 2166 psAttributesConnections, 2167 Callback&& callback) 2168 { 2169 BMCWEB_LOG_DEBUG << "getPowerSupplyAttributesData enter"; 2170 2171 if (psAttributesConnections.empty()) 2172 { 2173 BMCWEB_LOG_DEBUG << "Can't find PowerSupplyAttributes, no connections!"; 2174 callback(inventoryItems); 2175 return; 2176 } 2177 2178 // Assuming just one connection (service) for now 2179 auto it = psAttributesConnections.nth(0); 2180 2181 const std::string& psAttributesPath = (*it).first; 2182 const std::string& psAttributesConnection = (*it).second; 2183 2184 // Response handler for Get DeratingFactor property 2185 auto respHandler = [sensorsAsyncResp, inventoryItems, 2186 callback{std::forward<Callback>(callback)}]( 2187 const boost::system::error_code ec, 2188 const uint32_t value) { 2189 BMCWEB_LOG_DEBUG << "getPowerSupplyAttributesData respHandler enter"; 2190 if (ec) 2191 { 2192 BMCWEB_LOG_ERROR 2193 << "getPowerSupplyAttributesData respHandler DBus error " << ec; 2194 messages::internalError(sensorsAsyncResp->asyncResp->res); 2195 return; 2196 } 2197 2198 BMCWEB_LOG_DEBUG << "PS EfficiencyPercent value: " << value; 2199 // Store value in Power Supply Inventory Items 2200 for (InventoryItem& inventoryItem : *inventoryItems) 2201 { 2202 if (inventoryItem.isPowerSupply) 2203 { 2204 inventoryItem.powerSupplyEfficiencyPercent = 2205 static_cast<int>(value); 2206 } 2207 } 2208 2209 BMCWEB_LOG_DEBUG << "getPowerSupplyAttributesData respHandler exit"; 2210 callback(inventoryItems); 2211 }; 2212 2213 // Get the DeratingFactor property for the PowerSupplyAttributes 2214 // Currently only property on the interface/only one we care about 2215 sdbusplus::asio::getProperty<uint32_t>( 2216 *crow::connections::systemBus, psAttributesConnection, psAttributesPath, 2217 "xyz.openbmc_project.Control.PowerSupplyAttributes", "DeratingFactor", 2218 std::move(respHandler)); 2219 2220 BMCWEB_LOG_DEBUG << "getPowerSupplyAttributesData exit"; 2221 } 2222 2223 /** 2224 * @brief Gets the Power Supply Attributes such as EfficiencyPercent 2225 * 2226 * Gets the D-Bus connection (service) that provides Power Supply Attributes 2227 * data. Then gets the Power Supply Attributes data from the connection 2228 * (currently just assumes 1 connection) and stores the data in the inventory 2229 * item. 2230 * 2231 * This data is later used to provide sensor property values in the JSON 2232 * response. DeratingFactor on D-Bus is mapped to EfficiencyPercent on Redfish. 2233 * 2234 * Finds the Power Supply Attributes data asynchronously. Invokes callback 2235 * when information has been obtained. 2236 * 2237 * The callback must have the following signature: 2238 * @code 2239 * callback(std::shared_ptr<std::vector<InventoryItem>> inventoryItems) 2240 * @endcode 2241 * 2242 * @param sensorsAsyncResp Pointer to object holding response data. 2243 * @param inventoryItems D-Bus inventory items associated with sensors. 2244 * @param callback Callback to invoke when data has been obtained. 2245 */ 2246 template <typename Callback> 2247 void getPowerSupplyAttributes( 2248 std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp, 2249 std::shared_ptr<std::vector<InventoryItem>> inventoryItems, 2250 Callback&& callback) 2251 { 2252 BMCWEB_LOG_DEBUG << "getPowerSupplyAttributes enter"; 2253 2254 // Only need the power supply attributes when the Power Schema 2255 if (sensorsAsyncResp->chassisSubNode != sensors::node::power) 2256 { 2257 BMCWEB_LOG_DEBUG << "getPowerSupplyAttributes exit since not Power"; 2258 callback(inventoryItems); 2259 return; 2260 } 2261 2262 const std::array<std::string, 1> interfaces = { 2263 "xyz.openbmc_project.Control.PowerSupplyAttributes"}; 2264 2265 // Response handler for parsing output from GetSubTree 2266 auto respHandler = 2267 [callback{std::forward<Callback>(callback)}, sensorsAsyncResp, 2268 inventoryItems]( 2269 const boost::system::error_code ec, 2270 const dbus::utility::MapperGetSubTreeResponse& subtree) { 2271 BMCWEB_LOG_DEBUG << "getPowerSupplyAttributes respHandler enter"; 2272 if (ec) 2273 { 2274 messages::internalError(sensorsAsyncResp->asyncResp->res); 2275 BMCWEB_LOG_ERROR 2276 << "getPowerSupplyAttributes respHandler DBus error " << ec; 2277 return; 2278 } 2279 if (subtree.empty()) 2280 { 2281 BMCWEB_LOG_DEBUG << "Can't find Power Supply Attributes!"; 2282 callback(inventoryItems); 2283 return; 2284 } 2285 2286 // Currently we only support 1 power supply attribute, use this for 2287 // all the power supplies. Build map of object path to connection. 2288 // Assume just 1 connection and 1 path for now. 2289 boost::container::flat_map<std::string, std::string> 2290 psAttributesConnections; 2291 2292 if (subtree[0].first.empty() || subtree[0].second.empty()) 2293 { 2294 BMCWEB_LOG_DEBUG << "Power Supply Attributes mapper error!"; 2295 callback(inventoryItems); 2296 return; 2297 } 2298 2299 const std::string& psAttributesPath = subtree[0].first; 2300 const std::string& connection = subtree[0].second.begin()->first; 2301 2302 if (connection.empty()) 2303 { 2304 BMCWEB_LOG_DEBUG << "Power Supply Attributes mapper error!"; 2305 callback(inventoryItems); 2306 return; 2307 } 2308 2309 psAttributesConnections[psAttributesPath] = connection; 2310 BMCWEB_LOG_DEBUG << "Added mapping " << psAttributesPath << " -> " 2311 << connection; 2312 2313 getPowerSupplyAttributesData(sensorsAsyncResp, inventoryItems, 2314 psAttributesConnections, 2315 std::move(callback)); 2316 BMCWEB_LOG_DEBUG << "getPowerSupplyAttributes respHandler exit"; 2317 }; 2318 // Make call to ObjectMapper to find the PowerSupplyAttributes service 2319 crow::connections::systemBus->async_method_call( 2320 std::move(respHandler), "xyz.openbmc_project.ObjectMapper", 2321 "/xyz/openbmc_project/object_mapper", 2322 "xyz.openbmc_project.ObjectMapper", "GetSubTree", 2323 "/xyz/openbmc_project", 0, interfaces); 2324 BMCWEB_LOG_DEBUG << "getPowerSupplyAttributes exit"; 2325 } 2326 2327 /** 2328 * @brief Gets inventory items associated with sensors. 2329 * 2330 * Finds the inventory items that are associated with the specified sensors. 2331 * Then gets D-Bus data for the inventory items, such as presence and VPD. 2332 * 2333 * This data is later used to provide sensor property values in the JSON 2334 * response. 2335 * 2336 * Finds the inventory items asynchronously. Invokes callback when the 2337 * inventory items have been obtained. 2338 * 2339 * The callback must have the following signature: 2340 * @code 2341 * callback(std::shared_ptr<std::vector<InventoryItem>> inventoryItems) 2342 * @endcode 2343 * 2344 * @param sensorsAsyncResp Pointer to object holding response data. 2345 * @param sensorNames All sensors within the current chassis. 2346 * @param objectMgrPaths Mappings from connection name to DBus object path that 2347 * implements ObjectManager. 2348 * @param callback Callback to invoke when inventory items have been obtained. 2349 */ 2350 template <typename Callback> 2351 static void getInventoryItems( 2352 std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp, 2353 const std::shared_ptr<boost::container::flat_set<std::string>> sensorNames, 2354 std::shared_ptr<boost::container::flat_map<std::string, std::string>> 2355 objectMgrPaths, 2356 Callback&& callback) 2357 { 2358 BMCWEB_LOG_DEBUG << "getInventoryItems enter"; 2359 auto getInventoryItemAssociationsCb = 2360 [sensorsAsyncResp, objectMgrPaths, 2361 callback{std::forward<Callback>(callback)}]( 2362 std::shared_ptr<std::vector<InventoryItem>> inventoryItems) { 2363 BMCWEB_LOG_DEBUG << "getInventoryItemAssociationsCb enter"; 2364 auto getInventoryItemsConnectionsCb = 2365 [sensorsAsyncResp, inventoryItems, objectMgrPaths, 2366 callback{std::forward<const Callback>(callback)}]( 2367 std::shared_ptr<boost::container::flat_set<std::string>> 2368 invConnections) { 2369 BMCWEB_LOG_DEBUG << "getInventoryItemsConnectionsCb enter"; 2370 auto getInventoryItemsDataCb = 2371 [sensorsAsyncResp, inventoryItems, 2372 callback{std::move(callback)}]() { 2373 BMCWEB_LOG_DEBUG << "getInventoryItemsDataCb enter"; 2374 2375 auto getInventoryLedsCb = [sensorsAsyncResp, 2376 inventoryItems, 2377 callback{std::move( 2378 callback)}]() { 2379 BMCWEB_LOG_DEBUG << "getInventoryLedsCb enter"; 2380 // Find Power Supply Attributes and get the data 2381 getPowerSupplyAttributes(sensorsAsyncResp, 2382 inventoryItems, 2383 std::move(callback)); 2384 BMCWEB_LOG_DEBUG << "getInventoryLedsCb exit"; 2385 }; 2386 2387 // Find led connections and get the data 2388 getInventoryLeds(sensorsAsyncResp, inventoryItems, 2389 std::move(getInventoryLedsCb)); 2390 BMCWEB_LOG_DEBUG << "getInventoryItemsDataCb exit"; 2391 }; 2392 2393 // Get inventory item data from connections 2394 getInventoryItemsData(sensorsAsyncResp, inventoryItems, 2395 invConnections, objectMgrPaths, 2396 std::move(getInventoryItemsDataCb)); 2397 BMCWEB_LOG_DEBUG << "getInventoryItemsConnectionsCb exit"; 2398 }; 2399 2400 // Get connections that provide inventory item data 2401 getInventoryItemsConnections( 2402 sensorsAsyncResp, inventoryItems, 2403 std::move(getInventoryItemsConnectionsCb)); 2404 BMCWEB_LOG_DEBUG << "getInventoryItemAssociationsCb exit"; 2405 }; 2406 2407 // Get associations from sensors to inventory items 2408 getInventoryItemAssociations(sensorsAsyncResp, sensorNames, objectMgrPaths, 2409 std::move(getInventoryItemAssociationsCb)); 2410 BMCWEB_LOG_DEBUG << "getInventoryItems exit"; 2411 } 2412 2413 /** 2414 * @brief Returns JSON PowerSupply object for the specified inventory item. 2415 * 2416 * Searches for a JSON PowerSupply object that matches the specified inventory 2417 * item. If one is not found, a new PowerSupply object is added to the JSON 2418 * array. 2419 * 2420 * Multiple sensors are often associated with one power supply inventory item. 2421 * As a result, multiple sensor values are stored in one JSON PowerSupply 2422 * object. 2423 * 2424 * @param powerSupplyArray JSON array containing Redfish PowerSupply objects. 2425 * @param inventoryItem Inventory item for the power supply. 2426 * @param chassisId Chassis that contains the power supply. 2427 * @return JSON PowerSupply object for the specified inventory item. 2428 */ 2429 inline nlohmann::json& getPowerSupply(nlohmann::json& powerSupplyArray, 2430 const InventoryItem& inventoryItem, 2431 const std::string& chassisId) 2432 { 2433 // Check if matching PowerSupply object already exists in JSON array 2434 for (nlohmann::json& powerSupply : powerSupplyArray) 2435 { 2436 if (powerSupply["MemberId"] == inventoryItem.name) 2437 { 2438 return powerSupply; 2439 } 2440 } 2441 2442 // Add new PowerSupply object to JSON array 2443 powerSupplyArray.push_back({}); 2444 nlohmann::json& powerSupply = powerSupplyArray.back(); 2445 powerSupply["@odata.id"] = 2446 "/redfish/v1/Chassis/" + chassisId + "/Power#/PowerSupplies/"; 2447 powerSupply["MemberId"] = inventoryItem.name; 2448 powerSupply["Name"] = boost::replace_all_copy(inventoryItem.name, "_", " "); 2449 powerSupply["Manufacturer"] = inventoryItem.manufacturer; 2450 powerSupply["Model"] = inventoryItem.model; 2451 powerSupply["PartNumber"] = inventoryItem.partNumber; 2452 powerSupply["SerialNumber"] = inventoryItem.serialNumber; 2453 setLedState(powerSupply, &inventoryItem); 2454 2455 if (inventoryItem.powerSupplyEfficiencyPercent >= 0) 2456 { 2457 powerSupply["EfficiencyPercent"] = 2458 inventoryItem.powerSupplyEfficiencyPercent; 2459 } 2460 2461 powerSupply["Status"]["State"] = getState(&inventoryItem); 2462 const char* health = inventoryItem.isFunctional ? "OK" : "Critical"; 2463 powerSupply["Status"]["Health"] = health; 2464 2465 return powerSupply; 2466 } 2467 2468 /** 2469 * @brief Gets the values of the specified sensors. 2470 * 2471 * Stores the results as JSON in the SensorsAsyncResp. 2472 * 2473 * Gets the sensor values asynchronously. Stores the results later when the 2474 * information has been obtained. 2475 * 2476 * The sensorNames set contains all requested sensors for the current chassis. 2477 * 2478 * To minimize the number of DBus calls, the DBus method 2479 * org.freedesktop.DBus.ObjectManager.GetManagedObjects() is used to get the 2480 * values of all sensors provided by a connection (service). 2481 * 2482 * The connections set contains all the connections that provide sensor values. 2483 * 2484 * The objectMgrPaths map contains mappings from a connection name to the 2485 * corresponding DBus object path that implements ObjectManager. 2486 * 2487 * The InventoryItem vector contains D-Bus inventory items associated with the 2488 * sensors. Inventory item data is needed for some Redfish sensor properties. 2489 * 2490 * @param SensorsAsyncResp Pointer to object holding response data. 2491 * @param sensorNames All requested sensors within the current chassis. 2492 * @param connections Connections that provide sensor values. 2493 * @param objectMgrPaths Mappings from connection name to DBus object path that 2494 * implements ObjectManager. 2495 * @param inventoryItems Inventory items associated with the sensors. 2496 */ 2497 inline void getSensorData( 2498 const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp, 2499 const std::shared_ptr<boost::container::flat_set<std::string>>& sensorNames, 2500 const boost::container::flat_set<std::string>& connections, 2501 const std::shared_ptr<boost::container::flat_map<std::string, std::string>>& 2502 objectMgrPaths, 2503 const std::shared_ptr<std::vector<InventoryItem>>& inventoryItems) 2504 { 2505 BMCWEB_LOG_DEBUG << "getSensorData enter"; 2506 // Get managed objects from all services exposing sensors 2507 for (const std::string& connection : connections) 2508 { 2509 // Response handler to process managed objects 2510 auto getManagedObjectsCb = [sensorsAsyncResp, sensorNames, 2511 inventoryItems]( 2512 const boost::system::error_code ec, 2513 dbus::utility::ManagedObjectType& resp) { 2514 BMCWEB_LOG_DEBUG << "getManagedObjectsCb enter"; 2515 if (ec) 2516 { 2517 BMCWEB_LOG_ERROR << "getManagedObjectsCb DBUS error: " << ec; 2518 messages::internalError(sensorsAsyncResp->asyncResp->res); 2519 return; 2520 } 2521 // Go through all objects and update response with sensor data 2522 for (const auto& objDictEntry : resp) 2523 { 2524 const std::string& objPath = 2525 static_cast<const std::string&>(objDictEntry.first); 2526 BMCWEB_LOG_DEBUG << "getManagedObjectsCb parsing object " 2527 << objPath; 2528 2529 std::vector<std::string> split; 2530 // Reserve space for 2531 // /xyz/openbmc_project/sensors/<name>/<subname> 2532 split.reserve(6); 2533 boost::algorithm::split(split, objPath, boost::is_any_of("/")); 2534 if (split.size() < 6) 2535 { 2536 BMCWEB_LOG_ERROR << "Got path that isn't long enough " 2537 << objPath; 2538 continue; 2539 } 2540 // These indexes aren't intuitive, as boost::split puts an empty 2541 // string at the beginning 2542 const std::string& sensorType = split[4]; 2543 const std::string& sensorName = split[5]; 2544 BMCWEB_LOG_DEBUG << "sensorName " << sensorName 2545 << " sensorType " << sensorType; 2546 if (sensorNames->find(objPath) == sensorNames->end()) 2547 { 2548 BMCWEB_LOG_DEBUG << sensorName << " not in sensor list "; 2549 continue; 2550 } 2551 2552 // Find inventory item (if any) associated with sensor 2553 InventoryItem* inventoryItem = 2554 findInventoryItemForSensor(inventoryItems, objPath); 2555 2556 const std::string& sensorSchema = 2557 sensorsAsyncResp->chassisSubNode; 2558 2559 nlohmann::json* sensorJson = nullptr; 2560 2561 if (sensorSchema == sensors::node::sensors && 2562 !sensorsAsyncResp->efficientExpand) 2563 { 2564 sensorsAsyncResp->asyncResp->res.jsonValue["@odata.id"] = 2565 "/redfish/v1/Chassis/" + sensorsAsyncResp->chassisId + 2566 "/" + sensorsAsyncResp->chassisSubNode + "/" + 2567 sensorName; 2568 sensorJson = &(sensorsAsyncResp->asyncResp->res.jsonValue); 2569 } 2570 else 2571 { 2572 std::string fieldName; 2573 if (sensorsAsyncResp->efficientExpand) 2574 { 2575 fieldName = "Members"; 2576 } 2577 else if (sensorType == "temperature") 2578 { 2579 fieldName = "Temperatures"; 2580 } 2581 else if (sensorType == "fan" || sensorType == "fan_tach" || 2582 sensorType == "fan_pwm") 2583 { 2584 fieldName = "Fans"; 2585 } 2586 else if (sensorType == "voltage") 2587 { 2588 fieldName = "Voltages"; 2589 } 2590 else if (sensorType == "power") 2591 { 2592 if (sensorName == "total_power") 2593 { 2594 fieldName = "PowerControl"; 2595 } 2596 else if ((inventoryItem != nullptr) && 2597 (inventoryItem->isPowerSupply)) 2598 { 2599 fieldName = "PowerSupplies"; 2600 } 2601 else 2602 { 2603 // Other power sensors are in SensorCollection 2604 continue; 2605 } 2606 } 2607 else 2608 { 2609 BMCWEB_LOG_ERROR << "Unsure how to handle sensorType " 2610 << sensorType; 2611 continue; 2612 } 2613 2614 nlohmann::json& tempArray = 2615 sensorsAsyncResp->asyncResp->res.jsonValue[fieldName]; 2616 if (fieldName == "PowerControl") 2617 { 2618 if (tempArray.empty()) 2619 { 2620 // Put multiple "sensors" into a single 2621 // PowerControl. Follows MemberId naming and 2622 // naming in power.hpp. 2623 nlohmann::json::object_t power; 2624 power["@odata.id"] = 2625 "/redfish/v1/Chassis/" + 2626 sensorsAsyncResp->chassisId + "/" + 2627 sensorsAsyncResp->chassisSubNode + "#/" + 2628 fieldName + "/0"; 2629 tempArray.push_back(std::move(power)); 2630 } 2631 sensorJson = &(tempArray.back()); 2632 } 2633 else if (fieldName == "PowerSupplies") 2634 { 2635 if (inventoryItem != nullptr) 2636 { 2637 sensorJson = 2638 &(getPowerSupply(tempArray, *inventoryItem, 2639 sensorsAsyncResp->chassisId)); 2640 } 2641 } 2642 else if (fieldName == "Members") 2643 { 2644 nlohmann::json::object_t member; 2645 member["@odata.id"] = 2646 "/redfish/v1/Chassis/" + 2647 sensorsAsyncResp->chassisId + "/" + 2648 sensorsAsyncResp->chassisSubNode + "/" + sensorName; 2649 tempArray.push_back(std::move(member)); 2650 sensorJson = &(tempArray.back()); 2651 } 2652 else 2653 { 2654 nlohmann::json::object_t member; 2655 member["@odata.id"] = "/redfish/v1/Chassis/" + 2656 sensorsAsyncResp->chassisId + 2657 "/" + 2658 sensorsAsyncResp->chassisSubNode + 2659 "#/" + fieldName + "/"; 2660 tempArray.push_back(std::move(member)); 2661 sensorJson = &(tempArray.back()); 2662 } 2663 } 2664 2665 if (sensorJson != nullptr) 2666 { 2667 objectInterfacesToJson( 2668 sensorName, sensorType, sensorsAsyncResp, 2669 objDictEntry.second, *sensorJson, inventoryItem); 2670 } 2671 } 2672 if (sensorsAsyncResp.use_count() == 1) 2673 { 2674 sortJSONResponse(sensorsAsyncResp); 2675 if (sensorsAsyncResp->chassisSubNode == 2676 sensors::node::sensors && 2677 sensorsAsyncResp->efficientExpand) 2678 { 2679 sensorsAsyncResp->asyncResp->res 2680 .jsonValue["Members@odata.count"] = 2681 sensorsAsyncResp->asyncResp->res.jsonValue["Members"] 2682 .size(); 2683 } 2684 else if (sensorsAsyncResp->chassisSubNode == 2685 sensors::node::thermal) 2686 { 2687 populateFanRedundancy(sensorsAsyncResp); 2688 } 2689 } 2690 BMCWEB_LOG_DEBUG << "getManagedObjectsCb exit"; 2691 }; 2692 2693 // Find DBus object path that implements ObjectManager for the current 2694 // connection. If no mapping found, default to "/". 2695 auto iter = objectMgrPaths->find(connection); 2696 const std::string& objectMgrPath = 2697 (iter != objectMgrPaths->end()) ? iter->second : "/"; 2698 BMCWEB_LOG_DEBUG << "ObjectManager path for " << connection << " is " 2699 << objectMgrPath; 2700 2701 crow::connections::systemBus->async_method_call( 2702 getManagedObjectsCb, connection, objectMgrPath, 2703 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 2704 } 2705 BMCWEB_LOG_DEBUG << "getSensorData exit"; 2706 } 2707 2708 inline void processSensorList( 2709 const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp, 2710 const std::shared_ptr<boost::container::flat_set<std::string>>& sensorNames) 2711 { 2712 auto getConnectionCb = 2713 [sensorsAsyncResp, sensorNames]( 2714 const boost::container::flat_set<std::string>& connections) { 2715 BMCWEB_LOG_DEBUG << "getConnectionCb enter"; 2716 auto getObjectManagerPathsCb = 2717 [sensorsAsyncResp, sensorNames, 2718 connections](const std::shared_ptr<boost::container::flat_map< 2719 std::string, std::string>>& objectMgrPaths) { 2720 BMCWEB_LOG_DEBUG << "getObjectManagerPathsCb enter"; 2721 auto getInventoryItemsCb = 2722 [sensorsAsyncResp, sensorNames, connections, 2723 objectMgrPaths]( 2724 const std::shared_ptr<std::vector<InventoryItem>>& 2725 inventoryItems) { 2726 BMCWEB_LOG_DEBUG << "getInventoryItemsCb enter"; 2727 // Get sensor data and store results in JSON 2728 getSensorData(sensorsAsyncResp, sensorNames, 2729 connections, objectMgrPaths, 2730 inventoryItems); 2731 BMCWEB_LOG_DEBUG << "getInventoryItemsCb exit"; 2732 }; 2733 2734 // Get inventory items associated with sensors 2735 getInventoryItems(sensorsAsyncResp, sensorNames, 2736 objectMgrPaths, 2737 std::move(getInventoryItemsCb)); 2738 2739 BMCWEB_LOG_DEBUG << "getObjectManagerPathsCb exit"; 2740 }; 2741 2742 // Get mapping from connection names to the DBus object 2743 // paths that implement the ObjectManager interface 2744 getObjectManagerPaths(sensorsAsyncResp, 2745 std::move(getObjectManagerPathsCb)); 2746 BMCWEB_LOG_DEBUG << "getConnectionCb exit"; 2747 }; 2748 2749 // Get set of connections that provide sensor values 2750 getConnections(sensorsAsyncResp, sensorNames, std::move(getConnectionCb)); 2751 } 2752 2753 /** 2754 * @brief Entry point for retrieving sensors data related to requested 2755 * chassis. 2756 * @param SensorsAsyncResp Pointer to object holding response data 2757 */ 2758 inline void 2759 getChassisData(const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp) 2760 { 2761 BMCWEB_LOG_DEBUG << "getChassisData enter"; 2762 auto getChassisCb = 2763 [sensorsAsyncResp]( 2764 const std::shared_ptr<boost::container::flat_set<std::string>>& 2765 sensorNames) { 2766 BMCWEB_LOG_DEBUG << "getChassisCb enter"; 2767 processSensorList(sensorsAsyncResp, sensorNames); 2768 BMCWEB_LOG_DEBUG << "getChassisCb exit"; 2769 }; 2770 // SensorCollection doesn't contain the Redundancy property 2771 if (sensorsAsyncResp->chassisSubNode != sensors::node::sensors) 2772 { 2773 sensorsAsyncResp->asyncResp->res.jsonValue["Redundancy"] = 2774 nlohmann::json::array(); 2775 } 2776 // Get set of sensors in chassis 2777 getChassis(sensorsAsyncResp, std::move(getChassisCb)); 2778 BMCWEB_LOG_DEBUG << "getChassisData exit"; 2779 } 2780 2781 /** 2782 * @brief Find the requested sensorName in the list of all sensors supplied by 2783 * the chassis node 2784 * 2785 * @param sensorName The sensor name supplied in the PATCH request 2786 * @param sensorsList The list of sensors managed by the chassis node 2787 * @param sensorsModified The list of sensors that were found as a result of 2788 * repeated calls to this function 2789 */ 2790 inline bool findSensorNameUsingSensorPath( 2791 std::string_view sensorName, 2792 boost::container::flat_set<std::string>& sensorsList, 2793 boost::container::flat_set<std::string>& sensorsModified) 2794 { 2795 for (auto& chassisSensor : sensorsList) 2796 { 2797 sdbusplus::message::object_path path(chassisSensor); 2798 std::string thisSensorName = path.filename(); 2799 if (thisSensorName.empty()) 2800 { 2801 continue; 2802 } 2803 if (thisSensorName == sensorName) 2804 { 2805 sensorsModified.emplace(chassisSensor); 2806 return true; 2807 } 2808 } 2809 return false; 2810 } 2811 2812 /** 2813 * @brief Entry point for overriding sensor values of given sensor 2814 * 2815 * @param sensorAsyncResp response object 2816 * @param allCollections Collections extract from sensors' request patch info 2817 * @param chassisSubNode Chassis Node for which the query has to happen 2818 */ 2819 inline void setSensorsOverride( 2820 const std::shared_ptr<SensorsAsyncResp>& sensorAsyncResp, 2821 std::unordered_map<std::string, std::vector<nlohmann::json>>& 2822 allCollections) 2823 { 2824 BMCWEB_LOG_INFO << "setSensorsOverride for subNode" 2825 << sensorAsyncResp->chassisSubNode << "\n"; 2826 2827 const char* propertyValueName = nullptr; 2828 std::unordered_map<std::string, std::pair<double, std::string>> overrideMap; 2829 std::string memberId; 2830 double value = 0.0; 2831 for (auto& collectionItems : allCollections) 2832 { 2833 if (collectionItems.first == "Temperatures") 2834 { 2835 propertyValueName = "ReadingCelsius"; 2836 } 2837 else if (collectionItems.first == "Fans") 2838 { 2839 propertyValueName = "Reading"; 2840 } 2841 else 2842 { 2843 propertyValueName = "ReadingVolts"; 2844 } 2845 for (auto& item : collectionItems.second) 2846 { 2847 if (!json_util::readJson(item, sensorAsyncResp->asyncResp->res, 2848 "MemberId", memberId, propertyValueName, 2849 value)) 2850 { 2851 return; 2852 } 2853 overrideMap.emplace(memberId, 2854 std::make_pair(value, collectionItems.first)); 2855 } 2856 } 2857 2858 auto getChassisSensorListCb = [sensorAsyncResp, overrideMap]( 2859 const std::shared_ptr< 2860 boost::container::flat_set< 2861 std::string>>& sensorsList) { 2862 // Match sensor names in the PATCH request to those managed by the 2863 // chassis node 2864 const std::shared_ptr<boost::container::flat_set<std::string>> 2865 sensorNames = 2866 std::make_shared<boost::container::flat_set<std::string>>(); 2867 for (const auto& item : overrideMap) 2868 { 2869 const auto& sensor = item.first; 2870 if (!findSensorNameUsingSensorPath(sensor, *sensorsList, 2871 *sensorNames)) 2872 { 2873 BMCWEB_LOG_INFO << "Unable to find memberId " << item.first; 2874 messages::resourceNotFound(sensorAsyncResp->asyncResp->res, 2875 item.second.second, item.first); 2876 return; 2877 } 2878 } 2879 // Get the connection to which the memberId belongs 2880 auto getObjectsWithConnectionCb = [sensorAsyncResp, overrideMap]( 2881 const boost::container::flat_set< 2882 std::string>& /*connections*/, 2883 const std::set<std::pair< 2884 std::string, std::string>>& 2885 objectsWithConnection) { 2886 if (objectsWithConnection.size() != overrideMap.size()) 2887 { 2888 BMCWEB_LOG_INFO 2889 << "Unable to find all objects with proper connection " 2890 << objectsWithConnection.size() << " requested " 2891 << overrideMap.size() << "\n"; 2892 messages::resourceNotFound(sensorAsyncResp->asyncResp->res, 2893 sensorAsyncResp->chassisSubNode == 2894 sensors::node::thermal 2895 ? "Temperatures" 2896 : "Voltages", 2897 "Count"); 2898 return; 2899 } 2900 for (const auto& item : objectsWithConnection) 2901 { 2902 sdbusplus::message::object_path path(item.first); 2903 std::string sensorName = path.filename(); 2904 if (sensorName.empty()) 2905 { 2906 messages::internalError(sensorAsyncResp->asyncResp->res); 2907 return; 2908 } 2909 2910 const auto& iterator = overrideMap.find(sensorName); 2911 if (iterator == overrideMap.end()) 2912 { 2913 BMCWEB_LOG_INFO << "Unable to find sensor object" 2914 << item.first << "\n"; 2915 messages::internalError(sensorAsyncResp->asyncResp->res); 2916 return; 2917 } 2918 crow::connections::systemBus->async_method_call( 2919 [sensorAsyncResp](const boost::system::error_code ec) { 2920 if (ec) 2921 { 2922 if (ec.value() == 2923 boost::system::errc::permission_denied) 2924 { 2925 BMCWEB_LOG_WARNING 2926 << "Manufacturing mode is not Enabled...can't " 2927 "Override the sensor value. "; 2928 2929 messages::insufficientPrivilege( 2930 sensorAsyncResp->asyncResp->res); 2931 return; 2932 } 2933 BMCWEB_LOG_DEBUG 2934 << "setOverrideValueStatus DBUS error: " << ec; 2935 messages::internalError( 2936 sensorAsyncResp->asyncResp->res); 2937 } 2938 }, 2939 item.second, item.first, "org.freedesktop.DBus.Properties", 2940 "Set", "xyz.openbmc_project.Sensor.Value", "Value", 2941 dbus::utility::DbusVariantType(iterator->second.first)); 2942 } 2943 }; 2944 // Get object with connection for the given sensor name 2945 getObjectsWithConnection(sensorAsyncResp, sensorNames, 2946 std::move(getObjectsWithConnectionCb)); 2947 }; 2948 // get full sensor list for the given chassisId and cross verify the sensor. 2949 getChassis(sensorAsyncResp, std::move(getChassisSensorListCb)); 2950 } 2951 2952 /** 2953 * @brief Retrieves mapping of Redfish URIs to sensor value property to D-Bus 2954 * path of the sensor. 2955 * 2956 * Function builds valid Redfish response for sensor query of given chassis and 2957 * node. It then builds metadata about Redfish<->D-Bus correlations and provides 2958 * it to caller in a callback. 2959 * 2960 * @param chassis Chassis for which retrieval should be performed 2961 * @param node Node (group) of sensors. See sensors::node for supported values 2962 * @param mapComplete Callback to be called with retrieval result 2963 */ 2964 inline void retrieveUriToDbusMap(const std::string& chassis, 2965 const std::string& node, 2966 SensorsAsyncResp::DataCompleteCb&& mapComplete) 2967 { 2968 decltype(sensors::paths)::const_iterator pathIt = 2969 std::find_if(sensors::paths.cbegin(), sensors::paths.cend(), 2970 [&node](auto&& val) { return val.first == node; }); 2971 if (pathIt == sensors::paths.cend()) 2972 { 2973 BMCWEB_LOG_ERROR << "Wrong node provided : " << node; 2974 mapComplete(boost::beast::http::status::bad_request, {}); 2975 return; 2976 } 2977 2978 auto asyncResp = std::make_shared<bmcweb::AsyncResp>(); 2979 auto callback = 2980 [asyncResp, mapCompleteCb{std::move(mapComplete)}]( 2981 const boost::beast::http::status status, 2982 const boost::container::flat_map<std::string, std::string>& 2983 uriToDbus) { mapCompleteCb(status, uriToDbus); }; 2984 2985 auto resp = std::make_shared<SensorsAsyncResp>( 2986 asyncResp, chassis, pathIt->second, node, std::move(callback)); 2987 getChassisData(resp); 2988 } 2989 2990 namespace sensors 2991 { 2992 2993 inline void getChassisCallback( 2994 const std::shared_ptr<SensorsAsyncResp>& asyncResp, 2995 const std::shared_ptr<boost::container::flat_set<std::string>>& sensorNames) 2996 { 2997 BMCWEB_LOG_DEBUG << "getChassisCallback enter"; 2998 2999 nlohmann::json& entriesArray = 3000 asyncResp->asyncResp->res.jsonValue["Members"]; 3001 for (auto& sensor : *sensorNames) 3002 { 3003 BMCWEB_LOG_DEBUG << "Adding sensor: " << sensor; 3004 3005 sdbusplus::message::object_path path(sensor); 3006 std::string sensorName = path.filename(); 3007 if (sensorName.empty()) 3008 { 3009 BMCWEB_LOG_ERROR << "Invalid sensor path: " << sensor; 3010 messages::internalError(asyncResp->asyncResp->res); 3011 return; 3012 } 3013 nlohmann::json::object_t member; 3014 member["@odata.id"] = "/redfish/v1/Chassis/" + asyncResp->chassisId + 3015 "/" + asyncResp->chassisSubNode + "/" + 3016 sensorName; 3017 entriesArray.push_back(std::move(member)); 3018 } 3019 3020 asyncResp->asyncResp->res.jsonValue["Members@odata.count"] = 3021 entriesArray.size(); 3022 BMCWEB_LOG_DEBUG << "getChassisCallback exit"; 3023 } 3024 } // namespace sensors 3025 3026 inline void requestRoutesSensorCollection(App& app) 3027 { 3028 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/Sensors/") 3029 .privileges(redfish::privileges::getSensorCollection) 3030 .methods( 3031 boost::beast::http::verb::get)([&app]( 3032 const crow::Request& req, 3033 const std::shared_ptr< 3034 bmcweb::AsyncResp>& aResp, 3035 const std::string& chassisId) { 3036 query_param::QueryCapabilities capabilities = { 3037 .canDelegateExpandLevel = 1, 3038 }; 3039 query_param::Query delegatedQuery; 3040 if (!redfish::setUpRedfishRouteWithDelegation( 3041 app, req, aResp->res, delegatedQuery, capabilities)) 3042 { 3043 return; 3044 } 3045 3046 if (delegatedQuery.expandType != query_param::ExpandType::None) 3047 { 3048 // we perform efficient expand. 3049 auto asyncResp = std::make_shared<SensorsAsyncResp>( 3050 aResp, chassisId, sensors::dbus::sensorPaths, 3051 sensors::node::sensors, 3052 /*efficientExpand=*/true); 3053 getChassisData(asyncResp); 3054 3055 BMCWEB_LOG_DEBUG 3056 << "SensorCollection doGet exit via efficient expand handler"; 3057 return; 3058 }; 3059 3060 // if there's no efficient expand available, we use the default 3061 // Query Parameters route 3062 auto asyncResp = std::make_shared<SensorsAsyncResp>( 3063 aResp, chassisId, sensors::dbus::sensorPaths, 3064 sensors::node::sensors); 3065 3066 // We get all sensors as hyperlinkes in the chassis (this 3067 // implies we reply on the default query parameters handler) 3068 getChassis(asyncResp, 3069 std::bind_front(sensors::getChassisCallback, asyncResp)); 3070 BMCWEB_LOG_DEBUG << "SensorCollection doGet exit"; 3071 }); 3072 } 3073 3074 inline void requestRoutesSensor(App& app) 3075 { 3076 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/Sensors/<str>/") 3077 .privileges(redfish::privileges::getSensor) 3078 .methods( 3079 boost::beast::http::verb::get)([&app]( 3080 const crow::Request& req, 3081 const std::shared_ptr< 3082 bmcweb::AsyncResp>& aResp, 3083 const std::string& chassisId, 3084 const std::string& sensorName) { 3085 if (!redfish::setUpRedfishRoute(app, req, aResp->res)) 3086 { 3087 return; 3088 } 3089 BMCWEB_LOG_DEBUG << "Sensor doGet enter"; 3090 std::shared_ptr<SensorsAsyncResp> asyncResp = 3091 std::make_shared<SensorsAsyncResp>( 3092 aResp, chassisId, std::span<std::string_view>(), 3093 sensors::node::sensors); 3094 3095 const std::array<const char*, 1> interfaces = { 3096 "xyz.openbmc_project.Sensor.Value"}; 3097 3098 // Get a list of all of the sensors that implement Sensor.Value 3099 // and get the path and service name associated with the sensor 3100 crow::connections::systemBus->async_method_call( 3101 [asyncResp, sensorName]( 3102 const boost::system::error_code ec, 3103 const dbus::utility::MapperGetSubTreeResponse& subtree) { 3104 BMCWEB_LOG_DEBUG << "respHandler1 enter"; 3105 if (ec) 3106 { 3107 messages::internalError(asyncResp->asyncResp->res); 3108 BMCWEB_LOG_ERROR 3109 << "Sensor getSensorPaths resp_handler: " 3110 << "Dbus error " << ec; 3111 return; 3112 } 3113 3114 dbus::utility::MapperGetSubTreeResponse::const_iterator it = 3115 std::find_if( 3116 subtree.begin(), subtree.end(), 3117 [sensorName]( 3118 const std::pair<std::string, 3119 std::vector<std::pair< 3120 std::string, 3121 std::vector<std::string>>>>& 3122 object) { 3123 sdbusplus::message::object_path path( 3124 object.first); 3125 std::string name = path.filename(); 3126 if (name.empty()) 3127 { 3128 BMCWEB_LOG_ERROR << "Invalid sensor path: " 3129 << object.first; 3130 return false; 3131 } 3132 3133 return name == sensorName; 3134 }); 3135 3136 if (it == subtree.end()) 3137 { 3138 BMCWEB_LOG_ERROR << "Could not find path for sensor: " 3139 << sensorName; 3140 messages::resourceNotFound(asyncResp->asyncResp->res, 3141 "Sensor", sensorName); 3142 return; 3143 } 3144 std::string_view sensorPath = (*it).first; 3145 BMCWEB_LOG_DEBUG << "Found sensor path for sensor '" 3146 << sensorName << "': " << sensorPath; 3147 3148 const std::shared_ptr< 3149 boost::container::flat_set<std::string>> 3150 sensorList = std::make_shared< 3151 boost::container::flat_set<std::string>>(); 3152 3153 sensorList->emplace(sensorPath); 3154 processSensorList(asyncResp, sensorList); 3155 BMCWEB_LOG_DEBUG << "respHandler1 exit"; 3156 }, 3157 "xyz.openbmc_project.ObjectMapper", 3158 "/xyz/openbmc_project/object_mapper", 3159 "xyz.openbmc_project.ObjectMapper", "GetSubTree", 3160 "/xyz/openbmc_project/sensors", 2, interfaces); 3161 }); 3162 } 3163 3164 } // namespace redfish 3165