package cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpserver.codec; import cn.iocoder.yudao.module.iot.core.enums.modbus.IotModbusFrameFormatEnum; import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.common.utils.IotModbusCommonUtils; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import java.nio.charset.StandardCharsets; /** * IoT Modbus 帧编码器:负责将 Modbus 请求/响应编码为字节数组,支持 MODBUS_TCP(MBAP)和 MODBUS_RTU(CRC16)两种帧格式。 * * @author 芋道源码 */ @RequiredArgsConstructor @Slf4j public class IotModbusFrameEncoder { private final int customFunctionCode; // ==================== 编码 ==================== /** * 编码读请求 * * @param slaveId 从站地址 * @param functionCode 功能码 * @param startAddress 起始寄存器地址 * @param quantity 寄存器数量 * @param format 帧格式 * @param transactionId 事务 ID(TCP 模式下使用,RTU 模式传 null) * @return 编码后的字节数组 */ public byte[] encodeReadRequest(int slaveId, int functionCode, int startAddress, int quantity, IotModbusFrameFormatEnum format, Integer transactionId) { // PDU: [FC(1)] [StartAddress(2)] [Quantity(2)] byte[] pdu = new byte[5]; pdu[0] = (byte) functionCode; pdu[1] = (byte) ((startAddress >> 8) & 0xFF); pdu[2] = (byte) (startAddress & 0xFF); pdu[3] = (byte) ((quantity >> 8) & 0xFF); pdu[4] = (byte) (quantity & 0xFF); return wrapFrame(slaveId, pdu, format, transactionId); } /** * 编码写请求(单个寄存器 FC06 / 单个线圈 FC05) * * @param slaveId 从站地址 * @param functionCode 功能码 * @param address 寄存器地址 * @param value 值 * @param format 帧格式 * @param transactionId 事务 ID(TCP 模式下使用,RTU 模式传 null) * @return 编码后的字节数组 */ public byte[] encodeWriteSingleRequest(int slaveId, int functionCode, int address, int value, IotModbusFrameFormatEnum format, Integer transactionId) { // FC05 单写线圈:Modbus 标准要求 value 为 0xFF00(ON)或 0x0000(OFF) if (functionCode == IotModbusCommonUtils.FC_WRITE_SINGLE_COIL) { value = (value != 0) ? 0xFF00 : 0x0000; } // PDU: [FC(1)] [Address(2)] [Value(2)] byte[] pdu = new byte[5]; pdu[0] = (byte) functionCode; pdu[1] = (byte) ((address >> 8) & 0xFF); pdu[2] = (byte) (address & 0xFF); pdu[3] = (byte) ((value >> 8) & 0xFF); pdu[4] = (byte) (value & 0xFF); return wrapFrame(slaveId, pdu, format, transactionId); } /** * 编码写多个寄存器请求(FC16) * * @param slaveId 从站地址 * @param address 起始地址 * @param values 值数组 * @param format 帧格式 * @param transactionId 事务 ID(TCP 模式下使用,RTU 模式传 null) * @return 编码后的字节数组 */ public byte[] encodeWriteMultipleRegistersRequest(int slaveId, int address, int[] values, IotModbusFrameFormatEnum format, Integer transactionId) { // PDU: [FC(1)] [Address(2)] [Quantity(2)] [ByteCount(1)] [Values(N*2)] int quantity = values.length; int byteCount = quantity * 2; byte[] pdu = new byte[6 + byteCount]; pdu[0] = (byte) 16; // FC16 pdu[1] = (byte) ((address >> 8) & 0xFF); pdu[2] = (byte) (address & 0xFF); pdu[3] = (byte) ((quantity >> 8) & 0xFF); pdu[4] = (byte) (quantity & 0xFF); pdu[5] = (byte) byteCount; for (int i = 0; i < quantity; i++) { pdu[6 + i * 2] = (byte) ((values[i] >> 8) & 0xFF); pdu[6 + i * 2 + 1] = (byte) (values[i] & 0xFF); } return wrapFrame(slaveId, pdu, format, transactionId); } /** * 编码写多个线圈请求(FC15) *

* 按 Modbus FC15 标准,线圈值按 bit 打包(每个 byte 包含 8 个线圈状态)。 * * @param slaveId 从站地址 * @param address 起始地址 * @param values 线圈值数组(int[],非0 表示 ON,0 表示 OFF) * @param format 帧格式 * @param transactionId 事务 ID(TCP 模式下使用,RTU 模式传 null) * @return 编码后的字节数组 */ public byte[] encodeWriteMultipleCoilsRequest(int slaveId, int address, int[] values, IotModbusFrameFormatEnum format, Integer transactionId) { // PDU: [FC(1)] [Address(2)] [Quantity(2)] [ByteCount(1)] [CoilValues(N)] int quantity = values.length; int byteCount = (quantity + 7) / 8; // 向上取整 byte[] pdu = new byte[6 + byteCount]; pdu[0] = (byte) IotModbusCommonUtils.FC_WRITE_MULTIPLE_COILS; // FC15 pdu[1] = (byte) ((address >> 8) & 0xFF); pdu[2] = (byte) (address & 0xFF); pdu[3] = (byte) ((quantity >> 8) & 0xFF); pdu[4] = (byte) (quantity & 0xFF); pdu[5] = (byte) byteCount; // 按 bit 打包:每个 byte 的 bit0 对应最低地址的线圈 for (int i = 0; i < quantity; i++) { if (values[i] != 0) { pdu[6 + i / 8] |= (byte) (1 << (i % 8)); } } return wrapFrame(slaveId, pdu, format, transactionId); } /** * 编码自定义功能码帧(认证响应等) * * @param slaveId 从站地址 * @param jsonData JSON 数据 * @param format 帧格式 * @param transactionId 事务 ID(TCP 模式下使用,RTU 模式传 null) * @return 编码后的字节数组 */ public byte[] encodeCustomFrame(int slaveId, String jsonData, IotModbusFrameFormatEnum format, Integer transactionId) { byte[] jsonBytes = jsonData.getBytes(StandardCharsets.UTF_8); // PDU: [FC(1)] [ByteCount(1)] [JSON data(N)] byte[] pdu = new byte[2 + jsonBytes.length]; pdu[0] = (byte) customFunctionCode; pdu[1] = (byte) jsonBytes.length; System.arraycopy(jsonBytes, 0, pdu, 2, jsonBytes.length); return wrapFrame(slaveId, pdu, format, transactionId); } // ==================== 帧封装 ==================== /** * 将 PDU 封装为完整帧 * * @param slaveId 从站地址 * @param pdu PDU 数据(含 functionCode) * @param format 帧格式 * @param transactionId 事务 ID(TCP 模式下使用,RTU 模式可为 null) * @return 完整帧字节数组 */ private byte[] wrapFrame(int slaveId, byte[] pdu, IotModbusFrameFormatEnum format, Integer transactionId) { if (format == IotModbusFrameFormatEnum.MODBUS_TCP) { return wrapTcpFrame(slaveId, pdu, transactionId != null ? transactionId : 0); } else { return wrapRtuFrame(slaveId, pdu); } } /** * 封装 MODBUS_TCP 帧 * [TransactionId(2)] [ProtocolId(2,=0x0000)] [Length(2)] [UnitId(1)] [PDU...] */ private byte[] wrapTcpFrame(int slaveId, byte[] pdu, int transactionId) { int length = 1 + pdu.length; // UnitId + PDU byte[] frame = new byte[6 + length]; // MBAP(6) + UnitId(1) + PDU // MBAP Header frame[0] = (byte) ((transactionId >> 8) & 0xFF); frame[1] = (byte) (transactionId & 0xFF); frame[2] = 0; // Protocol ID high frame[3] = 0; // Protocol ID low frame[4] = (byte) ((length >> 8) & 0xFF); frame[5] = (byte) (length & 0xFF); // Unit ID frame[6] = (byte) slaveId; // PDU System.arraycopy(pdu, 0, frame, 7, pdu.length); return frame; } /** * 封装 MODBUS_RTU 帧 * [SlaveId(1)] [PDU...] [CRC(2)] */ private byte[] wrapRtuFrame(int slaveId, byte[] pdu) { byte[] frame = new byte[1 + pdu.length + 2]; // SlaveId + PDU + CRC frame[0] = (byte) slaveId; System.arraycopy(pdu, 0, frame, 1, pdu.length); // 计算并追加 CRC16 int crc = IotModbusCommonUtils.calculateCrc16(frame, frame.length - 2); frame[frame.length - 2] = (byte) (crc & 0xFF); // CRC Low frame[frame.length - 1] = (byte) ((crc >> 8) & 0xFF); // CRC High return frame; } }