blob: a5046b049b2d1deada5d4296f29561d302e65b90 [file] [log] [blame]
Jagpal Singh Gille92aba42025-10-16 00:00:13 -07001#include "device/device_factory.hpp"
2#include "modbus_server_tester.hpp"
3#include "port/base_port.hpp"
4
5#include <fcntl.h>
6
7#include <xyz/openbmc_project/Sensor/Value/client.hpp>
8
9#include <cmath>
10#include <string>
11
12#include <gtest/gtest.h>
13
14using namespace std::literals;
15using namespace testing;
16using SensorValueIntf =
17 sdbusplus::client::xyz::openbmc_project::sensor::Value<>;
18
19namespace TestIntf = phosphor::modbus::test;
20namespace ModbusIntf = phosphor::modbus::rtu;
21namespace PortIntf = phosphor::modbus::rtu::port;
22namespace PortConfigIntf = PortIntf::config;
23namespace DeviceIntf = phosphor::modbus::rtu::device;
24namespace DeviceConfigIntf = DeviceIntf::config;
25
26class MockPort : public PortIntf::BasePort
27{
28 public:
29 MockPort(sdbusplus::async::context& ctx,
30 const PortConfigIntf::Config& config,
31 const std::string& devicePath) : BasePort(ctx, config, devicePath)
32 {}
33};
34
35class SensorsTest : public ::testing::Test
36{
37 public:
38 PortConfigIntf::Config portConfig;
39 static constexpr const char* clientDevicePath = "/tmp/ttySensorsTestPort0";
40 static constexpr const char* serverDevicePath = "/tmp/ttySensorsTestPort1";
41 static constexpr auto portName = "TestPort0";
42 static constexpr auto baudRate = 115200;
43 static constexpr const auto strBaudeRate = "b115200";
44 std::string deviceName;
45 std::string fullSensorName;
46 std::string objectPath;
47 static constexpr auto serviceName =
48 "xyz.openbmc_project.TestModbusRTUSensors";
49 static constexpr auto sensorName = "OutletTemperature";
50 int socat_pid = -1;
51 sdbusplus::async::context ctx;
52 int fdClient = -1;
53 std::unique_ptr<TestIntf::ServerTester> serverTester;
54 int fdServer = -1;
55
56 SensorsTest()
57 {
58 portConfig.name = portName;
59 portConfig.portMode = PortConfigIntf::PortMode::rs485;
60 portConfig.baudRate = baudRate;
61 portConfig.rtsDelay = 1;
62
63 deviceName = std::format("ResorviorPumpUnit_{}_{}",
64 TestIntf::testDeviceAddress, portName);
65
66 fullSensorName = std::format("{}_{}", deviceName, sensorName);
67
68 objectPath = std::format(
69 "{}/{}/{}", SensorValueIntf::namespace_path::value,
70 SensorValueIntf::namespace_path::temperature, fullSensorName);
71
72 std::string socatCmd = std::format(
73 "socat -x -v -d -d pty,link={},rawer,echo=0,parenb,{} pty,link={},rawer,echo=0,parenb,{} & echo $!",
74 serverDevicePath, strBaudeRate, clientDevicePath, strBaudeRate);
75
76 // Start socat in the background and capture its PID
77 FILE* fp = popen(socatCmd.c_str(), "r");
78 EXPECT_NE(fp, nullptr) << "Failed to start socat: " << strerror(errno);
79 EXPECT_GT(fscanf(fp, "%d", &socat_pid), 0);
80 pclose(fp);
81
82 // Wait for socat to start up
83 sleep(1);
84
85 fdClient = open(clientDevicePath, O_RDWR | O_NOCTTY | O_NONBLOCK);
86 EXPECT_NE(fdClient, -1)
87 << "Failed to open serial port " << clientDevicePath
88 << " with error: " << strerror(errno);
89
90 fdServer = open(serverDevicePath, O_RDWR | O_NOCTTY | O_NONBLOCK);
91 EXPECT_NE(fdServer, -1)
92 << "Failed to open serial port " << serverDevicePath
93 << " with error: " << strerror(errno);
94
95 ctx.request_name(serviceName);
96
97 serverTester = std::make_unique<TestIntf::ServerTester>(ctx, fdServer);
98 }
99
100 ~SensorsTest() noexcept override
101 {
102 if (fdClient != -1)
103 {
104 close(fdClient);
105 fdClient = -1;
106 }
107 if (fdServer != -1)
108 {
109 close(fdServer);
110 fdServer = -1;
111 }
112 kill(socat_pid, SIGTERM);
113 }
114
115 auto testSensorCreation(std::string objectPath,
116 DeviceConfigIntf::SensorRegister sensorRegister,
117 double expectedValue)
118 -> sdbusplus::async::task<void>
119 {
120 DeviceConfigIntf::DeviceFactoryConfig deviceFactoryConfig = {
121 {
122 .address = TestIntf::testDeviceAddress,
123 .parity = ModbusIntf::Parity::none,
124 .baudRate = baudRate,
125 .name = deviceName,
126 .portName = portConfig.name,
127 .inventoryPath = sdbusplus::message::object_path(
128 "xyz/openbmc_project/Inventory/ResorviorPumpUnit"),
129 .sensorRegisters = {sensorRegister},
130 .statusRegisters = {},
131 .firmwareRegisters = {},
132 },
133 DeviceConfigIntf::DeviceType::reservoirPumpUnit,
134 DeviceConfigIntf::DeviceModel::RDF040DSS5193E0,
135 };
136
137 auto mockPort =
138 std::make_unique<MockPort>(ctx, portConfig, clientDevicePath);
139
140 auto device = DeviceIntf::DeviceFactory::create(
141 ctx, deviceFactoryConfig, *mockPort);
142
143 co_await device->readSensorRegisters();
144
145 auto properties = co_await SensorValueIntf(ctx)
146 .service(serviceName)
147 .path(objectPath)
148 .properties();
149
150 EXPECT_EQ(properties.value, expectedValue) << "Sensor value mismatch";
151 EXPECT_EQ(properties.unit, sensorRegister.unit)
152 << "Sensor unit mismatch";
153 EXPECT_TRUE(std::isnan(properties.min_value)) << "Min value mismatch";
154 EXPECT_TRUE(std::isnan(properties.max_value)) << "Max value mismatch";
155
156 co_return;
157 }
158
159 void SetUp() override
160 {
161 // Process request for sensor poll
162 ctx.spawn(serverTester->processRequests());
163 }
164};
165
166TEST_F(SensorsTest, TestSensorValueUnsigned)
167{
168 const DeviceConfigIntf::SensorRegister sensorRegister = {
169 .name = sensorName,
170 .pathSuffix = SensorValueIntf::namespace_path::temperature,
171 .unit = SensorValueIntf::Unit::DegreesC,
172 .offset = TestIntf::testReadHoldingRegisterTempUnsignedOffset,
173 .size = TestIntf::testReadHoldingRegisterTempCount,
174 .format = DeviceConfigIntf::SensorFormat::floatingPoint,
175 };
176
177 ctx.spawn(
178 testSensorCreation(objectPath, sensorRegister,
179 TestIntf::testReadHoldingRegisterTempUnsigned[0]));
180
181 ctx.spawn(sdbusplus::async::sleep_for(ctx, 1s) |
182 sdbusplus::async::execution::then([&]() { ctx.request_stop(); }));
183
184 ctx.run();
185}
186
187TEST_F(SensorsTest, TestSensorValueSigned)
188{
189 const DeviceConfigIntf::SensorRegister sensorRegister = {
190 .name = sensorName,
191 .pathSuffix = SensorValueIntf::namespace_path::temperature,
192 .unit = SensorValueIntf::Unit::DegreesC,
193 .offset = TestIntf::testReadHoldingRegisterTempSignedOffset,
194 .size = TestIntf::testReadHoldingRegisterTempCount,
195 .isSigned = true,
196 .format = DeviceConfigIntf::SensorFormat::floatingPoint,
197 };
198
199 // Convert expected hex value to a signed 16-bit integer for comparison
200 const int16_t expectedSigned =
201 static_cast<int16_t>(TestIntf::testReadHoldingRegisterTempSigned[0]);
202
203 ctx.spawn(testSensorCreation(objectPath, sensorRegister, expectedSigned));
204
205 ctx.spawn(sdbusplus::async::sleep_for(ctx, 1s) |
206 sdbusplus::async::execution::then([&]() { ctx.request_stop(); }));
207
208 ctx.run();
209}
210
211static auto applyValueSettings(double value, double shift, double scale,
212 uint8_t precision)
213{
214 return (shift + (scale * (value / (1ULL << precision))));
215}
216
217TEST_F(SensorsTest, TestSensorValueWithSettings)
218{
219 const DeviceConfigIntf::SensorRegister sensorRegister = {
220 .name = sensorName,
221 .pathSuffix = SensorValueIntf::namespace_path::temperature,
222 .unit = SensorValueIntf::Unit::DegreesC,
223 .offset = TestIntf::testReadHoldingRegisterTempUnsignedOffset,
224 .size = TestIntf::testReadHoldingRegisterTempCount,
225 .precision = 2,
226 .scale = 0.1,
227 .shift = 50,
228 .format = DeviceConfigIntf::SensorFormat::floatingPoint,
229 };
230
231 ctx.spawn(testSensorCreation(
232 objectPath, sensorRegister,
233 applyValueSettings(TestIntf::testReadHoldingRegisterTempUnsigned[0],
234 sensorRegister.shift, sensorRegister.scale,
235 sensorRegister.precision)));
236
237 ctx.spawn(sdbusplus::async::sleep_for(ctx, 1s) |
238 sdbusplus::async::execution::then([&]() { ctx.request_stop(); }));
239
240 ctx.run();
241}