2026-06-26 20b96473f2520590a0dca6b775b81e3ea06a77a0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
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)
     * <p>
     * 按 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;
    }
 
}