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