rtu: add read status register and event support

Add support to read the status registers and generate the appropriate
events.

Tested:
Unit Test -
```
> meson test -t 10 -C builddir/ --print-errorlogs --wrapper="valgrind --error-exitcode=1" test_events
ninja: Entering directory `/host/repos/Modbus/phosphor-modbus/builddir'
ninja: no work to do.
1/1 test_events        OK               9.69s

Ok:                1
Fail:              0
```

Tested on Qemu -
```
Apr 03 15:41:52 ventura phosphor-modbus-rtu[1654]: OPENBMC_MESSAGE_ID={"xyz.openbmc_project.Sensor.SensorFailure":{"SENSOR_NAME":"/xyz/openbmc_project/sensors/RPU_Coolant_Outlet_Thermometer_Status","_SOURCE":{"COLUMN":73,"FILE":"../git/common/ev
ents.cpp","FUNCTION":"sdbusplus::async::task<> phosphor::modbus::events::Events::generateSensorFailureEvent(sdbusplus::message::object_path, bool)","LINE":95,"PID":1654}}}
...
Apr 03 15:41:52 ventura phosphor-modbus-rtu[1654]: OPENBMC_MESSAGE_ID={"xyz.openbmc_project.Sensor.Threshold.ReadingCritical":{"READING_VALUE":1670.6000000000001,"SENSOR_NAME":"/xyz/openbmc_project/sensors/RPU_Coolant_Outlet_Temp_C","UNITS":"xyz
.openbmc_project.Sensor.Value.Unit.DegreesC","_SOURCE":{"COLUMN":67,"FILE":"../git/common/events.cpp","FUNCTION":"sdbusplus::async::task<> phosphor::modbus::events::Events::generateSensorReadingEvent(sdbusplus::message::object_path, phosphor::modbus::events::EventLevel, double, sdbusplus::common::xyz::openbmc_project::sensor::Value::Unit, bool)","LINE":48,"PID":1654}}}
```

Change-Id: Icd78f22cf07798d06916cc077ec3f8bfac9ee8d3
Signed-off-by: Jagpal Singh Gill <paligill@gmail.com>
diff --git a/common/events.cpp b/common/events.cpp
new file mode 100644
index 0000000..9c61365
--- /dev/null
+++ b/common/events.cpp
@@ -0,0 +1,342 @@
+#include "events.hpp"
+
+#include <phosphor-logging/commit.hpp>
+#include <phosphor-logging/lg2.hpp>
+#include <sdbusplus/async.hpp>
+#include <xyz/openbmc_project/Sensor/Threshold/event.hpp>
+#include <xyz/openbmc_project/Sensor/event.hpp>
+#include <xyz/openbmc_project/State/Fan/event.hpp>
+#include <xyz/openbmc_project/State/Filter/event.hpp>
+#include <xyz/openbmc_project/State/Leak/Detector/event.hpp>
+#include <xyz/openbmc_project/State/Power/event.hpp>
+#include <xyz/openbmc_project/State/Pump/event.hpp>
+#include <xyz/openbmc_project/State/SMC/event.hpp>
+
+namespace phosphor::modbus::events
+{
+
+PHOSPHOR_LOG2_USING;
+
+const std::unordered_map<EventLevel, std::string> eventLevelToName = {
+    {EventLevel::critical, "Critical"},
+    {EventLevel::warning, "Warning"},
+};
+
+auto Events::generateSensorReadingEvent(
+    sdbusplus::message::object_path objectPath, EventLevel level, double value,
+    SensorValueIntf::Unit unit, bool asserted) -> sdbusplus::async::task<>
+{
+    namespace error_intf =
+        sdbusplus::error::xyz::openbmc_project::sensor::Threshold;
+    namespace event_intf =
+        sdbusplus::event::xyz::openbmc_project::sensor::Threshold;
+
+    auto eventName =
+        objectPath.str + ".threshold." + eventLevelToName.at(level);
+    auto pendingEvent = pendingEvents.find(eventName);
+
+    if (asserted)
+    {
+        if (pendingEvent == pendingEvents.end())
+        {
+            sdbusplus::message::object_path eventPath{};
+            if (level == EventLevel::critical)
+            {
+                eventPath = co_await lg2::commit(
+                    ctx, error_intf::ReadingCritical("SENSOR_NAME", objectPath,
+                                                     "READING_VALUE", value,
+                                                     "UNITS", unit));
+            }
+            else
+            {
+                eventPath = co_await lg2::commit(
+                    ctx, error_intf::ReadingWarning("SENSOR_NAME", objectPath,
+                                                    "READING_VALUE", value,
+                                                    "UNITS", unit));
+            }
+
+            pendingEvents.emplace(eventName, eventPath);
+        }
+    }
+    else
+    {
+        if (pendingEvent != pendingEvents.end())
+        {
+            co_await lg2::resolve(ctx, pendingEvent->second);
+
+            co_await lg2::commit(ctx,
+                                 event_intf::SensorReadingNormalRange(
+                                     "SENSOR_NAME", objectPath, "READING_VALUE",
+                                     value, "UNITS", unit));
+
+            pendingEvents.erase(eventName);
+        }
+    }
+
+    debug("Sensor reading event for {EVENT_NAME} is {STATUS}", "EVENT_NAME",
+          eventName, "STATUS", (asserted ? "asserted" : "deasserted"));
+}
+
+auto Events::generateSensorFailureEvent(
+    sdbusplus::message::object_path objectPath, bool asserted)
+    -> sdbusplus::async::task<>
+{
+    namespace error_intf = sdbusplus::error::xyz::openbmc_project::Sensor;
+    namespace event_intf = sdbusplus::event::xyz::openbmc_project::Sensor;
+
+    auto eventName = objectPath.str + ".SensorFailure";
+    auto pendingEvent = pendingEvents.find(eventName);
+
+    if (asserted)
+    {
+        if (pendingEvent == pendingEvents.end())
+        {
+            auto eventPath = co_await lg2::commit(
+                ctx, error_intf::SensorFailure("SENSOR_NAME", objectPath));
+            pendingEvents.emplace(eventName, eventPath);
+        }
+    }
+    else
+    {
+        if (pendingEvent != pendingEvents.end())
+        {
+            co_await lg2::resolve(ctx, pendingEvent->second);
+
+            co_await lg2::commit(
+                ctx, event_intf::SensorRestored("SENSOR_NAME", objectPath));
+            pendingEvents.erase(eventName);
+        }
+    }
+
+    debug("Sensor failure event for {EVENT_NAME} is {STATUS}", "EVENT_NAME",
+          eventName, "STATUS", (asserted ? "asserted" : "deasserted"));
+}
+
+auto Events::generateControllerFailureEvent(
+    sdbusplus::message::object_path objectPath, std::string additionalInfo,
+    bool asserted) -> sdbusplus::async::task<>
+{
+    namespace error_intf = sdbusplus::error::xyz::openbmc_project::state::SMC;
+    namespace event_intf = sdbusplus::event::xyz::openbmc_project::state::SMC;
+
+    auto eventName = objectPath.str + ".ControllerFailure." + additionalInfo;
+    auto pendingEvent = pendingEvents.find(eventName);
+
+    if (asserted)
+    {
+        if (pendingEvent == pendingEvents.end())
+        {
+            auto eventPath = co_await lg2::commit(
+                ctx, error_intf::SMCFailed("IDENTIFIER", objectPath,
+                                           "FAILURE_TYPE", additionalInfo));
+            pendingEvents.emplace(eventName, eventPath);
+        }
+    }
+    else
+    {
+        if (pendingEvent != pendingEvents.end())
+        {
+            co_await lg2::resolve(ctx, pendingEvent->second);
+
+            co_await lg2::commit(
+                ctx, event_intf::SMCRestored("IDENTIFIER", objectPath));
+
+            pendingEvents.erase(eventName);
+        }
+    }
+
+    debug("Controller failure event for {EVENT_NAME} is {STATUS}", "EVENT_NAME",
+          eventName, "STATUS", (asserted ? "asserted" : "deasserted"));
+}
+
+auto Events::generatePowerFaultEvent(sdbusplus::message::object_path objectPath,
+                                     std::string additionalInfo, bool asserted)
+    -> sdbusplus::async::task<>
+{
+    namespace error_intf = sdbusplus::error::xyz::openbmc_project::state::Power;
+    namespace event_intf = sdbusplus::event::xyz::openbmc_project::state::Power;
+
+    auto eventName = objectPath.str + ".PowerFailure." + additionalInfo;
+    auto pendingEvent = pendingEvents.find(eventName);
+
+    if (asserted)
+    {
+        if (pendingEvent == pendingEvents.end())
+        {
+            auto eventPath = co_await lg2::commit(
+                ctx,
+                error_intf::PowerRailFault("POWER_RAIL", objectPath,
+                                           "FAILURE_DATA", additionalInfo));
+            pendingEvents.emplace(eventName, eventPath);
+        }
+    }
+    else
+    {
+        if (pendingEvent != pendingEvents.end())
+        {
+            co_await lg2::resolve(ctx, pendingEvent->second);
+
+            co_await lg2::commit(ctx, event_intf::PowerRailFaultRecovered(
+                                          "POWER_RAIL", objectPath));
+
+            pendingEvents.erase(eventName);
+        }
+    }
+
+    debug("Power fault event for {EVENT_NAME} is {STATUS}", "EVENT_NAME",
+          eventName, "STATUS", (asserted ? "asserted" : "deasserted"));
+}
+
+auto Events::generateFilterFailureEvent(
+    sdbusplus::message::object_path objectPath, bool asserted)
+    -> sdbusplus::async::task<>
+{
+    namespace error_intf =
+        sdbusplus::error::xyz::openbmc_project::state::Filter;
+    namespace event_intf =
+        sdbusplus::event::xyz::openbmc_project::state::Filter;
+
+    auto eventName = objectPath.str + ".FilterFailure";
+    auto pendingEvent = pendingEvents.find(eventName);
+
+    if (asserted)
+    {
+        if (pendingEvent == pendingEvents.end())
+        {
+            auto eventPath = co_await lg2::commit(
+                ctx,
+                error_intf::FilterRequiresService("FILTER_NAME", objectPath));
+            pendingEvents.emplace(eventName, eventPath);
+        }
+    }
+    else
+    {
+        if (pendingEvent != pendingEvents.end())
+        {
+            co_await lg2::resolve(ctx, pendingEvent->second);
+
+            co_await lg2::commit(
+                ctx, event_intf::FilterRestored("FILTER_NAME", objectPath));
+
+            pendingEvents.erase(eventName);
+        }
+    }
+
+    debug("Filter failure event for {EVENT_NAME} is {STATUS}", "EVENT_NAME",
+          eventName, "STATUS", (asserted ? "asserted" : "deasserted"));
+}
+
+auto Events::generatePumpFailureEvent(
+    sdbusplus::message::object_path objectPath, bool asserted)
+    -> sdbusplus::async::task<>
+{
+    namespace error_intf = sdbusplus::error::xyz::openbmc_project::state::Pump;
+    namespace event_intf = sdbusplus::event::xyz::openbmc_project::state::Pump;
+
+    auto eventName = objectPath.str + ".PumpFailure";
+    auto pendingEvent = pendingEvents.find(eventName);
+
+    if (asserted)
+    {
+        if (pendingEvent == pendingEvents.end())
+        {
+            auto eventPath = co_await lg2::commit(
+                ctx, error_intf::PumpFailed("PUMP_NAME", objectPath));
+            pendingEvents.emplace(eventName, eventPath);
+        }
+    }
+    else
+    {
+        if (pendingEvent != pendingEvents.end())
+        {
+            co_await lg2::resolve(ctx, pendingEvent->second);
+
+            co_await lg2::commit(
+                ctx, event_intf::PumpRestored("PUMP_NAME", objectPath));
+
+            pendingEvents.erase(eventName);
+        }
+    }
+
+    debug("Pump failure event for {EVENT_NAME} is {STATUS}", "EVENT_NAME",
+          eventName, "STATUS", (asserted ? "asserted" : "deasserted"));
+}
+
+auto Events::generateFanFailureEvent(sdbusplus::message::object_path objectPath,
+                                     bool asserted) -> sdbusplus::async::task<>
+{
+    namespace error_intf = sdbusplus::error::xyz::openbmc_project::state::Fan;
+    namespace event_intf = sdbusplus::event::xyz::openbmc_project::state::Fan;
+
+    auto eventName = objectPath.str + ".FanFailure";
+    auto pendingEvent = pendingEvents.find(eventName);
+
+    if (asserted)
+    {
+        if (pendingEvent == pendingEvents.end())
+        {
+            auto eventPath = co_await lg2::commit(
+                ctx, error_intf::FanFailed("FAN_NAME", objectPath));
+            pendingEvents.emplace(eventName, eventPath);
+        }
+    }
+    else
+    {
+        if (pendingEvent != pendingEvents.end())
+        {
+            co_await lg2::resolve(ctx, pendingEvent->second);
+
+            co_await lg2::commit(
+                ctx, event_intf::FanRestored("FAN_NAME", objectPath));
+
+            pendingEvents.erase(eventName);
+        }
+    }
+
+    debug("Fan failure event for {EVENT_NAME} is {STATUS}", "EVENT_NAME",
+          eventName, "STATUS", (asserted ? "asserted" : "deasserted"));
+}
+
+auto Events::generateLeakDetectedEvent(
+    sdbusplus::message::object_path objectPath, EventLevel level, bool asserted)
+    -> sdbusplus::async::task<>
+{
+    auto eventName = objectPath.str + ".Leak." + eventLevelToName.at(level);
+
+    if (!asserted)
+    {
+        auto pendingEvent = pendingEvents.find(eventName);
+        if (pendingEvent != pendingEvents.end())
+        {
+            co_await lg2::resolve(ctx, pendingEvent->second);
+
+            using DetectorNormal = sdbusplus::event::xyz::openbmc_project::
+                state::leak::Detector::LeakDetectedNormal;
+            co_await lg2::commit(ctx,
+                                 DetectorNormal("DETECTOR_NAME", objectPath));
+
+            pendingEvents.erase(eventName);
+        }
+        co_return;
+    }
+
+    namespace error_intf =
+        sdbusplus::error::xyz::openbmc_project::state::leak::Detector;
+    sdbusplus::message::object_path eventPath{};
+
+    if (level == EventLevel::critical)
+    {
+        eventPath = co_await lg2::commit(
+            ctx, error_intf::LeakDetectedCritical("DETECTOR_NAME", objectPath));
+        error("Critical leak detected for {PATH}", "PATH", objectPath);
+    }
+    else
+    {
+        eventPath = co_await lg2::commit(
+            ctx, error_intf::LeakDetectedWarning("DETECTOR_NAME", objectPath));
+        warning("Warning leak detected for {PATH}", "PATH", objectPath);
+    }
+    pendingEvents[eventName] = eventPath;
+}
+
+} // namespace phosphor::modbus::events
diff --git a/common/events.hpp b/common/events.hpp
new file mode 100644
index 0000000..8a13f82
--- /dev/null
+++ b/common/events.hpp
@@ -0,0 +1,64 @@
+#pragma once
+
+#include <sdbusplus/async.hpp>
+#include <xyz/openbmc_project/Sensor/Value/client.hpp>
+
+namespace phosphor::modbus::events
+{
+
+using SensorValueIntf =
+    sdbusplus::client::xyz::openbmc_project::sensor::Value<>;
+
+enum class EventLevel
+{
+    critical,
+    warning
+};
+
+class Events
+{
+  public:
+    Events() = delete;
+
+    explicit Events(sdbusplus::async::context& ctx) : ctx(ctx) {}
+
+    auto generateSensorReadingEvent(sdbusplus::message::object_path objectPath,
+                                    EventLevel level, double value,
+                                    SensorValueIntf::Unit unit, bool asserted)
+        -> sdbusplus::async::task<>;
+
+    auto generateSensorFailureEvent(sdbusplus::message::object_path objectPath,
+                                    bool asserted) -> sdbusplus::async::task<>;
+
+    auto generateControllerFailureEvent(
+        sdbusplus::message::object_path objectPath, std::string additionalInfo,
+        bool asserted) -> sdbusplus::async::task<>;
+
+    auto generatePowerFaultEvent(sdbusplus::message::object_path objectPath,
+                                 std::string additionalInfo, bool asserted)
+        -> sdbusplus::async::task<>;
+
+    auto generateFilterFailureEvent(sdbusplus::message::object_path objectPath,
+                                    bool asserted) -> sdbusplus::async::task<>;
+
+    auto generatePumpFailureEvent(sdbusplus::message::object_path objectPath,
+                                  bool asserted) -> sdbusplus::async::task<>;
+
+    auto generateFanFailureEvent(sdbusplus::message::object_path objectPath,
+                                 bool asserted) -> sdbusplus::async::task<>;
+
+    auto generateLeakDetectedEvent(sdbusplus::message::object_path objectPath,
+                                   EventLevel level, bool asserted)
+        -> sdbusplus::async::task<>;
+
+    // Button Pressed
+
+  private:
+    /** @brief Map type for event name to log event object path */
+    using event_map_t = std::map<std::string, sdbusplus::message::object_path>;
+
+    sdbusplus::async::context& ctx;
+    event_map_t pendingEvents;
+};
+
+} // namespace phosphor::modbus::events
diff --git a/common/meson.build b/common/meson.build
index cd17e78..8e40d37 100644
--- a/common/meson.build
+++ b/common/meson.build
@@ -1,6 +1,7 @@
 modbus_common_lib = static_library(
     'modbus_common_lib',
     'entity_manager_interface.cpp',
+    'events.cpp',
     include_directories: ['.'],
     dependencies: [default_deps],
 )
diff --git a/rtu/device/base_device.cpp b/rtu/device/base_device.cpp
index 73d1ca6..c075d55 100644
--- a/rtu/device/base_device.cpp
+++ b/rtu/device/base_device.cpp
@@ -1,6 +1,7 @@
 #include "base_device.hpp"
 
 #include <phosphor-logging/lg2.hpp>
+#include <xyz/openbmc_project/State/Leak/Detector/aserver.hpp>
 
 #include <numeric>
 
@@ -10,8 +11,9 @@
 PHOSPHOR_LOG2_USING;
 
 BaseDevice::BaseDevice(sdbusplus::async::context& ctx,
-                       const config::Config& config, PortIntf& serialPort) :
-    ctx(ctx), config(config), serialPort(serialPort)
+                       const config::Config& config, PortIntf& serialPort,
+                       EventIntf::Events& events) :
+    ctx(ctx), config(config), serialPort(serialPort), events(events)
 {
     createSensors();
 
@@ -146,6 +148,8 @@
             sensor->second->value(regVal);
         }
 
+        co_await readStatusRegisters();
+
         constexpr auto pollInterval = 3;
         co_await sdbusplus::async::sleep_for(
             ctx, std::chrono::seconds(pollInterval));
@@ -156,4 +160,148 @@
     co_return;
 }
 
+static auto getObjectPath(const config::Config& config, config::StatusType type,
+                          const std::string& name)
+    -> sdbusplus::message::object_path
+{
+    switch (type)
+    {
+        case config::StatusType::sensorReadingCritical:
+        case config::StatusType::sensorReadingWarning:
+        case config::StatusType::sensorFailure:
+            return sdbusplus::message::object_path(
+                std::string(SensorValueIntf::namespace_path::value) + "/" +
+                name);
+        case config::StatusType::controllerFailure:
+            return config.inventoryPath;
+        case config::StatusType::pumpFailure:
+            return sdbusplus::message::object_path(
+                "/xyz/openbmc_project/state/pump/" + name);
+        case config::StatusType::filterFailure:
+            return sdbusplus::message::object_path(
+                "/xyz/openbmc_project/state/filter/" + name);
+        case config::StatusType::powerFault:
+            return sdbusplus::message::object_path(
+                "/xyz/openbmc_project/state/power_rail/" + name);
+        case config::StatusType::fanFailure:
+            return sdbusplus::message::object_path(
+                "/xyz/openbmc_project/state/fan/" + name);
+        case config::StatusType::leakDetectedCritical:
+        case config::StatusType::leakDetectedWarning:
+            using DetectorIntf =
+                sdbusplus::aserver::xyz::openbmc_project::state::leak::Detector<
+                    Device>;
+            return sdbusplus::message::object_path(
+                std::string(DetectorIntf::namespace_path::value) + "/" +
+                DetectorIntf::namespace_path::detector + "/" + name);
+        case config::StatusType::unknown:
+            error("Unknown status type for {NAME}", "NAME", name);
+    }
+
+    return sdbusplus::message::object_path();
+}
+
+auto BaseDevice::readStatusRegisters() -> sdbusplus::async::task<void>
+{
+    for (const auto& [address, statusBits] : config.statusRegisters)
+    {
+        static constexpr auto maxRegisterSize = 1;
+        auto registers = std::vector<uint16_t>(maxRegisterSize);
+
+        auto ret = co_await serialPort.readHoldingRegisters(
+            config.address, address, config.baudRate, config.parity, registers);
+        if (!ret)
+        {
+            error("Failed to read holding registers for {DEVICE_ADDRESS}",
+                  "DEVICE_ADDRESS", config.address);
+            continue;
+        }
+
+        for (const auto& statusBit : statusBits)
+        {
+            static constexpr auto maxBitPoistion = 15;
+            if (statusBit.bitPosition > maxBitPoistion)
+            {
+                error("Invalid status bit position {POSITION} for {NAME}",
+                      "POSITION", statusBit.bitPosition, "NAME",
+                      statusBit.name);
+                continue;
+            }
+            auto statusBitValue =
+                ((registers[0] & (1 << statusBit.bitPosition)) != 0);
+            auto statusAsserted = (statusBitValue == statusBit.value);
+            auto objectPath =
+                getObjectPath(config, statusBit.type, statusBit.name);
+            double sensorValue = std::numeric_limits<double>::quiet_NaN();
+            SensorValueIntf::Unit sensorUnit = SensorValueIntf::Unit::Percent;
+            auto sensorIter = sensors.find(statusBit.name);
+            if (sensorIter != sensors.end())
+            {
+                sensorValue = sensorIter->second->value();
+                sensorUnit = sensorIter->second->unit();
+            }
+
+            co_await generateEvent(statusBit, objectPath, sensorValue,
+                                   sensorUnit, statusAsserted);
+        }
+    }
+
+    co_return;
+}
+
+auto BaseDevice::generateEvent(
+    const config::StatusBit& statusBit,
+    const sdbusplus::message::object_path& objectPath, double sensorValue,
+    SensorValueIntf::Unit sensorUnit, bool statusAsserted)
+    -> sdbusplus::async::task<void>
+{
+    switch (statusBit.type)
+    {
+        case config::StatusType::sensorReadingCritical:
+            co_await events.generateSensorReadingEvent(
+                objectPath, EventIntf::EventLevel::critical, sensorValue,
+                sensorUnit, statusAsserted);
+            break;
+        case config::StatusType::sensorReadingWarning:
+            co_await events.generateSensorReadingEvent(
+                objectPath, EventIntf::EventLevel::warning, sensorValue,
+                sensorUnit, statusAsserted);
+            break;
+        case config::StatusType::sensorFailure:
+            co_await events.generateSensorFailureEvent(objectPath,
+                                                       statusAsserted);
+            break;
+        case config::StatusType::controllerFailure:
+            co_await events.generateControllerFailureEvent(
+                objectPath, statusBit.name, statusAsserted);
+            break;
+        case config::StatusType::powerFault:
+            co_await events.generatePowerFaultEvent(objectPath, statusBit.name,
+                                                    statusAsserted);
+            break;
+        case config::StatusType::filterFailure:
+            co_await events.generateFilterFailureEvent(objectPath,
+                                                       statusAsserted);
+            break;
+        case config::StatusType::pumpFailure:
+            co_await events.generatePumpFailureEvent(objectPath,
+                                                     statusAsserted);
+            break;
+        case config::StatusType::fanFailure:
+            co_await events.generateFanFailureEvent(objectPath, statusAsserted);
+            break;
+        case config::StatusType::leakDetectedCritical:
+            co_await events.generateLeakDetectedEvent(
+                objectPath, EventIntf::EventLevel::critical, statusAsserted);
+            break;
+        case config::StatusType::leakDetectedWarning:
+            co_await events.generateLeakDetectedEvent(
+                objectPath, EventIntf::EventLevel::warning, statusAsserted);
+            break;
+        case config::StatusType::unknown:
+            error("Unknown status type for {NAME}", "NAME", statusBit.name);
+            break;
+    }
+}
+
 } // namespace phosphor::modbus::rtu::device
diff --git a/rtu/device/base_device.hpp b/rtu/device/base_device.hpp
index a64d708..3a2f0a8 100644
--- a/rtu/device/base_device.hpp
+++ b/rtu/device/base_device.hpp
@@ -1,6 +1,7 @@
 #pragma once
 
 #include "base_config.hpp"
+#include "common/events.hpp"
 #include "modbus/modbus.hpp"
 #include "port/base_port.hpp"
 
@@ -15,6 +16,7 @@
 using SensorValueIntf =
     sdbusplus::aserver::xyz::openbmc_project::sensor::Value<Device>;
 using PortIntf = phosphor::modbus::rtu::port::BasePort;
+namespace EventIntf = phosphor::modbus::events;
 
 class BaseDevice
 {
@@ -22,18 +24,27 @@
     BaseDevice() = delete;
 
     explicit BaseDevice(sdbusplus::async::context& ctx,
-                        const config::Config& config, PortIntf& serialPort);
+                        const config::Config& config, PortIntf& serialPort,
+                        EventIntf::Events& events);
 
     auto readSensorRegisters() -> sdbusplus::async::task<void>;
 
   private:
     auto createSensors() -> void;
 
+    auto readStatusRegisters() -> sdbusplus::async::task<void>;
+
+    auto generateEvent(const config::StatusBit& statusBit,
+                       const sdbusplus::message::object_path& objectPath,
+                       double sensorValue, SensorValueIntf::Unit sensorUnit,
+                       bool statusAsserted) -> sdbusplus::async::task<void>;
+
     using sensors_map_t =
         std::unordered_map<std::string, std::unique_ptr<SensorValueIntf>>;
     sdbusplus::async::context& ctx;
     const config::Config config;
     PortIntf& serialPort;
+    EventIntf::Events& events;
     sensors_map_t sensors;
 };
 
diff --git a/rtu/device/device_factory.cpp b/rtu/device/device_factory.cpp
index d707952..3efdbac 100644
--- a/rtu/device/device_factory.cpp
+++ b/rtu/device/device_factory.cpp
@@ -38,12 +38,14 @@
 
 auto DeviceFactory::create(sdbusplus::async::context& ctx,
                            const config::DeviceFactoryConfig& config,
-                           PortIntf& serialPort) -> std::unique_ptr<BaseDevice>
+                           PortIntf& serialPort, EventIntf::Events& events)
+    -> std::unique_ptr<BaseDevice>
 {
     switch (config.deviceType)
     {
         case config::DeviceType::reservoirPumpUnit:
-            return std::make_unique<ReservoirPumpUnit>(ctx, config, serialPort);
+            return std::make_unique<ReservoirPumpUnit>(ctx, config, serialPort,
+                                                       events);
         default:
             break;
     }
diff --git a/rtu/device/device_factory.hpp b/rtu/device/device_factory.hpp
index 5dca705..ca37b7b 100644
--- a/rtu/device/device_factory.hpp
+++ b/rtu/device/device_factory.hpp
@@ -44,7 +44,8 @@
 
     static auto create(sdbusplus::async::context& ctx,
                        const config::DeviceFactoryConfig& config,
-                       PortIntf& serialPort) -> std::unique_ptr<BaseDevice>;
+                       PortIntf& serialPort, EventIntf::Events& events)
+        -> std::unique_ptr<BaseDevice>;
 };
 
 } // namespace phosphor::modbus::rtu::device
diff --git a/rtu/device/reservoir_pump_unit.cpp b/rtu/device/reservoir_pump_unit.cpp
index 58debee..e4f8ae8 100644
--- a/rtu/device/reservoir_pump_unit.cpp
+++ b/rtu/device/reservoir_pump_unit.cpp
@@ -16,10 +16,10 @@
     validDevices = {{ModbusRDF040DSS5193E0ReservoirPumpUnitInterface,
                      config::DeviceModel::RDF040DSS5193E0}};
 
-ReservoirPumpUnit::ReservoirPumpUnit(sdbusplus::async::context& ctx,
-                                     const config::Config& config,
-                                     PortIntf& serialPort) :
-    BaseDevice(ctx, config, serialPort)
+ReservoirPumpUnit::ReservoirPumpUnit(
+    sdbusplus::async::context& ctx, const config::Config& config,
+    PortIntf& serialPort, EventIntf::Events& events) :
+    BaseDevice(ctx, config, serialPort, events)
 {
     info("Reservoir pump unit {NAME} created successfully", "NAME",
          config.name);
diff --git a/rtu/device/reservoir_pump_unit.hpp b/rtu/device/reservoir_pump_unit.hpp
index 19240f0..b3a51a5 100644
--- a/rtu/device/reservoir_pump_unit.hpp
+++ b/rtu/device/reservoir_pump_unit.hpp
@@ -19,7 +19,7 @@
   public:
     explicit ReservoirPumpUnit(sdbusplus::async::context& ctx,
                                const config::Config& config,
-                               PortIntf& serialPort);
+                               PortIntf& serialPort, EventIntf::Events& events);
 
     static auto getInterfaces() -> std::unordered_set<std::string>;
 
diff --git a/rtu/device_manager.cpp b/rtu/device_manager.cpp
index 804f6eb..27c9071 100644
--- a/rtu/device_manager.cpp
+++ b/rtu/device_manager.cpp
@@ -38,7 +38,8 @@
     ctx(ctx),
     entityManager(ctx, getInterfaces(),
                   std::bind_front(&DeviceManager::processConfigAdded, this),
-                  std::bind_front(&DeviceManager::processConfigRemoved, this))
+                  std::bind_front(&DeviceManager::processConfigRemoved, this)),
+    events(ctx)
 {
     ctx.spawn(entityManager.handleInventoryGet());
     info("DeviceManager created successfully");
@@ -151,8 +152,8 @@
             co_return;
         }
 
-        auto device =
-            DeviceFactoryIntf::create(ctx, config, *(serialPort->second));
+        auto device = DeviceFactoryIntf::create(ctx, config,
+                                                *(serialPort->second), events);
         ctx.spawn(device->readSensorRegisters());
         devices[config.name] = std::move(device);
     }
diff --git a/rtu/device_manager.hpp b/rtu/device_manager.hpp
index 75fe75b..08f15d6 100644
--- a/rtu/device_manager.hpp
+++ b/rtu/device_manager.hpp
@@ -1,6 +1,7 @@
 #pragma once
 
 #include "common/entity_manager_interface.hpp"
+#include "common/events.hpp"
 #include "device/base_device.hpp"
 #include "inventory/modbus_inventory.hpp"
 #include "port/base_port.hpp"
@@ -14,6 +15,7 @@
 namespace PortIntf = phosphor::modbus::rtu::port;
 namespace ModbusIntf = phosphor::modbus::rtu;
 namespace DeviceIntf = phosphor::modbus::rtu::device;
+namespace EventIntf = phosphor::modbus::events;
 
 class DeviceManager
 {
@@ -59,6 +61,7 @@
 
     sdbusplus::async::context& ctx;
     entity_manager::EntityManagerInterface entityManager;
+    EventIntf::Events events;
     inventory_device_map_t inventoryDevices;
     port_map_t ports;
     device_map_t devices; // Modbus devices
diff --git a/tests/meson.build b/tests/meson.build
index 8b1df07..c457029 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -70,6 +70,18 @@
         'test_sensors.cpp',
         'modbus_server_tester.cpp',
         device_src,
+        link_with: [modbus_common_lib],
+        dependencies: [gtest_dep, gmock_dep, default_deps, modbus_rtu_dep],
+        include_directories: ['.', common_include],
+    ),
+)
+
+test(
+    'test_events',
+    executable(
+        'test_events',
+        'test_events.cpp',
+        link_with: [modbus_common_lib],
         dependencies: [gtest_dep, gmock_dep, default_deps, modbus_rtu_dep],
         include_directories: ['.', common_include],
     ),
diff --git a/tests/test_events.cpp b/tests/test_events.cpp
new file mode 100644
index 0000000..1cffd2f
--- /dev/null
+++ b/tests/test_events.cpp
@@ -0,0 +1,299 @@
+#include "common/events.hpp"
+
+#include <xyz/openbmc_project/Logging/Create/aserver.hpp>
+#include <xyz/openbmc_project/Logging/Entry/aserver.hpp>
+#include <xyz/openbmc_project/Sensor/Threshold/event.hpp>
+#include <xyz/openbmc_project/Sensor/event.hpp>
+#include <xyz/openbmc_project/State/Leak/Detector/event.hpp>
+#include <xyz/openbmc_project/State/Power/event.hpp>
+#include <xyz/openbmc_project/State/SMC/event.hpp>
+
+#include <gtest/gtest.h>
+
+class TestEventServer;
+class TestEventEntry;
+
+using namespace std::literals;
+namespace EventIntf = phosphor::modbus::events;
+using EventServerIntf =
+    sdbusplus::aserver::xyz::openbmc_project::logging::Create<TestEventServer>;
+using EventEntryIntf =
+    sdbusplus::aserver::xyz::openbmc_project::logging::Entry<TestEventEntry>;
+
+namespace SensorThresholdErrorIntf =
+    sdbusplus::error::xyz::openbmc_project::sensor::Threshold;
+namespace SensorThresholdEventIntf =
+    sdbusplus::event::xyz::openbmc_project::sensor::Threshold;
+namespace SensorErrorIntf = sdbusplus::error::xyz::openbmc_project::Sensor;
+namespace SensorEventIntf = sdbusplus::event::xyz::openbmc_project::Sensor;
+namespace ControllerErrorIntf =
+    sdbusplus::error::xyz::openbmc_project::state::SMC;
+namespace ControllerEventIntf =
+    sdbusplus::event::xyz::openbmc_project::state::SMC;
+namespace PowerErrorIntf = sdbusplus::error::xyz::openbmc_project::state::Power;
+namespace PowerEventIntf = sdbusplus::event::xyz::openbmc_project::state::Power;
+namespace LeakErrorIntf =
+    sdbusplus::error::xyz::openbmc_project::state::leak::Detector;
+namespace LeakEventIntf =
+    sdbusplus::event::xyz::openbmc_project::state::leak::Detector;
+
+// Test Event Class to mock the EventEntry
+class TestEventEntry : public EventEntryIntf
+{
+  public:
+    TestEventEntry(sdbusplus::async::context& ctx, const char* path) :
+        EventEntryIntf(ctx, path)
+    {}
+
+    auto method_call(get_entry_t)
+        -> sdbusplus::async::task<get_entry_t::return_type>
+    {
+        get_entry_t::return_type fd1 = 0;
+        co_return fd1;
+    }
+};
+
+// Test Event Server Class to mock the EventServer
+class TestEventServer : public EventServerIntf
+{
+  public:
+    TestEventServer(sdbusplus::async::context& ctx, const char* path) :
+        EventServerIntf(ctx, path), ctx(ctx)
+    {}
+
+    auto method_call(create_t, auto message, auto, auto)
+        -> sdbusplus::async::task<create_t::return_type>
+
+    {
+        static int cnt = 1;
+        cnt++;
+
+        // Append the count to the object path to make it unique for each event
+        std::string objectPath =
+            "/xyz/openbmc_project/logging/entry/TestEvent1" +
+            std::to_string(cnt);
+        EXPECT_EQ(message, expectedEvent) << "Event name mismatch";
+
+        eventEntries.emplace_back(
+            std::make_unique<TestEventEntry>(ctx, objectPath.c_str()));
+
+        co_return sdbusplus::message::object_path(objectPath);
+    }
+
+    auto method_call(create_with_ffdc_files_t, auto, auto, auto, auto)
+        -> sdbusplus::async::task<create_with_ffdc_files_t::return_type>
+
+    {
+        co_return;
+    }
+
+    std::string expectedEvent = "";
+
+  private:
+    sdbusplus::async::context& ctx;
+    std::vector<std::unique_ptr<TestEventEntry>> eventEntries;
+};
+
+class EventsTest : public ::testing::Test
+{
+  public:
+    enum class EventTestType
+    {
+        sensorWarningEvent,
+        sensorCriticalEvent,
+        sensorFailureEvent,
+        controllerFailureEvent,
+        powerFailureEvent,
+        leakWarningEvent,
+        leakCriticalEvent,
+    };
+
+    static constexpr auto sensorObjectPath =
+        "/xyz/openbmc_project/sensors/OutletTemperature";
+    static constexpr auto serviceName = "xyz.openbmc_project.Logging";
+    static constexpr auto assert = true;
+    static constexpr auto deassert = false;
+    const char* objectPath = "/xyz/openbmc_project/logging";
+    sdbusplus::async::context ctx;
+    EventIntf::Events events;
+    TestEventServer eventServer;
+    sdbusplus::server::manager_t manager;
+
+    EventsTest() :
+        events(ctx), eventServer(ctx, objectPath), manager(ctx, objectPath)
+    {
+        ctx.request_name(serviceName);
+    }
+
+    ~EventsTest() noexcept override {}
+
+    auto testAssertEvent(EventTestType eventType)
+        -> sdbusplus::async::task<void>
+    {
+        switch (eventType)
+        {
+            case EventTestType::sensorWarningEvent:
+                eventServer.expectedEvent =
+                    SensorThresholdErrorIntf::ReadingWarning::errName;
+                co_await events.generateSensorReadingEvent(
+                    sdbusplus::message::object_path(sensorObjectPath),
+                    EventIntf::EventLevel::warning, 60,
+                    EventIntf::SensorValueIntf::Unit::DegreesC, assert);
+                break;
+            case EventTestType::sensorCriticalEvent:
+                eventServer.expectedEvent =
+                    SensorThresholdErrorIntf::ReadingCritical::errName;
+                co_await events.generateSensorReadingEvent(
+                    sdbusplus::message::object_path(sensorObjectPath),
+                    EventIntf::EventLevel::critical, 80,
+                    EventIntf::SensorValueIntf::Unit::DegreesC, assert);
+                break;
+            case EventTestType::sensorFailureEvent:
+                eventServer.expectedEvent =
+                    SensorErrorIntf::SensorFailure::errName;
+                co_await events.generateSensorFailureEvent(
+                    sdbusplus::message::object_path(sensorObjectPath), assert);
+                break;
+            case EventTestType::controllerFailureEvent:
+                eventServer.expectedEvent =
+                    ControllerErrorIntf::SMCFailed::errName;
+                co_await events.generateControllerFailureEvent(
+                    sdbusplus::message::object_path(sensorObjectPath), "",
+                    assert);
+                break;
+            case EventTestType::powerFailureEvent:
+                eventServer.expectedEvent =
+                    PowerErrorIntf::PowerRailFault::errName;
+                co_await events.generatePowerFaultEvent(
+                    sdbusplus::message::object_path(sensorObjectPath), "",
+                    assert);
+                break;
+            case EventTestType::leakWarningEvent:
+                eventServer.expectedEvent =
+                    LeakErrorIntf::LeakDetectedWarning::errName;
+                co_await events.generateLeakDetectedEvent(
+                    sdbusplus::message::object_path(sensorObjectPath),
+                    EventIntf::EventLevel::warning, assert);
+                break;
+            case EventTestType::leakCriticalEvent:
+                eventServer.expectedEvent =
+                    LeakErrorIntf::LeakDetectedCritical::errName;
+                co_await events.generateLeakDetectedEvent(
+                    sdbusplus::message::object_path(sensorObjectPath),
+                    EventIntf::EventLevel::critical, assert);
+                break;
+        }
+    }
+
+    auto testDeassertEvent(EventTestType eventType)
+        -> sdbusplus::async::task<void>
+    {
+        switch (eventType)
+        {
+            case EventTestType::sensorWarningEvent:
+                eventServer.expectedEvent =
+                    SensorThresholdEventIntf::SensorReadingNormalRange::errName;
+                co_await events.generateSensorReadingEvent(
+                    sdbusplus::message::object_path(sensorObjectPath),
+                    EventIntf::EventLevel::warning, 40,
+                    EventIntf::SensorValueIntf::Unit::DegreesC, deassert);
+                break;
+            case EventTestType::sensorCriticalEvent:
+                eventServer.expectedEvent =
+                    SensorThresholdEventIntf::SensorReadingNormalRange::errName;
+                co_await events.generateSensorReadingEvent(
+                    sdbusplus::message::object_path(sensorObjectPath),
+                    EventIntf::EventLevel::critical, 40,
+                    EventIntf::SensorValueIntf::Unit::DegreesC, deassert);
+                break;
+            case EventTestType::sensorFailureEvent:
+                eventServer.expectedEvent =
+                    SensorEventIntf::SensorRestored::errName;
+                co_await events.generateSensorFailureEvent(
+                    sdbusplus::message::object_path(sensorObjectPath),
+                    deassert);
+                break;
+            case EventTestType::controllerFailureEvent:
+                eventServer.expectedEvent =
+                    ControllerEventIntf::SMCRestored::errName;
+                co_await events.generateControllerFailureEvent(
+                    sdbusplus::message::object_path(sensorObjectPath), "",
+                    deassert);
+                break;
+            case EventTestType::powerFailureEvent:
+                eventServer.expectedEvent =
+                    PowerEventIntf::PowerRailFaultRecovered::errName;
+                co_await events.generatePowerFaultEvent(
+                    sdbusplus::message::object_path(sensorObjectPath), "",
+                    deassert);
+                break;
+            case EventTestType::leakWarningEvent:
+                eventServer.expectedEvent =
+                    LeakEventIntf::LeakDetectedNormal::errName;
+                co_await events.generateLeakDetectedEvent(
+                    sdbusplus::message::object_path(sensorObjectPath),
+                    EventIntf::EventLevel::warning, deassert);
+                break;
+            case EventTestType::leakCriticalEvent:
+                eventServer.expectedEvent =
+                    LeakEventIntf::LeakDetectedNormal::errName;
+                co_await events.generateLeakDetectedEvent(
+                    sdbusplus::message::object_path(sensorObjectPath),
+                    EventIntf::EventLevel::critical, deassert);
+                break;
+        }
+    }
+
+    auto testEvents(EventTestType eventType) -> sdbusplus::async::task<void>
+    {
+        co_await testAssertEvent(eventType);
+
+        co_await sdbusplus::async::sleep_for(ctx, 1s);
+
+        co_await testDeassertEvent(eventType);
+
+        ctx.request_stop();
+    }
+};
+
+TEST_F(EventsTest, TestEventsSensorWarning)
+{
+    ctx.spawn(testEvents(EventTestType::sensorWarningEvent));
+    ctx.run();
+}
+
+TEST_F(EventsTest, TestEventsSensorCritical)
+{
+    ctx.spawn(testEvents(EventTestType::sensorCriticalEvent));
+    ctx.run();
+}
+
+TEST_F(EventsTest, TestEventsSensorFailure)
+{
+    ctx.spawn(testEvents(EventTestType::sensorFailureEvent));
+    ctx.run();
+}
+
+TEST_F(EventsTest, TestEventsControllerFailure)
+{
+    ctx.spawn(testEvents(EventTestType::controllerFailureEvent));
+    ctx.run();
+}
+
+TEST_F(EventsTest, TestEventsPowerFailure)
+{
+    ctx.spawn(testEvents(EventTestType::powerFailureEvent));
+    ctx.run();
+}
+
+TEST_F(EventsTest, TestEventsLeakWarning)
+{
+    ctx.spawn(testEvents(EventTestType::leakWarningEvent));
+    ctx.run();
+}
+
+TEST_F(EventsTest, TestEventsLeakCritical)
+{
+    ctx.spawn(testEvents(EventTestType::leakCriticalEvent));
+    ctx.run();
+}
diff --git a/tests/test_sensors.cpp b/tests/test_sensors.cpp
index a5046b0..a6574cb 100644
--- a/tests/test_sensors.cpp
+++ b/tests/test_sensors.cpp
@@ -1,3 +1,4 @@
+#include "common/events.hpp"
 #include "device/device_factory.hpp"
 #include "modbus_server_tester.hpp"
 #include "port/base_port.hpp"
@@ -22,6 +23,7 @@
 namespace PortConfigIntf = PortIntf::config;
 namespace DeviceIntf = phosphor::modbus::rtu::device;
 namespace DeviceConfigIntf = DeviceIntf::config;
+namespace EventIntf = phosphor::modbus::events;
 
 class MockPort : public PortIntf::BasePort
 {
@@ -134,11 +136,12 @@
             DeviceConfigIntf::DeviceModel::RDF040DSS5193E0,
         };
 
-        auto mockPort =
-            std::make_unique<MockPort>(ctx, portConfig, clientDevicePath);
+        EventIntf::Events events{ctx};
+
+        MockPort mockPort(ctx, portConfig, clientDevicePath);
 
         auto device = DeviceIntf::DeviceFactory::create(
-            ctx, deviceFactoryConfig, *mockPort);
+            ctx, deviceFactoryConfig, mockPort, events);
 
         co_await device->readSensorRegisters();