Commit c608b81c4e9e347d42a56a0847cf94de449ee4f7
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; |