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/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