xref: /openbmc/phosphor-modbus/tests/test_sensors.cpp (revision e92aba4516471f5a01d4ab1f93eb9919ec05c21f)
1 #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 
14 using namespace std::literals;
15 using namespace testing;
16 using SensorValueIntf =
17     sdbusplus::client::xyz::openbmc_project::sensor::Value<>;
18 
19 namespace TestIntf = phosphor::modbus::test;
20 namespace ModbusIntf = phosphor::modbus::rtu;
21 namespace PortIntf = phosphor::modbus::rtu::port;
22 namespace PortConfigIntf = PortIntf::config;
23 namespace DeviceIntf = phosphor::modbus::rtu::device;
24 namespace DeviceConfigIntf = DeviceIntf::config;
25 
26 class 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 
35 class 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 
166 TEST_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 
187 TEST_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 
211 static auto applyValueSettings(double value, double shift, double scale,
212                                uint8_t precision)
213 {
214     return (shift + (scale * (value / (1ULL << precision))));
215 }
216 
217 TEST_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 }
242