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