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