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/meson.build b/meson.build
index bfbf63e..af6bf6e 100644
--- a/meson.build
+++ b/meson.build
@@ -72,6 +72,7 @@
 elif get_option('transport-implementation') == 'af-mctp'
   conf_data.set('PLDM_TRANSPORT_WITH_AF_MCTP', 1)
 endif
+conf_data.set('DEFAULT_SENSOR_UPDATER_INTERVAL', get_option('default-sensor-update-interval'))
 
 configure_file(output: 'config.h',
   configuration: conf_data
@@ -174,6 +175,7 @@
   'platform-mc/terminus.cpp',
   'platform-mc/platform_manager.cpp',
   'platform-mc/manager.cpp',
+  'platform-mc/numeric_sensor.cpp',
   'requester/mctp_endpoint_discovery.cpp',
   implicit_include_directories: false,
   dependencies: deps,
diff --git a/meson.options b/meson.options
index d52506e..63726dd 100644
--- a/meson.options
+++ b/meson.options
@@ -174,3 +174,18 @@
     value: 8384512,
     description: 'OEM-IBM: max DMA size'
 )
+
+## Default Sensor Update Interval Options
+option(
+    'default-sensor-update-interval',
+    type: 'integer',
+    min: 1,
+    max: 4294967295,
+    description: '''The default sensor polling interval in milliseconds.
+                    The value will be used when the internal is not configured
+                    in the PLDM sensor PDRs use `updateInterval` field. `pldmd`
+                    will send `GetSensorReading` to get the PLDM sensor values
+                    of the monitoring terminus after each configured
+                    interval.''',
+    value: 999
+)
\ No newline at end of file
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
diff --git a/platform-mc/numeric_sensor.hpp b/platform-mc/numeric_sensor.hpp
new file mode 100644
index 0000000..315787d
--- /dev/null
+++ b/platform-mc/numeric_sensor.hpp
@@ -0,0 +1,113 @@
+#pragma once
+
+#include "libpldm/platform.h"
+#include "libpldm/pldm.h"
+
+#include "common/types.hpp"
+
+#include <sdbusplus/server/object.hpp>
+#include <xyz/openbmc_project/Association/Definitions/server.hpp>
+#include <xyz/openbmc_project/Sensor/Threshold/Critical/server.hpp>
+#include <xyz/openbmc_project/Sensor/Threshold/Warning/server.hpp>
+#include <xyz/openbmc_project/Sensor/Value/server.hpp>
+#include <xyz/openbmc_project/State/Decorator/Availability/server.hpp>
+#include <xyz/openbmc_project/State/Decorator/OperationalStatus/server.hpp>
+
+#include <string>
+
+namespace pldm
+{
+namespace platform_mc
+{
+
+using SensorUnit = sdbusplus::xyz::openbmc_project::Sensor::server::Value::Unit;
+using ValueIntf = sdbusplus::server::object_t<
+    sdbusplus::xyz::openbmc_project::Sensor::server::Value>;
+using ThresholdWarningIntf = sdbusplus::server::object_t<
+    sdbusplus::xyz::openbmc_project::Sensor::Threshold::server::Warning>;
+using ThresholdCriticalIntf = sdbusplus::server::object_t<
+    sdbusplus::xyz::openbmc_project::Sensor::Threshold::server::Critical>;
+using OperationalStatusIntf =
+    sdbusplus::server::object_t<sdbusplus::xyz::openbmc_project::State::
+                                    Decorator::server::OperationalStatus>;
+using AvailabilityIntf = sdbusplus::server::object_t<
+    sdbusplus::xyz::openbmc_project::State::Decorator::server::Availability>;
+using AssociationDefinitionsInft = sdbusplus::server::object_t<
+    sdbusplus::xyz::openbmc_project::Association::server::Definitions>;
+
+/**
+ * @brief NumericSensor
+ *
+ * This class handles sensor reading updated by sensor manager and export
+ * status to D-Bus interface.
+ */
+class NumericSensor
+{
+  public:
+    NumericSensor(const pldm_tid_t tid, const bool sensorDisabled,
+                  std::shared_ptr<pldm_numeric_sensor_value_pdr> pdr,
+                  std::string& sensorName, std::string& associationPath);
+
+    NumericSensor(const pldm_tid_t tid, const bool sensorDisabled,
+                  std::shared_ptr<pldm_compact_numeric_sensor_pdr> pdr,
+                  std::string& sensorName, std::string& associationPath);
+
+    ~NumericSensor(){};
+
+    /** @brief ConversionFormula is used to convert raw value to the unit
+     * specified in PDR
+     *
+     *  @param[in] value - raw value
+     *  @return double - converted value
+     */
+    double conversionFormula(double value);
+
+    /** @brief UnitModifier is used to apply the unit modifier specified in PDR
+     *
+     *  @param[in] value - raw value
+     *  @return double - converted value
+     */
+    double unitModifier(double value);
+
+    /** @brief Terminus ID which the sensor belongs to */
+    pldm_tid_t tid;
+
+    /** @brief Sensor ID */
+    uint16_t sensorId;
+
+    /** @brief  The time of sensor update interval in usec */
+    uint64_t updateTime;
+
+    /** @brief  sensorName */
+    std::string sensorName;
+
+    /** @brief  sensorNameSpace */
+    std::string sensorNameSpace;
+
+    /** @brief indicate if sensor is polled in priority */
+    bool isPriority;
+
+  private:
+    std::unique_ptr<ValueIntf> valueIntf = nullptr;
+    std::unique_ptr<ThresholdWarningIntf> thresholdWarningIntf = nullptr;
+    std::unique_ptr<ThresholdCriticalIntf> thresholdCriticalIntf = nullptr;
+    std::unique_ptr<AvailabilityIntf> availabilityIntf = nullptr;
+    std::unique_ptr<OperationalStatusIntf> operationalStatusIntf = nullptr;
+    std::unique_ptr<AssociationDefinitionsInft> associationDefinitionsIntf =
+        nullptr;
+
+    /** @brief Amount of hysteresis associated with the sensor thresholds */
+    double hysteresis;
+
+    /** @brief The resolution of sensor in Units */
+    double resolution;
+
+    /** @brief A constant value that is added in as part of conversion process
+     * of converting a raw sensor reading to Units */
+    double offset;
+
+    /** @brief A power-of-10 multiplier for baseUnit */
+    int8_t baseUnitModifier;
+};
+} // namespace platform_mc
+} // namespace pldm
diff --git a/platform-mc/terminus.cpp b/platform-mc/terminus.cpp
index 2892dc1..5b2ab3c 100644
--- a/platform-mc/terminus.cpp
+++ b/platform-mc/terminus.cpp
@@ -82,6 +82,30 @@
     return std::nullopt;
 }
 
