1 #include "modbus/modbus.hpp"
2 #include "modbus/modbus_commands.hpp"
3 #include "modbus/modbus_message.hpp"
4
5 #include <fcntl.h>
6
7 #include <sdbusplus/async.hpp>
8
9 #include <iostream>
10 #include <string>
11
12 namespace phosphor::modbus::test
13 {
14
15 class MessageIntf : public phosphor::modbus::rtu::Message
16 {
17 friend class TestServer;
18 };
19
20 class TestServer
21 {
22 public:
23 explicit TestServer(sdbusplus::async::context& ctx, int fd);
24
25 private:
26 auto processRequests() -> sdbusplus::async::task<void>;
27 void processMessage(MessageIntf& request, size_t requestSize,
28 MessageIntf& response);
29
30 void processReadHoldingRegisters(MessageIntf& request, size_t requestSize,
31 MessageIntf& response);
32
33 sdbusplus::async::context& ctx;
34 int fd;
35 sdbusplus::async::fdio fdioInstance;
36 };
37
TestServer(sdbusplus::async::context & ctx,int fd)38 TestServer::TestServer(sdbusplus::async::context& ctx, int fd) :
39 ctx(ctx), fd(fd), fdioInstance(ctx, fd)
40 {
41 ctx.spawn(processRequests());
42 }
43
processRequests()44 auto TestServer::processRequests() -> sdbusplus::async::task<void>
45 {
46 while (!ctx.stop_requested())
47 {
48 MessageIntf request;
49 co_await fdioInstance.next();
50 auto ret = read(fd, request.raw.data(), request.raw.size());
51 // Request message need to be at least 4 bytes long - address(1),
52 // function code(1), ..., CRC(2)
53 if (ret < 4)
54 {
55 std::cerr << "Invalid Server message size:" << ret << ", drop it"
56 << std::endl;
57 continue;
58 }
59
60 MessageIntf response;
61 processMessage(request, ret, response);
62
63 ret = write(fd, response.raw.data(), response.len);
64 if (ret < 0)
65 {
66 std::cerr << "Failed to send response" << std::endl;
67 }
68 }
69 }
70
processMessage(MessageIntf & request,size_t requestSize,MessageIntf & response)71 void TestServer::processMessage(MessageIntf& request, size_t requestSize,
72 MessageIntf& response)
73 {
74 constexpr uint8_t readHoldingRegistersFunctionCode = 0x3;
75
76 switch (request.functionCode)
77 {
78 case readHoldingRegistersFunctionCode:
79 processReadHoldingRegisters(request, requestSize, response);
80 break;
81 default:
82 std::cerr << "Server received unknown request" << std::endl;
83 break;
84 }
85 }
86
processReadHoldingRegisters(MessageIntf & request,size_t requestSize,MessageIntf & response)87 void TestServer::processReadHoldingRegisters(
88 MessageIntf& request, size_t requestSize, MessageIntf& response)
89 {
90 uint16_t registerOffset = request.raw[2] << 8 | request.raw[3];
91 uint16_t registerCount = request.raw[4] << 8 | request.raw[5];
92
93 std::cout << "Received readHoldingRegisters request with size:"
94 << requestSize << ", registerOffset:" << registerOffset
95 << ", registerCount:" << registerCount << std::endl;
96
97 response << request.raw[0] << request.raw[1] << uint8_t(2 * registerCount);
98 for (int i = 0; i < registerCount; i++)
99 {
100 constexpr uint16_t readHoldingRegisterResponse = 0x4142;
101 response << uint16_t(readHoldingRegisterResponse);
102 }
103 response.appendCRC();
104 }
105
106 } // namespace phosphor::modbus::test
107
main(int argc,char * argv[])108 int main(int argc, char* argv[])
109 {
110 if (argc < 2)
111 {
112 std::cerr << "Usage: " << argv[0] << " <integer_value>" << std::endl;
113 return 1;
114 }
115
116 int interfaceValue = 1;
117 try
118 {
119 interfaceValue = std::stoi(argv[1]);
120 }
121 catch (const std::invalid_argument& e)
122 {
123 std::cerr << "Invalid argument: " << e.what() << std::endl;
124 return 1;
125 }
126 catch (const std::out_of_range& e)
127 {
128 std::cerr << "Argument out of range: " << e.what() << std::endl;
129 return 1;
130 }
131
132 using TestServerIntf = phosphor::modbus::test::TestServer;
133 std::string devicePathStr =
134 std::string("/dev/ttyV") + std::to_string(interfaceValue);
135 const char* serverDevicePath = devicePathStr.c_str();
136 std::cout << "Starting at device path" << serverDevicePath << std::endl;
137 constexpr auto path = "/xyz/openbmc_project";
138 constexpr auto serviceName = "xyz.openbmc_project.ModbusRTUTestServer";
139 sdbusplus::async::context ctx;
140
141 auto fdServer = open(serverDevicePath, O_RDWR | O_NOCTTY | O_NONBLOCK);
142 if (fdServer == -1)
143 {
144 std::cerr << "Failed to open serial port " << serverDevicePath
145 << " with error: " << strerror(errno) << std::endl;
146 return 1;
147 }
148
149 std::cout << "Creating Modbus RTU test server at " << path << std::endl;
150 TestServerIntf server{ctx, fdServer};
151
152 ctx.request_name(serviceName);
153
154 ctx.run();
155 return 0;
156 }
157