11516c21bSJanet Adkins #pragma once 21516c21bSJanet Adkins 3c9563608SJanet Adkins #include "dbus_utility.hpp" 4c9563608SJanet Adkins #include "generated/enums/resource.hpp" 5c9563608SJanet Adkins #include "generated/enums/sensor.hpp" 6c9563608SJanet Adkins #include "generated/enums/thermal.hpp" 7c9563608SJanet Adkins #include "str_utility.hpp" 8c9563608SJanet Adkins #include "utils/dbus_utils.hpp" 9c9563608SJanet Adkins #include "utils/json_utils.hpp" 10c9563608SJanet Adkins 11*6fe8751cSGeorge Liu #include <boost/url/format.hpp> 12c9563608SJanet Adkins #include <sdbusplus/unpack_properties.hpp> 13c9563608SJanet Adkins 141516c21bSJanet Adkins #include <algorithm> 151516c21bSJanet Adkins #include <format> 16*6fe8751cSGeorge Liu #include <functional> 17*6fe8751cSGeorge Liu #include <optional> 181516c21bSJanet Adkins #include <ranges> 191516c21bSJanet Adkins #include <string> 201516c21bSJanet Adkins #include <string_view> 21c9563608SJanet Adkins #include <tuple> 221516c21bSJanet Adkins #include <utility> 231516c21bSJanet Adkins #include <vector> 241516c21bSJanet Adkins 251516c21bSJanet Adkins namespace redfish 261516c21bSJanet Adkins { 271516c21bSJanet Adkins namespace sensor_utils 281516c21bSJanet Adkins { 291516c21bSJanet Adkins 300c728b42SJanet Adkins enum class ChassisSubNode 310c728b42SJanet Adkins { 320c728b42SJanet Adkins powerNode, 330c728b42SJanet Adkins sensorsNode, 340c728b42SJanet Adkins thermalNode, 35*6fe8751cSGeorge Liu thermalMetricsNode, 360c728b42SJanet Adkins unknownNode, 370c728b42SJanet Adkins }; 380c728b42SJanet Adkins 390c728b42SJanet Adkins constexpr std::string_view chassisSubNodeToString(ChassisSubNode subNode) 400c728b42SJanet Adkins { 410c728b42SJanet Adkins switch (subNode) 420c728b42SJanet Adkins { 430c728b42SJanet Adkins case ChassisSubNode::powerNode: 440c728b42SJanet Adkins return "Power"; 450c728b42SJanet Adkins case ChassisSubNode::sensorsNode: 460c728b42SJanet Adkins return "Sensors"; 470c728b42SJanet Adkins case ChassisSubNode::thermalNode: 480c728b42SJanet Adkins return "Thermal"; 49*6fe8751cSGeorge Liu case ChassisSubNode::thermalMetricsNode: 50*6fe8751cSGeorge Liu return "ThermalMetrics"; 510c728b42SJanet Adkins case ChassisSubNode::unknownNode: 520c728b42SJanet Adkins default: 530c728b42SJanet Adkins return ""; 540c728b42SJanet Adkins } 550c728b42SJanet Adkins } 560c728b42SJanet Adkins 570c728b42SJanet Adkins inline ChassisSubNode chassisSubNodeFromString(const std::string& subNodeStr) 580c728b42SJanet Adkins { 590c728b42SJanet Adkins // If none match unknownNode is returned 600c728b42SJanet Adkins ChassisSubNode subNode = ChassisSubNode::unknownNode; 610c728b42SJanet Adkins 620c728b42SJanet Adkins if (subNodeStr == "Power") 630c728b42SJanet Adkins { 640c728b42SJanet Adkins subNode = ChassisSubNode::powerNode; 650c728b42SJanet Adkins } 660c728b42SJanet Adkins else if (subNodeStr == "Sensors") 670c728b42SJanet Adkins { 680c728b42SJanet Adkins subNode = ChassisSubNode::sensorsNode; 690c728b42SJanet Adkins } 700c728b42SJanet Adkins else if (subNodeStr == "Thermal") 710c728b42SJanet Adkins { 720c728b42SJanet Adkins subNode = ChassisSubNode::thermalNode; 730c728b42SJanet Adkins } 74*6fe8751cSGeorge Liu else if (subNodeStr == "ThermalMetrics") 75*6fe8751cSGeorge Liu { 76*6fe8751cSGeorge Liu subNode = ChassisSubNode::thermalMetricsNode; 77*6fe8751cSGeorge Liu } 780c728b42SJanet Adkins 790c728b42SJanet Adkins return subNode; 800c728b42SJanet Adkins } 81c9563608SJanet Adkins 82*6fe8751cSGeorge Liu inline bool isExcerptNode(const ChassisSubNode subNode) 83*6fe8751cSGeorge Liu { 84*6fe8751cSGeorge Liu return (subNode == ChassisSubNode::thermalMetricsNode); 85*6fe8751cSGeorge Liu } 86*6fe8751cSGeorge Liu 87c9563608SJanet Adkins /** 88c9563608SJanet Adkins * Possible states for physical inventory leds 89c9563608SJanet Adkins */ 90c9563608SJanet Adkins enum class LedState 91c9563608SJanet Adkins { 92c9563608SJanet Adkins OFF, 93c9563608SJanet Adkins ON, 94c9563608SJanet Adkins BLINK, 95c9563608SJanet Adkins UNKNOWN 96c9563608SJanet Adkins }; 97c9563608SJanet Adkins 98c9563608SJanet Adkins /** 99c9563608SJanet Adkins * D-Bus inventory item associated with one or more sensors. 100c9563608SJanet Adkins */ 101c9563608SJanet Adkins class InventoryItem 102c9563608SJanet Adkins { 103c9563608SJanet Adkins public: 104c9563608SJanet Adkins explicit InventoryItem(const std::string& objPath) : objectPath(objPath) 105c9563608SJanet Adkins { 106c9563608SJanet Adkins // Set inventory item name to last node of object path 107c9563608SJanet Adkins sdbusplus::message::object_path path(objectPath); 108c9563608SJanet Adkins name = path.filename(); 109c9563608SJanet Adkins if (name.empty()) 110c9563608SJanet Adkins { 111c9563608SJanet Adkins BMCWEB_LOG_ERROR("Failed to find '/' in {}", objectPath); 112c9563608SJanet Adkins } 113c9563608SJanet Adkins } 114c9563608SJanet Adkins 115c9563608SJanet Adkins std::string objectPath; 116c9563608SJanet Adkins std::string name; 117c9563608SJanet Adkins bool isPresent = true; 118c9563608SJanet Adkins bool isFunctional = true; 119c9563608SJanet Adkins bool isPowerSupply = false; 120c9563608SJanet Adkins int powerSupplyEfficiencyPercent = -1; 121c9563608SJanet Adkins std::string manufacturer; 122c9563608SJanet Adkins std::string model; 123c9563608SJanet Adkins std::string partNumber; 124c9563608SJanet Adkins std::string serialNumber; 125c9563608SJanet Adkins std::set<std::string> sensors; 126c9563608SJanet Adkins std::string ledObjectPath; 127c9563608SJanet Adkins LedState ledState = LedState::UNKNOWN; 128c9563608SJanet Adkins }; 129c9563608SJanet Adkins 1301516c21bSJanet Adkins inline std::string getSensorId(std::string_view sensorName, 1311516c21bSJanet Adkins std::string_view sensorType) 1321516c21bSJanet Adkins { 1331516c21bSJanet Adkins std::string normalizedType(sensorType); 1341516c21bSJanet Adkins auto remove = std::ranges::remove(normalizedType, '_'); 1351516c21bSJanet Adkins normalizedType.erase(std::ranges::begin(remove), normalizedType.end()); 1361516c21bSJanet Adkins 1371516c21bSJanet Adkins return std::format("{}_{}", normalizedType, sensorName); 1381516c21bSJanet Adkins } 1391516c21bSJanet Adkins 1401516c21bSJanet Adkins inline std::pair<std::string, std::string> 1411516c21bSJanet Adkins splitSensorNameAndType(std::string_view sensorId) 1421516c21bSJanet Adkins { 1431516c21bSJanet Adkins size_t index = sensorId.find('_'); 1441516c21bSJanet Adkins if (index == std::string::npos) 1451516c21bSJanet Adkins { 1461516c21bSJanet Adkins return std::make_pair<std::string, std::string>("", ""); 1471516c21bSJanet Adkins } 1481516c21bSJanet Adkins std::string sensorType{sensorId.substr(0, index)}; 1491516c21bSJanet Adkins std::string sensorName{sensorId.substr(index + 1)}; 1501516c21bSJanet Adkins // fan_pwm and fan_tach need special handling 1511516c21bSJanet Adkins if (sensorType == "fantach" || sensorType == "fanpwm") 1521516c21bSJanet Adkins { 1531516c21bSJanet Adkins sensorType.insert(3, 1, '_'); 1541516c21bSJanet Adkins } 1551516c21bSJanet Adkins return std::make_pair(sensorType, sensorName); 1561516c21bSJanet Adkins } 1571516c21bSJanet Adkins 158c9563608SJanet Adkins namespace sensors 159c9563608SJanet Adkins { 160c9563608SJanet Adkins inline std::string_view toReadingUnits(std::string_view sensorType) 161c9563608SJanet Adkins { 162c9563608SJanet Adkins if (sensorType == "voltage") 163c9563608SJanet Adkins { 164c9563608SJanet Adkins return "V"; 165c9563608SJanet Adkins } 166c9563608SJanet Adkins if (sensorType == "power") 167c9563608SJanet Adkins { 168c9563608SJanet Adkins return "W"; 169c9563608SJanet Adkins } 170c9563608SJanet Adkins if (sensorType == "current") 171c9563608SJanet Adkins { 172c9563608SJanet Adkins return "A"; 173c9563608SJanet Adkins } 174c9563608SJanet Adkins if (sensorType == "fan_tach") 175c9563608SJanet Adkins { 176c9563608SJanet Adkins return "RPM"; 177c9563608SJanet Adkins } 178c9563608SJanet Adkins if (sensorType == "temperature") 179c9563608SJanet Adkins { 180c9563608SJanet Adkins return "Cel"; 181c9563608SJanet Adkins } 182c9563608SJanet Adkins if (sensorType == "fan_pwm" || sensorType == "utilization" || 183c9563608SJanet Adkins sensorType == "humidity") 184c9563608SJanet Adkins { 185c9563608SJanet Adkins return "%"; 186c9563608SJanet Adkins } 187c9563608SJanet Adkins if (sensorType == "altitude") 188c9563608SJanet Adkins { 189c9563608SJanet Adkins return "m"; 190c9563608SJanet Adkins } 191c9563608SJanet Adkins if (sensorType == "airflow") 192c9563608SJanet Adkins { 193c9563608SJanet Adkins return "cft_i/min"; 194c9563608SJanet Adkins } 195c9563608SJanet Adkins if (sensorType == "energy") 196c9563608SJanet Adkins { 197c9563608SJanet Adkins return "J"; 198c9563608SJanet Adkins } 199c9563608SJanet Adkins return ""; 200c9563608SJanet Adkins } 201c9563608SJanet Adkins 202c9563608SJanet Adkins inline sensor::ReadingType toReadingType(std::string_view sensorType) 203c9563608SJanet Adkins { 204c9563608SJanet Adkins if (sensorType == "voltage") 205c9563608SJanet Adkins { 206c9563608SJanet Adkins return sensor::ReadingType::Voltage; 207c9563608SJanet Adkins } 208c9563608SJanet Adkins if (sensorType == "power") 209c9563608SJanet Adkins { 210c9563608SJanet Adkins return sensor::ReadingType::Power; 211c9563608SJanet Adkins } 212c9563608SJanet Adkins if (sensorType == "current") 213c9563608SJanet Adkins { 214c9563608SJanet Adkins return sensor::ReadingType::Current; 215c9563608SJanet Adkins } 216c9563608SJanet Adkins if (sensorType == "fan_tach") 217c9563608SJanet Adkins { 218c9563608SJanet Adkins return sensor::ReadingType::Rotational; 219c9563608SJanet Adkins } 220c9563608SJanet Adkins if (sensorType == "temperature") 221c9563608SJanet Adkins { 222c9563608SJanet Adkins return sensor::ReadingType::Temperature; 223c9563608SJanet Adkins } 224c9563608SJanet Adkins if (sensorType == "fan_pwm" || sensorType == "utilization") 225c9563608SJanet Adkins { 226c9563608SJanet Adkins return sensor::ReadingType::Percent; 227c9563608SJanet Adkins } 228c9563608SJanet Adkins if (sensorType == "humidity") 229c9563608SJanet Adkins { 230c9563608SJanet Adkins return sensor::ReadingType::Humidity; 231c9563608SJanet Adkins } 232c9563608SJanet Adkins if (sensorType == "altitude") 233c9563608SJanet Adkins { 234c9563608SJanet Adkins return sensor::ReadingType::Altitude; 235c9563608SJanet Adkins } 236c9563608SJanet Adkins if (sensorType == "airflow") 237c9563608SJanet Adkins { 238c9563608SJanet Adkins return sensor::ReadingType::AirFlow; 239c9563608SJanet Adkins } 240c9563608SJanet Adkins if (sensorType == "energy") 241c9563608SJanet Adkins { 242c9563608SJanet Adkins return sensor::ReadingType::EnergyJoules; 243c9563608SJanet Adkins } 244c9563608SJanet Adkins return sensor::ReadingType::Invalid; 245c9563608SJanet Adkins } 246c9563608SJanet Adkins 247c9563608SJanet Adkins } // namespace sensors 248c9563608SJanet Adkins 249c9563608SJanet Adkins /** 250c9563608SJanet Adkins * @brief Returns the Redfish State value for the specified inventory item. 251c9563608SJanet Adkins * @param inventoryItem D-Bus inventory item associated with a sensor. 252c9563608SJanet Adkins * @param sensorAvailable Boolean representing if D-Bus sensor is marked as 253c9563608SJanet Adkins * available. 254c9563608SJanet Adkins * @return State value for inventory item. 255c9563608SJanet Adkins */ 256c9563608SJanet Adkins inline resource::State getState(const InventoryItem* inventoryItem, 257c9563608SJanet Adkins const bool sensorAvailable) 258c9563608SJanet Adkins { 259c9563608SJanet Adkins if ((inventoryItem != nullptr) && !(inventoryItem->isPresent)) 260c9563608SJanet Adkins { 261c9563608SJanet Adkins return resource::State::Absent; 262c9563608SJanet Adkins } 263c9563608SJanet Adkins 264c9563608SJanet Adkins if (!sensorAvailable) 265c9563608SJanet Adkins { 266c9563608SJanet Adkins return resource::State::UnavailableOffline; 267c9563608SJanet Adkins } 268c9563608SJanet Adkins 269c9563608SJanet Adkins return resource::State::Enabled; 270c9563608SJanet Adkins } 271c9563608SJanet Adkins 272c9563608SJanet Adkins /** 273c9563608SJanet Adkins * @brief Returns the Redfish Health value for the specified sensor. 274c9563608SJanet Adkins * @param sensorJson Sensor JSON object. 275c9563608SJanet Adkins * @param valuesDict Map of all sensor DBus values. 276c9563608SJanet Adkins * @param inventoryItem D-Bus inventory item associated with the sensor. Will 277c9563608SJanet Adkins * be nullptr if no associated inventory item was found. 278c9563608SJanet Adkins * @return Health value for sensor. 279c9563608SJanet Adkins */ 280c9563608SJanet Adkins inline std::string getHealth(nlohmann::json& sensorJson, 281c9563608SJanet Adkins const dbus::utility::DBusPropertiesMap& valuesDict, 282c9563608SJanet Adkins const InventoryItem* inventoryItem) 283c9563608SJanet Adkins { 284c9563608SJanet Adkins // Get current health value (if any) in the sensor JSON object. Some JSON 285c9563608SJanet Adkins // objects contain multiple sensors (such as PowerSupplies). We want to set 286c9563608SJanet Adkins // the overall health to be the most severe of any of the sensors. 287c9563608SJanet Adkins std::string currentHealth; 288c9563608SJanet Adkins auto statusIt = sensorJson.find("Status"); 289c9563608SJanet Adkins if (statusIt != sensorJson.end()) 290c9563608SJanet Adkins { 291c9563608SJanet Adkins auto healthIt = statusIt->find("Health"); 292c9563608SJanet Adkins if (healthIt != statusIt->end()) 293c9563608SJanet Adkins { 294c9563608SJanet Adkins std::string* health = healthIt->get_ptr<std::string*>(); 295c9563608SJanet Adkins if (health != nullptr) 296c9563608SJanet Adkins { 297c9563608SJanet Adkins currentHealth = *health; 298c9563608SJanet Adkins } 299c9563608SJanet Adkins } 300c9563608SJanet Adkins } 301c9563608SJanet Adkins 302c9563608SJanet Adkins // If current health in JSON object is already Critical, return that. This 303c9563608SJanet Adkins // should override the sensor health, which might be less severe. 304c9563608SJanet Adkins if (currentHealth == "Critical") 305c9563608SJanet Adkins { 306c9563608SJanet Adkins return "Critical"; 307c9563608SJanet Adkins } 308c9563608SJanet Adkins 309c9563608SJanet Adkins const bool* criticalAlarmHigh = nullptr; 310c9563608SJanet Adkins const bool* criticalAlarmLow = nullptr; 311c9563608SJanet Adkins const bool* warningAlarmHigh = nullptr; 312c9563608SJanet Adkins const bool* warningAlarmLow = nullptr; 313c9563608SJanet Adkins 314c9563608SJanet Adkins const bool success = sdbusplus::unpackPropertiesNoThrow( 315c9563608SJanet Adkins dbus_utils::UnpackErrorPrinter(), valuesDict, "CriticalAlarmHigh", 316c9563608SJanet Adkins criticalAlarmHigh, "CriticalAlarmLow", criticalAlarmLow, 317c9563608SJanet Adkins "WarningAlarmHigh", warningAlarmHigh, "WarningAlarmLow", 318c9563608SJanet Adkins warningAlarmLow); 319c9563608SJanet Adkins 320c9563608SJanet Adkins if (success) 321c9563608SJanet Adkins { 322c9563608SJanet Adkins // Check if sensor has critical threshold alarm 323c9563608SJanet Adkins if ((criticalAlarmHigh != nullptr && *criticalAlarmHigh) || 324c9563608SJanet Adkins (criticalAlarmLow != nullptr && *criticalAlarmLow)) 325c9563608SJanet Adkins { 326c9563608SJanet Adkins return "Critical"; 327c9563608SJanet Adkins } 328c9563608SJanet Adkins } 329c9563608SJanet Adkins 330c9563608SJanet Adkins // Check if associated inventory item is not functional 331c9563608SJanet Adkins if ((inventoryItem != nullptr) && !(inventoryItem->isFunctional)) 332c9563608SJanet Adkins { 333c9563608SJanet Adkins return "Critical"; 334c9563608SJanet Adkins } 335c9563608SJanet Adkins 336c9563608SJanet Adkins // If current health in JSON object is already Warning, return that. This 337c9563608SJanet Adkins // should override the sensor status, which might be less severe. 338c9563608SJanet Adkins if (currentHealth == "Warning") 339c9563608SJanet Adkins { 340c9563608SJanet Adkins return "Warning"; 341c9563608SJanet Adkins } 342c9563608SJanet Adkins 343c9563608SJanet Adkins if (success) 344c9563608SJanet Adkins { 345c9563608SJanet Adkins // Check if sensor has warning threshold alarm 346c9563608SJanet Adkins if ((warningAlarmHigh != nullptr && *warningAlarmHigh) || 347c9563608SJanet Adkins (warningAlarmLow != nullptr && *warningAlarmLow)) 348c9563608SJanet Adkins { 349c9563608SJanet Adkins return "Warning"; 350c9563608SJanet Adkins } 351c9563608SJanet Adkins } 352c9563608SJanet Adkins 353c9563608SJanet Adkins return "OK"; 354c9563608SJanet Adkins } 355c9563608SJanet Adkins 356c9563608SJanet Adkins inline void setLedState(nlohmann::json& sensorJson, 357c9563608SJanet Adkins const InventoryItem* inventoryItem) 358c9563608SJanet Adkins { 359c9563608SJanet Adkins if (inventoryItem != nullptr && !inventoryItem->ledObjectPath.empty()) 360c9563608SJanet Adkins { 361c9563608SJanet Adkins switch (inventoryItem->ledState) 362c9563608SJanet Adkins { 363c9563608SJanet Adkins case LedState::OFF: 364c9563608SJanet Adkins sensorJson["IndicatorLED"] = resource::IndicatorLED::Off; 365c9563608SJanet Adkins break; 366c9563608SJanet Adkins case LedState::ON: 367c9563608SJanet Adkins sensorJson["IndicatorLED"] = resource::IndicatorLED::Lit; 368c9563608SJanet Adkins break; 369c9563608SJanet Adkins case LedState::BLINK: 370c9563608SJanet Adkins sensorJson["IndicatorLED"] = resource::IndicatorLED::Blinking; 371c9563608SJanet Adkins break; 372c9563608SJanet Adkins default: 373c9563608SJanet Adkins break; 374c9563608SJanet Adkins } 375c9563608SJanet Adkins } 376c9563608SJanet Adkins } 377c9563608SJanet Adkins 378c9563608SJanet Adkins /** 379c9563608SJanet Adkins * @brief Builds a json sensor representation of a sensor. 380c9563608SJanet Adkins * @param sensorName The name of the sensor to be built 381c9563608SJanet Adkins * @param sensorType The type (temperature, fan_tach, etc) of the sensor to 382c9563608SJanet Adkins * build 383c9563608SJanet Adkins * @param chassisSubNode The subnode (thermal, sensor, etc) of the sensor 384c9563608SJanet Adkins * @param propertiesDict A dictionary of the properties to build the sensor 385c9563608SJanet Adkins * from. 386c9563608SJanet Adkins * @param sensorJson The json object to fill 387c9563608SJanet Adkins * @param inventoryItem D-Bus inventory item associated with the sensor. Will 388c9563608SJanet Adkins * be nullptr if no associated inventory item was found. 389c9563608SJanet Adkins */ 390c9563608SJanet Adkins inline void objectPropertiesToJson( 391c9563608SJanet Adkins std::string_view sensorName, std::string_view sensorType, 3920c728b42SJanet Adkins ChassisSubNode chassisSubNode, 393c9563608SJanet Adkins const dbus::utility::DBusPropertiesMap& propertiesDict, 394c9563608SJanet Adkins nlohmann::json& sensorJson, InventoryItem* inventoryItem) 395c9563608SJanet Adkins { 396*6fe8751cSGeorge Liu // Parameter to set to override the type we get from dbus, and force it to 397*6fe8751cSGeorge Liu // int, regardless of what is available. This is used for schemas like fan, 398*6fe8751cSGeorge Liu // that require integers, not floats. 399*6fe8751cSGeorge Liu bool forceToInt = false; 400*6fe8751cSGeorge Liu 401*6fe8751cSGeorge Liu nlohmann::json::json_pointer unit("/Reading"); 402*6fe8751cSGeorge Liu 403*6fe8751cSGeorge Liu // This ChassisSubNode builds sensor excerpts 404*6fe8751cSGeorge Liu bool isExcerpt = isExcerptNode(chassisSubNode); 405*6fe8751cSGeorge Liu 406*6fe8751cSGeorge Liu /* Sensor excerpts use different keys to reference the sensor. These are 407*6fe8751cSGeorge Liu * built by the caller. 408*6fe8751cSGeorge Liu * Additionally they don't include these additional properties. 409*6fe8751cSGeorge Liu */ 410*6fe8751cSGeorge Liu if (!isExcerpt) 411*6fe8751cSGeorge Liu { 4120c728b42SJanet Adkins if (chassisSubNode == ChassisSubNode::sensorsNode) 413c9563608SJanet Adkins { 414c9563608SJanet Adkins std::string subNodeEscaped = getSensorId(sensorName, sensorType); 415c9563608SJanet Adkins // For sensors in SensorCollection we set Id instead of MemberId, 416c9563608SJanet Adkins // including power sensors. 417c9563608SJanet Adkins sensorJson["Id"] = std::move(subNodeEscaped); 418c9563608SJanet Adkins 419c9563608SJanet Adkins std::string sensorNameEs(sensorName); 420c9563608SJanet Adkins std::replace(sensorNameEs.begin(), sensorNameEs.end(), '_', ' '); 421c9563608SJanet Adkins sensorJson["Name"] = std::move(sensorNameEs); 422c9563608SJanet Adkins } 423c9563608SJanet Adkins else if (sensorType != "power") 424c9563608SJanet Adkins { 425*6fe8751cSGeorge Liu // Set MemberId and Name for non-power sensors. For PowerSupplies 426*6fe8751cSGeorge Liu // and PowerControl, those properties have more general values 427*6fe8751cSGeorge Liu // because multiple sensors can be stored in the same JSON object. 428c9563608SJanet Adkins std::string sensorNameEs(sensorName); 429c9563608SJanet Adkins std::replace(sensorNameEs.begin(), sensorNameEs.end(), '_', ' '); 430c9563608SJanet Adkins sensorJson["Name"] = std::move(sensorNameEs); 431c9563608SJanet Adkins } 432c9563608SJanet Adkins 433c9563608SJanet Adkins const bool* checkAvailable = nullptr; 434c9563608SJanet Adkins bool available = true; 435c9563608SJanet Adkins const bool success = sdbusplus::unpackPropertiesNoThrow( 436c9563608SJanet Adkins dbus_utils::UnpackErrorPrinter(), propertiesDict, "Available", 437c9563608SJanet Adkins checkAvailable); 438c9563608SJanet Adkins if (!success) 439c9563608SJanet Adkins { 440c9563608SJanet Adkins messages::internalError(); 441c9563608SJanet Adkins } 442c9563608SJanet Adkins if (checkAvailable != nullptr) 443c9563608SJanet Adkins { 444c9563608SJanet Adkins available = *checkAvailable; 445c9563608SJanet Adkins } 446c9563608SJanet Adkins 447c9563608SJanet Adkins sensorJson["Status"]["State"] = getState(inventoryItem, available); 448c9563608SJanet Adkins sensorJson["Status"]["Health"] = 449c9563608SJanet Adkins getHealth(sensorJson, propertiesDict, inventoryItem); 450c9563608SJanet Adkins 4510c728b42SJanet Adkins if (chassisSubNode == ChassisSubNode::sensorsNode) 452c9563608SJanet Adkins { 453c9563608SJanet Adkins sensorJson["@odata.type"] = "#Sensor.v1_2_0.Sensor"; 454c9563608SJanet Adkins 455*6fe8751cSGeorge Liu sensor::ReadingType readingType = 456*6fe8751cSGeorge Liu sensors::toReadingType(sensorType); 457c9563608SJanet Adkins if (readingType == sensor::ReadingType::Invalid) 458c9563608SJanet Adkins { 459c9563608SJanet Adkins BMCWEB_LOG_ERROR("Redfish cannot map reading type for {}", 460c9563608SJanet Adkins sensorType); 461c9563608SJanet Adkins } 462c9563608SJanet Adkins else 463c9563608SJanet Adkins { 464c9563608SJanet Adkins sensorJson["ReadingType"] = readingType; 465c9563608SJanet Adkins } 466c9563608SJanet Adkins 467c9563608SJanet Adkins std::string_view readingUnits = sensors::toReadingUnits(sensorType); 468c9563608SJanet Adkins if (readingUnits.empty()) 469c9563608SJanet Adkins { 470c9563608SJanet Adkins BMCWEB_LOG_ERROR("Redfish cannot map reading unit for {}", 471c9563608SJanet Adkins sensorType); 472c9563608SJanet Adkins } 473c9563608SJanet Adkins else 474c9563608SJanet Adkins { 475c9563608SJanet Adkins sensorJson["ReadingUnits"] = readingUnits; 476c9563608SJanet Adkins } 477c9563608SJanet Adkins } 478c9563608SJanet Adkins else if (sensorType == "temperature") 479c9563608SJanet Adkins { 480c9563608SJanet Adkins unit = "/ReadingCelsius"_json_pointer; 481c9563608SJanet Adkins sensorJson["@odata.type"] = "#Thermal.v1_3_0.Temperature"; 482c9563608SJanet Adkins // TODO(ed) Documentation says that path should be type fan_tach, 483c9563608SJanet Adkins // implementation seems to implement fan 484c9563608SJanet Adkins } 485c9563608SJanet Adkins else if (sensorType == "fan" || sensorType == "fan_tach") 486c9563608SJanet Adkins { 487c9563608SJanet Adkins unit = "/Reading"_json_pointer; 488c9563608SJanet Adkins sensorJson["ReadingUnits"] = thermal::ReadingUnits::RPM; 489c9563608SJanet Adkins sensorJson["@odata.type"] = "#Thermal.v1_3_0.Fan"; 490c9563608SJanet Adkins setLedState(sensorJson, inventoryItem); 491c9563608SJanet Adkins forceToInt = true; 492c9563608SJanet Adkins } 493c9563608SJanet Adkins else if (sensorType == "fan_pwm") 494c9563608SJanet Adkins { 495c9563608SJanet Adkins unit = "/Reading"_json_pointer; 496c9563608SJanet Adkins sensorJson["ReadingUnits"] = thermal::ReadingUnits::Percent; 497c9563608SJanet Adkins sensorJson["@odata.type"] = "#Thermal.v1_3_0.Fan"; 498c9563608SJanet Adkins setLedState(sensorJson, inventoryItem); 499c9563608SJanet Adkins forceToInt = true; 500c9563608SJanet Adkins } 501c9563608SJanet Adkins else if (sensorType == "voltage") 502c9563608SJanet Adkins { 503c9563608SJanet Adkins unit = "/ReadingVolts"_json_pointer; 504c9563608SJanet Adkins sensorJson["@odata.type"] = "#Power.v1_0_0.Voltage"; 505c9563608SJanet Adkins } 506c9563608SJanet Adkins else if (sensorType == "power") 507c9563608SJanet Adkins { 508c9563608SJanet Adkins std::string lower; 509c9563608SJanet Adkins std::ranges::transform(sensorName, std::back_inserter(lower), 510c9563608SJanet Adkins bmcweb::asciiToLower); 511c9563608SJanet Adkins if (lower == "total_power") 512c9563608SJanet Adkins { 513c9563608SJanet Adkins sensorJson["@odata.type"] = "#Power.v1_0_0.PowerControl"; 514c9563608SJanet Adkins // Put multiple "sensors" into a single PowerControl, so have 515c9563608SJanet Adkins // generic names for MemberId and Name. Follows Redfish mockup. 516c9563608SJanet Adkins sensorJson["MemberId"] = "0"; 517c9563608SJanet Adkins sensorJson["Name"] = "Chassis Power Control"; 518c9563608SJanet Adkins unit = "/PowerConsumedWatts"_json_pointer; 519c9563608SJanet Adkins } 520c9563608SJanet Adkins else if (lower.find("input") != std::string::npos) 521c9563608SJanet Adkins { 522c9563608SJanet Adkins unit = "/PowerInputWatts"_json_pointer; 523c9563608SJanet Adkins } 524c9563608SJanet Adkins else 525c9563608SJanet Adkins { 526c9563608SJanet Adkins unit = "/PowerOutputWatts"_json_pointer; 527c9563608SJanet Adkins } 528c9563608SJanet Adkins } 529c9563608SJanet Adkins else 530c9563608SJanet Adkins { 531*6fe8751cSGeorge Liu BMCWEB_LOG_ERROR("Redfish cannot map object type for {}", 532*6fe8751cSGeorge Liu sensorName); 533c9563608SJanet Adkins return; 534c9563608SJanet Adkins } 535*6fe8751cSGeorge Liu } 536*6fe8751cSGeorge Liu 537c9563608SJanet Adkins // Map of dbus interface name, dbus property name and redfish property_name 538c9563608SJanet Adkins std::vector< 539c9563608SJanet Adkins std::tuple<const char*, const char*, nlohmann::json::json_pointer>> 540c9563608SJanet Adkins properties; 541c9563608SJanet Adkins 542c9563608SJanet Adkins properties.emplace_back("xyz.openbmc_project.Sensor.Value", "Value", unit); 543c9563608SJanet Adkins 544*6fe8751cSGeorge Liu if (!isExcerpt) 545*6fe8751cSGeorge Liu { 5460c728b42SJanet Adkins if (chassisSubNode == ChassisSubNode::sensorsNode) 547c9563608SJanet Adkins { 548c9563608SJanet Adkins properties.emplace_back( 549c9563608SJanet Adkins "xyz.openbmc_project.Sensor.Threshold.Warning", "WarningHigh", 550c9563608SJanet Adkins "/Thresholds/UpperCaution/Reading"_json_pointer); 551c9563608SJanet Adkins properties.emplace_back( 552c9563608SJanet Adkins "xyz.openbmc_project.Sensor.Threshold.Warning", "WarningLow", 553c9563608SJanet Adkins "/Thresholds/LowerCaution/Reading"_json_pointer); 554c9563608SJanet Adkins properties.emplace_back( 555c9563608SJanet Adkins "xyz.openbmc_project.Sensor.Threshold.Critical", "CriticalHigh", 556c9563608SJanet Adkins "/Thresholds/UpperCritical/Reading"_json_pointer); 557c9563608SJanet Adkins properties.emplace_back( 558c9563608SJanet Adkins "xyz.openbmc_project.Sensor.Threshold.Critical", "CriticalLow", 559c9563608SJanet Adkins "/Thresholds/LowerCritical/Reading"_json_pointer); 560cd5a898fSJanet Adkins 561cd5a898fSJanet Adkins /* Add additional properties specific to sensorType */ 562cd5a898fSJanet Adkins if (sensorType == "fan_tach") 563cd5a898fSJanet Adkins { 564*6fe8751cSGeorge Liu properties.emplace_back("xyz.openbmc_project.Sensor.Value", 565*6fe8751cSGeorge Liu "Value", "/SpeedRPM"_json_pointer); 566cd5a898fSJanet Adkins } 567c9563608SJanet Adkins } 568c9563608SJanet Adkins else if (sensorType != "power") 569c9563608SJanet Adkins { 570*6fe8751cSGeorge Liu properties.emplace_back( 571*6fe8751cSGeorge Liu "xyz.openbmc_project.Sensor.Threshold.Warning", "WarningHigh", 572c9563608SJanet Adkins "/UpperThresholdNonCritical"_json_pointer); 573*6fe8751cSGeorge Liu properties.emplace_back( 574*6fe8751cSGeorge Liu "xyz.openbmc_project.Sensor.Threshold.Warning", "WarningLow", 575c9563608SJanet Adkins "/LowerThresholdNonCritical"_json_pointer); 576*6fe8751cSGeorge Liu properties.emplace_back( 577*6fe8751cSGeorge Liu "xyz.openbmc_project.Sensor.Threshold.Critical", "CriticalHigh", 578c9563608SJanet Adkins "/UpperThresholdCritical"_json_pointer); 579*6fe8751cSGeorge Liu properties.emplace_back( 580*6fe8751cSGeorge Liu "xyz.openbmc_project.Sensor.Threshold.Critical", "CriticalLow", 581c9563608SJanet Adkins "/LowerThresholdCritical"_json_pointer); 582c9563608SJanet Adkins } 583c9563608SJanet Adkins 584c9563608SJanet Adkins // TODO Need to get UpperThresholdFatal and LowerThresholdFatal 585c9563608SJanet Adkins 5860c728b42SJanet Adkins if (chassisSubNode == ChassisSubNode::sensorsNode) 587c9563608SJanet Adkins { 588*6fe8751cSGeorge Liu properties.emplace_back("xyz.openbmc_project.Sensor.Value", 589*6fe8751cSGeorge Liu "MinValue", 590c9563608SJanet Adkins "/ReadingRangeMin"_json_pointer); 591*6fe8751cSGeorge Liu properties.emplace_back("xyz.openbmc_project.Sensor.Value", 592*6fe8751cSGeorge Liu "MaxValue", 593c9563608SJanet Adkins "/ReadingRangeMax"_json_pointer); 594c9563608SJanet Adkins properties.emplace_back("xyz.openbmc_project.Sensor.Accuracy", 595c9563608SJanet Adkins "Accuracy", "/Accuracy"_json_pointer); 596c9563608SJanet Adkins } 597c9563608SJanet Adkins else if (sensorType == "temperature") 598c9563608SJanet Adkins { 599*6fe8751cSGeorge Liu properties.emplace_back("xyz.openbmc_project.Sensor.Value", 600*6fe8751cSGeorge Liu "MinValue", 601c9563608SJanet Adkins "/MinReadingRangeTemp"_json_pointer); 602*6fe8751cSGeorge Liu properties.emplace_back("xyz.openbmc_project.Sensor.Value", 603*6fe8751cSGeorge Liu "MaxValue", 604c9563608SJanet Adkins "/MaxReadingRangeTemp"_json_pointer); 605c9563608SJanet Adkins } 606c9563608SJanet Adkins else if (sensorType != "power") 607c9563608SJanet Adkins { 608*6fe8751cSGeorge Liu properties.emplace_back("xyz.openbmc_project.Sensor.Value", 609*6fe8751cSGeorge Liu "MinValue", 610c9563608SJanet Adkins "/MinReadingRange"_json_pointer); 611*6fe8751cSGeorge Liu properties.emplace_back("xyz.openbmc_project.Sensor.Value", 612*6fe8751cSGeorge Liu "MaxValue", 613c9563608SJanet Adkins "/MaxReadingRange"_json_pointer); 614c9563608SJanet Adkins } 615*6fe8751cSGeorge Liu } 616c9563608SJanet Adkins 617c9563608SJanet Adkins for (const std::tuple<const char*, const char*, 618c9563608SJanet Adkins nlohmann::json::json_pointer>& p : properties) 619c9563608SJanet Adkins { 620c9563608SJanet Adkins for (const auto& [valueName, valueVariant] : propertiesDict) 621c9563608SJanet Adkins { 622c9563608SJanet Adkins if (valueName != std::get<1>(p)) 623c9563608SJanet Adkins { 624c9563608SJanet Adkins continue; 625c9563608SJanet Adkins } 626c9563608SJanet Adkins 627c9563608SJanet Adkins // The property we want to set may be nested json, so use 628c9563608SJanet Adkins // a json_pointer for easy indexing into the json structure. 629c9563608SJanet Adkins const nlohmann::json::json_pointer& key = std::get<2>(p); 630c9563608SJanet Adkins 631c9563608SJanet Adkins const double* doubleValue = std::get_if<double>(&valueVariant); 632c9563608SJanet Adkins if (doubleValue == nullptr) 633c9563608SJanet Adkins { 634c9563608SJanet Adkins BMCWEB_LOG_ERROR("Got value interface that wasn't double"); 635c9563608SJanet Adkins continue; 636c9563608SJanet Adkins } 637c9563608SJanet Adkins if (!std::isfinite(*doubleValue)) 638c9563608SJanet Adkins { 639c9563608SJanet Adkins if (valueName == "Value") 640c9563608SJanet Adkins { 641c9563608SJanet Adkins // Readings are allowed to be NAN for unavailable; coerce 642c9563608SJanet Adkins // them to null in the json response. 643c9563608SJanet Adkins sensorJson[key] = nullptr; 644c9563608SJanet Adkins continue; 645c9563608SJanet Adkins } 646c9563608SJanet Adkins BMCWEB_LOG_WARNING("Sensor value for {} was unexpectedly {}", 647c9563608SJanet Adkins valueName, *doubleValue); 648c9563608SJanet Adkins continue; 649c9563608SJanet Adkins } 650c9563608SJanet Adkins if (forceToInt) 651c9563608SJanet Adkins { 652c9563608SJanet Adkins sensorJson[key] = static_cast<int64_t>(*doubleValue); 653c9563608SJanet Adkins } 654c9563608SJanet Adkins else 655c9563608SJanet Adkins { 656c9563608SJanet Adkins sensorJson[key] = *doubleValue; 657c9563608SJanet Adkins } 658c9563608SJanet Adkins } 659c9563608SJanet Adkins } 660c9563608SJanet Adkins } 661c9563608SJanet Adkins 662*6fe8751cSGeorge Liu /** 663*6fe8751cSGeorge Liu * @brief Builds a json sensor excerpt representation of a sensor. 664*6fe8751cSGeorge Liu * 665*6fe8751cSGeorge Liu * @details This is a wrapper function to provide consistent setting of 666*6fe8751cSGeorge Liu * "DataSourceUri" for sensor excerpts and filling of properties. Since sensor 667*6fe8751cSGeorge Liu * excerpts usually have just the D-Bus path for the sensor that is accepted 668*6fe8751cSGeorge Liu * and used to build "DataSourceUri". 669*6fe8751cSGeorge Liu 670*6fe8751cSGeorge Liu * @param path The D-Bus path to the sensor to be built 671*6fe8751cSGeorge Liu * @param chassisId The Chassis Id for the sensor 672*6fe8751cSGeorge Liu * @param chassisSubNode The subnode (e.g. ThermalMetrics) of the sensor 673*6fe8751cSGeorge Liu * @param sensorTypeExpected The expected type of the sensor 674*6fe8751cSGeorge Liu * @param propertiesDict A dictionary of the properties to build the sensor 675*6fe8751cSGeorge Liu * from. 676*6fe8751cSGeorge Liu * @param sensorJson The json object to fill 677*6fe8751cSGeorge Liu * @returns True if sensorJson object filled. False on any error. 678*6fe8751cSGeorge Liu * Caller is responsible for handling error. 679*6fe8751cSGeorge Liu */ 680*6fe8751cSGeorge Liu inline bool objectExcerptToJson( 681*6fe8751cSGeorge Liu const std::string& path, const std::string_view chassisId, 682*6fe8751cSGeorge Liu ChassisSubNode chassisSubNode, 683*6fe8751cSGeorge Liu const std::optional<std::string>& sensorTypeExpected, 684*6fe8751cSGeorge Liu const dbus::utility::DBusPropertiesMap& propertiesDict, 685*6fe8751cSGeorge Liu nlohmann::json& sensorJson) 686*6fe8751cSGeorge Liu { 687*6fe8751cSGeorge Liu if (!isExcerptNode(chassisSubNode)) 688*6fe8751cSGeorge Liu { 689*6fe8751cSGeorge Liu BMCWEB_LOG_DEBUG("{} is not a sensor excerpt", 690*6fe8751cSGeorge Liu chassisSubNodeToString(chassisSubNode)); 691*6fe8751cSGeorge Liu return false; 692*6fe8751cSGeorge Liu } 693*6fe8751cSGeorge Liu 694*6fe8751cSGeorge Liu sdbusplus::message::object_path sensorPath(path); 695*6fe8751cSGeorge Liu std::string sensorName = sensorPath.filename(); 696*6fe8751cSGeorge Liu std::string sensorType = sensorPath.parent_path().filename(); 697*6fe8751cSGeorge Liu if (sensorName.empty() || sensorType.empty()) 698*6fe8751cSGeorge Liu { 699*6fe8751cSGeorge Liu BMCWEB_LOG_DEBUG("Invalid sensor path {}", path); 700*6fe8751cSGeorge Liu return false; 701*6fe8751cSGeorge Liu } 702*6fe8751cSGeorge Liu 703*6fe8751cSGeorge Liu if (sensorTypeExpected && (sensorType != *sensorTypeExpected)) 704*6fe8751cSGeorge Liu { 705*6fe8751cSGeorge Liu BMCWEB_LOG_DEBUG("{} is not expected type {}", path, 706*6fe8751cSGeorge Liu *sensorTypeExpected); 707*6fe8751cSGeorge Liu return false; 708*6fe8751cSGeorge Liu } 709*6fe8751cSGeorge Liu 710*6fe8751cSGeorge Liu // Sensor excerpts use DataSourceUri to reference full sensor Redfish path 711*6fe8751cSGeorge Liu sensorJson["DataSourceUri"] = 712*6fe8751cSGeorge Liu boost::urls::format("/redfish/v1/Chassis/{}/Sensors/{}", chassisId, 713*6fe8751cSGeorge Liu getSensorId(sensorName, sensorType)); 714*6fe8751cSGeorge Liu 715*6fe8751cSGeorge Liu // Fill in sensor excerpt properties 716*6fe8751cSGeorge Liu objectPropertiesToJson(sensorName, sensorType, chassisSubNode, 717*6fe8751cSGeorge Liu propertiesDict, sensorJson, nullptr); 718*6fe8751cSGeorge Liu 719*6fe8751cSGeorge Liu return true; 720*6fe8751cSGeorge Liu } 721*6fe8751cSGeorge Liu 722*6fe8751cSGeorge Liu // Maps D-Bus: Service, SensorPath 723*6fe8751cSGeorge Liu using SensorServicePathMap = std::pair<std::string, std::string>; 724*6fe8751cSGeorge Liu using SensorServicePathList = std::vector<SensorServicePathMap>; 725*6fe8751cSGeorge Liu 726*6fe8751cSGeorge Liu inline void getAllSensorObjects( 727*6fe8751cSGeorge Liu const std::string& associatedPath, const std::string& path, 728*6fe8751cSGeorge Liu std::span<const std::string_view> interfaces, const int32_t depth, 729*6fe8751cSGeorge Liu std::function<void(const boost::system::error_code& ec, 730*6fe8751cSGeorge Liu SensorServicePathList&)>&& callback) 731*6fe8751cSGeorge Liu { 732*6fe8751cSGeorge Liu sdbusplus::message::object_path endpointPath{associatedPath}; 733*6fe8751cSGeorge Liu endpointPath /= "all_sensors"; 734*6fe8751cSGeorge Liu 735*6fe8751cSGeorge Liu dbus::utility::getAssociatedSubTree( 736*6fe8751cSGeorge Liu endpointPath, sdbusplus::message::object_path(path), depth, interfaces, 737*6fe8751cSGeorge Liu [callback = std::move(callback)]( 738*6fe8751cSGeorge Liu const boost::system::error_code& ec, 739*6fe8751cSGeorge Liu const dbus::utility::MapperGetSubTreeResponse& subtree) { 740*6fe8751cSGeorge Liu SensorServicePathList sensorsServiceAndPath; 741*6fe8751cSGeorge Liu 742*6fe8751cSGeorge Liu if (ec) 743*6fe8751cSGeorge Liu { 744*6fe8751cSGeorge Liu callback(ec, sensorsServiceAndPath); 745*6fe8751cSGeorge Liu return; 746*6fe8751cSGeorge Liu } 747*6fe8751cSGeorge Liu 748*6fe8751cSGeorge Liu for (const auto& [sensorPath, serviceMaps] : subtree) 749*6fe8751cSGeorge Liu { 750*6fe8751cSGeorge Liu for (const auto& [service, mapInterfaces] : serviceMaps) 751*6fe8751cSGeorge Liu { 752*6fe8751cSGeorge Liu sensorsServiceAndPath.emplace_back(service, sensorPath); 753*6fe8751cSGeorge Liu } 754*6fe8751cSGeorge Liu } 755*6fe8751cSGeorge Liu 756*6fe8751cSGeorge Liu callback(ec, sensorsServiceAndPath); 757*6fe8751cSGeorge Liu }); 758*6fe8751cSGeorge Liu } 759*6fe8751cSGeorge Liu 7601516c21bSJanet Adkins } // namespace sensor_utils 7611516c21bSJanet Adkins } // namespace redfish 762