Commit 4d578b239c8a1a7f3f2cc7c16f094787bac4169d

Authored by YunaiV
1 parent 3ab33527

feat(iot):modbus-tcp-slave 整体代码进一步优化

yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpslave/IotModbusTcpSlaveProtocol.java
1 package cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpslave; 1 package cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpslave;
2 2
  3 +import cn.hutool.core.collection.CollUtil;
3 import cn.hutool.core.lang.Assert; 4 import cn.hutool.core.lang.Assert;
4 import cn.hutool.extra.spring.SpringUtil; 5 import cn.hutool.extra.spring.SpringUtil;
5 import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi; 6 import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi;
6 import cn.iocoder.yudao.module.iot.core.biz.dto.IotModbusDeviceConfigRespDTO; 7 import cn.iocoder.yudao.module.iot.core.biz.dto.IotModbusDeviceConfigRespDTO;
7 -import cn.iocoder.yudao.module.iot.core.enums.IotModbusModeEnum;  
8 import cn.iocoder.yudao.module.iot.core.enums.IotProtocolTypeEnum; 8 import cn.iocoder.yudao.module.iot.core.enums.IotProtocolTypeEnum;
9 import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus; 9 import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus;
10 import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils; 10 import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils;
@@ -32,9 +32,9 @@ import lombok.Getter; @@ -32,9 +32,9 @@ import lombok.Getter;
32 import lombok.extern.slf4j.Slf4j; 32 import lombok.extern.slf4j.Slf4j;
33 33
34 import java.util.List; 34 import java.util.List;
  35 +import java.util.Set;
