11516c21bSJanet Adkins #pragma once 21516c21bSJanet Adkins 3*c9563608SJanet Adkins #include "dbus_utility.hpp" 4*c9563608SJanet Adkins #include "generated/enums/resource.hpp" 5*c9563608SJanet Adkins #include "generated/enums/sensor.hpp" 6*c9563608SJanet Adkins #include "generated/enums/thermal.hpp" 7*c9563608SJanet Adkins #include "str_utility.hpp" 8*c9563608SJanet Adkins #include "utils/dbus_utils.hpp" 9*c9563608SJanet Adkins #include "utils/json_utils.hpp" 10*c9563608SJanet Adkins 11*c9563608SJanet Adkins #include <sdbusplus/unpack_properties.hpp> 12*c9563608SJanet Adkins 131516c21bSJanet Adkins #include <algorithm> 141516c21bSJanet Adkins #include <format> 151516c21bSJanet Adkins #include <ranges> 161516c21bSJanet Adkins #include <string> 171516c21bSJanet Adkins #include <string_view> 18*c9563608SJanet Adkins #include <tuple> 191516c21bSJanet Adkins #include <utility> 201516c21bSJanet Adkins #include <vector> 211516c21bSJanet Adkins 221516c21bSJanet Adkins namespace redfish 231516c21bSJanet Adkins { 241516c21bSJanet Adkins namespace sensor_utils 251516c21bSJanet Adkins { 261516c21bSJanet Adkins 27*c9563608SJanet Adkins static constexpr std::string_view powerNode = "Power"; 28*c9563608SJanet Adkins static constexpr std::string_view sensorsNode = "Sensors"; 29*c9563608SJanet Adkins static constexpr std::string_view thermalNode = "Thermal"; 30*c9563608SJanet Adkins 31*c9563608SJanet Adkins /** 32*c9563608SJanet Adkins * Possible states for physical inventory leds 33*c9563608SJanet Adkins */ 34*c9563608SJanet Adkins enum class LedState 35*c9563608SJanet Adkins { 36*c9563608SJanet Adkins OFF, 37*c9563608SJanet Adkins ON, 38*c9563608SJanet Adkins BLINK, 39*c9563608SJanet Adkins UNKNOWN 40*c9563608SJanet Adkins }; 41*c9563608SJanet Adkins 42*c9563608SJanet Adkins /** 43*c9563608SJanet Adkins * D-Bus inventory item associated with one or more sensors. 44*c9563608SJanet Adkins */ 45*c9563608SJanet Adkins class InventoryItem 46*c9563608SJanet Adkins { 47*c9563608SJanet Adkins public: 48*c9563608SJanet Adkins explicit InventoryItem(const std::string& objPath) : objectPath(objPath) 49*c9563608SJanet Adkins { 50*c9563608SJanet Adkins // Set inventory item name to last node of object path 51*c9563608SJanet Adkins sdbusplus::message::object_path path(objectPath); 52*c9563608SJanet Adkins name = path.filename(); 53*c9563608SJanet Adkins if (name.empty()) 54*c9563608SJanet Adkins { 55*c9563608SJanet Adkins BMCWEB_LOG_ERROR("Failed to find '/' in {}", objectPath); 56*c9563608SJanet Adkins } 57*c9563608SJanet Adkins } 58*c9563608SJanet Adkins 59*c9563608SJanet Adkins std::string objectPath; 60*c9563608SJanet Adkins std::string name; 61*c9563608SJanet Adkins bool isPresent = true; 62*c9563608SJanet Adkins bool isFunctional = true; 63*c9563608SJanet Adkins bool isPowerSupply = false; 64*c9563608SJanet Adkins int powerSupplyEfficiencyPercent = -1; 65*c9563608SJanet Adkins std::string manufacturer; 66*c9563608SJanet Adkins std::string model; 67*c9563608SJanet Adkins std::string partNumber; 68*c9563608SJanet Adkins std::string serialNumber; 69*c9563608SJanet Adkins std::set<std::string> sensors; 70*c9563608SJanet Adkins std::string ledObjectPath; 71*c9563608SJanet Adkins LedState ledState = LedState::UNKNOWN; 72*c9563608SJanet Adkins }; 73*c9563608SJanet Adkins 741516c21bSJanet Adkins inline std::string getSensorId(std::string_view sensorName, 751516c21bSJanet Adkins std::string_view sensorType) 761516c21bSJanet Adkins { 771516c21bSJanet Adkins std::string normalizedType(sensorType); 781516c21bSJanet Adkins auto remove = std::ranges::remove(normalizedType, '_'); 791516c21bSJanet Adkins normalizedType.erase(std::ranges::begin(remove), normalizedType.end()); 801516c21bSJanet Adkins 811516c21bSJanet Adkins return std::format("{}_{}", normalizedType, sensorName); 821516c21bSJanet Adkins } 831516c21bSJanet Adkins 841516c21bSJanet Adkins inline std::pair<std::string, std::string> 851516c21bSJanet Adkins splitSensorNameAndType(std::string_view sensorId) 861516c21bSJanet Adkins { 871516c21bSJanet Adkins size_t index = sensorId.find('_'); 881516c21bSJanet Adkins if (index == std::string::npos) 891516c21bSJanet Adkins { 901516c21bSJanet Adkins return std::make_pair<std::string, std::string>("", ""); 911516c21bSJanet Adkins } 921516c21bSJanet Adkins std::string sensorType{sensorId.substr(0, index)}; 931516c21bSJanet Adkins std::string sensorName{sensorId.substr(index + 1)}; 941516c21bSJanet Adkins // fan_pwm and fan_tach need special handling 951516c21bSJanet Adkins if (sensorType == "fantach" || sensorType == "fanpwm") 961516c21bSJanet Adkins { 971516c21bSJanet Adkins sensorType.insert(3, 1, '_'); 981516c21bSJanet Adkins } 991516c21bSJanet Adkins return std::make_pair(sensorType, sensorName); 1001516c21bSJanet Adkins } 1011516c21bSJanet Adkins 102*c9563608SJanet Adkins namespace sensors 103*c9563608SJanet Adkins { 104*c9563608SJanet Adkins inline std::string_view toReadingUnits(std::string_view sensorType) 105*c9563608SJanet Adkins { 106*c9563608SJanet Adkins if (sensorType == "voltage") 107*c9563608SJanet Adkins { 108*c9563608SJanet Adkins return "V"; 109*c9563608SJanet Adkins } 110*c9563608SJanet Adkins if (sensorType == "power") 111*c9563608SJanet Adkins { 112*c9563608SJanet Adkins return "W"; 113*c9563608SJanet Adkins } 114*c9563608SJanet Adkins if (sensorType == "current") 115*c9563608SJanet Adkins { 116*c9563608SJanet Adkins return "A"; 117*c9563608SJanet Adkins } 118*c9563608SJanet Adkins if (sensorType == "fan_tach") 119*c9563608SJanet Adkins { 120*c9563608SJanet Adkins return "RPM"; 121*c9563608SJanet Adkins } 122*c9563608SJanet Adkins if (sensorType == "temperature") 123*c9563608SJanet Adkins { 124*c9563608SJanet Adkins return "Cel"; 125*c9563608SJanet Adkins } 126*c9563608SJanet Adkins if (sensorType == "fan_pwm" || sensorType == "utilization" || 127*c9563608SJanet Adkins sensorType == "humidity") 128*c9563608SJanet Adkins { 129*c9563608SJanet Adkins return "%"; 130*c9563608SJanet Adkins } 131*c9563608SJanet Adkins if (sensorType == "altitude") 132*c9563608SJanet Adkins { 133*c9563608SJanet Adkins return "m"; 134*c9563608SJanet Adkins } 135*c9563608SJanet Adkins if (sensorType == "airflow") 136*c9563608SJanet Adkins { 137*c9563608SJanet Adkins return "cft_i/min"; 138*c9563608SJanet Adkins } 139*c9563608SJanet Adkins if (sensorType == "energy") 140*c9563608SJanet Adkins { 141*c9563608SJanet Adkins return "J"; 142*c9563608SJanet Adkins } 143*c9563608SJanet Adkins return ""; 144*c9563608SJanet Adkins } 145*c9563608SJanet Adkins 146*c9563608SJanet Adkins inline sensor::ReadingType toReadingType(std::string_view sensorType) 147*c9563608SJanet Adkins { 148*c9563608SJanet Adkins if (sensorType == "voltage") 149*c9563608SJanet Adkins { 150*c9563608SJanet Adkins return sensor::ReadingType::Voltage; 151*c9563608SJanet Adkins } 152*c9563608SJanet Adkins if (sensorType == "power") 153*c9563608SJanet Adkins { 154*c9563608SJanet Adkins return sensor::ReadingType::Power; 155*c9563608SJanet Adkins } 156*c9563608SJanet Adkins if (sensorType == "current") 157*c9563608SJanet Adkins { 158*c9563608SJanet Adkins return sensor::ReadingType::Current; 159*c9563608SJanet Adkins } 160*c9563608SJanet Adkins if (sensorType == "fan_tach") 161*c9563608SJanet Adkins { 162*c9563608SJanet Adkins return sensor::ReadingType::Rotational; 163*c9563608SJanet Adkins } 164*c9563608SJanet Adkins if (sensorType == "temperature") 165*c9563608SJanet Adkins { 166*c9563608SJanet Adkins return sensor::ReadingType::Temperature; 167*c9563608SJanet Adkins } 168*c9563608SJanet Adkins if (sensorType == "fan_pwm" || sensorType == "utilization") 169*c9563608SJanet Adkins { 170*c9563608SJanet Adkins return sensor::ReadingType::Percent; 171*c9563608SJanet Adkins } 172*c9563608SJanet Adkins if (sensorType == "humidity") 173*c9563608SJanet Adkins { 174*c9563608SJanet Adkins return sensor::ReadingType::Humidity; 175*c9563608SJanet Adkins } 176*c9563608SJanet Adkins if (sensorType == "altitude") 177*c9563608SJanet Adkins { 178*c9563608SJanet Adkins return sensor::ReadingType::Altitude; 179*c9563608SJanet Adkins } 180*c9563608SJanet Adkins if (sensorType == "airflow") 181*c9563608SJanet Adkins { 182*c9563608SJanet Adkins return sensor::ReadingType::AirFlow; 183*c9563608SJanet Adkins } 184*c9563608SJanet Adkins if (sensorType == "energy") 185*c9563608SJanet Adkins { 186*c9563608SJanet Adkins return sensor::ReadingType::EnergyJoules; 187*c9563608SJanet Adkins } 188*c9563608SJanet Adkins return sensor::ReadingType::Invalid; 189*c9563608SJanet Adkins } 190*c9563608SJanet Adkins 191*c9563608SJanet Adkins } // namespace sensors 192*c9563608SJanet Adkins 193*c9563608SJanet Adkins /** 194*c9563608SJanet Adkins * @brief Returns the Redfish State value for the specified inventory item. 195*c9563608SJanet Adkins * @param inventoryItem D-Bus inventory item associated with a sensor. 196*c9563608SJanet Adkins * @param sensorAvailable Boolean representing if D-Bus sensor is marked as 197*c9563608SJanet Adkins * available. 198*c9563608SJanet Adkins * @return State value for inventory item. 199*c9563608SJanet Adkins */ 200*c9563608SJanet Adkins inline resource::State getState(const InventoryItem* inventoryItem, 201*c9563608SJanet Adkins const bool sensorAvailable) 202*c9563608SJanet Adkins { 203*c9563608SJanet Adkins if ((inventoryItem != nullptr) && !(inventoryItem->isPresent)) 204*c9563608SJanet Adkins { 205*c9563608SJanet Adkins return resource::State::Absent; 206*c9563608SJanet Adkins } 207*c9563608SJanet Adkins 208*c9563608SJanet Adkins if (!sensorAvailable) 209*c9563608SJanet Adkins { 210*c9563608SJanet Adkins return resource::State::UnavailableOffline; 211*c9563608SJanet Adkins } 212*c9563608SJanet Adkins 213*c9563608SJanet Adkins return resource::State::Enabled; 214*c9563608SJanet Adkins } 215*c9563608SJanet Adkins 216*c9563608SJanet Adkins /** 217*c9563608SJanet Adkins * @brief Returns the Redfish Health value for the specified sensor. 218*c9563608SJanet Adkins * @param sensorJson Sensor JSON object. 219*c9563608SJanet Adkins * @param valuesDict Map of all sensor DBus values. 220*c9563608SJanet Adkins * @param inventoryItem D-Bus inventory item associated with the sensor. Will 221*c9563608SJanet Adkins * be nullptr if no associated inventory item was found. 222*c9563608SJanet Adkins * @return Health value for sensor. 223*c9563608SJanet Adkins */ 224*c9563608SJanet Adkins inline std::string getHealth(nlohmann::json& sensorJson, 225*c9563608SJanet Adkins const dbus::utility::DBusPropertiesMap& valuesDict, 226*c9563608SJanet Adkins const InventoryItem* inventoryItem) 227*c9563608SJanet Adkins { 228*c9563608SJanet Adkins // Get current health value (if any) in the sensor JSON object. Some JSON 229*c9563608SJanet Adkins // objects contain multiple sensors (such as PowerSupplies). We want to set 230*c9563608SJanet Adkins // the overall health to be the most severe of any of the sensors. 231*c9563608SJanet Adkins std::string currentHealth; 232*c9563608SJanet Adkins auto statusIt = sensorJson.find("Status"); 233*c9563608SJanet Adkins if (statusIt != sensorJson.end()) 234*c9563608SJanet Adkins { 235*c9563608SJanet Adkins auto healthIt = statusIt->find("Health"); 236*c9563608SJanet Adkins if (healthIt != statusIt->end()) 237*c9563608SJanet Adkins { 238*c9563608SJanet Adkins std::string* health = healthIt->get_ptr<std::string*>(); 239*c9563608SJanet Adkins if (health != nullptr) 240*c9563608SJanet Adkins { 241*c9563608SJanet Adkins currentHealth = *health; 242*c9563608SJanet Adkins } 243*c9563608SJanet Adkins } 244*c9563608SJanet Adkins } 245*c9563608SJanet Adkins 246*c9563608SJanet Adkins // If current health in JSON object is already Critical, return that. This 247*c9563608SJanet Adkins // should override the sensor health, which might be less severe. 248*c9563608SJanet Adkins if (currentHealth == "Critical") 249*c9563608SJanet Adkins { 250*c9563608SJanet Adkins return "Critical"; 251*c9563608SJanet Adkins } 252*c9563608SJanet Adkins 253*c9563608SJanet Adkins const bool* criticalAlarmHigh = nullptr; 254*c9563608SJanet Adkins const bool* criticalAlarmLow = nullptr; 255*c9563608SJanet Adkins const bool* warningAlarmHigh = nullptr; 256*c9563608SJanet Adkins const bool* warningAlarmLow = nullptr; 257*c9563608SJanet Adkins 258*c9563608SJanet Adkins const bool success = sdbusplus::unpackPropertiesNoThrow( 259*c9563608SJanet Adkins dbus_utils::UnpackErrorPrinter(), valuesDict, "CriticalAlarmHigh", 260*c9563608SJanet Adkins criticalAlarmHigh, "CriticalAlarmLow", criticalAlarmLow, 261*c9563608SJanet Adkins "WarningAlarmHigh", warningAlarmHigh, "WarningAlarmLow", 262*c9563608SJanet Adkins warningAlarmLow); 263*c9563608SJanet Adkins 264*c9563608SJanet Adkins if (success) 265*c9563608SJanet Adkins { 266*c9563608SJanet Adkins // Check if sensor has critical threshold alarm 267*c9563608SJanet Adkins if ((criticalAlarmHigh != nullptr && *criticalAlarmHigh) || 268*c9563608SJanet Adkins (criticalAlarmLow != nullptr && *criticalAlarmLow)) 269*c9563608SJanet Adkins { 270*c9563608SJanet Adkins return "Critical"; 271*c9563608SJanet Adkins } 272*c9563608SJanet Adkins } 273*c9563608SJanet Adkins 274*c9563608SJanet Adkins // Check if associated inventory item is not functional 275*c9563608SJanet Adkins if ((inventoryItem != nullptr) && !(inventoryItem->isFunctional)) 276*c9563608SJanet Adkins { 277*c9563608SJanet Adkins return "Critical"; 278*c9563608SJanet Adkins } 279*c9563608SJanet Adkins 280*c9563608SJanet Adkins // If current health in JSON object is already Warning, return that. This 281*c9563608SJanet Adkins // should override the sensor status, which might be less severe. 282*c9563608SJanet Adkins if (currentHealth == "Warning") 283*c9563608SJanet Adkins { 284*c9563608SJanet Adkins return "Warning"; 285*c9563608SJanet Adkins } 286*c9563608SJanet Adkins 287*c9563608SJanet Adkins if (success) 288*c9563608SJanet Adkins { 289*c9563608SJanet Adkins // Check if sensor has warning threshold alarm 290*c9563608SJanet Adkins if ((warningAlarmHigh != nullptr && *warningAlarmHigh) || 291*c9563608SJanet Adkins (warningAlarmLow != nullptr && *warningAlarmLow)) 292*c9563608SJanet Adkins { 293*c9563608SJanet Adkins return "Warning"; 294*c9563608SJanet Adkins } 295*c9563608SJanet Adkins } 296*c9563608SJanet Adkins 297*c9563608SJanet Adkins return "OK"; 298*c9563608SJanet Adkins } 299*c9563608SJanet Adkins 300*c9563608SJanet Adkins inline void setLedState(nlohmann::json& sensorJson, 301*c9563608SJanet Adkins const InventoryItem* inventoryItem) 302*c9563608SJanet Adkins { 303*c9563608SJanet Adkins if (inventoryItem != nullptr && !inventoryItem->ledObjectPath.empty()) 304*c9563608SJanet Adkins { 305*c9563608SJanet Adkins switch (inventoryItem->ledState) 306*c9563608SJanet Adkins { 307*c9563608SJanet Adkins case LedState::OFF: 308*c9563608SJanet Adkins sensorJson["IndicatorLED"] = resource::IndicatorLED::Off; 309*c9563608SJanet Adkins break; 310*c9563608SJanet Adkins case LedState::ON: 311*c9563608SJanet Adkins sensorJson["IndicatorLED"] = resource::IndicatorLED::Lit; 312*c9563608SJanet Adkins break; 313*c9563608SJanet Adkins case LedState::BLINK: 314*c9563608SJanet Adkins sensorJson["IndicatorLED"] = resource::IndicatorLED::Blinking; 315*c9563608SJanet Adkins break; 316*c9563608SJanet Adkins default: 317*c9563608SJanet Adkins break; 318*c9563608SJanet Adkins } 319*c9563608SJanet Adkins } 320*c9563608SJanet Adkins } 321*c9563608SJanet Adkins 322*c9563608SJanet Adkins /** 323*c9563608SJanet Adkins * @brief Builds a json sensor representation of a sensor. 324*c9563608SJanet Adkins * @param sensorName The name of the sensor to be built 325*c9563608SJanet Adkins * @param sensorType The type (temperature, fan_tach, etc) of the sensor to 326*c9563608SJanet Adkins * build 327*c9563608SJanet Adkins * @param chassisSubNode The subnode (thermal, sensor, etc) of the sensor 328*c9563608SJanet Adkins * @param propertiesDict A dictionary of the properties to build the sensor 329*c9563608SJanet Adkins * from. 330*c9563608SJanet Adkins * @param sensorJson The json object to fill 331*c9563608SJanet Adkins * @param inventoryItem D-Bus inventory item associated with the sensor. Will 332*c9563608SJanet Adkins * be nullptr if no associated inventory item was found. 333*c9563608SJanet Adkins */ 334*c9563608SJanet Adkins inline void objectPropertiesToJson( 335*c9563608SJanet Adkins std::string_view sensorName, std::string_view sensorType, 336*c9563608SJanet Adkins std::string_view chassisSubNode, 337*c9563608SJanet Adkins const dbus::utility::DBusPropertiesMap& propertiesDict, 338*c9563608SJanet Adkins nlohmann::json& sensorJson, InventoryItem* inventoryItem) 339*c9563608SJanet Adkins { 340*c9563608SJanet Adkins if (chassisSubNode == sensorsNode) 341*c9563608SJanet Adkins { 342*c9563608SJanet Adkins std::string subNodeEscaped = getSensorId(sensorName, sensorType); 343*c9563608SJanet Adkins // For sensors in SensorCollection we set Id instead of MemberId, 344*c9563608SJanet Adkins // including power sensors. 345*c9563608SJanet Adkins sensorJson["Id"] = std::move(subNodeEscaped); 346*c9563608SJanet Adkins 347*c9563608SJanet Adkins std::string sensorNameEs(sensorName); 348*c9563608SJanet Adkins std::replace(sensorNameEs.begin(), sensorNameEs.end(), '_', ' '); 349*c9563608SJanet Adkins sensorJson["Name"] = std::move(sensorNameEs); 350*c9563608SJanet Adkins } 351*c9563608SJanet Adkins else if (sensorType != "power") 352*c9563608SJanet Adkins { 353*c9563608SJanet Adkins // Set MemberId and Name for non-power sensors. For PowerSupplies and 354*c9563608SJanet Adkins // PowerControl, those properties have more general values because 355*c9563608SJanet Adkins // multiple sensors can be stored in the same JSON object. 356*c9563608SJanet Adkins std::string sensorNameEs(sensorName); 357*c9563608SJanet Adkins std::replace(sensorNameEs.begin(), sensorNameEs.end(), '_', ' '); 358*c9563608SJanet Adkins sensorJson["Name"] = std::move(sensorNameEs); 359*c9563608SJanet Adkins } 360*c9563608SJanet Adkins 361*c9563608SJanet Adkins const bool* checkAvailable = nullptr; 362*c9563608SJanet Adkins bool available = true; 363*c9563608SJanet Adkins const bool success = sdbusplus::unpackPropertiesNoThrow( 364*c9563608SJanet Adkins dbus_utils::UnpackErrorPrinter(), propertiesDict, "Available", 365*c9563608SJanet Adkins checkAvailable); 366*c9563608SJanet Adkins if (!success) 367*c9563608SJanet Adkins { 368*c9563608SJanet Adkins messages::internalError(); 369*c9563608SJanet Adkins } 370*c9563608SJanet Adkins if (checkAvailable != nullptr) 371*c9563608SJanet Adkins { 372*c9563608SJanet Adkins available = *checkAvailable; 373*c9563608SJanet Adkins } 374*c9563608SJanet Adkins 375*c9563608SJanet Adkins sensorJson["Status"]["State"] = getState(inventoryItem, available); 376*c9563608SJanet Adkins sensorJson["Status"]["Health"] = 377*c9563608SJanet Adkins getHealth(sensorJson, propertiesDict, inventoryItem); 378*c9563608SJanet Adkins 379*c9563608SJanet Adkins // Parameter to set to override the type we get from dbus, and force it to 380*c9563608SJanet Adkins // int, regardless of what is available. This is used for schemas like fan, 381*c9563608SJanet Adkins // that require integers, not floats. 382*c9563608SJanet Adkins bool forceToInt = false; 383*c9563608SJanet Adkins 384*c9563608SJanet Adkins nlohmann::json::json_pointer unit("/Reading"); 385*c9563608SJanet Adkins if (chassisSubNode == sensorsNode) 386*c9563608SJanet Adkins { 387*c9563608SJanet Adkins sensorJson["@odata.type"] = "#Sensor.v1_2_0.Sensor"; 388*c9563608SJanet Adkins 389*c9563608SJanet Adkins sensor::ReadingType readingType = sensors::toReadingType(sensorType); 390*c9563608SJanet Adkins if (readingType == sensor::ReadingType::Invalid) 391*c9563608SJanet Adkins { 392*c9563608SJanet Adkins BMCWEB_LOG_ERROR("Redfish cannot map reading type for {}", 393*c9563608SJanet Adkins sensorType); 394*c9563608SJanet Adkins } 395*c9563608SJanet Adkins else 396*c9563608SJanet Adkins { 397*c9563608SJanet Adkins sensorJson["ReadingType"] = readingType; 398*c9563608SJanet Adkins } 399*c9563608SJanet Adkins 400*c9563608SJanet Adkins std::string_view readingUnits = sensors::toReadingUnits(sensorType); 401*c9563608SJanet Adkins if (readingUnits.empty()) 402*c9563608SJanet Adkins { 403*c9563608SJanet Adkins BMCWEB_LOG_ERROR("Redfish cannot map reading unit for {}", 404*c9563608SJanet Adkins sensorType); 405*c9563608SJanet Adkins } 406*c9563608SJanet Adkins else 407*c9563608SJanet Adkins { 408*c9563608SJanet Adkins sensorJson["ReadingUnits"] = readingUnits; 409*c9563608SJanet Adkins } 410*c9563608SJanet Adkins } 411*c9563608SJanet Adkins else if (sensorType == "temperature") 412*c9563608SJanet Adkins { 413*c9563608SJanet Adkins unit = "/ReadingCelsius"_json_pointer; 414*c9563608SJanet Adkins sensorJson["@odata.type"] = "#Thermal.v1_3_0.Temperature"; 415*c9563608SJanet Adkins // TODO(ed) Documentation says that path should be type fan_tach, 416*c9563608SJanet Adkins // implementation seems to implement fan 417*c9563608SJanet Adkins } 418*c9563608SJanet Adkins else if (sensorType == "fan" || sensorType == "fan_tach") 419*c9563608SJanet Adkins { 420*c9563608SJanet Adkins unit = "/Reading"_json_pointer; 421*c9563608SJanet Adkins sensorJson["ReadingUnits"] = thermal::ReadingUnits::RPM; 422*c9563608SJanet Adkins sensorJson["@odata.type"] = "#Thermal.v1_3_0.Fan"; 423*c9563608SJanet Adkins setLedState(sensorJson, inventoryItem); 424*c9563608SJanet Adkins forceToInt = true; 425*c9563608SJanet Adkins } 426*c9563608SJanet Adkins else if (sensorType == "fan_pwm") 427*c9563608SJanet Adkins { 428*c9563608SJanet Adkins unit = "/Reading"_json_pointer; 429*c9563608SJanet Adkins sensorJson["ReadingUnits"] = thermal::ReadingUnits::Percent; 430*c9563608SJanet Adkins sensorJson["@odata.type"] = "#Thermal.v1_3_0.Fan"; 431*c9563608SJanet Adkins setLedState(sensorJson, inventoryItem); 432*c9563608SJanet Adkins forceToInt = true; 433*c9563608SJanet Adkins } 434*c9563608SJanet Adkins else if (sensorType == "voltage") 435*c9563608SJanet Adkins { 436*c9563608SJanet Adkins unit = "/ReadingVolts"_json_pointer; 437*c9563608SJanet Adkins sensorJson["@odata.type"] = "#Power.v1_0_0.Voltage"; 438*c9563608SJanet Adkins } 439*c9563608SJanet Adkins else if (sensorType == "power") 440*c9563608SJanet Adkins { 441*c9563608SJanet Adkins std::string lower; 442*c9563608SJanet Adkins std::ranges::transform(sensorName, std::back_inserter(lower), 443*c9563608SJanet Adkins bmcweb::asciiToLower); 444*c9563608SJanet Adkins if (lower == "total_power") 445*c9563608SJanet Adkins { 446*c9563608SJanet Adkins sensorJson["@odata.type"] = "#Power.v1_0_0.PowerControl"; 447*c9563608SJanet Adkins // Put multiple "sensors" into a single PowerControl, so have 448*c9563608SJanet Adkins // generic names for MemberId and Name. Follows Redfish mockup. 449*c9563608SJanet Adkins sensorJson["MemberId"] = "0"; 450*c9563608SJanet Adkins sensorJson["Name"] = "Chassis Power Control"; 451*c9563608SJanet Adkins unit = "/PowerConsumedWatts"_json_pointer; 452*c9563608SJanet Adkins } 453*c9563608SJanet Adkins else if (lower.find("input") != std::string::npos) 454*c9563608SJanet Adkins { 455*c9563608SJanet Adkins unit = "/PowerInputWatts"_json_pointer; 456*c9563608SJanet Adkins } 457*c9563608SJanet Adkins else 458*c9563608SJanet Adkins { 459*c9563608SJanet Adkins unit = "/PowerOutputWatts"_json_pointer; 460*c9563608SJanet Adkins } 461*c9563608SJanet Adkins } 462*c9563608SJanet Adkins else 463*c9563608SJanet Adkins { 464*c9563608SJanet Adkins BMCWEB_LOG_ERROR("Redfish cannot map object type for {}", sensorName); 465*c9563608SJanet Adkins return; 466*c9563608SJanet Adkins } 467*c9563608SJanet Adkins // Map of dbus interface name, dbus property name and redfish property_name 468*c9563608SJanet Adkins std::vector< 469*c9563608SJanet Adkins std::tuple<const char*, const char*, nlohmann::json::json_pointer>> 470*c9563608SJanet Adkins properties; 471*c9563608SJanet Adkins properties.reserve(7); 472*c9563608SJanet Adkins 473*c9563608SJanet Adkins properties.emplace_back("xyz.openbmc_project.Sensor.Value", "Value", unit); 474*c9563608SJanet Adkins 475*c9563608SJanet Adkins if (chassisSubNode == sensorsNode) 476*c9563608SJanet Adkins { 477*c9563608SJanet Adkins properties.emplace_back( 478*c9563608SJanet Adkins "xyz.openbmc_project.Sensor.Threshold.Warning", "WarningHigh", 479*c9563608SJanet Adkins "/Thresholds/UpperCaution/Reading"_json_pointer); 480*c9563608SJanet Adkins properties.emplace_back( 481*c9563608SJanet Adkins "xyz.openbmc_project.Sensor.Threshold.Warning", "WarningLow", 482*c9563608SJanet Adkins "/Thresholds/LowerCaution/Reading"_json_pointer); 483*c9563608SJanet Adkins properties.emplace_back( 484*c9563608SJanet Adkins "xyz.openbmc_project.Sensor.Threshold.Critical", "CriticalHigh", 485*c9563608SJanet Adkins "/Thresholds/UpperCritical/Reading"_json_pointer); 486*c9563608SJanet Adkins properties.emplace_back( 487*c9563608SJanet Adkins "xyz.openbmc_project.Sensor.Threshold.Critical", "CriticalLow", 488*c9563608SJanet Adkins "/Thresholds/LowerCritical/Reading"_json_pointer); 489*c9563608SJanet Adkins } 490*c9563608SJanet Adkins else if (sensorType != "power") 491*c9563608SJanet Adkins { 492*c9563608SJanet Adkins properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Warning", 493*c9563608SJanet Adkins "WarningHigh", 494*c9563608SJanet Adkins "/UpperThresholdNonCritical"_json_pointer); 495*c9563608SJanet Adkins properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Warning", 496*c9563608SJanet Adkins "WarningLow", 497*c9563608SJanet Adkins "/LowerThresholdNonCritical"_json_pointer); 498*c9563608SJanet Adkins properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Critical", 499*c9563608SJanet Adkins "CriticalHigh", 500*c9563608SJanet Adkins "/UpperThresholdCritical"_json_pointer); 501*c9563608SJanet Adkins properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Critical", 502*c9563608SJanet Adkins "CriticalLow", 503*c9563608SJanet Adkins "/LowerThresholdCritical"_json_pointer); 504*c9563608SJanet Adkins } 505*c9563608SJanet Adkins 506*c9563608SJanet Adkins // TODO Need to get UpperThresholdFatal and LowerThresholdFatal 507*c9563608SJanet Adkins 508*c9563608SJanet Adkins if (chassisSubNode == sensorsNode) 509*c9563608SJanet Adkins { 510*c9563608SJanet Adkins properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MinValue", 511*c9563608SJanet Adkins "/ReadingRangeMin"_json_pointer); 512*c9563608SJanet Adkins properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MaxValue", 513*c9563608SJanet Adkins "/ReadingRangeMax"_json_pointer); 514*c9563608SJanet Adkins properties.emplace_back("xyz.openbmc_project.Sensor.Accuracy", 515*c9563608SJanet Adkins "Accuracy", "/Accuracy"_json_pointer); 516*c9563608SJanet Adkins } 517*c9563608SJanet Adkins else if (sensorType == "temperature") 518*c9563608SJanet Adkins { 519*c9563608SJanet Adkins properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MinValue", 520*c9563608SJanet Adkins "/MinReadingRangeTemp"_json_pointer); 521*c9563608SJanet Adkins properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MaxValue", 522*c9563608SJanet Adkins "/MaxReadingRangeTemp"_json_pointer); 523*c9563608SJanet Adkins } 524*c9563608SJanet Adkins else if (sensorType != "power") 525*c9563608SJanet Adkins { 526*c9563608SJanet Adkins properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MinValue", 527*c9563608SJanet Adkins "/MinReadingRange"_json_pointer); 528*c9563608SJanet Adkins properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MaxValue", 529*c9563608SJanet Adkins "/MaxReadingRange"_json_pointer); 530*c9563608SJanet Adkins } 531*c9563608SJanet Adkins 532*c9563608SJanet Adkins for (const std::tuple<const char*, const char*, 533*c9563608SJanet Adkins nlohmann::json::json_pointer>& p : properties) 534*c9563608SJanet Adkins { 535*c9563608SJanet Adkins for (const auto& [valueName, valueVariant] : propertiesDict) 536*c9563608SJanet Adkins { 537*c9563608SJanet Adkins if (valueName != std::get<1>(p)) 538*c9563608SJanet Adkins { 539*c9563608SJanet Adkins continue; 540*c9563608SJanet Adkins } 541*c9563608SJanet Adkins 542*c9563608SJanet Adkins // The property we want to set may be nested json, so use 543*c9563608SJanet Adkins // a json_pointer for easy indexing into the json structure. 544*c9563608SJanet Adkins const nlohmann::json::json_pointer& key = std::get<2>(p); 545*c9563608SJanet Adkins 546*c9563608SJanet Adkins const double* doubleValue = std::get_if<double>(&valueVariant); 547*c9563608SJanet Adkins if (doubleValue == nullptr) 548*c9563608SJanet Adkins { 549*c9563608SJanet Adkins BMCWEB_LOG_ERROR("Got value interface that wasn't double"); 550*c9563608SJanet Adkins continue; 551*c9563608SJanet Adkins } 552*c9563608SJanet Adkins if (!std::isfinite(*doubleValue)) 553*c9563608SJanet Adkins { 554*c9563608SJanet Adkins if (valueName == "Value") 555*c9563608SJanet Adkins { 556*c9563608SJanet Adkins // Readings are allowed to be NAN for unavailable; coerce 557*c9563608SJanet Adkins // them to null in the json response. 558*c9563608SJanet Adkins sensorJson[key] = nullptr; 559*c9563608SJanet Adkins continue; 560*c9563608SJanet Adkins } 561*c9563608SJanet Adkins BMCWEB_LOG_WARNING("Sensor value for {} was unexpectedly {}", 562*c9563608SJanet Adkins valueName, *doubleValue); 563*c9563608SJanet Adkins continue; 564*c9563608SJanet Adkins } 565*c9563608SJanet Adkins if (forceToInt) 566*c9563608SJanet Adkins { 567*c9563608SJanet Adkins sensorJson[key] = static_cast<int64_t>(*doubleValue); 568*c9563608SJanet Adkins } 569*c9563608SJanet Adkins else 570*c9563608SJanet Adkins { 571*c9563608SJanet Adkins sensorJson[key] = *doubleValue; 572*c9563608SJanet Adkins } 573*c9563608SJanet Adkins } 574*c9563608SJanet Adkins } 575*c9563608SJanet Adkins } 576*c9563608SJanet Adkins 5771516c21bSJanet Adkins } // namespace sensor_utils 5781516c21bSJanet Adkins } // namespace redfish 579