package cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpserver.manager; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.lang.Assert; import cn.iocoder.yudao.module.iot.core.biz.dto.IotModbusDeviceConfigRespDTO; import cn.iocoder.yudao.module.iot.core.biz.dto.IotModbusPointRespDTO; import cn.iocoder.yudao.module.iot.core.enums.modbus.IotModbusFrameFormatEnum; import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.common.manager.AbstractIotModbusPollScheduler; import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.common.utils.IotModbusCommonUtils; import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpserver.codec.IotModbusFrameEncoder; import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpserver.manager.IotModbusTcpServerConnectionManager.ConnectionInfo; import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpserver.manager.IotModbusTcpServerPendingRequestManager.PendingRequest; import io.vertx.core.Vertx; import lombok.Getter; import lombok.extern.slf4j.Slf4j; import java.util.concurrent.atomic.AtomicInteger; /** * IoT Modbus TCP Server 轮询调度器:编码读请求帧,通过 TCP 连接发送到设备,注册 PendingRequest 等待响应 * * @author 芋道源码 */ @Slf4j public class IotModbusTcpServerPollScheduler extends AbstractIotModbusPollScheduler { private final IotModbusTcpServerConnectionManager connectionManager; private final IotModbusFrameEncoder frameEncoder; private final IotModbusTcpServerPendingRequestManager pendingRequestManager; private final IotModbusTcpServerConfigCacheService configCacheService; private final int requestTimeout; /** * TCP 事务 ID 自增器(与 DownstreamHandler 共享) */ @Getter private final AtomicInteger transactionIdCounter; public IotModbusTcpServerPollScheduler(Vertx vertx, IotModbusTcpServerConnectionManager connectionManager, IotModbusFrameEncoder frameEncoder, IotModbusTcpServerPendingRequestManager pendingRequestManager, int requestTimeout, AtomicInteger transactionIdCounter, IotModbusTcpServerConfigCacheService configCacheService) { super(vertx); this.connectionManager = connectionManager; this.frameEncoder = frameEncoder; this.pendingRequestManager = pendingRequestManager; this.requestTimeout = requestTimeout; this.transactionIdCounter = transactionIdCounter; this.configCacheService = configCacheService; } // ========== 轮询执行 ========== /** * 轮询单个点位 */ @Override @SuppressWarnings("DuplicatedCode") protected void pollPoint(Long deviceId, Long pointId) { // 1.1 从 configCache 获取最新配置 IotModbusDeviceConfigRespDTO config = configCacheService.getConfig(deviceId); if (config == null || CollUtil.isEmpty(config.getPoints())) { log.warn("[pollPoint][设备 {} 没有配置]", deviceId); return; } // 1.2 查找点位 IotModbusPointRespDTO point = IotModbusCommonUtils.findPointById(config, pointId); if (point == null) { log.warn("[pollPoint][设备 {} 点位 {} 未找到]", deviceId, pointId); return; } // 2.1 获取连接 ConnectionInfo connection = connectionManager.getConnectionInfoByDeviceId(deviceId); if (connection == null) { log.debug("[pollPoint][设备 {} 没有连接,跳过轮询]", deviceId); return; } // 2.2 获取 slave ID IotModbusFrameFormatEnum frameFormat = connection.getFrameFormat(); Assert.notNull(frameFormat, "设备 {} 的帧格式不能为空", deviceId); Integer slaveId = connection.getSlaveId(); Assert.notNull(connection.getSlaveId(), "设备 {} 的 slaveId 不能为空", deviceId); // 3.1 编码读请求 Integer transactionId = frameFormat == IotModbusFrameFormatEnum.MODBUS_TCP ? (transactionIdCounter.incrementAndGet() & 0xFFFF) : null; byte[] data = frameEncoder.encodeReadRequest(slaveId, point.getFunctionCode(), point.getRegisterAddress(), point.getRegisterCount(), frameFormat, transactionId); // 3.2 注册 PendingRequest PendingRequest pendingRequest = new PendingRequest( deviceId, point.getId(), point.getIdentifier(), slaveId, point.getFunctionCode(), point.getRegisterAddress(), point.getRegisterCount(), transactionId, System.currentTimeMillis() + requestTimeout); pendingRequestManager.addRequest(pendingRequest); // 3.3 发送读请求 connectionManager.sendToDevice(deviceId, data).onSuccess(v -> log.debug("[pollPoint][设备={}, 点位={}, FC={}, 地址={}, 数量={}]", deviceId, point.getIdentifier(), point.getFunctionCode(), point.getRegisterAddress(), point.getRegisterCount()) ).onFailure(e -> log.warn("[pollPoint][发送失败, 设备={}, 点位={}]", deviceId, point.getIdentifier(), e) ); } }