35 import java.util.concurrent.TimeUnit; 36 import java.util.concurrent.TimeUnit;
36 37
37 -// DONE @AI:不用主动上报!  
38 /** 38 /**
39 * IoT 网关 Modbus TCP Slave 协议 39 * IoT 网关 Modbus TCP Slave 协议
40 * <p> 40 * <p>
@@ -152,10 +152,7 @@ public class IotModbusTcpSlaveProtocol implements IotProtocol { @@ -152,10 +152,7 @@ public class IotModbusTcpSlaveProtocol implements IotProtocol {
152 } 152 }
153 153
154 try { 154 try {
155 - // 1.1 首次加载配置  
156 - // TODO @AI:可能首次不用加载;你在想想;  
157 - refreshConfig();  
158 - // 1.2 启动配置刷新定时器 155 + // 1. 启动配置刷新定时器
159 int refreshInterval = slaveConfig.getConfigRefreshInterval(); 156 int refreshInterval = slaveConfig.getConfigRefreshInterval();
160 configRefreshTimerId = vertx.setPeriodic( 157 configRefreshTimerId = vertx.setPeriodic(
161 TimeUnit.SECONDS.toMillis(refreshInterval), 158 TimeUnit.SECONDS.toMillis(refreshInterval),
@@ -286,6 +283,7 @@ public class IotModbusTcpSlaveProtocol implements IotProtocol { @@ -286,6 +283,7 @@ public class IotModbusTcpSlaveProtocol implements IotProtocol {
286 } 283 }
287 pollScheduler.stopPolling(info.getDeviceId()); 284 pollScheduler.stopPolling(info.getDeviceId());
288 pendingRequestManager.removeDevice(info.getDeviceId()); 285 pendingRequestManager.removeDevice(info.getDeviceId());
  286 + configCacheService.removeConfig(info.getDeviceId());
289 log.info("[handleConnection][连接关闭, deviceId={}, remoteAddress={}]", 287 log.info("[handleConnection][连接关闭, deviceId={}, remoteAddress={}]",
290 info.getDeviceId(), socket.remoteAddress()); 288 info.getDeviceId(), socket.remoteAddress());
291 }); 289 });
@@ -297,35 +295,30 @@ public class IotModbusTcpSlaveProtocol implements IotProtocol { @@ -297,35 +295,30 @@ public class IotModbusTcpSlaveProtocol implements IotProtocol {
297 } 295 }
298 296
299 /** 297 /**
300 - * 刷新配置 298 + * 刷新已连接设备的配置(定时调用)
  299 + * <p>
  300 + * 与 tcpmaster 不同,slave 只刷新已连接设备的配置,不做全量 diff。
  301 + * 设备的新增(认证时)和删除(断连时)分别在 {@link #handleConnection} 中处理。
301 */ 302 */
302 private synchronized void refreshConfig() { 303 private synchronized void refreshConfig() {
303 try { 304 try {
304 - // 1. 从 biz 拉取最新配置  
305 - List<IotModbusDeviceConfigRespDTO> configs = configCacheService.refreshConfig();  
306 - log.debug("[refreshConfig][获取到 {} 个 Modbus 设备配置]", configs.size()); 305 + // 1. 只刷新已连接设备的配置
  306 + Set<Long> connectedDeviceIds = connectionManager.getConnectedDeviceIds();
  307 + if (CollUtil.isEmpty(connectedDeviceIds)) {
  308 + return;
  309 + }
  310 + List<IotModbusDeviceConfigRespDTO> configs =
  311 + configCacheService.refreshConnectedDeviceConfigList(connectedDeviceIds);
  312 + log.debug("[refreshConfig][刷新了 {} 个已连接设备的配置]", configs.size());
307 313
308 - // 2. 更新已连接设备的轮询任务(仅 mode=1) 314 + // 2. 更新已连接设备的轮询任务
309 for (IotModbusDeviceConfigRespDTO config : configs) { 315 for (IotModbusDeviceConfigRespDTO config : configs) {
310 try { 316 try {
311 - if (config.getMode() != null  
312 - && config.getMode().equals(IotModbusModeEnum.POLLING.getMode())) {  
313 - // 只有已连接的设备才启动轮询  
314 - ConnectionInfo connInfo = connectionManager.getConnectionInfoByDeviceId(config.getDeviceId());  
315 - if (connInfo != null) {  
316 - pollScheduler.updatePolling(config);  
317 - }  
318 - } 317 + pollScheduler.updatePolling(config);
319 } catch (Exception e) { 318 } catch (Exception e) {
320 log.error("[refreshConfig][处理设备配置失败, deviceId={}]", config.getDeviceId(), e); 319 log.error("[refreshConfig][处理设备配置失败, deviceId={}]", config.getDeviceId(), e);
321 } 320 }
322 } 321 }
323 -  
324 - // 3. 清理已删除设备的资源  
325 - configCacheService.cleanupRemovedDevices(configs, deviceId -> {  
326 - pollScheduler.stopPolling(deviceId);  
327 - pendingRequestManager.removeDevice(deviceId);  
328 - });  
329 } catch (Exception e) { 322 } catch (Exception e) {
330 log.error("[refreshConfig][刷新配置失败]", e); 323 log.error("[refreshConfig][刷新配置失败]", e);
331 } 324 }
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpslave/handler/upstream/IotModbusTcpSlaveUpstreamHandler.java
@@ -16,7 +16,6 @@ import cn.iocoder.yudao.module.iot.core.biz.dto.IotModbusDeviceConfigRespDTO; @@ -16,7 +16,6 @@ import cn.iocoder.yudao.module.iot.core.biz.dto.IotModbusDeviceConfigRespDTO;
16 import cn.iocoder.yudao.module.iot.core.biz.dto.IotModbusPointRespDTO; 16 import cn.iocoder.yudao.module.iot.core.biz.dto.IotModbusPointRespDTO;
17 import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum; 17 import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
18 import cn.iocoder.yudao.module.iot.core.enums.IotModbusFrameFormatEnum; 18 import cn.iocoder.yudao.module.iot.core.enums.IotModbusFrameFormatEnum;
19 -import cn.iocoder.yudao.module.iot.core.enums.IotModbusModeEnum;  
20 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; 19 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
21 import cn.iocoder.yudao.module.iot.core.topic.IotDeviceIdentity; 20 import cn.iocoder.yudao.module.iot.core.topic.IotDeviceIdentity;
22 import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils; 21 import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils;
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpslave/manager/IotModbusTcpSlaveConfigCacheService.java
1 package cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpslave.manager; 1 package cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpslave.manager;
2 2
  3 +import cn.hutool.core.collection.CollUtil;
3 import cn.iocoder.yudao.framework.common.pojo.CommonResult; 4 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4 import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi; 5 import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi;
5 import cn.iocoder.yudao.module.iot.core.biz.dto.IotModbusDeviceConfigRespDTO; 6 import cn.iocoder.yudao.module.iot.core.biz.dto.IotModbusDeviceConfigRespDTO;
@@ -10,15 +11,18 @@ import lombok.extern.slf4j.Slf4j; @@ -10,15 +11,18 @@ import lombok.extern.slf4j.Slf4j;
10 import java.math.BigDecimal; 11 import java.math.BigDecimal;
11 import java.util.*; 12 import java.util.*;
12 import java.util.concurrent.ConcurrentHashMap; 13 import java.util.concurrent.ConcurrentHashMap;
13 -import java.util.function.Consumer;  
14 14
15 -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;  
16 -  
17 -// TODO @AI:和 IotModbusTcpConfigCacheService 基本一致?!  
18 /** 15 /**
19 * IoT Modbus TCP Slave 配置缓存服务 16 * IoT Modbus TCP Slave 配置缓存服务
20 * <p> 17 * <p>
21 - * 负责:从 biz 拉取 Modbus 设备配置,缓存配置数据,并检测配置变更 18 + * 与 tcpmaster 的 {@code IotModbusTcpConfigCacheService} 不同:
  19 + * - tcpmaster 启动时拉全量配置 → 主动建连,需要全量 diff 检测新增/删除设备
  20 + * - tcpslave 设备主动连接 → 认证时按需加载配置,断连时清理,定时刷新已连接设备的配置
  21 + * <p>
  22 + * 配置生命周期:
  23 + * 1. 认证时:{@link #loadDeviceConfig(Long)} 按 deviceId 从 API 加载配置到缓存
  24 + * 2. 断连时:{@link #removeConfig(Long)} 从缓存中移除
  25 + * 3. 定时刷新:{@link #refreshConnectedDeviceConfigList(Set)} 批量刷新已连接设备的配置
22 * 26 *
23 * @author 芋道源码 27 * @author 芋道源码
24 */ 28 */
@@ -33,43 +37,118 @@ public class IotModbusTcpSlaveConfigCacheService { @@ -33,43 +37,118 @@ public class IotModbusTcpSlaveConfigCacheService {
33 */ 37 */
34 private final Map<Long, IotModbusDeviceConfigRespDTO> configCache = new ConcurrentHashMap<>(); 38 private final Map<Long, IotModbusDeviceConfigRespDTO> configCache = new ConcurrentHashMap<>();
35 39
36 - // TODO @AI:它的 diff 算法,是不是不用和 IotModbusTcpConfigCacheService 完全一致;更多是1)首次连接时,查找;2)断开连接,移除;3)定时轮询更新; 40 + // ==================== 按需加载(认证时) ====================
  41 +
37 /** 42 /**
38 - * 已知的设备 ID 集合 43 + * 加载单个设备的配置(认证成功后调用)
  44 + * <p>
  45 + * 从远程 API 获取全量配置,然后按 deviceId 匹配。
  46 + * 如果远程获取失败,尝试从 Mock 数据中匹配。
  47 + *
  48 + * @param deviceId 设备 ID
  49 + * @return 设备配置,未找到返回 null
39 */ 50 */
40 - private final Set<Long> knownDeviceIds = ConcurrentHashMap.newKeySet(); 51 + public IotModbusDeviceConfigRespDTO loadDeviceConfig(Long deviceId) {
  52 + try {
  53 + // 1. 从远程 API 获取全量配置
  54 + // TODO @AI:等待修复,不着急;
  55 + CommonResult<List<IotModbusDeviceConfigRespDTO>> result = deviceApi.getEnabledModbusDeviceConfigs();
  56 + if (result != null && result.isSuccess() && result.getData() != null) {
  57 + for (IotModbusDeviceConfigRespDTO config : result.getData()) {
  58 + // 顺便更新缓存(其他已连接设备也受益)
  59 + configCache.put(config.getDeviceId(), config);
  60 + if (config.getDeviceId().equals(deviceId)) {
  61 + return config;
  62 + }
  63 + }
  64 + }
  65 + } catch (Exception e) {
  66 + log.error("[loadDeviceConfig][从远程获取配置失败, deviceId={}]", deviceId, e);
  67 + }
  68 +
  69 + // 2. 远程未找到,尝试 Mock 数据
  70 + // TODO @芋艿:测试完成后移除
  71 + for (IotModbusDeviceConfigRespDTO mockConfig : buildMockConfigs()) {
  72 + configCache.put(mockConfig.getDeviceId(), mockConfig);
  73 + if (mockConfig.getDeviceId().equals(deviceId)) {
  74 + return mockConfig;
  75 + }
  76 + }
  77 +
  78 + return configCache.get(deviceId);
  79 + }
  80 +
  81 + // ==================== 定时刷新(已连接设备) ====================
41 82
42 /** 83 /**
43 - * 刷新配置 84 + * 刷新已连接设备的配置缓存
  85 + * <p>
  86 + * 定时调用,从远程 API 拉取最新配置,只更新已连接设备的缓存。
44 * 87 *
45 - * @return 最新的配置列表 88 + * @param connectedDeviceIds 当前已连接的设备 ID 集合
  89 + * @return 已连接设备的最新配置列表
46 */ 90 */
47 - public List<IotModbusDeviceConfigRespDTO> refreshConfig() { 91 + public List<IotModbusDeviceConfigRespDTO> refreshConnectedDeviceConfigList(Set<Long> connectedDeviceIds) {
  92 + if (CollUtil.isEmpty(connectedDeviceIds)) {
  93 + return Collections.emptyList();
  94 + }
48 try { 95 try {
49 - // 1. 从远程获取配置  
50 - // TODO @AI:需要过滤下,只查找连接的设备列表;并且只有主动轮询的,才会处理;方法名,应该是 List 结尾; 96 + // 1. 从远程获取全量配置
  97 + // TODO @AI:传递 ids 批量查询;需要分批啦;
51 CommonResult<List<IotModbusDeviceConfigRespDTO>> result = deviceApi.getEnabledModbusDeviceConfigs(); 98 CommonResult<List<IotModbusDeviceConfigRespDTO>> result = deviceApi.getEnabledModbusDeviceConfigs();
52 - if (result == null || !result.isSuccess() || result.getData() == null) {  
53 - log.warn("[refreshConfig][获取 Modbus 配置失败: {}]", result);  
54 - return new ArrayList<>(configCache.values()); 99 + List<IotModbusDeviceConfigRespDTO> allConfigs;
  100 + if (result != null && result.isSuccess() && result.getData() != null) {
  101 + allConfigs = new ArrayList<>(result.getData());
  102 + } else {
  103 + log.warn("[refreshConnectedDeviceConfigList][获取 Modbus 配置失败: {}]", result);
  104 + allConfigs = new ArrayList<>();
55 } 105 }
56 - List<IotModbusDeviceConfigRespDTO> configs = new ArrayList<>(result.getData());  
57 106
58 - // 2. 追加 Mock 测试数据(一次性测试用途) 107 + // 2. 追加 Mock 测试数据
59 // TODO @芋艿:测试完成后移除 108 // TODO @芋艿:测试完成后移除
60 - configs.addAll(buildMockConfigs());  
61 -  
62 - // 3. 更新缓存  
63 - for (IotModbusDeviceConfigRespDTO config : configs) {  
64 - configCache.put(config.getDeviceId(), config); 109 + allConfigs.addAll(buildMockConfigs());
  110 +
  111 + // 3. 只保留已连接设备的配置,更新缓存
  112 + List<IotModbusDeviceConfigRespDTO> connectedConfigs = new ArrayList<>();
  113 + for (IotModbusDeviceConfigRespDTO config : allConfigs) {
  114 + if (connectedDeviceIds.contains(config.getDeviceId())) {
  115 + configCache.put(config.getDeviceId(), config);
  116 + connectedConfigs.add(config);
  117 + }
65 } 118 }
66 - return configs; 119 + return connectedConfigs;
67 } catch (Exception e) { 120 } catch (Exception e) {
68 - log.error("[refreshConfig][刷新配置失败]", e);  
69 - return new ArrayList<>(configCache.values()); 121 + log.error("[refreshConnectedDeviceConfigList][刷新配置失败]", e);
  122 + // 降级:返回缓存中已连接设备的配置
  123 + List<IotModbusDeviceConfigRespDTO> fallback = new ArrayList<>();
  124 + for (Long deviceId : connectedDeviceIds) {
  125 + IotModbusDeviceConfigRespDTO config = configCache.get(deviceId);
  126 + if (config != null) {
  127 + fallback.add(config);
  128 + }
  129 + }
  130 + return fallback;
70 } 131 }
71 } 132 }
72 133
  134 + // ==================== 缓存操作 ====================
  135 +
  136 + /**
  137 + * 获取设备配置
  138 + */
  139 + public IotModbusDeviceConfigRespDTO getConfig(Long deviceId) {
  140 + return configCache.get(deviceId);
  141 + }
  142 +
  143 + /**
  144 + * 移除设备配置缓存(设备断连时调用)
  145 + */
  146 + public void removeConfig(Long deviceId) {
  147 + configCache.remove(deviceId);
  148 + }
  149 +
  150 + // ==================== Mock 数据 ====================
  151 +
73 /** 152 /**
74 * 构建 Mock 测试配置数据(一次性测试用途) 153 * 构建 Mock 测试配置数据(一次性测试用途)
75 * 154 *
@@ -123,50 +202,4 @@ public class IotModbusTcpSlaveConfigCacheService { @@ -123,50 +202,4 @@ public class IotModbusTcpSlaveConfigCacheService {
123 return Collections.singletonList(config); 202 return Collections.singletonList(config);
124 } 203 }
125 204
126 - /**  
127 - * 获取设备配置  
128 - */  
129 - public IotModbusDeviceConfigRespDTO getConfig(Long deviceId) {  
130 - return configCache.get(deviceId);  
131 - }  
132 -  
133 - // TODO @AI:这个逻辑,是不是非必须?  
134 - /**  
135 - * 通过 clientId + username + password 查找设备配置(认证用)  
136 - * 暂通过遍历缓存实现,后续可优化为索引  
137 - */  
138 - public IotModbusDeviceConfigRespDTO findConfigByAuth(String clientId, String username, String password) {  
139 - // TODO @芋艿:测试完成后移除 mock 逻辑,改为正式查找  
140 - // Mock:通过 clientId(格式 productKey.deviceName)匹配缓存中的设备  
141 - if (clientId != null && clientId.contains(".")) {  
142 - String[] parts = clientId.split("\\.", 2);  
143 - String productKey = parts[0];  
144 - String deviceName = parts[1];  
145 - for (IotModbusDeviceConfigRespDTO config : configCache.values()) {  
146 - if (productKey.equals(config.getProductKey()) && deviceName.equals(config.getDeviceName())) {  
147 - return config;  
148 - }  
149 - }  
150 - }  
151 - return null;  
152 - }  
153 -  
154 - /**  
155 - * 清理已删除设备的资源  
156 - */  
157 - public void cleanupRemovedDevices(List<IotModbusDeviceConfigRespDTO> currentConfigs, Consumer<Long> cleanupAction) {  
158 - Set<Long> currentDeviceIds = convertSet(currentConfigs, IotModbusDeviceConfigRespDTO::getDeviceId);  
159 - Set<Long> removedDeviceIds = new HashSet<>(knownDeviceIds);  
160 - removedDeviceIds.removeAll(currentDeviceIds);  
161 -  
162 - for (Long deviceId : removedDeviceIds) {  
163 - log.info("[cleanupRemovedDevices][清理已删除设备: {}]", deviceId);  
164 - configCache.remove(deviceId);  
165 - cleanupAction.accept(deviceId);  
166 - }  
167 -  
168 - knownDeviceIds.clear();  
169 - knownDeviceIds.addAll(currentDeviceIds);  
170 - }  
171 -  
172 } 205 }
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpslave/manager/IotModbusTcpSlaveConnectionManager.java
@@ -4,9 +4,11 @@ import cn.iocoder.yudao.module.iot.core.enums.IotModbusFrameFormatEnum; @@ -4,9 +4,11 @@ import cn.iocoder.yudao.module.iot.core.enums.IotModbusFrameFormatEnum;
4 import io.vertx.core.buffer.Buffer; 4 import io.vertx.core.buffer.Buffer;
5 import io.vertx.core.net.NetSocket; 5 import io.vertx.core.net.NetSocket;
6 import lombok.Data; 6 import lombok.Data;
  7 +import lombok.experimental.Accessors;
7 import lombok.extern.slf4j.Slf4j; 8 import lombok.extern.slf4j.Slf4j;
8 9
9 import java.util.Map; 10 import java.util.Map;
  11 +import java.util.Set;
10 import java.util.concurrent.ConcurrentHashMap; 12 import java.util.concurrent.ConcurrentHashMap;
11 13
12 /** 14 /**
@@ -33,6 +35,7 @@ public class IotModbusTcpSlaveConnectionManager { @@ -33,6 +35,7 @@ public class IotModbusTcpSlaveConnectionManager {
33 * 连接信息 35 * 连接信息
34 */ 36 */
35 @Data 37 @Data
  38 + @Accessors(chain = true)
36 public static class ConnectionInfo { 39 public static class ConnectionInfo {
37 40
38 /** 41 /**
@@ -57,12 +60,6 @@ public class IotModbusTcpSlaveConnectionManager { @@ -57,12 +60,6 @@ public class IotModbusTcpSlaveConnectionManager {
57 */ 60 */
58 private IotModbusFrameFormatEnum frameFormat; 61 private IotModbusFrameFormatEnum frameFormat;
59 62
60 - // TODO @AI:mode 是否非必须?!  
61 - /**  
62 - * 模式:1-云端轮询 2-主动上报  
63 - */  
64 - private Integer mode;  
65 -  
66 } 63 }
67 64
68 /** 65 /**
@@ -75,17 +72,6 @@ public class IotModbusTcpSlaveConnectionManager { @@ -75,17 +72,6 @@ public class IotModbusTcpSlaveConnectionManager {
75 info.getDeviceId(), socket.remoteAddress()); 72 info.getDeviceId(), socket.remoteAddress());
76 } 73 }
77 74
78 - // TODO @芋艿:待定是不是要保留?!  
79 - /**  
80 - * 设置连接的帧格式(首帧检测后调用)  
81 - */  
82 - public void setFrameFormat(NetSocket socket, IotModbusFrameFormatEnum frameFormat) {  
83 - ConnectionInfo info = connectionMap.get(socket);  
84 - if (info != null) {  
85 - info.setFrameFormat(frameFormat);  
86 - }  
87 - }  
88 -  
89 /** 75 /**
90 * 获取连接信息 76 * 获取连接信息
91 */ 77 */
@@ -101,12 +87,11 @@ public class IotModbusTcpSlaveConnectionManager { @@ -101,12 +87,11 @@ public class IotModbusTcpSlaveConnectionManager {
101 return socket != null ? connectionMap.get(socket) : null; 87 return socket != null ? connectionMap.get(socket) : null;
102 } 88 }
103 89
104 - // TODO @AI:不用判断连接是否认证;  
105 /** 90 /**
106 - * 判断连接是否已认证 91 + * 获取所有已连接设备的 ID 集合
107 */ 92 */
108 - public boolean isAuthenticated(NetSocket socket) {  
109 - return connectionMap.containsKey(socket); 93 + public Set<Long> getConnectedDeviceIds() {
  94 + return deviceSocketMap.keySet();
110 } 95 }
111 96
112 /** 97 /**
@@ -130,8 +115,7 @@ public class IotModbusTcpSlaveConnectionManager { @@ -130,8 +115,7 @@ public class IotModbusTcpSlaveConnectionManager {
130 log.warn("[sendToDevice][设备 {} 没有连接]", deviceId); 115 log.warn("[sendToDevice][设备 {} 没有连接]", deviceId);
131 return; 116 return;
132 } 117 }
133 - // TODO @AI:直接复用 sendToSocket 方法?!  
134 - socket.write(Buffer.buffer(data)); 118 + sendToSocket(socket, data);
135 } 119 }
136 120
137 /** 121 /**
@@ -141,7 +125,6 @@ public class IotModbusTcpSlaveConnectionManager { @@ -141,7 +125,6 @@ public class IotModbusTcpSlaveConnectionManager {
141 socket.write(Buffer.buffer(data)); 125 socket.write(Buffer.buffer(data));
142 } 126 }
143 127
144 - // TODO @AI:貌似别的都没这个,是不是可以去掉哈?!  
145 /** 128 /**
146 * 关闭所有连接 129 * 关闭所有连接
147 */ 130 */
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpslave/manager/IotModbusTcpSlavePendingRequestManager.java
1 package cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpslave.manager; 1 package cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpslave.manager;
2 2
  3 +import cn.hutool.core.collection.CollUtil;
3 import cn.iocoder.yudao.module.iot.core.enums.IotModbusFrameFormatEnum; 4 import cn.iocoder.yudao.module.iot.core.enums.IotModbusFrameFormatEnum;
4 import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpslave.codec.IotModbusFrame; 5 import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpslave.codec.IotModbusFrame;
5 import lombok.AllArgsConstructor; 6 import lombok.AllArgsConstructor;
@@ -7,6 +8,7 @@ import lombok.Data; @@ -7,6 +8,7 @@ import lombok.Data;
7 import lombok.extern.slf4j.Slf4j; 8 import lombok.extern.slf4j.Slf4j;
8 9
9 import java.util.Deque; 10 import java.util.Deque;
  11 +import java.util.Iterator;
10 import java.util.Map; 12 import java.util.Map;
11 import java.util.concurrent.ConcurrentHashMap; 13 import java.util.concurrent.ConcurrentHashMap;
12 import java.util.concurrent.ConcurrentLinkedDeque; 14 import java.util.concurrent.ConcurrentLinkedDeque;
@@ -66,26 +68,24 @@ public class IotModbusTcpSlavePendingRequestManager { @@ -66,26 +68,24 @@ public class IotModbusTcpSlavePendingRequestManager {
66 public PendingRequest matchResponse(Long deviceId, IotModbusFrame frame, 68 public PendingRequest matchResponse(Long deviceId, IotModbusFrame frame,
67 IotModbusFrameFormatEnum frameFormat) { 69 IotModbusFrameFormatEnum frameFormat) {
68 Deque<PendingRequest> queue = pendingRequests.get(deviceId); 70 Deque<PendingRequest> queue = pendingRequests.get(deviceId);
69 - // TODO @AI:CollUtil.isEmpty(queue)  
70 - if (queue == null || queue.isEmpty()) { 71 + if (CollUtil.isEmpty(queue)) {
71 return null; 72 return null;
72 } 73 }
73 74
  75 + // TCP 模式:按 transactionId 精确匹配
74 if (frameFormat == IotModbusFrameFormatEnum.MODBUS_TCP && frame.getTransactionId() != null) { 76 if (frameFormat == IotModbusFrameFormatEnum.MODBUS_TCP && frame.getTransactionId() != null) {
75 - // TCP 模式:按 transactionId 精确匹配  
76 return matchByTransactionId(queue, frame.getTransactionId()); 77 return matchByTransactionId(queue, frame.getTransactionId());
77 - } else {  
78 - // RTU 模式:FIFO,匹配 slaveId + functionCode  
79 - return matchByFifo(queue, frame.getSlaveId(), frame.getFunctionCode());  
80 } 78 }
  79 + // RTU 模式:FIFO,匹配 slaveId + functionCode
  80 + return matchByFifo(queue, frame.getSlaveId(), frame.getFunctionCode());
81 } 81 }
82 82
83 /** 83 /**
84 * 按 transactionId 匹配 84 * 按 transactionId 匹配
85 */ 85 */
86 private PendingRequest matchByTransactionId(Deque<PendingRequest> queue, int transactionId) { 86 private PendingRequest matchByTransactionId(Deque<PendingRequest> queue, int transactionId) {
87 - // TODO @AI:需要兼容 jdk8;  
88 - for (var it = queue.iterator(); it.hasNext(); ) { 87 + Iterator<PendingRequest> it = queue.iterator();
  88 + while (it.hasNext()) {
89 PendingRequest req = it.next(); 89 PendingRequest req = it.next();
90 if (req.getTransactionId() != null && req.getTransactionId() == transactionId) { 90 if (req.getTransactionId() != null && req.getTransactionId() == transactionId) {
91 it.remove(); 91 it.remove();
@@ -99,8 +99,8 @@ public class IotModbusTcpSlavePendingRequestManager { @@ -99,8 +99,8 @@ public class IotModbusTcpSlavePendingRequestManager {
99 * 按 FIFO 匹配 99 * 按 FIFO 匹配
100 */ 100 */
101 private PendingRequest matchByFifo(Deque<PendingRequest> queue, int slaveId, int functionCode) { 101 private PendingRequest matchByFifo(Deque<PendingRequest> queue, int slaveId, int functionCode) {
102 - // TODO @AI:需要兼容 jdk8;  
103 - for (var it = queue.iterator(); it.hasNext(); ) { 102 + Iterator<PendingRequest> it = queue.iterator();
  103 + while (it.hasNext()) {
104 PendingRequest req = it.next(); 104 PendingRequest req = it.next();
105 if (req.getSlaveId() == slaveId && req.getFunctionCode() == functionCode) { 105 if (req.getSlaveId() == slaveId && req.getFunctionCode() == functionCode) {
106 it.remove(); 106 it.remove();
@@ -120,13 +120,11 @@ public class IotModbusTcpSlavePendingRequestManager { @@ -120,13 +120,11 @@ public class IotModbusTcpSlavePendingRequestManager {
120 int removed = 0; 120 int removed = 0;
121 while (!queue.isEmpty()) { 121 while (!queue.isEmpty()) {
122 PendingRequest req = queue.peekFirst(); 122 PendingRequest req = queue.peekFirst();
123 - // TODO @AI:if return 减少括号层级;  
124 - if (req != null && req.getExpireAt() < now) {  
125 - queue.pollFirst();  
126 - removed++;  
127 - } else { 123 + if (req == null || req.getExpireAt() >= now) {
128 break; // 队列有序,后面的没过期 124 break; // 队列有序,后面的没过期
129 } 125 }
  126 + queue.pollFirst();
  127 + removed++;
130 } 128 }
131 if (removed > 0) { 129 if (removed > 0) {
132 log.debug("[cleanupExpired][设备 {} 清理了 {} 个过期请求]", entry.getKey(), removed); 130 log.debug("[cleanupExpired][设备 {} 清理了 {} 个过期请求]", entry.getKey(), removed);
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpslave/manager/IotModbusTcpSlavePollScheduler.java
@@ -19,12 +19,11 @@ import java.util.concurrent.atomic.AtomicInteger; @@ -19,12 +19,11 @@ import java.util.concurrent.atomic.AtomicInteger;
19 19
20 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; 20 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
21 21
22 -// TODO @AI:和 IotModbusTcpPollScheduler 很像,是不是可以做一些复用?  
23 /** 22 /**
24 * IoT Modbus TCP Slave 轮询调度器 23 * IoT Modbus TCP Slave 轮询调度器
25 * <p> 24 * <p>
26 - * 管理点位的轮询定时器,为 mode=1(云端轮询)的设备调度读取任务。  
27 - * 与 tcpmaster 不同,这里不直接通过 j2mod 读取,而是: 25 + * 管理点位的轮询定时器,为云端轮询模式的设备调度读取任务。
  26 + * 与 tcpmaster 的 {@code IotModbusTcpPollScheduler} 不同,这里不通过 j2mod 直接读取,而是:
28 * 1. 编码 Modbus 读请求帧 27 * 1. 编码 Modbus 读请求帧
29 * 2. 通过 ConnectionManager 发送到设备的 TCP 连接 28 * 2. 通过 ConnectionManager 发送到设备的 TCP 连接
30 * 3. 将请求注册到 PendingRequestManager,等待设备响应 29 * 3. 将请求注册到 PendingRequestManager,等待设备响应
@@ -154,13 +153,15 @@ public class IotModbusTcpSlavePollScheduler { @@ -154,13 +153,15 @@ public class IotModbusTcpSlavePollScheduler {
154 } 153 }
155 154
156 // 2.1 确定帧格式和事务 ID 155 // 2.1 确定帧格式和事务 ID
157 - // TODO @AI:不允许为空!  
158 IotModbusFrameFormatEnum frameFormat = connInfo.getFrameFormat(); 156 IotModbusFrameFormatEnum frameFormat = connInfo.getFrameFormat();
159 if (frameFormat == null) { 157 if (frameFormat == null) {
160 - frameFormat = IotModbusFrameFormatEnum.MODBUS_TCP; 158 + log.warn("[pollPoint][设备 {} 帧格式为空,跳过轮询]", deviceId);
  159 + return;
161 } 160 }
162 - // TODO @AI:transactionId 需要根据设备来么?然后递增也根据 IotModbusFrameFormatEnum.MODBUS_TCP 提前判断;  
163 - int transactionId = transactionIdCounter.incrementAndGet() & 0xFFFF; 161 + // TODO @AI:是不是得按照设备递增?
  162 + Integer transactionId = frameFormat == IotModbusFrameFormatEnum.MODBUS_TCP
  163 + ? (transactionIdCounter.incrementAndGet() & 0xFFFF)
  164 + : null;
164 int slaveId = connInfo.getSlaveId() != null ? connInfo.getSlaveId() : 1; 165 int slaveId = connInfo.getSlaveId() != null ? connInfo.getSlaveId() : 1;
165 // 2.2 编码读请求 166 // 2.2 编码读请求
166 byte[] data = frameEncoder.encodeReadRequest(slaveId, point.getFunctionCode(), 167 byte[] data = frameEncoder.encodeReadRequest(slaveId, point.getFunctionCode(),
@@ -170,7 +171,7 @@ public class IotModbusTcpSlavePollScheduler { @@ -170,7 +171,7 @@ public class IotModbusTcpSlavePollScheduler {
170 deviceId, point.getId(), point.getIdentifier(), 171 deviceId, point.getId(), point.getIdentifier(),
171 slaveId, point.getFunctionCode(), 172 slaveId, point.getFunctionCode(),
172 point.getRegisterAddress(), point.getRegisterCount(), 173 point.getRegisterAddress(), point.getRegisterCount(),
173 - frameFormat == IotModbusFrameFormatEnum.MODBUS_TCP ? transactionId : null, 174 + transactionId,
174 System.currentTimeMillis() + requestTimeout); 175 System.currentTimeMillis() + requestTimeout);
175 pendingRequestManager.addRequest(pendingRequest); 176 pendingRequestManager.addRequest(pendingRequest);
176 177