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