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