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 1 package cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpslave;
2 2  
  3 +import cn.hutool.core.collection.CollUtil;
3 4 import cn.hutool.core.lang.Assert;
4 5 import cn.hutool.extra.spring.SpringUtil;
5 6 import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi;
6 7 import cn.iocoder.yudao.module.iot.core.biz.dto.IotModbusDeviceConfigRespDTO;
7   -import cn.iocoder.yudao.module.iot.core.enums.IotModbusModeEnum;
8 8 import cn.iocoder.yudao.module.iot.core.enums.IotProtocolTypeEnum;
9 9 import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus;
10 10 import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils;
... ... @@ -32,9 +32,9 @@ import lombok.Getter;
32 32 import lombok.extern.slf4j.Slf4j;
33 33  
34 34 import java.util.List;
  35 +import java.util.Set;
35 36 import java.util.concurrent.TimeUnit;
36 37  
37   -// DONE @AI:不用主动上报!
38 38 /**
39 39 * IoT 网关 Modbus TCP Slave 协议
40 40 * <p>
... ... @@ -152,10 +152,7 @@ public class IotModbusTcpSlaveProtocol implements IotProtocol {
152 152 }
153 153  
154 154 try {
155   - // 1.1 首次加载配置
156   - // TODO @AI:可能首次不用加载;你在想想;
157   - refreshConfig();
158   - // 1.2 启动配置刷新定时器
  155 + // 1. 启动配置刷新定时器
159 156 int refreshInterval = slaveConfig.getConfigRefreshInterval();
160 157 configRefreshTimerId = vertx.setPeriodic(
161 158 TimeUnit.SECONDS.toMillis(refreshInterval),
... ... @@ -286,6 +283,7 @@ public class IotModbusTcpSlaveProtocol implements IotProtocol {
286 283 }
287 284 pollScheduler.stopPolling(info.getDeviceId());
288 285 pendingRequestManager.removeDevice(info.getDeviceId());
  286 + configCacheService.removeConfig(info.getDeviceId());
289 287 log.info("[handleConnection][连接关闭, deviceId={}, remoteAddress={}]",
290 288 info.getDeviceId(), socket.remoteAddress());
291 289 });
... ... @@ -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 303 private synchronized void refreshConfig() {
303 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 315 for (IotModbusDeviceConfigRespDTO config : configs) {
310 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 318 } catch (Exception e) {
320 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 322 } catch (Exception e) {
330 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 16 import cn.iocoder.yudao.module.iot.core.biz.dto.IotModbusPointRespDTO;
17 17 import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
18 18 import cn.iocoder.yudao.module.iot.core.enums.IotModbusFrameFormatEnum;
19   -import cn.iocoder.yudao.module.iot.core.enums.IotModbusModeEnum;
20 19 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
21 20 import cn.iocoder.yudao.module.iot.core.topic.IotDeviceIdentity;
22 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 1 package cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpslave.manager;
2 2  
  3 +import cn.hutool.core.collection.CollUtil;
3 4 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4 5 import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi;
5 6 import cn.iocoder.yudao.module.iot.core.biz.dto.IotModbusDeviceConfigRespDTO;
... ... @@ -10,15 +11,18 @@ import lombok.extern.slf4j.Slf4j;
10 11 import java.math.BigDecimal;
11 12 import java.util.*;
12 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 16 * IoT Modbus TCP Slave 配置缓存服务
20 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 27 * @author 芋道源码
24 28 */
... ... @@ -33,43 +37,118 @@ public class IotModbusTcpSlaveConfigCacheService {
33 37 */
34 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 95 try {
49   - // 1. 从远程获取配置
50   - // TODO @AI:需要过滤下,只查找连接的设备列表;并且只有主动轮询的,才会处理;方法名,应该是 List 结尾;
  96 + // 1. 从远程获取全量配置
  97 + // TODO @AI:传递 ids 批量查询;需要分批啦;
51 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 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 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 153 * 构建 Mock 测试配置数据(一次性测试用途)
75 154 *
... ... @@ -123,50 +202,4 @@ public class IotModbusTcpSlaveConfigCacheService {
123 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 4 import io.vertx.core.buffer.Buffer;
5 5 import io.vertx.core.net.NetSocket;
6 6 import lombok.Data;
  7 +import lombok.experimental.Accessors;
7 8 import lombok.extern.slf4j.Slf4j;
8 9  
9 10 import java.util.Map;
  11 +import java.util.Set;
10 12 import java.util.concurrent.ConcurrentHashMap;
11 13  
12 14 /**
... ... @@ -33,6 +35,7 @@ public class IotModbusTcpSlaveConnectionManager {
33 35 * 连接信息
34 36 */
35 37 @Data
  38 + @Accessors(chain = true)
36 39 public static class ConnectionInfo {
37 40  
38 41 /**
... ... @@ -57,12 +60,6 @@ public class IotModbusTcpSlaveConnectionManager {
57 60 */
58 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 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 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 115 log.warn("[sendToDevice][设备 {} 没有连接]", deviceId);
131 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 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 1 package cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpslave.manager;
2 2  
  3 +import cn.hutool.core.collection.CollUtil;
3 4 import cn.iocoder.yudao.module.iot.core.enums.IotModbusFrameFormatEnum;
4 5 import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpslave.codec.IotModbusFrame;
5 6 import lombok.AllArgsConstructor;
... ... @@ -7,6 +8,7 @@ import lombok.Data;
7 8 import lombok.extern.slf4j.Slf4j;
8 9  
9 10 import java.util.Deque;
  11 +import java.util.Iterator;
10 12 import java.util.Map;
11 13 import java.util.concurrent.ConcurrentHashMap;
12 14 import java.util.concurrent.ConcurrentLinkedDeque;
... ... @@ -66,26 +68,24 @@ public class IotModbusTcpSlavePendingRequestManager {
66 68 public PendingRequest matchResponse(Long deviceId, IotModbusFrame frame,
67 69 IotModbusFrameFormatEnum frameFormat) {
68 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 72 return null;
72 73 }
73 74  
  75 + // TCP 模式:按 transactionId 精确匹配
74 76 if (frameFormat == IotModbusFrameFormatEnum.MODBUS_TCP && frame.getTransactionId() != null) {
75   - // TCP 模式:按 transactionId 精确匹配
76 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 84 * 按 transactionId 匹配
85 85 */
86 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 89 PendingRequest req = it.next();
90 90 if (req.getTransactionId() != null && req.getTransactionId() == transactionId) {
91 91 it.remove();
... ... @@ -99,8 +99,8 @@ public class IotModbusTcpSlavePendingRequestManager {
99 99 * 按 FIFO 匹配
100 100 */
101 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 104 PendingRequest req = it.next();
105 105 if (req.getSlaveId() == slaveId && req.getFunctionCode() == functionCode) {
106 106 it.remove();
... ... @@ -120,13 +120,11 @@ public class IotModbusTcpSlavePendingRequestManager {
120 120 int removed = 0;
121 121 while (!queue.isEmpty()) {
122 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 124 break; // 队列有序,后面的没过期
129 125 }
  126 + queue.pollFirst();
  127 + removed++;
130 128 }
131 129 if (removed > 0) {
132 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 19  
20 20 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
21 21  
22   -// TODO @AI:和 IotModbusTcpPollScheduler 很像,是不是可以做一些复用?
23 22 /**
24 23 * IoT Modbus TCP Slave 轮询调度器
25 24 * <p>
26   - * 管理点位的轮询定时器,为 mode=1(云端轮询)的设备调度读取任务。
27   - * 与 tcpmaster 不同,这里不直接通过 j2mod 读取,而是:
  25 + * 管理点位的轮询定时器,为云端轮询模式的设备调度读取任务。
  26 + * 与 tcpmaster 的 {@code IotModbusTcpPollScheduler} 不同,这里不通过 j2mod 直接读取,而是:
28 27 * 1. 编码 Modbus 读请求帧
29 28 * 2. 通过 ConnectionManager 发送到设备的 TCP 连接
30 29 * 3. 将请求注册到 PendingRequestManager,等待设备响应
... ... @@ -154,13 +153,15 @@ public class IotModbusTcpSlavePollScheduler {
154 153 }
155 154  
156 155 // 2.1 确定帧格式和事务 ID
157   - // TODO @AI:不允许为空!
158 156 IotModbusFrameFormatEnum frameFormat = connInfo.getFrameFormat();
159 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 165 int slaveId = connInfo.getSlaveId() != null ? connInfo.getSlaveId() : 1;
165 166 // 2.2 编码读请求
166 167 byte[] data = frameEncoder.encodeReadRequest(slaveId, point.getFunctionCode(),
... ... @@ -170,7 +171,7 @@ public class IotModbusTcpSlavePollScheduler {
170 171 deviceId, point.getId(), point.getIdentifier(),
171 172 slaveId, point.getFunctionCode(),
172 173 point.getRegisterAddress(), point.getRegisterCount(),
173   - frameFormat == IotModbusFrameFormatEnum.MODBUS_TCP ? transactionId : null,
  174 + transactionId,
174 175 System.currentTimeMillis() + requestTimeout);
175 176 pendingRequestManager.addRequest(pendingRequest);
176 177  
... ...