rtu: implement modbus sensor read

Read the modbus device config from the Entity Manager configuration and
create the sensor interfaces for related sensor register config.

Tested:
Added new Unit test named test_sensors -
```
> meson test -t 10 -C builddir/ --print-errorlogs --wrapper="valgrind --error-exitcode=1" test_sensors
ninja: Entering directory `/host/repos/Modbus/phosphor-modbus/builddir'
[2/2] Linking target tests/test_sensors
1/1 test_sensors        OK              13.98s

Ok:                1
Fail:              0
```

Tested on Qemu using Mock Modbus Device -
```
root@ventura:~# busctl tree xyz.openbmc_project.ModbusRTU
└─ /xyz
  └─ /xyz/openbmc_project
    ├─ /xyz/openbmc_project/inventory_source
    │ ├─ /xyz/openbmc_project/inventory_source/Heat_Exchanger_12_DevTTYUSB0
    │ ├─ /xyz/openbmc_project/inventory_source/Heat_Exchanger_12_DevTTYUSB1
    │ ├─ /xyz/openbmc_project/inventory_source/Reservoir_Pumping_Unit_12_DevTTYUSB0
    │ └─ /xyz/openbmc_project/inventory_source/Reservoir_Pumping_Unit_12_DevTTYUSB1
    └─ /xyz/openbmc_project/sensors
      └─ /xyz/openbmc_project/sensors/temperature
        ├─ /xyz/openbmc_project/sensors/temperature/Reservoir_Pumping_Unit_12_DevTTYUSB0_RPU_Coolant_Inlet_Temp_C
        ├─ /xyz/openbmc_project/sensors/temperature/Reservoir_Pumping_Unit_12_DevTTYUSB0_RPU_Coolant_Outlet_Temp_C
        ├─ /xyz/openbmc_project/sensors/temperature/Reservoir_Pumping_Unit_12_DevTTYUSB1_RPU_Coolant_Inlet_Temp_C
        └─ /xyz/openbmc_project/sensors/temperature/Reservoir_Pumping_Unit_12_DevTTYUSB1_RPU_Coolant_Outlet_Temp_C

