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