2026-06-26 20b96473f2520590a0dca6b775b81e3ea06a77a0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
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 从站模拟器(手动测试)
 *
 * <p>测试场景:模拟一个标准 Modbus TCP 从站设备,供 Modbus TCP Client 网关连接和读写数据
 *
 * <p>使用步骤:
 * <ol>
 *     <li>运行 {@link #testStartSlaveSimulator()} 启动模拟从站(默认端口 5020,从站地址 1)</li>
 *     <li>启动 yudao-module-iot-gateway 服务(需开启 modbus-tcp-client 协议)</li>
 *     <li>确保数据库有对应的 Modbus Client 设备配置(ip=127.0.0.1, port=5020, slaveId=1)</li>
 *     <li>网关会自动连接模拟从站并开始轮询读取寄存器数据</li>
 *     <li>模拟器每 5 秒自动更新输入寄存器和保持寄存器的值,模拟传感器数据变化</li>
 * </ol>
 *
 * <p>可用寄存器:
 * <ul>
 *     <li>线圈 (Coil, 功能码 01/05): 地址 0-9,交替 true/false</li>
 *     <li>离散输入 (Discrete Input, 功能码 02): 地址 0-9,每 3 个一个 true</li>
 *     <li>保持寄存器 (Holding Register, 功能码 03/06/16): 地址 0-19,初始值 0,100,200,...</li>
 *     <li>输入寄存器 (Input Register, 功能码 04): 地址 0-19,初始值 1,11,21,...</li>
 * </ul>
 *
 * @author 芋道源码
 */
@Slf4j
@Disabled
public class IoTModbusTcpClientIntegrationTest {
 
    private static final int PORT = 5020;
    private static final int SLAVE_ID = 1;
 
    /**
     * 启动 Modbus TCP 从站模拟器
     *
     * <p>模拟器会持续运行,每 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);
        }
    }
 
}