xref: /openbmc/phosphor-modbus/tests/test_sensors.cpp (revision 7184805ae4ede906935133e3e0f8ee2468bc781b)
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