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