Commit c608b81c4e9e347d42a56a0847cf94de449ee4f7

Authored by YunaiV
1 parent e13cd545

feat(iot):modbus-tcp-slave 优化大量代码,主要是 polling 轮询的复用;

Showing 24 changed files with 373 additions and 566 deletions
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/IotProtocolDownstreamSubscriber.java renamed to yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/AbstractIotProtocolDownstreamSubscriber.java
@@ -17,7 +17,7 @@ import lombok.extern.slf4j.Slf4j; @@ -17,7 +17,7 @@ import lombok.extern.slf4j.Slf4j;
17 */ 17 */
18 @AllArgsConstructor 18 @AllArgsConstructor
19 @Slf4j 19 @Slf4j
20 -public abstract class IotProtocolDownstreamSubscriber implements IotMessageSubscriber<IotDeviceMessage> { 20 +public abstract class AbstractIotProtocolDownstreamSubscriber implements IotMessageSubscriber<IotDeviceMessage> {
21 21
22 private final IotProtocol protocol; 22 private final IotProtocol protocol;
23 23
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/coap/handler/downstream/IotCoapDownstreamSubscriber.java
@@ -2,7 +2,7 @@ package cn.iocoder.yudao.module.iot.gateway.protocol.coap.handler.downstream; @@ -2,7 +2,7 @@ package cn.iocoder.yudao.module.iot.gateway.protocol.coap.handler.downstream;
2 2
3 import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus; 3 import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus;
4 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; 4 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
5 -import cn.iocoder.yudao.module.iot.gateway.protocol.IotProtocolDownstreamSubscriber; 5 +import cn.iocoder.yudao.module.iot.gateway.protocol.AbstractIotProtocolDownstreamSubscriber;
6 import cn.iocoder.yudao.module.iot.gateway.protocol.coap.IotCoapProtocol; 6 import cn.iocoder.yudao.module.iot.gateway.protocol.coap.IotCoapProtocol;
7 import lombok.extern.slf4j.Slf4j; 7 import lombok.extern.slf4j.Slf4j;
8 8
@@ -12,7 +12,7 @@ import lombok.extern.slf4j.Slf4j; @@ -12,7 +12,7 @@ import lombok.extern.slf4j.Slf4j;
12 * @author 芋道源码 12 * @author 芋道源码
13 */ 13 */
14 @Slf4j 14 @Slf4j
15 -public class IotCoapDownstreamSubscriber extends IotProtocolDownstreamSubscriber { 15 +public class IotCoapDownstreamSubscriber extends AbstractIotProtocolDownstreamSubscriber {
16 16
17 public IotCoapDownstreamSubscriber(IotCoapProtocol protocol, IotMessageBus messageBus) { 17 public IotCoapDownstreamSubscriber(IotCoapProtocol protocol, IotMessageBus messageBus) {
18 super(protocol, messageBus); 18 super(protocol, messageBus);
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/emqx/handler/downstream/IotEmqxDownstreamSubscriber.java
@@ -2,7 +2,7 @@ package cn.iocoder.yudao.module.iot.gateway.protocol.emqx.handler.downstream; @@ -2,7 +2,7 @@ package cn.iocoder.yudao.module.iot.gateway.protocol.emqx.handler.downstream;
2 2
3 import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus; 3 import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus;
4 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; 4 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
5 -import cn.iocoder.yudao.module.iot.gateway.protocol.IotProtocolDownstreamSubscriber; 5 +import cn.iocoder.yudao.module.iot.gateway.protocol.AbstractIotProtocolDownstreamSubscriber;
6 import cn.iocoder.yudao.module.iot.gateway.protocol.emqx.IotEmqxProtocol; 6 import cn.iocoder.yudao.module.iot.gateway.protocol.emqx.IotEmqxProtocol;
7 import lombok.extern.slf4j.Slf4j; 7 import lombok.extern.slf4j.Slf4j;
8 8
@@ -12,7 +12,7 @@ import lombok.extern.slf4j.Slf4j; @@ -12,7 +12,7 @@ import lombok.extern.slf4j.Slf4j;
12 * @author 芋道源码 12 * @author 芋道源码
13 */ 13 */
14 @Slf4j 14 @Slf4j
15 -public class IotEmqxDownstreamSubscriber extends IotProtocolDownstreamSubscriber { 15 +public class IotEmqxDownstreamSubscriber extends AbstractIotProtocolDownstreamSubscriber {
16 16
17 private final IotEmqxDownstreamHandler downstreamHandler; 17 private final IotEmqxDownstreamHandler downstreamHandler;
18 18
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/http/handler/downstream/IotHttpDownstreamSubscriber.java
@@ -3,7 +3,7 @@ package cn.iocoder.yudao.module.iot.gateway.protocol.http.handler.downstream; @@ -3,7 +3,7 @@ package cn.iocoder.yudao.module.iot.gateway.protocol.http.handler.downstream;
3 import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus; 3 import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus;
4 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; 4 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
5 import cn.iocoder.yudao.module.iot.gateway.protocol.IotProtocol; 5 import cn.iocoder.yudao.module.iot.gateway.protocol.IotProtocol;
6 -import cn.iocoder.yudao.module.iot.gateway.protocol.IotProtocolDownstreamSubscriber; 6 +import cn.iocoder.yudao.module.iot.gateway.protocol.AbstractIotProtocolDownstreamSubscriber;
7 import lombok.extern.slf4j.Slf4j; 7 import lombok.extern.slf4j.Slf4j;
8 8
9 /** 9 /**
@@ -13,7 +13,7 @@ import lombok.extern.slf4j.Slf4j; @@ -13,7 +13,7 @@ import lombok.extern.slf4j.Slf4j;
13 */ 13 */
14 14
15 @Slf4j 15 @Slf4j
16 -public class IotHttpDownstreamSubscriber extends IotProtocolDownstreamSubscriber { 16 +public class IotHttpDownstreamSubscriber extends AbstractIotProtocolDownstreamSubscriber {
17 17
18 public IotHttpDownstreamSubscriber(IotProtocol protocol, IotMessageBus messageBus) { 18 public IotHttpDownstreamSubscriber(IotProtocol protocol, IotMessageBus messageBus) {
19 super(protocol, messageBus); 19 super(protocol, messageBus);
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/common/manager/AbstractIotModbusPollScheduler.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.gateway.protocol.modbus.common.manager;
  2 +
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.iocoder.yudao.module.iot.core.biz.dto.IotModbusDeviceConfigRespDTO;
  5 +import cn.iocoder.yudao.module.iot.core.biz.dto.IotModbusPointRespDTO;
  6 +import io.vertx.core.Vertx;
  7 +import lombok.AllArgsConstructor;
  8 +import lombok.Data;
  9 +import lombok.extern.slf4j.Slf4j;
  10 +
  11 +import java.util.*;
  12 +import java.util.concurrent.ConcurrentHashMap;
  13 +import java.util.concurrent.ConcurrentLinkedQueue;
  14 +
  15 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
  16 +
  17 +/**
  18 + * Modbus 轮询调度器基类
  19 + * <p>
  20 + * 封装通用的定时器管理、per-device 请求队列限速逻辑。
  21 + * 子类只需实现 {@link #pollPoint(Long, Long)} 定义具体的轮询动作。
  22 + * <p>
  23 + *
  24 + * @author 芋道源码
  25 + */
  26 +@Slf4j
  27 +public abstract class AbstractIotModbusPollScheduler {
  28 +
  29 + protected final Vertx vertx;
  30 +
  31 + /**
  32 + * 同设备最小请求间隔(毫秒),防止 Modbus 设备性能不足时请求堆积
  33 + */
  34 + private static final long MIN_REQUEST_INTERVAL = 1000;
  35 +
  36 + /**
  37 + * 设备点位的定时器映射:deviceId -> (pointId -> PointTimerInfo)
  38 + */
  39 + private final Map<Long, Map<Long, PointTimerInfo>> devicePointTimers = new ConcurrentHashMap<>();
  40 +
  41 + /**
  42 + * per-device 请求队列:deviceId -> 待执行请求队列
  43 + */
  44 + private final Map<Long, Queue<Runnable>> deviceRequestQueues = new ConcurrentHashMap<>();
  45 + /**
  46 + * per-device 上次请求时间戳:deviceId -> lastRequestTimeMs
  47 + */
  48 + private final Map<Long, Long> deviceLastRequestTime = new ConcurrentHashMap<>();
  49 + /**
  50 + * per-device 延迟 timer 标记:deviceId -> 是否有延迟 timer 在等待
  51 + */
  52 + private final Map<Long, Boolean> deviceDelayTimerActive = new ConcurrentHashMap<>();
  53 +
  54 + protected AbstractIotModbusPollScheduler(Vertx vertx) {
  55 + this.vertx = vertx;
  56 + }
  57 +
  58 + /**
  59 + * 点位定时器信息
  60 + */
  61 + @Data
  62 + @AllArgsConstructor
  63 + private static class PointTimerInfo {
  64 +
  65 + /**
  66 + * Vert.x 定时器 ID
  67 + */
  68 + private Long timerId;
  69 + /**
  70 + * 轮询间隔(用于判断是否需要更新定时器)
  71 + */
  72 + private Integer pollInterval;
  73 +
  74 + }
  75 +
  76 + // ========== 轮询管理 ==========
  77 +
  78 + /**
  79 + * 更新轮询任务(增量更新)
  80 + *
  81 + * 1. 【删除】点位:停止对应的轮询定时器
  82 + * 2. 【新增】点位:创建对应的轮询定时器
  83 + * 3. 【修改】点位:pollInterval 变化,重建对应的轮询定时器
  84 + * 【修改】其他属性变化:不需要重建定时器(pollPoint 运行时从 configCache 取最新 point)
  85 + */
  86 + public void updatePolling(IotModbusDeviceConfigRespDTO config) {
  87 + Long deviceId = config.getDeviceId();
  88 + List<IotModbusPointRespDTO> newPoints = config.getPoints();
  89 + Map<Long, PointTimerInfo> currentTimers = devicePointTimers
  90 + .computeIfAbsent(deviceId, k -> new ConcurrentHashMap<>());
  91 + // 1.1 计算新配置中的点位 ID 集合
  92 + Set<Long> newPointIds = convertSet(newPoints, IotModbusPointRespDTO::getId);
  93 + // 1.2 计算删除的点位 ID 集合
  94 + Set<Long> removedPointIds = new HashSet<>(currentTimers.keySet());
  95 + removedPointIds.removeAll(newPointIds);
  96 +
  97 + // 2. 处理删除的点位:停止不再存在的定时器
  98 + for (Long pointId : removedPointIds) {
  99 + PointTimerInfo timerInfo = currentTimers.remove(pointId);
  100 + if (timerInfo != null) {
  101 + vertx.cancelTimer(timerInfo.getTimerId());
  102 + log.debug("[updatePolling][设备 {} 点位 {} 定时器已删除]", deviceId, pointId);
  103 + }
  104 + }
  105 +
  106 + // 3. 处理新增和修改的点位
  107 + if (CollUtil.isEmpty(newPoints)) {
  108 + return;
  109 + }
  110 + for (IotModbusPointRespDTO point : newPoints) {
  111 + Long pointId = point.getId();
  112 + Integer newPollInterval = point.getPollInterval();
  113 + PointTimerInfo existingTimer = currentTimers.get(pointId);
  114 + // 3.1 新增点位:创建定时器
  115 + if (existingTimer == null) {
  116 + Long timerId = createPollTimer(deviceId, pointId, newPollInterval);
  117 + if (timerId != null) {
  118 + currentTimers.put(pointId, new PointTimerInfo(timerId, newPollInterval));
  119 + log.debug("[updatePolling][设备 {} 点位 {} 定时器已创建, interval={}ms]",
  120 + deviceId, pointId, newPollInterval);
  121 + }
  122 + } else if (!Objects.equals(existingTimer.getPollInterval(), newPollInterval)) {
  123 + // 3.2 pollInterval 变化:重建定时器
  124 + vertx.cancelTimer(existingTimer.getTimerId());
  125 + Long timerId = createPollTimer(deviceId, pointId, newPollInterval);
  126 + if (timerId != null) {
  127 + currentTimers.put(pointId, new PointTimerInfo(timerId, newPollInterval));
  128 + log.debug("[updatePolling][设备 {} 点位 {} 定时器已更新, interval={}ms -> {}ms]",
  129 + deviceId, pointId, existingTimer.getPollInterval(), newPollInterval);
  130 + } else {
  131 + currentTimers.remove(pointId);
  132 + }
  133 + }
  134 + // 3.3 其他属性变化:无需重建定时器,因为 pollPoint() 运行时从 configCache 获取最新 point,自动使用新配置
  135 + }
  136 + }
  137 +
  138 + /**
  139 + * 创建轮询定时器
  140 + */
  141 + private Long createPollTimer(Long deviceId, Long pointId, Integer pollInterval) {
  142 + if (pollInterval == null || pollInterval <= 0) {
  143 + return null;
  144 + }
  145 + return vertx.setPeriodic(pollInterval, timerId -> {
  146 + try {
  147 + submitPollRequest(deviceId, pointId);
  148 + } catch (Exception e) {
  149 + log.error("[createPollTimer][轮询点位失败, deviceId={}, pointId={}]", deviceId, pointId, e);
  150 + }
  151 + });
  152 + }
  153 +
  154 + // ========== 请求队列(per-device 限速) ==========
  155 +
  156 + /**
  157 + * 提交轮询请求到设备请求队列(保证同设备请求间隔)
  158 + */
  159 + private void submitPollRequest(Long deviceId, Long pointId) {
  160 + // 1. 【重要】将请求添加到设备的请求队列
  161 + Queue<Runnable> queue = deviceRequestQueues.computeIfAbsent(deviceId, k -> new ConcurrentLinkedQueue<>());
  162 + queue.offer(() -> pollPoint(deviceId, pointId));
  163 +
  164 + // 2. 处理设备请求队列(如果没有延迟 timer 在等待)
  165 + processDeviceQueue(deviceId);
  166 + }
  167 +
  168 + /**
  169 + * 处理设备请求队列
  170 + */
  171 + private void processDeviceQueue(Long deviceId) {
  172 + Queue<Runnable> queue = deviceRequestQueues.get(deviceId);
  173 + if (CollUtil.isEmpty(queue)) {
  174 + return;
  175 + }
  176 + // 检查是否已有延迟 timer 在等待
  177 + if (Boolean.TRUE.equals(deviceDelayTimerActive.get(deviceId))) {
  178 + return;
  179 + }
  180 +
  181 + // 不满足间隔要求,延迟执行
  182 + long now = System.currentTimeMillis();
  183 + long lastTime = deviceLastRequestTime.getOrDefault(deviceId, 0L);
  184 + long elapsed = now - lastTime;
  185 + if (elapsed < MIN_REQUEST_INTERVAL) {
  186 + scheduleNextRequest(deviceId, MIN_REQUEST_INTERVAL - elapsed);
  187 + return;
  188 + }
  189 +
  190 + // 满足间隔要求,立即执行
  191 + Runnable task = queue.poll();
  192 + if (task == null) {
  193 + return;
  194 + }
  195 + deviceLastRequestTime.put(deviceId, now);
  196 + task.run();
  197 + // 继续处理队列中的下一个(如果有的话,需要延迟)
  198 + if (CollUtil.isNotEmpty(queue)) {
  199 + scheduleNextRequest(deviceId);
  200 + }
  201 + }
  202 +
  203 + private void scheduleNextRequest(Long deviceId) {
  204 + scheduleNextRequest(deviceId, MIN_REQUEST_INTERVAL);
  205 + }
  206 +
  207 + private void scheduleNextRequest(Long deviceId, long delayMs) {
  208 + deviceDelayTimerActive.put(deviceId, true);
  209 + vertx.setTimer(delayMs, id -> {
  210 + deviceDelayTimerActive.put(deviceId, false);
  211 + Queue<Runnable> queue = deviceRequestQueues.get(deviceId);
  212 + if (CollUtil.isEmpty(queue)) {
  213 + return;
  214 + }
  215 +
  216 + // 满足间隔要求,立即执行
  217 + Runnable task = queue.poll();
  218 + if (task == null) {
  219 + return;
  220 + }
  221 + deviceLastRequestTime.put(deviceId, System.currentTimeMillis());
  222 + task.run();
  223 + // 继续处理队列中的下一个(如果有的话,需要延迟)
  224 + if (CollUtil.isNotEmpty(queue)) {
  225 + scheduleNextRequest(deviceId);
  226 + }
  227 + });
  228 + }
  229 +
  230 + // ========== 轮询执行 ==========
  231 +
  232 + /**
  233 + * 轮询单个点位(子类实现具体的读取逻辑)
  234 + *
  235 + * @param deviceId 设备 ID
  236 + * @param pointId 点位 ID
  237 + */
  238 + protected abstract void pollPoint(Long deviceId, Long pointId);
  239 +
  240 + // ========== 停止 ==========
  241 +
  242 + /**
  243 + * 停止设备的轮询
  244 + */
  245 + public void stopPolling(Long deviceId) {
  246 + Map<Long, PointTimerInfo> timers = devicePointTimers.remove(deviceId);
  247 + if (CollUtil.isEmpty(timers)) {
  248 + return;
  249 + }
  250 + for (PointTimerInfo timerInfo : timers.values()) {
  251 + vertx.cancelTimer(timerInfo.getTimerId());
  252 + }
  253 + // 清理请求队列
  254 + deviceRequestQueues.remove(deviceId);
  255 + deviceLastRequestTime.remove(deviceId);
  256 + deviceDelayTimerActive.remove(deviceId);
  257 + log.debug("[stopPolling][设备 {} 停止了 {} 个轮询定时器]", deviceId, timers.size());
  258 + }
  259 +
  260 + /**
  261 + * 停止所有轮询
  262 + */
  263 + public void stopAll() {
  264 + for (Long deviceId : new ArrayList<>(devicePointTimers.keySet())) {
  265 + stopPolling(deviceId);
  266 + }
  267 + }
  268 +
  269 +}
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/common/IotModbusUtils.java renamed to yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/common/utils/IotModbusCommonUtils.java
1 -package cn.iocoder.yudao.module.iot.gateway.protocol.modbus.common; 1 +package cn.iocoder.yudao.module.iot.gateway.protocol.modbus.common.utils;
2 2
3 import cn.hutool.core.collection.CollUtil; 3 import cn.hutool.core.collection.CollUtil;
4 import cn.hutool.core.util.ArrayUtil; 4 import cn.hutool.core.util.ArrayUtil;
@@ -34,7 +34,7 @@ import java.nio.ByteOrder; @@ -34,7 +34,7 @@ import java.nio.ByteOrder;
34 */ 34 */
35 @UtilityClass 35 @UtilityClass
36 @Slf4j 36 @Slf4j
37 -public class IotModbusUtils { 37 +public class IotModbusCommonUtils {
38 38
39 /** FC01: 读线圈 */ 39 /** FC01: 读线圈 */
40 public static final int FC_READ_COILS = 1; 40 public static final int FC_READ_COILS = 1;
@@ -512,4 +512,15 @@ public class IotModbusUtils { @@ -512,4 +512,15 @@ public class IotModbusUtils {
512 return CollUtil.findOne(config.getPoints(), p -> identifier.equals(p.getIdentifier())); 512 return CollUtil.findOne(config.getPoints(), p -> identifier.equals(p.getIdentifier()));
513 } 513 }
514 514
  515 + /**
  516 + * 根据点位 ID 查找点位配置
  517 + *
  518 + * @param config 设备 Modbus 配置
  519 + * @param pointId 点位 ID
  520 + * @return 匹配的点位配置,未找到返回 null
  521 + */
  522 + public static IotModbusPointRespDTO findPointById(IotModbusDeviceConfigRespDTO config, Long pointId) {
  523 + return CollUtil.findOne(config.getPoints(), p -> p.getId().equals(pointId));
  524 + }
  525 +
515 } 526 }
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpmaster/client/IotModbusTcpClientUtils.java renamed to yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/common/utils/IotModbusTcpMasterUtils.java
1 -package cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpmaster.client; 1 +package cn.iocoder.yudao.module.iot.gateway.protocol.modbus.common.utils;
2 2
3 import cn.iocoder.yudao.module.iot.core.biz.dto.IotModbusPointRespDTO; 3 import cn.iocoder.yudao.module.iot.core.biz.dto.IotModbusPointRespDTO;
4 import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpmaster.manager.IotModbusTcpConnectionManager; 4 import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpmaster.manager.IotModbusTcpConnectionManager;
@@ -12,7 +12,7 @@ import io.vertx.core.Future; @@ -12,7 +12,7 @@ import io.vertx.core.Future;
12 import lombok.experimental.UtilityClass; 12 import lombok.experimental.UtilityClass;
13 import lombok.extern.slf4j.Slf4j; 13 import lombok.extern.slf4j.Slf4j;
14 14
15 -import static cn.iocoder.yudao.module.iot.gateway.protocol.modbus.common.IotModbusUtils.*; 15 +import static cn.iocoder.yudao.module.iot.gateway.protocol.modbus.common.utils.IotModbusCommonUtils.*;
16 16
17 /** 17 /**
18 * IoT Modbus TCP 客户端工具类 18 * IoT Modbus TCP 客户端工具类
@@ -26,7 +26,7 @@ import static cn.iocoder.yudao.module.iot.gateway.protocol.modbus.common.IotModb @@ -26,7 +26,7 @@ import static cn.iocoder.yudao.module.iot.gateway.protocol.modbus.common.IotModb
26 */ 26 */
27 @UtilityClass 27 @UtilityClass
28 @Slf4j 28 @Slf4j
29 -public class IotModbusTcpClientUtils { 29 +public class IotModbusTcpMasterUtils {
30 30
31 /** 31 /**
32 * 读取 Modbus 数据 32 * 读取 Modbus 数据
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpmaster/handler/downstream/IotModbusTcpDownstreamHandler.java
@@ -4,8 +4,8 @@ import cn.iocoder.yudao.module.iot.core.biz.dto.IotModbusDeviceConfigRespDTO; @@ -4,8 +4,8 @@ import cn.iocoder.yudao.module.iot.core.biz.dto.IotModbusDeviceConfigRespDTO;
4 import cn.iocoder.yudao.module.iot.core.biz.dto.IotModbusPointRespDTO; 4 import cn.iocoder.yudao.module.iot.core.biz.dto.IotModbusPointRespDTO;
5 import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum; 5 import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
6 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; 6 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
7 -import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.common.IotModbusUtils;  
8 -import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpmaster.client.IotModbusTcpClientUtils; 7 +import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.common.utils.IotModbusCommonUtils;
  8 +import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.common.utils.IotModbusTcpMasterUtils;
9 import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpmaster.manager.IotModbusTcpConfigCacheService; 9 import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpmaster.manager.IotModbusTcpConfigCacheService;
10 import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpmaster.manager.IotModbusTcpConnectionManager; 10 import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpmaster.manager.IotModbusTcpConnectionManager;
11 import lombok.RequiredArgsConstructor; 11 import lombok.RequiredArgsConstructor;
@@ -57,13 +57,13 @@ public class IotModbusTcpDownstreamHandler { @@ -57,13 +57,13 @@ public class IotModbusTcpDownstreamHandler {
57 String identifier = entry.getKey(); 57 String identifier = entry.getKey();
58 Object value = entry.getValue(); 58 Object value = entry.getValue();
59 // 2.1 查找对应的点位配置 59 // 2.1 查找对应的点位配置
60 - IotModbusPointRespDTO point = IotModbusUtils.findPoint(config, identifier); 60 + IotModbusPointRespDTO point = IotModbusCommonUtils.findPoint(config, identifier);
61 if (point == null) { 61 if (point == null) {
62 log.warn("[handle][设备 {} 没有点位配置: {}]", message.getDeviceId(), identifier); 62 log.warn("[handle][设备 {} 没有点位配置: {}]", message.getDeviceId(), identifier);
63 continue; 63 continue;
64 } 64 }
65 // 2.2 检查是否支持写操作 65 // 2.2 检查是否支持写操作
66 - if (!IotModbusUtils.isWritable(point.getFunctionCode())) { 66 + if (!IotModbusCommonUtils.isWritable(point.getFunctionCode())) {
67 log.warn("[handle][点位 {} 不支持写操作, 功能码={}]", identifier, point.getFunctionCode()); 67 log.warn("[handle][点位 {} 不支持写操作, 功能码={}]", identifier, point.getFunctionCode());
68 continue; 68 continue;
69 } 69 }
@@ -91,9 +91,9 @@ public class IotModbusTcpDownstreamHandler { @@ -91,9 +91,9 @@ public class IotModbusTcpDownstreamHandler {
91 } 91 }
92 92
93 // 2.1 转换属性值为原始值 93 // 2.1 转换属性值为原始值
94 - int[] rawValues = IotModbusUtils.convertToRawValues(value, point); 94 + int[] rawValues = IotModbusCommonUtils.convertToRawValues(value, point);
95 // 2.2 执行 Modbus 写入 95 // 2.2 执行 Modbus 写入
96 - IotModbusTcpClientUtils.write(connection, slaveId, point, rawValues) 96 + IotModbusTcpMasterUtils.write(connection, slaveId, point, rawValues)
97 .onSuccess(success -> log.info("[writeProperty][写入成功, deviceId={}, identifier={}, value={}]", 97 .onSuccess(success -> log.info("[writeProperty][写入成功, deviceId={}, identifier={}, value={}]",
98 config.getDeviceId(), point.getIdentifier(), value)) 98 config.getDeviceId(), point.getIdentifier(), value))
99 .onFailure(e -> log.error("[writeProperty][写入失败, deviceId={}, identifier={}]", 99 .onFailure(e -> log.error("[writeProperty][写入失败, deviceId={}, identifier={}]",
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpmaster/handler/downstream/IotModbusTcpDownstreamSubscriber.java
@@ -2,7 +2,7 @@ package cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpmaster.handler.do @@ -2,7 +2,7 @@ package cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpmaster.handler.do
2 2
3 import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus; 3 import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus;
4 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; 4 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
5 -import cn.iocoder.yudao.module.iot.gateway.protocol.IotProtocolDownstreamSubscriber; 5 +import cn.iocoder.yudao.module.iot.gateway.protocol.AbstractIotProtocolDownstreamSubscriber;
6 import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpmaster.IotModbusTcpMasterProtocol; 6 import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpmaster.IotModbusTcpMasterProtocol;
7 import lombok.extern.slf4j.Slf4j; 7 import lombok.extern.slf4j.Slf4j;
8 8
@@ -12,7 +12,7 @@ import lombok.extern.slf4j.Slf4j; @@ -12,7 +12,7 @@ import lombok.extern.slf4j.Slf4j;
12 * @author 芋道源码 12 * @author 芋道源码
13 */ 13 */
14 @Slf4j 14 @Slf4j
15 -public class IotModbusTcpDownstreamSubscriber extends IotProtocolDownstreamSubscriber { 15 +public class IotModbusTcpDownstreamSubscriber extends AbstractIotProtocolDownstreamSubscriber {
16 16
17 private final IotModbusTcpDownstreamHandler downstreamHandler; 17 private final IotModbusTcpDownstreamHandler downstreamHandler;
18 18
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpmaster/handler/upstream/IotModbusTcpUpstreamHandler.java
@@ -5,7 +5,7 @@ import cn.iocoder.yudao.module.iot.core.biz.dto.IotModbusDeviceConfigRespDTO; @@ -5,7 +5,7 @@ import cn.iocoder.yudao.module.iot.core.biz.dto.IotModbusDeviceConfigRespDTO;
5 import cn.iocoder.yudao.module.iot.core.biz.dto.IotModbusPointRespDTO; 5 import cn.iocoder.yudao.module.iot.core.biz.dto.IotModbusPointRespDTO;
6 import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum; 6 import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
7 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; 7 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
8 -import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.common.IotModbusUtils; 8 +import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.common.utils.IotModbusCommonUtils;
9 import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService; 9 import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService;
10 import lombok.extern.slf4j.Slf4j; 10 import lombok.extern.slf4j.Slf4j;
11 11
@@ -40,7 +40,7 @@ public class IotModbusTcpUpstreamHandler { @@ -40,7 +40,7 @@ public class IotModbusTcpUpstreamHandler {
40 int[] rawValue) { 40 int[] rawValue) {
41 try { 41 try {
42 // 1.1 转换原始值为物模型属性值 42 // 1.1 转换原始值为物模型属性值
43 - Object convertedValue = IotModbusUtils.convertToPropertyValue(rawValue, point); 43 + Object convertedValue = IotModbusCommonUtils.convertToPropertyValue(rawValue, point);
44 log.debug("[handleReadResult][设备={}, 属性={}, 原始值={}, 转换值={}]", 44 log.debug("[handleReadResult][设备={}, 属性={}, 原始值={}, 转换值={}]",
45 config.getDeviceId(), point.getIdentifier(), rawValue, convertedValue); 45 config.getDeviceId(), point.getIdentifier(), rawValue, convertedValue);
46 // 1.2 构造属性上报消息 46 // 1.2 构造属性上报消息
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpmaster/manager/IotModbusTcpPollScheduler.java
1 package cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpmaster.manager; 1 package cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpmaster.manager;
2 2
3 import cn.hutool.core.collection.CollUtil; 3 import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.lang.Assert;
4 import cn.iocoder.yudao.module.iot.core.biz.dto.IotModbusDeviceConfigRespDTO; 5 import cn.iocoder.yudao.module.iot.core.biz.dto.IotModbusDeviceConfigRespDTO;
5 import cn.iocoder.yudao.module.iot.core.biz.dto.IotModbusPointRespDTO; 6 import cn.iocoder.yudao.module.iot.core.biz.dto.IotModbusPointRespDTO;
6 -import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpmaster.client.IotModbusTcpClientUtils; 7 +import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.common.manager.AbstractIotModbusPollScheduler;
  8 +import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.common.utils.IotModbusCommonUtils;
  9 +import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.common.utils.IotModbusTcpMasterUtils;
7 import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpmaster.handler.upstream.IotModbusTcpUpstreamHandler; 10 import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpmaster.handler.upstream.IotModbusTcpUpstreamHandler;
8 import io.vertx.core.Vertx; 11 import io.vertx.core.Vertx;
9 -import lombok.AllArgsConstructor;  
10 -import lombok.Data;  
11 import lombok.extern.slf4j.Slf4j; 12 import lombok.extern.slf4j.Slf4j;
12 13
13 -import java.util.*;  
14 -import java.util.concurrent.ConcurrentHashMap;  
15 -import java.util.concurrent.ConcurrentLinkedQueue;  
16 -  
17 -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;  
18 -  
19 -// TODO @AI:类的命名上,要体现上 master。其它类似 /Users/yunai/Java/ruoyi-vue-pro-jdk25/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpmaster 也要!  
20 /** 14 /**
21 * IoT Modbus TCP Master 轮询调度器:管理点位的轮询定时器,调度读取任务并上报结果 15 * IoT Modbus TCP Master 轮询调度器:管理点位的轮询定时器,调度读取任务并上报结果
22 * 16 *
23 * @author 芋道源码 17 * @author 芋道源码
24 */ 18 */
25 @Slf4j 19 @Slf4j
26 -public class IotModbusTcpPollScheduler { 20 +public class IotModbusTcpPollScheduler extends AbstractIotModbusPollScheduler {
27 21
28 - private final Vertx vertx;  
29 private final IotModbusTcpConnectionManager connectionManager; 22 private final IotModbusTcpConnectionManager connectionManager;
30 private final IotModbusTcpUpstreamHandler upstreamHandler; 23 private final IotModbusTcpUpstreamHandler upstreamHandler;
31 private final IotModbusTcpConfigCacheService configCacheService; 24 private final IotModbusTcpConfigCacheService configCacheService;
32 25
33 - /**  
34 - * 同设备最小请求间隔(毫秒),防止 Modbus 设备性能不足时请求堆积  
35 - */  
36 - private static final long MIN_REQUEST_INTERVAL = 100;  
37 -  
38 - /**  
39 - * 设备点位的定时器映射:deviceId -> (pointId -> PointTimerInfo)  
40 - */  
41 - private final Map<Long, Map<Long, PointTimerInfo>> devicePointTimers = new ConcurrentHashMap<>();  
42 -  
43 - /**  
44 - * per-device 请求队列:deviceId -> 待执行请求队列  
45 - */  
46 - private final Map<Long, Queue<Runnable>> deviceRequestQueues = new ConcurrentHashMap<>();  
47 -  
48 - /**  
49 - * per-device 上次请求时间戳:deviceId -> lastRequestTimeMs  
50 - */  
51 - private final Map<Long, Long> deviceLastRequestTime = new ConcurrentHashMap<>();  
52 -  
53 - /**  
54 - * per-device 延迟 timer 标记:deviceId -> 是否有延迟 timer 在等待  
55 - */  
56 - private final Map<Long, Boolean> deviceDelayTimerActive = new ConcurrentHashMap<>();  
57 -  
58 public IotModbusTcpPollScheduler(Vertx vertx, 26 public IotModbusTcpPollScheduler(Vertx vertx,
59 IotModbusTcpConnectionManager connectionManager, 27 IotModbusTcpConnectionManager connectionManager,
60 IotModbusTcpUpstreamHandler upstreamHandler, 28 IotModbusTcpUpstreamHandler upstreamHandler,
61 IotModbusTcpConfigCacheService configCacheService) { 29 IotModbusTcpConfigCacheService configCacheService) {
62 - this.vertx = vertx; 30 + super(vertx);
63 this.connectionManager = connectionManager; 31 this.connectionManager = connectionManager;
64 this.upstreamHandler = upstreamHandler; 32 this.upstreamHandler = upstreamHandler;
65 this.configCacheService = configCacheService; 33 this.configCacheService = configCacheService;
66 } 34 }
67 35
68 - // ========== 点位定时器 ==========  
69 -  
70 - /**  
71 - * 点位定时器信息  
72 - */  
73 - @Data  
74 - @AllArgsConstructor  
75 - private static class PointTimerInfo {  
76 -  
77 - /**  
78 - * Vert.x 定时器 ID  
79 - */  
80 - private Long timerId;  
81 - /**  
82 - * 轮询间隔(用于判断是否需要更新定时器)  
83 - */  
84 - private Integer pollInterval;  
85 -  
86 - }  
87 -  
88 - // ========== 轮询管理 ==========  
89 -  
90 - /**  
91 - * 更新轮询任务(增量更新)  
92 - *  
93 - * 1. 【删除】点位:停止对应的轮询定时器  
94 - * 2. 【新增】点位:创建对应的轮询定时器  
95 - * 3. 【修改】点位:pollInterval 变化,重建对应的轮询定时器  
96 - * 4. 其他属性变化:不需要重建定时器(pollPoint 运行时从 configCache 取最新 point)  
97 - */  
98 - public void updatePolling(IotModbusDeviceConfigRespDTO config) {  
99 - Long deviceId = config.getDeviceId();  
100 - List<IotModbusPointRespDTO> newPoints = config.getPoints();  
101 - Map<Long, PointTimerInfo> currentTimers = devicePointTimers  
102 - .computeIfAbsent(deviceId, k -> new ConcurrentHashMap<>());  
103 - // 1.1 计算新配置中的点位 ID 集合  
104 - Set<Long> newPointIds = convertSet(newPoints, IotModbusPointRespDTO::getId);  
105 - // 1.2 计算删除的点位 ID 集合  
106 - Set<Long> removedPointIds = new HashSet<>(currentTimers.keySet());  
107 - removedPointIds.removeAll(newPointIds);  
108 -  
109 - // 2. 处理删除的点位:停止不再存在的定时器  
110 - for (Long pointId : removedPointIds) {  
111 - PointTimerInfo timerInfo = currentTimers.remove(pointId);  
112 - if (timerInfo != null) {  
113 - vertx.cancelTimer(timerInfo.getTimerId());  
114 - log.debug("[updatePolling][设备 {} 点位 {} 定时器已删除]", deviceId, pointId);  
115 - }  
116 - }  
117 -  
118 - // 3. 处理新增和修改的点位  
119 - if (CollUtil.isEmpty(newPoints)) {  
120 - return;  
121 - }  
122 - for (IotModbusPointRespDTO point : newPoints) {  
123 - Long pointId = point.getId();  
124 - Integer newPollInterval = point.getPollInterval();  
125 - PointTimerInfo existingTimer = currentTimers.get(pointId);  
126 - // 3.1 新增点位:创建定时器  
127 - if (existingTimer == null) {  
128 - Long timerId = createPollTimer(deviceId, pointId, newPollInterval);  
129 - if (timerId != null) {  
130 - currentTimers.put(pointId, new PointTimerInfo(timerId, newPollInterval));  
131 - log.debug("[updatePolling][设备 {} 点位 {} 定时器已创建, interval={}ms]",  
132 - deviceId, pointId, newPollInterval);  
133 - }  
134 - } else if (!Objects.equals(existingTimer.getPollInterval(), newPollInterval)) {  
135 - // 3.2 pollInterval 变化:重建定时器  
136 - vertx.cancelTimer(existingTimer.getTimerId());  
137 - Long timerId = createPollTimer(deviceId, pointId, newPollInterval);  
138 - if (timerId != null) {  
139 - currentTimers.put(pointId, new PointTimerInfo(timerId, newPollInterval));  
140 - log.debug("[updatePolling][设备 {} 点位 {} 定时器已更新, interval={}ms -> {}ms]",  
141 - deviceId, pointId, existingTimer.getPollInterval(), newPollInterval);  
142 - } else {  
143 - currentTimers.remove(pointId);  
144 - }  
145 - }  
146 - // 3.3 其他属性变化:无需重建定时器,因为 pollPoint() 运行时从 configCache 获取最新 point,自动使用新配置  
147 - }  
148 - }  
149 -  
150 - /**  
151 - * 创建轮询定时器  
152 - * <p>  
153 - * 闭包只捕获 deviceId 和 pointId,运行时从 configCache 获取最新配置,避免旧快照问题。  
154 - */  
155 - private Long createPollTimer(Long deviceId, Long pointId, Integer pollInterval) {  
156 - if (pollInterval == null || pollInterval <= 0) {  
157 - return null;  
158 - }  
159 - return vertx.setPeriodic(pollInterval, timerId -> {  
160 - try {  
161 - submitPollRequest(deviceId, pointId);  
162 - } catch (Exception e) {  
163 - log.error("[createPollTimer][轮询点位失败, deviceId={}, pointId={}]", deviceId, pointId, e);  
164 - }  
165 - });  
166 - }  
167 -  
168 - // ========== 请求队列(per-device 限速) ==========  
169 -  
170 - /**  
171 - * 提交轮询请求到设备请求队列(保证同设备请求间隔)  
172 - */  
173 - private void submitPollRequest(Long deviceId, Long pointId) {  
174 - Queue<Runnable> queue = deviceRequestQueues.computeIfAbsent(deviceId, k -> new ConcurrentLinkedQueue<>());  
175 - queue.offer(() -> pollPoint(deviceId, pointId));  
176 - processDeviceQueue(deviceId);  
177 - }  
178 -  
179 - /**  
180 - * 处理设备请求队列  
181 - */  
182 - private void processDeviceQueue(Long deviceId) {  
183 - Queue<Runnable> queue = deviceRequestQueues.get(deviceId);  
184 - if (CollUtil.isEmpty(queue)) {  
185 - return;  
186 - }  
187 - // 检查是否已有延迟 timer 在等待  
188 - if (Boolean.TRUE.equals(deviceDelayTimerActive.get(deviceId))) {  
189 - return;  
190 - }  
191 - long now = System.currentTimeMillis();  
192 - long lastTime = deviceLastRequestTime.getOrDefault(deviceId, 0L);  
193 - long elapsed = now - lastTime;  
194 - if (elapsed >= MIN_REQUEST_INTERVAL) {  
195 - // 满足间隔要求,立即执行  
196 - Runnable task = queue.poll();  
197 - if (task != null) {  
198 - deviceLastRequestTime.put(deviceId, now);  
199 - task.run();  
200 - // 继续处理队列中的下一个(如果有的话,需要延迟)  
201 - if (!queue.isEmpty()) {  
202 - scheduleNextRequest(deviceId);  
203 - }  
204 - }  
205 - } else {  
206 - // 需要延迟  
207 - scheduleNextRequest(deviceId, MIN_REQUEST_INTERVAL - elapsed);  
208 - }  
209 - }  
210 -  
211 - private void scheduleNextRequest(Long deviceId) {  
212 - scheduleNextRequest(deviceId, MIN_REQUEST_INTERVAL);  
213 - }  
214 -  
215 - private void scheduleNextRequest(Long deviceId, long delayMs) {  
216 - deviceDelayTimerActive.put(deviceId, true);  
217 - vertx.setTimer(delayMs, id -> {  
218 - deviceDelayTimerActive.put(deviceId, false);  
219 - Queue<Runnable> queue = deviceRequestQueues.get(deviceId);  
220 - if (CollUtil.isEmpty(queue)) {  
221 - Runnable task = queue.poll();  
222 - if (task != null) {  
223 - deviceLastRequestTime.put(deviceId, System.currentTimeMillis());  
224 - task.run();  
225 - }  
226 - // 继续处理  
227 - if (queue != null && !queue.isEmpty()) {  
228 - scheduleNextRequest(deviceId);  
229 - }  
230 - }  
231 - });  
232 - }  
233 -  
234 // ========== 轮询执行 ========== 36 // ========== 轮询执行 ==========
235 37
236 /** 38 /**
237 * 轮询单个点位 39 * 轮询单个点位
238 */ 40 */
239 - private void pollPoint(Long deviceId, Long pointId) { 41 + @Override
  42 + protected void pollPoint(Long deviceId, Long pointId) {
240 // 1.1 从 configCache 获取最新配置 43 // 1.1 从 configCache 获取最新配置
241 IotModbusDeviceConfigRespDTO config = configCacheService.getConfig(deviceId); 44 IotModbusDeviceConfigRespDTO config = configCacheService.getConfig(deviceId);
242 if (config == null || CollUtil.isEmpty(config.getPoints())) { 45 if (config == null || CollUtil.isEmpty(config.getPoints())) {
@@ -244,8 +47,7 @@ public class IotModbusTcpPollScheduler { @@ -244,8 +47,7 @@ public class IotModbusTcpPollScheduler {
244 return; 47 return;
245 } 48 }
246 // 1.2 查找点位 49 // 1.2 查找点位
247 - // TODO @AI:是不是这里,可以抽到 IotModbusUtils 里?感觉应该有几个地方需要的;  
248 - IotModbusPointRespDTO point = CollUtil.findOne(config.getPoints(), p -> p.getId().equals(pointId)); 50 + IotModbusPointRespDTO point = IotModbusCommonUtils.findPointById(config, pointId);
249 if (point == null) { 51 if (point == null) {
250 log.warn("[pollPoint][设备 {} 点位 {} 未找到]", deviceId, pointId); 52 log.warn("[pollPoint][设备 {} 点位 {} 未找到]", deviceId, pointId);
251 return; 53 return;
@@ -259,45 +61,13 @@ public class IotModbusTcpPollScheduler { @@ -259,45 +61,13 @@ public class IotModbusTcpPollScheduler {
259 } 61 }
260 // 2.2 获取 slave ID 62 // 2.2 获取 slave ID
261 Integer slaveId = connectionManager.getSlaveId(deviceId); 63 Integer slaveId = connectionManager.getSlaveId(deviceId);
262 - if (slaveId == null) {  
263 - log.warn("[pollPoint][设备 {} 没有 slaveId]", deviceId);  
264 - return;  
265 - } 64 + Assert.notNull(slaveId, "设备 {} 没有配置 slaveId", deviceId);
266 65
267 // 3. 执行 Modbus 读取 66 // 3. 执行 Modbus 读取
268 - IotModbusTcpClientUtils.read(connection, slaveId, point) 67 + IotModbusTcpMasterUtils.read(connection, slaveId, point)
269 .onSuccess(rawValue -> upstreamHandler.handleReadResult(config, point, rawValue)) 68 .onSuccess(rawValue -> upstreamHandler.handleReadResult(config, point, rawValue))
270 .onFailure(e -> log.error("[pollPoint][读取点位失败, deviceId={}, identifier={}]", 69 .onFailure(e -> log.error("[pollPoint][读取点位失败, deviceId={}, identifier={}]",
271 deviceId, point.getIdentifier(), e)); 70 deviceId, point.getIdentifier(), e));
272 } 71 }
273 72
274 - // ========== 停止 ==========  
275 -  
276 - /**  
277 - * 停止设备的轮询  
278 - */  
279 - public void stopPolling(Long deviceId) {  
280 - Map<Long, PointTimerInfo> timers = devicePointTimers.remove(deviceId);  
281 - if (CollUtil.isEmpty(timers)) {  
282 - return;  
283 - }  
284 - for (PointTimerInfo timerInfo : timers.values()) {  
285 - vertx.cancelTimer(timerInfo.getTimerId());  
286 - }  
287 - // 清理请求队列  
288 - deviceRequestQueues.remove(deviceId);  
289 - deviceLastRequestTime.remove(deviceId);  
290 - deviceDelayTimerActive.remove(deviceId);  
291 - log.debug("[stopPolling][设备 {} 停止了 {} 个轮询定时器]", deviceId, timers.size());  
292 - }  
293 -  
294 - /**  
295 - * 停止所有轮询  
296 - */  
297 - public void stopAll() {  
298 - for (Long deviceId : new ArrayList<>(devicePointTimers.keySet())) {  
299 - stopPolling(deviceId);  
300 - }  
301 - }  
302 -  
303 } 73 }
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpslave/codec/IotModbusFrame.java
1 package cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpslave.codec; 1 package cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpslave.codec;
2 2
3 import cn.iocoder.yudao.module.iot.core.enums.IotModbusFrameFormatEnum; 3 import cn.iocoder.yudao.module.iot.core.enums.IotModbusFrameFormatEnum;
4 -import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.common.IotModbusUtils; 4 +import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.common.utils.IotModbusCommonUtils;
5 import lombok.Data; 5 import lombok.Data;
6 import lombok.experimental.Accessors; 6 import lombok.experimental.Accessors;
7 7
@@ -39,7 +39,7 @@ public class IotModbusFrame { @@ -39,7 +39,7 @@ public class IotModbusFrame {
39 * 当功能码最高位为 1 时(异常响应),此字段存储异常码。 39 * 当功能码最高位为 1 时(异常响应),此字段存储异常码。
40 * 为 null 表示非异常响应。 40 * 为 null 表示非异常响应。
41 * 41 *
42 - * @see IotModbusUtils#FC_EXCEPTION_MASK 42 + * @see IotModbusCommonUtils#FC_EXCEPTION_MASK
43 */ 43 */
44 private Integer exceptionCode; 44 private Integer exceptionCode;
45 45
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpslave/codec/IotModbusFrameDecoder.java
1 package cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpslave.codec; 1 package cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpslave.codec;
2 2
3 import cn.iocoder.yudao.module.iot.core.enums.IotModbusFrameFormatEnum; 3 import cn.iocoder.yudao.module.iot.core.enums.IotModbusFrameFormatEnum;
4 -import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.common.IotModbusUtils; 4 +import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.common.utils.IotModbusCommonUtils;
5 import io.vertx.core.Handler; 5 import io.vertx.core.Handler;
6 import io.vertx.core.buffer.Buffer; 6 import io.vertx.core.buffer.Buffer;
7 import io.vertx.core.parsetools.RecordParser; 7 import io.vertx.core.parsetools.RecordParser;
@@ -96,7 +96,7 @@ public class IotModbusFrameDecoder { @@ -96,7 +96,7 @@ public class IotModbusFrameDecoder {
96 return null; 96 return null;
97 } 97 }
98 // 校验 CRC 98 // 校验 CRC
99 - if (!IotModbusUtils.verifyCrc16(data)) { 99 + if (!IotModbusCommonUtils.verifyCrc16(data)) {
100 log.warn("[decodeRtuResponse][CRC 校验失败]"); 100 log.warn("[decodeRtuResponse][CRC 校验失败]");
101 return null; 101 return null;
102 } 102 }
@@ -119,8 +119,8 @@ public class IotModbusFrameDecoder { @@ -119,8 +119,8 @@ public class IotModbusFrameDecoder {
119 .setPdu(pdu) 119 .setPdu(pdu)
120 .setTransactionId(transactionId); 120 .setTransactionId(transactionId);
121 // 异常响应 121 // 异常响应
122 - if (IotModbusUtils.isExceptionResponse(functionCode)) {  
123 - frame.setFunctionCode(IotModbusUtils.extractOriginalFunctionCode(functionCode)); 122 + if (IotModbusCommonUtils.isExceptionResponse(functionCode)) {
  123 + frame.setFunctionCode(IotModbusCommonUtils.extractOriginalFunctionCode(functionCode));
124 if (pdu.length >= 1) { 124 if (pdu.length >= 1) {
125 frame.setExceptionCode(pdu[0] & 0xFF); 125 frame.setExceptionCode(pdu[0] & 0xFF);
126 } 126 }
@@ -281,7 +281,7 @@ public class IotModbusFrameDecoder { @@ -281,7 +281,7 @@ public class IotModbusFrameDecoder {
281 this.slaveId = bytes[0]; 281 this.slaveId = bytes[0];
282 this.functionCode = bytes[1]; 282 this.functionCode = bytes[1];
283 int fc = functionCode & 0xFF; 283 int fc = functionCode & 0xFF;
284 - if (IotModbusUtils.isExceptionResponse(fc)) { 284 + if (IotModbusCommonUtils.isExceptionResponse(fc)) {
285 // 异常响应:完整帧 = slaveId(1) + FC(1) + exceptionCode(1) + CRC(2) = 5 字节 285 // 异常响应:完整帧 = slaveId(1) + FC(1) + exceptionCode(1) + CRC(2) = 5 字节
286 // 已有 6 字节(多 1 字节),取前 5 字节组装 286 // 已有 6 字节(多 1 字节),取前 5 字节组装
287 Buffer frame = Buffer.buffer(5); 287 Buffer frame = Buffer.buffer(5);
@@ -290,7 +290,7 @@ public class IotModbusFrameDecoder { @@ -290,7 +290,7 @@ public class IotModbusFrameDecoder {
290 frame.appendBytes(bytes, 2, 3); // exceptionCode + CRC 290 frame.appendBytes(bytes, 2, 3); // exceptionCode + CRC
291 emitFrame(frame); 291 emitFrame(frame);
292 resetToHeader(); 292 resetToHeader();
293 - } else if (IotModbusUtils.isReadResponse(fc) || fc == customFunctionCode) { 293 + } else if (IotModbusCommonUtils.isReadResponse(fc) || fc == customFunctionCode) {
294 // 读响应或自定义 FC:bytes[2] = byteCount 294 // 读响应或自定义 FC:bytes[2] = byteCount
295 this.byteCount = bytes[2]; 295 this.byteCount = bytes[2];
296 int bc = byteCount & 0xFF; 296 int bc = byteCount & 0xFF;
@@ -315,7 +315,7 @@ public class IotModbusFrameDecoder { @@ -315,7 +315,7 @@ public class IotModbusFrameDecoder {
315 this.expectedDataLen = bc + 2; // byteCount 个数据 + 2 CRC 315 this.expectedDataLen = bc + 2; // byteCount 个数据 + 2 CRC
316 parser.fixedSizeMode(remaining); 316 parser.fixedSizeMode(remaining);
317 } 317 }
318 - } else if (IotModbusUtils.isWriteResponse(fc)) { 318 + } else if (IotModbusCommonUtils.isWriteResponse(fc)) {
319 // 写响应:总长 = slaveId(1) + FC(1) + addr(2) + value/qty(2) + CRC(2) = 8 字节 319 // 写响应:总长 = slaveId(1) + FC(1) + addr(2) + value/qty(2) + CRC(2) = 8 字节
320 // 已有 6 字节,还需 2 字节 320 // 已有 6 字节,还需 2 字节
321 state = STATE_WRITE_BODY; 321 state = STATE_WRITE_BODY;
@@ -356,15 +356,15 @@ public class IotModbusFrameDecoder { @@ -356,15 +356,15 @@ public class IotModbusFrameDecoder {
356 this.slaveId = header[0]; 356 this.slaveId = header[0];
357 this.functionCode = header[1]; 357 this.functionCode = header[1];
358 int fc = functionCode & 0xFF; 358 int fc = functionCode & 0xFF;
359 - if (IotModbusUtils.isExceptionResponse(fc)) { 359 + if (IotModbusCommonUtils.isExceptionResponse(fc)) {
360 // 异常响应 360 // 异常响应
361 state = STATE_EXCEPTION_BODY; 361 state = STATE_EXCEPTION_BODY;
362 parser.fixedSizeMode(3); // exceptionCode(1) + CRC(2) 362 parser.fixedSizeMode(3); // exceptionCode(1) + CRC(2)
363 - } else if (IotModbusUtils.isReadResponse(fc) || fc == customFunctionCode) { 363 + } else if (IotModbusCommonUtils.isReadResponse(fc) || fc == customFunctionCode) {
364 // 读响应或自定义 FC 364 // 读响应或自定义 FC
365 state = STATE_READ_BYTE_COUNT; 365 state = STATE_READ_BYTE_COUNT;
366 parser.fixedSizeMode(1); // byteCount 366 parser.fixedSizeMode(1); // byteCount
367 - } else if (IotModbusUtils.isWriteResponse(fc)) { 367 + } else if (IotModbusCommonUtils.isWriteResponse(fc)) {
368 // 写响应 368 // 写响应
369 state = STATE_WRITE_BODY; 369 state = STATE_WRITE_BODY;
370 pendingData = Buffer.buffer(); 370 pendingData = Buffer.buffer();
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpslave/codec/IotModbusFrameEncoder.java
1 package cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpslave.codec; 1 package cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpslave.codec;
2 2
3 import cn.iocoder.yudao.module.iot.core.enums.IotModbusFrameFormatEnum; 3 import cn.iocoder.yudao.module.iot.core.enums.IotModbusFrameFormatEnum;
4 -import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.common.IotModbusUtils; 4 +import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.common.utils.IotModbusCommonUtils;
5 import lombok.RequiredArgsConstructor; 5 import lombok.RequiredArgsConstructor;
6 import lombok.extern.slf4j.Slf4j; 6 import lombok.extern.slf4j.Slf4j;
7 7
@@ -60,7 +60,7 @@ public class IotModbusFrameEncoder { @@ -60,7 +60,7 @@ public class IotModbusFrameEncoder {
60 public byte[] encodeWriteSingleRequest(int slaveId, int functionCode, int address, int value, 60 public byte[] encodeWriteSingleRequest(int slaveId, int functionCode, int address, int value,
61 IotModbusFrameFormatEnum format, Integer transactionId) { 61 IotModbusFrameFormatEnum format, Integer transactionId) {
62 // FC05 单写线圈:Modbus 标准要求 value 为 0xFF00(ON)或 0x0000(OFF) 62 // FC05 单写线圈:Modbus 标准要求 value 为 0xFF00(ON)或 0x0000(OFF)
63 - if (functionCode == IotModbusUtils.FC_WRITE_SINGLE_COIL) { 63 + if (functionCode == IotModbusCommonUtils.FC_WRITE_SINGLE_COIL) {
64 value = (value != 0) ? 0xFF00 : 0x0000; 64 value = (value != 0) ? 0xFF00 : 0x0000;
65 } 65 }
66 // PDU: [FC(1)] [Address(2)] [Value(2)] 66 // PDU: [FC(1)] [Address(2)] [Value(2)]
@@ -120,7 +120,7 @@ public class IotModbusFrameEncoder { @@ -120,7 +120,7 @@ public class IotModbusFrameEncoder {
120 int quantity = values.length; 120 int quantity = values.length;
121 int byteCount = (quantity + 7) / 8; // 向上取整 121 int byteCount = (quantity + 7) / 8; // 向上取整
122 byte[] pdu = new byte[6 + byteCount]; 122 byte[] pdu = new byte[6 + byteCount];
123 - pdu[0] = (byte) IotModbusUtils.FC_WRITE_MULTIPLE_COILS; // FC15 123 + pdu[0] = (byte) IotModbusCommonUtils.FC_WRITE_MULTIPLE_COILS; // FC15
124 pdu[1] = (byte) ((address >> 8) & 0xFF); 124 pdu[1] = (byte) ((address >> 8) & 0xFF);
125 pdu[2] = (byte) (address & 0xFF); 125 pdu[2] = (byte) (address & 0xFF);
126 pdu[3] = (byte) ((quantity >> 8) & 0xFF); 126 pdu[3] = (byte) ((quantity >> 8) & 0xFF);
@@ -204,7 +204,7 @@ public class IotModbusFrameEncoder { @@ -204,7 +204,7 @@ public class IotModbusFrameEncoder {
204 frame[0] = (byte) slaveId; 204 frame[0] = (byte) slaveId;
205 System.arraycopy(pdu, 0, frame, 1, pdu.length); 205 System.arraycopy(pdu, 0, frame, 1, pdu.length);
206 // 计算并追加 CRC16 206 // 计算并追加 CRC16
207 - int crc = IotModbusUtils.calculateCrc16(frame, frame.length - 2); 207 + int crc = IotModbusCommonUtils.calculateCrc16(frame, frame.length - 2);
208 frame[frame.length - 2] = (byte) (crc & 0xFF); // CRC Low 208 frame[frame.length - 2] = (byte) (crc & 0xFF); // CRC Low
209 frame[frame.length - 1] = (byte) ((crc >> 8) & 0xFF); // CRC High 209 frame[frame.length - 1] = (byte) ((crc >> 8) & 0xFF); // CRC High
210 return frame; 210 return frame;
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpslave/handler/downstream/IotModbusTcpSlaveDownstreamHandler.java
@@ -7,7 +7,7 @@ import cn.iocoder.yudao.module.iot.core.biz.dto.IotModbusPointRespDTO; @@ -7,7 +7,7 @@ import cn.iocoder.yudao.module.iot.core.biz.dto.IotModbusPointRespDTO;
7 import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum; 7 import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
8 import cn.iocoder.yudao.module.iot.core.enums.IotModbusFrameFormatEnum; 8 import cn.iocoder.yudao.module.iot.core.enums.IotModbusFrameFormatEnum;
9 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; 9 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
10 -import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.common.IotModbusUtils; 10 +import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.common.utils.IotModbusCommonUtils;
11 import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpslave.codec.IotModbusFrameEncoder; 11 import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpslave.codec.IotModbusFrameEncoder;
12 import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpslave.manager.IotModbusTcpSlaveConfigCacheService; 12 import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpslave.manager.IotModbusTcpSlaveConfigCacheService;
13 import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpslave.manager.IotModbusTcpSlaveConnectionManager; 13 import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpslave.manager.IotModbusTcpSlaveConnectionManager;
@@ -82,13 +82,13 @@ public class IotModbusTcpSlaveDownstreamHandler { @@ -82,13 +82,13 @@ public class IotModbusTcpSlaveDownstreamHandler {
82 String identifier = entry.getKey(); 82 String identifier = entry.getKey();
83 Object value = entry.getValue(); 83 Object value = entry.getValue();
84 // 2.1 查找对应的点位配置 84 // 2.1 查找对应的点位配置
85 - IotModbusPointRespDTO point = IotModbusUtils.findPoint(config, identifier); 85 + IotModbusPointRespDTO point = IotModbusCommonUtils.findPoint(config, identifier);
86 if (point == null) { 86 if (point == null) {
87 log.warn("[handle][设备 {} 没有点位配置: {}]", message.getDeviceId(), identifier); 87 log.warn("[handle][设备 {} 没有点位配置: {}]", message.getDeviceId(), identifier);
88 continue; 88 continue;
89 } 89 }
90 // 2.2 检查是否支持写操作 90 // 2.2 检查是否支持写操作
91 - if (!IotModbusUtils.isWritable(point.getFunctionCode())) { 91 + if (!IotModbusCommonUtils.isWritable(point.getFunctionCode())) {
92 log.warn("[handle][点位 {} 不支持写操作, 功能码={}]", identifier, point.getFunctionCode()); 92 log.warn("[handle][点位 {} 不支持写操作, 功能码={}]", identifier, point.getFunctionCode());
93 continue; 93 continue;
94 } 94 }
@@ -104,7 +104,7 @@ public class IotModbusTcpSlaveDownstreamHandler { @@ -104,7 +104,7 @@ public class IotModbusTcpSlaveDownstreamHandler {
104 private void writeProperty(Long deviceId, ConnectionInfo connInfo, 104 private void writeProperty(Long deviceId, ConnectionInfo connInfo,
105 IotModbusPointRespDTO point, Object value) { 105 IotModbusPointRespDTO point, Object value) {
106 // 1.1 转换属性值为原始值 106 // 1.1 转换属性值为原始值
107 - int[] rawValues = IotModbusUtils.convertToRawValues(value, point); 107 + int[] rawValues = IotModbusCommonUtils.convertToRawValues(value, point);
108 108
109 // 1.2 确定帧格式和事务 ID 109 // 1.2 确定帧格式和事务 ID
110 IotModbusFrameFormatEnum frameFormat = connInfo.getFrameFormat(); 110 IotModbusFrameFormatEnum frameFormat = connInfo.getFrameFormat();
@@ -117,8 +117,8 @@ public class IotModbusTcpSlaveDownstreamHandler { @@ -117,8 +117,8 @@ public class IotModbusTcpSlaveDownstreamHandler {
117 // 1.3 编码写请求 117 // 1.3 编码写请求
118 byte[] data; 118 byte[] data;
119 int readFunctionCode = point.getFunctionCode(); 119 int readFunctionCode = point.getFunctionCode();
120 - Integer writeSingleCode = IotModbusUtils.getWriteSingleFunctionCode(readFunctionCode);  
121 - Integer writeMultipleCode = IotModbusUtils.getWriteMultipleFunctionCode(readFunctionCode); 120 + Integer writeSingleCode = IotModbusCommonUtils.getWriteSingleFunctionCode(readFunctionCode);
  121 + Integer writeMultipleCode = IotModbusCommonUtils.getWriteMultipleFunctionCode(readFunctionCode);
122 if (rawValues.length == 1 && writeSingleCode != null) { 122 if (rawValues.length == 1 && writeSingleCode != null) {
123 // 单个值:使用单写功能码(FC05/FC06) 123 // 单个值:使用单写功能码(FC05/FC06)
124 data = frameEncoder.encodeWriteSingleRequest(slaveId, writeSingleCode, 124 data = frameEncoder.encodeWriteSingleRequest(slaveId, writeSingleCode,
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpslave/handler/downstream/IotModbusTcpSlaveDownstreamSubscriber.java
@@ -2,7 +2,7 @@ package cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpslave.handler.dow @@ -2,7 +2,7 @@ package cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpslave.handler.dow
2 2
3 import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus; 3 import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus;
4 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; 4 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
5 -import cn.iocoder.yudao.module.iot.gateway.protocol.IotProtocolDownstreamSubscriber; 5 +import cn.iocoder.yudao.module.iot.gateway.protocol.AbstractIotProtocolDownstreamSubscriber;
6 import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpslave.IotModbusTcpSlaveProtocol; 6 import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpslave.IotModbusTcpSlaveProtocol;
7 import lombok.extern.slf4j.Slf4j; 7 import lombok.extern.slf4j.Slf4j;
8 8
@@ -12,7 +12,7 @@ import lombok.extern.slf4j.Slf4j; @@ -12,7 +12,7 @@ import lombok.extern.slf4j.Slf4j;
12 * @author 芋道源码 12 * @author 芋道源码
13 */ 13 */
14 @Slf4j 14 @Slf4j
15 -public class IotModbusTcpSlaveDownstreamSubscriber extends IotProtocolDownstreamSubscriber { 15 +public class IotModbusTcpSlaveDownstreamSubscriber extends AbstractIotProtocolDownstreamSubscriber {
16 16
17 private final IotModbusTcpSlaveDownstreamHandler downstreamHandler; 17 private final IotModbusTcpSlaveDownstreamHandler downstreamHandler;
18 18
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpslave/handler/upstream/IotModbusTcpSlaveUpstreamHandler.java
@@ -19,7 +19,7 @@ import cn.iocoder.yudao.module.iot.core.enums.IotModbusFrameFormatEnum; @@ -19,7 +19,7 @@ import cn.iocoder.yudao.module.iot.core.enums.IotModbusFrameFormatEnum;
19 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; 19 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
20 import cn.iocoder.yudao.module.iot.core.topic.IotDeviceIdentity; 20 import cn.iocoder.yudao.module.iot.core.topic.IotDeviceIdentity;
21 import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils; 21 import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils;
22 -import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.common.IotModbusUtils; 22 +import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.common.utils.IotModbusCommonUtils;
23 import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpslave.codec.IotModbusFrame; 23 import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpslave.codec.IotModbusFrame;
24 import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpslave.codec.IotModbusFrameEncoder; 24 import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpslave.codec.IotModbusFrameEncoder;
25 import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpslave.manager.IotModbusTcpSlaveConfigCacheService; 25 import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpslave.manager.IotModbusTcpSlaveConfigCacheService;
@@ -237,7 +237,7 @@ public class IotModbusTcpSlaveUpstreamHandler { @@ -237,7 +237,7 @@ public class IotModbusTcpSlaveUpstreamHandler {
237 return; 237 return;
238 } 238 }
239 // 2.2 提取寄存器值 239 // 2.2 提取寄存器值
240 - int[] rawValues = IotModbusUtils.extractValues(frame); 240 + int[] rawValues = IotModbusCommonUtils.extractValues(frame);
241 if (rawValues == null) { 241 if (rawValues == null) {
242 log.warn("[handlePollingResponse][提取寄存器值失败, deviceId={}, identifier={}]", 242 log.warn("[handlePollingResponse][提取寄存器值失败, deviceId={}, identifier={}]",
243 info.getDeviceId(), request.getIdentifier()); 243 info.getDeviceId(), request.getIdentifier());
@@ -248,14 +248,13 @@ public class IotModbusTcpSlaveUpstreamHandler { @@ -248,14 +248,13 @@ public class IotModbusTcpSlaveUpstreamHandler {
248 if (config == null || CollUtil.isEmpty(config.getPoints())) { 248 if (config == null || CollUtil.isEmpty(config.getPoints())) {
249 return; 249 return;
250 } 250 }
251 - IotModbusPointRespDTO point = CollUtil.findOne(config.getPoints(),  
252 - p -> p.getId().equals(request.getPointId())); 251 + IotModbusPointRespDTO point = IotModbusCommonUtils.findPointById(config, request.getPointId());
253 if (point == null) { 252 if (point == null) {
254 return; 253 return;
255 } 254 }
256 255
257 // 3.1 点位翻译 256 // 3.1 点位翻译
258 - Object convertedValue = IotModbusUtils.convertToPropertyValue(rawValues, point); 257 + Object convertedValue = IotModbusCommonUtils.convertToPropertyValue(rawValues, point);
259 // 3.2 上报属性 258 // 3.2 上报属性
260 Map<String, Object> params = MapUtil.of(request.getIdentifier(), convertedValue); 259 Map<String, Object> params = MapUtil.of(request.getIdentifier(), convertedValue);
261 IotDeviceMessage message = IotDeviceMessage.requestOf( 260 IotDeviceMessage message = IotDeviceMessage.requestOf(
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpslave/manager/IotModbusTcpSlaveConnectionManager.java
@@ -75,8 +75,8 @@ public class IotModbusTcpSlaveConnectionManager { @@ -75,8 +75,8 @@ public class IotModbusTcpSlaveConnectionManager {
75 try { 75 try {
76 oldSocket.close(); 76 oldSocket.close();
77 } catch (Exception e) { 77 } catch (Exception e) {
78 - // TODO @AI:这里日志可以打的更完整一点,方便追溯;比如:设备 ID、旧连接地址等  
79 - log.warn("[registerConnection][关闭旧 socket 失败]", e); 78 + log.warn("[registerConnection][关闭旧 socket 失败, deviceId={}, oldRemote={}]",
  79 + info.getDeviceId(), oldSocket.remoteAddress(), e);
80 } 80 }
81 } 81 }
82 82
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpslave/manager/IotModbusTcpSlavePollScheduler.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.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.lang.Assert;
4 import cn.iocoder.yudao.module.iot.core.biz.dto.IotModbusDeviceConfigRespDTO; 5 import cn.iocoder.yudao.module.iot.core.biz.dto.IotModbusDeviceConfigRespDTO;
5 import cn.iocoder.yudao.module.iot.core.biz.dto.IotModbusPointRespDTO; 6 import cn.iocoder.yudao.module.iot.core.biz.dto.IotModbusPointRespDTO;
6 import cn.iocoder.yudao.module.iot.core.enums.IotModbusFrameFormatEnum; 7 import cn.iocoder.yudao.module.iot.core.enums.IotModbusFrameFormatEnum;
  8 +import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.common.manager.AbstractIotModbusPollScheduler;
  9 +import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.common.utils.IotModbusCommonUtils;
7 import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpslave.codec.IotModbusFrameEncoder; 10 import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpslave.codec.IotModbusFrameEncoder;
8 import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpslave.manager.IotModbusTcpSlaveConnectionManager.ConnectionInfo; 11 import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpslave.manager.IotModbusTcpSlaveConnectionManager.ConnectionInfo;
9 import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpslave.manager.IotModbusTcpSlavePendingRequestManager.PendingRequest; 12 import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpslave.manager.IotModbusTcpSlavePendingRequestManager.PendingRequest;
10 import io.vertx.core.Vertx; 13 import io.vertx.core.Vertx;
11 -import lombok.AllArgsConstructor;  
12 -import lombok.Data;  
13 import lombok.extern.slf4j.Slf4j; 14 import lombok.extern.slf4j.Slf4j;
14 15
15 -import java.util.*;  
16 -import java.util.concurrent.ConcurrentHashMap;  
17 -import java.util.concurrent.ConcurrentLinkedQueue;  
18 import java.util.concurrent.atomic.AtomicInteger; 16 import java.util.concurrent.atomic.AtomicInteger;
19 17
20 -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;  
21 -  
22 /** 18 /**
23 - * IoT Modbus TCP Slave 轮询调度器  
24 - * <p>  
25 - * 管理点位的轮询定时器,为云端轮询模式的设备调度读取任务。  
26 - * 与 tcpmaster 的 {@code IotModbusTcpPollScheduler} 不同,这里不通过 j2mod 直接读取,而是:  
27 - * 1. 编码 Modbus 读请求帧  
28 - * 2. 通过 ConnectionManager 发送到设备的 TCP 连接  
29 - * 3. 将请求注册到 PendingRequestManager,等待设备响应  
30 - * <p>  
31 - * 闭包只捕获 deviceId + pointId,运行时从 configCacheService 获取最新 config 和 point,  
32 - * 避免闭包捕获旧快照导致上报消息用旧身份的问题。 19 + * IoT Modbus TCP Slave 轮询调度器:编码读请求帧,通过 TCP 连接发送到设备,注册 PendingRequest 等待响应
33 * 20 *
34 * @author 芋道源码 21 * @author 芋道源码
35 */ 22 */
36 @Slf4j 23 @Slf4j
37 -public class IotModbusTcpSlavePollScheduler { 24 +public class IotModbusTcpSlavePollScheduler extends AbstractIotModbusPollScheduler {
38 25
39 - private final Vertx vertx;  
40 private final IotModbusTcpSlaveConnectionManager connectionManager; 26 private final IotModbusTcpSlaveConnectionManager connectionManager;
41 private final IotModbusFrameEncoder frameEncoder; 27 private final IotModbusFrameEncoder frameEncoder;
42 private final IotModbusTcpSlavePendingRequestManager pendingRequestManager; 28 private final IotModbusTcpSlavePendingRequestManager pendingRequestManager;
@@ -47,29 +33,6 @@ public class IotModbusTcpSlavePollScheduler { @@ -47,29 +33,6 @@ public class IotModbusTcpSlavePollScheduler {
47 */ 33 */
48 private final AtomicInteger transactionIdCounter; 34 private final AtomicInteger transactionIdCounter;
49 35
50 - /**  
51 - * 同设备最小请求间隔(毫秒),防止 Modbus 设备性能不足时请求堆积  
52 - */  
53 - private static final long MIN_REQUEST_INTERVAL = 200;  
54 -  
55 - /**  
56 - * 设备点位的定时器映射:deviceId -> (pointId -> PointTimerInfo)  
57 - */  
58 - private final Map<Long, Map<Long, PointTimerInfo>> devicePointTimers = new ConcurrentHashMap<>();  
59 -  
60 - /**  
61 - * per-device 请求队列:deviceId -> 待执行请求队列  
62 - */  
63 - private final Map<Long, Queue<Runnable>> deviceRequestQueues = new ConcurrentHashMap<>();  
64 - /**  
65 - * per-device 上次请求时间戳:deviceId -> lastRequestTimeMs  
66 - */  
67 - private final Map<Long, Long> deviceLastRequestTime = new ConcurrentHashMap<>();  
68 - /**  
69 - * per-device 延迟 timer 标记:deviceId -> 是否有延迟 timer 在等待  
70 - */  
71 - private final Map<Long, Boolean> deviceDelayTimerActive = new ConcurrentHashMap<>();  
72 -  
73 public IotModbusTcpSlavePollScheduler(Vertx vertx, 36 public IotModbusTcpSlavePollScheduler(Vertx vertx,
74 IotModbusTcpSlaveConnectionManager connectionManager, 37 IotModbusTcpSlaveConnectionManager connectionManager,
75 IotModbusFrameEncoder frameEncoder, 38 IotModbusFrameEncoder frameEncoder,
@@ -77,7 +40,7 @@ public class IotModbusTcpSlavePollScheduler { @@ -77,7 +40,7 @@ public class IotModbusTcpSlavePollScheduler {
77 int requestTimeout, 40 int requestTimeout,
78 AtomicInteger transactionIdCounter, 41 AtomicInteger transactionIdCounter,
79 IotModbusTcpSlaveConfigCacheService configCacheService) { 42 IotModbusTcpSlaveConfigCacheService configCacheService) {
80 - this.vertx = vertx; 43 + super(vertx);
81 this.connectionManager = connectionManager; 44 this.connectionManager = connectionManager;
82 this.frameEncoder = frameEncoder; 45 this.frameEncoder = frameEncoder;
83 this.pendingRequestManager = pendingRequestManager; 46 this.pendingRequestManager = pendingRequestManager;
@@ -86,185 +49,13 @@ public class IotModbusTcpSlavePollScheduler { @@ -86,185 +49,13 @@ public class IotModbusTcpSlavePollScheduler {
86 this.configCacheService = configCacheService; 49 this.configCacheService = configCacheService;
87 } 50 }
88 51
89 - /**  
90 - * 点位定时器信息  
91 - */  
92 - @Data  
93 - @AllArgsConstructor  
94 - private static class PointTimerInfo {  
95 -  
96 - /**  
97 - * Vert.x 定时器 ID  
98 - */  
99 - private Long timerId;  
100 - /**  
101 - * 轮询间隔(用于判断是否需要更新定时器)  
102 - */  
103 - private Integer pollInterval;  
104 -  
105 - }  
106 -  
107 - // ========== 轮询管理 ==========  
108 -  
109 - /**  
110 - * 更新轮询任务(增量更新)  
111 - *  
112 - * 1. 【删除】点位:停止对应的轮询定时器  
113 - * 2. 【新增】点位:创建对应的轮询定时器  
114 - * 3. 【修改】点位:pollInterval 变化,重建对应的轮询定时器  
115 - * 4. 其他属性变化:不需要重建定时器(pollPoint 运行时从 configCache 取最新 point)  
116 - */  
117 - public void updatePolling(IotModbusDeviceConfigRespDTO config) {  
118 - Long deviceId = config.getDeviceId();  
119 - List<IotModbusPointRespDTO> newPoints = config.getPoints();  
120 - Map<Long, PointTimerInfo> currentTimers = devicePointTimers  
121 - .computeIfAbsent(deviceId, k -> new ConcurrentHashMap<>());  
122 - // 1.1 计算新配置中的点位 ID 集合  
123 - Set<Long> newPointIds = convertSet(newPoints, IotModbusPointRespDTO::getId);  
124 - // 1.2 计算删除的点位 ID 集合  
125 - Set<Long> removedPointIds = new HashSet<>(currentTimers.keySet());  
126 - removedPointIds.removeAll(newPointIds);  
127 -  
128 - // 2. 处理删除的点位:停止不再存在的定时器  
129 - for (Long pointId : removedPointIds) {  
130 - PointTimerInfo timerInfo = currentTimers.remove(pointId);  
131 - if (timerInfo != null) {  
132 - vertx.cancelTimer(timerInfo.getTimerId());  
133 - log.debug("[updatePolling][设备 {} 点位 {} 定时器已删除]", deviceId, pointId);  
134 - }  
135 - }  
136 -  
137 - // 3. 处理新增和修改的点位  
138 - if (CollUtil.isEmpty(newPoints)) {  
139 - return;  
140 - }  
141 - for (IotModbusPointRespDTO point : newPoints) {  
142 - Long pointId = point.getId();  
143 - Integer newPollInterval = point.getPollInterval();  
144 - PointTimerInfo existingTimer = currentTimers.get(pointId);  
145 - // 3.1 新增点位:创建定时器  
146 - if (existingTimer == null) {  
147 - Long timerId = createPollTimer(deviceId, pointId, newPollInterval);  
148 - if (timerId != null) {  
149 - currentTimers.put(pointId, new PointTimerInfo(timerId, newPollInterval));  
150 - log.debug("[updatePolling][设备 {} 点位 {} 定时器已创建, interval={}ms]",  
151 - deviceId, pointId, newPollInterval);  
152 - }  
153 - } else if (!Objects.equals(existingTimer.getPollInterval(), newPollInterval)) {  
154 - // 3.2 pollInterval 变化:重建定时器  
155 - vertx.cancelTimer(existingTimer.getTimerId());  
156 - Long timerId = createPollTimer(deviceId, pointId, newPollInterval);  
157 - if (timerId != null) {  
158 - currentTimers.put(pointId, new PointTimerInfo(timerId, newPollInterval));  
159 - log.debug("[updatePolling][设备 {} 点位 {} 定时器已更新, interval={}ms -> {}ms]",  
160 - deviceId, pointId, existingTimer.getPollInterval(), newPollInterval);  
161 - } else {  
162 - currentTimers.remove(pointId);  
163 - }  
164 - }  
165 - // 3.3 其他属性变化:无需重建定时器,因为 pollPoint() 运行时从 configCache 获取最新 point,自动使用新配置  
166 - }  
167 - }  
168 -  
169 - /**  
170 - * 创建轮询定时器  
171 - * <p>  
172 - * 闭包只捕获 deviceId 和 pointId,运行时从 configCache 获取最新配置,避免旧快照问题。  
173 - */  
174 - private Long createPollTimer(Long deviceId, Long pointId, Integer pollInterval) {  
175 - if (pollInterval == null || pollInterval <= 0) {  
176 - return null;  
177 - }  
178 - return vertx.setPeriodic(pollInterval, timerId -> {  
179 - try {  
180 - submitPollRequest(deviceId, pointId);  
181 - } catch (Exception e) {  
182 - log.error("[createPollTimer][轮询点位失败, deviceId={}, pointId={}]", deviceId, pointId, e);  
183 - }  
184 - });  
185 - }  
186 -  
187 - // ========== 请求队列(per-device 限速) ==========  
188 -  
189 - /**  
190 - * 提交轮询请求到设备请求队列(保证同设备请求间隔)  
191 - */  
192 - private void submitPollRequest(Long deviceId, Long pointId) {  
193 - // 1. 将请求添加到设备的请求队列  
194 - Queue<Runnable> queue = deviceRequestQueues.computeIfAbsent(deviceId, k -> new ConcurrentLinkedQueue<>());  
195 - queue.offer(() -> pollPoint(deviceId, pointId));  
196 -  
197 - // 2. 处理设备请求队列(如果没有延迟 timer 在等待)  
198 - processDeviceQueue(deviceId);  
199 - }  
200 -  
201 - /**  
202 - * 处理设备请求队列  
203 - */  
204 - private void processDeviceQueue(Long deviceId) {  
205 - Queue<Runnable> queue = deviceRequestQueues.get(deviceId);  
206 - if (CollUtil.isEmpty(queue)) {  
207 - return;  
208 - }  
209 - // 检查是否已有延迟 timer 在等待  
210 - if (Boolean.TRUE.equals(deviceDelayTimerActive.get(deviceId))) {  
211 - return;  
212 - }  
213 -  
214 - // TODO @AI:可以改成不满足间隔,然后 return,简化括号层级;  
215 - long now = System.currentTimeMillis();  
216 - long lastTime = deviceLastRequestTime.getOrDefault(deviceId, 0L);  
217 - long elapsed = now - lastTime;  
218 -  
219 - if (elapsed >= MIN_REQUEST_INTERVAL) {  
220 - // 满足间隔要求,立即执行  
221 - Runnable task = queue.poll();  
222 - if (task != null) {  
223 - deviceLastRequestTime.put(deviceId, now);  
224 - task.run();  
225 - // 继续处理队列中的下一个(如果有的话,需要延迟)  
226 - if (!queue.isEmpty()) {  
227 - scheduleNextRequest(deviceId);  
228 - }  
229 - }  
230 - } else {  
231 - // 需要延迟  
232 - scheduleNextRequest(deviceId, MIN_REQUEST_INTERVAL - elapsed);  
233 - }  
234 - }  
235 -  
236 - private void scheduleNextRequest(Long deviceId) {  
237 - scheduleNextRequest(deviceId, MIN_REQUEST_INTERVAL);  
238 - }  
239 -  
240 - private void scheduleNextRequest(Long deviceId, long delayMs) {  
241 - deviceDelayTimerActive.put(deviceId, true);  
242 - vertx.setTimer(delayMs, id -> {  
243 - deviceDelayTimerActive.put(deviceId, false);  
244 - Queue<Runnable> queue = deviceRequestQueues.get(deviceId);  
245 - // TODO @AI:if return?简化下?  
246 - if (CollUtil.isEmpty(queue)) {  
247 - Runnable task = queue.poll();  
248 - if (task != null) {  
249 - deviceLastRequestTime.put(deviceId, System.currentTimeMillis());  
250 - task.run();  
251 - }  
252 - // 继续处理  
253 - if (queue != null && !queue.isEmpty()) {  
254 - scheduleNextRequest(deviceId);  
255 - }  
256 - }  
257 - });  
258 - }  
259 -  
260 // ========== 轮询执行 ========== 52 // ========== 轮询执行 ==========
261 53
262 /** 54 /**
263 * 轮询单个点位 55 * 轮询单个点位
264 - * <p>  
265 - * 运行时从 configCacheService 获取最新的 config 和 point,而非使用闭包捕获的旧引用。  
266 */ 56 */
267 - private void pollPoint(Long deviceId, Long pointId) { 57 + @Override
  58 + protected void pollPoint(Long deviceId, Long pointId) {
268 // 1.1 从 configCache 获取最新配置 59 // 1.1 从 configCache 获取最新配置
269 IotModbusDeviceConfigRespDTO config = configCacheService.getConfig(deviceId); 60 IotModbusDeviceConfigRespDTO config = configCacheService.getConfig(deviceId);
270 if (config == null || CollUtil.isEmpty(config.getPoints())) { 61 if (config == null || CollUtil.isEmpty(config.getPoints())) {
@@ -272,34 +63,31 @@ public class IotModbusTcpSlavePollScheduler { @@ -272,34 +63,31 @@ public class IotModbusTcpSlavePollScheduler {
272 return; 63 return;
273 } 64 }
274 // 1.2 查找点位 65 // 1.2 查找点位
275 - IotModbusPointRespDTO point = CollUtil.findOne(config.getPoints(), p -> p.getId().equals(pointId)); 66 + IotModbusPointRespDTO point = IotModbusCommonUtils.findPointById(config, pointId);
276 if (point == null) { 67 if (point == null) {
277 log.warn("[pollPoint][设备 {} 点位 {} 未找到]", deviceId, pointId); 68 log.warn("[pollPoint][设备 {} 点位 {} 未找到]", deviceId, pointId);
278 return; 69 return;
279 } 70 }
280 71
281 - // 2. 获取连接信息  
282 - ConnectionInfo connInfo = connectionManager.getConnectionInfoByDeviceId(deviceId);  
283 - if (connInfo == null) { 72 + // 2.1 获取连接
  73 + ConnectionInfo connection = connectionManager.getConnectionInfoByDeviceId(deviceId);
  74 + if (connection == null) {
284 log.debug("[pollPoint][设备 {} 没有连接,跳过轮询]", deviceId); 75 log.debug("[pollPoint][设备 {} 没有连接,跳过轮询]", deviceId);
285 return; 76 return;
286 } 77 }
  78 + // 2.2 获取 slave ID
  79 + IotModbusFrameFormatEnum frameFormat = connection.getFrameFormat();
  80 + Assert.notNull(frameFormat, "设备 {} 的帧格式不能为空", deviceId);
  81 + int slaveId = connection.getSlaveId();
  82 + Assert.notNull(connection.getSlaveId(), "设备 {} 的 slaveId 不能为空", deviceId);
287 83
288 - // 3.1 确定帧格式和事务 ID  
289 - IotModbusFrameFormatEnum frameFormat = connInfo.getFrameFormat();  
290 - if (frameFormat == null) {  
291 - log.warn("[pollPoint][设备 {} 帧格式为空,跳过轮询]", deviceId);  
292 - return;  
293 - } 84 + // 3.1 编码读请求
294 Integer transactionId = frameFormat == IotModbusFrameFormatEnum.MODBUS_TCP 85 Integer transactionId = frameFormat == IotModbusFrameFormatEnum.MODBUS_TCP
295 ? (transactionIdCounter.incrementAndGet() & 0xFFFF) 86 ? (transactionIdCounter.incrementAndGet() & 0xFFFF)
296 : null; 87 : null;
297 - // TODO @AI:这里断言必须非空!  
298 - int slaveId = connInfo.getSlaveId() != null ? connInfo.getSlaveId() : 1;  
299 - // 3.2 编码读请求  
300 byte[] data = frameEncoder.encodeReadRequest(slaveId, point.getFunctionCode(), 88 byte[] data = frameEncoder.encodeReadRequest(slaveId, point.getFunctionCode(),
301 point.getRegisterAddress(), point.getRegisterCount(), frameFormat, transactionId); 89 point.getRegisterAddress(), point.getRegisterCount(), frameFormat, transactionId);
302 - // 3.3 注册 PendingRequest 90 + // 3.2 注册 PendingRequest
303 PendingRequest pendingRequest = new PendingRequest( 91 PendingRequest pendingRequest = new PendingRequest(
304 deviceId, point.getId(), point.getIdentifier(), 92 deviceId, point.getId(), point.getIdentifier(),
305 slaveId, point.getFunctionCode(), 93 slaveId, point.getFunctionCode(),
@@ -307,41 +95,11 @@ public class IotModbusTcpSlavePollScheduler { @@ -307,41 +95,11 @@ public class IotModbusTcpSlavePollScheduler {
307 transactionId, 95 transactionId,
308 System.currentTimeMillis() + requestTimeout); 96 System.currentTimeMillis() + requestTimeout);
309 pendingRequestManager.addRequest(pendingRequest); 97 pendingRequestManager.addRequest(pendingRequest);
310 -  
311 - // 4. 发送读请求 98 + // 3.3 发送读请求
312 connectionManager.sendToDevice(deviceId, data); 99 connectionManager.sendToDevice(deviceId, data);
313 log.debug("[pollPoint][设备={}, 点位={}, FC={}, 地址={}, 数量={}]", 100 log.debug("[pollPoint][设备={}, 点位={}, FC={}, 地址={}, 数量={}]",
314 deviceId, point.getIdentifier(), point.getFunctionCode(), 101 deviceId, point.getIdentifier(), point.getFunctionCode(),
315 point.getRegisterAddress(), point.getRegisterCount()); 102 point.getRegisterAddress(), point.getRegisterCount());
316 } 103 }
317 104
318 - // ========== 停止 ==========  
319 -  
320 - /**  
321 - * 停止设备的轮询  
322 - */  
323 - public void stopPolling(Long deviceId) {  
324 - Map<Long, PointTimerInfo> timers = devicePointTimers.remove(deviceId);  
325 - if (CollUtil.isEmpty(timers)) {  
326 - return;  
327 - }  
328 - for (PointTimerInfo timerInfo : timers.values()) {  
329 - vertx.cancelTimer(timerInfo.getTimerId());  
330 - }  
331 - // 清理请求队列  
332 - deviceRequestQueues.remove(deviceId);  
333 - deviceLastRequestTime.remove(deviceId);  
334 - deviceDelayTimerActive.remove(deviceId);  
335 - log.debug("[stopPolling][设备 {} 停止了 {} 个轮询定时器]", deviceId, timers.size());  
336 - }  
337 -  
338 - /**  
339 - * 停止所有轮询  
340 - */  
341 - public void stopAll() {  
342 - for (Long deviceId : new ArrayList<>(devicePointTimers.keySet())) {  
343 - stopPolling(deviceId);  
344 - }  
345 - }  
346 -  
347 } 105 }
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/handler/downstream/IotMqttDownstreamSubscriber.java
@@ -2,7 +2,7 @@ package cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.handler.downstream; @@ -2,7 +2,7 @@ package cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.handler.downstream;
2 2
3 import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus; 3 import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus;
4 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; 4 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
5 -import cn.iocoder.yudao.module.iot.gateway.protocol.IotProtocolDownstreamSubscriber; 5 +import cn.iocoder.yudao.module.iot.gateway.protocol.AbstractIotProtocolDownstreamSubscriber;
6 import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.IotMqttProtocol; 6 import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.IotMqttProtocol;
7 import lombok.extern.slf4j.Slf4j; 7 import lombok.extern.slf4j.Slf4j;
8 8
@@ -12,7 +12,7 @@ import lombok.extern.slf4j.Slf4j; @@ -12,7 +12,7 @@ import lombok.extern.slf4j.Slf4j;
12 * @author 芋道源码 12 * @author 芋道源码
13 */ 13 */
14 @Slf4j 14 @Slf4j
15 -public class IotMqttDownstreamSubscriber extends IotProtocolDownstreamSubscriber { 15 +public class IotMqttDownstreamSubscriber extends AbstractIotProtocolDownstreamSubscriber {
16 16
17 private final IotMqttDownstreamHandler downstreamHandler; 17 private final IotMqttDownstreamHandler downstreamHandler;
18 18
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/handler/downstream/IotTcpDownstreamSubscriber.java
@@ -3,7 +3,7 @@ package cn.iocoder.yudao.module.iot.gateway.protocol.tcp.handler.downstream; @@ -3,7 +3,7 @@ package cn.iocoder.yudao.module.iot.gateway.protocol.tcp.handler.downstream;
3 import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus; 3 import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus;
4 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; 4 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
5 import cn.iocoder.yudao.module.iot.gateway.protocol.IotProtocol; 5 import cn.iocoder.yudao.module.iot.gateway.protocol.IotProtocol;
6 -import cn.iocoder.yudao.module.iot.gateway.protocol.IotProtocolDownstreamSubscriber; 6 +import cn.iocoder.yudao.module.iot.gateway.protocol.AbstractIotProtocolDownstreamSubscriber;
7 import lombok.extern.slf4j.Slf4j; 7 import lombok.extern.slf4j.Slf4j;
8 8
9 /** 9 /**
@@ -12,7 +12,7 @@ import lombok.extern.slf4j.Slf4j; @@ -12,7 +12,7 @@ import lombok.extern.slf4j.Slf4j;
12 * @author 芋道源码 12 * @author 芋道源码
13 */ 13 */
14 @Slf4j 14 @Slf4j
15 -public class IotTcpDownstreamSubscriber extends IotProtocolDownstreamSubscriber { 15 +public class IotTcpDownstreamSubscriber extends AbstractIotProtocolDownstreamSubscriber {
16 16
17 private final IotTcpDownstreamHandler downstreamHandler; 17 private final IotTcpDownstreamHandler downstreamHandler;
18 18
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/udp/handler/downstream/IotUdpDownstreamSubscriber.java
@@ -3,7 +3,7 @@ package cn.iocoder.yudao.module.iot.gateway.protocol.udp.handler.downstream; @@ -3,7 +3,7 @@ package cn.iocoder.yudao.module.iot.gateway.protocol.udp.handler.downstream;
3 import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus; 3 import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus;
4 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; 4 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
5 import cn.iocoder.yudao.module.iot.gateway.protocol.IotProtocol; 5 import cn.iocoder.yudao.module.iot.gateway.protocol.IotProtocol;
6 -import cn.iocoder.yudao.module.iot.gateway.protocol.IotProtocolDownstreamSubscriber; 6 +import cn.iocoder.yudao.module.iot.gateway.protocol.AbstractIotProtocolDownstreamSubscriber;
7 import lombok.extern.slf4j.Slf4j; 7 import lombok.extern.slf4j.Slf4j;
8 8
9 /** 9 /**
@@ -12,7 +12,7 @@ import lombok.extern.slf4j.Slf4j; @@ -12,7 +12,7 @@ import lombok.extern.slf4j.Slf4j;
12 * @author 芋道源码 12 * @author 芋道源码
13 */ 13 */
14 @Slf4j 14 @Slf4j
15 -public class IotUdpDownstreamSubscriber extends IotProtocolDownstreamSubscriber { 15 +public class IotUdpDownstreamSubscriber extends AbstractIotProtocolDownstreamSubscriber {
16 16
17 private final IotUdpDownstreamHandler downstreamHandler; 17 private final IotUdpDownstreamHandler downstreamHandler;
18 18
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/websocket/handler/downstream/IotWebSocketDownstreamSubscriber.java
@@ -2,7 +2,7 @@ package cn.iocoder.yudao.module.iot.gateway.protocol.websocket.handler.downstrea @@ -2,7 +2,7 @@ package cn.iocoder.yudao.module.iot.gateway.protocol.websocket.handler.downstrea
2 2
3 import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus; 3 import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus;
4 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; 4 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
5 -import cn.iocoder.yudao.module.iot.gateway.protocol.IotProtocolDownstreamSubscriber; 5 +import cn.iocoder.yudao.module.iot.gateway.protocol.AbstractIotProtocolDownstreamSubscriber;
6 import cn.iocoder.yudao.module.iot.gateway.protocol.websocket.IotWebSocketProtocol; 6 import cn.iocoder.yudao.module.iot.gateway.protocol.websocket.IotWebSocketProtocol;
7 import lombok.extern.slf4j.Slf4j; 7 import lombok.extern.slf4j.Slf4j;
8 8
@@ -12,7 +12,7 @@ import lombok.extern.slf4j.Slf4j; @@ -12,7 +12,7 @@ import lombok.extern.slf4j.Slf4j;
12 * @author 芋道源码 12 * @author 芋道源码
13 */ 13 */
14 @Slf4j 14 @Slf4j
15 -public class IotWebSocketDownstreamSubscriber extends IotProtocolDownstreamSubscriber { 15 +public class IotWebSocketDownstreamSubscriber extends AbstractIotProtocolDownstreamSubscriber {
16 16
17 private final IotWebSocketDownstreamHandler downstreamHandler; 17 private final IotWebSocketDownstreamHandler downstreamHandler;
18 18
yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpslave/IotModbusTcpSlaveModbusRtuIntegrationTest.java
@@ -8,7 +8,7 @@ import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils; @@ -8,7 +8,7 @@ import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils;
8 import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpslave.codec.IotModbusFrame; 8 import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpslave.codec.IotModbusFrame;
9 import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpslave.codec.IotModbusFrameDecoder; 9 import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpslave.codec.IotModbusFrameDecoder;
10 import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpslave.codec.IotModbusFrameEncoder; 10 import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpslave.codec.IotModbusFrameEncoder;
11 -import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.common.IotModbusUtils; 11 +import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.common.utils.IotModbusCommonUtils;
12 import io.vertx.core.Vertx; 12 import io.vertx.core.Vertx;
13 import io.vertx.core.buffer.Buffer; 13 import io.vertx.core.buffer.Buffer;
14 import io.vertx.core.net.NetClient; 14 import io.vertx.core.net.NetClient;
@@ -285,7 +285,7 @@ public class IotModbusTcpSlaveModbusRtuIntegrationTest { @@ -285,7 +285,7 @@ public class IotModbusTcpSlaveModbusRtuIntegrationTest {
285 frame[3 + i * 2 + 1] = (byte) (registerValues[i] & 0xFF); 285 frame[3 + i * 2 + 1] = (byte) (registerValues[i] & 0xFF);
286 } 286 }
287 // 计算 CRC16 287 // 计算 CRC16
288 - int crc = IotModbusUtils.calculateCrc16(frame, totalLength - 2); 288 + int crc = IotModbusCommonUtils.calculateCrc16(frame, totalLength - 2);
289 frame[totalLength - 2] = (byte) (crc & 0xFF); // CRC Low 289 frame[totalLength - 2] = (byte) (crc & 0xFF); // CRC Low
290 frame[totalLength - 1] = (byte) ((crc >> 8) & 0xFF); // CRC High 290 frame[totalLength - 1] = (byte) ((crc >> 8) & 0xFF); // CRC High
291 return frame; 291 return frame;