platform-mc: Create Numeric sensor D-Bus object

Added numeric_sensor class. The NumericSensor class will create the
Numeric sensor D-Bus object. The class also handles sensor status and
exports its status to D-Bus interfaces.

tested: Verified on ast2600 EVB which is connected to a PLDM device
over I2C. bmcweb can display the state of numeric sensor.

Signed-off-by: Thu Nguyen <thu@os.amperecomputing.com>
Change-Id: I1c7de2e74100ed787ed2119896d3c5b36098dd96
diff --git a/platform-mc/numeric_sensor.cpp b/platform-mc/numeric_sensor.cpp
new file mode 100644
index 0000000..be7ece3
--- /dev/null
+++ b/platform-mc/numeric_sensor.cpp
@@ -0,0 +1,585 @@
+#include "numeric_sensor.hpp"
+
+#include "libpldm/platform.h"
+
+#include "common/utils.hpp"
+#include "requester/handler.hpp"
+
+#include <limits>
+#include <regex>
+
+PHOSPHOR_LOG2_USING;
+
+namespace pldm
+{
+namespace platform_mc
+{
+
+NumericSensor::NumericSensor(const pldm_tid_t tid, const bool sensorDisabled,
+                             std::shared_ptr<pldm_numeric_sensor_value_pdr> pdr,
+                             std::string& sensorName,
+                             std::string& associationPath) :
+    tid(tid),
+    sensorName(sensorName), isPriority(false)
+{
+    if (!pdr)
+    {
+        throw sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument();
+    }
+
+    sensorId = pdr->sensor_id;
+    std::string path;
+    SensorUnit sensorUnit = SensorUnit::DegreesC;
+
+    switch (pdr->base_unit)
+    {
+        case PLDM_SENSOR_UNIT_DEGRESS_C:
+            sensorNameSpace = "/xyz/openbmc_project/sensors/temperature/";
+            sensorUnit = SensorUnit::DegreesC;
+            break;
+        case PLDM_SENSOR_UNIT_VOLTS:
+            sensorNameSpace = "/xyz/openbmc_project/sensors/voltage/";
+            sensorUnit = SensorUnit::Volts;
+            break;
+        case PLDM_SENSOR_UNIT_AMPS:
+            sensorNameSpace = "/xyz/openbmc_project/sensors/current/";
+            sensorUnit = SensorUnit::Amperes;
+            break;
+        case PLDM_SENSOR_UNIT_RPM:
+            sensorNameSpace = "/xyz/openbmc_project/sensors/fan_pwm/";
+            sensorUnit = SensorUnit::RPMS;
+            break;
+        case PLDM_SENSOR_UNIT_WATTS:
+            sensorNameSpace = "/xyz/openbmc_project/sensors/power/";
+            sensorUnit = SensorUnit::Watts;
+            break;
+        case PLDM_SENSOR_UNIT_JOULES:
+            sensorNameSpace = "/xyz/openbmc_project/sensors/energy/";
+            sensorUnit = SensorUnit::Joules;
+            break;
+        case PLDM_SENSOR_UNIT_PERCENTAGE:
+            sensorNameSpace = "/xyz/openbmc_project/sensors/utilization/";
+            sensorUnit = SensorUnit::Percent;
+            break;
+        default:
+            lg2::error("Sensor {NAME} has Invalid baseUnit {UNIT}.", "NAME",
+                       sensorName, "UNIT", pdr->base_unit);
+            throw sdbusplus::xyz::openbmc_project::Common::Error::
+                InvalidArgument();
+            break;
+    }
+
+    path = sensorNameSpace + sensorName;
+    try
+    {
+        auto service = pldm::utils::DBusHandler().getService(
+            path.c_str(), "xyz.openbmc_project.Sensor.Value");
+        if (!service.empty())
+        {
+            throw sdbusplus::xyz::openbmc_project::Common::Error::
+                TooManyResources();
+        }
+    }
+    catch (const std::exception&)
+    {
+        /* The sensor object path is not created */
+    }
+
+    auto& bus = pldm::utils::DBusHandler::getBus();
+    try
+    {
+        associationDefinitionsIntf =
+            std::make_unique<AssociationDefinitionsInft>(bus, path.c_str());
+    }
+    catch (const sdbusplus::exception_t& e)
+    {
+        lg2::error(
+            "Failed to create association interface for numeric sensor {PATH} error - {ERROR}",
+            "PATH", path, "ERROR", e);
+        throw sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument();
+    }
+
+    associationDefinitionsIntf->associations(
+        {{"chassis", "all_sensors", associationPath}});
+
+    double maxValue = std::numeric_limits<double>::quiet_NaN();
+    double minValue = std::numeric_limits<double>::quiet_NaN();
+
+    switch (pdr->sensor_data_size)
+    {
+        case PLDM_SENSOR_DATA_SIZE_UINT8:
+            maxValue = pdr->max_readable.value_u8;
+            minValue = pdr->min_readable.value_u8;
+            hysteresis = pdr->hysteresis.value_u8;
+            break;
+        case PLDM_SENSOR_DATA_SIZE_SINT8:
+            maxValue = pdr->max_readable.value_s8;
+            minValue = pdr->min_readable.value_s8;
+            hysteresis = pdr->hysteresis.value_s8;
+            break;
+        case PLDM_SENSOR_DATA_SIZE_UINT16:
+            maxValue = pdr->max_readable.value_u16;
+            minValue = pdr->min_readable.value_u16;
+            hysteresis = pdr->hysteresis.value_u16;
+            break;
+        case PLDM_SENSOR_DATA_SIZE_SINT16:
+            maxValue = pdr->max_readable.value_s16;
+            minValue = pdr->min_readable.value_s16;
+            hysteresis = pdr->hysteresis.value_s16;
+            break;
+        case PLDM_SENSOR_DATA_SIZE_UINT32:
+            maxValue = pdr->max_readable.value_u32;
+            minValue = pdr->min_readable.value_u32;
+            hysteresis = pdr->hysteresis.value_u32;
+            break;
+        case PLDM_SENSOR_DATA_SIZE_SINT32:
+            maxValue = pdr->max_readable.value_s32;
+            minValue = pdr->min_readable.value_s32;
+            hysteresis = pdr->hysteresis.value_s32;
+            break;
+    }
+
+    bool hasCriticalThresholds = false;
+    bool hasWarningThresholds = false;
+    double criticalHigh = std::numeric_limits<double>::quiet_NaN();
+    double criticalLow = std::numeric_limits<double>::quiet_NaN();
+    double warningHigh = std::numeric_limits<double>::quiet_NaN();
+    double warningLow = std::numeric_limits<double>::quiet_NaN();
+
+    if (pdr->supported_thresholds.bits.bit0)
+    {
+        hasWarningThresholds = true;
+        switch (pdr->range_field_format)
+        {
+            case PLDM_RANGE_FIELD_FORMAT_UINT8:
+                warningHigh = pdr->warning_high.value_u8;
+                break;
+            case PLDM_RANGE_FIELD_FORMAT_SINT8:
+                warningHigh = pdr->warning_high.value_s8;
+                break;
+            case PLDM_RANGE_FIELD_FORMAT_UINT16:
+                warningHigh = pdr->warning_high.value_u16;
+                break;
+            case PLDM_RANGE_FIELD_FORMAT_SINT16:
+                warningHigh = pdr->warning_high.value_s16;
+                break;
+            case PLDM_RANGE_FIELD_FORMAT_UINT32:
+                warningHigh = pdr->warning_high.value_u32;
+                break;
+            case PLDM_RANGE_FIELD_FORMAT_SINT32:
+                warningHigh = pdr->warning_high.value_s32;
+                break;
+            case PLDM_RANGE_FIELD_FORMAT_REAL32:
+                warningHigh = pdr->warning_high.value_f32;
+                break;
+        }
+    }
+
+    if (pdr->supported_thresholds.bits.bit3)
+    {
+        hasWarningThresholds = true;
+        switch (pdr->range_field_format)
+        {
+            case PLDM_RANGE_FIELD_FORMAT_UINT8:
+                warningLow = pdr->warning_low.value_u8;
+                break;
+            case PLDM_RANGE_FIELD_FORMAT_SINT8:
+                warningLow = pdr->warning_low.value_s8;
+                break;
+            case PLDM_RANGE_FIELD_FORMAT_UINT16:
+                warningLow = pdr->warning_low.value_u16;
+                break;
+            case PLDM_RANGE_FIELD_FORMAT_SINT16:
+                warningLow = pdr->warning_low.value_s16;
+                break;
+            case PLDM_RANGE_FIELD_FORMAT_UINT32:
+                warningLow = pdr->warning_low.value_u32;
+                break;
+            case PLDM_RANGE_FIELD_FORMAT_SINT32:
+                warningLow = pdr->warning_low.value_s32;
+                break;
+            case PLDM_RANGE_FIELD_FORMAT_REAL32:
+                warningLow = pdr->warning_low.value_f32;
+                break;
+        }
+    }
+
+    if (pdr->supported_thresholds.bits.bit1)
+    {
+        hasCriticalThresholds = true;
+        switch (pdr->range_field_format)
+        {
+            case PLDM_RANGE_FIELD_FORMAT_UINT8:
+                criticalHigh = pdr->critical_high.value_u8;
+                break;
+            case PLDM_RANGE_FIELD_FORMAT_SINT8:
+                criticalHigh = pdr->critical_high.value_s8;
+                break;
+            case PLDM_RANGE_FIELD_FORMAT_UINT16:
+                criticalHigh = pdr->critical_high.value_u16;
+                break;
+            case PLDM_RANGE_FIELD_FORMAT_SINT16:
+                criticalHigh = pdr->critical_high.value_s16;
+                break;
+            case PLDM_RANGE_FIELD_FORMAT_UINT32:
+                criticalHigh = pdr->critical_high.value_u32;
+                break;
+            case PLDM_RANGE_FIELD_FORMAT_SINT32:
+                criticalHigh = pdr->critical_high.value_s32;
+                break;
+            case PLDM_RANGE_FIELD_FORMAT_REAL32:
+                criticalHigh = pdr->critical_high.value_f32;
+                break;
+        }
+    }
+
+    if (pdr->supported_thresholds.bits.bit4)
+    {
+        hasCriticalThresholds = true;
+        switch (pdr->range_field_format)
+        {
+            case PLDM_RANGE_FIELD_FORMAT_UINT8:
+                criticalLow = pdr->critical_low.value_u8;
+                break;
+            case PLDM_RANGE_FIELD_FORMAT_SINT8:
+                criticalLow = pdr->critical_low.value_s8;
+                break;
+            case PLDM_RANGE_FIELD_FORMAT_UINT16:
+                criticalLow = pdr->critical_low.value_u16;
+                break;
+            case PLDM_RANGE_FIELD_FORMAT_SINT16:
+                criticalLow = pdr->critical_low.value_s16;
+                break;
+            case PLDM_RANGE_FIELD_FORMAT_UINT32:
+                criticalLow = pdr->critical_low.value_u32;
+                break;
+            case PLDM_RANGE_FIELD_FORMAT_SINT32:
+                criticalLow = pdr->critical_low.value_s32;
+                break;
+            case PLDM_RANGE_FIELD_FORMAT_REAL32:
+                criticalLow = pdr->critical_low.value_f32;
+                break;
+        }
+    }
+
+    resolution = pdr->resolution;
+    offset = pdr->offset;
+    baseUnitModifier = pdr->unit_modifier;
+
+    /**
+     * DEFAULT_SENSOR_UPDATER_INTERVAL is in milliseconds
+     * updateTime is in microseconds
+     */
+    updateTime = static_cast<uint64_t>(DEFAULT_SENSOR_UPDATER_INTERVAL * 1000);
+    if (!std::isnan(pdr->update_interval))
+    {
+        updateTime = pdr->update_interval * 1000000;
+    }
+
+    try
+    {
+        valueIntf = std::make_unique<ValueIntf>(bus, path.c_str());
+    }
+    catch (const sdbusplus::exception_t& e)
+    {
+        lg2::error(
+            "Failed to create Value interface for numeric sensor {PATH} error - {ERROR}",
+            "PATH", path, "ERROR", e);
+        throw sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument();
+    }
+    valueIntf->maxValue(unitModifier(conversionFormula(maxValue)));
+    valueIntf->minValue(unitModifier(conversionFormula(minValue)));
+    hysteresis = unitModifier(conversionFormula(hysteresis));
+    valueIntf->unit(sensorUnit);
+
+    try
+    {
+        availabilityIntf = std::make_unique<AvailabilityIntf>(bus,
+                                                              path.c_str());
+    }
+    catch (const sdbusplus::exception_t& e)
+    {
+        lg2::error(
+            "Failed to create Availability interface for numeric sensor {PATH} error - {ERROR}",
+            "PATH", path, "ERROR", e);
+        throw sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument();
+    }
+    availabilityIntf->available(true);
+
+    try
+    {
+        operationalStatusIntf =
+            std::make_unique<OperationalStatusIntf>(bus, path.c_str());
+    }
+    catch (const sdbusplus::exception_t& e)
+    {
+        lg2::error(
+            "Failed to create Operation status interface for numeric sensor {PATH} error - {ERROR}",
+            "PATH", path, "ERROR", e);
+        throw sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument();
+    }
+    operationalStatusIntf->functional(!sensorDisabled);
+
+    if (hasWarningThresholds)
+    {
+        try
+        {
+            thresholdWarningIntf =
+                std::make_unique<ThresholdWarningIntf>(bus, path.c_str());
+        }
+        catch (const sdbusplus::exception_t& e)
+        {
+            lg2::error(
+                "Failed to create Threshold warning interface for numeric sensor {PATH} error - {ERROR}",
+                "PATH", path, "ERROR", e);
+            throw sdbusplus::xyz::openbmc_project::Common::Error::
+                InvalidArgument();
+        }
+        thresholdWarningIntf->warningHigh(unitModifier(warningHigh));
+        thresholdWarningIntf->warningLow(unitModifier(warningLow));
+    }
+
+    if (hasCriticalThresholds)
+    {
+        try
+        {
+            thresholdCriticalIntf =
+                std::make_unique<ThresholdCriticalIntf>(bus, path.c_str());
+        }
+        catch (const sdbusplus::exception_t& e)
+        {
+            lg2::error(
+                "Failed to create Threshold critical interface for numeric sensor {PATH} error - {ERROR}",
+                "PATH", path, "ERROR", e);
+            throw sdbusplus::xyz::openbmc_project::Common::Error::
+                InvalidArgument();
+        }
+        thresholdCriticalIntf->criticalHigh(unitModifier(criticalHigh));
+        thresholdCriticalIntf->criticalLow(unitModifier(criticalLow));
+    }
+}
+
+NumericSensor::NumericSensor(
+    const pldm_tid_t tid, const bool sensorDisabled,
+    std::shared_ptr<pldm_compact_numeric_sensor_pdr> pdr,
+    std::string& sensorName, std::string& associationPath) :
+    tid(tid),
+    sensorName(sensorName), isPriority(false)
+{
+    if (!pdr)
+    {
+        throw sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument();
+    }
+
+    sensorId = pdr->sensor_id;
+    std::string path;
+    SensorUnit sensorUnit = SensorUnit::DegreesC;
+
+    switch (pdr->base_unit)
+    {
+        case PLDM_SENSOR_UNIT_DEGRESS_C:
+            sensorNameSpace = "/xyz/openbmc_project/sensors/temperature/";
+            sensorUnit = SensorUnit::DegreesC;
+            break;
+        case PLDM_SENSOR_UNIT_VOLTS:
+            sensorNameSpace = "/xyz/openbmc_project/sensors/voltage/";
+            sensorUnit = SensorUnit::Volts;
+            break;
+        case PLDM_SENSOR_UNIT_AMPS:
+            sensorNameSpace = "/xyz/openbmc_project/sensors/current/";
+            sensorUnit = SensorUnit::Amperes;
+            break;
+        case PLDM_SENSOR_UNIT_RPM:
+            sensorNameSpace = "/xyz/openbmc_project/sensors/fan_pwm/";
+            sensorUnit = SensorUnit::RPMS;
+            break;
+        case PLDM_SENSOR_UNIT_WATTS:
+            sensorNameSpace = "/xyz/openbmc_project/sensors/power/";
+            sensorUnit = SensorUnit::Watts;
+            break;
+        case PLDM_SENSOR_UNIT_JOULES:
+            sensorNameSpace = "/xyz/openbmc_project/sensors/energy/";
+            sensorUnit = SensorUnit::Joules;
+            break;
+        case PLDM_SENSOR_UNIT_PERCENTAGE:
+            sensorNameSpace = "/xyz/openbmc_project/sensors/utilization/";
+            sensorUnit = SensorUnit::Percent;
+            break;
+        default:
+            lg2::error("Sensor {NAME} has Invalid baseUnit {UNIT}.", "NAME",
+                       sensorName, "UNIT", pdr->base_unit);
+            throw sdbusplus::xyz::openbmc_project::Common::Error::
+                InvalidArgument();
+            break;
+    }
+
+    path = sensorNameSpace + sensorName;
+    try
+    {
+        auto service = pldm::utils::DBusHandler().getService(
+            path.c_str(), "xyz.openbmc_project.Sensor.Value");
+        if (!service.empty())
+        {
+            throw sdbusplus::xyz::openbmc_project::Common::Error::
+                TooManyResources();
+        }
+    }
+    catch (const std::exception&)
+    {
+        /* The sensor object path is not created */
+    }
+
+    auto& bus = pldm::utils::DBusHandler::getBus();
+    try
+    {
+        associationDefinitionsIntf =
+            std::make_unique<AssociationDefinitionsInft>(bus, path.c_str());
+    }
+    catch (const sdbusplus::exception_t& e)
+    {
+        lg2::error(
+            "Failed to create Association interface for compact numeric sensor {PATH} error - {ERROR}",
+            "PATH", path, "ERROR", e);
+        throw sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument();
+    }
+    associationDefinitionsIntf->associations(
+        {{"chassis", "all_sensors", associationPath.c_str()}});
+
+    double maxValue = std::numeric_limits<double>::quiet_NaN();
+    double minValue = std::numeric_limits<double>::quiet_NaN();
+    bool hasWarningThresholds = false;
+    bool hasCriticalThresholds = false;
+    double criticalHigh = std::numeric_limits<double>::quiet_NaN();
+    double criticalLow = std::numeric_limits<double>::quiet_NaN();
+    double warningHigh = std::numeric_limits<double>::quiet_NaN();
+    double warningLow = std::numeric_limits<double>::quiet_NaN();
+
+    if (pdr->range_field_support.bits.bit0)
+    {
+        hasWarningThresholds = true;
+        warningHigh = pdr->warning_high;
+    }
+    if (pdr->range_field_support.bits.bit1)
+    {
+        hasWarningThresholds = true;
+        warningLow = pdr->warning_low;
+    }
+
+    if (pdr->range_field_support.bits.bit2)
+    {
+        hasCriticalThresholds = true;
+        criticalHigh = pdr->critical_high;
+    }
+
+    if (pdr->range_field_support.bits.bit3)
+    {
+        hasCriticalThresholds = true;
+        criticalLow = pdr->critical_low;
+    }
+
+    resolution = std::numeric_limits<double>::quiet_NaN();
+    offset = std::numeric_limits<double>::quiet_NaN();
+    baseUnitModifier = pdr->unit_modifier;
+
+    /**
+     * DEFAULT_SENSOR_UPDATER_INTERVAL is in milliseconds
+     * updateTime is in microseconds
+     */
+    updateTime = static_cast<uint64_t>(DEFAULT_SENSOR_UPDATER_INTERVAL * 1000);
+    try
+    {
+        valueIntf = std::make_unique<ValueIntf>(bus, path.c_str());
+    }
+    catch (const sdbusplus::exception_t& e)
+    {
+        lg2::error(
+            "Failed to create Value interface for compact numeric sensor {PATH} error - {ERROR}",
+            "PATH", path, "ERROR", e);
+        throw sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument();
+    }
+    valueIntf->maxValue(unitModifier(conversionFormula(maxValue)));
+    valueIntf->minValue(unitModifier(conversionFormula(minValue)));
+    hysteresis = unitModifier(conversionFormula(hysteresis));
+    valueIntf->unit(sensorUnit);
+
+    try
+    {
+        availabilityIntf = std::make_unique<AvailabilityIntf>(bus,
+                                                              path.c_str());
+    }
+    catch (const sdbusplus::exception_t& e)
+    {
+        lg2::error(
+            "Failed to create Availability interface for compact numeric sensor {PATH} error - {ERROR}",
+            "PATH", path, "ERROR", e);
+        throw sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument();
+    }
+    availabilityIntf->available(true);
+
+    try
+    {
+        operationalStatusIntf =
+            std::make_unique<OperationalStatusIntf>(bus, path.c_str());
+    }
+    catch (const sdbusplus::exception_t& e)
+    {
+        lg2::error(
+            "Failed to create Operational status interface for compact numeric sensor {PATH} error - {ERROR}",
+            "PATH", path, "ERROR", e);
+        throw sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument();
+    }
+    operationalStatusIntf->functional(!sensorDisabled);
+
+    if (hasWarningThresholds)
+    {
+        try
+        {
+            thresholdWarningIntf =
+                std::make_unique<ThresholdWarningIntf>(bus, path.c_str());
+        }
+        catch (const sdbusplus::exception_t& e)
+        {
+            lg2::error(
+                "Failed to create Warning threshold interface for compact numeric sensor {PATH} error - {ERROR}",
+                "PATH", path, "ERROR", e);
+            throw sdbusplus::xyz::openbmc_project::Common::Error::
+                InvalidArgument();
+        }
+        thresholdWarningIntf->warningHigh(unitModifier(warningHigh));
+        thresholdWarningIntf->warningLow(unitModifier(warningLow));
+    }
+
+    if (hasCriticalThresholds)
+    {
+        try
+        {
+            thresholdCriticalIntf =
+                std::make_unique<ThresholdCriticalIntf>(bus, path.c_str());
+        }
+        catch (const sdbusplus::exception_t& e)
+        {
+            lg2::error(
+                "Failed to create Critical threshold interface for compact numeric sensor {PATH} error - {ERROR}",
+                "PATH", path, "ERROR", e);
+            throw sdbusplus::xyz::openbmc_project::Common::Error::
+                InvalidArgument();
+        }
+        thresholdCriticalIntf->criticalHigh(unitModifier(criticalHigh));
+        thresholdCriticalIntf->criticalLow(unitModifier(criticalLow));
+    }
+}
+
+double NumericSensor::conversionFormula(double value)
+{
+    double convertedValue = value;
+    convertedValue *= std::isnan(resolution) ? 1 : resolution;
+    convertedValue += std::isnan(offset) ? 0 : offset;
+    return convertedValue;
+}
+
+double NumericSensor::unitModifier(double value)
+{
+    return std::isnan(value) ? value : value * std::pow(10, baseUnitModifier);
+}
+} // namespace platform_mc
+} // namespace pldm