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/tests/meson.build b/tests/meson.build
index bcc4fad..8b1df07 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -62,3 +62,15 @@
include_directories: ['.', common_include],
),
)
+
+test(
+ 'test_sensors',
+ executable(
+ 'test_sensors',
+ 'test_sensors.cpp',
+ 'modbus_server_tester.cpp',
+ device_src,
+ dependencies: [gtest_dep, gmock_dep, default_deps, modbus_rtu_dep],
+ include_directories: ['.', common_include],
+ ),
+)
diff --git a/tests/modbus_server_tester.cpp b/tests/modbus_server_tester.cpp
index fe1b825..1b09dc2 100644
--- a/tests/modbus_server_tester.cpp
+++ b/tests/modbus_server_tester.cpp
@@ -122,35 +122,8 @@
uint16_t registerOffset = request.raw[2] << 8 | request.raw[3];
uint16_t registerCount = request.raw[4] << 8 | request.raw[5];
- if (registerOffset == testSuccessReadHoldingRegisterOffset ||
- registerOffset == testSuccessReadHoldingRegisterSegmentedOffset)
+ if (registerOffset == testFailureReadHoldingRegister)
{
- checkRequestSize(registerCount, testSuccessReadHoldingRegisterCount);
-
- response << request.raw[0] << request.raw[1]
- << uint8_t(2 * registerCount)
- << uint16_t(testSuccessReadHoldingRegisterResponse[0])
- << uint16_t(testSuccessReadHoldingRegisterResponse[1]);
- response.appendCRC();
- segmentedResponse =
- (registerOffset == testSuccessReadHoldingRegisterSegmentedOffset);
- }
- else if (registerOffset == testReadHoldingRegisterModelOffset)
- {
- checkRequestSize(registerCount, testReadHoldingRegisterModelCount);
-
- response << request.raw[0] << request.raw[1]
- << uint8_t(2 * testReadHoldingRegisterModelCount);
- for (size_t i = 0; i < testReadHoldingRegisterModelCount; i++)
- {
- response << uint16_t(testReadHoldingRegisterModel[i]);
- }
- response.appendCRC();
- }
- else if (registerOffset == testFailureReadHoldingRegister)
- {
- checkRequestSize(registerCount, testSuccessReadHoldingRegisterCount);
-
response << request.raw[0]
<< (uint8_t)readHoldingRegistersErrorFunctionCode
<< uint8_t(RTUIntf::ModbusExceptionCode::illegalFunctionCode);
@@ -158,7 +131,29 @@
}
else
{
- FAIL() << "Invalid register offset:" << registerOffset;
+ auto expectedResponseIter =
+ testReadHoldingRegisterMap.find(registerOffset);
+ if (expectedResponseIter == testReadHoldingRegisterMap.end())
+ {
+ FAIL() << "Invalid register offset:" << registerOffset;
+ return;
+ }
+
+ checkRequestSize(registerCount,
+ std::get<0>(expectedResponseIter->second));
+
+ auto& expectedResponse = std::get<1>(expectedResponseIter->second);
+
+ response << request.raw[0] << request.raw[1]
+ << uint8_t(2 * registerCount);
+ for (size_t i = 0; i < registerCount; i++)
+ {
+ response << uint16_t(expectedResponse[i]);
+ }
+ response.appendCRC();
+
+ segmentedResponse =
+ (registerOffset == testSuccessReadHoldingRegisterSegmentedOffset);
}
}
diff --git a/tests/modbus_server_tester.hpp b/tests/modbus_server_tester.hpp
index c9b4416..ccfa204 100644
--- a/tests/modbus_server_tester.hpp
+++ b/tests/modbus_server_tester.hpp
@@ -19,18 +19,43 @@
constexpr uint16_t testSuccessReadHoldingRegisterOffset = 0x0102;
constexpr uint16_t testSuccessReadHoldingRegisterCount = 0x2;
constexpr uint16_t testSuccessReadHoldingRegisterSegmentedOffset = 0x0103;
-constexpr std::array<uint16_t, testSuccessReadHoldingRegisterCount>
- testSuccessReadHoldingRegisterResponse = {0x1234, 0x5678};
+const std::vector<uint16_t> testSuccessReadHoldingRegisterResponse = {
+ 0x1234, 0x5678};
constexpr uint16_t testFailureReadHoldingRegister = 0x0105;
// Device Inventory Testing Constants
constexpr uint16_t testReadHoldingRegisterModelOffset = 0x0112;
constexpr uint16_t testReadHoldingRegisterModelCount = 0x8;
-constexpr std::array<uint16_t, testReadHoldingRegisterModelCount>
- testReadHoldingRegisterModel = {0x5244, 0x4630, 0x3430, 0x4453,
- 0x5335, 0x3139, 0x0000, 0x3000};
+const std::vector<uint16_t> testReadHoldingRegisterModel = {
+ 0x5244, 0x4630, 0x3430, 0x4453, 0x5335, 0x3139, 0x0000, 0x3000};
constexpr std::string testReadHoldingRegisterModelStr = "RDF040DSS519";
+// Device Sensors Testing Constants
+constexpr uint16_t testReadHoldingRegisterTempCount = 0x1;
+constexpr uint16_t testReadHoldingRegisterTempUnsignedOffset = 0x0113;
+const std::vector<uint16_t> testReadHoldingRegisterTempUnsigned = {
+ 0x0050}; // 80.0
+constexpr uint16_t testReadHoldingRegisterTempSignedOffset = 0x0114;
+const std::vector<uint16_t> testReadHoldingRegisterTempSigned = {
+ 0xFFB0}; // -80.0
+
+static const std::map<uint16_t, std::tuple<uint16_t, std::vector<uint16_t>>>
+ testReadHoldingRegisterMap = {
+ {testSuccessReadHoldingRegisterOffset,
+ {testSuccessReadHoldingRegisterCount,
+ testSuccessReadHoldingRegisterResponse}},
+ {testSuccessReadHoldingRegisterSegmentedOffset,
+ {testSuccessReadHoldingRegisterCount,
+ testSuccessReadHoldingRegisterResponse}},
+ {testReadHoldingRegisterModelOffset,
+ {testReadHoldingRegisterModelCount, testReadHoldingRegisterModel}},
+ {testReadHoldingRegisterTempUnsignedOffset,
+ {testReadHoldingRegisterTempCount,
+ testReadHoldingRegisterTempUnsigned}},
+ {testReadHoldingRegisterTempSignedOffset,
+ {testReadHoldingRegisterTempCount, testReadHoldingRegisterTempSigned}},
+};
+
class ServerTester
{
public:
diff --git a/tests/test_sensors.cpp b/tests/test_sensors.cpp
new file mode 100644
index 0000000..a5046b0
--- /dev/null
+++ b/tests/test_sensors.cpp
@@ -0,0 +1,241 @@
+#include "device/device_factory.hpp"
+#include "modbus_server_tester.hpp"
+#include "port/base_port.hpp"
+
+#include <fcntl.h>
+
+#include <xyz/openbmc_project/Sensor/Value/client.hpp>
+
+#include <cmath>
+#include <string>
+
+#include <gtest/gtest.h>
+
+using namespace std::literals;
+using namespace testing;
+using SensorValueIntf =
+ sdbusplus::client::xyz::openbmc_project::sensor::Value<>;
+
+namespace TestIntf = phosphor::modbus::test;
+namespace ModbusIntf = phosphor::modbus::rtu;
+namespace PortIntf = phosphor::modbus::rtu::port;
+namespace PortConfigIntf = PortIntf::config;
+namespace DeviceIntf = phosphor::modbus::rtu::device;
+namespace DeviceConfigIntf = DeviceIntf::config;
+
+class MockPort : public PortIntf::BasePort
+{
+ public:
+ MockPort(sdbusplus::async::context& ctx,
+ const PortConfigIntf::Config& config,
+ const std::string& devicePath) : BasePort(ctx, config, devicePath)
+ {}
+};
+
+class SensorsTest : public ::testing::Test
+{
+ public:
+ PortConfigIntf::Config portConfig;
+ static constexpr const char* clientDevicePath = "/tmp/ttySensorsTestPort0";
+ static constexpr const char* serverDevicePath = "/tmp/ttySensorsTestPort1";
+ static constexpr auto portName = "TestPort0";
+ static constexpr auto baudRate = 115200;
+ static constexpr const auto strBaudeRate = "b115200";
+ std::string deviceName;
+ std::string fullSensorName;
+ std::string objectPath;
+ static constexpr auto serviceName =
+ "xyz.openbmc_project.TestModbusRTUSensors";
+ static constexpr auto sensorName = "OutletTemperature";
+ int socat_pid = -1;
+ sdbusplus::async::context ctx;
+ int fdClient = -1;
+ std::unique_ptr<TestIntf::ServerTester> serverTester;
+ int fdServer = -1;
+
+ SensorsTest()
+ {
+ portConfig.name = portName;
+ portConfig.portMode = PortConfigIntf::PortMode::rs485;
+ portConfig.baudRate = baudRate;
+ portConfig.rtsDelay = 1;
+
+ deviceName = std::format("ResorviorPumpUnit_{}_{}",
+ TestIntf::testDeviceAddress, portName);
+
+ fullSensorName = std::format("{}_{}", deviceName, sensorName);
+
+ objectPath = std::format(
+ "{}/{}/{}", SensorValueIntf::namespace_path::value,
+ SensorValueIntf::namespace_path::temperature, fullSensorName);
+
+ std::string socatCmd = std::format(
+ "socat -x -v -d -d pty,link={},rawer,echo=0,parenb,{} pty,link={},rawer,echo=0,parenb,{} & echo $!",
+ serverDevicePath, strBaudeRate, clientDevicePath, strBaudeRate);
+
+ // Start socat in the background and capture its PID
+ FILE* fp = popen(socatCmd.c_str(), "r");
+ EXPECT_NE(fp, nullptr) << "Failed to start socat: " << strerror(errno);
+ EXPECT_GT(fscanf(fp, "%d", &socat_pid), 0);
+ pclose(fp);
+
+ // Wait for socat to start up
+ sleep(1);
+
+ fdClient = open(clientDevicePath, O_RDWR | O_NOCTTY | O_NONBLOCK);
+ EXPECT_NE(fdClient, -1)
+ << "Failed to open serial port " << clientDevicePath
+ << " with error: " << strerror(errno);
+
+ fdServer = open(serverDevicePath, O_RDWR | O_NOCTTY | O_NONBLOCK);
+ EXPECT_NE(fdServer, -1)
+ << "Failed to open serial port " << serverDevicePath
+ << " with error: " << strerror(errno);
+
+ ctx.request_name(serviceName);
+
+ serverTester = std::make_unique<TestIntf::ServerTester>(ctx, fdServer);
+ }
+
+ ~SensorsTest() noexcept override
+ {
+ if (fdClient != -1)
+ {
+ close(fdClient);
+ fdClient = -1;
+ }
+ if (fdServer != -1)
+ {
+ close(fdServer);
+ fdServer = -1;
+ }
+ kill(socat_pid, SIGTERM);
+ }
+
+ auto testSensorCreation(std::string objectPath,
+ DeviceConfigIntf::SensorRegister sensorRegister,
+ double expectedValue)
+ -> sdbusplus::async::task<void>
+ {
+ DeviceConfigIntf::DeviceFactoryConfig deviceFactoryConfig = {
+ {
+ .address = TestIntf::testDeviceAddress,
+ .parity = ModbusIntf::Parity::none,
+ .baudRate = baudRate,
+ .name = deviceName,
+ .portName = portConfig.name,
+ .inventoryPath = sdbusplus::message::object_path(
+ "xyz/openbmc_project/Inventory/ResorviorPumpUnit"),
+ .sensorRegisters = {sensorRegister},
+ .statusRegisters = {},
+ .firmwareRegisters = {},
+ },
+ DeviceConfigIntf::DeviceType::reservoirPumpUnit,
+ DeviceConfigIntf::DeviceModel::RDF040DSS5193E0,
+ };
+
+ auto mockPort =
+ std::make_unique<MockPort>(ctx, portConfig, clientDevicePath);
+
+ auto device = DeviceIntf::DeviceFactory::create(
+ ctx, deviceFactoryConfig, *mockPort);
+
+ co_await device->readSensorRegisters();
+
+ auto properties = co_await SensorValueIntf(ctx)
+ .service(serviceName)
+ .path(objectPath)
+ .properties();
+
+ EXPECT_EQ(properties.value, expectedValue) << "Sensor value mismatch";
+ EXPECT_EQ(properties.unit, sensorRegister.unit)
+ << "Sensor unit mismatch";
+ EXPECT_TRUE(std::isnan(properties.min_value)) << "Min value mismatch";
+ EXPECT_TRUE(std::isnan(properties.max_value)) << "Max value mismatch";
+
+ co_return;
+ }
+
+ void SetUp() override
+ {
+ // Process request for sensor poll
+ ctx.spawn(serverTester->processRequests());
+ }
+};
+
+TEST_F(SensorsTest, TestSensorValueUnsigned)
+{
+ const DeviceConfigIntf::SensorRegister sensorRegister = {
+ .name = sensorName,
+ .pathSuffix = SensorValueIntf::namespace_path::temperature,
+ .unit = SensorValueIntf::Unit::DegreesC,
+ .offset = TestIntf::testReadHoldingRegisterTempUnsignedOffset,
+ .size = TestIntf::testReadHoldingRegisterTempCount,
+ .format = DeviceConfigIntf::SensorFormat::floatingPoint,
+ };
+
+ ctx.spawn(
+ testSensorCreation(objectPath, sensorRegister,
+ TestIntf::testReadHoldingRegisterTempUnsigned[0]));
+
+ ctx.spawn(sdbusplus::async::sleep_for(ctx, 1s) |
+ sdbusplus::async::execution::then([&]() { ctx.request_stop(); }));
+
+ ctx.run();
+}
+
+TEST_F(SensorsTest, TestSensorValueSigned)
+{
+ const DeviceConfigIntf::SensorRegister sensorRegister = {
+ .name = sensorName,
+ .pathSuffix = SensorValueIntf::namespace_path::temperature,
+ .unit = SensorValueIntf::Unit::DegreesC,
+ .offset = TestIntf::testReadHoldingRegisterTempSignedOffset,
+ .size = TestIntf::testReadHoldingRegisterTempCount,
+ .isSigned = true,
+ .format = DeviceConfigIntf::SensorFormat::floatingPoint,
+ };
+
+ // Convert expected hex value to a signed 16-bit integer for comparison
+ const int16_t expectedSigned =
+ static_cast<int16_t>(TestIntf::testReadHoldingRegisterTempSigned[0]);
+
+ ctx.spawn(testSensorCreation(objectPath, sensorRegister, expectedSigned));
+
+ ctx.spawn(sdbusplus::async::sleep_for(ctx, 1s) |
+ sdbusplus::async::execution::then([&]() { ctx.request_stop(); }));
+
+ ctx.run();
+}
+
+static auto applyValueSettings(double value, double shift, double scale,
+ uint8_t precision)
+{
+ return (shift + (scale * (value / (1ULL << precision))));
+}
+
+TEST_F(SensorsTest, TestSensorValueWithSettings)
+{
+ const DeviceConfigIntf::SensorRegister sensorRegister = {
+ .name = sensorName,
+ .pathSuffix = SensorValueIntf::namespace_path::temperature,
+ .unit = SensorValueIntf::Unit::DegreesC,
+ .offset = TestIntf::testReadHoldingRegisterTempUnsignedOffset,
+ .size = TestIntf::testReadHoldingRegisterTempCount,
+ .precision = 2,
+ .scale = 0.1,
+ .shift = 50,
+ .format = DeviceConfigIntf::SensorFormat::floatingPoint,
+ };
+
+ ctx.spawn(testSensorCreation(
+ objectPath, sensorRegister,
+ applyValueSettings(TestIntf::testReadHoldingRegisterTempUnsigned[0],
+ sensorRegister.shift, sensorRegister.scale,
+ sensorRegister.precision)));
+
+ ctx.spawn(sdbusplus::async::sleep_for(ctx, 1s) |
+ sdbusplus::async::execution::then([&]() { ctx.request_stop(); }));
+
+ ctx.run();
+}