+bool Terminus::createInventoryPath(std::string tName)
+{
+    if (tName.empty())
+    {
+        return false;
+    }
+
+    inventoryPath = "/xyz/openbmc_project/inventory/system/board/" + tName;
+    try
+    {
+        inventoryItemBoardInft = std::make_unique<InventoryItemBoardIntf>(
+            utils::DBusHandler::getBus(), inventoryPath.c_str());
+        return true;
+    }
+    catch (const sdbusplus::exception_t& e)
+    {
+        lg2::error(
+            "Failed to create Inventory Board interface for device {PATH}",
+            "PATH", inventoryPath);
+    }
+
+    return false;
+}
+
 void Terminus::parseTerminusPDRs()
 {
     std::vector<std::shared_ptr<pldm_numeric_sensor_value_pdr>>
@@ -177,6 +201,30 @@
                   "NAME", tName.value());
         terminusName = static_cast<std::string>(tName.value());
     }
+
+    if (terminusName.empty() &&
+        (numericSensorPdrs.size() || compactNumericSensorPdrs.size()))
+    {
+        lg2::error(
+            "Terminus ID {TID}: DOES NOT have name. Skip Adding sensors.",
+            "TID", tid);
+        return;
+    }
+
+    if (createInventoryPath(terminusName))
+    {
+        lg2::error("Terminus ID {TID}: Created Inventory path.", "TID", tid);
+    }
+
+    for (auto pdr : numericSensorPdrs)
+    {
+        addNumericSensor(pdr);
+    }
+
+    for (auto pdr : compactNumericSensorPdrs)
+    {
+        addCompactNumericSensor(pdr);
+    }
 }
 
 std::shared_ptr<SensorAuxiliaryNames>
