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_config.cpp b/rtu/device/base_config.cpp
new file mode 100644
index 0000000..d9e7e4f
--- /dev/null
+++ b/rtu/device/base_config.cpp
@@ -0,0 +1,362 @@
+#include "base_config.hpp"
+
+#include "common/entity_manager_interface.hpp"
+
+#include <phosphor-logging/lg2.hpp>
+#include <xyz/openbmc_project/Inventory/Item/client.hpp>
+#include <xyz/openbmc_project/State/Leak/Detector/aserver.hpp>
+
+#include <flat_map>
+
+namespace phosphor::modbus::rtu::device::config
+{
+
+PHOSPHOR_LOG2_USING;
+
+using BasicVariantType =
+    std::variant<std::vector<std::string>, std::vector<uint8_t>, std::string,
+                 int64_t, uint64_t, double, int32_t, uint32_t, int16_t,
+                 uint16_t, uint8_t, bool>;
+using InventoryBaseConfigMap = std::flat_map<std::string, BasicVariantType>;
+using InventoryData = std::flat_map<std::string, InventoryBaseConfigMap>;
+using ManagedObjectType =
+    std::flat_map<sdbusplus::message::object_path, InventoryData>;
+
+static constexpr std::array<std::pair<std::string_view, Parity>, 3>
+    validParities = {
+        {{"Odd", Parity::odd}, {"Even", Parity::even}, {"None", Parity::none}}};
+
+template <typename T>
+auto getValue(const InventoryBaseConfigMap& configMap, const std::string& key,
+              const std::string& contextName) -> T
+{
+    auto iter = configMap.find(key);
+    if (iter == configMap.end())
+    {
+        throw std::runtime_error(
+            "Missing property " + key + " for " + contextName);
+    }
+
+    try
+    {
+        return std::get<T>(iter->second);
+    }
+    catch (const std::bad_variant_access& ex)
+    {
+        throw std::runtime_error(
+            "Incorrect type for property " + key + " in " + contextName);
+    }
+}
+
+static inline auto getDataParity(Config& config,
+                                 const InventoryBaseConfigMap& configMap)
+    -> void
+{
+    auto receivedParity =
+        getValue<std::string>(configMap, "DataParity", config.name);
+
+    for (const auto& [parityStr, parity] : validParities)
+    {
+        if (parityStr == receivedParity)
+        {
+            config.parity = parity;
+            break;
+        }
+    }
+
+    if (config.parity == Parity::unknown)
+    {
+        throw std::runtime_error(
+            "Invalid parity " + receivedParity + " for " + config.name);
+    }
+}
+
+static auto processDeviceInterface(Config& config,
+                                   const InventoryBaseConfigMap& configMap)
+    -> void
+{
+    debug("Processing device config");
+
+    config.name = getValue<std::string>(configMap, "Name", config.name);
+
+    std::replace(config.name.begin(), config.name.end(), ' ', '_');
+
+    config.address = getValue<uint64_t>(configMap, "Address", config.name);
+
+    getDataParity(config, configMap);
+
+    config.baudRate = getValue<uint64_t>(configMap, "BaudRate", config.name);
+
+    config.portName =
+        getValue<std::string>(configMap, "SerialPort", config.name);
+
+    getValue<std::string>(configMap, "Type", config.name);
+}
+
+static const auto sensorTypes = std::unordered_map<
+    std::string_view, std::pair<std::string_view, SensorValueIntf::Unit>>{
+    {"FanTach",
+     {SensorValueIntf::namespace_path::fan_tach, SensorValueIntf::Unit::RPMS}},
+    {"LiquidFlow",
+     {SensorValueIntf::namespace_path::liquidflow, SensorValueIntf::Unit::LPM}},
+    {"Power",
+     {SensorValueIntf::namespace_path::power, SensorValueIntf::Unit::Watts}},
+    {"Pressure",
+     {SensorValueIntf::namespace_path::pressure,
+      SensorValueIntf::Unit::Pascals}},
+    {"Temperature",
+     {SensorValueIntf::namespace_path::temperature,
+      SensorValueIntf::Unit::DegreesC}},
+};
+
+static const auto formatTypes =
+    std::unordered_map<std::string_view, SensorFormat>{
+        {"Integer", SensorFormat::integer},
+        {"Float", SensorFormat::floatingPoint}};
+
+static auto processRegisterType(SensorRegister& sensorRegister,
+                                const InventoryBaseConfigMap& configMap) -> void
+{
+    auto registerType =
+        getValue<std::string>(configMap, "RegisterType", sensorRegister.name);
+
+    auto type = sensorTypes.find(registerType);
+    if (type == sensorTypes.end())
+    {
+        throw std::runtime_error("Invalid RegisterType " + registerType +
+                                 " for " + sensorRegister.name);
+    }
+    sensorRegister.pathSuffix = type->second.first;
+    sensorRegister.unit = type->second.second;
+}
+
+static auto processRegisterFormat(SensorRegister& sensorRegister,
+                                  const InventoryBaseConfigMap& configMap)
+    -> void
+{
+    auto format =
+        getValue<std::string>(configMap, "Format", sensorRegister.name);
+
+    auto formatIter = formatTypes.find(format);
+    if (formatIter == formatTypes.end())
+    {
+        throw std::runtime_error(
+            "Invalid Format " + format + " for " + sensorRegister.name);
+    }
+    sensorRegister.format = formatIter->second;
+}
+
+static auto processSensorRegistersInterface(
+    Config& config, const InventoryBaseConfigMap& configMap) -> void
+{
+    SensorRegister sensorRegister = {};
+
+    sensorRegister.name = getValue<std::string>(configMap, "Name", config.name);
+
+    processRegisterType(sensorRegister, configMap);
+
+    sensorRegister.offset =
+        getValue<uint64_t>(configMap, "Address", config.name);
+
+    sensorRegister.size = getValue<uint64_t>(configMap, "Size", config.name);
+
+    sensorRegister.precision =
+        getValue<uint64_t>(configMap, "Precision", config.name);
+
+    sensorRegister.shift = getValue<double>(configMap, "Shift", config.name);
+
+    sensorRegister.scale = getValue<double>(configMap, "Scale", config.name);
+
+    sensorRegister.isSigned = getValue<bool>(configMap, "Signed", config.name);
+
+    processRegisterFormat(sensorRegister, configMap);
+
+    config.sensorRegisters.emplace_back(sensorRegister);
+}
+
+static const auto statusBitTypes =
+    std::unordered_map<std::string_view, StatusType>{
+        {"ControllerFailure", StatusType::controllerFailure},
+        {"FanFailure", StatusType::fanFailure},
+        {"FilterFailure", StatusType::filterFailure},
+        {"PowerFault", StatusType::powerFault},
+        {"PumpFailure", StatusType::pumpFailure},
+        {"LeakDetectedCritical", StatusType::leakDetectedCritical},
+        {"LeakDetectedWarning", StatusType::leakDetectedWarning},
+        {"SensorFailure", StatusType::sensorFailure},
+        {"SensorReadingCritical", StatusType::sensorReadingCritical},
+        {"SensorReadingWarning", StatusType::sensorReadingWarning}};
+
+static auto processStatusBitsInterface(Config& config,
+                                       const InventoryBaseConfigMap& configMap)
+    -> void
+{
+    debug("Processing StatusBits for {NAME}", "NAME", config.name);
+
+    StatusBit statusBit = {};
+
+    statusBit.name = getValue<std::string>(configMap, "Name", config.name);
+
+    auto type = getValue<std::string>(configMap, "StatusType", config.name);
+    auto typeIter = statusBitTypes.find(type);
+    if (typeIter == statusBitTypes.end())
+    {
+        throw std::runtime_error(
+            "Invalid StatusType " + type + " for " + statusBit.name);
+    }
+    statusBit.type = typeIter->second;
+
+    statusBit.bitPosition =
+        getValue<uint64_t>(configMap, "BitPosition", config.name);
+
+    statusBit.value = getValue<bool>(configMap, "Value", config.name);
+
+    auto address = getValue<uint64_t>(configMap, "Address", config.name);
+
+    config.statusRegisters[address].emplace_back(statusBit);
+}
+
+static const auto firmwareRegisterTypes =
+    std::unordered_map<std::string_view, FirmwareRegisterType>{
+        {"Version", FirmwareRegisterType::version},
+        {"Update", FirmwareRegisterType::update}};
+
+static auto processFirmwareRegistersInterface(
+    Config& config, const InventoryBaseConfigMap& configMap) -> void
+{
+    debug("Processing FirmwareRegisters for {NAME}", "NAME", config.name);
+
+    FirmwareRegister firmwareRegister = {};
+
+    firmwareRegister.name =
+        getValue<std::string>(configMap, "Name", config.name);
+
+    firmwareRegister.offset =
+        getValue<uint64_t>(configMap, "Address", firmwareRegister.name);
+
+    firmwareRegister.size =
+        getValue<uint64_t>(configMap, "Size", firmwareRegister.name);
+
+    auto registerType =
+        getValue<std::string>(configMap, "RegisterType", firmwareRegister.name);
+    auto registerTypeIter = firmwareRegisterTypes.find(registerType);
+    if (registerTypeIter == firmwareRegisterTypes.end())
+    {
+        throw std::runtime_error("Invalid RegisterType " + registerType +
+                                 " for " + firmwareRegister.name);
+    }
+    firmwareRegister.type = registerTypeIter->second;
+
+    config.firmwareRegisters.emplace_back(firmwareRegister);
+}
+
+static auto printConfig(const Config& config) -> void
+{
+    info("Device Config for {NAME}: {ADDRESS} {PORT} {INV_PATH}", "NAME",
+         config.name, "ADDRESS", config.address, "PORT", config.portName,
+         "INV_PATH", config.inventoryPath);
+
+    for (const auto& sensorRegister : config.sensorRegisters)
+    {
+        info(
+            "Sensor Register {NAME} {ADDRESS} {SIZE} {PRECISION} {SCALE} {SIGNED} {FORMAT} {UNIT} {PATH_SUFFIX}",
+            "NAME", sensorRegister.name, "ADDRESS", sensorRegister.offset,
+            "SIZE", sensorRegister.size, "PRECISION", sensorRegister.precision,
+            "SCALE", sensorRegister.scale, "SIGNED", sensorRegister.isSigned,
+            "FORMAT", sensorRegister.format, "UNIT", sensorRegister.unit,
+            "PATH_SUFFIX", sensorRegister.pathSuffix);
+    }
+
+    for (const auto& [address, statusBits] : config.statusRegisters)
+    {
+        for (const auto& statusBit : statusBits)
+        {
+            info("Status Bit {NAME} {ADDRESS} {BIT_POSITION} {VALUE} {TYPE}",
+                 "NAME", statusBit.name, "ADDRESS", address, "BIT_POSITION",
+                 statusBit.bitPosition, "VALUE", statusBit.value, "TYPE",
+                 statusBit.type);
+        }
+    }
+
+    for (const auto& firmwareRegister : config.firmwareRegisters)
+    {
+        info("Firmware Register {NAME} {ADDRESS} {SIZE} {TYPE}", "NAME",
+             firmwareRegister.name, "ADDRESS", firmwareRegister.offset, "SIZE",
+             firmwareRegister.size, "TYPE", firmwareRegister.type);
+    }
+}
+
+static auto getConfigSubTree(Config& config, const std::string& interfaceName,
+                             const InventoryData& deviceConfig) -> void
+{
+    std::string firmwareRegistersInterface =
+        interfaceName + ".FirmwareRegisters";
+    std::string sensorRegistersInterface = interfaceName + ".SensorRegisters";
+    std::string statusBitsInterface = interfaceName + ".StatusBits";
+
+    for (const auto& [curInterface, interfaceConfig] : deviceConfig)
+    {
+        if (curInterface == interfaceName)
+        {
+            processDeviceInterface(config, interfaceConfig);
+        }
+        else if (curInterface.starts_with(sensorRegistersInterface))
+        {
+            processSensorRegistersInterface(config, interfaceConfig);
+        }
+        else if (curInterface.starts_with(statusBitsInterface))
+        {
+            processStatusBitsInterface(config, interfaceConfig);
+        }
+        else if (curInterface.starts_with(firmwareRegistersInterface))
+        {
+            processFirmwareRegistersInterface(config, interfaceConfig);
+        }
+    }
+}
+
+auto updateBaseConfig(sdbusplus::async::context& ctx,
+                      const sdbusplus::message::object_path& objectPath,
+                      const std::string& interfaceName, Config& config)
+    -> sdbusplus::async::task<bool>
+{
+    config.inventoryPath = objectPath.parent_path();
+
+    using InventoryIntf =
+        sdbusplus::client::xyz::openbmc_project::inventory::Item<>;
+
+    constexpr auto entityManager =
+        sdbusplus::async::proxy()
+            .service(entity_manager::EntityManagerInterface::serviceName)
+            .path(InventoryIntf::namespace_path)
+            .interface("org.freedesktop.DBus.ObjectManager");
+
+    for (const auto& [path, deviceConfig] :
+         co_await entityManager.call<ManagedObjectType>(ctx,
+                                                        "GetManagedObjects"))
+    {
+        if (path.str != objectPath.str)
+        {
+            debug("Skipping device {PATH}", "PATH", path.str);
+            continue;
+        }
+        debug("Processing device {PATH}", "PATH", path.str);
+
+        try
+        {
+            getConfigSubTree(config, interfaceName, deviceConfig);
+        }
+        catch (std::exception& e)
+        {
+            error("Failed to process device {PATH} with {ERROR}", "PATH",
+                  path.str, "ERROR", e);
+            co_return false;
+        }
+    }
+
+    printConfig(config);
+
+    co_return true;
+}
+
+} // namespace phosphor::modbus::rtu::device::config