Commit de1a53a5f14af453f3c22459c409db6d26e5f0e5

Authored by YunaiV
1 parent 4003f4b0

feat:【iot】UDP 协议:兼容下行的时候,基于连接的 codec 处理

yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/config/IotGatewayConfiguration.java
@@ -223,11 +223,9 @@ public class IotGatewayConfiguration { @@ -223,11 +223,9 @@ public class IotGatewayConfiguration {
223 @Bean 223 @Bean
224 public IotUdpDownstreamSubscriber iotUdpDownstreamSubscriber(IotUdpUpstreamProtocol protocolHandler, 224 public IotUdpDownstreamSubscriber iotUdpDownstreamSubscriber(IotUdpUpstreamProtocol protocolHandler,
225 IotDeviceMessageService messageService, 225 IotDeviceMessageService messageService,
226 - IotDeviceService deviceService,  
227 IotUdpSessionManager sessionManager, 226 IotUdpSessionManager sessionManager,
228 IotMessageBus messageBus) { 227 IotMessageBus messageBus) {
229 - return new IotUdpDownstreamSubscriber(protocolHandler, messageService, deviceService, sessionManager,  
230 - messageBus); 228 + return new IotUdpDownstreamSubscriber(protocolHandler, messageService, sessionManager, messageBus);
231 } 229 }
232 230
233 } 231 }
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/udp/IotUdpDownstreamSubscriber.java
@@ -6,7 +6,6 @@ import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; @@ -6,7 +6,6 @@ import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
6 import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils; 6 import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils;
7 import cn.iocoder.yudao.module.iot.gateway.protocol.udp.manager.IotUdpSessionManager; 7 import cn.iocoder.yudao.module.iot.gateway.protocol.udp.manager.IotUdpSessionManager;
8 import cn.iocoder.yudao.module.iot.gateway.protocol.udp.router.IotUdpDownstreamHandler; 8 import cn.iocoder.yudao.module.iot.gateway.protocol.udp.router.IotUdpDownstreamHandler;
9 -import cn.iocoder.yudao.module.iot.gateway.service.device.IotDeviceService;  
10 import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService; 9 import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService;
11 import jakarta.annotation.PostConstruct; 10 import jakarta.annotation.PostConstruct;
12 import lombok.RequiredArgsConstructor; 11 import lombok.RequiredArgsConstructor;
@@ -25,8 +24,6 @@ public class IotUdpDownstreamSubscriber implements IotMessageSubscriber<IotDevic @@ -25,8 +24,6 @@ public class IotUdpDownstreamSubscriber implements IotMessageSubscriber<IotDevic
25 24
26 private final IotDeviceMessageService messageService; 25 private final IotDeviceMessageService messageService;
27 26
28 - private final IotDeviceService deviceService;  
29 -  
30 private final IotUdpSessionManager sessionManager; 27 private final IotUdpSessionManager sessionManager;
31 28
32 private final IotMessageBus messageBus; 29 private final IotMessageBus messageBus;
@@ -36,7 +33,7 @@ public class IotUdpDownstreamSubscriber implements IotMessageSubscriber<IotDevic @@ -36,7 +33,7 @@ public class IotUdpDownstreamSubscriber implements IotMessageSubscriber<IotDevic
36 @PostConstruct 33 @PostConstruct
37 public void init() { 34 public void init() {
38 // 初始化下游处理器 35 // 初始化下游处理器
39 - this.downstreamHandler = new IotUdpDownstreamHandler(messageService, deviceService, sessionManager, protocol); 36 + this.downstreamHandler = new IotUdpDownstreamHandler(messageService, sessionManager, protocol);
40 37
41 messageBus.register(this); 38 messageBus.register(this);
42 log.info("[init][UDP 下游订阅者初始化完成,服务器 ID: {},Topic: {}]", 39 log.info("[init][UDP 下游订阅者初始化完成,服务器 ID: {},Topic: {}]",
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/udp/manager/IotUdpSessionManager.java
@@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.iot.gateway.protocol.udp.manager; @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.iot.gateway.protocol.udp.manager;
2 2
3 import io.vertx.core.buffer.Buffer; 3 import io.vertx.core.buffer.Buffer;
4 import io.vertx.core.datagram.DatagramSocket; 4 import io.vertx.core.datagram.DatagramSocket;
  5 +import lombok.Data;
5 import lombok.extern.slf4j.Slf4j; 6 import lombok.extern.slf4j.Slf4j;
6 import org.springframework.stereotype.Component; 7 import org.springframework.stereotype.Component;
7 8
@@ -29,9 +30,9 @@ import java.util.concurrent.ConcurrentHashMap; @@ -29,9 +30,9 @@ import java.util.concurrent.ConcurrentHashMap;
29 public class IotUdpSessionManager { 30 public class IotUdpSessionManager {
30 31
31 /** 32 /**
32 - * 设备 ID -> 设备地址(用于下行消息发送) 33 + * 设备 ID -> 会话信息(包含地址和 codecType)
33 */ 34 */
34 - private final Map<Long, InetSocketAddress> deviceAddressMap = new ConcurrentHashMap<>(); 35 + private final Map<Long, SessionInfo> deviceSessionMap = new ConcurrentHashMap<>();
35 36
36 /** 37 /**
37 * 设备地址 Key -> 最后活跃时间(用于清理) 38 * 设备地址 Key -> 最后活跃时间(用于清理)
@@ -44,18 +45,41 @@ public class IotUdpSessionManager { @@ -44,18 +45,41 @@ public class IotUdpSessionManager {
44 private final Map<String, Long> addressDeviceMap = new ConcurrentHashMap<>(); 45 private final Map<String, Long> addressDeviceMap = new ConcurrentHashMap<>();
45 46
46 /** 47 /**
47 - * 更新设备地址(每次收到上行消息时调用) 48 + * 更新设备会话(每次收到上行消息时调用)
48 * 49 *
49 - * @param deviceId 设备 ID  
50 - * @param address 设备地址 50 + * @param deviceId 设备 ID
  51 + * @param address 设备地址
  52 + * @param codecType 消息编解码类型
51 */ 53 */
52 - public void updateDeviceAddress(Long deviceId, InetSocketAddress address) { 54 + public void updateDeviceSession(Long deviceId, InetSocketAddress address, String codecType) {
53 String addressKey = buildAddressKey(address); 55 String addressKey = buildAddressKey(address);
54 - // 更新设备地址映射  
55 - deviceAddressMap.put(deviceId, address); 56 + // 更新设备会话映射
  57 + deviceSessionMap.put(deviceId, new SessionInfo().setAddress(address).setCodecType(codecType));
56 lastActiveTimeMap.put(addressKey, LocalDateTime.now()); 58 lastActiveTimeMap.put(addressKey, LocalDateTime.now());
57 addressDeviceMap.put(addressKey, deviceId); 59 addressDeviceMap.put(addressKey, deviceId);
58 - log.debug("[updateDeviceAddress][更新设备地址,设备 ID: {},地址: {}]", deviceId, addressKey); 60 + log.debug("[updateDeviceSession][更新设备会话,设备 ID: {},地址: {},codecType: {}]", deviceId, addressKey, codecType);
  61 + }
  62 +
  63 + /**
  64 + * 更新设备地址(兼容旧接口,默认不更新 codecType)
  65 + *
  66 + * @param deviceId 设备 ID
  67 + * @param address 设备地址
  68 + */
  69 + public void updateDeviceAddress(Long deviceId, InetSocketAddress address) {
  70 + SessionInfo sessionInfo = deviceSessionMap.get(deviceId);
  71 + String codecType = sessionInfo != null ? sessionInfo.getCodecType() : null;
  72 + updateDeviceSession(deviceId, address, codecType);
  73 + }
  74 +
  75 + /**
  76 + * 获取设备会话信息
  77 + *
  78 + * @param deviceId 设备 ID
  79 + * @return 会话信息
  80 + */
  81 + public SessionInfo getSessionInfo(Long deviceId) {
  82 + return deviceSessionMap.get(deviceId);
59 } 83 }
60 84
61 /** 85 /**
@@ -65,7 +89,7 @@ public class IotUdpSessionManager { @@ -65,7 +89,7 @@ public class IotUdpSessionManager {
65 * @return 是否在线 89 * @return 是否在线
66 */ 90 */
67 public boolean isDeviceOnline(Long deviceId) { 91 public boolean isDeviceOnline(Long deviceId) {
68 - return deviceAddressMap.containsKey(deviceId); 92 + return deviceSessionMap.containsKey(deviceId);
69 } 93 }
70 94
71 /** 95 /**
@@ -87,12 +111,13 @@ public class IotUdpSessionManager { @@ -87,12 +111,13 @@ public class IotUdpSessionManager {
87 * @return 是否发送成功 111 * @return 是否发送成功
88 */ 112 */
89 public boolean sendToDevice(Long deviceId, byte[] data, DatagramSocket socket) { 113 public boolean sendToDevice(Long deviceId, byte[] data, DatagramSocket socket) {
90 - InetSocketAddress address = deviceAddressMap.get(deviceId);  
91 - if (address == null) {  
92 - log.warn("[sendToDevice][设备地址不存在,设备 ID: {}]", deviceId); 114 + SessionInfo sessionInfo = deviceSessionMap.get(deviceId);
  115 + if (sessionInfo == null || sessionInfo.getAddress() == null) {
  116 + log.warn("[sendToDevice][设备会话不存在,设备 ID: {}]", deviceId);
93 return false; 117 return false;
94 } 118 }
95 119
  120 + InetSocketAddress address = sessionInfo.getAddress();
96 try { 121 try {
97 socket.send(Buffer.buffer(data), address.getPort(), address.getHostString(), result -> { 122 socket.send(Buffer.buffer(data), address.getPort(), address.getHostString(), result -> {
98 if (result.succeeded()) { 123 if (result.succeeded()) {
@@ -134,8 +159,8 @@ public class IotUdpSessionManager { @@ -134,8 +159,8 @@ public class IotUdpSessionManager {
134 iterator.remove(); 159 iterator.remove();
135 continue; 160 continue;
136 } 161 }
137 - InetSocketAddress address = deviceAddressMap.remove(deviceId);  
138 - if (address == null) { 162 + SessionInfo sessionInfo = deviceSessionMap.remove(deviceId);
  163 + if (sessionInfo == null) {
139 iterator.remove(); 164 iterator.remove();
140 continue; 165 continue;
141 } 166 }
@@ -157,4 +182,22 @@ public class IotUdpSessionManager { @@ -157,4 +182,22 @@ public class IotUdpSessionManager {
157 return address.getHostString() + ":" + address.getPort(); 182 return address.getHostString() + ":" + address.getPort();
158 } 183 }
159 184
  185 + /**
  186 + * 会话信息
  187 + */
  188 + @Data
  189 + public static class SessionInfo {
  190 +
  191 + /**
  192 + * 设备地址
  193 + */
  194 + private InetSocketAddress address;
  195 +
  196 + /**
  197 + * 消息编解码类型
  198 + */
  199 + private String codecType;
  200 +
  201 + }
  202 +
160 } 203 }
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/udp/router/IotUdpDownstreamHandler.java
1 package cn.iocoder.yudao.module.iot.gateway.protocol.udp.router; 1 package cn.iocoder.yudao.module.iot.gateway.protocol.udp.router;
2 2
3 -import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceRespDTO;  
4 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; 3 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
5 import cn.iocoder.yudao.module.iot.gateway.protocol.udp.IotUdpUpstreamProtocol; 4 import cn.iocoder.yudao.module.iot.gateway.protocol.udp.IotUdpUpstreamProtocol;
6 import cn.iocoder.yudao.module.iot.gateway.protocol.udp.manager.IotUdpSessionManager; 5 import cn.iocoder.yudao.module.iot.gateway.protocol.udp.manager.IotUdpSessionManager;
7 -import cn.iocoder.yudao.module.iot.gateway.service.device.IotDeviceService;  
8 import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService; 6 import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService;
9 import io.vertx.core.datagram.DatagramSocket; 7 import io.vertx.core.datagram.DatagramSocket;
10 import lombok.extern.slf4j.Slf4j; 8 import lombok.extern.slf4j.Slf4j;
@@ -19,18 +17,14 @@ public class IotUdpDownstreamHandler { @@ -19,18 +17,14 @@ public class IotUdpDownstreamHandler {
19 17
20 private final IotDeviceMessageService deviceMessageService; 18 private final IotDeviceMessageService deviceMessageService;
21 19
22 - private final IotDeviceService deviceService;  
23 -  
24 private final IotUdpSessionManager sessionManager; 20 private final IotUdpSessionManager sessionManager;
25 21
26 private final IotUdpUpstreamProtocol protocol; 22 private final IotUdpUpstreamProtocol protocol;
27 23
28 public IotUdpDownstreamHandler(IotDeviceMessageService deviceMessageService, 24 public IotUdpDownstreamHandler(IotDeviceMessageService deviceMessageService,
29 - IotDeviceService deviceService,  
30 IotUdpSessionManager sessionManager, 25 IotUdpSessionManager sessionManager,
31 IotUdpUpstreamProtocol protocol) { 26 IotUdpUpstreamProtocol protocol) {
32 this.deviceMessageService = deviceMessageService; 27 this.deviceMessageService = deviceMessageService;
33 - this.deviceService = deviceService;  
34 this.sessionManager = sessionManager; 28 this.sessionManager = sessionManager;
35 this.protocol = protocol; 29 this.protocol = protocol;
36 } 30 }
@@ -45,21 +39,15 @@ public class IotUdpDownstreamHandler { @@ -45,21 +39,15 @@ public class IotUdpDownstreamHandler {
45 log.info("[handle][处理下行消息,设备 ID: {},方法: {},消息 ID: {}]", 39 log.info("[handle][处理下行消息,设备 ID: {},方法: {},消息 ID: {}]",
46 message.getDeviceId(), message.getMethod(), message.getId()); 40 message.getDeviceId(), message.getMethod(), message.getId());
47 41
48 - // 1.1 获取设备信息  
49 - IotDeviceRespDTO deviceInfo = deviceService.getDeviceFromCache(message.getDeviceId());  
50 - if (deviceInfo == null) {  
51 - log.error("[handle][设备不存在,设备 ID: {}]", message.getDeviceId());  
52 - return;  
53 - }  
54 - // 1.2 检查设备是否在线(即是否有地址映射)  
55 - if (sessionManager.isDeviceOffline(message.getDeviceId())) { 42 + // 1. 获取会话信息(包含 codecType)
  43 + IotUdpSessionManager.SessionInfo sessionInfo = sessionManager.getSessionInfo(message.getDeviceId());
  44 + if (sessionInfo == null) {
56 log.warn("[handle][设备不在线,设备 ID: {}]", message.getDeviceId()); 45 log.warn("[handle][设备不在线,设备 ID: {}]", message.getDeviceId());
57 return; 46 return;
58 } 47 }
59 48
60 - // 2. 根据产品 Key 和设备名称编码消息,并发送到设备  
61 - byte[] bytes = deviceMessageService.encodeDeviceMessage(message, deviceInfo.getProductKey(),  
62 - deviceInfo.getDeviceName()); 49 + // 2. 使用会话中的 codecType 编码消息,并发送到设备
  50 + byte[] bytes = deviceMessageService.encodeDeviceMessage(message, sessionInfo.getCodecType());
63 DatagramSocket socket = protocol.getUdpSocket(); 51 DatagramSocket socket = protocol.getUdpSocket();
64 if (socket == null) { 52 if (socket == null) {
65 log.error("[handle][UDP Socket 不可用,设备 ID: {}]", message.getDeviceId()); 53 log.error("[handle][UDP Socket 不可用,设备 ID: {}]", message.getDeviceId());
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/udp/router/IotUdpUpstreamHandler.java
@@ -195,8 +195,8 @@ public class IotUdpUpstreamHandler { @@ -195,8 +195,8 @@ public class IotUdpUpstreamHandler {
195 // 3.1 生成 JWT Token(无状态) 195 // 3.1 生成 JWT Token(无状态)
196 String token = deviceTokenService.createToken(device.getProductKey(), device.getDeviceName()); 196 String token = deviceTokenService.createToken(device.getProductKey(), device.getDeviceName());
197 197
198 - // 3.2 更新设备地址映射(用于下行消息)  
199 - sessionManager.updateDeviceAddress(device.getId(), senderAddress); 198 + // 3.2 更新设备会话信息(用于下行消息,保存 codecType)
  199 + sessionManager.updateDeviceSession(device.getId(), senderAddress, codecType);
200 200
201 // 3.3 发送上线消息 201 // 3.3 发送上线消息
202 sendOnlineMessage(device); 202 sendOnlineMessage(device);
@@ -298,8 +298,8 @@ public class IotUdpUpstreamHandler { @@ -298,8 +298,8 @@ public class IotUdpUpstreamHandler {
298 return; 298 return;
299 } 299 }
300 300
301 - // 3. 更新设备地址映射(保持最新)  
302 - sessionManager.updateDeviceAddress(device.getId(), senderAddress); 301 + // 3. 更新设备会话信息(保持最新,保存 codecType)
  302 + sessionManager.updateDeviceSession(device.getId(), senderAddress, codecType);
303 303
304 // 4. 将 body 设置为实际的 params,发送消息到消息总线 304 // 4. 将 body 设置为实际的 params,发送消息到消息总线
305 message.setParams(body); 305 message.setParams(body);
yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/udp/IotDirectDeviceUdpProtocolIntegrationTest.java
1 package cn.iocoder.yudao.module.iot.gateway.protocol.udp; 1 package cn.iocoder.yudao.module.iot.gateway.protocol.udp;
2 2
3 import cn.hutool.core.map.MapUtil; 3 import cn.hutool.core.map.MapUtil;
  4 +import cn.hutool.core.util.HexUtil;
4 import cn.hutool.core.util.IdUtil; 5 import cn.hutool.core.util.IdUtil;
5 -import cn.iocoder.yudao.framework.common.util.json.JsonUtils;  
6 import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO; 6 import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO;
7 import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum; 7 import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
8 -import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterReqDTO; 8 +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
9 import cn.iocoder.yudao.module.iot.core.topic.event.IotDeviceEventPostReqDTO; 9 import cn.iocoder.yudao.module.iot.core.topic.event.IotDeviceEventPostReqDTO;
10 import cn.iocoder.yudao.module.iot.core.topic.property.IotDevicePropertyPostReqDTO; 10 import cn.iocoder.yudao.module.iot.core.topic.property.IotDevicePropertyPostReqDTO;
11 import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils; 11 import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils;
  12 +import cn.iocoder.yudao.module.iot.gateway.codec.IotDeviceMessageCodec;
  13 +import cn.iocoder.yudao.module.iot.gateway.codec.tcp.IotTcpBinaryDeviceMessageCodec;
  14 +import cn.iocoder.yudao.module.iot.gateway.codec.tcp.IotTcpJsonDeviceMessageCodec;
12 import lombok.extern.slf4j.Slf4j; 15 import lombok.extern.slf4j.Slf4j;
13 import org.junit.jupiter.api.Test; 16 import org.junit.jupiter.api.Test;
14 17
15 import java.net.DatagramPacket; 18 import java.net.DatagramPacket;
16 import java.net.DatagramSocket; 19 import java.net.DatagramSocket;
17 import java.net.InetAddress; 20 import java.net.InetAddress;
18 -import java.nio.charset.StandardCharsets;  
19 import java.util.HashMap; 21 import java.util.HashMap;
20 import java.util.Map; 22 import java.util.Map;
21 23
@@ -24,10 +26,16 @@ import java.util.Map; @@ -24,10 +26,16 @@ import java.util.Map;
24 * 26 *
25 * <p>测试场景:直连设备(IotProductDeviceTypeEnum 的 DIRECT 类型)通过 UDP 协议直接连接平台 27 * <p>测试场景:直连设备(IotProductDeviceTypeEnum 的 DIRECT 类型)通过 UDP 协议直接连接平台
26 * 28 *
  29 + * <p>支持两种编解码格式:
  30 + * <ul>
  31 + * <li>{@link IotTcpJsonDeviceMessageCodec} - JSON 格式</li>
  32 + * <li>{@link IotTcpBinaryDeviceMessageCodec} - 二进制格式</li>
  33 + * </ul>
  34 + *
27 * <p>使用步骤: 35 * <p>使用步骤:
28 * <ol> 36 * <ol>
29 - * <li>启动 yudao-module-iot-gateway 服务(UDP 端口 8092)</li>  
30 - * <li>运行 {@link #testDeviceRegister()} 测试直连设备动态注册(一型一密)</li> 37 + * <li>启动 yudao-module-iot-gateway 服务(UDP 端口 8093)</li>
  38 + * <li>修改 {@link #CODEC} 选择测试的编解码格式</li>
31 * <li>运行 {@link #testAuth()} 获取设备 token,将返回的 token 粘贴到 {@link #TOKEN} 常量</li> 39 * <li>运行 {@link #testAuth()} 获取设备 token,将返回的 token 粘贴到 {@link #TOKEN} 常量</li>
32 * <li>运行以下测试方法: 40 * <li>运行以下测试方法:
33 * <ul> 41 * <ul>
@@ -48,6 +56,10 @@ public class IotDirectDeviceUdpProtocolIntegrationTest { @@ -48,6 +56,10 @@ public class IotDirectDeviceUdpProtocolIntegrationTest {
48 private static final int SERVER_PORT = 8093; 56 private static final int SERVER_PORT = 8093;
49 private static final int TIMEOUT_MS = 5000; 57 private static final int TIMEOUT_MS = 5000;
50 58
  59 + // ===================== 编解码器选择(修改此处切换 JSON / Binary) =====================
  60 + private static final IotDeviceMessageCodec CODEC = new IotTcpJsonDeviceMessageCodec();
  61 +// private static final IotDeviceMessageCodec CODEC = new IotTcpBinaryDeviceMessageCodec();
  62 +
51 // ===================== 直连设备信息(根据实际情况修改,从 iot_device 表查询子设备) ===================== 63 // ===================== 直连设备信息(根据实际情况修改,从 iot_device 表查询子设备) =====================
52 private static final String PRODUCT_KEY = "4aymZgOTOOCrDKRT"; 64 private static final String PRODUCT_KEY = "4aymZgOTOOCrDKRT";
53 private static final String DEVICE_NAME = "small"; 65 private static final String DEVICE_NAME = "small";
@@ -65,27 +77,32 @@ public class IotDirectDeviceUdpProtocolIntegrationTest { @@ -65,27 +77,32 @@ public class IotDirectDeviceUdpProtocolIntegrationTest {
65 */ 77 */
66 @Test 78 @Test
67 public void testAuth() throws Exception { 79 public void testAuth() throws Exception {
68 - // 1.1 构建请求 80 + // 1.1 构建认证消息
69 IotDeviceAuthReqDTO authInfo = IotDeviceAuthUtils.getAuthInfo(PRODUCT_KEY, DEVICE_NAME, DEVICE_SECRET); 81 IotDeviceAuthReqDTO authInfo = IotDeviceAuthUtils.getAuthInfo(PRODUCT_KEY, DEVICE_NAME, DEVICE_SECRET);
70 IotDeviceAuthReqDTO authReqDTO = new IotDeviceAuthReqDTO() 82 IotDeviceAuthReqDTO authReqDTO = new IotDeviceAuthReqDTO()
71 .setClientId(authInfo.getClientId()) 83 .setClientId(authInfo.getClientId())
72 .setUsername(authInfo.getUsername()) 84 .setUsername(authInfo.getUsername())
73 .setPassword(authInfo.getPassword()); 85 .setPassword(authInfo.getPassword());
74 - String payload = JsonUtils.toJsonString(MapUtil.builder()  
75 - .put("id", IdUtil.fastSimpleUUID())  
76 - .put("method", "auth")  
77 - .put("params", authReqDTO)  
78 - .build());  
79 - // 1.2 输出请求  
80 - log.info("[testAuth][请求体: {}]", payload); 86 + IotDeviceMessage request = IotDeviceMessage.of(IdUtil.fastSimpleUUID(), "auth", authReqDTO, null, null, null);
  87 + // 1.2 编码
  88 + byte[] payload = CODEC.encode(request);
  89 + log.info("[testAuth][Codec: {}, 请求消息: {}, 数据包长度: {} 字节]", CODEC.type(), request, payload.length);
  90 + if (CODEC instanceof IotTcpBinaryDeviceMessageCodec) {
  91 + log.info("[testAuth][二进制数据包(HEX): {}]", HexUtil.encodeHexStr(payload));
  92 + }
81 93
82 // 2.1 发送请求 94 // 2.1 发送请求
83 try (DatagramSocket socket = new DatagramSocket()) { 95 try (DatagramSocket socket = new DatagramSocket()) {
84 socket.setSoTimeout(TIMEOUT_MS); 96 socket.setSoTimeout(TIMEOUT_MS);
85 - String response = sendAndReceive(socket, payload);  
86 - // 2.2 输出结果  
87 - log.info("[testAuth][响应体: {}]", response);  
88 - log.info("[testAuth][请将返回的 token 复制到 TOKEN 常量中]"); 97 + byte[] responseBytes = sendAndReceive(socket, payload);
  98 + // 2.2 解码响应
  99 + if (responseBytes != null) {
  100 + IotDeviceMessage response = CODEC.decode(responseBytes);
  101 + log.info("[testAuth][响应消息: {}]", response);
  102 + log.info("[testAuth][请将返回的 token 复制到 TOKEN 常量中]");
  103 + } else {
  104 + log.warn("[testAuth][未收到响应]");
  105 + }
89 } 106 }
90 } 107 }
91 108
@@ -96,25 +113,30 @@ public class IotDirectDeviceUdpProtocolIntegrationTest { @@ -96,25 +113,30 @@ public class IotDirectDeviceUdpProtocolIntegrationTest {
96 */ 113 */
97 @Test 114 @Test
98 public void testPropertyPost() throws Exception { 115 public void testPropertyPost() throws Exception {
99 - // 1.1 构建请求(UDP 协议:token 放在 params 中)  
100 - String payload = JsonUtils.toJsonString(MapUtil.builder()  
101 - .put("id", IdUtil.fastSimpleUUID())  
102 - .put("method", IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod())  
103 - .put("version", "1.0")  
104 - .put("params", withToken(IotDevicePropertyPostReqDTO.of(MapUtil.<String, Object>builder() 116 + // 1.1 构建属性上报消息(UDP 协议:token 放在 params 中)
  117 + IotDeviceMessage request = IotDeviceMessage.of(
  118 + IdUtil.fastSimpleUUID(),
  119 + IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod(),
  120 + withToken(IotDevicePropertyPostReqDTO.of(MapUtil.<String, Object>builder()
105 .put("width", 1) 121 .put("width", 1)
106 .put("height", "2") 122 .put("height", "2")
107 - .build())))  
108 - .build());  
109 - // 1.2 输出请求  
110 - log.info("[testPropertyPost][请求体: {}]", payload); 123 + .build())),
  124 + null, null, null);
  125 + // 1.2 编码
  126 + byte[] payload = CODEC.encode(request);
  127 + log.info("[testPropertyPost][Codec: {}, 请求消息: {}]", CODEC.type(), request);
111 128
112 - // 2. 发送请求 129 + // 2.1 发送请求
113 try (DatagramSocket socket = new DatagramSocket()) { 130 try (DatagramSocket socket = new DatagramSocket()) {
114 socket.setSoTimeout(TIMEOUT_MS); 131 socket.setSoTimeout(TIMEOUT_MS);
115 - String response = sendAndReceive(socket, payload);  
116 - // 2.2 输出结果  
117 - log.info("[testPropertyPost][响应体: {}]", response); 132 + byte[] responseBytes = sendAndReceive(socket, payload);
  133 + // 2.2 解码响应
  134 + if (responseBytes != null) {
  135 + IotDeviceMessage response = CODEC.decode(responseBytes);
  136 + log.info("[testPropertyPost][响应消息: {}]", response);
  137 + } else {
  138 + log.warn("[testPropertyPost][未收到响应]");
  139 + }
118 } 140 }
119 } 141 }
120 142
@@ -125,60 +147,30 @@ public class IotDirectDeviceUdpProtocolIntegrationTest { @@ -125,60 +147,30 @@ public class IotDirectDeviceUdpProtocolIntegrationTest {
125 */ 147 */
126 @Test 148 @Test
127 public void testEventPost() throws Exception { 149 public void testEventPost() throws Exception {
128 - // 1.1 构建请求(UDP 协议:token 放在 params 中)  
129 - String payload = JsonUtils.toJsonString(MapUtil.builder()  
130 - .put("id", IdUtil.fastSimpleUUID())  
131 - .put("method", IotDeviceMessageMethodEnum.EVENT_POST.getMethod())  
132 - .put("version", "1.0")  
133 - .put("params", withToken(IotDeviceEventPostReqDTO.of( 150 + // 1.1 构建事件上报消息(UDP 协议:token 放在 params 中)
  151 + IotDeviceMessage request = IotDeviceMessage.of(
  152 + IdUtil.fastSimpleUUID(),
  153 + IotDeviceMessageMethodEnum.EVENT_POST.getMethod(),
  154 + withToken(IotDeviceEventPostReqDTO.of(
134 "eat", 155 "eat",
135 MapUtil.<String, Object>builder().put("rice", 3).build(), 156 MapUtil.<String, Object>builder().put("rice", 3).build(),
136 - System.currentTimeMillis())))  
137 - .build());  
138 - // 1.2 输出请求  
139 - log.info("[testEventPost][请求体: {}]", payload);  
140 -  
141 - // 2.1 发送请求  
142 - try (DatagramSocket socket = new DatagramSocket()) {  
143 - socket.setSoTimeout(TIMEOUT_MS);  
144 - String response = sendAndReceive(socket, payload);  
145 - // 2.2 输出结果  
146 - log.info("[testEventPost][响应体: {}]", response);  
147 - }  
148 - }  
149 -  
150 - // ===================== 动态注册测试 =====================  
151 -  
152 - /**  
153 - * 直连设备动态注册测试(一型一密)  
154 - * <p>  
155 - * 使用产品密钥(productSecret)验证身份,成功后返回设备密钥(deviceSecret)  
156 - * <p>  
157 - * 注意:此接口不需要 Token 认证  
158 - */  
159 - @Test  
160 - public void testDeviceRegister() throws Exception {  
161 - // 1.1 构建请求参数  
162 - IotDeviceRegisterReqDTO reqDTO = new IotDeviceRegisterReqDTO();  
163 - reqDTO.setProductKey(PRODUCT_KEY);  
164 - reqDTO.setDeviceName("test-" + System.currentTimeMillis());  
165 - reqDTO.setProductSecret("test-product-secret");  
166 - // 1.2 构建请求  
167 - String payload = JsonUtils.toJsonString(MapUtil.builder()  
168 - .put("id", IdUtil.fastSimpleUUID())  
169 - .put("method", IotDeviceMessageMethodEnum.DEVICE_REGISTER.getMethod())  
170 - .put("params", reqDTO)  
171 - .build());  
172 - // 1.3 输出请求  
173 - log.info("[testDeviceRegister][请求体: {}]", payload); 157 + System.currentTimeMillis())),
  158 + null, null, null);
  159 + // 1.2 编码
  160 + byte[] payload = CODEC.encode(request);
  161 + log.info("[testEventPost][Codec: {}, 请求消息: {}]", CODEC.type(), request);
174 162
175 // 2.1 发送请求 163 // 2.1 发送请求
176 try (DatagramSocket socket = new DatagramSocket()) { 164 try (DatagramSocket socket = new DatagramSocket()) {
177 socket.setSoTimeout(TIMEOUT_MS); 165 socket.setSoTimeout(TIMEOUT_MS);
178 - String response = sendAndReceive(socket, payload);  
179 - // 2.2 输出结果  
180 - log.info("[testDeviceRegister][响应体: {}]", response);  
181 - log.info("[testDeviceRegister][成功后可使用返回的 deviceSecret 进行一机一密认证]"); 166 + byte[] responseBytes = sendAndReceive(socket, payload);
  167 + // 2.2 解码响应
  168 + if (responseBytes != null) {
  169 + IotDeviceMessage response = CODEC.decode(responseBytes);
  170 + log.info("[testEventPost][响应消息: {}]", response);
  171 + } else {
  172 + log.warn("[testEventPost][未收到响应]");
  173 + }
182 } 174 }
183 } 175 }
184 176
@@ -201,20 +193,18 @@ public class IotDirectDeviceUdpProtocolIntegrationTest { @@ -201,20 +193,18 @@ public class IotDirectDeviceUdpProtocolIntegrationTest {
201 return result; 193 return result;
202 } 194 }
203 195
204 -  
205 /** 196 /**
206 * 发送 UDP 请求并接收响应 197 * 发送 UDP 请求并接收响应
207 * 198 *
208 * @param socket UDP Socket 199 * @param socket UDP Socket
209 - * @param payload 请求体  
210 - * @return 响应内容 200 + * @param payload 请求数据
  201 + * @return 响应数据
211 */ 202 */
212 - public static String sendAndReceive(DatagramSocket socket, String payload) throws Exception {  
213 - byte[] sendData = payload.getBytes(StandardCharsets.UTF_8); 203 + public static byte[] sendAndReceive(DatagramSocket socket, byte[] payload) throws Exception {
214 InetAddress address = InetAddress.getByName(SERVER_HOST); 204 InetAddress address = InetAddress.getByName(SERVER_HOST);
215 205
216 // 发送请求 206 // 发送请求
217 - DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, address, SERVER_PORT); 207 + DatagramPacket sendPacket = new DatagramPacket(payload, payload.length, address, SERVER_PORT);
218 socket.send(sendPacket); 208 socket.send(sendPacket);
219 209
220 // 接收响应 210 // 接收响应
@@ -222,7 +212,9 @@ public class IotDirectDeviceUdpProtocolIntegrationTest { @@ -222,7 +212,9 @@ public class IotDirectDeviceUdpProtocolIntegrationTest {
222 DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length); 212 DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length);
223 try { 213 try {
224 socket.receive(receivePacket); 214 socket.receive(receivePacket);
225 - return new String(receivePacket.getData(), 0, receivePacket.getLength(), StandardCharsets.UTF_8); 215 + byte[] response = new byte[receivePacket.getLength()];
  216 + System.arraycopy(receivePacket.getData(), 0, response, 0, receivePacket.getLength());
  217 + return response;
226 } catch (java.net.SocketTimeoutException e) { 218 } catch (java.net.SocketTimeoutException e) {
227 log.warn("[sendAndReceive][接收响应超时]"); 219 log.warn("[sendAndReceive][接收响应超时]");
228 return null; 220 return null;
yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/udp/IotGatewayDeviceUdpProtocolIntegrationTest.java
1 package cn.iocoder.yudao.module.iot.gateway.protocol.udp; 1 package cn.iocoder.yudao.module.iot.gateway.protocol.udp;
2 2
3 import cn.hutool.core.map.MapUtil; 3 import cn.hutool.core.map.MapUtil;
  4 +import cn.hutool.core.util.HexUtil;
4 import cn.hutool.core.util.IdUtil; 5 import cn.hutool.core.util.IdUtil;
5 -import cn.iocoder.yudao.framework.common.util.json.JsonUtils;  
6 import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO; 6 import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO;
7 import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum; 7 import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
  8 +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
8 import cn.iocoder.yudao.module.iot.core.topic.IotDeviceIdentity; 9 import cn.iocoder.yudao.module.iot.core.topic.IotDeviceIdentity;
9 import cn.iocoder.yudao.module.iot.core.topic.auth.IotSubDeviceRegisterReqDTO; 10 import cn.iocoder.yudao.module.iot.core.topic.auth.IotSubDeviceRegisterReqDTO;
10 import cn.iocoder.yudao.module.iot.core.topic.property.IotDevicePropertyPackPostReqDTO; 11 import cn.iocoder.yudao.module.iot.core.topic.property.IotDevicePropertyPackPostReqDTO;
@@ -12,6 +13,9 @@ import cn.iocoder.yudao.module.iot.core.topic.topo.IotDeviceTopoAddReqDTO; @@ -12,6 +13,9 @@ import cn.iocoder.yudao.module.iot.core.topic.topo.IotDeviceTopoAddReqDTO;
12 import cn.iocoder.yudao.module.iot.core.topic.topo.IotDeviceTopoDeleteReqDTO; 13 import cn.iocoder.yudao.module.iot.core.topic.topo.IotDeviceTopoDeleteReqDTO;
13 import cn.iocoder.yudao.module.iot.core.topic.topo.IotDeviceTopoGetReqDTO; 14 import cn.iocoder.yudao.module.iot.core.topic.topo.IotDeviceTopoGetReqDTO;
14 import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils; 15 import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils;
  16 +import cn.iocoder.yudao.module.iot.gateway.codec.IotDeviceMessageCodec;
  17 +import cn.iocoder.yudao.module.iot.gateway.codec.tcp.IotTcpBinaryDeviceMessageCodec;
  18 +import cn.iocoder.yudao.module.iot.gateway.codec.tcp.IotTcpJsonDeviceMessageCodec;
15 import lombok.extern.slf4j.Slf4j; 19 import lombok.extern.slf4j.Slf4j;
16 import org.junit.jupiter.api.Test; 20 import org.junit.jupiter.api.Test;
17 21
@@ -28,9 +32,16 @@ import static cn.iocoder.yudao.module.iot.gateway.protocol.udp.IotDirectDeviceUd @@ -28,9 +32,16 @@ import static cn.iocoder.yudao.module.iot.gateway.protocol.udp.IotDirectDeviceUd
28 * 32 *
29 * <p>测试场景:网关设备(IotProductDeviceTypeEnum 的 GATEWAY 类型)通过 UDP 协议管理子设备拓扑关系 33 * <p>测试场景:网关设备(IotProductDeviceTypeEnum 的 GATEWAY 类型)通过 UDP 协议管理子设备拓扑关系
30 * 34 *
  35 + * <p>支持两种编解码格式:
  36 + * <ul>
  37 + * <li>{@link IotTcpJsonDeviceMessageCodec} - JSON 格式</li>
  38 + * <li>{@link IotTcpBinaryDeviceMessageCodec} - 二进制格式</li>
  39 + * </ul>
  40 + *
31 * <p>使用步骤: 41 * <p>使用步骤:
32 * <ol> 42 * <ol>
33 * <li>启动 yudao-module-iot-gateway 服务(UDP 端口 8093)</li> 43 * <li>启动 yudao-module-iot-gateway 服务(UDP 端口 8093)</li>
  44 + * <li>修改 {@link #CODEC} 选择测试的编解码格式</li>
34 * <li>运行 {@link #testAuth()} 获取网关设备 token,将返回的 token 粘贴到 {@link #GATEWAY_TOKEN} 常量</li> 45 * <li>运行 {@link #testAuth()} 获取网关设备 token,将返回的 token 粘贴到 {@link #GATEWAY_TOKEN} 常量</li>
35 * <li>运行以下测试方法: 46 * <li>运行以下测试方法:
36 * <ul> 47 * <ul>
@@ -50,10 +61,12 @@ import static cn.iocoder.yudao.module.iot.gateway.protocol.udp.IotDirectDeviceUd @@ -50,10 +61,12 @@ import static cn.iocoder.yudao.module.iot.gateway.protocol.udp.IotDirectDeviceUd
50 @Slf4j 61 @Slf4j
51 public class IotGatewayDeviceUdpProtocolIntegrationTest { 62 public class IotGatewayDeviceUdpProtocolIntegrationTest {
52 63
53 - private static final String SERVER_HOST = "127.0.0.1";  
54 - private static final int SERVER_PORT = 8093;  
55 private static final int TIMEOUT_MS = 5000; 64 private static final int TIMEOUT_MS = 5000;
56 65
  66 + // ===================== 编解码器选择(修改此处切换 JSON / Binary) =====================
  67 + private static final IotDeviceMessageCodec CODEC = new IotTcpJsonDeviceMessageCodec();
  68 +// private static final IotDeviceMessageCodec CODEC = new IotTcpBinaryDeviceMessageCodec();
  69 +
57 // ===================== 网关设备信息(根据实际情况修改,从 iot_device 表查询网关设备) ===================== 70 // ===================== 网关设备信息(根据实际情况修改,从 iot_device 表查询网关设备) =====================
58 private static final String GATEWAY_PRODUCT_KEY = "m6XcS1ZJ3TW8eC0v"; 71 private static final String GATEWAY_PRODUCT_KEY = "m6XcS1ZJ3TW8eC0v";
59 private static final String GATEWAY_DEVICE_NAME = "sub-ddd"; 72 private static final String GATEWAY_DEVICE_NAME = "sub-ddd";
@@ -76,28 +89,33 @@ public class IotGatewayDeviceUdpProtocolIntegrationTest { @@ -76,28 +89,33 @@ public class IotGatewayDeviceUdpProtocolIntegrationTest {
76 */ 89 */
77 @Test 90 @Test
78 public void testAuth() throws Exception { 91 public void testAuth() throws Exception {
79 - // 1.1 构建请求 92 + // 1.1 构建认证消息
80 IotDeviceAuthReqDTO authInfo = IotDeviceAuthUtils.getAuthInfo( 93 IotDeviceAuthReqDTO authInfo = IotDeviceAuthUtils.getAuthInfo(
81 GATEWAY_PRODUCT_KEY, GATEWAY_DEVICE_NAME, GATEWAY_DEVICE_SECRET); 94 GATEWAY_PRODUCT_KEY, GATEWAY_DEVICE_NAME, GATEWAY_DEVICE_SECRET);
82 IotDeviceAuthReqDTO authReqDTO = new IotDeviceAuthReqDTO() 95 IotDeviceAuthReqDTO authReqDTO = new IotDeviceAuthReqDTO()
83 .setClientId(authInfo.getClientId()) 96 .setClientId(authInfo.getClientId())
84 .setUsername(authInfo.getUsername()) 97 .setUsername(authInfo.getUsername())
85 .setPassword(authInfo.getPassword()); 98 .setPassword(authInfo.getPassword());
86 - String payload = JsonUtils.toJsonString(MapUtil.builder()  
87 - .put("id", IdUtil.fastSimpleUUID())  
88 - .put("method", "auth")  
89 - .put("params", authReqDTO)  
90 - .build());  
91 - // 1.2 输出请求  
92 - log.info("[testAuth][请求体: {}]", payload); 99 + IotDeviceMessage request = IotDeviceMessage.of(IdUtil.fastSimpleUUID(), "auth", authReqDTO, null, null, null);
  100 + // 1.2 编码
  101 + byte[] payload = CODEC.encode(request);
  102 + log.info("[testAuth][Codec: {}, 请求消息: {}, 数据包长度: {} 字节]", CODEC.type(), request, payload.length);
  103 + if (CODEC instanceof IotTcpBinaryDeviceMessageCodec) {
  104 + log.info("[testAuth][二进制数据包(HEX): {}]", HexUtil.encodeHexStr(payload));
  105 + }
93 106
94 // 2.1 发送请求 107 // 2.1 发送请求
95 try (DatagramSocket socket = new DatagramSocket()) { 108 try (DatagramSocket socket = new DatagramSocket()) {
96 socket.setSoTimeout(TIMEOUT_MS); 109 socket.setSoTimeout(TIMEOUT_MS);
97 - String response = sendAndReceive(socket, payload);  
98 - // 2.2 输出结果  
99 - log.info("[testAuth][响应体: {}]", response);  
100 - log.info("[testAuth][请将返回的 token 复制到 GATEWAY_TOKEN 常量中]"); 110 + byte[] responseBytes = sendAndReceive(socket, payload);
  111 + // 2.2 解码响应
  112 + if (responseBytes != null) {
  113 + IotDeviceMessage response = CODEC.decode(responseBytes);
  114 + log.info("[testAuth][响应消息: {}]", response);
  115 + log.info("[testAuth][请将返回的 token 复制到 GATEWAY_TOKEN 常量中]");
  116 + } else {
  117 + log.warn("[testAuth][未收到响应]");
  118 + }
101 } 119 }
102 } 120 }
103 121
@@ -120,21 +138,26 @@ public class IotGatewayDeviceUdpProtocolIntegrationTest { @@ -120,21 +138,26 @@ public class IotGatewayDeviceUdpProtocolIntegrationTest {
120 // 1.2 构建请求参数 138 // 1.2 构建请求参数
121 IotDeviceTopoAddReqDTO params = new IotDeviceTopoAddReqDTO(); 139 IotDeviceTopoAddReqDTO params = new IotDeviceTopoAddReqDTO();
122 params.setSubDevices(Collections.singletonList(subDeviceAuth)); 140 params.setSubDevices(Collections.singletonList(subDeviceAuth));
123 - String payload = JsonUtils.toJsonString(MapUtil.builder()  
124 - .put("id", IdUtil.fastSimpleUUID())  
125 - .put("method", IotDeviceMessageMethodEnum.TOPO_ADD.getMethod())  
126 - .put("version", "1.0")  
127 - .put("params", withToken(params))  
128 - .build());  
129 - // 1.3 输出请求  
130 - log.info("[testTopoAdd][请求体: {}]", payload); 141 + IotDeviceMessage request = IotDeviceMessage.of(
  142 + IdUtil.fastSimpleUUID(),
  143 + IotDeviceMessageMethodEnum.TOPO_ADD.getMethod(),
  144 + withToken(params),
  145 + null, null, null);
  146 + // 1.3 编码
  147 + byte[] payload = CODEC.encode(request);
  148 + log.info("[testTopoAdd][Codec: {}, 请求消息: {}]", CODEC.type(), request);
131 149
132 // 2.1 发送请求 150 // 2.1 发送请求
133 try (DatagramSocket socket = new DatagramSocket()) { 151 try (DatagramSocket socket = new DatagramSocket()) {
134 socket.setSoTimeout(TIMEOUT_MS); 152 socket.setSoTimeout(TIMEOUT_MS);
135 - String response = sendAndReceive(socket, payload);  
136 - // 2.2 输出结果  
137 - log.info("[testTopoAdd][响应体: {}]", response); 153 + byte[] responseBytes = sendAndReceive(socket, payload);
  154 + // 2.2 解码响应
  155 + if (responseBytes != null) {
  156 + IotDeviceMessage response = CODEC.decode(responseBytes);
  157 + log.info("[testTopoAdd][响应消息: {}]", response);
  158 + } else {
  159 + log.warn("[testTopoAdd][未收到响应]");
  160 + }
138 } 161 }
139 } 162 }
140 163
@@ -149,21 +172,26 @@ public class IotGatewayDeviceUdpProtocolIntegrationTest { @@ -149,21 +172,26 @@ public class IotGatewayDeviceUdpProtocolIntegrationTest {
149 IotDeviceTopoDeleteReqDTO params = new IotDeviceTopoDeleteReqDTO(); 172 IotDeviceTopoDeleteReqDTO params = new IotDeviceTopoDeleteReqDTO();
150 params.setSubDevices(Collections.singletonList( 173 params.setSubDevices(Collections.singletonList(
151 new IotDeviceIdentity(SUB_DEVICE_PRODUCT_KEY, SUB_DEVICE_NAME))); 174 new IotDeviceIdentity(SUB_DEVICE_PRODUCT_KEY, SUB_DEVICE_NAME)));
152 - String payload = JsonUtils.toJsonString(MapUtil.builder()  
153 - .put("id", IdUtil.fastSimpleUUID())  
154 - .put("method", IotDeviceMessageMethodEnum.TOPO_DELETE.getMethod())  
155 - .put("version", "1.0")  
156 - .put("params", withToken(params))  
157 - .build());  
158 - // 1.2 输出请求  
159 - log.info("[testTopoDelete][请求体: {}]", payload); 175 + IotDeviceMessage request = IotDeviceMessage.of(
  176 + IdUtil.fastSimpleUUID(),
  177 + IotDeviceMessageMethodEnum.TOPO_DELETE.getMethod(),
  178 + withToken(params),
  179 + null, null, null);
  180 + // 1.2 编码
  181 + byte[] payload = CODEC.encode(request);
  182 + log.info("[testTopoDelete][Codec: {}, 请求消息: {}]", CODEC.type(), request);
160 183
161 // 2.1 发送请求 184 // 2.1 发送请求
162 try (DatagramSocket socket = new DatagramSocket()) { 185 try (DatagramSocket socket = new DatagramSocket()) {
163 socket.setSoTimeout(TIMEOUT_MS); 186 socket.setSoTimeout(TIMEOUT_MS);
164 - String response = sendAndReceive(socket, payload);  
165 - // 2.2 输出结果  
166 - log.info("[testTopoDelete][响应体: {}]", response); 187 + byte[] responseBytes = sendAndReceive(socket, payload);
  188 + // 2.2 解码响应
  189 + if (responseBytes != null) {
  190 + IotDeviceMessage response = CODEC.decode(responseBytes);
  191 + log.info("[testTopoDelete][响应消息: {}]", response);
  192 + } else {
  193 + log.warn("[testTopoDelete][未收到响应]");
  194 + }
167 } 195 }
168 } 196 }
169 197
@@ -176,21 +204,26 @@ public class IotGatewayDeviceUdpProtocolIntegrationTest { @@ -176,21 +204,26 @@ public class IotGatewayDeviceUdpProtocolIntegrationTest {
176 public void testTopoGet() throws Exception { 204 public void testTopoGet() throws Exception {
177 // 1.1 构建请求参数(目前为空,预留扩展) 205 // 1.1 构建请求参数(目前为空,预留扩展)
178 IotDeviceTopoGetReqDTO params = new IotDeviceTopoGetReqDTO(); 206 IotDeviceTopoGetReqDTO params = new IotDeviceTopoGetReqDTO();
179 - String payload = JsonUtils.toJsonString(MapUtil.builder()  
180 - .put("id", IdUtil.fastSimpleUUID())  
181 - .put("method", IotDeviceMessageMethodEnum.TOPO_GET.getMethod())  
182 - .put("version", "1.0")  
183 - .put("params", withToken(params))  
184 - .build());  
185 - // 1.2 输出请求  
186 - log.info("[testTopoGet][请求体: {}]", payload); 207 + IotDeviceMessage request = IotDeviceMessage.of(
  208 + IdUtil.fastSimpleUUID(),
  209 + IotDeviceMessageMethodEnum.TOPO_GET.getMethod(),
  210 + withToken(params),
  211 + null, null, null);
  212 + // 1.2 编码
  213 + byte[] payload = CODEC.encode(request);
  214 + log.info("[testTopoGet][Codec: {}, 请求消息: {}]", CODEC.type(), request);
187 215
188 // 2.1 发送请求 216 // 2.1 发送请求
189 try (DatagramSocket socket = new DatagramSocket()) { 217 try (DatagramSocket socket = new DatagramSocket()) {
190 socket.setSoTimeout(TIMEOUT_MS); 218 socket.setSoTimeout(TIMEOUT_MS);
191 - String response = sendAndReceive(socket, payload);  
192 - // 2.2 输出结果  
193 - log.info("[testTopoGet][响应体: {}]", response); 219 + byte[] responseBytes = sendAndReceive(socket, payload);
  220 + // 2.2 解码响应
  221 + if (responseBytes != null) {
  222 + IotDeviceMessage response = CODEC.decode(responseBytes);
  223 + log.info("[testTopoGet][响应消息: {}]", response);
  224 + } else {
  225 + log.warn("[testTopoGet][未收到响应]");
  226 + }
194 } 227 }
195 } 228 }
196 229
@@ -209,21 +242,26 @@ public class IotGatewayDeviceUdpProtocolIntegrationTest { @@ -209,21 +242,26 @@ public class IotGatewayDeviceUdpProtocolIntegrationTest {
209 IotSubDeviceRegisterReqDTO subDevice = new IotSubDeviceRegisterReqDTO(); 242 IotSubDeviceRegisterReqDTO subDevice = new IotSubDeviceRegisterReqDTO();
210 subDevice.setProductKey(SUB_DEVICE_PRODUCT_KEY); 243 subDevice.setProductKey(SUB_DEVICE_PRODUCT_KEY);
211 subDevice.setDeviceName("mougezishebei"); 244 subDevice.setDeviceName("mougezishebei");
212 - String payload = JsonUtils.toJsonString(MapUtil.builder()  
213 - .put("id", IdUtil.fastSimpleUUID())  
214 - .put("method", IotDeviceMessageMethodEnum.SUB_DEVICE_REGISTER.getMethod())  
215 - .put("version", "1.0")  
216 - .put("params", withToken(Collections.singletonList(subDevice)))  
217 - .build());  
218 - // 1.2 输出请求  
219 - log.info("[testSubDeviceRegister][请求体: {}]", payload); 245 + IotDeviceMessage request = IotDeviceMessage.of(
  246 + IdUtil.fastSimpleUUID(),
  247 + IotDeviceMessageMethodEnum.SUB_DEVICE_REGISTER.getMethod(),
  248 + withToken(Collections.singletonList(subDevice)),
  249 + null, null, null);
  250 + // 1.2 编码
  251 + byte[] payload = CODEC.encode(request);
  252 + log.info("[testSubDeviceRegister][Codec: {}, 请求消息: {}]", CODEC.type(), request);
220 253
221 // 2.1 发送请求 254 // 2.1 发送请求
222 try (DatagramSocket socket = new DatagramSocket()) { 255 try (DatagramSocket socket = new DatagramSocket()) {
223 socket.setSoTimeout(TIMEOUT_MS); 256 socket.setSoTimeout(TIMEOUT_MS);
224 - String response = sendAndReceive(socket, payload);  
225 - // 2.2 输出结果  
226 - log.info("[testSubDeviceRegister][响应体: {}]", response); 257 + byte[] responseBytes = sendAndReceive(socket, payload);
  258 + // 2.2 解码响应
  259 + if (responseBytes != null) {
  260 + IotDeviceMessage response = CODEC.decode(responseBytes);
  261 + log.info("[testSubDeviceRegister][响应消息: {}]", response);
  262 + } else {
  263 + log.warn("[testSubDeviceRegister][未收到响应]");
  264 + }
227 } 265 }
228 } 266 }
229 267
@@ -268,21 +306,26 @@ public class IotGatewayDeviceUdpProtocolIntegrationTest { @@ -268,21 +306,26 @@ public class IotGatewayDeviceUdpProtocolIntegrationTest {
268 params.setProperties(gatewayProperties); 306 params.setProperties(gatewayProperties);
269 params.setEvents(gatewayEvents); 307 params.setEvents(gatewayEvents);
270 params.setSubDevices(List.of(subDeviceData)); 308 params.setSubDevices(List.of(subDeviceData));
271 - String payload = JsonUtils.toJsonString(MapUtil.builder()  
272 - .put("id", IdUtil.fastSimpleUUID())  
273 - .put("method", IotDeviceMessageMethodEnum.PROPERTY_PACK_POST.getMethod())  
274 - .put("version", "1.0")  
275 - .put("params", withToken(params))  
276 - .build());  
277 - // 1.7 输出请求  
278 - log.info("[testPropertyPackPost][请求体: {}]", payload); 309 + IotDeviceMessage request = IotDeviceMessage.of(
  310 + IdUtil.fastSimpleUUID(),
  311 + IotDeviceMessageMethodEnum.PROPERTY_PACK_POST.getMethod(),
  312 + withToken(params),
  313 + null, null, null);
  314 + // 1.7 编码
  315 + byte[] payload = CODEC.encode(request);
  316 + log.info("[testPropertyPackPost][Codec: {}, 请求消息: {}]", CODEC.type(), request);
279 317
280 // 2.1 发送请求 318 // 2.1 发送请求
281 try (DatagramSocket socket = new DatagramSocket()) { 319 try (DatagramSocket socket = new DatagramSocket()) {
282 socket.setSoTimeout(TIMEOUT_MS); 320 socket.setSoTimeout(TIMEOUT_MS);
283 - String response = sendAndReceive(socket, payload);  
284 - // 2.2 输出结果  
285 - log.info("[testPropertyPackPost][响应体: {}]", response); 321 + byte[] responseBytes = sendAndReceive(socket, payload);
  322 + // 2.2 解码响应
  323 + if (responseBytes != null) {
  324 + IotDeviceMessage response = CODEC.decode(responseBytes);
  325 + log.info("[testPropertyPackPost][响应消息: {}]", response);
  326 + } else {
  327 + log.warn("[testPropertyPackPost][未收到响应]");
  328 + }
286 } 329 }
287 } 330 }
288 331
yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/udp/IotGatewaySubDeviceUdpProtocolIntegrationTest.java
1 package cn.iocoder.yudao.module.iot.gateway.protocol.udp; 1 package cn.iocoder.yudao.module.iot.gateway.protocol.udp;
2 2
3 import cn.hutool.core.map.MapUtil; 3 import cn.hutool.core.map.MapUtil;
  4 +import cn.hutool.core.util.HexUtil;
4 import cn.hutool.core.util.IdUtil; 5 import cn.hutool.core.util.IdUtil;
5 -import cn.iocoder.yudao.framework.common.util.json.JsonUtils;  
6 import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO; 6 import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO;
7 import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum; 7 import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
  8 +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
8 import cn.iocoder.yudao.module.iot.core.topic.event.IotDeviceEventPostReqDTO; 9 import cn.iocoder.yudao.module.iot.core.topic.event.IotDeviceEventPostReqDTO;
9 import cn.iocoder.yudao.module.iot.core.topic.property.IotDevicePropertyPostReqDTO; 10 import cn.iocoder.yudao.module.iot.core.topic.property.IotDevicePropertyPostReqDTO;
10 import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils; 11 import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils;
  12 +import cn.iocoder.yudao.module.iot.gateway.codec.IotDeviceMessageCodec;
  13 +import cn.iocoder.yudao.module.iot.gateway.codec.tcp.IotTcpBinaryDeviceMessageCodec;
  14 +import cn.iocoder.yudao.module.iot.gateway.codec.tcp.IotTcpJsonDeviceMessageCodec;
11 import lombok.extern.slf4j.Slf4j; 15 import lombok.extern.slf4j.Slf4j;
12 import org.junit.jupiter.api.Test; 16 import org.junit.jupiter.api.Test;
13 17
@@ -25,10 +29,17 @@ import static cn.iocoder.yudao.module.iot.gateway.protocol.udp.IotDirectDeviceUd @@ -25,10 +29,17 @@ import static cn.iocoder.yudao.module.iot.gateway.protocol.udp.IotDirectDeviceUd
25 * <p><b>重要说明:子设备无法直接连接平台,所有请求均由网关设备(Gateway)代为转发。</b> 29 * <p><b>重要说明:子设备无法直接连接平台,所有请求均由网关设备(Gateway)代为转发。</b>
26 * <p>网关设备转发子设备请求时,Token 使用子设备自己的信息。 30 * <p>网关设备转发子设备请求时,Token 使用子设备自己的信息。
27 * 31 *
  32 + * <p>支持两种编解码格式:
  33 + * <ul>
  34 + * <li>{@link IotTcpJsonDeviceMessageCodec} - JSON 格式</li>
  35 + * <li>{@link IotTcpBinaryDeviceMessageCodec} - 二进制格式</li>
  36 + * </ul>
  37 + *
28 * <p>使用步骤: 38 * <p>使用步骤:
29 * <ol> 39 * <ol>
30 * <li>启动 yudao-module-iot-gateway 服务(UDP 端口 8093)</li> 40 * <li>启动 yudao-module-iot-gateway 服务(UDP 端口 8093)</li>
31 * <li>确保子设备已通过 {@link IotGatewayDeviceUdpProtocolIntegrationTest#testTopoAdd()} 绑定到网关</li> 41 * <li>确保子设备已通过 {@link IotGatewayDeviceUdpProtocolIntegrationTest#testTopoAdd()} 绑定到网关</li>
  42 + * <li>修改 {@link #CODEC} 选择测试的编解码格式</li>
32 * <li>运行 {@link #testAuth()} 获取子设备 token,将返回的 token 粘贴到 {@link #TOKEN} 常量</li> 43 * <li>运行 {@link #testAuth()} 获取子设备 token,将返回的 token 粘贴到 {@link #TOKEN} 常量</li>
33 * <li>运行以下测试方法: 44 * <li>运行以下测试方法:
34 * <ul> 45 * <ul>
@@ -45,10 +56,12 @@ import static cn.iocoder.yudao.module.iot.gateway.protocol.udp.IotDirectDeviceUd @@ -45,10 +56,12 @@ import static cn.iocoder.yudao.module.iot.gateway.protocol.udp.IotDirectDeviceUd
45 @Slf4j 56 @Slf4j
46 public class IotGatewaySubDeviceUdpProtocolIntegrationTest { 57 public class IotGatewaySubDeviceUdpProtocolIntegrationTest {
47 58
48 - private static final String SERVER_HOST = "127.0.0.1";  
49 - private static final int SERVER_PORT = 8093;  
50 private static final int TIMEOUT_MS = 5000; 59 private static final int TIMEOUT_MS = 5000;
51 60
  61 + // ===================== 编解码器选择(修改此处切换 JSON / Binary) =====================
  62 + private static final IotDeviceMessageCodec CODEC = new IotTcpJsonDeviceMessageCodec();
  63 +// private static final IotDeviceMessageCodec CODEC = new IotTcpBinaryDeviceMessageCodec();
  64 +
52 // ===================== 网关子设备信息(根据实际情况修改,从 iot_device 表查询子设备) ===================== 65 // ===================== 网关子设备信息(根据实际情况修改,从 iot_device 表查询子设备) =====================
53 private static final String PRODUCT_KEY = "jAufEMTF1W6wnPhn"; 66 private static final String PRODUCT_KEY = "jAufEMTF1W6wnPhn";
54 private static final String DEVICE_NAME = "chazuo-it"; 67 private static final String DEVICE_NAME = "chazuo-it";
@@ -66,27 +79,32 @@ public class IotGatewaySubDeviceUdpProtocolIntegrationTest { @@ -66,27 +79,32 @@ public class IotGatewaySubDeviceUdpProtocolIntegrationTest {
66 */ 79 */
67 @Test 80 @Test
68 public void testAuth() throws Exception { 81 public void testAuth() throws Exception {
69 - // 1.1 构建请求 82 + // 1.1 构建认证消息
70 IotDeviceAuthReqDTO authInfo = IotDeviceAuthUtils.getAuthInfo(PRODUCT_KEY, DEVICE_NAME, DEVICE_SECRET); 83 IotDeviceAuthReqDTO authInfo = IotDeviceAuthUtils.getAuthInfo(PRODUCT_KEY, DEVICE_NAME, DEVICE_SECRET);
71 IotDeviceAuthReqDTO authReqDTO = new IotDeviceAuthReqDTO() 84 IotDeviceAuthReqDTO authReqDTO = new IotDeviceAuthReqDTO()
72 .setClientId(authInfo.getClientId()) 85 .setClientId(authInfo.getClientId())
73 .setUsername(authInfo.getUsername()) 86 .setUsername(authInfo.getUsername())
74 .setPassword(authInfo.getPassword()); 87 .setPassword(authInfo.getPassword());
75 - String payload = JsonUtils.toJsonString(MapUtil.builder()  
76 - .put("id", IdUtil.fastSimpleUUID())  
77 - .put("method", "auth")  
78 - .put("params", authReqDTO)  
79 - .build());  
80 - // 1.2 输出请求  
81 - log.info("[testAuth][请求体: {}]", payload); 88 + IotDeviceMessage request = IotDeviceMessage.of(IdUtil.fastSimpleUUID(), "auth", authReqDTO, null, null, null);
  89 + // 1.2 编码
  90 + byte[] payload = CODEC.encode(request);
  91 + log.info("[testAuth][Codec: {}, 请求消息: {}, 数据包长度: {} 字节]", CODEC.type(), request, payload.length);
  92 + if (CODEC instanceof IotTcpBinaryDeviceMessageCodec) {
  93 + log.info("[testAuth][二进制数据包(HEX): {}]", HexUtil.encodeHexStr(payload));
  94 + }
82 95
83 // 2.1 发送请求 96 // 2.1 发送请求
84 try (DatagramSocket socket = new DatagramSocket()) { 97 try (DatagramSocket socket = new DatagramSocket()) {
85 socket.setSoTimeout(TIMEOUT_MS); 98 socket.setSoTimeout(TIMEOUT_MS);
86 - String response = sendAndReceive(socket, payload);  
87 - // 2.2 输出结果  
88 - log.info("[testAuth][响应体: {}]", response);  
89 - log.info("[testAuth][请将返回的 token 复制到 TOKEN 常量中]"); 99 + byte[] responseBytes = sendAndReceive(socket, payload);
  100 + // 2.2 解码响应
  101 + if (responseBytes != null) {
  102 + IotDeviceMessage response = CODEC.decode(responseBytes);
  103 + log.info("[testAuth][响应消息: {}]", response);
  104 + log.info("[testAuth][请将返回的 token 复制到 TOKEN 常量中]");
  105 + } else {
  106 + log.warn("[testAuth][未收到响应]");
  107 + }
90 } 108 }
91 } 109 }
92 110
@@ -97,27 +115,32 @@ public class IotGatewaySubDeviceUdpProtocolIntegrationTest { @@ -97,27 +115,32 @@ public class IotGatewaySubDeviceUdpProtocolIntegrationTest {
97 */ 115 */
98 @Test 116 @Test
99 public void testPropertyPost() throws Exception { 117 public void testPropertyPost() throws Exception {
100 - // 1.1 构建请求(UDP 协议:token 放在 params 中)  
101 - String payload = JsonUtils.toJsonString(MapUtil.builder()  
102 - .put("id", IdUtil.fastSimpleUUID())  
103 - .put("method", IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod())  
104 - .put("version", "1.0")  
105 - .put("params", withToken(IotDevicePropertyPostReqDTO.of(MapUtil.<String, Object>builder() 118 + // 1.1 构建属性上报消息(UDP 协议:token 放在 params 中)
  119 + IotDeviceMessage request = IotDeviceMessage.of(
  120 + IdUtil.fastSimpleUUID(),
  121 + IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod(),
  122 + withToken(IotDevicePropertyPostReqDTO.of(MapUtil.<String, Object>builder()
106 .put("power", 100) 123 .put("power", 100)
107 .put("status", "online") 124 .put("status", "online")
108 .put("temperature", 36.5) 125 .put("temperature", 36.5)
109 - .build())))  
110 - .build());  
111 - // 1.2 输出请求 126 + .build())),
  127 + null, null, null);
  128 + // 1.2 编码
  129 + byte[] payload = CODEC.encode(request);
112 log.info("[testPropertyPost][子设备属性上报 - 请求实际由 Gateway 代为转发]"); 130 log.info("[testPropertyPost][子设备属性上报 - 请求实际由 Gateway 代为转发]");
113 - log.info("[testPropertyPost][请求体: {}]", payload); 131 + log.info("[testPropertyPost][Codec: {}, 请求消息: {}]", CODEC.type(), request);
114 132
115 // 2.1 发送请求 133 // 2.1 发送请求
116 try (DatagramSocket socket = new DatagramSocket()) { 134 try (DatagramSocket socket = new DatagramSocket()) {
117 socket.setSoTimeout(TIMEOUT_MS); 135 socket.setSoTimeout(TIMEOUT_MS);
118 - String response = sendAndReceive(socket, payload);  
119 - // 2.2 输出结果  
120 - log.info("[testPropertyPost][响应体: {}]", response); 136 + byte[] responseBytes = sendAndReceive(socket, payload);
  137 + // 2.2 解码响应
  138 + if (responseBytes != null) {
  139 + IotDeviceMessage response = CODEC.decode(responseBytes);
  140 + log.info("[testPropertyPost][响应消息: {}]", response);
  141 + } else {
  142 + log.warn("[testPropertyPost][未收到响应]");
  143 + }
121 } 144 }
122 } 145 }
123 146
@@ -128,12 +151,11 @@ public class IotGatewaySubDeviceUdpProtocolIntegrationTest { @@ -128,12 +151,11 @@ public class IotGatewaySubDeviceUdpProtocolIntegrationTest {
128 */ 151 */
129 @Test 152 @Test
130 public void testEventPost() throws Exception { 153 public void testEventPost() throws Exception {
131 - // 1.1 构建请求(UDP 协议:token 放在 params 中)  
132 - String payload = JsonUtils.toJsonString(MapUtil.builder()  
133 - .put("id", IdUtil.fastSimpleUUID())  
134 - .put("method", IotDeviceMessageMethodEnum.EVENT_POST.getMethod())  
135 - .put("version", "1.0")  
136 - .put("params", withToken(IotDeviceEventPostReqDTO.of( 154 + // 1.1 构建事件上报消息(UDP 协议:token 放在 params 中)
  155 + IotDeviceMessage request = IotDeviceMessage.of(
  156 + IdUtil.fastSimpleUUID(),
  157 + IotDeviceMessageMethodEnum.EVENT_POST.getMethod(),
  158 + withToken(IotDeviceEventPostReqDTO.of(
137 "alarm", 159 "alarm",
138 MapUtil.<String, Object>builder() 160 MapUtil.<String, Object>builder()
139 .put("level", "warning") 161 .put("level", "warning")
@@ -141,18 +163,24 @@ public class IotGatewaySubDeviceUdpProtocolIntegrationTest { @@ -141,18 +163,24 @@ public class IotGatewaySubDeviceUdpProtocolIntegrationTest {
141 .put("threshold", 40) 163 .put("threshold", 40)
142 .put("current", 42) 164 .put("current", 42)
143 .build(), 165 .build(),
144 - System.currentTimeMillis())))  
145 - .build());  
146 - // 1.2 输出请求 166 + System.currentTimeMillis())),
  167 + null, null, null);
  168 + // 1.2 编码
  169 + byte[] payload = CODEC.encode(request);
147 log.info("[testEventPost][子设备事件上报 - 请求实际由 Gateway 代为转发]"); 170 log.info("[testEventPost][子设备事件上报 - 请求实际由 Gateway 代为转发]");
148 - log.info("[testEventPost][请求体: {}]", payload); 171 + log.info("[testEventPost][Codec: {}, 请求消息: {}]", CODEC.type(), request);
149 172
150 // 2.1 发送请求 173 // 2.1 发送请求
151 try (DatagramSocket socket = new DatagramSocket()) { 174 try (DatagramSocket socket = new DatagramSocket()) {
152 socket.setSoTimeout(TIMEOUT_MS); 175 socket.setSoTimeout(TIMEOUT_MS);
153 - String response = sendAndReceive(socket, payload);  
154 - // 2.2 输出结果  
155 - log.info("[testEventPost][响应体: {}]", response); 176 + byte[] responseBytes = sendAndReceive(socket, payload);
  177 + // 2.2 解码响应
  178 + if (responseBytes != null) {
  179 + IotDeviceMessage response = CODEC.decode(responseBytes);
  180 + log.info("[testEventPost][响应消息: {}]", response);
  181 + } else {
  182 + log.warn("[testEventPost][未收到响应]");
  183 + }
156 } 184 }
157 } 185 }
158 186