@@ -329,6 +377,55 @@
     return parsedPdr;
 }
 
+void Terminus::addNumericSensor(
+    const std::shared_ptr<pldm_numeric_sensor_value_pdr> pdr)
+{
+    uint16_t sensorId = pdr->sensor_id;
+    if (terminusName.empty())
+    {
+        lg2::error(
+            "Terminus ID {TID}: DOES NOT have name. Skip Adding sensors.",
+            "TID", tid);
+        return;
+    }
+    std::string sensorName = terminusName + "_" + "Sensor_" +
+                             std::to_string(pdr->sensor_id);
+
+    if (pdr->sensor_auxiliary_names_pdr)
+    {
+        auto sensorAuxiliaryNames = getSensorAuxiliaryNames(sensorId);
+        if (sensorAuxiliaryNames)
+        {
+            const auto& [sensorId, sensorCnt,
+                         sensorNames] = *sensorAuxiliaryNames;
+            if (sensorCnt == 1)
+            {
+                for (const auto& [languageTag, name] : sensorNames[0])
+                {
+                    if (languageTag == "en" && !name.empty())
+                    {
+                        sensorName = terminusName + "_" + name;
+                    }
+                }
+            }
+        }
+    }
+
+    try
+    {
+        auto sensor = std::make_shared<NumericSensor>(
+            tid, true, pdr, sensorName, inventoryPath);
+        lg2::info("Created NumericSensor {NAME}", "NAME", sensorName);
+        numericSensors.emplace_back(sensor);
+    }
+    catch (const sdbusplus::exception_t& e)
+    {
+        lg2::error(
+            "Failed to create NumericSensor. error - {ERROR} sensorname - {NAME}",
+            "ERROR", e, "NAME", sensorName);
+    }
+}
+
 std::shared_ptr<SensorAuxiliaryNames>
     Terminus::parseCompactNumericSensorNames(const std::vector<uint8_t>& sPdr)
 {
@@ -393,5 +490,50 @@
     return parsedPdr;
 }
 
+void Terminus::addCompactNumericSensor(
+    const std::shared_ptr<pldm_compact_numeric_sensor_pdr> pdr)
+{
+    uint16_t sensorId = pdr->sensor_id;
+    if (terminusName.empty())
+    {
+        lg2::error(
+            "Terminus ID {TID}: DOES NOT have name. Skip Adding sensors.",
+            "TID", tid);
+        return;
+    }
+    std::string sensorName = terminusName + "_" + "Sensor_" +
+                             std::to_string(pdr->sensor_id);
+
+    auto sensorAuxiliaryNames = getSensorAuxiliaryNames(sensorId);
+    if (sensorAuxiliaryNames)
+    {
+        const auto& [sensorId, sensorCnt, sensorNames] = *sensorAuxiliaryNames;
+        if (sensorCnt == 1)
+        {
+            for (const auto& [languageTag, name] : sensorNames[0])
+            {
+                if (languageTag == "en" && !name.empty())
+                {
+                    sensorName = terminusName + "_" + name;
+                }
+            }
+        }
+    }
+
+    try
+    {
+        auto sensor = std::make_shared<NumericSensor>(
+            tid, true, pdr, sensorName, inventoryPath);
+        lg2::info("Created Compact NumericSensor {NAME}", "NAME", sensorName);
+        numericSensors.emplace_back(sensor);
+    }
+    catch (const sdbusplus::exception_t& e)
+    {
+        lg2::error(
+            "Failed to create Compact NumericSensor. error - {ERROR} sensorname - {NAME}",
+            "ERROR", e, "NAME", sensorName);
+    }
+}
+
 } // namespace platform_mc
 } // namespace pldm