busctl introspect xyz.openbmc_project.ModbusRTU /xyz/openbmc_project/sensors/temperature/Reservoir_Pumping_Unit_12_DevTTYUSB1_RPU_Coolant_Outlet_Temp_C
NAME                                TYPE      SIGNATURE RESULT/VALUE                             FLAGS
org.freedesktop.DBus.Introspectable interface -         -                                        -
.Introspect                         method    -         s                                        -
org.freedesktop.DBus.Peer           interface -         -                                        -
.GetMachineId                       method    -         s                                        -
.Ping                               method    -         -                                        -
org.freedesktop.DBus.Properties     interface -         -                                        -
.Get                                method    ss        v                                        -
.GetAll                             method    s         a{sv}                                    -
.Set                                method    ssv       -                                        -
.PropertiesChanged                  signal    sa{sv}as  -                                        -
xyz.openbmc_project.Sensor.Value    interface -         -                                        -
.MaxValue                           property  d         nan                                      emits-change writable
.MinValue                           property  d         nan                                      emits-change writable
.Unit                               property  s         "xyz.openbmc_project.Sensor.Value.Unit.… emits-change writable
.Value                              property  d         1670.6                                   emits-change writable
```

Change-Id: I1368e8df5999b5cee9ac19d185ee110a9ecc3021
Signed-off-by: Jagpal Singh Gill <paligill@gmail.com>
diff --git a/rtu/device/base_device.cpp b/rtu/device/base_device.cpp
new file mode 100644
index 0000000..73d1ca6
--- /dev/null
+++ b/rtu/device/base_device.cpp
@@ -0,0 +1,159 @@
+#include "base_device.hpp"
+
+#include <phosphor-logging/lg2.hpp>
+
+#include <numeric>
+
+namespace phosphor::modbus::rtu::device
+{
+
+PHOSPHOR_LOG2_USING;
+
+BaseDevice::BaseDevice(sdbusplus::async::context& ctx,
+                       const config::Config& config, PortIntf& serialPort) :
+    ctx(ctx), config(config), serialPort(serialPort)
+{
+    createSensors();
+
+    info("Successfully created device {NAME}", "NAME", config.name);
+}
+
+static auto getObjectPath(const std::string& sensorType,
+                          const std::string& sensorName)
+    -> sdbusplus::message::object_path
+{
+    return sdbusplus::message::object_path(
+        std::string(SensorValueIntf::namespace_path::value) + "/" + sensorType +
+        "/" + sensorName);
+}
+
+auto BaseDevice::createSensors() -> void
+{
+    for (const auto& sensorRegister : config.sensorRegisters)
+    {
+        SensorValueIntf::properties_t initProperties = {
+            std::numeric_limits<double>::quiet_NaN(),
+            std::numeric_limits<double>::quiet_NaN(),
+            std::numeric_limits<double>::quiet_NaN(), sensorRegister.unit};
+
+        auto sensorPath = getObjectPath(
+            sensorRegister.pathSuffix, config.name + "_" + sensorRegister.name);
+
+        auto sensor = std::make_unique<SensorValueIntf>(
+            ctx, sensorPath.str.c_str(), initProperties);
+
+        sensor->emit_added();
+
+        sensors.emplace(sensorRegister.name, std::move(sensor));
+    }
+
+    return;
+}
+
+static auto getRawIntegerFromRegister(const std::vector<uint16_t>& reg,
+                                      bool sign) -> int64_t
+{
+    if (reg.empty())
+    {
+        return 0;
+    }
+
+    uint64_t accumulator = 0;
+    for (auto val : reg)
+    {
+        accumulator = (accumulator << 16) | val;
+    }
+
+    int64_t result = 0;
+
+    if (sign)
+    {
+        if (reg.size() == 1)
+        {
+            result = static_cast<int16_t>(accumulator);
+        }
+        else if (reg.size() == 2)
+        {
+            result = static_cast<int32_t>(accumulator);
+        }
+        else
+        {
+            result = static_cast<int64_t>(accumulator);
+        }
+    }
+    else
+    {
+        if (reg.size() == 1)
+        {
+            result = static_cast<uint16_t>(accumulator);
+        }
+        else if (reg.size() == 2)
+        {
+            result = static_cast<uint32_t>(accumulator);
+        }
+        else
+        {
+            result = static_cast<int64_t>(accumulator);
+        }
+    }
+
+    return result;
+}
+
+auto BaseDevice::readSensorRegisters() -> sdbusplus::async::task<void>
+{
+    while (!ctx.stop_requested())
+    {
+        for (const auto& sensorRegister : config.sensorRegisters)
+        {
+            auto sensor = sensors.find(sensorRegister.name);
+            if (sensor == sensors.end())
+            {
+                error("Sensor not found for {NAME}", "NAME",
+                      sensorRegister.name);
+                continue;
+            }
+
+            if (sensorRegister.size > 4)
+            {
+                error("Unsupported size for register {NAME}", "NAME",
+                      sensorRegister.name);
+                continue;
+            }
+
+            auto registers = std::vector<uint16_t>(sensorRegister.size);
+            auto ret = co_await serialPort.readHoldingRegisters(
+                config.address, sensorRegister.offset, config.baudRate,
+                config.parity, registers);
+            if (!ret)
+            {
+                error(
+                    "Failed to read holding registers {NAME} for {DEVICE_ADDRESS}",
+                    "NAME", sensorRegister.name, "DEVICE_ADDRESS",
+                    config.address);
+                continue;
+            }
+
+            double regVal = static_cast<double>(
+                getRawIntegerFromRegister(registers, sensorRegister.isSigned));
+            if (sensorRegister.format == config::SensorFormat::floatingPoint)
+            {
+                regVal = sensorRegister.shift +
+                         (sensorRegister.scale *
+                          (regVal / (1ULL << sensorRegister.precision)));
+            }
+
+            sensor->second->value(regVal);
+        }
+
+        constexpr auto pollInterval = 3;
+        co_await sdbusplus::async::sleep_for(
+            ctx, std::chrono::seconds(pollInterval));
+        debug("Polling sensors for {NAME} in {INTERVAL} seconds", "NAME",
+              config.name, "INTERVAL", pollInterval);
+    }
+
+    co_return;
+}
+
+} // namespace phosphor::modbus::rtu::device