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