|
|
@@ -2,12 +2,15 @@ package cn.iocoder.yudao.module.iot.gateway.protocol.mqtt;
|
|
|
|
|
|
import cn.hutool.core.map.MapUtil;
|
|
|
import cn.hutool.core.util.IdUtil;
|
|
|
-import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
|
|
import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO;
|
|
|
import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
|
|
|
+import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
|
|
|
+import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterReqDTO;
|
|
|
import cn.iocoder.yudao.module.iot.core.topic.event.IotDeviceEventPostReqDTO;
|
|
|
import cn.iocoder.yudao.module.iot.core.topic.property.IotDevicePropertyPostReqDTO;
|
|
|
import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils;
|
|
|
+import cn.iocoder.yudao.module.iot.gateway.codec.IotDeviceMessageCodec;
|
|
|
+import cn.iocoder.yudao.module.iot.gateway.codec.alink.IotAlinkDeviceMessageCodec;
|
|
|
import io.netty.handler.codec.mqtt.MqttQoS;
|
|
|
import io.vertx.core.Vertx;
|
|
|
import io.vertx.core.buffer.Buffer;
|
|
|
@@ -18,6 +21,7 @@ import org.junit.jupiter.api.AfterAll;
|
|
|
import org.junit.jupiter.api.BeforeAll;
|
|
|
import org.junit.jupiter.api.Test;
|
|
|
|
|
|
+import java.util.concurrent.CompletableFuture;
|
|
|
import java.util.concurrent.CountDownLatch;
|
|
|
import java.util.concurrent.TimeUnit;
|
|
|
|
|
|
@@ -31,9 +35,10 @@ import java.util.concurrent.TimeUnit;
|
|
|
* <li>启动 yudao-module-iot-gateway 服务(MQTT 端口 1883)</li>
|
|
|
* <li>运行以下测试方法:
|
|
|
* <ul>
|
|
|
- * <li>{@link #testConnect()} - 设备连接认证</li>
|
|
|
+ * <li>{@link #testAuth()} - 设备连接认证</li>
|
|
|
* <li>{@link #testPropertyPost()} - 设备属性上报</li>
|
|
|
* <li>{@link #testEventPost()} - 设备事件上报</li>
|
|
|
+ * <li>{@link #testSubscribe()} - 订阅下行消息</li>
|
|
|
* </ul>
|
|
|
* </li>
|
|
|
* </ol>
|
|
|
@@ -50,6 +55,9 @@ public class IotDirectDeviceMqttProtocolIntegrationTest {
|
|
|
private static final int SERVER_PORT = 1883;
|
|
|
private static final int TIMEOUT_SECONDS = 10;
|
|
|
|
|
|
+ // ===================== 编解码器(MQTT 使用 Alink 协议) =====================
|
|
|
+ private static final IotDeviceMessageCodec CODEC = new IotAlinkDeviceMessageCodec();
|
|
|
+
|
|
|
// ===================== 直连设备信息(根据实际情况修改,从 iot_device 表查询) =====================
|
|
|
private static final String PRODUCT_KEY = "4aymZgOTOOCrDKRT";
|
|
|
private static final String DEVICE_NAME = "small";
|
|
|
@@ -73,10 +81,10 @@ public class IotDirectDeviceMqttProtocolIntegrationTest {
|
|
|
// ===================== 连接认证测试 =====================
|
|
|
|
|
|
/**
|
|
|
- * 连接认证测试:设备通过 MQTT 协议连接平台
|
|
|
+ * 认证测试:获取设备 Token
|
|
|
*/
|
|
|
@Test
|
|
|
- public void testConnect() throws Exception {
|
|
|
+ public void testAuth() throws Exception {
|
|
|
CountDownLatch latch = new CountDownLatch(1);
|
|
|
|
|
|
// 1. 构建认证信息
|
|
|
@@ -84,16 +92,8 @@ public class IotDirectDeviceMqttProtocolIntegrationTest {
|
|
|
log.info("[testConnect][认证信息: clientId={}, username={}, password={}]",
|
|
|
authInfo.getClientId(), authInfo.getUsername(), authInfo.getPassword());
|
|
|
|
|
|
- // 2. 创建 MQTT 客户端配置
|
|
|
- MqttClientOptions options = new MqttClientOptions()
|
|
|
- .setClientId(authInfo.getClientId())
|
|
|
- .setUsername(authInfo.getUsername())
|
|
|
- .setPassword(authInfo.getPassword())
|
|
|
- .setCleanSession(true)
|
|
|
- .setKeepAliveInterval(60);
|
|
|
-
|
|
|
- // 3. 创建 MQTT 客户端并连接
|
|
|
- MqttClient client = MqttClient.create(vertx, options);
|
|
|
+ // 2. 创建客户端并连接
|
|
|
+ MqttClient client = connect(authInfo);
|
|
|
client.connect(SERVER_PORT, SERVER_HOST)
|
|
|
.onComplete(ar -> {
|
|
|
if (ar.succeeded()) {
|
|
|
@@ -114,7 +114,7 @@ public class IotDirectDeviceMqttProtocolIntegrationTest {
|
|
|
}
|
|
|
});
|
|
|
|
|
|
- // 4. 等待测试完成
|
|
|
+ // 3. 等待测试完成
|
|
|
boolean completed = latch.await(TIMEOUT_SECONDS, TimeUnit.SECONDS);
|
|
|
if (!completed) {
|
|
|
log.warn("[testConnect][测试超时]");
|
|
|
@@ -128,86 +128,31 @@ public class IotDirectDeviceMqttProtocolIntegrationTest {
|
|
|
*/
|
|
|
@Test
|
|
|
public void testPropertyPost() throws Exception {
|
|
|
- CountDownLatch latch = new CountDownLatch(1);
|
|
|
-
|
|
|
- // 1. 构建认证信息
|
|
|
- IotDeviceAuthReqDTO authInfo = IotDeviceAuthUtils.getAuthInfo(PRODUCT_KEY, DEVICE_NAME, DEVICE_SECRET);
|
|
|
-
|
|
|
- // 2. 创建 MQTT 客户端配置
|
|
|
- MqttClientOptions options = new MqttClientOptions()
|
|
|
- .setClientId(authInfo.getClientId())
|
|
|
- .setUsername(authInfo.getUsername())
|
|
|
- .setPassword(authInfo.getPassword())
|
|
|
- .setCleanSession(true)
|
|
|
- .setKeepAliveInterval(60);
|
|
|
-
|
|
|
- // 3. 创建 MQTT 客户端并连接
|
|
|
- MqttClient client = MqttClient.create(vertx, options);
|
|
|
- client.connect(SERVER_PORT, SERVER_HOST)
|
|
|
- .onComplete(connectAr -> {
|
|
|
- if (connectAr.succeeded()) {
|
|
|
- log.info("[testPropertyPost][连接成功]");
|
|
|
-
|
|
|
- // 4.1 设置消息处理器,接收 _reply 响应
|
|
|
- client.publishHandler(message -> {
|
|
|
- log.info("[testPropertyPost][收到响应: topic={}, payload={}]",
|
|
|
- message.topicName(), message.payload().toString());
|
|
|
- });
|
|
|
-
|
|
|
- // 4.2 订阅 _reply 主题
|
|
|
- String replyTopic = String.format("/sys/%s/%s/thing/property/post_reply", PRODUCT_KEY, DEVICE_NAME);
|
|
|
- client.subscribe(replyTopic, MqttQoS.AT_LEAST_ONCE.value())
|
|
|
- .onComplete(subscribeAr -> {
|
|
|
- if (subscribeAr.succeeded()) {
|
|
|
- log.info("[testPropertyPost][订阅响应主题成功: {}]", replyTopic);
|
|
|
-
|
|
|
- // 5. 构建属性上报消息(Alink 协议格式)
|
|
|
- String topic = String.format("/sys/%s/%s/thing/property/post", PRODUCT_KEY, DEVICE_NAME);
|
|
|
- String payload = JsonUtils.toJsonString(MapUtil.builder()
|
|
|
- .put("id", IdUtil.fastSimpleUUID())
|
|
|
- .put("version", "1.0")
|
|
|
- .put("method", IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod())
|
|
|
- .put("params", IotDevicePropertyPostReqDTO.of(MapUtil.<String, Object>builder()
|
|
|
- .put("width", 1)
|
|
|
- .put("height", "2")
|
|
|
- .build()))
|
|
|
- .build());
|
|
|
- log.info("[testPropertyPost][发送消息: topic={}, payload={}]", topic, payload);
|
|
|
-
|
|
|
- // 6. 发布消息
|
|
|
- client.publish(topic, Buffer.buffer(payload), MqttQoS.AT_LEAST_ONCE, false, false)
|
|
|
- .onComplete(publishAr -> {
|
|
|
- if (publishAr.succeeded()) {
|
|
|
- log.info("[testPropertyPost][消息发布成功,messageId={}]", publishAr.result());
|
|
|
- } else {
|
|
|
- log.error("[testPropertyPost][消息发布失败]", publishAr.cause());
|
|
|
- }
|
|
|
-
|
|
|
- // 等待一会儿接收响应
|
|
|
- vertx.setTimer(2000, id -> {
|
|
|
- client.disconnect()
|
|
|
- .onComplete(disconnectAr -> {
|
|
|
- log.info("[testPropertyPost][断开连接]");
|
|
|
- latch.countDown();
|
|
|
- });
|
|
|
- });
|
|
|
- });
|
|
|
- } else {
|
|
|
- log.error("[testPropertyPost][订阅响应主题失败]", subscribeAr.cause());
|
|
|
- latch.countDown();
|
|
|
- }
|
|
|
- });
|
|
|
- } else {
|
|
|
- log.error("[testPropertyPost][连接失败]", connectAr.cause());
|
|
|
- latch.countDown();
|
|
|
- }
|
|
|
- });
|
|
|
-
|
|
|
- // 7. 等待测试完成
|
|
|
- boolean completed = latch.await(TIMEOUT_SECONDS, TimeUnit.SECONDS);
|
|
|
- if (!completed) {
|
|
|
- log.warn("[testPropertyPost][测试超时]");
|
|
|
- }
|
|
|
+ // 1. 连接并认证
|
|
|
+ MqttClient client = connectAndAuth();
|
|
|
+ log.info("[testPropertyPost][连接认证成功]");
|
|
|
+
|
|
|
+ // 2. 订阅 _reply 主题
|
|
|
+ String replyTopic = String.format("/sys/%s/%s/thing/property/post_reply", PRODUCT_KEY, DEVICE_NAME);
|
|
|
+ subscribeReply(client, replyTopic);
|
|
|
+
|
|
|
+ // 3. 构建属性上报消息
|
|
|
+ IotDeviceMessage request = IotDeviceMessage.of(
|
|
|
+ IdUtil.fastSimpleUUID(),
|
|
|
+ IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod(),
|
|
|
+ IotDevicePropertyPostReqDTO.of(MapUtil.<String, Object>builder()
|
|
|
+ .put("width", 1)
|
|
|
+ .put("height", "2")
|
|
|
+ .build()),
|
|
|
+ null, null, null);
|
|
|
+
|
|
|
+ // 4. 发布消息并等待响应
|
|
|
+ String topic = String.format("/sys/%s/%s/thing/property/post", PRODUCT_KEY, DEVICE_NAME);
|
|
|
+ IotDeviceMessage response = publishAndWaitReply(client, topic, request);
|
|
|
+ log.info("[testPropertyPost][响应消息: {}]", response);
|
|
|
+
|
|
|
+ // 5. 断开连接
|
|
|
+ disconnect(client);
|
|
|
}
|
|
|
|
|
|
// ===================== 直连设备事件上报测试 =====================
|
|
|
@@ -217,154 +162,244 @@ public class IotDirectDeviceMqttProtocolIntegrationTest {
|
|
|
*/
|
|
|
@Test
|
|
|
public void testEventPost() throws Exception {
|
|
|
- CountDownLatch latch = new CountDownLatch(1);
|
|
|
+ // 1. 连接并认证
|
|
|
+ MqttClient client = connectAndAuth();
|
|
|
+ log.info("[testEventPost][连接认证成功]");
|
|
|
+
|
|
|
+ // 2. 订阅 _reply 主题
|
|
|
+ String replyTopic = String.format("/sys/%s/%s/thing/event/post_reply", PRODUCT_KEY, DEVICE_NAME);
|
|
|
+ subscribeReply(client, replyTopic);
|
|
|
+
|
|
|
+ // 3. 构建事件上报消息
|
|
|
+ IotDeviceMessage request = IotDeviceMessage.of(
|
|
|
+ IdUtil.fastSimpleUUID(),
|
|
|
+ IotDeviceMessageMethodEnum.EVENT_POST.getMethod(),
|
|
|
+ IotDeviceEventPostReqDTO.of(
|
|
|
+ "eat",
|
|
|
+ MapUtil.<String, Object>builder().put("rice", 3).build(),
|
|
|
+ System.currentTimeMillis()),
|
|
|
+ null, null, null);
|
|
|
+
|
|
|
+ // 4. 发布消息并等待响应
|
|
|
+ String topic = String.format("/sys/%s/%s/thing/event/post", PRODUCT_KEY, DEVICE_NAME);
|
|
|
+ IotDeviceMessage response = publishAndWaitReply(client, topic, request);
|
|
|
+ log.info("[testEventPost][响应消息: {}]", response);
|
|
|
+
|
|
|
+ // 5. 断开连接
|
|
|
+ disconnect(client);
|
|
|
+ }
|
|
|
|
|
|
- // 1. 构建认证信息
|
|
|
- IotDeviceAuthReqDTO authInfo = IotDeviceAuthUtils.getAuthInfo(PRODUCT_KEY, DEVICE_NAME, DEVICE_SECRET);
|
|
|
+ // ===================== 设备动态注册测试(一型一密) =====================
|
|
|
|
|
|
- // 2. 创建 MQTT 客户端配置
|
|
|
- MqttClientOptions options = new MqttClientOptions()
|
|
|
- .setClientId(authInfo.getClientId())
|
|
|
- .setUsername(authInfo.getUsername())
|
|
|
- .setPassword(authInfo.getPassword())
|
|
|
- .setCleanSession(true)
|
|
|
- .setKeepAliveInterval(60);
|
|
|
+ /**
|
|
|
+ * 直连设备动态注册测试(一型一密)
|
|
|
+ * <p>
|
|
|
+ * 使用产品密钥(productSecret)验证身份,成功后返回设备密钥(deviceSecret)
|
|
|
+ * <p>
|
|
|
+ * 注意:此接口不需要认证
|
|
|
+ */
|
|
|
+ @Test
|
|
|
+ public void testDeviceRegister() throws Exception {
|
|
|
+ // 1. 连接并认证(使用已有设备连接)
|
|
|
+ MqttClient client = connectAndAuth();
|
|
|
+ log.info("[testDeviceRegister][连接认证成功]");
|
|
|
+
|
|
|
+ // 2.1 构建注册消息
|
|
|
+ IotDeviceRegisterReqDTO registerReqDTO = new IotDeviceRegisterReqDTO();
|
|
|
+ registerReqDTO.setProductKey(PRODUCT_KEY);
|
|
|
+ registerReqDTO.setDeviceName("test-mqtt-" + System.currentTimeMillis());
|
|
|
+ registerReqDTO.setProductSecret("test-product-secret");
|
|
|
+ IotDeviceMessage request = IotDeviceMessage.of(IdUtil.fastSimpleUUID(),
|
|
|
+ IotDeviceMessageMethodEnum.DEVICE_REGISTER.getMethod(), registerReqDTO, null, null, null);
|
|
|
+ // 2.2 订阅 _reply 主题
|
|
|
+ String replyTopic = String.format("/sys/%s/%s/thing/auth/register_reply",
|
|
|
+ registerReqDTO.getProductKey(), registerReqDTO.getDeviceName());
|
|
|
+ subscribeReply(client, replyTopic);
|
|
|
+
|
|
|
+ // 3. 发布消息并等待响应
|
|
|
+ String topic = String.format("/sys/%s/%s/thing/auth/register",
|
|
|
+ registerReqDTO.getProductKey(), registerReqDTO.getDeviceName());
|
|
|
+ IotDeviceMessage response = publishAndWaitReply(client, topic, request);
|
|
|
+ log.info("[testDeviceRegister][响应消息: {}]", response);
|
|
|
+ log.info("[testDeviceRegister][成功后可使用返回的 deviceSecret 进行一机一密认证]");
|
|
|
+
|
|
|
+ // 4. 断开连接
|
|
|
+ disconnect(client);
|
|
|
+ }
|
|
|
|
|
|
- // 3. 创建 MQTT 客户端并连接
|
|
|
- MqttClient client = MqttClient.create(vertx, options);
|
|
|
- // TODO @AI:可以像 tcp 里面一样,有个复用么?auth 流程;
|
|
|
- client.connect(SERVER_PORT, SERVER_HOST)
|
|
|
- .onComplete(connectAr -> {
|
|
|
- if (connectAr.succeeded()) {
|
|
|
- log.info("[testEventPost][连接成功]");
|
|
|
-
|
|
|
- // 4.1 设置消息处理器,接收 _reply 响应
|
|
|
- client.publishHandler(message -> {
|
|
|
- log.info("[testEventPost][收到响应: topic={}, payload={}]",
|
|
|
- message.topicName(), message.payload().toString());
|
|
|
- });
|
|
|
+ // ===================== 订阅下行消息测试 =====================
|
|
|
|
|
|
- // 4.2 订阅 _reply 主题
|
|
|
- String replyTopic = String.format("/sys/%s/%s/thing/event/post_reply", PRODUCT_KEY, DEVICE_NAME);
|
|
|
- client.subscribe(replyTopic, MqttQoS.AT_LEAST_ONCE.value())
|
|
|
- .onComplete(subscribeAr -> {
|
|
|
- if (subscribeAr.succeeded()) {
|
|
|
- log.info("[testEventPost][订阅响应主题成功: {}]", replyTopic);
|
|
|
-
|
|
|
- // 5. 构建事件上报消息(Alink 协议格式)
|
|
|
- String topic = String.format("/sys/%s/%s/thing/event/post", PRODUCT_KEY, DEVICE_NAME);
|
|
|
- String payload = JsonUtils.toJsonString(MapUtil.builder()
|
|
|
- .put("id", IdUtil.fastSimpleUUID())
|
|
|
- .put("version", "1.0")
|
|
|
- .put("method", IotDeviceMessageMethodEnum.EVENT_POST.getMethod())
|
|
|
- .put("params", IotDeviceEventPostReqDTO.of(
|
|
|
- "eat",
|
|
|
- MapUtil.<String, Object>builder().put("rice", 3).build(),
|
|
|
- System.currentTimeMillis()))
|
|
|
- .build());
|
|
|
- log.info("[testEventPost][发送消息: topic={}, payload={}]", topic, payload);
|
|
|
-
|
|
|
- // 6. 发布消息
|
|
|
- client.publish(topic, Buffer.buffer(payload), MqttQoS.AT_LEAST_ONCE, false, false)
|
|
|
- .onComplete(publishAr -> {
|
|
|
- if (publishAr.succeeded()) {
|
|
|
- log.info("[testEventPost][消息发布成功,messageId={}]", publishAr.result());
|
|
|
- } else {
|
|
|
- log.error("[testEventPost][消息发布失败]", publishAr.cause());
|
|
|
- }
|
|
|
-
|
|
|
- // 等待一会儿接收响应
|
|
|
- vertx.setTimer(2000, id -> {
|
|
|
- client.disconnect()
|
|
|
- .onComplete(disconnectAr -> {
|
|
|
- log.info("[testEventPost][断开连接]");
|
|
|
- latch.countDown();
|
|
|
- });
|
|
|
- });
|
|
|
- });
|
|
|
- } else {
|
|
|
- log.error("[testEventPost][订阅响应主题失败]", subscribeAr.cause());
|
|
|
+ /**
|
|
|
+ * 订阅下行消息测试:订阅服务端下发的消息
|
|
|
+ */
|
|
|
+ @Test
|
|
|
+ public void testSubscribe() throws Exception {
|
|
|
+ CountDownLatch latch = new CountDownLatch(1);
|
|
|
+
|
|
|
+ // 1. 连接并认证
|
|
|
+ MqttClient client = connectAndAuth();
|
|
|
+ log.info("[testSubscribe][连接认证成功]");
|
|
|
+
|
|
|
+ // 2. 设置消息处理器
|
|
|
+ client.publishHandler(message -> {
|
|
|
+ log.info("[testSubscribe][收到消息: topic={}, payload={}]",
|
|
|
+ message.topicName(), message.payload().toString());
|
|
|
+ });
|
|
|
+
|
|
|
+ // 3. 订阅下行主题
|
|
|
+ String topic = String.format("/sys/%s/%s/thing/service/#", PRODUCT_KEY, DEVICE_NAME);
|
|
|
+ log.info("[testSubscribe][订阅主题: {}]", topic);
|
|
|
+
|
|
|
+ client.subscribe(topic, MqttQoS.AT_LEAST_ONCE.value())
|
|
|
+ .onComplete(subscribeAr -> {
|
|
|
+ if (subscribeAr.succeeded()) {
|
|
|
+ log.info("[testSubscribe][订阅成功,等待下行消息... (30秒后自动断开)]");
|
|
|
+ // 保持连接 30 秒等待消息
|
|
|
+ vertx.setTimer(30000, id -> {
|
|
|
+ client.disconnect()
|
|
|
+ .onComplete(disconnectAr -> {
|
|
|
+ log.info("[testSubscribe][断开连接]");
|
|
|
latch.countDown();
|
|
|
- }
|
|
|
- });
|
|
|
+ });
|
|
|
+ });
|
|
|
} else {
|
|
|
- log.error("[testEventPost][连接失败]", connectAr.cause());
|
|
|
+ log.error("[testSubscribe][订阅失败]", subscribeAr.cause());
|
|
|
latch.countDown();
|
|
|
}
|
|
|
});
|
|
|
|
|
|
- // 7. 等待测试完成
|
|
|
- boolean completed = latch.await(TIMEOUT_SECONDS, TimeUnit.SECONDS);
|
|
|
+ // 4. 等待测试完成
|
|
|
+ boolean completed = latch.await(60, TimeUnit.SECONDS);
|
|
|
if (!completed) {
|
|
|
- log.warn("[testEventPost][测试超时]");
|
|
|
+ log.warn("[testSubscribe][测试超时]");
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- // ===================== 订阅下行消息测试 =====================
|
|
|
+ // ===================== 辅助方法 =====================
|
|
|
|
|
|
/**
|
|
|
- * 订阅下行消息测试:订阅服务端下发的消息
|
|
|
+ * 创建 MQTT 客户端
|
|
|
+ *
|
|
|
+ * @param authInfo 认证信息
|
|
|
+ * @return MQTT 客户端
|
|
|
*/
|
|
|
- @Test
|
|
|
- public void testSubscribe() throws Exception {
|
|
|
- CountDownLatch latch = new CountDownLatch(1);
|
|
|
-
|
|
|
- // 1. 构建认证信息
|
|
|
- IotDeviceAuthReqDTO authInfo = IotDeviceAuthUtils.getAuthInfo(PRODUCT_KEY, DEVICE_NAME, DEVICE_SECRET);
|
|
|
-
|
|
|
- // 2. 创建 MQTT 客户端配置
|
|
|
+ private MqttClient connect(IotDeviceAuthReqDTO authInfo) {
|
|
|
MqttClientOptions options = new MqttClientOptions()
|
|
|
.setClientId(authInfo.getClientId())
|
|
|
.setUsername(authInfo.getUsername())
|
|
|
.setPassword(authInfo.getPassword())
|
|
|
.setCleanSession(true)
|
|
|
.setKeepAliveInterval(60);
|
|
|
+ return MqttClient.create(vertx, options);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 连接并认证设备
|
|
|
+ *
|
|
|
+ * @return 已认证的 MQTT 客户端
|
|
|
+ */
|
|
|
+ private MqttClient connectAndAuth() throws Exception {
|
|
|
+ // 1. 创建客户端并连接
|
|
|
+ IotDeviceAuthReqDTO authInfo = IotDeviceAuthUtils.getAuthInfo(PRODUCT_KEY, DEVICE_NAME, DEVICE_SECRET);
|
|
|
+ MqttClient client = connect(authInfo);
|
|
|
|
|
|
- // 3. 创建 MQTT 客户端并连接
|
|
|
- MqttClient client = MqttClient.create(vertx, options);
|
|
|
+ // 2.1 连接
|
|
|
+ CompletableFuture<MqttClient> future = new CompletableFuture<>();
|
|
|
client.connect(SERVER_PORT, SERVER_HOST)
|
|
|
- .onComplete(connectAr -> {
|
|
|
- if (connectAr.succeeded()) {
|
|
|
- log.info("[testSubscribe][连接成功]");
|
|
|
-
|
|
|
- // 4. 设置消息处理器
|
|
|
- client.publishHandler(message -> {
|
|
|
- log.info("[testSubscribe][收到消息: topic={}, payload={}]",
|
|
|
- message.topicName(), message.payload().toString());
|
|
|
- });
|
|
|
+ .onComplete(ar -> {
|
|
|
+ if (ar.succeeded()) {
|
|
|
+ future.complete(client);
|
|
|
+ } else {
|
|
|
+ future.completeExceptionally(ar.cause());
|
|
|
+ }
|
|
|
+ });
|
|
|
+ // 2.2 等待连接结果
|
|
|
+ return future.get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
|
|
|
+ }
|
|
|
|
|
|
- // 5. 订阅下行主题
|
|
|
- String topic = String.format("/sys/%s/%s/thing/service/#", PRODUCT_KEY, DEVICE_NAME);
|
|
|
- log.info("[testSubscribe][订阅主题: {}]", topic);
|
|
|
-
|
|
|
- client.subscribe(topic, MqttQoS.AT_LEAST_ONCE.value())
|
|
|
- .onComplete(subscribeAr -> {
|
|
|
- if (subscribeAr.succeeded()) {
|
|
|
- log.info("[testSubscribe][订阅成功,等待下行消息... (按 Ctrl+C 结束)]");
|
|
|
- // 保持连接 30 秒等待消息
|
|
|
- vertx.setTimer(30000, id -> {
|
|
|
- client.disconnect()
|
|
|
- .onComplete(disconnectAr -> {
|
|
|
- log.info("[testSubscribe][断开连接]");
|
|
|
- latch.countDown();
|
|
|
- });
|
|
|
- });
|
|
|
- } else {
|
|
|
- log.error("[testSubscribe][订阅失败]", subscribeAr.cause());
|
|
|
- latch.countDown();
|
|
|
- }
|
|
|
- });
|
|
|
+ /**
|
|
|
+ * 订阅响应主题
|
|
|
+ *
|
|
|
+ * @param client MQTT 客户端
|
|
|
+ * @param replyTopic 响应主题
|
|
|
+ */
|
|
|
+ private void subscribeReply(MqttClient client, String replyTopic) throws Exception {
|
|
|
+ // 1. 订阅响应主题
|
|
|
+ CompletableFuture<Void> future = new CompletableFuture<>();
|
|
|
+ client.subscribe(replyTopic, MqttQoS.AT_LEAST_ONCE.value())
|
|
|
+ .onComplete(ar -> {
|
|
|
+ if (ar.succeeded()) {
|
|
|
+ log.info("[subscribeReply][订阅响应主题成功: {}]", replyTopic);
|
|
|
+ future.complete(null);
|
|
|
} else {
|
|
|
- log.error("[testSubscribe][连接失败]", connectAr.cause());
|
|
|
- latch.countDown();
|
|
|
+ future.completeExceptionally(ar.cause());
|
|
|
}
|
|
|
});
|
|
|
+ // 2. 等待订阅结果
|
|
|
+ future.get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
|
|
|
+ }
|
|
|
|
|
|
- // 6. 等待测试完成
|
|
|
- boolean completed = latch.await(60, TimeUnit.SECONDS);
|
|
|
- if (!completed) {
|
|
|
- log.warn("[testSubscribe][测试超时]");
|
|
|
+ /**
|
|
|
+ * 发布消息并等待响应
|
|
|
+ *
|
|
|
+ * @param client MQTT 客户端
|
|
|
+ * @param topic 发布主题
|
|
|
+ * @param request 请求消息
|
|
|
+ * @return 响应消息
|
|
|
+ */
|
|
|
+ private IotDeviceMessage publishAndWaitReply(MqttClient client, String topic, IotDeviceMessage request) {
|
|
|
+ // 1. 设置消息处理器,接收响应
|
|
|
+ CompletableFuture<IotDeviceMessage> future = new CompletableFuture<>();
|
|
|
+ client.publishHandler(message -> {
|
|
|
+ log.info("[publishAndWaitReply][收到响应: topic={}, payload={}]",
|
|
|
+ message.topicName(), message.payload().toString());
|
|
|
+ IotDeviceMessage response = CODEC.decode(message.payload().getBytes());
|
|
|
+ future.complete(response);
|
|
|
+ });
|
|
|
+
|
|
|
+ // 2. 编码并发布消息
|
|
|
+ byte[] payload = CODEC.encode(request);
|
|
|
+ log.info("[publishAndWaitReply][Codec: {}, 发送消息: topic={}, payload={}]",
|
|
|
+ CODEC.type(), topic, new String(payload));
|
|
|
+
|
|
|
+ client.publish(topic, Buffer.buffer(payload), MqttQoS.AT_LEAST_ONCE, false, false)
|
|
|
+ .onComplete(ar -> {
|
|
|
+ if (ar.succeeded()) {
|
|
|
+ log.info("[publishAndWaitReply][消息发布成功,messageId={}]", ar.result());
|
|
|
+ } else {
|
|
|
+ log.error("[publishAndWaitReply][消息发布失败]", ar.cause());
|
|
|
+ future.completeExceptionally(ar.cause());
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // 3. 等待响应(超时返回 null)
|
|
|
+ try {
|
|
|
+ return future.get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.warn("[publishAndWaitReply][等待响应超时或失败]");
|
|
|
+ return null;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 断开连接
|
|
|
+ *
|
|
|
+ * @param client MQTT 客户端
|
|
|
+ */
|
|
|
+ private void disconnect(MqttClient client) throws Exception {
|
|
|
+ // 1. 断开连接
|
|
|
+ CompletableFuture<Void> future = new CompletableFuture<>();
|
|
|
+ client.disconnect()
|
|
|
+ .onComplete(ar -> {
|
|
|
+ if (ar.succeeded()) {
|
|
|
+ log.info("[disconnect][断开连接成功]");
|
|
|
+ future.complete(null);
|
|
|
+ } else {
|
|
|
+ future.completeExceptionally(ar.cause());
|
|
|
+ }
|
|
|
+ });
|
|
|
+ // 2. 等待断开结果
|
|
|
+ future.get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
|
|
|
+ }
|
|
|
+
|
|
|
}
|