blob: ca0fdbf1e2dfbe487eaca8740b4de9e34d82e3d9 [file] [log] [blame]
Jagpal Singh Gillacfdd552025-12-09 06:02:14 -08001#include "common/events.hpp"
2#include "device/device_factory.hpp"
3#include "modbus_server_tester.hpp"
4#include "port/base_port.hpp"
5#include "test_base.hpp"
6
7#include <xyz/openbmc_project/Logging/Create/aserver.hpp>
8#include <xyz/openbmc_project/Logging/Entry/aserver.hpp>
9#include <xyz/openbmc_project/Sensor/Threshold/Critical/client.hpp>
10#include <xyz/openbmc_project/Sensor/Value/client.hpp>
11#include <xyz/openbmc_project/State/Decorator/Availability/client.hpp>
12#include <xyz/openbmc_project/State/Decorator/OperationalStatus/client.hpp>
13
14#include <cmath>
15#include <string>
16
17#include <gtest/gtest.h>
18
19using namespace std::literals;
20using namespace testing;
21using SensorValueIntf =
22 sdbusplus::client::xyz::openbmc_project::sensor::Value<>;
23using OperationalStatusIntf = sdbusplus::client::xyz::openbmc_project::state::
24 decorator::OperationalStatus<>;
25using AvailabilityIntf =
26 sdbusplus::client::xyz::openbmc_project::state::decorator::Availability<>;
27using ThresholdCriticalIntf =
28 sdbusplus::client::xyz::openbmc_project::sensor::threshold::Critical<>;
29
30class TestEventServer;
31class TestEventEntry;
32
33using EventServerIntf =
34 sdbusplus::aserver::xyz::openbmc_project::logging::Create<TestEventServer>;
35using EventEntryIntf =
36 sdbusplus::aserver::xyz::openbmc_project::logging::Entry<TestEventEntry>;
37
38namespace ModbusIntf = phosphor::modbus::rtu;
39namespace PortIntf = phosphor::modbus::rtu::port;
40namespace PortConfigIntf = PortIntf::config;
41namespace DeviceIntf = phosphor::modbus::rtu::device;
42namespace DeviceConfigIntf = DeviceIntf::config;
43namespace EventIntf = phosphor::modbus::events;
44
45class MockPort : public PortIntf::BasePort
46{
47 public:
48 MockPort(sdbusplus::async::context& ctx,
49 const PortConfigIntf::Config& config,
50 const std::string& devicePath) : BasePort(ctx, config, devicePath)
51 {}
52};
53
54// Test Event Class to mock the EventEntry
55class TestEventEntry : public EventEntryIntf
56{
57 public:
58 TestEventEntry(sdbusplus::async::context& ctx, const char* path) :
59 EventEntryIntf(ctx, path)
60 {}
61
62 auto method_call(get_entry_t)
63 -> sdbusplus::async::task<get_entry_t::return_type>
64 {
65 get_entry_t::return_type fd1 = 0;
66 co_return fd1;
67 }
68};
69
70// Test Event Server Class to mock the EventServer
71class TestEventServer : public EventServerIntf
72{
73 public:
74 TestEventServer(sdbusplus::async::context& ctx, const char* path) :
75 EventServerIntf(ctx, path), ctx(ctx)
76 {}
77
78 auto method_call(create_t, auto message, auto, auto)
79 -> sdbusplus::async::task<create_t::return_type>
80
81 {
82 static int cnt = 100;
83 cnt++;
84
85 // Append the count to the object path to make it unique for each event
86 std::string objectPath =
87 "/xyz/openbmc_project/logging/entry/TestEvent" +
88 std::to_string(cnt);
89 EXPECT_EQ(message, expectedEvent) << "Event name mismatch";
90
91 eventEntries.emplace_back(
92 std::make_unique<TestEventEntry>(ctx, objectPath.c_str()));
93
94 co_return sdbusplus::message::object_path(objectPath);
95 }
96
97 auto method_call(create_with_ffdc_files_t, auto, auto, auto, auto)
98 -> sdbusplus::async::task<create_with_ffdc_files_t::return_type>
99
100 {
101 co_return;
102 }
103
104 std::string expectedEvent;
105
106 private:
107 sdbusplus::async::context& ctx;
108 std::vector<std::unique_ptr<TestEventEntry>> eventEntries;
109};
110
111class DeviceEventsTest : public BaseTest
112{
113 public:
114 PortConfigIntf::Config portConfig;
115 static constexpr const char* clientDevicePath =
116 "/tmp/ttyDeviceEventsTestPort0";
117 static constexpr const char* serverDevicePath =
118 "/tmp/ttyDeviceEventsTestPort1";
119 static constexpr auto portName = "TestPort0";
120 std::string deviceName;
121 std::string fullSensorName;
122 std::string objectPath;
123 const char* loggingObjectPath = "/xyz/openbmc_project/logging";
124 static constexpr auto serviceName = "xyz.openbmc_project.Logging";
125 static constexpr auto sensorName = "OutletTemperature";
126 TestEventServer eventServer;
127 sdbusplus::server::manager_t manager;
128
129 DeviceEventsTest() :
130 BaseTest(clientDevicePath, serverDevicePath, serviceName),
131 eventServer(ctx, loggingObjectPath), manager(ctx, loggingObjectPath)
132 {
133 portConfig.name = portName;
134 portConfig.portMode = PortConfigIntf::PortMode::rs485;
135 portConfig.baudRate = baudRate;
136 portConfig.rtsDelay = 1;
137
138 deviceName = std::format("ResorviorPumpUnit_{}_{}",
139 TestIntf::testDeviceAddress, portName);
140
141 fullSensorName = std::format("{}_{}", deviceName, sensorName);
142
143 objectPath = std::format(
144 "{}/{}/{}", SensorValueIntf::namespace_path::value,
145 SensorValueIntf::namespace_path::temperature, fullSensorName);
146 }
147
148 auto verifyValue(bool currentValue, bool expectedValue,
149 const std::string& failureStr) -> void
150 {
151 EXPECT_EQ(currentValue, expectedValue) << failureStr;
152 }
153
154 auto verifyValue(double currentValue, double expectedValue,
155 const std::string& failureStr) -> void
156 {
157 EXPECT_EQ(currentValue, expectedValue) << failureStr;
158 }
159
160 auto verifyResult(
161 SensorValueIntf::properties_t& properties,
162 OperationalStatusIntf::properties_t& operationalProperties,
163 AvailabilityIntf::properties_t& availabilityProperties,
164 ThresholdCriticalIntf::properties_t& thresholdProperties,
165 double expectedValue, SensorValueIntf::Unit expectedUnit) -> void
166 {
167 if (std::isnan(expectedValue))
168 {
169 EXPECT_TRUE(std::isnan(properties.value))
170 << "Sensor value should be Nan";
171 verifyValue(operationalProperties.functional, false,
172 "Operational status mismatch");
173 verifyValue(availabilityProperties.available, false,
174 "Availability mismatch");
175 verifyValue(thresholdProperties.critical_alarm_high, false,
176 "Critical Alarm mismatch");
177 }
178 else
179 {
180 verifyValue(properties.value, expectedValue,
181 "Sensor value mismatch");
182 verifyValue(operationalProperties.functional, true,
183 "Operational status mismatch");
184 verifyValue(availabilityProperties.available, true,
185 "Availability mismatch");
186 verifyValue(thresholdProperties.critical_alarm_high, true,
187 "Critical Alarm mismatch");
188 }
189
190 EXPECT_EQ(properties.unit, expectedUnit) << "Sensor unit mismatch";
191 EXPECT_TRUE(std::isnan(properties.min_value)) << "Min value mismatch";
192 EXPECT_TRUE(std::isnan(properties.max_value)) << "Max value mismatch";
193 }
194
195 auto testSensorCreation(std::string objectPath,
196 DeviceConfigIntf::StatusType statusType,
197 double expectedValue)
198 -> sdbusplus::async::task<void>
199 {
200 DeviceConfigIntf::StatusBit statusBit = {
201 .name = sensorName,
202 .type = statusType,
203 .bitPosition = 0,
204 .value = true};
205 DeviceConfigIntf::Config::status_registers_t statusRegisters = {
206 {TestIntf::testReadHoldingRegisterEventOffset, {statusBit}}};
207 DeviceConfigIntf::Config::sensor_registers_t sensorRegisters = {{
208 .name = sensorName,
209 .pathSuffix = SensorValueIntf::namespace_path::temperature,
210 .unit = SensorValueIntf::Unit::DegreesC,
211 .offset = TestIntf::testReadHoldingRegisterTempUnsignedOffset,
212 .size = TestIntf::testReadHoldingRegisterTempCount,
213 .format = DeviceConfigIntf::SensorFormat::floatingPoint,
214 }};
215 DeviceConfigIntf::DeviceFactoryConfig deviceFactoryConfig = {
216 {
217 .address = TestIntf::testDeviceAddress,
218 .parity = ModbusIntf::Parity::none,
219 .baudRate = baudRate,
220 .name = deviceName,
221 .portName = portConfig.name,
222 .inventoryPath = sdbusplus::message::object_path(
223 "xyz/openbmc_project/Inventory/ResorviorPumpUnit"),
224 .sensorRegisters = sensorRegisters,
225 .statusRegisters = statusRegisters,
226 .firmwareRegisters = {},
227 },
228 DeviceConfigIntf::DeviceType::reservoirPumpUnit,
229 DeviceConfigIntf::DeviceModel::RDF040DSS5193E0,
230 };
231 EventIntf::Events events{ctx};
232 MockPort mockPort(ctx, portConfig, clientDevicePath);
233 auto device = DeviceIntf::DeviceFactory::create(
234 ctx, deviceFactoryConfig, mockPort, events);
235 co_await device->readSensorRegisters();
236 auto properties = co_await SensorValueIntf(ctx)
237 .service(serviceName)
238 .path(objectPath)
239 .properties();
240 auto operationalProperties =
241 co_await OperationalStatusIntf(ctx)
242 .service(serviceName)
243 .path(objectPath)
244 .properties();
245 auto availabilityProperties =
246 co_await AvailabilityIntf(ctx)
247 .service(serviceName)
248 .path(objectPath)
249 .properties();
250 auto thresholdProperties =
251 co_await ThresholdCriticalIntf(ctx)
252 .service(serviceName)
253 .path(objectPath)
254 .properties();
255 verifyResult(properties, operationalProperties, availabilityProperties,
256 thresholdProperties, expectedValue,
257 sensorRegisters[0].unit);
258 co_return;
259 }
260};
261
262TEST_F(DeviceEventsTest, TestSensorReadingCritical)
263{
264 eventServer.expectedEvent =
265 "xyz.openbmc_project.Sensor.Threshold.ReadingCritical";
266
267 ctx.spawn(testSensorCreation(
268 objectPath, DeviceConfigIntf::StatusType::sensorReadingCritical,
269 TestIntf::testReadHoldingRegisterTempUnsigned[0]));
270
271 ctx.spawn(sdbusplus::async::sleep_for(ctx, 1s) |
272 sdbusplus::async::execution::then([&]() { ctx.request_stop(); }));
273
274 ctx.run();
275}
276
277TEST_F(DeviceEventsTest, TestSensorFailure)
278{
279 eventServer.expectedEvent = "xyz.openbmc_project.Sensor.SensorFailure";
280
281 ctx.spawn(testSensorCreation(objectPath,
282 DeviceConfigIntf::StatusType::sensorFailure,
283 std::numeric_limits<double>::quiet_NaN()));
284
285 ctx.spawn(sdbusplus::async::sleep_for(ctx, 1s) |
286 sdbusplus::async::execution::then([&]() { ctx.request_stop(); }));
287
288 ctx.run();
289}