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