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
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
package cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpclient;
 
import cn.hutool.core.lang.Assert;
import cn.hutool.extra.spring.SpringUtil;
import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi;
import cn.iocoder.yudao.module.iot.core.biz.dto.IotModbusDeviceConfigRespDTO;
import cn.iocoder.yudao.module.iot.core.enums.IotProtocolTypeEnum;
import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus;
import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils;
import cn.iocoder.yudao.module.iot.gateway.config.IotGatewayProperties.ProtocolProperties;
import cn.iocoder.yudao.module.iot.gateway.protocol.IotProtocol;
import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpclient.handler.downstream.IotModbusTcpClientDownstreamHandler;
import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpclient.handler.downstream.IotModbusTcpClientDownstreamSubscriber;
import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpclient.handler.upstream.IotModbusTcpClientUpstreamHandler;
import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpclient.manager.IotModbusTcpClientConfigCacheService;
import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpclient.manager.IotModbusTcpClientConnectionManager;
import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpclient.manager.IotModbusTcpClientPollScheduler;
import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService;
import io.vertx.core.Vertx;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RedissonClient;
 
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
 
/**
 * IoT 网关 Modbus TCP Client 协议:主动轮询 Modbus 从站设备数据
 *
 * @author 芋道源码
 */
@Slf4j
public class IotModbusTcpClientProtocol implements IotProtocol {
 
    /**
     * 协议配置
     */
    private final ProtocolProperties properties;
    /**
     * 服务器 ID(用于消息追踪,全局唯一)
     */
    @Getter
    private final String serverId;
 
    /**
     * 运行状态
     */
    @Getter
    private volatile boolean running = false;
 
    /**
     * Vert.x 实例
     */
    private final Vertx vertx;
    /**
     * 配置刷新定时器 ID
     */
    private Long configRefreshTimerId;
 
    /**
     * 连接管理器
     */
    private final IotModbusTcpClientConnectionManager connectionManager;
    /**
     * 下行消息订阅者
     */
    private IotModbusTcpClientDownstreamSubscriber downstreamSubscriber;
 
    private final IotModbusTcpClientConfigCacheService configCacheService;
    private final IotModbusTcpClientPollScheduler pollScheduler;
 
    public IotModbusTcpClientProtocol(ProtocolProperties properties) {
        IotModbusTcpClientConfig modbusTcpClientConfig = properties.getModbusTcpClient();
        Assert.notNull(modbusTcpClientConfig, "Modbus TCP Client 协议配置(modbusTcpClient)不能为空");
        this.properties = properties;
        this.serverId = IotDeviceMessageUtils.generateServerId(properties.getPort());
 
        // 初始化 Vertx
        this.vertx = Vertx.vertx();
 
        // 初始化 Manager
        RedissonClient redissonClient = SpringUtil.getBean(RedissonClient.class);
        IotDeviceCommonApi deviceApi = SpringUtil.getBean(IotDeviceCommonApi.class);
        IotDeviceMessageService messageService = SpringUtil.getBean(IotDeviceMessageService.class);
        this.configCacheService = new IotModbusTcpClientConfigCacheService(deviceApi);
        this.connectionManager = new IotModbusTcpClientConnectionManager(redissonClient, vertx,
                messageService, configCacheService, serverId);
 
        // 初始化 Handler
        IotModbusTcpClientUpstreamHandler upstreamHandler = new IotModbusTcpClientUpstreamHandler(messageService, serverId);
 
        // 初始化轮询调度器
        this.pollScheduler = new IotModbusTcpClientPollScheduler(vertx, connectionManager, upstreamHandler, configCacheService);
    }
 
    @Override
    public String getId() {
        return properties.getId();
    }
 
    @Override
    public IotProtocolTypeEnum getType() {
        return IotProtocolTypeEnum.MODBUS_TCP_CLIENT;
    }
 
