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