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