    @Override
    public void start() {
        if (running) {
            log.warn("[start][IoT Modbus TCP Client 协议 {} 已经在运行中]", getId());
            return;
        }
 
        try {
            // 1.1 首次加载配置
            refreshConfig();
            // 1.2 启动配置刷新定时器
            int refreshInterval = properties.getModbusTcpClient().getConfigRefreshInterval();
            configRefreshTimerId = vertx.setPeriodic(
                    TimeUnit.SECONDS.toMillis(refreshInterval),
                    id -> refreshConfig()
            );
            running = true;
            log.info("[start][IoT Modbus TCP Client 协议 {} 启动成功,serverId={}]", getId(), serverId);
 
            // 2. 启动下行消息订阅者
            IotMessageBus messageBus = SpringUtil.getBean(IotMessageBus.class);
            IotModbusTcpClientDownstreamHandler downstreamHandler = new IotModbusTcpClientDownstreamHandler(connectionManager,
                    configCacheService);
            this.downstreamSubscriber = new IotModbusTcpClientDownstreamSubscriber(this, downstreamHandler, messageBus);
            this.downstreamSubscriber.start();
        } catch (Exception e) {
            log.error("[start][IoT Modbus TCP Client 协议 {} 启动失败]", getId(), e);
            stop0();
            throw e;
        }
    }
 
    @Override
    public void stop() {
        if (!running) {
            return;
        }
        stop0();
    }
 
    private void stop0() {
        // 1. 停止下行消息订阅者
        if (downstreamSubscriber != null) {
            try {
                downstreamSubscriber.stop();
                log.info("[stop][IoT Modbus TCP Client 协议 {} 下行消息订阅者已停止]", getId());
            } catch (Exception e) {
                log.error("[stop][IoT Modbus TCP Client 协议 {} 下行消息订阅者停止失败]", getId(), e);
            }
            downstreamSubscriber = null;
        }
 
        // 2.1 取消配置刷新定时器
        if (configRefreshTimerId != null) {
            vertx.cancelTimer(configRefreshTimerId);
            configRefreshTimerId = null;
        }
        // 2.2 停止轮询调度器
        pollScheduler.stopAll();
        // 2.3 关闭所有连接
        connectionManager.closeAll();
 
        // 3. 关闭 Vert.x 实例
        if (vertx != null) {
            try {
                vertx.close().result();
                log.info("[stop][IoT Modbus TCP Client 协议 {} Vertx 已关闭]", getId());
            } catch (Exception e) {
                log.error("[stop][IoT Modbus TCP Client 协议 {} Vertx 关闭失败]", getId(), e);
            }
        }
        running = false;
        log.info("[stop][IoT Modbus TCP Client 协议 {} 已停止]", getId());
    }
 
    /**
     * 刷新配置
     */
    private synchronized void refreshConfig() {
        try {
            // 1. 从 biz 拉取最新配置(API 失败时返回 null)
            List<IotModbusDeviceConfigRespDTO> configs = configCacheService.refreshConfig();
            if (configs == null) {
                log.warn("[refreshConfig][API 失败,跳过本轮刷新]");
                return;
            }
            log.debug("[refreshConfig][获取到 {} 个 Modbus 设备配置]", configs.size());
 
            // 2. 更新连接和轮询任务
            for (IotModbusDeviceConfigRespDTO config : configs) {
                try {
                    // 2.1 确保连接存在
                    connectionManager.ensureConnection(config);
                    // 2.2 更新轮询任务
                    pollScheduler.updatePolling(config);
                } catch (Exception e) {
                    log.error("[refreshConfig][处理设备配置失败, deviceId={}]", config.getDeviceId(), e);
                }
            }
 
            // 3. 清理已删除设备的资源
            Set<Long> removedDeviceIds = configCacheService.cleanupRemovedDevices(configs);
            for (Long deviceId : removedDeviceIds) {
                pollScheduler.stopPolling(deviceId);
                connectionManager.removeDevice(deviceId);
            }
        } catch (Exception e) {
            log.error("[refreshConfig][刷新配置失败]", e);
        }
    }
 
}