From 3ab33527e3d3ea0c7a11789d9585e3079e9ee46d Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sun, 8 Feb 2026 10:13:06 +0800 Subject: [PATCH] =?UTF-8?q?feat=EF=BC=88iot=EF=BC=89=EF=BC=9Amodbus-tcp-sl?= =?UTF-8?q?ave=20=E6=95=B4=E4=BD=93=E4=BB=A3=E7=A0=81=E8=BF=9B=E4=B8=80?= =?UTF-8?q?=E6=AD=A5=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../device/IotDeviceModbusPointDO.java | 3 +- .../core/biz/dto/IotModbusPointRespDTO.java | 3 +- .../core/enums/IotModbusFunctionCodeEnum.java | 60 ---- .../modbus/common/IotModbusUtils.java | 261 ++++++++++++++++++ .../tcpmaster/client/IotModbusTcpClient.java | 47 ++-- .../IotModbusTcpDownstreamHandler.java | 28 +- .../IotModbusTcpDownstreamSubscriber.java | 48 +--- .../tcpslave/IotModbusTcpSlaveConfig.java | 3 +- .../modbus/tcpslave/codec/IotModbusFrame.java | 27 +- .../tcpslave/codec/IotModbusFrameDecoder.java | 30 +- .../tcpslave/codec/IotModbusFrameEncoder.java | 1 + .../modbus/tcpslave/codec/IotModbusUtils.java | 127 --------- .../IotModbusTcpSlaveDownstreamHandler.java | 56 ++-- ...IotModbusTcpSlaveDownstreamSubscriber.java | 48 +--- .../IotModbusTcpSlaveUpstreamHandler.java | 160 +++++------ .../IotModbusTcpSlaveConfigCacheService.java | 2 +- ...odbusTcpSlaveModbusRtuIntegrationTest.java | 2 +- 17 files changed, 427 insertions(+), 479 deletions(-) delete mode 100644 yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/enums/IotModbusFunctionCodeEnum.java create mode 100644 yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/common/IotModbusUtils.java delete mode 100644 yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpslave/codec/IotModbusUtils.java diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/device/IotDeviceModbusPointDO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/device/IotDeviceModbusPointDO.java index 60410848e2..dd3a3a9609 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/device/IotDeviceModbusPointDO.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/device/IotDeviceModbusPointDO.java @@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.iot.dal.dataobject.device; import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO; import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotThingModelDO; import cn.iocoder.yudao.module.iot.core.enums.IotModbusByteOrderEnum; -import cn.iocoder.yudao.module.iot.core.enums.IotModbusFunctionCodeEnum; import cn.iocoder.yudao.module.iot.core.enums.IotModbusRawDataTypeEnum; import com.baomidou.mybatisplus.annotation.KeySequence; import com.baomidou.mybatisplus.annotation.TableId; @@ -62,7 +61,7 @@ public class IotDeviceModbusPointDO extends TenantBaseDO { /** * Modbus 功能码 * - * 枚举 {@link IotModbusFunctionCodeEnum} + * 取值范围:FC01-04(读线圈、读离散输入、读保持寄存器、读输入寄存器) */ private Integer functionCode; /** diff --git a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/biz/dto/IotModbusPointRespDTO.java b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/biz/dto/IotModbusPointRespDTO.java index 129553dfd8..0424f1bc16 100644 --- a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/biz/dto/IotModbusPointRespDTO.java +++ b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/biz/dto/IotModbusPointRespDTO.java @@ -1,7 +1,6 @@ package cn.iocoder.yudao.module.iot.core.biz.dto; import cn.iocoder.yudao.module.iot.core.enums.IotModbusByteOrderEnum; -import cn.iocoder.yudao.module.iot.core.enums.IotModbusFunctionCodeEnum; import cn.iocoder.yudao.module.iot.core.enums.IotModbusRawDataTypeEnum; import lombok.Data; @@ -33,7 +32,7 @@ public class IotModbusPointRespDTO { /** * Modbus 功能码 * - * 枚举 {@link IotModbusFunctionCodeEnum} + * 取值范围:FC01-04(读线圈、读离散输入、读保持寄存器、读输入寄存器) */ private Integer functionCode; /** diff --git a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/enums/IotModbusFunctionCodeEnum.java b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/enums/IotModbusFunctionCodeEnum.java deleted file mode 100644 index 5487f51410..0000000000 --- a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/enums/IotModbusFunctionCodeEnum.java +++ /dev/null @@ -1,60 +0,0 @@ -package cn.iocoder.yudao.module.iot.core.enums; - -import cn.iocoder.yudao.framework.common.core.ArrayValuable; -import lombok.Getter; -import lombok.RequiredArgsConstructor; - -import java.util.Arrays; - -/** - * IoT Modbus 功能码枚举 - * - * @author 芋道源码 - */ -@Getter -@RequiredArgsConstructor -public enum IotModbusFunctionCodeEnum implements ArrayValuable { - - READ_COILS(1, "读线圈", true, 5, 15), - READ_DISCRETE_INPUTS(2, "读离散输入", false, null, null), - READ_HOLDING_REGISTERS(3, "读保持寄存器", true, 6, 16), - READ_INPUT_REGISTERS(4, "读输入寄存器", false, null, null); - - public static final Integer[] ARRAYS = Arrays.stream(values()) - .map(IotModbusFunctionCodeEnum::getCode) - .toArray(Integer[]::new); - - /** - * 功能码 - */ - private final Integer code; - /** - * 名称 - */ - private final String name; - /** - * 是否支持写操作 - */ - private final Boolean writable; - /** - * 单个写功能码 - */ - private final Integer writeSingleCode; - /** - * 多个写功能码 - */ - private final Integer writeMultipleCode; - - @Override - public Integer[] array() { - return ARRAYS; - } - - public static IotModbusFunctionCodeEnum valueOf(Integer code) { - return Arrays.stream(values()) - .filter(e -> e.getCode().equals(code)) - .findFirst() - .orElse(null); - } - -} diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/common/IotModbusUtils.java b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/common/IotModbusUtils.java new file mode 100644 index 0000000000..337c19fa93 --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/common/IotModbusUtils.java @@ -0,0 +1,261 @@ +package cn.iocoder.yudao.module.iot.gateway.protocol.modbus.common; + +import cn.hutool.core.collection.CollUtil; +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.gateway.protocol.modbus.tcpslave.codec.IotModbusFrame; +import lombok.extern.slf4j.Slf4j; + +/** + * IoT Modbus 工具类 + *

+ * 提供: + * 1. Modbus 协议常量(功能码、掩码等) + * 2. CRC-16/MODBUS 计算和校验 + * 3. 功能码分类判断 + * 4. 从解码后的 ${IotModbusFrame} 中提取寄存器值(用于后续的点位翻译) + * + * @author 芋道源码 + */ +@Slf4j +public class IotModbusUtils { + + /** FC01: 读线圈 */ + public static final int FC_READ_COILS = 1; + /** FC02: 读离散输入 */ + public static final int FC_READ_DISCRETE_INPUTS = 2; + /** FC03: 读保持寄存器 */ + public static final int FC_READ_HOLDING_REGISTERS = 3; + /** FC04: 读输入寄存器 */ + public static final int FC_READ_INPUT_REGISTERS = 4; + + /** FC05: 写单个线圈 */ + public static final int FC_WRITE_SINGLE_COIL = 5; + /** FC06: 写单个寄存器 */ + public static final int FC_WRITE_SINGLE_REGISTER = 6; + /** FC15: 写多个线圈 */ + public static final int FC_WRITE_MULTIPLE_COILS = 15; + /** FC16: 写多个寄存器 */ + public static final int FC_WRITE_MULTIPLE_REGISTERS = 16; + + /** + * 异常响应掩码:响应帧的功能码最高位为 1 时,表示异常响应 + * 例如:请求 FC=0x03,异常响应 FC=0x83(0x03 | 0x80) + */ + public static final int FC_EXCEPTION_MASK = 0x80; + + /** + * 功能码掩码:用于从异常响应中提取原始功能码 + * 例如:异常 FC=0x83,原始 FC = 0x83 & 0x7F = 0x03 + */ + public static final int FC_MASK = 0x7F; + + // ==================== 功能码分类判断 ==================== + + /** + * 判断是否为读响应(FC01-04) + */ + public static boolean isReadResponse(int functionCode) { + return functionCode >= FC_READ_COILS && functionCode <= FC_READ_INPUT_REGISTERS; + } + + /** + * 判断是否为写响应(FC05/06/15/16) + */ + public static boolean isWriteResponse(int functionCode) { + return functionCode == FC_WRITE_SINGLE_COIL || functionCode == FC_WRITE_SINGLE_REGISTER + || functionCode == FC_WRITE_MULTIPLE_COILS || functionCode == FC_WRITE_MULTIPLE_REGISTERS; + } + + /** + * 判断是否为异常响应 + */ + public static boolean isExceptionResponse(int functionCode) { + return (functionCode & FC_EXCEPTION_MASK) != 0; + } + + /** + * 从异常响应中提取原始功能码 + */ + public static int extractOriginalFunctionCode(int exceptionFunctionCode) { + return exceptionFunctionCode & FC_MASK; + } + + /** + * 判断读功能码是否支持写操作 + *

+ * FC01(读线圈)和 FC03(读保持寄存器)支持写操作; + * FC02(读离散输入)和 FC04(读输入寄存器)为只读。 + * + * @param readFunctionCode 读功能码(FC01-04) + * @return 是否支持写操作 + */ + @SuppressWarnings("BooleanMethodIsAlwaysInverted") + public static boolean isWritable(int readFunctionCode) { + return readFunctionCode == FC_READ_COILS || readFunctionCode == FC_READ_HOLDING_REGISTERS; + } + + /** + * 获取单写功能码 + *

+ * FC01(读线圈)→ FC05(写单个线圈); + * FC03(读保持寄存器)→ FC06(写单个寄存器); + * 其他返回 null(不支持写)。 + * + * @param readFunctionCode 读功能码 + * @return 单写功能码,不支持写时返回 null + */ + @SuppressWarnings("EnhancedSwitchMigration") + public static Integer getWriteSingleFunctionCode(int readFunctionCode) { + switch (readFunctionCode) { + case FC_READ_COILS: + return FC_WRITE_SINGLE_COIL; + case FC_READ_HOLDING_REGISTERS: + return FC_WRITE_SINGLE_REGISTER; + default: + return null; + } + } + + /** + * 获取多写功能码 + *

+ * FC01(读线圈)→ FC15(写多个线圈); + * FC03(读保持寄存器)→ FC16(写多个寄存器); + * 其他返回 null(不支持写)。 + * + * @param readFunctionCode 读功能码 + * @return 多写功能码,不支持写时返回 null + */ + @SuppressWarnings("EnhancedSwitchMigration") + public static Integer getWriteMultipleFunctionCode(int readFunctionCode) { + switch (readFunctionCode) { + case FC_READ_COILS: + return FC_WRITE_MULTIPLE_COILS; + case FC_READ_HOLDING_REGISTERS: + return FC_WRITE_MULTIPLE_REGISTERS; + default: + return null; + } + } + + // ==================== 点位查找 ==================== + + /** + * 查找点位配置 + * + * @param config 设备 Modbus 配置 + * @param identifier 点位标识符 + * @return 匹配的点位配置,未找到返回 null + */ + public static IotModbusPointRespDTO findPoint(IotModbusDeviceConfigRespDTO config, String identifier) { + return CollUtil.findOne(config.getPoints(), p -> identifier.equals(p.getIdentifier())); + } + + // ==================== CRC16 工具 ==================== + + /** + * 计算 CRC-16/MODBUS + * + * @param data 数据 + * @param length 计算长度 + * @return CRC16 值 + */ + public static int calculateCrc16(byte[] data, int length) { + int crc = 0xFFFF; + for (int i = 0; i < length; i++) { + crc ^= (data[i] & 0xFF); + for (int j = 0; j < 8; j++) { + if ((crc & 0x0001) != 0) { + crc >>= 1; + crc ^= 0xA001; + } else { + crc >>= 1; + } + } + } + return crc; + } + + /** + * 校验 CRC16 + * + * @param data 包含 CRC 的完整数据 + * @return 校验是否通过 + */ + public static boolean verifyCrc16(byte[] data) { + if (data.length < 3) { + return false; + } + int computed = calculateCrc16(data, data.length - 2); + int received = (data[data.length - 2] & 0xFF) | ((data[data.length - 1] & 0xFF) << 8); + return computed == received; + } + + // ==================== 响应值提取 ==================== + + /** + * 从帧中提取寄存器值(FC01-04 读响应) + * + * @param frame 解码后的 Modbus 帧 + * @return 寄存器值数组(int[]),失败返回 null + */ + @SuppressWarnings("EnhancedSwitchMigration") + public static int[] extractValues(IotModbusFrame frame) { + if (frame == null || frame.isException()) { + return null; + } + byte[] pdu = frame.getPdu(); + if (pdu == null || pdu.length < 1) { + return null; + } + + int functionCode = frame.getFunctionCode(); + switch (functionCode) { + case FC_READ_COILS: + case FC_READ_DISCRETE_INPUTS: + return extractCoilValues(pdu); + case FC_READ_HOLDING_REGISTERS: + case FC_READ_INPUT_REGISTERS: + return extractRegisterValues(pdu); + default: + log.warn("[extractValues][不支持的功能码: {}]", functionCode); + return null; + } + } + + /** + * 提取线圈/离散输入值 + * PDU 格式(FC01/02 响应):[ByteCount(1)] [CoilStatus(N)] + */ + private static int[] extractCoilValues(byte[] pdu) { + if (pdu.length < 2) { + return null; + } + int byteCount = pdu[0] & 0xFF; + int bitCount = byteCount * 8; + int[] values = new int[bitCount]; + for (int i = 0; i < bitCount && (1 + i / 8) < pdu.length; i++) { + values[i] = ((pdu[1 + i / 8] >> (i % 8)) & 0x01); + } + return values; + } + + /** + * 提取寄存器值 + * PDU 格式(FC03/04 响应):[ByteCount(1)] [RegisterData(N*2)] + */ + private static int[] extractRegisterValues(byte[] pdu) { + if (pdu.length < 2) { + return null; + } + int byteCount = pdu[0] & 0xFF; + int registerCount = byteCount / 2; + int[] values = new int[registerCount]; + for (int i = 0; i < registerCount && (1 + i * 2 + 1) < pdu.length; i++) { + values[i] = ((pdu[1 + i * 2] & 0xFF) << 8) | (pdu[1 + i * 2 + 1] & 0xFF); + } + return values; + } + +} diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpmaster/client/IotModbusTcpClient.java b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpmaster/client/IotModbusTcpClient.java index f789c2c0cd..18fc211409 100644 --- a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpmaster/client/IotModbusTcpClient.java +++ b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpmaster/client/IotModbusTcpClient.java @@ -1,7 +1,6 @@ package cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpmaster.client; import cn.iocoder.yudao.module.iot.core.biz.dto.IotModbusPointRespDTO; -import cn.iocoder.yudao.module.iot.core.enums.IotModbusFunctionCodeEnum; import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpmaster.manager.IotModbusTcpConnectionManager; import com.ghgande.j2mod.modbus.io.ModbusTCPTransaction; import com.ghgande.j2mod.modbus.msg.*; @@ -12,6 +11,8 @@ import com.ghgande.j2mod.modbus.util.BitVector; import io.vertx.core.Future; import lombok.extern.slf4j.Slf4j; +import static cn.iocoder.yudao.module.iot.gateway.protocol.modbus.common.IotModbusUtils.*; + // TODO @AI:感觉它更像一个工具类;但是名字叫 client 很奇怪; /** * IoT Modbus TCP 客户端 @@ -98,18 +99,14 @@ public class IotModbusTcpClient { */ @SuppressWarnings("EnhancedSwitchMigration") private ModbusRequest createReadRequest(Integer functionCode, Integer address, Integer count) { - IotModbusFunctionCodeEnum functionCodeEnum = IotModbusFunctionCodeEnum.valueOf(functionCode); - if (functionCodeEnum == null) { - throw new IllegalArgumentException("不支持的功能码: " + functionCode); - } - switch (functionCodeEnum) { - case READ_COILS: + switch (functionCode) { + case FC_READ_COILS: return new ReadCoilsRequest(address, count); - case READ_DISCRETE_INPUTS: + case FC_READ_DISCRETE_INPUTS: return new ReadInputDiscretesRequest(address, count); - case READ_HOLDING_REGISTERS: + case FC_READ_HOLDING_REGISTERS: return new ReadMultipleRegistersRequest(address, count); - case READ_INPUT_REGISTERS: + case FC_READ_INPUT_REGISTERS: return new ReadInputRegistersRequest(address, count); default: throw new IllegalArgumentException("不支持的功能码: " + functionCode); @@ -119,13 +116,10 @@ public class IotModbusTcpClient { /** * 创建写入请求 */ + @SuppressWarnings("EnhancedSwitchMigration") private ModbusRequest createWriteRequest(Integer functionCode, Integer address, Integer count, int[] values) { - IotModbusFunctionCodeEnum functionCodeEnum = IotModbusFunctionCodeEnum.valueOf(functionCode); - if (functionCodeEnum == null) { - throw new IllegalArgumentException("不支持的功能码: " + functionCode); - } - switch (functionCodeEnum) { - case READ_COILS: // 写线圈(使用功能码 5 或 15) + switch (functionCode) { + case FC_READ_COILS: // 写线圈(使用功能码 5 或 15) if (count == 1) { return new WriteCoilRequest(address, values[0] != 0); } else { @@ -135,7 +129,7 @@ public class IotModbusTcpClient { } return new WriteMultipleCoilsRequest(address, bv); } - case READ_HOLDING_REGISTERS: // 写保持寄存器(使用功能码 6 或 16) + case FC_READ_HOLDING_REGISTERS: // 写保持寄存器(使用功能码 6 或 16) if (count == 1) { return new WriteSingleRegisterRequest(address, new SimpleRegister(values[0])); } else { @@ -145,8 +139,8 @@ public class IotModbusTcpClient { } return new WriteMultipleRegistersRequest(address, registers); } - case READ_DISCRETE_INPUTS: // 只读 - case READ_INPUT_REGISTERS: // 只读 + case FC_READ_DISCRETE_INPUTS: // 只读 + case FC_READ_INPUT_REGISTERS: // 只读 return null; default: throw new IllegalArgumentException("不支持的功能码: " + functionCode); @@ -156,13 +150,10 @@ public class IotModbusTcpClient { /** * 从响应中提取值 */ + @SuppressWarnings("EnhancedSwitchMigration") private int[] extractValues(ModbusResponse response, Integer functionCode) { - IotModbusFunctionCodeEnum functionCodeEnum = IotModbusFunctionCodeEnum.valueOf(functionCode); - if (functionCodeEnum == null) { - throw new IllegalArgumentException("不支持的功能码: " + functionCode); - } - switch (functionCodeEnum) { - case READ_COILS: + switch (functionCode) { + case FC_READ_COILS: ReadCoilsResponse coilsResponse = (ReadCoilsResponse) response; int bitCount = coilsResponse.getBitCount(); int[] coilValues = new int[bitCount]; @@ -170,7 +161,7 @@ public class IotModbusTcpClient { coilValues[i] = coilsResponse.getCoilStatus(i) ? 1 : 0; } return coilValues; - case READ_DISCRETE_INPUTS: + case FC_READ_DISCRETE_INPUTS: ReadInputDiscretesResponse discretesResponse = (ReadInputDiscretesResponse) response; int discreteCount = discretesResponse.getBitCount(); int[] discreteValues = new int[discreteCount]; @@ -178,7 +169,7 @@ public class IotModbusTcpClient { discreteValues[i] = discretesResponse.getDiscreteStatus(i) ? 1 : 0; } return discreteValues; - case READ_HOLDING_REGISTERS: + case FC_READ_HOLDING_REGISTERS: ReadMultipleRegistersResponse holdingResponse = (ReadMultipleRegistersResponse) response; InputRegister[] holdingRegisters = holdingResponse.getRegisters(); int[] holdingValues = new int[holdingRegisters.length]; @@ -186,7 +177,7 @@ public class IotModbusTcpClient { holdingValues[i] = holdingRegisters[i].getValue(); } return holdingValues; - case READ_INPUT_REGISTERS: + case FC_READ_INPUT_REGISTERS: ReadInputRegistersResponse inputResponse = (ReadInputRegistersResponse) response; InputRegister[] inputRegisters = inputResponse.getRegisters(); int[] inputValues = new int[inputRegisters.length]; diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpmaster/handler/downstream/IotModbusTcpDownstreamHandler.java b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpmaster/handler/downstream/IotModbusTcpDownstreamHandler.java index c430fcbe95..b12fdfb3f7 100644 --- a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpmaster/handler/downstream/IotModbusTcpDownstreamHandler.java +++ b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpmaster/handler/downstream/IotModbusTcpDownstreamHandler.java @@ -1,12 +1,11 @@ package cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpmaster.handler.downstream; -import cn.hutool.core.collection.CollUtil; 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.IotDeviceMessageMethodEnum; -import cn.iocoder.yudao.module.iot.core.enums.IotModbusFunctionCodeEnum; import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.common.IotModbusDataConverter; +import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.common.IotModbusUtils; import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpmaster.client.IotModbusTcpClient; import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpmaster.manager.IotModbusTcpConfigCacheService; import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpmaster.manager.IotModbusTcpConnectionManager; @@ -60,19 +59,19 @@ public class IotModbusTcpDownstreamHandler { for (Map.Entry entry : propertyMap.entrySet()) { String identifier = entry.getKey(); Object value = entry.getValue(); - // 2.1.1 查找对应的点位配置 - IotModbusPointRespDTO point = findPoint(config, identifier); + // 2.1 查找对应的点位配置 + IotModbusPointRespDTO point = IotModbusUtils.findPoint(config, identifier); if (point == null) { log.warn("[handle][设备 {} 没有点位配置: {}]", message.getDeviceId(), identifier); continue; } - // 2.1.2 检查是否支持写操作 - if (!isWritable(point.getFunctionCode())) { + // 2.2 检查是否支持写操作 + if (!IotModbusUtils.isWritable(point.getFunctionCode())) { log.warn("[handle][点位 {} 不支持写操作, 功能码={}]", identifier, point.getFunctionCode()); continue; } - // 2.2 执行写入 + // 2.3 执行写入 writeProperty(config, point, value); } } @@ -104,19 +103,4 @@ public class IotModbusTcpDownstreamHandler { config.getDeviceId(), point.getIdentifier(), e)); } - /** - * 查找点位配置 - */ - private IotModbusPointRespDTO findPoint(IotModbusDeviceConfigRespDTO config, String identifier) { - return CollUtil.findOne(config.getPoints(), p -> identifier.equals(p.getIdentifier())); - } - - /** - * 检查功能码是否支持写操作 - */ - private boolean isWritable(Integer functionCode) { - IotModbusFunctionCodeEnum functionCodeEnum = IotModbusFunctionCodeEnum.valueOf(functionCode); - return functionCodeEnum != null && Boolean.TRUE.equals(functionCodeEnum.getWritable()); - } - } diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpmaster/handler/downstream/IotModbusTcpDownstreamSubscriber.java b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpmaster/handler/downstream/IotModbusTcpDownstreamSubscriber.java index d50608a0c8..c8e22aff79 100644 --- a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpmaster/handler/downstream/IotModbusTcpDownstreamSubscriber.java +++ b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpmaster/handler/downstream/IotModbusTcpDownstreamSubscriber.java @@ -1,61 +1,31 @@ package cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpmaster.handler.downstream; import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus; -import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageSubscriber; import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; -import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils; +import cn.iocoder.yudao.module.iot.gateway.protocol.IotProtocolDownstreamSubscriber; import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpmaster.IotModbusTcpMasterProtocol; -import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -// TODO @AI:是不是可以继承 /Users/yunai/Java/ruoyi-vue-pro-jdk25/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/IotProtocolDownstreamSubscriber.java /** * IoT Modbus TCP 下行消息订阅器:订阅消息总线的下行消息并转发给处理器 * * @author 芋道源码 */ -@RequiredArgsConstructor @Slf4j -public class IotModbusTcpDownstreamSubscriber implements IotMessageSubscriber { +public class IotModbusTcpDownstreamSubscriber extends IotProtocolDownstreamSubscriber { - private final IotModbusTcpMasterProtocol protocol; private final IotModbusTcpDownstreamHandler downstreamHandler; - private final IotMessageBus messageBus; - /** - * 启动订阅 - */ - public void start() { - messageBus.register(this); - log.info("[start][Modbus TCP Master 下行消息订阅器已启动, topic={}]", getTopic()); - } - - /** - * 停止订阅 - */ - public void stop() { - messageBus.unregister(this); - log.info("[stop][Modbus TCP Master 下行消息订阅器已停止]"); + public IotModbusTcpDownstreamSubscriber(IotModbusTcpMasterProtocol protocol, + IotModbusTcpDownstreamHandler downstreamHandler, + IotMessageBus messageBus) { + super(protocol, messageBus); + this.downstreamHandler = downstreamHandler; } @Override - public String getTopic() { - return IotDeviceMessageUtils.buildMessageBusGatewayDeviceMessageTopic(protocol.getServerId()); - } - - @Override - public String getGroup() { - return getTopic(); // 点对点消费 - } - - @Override - public void onMessage(IotDeviceMessage message) { - log.debug("[onMessage][收到下行消息: {}]", message); - try { - downstreamHandler.handle(message); - } catch (Exception e) { - log.error("[onMessage][处理下行消息失败]", e); - } + protected void handleMessage(IotDeviceMessage message) { + downstreamHandler.handle(message); } } diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpslave/IotModbusTcpSlaveConfig.java b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpslave/IotModbusTcpSlaveConfig.java index 25d377d2b8..60185f1eb0 100644 --- a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpslave/IotModbusTcpSlaveConfig.java +++ b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpslave/IotModbusTcpSlaveConfig.java @@ -1,5 +1,6 @@ package cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpslave; +import jakarta.validation.constraints.Max; import jakarta.validation.constraints.Min; import jakarta.validation.constraints.NotNull; import lombok.Data; @@ -25,7 +26,7 @@ public class IotModbusTcpSlaveConfig { */ @NotNull(message = "自定义功能码不能为空") @Min(value = 65, message = "自定义功能码不能小于 65") - // TODO @AI:搞个范围; + @Max(value = 72, message = "自定义功能码不能大于 72") private Integer customFunctionCode = 65; /** diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpslave/codec/IotModbusFrame.java b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpslave/codec/IotModbusFrame.java index a13c8148f2..6ac6930337 100644 --- a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpslave/codec/IotModbusFrame.java +++ b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpslave/codec/IotModbusFrame.java @@ -1,5 +1,7 @@ package cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpslave.codec; +import cn.iocoder.yudao.module.iot.core.enums.IotModbusFrameFormatEnum; +import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.common.IotModbusUtils; import lombok.Data; import lombok.experimental.Accessors; @@ -25,19 +27,19 @@ public class IotModbusFrame { */ private byte[] pdu; /** - * 事务标识符(TCP 模式特有) - * - * // TODO @AI:最好是 @某个类型独有; + * 事务标识符 + *

+ * 仅 {@link IotModbusFrameFormatEnum#MODBUS_TCP} 格式有值, */ private Integer transactionId; /** - * 是否异常响应 - */ - private boolean exception; - // TODO @AI:是不是要枚举一些异常;另外,是不是覆盖掉 exception;因为只要判断有异常码是不是就可以了; - /** - * 异常码(当 exception=true 时有效) + * 异常码 + *

+ * 当功能码最高位为 1 时(异常响应),此字段存储异常码。 + * 为 null 表示非异常响应。 + * + * @see IotModbusUtils#FC_EXCEPTION_MASK */ private Integer exceptionCode; @@ -46,4 +48,11 @@ public class IotModbusFrame { */ private String customData; + /** + * 是否异常响应(基于 exceptionCode 是否有值判断) + */ + public boolean isException() { + return exceptionCode != null; + } + } diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpslave/codec/IotModbusFrameDecoder.java b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpslave/codec/IotModbusFrameDecoder.java index 8ea473738f..09d98e0dba 100644 --- a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpslave/codec/IotModbusFrameDecoder.java +++ b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpslave/codec/IotModbusFrameDecoder.java @@ -1,6 +1,7 @@ package cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpslave.codec; import cn.iocoder.yudao.module.iot.core.enums.IotModbusFrameFormatEnum; +import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.common.IotModbusUtils; import io.vertx.core.Handler; import io.vertx.core.buffer.Buffer; import io.vertx.core.parsetools.RecordParser; @@ -118,11 +119,8 @@ public class IotModbusFrameDecoder { .setPdu(pdu) .setTransactionId(transactionId); // 异常响应 - // TODO @AI:0x80 看看是不是要枚举; - if ((functionCode & 0x80) != 0) { - frame.setException(true); - // TODO @AI:0x7f 看看是不是要枚举; - frame.setFunctionCode(functionCode & 0x7F); + if (IotModbusUtils.isExceptionResponse(functionCode)) { + frame.setFunctionCode(IotModbusUtils.extractOriginalFunctionCode(functionCode)); if (pdu.length >= 1) { frame.setExceptionCode(pdu[0] & 0xFF); } @@ -247,7 +245,7 @@ public class IotModbusFrameDecoder { * 状态机流程: * Phase 1: fixedSizeMode(2) → 读 slaveId + functionCode * Phase 2: 根据 functionCode 确定剩余长度: - * - 异常响应 (FC & 0x80):fixedSizeMode(3) → exceptionCode(1) + CRC(2) + * - 异常响应 (FC & EXCEPTION_MASK):fixedSizeMode(3) → exceptionCode(1) + CRC(2) * - 自定义 FC / FC01-04 响应:fixedSizeMode(1) → 读 byteCount → fixedSizeMode(byteCount + 2) * - FC05/06 响应:fixedSizeMode(6) → addr(2) + value(2) + CRC(2) * - FC15/16 响应:fixedSizeMode(6) → addr(2) + quantity(2) + CRC(2) @@ -283,7 +281,7 @@ public class IotModbusFrameDecoder { this.slaveId = bytes[0]; this.functionCode = bytes[1]; int fc = functionCode & 0xFF; - if ((fc & 0x80) != 0) { + if (IotModbusUtils.isExceptionResponse(fc)) { // 异常响应:完整帧 = slaveId(1) + FC(1) + exceptionCode(1) + CRC(2) = 5 字节 // 已有 6 字节(多 1 字节),取前 5 字节组装 Buffer frame = Buffer.buffer(5); @@ -292,7 +290,7 @@ public class IotModbusFrameDecoder { frame.appendBytes(bytes, 2, 3); // exceptionCode + CRC emitFrame(frame); resetToHeader(); - } else if (isReadResponse(fc) || fc == customFunctionCode) { + } else if (IotModbusUtils.isReadResponse(fc) || fc == customFunctionCode) { // 读响应或自定义 FC:bytes[2] = byteCount this.byteCount = bytes[2]; int bc = byteCount & 0xFF; @@ -317,7 +315,7 @@ public class IotModbusFrameDecoder { this.expectedDataLen = bc + 2; // byteCount 个数据 + 2 CRC parser.fixedSizeMode(remaining); } - } else if (isWriteResponse(fc)) { + } else if (IotModbusUtils.isWriteResponse(fc)) { // 写响应:总长 = slaveId(1) + FC(1) + addr(2) + value/qty(2) + CRC(2) = 8 字节 // 已有 6 字节,还需 2 字节 state = STATE_WRITE_BODY; @@ -358,15 +356,15 @@ public class IotModbusFrameDecoder { this.slaveId = header[0]; this.functionCode = header[1]; int fc = functionCode & 0xFF; - if ((fc & 0x80) != 0) { + if (IotModbusUtils.isExceptionResponse(fc)) { // 异常响应 state = STATE_EXCEPTION_BODY; parser.fixedSizeMode(3); // exceptionCode(1) + CRC(2) - } else if (isReadResponse(fc) || fc == customFunctionCode) { + } else if (IotModbusUtils.isReadResponse(fc) || fc == customFunctionCode) { // 读响应或自定义 FC state = STATE_READ_BYTE_COUNT; parser.fixedSizeMode(1); // byteCount - } else if (isWriteResponse(fc)) { + } else if (IotModbusUtils.isWriteResponse(fc)) { // 写响应 state = STATE_WRITE_BODY; pendingData = Buffer.buffer(); @@ -438,14 +436,6 @@ public class IotModbusFrameDecoder { parser.fixedSizeMode(2); // slaveId + FC } - // TODO @AI:可以抽到 IotModbusUtils 里? - private boolean isReadResponse(int fc) { - return fc >= 1 && fc <= 4; - } - - private boolean isWriteResponse(int fc) { - return fc == 5 || fc == 6 || fc == 15 || fc == 16; - } } } diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpslave/codec/IotModbusFrameEncoder.java b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpslave/codec/IotModbusFrameEncoder.java index 0790ab3b36..723a57df8d 100644 --- a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpslave/codec/IotModbusFrameEncoder.java +++ b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpslave/codec/IotModbusFrameEncoder.java @@ -1,6 +1,7 @@ package cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpslave.codec; import cn.iocoder.yudao.module.iot.core.enums.IotModbusFrameFormatEnum; +import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.common.IotModbusUtils; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpslave/codec/IotModbusUtils.java b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpslave/codec/IotModbusUtils.java deleted file mode 100644 index f93f55cf9a..0000000000 --- a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpslave/codec/IotModbusUtils.java +++ /dev/null @@ -1,127 +0,0 @@ -package cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpslave.codec; - -import lombok.extern.slf4j.Slf4j; - -/** - * IoT Modbus 工具类 - *

- * 提供: - * 1. CRC-16/MODBUS 计算和校验 - * 2. 从解码后的 IotModbusFrame 中提取寄存器值(用于后续的点位翻译) - * - * @author 芋道源码 - */ -@Slf4j -public class IotModbusUtils { - - // TODO @AI:可以把 1、2、3、4、5 这些 fucntion code 在这里枚举下。 - // TODO @AI:一些枚举 0x80 这些可以这里枚举; - - // ==================== CRC16 工具 ==================== - - /** - * 计算 CRC-16/MODBUS - * - * @param data 数据 - * @param length 计算长度 - * @return CRC16 值 - */ - public static int calculateCrc16(byte[] data, int length) { - int crc = 0xFFFF; - for (int i = 0; i < length; i++) { - crc ^= (data[i] & 0xFF); - for (int j = 0; j < 8; j++) { - if ((crc & 0x0001) != 0) { - crc >>= 1; - crc ^= 0xA001; - } else { - crc >>= 1; - } - } - } - return crc; - } - - /** - * 校验 CRC16 - * - * @param data 包含 CRC 的完整数据 - * @return 校验是否通过 - */ - public static boolean verifyCrc16(byte[] data) { - if (data.length < 3) { - return false; - } - int computed = calculateCrc16(data, data.length - 2); - int received = (data[data.length - 2] & 0xFF) | ((data[data.length - 1] & 0xFF) << 8); - return computed == received; - } - - // ==================== 响应值提取 ==================== - - /** - * 从帧中提取寄存器值(FC01-04 读响应) - * - * @param frame 解码后的 Modbus 帧 - * @return 寄存器值数组(int[]),失败返回 null - */ - @SuppressWarnings("EnhancedSwitchMigration") - public static int[] extractValues(IotModbusFrame frame) { - if (frame == null || frame.isException()) { - return null; - } - byte[] pdu = frame.getPdu(); - if (pdu == null || pdu.length < 1) { - return null; - } - - // TODO @AI:jmodbus 看看有没可以复用的枚举类 - int functionCode = frame.getFunctionCode(); - switch (functionCode) { - case 1: // Read Coils - case 2: // Read Discrete Inputs - return extractCoilValues(pdu); - case 3: // Read Holding Registers - case 4: // Read Input Registers - return extractRegisterValues(pdu); - default: - log.warn("[extractValues][不支持的功能码: {}]", functionCode); - return null; - } - } - - /** - * 提取线圈/离散输入值 - * PDU 格式(FC01/02 响应):[ByteCount(1)] [CoilStatus(N)] - */ - private static int[] extractCoilValues(byte[] pdu) { - if (pdu.length < 2) { - return null; - } - int byteCount = pdu[0] & 0xFF; - int bitCount = byteCount * 8; - int[] values = new int[bitCount]; - for (int i = 0; i < bitCount && (1 + i / 8) < pdu.length; i++) { - values[i] = ((pdu[1 + i / 8] >> (i % 8)) & 0x01); - } - return values; - } - - /** - * 提取寄存器值 - * PDU 格式(FC03/04 响应):[ByteCount(1)] [RegisterData(N*2)] - */ - private static int[] extractRegisterValues(byte[] pdu) { - if (pdu.length < 2) { - return null; - } - int byteCount = pdu[0] & 0xFF; - int registerCount = byteCount / 2; - int[] values = new int[registerCount]; - for (int i = 0; i < registerCount && (1 + i * 2 + 1) < pdu.length; i++) { - values[i] = ((pdu[1 + i * 2] & 0xFF) << 8) | (pdu[1 + i * 2 + 1] & 0xFF); - } - return values; - } - -} diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpslave/handler/downstream/IotModbusTcpSlaveDownstreamHandler.java b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpslave/handler/downstream/IotModbusTcpSlaveDownstreamHandler.java index 5a47a09c16..56c3478ed9 100644 --- a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpslave/handler/downstream/IotModbusTcpSlaveDownstreamHandler.java +++ b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpslave/handler/downstream/IotModbusTcpSlaveDownstreamHandler.java @@ -1,13 +1,14 @@ package cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpslave.handler.downstream; -import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.ObjUtil; 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.IotDeviceMessageMethodEnum; import cn.iocoder.yudao.module.iot.core.enums.IotModbusFrameFormatEnum; -import cn.iocoder.yudao.module.iot.core.enums.IotModbusFunctionCodeEnum; import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.common.IotModbusDataConverter; +import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.common.IotModbusUtils; import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpslave.codec.IotModbusFrameEncoder; import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpslave.manager.IotModbusTcpSlaveConfigCacheService; import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpslave.manager.IotModbusTcpSlaveConnectionManager; @@ -18,7 +19,6 @@ import lombok.extern.slf4j.Slf4j; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; -// TODO @AI:看看能不能和 /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/handler/downstream/IotModbusTcpDownstreamHandler.java 有一些复用逻辑; /** * IoT Modbus TCP Slave 下行消息处理器 *

@@ -48,7 +48,7 @@ public class IotModbusTcpSlaveDownstreamHandler { @SuppressWarnings("unchecked") public void handle(IotDeviceMessage message) { // 1.1 检查是否是属性设置消息 - if (!IotDeviceMessageMethodEnum.PROPERTY_SET.getMethod().equals(message.getMethod())) { + if (ObjUtil.notEqual(IotDeviceMessageMethodEnum.PROPERTY_SET.getMethod(), message.getMethod())) { log.debug("[handle][忽略非属性设置消息: {}]", message.getMethod()); return; } @@ -76,13 +76,13 @@ public class IotModbusTcpSlaveDownstreamHandler { String identifier = entry.getKey(); Object value = entry.getValue(); // 2.1 查找对应的点位配置 - IotModbusPointRespDTO point = findPoint(config, identifier); + IotModbusPointRespDTO point = IotModbusUtils.findPoint(config, identifier); if (point == null) { log.warn("[handle][设备 {} 没有点位配置: {}]", message.getDeviceId(), identifier); continue; } // 2.2 检查是否支持写操作 - if (!isWritable(point.getFunctionCode())) { + if (!IotModbusUtils.isWritable(point.getFunctionCode())) { log.warn("[handle][点位 {} 不支持写操作, 功能码={}]", identifier, point.getFunctionCode()); continue; } @@ -97,56 +97,36 @@ public class IotModbusTcpSlaveDownstreamHandler { */ private void writeProperty(Long deviceId, ConnectionInfo connInfo, IotModbusPointRespDTO point, Object value) { - // 1. 转换属性值为原始值 + // 1.1 转换属性值为原始值 int[] rawValues = dataConverter.convertToRawValues(value, point); - // 2. 确定帧格式和事务 ID + // 1.2 确定帧格式和事务 ID IotModbusFrameFormatEnum frameFormat = connInfo.getFrameFormat(); - if (frameFormat == null) { - frameFormat = IotModbusFrameFormatEnum.MODBUS_TCP; - } + Assert.notNull(frameFormat, "连接帧格式不能为空"); int transactionId = transactionIdCounter.incrementAndGet() & 0xFFFF; int slaveId = connInfo.getSlaveId() != null ? connInfo.getSlaveId() : 1; - - // 3. 编码写请求 + // 1.3 编码写请求 byte[] data; - IotModbusFunctionCodeEnum fcEnum = IotModbusFunctionCodeEnum.valueOf(point.getFunctionCode()); - if (fcEnum == null) { - log.warn("[writeProperty][未知功能码: {}]", point.getFunctionCode()); - return; - } - if (rawValues.length == 1 && fcEnum.getWriteSingleCode() != null) { + int readFunctionCode = point.getFunctionCode(); + Integer writeSingleCode = IotModbusUtils.getWriteSingleFunctionCode(readFunctionCode); + Integer writeMultipleCode = IotModbusUtils.getWriteMultipleFunctionCode(readFunctionCode); + if (rawValues.length == 1 && writeSingleCode != null) { // 单个值:使用单写功能码(FC05/FC06) - data = frameEncoder.encodeWriteSingleRequest(slaveId, fcEnum.getWriteSingleCode(), + data = frameEncoder.encodeWriteSingleRequest(slaveId, writeSingleCode, point.getRegisterAddress(), rawValues[0], frameFormat, transactionId); - } else if (fcEnum.getWriteMultipleCode() != null) { + } else if (writeMultipleCode != null) { // 多个值:使用多写功能码(FC15/FC16) data = frameEncoder.encodeWriteMultipleRegistersRequest(slaveId, point.getRegisterAddress(), rawValues, frameFormat, transactionId); } else { - log.warn("[writeProperty][点位 {} 不支持写操作]", point.getIdentifier()); + log.warn("[writeProperty][点位 {} 不支持写操作, 功能码={}]", point.getIdentifier(), readFunctionCode); return; } - // 4. 发送 + // 2. 发送 connectionManager.sendToDevice(deviceId, data); log.info("[writeProperty][写入成功, deviceId={}, identifier={}, value={}]", deviceId, point.getIdentifier(), value); } - /** - * 查找点位配置 - */ - private IotModbusPointRespDTO findPoint(IotModbusDeviceConfigRespDTO config, String identifier) { - return CollUtil.findOne(config.getPoints(), p -> identifier.equals(p.getIdentifier())); - } - - /** - * 检查功能码是否支持写操作 - */ - private boolean isWritable(Integer functionCode) { - IotModbusFunctionCodeEnum functionCodeEnum = IotModbusFunctionCodeEnum.valueOf(functionCode); - return functionCodeEnum != null && Boolean.TRUE.equals(functionCodeEnum.getWritable()); - } - } diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpslave/handler/downstream/IotModbusTcpSlaveDownstreamSubscriber.java b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpslave/handler/downstream/IotModbusTcpSlaveDownstreamSubscriber.java index 5c74c27a9d..4e7b882770 100644 --- a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpslave/handler/downstream/IotModbusTcpSlaveDownstreamSubscriber.java +++ b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpslave/handler/downstream/IotModbusTcpSlaveDownstreamSubscriber.java @@ -1,61 +1,31 @@ package cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpslave.handler.downstream; import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus; -import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageSubscriber; import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; -import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils; +import cn.iocoder.yudao.module.iot.gateway.protocol.IotProtocolDownstreamSubscriber; import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpslave.IotModbusTcpSlaveProtocol; -import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -// TODO @AI:是不是可以继承 /Users/yunai/Java/ruoyi-vue-pro-jdk25/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/IotProtocolDownstreamSubscriber.java /** * IoT Modbus TCP Slave 下行消息订阅器:订阅消息总线的下行消息并转发给处理器 * * @author 芋道源码 */ -@RequiredArgsConstructor @Slf4j -public class IotModbusTcpSlaveDownstreamSubscriber implements IotMessageSubscriber { +public class IotModbusTcpSlaveDownstreamSubscriber extends IotProtocolDownstreamSubscriber { - private final IotModbusTcpSlaveProtocol protocol; private final IotModbusTcpSlaveDownstreamHandler downstreamHandler; - private final IotMessageBus messageBus; - /** - * 启动订阅 - */ - public void start() { - messageBus.register(this); - log.info("[start][Modbus TCP Slave 下行消息订阅器已启动, topic={}]", getTopic()); - } - - /** - * 停止订阅 - */ - public void stop() { - messageBus.unregister(this); - log.info("[stop][Modbus TCP Slave 下行消息订阅器已停止]"); + public IotModbusTcpSlaveDownstreamSubscriber(IotModbusTcpSlaveProtocol protocol, + IotModbusTcpSlaveDownstreamHandler downstreamHandler, + IotMessageBus messageBus) { + super(protocol, messageBus); + this.downstreamHandler = downstreamHandler; } @Override - public String getTopic() { - return IotDeviceMessageUtils.buildMessageBusGatewayDeviceMessageTopic(protocol.getServerId()); - } - - @Override - public String getGroup() { - return getTopic(); // 点对点消费 - } - - @Override - public void onMessage(IotDeviceMessage message) { - log.debug("[onMessage][收到下行消息: {}]", message); - try { - downstreamHandler.handle(message); - } catch (Exception e) { - log.error("[onMessage][处理下行消息失败]", e); - } + protected void handleMessage(IotDeviceMessage message) { + downstreamHandler.handle(message); } } diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpslave/handler/upstream/IotModbusTcpSlaveUpstreamHandler.java b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpslave/handler/upstream/IotModbusTcpSlaveUpstreamHandler.java index 15ae18f996..69aebe98d3 100644 --- a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpslave/handler/upstream/IotModbusTcpSlaveUpstreamHandler.java +++ b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpslave/handler/upstream/IotModbusTcpSlaveUpstreamHandler.java @@ -5,14 +5,15 @@ import cn.hutool.core.lang.Assert; import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.BooleanUtil; import cn.hutool.core.util.StrUtil; -import cn.hutool.json.JSONObject; -import cn.hutool.json.JSONUtil; import cn.iocoder.yudao.framework.common.exception.ServiceException; +import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants; import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi; import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO; import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceRespDTO; 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.IotDeviceMessageMethodEnum; import cn.iocoder.yudao.module.iot.core.enums.IotModbusFrameFormatEnum; import cn.iocoder.yudao.module.iot.core.enums.IotModbusModeEnum; @@ -20,9 +21,9 @@ import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; import cn.iocoder.yudao.module.iot.core.topic.IotDeviceIdentity; import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils; import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.common.IotModbusDataConverter; +import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.common.IotModbusUtils; import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpslave.codec.IotModbusFrame; import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpslave.codec.IotModbusFrameEncoder; -import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpslave.codec.IotModbusUtils; import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpslave.manager.IotModbusTcpSlaveConfigCacheService; import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpslave.manager.IotModbusTcpSlaveConnectionManager; import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpslave.manager.IotModbusTcpSlaveConnectionManager.ConnectionInfo; @@ -40,14 +41,12 @@ import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeC import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.invalidParamException; -// DONE @AI:逻辑有点多,看看是不是分区域! => 已按区域划分:认证 / 轮询响应 /** * IoT Modbus TCP Slave 上行数据处理器 *

* 处理: * 1. 自定义 FC 认证 - * 2. 轮询响应(mode=1)→ 点位翻译 → thing.property.post - * // DONE @AI:不用主动上报;主动上报走标准 tcp 即可 + * 2. 轮询响应 → 点位翻译 → thing.property.post * * @author 芋道源码 */ @@ -105,29 +104,14 @@ public class IotModbusTcpSlaveUpstreamHandler { return; } - // 2. 自定义功能码(认证等扩展) + // 2. 情况一:自定义功能码(认证等扩展) if (StrUtil.isNotEmpty(frame.getCustomData())) { handleCustomFrame(socket, frame, frameFormat); return; } - // 1.2 未认证连接,丢弃 - // TODO @AI:把 1.2、1.3 拿到 handlePollingResponse 里;是否需要登录,自己知道! - if (!connectionManager.isAuthenticated(socket)) { - log.warn("[handleFrame][未认证连接, 丢弃数据, remoteAddress={}]", socket.remoteAddress()); - return; - } - - // 3. DONE @AI:断言必须是云端轮询(不再支持主动上报) - // TODO @AI:貌似只能轮询到一次?! - // 1.3 标准 Modbus 响应(只支持云端轮询模式) - // TODO @AI:可以把 - ConnectionInfo info = connectionManager.getConnectionInfo(socket); - if (info == null) { - log.warn("[handleFrame][已认证但连接信息为空, remoteAddress={}]", socket.remoteAddress()); - return; - } - handlePollingResponse(info, frame, frameFormat); + // 3. 情况二:标准 Modbus 响应 → 轮询响应处理 + handlePollingResponse(socket, frame, frameFormat); } // ========== 自定义 FC 处理(认证等) ========== @@ -138,96 +122,83 @@ public class IotModbusTcpSlaveUpstreamHandler { * 异常分层翻译,参考 {@link cn.iocoder.yudao.module.iot.gateway.protocol.http.handler.upstream.IotHttpAbstractHandler} */ private void handleCustomFrame(NetSocket socket, IotModbusFrame frame, IotModbusFrameFormatEnum frameFormat) { + String method = null; try { - JSONObject json = JSONUtil.parseObj(frame.getCustomData()); - String method = json.getStr("method"); + IotDeviceMessage message = JsonUtils.parseObject(frame.getCustomData(), IotDeviceMessage.class); + if (message == null) { + throw invalidParamException("自定义 FC 数据解析失败"); + } + method = message.getMethod(); if (METHOD_AUTH.equals(method)) { - handleAuth(socket, frame, json, frameFormat); + handleAuth(socket, frame, frameFormat, message.getParams()); return; } log.warn("[handleCustomFrame][未知 method: {}, frame: slaveId={}, FC={}, customData={}]", method, frame.getSlaveId(), frame.getFunctionCode(), frame.getCustomData()); } catch (ServiceException e) { // 已知业务异常,返回对应的错误码和错误信息 - sendCustomResponse(socket, frame, frameFormat, e.getCode(), e.getMessage()); + sendCustomResponse(socket, frame, frameFormat, method, e.getCode(), e.getMessage()); } catch (IllegalArgumentException e) { // 参数校验异常,返回 400 错误 - sendCustomResponse(socket, frame, frameFormat, BAD_REQUEST.getCode(), e.getMessage()); + sendCustomResponse(socket, frame, frameFormat, method, BAD_REQUEST.getCode(), e.getMessage()); } catch (Exception e) { // 其他未知异常,返回 500 错误 log.error("[handleCustomFrame][解析自定义 FC 数据失败, frame: slaveId={}, FC={}, customData={}]", frame.getSlaveId(), frame.getFunctionCode(), frame.getCustomData(), e); - sendCustomResponse(socket, frame, frameFormat, + sendCustomResponse(socket, frame, frameFormat, method, INTERNAL_SERVER_ERROR.getCode(), INTERNAL_SERVER_ERROR.getMsg()); } } - // TODO @芋艿:在 review 下这个类; - // TODO @AI:不传递 json,直接在 frame /** * 处理认证请求 */ - private void handleAuth(NetSocket socket, IotModbusFrame frame, JSONObject json, - IotModbusFrameFormatEnum frameFormat) { - // TODO @AI:是不是可以 JsonUtils.convert(json, IotDeviceAuthReqDTO.class); - JSONObject params = json.getJSONObject("params"); - if (params == null) { - throw invalidParamException("params 不能为空"); - } - // DONE @AI:参数判空 - String clientId = params.getStr("clientId"); - String username = params.getStr("username"); - String password = params.getStr("password"); - // TODO @AI:逐个判空; - if (StrUtil.hasBlank(clientId, username, password)) { - throw invalidParamException("clientId、username、password 不能为空"); - } + @SuppressWarnings("DataFlowIssue") + private void handleAuth(NetSocket socket, IotModbusFrame frame, IotModbusFrameFormatEnum frameFormat, Object params) { + // 1. 解析认证参数 + IotDeviceAuthReqDTO request = JsonUtils.convertObject(params, IotDeviceAuthReqDTO.class); + Assert.notNull(request, "认证参数不能为空"); + Assert.notBlank(request.getClientId(), "clientId 不能为空"); + Assert.notBlank(request.getUsername(), "username 不能为空"); + Assert.notBlank(request.getPassword(), "password 不能为空"); - // 1. 调用认证 API - IotDeviceAuthReqDTO authReq = new IotDeviceAuthReqDTO() - .setClientId(clientId).setUsername(username).setPassword(password); - CommonResult authResult = deviceApi.authDevice(authReq); - authResult.checkError(); - if (BooleanUtil.isFalse(authResult.getData())) { - log.warn("[handleAuth][认证失败, clientId={}, username={}]", clientId, username); - sendCustomResponse(socket, frame, frameFormat, 1, "认证失败"); + // 2.1 调用认证 API + CommonResult result = deviceApi.authDevice(request); + result.checkError(); + if (BooleanUtil.isFalse(result.getData())) { + log.warn("[handleAuth][认证失败, clientId={}, username={}]", request.getClientId(), request.getUsername()); + sendCustomResponse(socket, frame, frameFormat, METHOD_AUTH, 1, "认证失败"); return; } - - // 2.1 认证成功,查找设备配置 - IotModbusDeviceConfigRespDTO config = configCacheService.findConfigByAuth(clientId, username, password); - if (config == null) { - log.info("[handleAuth][认证成功但未找到设备配置, clientId={}, username={}]", clientId, username); - } // 2.2 解析设备信息 - IotDeviceIdentity deviceInfo = IotDeviceAuthUtils.parseUsername(username); + IotDeviceIdentity deviceInfo = IotDeviceAuthUtils.parseUsername(request.getUsername()); Assert.notNull(deviceInfo, "解析设备信息失败"); // 2.3 获取设备信息 - // DONE @AI:IotDeviceService 作为构造参数传入,不通过 SpringUtil.getBean IotDeviceRespDTO device = deviceService.getDeviceFromCache(deviceInfo.getProductKey(), deviceInfo.getDeviceName()); Assert.notNull(device, "设备不存在"); - // 3. 注册连接 + // 3.1 注册连接 ConnectionInfo connectionInfo = new ConnectionInfo() .setDeviceId(device.getId()) + .setProductKey(deviceInfo.getProductKey()) + .setDeviceName(deviceInfo.getDeviceName()) .setSlaveId(frame.getSlaveId()) - .setFrameFormat(frameFormat) - .setMode(config != null ? config.getMode() : IotModbusModeEnum.POLLING.getMode()); - if (config != null) { - connectionInfo.setDeviceId(config.getDeviceId()) - .setProductKey(config.getProductKey()) - .setDeviceName(config.getDeviceName()); - } + .setFrameFormat(frameFormat); connectionManager.registerConnection(socket, connectionInfo); + // 3.2 发送上线消息 + IotDeviceMessage onlineMessage = IotDeviceMessage.buildStateUpdateOnline(); + messageService.sendDeviceMessage(onlineMessage, deviceInfo.getProductKey(), deviceInfo.getDeviceName(), serverId); + // 3.3 发送成功响应 + sendCustomResponse(socket, frame, frameFormat, METHOD_AUTH, + GlobalErrorCodeConstants.SUCCESS.getCode(), "success"); + log.info("[handleAuth][认证成功, clientId={}, deviceId={}]", request.getClientId(), device.getId()); - // 4. 发送认证成功响应 - sendCustomResponse(socket, frame, frameFormat, 0, "success"); - log.info("[handleAuth][认证成功, clientId={}, deviceId={}]", clientId, - config != null ? config.getDeviceId() : device.getId()); - - // 5. 直接启动轮询 + // 4. 加载设备配置并启动轮询 + IotModbusDeviceConfigRespDTO config = configCacheService.loadDeviceConfig(device.getId()); if (config != null) { pollScheduler.updatePolling(config); + } else { + log.warn("[handleAuth][认证成功但未找到设备配置, deviceId={}]", device.getId()); } } @@ -236,12 +207,13 @@ public class IotModbusTcpSlaveUpstreamHandler { */ private void sendCustomResponse(NetSocket socket, IotModbusFrame frame, IotModbusFrameFormatEnum frameFormat, - int code, String message) { - JSONObject resp = new JSONObject(); - resp.set("method", METHOD_AUTH); - resp.set("code", code); - resp.set("message", message); - byte[] data = frameEncoder.encodeCustomFrame(frame.getSlaveId(), resp.toString(), + String method, int code, String message) { + Map response = MapUtil.builder() + .put("method", method) + .put("code", code) + .put("message", message) + .build(); + byte[] data = frameEncoder.encodeCustomFrame(frame.getSlaveId(), JsonUtils.toJsonString(response), frameFormat, frame.getTransactionId() != null ? frame.getTransactionId() : 0); connectionManager.sendToSocket(socket, data); } @@ -251,9 +223,16 @@ public class IotModbusTcpSlaveUpstreamHandler { /** * 处理轮询响应(云端轮询模式) */ - private void handlePollingResponse(ConnectionInfo info, IotModbusFrame frame, + private void handlePollingResponse(NetSocket socket, IotModbusFrame frame, IotModbusFrameFormatEnum frameFormat) { - // 1.1 匹配 PendingRequest + // 1. 获取连接信息(未认证连接丢弃) + ConnectionInfo info = connectionManager.getConnectionInfo(socket); + if (info == null) { + log.warn("[handlePollingResponse][未认证连接, 丢弃数据, remoteAddress={}]", socket.remoteAddress()); + return; + } + + // 2.1 匹配 PendingRequest PendingRequest request = pendingRequestManager.matchResponse( info.getDeviceId(), frame, frameFormat); if (request == null) { @@ -261,26 +240,27 @@ public class IotModbusTcpSlaveUpstreamHandler { info.getDeviceId(), frame.getFunctionCode()); return; } - // 1.2 提取寄存器值 + // 2.2 提取寄存器值 int[] rawValues = IotModbusUtils.extractValues(frame); if (rawValues == null) { log.warn("[handlePollingResponse][提取寄存器值失败, deviceId={}, identifier={}]", info.getDeviceId(), request.getIdentifier()); return; } - // 1.3 查找点位配置 + // 2.3 查找点位配置 IotModbusDeviceConfigRespDTO config = configCacheService.getConfig(info.getDeviceId()); if (config == null || CollUtil.isEmpty(config.getPoints())) { return; } - var point = CollUtil.findOne(config.getPoints(), p -> p.getId().equals(request.getPointId())); + IotModbusPointRespDTO point = CollUtil.findOne(config.getPoints(), + p -> p.getId().equals(request.getPointId())); if (point == null) { return; } - // 2.1 点位翻译 + // 3.1 点位翻译 Object convertedValue = dataConverter.convertToPropertyValue(rawValues, point); - // 2.2 上报属性 + // 3.2 上报属性 Map params = MapUtil.of(request.getIdentifier(), convertedValue); IotDeviceMessage message = IotDeviceMessage.requestOf( IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod(), params); diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpslave/manager/IotModbusTcpSlaveConfigCacheService.java b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpslave/manager/IotModbusTcpSlaveConfigCacheService.java index 382e8ab56e..16f1e47854 100644 --- a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpslave/manager/IotModbusTcpSlaveConfigCacheService.java +++ b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpslave/manager/IotModbusTcpSlaveConfigCacheService.java @@ -80,7 +80,7 @@ public class IotModbusTcpSlaveConfigCacheService { */ private List buildMockConfigs() { IotModbusDeviceConfigRespDTO config = new IotModbusDeviceConfigRespDTO(); - config.setDeviceId(1L); + config.setDeviceId(25L); config.setProductKey("4aymZgOTOOCrDKRT"); config.setDeviceName("small"); config.setSlaveId(1); diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpslave/IotModbusTcpSlaveModbusRtuIntegrationTest.java b/yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpslave/IotModbusTcpSlaveModbusRtuIntegrationTest.java index f9cb923893..faea89ee9b 100644 --- a/yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbus/tcpslave/IotModbusTcpSlaveModbusRtuIntegrationTest.java +++ b/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; import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpslave.codec.IotModbusFrame; import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpslave.codec.IotModbusFrameDecoder; import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpslave.codec.IotModbusFrameEncoder; -import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpslave.codec.IotModbusUtils; +import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.common.IotModbusUtils; import io.vertx.core.Vertx; import io.vertx.core.buffer.Buffer; import io.vertx.core.net.NetClient;