diff --git a/platform-mc/terminus.hpp b/platform-mc/terminus.hpp
index aacf288..8a39b2e 100644
--- a/platform-mc/terminus.hpp
+++ b/platform-mc/terminus.hpp
@@ -3,7 +3,9 @@
 #include "libpldm/platform.h"
 
 #include "common/types.hpp"
+#include "numeric_sensor.hpp"
 #include "requester/handler.hpp"
+#include "terminus.hpp"
 
 #include <sdbusplus/server/object.hpp>
 #include <sdeventplus/event.hpp>
@@ -32,6 +34,8 @@
 using SensorAuxiliaryNames = std::tuple<
     SensorId, SensorCnt,
     std::vector<std::vector<std::pair<NameLanguageTag, SensorName>>>>;
+using InventoryItemBoardIntf = sdbusplus::server::object_t<
+    sdbusplus::xyz::openbmc_project::Inventory::Item::server::Board>;
 
 /** @struct EntityKey
  *
@@ -129,6 +133,9 @@
     /** @brief A flag to indicate if terminus has been initialized */
     bool initialized = false;
 
+    /** @brief A list of numericSensors */
+    std::vector<std::shared_ptr<NumericSensor>> numericSensors{};
+
     /** @brief Get Sensor Auxiliary Names by sensorID
      *
      *  @param[in] id - sensor ID
@@ -143,6 +150,15 @@
      */
     std::optional<std::string_view> findTerminusName();
 
+    /** @brief Construct the NumericSensor sensor class for the PLDM sensor.
+     *         The NumericSensor class will handle create D-Bus object path,
+     *         provide the APIs to update sensor value, threshold...
+     *
+     *  @param[in] pdr - the numeric sensor PDR info
+     */
+    void addNumericSensor(
+        const std::shared_ptr<pldm_numeric_sensor_value_pdr> pdr);
+
     /** @brief Parse the numeric sensor PDRs
      *
      *  @param[in] pdrData - the response PDRs from GetPDR command
@@ -167,6 +183,14 @@
     std::shared_ptr<EntityAuxiliaryNames>
         parseEntityAuxiliaryNamesPDR(const std::vector<uint8_t>& pdrData);
 
+    /** @brief Construct the NumericSensor sensor class for the compact numeric
+     *         PLDM sensor.
+     *
+     *  @param[in] pdr - the compact numeric sensor PDR info
+     */
+    void addCompactNumericSensor(
+        const std::shared_ptr<pldm_compact_numeric_sensor_pdr> pdr);
+
     /** @brief Parse the compact numeric sensor PDRs
      *
      *  @param[in] pdrData - the response PDRs from GetPDR command
@@ -183,6 +207,15 @@
     std::shared_ptr<SensorAuxiliaryNames>
         parseCompactNumericSensorNames(const std::vector<uint8_t>& pdrData);
 
+    /** @brief Create the terminus inventory path to
+     *         /xyz/openbmc_project/inventory/Item/Board/.
+     *
+     *  @param[in] tName - the terminus name
+     *  @return true/false: True if there is no error in creating inventory path
+     *
+     */
+    bool createInventoryPath(std::string tName);
+
     /* @brief The terminus's TID */
     pldm_tid_t tid;
 
@@ -209,6 +242,11 @@
 
     /** @brief Terminus name */
     EntityName terminusName{};
+    /* @brief The pointer of iventory D-Bus interface for the terminus */
+    std::unique_ptr<InventoryItemBoardIntf> inventoryItemBoardInft = nullptr;
+
+    /* @brief Inventory D-Bus object path of the terminus */
+    std::string inventoryPath;
 };
 } // namespace platform_mc
 } // namespace pldm
