platform-mc: Add sensor manager

Added sensor_manager class. The sensor_manager class manages the timing
of sensor polling.

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: Gilbert Chen <gilbert.chen@arm.com>
Signed-off-by: Thu Nguyen <thu@os.amperecomputing.com>
Change-Id: I4257f823ea26d7fdb322cc82d847e94db056258c
diff --git a/common/types.hpp b/common/types.hpp
index 1a91235..661c2e7 100644
--- a/common/types.hpp
+++ b/common/types.hpp
@@ -15,6 +15,7 @@
 namespace pldm
 {
 
+using Availability = bool;
 using eid = uint8_t;
 using UUID = std::string;
 using Request = std::vector<uint8_t>;
diff --git a/meson.build b/meson.build
index af6bf6e..e8c3bc8 100644
--- a/meson.build
+++ b/meson.build
@@ -73,6 +73,7 @@
   conf_data.set('PLDM_TRANSPORT_WITH_AF_MCTP', 1)
 endif
 conf_data.set('DEFAULT_SENSOR_UPDATER_INTERVAL', get_option('default-sensor-update-interval'))
+conf_data.set('SENSOR_POLLING_TIME', get_option('sensor-polling-time'))
 
 configure_file(output: 'config.h',
   configuration: conf_data
@@ -175,6 +176,7 @@
   'platform-mc/terminus.cpp',
   'platform-mc/platform_manager.cpp',
   'platform-mc/manager.cpp',
+  'platform-mc/sensor_manager.cpp',
   'platform-mc/numeric_sensor.cpp',
   'requester/mctp_endpoint_discovery.cpp',
   implicit_include_directories: false,
diff --git a/meson.options b/meson.options
index 63726dd..8a7138a 100644
--- a/meson.options
+++ b/meson.options
@@ -188,4 +188,23 @@
                     of the monitoring terminus after each configured
                     interval.''',
     value: 999
-)
\ No newline at end of file
+)
+
+# Platform-mc configuration parameters
+
+## Sensor Polling Options
+option(
+    'sensor-polling-time',
+    type: 'integer',
+    min: 1,
+    max: 10000,
+    description: '''The configured timeout in milliseconds of the common sensor
+                    polling timer of each terminus which will trigger the
+                    terminus sensor reading task. The task will check
+                    whether the sensor in the terminus sensors list need to
+                    be updated by comparing the sensor `updateInterval` with the
+                    interval between current timestamp and latest updated
+                    timestamp of the sensor. The task will send
+                    `GetSensorReading` if the sensor need to be updated.''',
+    value: 249
+)
diff --git a/platform-mc/manager.hpp b/platform-mc/manager.hpp
index 7636e94..d8210b4 100644
--- a/platform-mc/manager.hpp
+++ b/platform-mc/manager.hpp
@@ -7,6 +7,7 @@
 #include "platform_manager.hpp"
 #include "requester/handler.hpp"
 #include "requester/mctp_endpoint_discovery.hpp"
+#include "sensor_manager.hpp"
 #include "terminus_manager.hpp"
 
 namespace pldm
@@ -33,7 +34,8 @@
     explicit Manager(sdeventplus::Event& event, RequesterHandler& handler,
                      pldm::InstanceIdDb& instanceIdDb) :
         terminusManager(event, handler, instanceIdDb, termini, this),
-        platformManager(terminusManager, termini)
+        platformManager(terminusManager, termini),
+        sensorManager(event, terminusManager, termini)
     {}
 
     /** @brief Helper function to do the actions before discovering terminus
@@ -68,6 +70,33 @@
         terminusManager.removeMctpTerminus(mctpInfos);
     }
 
+    /** @brief Helper function to start sensor polling of the terminus TID
+     */
+    void startSensorPolling(pldm_tid_t tid)
+    {
+        sensorManager.startPolling(tid);
+    }
+
+    /** @brief Helper function to set available state for pldm request (sensor
+     *         polling and event polling) of the terminus TID. The `false` state
+     *         will trigger stop flow in coroutine of sensor polling/event
+     *         polling task to stop.
+     */
+    void updateAvailableState(pldm_tid_t tid, Availability state)
+    {
+        if (termini.contains(tid))
+        {
+            sensorManager.updateAvailableState(tid, state);
+        }
+    }
+
+    /** @brief Helper function to stop sensor polling of the terminus TID
+     */
+    void stopSensorPolling(pldm_tid_t tid)
+    {
+        sensorManager.stopPolling(tid);
+    }
+
   private:
     /** @brief List of discovered termini */
     TerminiMapper termini{};
@@ -77,6 +106,9 @@
 
     /** @brief Platform interface for calling the hook functions */
     PlatformManager platformManager;
+
+    /** @brief Store platform manager handler */
+    SensorManager sensorManager;
 };
 } // namespace platform_mc
 } // namespace pldm
diff --git a/platform-mc/numeric_sensor.cpp b/platform-mc/numeric_sensor.cpp
index ad563fd..af6efad 100644
--- a/platform-mc/numeric_sensor.cpp
+++ b/platform-mc/numeric_sensor.cpp
@@ -15,11 +15,10 @@
 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)
+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)
 {
     if (!pdr)
     {
@@ -264,6 +263,7 @@
     resolution = pdr->resolution;
     offset = pdr->offset;
     baseUnitModifier = pdr->unit_modifier;
+    timeStamp = 0;
 
     /**
      * DEFAULT_SENSOR_UPDATER_INTERVAL is in milliseconds
@@ -362,7 +362,7 @@
     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)
+    tid(tid), sensorName(sensorName)
 {
     if (!pdr)
     {
@@ -478,6 +478,7 @@
     resolution = std::numeric_limits<double>::quiet_NaN();
     offset = std::numeric_limits<double>::quiet_NaN();
     baseUnitModifier = pdr->unit_modifier;
+    timeStamp = 0;
 
     /**
      * DEFAULT_SENSOR_UPDATER_INTERVAL is in milliseconds
@@ -579,5 +580,175 @@
 {
     return std::isnan(value) ? value : value * std::pow(10, baseUnitModifier);
 }
+
+void NumericSensor::updateReading(bool available, bool functional, double value)
+{
+    if (!availabilityIntf || !operationalStatusIntf || !valueIntf)
+    {
+        lg2::error(
+            "Failed to update sensor {NAME} D-Bus interface don't exist.",
+            "NAME", sensorName);
+        return;
+    }
+    availabilityIntf->available(available);
+    operationalStatusIntf->functional(functional);
+    double curValue = valueIntf->value();
+    double newValue = std::numeric_limits<double>::quiet_NaN();
+    if (functional && available)
+    {
+        newValue = unitModifier(conversionFormula(value));
+        if (newValue != curValue &&
+            (!std::isnan(newValue) || !std::isnan(curValue)))
+        {
+            valueIntf->value(newValue);
+            updateThresholds();
+        }
+    }
+    else
+    {
+        if (newValue != curValue &&
+            (!std::isnan(newValue) || !std::isnan(curValue)))
+        {
+            valueIntf->value(std::numeric_limits<double>::quiet_NaN());
+        }
+    }
+}
+
+void NumericSensor::handleErrGetSensorReading()
+{
+    if (!operationalStatusIntf || !valueIntf)
+    {
+        lg2::error(
+            "Failed to update sensor {NAME} D-Bus interfaces don't exist.",
+            "NAME", sensorName);
+        return;
+    }
+    operationalStatusIntf->functional(false);
+    valueIntf->value(std::numeric_limits<double>::quiet_NaN());
+}
+
+bool NumericSensor::checkThreshold(bool alarm, bool direction, double value,
+                                   double threshold, double hyst)
+{
+    if (direction)
+    {
+        if (value >= threshold)
+        {
+            return true;
+        }
+        if (value < (threshold - hyst))
+        {
+            return false;
+        }
+    }
+    else
+    {
+        if (value <= threshold)
+        {
+            return true;
+        }
+        if (value > (threshold + hyst))
+        {
+            return false;
+        }
+    }
+    return alarm;
+}
+
+void NumericSensor::updateThresholds()
+{
+    if (!valueIntf)
+    {
+        lg2::error(
+            "Failed to update thresholds sensor {NAME} D-Bus interfaces don't exist.",
+            "NAME", sensorName);
+        return;
+    }
+
+    auto value = valueIntf->value();
+
+    if (thresholdWarningIntf &&
+        !std::isnan(thresholdWarningIntf->warningHigh()))
+    {
+        auto threshold = thresholdWarningIntf->warningHigh();
+        auto alarm = thresholdWarningIntf->warningAlarmHigh();
+        auto newAlarm =
+            checkThreshold(alarm, true, value, threshold, hysteresis);
+        if (alarm != newAlarm)
+        {
+            thresholdWarningIntf->warningAlarmHigh(newAlarm);
+            if (newAlarm)
+            {
+                thresholdWarningIntf->warningHighAlarmAsserted(value);
+            }
+            else
+            {
+                thresholdWarningIntf->warningHighAlarmDeasserted(value);
+            }
+        }
+    }
+
+    if (thresholdWarningIntf && !std::isnan(thresholdWarningIntf->warningLow()))
+    {
+        auto threshold = thresholdWarningIntf->warningLow();
+        auto alarm = thresholdWarningIntf->warningAlarmLow();
+        auto newAlarm =
+            checkThreshold(alarm, false, value, threshold, hysteresis);
+        if (alarm != newAlarm)
+        {
+            thresholdWarningIntf->warningAlarmLow(newAlarm);
+            if (newAlarm)
+            {
+                thresholdWarningIntf->warningLowAlarmAsserted(value);
+            }
+            else
+            {
+                thresholdWarningIntf->warningLowAlarmDeasserted(value);
+            }
+        }
+    }
+
+    if (thresholdCriticalIntf &&
+        !std::isnan(thresholdCriticalIntf->criticalHigh()))
+    {
+        auto threshold = thresholdCriticalIntf->criticalHigh();
+        auto alarm = thresholdCriticalIntf->criticalAlarmHigh();
+        auto newAlarm =
+            checkThreshold(alarm, true, value, threshold, hysteresis);
+        if (alarm != newAlarm)
+        {
+            thresholdCriticalIntf->criticalAlarmHigh(newAlarm);
+            if (newAlarm)
+            {
+                thresholdCriticalIntf->criticalHighAlarmAsserted(value);
+            }
+            else
+            {
+                thresholdCriticalIntf->criticalHighAlarmDeasserted(value);
+            }
+        }
+    }
+
+    if (thresholdCriticalIntf &&
+        !std::isnan(thresholdCriticalIntf->criticalLow()))
+    {
+        auto threshold = thresholdCriticalIntf->criticalLow();
+        auto alarm = thresholdCriticalIntf->criticalAlarmLow();
+        auto newAlarm =
+            checkThreshold(alarm, false, value, threshold, hysteresis);
+        if (alarm != newAlarm)
+        {
+            thresholdCriticalIntf->criticalAlarmLow(newAlarm);
+            if (newAlarm)
+            {
+                thresholdCriticalIntf->criticalLowAlarmAsserted(value);
+            }
+            else
+            {
+                thresholdCriticalIntf->criticalLowAlarmDeasserted(value);
+            }
+        }
+    }
+}
 } // namespace platform_mc
 } // namespace pldm
diff --git a/platform-mc/numeric_sensor.hpp b/platform-mc/numeric_sensor.hpp
index 770a286..7b2fc6a 100644
--- a/platform-mc/numeric_sensor.hpp
+++ b/platform-mc/numeric_sensor.hpp
@@ -54,6 +54,15 @@
 
     ~NumericSensor() {};
 
+    /** @brief The function called by Sensor Manager to set sensor to
+     * error status.
+     */
+    void handleErrGetSensorReading();
+
+    /** @brief Updating the sensor status to D-Bus interface
+     */
+    void updateReading(bool available, bool functional, double value = 0);
+
     /** @brief ConversionFormula is used to convert raw value to the unit
      * specified in PDR
      *
@@ -69,12 +78,103 @@
      */
     double unitModifier(double value);
 
+    /** @brief Check if value is over threshold.
+     *
+     *  @param[in] alarm - previous alarm state
+     *  @param[in] direction - upper or lower threshold checking
+     *  @param[in] value - raw value
+     *  @param[in] threshold - threshold value
+     *  @param[in] hyst - hysteresis value
+     *  @return bool - new alarm state
+     */
+    bool checkThreshold(bool alarm, bool direction, double value,
+                        double threshold, double hyst);
+
+    /** @brief Updating the association to D-Bus interface
+     *  @param[in] inventoryPath - inventory path of the entity
+     */
+    inline void setInventoryPath(const std::string& inventoryPath)
+    {
+        if (associationDefinitionsIntf)
+        {
+            associationDefinitionsIntf->associations(
+                {{"chassis", "all_sensors", inventoryPath}});
+        }
+    }
+
+    /** @brief Get Upper Critical threshold
+     *
+     *  @return double - Upper Critical threshold
+     */
+    double getThresholdUpperCritical()
+    {
+        if (thresholdCriticalIntf)
+        {
+            return thresholdCriticalIntf->criticalHigh();
+        }
+        else
+        {
+            return std::numeric_limits<double>::quiet_NaN();
+        }
+    };
+
+    /** @brief Get Lower Critical threshold
+     *
+     *  @return double - Lower Critical threshold
+     */
+    double getThresholdLowerCritical()
+    {
+        if (thresholdCriticalIntf)
+        {
+            return thresholdCriticalIntf->criticalLow();
+        }
+        else
+        {
+            return std::numeric_limits<double>::quiet_NaN();
+        }
+    };
+
+    /** @brief Get Upper Warning threshold
+     *
+     *  @return double - Upper Warning threshold
+     */
+    double getThresholdUpperWarning()
+    {
+        if (thresholdWarningIntf)
+        {
+            return thresholdWarningIntf->warningHigh();
+        }
+        else
+        {
+            return std::numeric_limits<double>::quiet_NaN();
+        }
+    };
+
+    /** @brief Get Lower Warning threshold
+     *
+     *  @return double - Lower Warning threshold
+     */
+    double getThresholdLowerWarning()
+    {
+        if (thresholdWarningIntf)
+        {
+            return thresholdWarningIntf->warningLow();
+        }
+        else
+        {
+            return std::numeric_limits<double>::quiet_NaN();
+        }
+    };
+
     /** @brief Terminus ID which the sensor belongs to */
     pldm_tid_t tid;
 
     /** @brief Sensor ID */
     uint16_t sensorId;
 
+    /** @brief  The time stamp since last getSensorReading command in usec */
+    uint64_t timeStamp;
+
     /** @brief  The time of sensor update interval in usec */
     uint64_t updateTime;
 
@@ -84,10 +184,13 @@
     /** @brief  sensorNameSpace */
     std::string sensorNameSpace;
 
-    /** @brief indicate if sensor is polled in priority */
-    bool isPriority;
-
   private:
+    /**
+     * @brief Check sensor reading if any threshold has been crossed and update
+     * Threshold interfaces accordingly
+     */
+    void updateThresholds();
+
     std::unique_ptr<ValueIntf> valueIntf = nullptr;
     std::unique_ptr<ThresholdWarningIntf> thresholdWarningIntf = nullptr;
     std::unique_ptr<ThresholdCriticalIntf> thresholdCriticalIntf = nullptr;
diff --git a/platform-mc/sensor_manager.cpp b/platform-mc/sensor_manager.cpp
new file mode 100644
index 0000000..fcfd0a6
--- /dev/null
+++ b/platform-mc/sensor_manager.cpp
@@ -0,0 +1,362 @@
+#include "sensor_manager.hpp"
+
+#include "terminus_manager.hpp"
+
+#include <phosphor-logging/lg2.hpp>
+
+#include <exception>
+
+namespace pldm
+{
+namespace platform_mc
+{
+
+SensorManager::SensorManager(sdeventplus::Event& event,
+                             TerminusManager& terminusManager,
+                             TerminiMapper& termini) :
+    event(event), terminusManager(terminusManager), termini(termini),
+    pollingTime(SENSOR_POLLING_TIME)
+{}
+
+void SensorManager::startPolling(pldm_tid_t tid)
+{
+    if (!termini.contains(tid))
+    {
+        return;
+    }
+
+    /* tid already initializes roundRobinSensors list */
+    if (sensorPollTimers.contains(tid))
+    {
+        lg2::info("Terminus ID {TID}: sensor poll timer already exists.", "TID",
+                  tid);
+        return;
+    }
+    // numeric sensor
+    auto terminus = termini[tid];
+    for (auto& sensor : terminus->numericSensors)
+    {
+        roundRobinSensors[tid].push(sensor);
+    }
+
+    updateAvailableState(tid, true);
+
+    if (!roundRobinSensors[tid].size())
+    {
+        lg2::info("Terminus ID {TID}: no sensors to poll.", "TID", tid);
+        return;
+    }
+
+    sensorPollTimers[tid] = std::make_unique<sdbusplus::Timer>(
+        event.get(),
+        std::bind_front(&SensorManager::doSensorPolling, this, tid));
+
+    try
+    {
+        if (sensorPollTimers[tid] && !sensorPollTimers[tid]->isRunning())
+        {
+            sensorPollTimers[tid]->start(
+                duration_cast<std::chrono::milliseconds>(
+                    std::chrono::milliseconds(pollingTime)),
+                true);
+        }
+    }
+    catch (const std::exception& e)
+    {
+        lg2::error(
+            "Terminus ID {TID}: Failed to start sensor polling timer. Exception: {EXCEPTION}",
+            "TID", tid, "EXCEPTION", e);
+        return;
+    }
+}
+
+void SensorManager::stopPolling(pldm_tid_t tid)
+{
+    /* Stop polling timer */
+    if (sensorPollTimers.contains(tid))
+    {
+        sensorPollTimers[tid]->stop();
+        sensorPollTimers.erase(tid);
+    }
+
+    roundRobinSensors.erase(tid);
+
+    if (doSensorPollingTaskHandles.contains(tid))
+    {
+        auto& [scope, rcOpt] = doSensorPollingTaskHandles[tid];
+        scope.request_stop();
+        doSensorPollingTaskHandles.erase(tid);
+    }
+
+    availableState.erase(tid);
+}
+
+void SensorManager::doSensorPolling(pldm_tid_t tid)
+{
+    auto it = doSensorPollingTaskHandles.find(tid);
+    if (it != doSensorPollingTaskHandles.end())
+    {
+        auto& [scope, rcOpt] = it->second;
+        if (!rcOpt.has_value())
+        {
+            return;
+        }
+        doSensorPollingTaskHandles.erase(tid);
+    }
+
+    auto& [scope, rcOpt] =
+        doSensorPollingTaskHandles
+            .emplace(std::piecewise_construct, std::forward_as_tuple(tid),
+                     std::forward_as_tuple())
+            .first->second;
+    scope.spawn(
+        stdexec::just() | stdexec::let_value([this, &rcOpt,
+                                              tid] -> exec::task<void> {
+            auto res =
+                co_await stdexec::stopped_as_optional(doSensorPollingTask(tid));
+            if (res.has_value())
+            {
+                rcOpt = *res;
+            }
+            else
+            {
+                lg2::info("Stopped polling for Terminus ID {TID}", "TID", tid);
+                try
+                {
+                    if (sensorPollTimers.contains(tid) &&
+                        sensorPollTimers[tid] &&
+                        sensorPollTimers[tid]->isRunning())
+                    {
+                        sensorPollTimers[tid]->stop();
+                    }
+                }
+                catch (const std::exception& e)
+                {
+                    lg2::error(
+                        "Terminus ID {TID}: Failed to stop polling timer. Exception: {EXCEPTION}",
+                        "TID", tid, "EXCEPTION", e);
+                }
+                rcOpt = PLDM_SUCCESS;
+            }
+        }),
+        exec::default_task_context<void>(exec::inline_scheduler{}));
+}
+
+exec::task<int> SensorManager::doSensorPollingTask(pldm_tid_t tid)
+{
+    uint64_t t0 = 0;
+    uint64_t t1 = 0;
+    uint64_t elapsed = 0;
+    uint64_t pollingTimeInUsec = pollingTime * 1000;
+    uint8_t rc = PLDM_SUCCESS;
+
+    do
+    {
+        if ((!sensorPollTimers.contains(tid)) ||
+            (sensorPollTimers[tid] && !sensorPollTimers[tid]->isRunning()))
+        {
+            co_return PLDM_ERROR;
+        }
+
+        sd_event_now(event.get(), CLOCK_MONOTONIC, &t0);
+
+        /**
+         * Terminus is not available for PLDM request.
+         * The terminus manager will trigger recovery process to recovery the
+         * communication between the local terminus and the remote terminus.
+         * The sensor polling should be stopped while recovering the
+         * communication.
+         */
+        if (!getAvailableState(tid))
+        {
+            lg2::info(
+                "Terminus ID {TID} is not available for PLDM request from {NOW}.",
+                "TID", tid, "NOW", pldm::utils::getCurrentSystemTime());
+            co_await stdexec::just_stopped();
+        }
+
+        if (!termini.contains(tid))
+        {
+            co_return PLDM_SUCCESS;
+        }
+
+        sd_event_now(event.get(), CLOCK_MONOTONIC, &t1);
+        auto toBeUpdated = roundRobinSensors[tid].size();
+        while (((t1 - t0) < pollingTimeInUsec) && (toBeUpdated > 0))
+        {
+            if (!getAvailableState(tid))
+            {
+                lg2::info(
+                    "Terminus ID {TID} is not available for PLDM request from {NOW}.",
+                    "TID", tid, "NOW", pldm::utils::getCurrentSystemTime());
+                co_await stdexec::just_stopped();
+            }
+
+            auto sensor = roundRobinSensors[tid].front();
+
+            sd_event_now(event.get(), CLOCK_MONOTONIC, &t1);
+            elapsed = t1 - sensor->timeStamp;
+            if ((sensor->updateTime <= elapsed) || (!sensor->timeStamp))
+            {
+                rc = co_await getSensorReading(sensor);
+
+                if ((!sensorPollTimers.contains(tid)) ||
+                    (sensorPollTimers[tid] &&
+                     !sensorPollTimers[tid]->isRunning()))
+                {
+                    co_return PLDM_ERROR;
+                }
+                sd_event_now(event.get(), CLOCK_MONOTONIC, &t1);
+                if (rc == PLDM_SUCCESS)
+                {
+                    sensor->timeStamp = t1;
+                }
+                else
+                {
+                    lg2::error(
+                        "Failed to get sensor value for terminus {TID}, error: {RC}",
+                        "TID", tid, "RC", rc);
+                }
+            }
+
+            toBeUpdated--;
+            if (roundRobinSensors.contains(tid))
+            {
+                roundRobinSensors[tid].pop();
+                roundRobinSensors[tid].push(std::move(sensor));
+            }
+            sd_event_now(event.get(), CLOCK_MONOTONIC, &t1);
+        }
+
+        sd_event_now(event.get(), CLOCK_MONOTONIC, &t1);
+    } while ((t1 - t0) >= pollingTimeInUsec);
+
+    co_return PLDM_SUCCESS;
+}
+
+exec::task<int>
+    SensorManager::getSensorReading(std::shared_ptr<NumericSensor> sensor)
+{
+    if (!sensor)
+    {
+        lg2::error("Call `getSensorReading` with null `sensor` pointer.");
+        co_return PLDM_ERROR_INVALID_DATA;
+    }
+
+    auto tid = sensor->tid;
+    auto sensorId = sensor->sensorId;
+    Request request(sizeof(pldm_msg_hdr) + PLDM_GET_SENSOR_READING_REQ_BYTES);
+    auto requestMsg = reinterpret_cast<pldm_msg*>(request.data());
+    auto rc = encode_get_sensor_reading_req(0, sensorId, false, requestMsg);
+    if (rc)
+    {
+        lg2::error(
+            "Failed to encode request GetSensorReading for terminus ID {TID}, sensor Id {ID}, error {RC}.",
+            "TID", tid, "ID", sensorId, "RC", rc);
+        co_return rc;
+    }
+
+    if (!getAvailableState(tid))
+    {
+        lg2::info(
+            "Terminus ID {TID} is not available for PLDM request from {NOW}.",
+            "TID", tid, "NOW", pldm::utils::getCurrentSystemTime());
+        co_await stdexec::just_stopped();
+    }
+
+    const pldm_msg* responseMsg = nullptr;
+    size_t responseLen = 0;
+    rc = co_await terminusManager.sendRecvPldmMsg(tid, request, &responseMsg,
+                                                  &responseLen);
+    if (rc)
+    {
+        lg2::error(
+            "Failed to send GetSensorReading message for terminus {TID}, sensor Id {ID}, error {RC}",
+            "TID", tid, "ID", sensorId, "RC", rc);
+        co_return rc;
+    }
+
+    if ((!sensorPollTimers.contains(tid)) ||
+        (sensorPollTimers[tid] && !sensorPollTimers[tid]->isRunning()))
+    {
+        co_return PLDM_ERROR;
+    }
+
+    uint8_t completionCode = PLDM_SUCCESS;
+    uint8_t sensorDataSize = PLDM_SENSOR_DATA_SIZE_SINT32;
+    uint8_t sensorOperationalState = 0;
+    uint8_t sensorEventMessageEnable = 0;
+    uint8_t presentState = 0;
+    uint8_t previousState = 0;
+    uint8_t eventState = 0;
+    union_sensor_data_size presentReading;
+    rc = decode_get_sensor_reading_resp(
+        responseMsg, responseLen, &completionCode, &sensorDataSize,
+        &sensorOperationalState, &sensorEventMessageEnable, &presentState,
+        &previousState, &eventState,
+        reinterpret_cast<uint8_t*>(&presentReading));
+    if (rc)
+    {
+        lg2::error(
+            "Failed to decode response GetSensorReading for terminus ID {TID}, sensor Id {ID}, error {RC}.",
+            "TID", tid, "ID", sensorId, "RC", rc);
+        sensor->handleErrGetSensorReading();
+        co_return rc;
+    }
+
+    if (completionCode != PLDM_SUCCESS)
+    {
+        lg2::error(
+            "Error : GetSensorReading for terminus ID {TID}, sensor Id {ID}, complete code {CC}.",
+            "TID", tid, "ID", sensorId, "CC", completionCode);
+        co_return completionCode;
+    }
+
+    double value = std::numeric_limits<double>::quiet_NaN();
+    switch (sensorOperationalState)
+    {
+        case PLDM_SENSOR_ENABLED:
+            break;
+        case PLDM_SENSOR_DISABLED:
+            sensor->updateReading(true, false, value);
+            co_return completionCode;
+        case PLDM_SENSOR_FAILED:
+            sensor->updateReading(false, true, value);
+            co_return completionCode;
+        case PLDM_SENSOR_UNAVAILABLE:
+        default:
+            sensor->updateReading(false, false, value);
+            co_return completionCode;
+    }
+
+    switch (sensorDataSize)
+    {
+        case PLDM_SENSOR_DATA_SIZE_UINT8:
+            value = static_cast<double>(presentReading.value_u8);
+            break;
+        case PLDM_SENSOR_DATA_SIZE_SINT8:
+            value = static_cast<double>(presentReading.value_s8);
+            break;
+        case PLDM_SENSOR_DATA_SIZE_UINT16:
+            value = static_cast<double>(presentReading.value_u16);
+            break;
+        case PLDM_SENSOR_DATA_SIZE_SINT16:
+            value = static_cast<double>(presentReading.value_s16);
+            break;
+        case PLDM_SENSOR_DATA_SIZE_UINT32:
+            value = static_cast<double>(presentReading.value_u32);
+            break;
+        case PLDM_SENSOR_DATA_SIZE_SINT32:
+            value = static_cast<double>(presentReading.value_s32);
+            break;
+        default:
+            value = std::numeric_limits<double>::quiet_NaN();
+            break;
+    }
+
+    sensor->updateReading(true, true, value);
+    co_return completionCode;
+}
+
+} // namespace platform_mc
+} // namespace pldm
diff --git a/platform-mc/sensor_manager.hpp b/platform-mc/sensor_manager.hpp
new file mode 100644
index 0000000..1c7c788
--- /dev/null
+++ b/platform-mc/sensor_manager.hpp
@@ -0,0 +1,116 @@
+#pragma once
+
+#include "libpldm/platform.h"
+#include "libpldm/pldm.h"
+
+#include "common/types.hpp"
+#include "requester/handler.hpp"
+#include "terminus.hpp"
+#include "terminus_manager.hpp"
+
+#include <map>
+#include <memory>
+#include <optional>
+#include <utility>
+#include <vector>
+
+namespace pldm
+{
+namespace platform_mc
+{
+
+/**
+ * @brief SensorManager
+ *
+ * This class manages the sensors found in terminus and provides
+ * function calls for other classes to start/stop sensor monitoring.
+ *
+ */
+class SensorManager
+{
+  public:
+    SensorManager() = delete;
+    SensorManager(const SensorManager&) = delete;
+    SensorManager(SensorManager&&) = delete;
+    SensorManager& operator=(const SensorManager&) = delete;
+    SensorManager& operator=(SensorManager&&) = delete;
+    virtual ~SensorManager() = default;
+
+    explicit SensorManager(sdeventplus::Event& event,
+                           TerminusManager& terminusManager,
+                           TerminiMapper& termini);
+
+    /** @brief starting sensor polling task
+     */
+    void startPolling(pldm_tid_t tid);
+
+    /** @brief stopping sensor polling task
+     */
+    void stopPolling(pldm_tid_t tid);
+
+    /** @brief Set available state of terminus for pldm request.
+     */
+    void updateAvailableState(pldm_tid_t tid, Availability state)
+    {
+        availableState[tid] = state;
+    };
+
+    /** @brief Get available state of terminus for pldm request.
+     */
+    bool getAvailableState(pldm_tid_t tid)
+    {
+        if (!availableState.contains(tid))
+        {
+            return false;
+        }
+        return availableState[tid];
+    };
+
+  protected:
+    /** @brief start a coroutine for polling all sensors.
+     */
+    virtual void doSensorPolling(pldm_tid_t tid);
+
+    /** @brief polling all sensors in each terminus
+     *
+     *  @param[in] tid - Destination TID
+     *  @return coroutine return_value - PLDM completion code
+     */
+    exec::task<int> doSensorPollingTask(pldm_tid_t tid);
+
+    /** @brief Sending getSensorReading command for the sensor
+     *
+     *  @param[in] sensor - the sensor to be updated
+     *  @return coroutine return_value - PLDM completion code
+     */
+    exec::task<int> getSensorReading(std::shared_ptr<NumericSensor> sensor);
+
+    /** @brief Reference to to PLDM daemon's main event loop.
+     */
+    sdeventplus::Event& event;
+
+    /** @brief reference of terminusManager */
+    TerminusManager& terminusManager;
+
+    /** @brief List of discovered termini */
+    TerminiMapper& termini;
+
+    /** @brief sensor polling interval in ms. */
+    uint32_t pollingTime;
+
+    /** @brief sensor polling timers */
+    std::map<pldm_tid_t, std::unique_ptr<sdbusplus::Timer>> sensorPollTimers;
+
+    /** @brief coroutine handle of doSensorPollingTasks */
+    std::map<pldm_tid_t, std::pair<exec::async_scope, std::optional<int>>>
+        doSensorPollingTaskHandles;
+
+    /** @brief Available state for pldm request of terminus */
+    std::map<pldm_tid_t, Availability> availableState;
+
+    /** @brief round robin sensor list */
+    std::map<pldm_tid_t, std::queue<std::shared_ptr<NumericSensor>>>
+        roundRobinSensors;
+};
+} // namespace platform_mc
+} // namespace pldm
diff --git a/platform-mc/terminus_manager.cpp b/platform-mc/terminus_manager.cpp
index 7567d89..cbf56c5 100644
--- a/platform-mc/terminus_manager.cpp
+++ b/platform-mc/terminus_manager.cpp
@@ -162,6 +162,7 @@
 
 exec::task<int> TerminusManager::discoverMctpTerminusTask()
 {
+    std::vector<pldm_tid_t> addedTids;
     while (!queuedMctpInfos.empty())
     {
         if (manager)
@@ -177,11 +178,23 @@
             {
                 co_await initMctpTerminus(mctpInfo);
             }
+
+            /* Get TID of initialized terminus */
+            auto tid = toTid(mctpInfo);
+            if (!tid)
+            {
+                co_return PLDM_ERROR;
+            }
+            addedTids.push_back(tid.value());
         }
 
         if (manager)
         {
             co_await manager->afterDiscoverTerminus();
+            for (const auto& tid : addedTids)
+            {
+                manager->startSensorPolling(tid);
+            }
         }
 
         queuedMctpInfos.pop();
@@ -201,6 +214,11 @@
             continue;
         }
 
+        if (manager)
+        {
+            manager->stopSensorPolling(it->second->getTid());
+        }
+
         unmapTid(it->first);
         termini.erase(it);
     }
diff --git a/platform-mc/test/meson.build b/platform-mc/test/meson.build
index 6b95f90..64177cc 100644
--- a/platform-mc/test/meson.build
+++ b/platform-mc/test/meson.build
@@ -4,6 +4,7 @@
             '../terminus.cpp',
             '../platform_manager.cpp',
             '../manager.cpp',
+            '../sensor_manager.cpp',
             '../numeric_sensor.cpp',
             '../../requester/mctp_endpoint_discovery.cpp'],
             include_directories: ['../../requester', '../../pldmd'])
@@ -12,6 +13,8 @@
   'terminus_manager_test',
   'terminus_test',
   'platform_manager_test',
+  'sensor_manager_test',
+  'numeric_sensor_test',
 ]
 
 foreach t : tests
diff --git a/platform-mc/test/mock_sensor_manager.hpp b/platform-mc/test/mock_sensor_manager.hpp
new file mode 100644
index 0000000..a52e4ea
--- /dev/null
+++ b/platform-mc/test/mock_sensor_manager.hpp
@@ -0,0 +1,24 @@
+#pragma once
+
+#include "platform-mc/sensor_manager.hpp"
+
+#include <gmock/gmock.h>
+
+namespace pldm
+{
+namespace platform_mc
+{
+
+class MockSensorManager : public SensorManager
+{
+  public:
+    MockSensorManager(sdeventplus::Event& event,
+                      TerminusManager& terminusManager,
+                      TerminiMapper& termini) :
+        SensorManager(event, terminusManager, termini) {};
+
+    MOCK_METHOD(void, doSensorPolling, (pldm_tid_t tid), (override));
+};
+
+} // namespace platform_mc
+} // namespace pldm
diff --git a/platform-mc/test/numeric_sensor_test.cpp b/platform-mc/test/numeric_sensor_test.cpp
new file mode 100644
index 0000000..8b59401
--- /dev/null
+++ b/platform-mc/test/numeric_sensor_test.cpp
@@ -0,0 +1,272 @@
+
+#include "libpldm/entity.h"
+#include "libpldm/platform.h"
+
+#include "platform-mc/numeric_sensor.hpp"
+#include "platform-mc/terminus.hpp"
+
+#include <gtest/gtest.h>
+
+TEST(NumericSensor, conversionFormula)
+{
+    std::vector<uint8_t> pdr1{
+        0x1,
+        0x0,
+        0x0,
+        0x0,                     // record handle
+        0x1,                     // PDRHeaderVersion
+        PLDM_NUMERIC_SENSOR_PDR, // PDRType
+        0x0,
+        0x0,                     // recordChangeNumber
+        PLDM_PDR_NUMERIC_SENSOR_PDR_FIXED_LENGTH +
+            PLDM_PDR_NUMERIC_SENSOR_PDR_VARIED_SENSOR_DATA_SIZE_MIN_LENGTH +
+            PLDM_PDR_NUMERIC_SENSOR_PDR_VARIED_RANGE_FIELD_MIN_LENGTH,
+        0,                             // dataLength
+        0,
+        0,                             // PLDMTerminusHandle
+        0x1,
+        0x0,                           // sensorID=1
+        PLDM_ENTITY_POWER_SUPPLY,
+        0,                             // entityType=Power Supply(120)
+        1,
+        0,                             // entityInstanceNumber
+        0x1,
+        0x0,                           // containerID=1
+        PLDM_NO_INIT,                  // sensorInit
+        false,                         // sensorAuxiliaryNamesPDR
+        PLDM_SENSOR_UNIT_DEGRESS_C,    // baseUint(2)=degrees C
+        1,                             // unitModifier = 1
+        0,                             // rateUnit
+        0,                             // baseOEMUnitHandle
+        0,                             // auxUnit
+        0,                             // auxUnitModifier
+        0,                             // auxRateUnit
+        0,                             // rel
+        0,                             // auxOEMUnitHandle
+        true,                          // isLinear
+        PLDM_RANGE_FIELD_FORMAT_SINT8, // sensorDataSize
+        0,
+        0,
+        0xc0,
+        0x3f, // resolution=1.5
+        0,
+        0,
+        0x80,
+        0x3f, // offset=1.0
+        0,
+        0,    // accuracy
+        0,    // plusTolerance
+        0,    // minusTolerance
+        2,    // hysteresis
+        0,    // supportedThresholds
+        0,    // thresholdAndHysteresisVolatility
+        0,
+        0,
+        0x80,
+        0x3f, // stateTransistionInterval=1.0
+        0,
+        0,
+        0x80,
+        0x3f,                          // updateInverval=1.0
+        255,                           // maxReadable
+        0,                             // minReadable
+        PLDM_RANGE_FIELD_FORMAT_UINT8, // rangeFieldFormat
+        0,                             // rangeFieldsupport
+        0,                             // nominalValue
+        0,                             // normalMax
+        0,                             // normalMin
+        0,                             // warningHigh
+        0,                             // warningLow
+        0,                             // criticalHigh
+        0,                             // criticalLow
+        0,                             // fatalHigh
+        0                              // fatalLow
+    };
+
+    auto numericSensorPdr = std::make_shared<pldm_numeric_sensor_value_pdr>();
+    std::printf("pdr size=%ld\n", pdr1.size());
+    auto rc = decode_numeric_sensor_pdr_data(pdr1.data(), pdr1.size(),
+                                             numericSensorPdr.get());
+    EXPECT_EQ(rc, PLDM_SUCCESS);
+
+    std::string sensorName{"test1"};
+    std::string inventoryPath{
+        "/xyz/openbmc_project/inventroy/Item/Board/PLDM_device_1"};
+    pldm::platform_mc::NumericSensor sensor(0x01, true, numericSensorPdr,
+                                            sensorName, inventoryPath);
+    double reading = 40.0;
+    double convertedValue = 0;
+    convertedValue = sensor.conversionFormula(reading);
+    convertedValue = sensor.unitModifier(convertedValue);
+
+    // (40*1.5 + 1.0 ) * 10^1 = 610
+    EXPECT_EQ(610, convertedValue);
+}
+
+TEST(NumericSensor, checkThreshold)
+{
+    std::vector<uint8_t> pdr1{
+        0x1,
+        0x0,
+        0x0,
+        0x0,                     // record handle
+        0x1,                     // PDRHeaderVersion
+        PLDM_NUMERIC_SENSOR_PDR, // PDRType
+        0x0,
+        0x0,                     // recordChangeNumber
+        PLDM_PDR_NUMERIC_SENSOR_PDR_FIXED_LENGTH +
+            PLDM_PDR_NUMERIC_SENSOR_PDR_VARIED_SENSOR_DATA_SIZE_MIN_LENGTH +
+            PLDM_PDR_NUMERIC_SENSOR_PDR_VARIED_RANGE_FIELD_MIN_LENGTH,
+        0,                             // dataLength
+        0,
+        0,                             // PLDMTerminusHandle
+        0x1,
+        0x0,                           // sensorID=1
+        PLDM_ENTITY_POWER_SUPPLY,
+        0,                             // entityType=Power Supply(120)
+        1,
+        0,                             // entityInstanceNumber
+        0x1,
+        0x0,                           // containerID=1
+        PLDM_NO_INIT,                  // sensorInit
+        false,                         // sensorAuxiliaryNamesPDR
+        PLDM_SENSOR_UNIT_DEGRESS_C,    // baseUint(2)=degrees C
+        1,                             // unitModifier = 1
+        0,                             // rateUnit
+        0,                             // baseOEMUnitHandle
+        0,                             // auxUnit
+        0,                             // auxUnitModifier
+        0,                             // auxRateUnit
+        0,                             // rel
+        0,                             // auxOEMUnitHandle
+        true,                          // isLinear
+        PLDM_RANGE_FIELD_FORMAT_SINT8, // sensorDataSize
+        0,
+        0,
+        0xc0,
+        0x3f, // resolution=1.5
+        0,
+        0,
+        0x80,
+        0x3f, // offset=1.0
+        0,
+        0,    // accuracy
+        0,    // plusTolerance
+        0,    // minusTolerance
+        2,    // hysteresis
+        0,    // supportedThresholds
+        0,    // thresholdAndHysteresisVolatility
+        0,
+        0,
+        0x80,
+        0x3f, // stateTransistionInterval=1.0
+        0,
+        0,
+        0x80,
+        0x3f,                          // updateInverval=1.0
+        255,                           // maxReadable
+        0,                             // minReadable
+        PLDM_RANGE_FIELD_FORMAT_UINT8, // rangeFieldFormat
+        0,                             // rangeFieldsupport
+        0,                             // nominalValue
+        0,                             // normalMax
+        0,                             // normalMin
+        0,                             // warningHigh
+        0,                             // warningLow
+        0,                             // criticalHigh
+        0,                             // criticalLow
+        0,                             // fatalHigh
+        0                              // fatalLow
+    };
+
+    auto numericSensorPdr = std::make_shared<pldm_numeric_sensor_value_pdr>();
+    auto rc = decode_numeric_sensor_pdr_data(pdr1.data(), pdr1.size(),
+                                             numericSensorPdr.get());
+    EXPECT_EQ(rc, PLDM_SUCCESS);
+    std::string sensorName{"test1"};
+    std::string inventoryPath{
+        "/xyz/openbmc_project/inventroy/Item/Board/PLDM_device_1"};
+    pldm::platform_mc::NumericSensor sensor(0x01, true, numericSensorPdr,
+                                            sensorName, inventoryPath);
+
+    bool highAlarm = false;
+    bool lowAlarm = false;
+    double highThreshold = 40;
+    double lowThreshold = 30;
+    double hysteresis = 2;
+
+    // reading     35->40->45->38->35->30->25->32->35
+    // highAlarm    F->T ->T ->T ->F ->F ->F -> F-> F
+    // lowAlarm     F->F ->F ->F ->F ->T ->T -> T ->F
+    double reading = 35;
+    highAlarm = sensor.checkThreshold(highAlarm, true, reading, highThreshold,
+                                      hysteresis);
+    EXPECT_EQ(false, highAlarm);
+    lowAlarm = sensor.checkThreshold(lowAlarm, false, reading, lowThreshold,
+                                     hysteresis);
+    EXPECT_EQ(false, lowAlarm);
+
+    reading = 40;
+    highAlarm = sensor.checkThreshold(highAlarm, true, reading, highThreshold,
+                                      hysteresis);
+    EXPECT_EQ(true, highAlarm);
+    lowAlarm = sensor.checkThreshold(lowAlarm, false, reading, lowThreshold,
+                                     hysteresis);
+    EXPECT_EQ(false, lowAlarm);
+
+    reading = 45;
+    highAlarm = sensor.checkThreshold(highAlarm, true, reading, highThreshold,
+                                      hysteresis);
+    EXPECT_EQ(true, highAlarm);
+    lowAlarm = sensor.checkThreshold(lowAlarm, false, reading, lowThreshold,
+                                     hysteresis);
+    EXPECT_EQ(false, lowAlarm);
+
+    reading = 38;
+    highAlarm = sensor.checkThreshold(highAlarm, true, reading, highThreshold,
+                                      hysteresis);
+    EXPECT_EQ(true, highAlarm);
+    lowAlarm = sensor.checkThreshold(lowAlarm, false, reading, lowThreshold,
+                                     hysteresis);
+    EXPECT_EQ(false, lowAlarm);
+
+    reading = 35;
+    highAlarm = sensor.checkThreshold(highAlarm, true, reading, highThreshold,
+                                      hysteresis);
+    EXPECT_EQ(false, highAlarm);
+    lowAlarm = sensor.checkThreshold(lowAlarm, false, reading, lowThreshold,
+                                     hysteresis);
+    EXPECT_EQ(false, lowAlarm);
+
+    reading = 30;
+    highAlarm = sensor.checkThreshold(highAlarm, true, reading, highThreshold,
+                                      hysteresis);
+    EXPECT_EQ(false, highAlarm);
+    lowAlarm = sensor.checkThreshold(lowAlarm, false, reading, lowThreshold,
+                                     hysteresis);
+    EXPECT_EQ(true, lowAlarm);
+
+    reading = 25;
+    highAlarm = sensor.checkThreshold(highAlarm, true, reading, highThreshold,
+                                      hysteresis);
+    EXPECT_EQ(false, highAlarm);
+    lowAlarm = sensor.checkThreshold(lowAlarm, false, reading, lowThreshold,
+                                     hysteresis);
+    EXPECT_EQ(true, lowAlarm);
+
+    reading = 32;
+    highAlarm = sensor.checkThreshold(highAlarm, true, reading, highThreshold,
+                                      hysteresis);
+    EXPECT_EQ(false, highAlarm);
+    lowAlarm = sensor.checkThreshold(lowAlarm, false, reading, lowThreshold,
+                                     hysteresis);
+    EXPECT_EQ(true, lowAlarm);
+
+    reading = 35;
+    highAlarm = sensor.checkThreshold(highAlarm, true, reading, highThreshold,
+                                      hysteresis);
+    EXPECT_EQ(false, highAlarm);
+    lowAlarm = sensor.checkThreshold(lowAlarm, false, reading, lowThreshold,
+                                     hysteresis);
+    EXPECT_EQ(false, lowAlarm);
+}
diff --git a/platform-mc/test/platform_manager_test.cpp b/platform-mc/test/platform_manager_test.cpp
index 8afad35..73baa1a 100644
--- a/platform-mc/test/platform_manager_test.cpp
+++ b/platform-mc/test/platform_manager_test.cpp
@@ -63,7 +63,7 @@
             0x0, 0x0,  0x0,  0x0,          0x0, 0x0, // updateTime
             0x0, 0x0,  0x0,  0x0,          0x0, 0x0, 0x0,
             0x0, 0x0,  0x0,  0x0,          0x0, 0x0, // OEMUpdateTime
-            1,   0x0,  0x0,  0x0,                    // recordCount
+            2,   0x0,  0x0,  0x0,                    // recordCount
             0x0, 0x1,  0x0,  0x0,                    // repositorySize
             59,  0x0,  0x0,  0x0,                    // largestRecordSize
             0x0 // dataTransferHandleTimeout
@@ -76,12 +76,12 @@
     // queue getPDR responses
     const size_t getPdrRespLen = 81;
     std::array<uint8_t, sizeof(pldm_msg_hdr) + getPdrRespLen> getPdrResp{
-        0x0, 0x02, 0x51, PLDM_SUCCESS, 0x0, 0x0, 0x0, 0x0, // nextRecordHandle
+        0x0, 0x02, 0x51, PLDM_SUCCESS, 0x1, 0x0, 0x0, 0x0, // nextRecordHandle
         0x0, 0x0, 0x0, 0x0, // nextDataTransferHandle
         0x5,                // transferFlag
         69, 0x0,            // responseCount
         // numeric Sensor PDR
-        0x1, 0x0, 0x0,
+        0x0, 0x0, 0x0,
         0x0,                     // record handle
         0x1,                     // PDRHeaderVersion
         PLDM_NUMERIC_SENSOR_PDR, // PDRType
@@ -147,9 +147,47 @@
         reinterpret_cast<pldm_msg*>(getPdrResp.data()), sizeof(getPdrResp));
     EXPECT_EQ(rc, PLDM_SUCCESS);
 
+    const size_t getPdrAuxNameRespLen = 39;
+    std::array<uint8_t, sizeof(pldm_msg_hdr) + getPdrAuxNameRespLen>
+        getPdrAuxNameResp{
+            0x0, 0x02, 0x51, PLDM_SUCCESS, 0x0, 0x0, 0x0,
+            0x0,                // nextRecordHandle
+            0x0, 0x0, 0x0, 0x0, // nextDataTransferHandle
+            0x5,                // transferFlag
+            0x1b, 0x0,          // responseCount
+            // Common PDR Header
+            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"
+        };
+    rc = mockTerminusManager.enqueueResponse(
+        reinterpret_cast<pldm_msg*>(getPdrAuxNameResp.data()),
+        sizeof(getPdrAuxNameResp));
+    EXPECT_EQ(rc, PLDM_SUCCESS);
+
     stdexec::sync_wait(platformManager.initTerminus());
     EXPECT_EQ(true, terminus->initialized);
-    EXPECT_EQ(1, terminus->pdrs.size());
+    EXPECT_EQ(2, terminus->pdrs.size());
+    EXPECT_EQ(1, terminus->numericSensors.size());
+    EXPECT_EQ("S0", terminus->getTerminusName());
 }
 
 TEST_F(PlatformManagerTest, parseTerminusNameTest)
@@ -440,6 +478,7 @@
     stdexec::sync_wait(platformManager.initTerminus());
     EXPECT_EQ(true, terminus->initialized);
     EXPECT_EQ(0, terminus->pdrs.size());
+    EXPECT_EQ(0, terminus->numericSensors.size());
 }
 
 TEST_F(PlatformManagerTest, negativeInitTerminusTest2)
@@ -471,4 +510,5 @@
     stdexec::sync_wait(platformManager.initTerminus());
     EXPECT_EQ(true, terminus->initialized);
     EXPECT_EQ(0, terminus->pdrs.size());
+    EXPECT_EQ(0, terminus->numericSensors.size());
 }
diff --git a/platform-mc/test/sensor_manager_test.cpp b/platform-mc/test/sensor_manager_test.cpp
new file mode 100644
index 0000000..03cb7fd
--- /dev/null
+++ b/platform-mc/test/sensor_manager_test.cpp
@@ -0,0 +1,172 @@
+#include "common/instance_id.hpp"
+#include "common/types.hpp"
+#include "mock_sensor_manager.hpp"
+#include "platform-mc/terminus_manager.hpp"
+#include "test/test_instance_id.hpp"
+
+#include <sdeventplus/event.hpp>
+
+#include <gtest/gtest.h>
+
+using ::testing::_;
+using ::testing::Between;
+using ::testing::Return;
+
+class SensorManagerTest : public testing::Test
+{
+  protected:
+    SensorManagerTest() :
+        bus(pldm::utils::DBusHandler::getBus()),
+        event(sdeventplus::Event::get_default()), instanceIdDb(),
+        reqHandler(pldmTransport, event, instanceIdDb, false),
+        terminusManager(event, reqHandler, instanceIdDb, termini, nullptr),
+        sensorManager(event, terminusManager, termini)
+    {}
+
+    void runEventLoopForSeconds(uint64_t sec)
+    {
+        uint64_t t0 = 0;
+        uint64_t t1 = 0;
+        uint64_t usec = sec * 1000000;
+        uint64_t elapsed = 0;
+        sd_event_now(event.get(), CLOCK_MONOTONIC, &t0);
+        do
+        {
+            if (!sd_event_run(event.get(), usec - elapsed))
+            {
+                break;
+            }
+            sd_event_now(event.get(), CLOCK_MONOTONIC, &t1);
+            elapsed = t1 - t0;
+        } while (elapsed < usec);
+    }
+
+    PldmTransport* pldmTransport = nullptr;
+    sdbusplus::bus::bus& bus;
+    sdeventplus::Event event;
+    TestInstanceIdDb instanceIdDb;
+    pldm::requester::Handler<pldm::requester::Request> reqHandler;
+    pldm::platform_mc::TerminusManager terminusManager;
+    pldm::platform_mc::MockSensorManager sensorManager;
+    std::map<pldm_tid_t, std::shared_ptr<pldm::platform_mc::Terminus>> termini;
+
+    std::vector<uint8_t> pdr1{
+        0x1,
+        0x0,
+        0x0,
+        0x0,                     // record handle
+        0x1,                     // PDRHeaderVersion
+        PLDM_NUMERIC_SENSOR_PDR, // PDRType
+        0x0,
+        0x0,                     // recordChangeNumber
+        PLDM_PDR_NUMERIC_SENSOR_PDR_FIXED_LENGTH +
+            PLDM_PDR_NUMERIC_SENSOR_PDR_VARIED_SENSOR_DATA_SIZE_MIN_LENGTH +
+            PLDM_PDR_NUMERIC_SENSOR_PDR_VARIED_RANGE_FIELD_MIN_LENGTH,
+        0,                             // dataLength
+        0,
+        0,                             // PLDMTerminusHandle
+        0x1,
+        0x0,                           // sensorID=1
+        PLDM_ENTITY_POWER_SUPPLY,
+        0,                             // entityType=Power Supply(120)
+        1,
+        0,                             // entityInstanceNumber
+        0x1,
+        0x0,                           // containerID=1
+        PLDM_NO_INIT,                  // sensorInit
+        false,                         // sensorAuxiliaryNamesPDR
+        PLDM_SENSOR_UNIT_DEGRESS_C,    // baseUint(2)=degrees C
+        1,                             // unitModifier = 1
+        0,                             // rateUnit
+        0,                             // baseOEMUnitHandle
+        0,                             // auxUnit
+        0,                             // auxUnitModifier
+        0,                             // auxRateUnit
+        0,                             // rel
+        0,                             // auxOEMUnitHandle
+        true,                          // isLinear
+        PLDM_RANGE_FIELD_FORMAT_SINT8, // sensorDataSize
+        0,
+        0,
+        0xc0,
+        0x3f, // resolution=1.5
+        0,
+        0,
+        0x80,
+        0x3f, // offset=1.0
+        0,
+        0,    // accuracy
+        0,    // plusTolerance
+        0,    // minusTolerance
+        2,    // hysteresis
+        0,    // supportedThresholds
+        0,    // thresholdAndHysteresisVolatility
+        0,
+        0,
+        0x80,
+        0x3f, // stateTransistionInterval=1.0
+        0,
+        0,
+        0x80,
+        0x3f,                          // updateInverval=1.0
+        255,                           // maxReadable
+        0,                             // minReadable
+        PLDM_RANGE_FIELD_FORMAT_UINT8, // rangeFieldFormat
+        0,                             // rangeFieldsupport
+        0,                             // nominalValue
+        0,                             // normalMax
+        0,                             // normalMin
+        0,                             // warningHigh
+        0,                             // warningLow
+        0,                             // criticalHigh
+        0,                             // criticalLow
+        0,                             // fatalHigh
+        0                              // fatalLow
+    };
+
+    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"
+    };
+};
+
+TEST_F(SensorManagerTest, sensorPollingTest)
+{
+    uint64_t seconds = 10;
+    uint64_t expectedTimes = (seconds * 1000) / SENSOR_POLLING_TIME;
+
+    pldm_tid_t tid = 1;
+    termini[tid] = std::make_shared<pldm::platform_mc::Terminus>(tid, 0);
+    termini[tid]->pdrs.push_back(pdr1);
+    termini[tid]->pdrs.push_back(pdr2);
+    termini[tid]->parseTerminusPDRs();
+
+    EXPECT_CALL(sensorManager, doSensorPolling(tid))
+        .Times(Between(expectedTimes - 3, expectedTimes + 3))
+        .WillRepeatedly(Return());
+
+    sensorManager.startPolling(tid);
+
+    runEventLoopForSeconds(seconds);
+
+    sensorManager.stopPolling(tid);
+}
diff --git a/pldmd/pldmd.cpp b/pldmd/pldmd.cpp
index 13d28f2..8637a15 100644
--- a/pldmd/pldmd.cpp
+++ b/pldmd/pldmd.cpp
@@ -196,6 +196,8 @@
     auto& bus = pldm::utils::DBusHandler::getBus();
     sdbusplus::server::manager_t objManager(bus,
                                             "/xyz/openbmc_project/software");
+    sdbusplus::server::manager_t sensorObjManager(
+        bus, "/xyz/openbmc_project/sensors");
 
     InstanceIdDb instanceIdDb;
     dbus_api::Requester dbusImplReq(bus, "/xyz/openbmc_project/pldm",