package cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpclient; import com.ghgande.j2mod.modbus.procimg.*; import com.ghgande.j2mod.modbus.slave.ModbusSlave; import com.ghgande.j2mod.modbus.slave.ModbusSlaveFactory; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; /** * Modbus TCP 从站模拟器(手动测试) * *

测试场景:模拟一个标准 Modbus TCP 从站设备,供 Modbus TCP Client 网关连接和读写数据 * *

使用步骤: *

    *
  1. 运行 {@link #testStartSlaveSimulator()} 启动模拟从站(默认端口 5020,从站地址 1)
  2. *
  3. 启动 yudao-module-iot-gateway 服务(需开启 modbus-tcp-client 协议)
  4. *
  5. 确保数据库有对应的 Modbus Client 设备配置(ip=127.0.0.1, port=5020, slaveId=1)
  6. *
  7. 网关会自动连接模拟从站并开始轮询读取寄存器数据
  8. *
  9. 模拟器每 5 秒自动更新输入寄存器和保持寄存器的值,模拟传感器数据变化
  10. *
* *

可用寄存器: *

* * @author 芋道源码 */ @Slf4j @Disabled public class IoTModbusTcpClientIntegrationTest { private static final int PORT = 5020; private static final int SLAVE_ID = 1; /** * 启动 Modbus TCP 从站模拟器 * *

模拟器会持续运行,每 5 秒更新一次寄存器数据,直到手动停止 */ @SuppressWarnings({"InfiniteLoopStatement", "BusyWait"}) @Test public void testStartSlaveSimulator() throws Exception { // 1. 创建进程映像(Process Image),存储寄存器数据 SimpleProcessImage spi = new SimpleProcessImage(SLAVE_ID); // 2.1 初始化线圈(Coil,功能码 01/05)- 离散输出,可读写 // 地址 0-9,共 10 个线圈 for (int i = 0; i < 10; i++) { spi.addDigitalOut(new SimpleDigitalOut(i % 2 == 0)); // 交替 true/false } // 2.2 初始化离散输入(Discrete Input,功能码 02)- 只读 // 地址 0-9,共 10 个离散输入 for (int i = 0; i < 10; i++) { spi.addDigitalIn(new SimpleDigitalIn(i % 3 == 0)); // 每 3 个一个 true } // 2.3 初始化保持寄存器(Holding Register,功能码 03/06/16)- 可读写 // 地址 0-19,共 20 个寄存器 for (int i = 0; i < 20; i++) { spi.addRegister(new SimpleRegister(i * 100)); // 值为 0, 100, 200, ... } // 2.4 初始化输入寄存器(Input Register,功能码 04)- 只读 // 地址 0-19,共 20 个寄存器 SimpleInputRegister[] inputRegisters = new SimpleInputRegister[20]; for (int i = 0; i < 20; i++) { inputRegisters[i] = new SimpleInputRegister(i * 10 + 1); // 值为 1, 11, 21, ... spi.addInputRegister(inputRegisters[i]); } // 3.1 创建 Modbus TCP 从站 ModbusSlave slave = ModbusSlaveFactory.createTCPSlave(PORT, 5); slave.addProcessImage(SLAVE_ID, spi); // 3.2 启动从站 slave.open(); log.info("[testStartSlaveSimulator][Modbus TCP 从站模拟器已启动, 端口: {}, 从站地址: {}]", PORT, SLAVE_ID); log.info("[testStartSlaveSimulator][可用寄存器: 线圈(01/05) 0-9, 离散输入(02) 0-9, " + "保持寄存器(03/06/16) 0-19, 输入寄存器(04) 0-19]"); // 4. 添加关闭钩子 Runtime.getRuntime().addShutdownHook(new Thread(() -> { log.info("[testStartSlaveSimulator][正在关闭模拟器...]"); slave.close(); log.info("[testStartSlaveSimulator][模拟器已关闭]"); })); // 5. 保持运行,定时更新输入寄存器模拟数据变化 int counter = 0; while (true) { Thread.sleep(5000); // 每 5 秒更新一次 counter++; // 更新输入寄存器的值,模拟传感器数据变化 for (int i = 0; i < 20; i++) { int newValue = (i * 10 + 1) + counter; inputRegisters[i].setValue(newValue); } // 更新保持寄存器的第一个值 spi.getRegister(0).setValue(counter * 100); log.info("[testStartSlaveSimulator][数据已更新, counter={}, 保持寄存器[0]={}, 输入寄存器[0]={}]", counter, counter * 100, 1 + counter); } } }