140e9b92eSEd Tanous // SPDX-License-Identifier: Apache-2.0 240e9b92eSEd Tanous // SPDX-FileCopyrightText: Copyright OpenBMC Authors 31516c21bSJanet Adkins #pragma once 41516c21bSJanet Adkins 5c9563608SJanet Adkins #include "dbus_utility.hpp" 6d7857201SEd Tanous #include "error_messages.hpp" 7c9563608SJanet Adkins #include "generated/enums/resource.hpp" 8c9563608SJanet Adkins #include "generated/enums/sensor.hpp" 9c9563608SJanet Adkins #include "generated/enums/thermal.hpp" 10d7857201SEd Tanous #include "logging.hpp" 11c9563608SJanet Adkins #include "str_utility.hpp" 12c9563608SJanet Adkins #include "utils/dbus_utils.hpp" 13c9563608SJanet Adkins 146fe8751cSGeorge Liu #include <boost/url/format.hpp> 15d7857201SEd Tanous #include <nlohmann/json.hpp> 16d7857201SEd Tanous #include <sdbusplus/message/native_types.hpp> 17c9563608SJanet Adkins #include <sdbusplus/unpack_properties.hpp> 18c9563608SJanet Adkins 191516c21bSJanet Adkins #include <algorithm> 20d7857201SEd Tanous #include <cmath> 21d7857201SEd Tanous #include <cstddef> 22d7857201SEd Tanous #include <cstdint> 231516c21bSJanet Adkins #include <format> 246fe8751cSGeorge Liu #include <functional> 25d7857201SEd Tanous #include <iterator> 266fe8751cSGeorge Liu #include <optional> 271516c21bSJanet Adkins #include <ranges> 28d7857201SEd Tanous #include <set> 29d7857201SEd Tanous #include <span> 301516c21bSJanet Adkins #include <string> 311516c21bSJanet Adkins #include <string_view> 32c9563608SJanet Adkins #include <tuple> 331516c21bSJanet Adkins #include <utility> 34d7857201SEd Tanous #include <variant> 351516c21bSJanet Adkins #include <vector> 361516c21bSJanet Adkins 371516c21bSJanet Adkins namespace redfish 381516c21bSJanet Adkins { 391516c21bSJanet Adkins namespace sensor_utils 401516c21bSJanet Adkins { 411516c21bSJanet Adkins 420c728b42SJanet Adkins enum class ChassisSubNode 430c728b42SJanet Adkins { 440c728b42SJanet Adkins powerNode, 450c728b42SJanet Adkins sensorsNode, 460c728b42SJanet Adkins thermalNode, 476fe8751cSGeorge Liu thermalMetricsNode, 480c728b42SJanet Adkins unknownNode, 490c728b42SJanet Adkins }; 500c728b42SJanet Adkins 510c728b42SJanet Adkins constexpr std::string_view chassisSubNodeToString(ChassisSubNode subNode) 520c728b42SJanet Adkins { 530c728b42SJanet Adkins switch (subNode) 540c728b42SJanet Adkins { 550c728b42SJanet Adkins case ChassisSubNode::powerNode: 560c728b42SJanet Adkins return "Power"; 570c728b42SJanet Adkins case ChassisSubNode::sensorsNode: 580c728b42SJanet Adkins return "Sensors"; 590c728b42SJanet Adkins case ChassisSubNode::thermalNode: 600c728b42SJanet Adkins return "Thermal"; 616fe8751cSGeorge Liu case ChassisSubNode::thermalMetricsNode: 626fe8751cSGeorge Liu return "ThermalMetrics"; 630c728b42SJanet Adkins case ChassisSubNode::unknownNode: 640c728b42SJanet Adkins default: 650c728b42SJanet Adkins return ""; 660c728b42SJanet Adkins } 670c728b42SJanet Adkins } 680c728b42SJanet Adkins 690c728b42SJanet Adkins inline ChassisSubNode chassisSubNodeFromString(const std::string& subNodeStr) 700c728b42SJanet Adkins { 710c728b42SJanet Adkins // If none match unknownNode is returned 720c728b42SJanet Adkins ChassisSubNode subNode = ChassisSubNode::unknownNode; 730c728b42SJanet Adkins 740c728b42SJanet Adkins if (subNodeStr == "Power") 750c728b42SJanet Adkins { 760c728b42SJanet Adkins subNode = ChassisSubNode::powerNode; 770c728b42SJanet Adkins } 780c728b42SJanet Adkins else if (subNodeStr == "Sensors") 790c728b42SJanet Adkins { 800c728b42SJanet Adkins subNode = ChassisSubNode::sensorsNode; 810c728b42SJanet Adkins } 820c728b42SJanet Adkins else if (subNodeStr == "Thermal") 830c728b42SJanet Adkins { 840c728b42SJanet Adkins subNode = ChassisSubNode::thermalNode; 850c728b42SJanet Adkins } 866fe8751cSGeorge Liu else if (subNodeStr == "ThermalMetrics") 876fe8751cSGeorge Liu { 886fe8751cSGeorge Liu subNode = ChassisSubNode::thermalMetricsNode; 896fe8751cSGeorge Liu } 900c728b42SJanet Adkins 910c728b42SJanet Adkins return subNode; 920c728b42SJanet Adkins } 93c9563608SJanet Adkins 946fe8751cSGeorge Liu inline bool isExcerptNode(const ChassisSubNode subNode) 956fe8751cSGeorge Liu { 966fe8751cSGeorge Liu return (subNode == ChassisSubNode::thermalMetricsNode); 976fe8751cSGeorge Liu } 986fe8751cSGeorge Liu 99c9563608SJanet Adkins /** 100c9563608SJanet Adkins * Possible states for physical inventory leds 101c9563608SJanet Adkins */ 102c9563608SJanet Adkins enum class LedState 103c9563608SJanet Adkins { 104c9563608SJanet Adkins OFF, 105c9563608SJanet Adkins ON, 106c9563608SJanet Adkins BLINK, 107c9563608SJanet Adkins UNKNOWN 108c9563608SJanet Adkins }; 109c9563608SJanet Adkins 110c9563608SJanet Adkins /** 111c9563608SJanet Adkins * D-Bus inventory item associated with one or more sensors. 112c9563608SJanet Adkins */ 113c9563608SJanet Adkins class InventoryItem 114c9563608SJanet Adkins { 115c9563608SJanet Adkins public: 116c9563608SJanet Adkins explicit InventoryItem(const std::string& objPath) : objectPath(objPath) 117c9563608SJanet Adkins { 118c9563608SJanet Adkins // Set inventory item name to last node of object path 119c9563608SJanet Adkins sdbusplus::message::object_path path(objectPath); 120c9563608SJanet Adkins name = path.filename(); 121c9563608SJanet Adkins if (name.empty()) 122c9563608SJanet Adkins { 123c9563608SJanet Adkins BMCWEB_LOG_ERROR("Failed to find '/' in {}", objectPath); 124c9563608SJanet Adkins } 125c9563608SJanet Adkins } 126c9563608SJanet Adkins 127c9563608SJanet Adkins std::string objectPath; 128c9563608SJanet Adkins std::string name; 129c9563608SJanet Adkins bool isPresent = true; 130c9563608SJanet Adkins bool isFunctional = true; 131c9563608SJanet Adkins bool isPowerSupply = false; 132c9563608SJanet Adkins int powerSupplyEfficiencyPercent = -1; 133c9563608SJanet Adkins std::string manufacturer; 134c9563608SJanet Adkins std::string model; 135c9563608SJanet Adkins std::string partNumber; 136c9563608SJanet Adkins std::string serialNumber; 137c9563608SJanet Adkins std::set<std::string> sensors; 138c9563608SJanet Adkins std::string ledObjectPath; 139c9563608SJanet Adkins LedState ledState = LedState::UNKNOWN; 140c9563608SJanet Adkins }; 141c9563608SJanet Adkins 1421516c21bSJanet Adkins inline std::string getSensorId(std::string_view sensorName, 1431516c21bSJanet Adkins std::string_view sensorType) 1441516c21bSJanet Adkins { 1451516c21bSJanet Adkins std::string normalizedType(sensorType); 1461516c21bSJanet Adkins auto remove = std::ranges::remove(normalizedType, '_'); 1471516c21bSJanet Adkins normalizedType.erase(std::ranges::begin(remove), normalizedType.end()); 1481516c21bSJanet Adkins 1491516c21bSJanet Adkins return std::format("{}_{}", normalizedType, sensorName); 1501516c21bSJanet Adkins } 1511516c21bSJanet Adkins 152*504af5a0SPatrick Williams inline std::pair<std::string, std::string> splitSensorNameAndType( 153*504af5a0SPatrick Williams std::string_view sensorId) 1541516c21bSJanet Adkins { 1551516c21bSJanet Adkins size_t index = sensorId.find('_'); 1561516c21bSJanet Adkins if (index == std::string::npos) 1571516c21bSJanet Adkins { 1581516c21bSJanet Adkins return std::make_pair<std::string, std::string>("", ""); 1591516c21bSJanet Adkins } 1601516c21bSJanet Adkins std::string sensorType{sensorId.substr(0, index)}; 1611516c21bSJanet Adkins std::string sensorName{sensorId.substr(index + 1)}; 1621516c21bSJanet Adkins // fan_pwm and fan_tach need special handling 1631516c21bSJanet Adkins if (sensorType == "fantach" || sensorType == "fanpwm") 1641516c21bSJanet Adkins { 1651516c21bSJanet Adkins sensorType.insert(3, 1, '_'); 1661516c21bSJanet Adkins } 1671516c21bSJanet Adkins return std::make_pair(sensorType, sensorName); 1681516c21bSJanet Adkins } 1691516c21bSJanet Adkins 170c9563608SJanet Adkins namespace sensors 171c9563608SJanet Adkins { 172c9563608SJanet Adkins inline std::string_view toReadingUnits(std::string_view sensorType) 173c9563608SJanet Adkins { 174c9563608SJanet Adkins if (sensorType == "voltage") 175c9563608SJanet Adkins { 176c9563608SJanet Adkins return "V"; 177c9563608SJanet Adkins } 178c9563608SJanet Adkins if (sensorType == "power") 179c9563608SJanet Adkins { 180c9563608SJanet Adkins return "W"; 181c9563608SJanet Adkins } 182c9563608SJanet Adkins if (sensorType == "current") 183c9563608SJanet Adkins { 184c9563608SJanet Adkins return "A"; 185c9563608SJanet Adkins } 186c9563608SJanet Adkins if (sensorType == "fan_tach") 187c9563608SJanet Adkins { 188c9563608SJanet Adkins return "RPM"; 189c9563608SJanet Adkins } 190c9563608SJanet Adkins if (sensorType == "temperature") 191c9563608SJanet Adkins { 192c9563608SJanet Adkins return "Cel"; 193c9563608SJanet Adkins } 194c9563608SJanet Adkins if (sensorType == "fan_pwm" || sensorType == "utilization" || 195c9563608SJanet Adkins sensorType == "humidity") 196c9563608SJanet Adkins { 197c9563608SJanet Adkins return "%"; 198c9563608SJanet Adkins } 199c9563608SJanet Adkins if (sensorType == "altitude") 200c9563608SJanet Adkins { 201c9563608SJanet Adkins return "m"; 202c9563608SJanet Adkins } 203c9563608SJanet Adkins if (sensorType == "airflow") 204c9563608SJanet Adkins { 205c9563608SJanet Adkins return "cft_i/min"; 206c9563608SJanet Adkins } 207c9563608SJanet Adkins if (sensorType == "energy") 208c9563608SJanet Adkins { 209c9563608SJanet Adkins return "J"; 210c9563608SJanet Adkins } 211c9563608SJanet Adkins return ""; 212c9563608SJanet Adkins } 213c9563608SJanet Adkins 214c9563608SJanet Adkins inline sensor::ReadingType toReadingType(std::string_view sensorType) 215c9563608SJanet Adkins { 216c9563608SJanet Adkins if (sensorType == "voltage") 217c9563608SJanet Adkins { 218c9563608SJanet Adkins return sensor::ReadingType::Voltage; 219c9563608SJanet Adkins } 220c9563608SJanet Adkins if (sensorType == "power") 221c9563608SJanet Adkins { 222c9563608SJanet Adkins return sensor::ReadingType::Power; 223c9563608SJanet Adkins } 224c9563608SJanet Adkins if (sensorType == "current") 225c9563608SJanet Adkins { 226c9563608SJanet Adkins return sensor::ReadingType::Current; 227c9563608SJanet Adkins } 228c9563608SJanet Adkins if (sensorType == "fan_tach") 229c9563608SJanet Adkins { 230c9563608SJanet Adkins return sensor::ReadingType::Rotational; 231c9563608SJanet Adkins } 232c9563608SJanet Adkins if (sensorType == "temperature") 233c9563608SJanet Adkins { 234c9563608SJanet Adkins return sensor::ReadingType::Temperature; 235c9563608SJanet Adkins } 236c9563608SJanet Adkins if (sensorType == "fan_pwm" || sensorType == "utilization") 237c9563608SJanet Adkins { 238c9563608SJanet Adkins return sensor::ReadingType::Percent; 239c9563608SJanet Adkins } 240c9563608SJanet Adkins if (sensorType == "humidity") 241c9563608SJanet Adkins { 242c9563608SJanet Adkins return sensor::ReadingType::Humidity; 243c9563608SJanet Adkins } 244c9563608SJanet Adkins if (sensorType == "altitude") 245c9563608SJanet Adkins { 246c9563608SJanet Adkins return sensor::ReadingType::Altitude; 247c9563608SJanet Adkins } 248c9563608SJanet Adkins if (sensorType == "airflow") 249c9563608SJanet Adkins { 250c9563608SJanet Adkins return sensor::ReadingType::AirFlow; 251c9563608SJanet Adkins } 252c9563608SJanet Adkins if (sensorType == "energy") 253c9563608SJanet Adkins { 254c9563608SJanet Adkins return sensor::ReadingType::EnergyJoules; 255c9563608SJanet Adkins } 256c9563608SJanet Adkins return sensor::ReadingType::Invalid; 257c9563608SJanet Adkins } 258c9563608SJanet Adkins 259c9563608SJanet Adkins } // namespace sensors 260c9563608SJanet Adkins 261c9563608SJanet Adkins /** 262c9563608SJanet Adkins * @brief Returns the Redfish State value for the specified inventory item. 263c9563608SJanet Adkins * @param inventoryItem D-Bus inventory item associated with a sensor. 264c9563608SJanet Adkins * @param sensorAvailable Boolean representing if D-Bus sensor is marked as 265c9563608SJanet Adkins * available. 266c9563608SJanet Adkins * @return State value for inventory item. 267c9563608SJanet Adkins */ 268c9563608SJanet Adkins inline resource::State getState(const InventoryItem* inventoryItem, 269c9563608SJanet Adkins const bool sensorAvailable) 270c9563608SJanet Adkins { 271c9563608SJanet Adkins if ((inventoryItem != nullptr) && !(inventoryItem->isPresent)) 272c9563608SJanet Adkins { 273c9563608SJanet Adkins return resource::State::Absent; 274c9563608SJanet Adkins } 275c9563608SJanet Adkins 276c9563608SJanet Adkins if (!sensorAvailable) 277c9563608SJanet Adkins { 278c9563608SJanet Adkins return resource::State::UnavailableOffline; 279c9563608SJanet Adkins } 280c9563608SJanet Adkins 281c9563608SJanet Adkins return resource::State::Enabled; 282c9563608SJanet Adkins } 283c9563608SJanet Adkins 284c9563608SJanet Adkins /** 285c9563608SJanet Adkins * @brief Returns the Redfish Health value for the specified sensor. 286c9563608SJanet Adkins * @param sensorJson Sensor JSON object. 287c9563608SJanet Adkins * @param valuesDict Map of all sensor DBus values. 288c9563608SJanet Adkins * @param inventoryItem D-Bus inventory item associated with the sensor. Will 289c9563608SJanet Adkins * be nullptr if no associated inventory item was found. 290c9563608SJanet Adkins * @return Health value for sensor. 291c9563608SJanet Adkins */ 292c9563608SJanet Adkins inline std::string getHealth(nlohmann::json& sensorJson, 293c9563608SJanet Adkins const dbus::utility::DBusPropertiesMap& valuesDict, 294c9563608SJanet Adkins const InventoryItem* inventoryItem) 295c9563608SJanet Adkins { 296c9563608SJanet Adkins // Get current health value (if any) in the sensor JSON object. Some JSON 297c9563608SJanet Adkins // objects contain multiple sensors (such as PowerSupplies). We want to set 298c9563608SJanet Adkins // the overall health to be the most severe of any of the sensors. 299c9563608SJanet Adkins std::string currentHealth; 300c9563608SJanet Adkins auto statusIt = sensorJson.find("Status"); 301c9563608SJanet Adkins if (statusIt != sensorJson.end()) 302c9563608SJanet Adkins { 303c9563608SJanet Adkins auto healthIt = statusIt->find("Health"); 304c9563608SJanet Adkins if (healthIt != statusIt->end()) 305c9563608SJanet Adkins { 306c9563608SJanet Adkins std::string* health = healthIt->get_ptr<std::string*>(); 307c9563608SJanet Adkins if (health != nullptr) 308c9563608SJanet Adkins { 309c9563608SJanet Adkins currentHealth = *health; 310c9563608SJanet Adkins } 311c9563608SJanet Adkins } 312c9563608SJanet Adkins } 313c9563608SJanet Adkins 314c9563608SJanet Adkins // If current health in JSON object is already Critical, return that. This 315c9563608SJanet Adkins // should override the sensor health, which might be less severe. 316c9563608SJanet Adkins if (currentHealth == "Critical") 317c9563608SJanet Adkins { 318c9563608SJanet Adkins return "Critical"; 319c9563608SJanet Adkins } 320c9563608SJanet Adkins 321c9563608SJanet Adkins const bool* criticalAlarmHigh = nullptr; 322c9563608SJanet Adkins const bool* criticalAlarmLow = nullptr; 323c9563608SJanet Adkins const bool* warningAlarmHigh = nullptr; 324c9563608SJanet Adkins const bool* warningAlarmLow = nullptr; 325c9563608SJanet Adkins 326c9563608SJanet Adkins const bool success = sdbusplus::unpackPropertiesNoThrow( 327c9563608SJanet Adkins dbus_utils::UnpackErrorPrinter(), valuesDict, "CriticalAlarmHigh", 328c9563608SJanet Adkins criticalAlarmHigh, "CriticalAlarmLow", criticalAlarmLow, 329c9563608SJanet Adkins "WarningAlarmHigh", warningAlarmHigh, "WarningAlarmLow", 330c9563608SJanet Adkins warningAlarmLow); 331c9563608SJanet Adkins 332c9563608SJanet Adkins if (success) 333c9563608SJanet Adkins { 334c9563608SJanet Adkins // Check if sensor has critical threshold alarm 335c9563608SJanet Adkins if ((criticalAlarmHigh != nullptr && *criticalAlarmHigh) || 336c9563608SJanet Adkins (criticalAlarmLow != nullptr && *criticalAlarmLow)) 337c9563608SJanet Adkins { 338c9563608SJanet Adkins return "Critical"; 339c9563608SJanet Adkins } 340c9563608SJanet Adkins } 341c9563608SJanet Adkins 342c9563608SJanet Adkins // Check if associated inventory item is not functional 343c9563608SJanet Adkins if ((inventoryItem != nullptr) && !(inventoryItem->isFunctional)) 344c9563608SJanet Adkins { 345c9563608SJanet Adkins return "Critical"; 346c9563608SJanet Adkins } 347c9563608SJanet Adkins 348c9563608SJanet Adkins // If current health in JSON object is already Warning, return that. This 349c9563608SJanet Adkins // should override the sensor status, which might be less severe. 350c9563608SJanet Adkins if (currentHealth == "Warning") 351c9563608SJanet Adkins { 352c9563608SJanet Adkins return "Warning"; 353c9563608SJanet Adkins } 354c9563608SJanet Adkins 355c9563608SJanet Adkins if (success) 356c9563608SJanet Adkins { 357c9563608SJanet Adkins // Check if sensor has warning threshold alarm 358c9563608SJanet Adkins if ((warningAlarmHigh != nullptr && *warningAlarmHigh) || 359c9563608SJanet Adkins (warningAlarmLow != nullptr && *warningAlarmLow)) 360c9563608SJanet Adkins { 361c9563608SJanet Adkins return "Warning"; 362c9563608SJanet Adkins } 363c9563608SJanet Adkins } 364c9563608SJanet Adkins 365c9563608SJanet Adkins return "OK"; 366c9563608SJanet Adkins } 367c9563608SJanet Adkins 368c9563608SJanet Adkins inline void setLedState(nlohmann::json& sensorJson, 369c9563608SJanet Adkins const InventoryItem* inventoryItem) 370c9563608SJanet Adkins { 371c9563608SJanet Adkins if (inventoryItem != nullptr && !inventoryItem->ledObjectPath.empty()) 372c9563608SJanet Adkins { 373c9563608SJanet Adkins switch (inventoryItem->ledState) 374c9563608SJanet Adkins { 375c9563608SJanet Adkins case LedState::OFF: 376c9563608SJanet Adkins sensorJson["IndicatorLED"] = resource::IndicatorLED::Off; 377c9563608SJanet Adkins break; 378c9563608SJanet Adkins case LedState::ON: 379c9563608SJanet Adkins sensorJson["IndicatorLED"] = resource::IndicatorLED::Lit; 380c9563608SJanet Adkins break; 381c9563608SJanet Adkins case LedState::BLINK: 382c9563608SJanet Adkins sensorJson["IndicatorLED"] = resource::IndicatorLED::Blinking; 383c9563608SJanet Adkins break; 384c9563608SJanet Adkins default: 385c9563608SJanet Adkins break; 386c9563608SJanet Adkins } 387c9563608SJanet Adkins } 388c9563608SJanet Adkins } 389c9563608SJanet Adkins 390c9563608SJanet Adkins /** 391c9563608SJanet Adkins * @brief Builds a json sensor representation of a sensor. 392c9563608SJanet Adkins * @param sensorName The name of the sensor to be built 393c9563608SJanet Adkins * @param sensorType The type (temperature, fan_tach, etc) of the sensor to 394c9563608SJanet Adkins * build 395c9563608SJanet Adkins * @param chassisSubNode The subnode (thermal, sensor, etc) of the sensor 396c9563608SJanet Adkins * @param propertiesDict A dictionary of the properties to build the sensor 397c9563608SJanet Adkins * from. 398c9563608SJanet Adkins * @param sensorJson The json object to fill 399c9563608SJanet Adkins * @param inventoryItem D-Bus inventory item associated with the sensor. Will 400c9563608SJanet Adkins * be nullptr if no associated inventory item was found. 401c9563608SJanet Adkins */ 402c9563608SJanet Adkins inline void objectPropertiesToJson( 403c9563608SJanet Adkins std::string_view sensorName, std::string_view sensorType, 4040c728b42SJanet Adkins ChassisSubNode chassisSubNode, 405c9563608SJanet Adkins const dbus::utility::DBusPropertiesMap& propertiesDict, 406c9563608SJanet Adkins nlohmann::json& sensorJson, InventoryItem* inventoryItem) 407c9563608SJanet Adkins { 4086fe8751cSGeorge Liu // Parameter to set to override the type we get from dbus, and force it to 4096fe8751cSGeorge Liu // int, regardless of what is available. This is used for schemas like fan, 4106fe8751cSGeorge Liu // that require integers, not floats. 4116fe8751cSGeorge Liu bool forceToInt = false; 4126fe8751cSGeorge Liu 4136fe8751cSGeorge Liu nlohmann::json::json_pointer unit("/Reading"); 4146fe8751cSGeorge Liu 4156fe8751cSGeorge Liu // This ChassisSubNode builds sensor excerpts 4166fe8751cSGeorge Liu bool isExcerpt = isExcerptNode(chassisSubNode); 4176fe8751cSGeorge Liu 4186fe8751cSGeorge Liu /* Sensor excerpts use different keys to reference the sensor. These are 4196fe8751cSGeorge Liu * built by the caller. 4206fe8751cSGeorge Liu * Additionally they don't include these additional properties. 4216fe8751cSGeorge Liu */ 4226fe8751cSGeorge Liu if (!isExcerpt) 4236fe8751cSGeorge Liu { 4240c728b42SJanet Adkins if (chassisSubNode == ChassisSubNode::sensorsNode) 425c9563608SJanet Adkins { 426c9563608SJanet Adkins std::string subNodeEscaped = getSensorId(sensorName, sensorType); 427c9563608SJanet Adkins // For sensors in SensorCollection we set Id instead of MemberId, 428c9563608SJanet Adkins // including power sensors. 429c9563608SJanet Adkins sensorJson["Id"] = std::move(subNodeEscaped); 430c9563608SJanet Adkins 431c9563608SJanet Adkins std::string sensorNameEs(sensorName); 432c9563608SJanet Adkins std::replace(sensorNameEs.begin(), sensorNameEs.end(), '_', ' '); 433c9563608SJanet Adkins sensorJson["Name"] = std::move(sensorNameEs); 434c9563608SJanet Adkins } 435c9563608SJanet Adkins else if (sensorType != "power") 436c9563608SJanet Adkins { 4376fe8751cSGeorge Liu // Set MemberId and Name for non-power sensors. For PowerSupplies 4386fe8751cSGeorge Liu // and PowerControl, those properties have more general values 4396fe8751cSGeorge Liu // because multiple sensors can be stored in the same JSON object. 440c9563608SJanet Adkins std::string sensorNameEs(sensorName); 441c9563608SJanet Adkins std::replace(sensorNameEs.begin(), sensorNameEs.end(), '_', ' '); 442c9563608SJanet Adkins sensorJson["Name"] = std::move(sensorNameEs); 443c9563608SJanet Adkins } 444c9563608SJanet Adkins 445c9563608SJanet Adkins const bool* checkAvailable = nullptr; 446c9563608SJanet Adkins bool available = true; 447c9563608SJanet Adkins const bool success = sdbusplus::unpackPropertiesNoThrow( 448c9563608SJanet Adkins dbus_utils::UnpackErrorPrinter(), propertiesDict, "Available", 449c9563608SJanet Adkins checkAvailable); 450c9563608SJanet Adkins if (!success) 451c9563608SJanet Adkins { 452c9563608SJanet Adkins messages::internalError(); 453c9563608SJanet Adkins } 454c9563608SJanet Adkins if (checkAvailable != nullptr) 455c9563608SJanet Adkins { 456c9563608SJanet Adkins available = *checkAvailable; 457c9563608SJanet Adkins } 458c9563608SJanet Adkins 459c9563608SJanet Adkins sensorJson["Status"]["State"] = getState(inventoryItem, available); 460c9563608SJanet Adkins sensorJson["Status"]["Health"] = 461c9563608SJanet Adkins getHealth(sensorJson, propertiesDict, inventoryItem); 462c9563608SJanet Adkins 4630c728b42SJanet Adkins if (chassisSubNode == ChassisSubNode::sensorsNode) 464c9563608SJanet Adkins { 465c9563608SJanet Adkins sensorJson["@odata.type"] = "#Sensor.v1_2_0.Sensor"; 466c9563608SJanet Adkins 4676fe8751cSGeorge Liu sensor::ReadingType readingType = 4686fe8751cSGeorge Liu sensors::toReadingType(sensorType); 469c9563608SJanet Adkins if (readingType == sensor::ReadingType::Invalid) 470c9563608SJanet Adkins { 471c9563608SJanet Adkins BMCWEB_LOG_ERROR("Redfish cannot map reading type for {}", 472c9563608SJanet Adkins sensorType); 473c9563608SJanet Adkins } 474c9563608SJanet Adkins else 475c9563608SJanet Adkins { 476c9563608SJanet Adkins sensorJson["ReadingType"] = readingType; 477c9563608SJanet Adkins } 478c9563608SJanet Adkins 479c9563608SJanet Adkins std::string_view readingUnits = sensors::toReadingUnits(sensorType); 480c9563608SJanet Adkins if (readingUnits.empty()) 481c9563608SJanet Adkins { 482c9563608SJanet Adkins BMCWEB_LOG_ERROR("Redfish cannot map reading unit for {}", 483c9563608SJanet Adkins sensorType); 484c9563608SJanet Adkins } 485c9563608SJanet Adkins else 486c9563608SJanet Adkins { 487c9563608SJanet Adkins sensorJson["ReadingUnits"] = readingUnits; 488c9563608SJanet Adkins } 489c9563608SJanet Adkins } 490c9563608SJanet Adkins else if (sensorType == "temperature") 491c9563608SJanet Adkins { 492c9563608SJanet Adkins unit = "/ReadingCelsius"_json_pointer; 493c9563608SJanet Adkins sensorJson["@odata.type"] = "#Thermal.v1_3_0.Temperature"; 494c9563608SJanet Adkins // TODO(ed) Documentation says that path should be type fan_tach, 495c9563608SJanet Adkins // implementation seems to implement fan 496c9563608SJanet Adkins } 497c9563608SJanet Adkins else if (sensorType == "fan" || sensorType == "fan_tach") 498c9563608SJanet Adkins { 499c9563608SJanet Adkins unit = "/Reading"_json_pointer; 500c9563608SJanet Adkins sensorJson["ReadingUnits"] = thermal::ReadingUnits::RPM; 501c9563608SJanet Adkins sensorJson["@odata.type"] = "#Thermal.v1_3_0.Fan"; 502c9563608SJanet Adkins setLedState(sensorJson, inventoryItem); 503c9563608SJanet Adkins forceToInt = true; 504c9563608SJanet Adkins } 505c9563608SJanet Adkins else if (sensorType == "fan_pwm") 506c9563608SJanet Adkins { 507c9563608SJanet Adkins unit = "/Reading"_json_pointer; 508c9563608SJanet Adkins sensorJson["ReadingUnits"] = thermal::ReadingUnits::Percent; 509c9563608SJanet Adkins sensorJson["@odata.type"] = "#Thermal.v1_3_0.Fan"; 510c9563608SJanet Adkins setLedState(sensorJson, inventoryItem); 511c9563608SJanet Adkins forceToInt = true; 512c9563608SJanet Adkins } 513c9563608SJanet Adkins else if (sensorType == "voltage") 514c9563608SJanet Adkins { 515c9563608SJanet Adkins unit = "/ReadingVolts"_json_pointer; 516c9563608SJanet Adkins sensorJson["@odata.type"] = "#Power.v1_0_0.Voltage"; 517c9563608SJanet Adkins } 518c9563608SJanet Adkins else if (sensorType == "power") 519c9563608SJanet Adkins { 520c9563608SJanet Adkins std::string lower; 521c9563608SJanet Adkins std::ranges::transform(sensorName, std::back_inserter(lower), 522c9563608SJanet Adkins bmcweb::asciiToLower); 523c9563608SJanet Adkins if (lower == "total_power") 524c9563608SJanet Adkins { 525c9563608SJanet Adkins sensorJson["@odata.type"] = "#Power.v1_0_0.PowerControl"; 526c9563608SJanet Adkins // Put multiple "sensors" into a single PowerControl, so have 527c9563608SJanet Adkins // generic names for MemberId and Name. Follows Redfish mockup. 528c9563608SJanet Adkins sensorJson["MemberId"] = "0"; 529c9563608SJanet Adkins sensorJson["Name"] = "Chassis Power Control"; 530c9563608SJanet Adkins unit = "/PowerConsumedWatts"_json_pointer; 531c9563608SJanet Adkins } 532c9563608SJanet Adkins else if (lower.find("input") != std::string::npos) 533c9563608SJanet Adkins { 534c9563608SJanet Adkins unit = "/PowerInputWatts"_json_pointer; 535c9563608SJanet Adkins } 536c9563608SJanet Adkins else 537c9563608SJanet Adkins { 538c9563608SJanet Adkins unit = "/PowerOutputWatts"_json_pointer; 539c9563608SJanet Adkins } 540c9563608SJanet Adkins } 541c9563608SJanet Adkins else 542c9563608SJanet Adkins { 5436fe8751cSGeorge Liu BMCWEB_LOG_ERROR("Redfish cannot map object type for {}", 5446fe8751cSGeorge Liu sensorName); 545c9563608SJanet Adkins return; 546c9563608SJanet Adkins } 5476fe8751cSGeorge Liu } 5486fe8751cSGeorge Liu 549c9563608SJanet Adkins // Map of dbus interface name, dbus property name and redfish property_name 550c9563608SJanet Adkins std::vector< 551c9563608SJanet Adkins std::tuple<const char*, const char*, nlohmann::json::json_pointer>> 552c9563608SJanet Adkins properties; 553c9563608SJanet Adkins 554c9563608SJanet Adkins properties.emplace_back("xyz.openbmc_project.Sensor.Value", "Value", unit); 555c9563608SJanet Adkins 5566fe8751cSGeorge Liu if (!isExcerpt) 5576fe8751cSGeorge Liu { 5580c728b42SJanet Adkins if (chassisSubNode == ChassisSubNode::sensorsNode) 559c9563608SJanet Adkins { 560c9563608SJanet Adkins properties.emplace_back( 561c9563608SJanet Adkins "xyz.openbmc_project.Sensor.Threshold.Warning", "WarningHigh", 562c9563608SJanet Adkins "/Thresholds/UpperCaution/Reading"_json_pointer); 563c9563608SJanet Adkins properties.emplace_back( 564c9563608SJanet Adkins "xyz.openbmc_project.Sensor.Threshold.Warning", "WarningLow", 565c9563608SJanet Adkins "/Thresholds/LowerCaution/Reading"_json_pointer); 566c9563608SJanet Adkins properties.emplace_back( 567c9563608SJanet Adkins "xyz.openbmc_project.Sensor.Threshold.Critical", "CriticalHigh", 568c9563608SJanet Adkins "/Thresholds/UpperCritical/Reading"_json_pointer); 569c9563608SJanet Adkins properties.emplace_back( 570c9563608SJanet Adkins "xyz.openbmc_project.Sensor.Threshold.Critical", "CriticalLow", 571c9563608SJanet Adkins "/Thresholds/LowerCritical/Reading"_json_pointer); 572cd5a898fSJanet Adkins 573cd5a898fSJanet Adkins /* Add additional properties specific to sensorType */ 574cd5a898fSJanet Adkins if (sensorType == "fan_tach") 575cd5a898fSJanet Adkins { 5766fe8751cSGeorge Liu properties.emplace_back("xyz.openbmc_project.Sensor.Value", 5776fe8751cSGeorge Liu "Value", "/SpeedRPM"_json_pointer); 578cd5a898fSJanet Adkins } 579c9563608SJanet Adkins } 580c9563608SJanet Adkins else if (sensorType != "power") 581c9563608SJanet Adkins { 5826fe8751cSGeorge Liu properties.emplace_back( 5836fe8751cSGeorge Liu "xyz.openbmc_project.Sensor.Threshold.Warning", "WarningHigh", 584c9563608SJanet Adkins "/UpperThresholdNonCritical"_json_pointer); 5856fe8751cSGeorge Liu properties.emplace_back( 5866fe8751cSGeorge Liu "xyz.openbmc_project.Sensor.Threshold.Warning", "WarningLow", 587c9563608SJanet Adkins "/LowerThresholdNonCritical"_json_pointer); 5886fe8751cSGeorge Liu properties.emplace_back( 5896fe8751cSGeorge Liu "xyz.openbmc_project.Sensor.Threshold.Critical", "CriticalHigh", 590c9563608SJanet Adkins "/UpperThresholdCritical"_json_pointer); 5916fe8751cSGeorge Liu properties.emplace_back( 5926fe8751cSGeorge Liu "xyz.openbmc_project.Sensor.Threshold.Critical", "CriticalLow", 593c9563608SJanet Adkins "/LowerThresholdCritical"_json_pointer); 594c9563608SJanet Adkins } 595c9563608SJanet Adkins 596c9563608SJanet Adkins // TODO Need to get UpperThresholdFatal and LowerThresholdFatal 597c9563608SJanet Adkins 5980c728b42SJanet Adkins if (chassisSubNode == ChassisSubNode::sensorsNode) 599c9563608SJanet Adkins { 6006fe8751cSGeorge Liu properties.emplace_back("xyz.openbmc_project.Sensor.Value", 6016fe8751cSGeorge Liu "MinValue", 602c9563608SJanet Adkins "/ReadingRangeMin"_json_pointer); 6036fe8751cSGeorge Liu properties.emplace_back("xyz.openbmc_project.Sensor.Value", 6046fe8751cSGeorge Liu "MaxValue", 605c9563608SJanet Adkins "/ReadingRangeMax"_json_pointer); 606c9563608SJanet Adkins properties.emplace_back("xyz.openbmc_project.Sensor.Accuracy", 607c9563608SJanet Adkins "Accuracy", "/Accuracy"_json_pointer); 608c9563608SJanet Adkins } 609c9563608SJanet Adkins else if (sensorType == "temperature") 610c9563608SJanet Adkins { 6116fe8751cSGeorge Liu properties.emplace_back("xyz.openbmc_project.Sensor.Value", 6126fe8751cSGeorge Liu "MinValue", 613c9563608SJanet Adkins "/MinReadingRangeTemp"_json_pointer); 6146fe8751cSGeorge Liu properties.emplace_back("xyz.openbmc_project.Sensor.Value", 6156fe8751cSGeorge Liu "MaxValue", 616c9563608SJanet Adkins "/MaxReadingRangeTemp"_json_pointer); 617c9563608SJanet Adkins } 618c9563608SJanet Adkins else if (sensorType != "power") 619c9563608SJanet Adkins { 6206fe8751cSGeorge Liu properties.emplace_back("xyz.openbmc_project.Sensor.Value", 6216fe8751cSGeorge Liu "MinValue", 622c9563608SJanet Adkins "/MinReadingRange"_json_pointer); 6236fe8751cSGeorge Liu properties.emplace_back("xyz.openbmc_project.Sensor.Value", 6246fe8751cSGeorge Liu "MaxValue", 625c9563608SJanet Adkins "/MaxReadingRange"_json_pointer); 626c9563608SJanet Adkins } 6276fe8751cSGeorge Liu } 628c9563608SJanet Adkins 629c9563608SJanet Adkins for (const std::tuple<const char*, const char*, 630c9563608SJanet Adkins nlohmann::json::json_pointer>& p : properties) 631c9563608SJanet Adkins { 632c9563608SJanet Adkins for (const auto& [valueName, valueVariant] : propertiesDict) 633c9563608SJanet Adkins { 634c9563608SJanet Adkins if (valueName != std::get<1>(p)) 635c9563608SJanet Adkins { 636c9563608SJanet Adkins continue; 637c9563608SJanet Adkins } 638c9563608SJanet Adkins 639c9563608SJanet Adkins // The property we want to set may be nested json, so use 640c9563608SJanet Adkins // a json_pointer for easy indexing into the json structure. 641c9563608SJanet Adkins const nlohmann::json::json_pointer& key = std::get<2>(p); 642c9563608SJanet Adkins 643c9563608SJanet Adkins const double* doubleValue = std::get_if<double>(&valueVariant); 644c9563608SJanet Adkins if (doubleValue == nullptr) 645c9563608SJanet Adkins { 646c9563608SJanet Adkins BMCWEB_LOG_ERROR("Got value interface that wasn't double"); 647c9563608SJanet Adkins continue; 648c9563608SJanet Adkins } 649c9563608SJanet Adkins if (!std::isfinite(*doubleValue)) 650c9563608SJanet Adkins { 651c9563608SJanet Adkins if (valueName == "Value") 652c9563608SJanet Adkins { 653c9563608SJanet Adkins // Readings are allowed to be NAN for unavailable; coerce 654c9563608SJanet Adkins // them to null in the json response. 655c9563608SJanet Adkins sensorJson[key] = nullptr; 656c9563608SJanet Adkins continue; 657c9563608SJanet Adkins } 658c9563608SJanet Adkins BMCWEB_LOG_WARNING("Sensor value for {} was unexpectedly {}", 659c9563608SJanet Adkins valueName, *doubleValue); 660c9563608SJanet Adkins continue; 661c9563608SJanet Adkins } 662c9563608SJanet Adkins if (forceToInt) 663c9563608SJanet Adkins { 664c9563608SJanet Adkins sensorJson[key] = static_cast<int64_t>(*doubleValue); 665c9563608SJanet Adkins } 666c9563608SJanet Adkins else 667c9563608SJanet Adkins { 668c9563608SJanet Adkins sensorJson[key] = *doubleValue; 669c9563608SJanet Adkins } 670c9563608SJanet Adkins } 671c9563608SJanet Adkins } 672c9563608SJanet Adkins } 673c9563608SJanet Adkins 6746fe8751cSGeorge Liu /** 6756fe8751cSGeorge Liu * @brief Builds a json sensor excerpt representation of a sensor. 6766fe8751cSGeorge Liu * 6776fe8751cSGeorge Liu * @details This is a wrapper function to provide consistent setting of 6786fe8751cSGeorge Liu * "DataSourceUri" for sensor excerpts and filling of properties. Since sensor 6796fe8751cSGeorge Liu * excerpts usually have just the D-Bus path for the sensor that is accepted 6806fe8751cSGeorge Liu * and used to build "DataSourceUri". 6816fe8751cSGeorge Liu 6826fe8751cSGeorge Liu * @param path The D-Bus path to the sensor to be built 6836fe8751cSGeorge Liu * @param chassisId The Chassis Id for the sensor 6846fe8751cSGeorge Liu * @param chassisSubNode The subnode (e.g. ThermalMetrics) of the sensor 6856fe8751cSGeorge Liu * @param sensorTypeExpected The expected type of the sensor 6866fe8751cSGeorge Liu * @param propertiesDict A dictionary of the properties to build the sensor 6876fe8751cSGeorge Liu * from. 6886fe8751cSGeorge Liu * @param sensorJson The json object to fill 6896fe8751cSGeorge Liu * @returns True if sensorJson object filled. False on any error. 6906fe8751cSGeorge Liu * Caller is responsible for handling error. 6916fe8751cSGeorge Liu */ 6926fe8751cSGeorge Liu inline bool objectExcerptToJson( 6936fe8751cSGeorge Liu const std::string& path, const std::string_view chassisId, 6946fe8751cSGeorge Liu ChassisSubNode chassisSubNode, 6956fe8751cSGeorge Liu const std::optional<std::string>& sensorTypeExpected, 6966fe8751cSGeorge Liu const dbus::utility::DBusPropertiesMap& propertiesDict, 6976fe8751cSGeorge Liu nlohmann::json& sensorJson) 6986fe8751cSGeorge Liu { 6996fe8751cSGeorge Liu if (!isExcerptNode(chassisSubNode)) 7006fe8751cSGeorge Liu { 7016fe8751cSGeorge Liu BMCWEB_LOG_DEBUG("{} is not a sensor excerpt", 7026fe8751cSGeorge Liu chassisSubNodeToString(chassisSubNode)); 7036fe8751cSGeorge Liu return false; 7046fe8751cSGeorge Liu } 7056fe8751cSGeorge Liu 7066fe8751cSGeorge Liu sdbusplus::message::object_path sensorPath(path); 7076fe8751cSGeorge Liu std::string sensorName = sensorPath.filename(); 7086fe8751cSGeorge Liu std::string sensorType = sensorPath.parent_path().filename(); 7096fe8751cSGeorge Liu if (sensorName.empty() || sensorType.empty()) 7106fe8751cSGeorge Liu { 7116fe8751cSGeorge Liu BMCWEB_LOG_DEBUG("Invalid sensor path {}", path); 7126fe8751cSGeorge Liu return false; 7136fe8751cSGeorge Liu } 7146fe8751cSGeorge Liu 7156fe8751cSGeorge Liu if (sensorTypeExpected && (sensorType != *sensorTypeExpected)) 7166fe8751cSGeorge Liu { 7176fe8751cSGeorge Liu BMCWEB_LOG_DEBUG("{} is not expected type {}", path, 7186fe8751cSGeorge Liu *sensorTypeExpected); 7196fe8751cSGeorge Liu return false; 7206fe8751cSGeorge Liu } 7216fe8751cSGeorge Liu 7226fe8751cSGeorge Liu // Sensor excerpts use DataSourceUri to reference full sensor Redfish path 7236fe8751cSGeorge Liu sensorJson["DataSourceUri"] = 7246fe8751cSGeorge Liu boost::urls::format("/redfish/v1/Chassis/{}/Sensors/{}", chassisId, 7256fe8751cSGeorge Liu getSensorId(sensorName, sensorType)); 7266fe8751cSGeorge Liu 7276fe8751cSGeorge Liu // Fill in sensor excerpt properties 7286fe8751cSGeorge Liu objectPropertiesToJson(sensorName, sensorType, chassisSubNode, 7296fe8751cSGeorge Liu propertiesDict, sensorJson, nullptr); 7306fe8751cSGeorge Liu 7316fe8751cSGeorge Liu return true; 7326fe8751cSGeorge Liu } 7336fe8751cSGeorge Liu 7346fe8751cSGeorge Liu // Maps D-Bus: Service, SensorPath 7356fe8751cSGeorge Liu using SensorServicePathMap = std::pair<std::string, std::string>; 7366fe8751cSGeorge Liu using SensorServicePathList = std::vector<SensorServicePathMap>; 7376fe8751cSGeorge Liu 7386fe8751cSGeorge Liu inline void getAllSensorObjects( 7396fe8751cSGeorge Liu const std::string& associatedPath, const std::string& path, 7406fe8751cSGeorge Liu std::span<const std::string_view> interfaces, const int32_t depth, 7416fe8751cSGeorge Liu std::function<void(const boost::system::error_code& ec, 7426fe8751cSGeorge Liu SensorServicePathList&)>&& callback) 7436fe8751cSGeorge Liu { 7446fe8751cSGeorge Liu sdbusplus::message::object_path endpointPath{associatedPath}; 7456fe8751cSGeorge Liu endpointPath /= "all_sensors"; 7466fe8751cSGeorge Liu 7476fe8751cSGeorge Liu dbus::utility::getAssociatedSubTree( 7486fe8751cSGeorge Liu endpointPath, sdbusplus::message::object_path(path), depth, interfaces, 7496fe8751cSGeorge Liu [callback = std::move(callback)]( 7506fe8751cSGeorge Liu const boost::system::error_code& ec, 7516fe8751cSGeorge Liu const dbus::utility::MapperGetSubTreeResponse& subtree) { 7526fe8751cSGeorge Liu SensorServicePathList sensorsServiceAndPath; 7536fe8751cSGeorge Liu 7546fe8751cSGeorge Liu if (ec) 7556fe8751cSGeorge Liu { 7566fe8751cSGeorge Liu callback(ec, sensorsServiceAndPath); 7576fe8751cSGeorge Liu return; 7586fe8751cSGeorge Liu } 7596fe8751cSGeorge Liu 7606fe8751cSGeorge Liu for (const auto& [sensorPath, serviceMaps] : subtree) 7616fe8751cSGeorge Liu { 7626fe8751cSGeorge Liu for (const auto& [service, mapInterfaces] : serviceMaps) 7636fe8751cSGeorge Liu { 7646fe8751cSGeorge Liu sensorsServiceAndPath.emplace_back(service, sensorPath); 7656fe8751cSGeorge Liu } 7666fe8751cSGeorge Liu } 7676fe8751cSGeorge Liu 7686fe8751cSGeorge Liu callback(ec, sensorsServiceAndPath); 7696fe8751cSGeorge Liu }); 7706fe8751cSGeorge Liu } 7716fe8751cSGeorge Liu 7721516c21bSJanet Adkins } // namespace sensor_utils 7731516c21bSJanet Adkins } // namespace redfish 774