1*40e9b92eSEd Tanous // SPDX-License-Identifier: Apache-2.0 2*40e9b92eSEd Tanous // SPDX-FileCopyrightText: Copyright OpenBMC Authors 31516c21bSJanet Adkins #pragma once 41516c21bSJanet Adkins 5c9563608SJanet Adkins #include "dbus_utility.hpp" 6c9563608SJanet Adkins #include "generated/enums/resource.hpp" 7c9563608SJanet Adkins #include "generated/enums/sensor.hpp" 8c9563608SJanet Adkins #include "generated/enums/thermal.hpp" 9c9563608SJanet Adkins #include "str_utility.hpp" 10c9563608SJanet Adkins #include "utils/dbus_utils.hpp" 11c9563608SJanet Adkins #include "utils/json_utils.hpp" 12c9563608SJanet Adkins 136fe8751cSGeorge Liu #include <boost/url/format.hpp> 14c9563608SJanet Adkins #include <sdbusplus/unpack_properties.hpp> 15c9563608SJanet Adkins 161516c21bSJanet Adkins #include <algorithm> 171516c21bSJanet Adkins #include <format> 186fe8751cSGeorge Liu #include <functional> 196fe8751cSGeorge Liu #include <optional> 201516c21bSJanet Adkins #include <ranges> 211516c21bSJanet Adkins #include <string> 221516c21bSJanet Adkins #include <string_view> 23c9563608SJanet Adkins #include <tuple> 241516c21bSJanet Adkins #include <utility> 251516c21bSJanet Adkins #include <vector> 261516c21bSJanet Adkins 271516c21bSJanet Adkins namespace redfish 281516c21bSJanet Adkins { 291516c21bSJanet Adkins namespace sensor_utils 301516c21bSJanet Adkins { 311516c21bSJanet Adkins 320c728b42SJanet Adkins enum class ChassisSubNode 330c728b42SJanet Adkins { 340c728b42SJanet Adkins powerNode, 350c728b42SJanet Adkins sensorsNode, 360c728b42SJanet Adkins thermalNode, 376fe8751cSGeorge Liu thermalMetricsNode, 380c728b42SJanet Adkins unknownNode, 390c728b42SJanet Adkins }; 400c728b42SJanet Adkins 410c728b42SJanet Adkins constexpr std::string_view chassisSubNodeToString(ChassisSubNode subNode) 420c728b42SJanet Adkins { 430c728b42SJanet Adkins switch (subNode) 440c728b42SJanet Adkins { 450c728b42SJanet Adkins case ChassisSubNode::powerNode: 460c728b42SJanet Adkins return "Power"; 470c728b42SJanet Adkins case ChassisSubNode::sensorsNode: 480c728b42SJanet Adkins return "Sensors"; 490c728b42SJanet Adkins case ChassisSubNode::thermalNode: 500c728b42SJanet Adkins return "Thermal"; 516fe8751cSGeorge Liu case ChassisSubNode::thermalMetricsNode: 526fe8751cSGeorge Liu return "ThermalMetrics"; 530c728b42SJanet Adkins case ChassisSubNode::unknownNode: 540c728b42SJanet Adkins default: 550c728b42SJanet Adkins return ""; 560c728b42SJanet Adkins } 570c728b42SJanet Adkins } 580c728b42SJanet Adkins 590c728b42SJanet Adkins inline ChassisSubNode chassisSubNodeFromString(const std::string& subNodeStr) 600c728b42SJanet Adkins { 610c728b42SJanet Adkins // If none match unknownNode is returned 620c728b42SJanet Adkins ChassisSubNode subNode = ChassisSubNode::unknownNode; 630c728b42SJanet Adkins 640c728b42SJanet Adkins if (subNodeStr == "Power") 650c728b42SJanet Adkins { 660c728b42SJanet Adkins subNode = ChassisSubNode::powerNode; 670c728b42SJanet Adkins } 680c728b42SJanet Adkins else if (subNodeStr == "Sensors") 690c728b42SJanet Adkins { 700c728b42SJanet Adkins subNode = ChassisSubNode::sensorsNode; 710c728b42SJanet Adkins } 720c728b42SJanet Adkins else if (subNodeStr == "Thermal") 730c728b42SJanet Adkins { 740c728b42SJanet Adkins subNode = ChassisSubNode::thermalNode; 750c728b42SJanet Adkins } 766fe8751cSGeorge Liu else if (subNodeStr == "ThermalMetrics") 776fe8751cSGeorge Liu { 786fe8751cSGeorge Liu subNode = ChassisSubNode::thermalMetricsNode; 796fe8751cSGeorge Liu } 800c728b42SJanet Adkins 810c728b42SJanet Adkins return subNode; 820c728b42SJanet Adkins } 83c9563608SJanet Adkins 846fe8751cSGeorge Liu inline bool isExcerptNode(const ChassisSubNode subNode) 856fe8751cSGeorge Liu { 866fe8751cSGeorge Liu return (subNode == ChassisSubNode::thermalMetricsNode); 876fe8751cSGeorge Liu } 886fe8751cSGeorge Liu 89c9563608SJanet Adkins /** 90c9563608SJanet Adkins * Possible states for physical inventory leds 91c9563608SJanet Adkins */ 92c9563608SJanet Adkins enum class LedState 93c9563608SJanet Adkins { 94c9563608SJanet Adkins OFF, 95c9563608SJanet Adkins ON, 96c9563608SJanet Adkins BLINK, 97c9563608SJanet Adkins UNKNOWN 98c9563608SJanet Adkins }; 99c9563608SJanet Adkins 100c9563608SJanet Adkins /** 101c9563608SJanet Adkins * D-Bus inventory item associated with one or more sensors. 102c9563608SJanet Adkins */ 103c9563608SJanet Adkins class InventoryItem 104c9563608SJanet Adkins { 105c9563608SJanet Adkins public: 106c9563608SJanet Adkins explicit InventoryItem(const std::string& objPath) : objectPath(objPath) 107c9563608SJanet Adkins { 108c9563608SJanet Adkins // Set inventory item name to last node of object path 109c9563608SJanet Adkins sdbusplus::message::object_path path(objectPath); 110c9563608SJanet Adkins name = path.filename(); 111c9563608SJanet Adkins if (name.empty()) 112c9563608SJanet Adkins { 113c9563608SJanet Adkins BMCWEB_LOG_ERROR("Failed to find '/' in {}", objectPath); 114c9563608SJanet Adkins } 115c9563608SJanet Adkins } 116c9563608SJanet Adkins 117c9563608SJanet Adkins std::string objectPath; 118c9563608SJanet Adkins std::string name; 119c9563608SJanet Adkins bool isPresent = true; 120c9563608SJanet Adkins bool isFunctional = true; 121c9563608SJanet Adkins bool isPowerSupply = false; 122c9563608SJanet Adkins int powerSupplyEfficiencyPercent = -1; 123c9563608SJanet Adkins std::string manufacturer; 124c9563608SJanet Adkins std::string model; 125c9563608SJanet Adkins std::string partNumber; 126c9563608SJanet Adkins std::string serialNumber; 127c9563608SJanet Adkins std::set<std::string> sensors; 128c9563608SJanet Adkins std::string ledObjectPath; 129c9563608SJanet Adkins LedState ledState = LedState::UNKNOWN; 130c9563608SJanet Adkins }; 131c9563608SJanet Adkins 1321516c21bSJanet Adkins inline std::string getSensorId(std::string_view sensorName, 1331516c21bSJanet Adkins std::string_view sensorType) 1341516c21bSJanet Adkins { 1351516c21bSJanet Adkins std::string normalizedType(sensorType); 1361516c21bSJanet Adkins auto remove = std::ranges::remove(normalizedType, '_'); 1371516c21bSJanet Adkins normalizedType.erase(std::ranges::begin(remove), normalizedType.end()); 1381516c21bSJanet Adkins 1391516c21bSJanet Adkins return std::format("{}_{}", normalizedType, sensorName); 1401516c21bSJanet Adkins } 1411516c21bSJanet Adkins 1421516c21bSJanet Adkins inline std::pair<std::string, std::string> 1431516c21bSJanet Adkins splitSensorNameAndType(std::string_view sensorId) 1441516c21bSJanet Adkins { 1451516c21bSJanet Adkins size_t index = sensorId.find('_'); 1461516c21bSJanet Adkins if (index == std::string::npos) 1471516c21bSJanet Adkins { 1481516c21bSJanet Adkins return std::make_pair<std::string, std::string>("", ""); 1491516c21bSJanet Adkins } 1501516c21bSJanet Adkins std::string sensorType{sensorId.substr(0, index)}; 1511516c21bSJanet Adkins std::string sensorName{sensorId.substr(index + 1)}; 1521516c21bSJanet Adkins // fan_pwm and fan_tach need special handling 1531516c21bSJanet Adkins if (sensorType == "fantach" || sensorType == "fanpwm") 1541516c21bSJanet Adkins { 1551516c21bSJanet Adkins sensorType.insert(3, 1, '_'); 1561516c21bSJanet Adkins } 1571516c21bSJanet Adkins return std::make_pair(sensorType, sensorName); 1581516c21bSJanet Adkins } 1591516c21bSJanet Adkins 160c9563608SJanet Adkins namespace sensors 161c9563608SJanet Adkins { 162c9563608SJanet Adkins inline std::string_view toReadingUnits(std::string_view sensorType) 163c9563608SJanet Adkins { 164c9563608SJanet Adkins if (sensorType == "voltage") 165c9563608SJanet Adkins { 166c9563608SJanet Adkins return "V"; 167c9563608SJanet Adkins } 168c9563608SJanet Adkins if (sensorType == "power") 169c9563608SJanet Adkins { 170c9563608SJanet Adkins return "W"; 171c9563608SJanet Adkins } 172c9563608SJanet Adkins if (sensorType == "current") 173c9563608SJanet Adkins { 174c9563608SJanet Adkins return "A"; 175c9563608SJanet Adkins } 176c9563608SJanet Adkins if (sensorType == "fan_tach") 177c9563608SJanet Adkins { 178c9563608SJanet Adkins return "RPM"; 179c9563608SJanet Adkins } 180c9563608SJanet Adkins if (sensorType == "temperature") 181c9563608SJanet Adkins { 182c9563608SJanet Adkins return "Cel"; 183c9563608SJanet Adkins } 184c9563608SJanet Adkins if (sensorType == "fan_pwm" || sensorType == "utilization" || 185c9563608SJanet Adkins sensorType == "humidity") 186c9563608SJanet Adkins { 187c9563608SJanet Adkins return "%"; 188c9563608SJanet Adkins } 189c9563608SJanet Adkins if (sensorType == "altitude") 190c9563608SJanet Adkins { 191c9563608SJanet Adkins return "m"; 192c9563608SJanet Adkins } 193c9563608SJanet Adkins if (sensorType == "airflow") 194c9563608SJanet Adkins { 195c9563608SJanet Adkins return "cft_i/min"; 196c9563608SJanet Adkins } 197c9563608SJanet Adkins if (sensorType == "energy") 198c9563608SJanet Adkins { 199c9563608SJanet Adkins return "J"; 200c9563608SJanet Adkins } 201c9563608SJanet Adkins return ""; 202c9563608SJanet Adkins } 203c9563608SJanet Adkins 204c9563608SJanet Adkins inline sensor::ReadingType toReadingType(std::string_view sensorType) 205c9563608SJanet Adkins { 206c9563608SJanet Adkins if (sensorType == "voltage") 207c9563608SJanet Adkins { 208c9563608SJanet Adkins return sensor::ReadingType::Voltage; 209c9563608SJanet Adkins } 210c9563608SJanet Adkins if (sensorType == "power") 211c9563608SJanet Adkins { 212c9563608SJanet Adkins return sensor::ReadingType::Power; 213c9563608SJanet Adkins } 214c9563608SJanet Adkins if (sensorType == "current") 215c9563608SJanet Adkins { 216c9563608SJanet Adkins return sensor::ReadingType::Current; 217c9563608SJanet Adkins } 218c9563608SJanet Adkins if (sensorType == "fan_tach") 219c9563608SJanet Adkins { 220c9563608SJanet Adkins return sensor::ReadingType::Rotational; 221c9563608SJanet Adkins } 222c9563608SJanet Adkins if (sensorType == "temperature") 223c9563608SJanet Adkins { 224c9563608SJanet Adkins return sensor::ReadingType::Temperature; 225c9563608SJanet Adkins } 226c9563608SJanet Adkins if (sensorType == "fan_pwm" || sensorType == "utilization") 227c9563608SJanet Adkins { 228c9563608SJanet Adkins return sensor::ReadingType::Percent; 229c9563608SJanet Adkins } 230c9563608SJanet Adkins if (sensorType == "humidity") 231c9563608SJanet Adkins { 232c9563608SJanet Adkins return sensor::ReadingType::Humidity; 233c9563608SJanet Adkins } 234c9563608SJanet Adkins if (sensorType == "altitude") 235c9563608SJanet Adkins { 236c9563608SJanet Adkins return sensor::ReadingType::Altitude; 237c9563608SJanet Adkins } 238c9563608SJanet Adkins if (sensorType == "airflow") 239c9563608SJanet Adkins { 240c9563608SJanet Adkins return sensor::ReadingType::AirFlow; 241c9563608SJanet Adkins } 242c9563608SJanet Adkins if (sensorType == "energy") 243c9563608SJanet Adkins { 244c9563608SJanet Adkins return sensor::ReadingType::EnergyJoules; 245c9563608SJanet Adkins } 246c9563608SJanet Adkins return sensor::ReadingType::Invalid; 247c9563608SJanet Adkins } 248c9563608SJanet Adkins 249c9563608SJanet Adkins } // namespace sensors 250c9563608SJanet Adkins 251c9563608SJanet Adkins /** 252c9563608SJanet Adkins * @brief Returns the Redfish State value for the specified inventory item. 253c9563608SJanet Adkins * @param inventoryItem D-Bus inventory item associated with a sensor. 254c9563608SJanet Adkins * @param sensorAvailable Boolean representing if D-Bus sensor is marked as 255c9563608SJanet Adkins * available. 256c9563608SJanet Adkins * @return State value for inventory item. 257c9563608SJanet Adkins */ 258c9563608SJanet Adkins inline resource::State getState(const InventoryItem* inventoryItem, 259c9563608SJanet Adkins const bool sensorAvailable) 260c9563608SJanet Adkins { 261c9563608SJanet Adkins if ((inventoryItem != nullptr) && !(inventoryItem->isPresent)) 262c9563608SJanet Adkins { 263c9563608SJanet Adkins return resource::State::Absent; 264c9563608SJanet Adkins } 265c9563608SJanet Adkins 266c9563608SJanet Adkins if (!sensorAvailable) 267c9563608SJanet Adkins { 268c9563608SJanet Adkins return resource::State::UnavailableOffline; 269c9563608SJanet Adkins } 270c9563608SJanet Adkins 271c9563608SJanet Adkins return resource::State::Enabled; 272c9563608SJanet Adkins } 273c9563608SJanet Adkins 274c9563608SJanet Adkins /** 275c9563608SJanet Adkins * @brief Returns the Redfish Health value for the specified sensor. 276c9563608SJanet Adkins * @param sensorJson Sensor JSON object. 277c9563608SJanet Adkins * @param valuesDict Map of all sensor DBus values. 278c9563608SJanet Adkins * @param inventoryItem D-Bus inventory item associated with the sensor. Will 279c9563608SJanet Adkins * be nullptr if no associated inventory item was found. 280c9563608SJanet Adkins * @return Health value for sensor. 281c9563608SJanet Adkins */ 282c9563608SJanet Adkins inline std::string getHealth(nlohmann::json& sensorJson, 283c9563608SJanet Adkins const dbus::utility::DBusPropertiesMap& valuesDict, 284c9563608SJanet Adkins const InventoryItem* inventoryItem) 285c9563608SJanet Adkins { 286c9563608SJanet Adkins // Get current health value (if any) in the sensor JSON object. Some JSON 287c9563608SJanet Adkins // objects contain multiple sensors (such as PowerSupplies). We want to set 288c9563608SJanet Adkins // the overall health to be the most severe of any of the sensors. 289c9563608SJanet Adkins std::string currentHealth; 290c9563608SJanet Adkins auto statusIt = sensorJson.find("Status"); 291c9563608SJanet Adkins if (statusIt != sensorJson.end()) 292c9563608SJanet Adkins { 293c9563608SJanet Adkins auto healthIt = statusIt->find("Health"); 294c9563608SJanet Adkins if (healthIt != statusIt->end()) 295c9563608SJanet Adkins { 296c9563608SJanet Adkins std::string* health = healthIt->get_ptr<std::string*>(); 297c9563608SJanet Adkins if (health != nullptr) 298c9563608SJanet Adkins { 299c9563608SJanet Adkins currentHealth = *health; 300c9563608SJanet Adkins } 301c9563608SJanet Adkins } 302c9563608SJanet Adkins } 303c9563608SJanet Adkins 304c9563608SJanet Adkins // If current health in JSON object is already Critical, return that. This 305c9563608SJanet Adkins // should override the sensor health, which might be less severe. 306c9563608SJanet Adkins if (currentHealth == "Critical") 307c9563608SJanet Adkins { 308c9563608SJanet Adkins return "Critical"; 309c9563608SJanet Adkins } 310c9563608SJanet Adkins 311c9563608SJanet Adkins const bool* criticalAlarmHigh = nullptr; 312c9563608SJanet Adkins const bool* criticalAlarmLow = nullptr; 313c9563608SJanet Adkins const bool* warningAlarmHigh = nullptr; 314c9563608SJanet Adkins const bool* warningAlarmLow = nullptr; 315c9563608SJanet Adkins 316c9563608SJanet Adkins const bool success = sdbusplus::unpackPropertiesNoThrow( 317c9563608SJanet Adkins dbus_utils::UnpackErrorPrinter(), valuesDict, "CriticalAlarmHigh", 318c9563608SJanet Adkins criticalAlarmHigh, "CriticalAlarmLow", criticalAlarmLow, 319c9563608SJanet Adkins "WarningAlarmHigh", warningAlarmHigh, "WarningAlarmLow", 320c9563608SJanet Adkins warningAlarmLow); 321c9563608SJanet Adkins 322c9563608SJanet Adkins if (success) 323c9563608SJanet Adkins { 324c9563608SJanet Adkins // Check if sensor has critical threshold alarm 325c9563608SJanet Adkins if ((criticalAlarmHigh != nullptr && *criticalAlarmHigh) || 326c9563608SJanet Adkins (criticalAlarmLow != nullptr && *criticalAlarmLow)) 327c9563608SJanet Adkins { 328c9563608SJanet Adkins return "Critical"; 329c9563608SJanet Adkins } 330c9563608SJanet Adkins } 331c9563608SJanet Adkins 332c9563608SJanet Adkins // Check if associated inventory item is not functional 333c9563608SJanet Adkins if ((inventoryItem != nullptr) && !(inventoryItem->isFunctional)) 334c9563608SJanet Adkins { 335c9563608SJanet Adkins return "Critical"; 336c9563608SJanet Adkins } 337c9563608SJanet Adkins 338c9563608SJanet Adkins // If current health in JSON object is already Warning, return that. This 339c9563608SJanet Adkins // should override the sensor status, which might be less severe. 340c9563608SJanet Adkins if (currentHealth == "Warning") 341c9563608SJanet Adkins { 342c9563608SJanet Adkins return "Warning"; 343c9563608SJanet Adkins } 344c9563608SJanet Adkins 345c9563608SJanet Adkins if (success) 346c9563608SJanet Adkins { 347c9563608SJanet Adkins // Check if sensor has warning threshold alarm 348c9563608SJanet Adkins if ((warningAlarmHigh != nullptr && *warningAlarmHigh) || 349c9563608SJanet Adkins (warningAlarmLow != nullptr && *warningAlarmLow)) 350c9563608SJanet Adkins { 351c9563608SJanet Adkins return "Warning"; 352c9563608SJanet Adkins } 353c9563608SJanet Adkins } 354c9563608SJanet Adkins 355c9563608SJanet Adkins return "OK"; 356c9563608SJanet Adkins } 357c9563608SJanet Adkins 358c9563608SJanet Adkins inline void setLedState(nlohmann::json& sensorJson, 359c9563608SJanet Adkins const InventoryItem* inventoryItem) 360c9563608SJanet Adkins { 361c9563608SJanet Adkins if (inventoryItem != nullptr && !inventoryItem->ledObjectPath.empty()) 362c9563608SJanet Adkins { 363c9563608SJanet Adkins switch (inventoryItem->ledState) 364c9563608SJanet Adkins { 365c9563608SJanet Adkins case LedState::OFF: 366c9563608SJanet Adkins sensorJson["IndicatorLED"] = resource::IndicatorLED::Off; 367c9563608SJanet Adkins break; 368c9563608SJanet Adkins case LedState::ON: 369c9563608SJanet Adkins sensorJson["IndicatorLED"] = resource::IndicatorLED::Lit; 370c9563608SJanet Adkins break; 371c9563608SJanet Adkins case LedState::BLINK: 372c9563608SJanet Adkins sensorJson["IndicatorLED"] = resource::IndicatorLED::Blinking; 373c9563608SJanet Adkins break; 374c9563608SJanet Adkins default: 375c9563608SJanet Adkins break; 376c9563608SJanet Adkins } 377c9563608SJanet Adkins } 378c9563608SJanet Adkins } 379c9563608SJanet Adkins 380c9563608SJanet Adkins /** 381c9563608SJanet Adkins * @brief Builds a json sensor representation of a sensor. 382c9563608SJanet Adkins * @param sensorName The name of the sensor to be built 383c9563608SJanet Adkins * @param sensorType The type (temperature, fan_tach, etc) of the sensor to 384c9563608SJanet Adkins * build 385c9563608SJanet Adkins * @param chassisSubNode The subnode (thermal, sensor, etc) of the sensor 386c9563608SJanet Adkins * @param propertiesDict A dictionary of the properties to build the sensor 387c9563608SJanet Adkins * from. 388c9563608SJanet Adkins * @param sensorJson The json object to fill 389c9563608SJanet Adkins * @param inventoryItem D-Bus inventory item associated with the sensor. Will 390c9563608SJanet Adkins * be nullptr if no associated inventory item was found. 391c9563608SJanet Adkins */ 392c9563608SJanet Adkins inline void objectPropertiesToJson( 393c9563608SJanet Adkins std::string_view sensorName, std::string_view sensorType, 3940c728b42SJanet Adkins ChassisSubNode chassisSubNode, 395c9563608SJanet Adkins const dbus::utility::DBusPropertiesMap& propertiesDict, 396c9563608SJanet Adkins nlohmann::json& sensorJson, InventoryItem* inventoryItem) 397c9563608SJanet Adkins { 3986fe8751cSGeorge Liu // Parameter to set to override the type we get from dbus, and force it to 3996fe8751cSGeorge Liu // int, regardless of what is available. This is used for schemas like fan, 4006fe8751cSGeorge Liu // that require integers, not floats. 4016fe8751cSGeorge Liu bool forceToInt = false; 4026fe8751cSGeorge Liu 4036fe8751cSGeorge Liu nlohmann::json::json_pointer unit("/Reading"); 4046fe8751cSGeorge Liu 4056fe8751cSGeorge Liu // This ChassisSubNode builds sensor excerpts 4066fe8751cSGeorge Liu bool isExcerpt = isExcerptNode(chassisSubNode); 4076fe8751cSGeorge Liu 4086fe8751cSGeorge Liu /* Sensor excerpts use different keys to reference the sensor. These are 4096fe8751cSGeorge Liu * built by the caller. 4106fe8751cSGeorge Liu * Additionally they don't include these additional properties. 4116fe8751cSGeorge Liu */ 4126fe8751cSGeorge Liu if (!isExcerpt) 4136fe8751cSGeorge Liu { 4140c728b42SJanet Adkins if (chassisSubNode == ChassisSubNode::sensorsNode) 415c9563608SJanet Adkins { 416c9563608SJanet Adkins std::string subNodeEscaped = getSensorId(sensorName, sensorType); 417c9563608SJanet Adkins // For sensors in SensorCollection we set Id instead of MemberId, 418c9563608SJanet Adkins // including power sensors. 419c9563608SJanet Adkins sensorJson["Id"] = std::move(subNodeEscaped); 420c9563608SJanet Adkins 421c9563608SJanet Adkins std::string sensorNameEs(sensorName); 422c9563608SJanet Adkins std::replace(sensorNameEs.begin(), sensorNameEs.end(), '_', ' '); 423c9563608SJanet Adkins sensorJson["Name"] = std::move(sensorNameEs); 424c9563608SJanet Adkins } 425c9563608SJanet Adkins else if (sensorType != "power") 426c9563608SJanet Adkins { 4276fe8751cSGeorge Liu // Set MemberId and Name for non-power sensors. For PowerSupplies 4286fe8751cSGeorge Liu // and PowerControl, those properties have more general values 4296fe8751cSGeorge Liu // because multiple sensors can be stored in the same JSON object. 430c9563608SJanet Adkins std::string sensorNameEs(sensorName); 431c9563608SJanet Adkins std::replace(sensorNameEs.begin(), sensorNameEs.end(), '_', ' '); 432c9563608SJanet Adkins sensorJson["Name"] = std::move(sensorNameEs); 433c9563608SJanet Adkins } 434c9563608SJanet Adkins 435c9563608SJanet Adkins const bool* checkAvailable = nullptr; 436c9563608SJanet Adkins bool available = true; 437c9563608SJanet Adkins const bool success = sdbusplus::unpackPropertiesNoThrow( 438c9563608SJanet Adkins dbus_utils::UnpackErrorPrinter(), propertiesDict, "Available", 439c9563608SJanet Adkins checkAvailable); 440c9563608SJanet Adkins if (!success) 441c9563608SJanet Adkins { 442c9563608SJanet Adkins messages::internalError(); 443c9563608SJanet Adkins } 444c9563608SJanet Adkins if (checkAvailable != nullptr) 445c9563608SJanet Adkins { 446c9563608SJanet Adkins available = *checkAvailable; 447c9563608SJanet Adkins } 448c9563608SJanet Adkins 449c9563608SJanet Adkins sensorJson["Status"]["State"] = getState(inventoryItem, available); 450c9563608SJanet Adkins sensorJson["Status"]["Health"] = 451c9563608SJanet Adkins getHealth(sensorJson, propertiesDict, inventoryItem); 452c9563608SJanet Adkins 4530c728b42SJanet Adkins if (chassisSubNode == ChassisSubNode::sensorsNode) 454c9563608SJanet Adkins { 455c9563608SJanet Adkins sensorJson["@odata.type"] = "#Sensor.v1_2_0.Sensor"; 456c9563608SJanet Adkins 4576fe8751cSGeorge Liu sensor::ReadingType readingType = 4586fe8751cSGeorge Liu sensors::toReadingType(sensorType); 459c9563608SJanet Adkins if (readingType == sensor::ReadingType::Invalid) 460c9563608SJanet Adkins { 461c9563608SJanet Adkins BMCWEB_LOG_ERROR("Redfish cannot map reading type for {}", 462c9563608SJanet Adkins sensorType); 463c9563608SJanet Adkins } 464c9563608SJanet Adkins else 465c9563608SJanet Adkins { 466c9563608SJanet Adkins sensorJson["ReadingType"] = readingType; 467c9563608SJanet Adkins } 468c9563608SJanet Adkins 469c9563608SJanet Adkins std::string_view readingUnits = sensors::toReadingUnits(sensorType); 470c9563608SJanet Adkins if (readingUnits.empty()) 471c9563608SJanet Adkins { 472c9563608SJanet Adkins BMCWEB_LOG_ERROR("Redfish cannot map reading unit for {}", 473c9563608SJanet Adkins sensorType); 474c9563608SJanet Adkins } 475c9563608SJanet Adkins else 476c9563608SJanet Adkins { 477c9563608SJanet Adkins sensorJson["ReadingUnits"] = readingUnits; 478c9563608SJanet Adkins } 479c9563608SJanet Adkins } 480c9563608SJanet Adkins else if (sensorType == "temperature") 481c9563608SJanet Adkins { 482c9563608SJanet Adkins unit = "/ReadingCelsius"_json_pointer; 483c9563608SJanet Adkins sensorJson["@odata.type"] = "#Thermal.v1_3_0.Temperature"; 484c9563608SJanet Adkins // TODO(ed) Documentation says that path should be type fan_tach, 485c9563608SJanet Adkins // implementation seems to implement fan 486c9563608SJanet Adkins } 487c9563608SJanet Adkins else if (sensorType == "fan" || sensorType == "fan_tach") 488c9563608SJanet Adkins { 489c9563608SJanet Adkins unit = "/Reading"_json_pointer; 490c9563608SJanet Adkins sensorJson["ReadingUnits"] = thermal::ReadingUnits::RPM; 491c9563608SJanet Adkins sensorJson["@odata.type"] = "#Thermal.v1_3_0.Fan"; 492c9563608SJanet Adkins setLedState(sensorJson, inventoryItem); 493c9563608SJanet Adkins forceToInt = true; 494c9563608SJanet Adkins } 495c9563608SJanet Adkins else if (sensorType == "fan_pwm") 496c9563608SJanet Adkins { 497c9563608SJanet Adkins unit = "/Reading"_json_pointer; 498c9563608SJanet Adkins sensorJson["ReadingUnits"] = thermal::ReadingUnits::Percent; 499c9563608SJanet Adkins sensorJson["@odata.type"] = "#Thermal.v1_3_0.Fan"; 500c9563608SJanet Adkins setLedState(sensorJson, inventoryItem); 501c9563608SJanet Adkins forceToInt = true; 502c9563608SJanet Adkins } 503c9563608SJanet Adkins else if (sensorType == "voltage") 504c9563608SJanet Adkins { 505c9563608SJanet Adkins unit = "/ReadingVolts"_json_pointer; 506c9563608SJanet Adkins sensorJson["@odata.type"] = "#Power.v1_0_0.Voltage"; 507c9563608SJanet Adkins } 508c9563608SJanet Adkins else if (sensorType == "power") 509c9563608SJanet Adkins { 510c9563608SJanet Adkins std::string lower; 511c9563608SJanet Adkins std::ranges::transform(sensorName, std::back_inserter(lower), 512c9563608SJanet Adkins bmcweb::asciiToLower); 513c9563608SJanet Adkins if (lower == "total_power") 514c9563608SJanet Adkins { 515c9563608SJanet Adkins sensorJson["@odata.type"] = "#Power.v1_0_0.PowerControl"; 516c9563608SJanet Adkins // Put multiple "sensors" into a single PowerControl, so have 517c9563608SJanet Adkins // generic names for MemberId and Name. Follows Redfish mockup. 518c9563608SJanet Adkins sensorJson["MemberId"] = "0"; 519c9563608SJanet Adkins sensorJson["Name"] = "Chassis Power Control"; 520c9563608SJanet Adkins unit = "/PowerConsumedWatts"_json_pointer; 521c9563608SJanet Adkins } 522c9563608SJanet Adkins else if (lower.find("input") != std::string::npos) 523c9563608SJanet Adkins { 524c9563608SJanet Adkins unit = "/PowerInputWatts"_json_pointer; 525c9563608SJanet Adkins } 526c9563608SJanet Adkins else 527c9563608SJanet Adkins { 528c9563608SJanet Adkins unit = "/PowerOutputWatts"_json_pointer; 529c9563608SJanet Adkins } 530c9563608SJanet Adkins } 531c9563608SJanet Adkins else 532c9563608SJanet Adkins { 5336fe8751cSGeorge Liu BMCWEB_LOG_ERROR("Redfish cannot map object type for {}", 5346fe8751cSGeorge Liu sensorName); 535c9563608SJanet Adkins return; 536c9563608SJanet Adkins } 5376fe8751cSGeorge Liu } 5386fe8751cSGeorge Liu 539c9563608SJanet Adkins // Map of dbus interface name, dbus property name and redfish property_name 540c9563608SJanet Adkins std::vector< 541c9563608SJanet Adkins std::tuple<const char*, const char*, nlohmann::json::json_pointer>> 542c9563608SJanet Adkins properties; 543c9563608SJanet Adkins 544c9563608SJanet Adkins properties.emplace_back("xyz.openbmc_project.Sensor.Value", "Value", unit); 545c9563608SJanet Adkins 5466fe8751cSGeorge Liu if (!isExcerpt) 5476fe8751cSGeorge Liu { 5480c728b42SJanet Adkins if (chassisSubNode == ChassisSubNode::sensorsNode) 549c9563608SJanet Adkins { 550c9563608SJanet Adkins properties.emplace_back( 551c9563608SJanet Adkins "xyz.openbmc_project.Sensor.Threshold.Warning", "WarningHigh", 552c9563608SJanet Adkins "/Thresholds/UpperCaution/Reading"_json_pointer); 553c9563608SJanet Adkins properties.emplace_back( 554c9563608SJanet Adkins "xyz.openbmc_project.Sensor.Threshold.Warning", "WarningLow", 555c9563608SJanet Adkins "/Thresholds/LowerCaution/Reading"_json_pointer); 556c9563608SJanet Adkins properties.emplace_back( 557c9563608SJanet Adkins "xyz.openbmc_project.Sensor.Threshold.Critical", "CriticalHigh", 558c9563608SJanet Adkins "/Thresholds/UpperCritical/Reading"_json_pointer); 559c9563608SJanet Adkins properties.emplace_back( 560c9563608SJanet Adkins "xyz.openbmc_project.Sensor.Threshold.Critical", "CriticalLow", 561c9563608SJanet Adkins "/Thresholds/LowerCritical/Reading"_json_pointer); 562cd5a898fSJanet Adkins 563cd5a898fSJanet Adkins /* Add additional properties specific to sensorType */ 564cd5a898fSJanet Adkins if (sensorType == "fan_tach") 565cd5a898fSJanet Adkins { 5666fe8751cSGeorge Liu properties.emplace_back("xyz.openbmc_project.Sensor.Value", 5676fe8751cSGeorge Liu "Value", "/SpeedRPM"_json_pointer); 568cd5a898fSJanet Adkins } 569c9563608SJanet Adkins } 570c9563608SJanet Adkins else if (sensorType != "power") 571c9563608SJanet Adkins { 5726fe8751cSGeorge Liu properties.emplace_back( 5736fe8751cSGeorge Liu "xyz.openbmc_project.Sensor.Threshold.Warning", "WarningHigh", 574c9563608SJanet Adkins "/UpperThresholdNonCritical"_json_pointer); 5756fe8751cSGeorge Liu properties.emplace_back( 5766fe8751cSGeorge Liu "xyz.openbmc_project.Sensor.Threshold.Warning", "WarningLow", 577c9563608SJanet Adkins "/LowerThresholdNonCritical"_json_pointer); 5786fe8751cSGeorge Liu properties.emplace_back( 5796fe8751cSGeorge Liu "xyz.openbmc_project.Sensor.Threshold.Critical", "CriticalHigh", 580c9563608SJanet Adkins "/UpperThresholdCritical"_json_pointer); 5816fe8751cSGeorge Liu properties.emplace_back( 5826fe8751cSGeorge Liu "xyz.openbmc_project.Sensor.Threshold.Critical", "CriticalLow", 583c9563608SJanet Adkins "/LowerThresholdCritical"_json_pointer); 584c9563608SJanet Adkins } 585c9563608SJanet Adkins 586c9563608SJanet Adkins // TODO Need to get UpperThresholdFatal and LowerThresholdFatal 587c9563608SJanet Adkins 5880c728b42SJanet Adkins if (chassisSubNode == ChassisSubNode::sensorsNode) 589c9563608SJanet Adkins { 5906fe8751cSGeorge Liu properties.emplace_back("xyz.openbmc_project.Sensor.Value", 5916fe8751cSGeorge Liu "MinValue", 592c9563608SJanet Adkins "/ReadingRangeMin"_json_pointer); 5936fe8751cSGeorge Liu properties.emplace_back("xyz.openbmc_project.Sensor.Value", 5946fe8751cSGeorge Liu "MaxValue", 595c9563608SJanet Adkins "/ReadingRangeMax"_json_pointer); 596c9563608SJanet Adkins properties.emplace_back("xyz.openbmc_project.Sensor.Accuracy", 597c9563608SJanet Adkins "Accuracy", "/Accuracy"_json_pointer); 598c9563608SJanet Adkins } 599c9563608SJanet Adkins else if (sensorType == "temperature") 600c9563608SJanet Adkins { 6016fe8751cSGeorge Liu properties.emplace_back("xyz.openbmc_project.Sensor.Value", 6026fe8751cSGeorge Liu "MinValue", 603c9563608SJanet Adkins "/MinReadingRangeTemp"_json_pointer); 6046fe8751cSGeorge Liu properties.emplace_back("xyz.openbmc_project.Sensor.Value", 6056fe8751cSGeorge Liu "MaxValue", 606c9563608SJanet Adkins "/MaxReadingRangeTemp"_json_pointer); 607c9563608SJanet Adkins } 608c9563608SJanet Adkins else if (sensorType != "power") 609c9563608SJanet Adkins { 6106fe8751cSGeorge Liu properties.emplace_back("xyz.openbmc_project.Sensor.Value", 6116fe8751cSGeorge Liu "MinValue", 612c9563608SJanet Adkins "/MinReadingRange"_json_pointer); 6136fe8751cSGeorge Liu properties.emplace_back("xyz.openbmc_project.Sensor.Value", 6146fe8751cSGeorge Liu "MaxValue", 615c9563608SJanet Adkins "/MaxReadingRange"_json_pointer); 616c9563608SJanet Adkins } 6176fe8751cSGeorge Liu } 618c9563608SJanet Adkins 619c9563608SJanet Adkins for (const std::tuple<const char*, const char*, 620c9563608SJanet Adkins nlohmann::json::json_pointer>& p : properties) 621c9563608SJanet Adkins { 622c9563608SJanet Adkins for (const auto& [valueName, valueVariant] : propertiesDict) 623c9563608SJanet Adkins { 624c9563608SJanet Adkins if (valueName != std::get<1>(p)) 625c9563608SJanet Adkins { 626c9563608SJanet Adkins continue; 627c9563608SJanet Adkins } 628c9563608SJanet Adkins 629c9563608SJanet Adkins // The property we want to set may be nested json, so use 630c9563608SJanet Adkins // a json_pointer for easy indexing into the json structure. 631c9563608SJanet Adkins const nlohmann::json::json_pointer& key = std::get<2>(p); 632c9563608SJanet Adkins 633c9563608SJanet Adkins const double* doubleValue = std::get_if<double>(&valueVariant); 634c9563608SJanet Adkins if (doubleValue == nullptr) 635c9563608SJanet Adkins { 636c9563608SJanet Adkins BMCWEB_LOG_ERROR("Got value interface that wasn't double"); 637c9563608SJanet Adkins continue; 638c9563608SJanet Adkins } 639c9563608SJanet Adkins if (!std::isfinite(*doubleValue)) 640c9563608SJanet Adkins { 641c9563608SJanet Adkins if (valueName == "Value") 642c9563608SJanet Adkins { 643c9563608SJanet Adkins // Readings are allowed to be NAN for unavailable; coerce 644c9563608SJanet Adkins // them to null in the json response. 645c9563608SJanet Adkins sensorJson[key] = nullptr; 646c9563608SJanet Adkins continue; 647c9563608SJanet Adkins } 648c9563608SJanet Adkins BMCWEB_LOG_WARNING("Sensor value for {} was unexpectedly {}", 649c9563608SJanet Adkins valueName, *doubleValue); 650c9563608SJanet Adkins continue; 651c9563608SJanet Adkins } 652c9563608SJanet Adkins if (forceToInt) 653c9563608SJanet Adkins { 654c9563608SJanet Adkins sensorJson[key] = static_cast<int64_t>(*doubleValue); 655c9563608SJanet Adkins } 656c9563608SJanet Adkins else 657c9563608SJanet Adkins { 658c9563608SJanet Adkins sensorJson[key] = *doubleValue; 659c9563608SJanet Adkins } 660c9563608SJanet Adkins } 661c9563608SJanet Adkins } 662c9563608SJanet Adkins } 663c9563608SJanet Adkins 6646fe8751cSGeorge Liu /** 6656fe8751cSGeorge Liu * @brief Builds a json sensor excerpt representation of a sensor. 6666fe8751cSGeorge Liu * 6676fe8751cSGeorge Liu * @details This is a wrapper function to provide consistent setting of 6686fe8751cSGeorge Liu * "DataSourceUri" for sensor excerpts and filling of properties. Since sensor 6696fe8751cSGeorge Liu * excerpts usually have just the D-Bus path for the sensor that is accepted 6706fe8751cSGeorge Liu * and used to build "DataSourceUri". 6716fe8751cSGeorge Liu 6726fe8751cSGeorge Liu * @param path The D-Bus path to the sensor to be built 6736fe8751cSGeorge Liu * @param chassisId The Chassis Id for the sensor 6746fe8751cSGeorge Liu * @param chassisSubNode The subnode (e.g. ThermalMetrics) of the sensor 6756fe8751cSGeorge Liu * @param sensorTypeExpected The expected type of the sensor 6766fe8751cSGeorge Liu * @param propertiesDict A dictionary of the properties to build the sensor 6776fe8751cSGeorge Liu * from. 6786fe8751cSGeorge Liu * @param sensorJson The json object to fill 6796fe8751cSGeorge Liu * @returns True if sensorJson object filled. False on any error. 6806fe8751cSGeorge Liu * Caller is responsible for handling error. 6816fe8751cSGeorge Liu */ 6826fe8751cSGeorge Liu inline bool objectExcerptToJson( 6836fe8751cSGeorge Liu const std::string& path, const std::string_view chassisId, 6846fe8751cSGeorge Liu ChassisSubNode chassisSubNode, 6856fe8751cSGeorge Liu const std::optional<std::string>& sensorTypeExpected, 6866fe8751cSGeorge Liu const dbus::utility::DBusPropertiesMap& propertiesDict, 6876fe8751cSGeorge Liu nlohmann::json& sensorJson) 6886fe8751cSGeorge Liu { 6896fe8751cSGeorge Liu if (!isExcerptNode(chassisSubNode)) 6906fe8751cSGeorge Liu { 6916fe8751cSGeorge Liu BMCWEB_LOG_DEBUG("{} is not a sensor excerpt", 6926fe8751cSGeorge Liu chassisSubNodeToString(chassisSubNode)); 6936fe8751cSGeorge Liu return false; 6946fe8751cSGeorge Liu } 6956fe8751cSGeorge Liu 6966fe8751cSGeorge Liu sdbusplus::message::object_path sensorPath(path); 6976fe8751cSGeorge Liu std::string sensorName = sensorPath.filename(); 6986fe8751cSGeorge Liu std::string sensorType = sensorPath.parent_path().filename(); 6996fe8751cSGeorge Liu if (sensorName.empty() || sensorType.empty()) 7006fe8751cSGeorge Liu { 7016fe8751cSGeorge Liu BMCWEB_LOG_DEBUG("Invalid sensor path {}", path); 7026fe8751cSGeorge Liu return false; 7036fe8751cSGeorge Liu } 7046fe8751cSGeorge Liu 7056fe8751cSGeorge Liu if (sensorTypeExpected && (sensorType != *sensorTypeExpected)) 7066fe8751cSGeorge Liu { 7076fe8751cSGeorge Liu BMCWEB_LOG_DEBUG("{} is not expected type {}", path, 7086fe8751cSGeorge Liu *sensorTypeExpected); 7096fe8751cSGeorge Liu return false; 7106fe8751cSGeorge Liu } 7116fe8751cSGeorge Liu 7126fe8751cSGeorge Liu // Sensor excerpts use DataSourceUri to reference full sensor Redfish path 7136fe8751cSGeorge Liu sensorJson["DataSourceUri"] = 7146fe8751cSGeorge Liu boost::urls::format("/redfish/v1/Chassis/{}/Sensors/{}", chassisId, 7156fe8751cSGeorge Liu getSensorId(sensorName, sensorType)); 7166fe8751cSGeorge Liu 7176fe8751cSGeorge Liu // Fill in sensor excerpt properties 7186fe8751cSGeorge Liu objectPropertiesToJson(sensorName, sensorType, chassisSubNode, 7196fe8751cSGeorge Liu propertiesDict, sensorJson, nullptr); 7206fe8751cSGeorge Liu 7216fe8751cSGeorge Liu return true; 7226fe8751cSGeorge Liu } 7236fe8751cSGeorge Liu 7246fe8751cSGeorge Liu // Maps D-Bus: Service, SensorPath 7256fe8751cSGeorge Liu using SensorServicePathMap = std::pair<std::string, std::string>; 7266fe8751cSGeorge Liu using SensorServicePathList = std::vector<SensorServicePathMap>; 7276fe8751cSGeorge Liu 7286fe8751cSGeorge Liu inline void getAllSensorObjects( 7296fe8751cSGeorge Liu const std::string& associatedPath, const std::string& path, 7306fe8751cSGeorge Liu std::span<const std::string_view> interfaces, const int32_t depth, 7316fe8751cSGeorge Liu std::function<void(const boost::system::error_code& ec, 7326fe8751cSGeorge Liu SensorServicePathList&)>&& callback) 7336fe8751cSGeorge Liu { 7346fe8751cSGeorge Liu sdbusplus::message::object_path endpointPath{associatedPath}; 7356fe8751cSGeorge Liu endpointPath /= "all_sensors"; 7366fe8751cSGeorge Liu 7376fe8751cSGeorge Liu dbus::utility::getAssociatedSubTree( 7386fe8751cSGeorge Liu endpointPath, sdbusplus::message::object_path(path), depth, interfaces, 7396fe8751cSGeorge Liu [callback = std::move(callback)]( 7406fe8751cSGeorge Liu const boost::system::error_code& ec, 7416fe8751cSGeorge Liu const dbus::utility::MapperGetSubTreeResponse& subtree) { 7426fe8751cSGeorge Liu SensorServicePathList sensorsServiceAndPath; 7436fe8751cSGeorge Liu 7446fe8751cSGeorge Liu if (ec) 7456fe8751cSGeorge Liu { 7466fe8751cSGeorge Liu callback(ec, sensorsServiceAndPath); 7476fe8751cSGeorge Liu return; 7486fe8751cSGeorge Liu } 7496fe8751cSGeorge Liu 7506fe8751cSGeorge Liu for (const auto& [sensorPath, serviceMaps] : subtree) 7516fe8751cSGeorge Liu { 7526fe8751cSGeorge Liu for (const auto& [service, mapInterfaces] : serviceMaps) 7536fe8751cSGeorge Liu { 7546fe8751cSGeorge Liu sensorsServiceAndPath.emplace_back(service, sensorPath); 7556fe8751cSGeorge Liu } 7566fe8751cSGeorge Liu } 7576fe8751cSGeorge Liu 7586fe8751cSGeorge Liu callback(ec, sensorsServiceAndPath); 7596fe8751cSGeorge Liu }); 7606fe8751cSGeorge Liu } 7616fe8751cSGeorge Liu 7621516c21bSJanet Adkins } // namespace sensor_utils 7631516c21bSJanet Adkins } // namespace redfish 764