#include "modbus/modbus.hpp" #include "modbus/modbus_commands.hpp" #include "modbus/modbus_message.hpp" #include #include #include #include namespace phosphor::modbus::test { class MessageIntf : public phosphor::modbus::rtu::Message { friend class TestServer; }; class TestServer { public: explicit TestServer(sdbusplus::async::context& ctx, int fd); private: auto processRequests() -> sdbusplus::async::task; void processMessage(MessageIntf& request, size_t requestSize, MessageIntf& response); void processReadHoldingRegisters(MessageIntf& request, size_t requestSize, MessageIntf& response); sdbusplus::async::context& ctx; int fd; sdbusplus::async::fdio fdioInstance; }; TestServer::TestServer(sdbusplus::async::context& ctx, int fd) : ctx(ctx), fd(fd), fdioInstance(ctx, fd) { ctx.spawn(processRequests()); } auto TestServer::processRequests() -> sdbusplus::async::task { while (!ctx.stop_requested()) { MessageIntf request; co_await fdioInstance.next(); auto ret = read(fd, request.raw.data(), request.raw.size()); // Request message need to be at least 4 bytes long - address(1), // function code(1), ..., CRC(2) if (ret < 4) { std::cerr << "Invalid Server message size:" << ret << ", drop it" << std::endl; continue; } MessageIntf response; processMessage(request, ret, response); ret = write(fd, response.raw.data(), response.len); if (ret < 0) { std::cerr << "Failed to send response" << std::endl; } } } void TestServer::processMessage(MessageIntf& request, size_t requestSize, MessageIntf& response) { constexpr uint8_t readHoldingRegistersFunctionCode = 0x3; switch (request.functionCode) { case readHoldingRegistersFunctionCode: processReadHoldingRegisters(request, requestSize, response); break; default: std::cerr << "Server received unknown request" << std::endl; break; } } void TestServer::processReadHoldingRegisters( MessageIntf& request, size_t requestSize, MessageIntf& response) { uint16_t registerOffset = request.raw[2] << 8 | request.raw[3]; uint16_t registerCount = request.raw[4] << 8 | request.raw[5]; std::cout << "Received readHoldingRegisters request with size:" << requestSize << ", registerOffset:" << registerOffset << ", registerCount:" << registerCount << std::endl; response << request.raw[0] << request.raw[1] << uint8_t(2 * registerCount); for (int i = 0; i < registerCount; i++) { constexpr uint16_t readHoldingRegisterResponse = 0x4142; response << uint16_t(readHoldingRegisterResponse); } response.appendCRC(); } } // namespace phosphor::modbus::test int main(int argc, char* argv[]) { if (argc < 2) { std::cerr << "Usage: " << argv[0] << " " << std::endl; return 1; } int interfaceValue = 1; try { interfaceValue = std::stoi(argv[1]); } catch (const std::invalid_argument& e) { std::cerr << "Invalid argument: " << e.what() << std::endl; return 1; } catch (const std::out_of_range& e) { std::cerr << "Argument out of range: " << e.what() << std::endl; return 1; } using TestServerIntf = phosphor::modbus::test::TestServer; std::string devicePathStr = std::string("/dev/ttyV") + std::to_string(interfaceValue); const char* serverDevicePath = devicePathStr.c_str(); std::cout << "Starting at device path" << serverDevicePath << std::endl; constexpr auto path = "/xyz/openbmc_project"; constexpr auto serviceName = "xyz.openbmc_project.ModbusRTUTestServer"; sdbusplus::async::context ctx; auto fdServer = open(serverDevicePath, O_RDWR | O_NOCTTY | O_NONBLOCK); if (fdServer == -1) { std::cerr << "Failed to open serial port " << serverDevicePath << " with error: " << strerror(errno) << std::endl; return 1; } std::cout << "Creating Modbus RTU test server at " << path << std::endl; TestServerIntf server{ctx, fdServer}; ctx.request_name(serviceName); ctx.run(); return 0; }