package cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpserver; import cn.hutool.core.util.HexUtil; import cn.hutool.json.JSONObject; import cn.hutool.json.JSONUtil; import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO; import cn.iocoder.yudao.module.iot.core.enums.modbus.IotModbusFrameFormatEnum; import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils; import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.common.utils.IotModbusCommonUtils; import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpserver.codec.IotModbusFrame; import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpserver.codec.IotModbusFrameDecoder; import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpserver.codec.IotModbusFrameEncoder; import io.vertx.core.Vertx; import io.vertx.core.buffer.Buffer; import io.vertx.core.net.NetClient; import io.vertx.core.net.NetClientOptions; import io.vertx.core.net.NetSocket; import io.vertx.core.parsetools.RecordParser; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; import static org.junit.jupiter.api.Assertions.assertEquals; /** * IoT Modbus TCP Server 协议集成测试 — MODBUS_RTU 帧格式(手动测试) * *
测试场景:设备(TCP Client)连接到网关(TCP Server),使用 MODBUS_RTU(CRC16)帧格式通信 * *
使用步骤: *
* 注意:需手动在平台触发 property.set
*/
@Test
public void testPropertySetWrite() throws Exception {
NetSocket socket = connect().get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
try {
// 1. 先认证
IotModbusFrame authResponse = authenticate(socket);
log.info("[testPropertySetWrite][认证响应: {}]", authResponse.getCustomData());
// 2. 等待网关下发写请求(需手动在平台触发 property.set)
log.info("[testPropertySetWrite][等待网关下发写请求(请在平台触发 property.set)...]");
IotModbusFrame writeRequest = waitForRequest(socket);
log.info("[testPropertySetWrite][收到写请求: slaveId={}, FC={}, pdu={}]",
writeRequest.getSlaveId(), writeRequest.getFunctionCode(),
HexUtil.encodeHexStr(writeRequest.getPdu()));
} finally {
socket.close();
}
}
// ===================== 辅助方法 =====================
/**
* 建立 TCP 连接
*/
private CompletableFuture
* JSON: {"method":"auth","params":{"clientId":"...","username":"...","password":"..."}}
*
* RTU 帧格式:[SlaveId(1)] [FC=0x41(1)] [ByteCount(1)] [JSON(N)] [CRC16(2)]
*/
private byte[] buildAuthFrame(String clientId, String username, String password) {
JSONObject params = new JSONObject();
params.set("clientId", clientId);
params.set("username", username);
params.set("password", password);
JSONObject json = new JSONObject();
json.set("method", "auth");
json.set("params", params);
return FRAME_ENCODER.encodeCustomFrame(SLAVE_ID, json.toString(),
IotModbusFrameFormatEnum.MODBUS_RTU, 0);
}
/**
* 构造 FC03/FC01-04 读响应帧(MODBUS_RTU 格式)
*
* RTU 帧格式:[SlaveId(1)] [FC(1)] [ByteCount(1)] [RegisterData(N*2)] [CRC16(2)]
*/
private byte[] buildReadResponse(int slaveId, int functionCode, int[] registerValues) {
int byteCount = registerValues.length * 2;
// 帧长度:SlaveId(1) + FC(1) + ByteCount(1) + Data(N*2) + CRC(2)
int totalLength = 1 + 1 + 1 + byteCount + 2;
byte[] frame = new byte[totalLength];
frame[0] = (byte) slaveId;
frame[1] = (byte) functionCode;
frame[2] = (byte) byteCount;
for (int i = 0; i < registerValues.length; i++) {
frame[3 + i * 2] = (byte) ((registerValues[i] >> 8) & 0xFF);
frame[3 + i * 2 + 1] = (byte) (registerValues[i] & 0xFF);
}
// 计算 CRC16
int crc = IotModbusCommonUtils.calculateCrc16(frame, totalLength - 2);
frame[totalLength - 2] = (byte) (crc & 0xFF); // CRC Low
frame[totalLength - 1] = (byte) ((crc >> 8) & 0xFF); // CRC High
return frame;
}
}