diff --git a/platform-mc/test/meson.build b/platform-mc/test/meson.build
index 8c4f570..6b95f90 100644
--- a/platform-mc/test/meson.build
+++ b/platform-mc/test/meson.build
@@ -4,6 +4,7 @@
             '../terminus.cpp',
             '../platform_manager.cpp',
             '../manager.cpp',
+            '../numeric_sensor.cpp',
             '../../requester/mctp_endpoint_discovery.cpp'],
             include_directories: ['../../requester', '../../pldmd'])
 
diff --git a/platform-mc/test/terminus_test.cpp b/platform-mc/test/terminus_test.cpp
index 8919b19..925f284 100644
--- a/platform-mc/test/terminus_test.cpp
+++ b/platform-mc/test/terminus_test.cpp
@@ -1,5 +1,6 @@
 #include "libpldm/entity.h"
 
+#include "platform-mc/numeric_sensor.hpp"
 #include "platform-mc/terminus.hpp"
 
 #include <gtest/gtest.h>
@@ -62,7 +63,32 @@
         0x0 // sensorName
     };
 
+    std::vector<uint8_t> pdr2{
+        0x1, 0x0, 0x0,
+        0x0,                             // record handle
+        0x1,                             // PDRHeaderVersion
+        PLDM_ENTITY_AUXILIARY_NAMES_PDR, // PDRType
+        0x1,
+        0x0,                             // recordChangeNumber
+        0x11,
+        0,                               // dataLength
+        /* Entity Auxiliary Names PDR Data*/
+        3,
+        0x80, // entityType system software
+        0x1,
+        0x0,  // Entity instance number =1
+        0,
+        0,    // Overal system
+        0,    // shared Name Count one name only
+        01,   // nameStringCount
+        0x65, 0x6e, 0x00,
+        0x00, // Language Tag "en"
+        0x53, 0x00, 0x30, 0x00,
+        0x00  // Entity Name "S0"
+    };
+
     t1.pdrs.emplace_back(pdr1);
+    t1.pdrs.emplace_back(pdr2);
     t1.parseTerminusPDRs();
 
     auto sensorAuxNames = t1.getSensorAuxiliaryNames(0);
@@ -78,4 +104,41 @@
     EXPECT_EQ(1, names[0].size());
     EXPECT_EQ("en", names[0][0].first);
     EXPECT_EQ("TEMP1", names[0][0].second);
+    EXPECT_EQ(2, t1.pdrs.size());
+    EXPECT_EQ("S0", t1.getTerminusName());
+}
+
+TEST(TerminusTest, parsePDRTestNoSensorPDR)
+{
+    auto t1 = pldm::platform_mc::Terminus(1,
+                                          1 << PLDM_BASE | 1 << PLDM_PLATFORM);
+    std::vector<uint8_t> pdr1{
+        0x1, 0x0, 0x0,
+        0x0,                             // record handle
+        0x1,                             // PDRHeaderVersion
+        PLDM_ENTITY_AUXILIARY_NAMES_PDR, // PDRType
+        0x1,
+        0x0,                             // recordChangeNumber
+        0x11,
+        0,                               // dataLength
+        /* Entity Auxiliary Names PDR Data*/
+        3,
+        0x80, // entityType system software
+        0x1,
+        0x0,  // Entity instance number =1
+        0,
+        0,    // Overal system
+        0,    // shared Name Count one name only
+        01,   // nameStringCount
+        0x65, 0x6e, 0x00,
+        0x00, // Language Tag "en"
+        0x53, 0x00, 0x30, 0x00,
+        0x00  // Entity Name "S0"
+    };
+
+    t1.pdrs.emplace_back(pdr1);
+    t1.parseTerminusPDRs();
+
+    auto sensorAuxNames = t1.getSensorAuxiliaryNames(1);
+    EXPECT_EQ(nullptr, sensorAuxNames);
 }