140e9b92eSEd Tanous // SPDX-License-Identifier: Apache-2.0 240e9b92eSEd Tanous // SPDX-FileCopyrightText: Copyright OpenBMC Authors 31516c21bSJanet Adkins #pragma once 41516c21bSJanet Adkins 5c9563608SJanet Adkins #include "dbus_utility.hpp" 6d7857201SEd Tanous #include "error_messages.hpp" 7c9563608SJanet Adkins #include "generated/enums/resource.hpp" 8c9563608SJanet Adkins #include "generated/enums/sensor.hpp" 9c9563608SJanet Adkins #include "generated/enums/thermal.hpp" 10d7857201SEd Tanous #include "logging.hpp" 11c9563608SJanet Adkins #include "str_utility.hpp" 12c9563608SJanet Adkins #include "utils/dbus_utils.hpp" 13c9563608SJanet Adkins 146fe8751cSGeorge Liu #include <boost/url/format.hpp> 15d7857201SEd Tanous #include <nlohmann/json.hpp> 16d7857201SEd Tanous #include <sdbusplus/message/native_types.hpp> 17c9563608SJanet Adkins #include <sdbusplus/unpack_properties.hpp> 18c9563608SJanet Adkins 191516c21bSJanet Adkins #include <algorithm> 20d7857201SEd Tanous #include <cmath> 21d7857201SEd Tanous #include <cstddef> 22d7857201SEd Tanous #include <cstdint> 231516c21bSJanet Adkins #include <format> 246fe8751cSGeorge Liu #include <functional> 25d7857201SEd Tanous #include <iterator> 266fe8751cSGeorge Liu #include <optional> 271516c21bSJanet Adkins #include <ranges> 28d7857201SEd Tanous #include <set> 29d7857201SEd Tanous #include <span> 301516c21bSJanet Adkins #include <string> 311516c21bSJanet Adkins #include <string_view> 32c9563608SJanet Adkins #include <tuple> 331516c21bSJanet Adkins #include <utility> 34d7857201SEd Tanous #include <variant> 351516c21bSJanet Adkins #include <vector> 361516c21bSJanet Adkins 371516c21bSJanet Adkins namespace redfish 381516c21bSJanet Adkins { 391516c21bSJanet Adkins namespace sensor_utils 401516c21bSJanet Adkins { 411516c21bSJanet Adkins 420c728b42SJanet Adkins enum class ChassisSubNode 430c728b42SJanet Adkins { 440c728b42SJanet Adkins powerNode, 450c728b42SJanet Adkins sensorsNode, 460c728b42SJanet Adkins thermalNode, 476fe8751cSGeorge Liu thermalMetricsNode, 480c728b42SJanet Adkins unknownNode, 490c728b42SJanet Adkins }; 500c728b42SJanet Adkins 510c728b42SJanet Adkins constexpr std::string_view chassisSubNodeToString(ChassisSubNode subNode) 520c728b42SJanet Adkins { 530c728b42SJanet Adkins switch (subNode) 540c728b42SJanet Adkins { 550c728b42SJanet Adkins case ChassisSubNode::powerNode: 560c728b42SJanet Adkins return "Power"; 570c728b42SJanet Adkins case ChassisSubNode::sensorsNode: 580c728b42SJanet Adkins return "Sensors"; 590c728b42SJanet Adkins case ChassisSubNode::thermalNode: 600c728b42SJanet Adkins return "Thermal"; 616fe8751cSGeorge Liu case ChassisSubNode::thermalMetricsNode: 626fe8751cSGeorge Liu return "ThermalMetrics"; 630c728b42SJanet Adkins case ChassisSubNode::unknownNode: 640c728b42SJanet Adkins default: 650c728b42SJanet Adkins return ""; 660c728b42SJanet Adkins } 670c728b42SJanet Adkins } 680c728b42SJanet Adkins 690c728b42SJanet Adkins inline ChassisSubNode chassisSubNodeFromString(const std::string& subNodeStr) 700c728b42SJanet Adkins { 710c728b42SJanet Adkins // If none match unknownNode is returned 720c728b42SJanet Adkins ChassisSubNode subNode = ChassisSubNode::unknownNode; 730c728b42SJanet Adkins 740c728b42SJanet Adkins if (subNodeStr == "Power") 750c728b42SJanet Adkins { 760c728b42SJanet Adkins subNode = ChassisSubNode::powerNode; 770c728b42SJanet Adkins } 780c728b42SJanet Adkins else if (subNodeStr == "Sensors") 790c728b42SJanet Adkins { 800c728b42SJanet Adkins subNode = ChassisSubNode::sensorsNode; 810c728b42SJanet Adkins } 820c728b42SJanet Adkins else if (subNodeStr == "Thermal") 830c728b42SJanet Adkins { 840c728b42SJanet Adkins subNode = ChassisSubNode::thermalNode; 850c728b42SJanet Adkins } 866fe8751cSGeorge Liu else if (subNodeStr == "ThermalMetrics") 876fe8751cSGeorge Liu { 886fe8751cSGeorge Liu subNode = ChassisSubNode::thermalMetricsNode; 896fe8751cSGeorge Liu } 900c728b42SJanet Adkins 910c728b42SJanet Adkins return subNode; 920c728b42SJanet Adkins } 93c9563608SJanet Adkins 946fe8751cSGeorge Liu inline bool isExcerptNode(const ChassisSubNode subNode) 956fe8751cSGeorge Liu { 966fe8751cSGeorge Liu return (subNode == ChassisSubNode::thermalMetricsNode); 976fe8751cSGeorge Liu } 986fe8751cSGeorge Liu 99c9563608SJanet Adkins /** 100c9563608SJanet Adkins * Possible states for physical inventory leds 101c9563608SJanet Adkins */ 102c9563608SJanet Adkins enum class LedState 103c9563608SJanet Adkins { 104c9563608SJanet Adkins OFF, 105c9563608SJanet Adkins ON, 106c9563608SJanet Adkins BLINK, 107c9563608SJanet Adkins UNKNOWN 108c9563608SJanet Adkins }; 109c9563608SJanet Adkins 110c9563608SJanet Adkins /** 111c9563608SJanet Adkins * D-Bus inventory item associated with one or more sensors. 112c9563608SJanet Adkins */ 113c9563608SJanet Adkins class InventoryItem 114c9563608SJanet Adkins { 115c9563608SJanet Adkins public: 116c9563608SJanet Adkins explicit InventoryItem(const std::string& objPath) : objectPath(objPath) 117c9563608SJanet Adkins { 118c9563608SJanet Adkins // Set inventory item name to last node of object path 119c9563608SJanet Adkins sdbusplus::message::object_path path(objectPath); 120c9563608SJanet Adkins name = path.filename(); 121c9563608SJanet Adkins if (name.empty()) 122c9563608SJanet Adkins { 123c9563608SJanet Adkins BMCWEB_LOG_ERROR("Failed to find '/' in {}", objectPath); 124c9563608SJanet Adkins } 125c9563608SJanet Adkins } 126c9563608SJanet Adkins 127c9563608SJanet Adkins std::string objectPath; 128c9563608SJanet Adkins std::string name; 129c9563608SJanet Adkins bool isPresent = true; 130c9563608SJanet Adkins bool isFunctional = true; 131c9563608SJanet Adkins bool isPowerSupply = false; 132c9563608SJanet Adkins int powerSupplyEfficiencyPercent = -1; 133c9563608SJanet Adkins std::string manufacturer; 134c9563608SJanet Adkins std::string model; 135c9563608SJanet Adkins std::string partNumber; 136c9563608SJanet Adkins std::string serialNumber; 137c9563608SJanet Adkins std::set<std::string> sensors; 138c9563608SJanet Adkins std::string ledObjectPath; 139c9563608SJanet Adkins LedState ledState = LedState::UNKNOWN; 140c9563608SJanet Adkins }; 141c9563608SJanet Adkins 1421516c21bSJanet Adkins inline std::string getSensorId(std::string_view sensorName, 1431516c21bSJanet Adkins std::string_view sensorType) 1441516c21bSJanet Adkins { 1451516c21bSJanet Adkins std::string normalizedType(sensorType); 1461516c21bSJanet Adkins auto remove = std::ranges::remove(normalizedType, '_'); 1471516c21bSJanet Adkins normalizedType.erase(std::ranges::begin(remove), normalizedType.end()); 1481516c21bSJanet Adkins 1491516c21bSJanet Adkins return std::format("{}_{}", normalizedType, sensorName); 1501516c21bSJanet Adkins } 1511516c21bSJanet Adkins 152504af5a0SPatrick Williams inline std::pair<std::string, std::string> splitSensorNameAndType( 153504af5a0SPatrick Williams std::string_view sensorId) 1541516c21bSJanet Adkins { 1551516c21bSJanet Adkins size_t index = sensorId.find('_'); 1561516c21bSJanet Adkins if (index == std::string::npos) 1571516c21bSJanet Adkins { 1581516c21bSJanet Adkins return std::make_pair<std::string, std::string>("", ""); 1591516c21bSJanet Adkins } 1601516c21bSJanet Adkins std::string sensorType{sensorId.substr(0, index)}; 1611516c21bSJanet Adkins std::string sensorName{sensorId.substr(index + 1)}; 1621516c21bSJanet Adkins // fan_pwm and fan_tach need special handling 1631516c21bSJanet Adkins if (sensorType == "fantach" || sensorType == "fanpwm") 1641516c21bSJanet Adkins { 1651516c21bSJanet Adkins sensorType.insert(3, 1, '_'); 1661516c21bSJanet Adkins } 1671516c21bSJanet Adkins return std::make_pair(sensorType, sensorName); 1681516c21bSJanet Adkins } 1691516c21bSJanet Adkins 170c9563608SJanet Adkins namespace sensors 171c9563608SJanet Adkins { 172c9563608SJanet Adkins inline std::string_view toReadingUnits(std::string_view sensorType) 173c9563608SJanet Adkins { 174c9563608SJanet Adkins if (sensorType == "voltage") 175c9563608SJanet Adkins { 176c9563608SJanet Adkins return "V"; 177c9563608SJanet Adkins } 178c9563608SJanet Adkins if (sensorType == "power") 179c9563608SJanet Adkins { 180c9563608SJanet Adkins return "W"; 181c9563608SJanet Adkins } 182c9563608SJanet Adkins if (sensorType == "current") 183c9563608SJanet Adkins { 184c9563608SJanet Adkins return "A"; 185c9563608SJanet Adkins } 186c9563608SJanet Adkins if (sensorType == "fan_tach") 187c9563608SJanet Adkins { 188c9563608SJanet Adkins return "RPM"; 189c9563608SJanet Adkins } 190c9563608SJanet Adkins if (sensorType == "temperature") 191c9563608SJanet Adkins { 192c9563608SJanet Adkins return "Cel"; 193c9563608SJanet Adkins } 194c9563608SJanet Adkins if (sensorType == "fan_pwm" || sensorType == "utilization" || 195c9563608SJanet Adkins sensorType == "humidity") 196c9563608SJanet Adkins { 197c9563608SJanet Adkins return "%"; 198c9563608SJanet Adkins } 199c9563608SJanet Adkins if (sensorType == "altitude") 200c9563608SJanet Adkins { 201c9563608SJanet Adkins return "m"; 202c9563608SJanet Adkins } 203c9563608SJanet Adkins if (sensorType == "airflow") 204c9563608SJanet Adkins { 205c9563608SJanet Adkins return "cft_i/min"; 206c9563608SJanet Adkins } 207c9563608SJanet Adkins if (sensorType == "energy") 208c9563608SJanet Adkins { 209c9563608SJanet Adkins return "J"; 210c9563608SJanet Adkins } 21144914192SZev Weiss if (sensorType == "liquidflow") 21244914192SZev Weiss { 21344914192SZev Weiss return "L/min"; 21444914192SZev Weiss } 21544914192SZev Weiss if (sensorType == "pressure") 21644914192SZev Weiss { 21744914192SZev Weiss return "Pa"; 21844914192SZev Weiss } 219c9563608SJanet Adkins return ""; 220c9563608SJanet Adkins } 221c9563608SJanet Adkins 222c9563608SJanet Adkins inline sensor::ReadingType toReadingType(std::string_view sensorType) 223c9563608SJanet Adkins { 224c9563608SJanet Adkins if (sensorType == "voltage") 225c9563608SJanet Adkins { 226c9563608SJanet Adkins return sensor::ReadingType::Voltage; 227c9563608SJanet Adkins } 228c9563608SJanet Adkins if (sensorType == "power") 229c9563608SJanet Adkins { 230c9563608SJanet Adkins return sensor::ReadingType::Power; 231c9563608SJanet Adkins } 232c9563608SJanet Adkins if (sensorType == "current") 233c9563608SJanet Adkins { 234c9563608SJanet Adkins return sensor::ReadingType::Current; 235c9563608SJanet Adkins } 236c9563608SJanet Adkins if (sensorType == "fan_tach") 237c9563608SJanet Adkins { 238c9563608SJanet Adkins return sensor::ReadingType::Rotational; 239c9563608SJanet Adkins } 240c9563608SJanet Adkins if (sensorType == "temperature") 241c9563608SJanet Adkins { 242c9563608SJanet Adkins return sensor::ReadingType::Temperature; 243c9563608SJanet Adkins } 244c9563608SJanet Adkins if (sensorType == "fan_pwm" || sensorType == "utilization") 245c9563608SJanet Adkins { 246c9563608SJanet Adkins return sensor::ReadingType::Percent; 247c9563608SJanet Adkins } 248c9563608SJanet Adkins if (sensorType == "humidity") 249c9563608SJanet Adkins { 250c9563608SJanet Adkins return sensor::ReadingType::Humidity; 251c9563608SJanet Adkins } 252c9563608SJanet Adkins if (sensorType == "altitude") 253c9563608SJanet Adkins { 254c9563608SJanet Adkins return sensor::ReadingType::Altitude; 255c9563608SJanet Adkins } 256c9563608SJanet Adkins if (sensorType == "airflow") 257c9563608SJanet Adkins { 258c9563608SJanet Adkins return sensor::ReadingType::AirFlow; 259c9563608SJanet Adkins } 260c9563608SJanet Adkins if (sensorType == "energy") 261c9563608SJanet Adkins { 262c9563608SJanet Adkins return sensor::ReadingType::EnergyJoules; 263c9563608SJanet Adkins } 26444914192SZev Weiss if (sensorType == "liquidflow") 26544914192SZev Weiss { 26644914192SZev Weiss return sensor::ReadingType::LiquidFlowLPM; 26744914192SZev Weiss } 26844914192SZev Weiss if (sensorType == "pressure") 26944914192SZev Weiss { 27044914192SZev Weiss return sensor::ReadingType::PressurePa; 27144914192SZev Weiss } 272c9563608SJanet Adkins return sensor::ReadingType::Invalid; 273c9563608SJanet Adkins } 274c9563608SJanet Adkins 275c9563608SJanet Adkins } // namespace sensors 276c9563608SJanet Adkins 277c9563608SJanet Adkins /** 278c9563608SJanet Adkins * @brief Returns the Redfish State value for the specified inventory item. 279c9563608SJanet Adkins * @param inventoryItem D-Bus inventory item associated with a sensor. 280c9563608SJanet Adkins * @param sensorAvailable Boolean representing if D-Bus sensor is marked as 281c9563608SJanet Adkins * available. 282c9563608SJanet Adkins * @return State value for inventory item. 283c9563608SJanet Adkins */ 284c9563608SJanet Adkins inline resource::State getState(const InventoryItem* inventoryItem, 285c9563608SJanet Adkins const bool sensorAvailable) 286c9563608SJanet Adkins { 287c9563608SJanet Adkins if ((inventoryItem != nullptr) && !(inventoryItem->isPresent)) 288c9563608SJanet Adkins { 289c9563608SJanet Adkins return resource::State::Absent; 290c9563608SJanet Adkins } 291c9563608SJanet Adkins 292c9563608SJanet Adkins if (!sensorAvailable) 293c9563608SJanet Adkins { 294c9563608SJanet Adkins return resource::State::UnavailableOffline; 295c9563608SJanet Adkins } 296c9563608SJanet Adkins 297c9563608SJanet Adkins return resource::State::Enabled; 298c9563608SJanet Adkins } 299c9563608SJanet Adkins 300c9563608SJanet Adkins /** 301c9563608SJanet Adkins * @brief Returns the Redfish Health value for the specified sensor. 302c9563608SJanet Adkins * @param sensorJson Sensor JSON object. 303c9563608SJanet Adkins * @param valuesDict Map of all sensor DBus values. 304c9563608SJanet Adkins * @param inventoryItem D-Bus inventory item associated with the sensor. Will 305c9563608SJanet Adkins * be nullptr if no associated inventory item was found. 306c9563608SJanet Adkins * @return Health value for sensor. 307c9563608SJanet Adkins */ 308c9563608SJanet Adkins inline std::string getHealth(nlohmann::json& sensorJson, 309c9563608SJanet Adkins const dbus::utility::DBusPropertiesMap& valuesDict, 310c9563608SJanet Adkins const InventoryItem* inventoryItem) 311c9563608SJanet Adkins { 312c9563608SJanet Adkins // Get current health value (if any) in the sensor JSON object. Some JSON 313c9563608SJanet Adkins // objects contain multiple sensors (such as PowerSupplies). We want to set 314c9563608SJanet Adkins // the overall health to be the most severe of any of the sensors. 315c9563608SJanet Adkins std::string currentHealth; 316c9563608SJanet Adkins auto statusIt = sensorJson.find("Status"); 317c9563608SJanet Adkins if (statusIt != sensorJson.end()) 318c9563608SJanet Adkins { 319c9563608SJanet Adkins auto healthIt = statusIt->find("Health"); 320c9563608SJanet Adkins if (healthIt != statusIt->end()) 321c9563608SJanet Adkins { 322c9563608SJanet Adkins std::string* health = healthIt->get_ptr<std::string*>(); 323c9563608SJanet Adkins if (health != nullptr) 324c9563608SJanet Adkins { 325c9563608SJanet Adkins currentHealth = *health; 326c9563608SJanet Adkins } 327c9563608SJanet Adkins } 328c9563608SJanet Adkins } 329c9563608SJanet Adkins 330c9563608SJanet Adkins // If current health in JSON object is already Critical, return that. This 331c9563608SJanet Adkins // should override the sensor health, which might be less severe. 332c9563608SJanet Adkins if (currentHealth == "Critical") 333c9563608SJanet Adkins { 334c9563608SJanet Adkins return "Critical"; 335c9563608SJanet Adkins } 336c9563608SJanet Adkins 337c9563608SJanet Adkins const bool* criticalAlarmHigh = nullptr; 338c9563608SJanet Adkins const bool* criticalAlarmLow = nullptr; 339c9563608SJanet Adkins const bool* warningAlarmHigh = nullptr; 340c9563608SJanet Adkins const bool* warningAlarmLow = nullptr; 341c9563608SJanet Adkins 342c9563608SJanet Adkins const bool success = sdbusplus::unpackPropertiesNoThrow( 343c9563608SJanet Adkins dbus_utils::UnpackErrorPrinter(), valuesDict, "CriticalAlarmHigh", 344c9563608SJanet Adkins criticalAlarmHigh, "CriticalAlarmLow", criticalAlarmLow, 345c9563608SJanet Adkins "WarningAlarmHigh", warningAlarmHigh, "WarningAlarmLow", 346c9563608SJanet Adkins warningAlarmLow); 347c9563608SJanet Adkins 348c9563608SJanet Adkins if (success) 349c9563608SJanet Adkins { 350c9563608SJanet Adkins // Check if sensor has critical threshold alarm 351c9563608SJanet Adkins if ((criticalAlarmHigh != nullptr && *criticalAlarmHigh) || 352c9563608SJanet Adkins (criticalAlarmLow != nullptr && *criticalAlarmLow)) 353c9563608SJanet Adkins { 354c9563608SJanet Adkins return "Critical"; 355c9563608SJanet Adkins } 356c9563608SJanet Adkins } 357c9563608SJanet Adkins 358c9563608SJanet Adkins // Check if associated inventory item is not functional 359c9563608SJanet Adkins if ((inventoryItem != nullptr) && !(inventoryItem->isFunctional)) 360c9563608SJanet Adkins { 361c9563608SJanet Adkins return "Critical"; 362c9563608SJanet Adkins } 363c9563608SJanet Adkins 364c9563608SJanet Adkins // If current health in JSON object is already Warning, return that. This 365c9563608SJanet Adkins // should override the sensor status, which might be less severe. 366c9563608SJanet Adkins if (currentHealth == "Warning") 367c9563608SJanet Adkins { 368c9563608SJanet Adkins return "Warning"; 369c9563608SJanet Adkins } 370c9563608SJanet Adkins 371c9563608SJanet Adkins if (success) 372c9563608SJanet Adkins { 373c9563608SJanet Adkins // Check if sensor has warning threshold alarm 374c9563608SJanet Adkins if ((warningAlarmHigh != nullptr && *warningAlarmHigh) || 375c9563608SJanet Adkins (warningAlarmLow != nullptr && *warningAlarmLow)) 376c9563608SJanet Adkins { 377c9563608SJanet Adkins return "Warning"; 378c9563608SJanet Adkins } 379c9563608SJanet Adkins } 380c9563608SJanet Adkins 381c9563608SJanet Adkins return "OK"; 382c9563608SJanet Adkins } 383c9563608SJanet Adkins 384c9563608SJanet Adkins inline void setLedState(nlohmann::json& sensorJson, 385c9563608SJanet Adkins const InventoryItem* inventoryItem) 386c9563608SJanet Adkins { 387c9563608SJanet Adkins if (inventoryItem != nullptr && !inventoryItem->ledObjectPath.empty()) 388c9563608SJanet Adkins { 389c9563608SJanet Adkins switch (inventoryItem->ledState) 390c9563608SJanet Adkins { 391c9563608SJanet Adkins case LedState::OFF: 392c9563608SJanet Adkins sensorJson["IndicatorLED"] = resource::IndicatorLED::Off; 393c9563608SJanet Adkins break; 394c9563608SJanet Adkins case LedState::ON: 395c9563608SJanet Adkins sensorJson["IndicatorLED"] = resource::IndicatorLED::Lit; 396c9563608SJanet Adkins break; 397c9563608SJanet Adkins case LedState::BLINK: 398c9563608SJanet Adkins sensorJson["IndicatorLED"] = resource::IndicatorLED::Blinking; 399c9563608SJanet Adkins break; 400c9563608SJanet Adkins default: 401c9563608SJanet Adkins break; 402c9563608SJanet Adkins } 403c9563608SJanet Adkins } 404c9563608SJanet Adkins } 405c9563608SJanet Adkins 406c9563608SJanet Adkins /** 407c9563608SJanet Adkins * @brief Builds a json sensor representation of a sensor. 408c9563608SJanet Adkins * @param sensorName The name of the sensor to be built 409c9563608SJanet Adkins * @param sensorType The type (temperature, fan_tach, etc) of the sensor to 410c9563608SJanet Adkins * build 411c9563608SJanet Adkins * @param chassisSubNode The subnode (thermal, sensor, etc) of the sensor 412c9563608SJanet Adkins * @param propertiesDict A dictionary of the properties to build the sensor 413c9563608SJanet Adkins * from. 414c9563608SJanet Adkins * @param sensorJson The json object to fill 415c9563608SJanet Adkins * @param inventoryItem D-Bus inventory item associated with the sensor. Will 416c9563608SJanet Adkins * be nullptr if no associated inventory item was found. 417c9563608SJanet Adkins */ 418c9563608SJanet Adkins inline void objectPropertiesToJson( 419c9563608SJanet Adkins std::string_view sensorName, std::string_view sensorType, 4200c728b42SJanet Adkins ChassisSubNode chassisSubNode, 421c9563608SJanet Adkins const dbus::utility::DBusPropertiesMap& propertiesDict, 422c9563608SJanet Adkins nlohmann::json& sensorJson, InventoryItem* inventoryItem) 423c9563608SJanet Adkins { 4246fe8751cSGeorge Liu // Parameter to set to override the type we get from dbus, and force it to 4256fe8751cSGeorge Liu // int, regardless of what is available. This is used for schemas like fan, 4266fe8751cSGeorge Liu // that require integers, not floats. 4276fe8751cSGeorge Liu bool forceToInt = false; 4286fe8751cSGeorge Liu 4296fe8751cSGeorge Liu nlohmann::json::json_pointer unit("/Reading"); 4306fe8751cSGeorge Liu 4316fe8751cSGeorge Liu // This ChassisSubNode builds sensor excerpts 4326fe8751cSGeorge Liu bool isExcerpt = isExcerptNode(chassisSubNode); 4336fe8751cSGeorge Liu 4346fe8751cSGeorge Liu /* Sensor excerpts use different keys to reference the sensor. These are 4356fe8751cSGeorge Liu * built by the caller. 4366fe8751cSGeorge Liu * Additionally they don't include these additional properties. 4376fe8751cSGeorge Liu */ 4386fe8751cSGeorge Liu if (!isExcerpt) 4396fe8751cSGeorge Liu { 4400c728b42SJanet Adkins if (chassisSubNode == ChassisSubNode::sensorsNode) 441c9563608SJanet Adkins { 442c9563608SJanet Adkins std::string subNodeEscaped = getSensorId(sensorName, sensorType); 443c9563608SJanet Adkins // For sensors in SensorCollection we set Id instead of MemberId, 444c9563608SJanet Adkins // including power sensors. 445c9563608SJanet Adkins sensorJson["Id"] = std::move(subNodeEscaped); 446c9563608SJanet Adkins 447c9563608SJanet Adkins std::string sensorNameEs(sensorName); 448c9563608SJanet Adkins std::replace(sensorNameEs.begin(), sensorNameEs.end(), '_', ' '); 449c9563608SJanet Adkins sensorJson["Name"] = std::move(sensorNameEs); 450c9563608SJanet Adkins } 451c9563608SJanet Adkins else if (sensorType != "power") 452c9563608SJanet Adkins { 4536fe8751cSGeorge Liu // Set MemberId and Name for non-power sensors. For PowerSupplies 4546fe8751cSGeorge Liu // and PowerControl, those properties have more general values 4556fe8751cSGeorge Liu // because multiple sensors can be stored in the same JSON object. 456c9563608SJanet Adkins std::string sensorNameEs(sensorName); 457c9563608SJanet Adkins std::replace(sensorNameEs.begin(), sensorNameEs.end(), '_', ' '); 458c9563608SJanet Adkins sensorJson["Name"] = std::move(sensorNameEs); 459c9563608SJanet Adkins } 460c9563608SJanet Adkins 461c9563608SJanet Adkins const bool* checkAvailable = nullptr; 462c9563608SJanet Adkins bool available = true; 463c9563608SJanet Adkins const bool success = sdbusplus::unpackPropertiesNoThrow( 464c9563608SJanet Adkins dbus_utils::UnpackErrorPrinter(), propertiesDict, "Available", 465c9563608SJanet Adkins checkAvailable); 466c9563608SJanet Adkins if (!success) 467c9563608SJanet Adkins { 468c9563608SJanet Adkins messages::internalError(); 469c9563608SJanet Adkins } 470c9563608SJanet Adkins if (checkAvailable != nullptr) 471c9563608SJanet Adkins { 472c9563608SJanet Adkins available = *checkAvailable; 473c9563608SJanet Adkins } 474c9563608SJanet Adkins 475c9563608SJanet Adkins sensorJson["Status"]["State"] = getState(inventoryItem, available); 476c9563608SJanet Adkins sensorJson["Status"]["Health"] = 477c9563608SJanet Adkins getHealth(sensorJson, propertiesDict, inventoryItem); 478c9563608SJanet Adkins 4790c728b42SJanet Adkins if (chassisSubNode == ChassisSubNode::sensorsNode) 480c9563608SJanet Adkins { 481c9563608SJanet Adkins sensorJson["@odata.type"] = "#Sensor.v1_2_0.Sensor"; 482c9563608SJanet Adkins 4836fe8751cSGeorge Liu sensor::ReadingType readingType = 4846fe8751cSGeorge Liu sensors::toReadingType(sensorType); 485c9563608SJanet Adkins if (readingType == sensor::ReadingType::Invalid) 486c9563608SJanet Adkins { 487c9563608SJanet Adkins BMCWEB_LOG_ERROR("Redfish cannot map reading type for {}", 488c9563608SJanet Adkins sensorType); 489c9563608SJanet Adkins } 490c9563608SJanet Adkins else 491c9563608SJanet Adkins { 492c9563608SJanet Adkins sensorJson["ReadingType"] = readingType; 493c9563608SJanet Adkins } 494c9563608SJanet Adkins 495c9563608SJanet Adkins std::string_view readingUnits = sensors::toReadingUnits(sensorType); 496c9563608SJanet Adkins if (readingUnits.empty()) 497c9563608SJanet Adkins { 498c9563608SJanet Adkins BMCWEB_LOG_ERROR("Redfish cannot map reading unit for {}", 499c9563608SJanet Adkins sensorType); 500c9563608SJanet Adkins } 501c9563608SJanet Adkins else 502c9563608SJanet Adkins { 503c9563608SJanet Adkins sensorJson["ReadingUnits"] = readingUnits; 504c9563608SJanet Adkins } 505c9563608SJanet Adkins } 506c9563608SJanet Adkins else if (sensorType == "temperature") 507c9563608SJanet Adkins { 508c9563608SJanet Adkins unit = "/ReadingCelsius"_json_pointer; 509c9563608SJanet Adkins sensorJson["@odata.type"] = "#Thermal.v1_3_0.Temperature"; 510c9563608SJanet Adkins // TODO(ed) Documentation says that path should be type fan_tach, 511c9563608SJanet Adkins // implementation seems to implement fan 512c9563608SJanet Adkins } 513c9563608SJanet Adkins else if (sensorType == "fan" || sensorType == "fan_tach") 514c9563608SJanet Adkins { 515c9563608SJanet Adkins unit = "/Reading"_json_pointer; 516c9563608SJanet Adkins sensorJson["ReadingUnits"] = thermal::ReadingUnits::RPM; 517c9563608SJanet Adkins sensorJson["@odata.type"] = "#Thermal.v1_3_0.Fan"; 518*f664fd8aSJanet Adkins if constexpr (BMCWEB_REDFISH_ALLOW_DEPRECATED_INDICATORLED) 519*f664fd8aSJanet Adkins { 520c9563608SJanet Adkins setLedState(sensorJson, inventoryItem); 521*f664fd8aSJanet Adkins } 522c9563608SJanet Adkins forceToInt = true; 523c9563608SJanet Adkins } 524c9563608SJanet Adkins else if (sensorType == "fan_pwm") 525c9563608SJanet Adkins { 526c9563608SJanet Adkins unit = "/Reading"_json_pointer; 527c9563608SJanet Adkins sensorJson["ReadingUnits"] = thermal::ReadingUnits::Percent; 528c9563608SJanet Adkins sensorJson["@odata.type"] = "#Thermal.v1_3_0.Fan"; 529*f664fd8aSJanet Adkins if constexpr (BMCWEB_REDFISH_ALLOW_DEPRECATED_INDICATORLED) 530*f664fd8aSJanet Adkins { 531c9563608SJanet Adkins setLedState(sensorJson, inventoryItem); 532*f664fd8aSJanet Adkins } 533c9563608SJanet Adkins forceToInt = true; 534c9563608SJanet Adkins } 535c9563608SJanet Adkins else if (sensorType == "voltage") 536c9563608SJanet Adkins { 537c9563608SJanet Adkins unit = "/ReadingVolts"_json_pointer; 538c9563608SJanet Adkins sensorJson["@odata.type"] = "#Power.v1_0_0.Voltage"; 539c9563608SJanet Adkins } 540c9563608SJanet Adkins else if (sensorType == "power") 541c9563608SJanet Adkins { 542c9563608SJanet Adkins std::string lower; 543c9563608SJanet Adkins std::ranges::transform(sensorName, std::back_inserter(lower), 544c9563608SJanet Adkins bmcweb::asciiToLower); 545c9563608SJanet Adkins if (lower == "total_power") 546c9563608SJanet Adkins { 547c9563608SJanet Adkins sensorJson["@odata.type"] = "#Power.v1_0_0.PowerControl"; 548c9563608SJanet Adkins // Put multiple "sensors" into a single PowerControl, so have 549c9563608SJanet Adkins // generic names for MemberId and Name. Follows Redfish mockup. 550c9563608SJanet Adkins sensorJson["MemberId"] = "0"; 551c9563608SJanet Adkins sensorJson["Name"] = "Chassis Power Control"; 552c9563608SJanet Adkins unit = "/PowerConsumedWatts"_json_pointer; 553c9563608SJanet Adkins } 554c9563608SJanet Adkins else if (lower.find("input") != std::string::npos) 555c9563608SJanet Adkins { 556c9563608SJanet Adkins unit = "/PowerInputWatts"_json_pointer; 557c9563608SJanet Adkins } 558c9563608SJanet Adkins else 559c9563608SJanet Adkins { 560c9563608SJanet Adkins unit = "/PowerOutputWatts"_json_pointer; 561c9563608SJanet Adkins } 562c9563608SJanet Adkins } 563c9563608SJanet Adkins else 564c9563608SJanet Adkins { 5656fe8751cSGeorge Liu BMCWEB_LOG_ERROR("Redfish cannot map object type for {}", 5666fe8751cSGeorge Liu sensorName); 567c9563608SJanet Adkins return; 568c9563608SJanet Adkins } 5696fe8751cSGeorge Liu } 5706fe8751cSGeorge Liu 571c9563608SJanet Adkins // Map of dbus interface name, dbus property name and redfish property_name 572c9563608SJanet Adkins std::vector< 573c9563608SJanet Adkins std::tuple<const char*, const char*, nlohmann::json::json_pointer>> 574c9563608SJanet Adkins properties; 575c9563608SJanet Adkins 576c9563608SJanet Adkins properties.emplace_back("xyz.openbmc_project.Sensor.Value", "Value", unit); 577c9563608SJanet Adkins 5786fe8751cSGeorge Liu if (!isExcerpt) 5796fe8751cSGeorge Liu { 5800c728b42SJanet Adkins if (chassisSubNode == ChassisSubNode::sensorsNode) 581c9563608SJanet Adkins { 582c9563608SJanet Adkins properties.emplace_back( 583c9563608SJanet Adkins "xyz.openbmc_project.Sensor.Threshold.Warning", "WarningHigh", 584c9563608SJanet Adkins "/Thresholds/UpperCaution/Reading"_json_pointer); 585c9563608SJanet Adkins properties.emplace_back( 586c9563608SJanet Adkins "xyz.openbmc_project.Sensor.Threshold.Warning", "WarningLow", 587c9563608SJanet Adkins "/Thresholds/LowerCaution/Reading"_json_pointer); 588c9563608SJanet Adkins properties.emplace_back( 589c9563608SJanet Adkins "xyz.openbmc_project.Sensor.Threshold.Critical", "CriticalHigh", 590c9563608SJanet Adkins "/Thresholds/UpperCritical/Reading"_json_pointer); 591c9563608SJanet Adkins properties.emplace_back( 592c9563608SJanet Adkins "xyz.openbmc_project.Sensor.Threshold.Critical", "CriticalLow", 593c9563608SJanet Adkins "/Thresholds/LowerCritical/Reading"_json_pointer); 5946f5be432SPotin Lai properties.emplace_back( 5956f5be432SPotin Lai "xyz.openbmc_project.Sensor.Threshold.HardShutdown", 5966f5be432SPotin Lai "HardShutdownHigh", 5976f5be432SPotin Lai "/Thresholds/UpperFatal/Reading"_json_pointer); 5986f5be432SPotin Lai properties.emplace_back( 5996f5be432SPotin Lai "xyz.openbmc_project.Sensor.Threshold.HardShutdown", 6006f5be432SPotin Lai "HardShutdownLow", 6016f5be432SPotin Lai "/Thresholds/LowerFatal/Reading"_json_pointer); 602cd5a898fSJanet Adkins 603cd5a898fSJanet Adkins /* Add additional properties specific to sensorType */ 604cd5a898fSJanet Adkins if (sensorType == "fan_tach") 605cd5a898fSJanet Adkins { 6066fe8751cSGeorge Liu properties.emplace_back("xyz.openbmc_project.Sensor.Value", 6076fe8751cSGeorge Liu "Value", "/SpeedRPM"_json_pointer); 608cd5a898fSJanet Adkins } 609c9563608SJanet Adkins } 610c9563608SJanet Adkins else if (sensorType != "power") 611c9563608SJanet Adkins { 6126fe8751cSGeorge Liu properties.emplace_back( 6136fe8751cSGeorge Liu "xyz.openbmc_project.Sensor.Threshold.Warning", "WarningHigh", 614c9563608SJanet Adkins "/UpperThresholdNonCritical"_json_pointer); 6156fe8751cSGeorge Liu properties.emplace_back( 6166fe8751cSGeorge Liu "xyz.openbmc_project.Sensor.Threshold.Warning", "WarningLow", 617c9563608SJanet Adkins "/LowerThresholdNonCritical"_json_pointer); 6186fe8751cSGeorge Liu properties.emplace_back( 6196fe8751cSGeorge Liu "xyz.openbmc_project.Sensor.Threshold.Critical", "CriticalHigh", 620c9563608SJanet Adkins "/UpperThresholdCritical"_json_pointer); 6216fe8751cSGeorge Liu properties.emplace_back( 6226fe8751cSGeorge Liu "xyz.openbmc_project.Sensor.Threshold.Critical", "CriticalLow", 623c9563608SJanet Adkins "/LowerThresholdCritical"_json_pointer); 624c9563608SJanet Adkins } 625c9563608SJanet Adkins 626c9563608SJanet Adkins // TODO Need to get UpperThresholdFatal and LowerThresholdFatal 627c9563608SJanet Adkins 6280c728b42SJanet Adkins if (chassisSubNode == ChassisSubNode::sensorsNode) 629c9563608SJanet Adkins { 6306fe8751cSGeorge Liu properties.emplace_back("xyz.openbmc_project.Sensor.Value", 6316fe8751cSGeorge Liu "MinValue", 632c9563608SJanet Adkins "/ReadingRangeMin"_json_pointer); 6336fe8751cSGeorge Liu properties.emplace_back("xyz.openbmc_project.Sensor.Value", 6346fe8751cSGeorge Liu "MaxValue", 635c9563608SJanet Adkins "/ReadingRangeMax"_json_pointer); 636c9563608SJanet Adkins properties.emplace_back("xyz.openbmc_project.Sensor.Accuracy", 637c9563608SJanet Adkins "Accuracy", "/Accuracy"_json_pointer); 638c9563608SJanet Adkins } 639c9563608SJanet Adkins else if (sensorType == "temperature") 640c9563608SJanet Adkins { 6416fe8751cSGeorge Liu properties.emplace_back("xyz.openbmc_project.Sensor.Value", 6426fe8751cSGeorge Liu "MinValue", 643c9563608SJanet Adkins "/MinReadingRangeTemp"_json_pointer); 6446fe8751cSGeorge Liu properties.emplace_back("xyz.openbmc_project.Sensor.Value", 6456fe8751cSGeorge Liu "MaxValue", 646c9563608SJanet Adkins "/MaxReadingRangeTemp"_json_pointer); 647c9563608SJanet Adkins } 648c9563608SJanet Adkins else if (sensorType != "power") 649c9563608SJanet Adkins { 6506fe8751cSGeorge Liu properties.emplace_back("xyz.openbmc_project.Sensor.Value", 6516fe8751cSGeorge Liu "MinValue", 652c9563608SJanet Adkins "/MinReadingRange"_json_pointer); 6536fe8751cSGeorge Liu properties.emplace_back("xyz.openbmc_project.Sensor.Value", 6546fe8751cSGeorge Liu "MaxValue", 655c9563608SJanet Adkins "/MaxReadingRange"_json_pointer); 656c9563608SJanet Adkins } 6576fe8751cSGeorge Liu } 658c9563608SJanet Adkins 659c9563608SJanet Adkins for (const std::tuple<const char*, const char*, 660c9563608SJanet Adkins nlohmann::json::json_pointer>& p : properties) 661c9563608SJanet Adkins { 662c9563608SJanet Adkins for (const auto& [valueName, valueVariant] : propertiesDict) 663c9563608SJanet Adkins { 664c9563608SJanet Adkins if (valueName != std::get<1>(p)) 665c9563608SJanet Adkins { 666c9563608SJanet Adkins continue; 667c9563608SJanet Adkins } 668c9563608SJanet Adkins 669c9563608SJanet Adkins // The property we want to set may be nested json, so use 670c9563608SJanet Adkins // a json_pointer for easy indexing into the json structure. 671c9563608SJanet Adkins const nlohmann::json::json_pointer& key = std::get<2>(p); 672c9563608SJanet Adkins 673c9563608SJanet Adkins const double* doubleValue = std::get_if<double>(&valueVariant); 674c9563608SJanet Adkins if (doubleValue == nullptr) 675c9563608SJanet Adkins { 676c9563608SJanet Adkins BMCWEB_LOG_ERROR("Got value interface that wasn't double"); 677c9563608SJanet Adkins continue; 678c9563608SJanet Adkins } 679c9563608SJanet Adkins if (!std::isfinite(*doubleValue)) 680c9563608SJanet Adkins { 681c9563608SJanet Adkins if (valueName == "Value") 682c9563608SJanet Adkins { 683c9563608SJanet Adkins // Readings are allowed to be NAN for unavailable; coerce 684c9563608SJanet Adkins // them to null in the json response. 685c9563608SJanet Adkins sensorJson[key] = nullptr; 686c9563608SJanet Adkins continue; 687c9563608SJanet Adkins } 688c9563608SJanet Adkins BMCWEB_LOG_WARNING("Sensor value for {} was unexpectedly {}", 689c9563608SJanet Adkins valueName, *doubleValue); 690c9563608SJanet Adkins continue; 691c9563608SJanet Adkins } 692c9563608SJanet Adkins if (forceToInt) 693c9563608SJanet Adkins { 694c9563608SJanet Adkins sensorJson[key] = static_cast<int64_t>(*doubleValue); 695c9563608SJanet Adkins } 696c9563608SJanet Adkins else 697c9563608SJanet Adkins { 698c9563608SJanet Adkins sensorJson[key] = *doubleValue; 699c9563608SJanet Adkins } 700c9563608SJanet Adkins } 701c9563608SJanet Adkins } 702c9563608SJanet Adkins } 703c9563608SJanet Adkins 7046fe8751cSGeorge Liu /** 7056fe8751cSGeorge Liu * @brief Builds a json sensor excerpt representation of a sensor. 7066fe8751cSGeorge Liu * 7076fe8751cSGeorge Liu * @details This is a wrapper function to provide consistent setting of 7086fe8751cSGeorge Liu * "DataSourceUri" for sensor excerpts and filling of properties. Since sensor 7096fe8751cSGeorge Liu * excerpts usually have just the D-Bus path for the sensor that is accepted 7106fe8751cSGeorge Liu * and used to build "DataSourceUri". 7116fe8751cSGeorge Liu 7126fe8751cSGeorge Liu * @param path The D-Bus path to the sensor to be built 7136fe8751cSGeorge Liu * @param chassisId The Chassis Id for the sensor 7146fe8751cSGeorge Liu * @param chassisSubNode The subnode (e.g. ThermalMetrics) of the sensor 7156fe8751cSGeorge Liu * @param sensorTypeExpected The expected type of the sensor 7166fe8751cSGeorge Liu * @param propertiesDict A dictionary of the properties to build the sensor 7176fe8751cSGeorge Liu * from. 7186fe8751cSGeorge Liu * @param sensorJson The json object to fill 7196fe8751cSGeorge Liu * @returns True if sensorJson object filled. False on any error. 7206fe8751cSGeorge Liu * Caller is responsible for handling error. 7216fe8751cSGeorge Liu */ 7226fe8751cSGeorge Liu inline bool objectExcerptToJson( 7236fe8751cSGeorge Liu const std::string& path, const std::string_view chassisId, 7246fe8751cSGeorge Liu ChassisSubNode chassisSubNode, 7256fe8751cSGeorge Liu const std::optional<std::string>& sensorTypeExpected, 7266fe8751cSGeorge Liu const dbus::utility::DBusPropertiesMap& propertiesDict, 7276fe8751cSGeorge Liu nlohmann::json& sensorJson) 7286fe8751cSGeorge Liu { 7296fe8751cSGeorge Liu if (!isExcerptNode(chassisSubNode)) 7306fe8751cSGeorge Liu { 7316fe8751cSGeorge Liu BMCWEB_LOG_DEBUG("{} is not a sensor excerpt", 7326fe8751cSGeorge Liu chassisSubNodeToString(chassisSubNode)); 7336fe8751cSGeorge Liu return false; 7346fe8751cSGeorge Liu } 7356fe8751cSGeorge Liu 7366fe8751cSGeorge Liu sdbusplus::message::object_path sensorPath(path); 7376fe8751cSGeorge Liu std::string sensorName = sensorPath.filename(); 7386fe8751cSGeorge Liu std::string sensorType = sensorPath.parent_path().filename(); 7396fe8751cSGeorge Liu if (sensorName.empty() || sensorType.empty()) 7406fe8751cSGeorge Liu { 7416fe8751cSGeorge Liu BMCWEB_LOG_DEBUG("Invalid sensor path {}", path); 7426fe8751cSGeorge Liu return false; 7436fe8751cSGeorge Liu } 7446fe8751cSGeorge Liu 7456fe8751cSGeorge Liu if (sensorTypeExpected && (sensorType != *sensorTypeExpected)) 7466fe8751cSGeorge Liu { 7476fe8751cSGeorge Liu BMCWEB_LOG_DEBUG("{} is not expected type {}", path, 7486fe8751cSGeorge Liu *sensorTypeExpected); 7496fe8751cSGeorge Liu return false; 7506fe8751cSGeorge Liu } 7516fe8751cSGeorge Liu 7526fe8751cSGeorge Liu // Sensor excerpts use DataSourceUri to reference full sensor Redfish path 7536fe8751cSGeorge Liu sensorJson["DataSourceUri"] = 7546fe8751cSGeorge Liu boost::urls::format("/redfish/v1/Chassis/{}/Sensors/{}", chassisId, 7556fe8751cSGeorge Liu getSensorId(sensorName, sensorType)); 7566fe8751cSGeorge Liu 7576fe8751cSGeorge Liu // Fill in sensor excerpt properties 7586fe8751cSGeorge Liu objectPropertiesToJson(sensorName, sensorType, chassisSubNode, 7596fe8751cSGeorge Liu propertiesDict, sensorJson, nullptr); 7606fe8751cSGeorge Liu 7616fe8751cSGeorge Liu return true; 7626fe8751cSGeorge Liu } 7636fe8751cSGeorge Liu 7646fe8751cSGeorge Liu // Maps D-Bus: Service, SensorPath 7656fe8751cSGeorge Liu using SensorServicePathMap = std::pair<std::string, std::string>; 7666fe8751cSGeorge Liu using SensorServicePathList = std::vector<SensorServicePathMap>; 7676fe8751cSGeorge Liu 7686fe8751cSGeorge Liu inline void getAllSensorObjects( 7696fe8751cSGeorge Liu const std::string& associatedPath, const std::string& path, 7706fe8751cSGeorge Liu std::span<const std::string_view> interfaces, const int32_t depth, 7716fe8751cSGeorge Liu std::function<void(const boost::system::error_code& ec, 7726fe8751cSGeorge Liu SensorServicePathList&)>&& callback) 7736fe8751cSGeorge Liu { 7746fe8751cSGeorge Liu sdbusplus::message::object_path endpointPath{associatedPath}; 7756fe8751cSGeorge Liu endpointPath /= "all_sensors"; 7766fe8751cSGeorge Liu 7776fe8751cSGeorge Liu dbus::utility::getAssociatedSubTree( 7786fe8751cSGeorge Liu endpointPath, sdbusplus::message::object_path(path), depth, interfaces, 7796fe8751cSGeorge Liu [callback = std::move(callback)]( 7806fe8751cSGeorge Liu const boost::system::error_code& ec, 7816fe8751cSGeorge Liu const dbus::utility::MapperGetSubTreeResponse& subtree) { 7826fe8751cSGeorge Liu SensorServicePathList sensorsServiceAndPath; 7836fe8751cSGeorge Liu 7846fe8751cSGeorge Liu if (ec) 7856fe8751cSGeorge Liu { 7866fe8751cSGeorge Liu callback(ec, sensorsServiceAndPath); 7876fe8751cSGeorge Liu return; 7886fe8751cSGeorge Liu } 7896fe8751cSGeorge Liu 7906fe8751cSGeorge Liu for (const auto& [sensorPath, serviceMaps] : subtree) 7916fe8751cSGeorge Liu { 7926fe8751cSGeorge Liu for (const auto& [service, mapInterfaces] : serviceMaps) 7936fe8751cSGeorge Liu { 7946fe8751cSGeorge Liu sensorsServiceAndPath.emplace_back(service, sensorPath); 7956fe8751cSGeorge Liu } 7966fe8751cSGeorge Liu } 7976fe8751cSGeorge Liu 7986fe8751cSGeorge Liu callback(ec, sensorsServiceAndPath); 7996fe8751cSGeorge Liu }); 8006fe8751cSGeorge Liu } 8016fe8751cSGeorge Liu 8021516c21bSJanet Adkins } // namespace sensor_utils 8031516c21bSJanet Adkins } // namespace redfish 804