Commit 4d578b239c8a1a7f3f2cc7c16f094787bac4169d
1 parent
3ab33527
feat(iot):modbus-tcp-slave 整体代码进一步优化
Showing
6 changed files
with
152 additions
and
145 deletions
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 | ... | ... |