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 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 removedDeviceIds = configCacheService.cleanupRemovedDevices(configs); for (Long deviceId : removedDeviceIds) { pollScheduler.stopPolling(deviceId); connectionManager.removeDevice(deviceId); } } catch (Exception e) { log.error("[refreshConfig][刷新配置失败]", e); } } }