|
|
|
@ -0,0 +1,806 @@
|
|
|
|
|
|
|
|
package com.data.emqx;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* @author licd
|
|
|
|
|
|
|
|
* @className MqttDeviceClient
|
|
|
|
|
|
|
|
* @description 集疏运终端设备MQTT协议接收客户端
|
|
|
|
|
|
|
|
* @date 2026/03/17
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import com.alibaba.fastjson.JSON;
|
|
|
|
|
|
|
|
import com.alibaba.fastjson.JSONArray;
|
|
|
|
|
|
|
|
import com.alibaba.fastjson.JSONObject;
|
|
|
|
|
|
|
|
import com.data.emqx.service.IDeviceDataService;
|
|
|
|
|
|
|
|
import org.eclipse.paho.client.mqttv3.*;
|
|
|
|
|
|
|
|
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
|
|
|
|
|
|
|
|
import org.slf4j.Logger;
|
|
|
|
|
|
|
|
import org.slf4j.LoggerFactory;
|
|
|
|
|
|
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
|
|
|
|
|
|
import org.springframework.beans.factory.annotation.Value;
|
|
|
|
|
|
|
|
import org.springframework.stereotype.Component;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import javax.annotation.PostConstruct;
|
|
|
|
|
|
|
|
import java.util.List;
|
|
|
|
|
|
|
|
import java.util.concurrent.ExecutorService;
|
|
|
|
|
|
|
|
import java.util.concurrent.Executors;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Component
|
|
|
|
|
|
|
|
public class MqttDeviceClient {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private static final Logger logger = LoggerFactory.getLogger(MqttDeviceClient.class);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Broker配置
|
|
|
|
|
|
|
|
@Value("${device.mqtt.broker:tcp://127.0.0.1:1883}")
|
|
|
|
|
|
|
|
private String broker;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Value("${device.mqtt.username:emqx_public}")
|
|
|
|
|
|
|
|
private String username;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Value("${device.mqtt.password:emqx_public}")
|
|
|
|
|
|
|
|
private String password;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Value("${device.mqtt.clientId:server_client_001}")
|
|
|
|
|
|
|
|
private String clientId;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// V2.0.1 Topic配置 - 动态层级结构
|
|
|
|
|
|
|
|
// 设备端发布(上行): WgtRfid/device/${did}
|
|
|
|
|
|
|
|
// 服务器下发(下行): WgtRfid/server/${did}
|
|
|
|
|
|
|
|
private static final String TOPIC_DEVICE_UPLINK = "WgtRfid/device/#";
|
|
|
|
|
|
|
|
private static final String TOPIC_SERVER_DOWNLINK_PREFIX = "WgtRfid/server/";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 兼容旧版 Topic(V1.0.1)
|
|
|
|
|
|
|
|
private static final String TOPIC_RFID_WGT = "Topic_RFID_WGT";
|
|
|
|
|
|
|
|
private static final String TOPIC_HEART = "Topic_Heart";
|
|
|
|
|
|
|
|
private static final String TOPIC_ALARM = "Topic_ALARM";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// V2.0.1 QoS配置 - 统一为QoS 0
|
|
|
|
|
|
|
|
private static final int QOS = 0;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// V2.0.1 消息类型
|
|
|
|
|
|
|
|
private static final String MSG_TYPE_WEIGHT_RFID = "weight_rfid";
|
|
|
|
|
|
|
|
private static final String MSG_TYPE_ALARM = "alarm";
|
|
|
|
|
|
|
|
private static final String MSG_TYPE_HEART = "heart";
|
|
|
|
|
|
|
|
private static final String MSG_TYPE_OTA_PROGRESS = "ota_progress";
|
|
|
|
|
|
|
|
private static final String MSG_TYPE_PARAM_READ = "param_read";
|
|
|
|
|
|
|
|
private static final String MSG_TYPE_PARAM_WRITE = "param_write";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private MqttClient mqttClient;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 异步ACK发送线程池(避免在messageArrived回调中直接调用publish导致死锁)
|
|
|
|
|
|
|
|
private final ExecutorService ackSenderExecutor = Executors.newFixedThreadPool(2);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Autowired
|
|
|
|
|
|
|
|
private IDeviceDataService deviceDataService;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* 初始化MQTT客户端连接
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
@PostConstruct
|
|
|
|
|
|
|
|
public void init() {
|
|
|
|
|
|
|
|
// 测试数据保存功能
|
|
|
|
|
|
|
|
// testDataSave();
|
|
|
|
|
|
|
|
connect();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* 测试数据保存功能
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
private void testDataSave() {
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
logger.info("=== 开始测试数据保存功能 ===");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 测试保存称重&标签数据
|
|
|
|
|
|
|
|
if (deviceDataService != null) {
|
|
|
|
|
|
|
|
String deviceId = "test_device_001";
|
|
|
|
|
|
|
|
Long seqNum = 123456789L;
|
|
|
|
|
|
|
|
Long sendTime = System.currentTimeMillis() / 1000;
|
|
|
|
|
|
|
|
Integer weight = 1000;
|
|
|
|
|
|
|
|
String rfid = "e280111122223333,b001111122223333";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
deviceDataService.saveWeightRfidData(deviceId, seqNum, sendTime, weight, rfid);
|
|
|
|
|
|
|
|
logger.info("称重&标签数据保存测试成功");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 测试保存告警数据
|
|
|
|
|
|
|
|
Long alarmSeqNum = 987654321L;
|
|
|
|
|
|
|
|
String uwb = "ID:1234,dist:2000; ID:5678,dist:1500";
|
|
|
|
|
|
|
|
com.alibaba.fastjson.JSONArray uwbArray = new com.alibaba.fastjson.JSONArray();
|
|
|
|
|
|
|
|
com.alibaba.fastjson.JSONObject uwbObj1 = new com.alibaba.fastjson.JSONObject();
|
|
|
|
|
|
|
|
uwbObj1.put("ID", "1234");
|
|
|
|
|
|
|
|
uwbObj1.put("Dist", "2000 cm");
|
|
|
|
|
|
|
|
uwbArray.add(uwbObj1);
|
|
|
|
|
|
|
|
com.alibaba.fastjson.JSONObject uwbObj2 = new com.alibaba.fastjson.JSONObject();
|
|
|
|
|
|
|
|
uwbObj2.put("ID", "5678");
|
|
|
|
|
|
|
|
uwbObj2.put("Dist", "1500 cm");
|
|
|
|
|
|
|
|
uwbArray.add(uwbObj2);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
deviceDataService.saveAlarmData(deviceId, alarmSeqNum, sendTime, uwb, uwbArray);
|
|
|
|
|
|
|
|
logger.info("告警数据保存测试成功");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 测试保存心跳数据
|
|
|
|
|
|
|
|
Double sysVol = 12.8;
|
|
|
|
|
|
|
|
Integer netRSSI = -75;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
deviceDataService.saveHeartData(deviceId, sysVol, netRSSI, sendTime);
|
|
|
|
|
|
|
|
logger.info("心跳数据保存测试成功");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// V2.0.1 测试保存OTA升级进度数据
|
|
|
|
|
|
|
|
String taskID = "ota_task_001";
|
|
|
|
|
|
|
|
Integer progress = 50;
|
|
|
|
|
|
|
|
String otaStatus = "InProgress";
|
|
|
|
|
|
|
|
deviceDataService.saveOtaProgressData(deviceId, taskID, progress, otaStatus);
|
|
|
|
|
|
|
|
logger.info("OTA升级进度数据保存测试成功");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// V2.0.1 测试保存设备参数写入响应
|
|
|
|
|
|
|
|
Long paramSeqNum = 111222333L;
|
|
|
|
|
|
|
|
String paramStatus = "OK";
|
|
|
|
|
|
|
|
String failReason = null;
|
|
|
|
|
|
|
|
deviceDataService.saveParamWriteData(deviceId, paramSeqNum, paramStatus, failReason);
|
|
|
|
|
|
|
|
logger.info("设备参数写入响应保存测试成功");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// V2.0.1 测试保存设备参数期望值(用于设备上线后自动拉平配置)
|
|
|
|
|
|
|
|
// 需要通过 DeviceDataServiceImpl 直接调用
|
|
|
|
|
|
|
|
if (deviceDataService instanceof com.data.emqx.service.impl.DeviceDataServiceImpl) {
|
|
|
|
|
|
|
|
com.data.emqx.service.impl.DeviceDataServiceImpl impl =
|
|
|
|
|
|
|
|
(com.data.emqx.service.impl.DeviceDataServiceImpl) deviceDataService;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 保存几个设备参数期望值
|
|
|
|
|
|
|
|
impl.saveParamExpectation(deviceId, "Rs485_PollMs", "1000");
|
|
|
|
|
|
|
|
impl.saveParamExpectation(deviceId, "UWB_AlarmMs", "5000");
|
|
|
|
|
|
|
|
impl.saveParamExpectation(deviceId, "VoiceAlarmMs", "3000");
|
|
|
|
|
|
|
|
impl.saveParamExpectation(deviceId, "UWB_IgnoreCm", "50");
|
|
|
|
|
|
|
|
impl.saveParamExpectation(deviceId, "UWB_AlarmCm", "200");
|
|
|
|
|
|
|
|
logger.info("设备参数期望值保存测试成功");
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
logger.error("deviceDataService 为 null,无法测试数据保存功能");
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
logger.info("=== 数据保存功能测试完成 ===");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// V2.0.1 测试设备上线参数同步功能
|
|
|
|
|
|
|
|
testDeviceParamSync("test_device_001");
|
|
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
|
|
|
logger.error("测试数据保存功能异常: {}", e.getMessage(), e);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* 连接MQTT服务器
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
public void connect() {
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
// 创建MQTT客户端
|
|
|
|
|
|
|
|
mqttClient = new MqttClient(broker, clientId, new MemoryPersistence());
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 配置连接选项
|
|
|
|
|
|
|
|
MqttConnectOptions connOpts = new MqttConnectOptions();
|
|
|
|
|
|
|
|
connOpts.setUserName(username);
|
|
|
|
|
|
|
|
connOpts.setPassword(password.toCharArray());
|
|
|
|
|
|
|
|
connOpts.setCleanSession(true);
|
|
|
|
|
|
|
|
connOpts.setAutomaticReconnect(true);
|
|
|
|
|
|
|
|
connOpts.setConnectionTimeout(30);
|
|
|
|
|
|
|
|
connOpts.setKeepAliveInterval(60);
|
|
|
|
|
|
|
|
connOpts.setMaxInflight(100);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 设置断线重连回调
|
|
|
|
|
|
|
|
mqttClient.setCallback(new MqttCallbackExtended() {
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
|
|
|
public void connectComplete(boolean reconnect, String serverURI) {
|
|
|
|
|
|
|
|
if (reconnect) {
|
|
|
|
|
|
|
|
logger.info("MQTT连接已自动重连到: {}", serverURI);
|
|
|
|
|
|
|
|
subscribeTopics();
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
logger.info("MQTT连接已建立: {}", serverURI);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
|
|
|
public void connectionLost(Throwable cause) {
|
|
|
|
|
|
|
|
logger.error("MQTT连接断开: {}", cause.getMessage(), cause);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
|
|
|
public void messageArrived(String topic, MqttMessage message) {
|
|
|
|
|
|
|
|
processMessage(topic, message);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
|
|
|
public void deliveryComplete(IMqttDeliveryToken token) {
|
|
|
|
|
|
|
|
logger.debug("消息发送完成");
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 建立连接
|
|
|
|
|
|
|
|
logger.info("正在连接MQTT Broker: {}", broker);
|
|
|
|
|
|
|
|
mqttClient.connect(connOpts);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 订阅Topic
|
|
|
|
|
|
|
|
subscribeTopics();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
logger.info("MQTT客户端初始化完成");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} catch (MqttException e) {
|
|
|
|
|
|
|
|
logger.error("MQTT连接失败: {}", e.getMessage(), e);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* 订阅Topic(V2.0.1 动态层级结构)
|
|
|
|
|
|
|
|
* 设备端发布到: WgtRfid/device/${did}
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
private void subscribeTopics() {
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
// V2.0.1 使用通配符订阅所有设备的动态Topic
|
|
|
|
|
|
|
|
IMqttToken token = mqttClient.subscribeWithResponse(TOPIC_DEVICE_UPLINK, QOS);
|
|
|
|
|
|
|
|
token.waitForCompletion();
|
|
|
|
|
|
|
|
if (token.isComplete()) {
|
|
|
|
|
|
|
|
logger.info("订阅成功 - Topic: {}, QoS: {} (V2.0.1动态Topic)", TOPIC_DEVICE_UPLINK, QOS);
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
logger.error("订阅失败 - Topic: {}", TOPIC_DEVICE_UPLINK);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 兼容旧版 Topic(V1.0.1)
|
|
|
|
|
|
|
|
subscribeTopic(TOPIC_RFID_WGT);
|
|
|
|
|
|
|
|
subscribeTopic(TOPIC_HEART);
|
|
|
|
|
|
|
|
subscribeTopic(TOPIC_ALARM);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} catch (MqttException e) {
|
|
|
|
|
|
|
|
logger.error("订阅Topic异常: {}", e.getMessage(), e);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* 订阅单个Topic
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
private void subscribeTopic(String topic) {
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
IMqttToken token = mqttClient.subscribeWithResponse(topic, QOS);
|
|
|
|
|
|
|
|
token.waitForCompletion();
|
|
|
|
|
|
|
|
if (token.isComplete()) {
|
|
|
|
|
|
|
|
logger.info("订阅成功 - Topic: {}, QoS: {} (旧版兼容)", topic, QOS);
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
logger.error("订阅失败 - Topic: {}", topic);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
} catch (MqttException e) {
|
|
|
|
|
|
|
|
logger.error("订阅Topic异常: {}, Error: {}", topic, e.getMessage());
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* 处理接收到的消息(兼容V1.0.1和V2.0.1)
|
|
|
|
|
|
|
|
* V2.0.1 Topic格式: WgtRfid/device/${did} - deviceId从Topic路径提取
|
|
|
|
|
|
|
|
* V1.0.1 Topic格式: Topic_RFID_WGT/Topic_HEART/Topic_ALARM - deviceId从JSON提取
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
private void processMessage(String topic, MqttMessage message) {
|
|
|
|
|
|
|
|
// 调试:确认消息被接收
|
|
|
|
|
|
|
|
String debugMsg = "===== processMessage 方法被调用 ===== Topic: " + topic + ", QoS: " + message.getQos();
|
|
|
|
|
|
|
|
logger.info(debugMsg);
|
|
|
|
|
|
|
|
System.out.println(debugMsg);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
String payload = new String(message.getPayload());
|
|
|
|
|
|
|
|
logger.info("收到消息 - Topic: {}, Payload: {}", topic, payload);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
// 解析JSON(先解析,因为旧版需要从JSON提取deviceId)
|
|
|
|
|
|
|
|
JSONObject jsonObject = JSON.parseObject(payload);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// V2.0.1: deviceId从Topic路径提取
|
|
|
|
|
|
|
|
// V1.0.1: deviceId从JSON提取(兼容旧版)
|
|
|
|
|
|
|
|
String deviceId = extractDeviceIdFromTopic(topic);
|
|
|
|
|
|
|
|
boolean isOldProtocol = false;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 如果从Topic无法提取deviceId,尝试从JSON提取(旧版兼容)
|
|
|
|
|
|
|
|
if (deviceId == null || deviceId.isEmpty()) {
|
|
|
|
|
|
|
|
deviceId = jsonObject.getString("deviceID");
|
|
|
|
|
|
|
|
isOldProtocol = true;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 调试:显示提取的deviceId
|
|
|
|
|
|
|
|
String deviceIdMsg = "从" + (isOldProtocol ? "JSON" : "Topic") + "提取的DeviceID: " + (deviceId != null ? deviceId : "null") + " (协议版本: " + (isOldProtocol ? "V1.0.1" : "V2.0.1") + ")";
|
|
|
|
|
|
|
|
logger.info(deviceIdMsg);
|
|
|
|
|
|
|
|
System.out.println(deviceIdMsg);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (deviceId == null || deviceId.isEmpty()) {
|
|
|
|
|
|
|
|
logger.error("无法从Topic或JSON提取deviceId: {}", topic);
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 获取消息类型
|
|
|
|
|
|
|
|
String messageType = jsonObject.getString("messageType");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 旧版兼容:如果没有messageType字段,根据Topic推断消息类型
|
|
|
|
|
|
|
|
if (messageType == null || messageType.isEmpty()) {
|
|
|
|
|
|
|
|
messageType = inferMessageTypeFromTopic(topic);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (messageType == null) {
|
|
|
|
|
|
|
|
logger.error("消息缺少必要字段: messageType");
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 根据messageType处理不同类型的消息(传递协议版本)
|
|
|
|
|
|
|
|
switch (messageType) {
|
|
|
|
|
|
|
|
case MSG_TYPE_WEIGHT_RFID:
|
|
|
|
|
|
|
|
processWeightRfidData(deviceId, jsonObject, isOldProtocol);
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case MSG_TYPE_ALARM:
|
|
|
|
|
|
|
|
processAlarmData(deviceId, jsonObject, isOldProtocol);
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case MSG_TYPE_HEART:
|
|
|
|
|
|
|
|
processHeartData(deviceId, jsonObject);
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case MSG_TYPE_OTA_PROGRESS:
|
|
|
|
|
|
|
|
processOtaProgressData(deviceId, jsonObject);
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case MSG_TYPE_PARAM_READ:
|
|
|
|
|
|
|
|
processParamReadData(deviceId, jsonObject);
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case MSG_TYPE_PARAM_WRITE:
|
|
|
|
|
|
|
|
processParamWriteData(deviceId, jsonObject);
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
|
|
|
logger.warn("未知的消息类型: {}", messageType);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
|
|
|
logger.error("消息处理异常: {}", e.getMessage(), e);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* 从Topic路径提取deviceId
|
|
|
|
|
|
|
|
* V2.0.1 Topic格式: WgtRfid/device/${did}
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
private String extractDeviceIdFromTopic(String topic) {
|
|
|
|
|
|
|
|
if (topic == null || topic.isEmpty()) {
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
String prefix = "WgtRfid/device/";
|
|
|
|
|
|
|
|
if (topic.startsWith(prefix)) {
|
|
|
|
|
|
|
|
return topic.substring(prefix.length());
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* 从Topic推断消息类型(旧版兼容)
|
|
|
|
|
|
|
|
* V1.0.1 的Topic直接表示消息类型
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
private String inferMessageTypeFromTopic(String topic) {
|
|
|
|
|
|
|
|
if (topic == null || topic.isEmpty()) {
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (topic) {
|
|
|
|
|
|
|
|
case TOPIC_RFID_WGT:
|
|
|
|
|
|
|
|
return MSG_TYPE_WEIGHT_RFID;
|
|
|
|
|
|
|
|
case TOPIC_HEART:
|
|
|
|
|
|
|
|
return MSG_TYPE_HEART;
|
|
|
|
|
|
|
|
case TOPIC_ALARM:
|
|
|
|
|
|
|
|
return MSG_TYPE_ALARM;
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* 处理称重&标签数据
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
private void processWeightRfidData(String deviceId, JSONObject jsonObject, boolean isOldProtocol) {
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
// 调试:确认方法被调用
|
|
|
|
|
|
|
|
String debugMsg = "===== processWeightRfidData 方法被调用 ===== DeviceID: " + deviceId + ", Payload: " + jsonObject.toJSONString() + ", Protocol: " + (isOldProtocol ? "V1.0.1" : "V2.0.1");
|
|
|
|
|
|
|
|
logger.info(debugMsg);
|
|
|
|
|
|
|
|
System.out.println(debugMsg);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
logger.info("处理称重&标签数据 - DeviceID: {}", deviceId);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Long seqNum = jsonObject.getLong("seqNum");
|
|
|
|
|
|
|
|
if (seqNum == null) {
|
|
|
|
|
|
|
|
logger.error("非法消息:seqNum 缺失,丢弃消息且不发送ACK。Payload: {}", jsonObject.toJSONString());
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 检查是否已存在相同数据
|
|
|
|
|
|
|
|
if (deviceDataService != null && deviceDataService.existsWeightRfidData(deviceId, seqNum)) {
|
|
|
|
|
|
|
|
logger.info("称重数据已存在,直接返回ACK - DeviceID: {}, SeqNum: {}", deviceId, seqNum);
|
|
|
|
|
|
|
|
// 异步发送ACK(避免在messageArrived回调中直接调用publish导致死锁)
|
|
|
|
|
|
|
|
final Long finalSeqNum1 = seqNum;
|
|
|
|
|
|
|
|
final boolean finalIsOldProtocol1 = isOldProtocol;
|
|
|
|
|
|
|
|
ackSenderExecutor.execute(() -> sendAck(deviceId, finalSeqNum1, "weight_rfid", finalIsOldProtocol1));
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Long sendTime = jsonObject.getLong("sendTime");
|
|
|
|
|
|
|
|
Integer weight = jsonObject.getInteger("weight");
|
|
|
|
|
|
|
|
if (weight == null) {
|
|
|
|
|
|
|
|
logger.warn("警告:weight 字段缺失,使用默认值0。DeviceID: {}", deviceId);
|
|
|
|
|
|
|
|
weight = 0;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
String rfidStr = null;
|
|
|
|
|
|
|
|
JSONArray rfidArray = jsonObject.getJSONArray("RFID");
|
|
|
|
|
|
|
|
if (rfidArray != null && !rfidArray.isEmpty()) {
|
|
|
|
|
|
|
|
List<String> rfidList = rfidArray.toJavaList(String.class);
|
|
|
|
|
|
|
|
rfidStr = String.join(",", rfidList);
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
rfidStr = "";
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
logger.info("称重数据 - SeqNum: {}, Weight: {}, RFID: {}", seqNum, weight, rfidStr);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (deviceDataService != null) {
|
|
|
|
|
|
|
|
deviceDataService.saveWeightRfidData(deviceId, seqNum, sendTime, weight, rfidStr);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 异步发送ACK(避免在messageArrived回调中直接调用publish导致死锁)
|
|
|
|
|
|
|
|
final Long finalSeqNum2 = seqNum;
|
|
|
|
|
|
|
|
final boolean finalIsOldProtocol2 = isOldProtocol;
|
|
|
|
|
|
|
|
ackSenderExecutor.execute(() -> sendAck(deviceId, finalSeqNum2, "weight_rfid", finalIsOldProtocol2));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
|
|
|
logger.error("处理称重&标签数据异常: {}", e.getMessage(), e);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* 处理告警数据
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
private void processAlarmData(String deviceId, JSONObject jsonObject, boolean isOldProtocol) {
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
logger.info("处理告警数据 - DeviceID: {}", deviceId);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Long seqNum = jsonObject.getLong("seqNum");
|
|
|
|
|
|
|
|
if (seqNum == null) {
|
|
|
|
|
|
|
|
logger.error("非法消息:seqNum 缺失,丢弃消息且不发送ACK。Payload: {}", jsonObject.toJSONString());
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 检查是否已存在相同数据
|
|
|
|
|
|
|
|
if (deviceDataService != null && deviceDataService.existsAlarmData(deviceId, seqNum)) {
|
|
|
|
|
|
|
|
logger.info("告警数据已存在,直接返回ACK - DeviceID: {}, SeqNum: {}", deviceId, seqNum);
|
|
|
|
|
|
|
|
// 异步发送ACK(避免在messageArrived回调中直接调用publish导致死锁)
|
|
|
|
|
|
|
|
final Long finalSeqNum3 = seqNum;
|
|
|
|
|
|
|
|
final boolean finalIsOldProtocol3 = isOldProtocol;
|
|
|
|
|
|
|
|
ackSenderExecutor.execute(() -> sendAck(deviceId, finalSeqNum3, "alarm", finalIsOldProtocol3));
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Long sendTime = jsonObject.getLong("sendTime");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
StringBuilder uwbInfo = new StringBuilder();
|
|
|
|
|
|
|
|
JSONArray uwbArray = jsonObject.getJSONArray("UWB");
|
|
|
|
|
|
|
|
if (uwbArray != null && !uwbArray.isEmpty()) {
|
|
|
|
|
|
|
|
for (int i = 0; i < uwbArray.size(); i++) {
|
|
|
|
|
|
|
|
JSONObject uwbObj = uwbArray.getJSONObject(i);
|
|
|
|
|
|
|
|
if (uwbObj == null) continue;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Integer id = uwbObj.getInteger("ID");
|
|
|
|
|
|
|
|
Integer dist = uwbObj.getInteger("dist");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (id == null || dist == null) {
|
|
|
|
|
|
|
|
logger.warn("UWB子项数据不完整,跳过该项。Item: {}", uwbObj.toJSONString());
|
|
|
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (uwbInfo.length() > 0) {
|
|
|
|
|
|
|
|
uwbInfo.append("; ");
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
uwbInfo.append("ID:").append(id).append(",dist:").append(dist);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
logger.info("告警数据 - SeqNum: {}, SendTime: {}, UWB: {}", seqNum, sendTime, uwbInfo);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (deviceDataService != null) {
|
|
|
|
|
|
|
|
deviceDataService.saveAlarmData(deviceId, seqNum, sendTime, uwbInfo.toString(), uwbArray);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 异步发送ACK(避免在messageArrived回调中直接调用publish导致死锁)
|
|
|
|
|
|
|
|
final Long finalSeqNum4 = seqNum;
|
|
|
|
|
|
|
|
final boolean finalIsOldProtocol4 = isOldProtocol;
|
|
|
|
|
|
|
|
ackSenderExecutor.execute(() -> sendAck(deviceId, finalSeqNum4, "alarm", finalIsOldProtocol4));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
|
|
|
logger.error("处理告警数据异常: {}", e.getMessage(), e);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* 处理心跳数据
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
private void processHeartData(String deviceId, JSONObject jsonObject) {
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
logger.info("处理心跳数据 - DeviceID: {}", deviceId);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Double sysVol = jsonObject.getDouble("sysVol");
|
|
|
|
|
|
|
|
Integer netRSSI = jsonObject.getInteger("netRSSI");
|
|
|
|
|
|
|
|
Long sendTime = jsonObject.getLong("sendTime");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 检查是否已存在相同数据
|
|
|
|
|
|
|
|
if (deviceDataService != null && deviceDataService.existsHeartData(deviceId, sendTime)) {
|
|
|
|
|
|
|
|
logger.info("心跳数据已存在,直接返回ACK - DeviceID: {}, SendTime: {}", deviceId, sendTime);
|
|
|
|
|
|
|
|
// 心跳数据没有seqNum,不需要发送ACK
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
logger.info("心跳数据 - sysVol: {}, netRSSI: {}, sendTime: {}",
|
|
|
|
|
|
|
|
sysVol, netRSSI, sendTime);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (deviceDataService != null) {
|
|
|
|
|
|
|
|
deviceDataService.saveHeartData(deviceId, sysVol, netRSSI, sendTime);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
|
|
|
logger.error("处理心跳数据异常: {}", e.getMessage(), e);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* V2.0.1 处理OTA升级进度数据
|
|
|
|
|
|
|
|
* 设备上报格式:{"messageType": "ota_progress", "taskID": "xxx", "progress": 0-100, "status": "OK/Fail"}
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
private void processOtaProgressData(String deviceId, JSONObject jsonObject) {
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
logger.info("处理OTA升级进度数据 - DeviceID: {}", deviceId);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
String taskID = jsonObject.getString("taskID");
|
|
|
|
|
|
|
|
Integer progress = jsonObject.getInteger("progress");
|
|
|
|
|
|
|
|
String status = jsonObject.getString("status");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
logger.info("OTA进度数据 - DeviceID: {}, TaskID: {}, Progress: {}%, Status: {}",
|
|
|
|
|
|
|
|
deviceId, taskID, progress, status);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (deviceDataService != null) {
|
|
|
|
|
|
|
|
deviceDataService.saveOtaProgressData(deviceId, taskID, progress, status);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
|
|
|
logger.error("处理OTA升级进度数据异常: {}", e.getMessage(), e);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* V2.0.1 处理设备参数读取响应数据
|
|
|
|
|
|
|
|
* 设备上报格式:{"messageType": "param_read", "seqNum": xxx, "params": {"Rs485_PollMs": xxx, ...}}
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
private void processParamReadData(String deviceId, JSONObject jsonObject) {
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
logger.info("处理设备参数读取响应 - DeviceID: {}", deviceId);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Long seqNum = jsonObject.getLong("seqNum");
|
|
|
|
|
|
|
|
JSONObject params = jsonObject.getJSONObject("params");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
logger.info("参数读取响应 - DeviceID: {}, SeqNum: {}, Params: {}",
|
|
|
|
|
|
|
|
deviceId, seqNum, params != null ? params.toJSONString() : "null");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (deviceDataService != null && params != null) {
|
|
|
|
|
|
|
|
deviceDataService.saveParamReadData(deviceId, seqNum, params);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (seqNum != null) {
|
|
|
|
|
|
|
|
// 异步发送ACK(避免在messageArrived回调中直接调用publish导致死锁)
|
|
|
|
|
|
|
|
final Long finalSeqNum5 = seqNum;
|
|
|
|
|
|
|
|
ackSenderExecutor.execute(() -> sendAck(deviceId, finalSeqNum5, MSG_TYPE_PARAM_READ));
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
|
|
|
logger.error("处理设备参数读取响应异常: {}", e.getMessage(), e);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* V2.0.1 处理设备参数写入响应数据
|
|
|
|
|
|
|
|
* 设备上报格式:{"messageType": "param_write", "seqNum": xxx, "status": "OK/Fail", "failReason": "xxx"}
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
private void processParamWriteData(String deviceId, JSONObject jsonObject) {
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
logger.info("处理设备参数写入响应 - DeviceID: {}", deviceId);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Long seqNum = jsonObject.getLong("seqNum");
|
|
|
|
|
|
|
|
String status = jsonObject.getString("status");
|
|
|
|
|
|
|
|
String failReason = jsonObject.getString("failReason");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
logger.info("参数写入响应 - DeviceID: {}, SeqNum: {}, Status: {}, FailReason: {}",
|
|
|
|
|
|
|
|
deviceId, seqNum, status, failReason);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (deviceDataService != null) {
|
|
|
|
|
|
|
|
deviceDataService.saveParamWriteData(deviceId, seqNum, status, failReason);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (seqNum != null) {
|
|
|
|
|
|
|
|
// 异步发送ACK(避免在messageArrived回调中直接调用publish导致死锁)
|
|
|
|
|
|
|
|
final Long finalSeqNum6 = seqNum;
|
|
|
|
|
|
|
|
ackSenderExecutor.execute(() -> sendAck(deviceId, finalSeqNum6, MSG_TYPE_PARAM_WRITE));
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
|
|
|
logger.error("处理设备参数写入响应异常: {}", e.getMessage(), e);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* 发送ACK回复(兼容V1.0.1和V2.0.1协议)
|
|
|
|
|
|
|
|
* ACK格式:{"messageType": "xxx", "seqNum": xxx, "ackTime": UTC0时间戳(Uint32)}
|
|
|
|
|
|
|
|
* V2.0.1: 下行Topic格式: WgtRfid/server/${deviceId}
|
|
|
|
|
|
|
|
* V1.0.1: 下行Topic格式: ${deviceId}(直接使用设备ID)
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
private void sendAck(String deviceId, Long seqNum, String messageType) {
|
|
|
|
|
|
|
|
// 默认使用V2.0.1协议
|
|
|
|
|
|
|
|
sendAck(deviceId, seqNum, messageType, false);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* 发送ACK回复(兼容V1.0.1和V2.0.1协议)
|
|
|
|
|
|
|
|
* @param deviceId 设备ID
|
|
|
|
|
|
|
|
* @param seqNum 序列号
|
|
|
|
|
|
|
|
* @param messageType 消息类型
|
|
|
|
|
|
|
|
* @param isOldProtocol 是否为旧版协议(V1.0.1)
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
private void sendAck(String deviceId, Long seqNum, String messageType, boolean isOldProtocol) {
|
|
|
|
|
|
|
|
String startMsg = "开始发送ACK - DeviceID: " + deviceId + ", Type: " + messageType + ", SeqNum: " + seqNum + ", Protocol: " + (isOldProtocol ? "V1.0.1" : "V2.0.1");
|
|
|
|
|
|
|
|
logger.info(startMsg);
|
|
|
|
|
|
|
|
System.out.println(startMsg);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (seqNum == null) {
|
|
|
|
|
|
|
|
String errorMsg = "尝试发送ACK但seqNum为空,拒绝发送!DeviceID: " + deviceId;
|
|
|
|
|
|
|
|
logger.error(errorMsg);
|
|
|
|
|
|
|
|
System.out.println(errorMsg);
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (mqttClient == null) {
|
|
|
|
|
|
|
|
String errorMsg = "MQTT客户端未初始化,无法发送ACK!DeviceID: " + deviceId;
|
|
|
|
|
|
|
|
logger.error(errorMsg);
|
|
|
|
|
|
|
|
System.out.println(errorMsg);
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!mqttClient.isConnected()) {
|
|
|
|
|
|
|
|
String errorMsg = "MQTT客户端未连接,无法发送ACK!DeviceID: " + deviceId;
|
|
|
|
|
|
|
|
logger.error(errorMsg);
|
|
|
|
|
|
|
|
System.out.println(errorMsg);
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
JSONObject ackJson = new JSONObject();
|
|
|
|
|
|
|
|
ackJson.put("messageType", messageType);
|
|
|
|
|
|
|
|
ackJson.put("seqNum", seqNum);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
long utcSeconds = System.currentTimeMillis() / 1000;
|
|
|
|
|
|
|
|
ackJson.put("ackTime", utcSeconds);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
String ackPayload = ackJson.toJSONString();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 确定下行Topic
|
|
|
|
|
|
|
|
String downlinkTopic;
|
|
|
|
|
|
|
|
if (isOldProtocol) {
|
|
|
|
|
|
|
|
// V1.0.1: 直接使用deviceId作为Topic
|
|
|
|
|
|
|
|
downlinkTopic = deviceId;
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
// V2.0.1: 使用动态下行Topic
|
|
|
|
|
|
|
|
downlinkTopic = TOPIC_SERVER_DOWNLINK_PREFIX + deviceId;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
String prepareMsg = "准备发送ACK消息 - Topic: " + downlinkTopic + ", Payload: " + ackPayload;
|
|
|
|
|
|
|
|
logger.info(prepareMsg);
|
|
|
|
|
|
|
|
System.out.println(prepareMsg);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
MqttMessage ackMessage = new MqttMessage(ackPayload.getBytes());
|
|
|
|
|
|
|
|
ackMessage.setQos(QOS);
|
|
|
|
|
|
|
|
String createMsg = "ACK消息创建成功 - QoS: " + QOS;
|
|
|
|
|
|
|
|
logger.info(createMsg);
|
|
|
|
|
|
|
|
System.out.println(createMsg);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
mqttClient.publish(downlinkTopic, ackMessage);
|
|
|
|
|
|
|
|
String successMsg = "ACK已成功发送 - DeviceID: " + deviceId + ", Type: " + messageType + ", SeqNum: " + seqNum + ", ackTime: " + utcSeconds + ", Topic: " + downlinkTopic;
|
|
|
|
|
|
|
|
logger.info(successMsg);
|
|
|
|
|
|
|
|
System.out.println(successMsg);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} catch (MqttException e) {
|
|
|
|
|
|
|
|
String errorMsg = "发送ACK失败 - DeviceID: " + deviceId + ", SeqNum: " + seqNum + ", Error: " + e.getMessage();
|
|
|
|
|
|
|
|
logger.error(errorMsg, e);
|
|
|
|
|
|
|
|
System.out.println(errorMsg);
|
|
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
|
|
|
String errorMsg = "发送ACK时发生未知异常 - DeviceID: " + deviceId + ", SeqNum: " + seqNum + ", Error: " + e.getMessage();
|
|
|
|
|
|
|
|
logger.error(errorMsg, e);
|
|
|
|
|
|
|
|
System.out.println(errorMsg);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* 断开连接
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
public void disconnect() {
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
if (mqttClient != null && mqttClient.isConnected()) {
|
|
|
|
|
|
|
|
mqttClient.disconnect();
|
|
|
|
|
|
|
|
mqttClient.close();
|
|
|
|
|
|
|
|
logger.info("MQTT连接已断开");
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
} catch (MqttException e) {
|
|
|
|
|
|
|
|
logger.error("断开MQTT连接异常: {}", e.getMessage(), e);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* V2.0.1 设备上线参数同步测试方法
|
|
|
|
|
|
|
|
* 模拟设备上线后,服务器查询待同步的参数并发送给设备
|
|
|
|
|
|
|
|
* 这是"设备期望值"功能的核心使用场景
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
private void testDeviceParamSync(String deviceId) {
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
logger.info("=== 开始设备上线参数同步测试 ===");
|
|
|
|
|
|
|
|
logger.info("设备ID: {}", deviceId);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 查询设备待同步的参数(状态为Pending的参数)
|
|
|
|
|
|
|
|
if (deviceDataService instanceof com.data.emqx.service.impl.DeviceDataServiceImpl) {
|
|
|
|
|
|
|
|
com.data.emqx.service.impl.DeviceDataServiceImpl impl =
|
|
|
|
|
|
|
|
(com.data.emqx.service.impl.DeviceDataServiceImpl) deviceDataService;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 获取 DeviceParamWriteMapper
|
|
|
|
|
|
|
|
// 这里为了测试,我们直接模拟查询待同步参数
|
|
|
|
|
|
|
|
logger.info("查询设备待同步的参数...");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 模拟查询结果(实际应该调用 deviceParamWriteMapper.selectPendingParamsByDeviceId)
|
|
|
|
|
|
|
|
// 这里打印一些测试信息
|
|
|
|
|
|
|
|
logger.info("设备 {} 的待同步参数列表:", deviceId);
|
|
|
|
|
|
|
|
logger.info(" - Rs485_PollMs: 1000 (Pending)");
|
|
|
|
|
|
|
|
logger.info(" - UWB_AlarmMs: 5000 (Pending)");
|
|
|
|
|
|
|
|
logger.info(" - VoiceAlarmMs: 3000 (Pending)");
|
|
|
|
|
|
|
|
logger.info(" - UWB_IgnoreCm: 50 (Pending)");
|
|
|
|
|
|
|
|
logger.info(" - UWB_AlarmCm: 200 (Pending)");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 模拟发送参数同步指令到设备
|
|
|
|
|
|
|
|
sendParamSyncCommand(deviceId);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
logger.info("=== 设备上线参数同步测试完成 ===");
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
|
|
|
logger.error("设备上线参数同步测试异常: {}", e.getMessage(), e);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
|
|
* 发送参数同步指令到设备
|
|
|
|
|
|
|
|
* V2.0.1: 下行Topic格式: WgtRfid/server/${deviceId}
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
private void sendParamSyncCommand(String deviceId) {
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
if (mqttClient == null || !mqttClient.isConnected()) {
|
|
|
|
|
|
|
|
logger.warn("MQTT客户端未连接,无法发送参数同步指令");
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 构建参数同步指令
|
|
|
|
|
|
|
|
JSONObject paramSyncJson = new JSONObject();
|
|
|
|
|
|
|
|
paramSyncJson.put("messageType", "param_write");
|
|
|
|
|
|
|
|
paramSyncJson.put("seqNum", System.currentTimeMillis());
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 参数列表
|
|
|
|
|
|
|
|
JSONObject params = new JSONObject();
|
|
|
|
|
|
|
|
params.put("Rs485_PollMs", 1000);
|
|
|
|
|
|
|
|
params.put("UWB_AlarmMs", 5000);
|
|
|
|
|
|
|
|
params.put("VoiceAlarmMs", 3000);
|
|
|
|
|
|
|
|
params.put("UWB_IgnoreCm", 50);
|
|
|
|
|
|
|
|
params.put("UWB_AlarmCm", 200);
|
|
|
|
|
|
|
|
paramSyncJson.put("params", params);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
String payload = paramSyncJson.toJSONString();
|
|
|
|
|
|
|
|
String downlinkTopic = TOPIC_SERVER_DOWNLINK_PREFIX + deviceId;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
MqttMessage message = new MqttMessage(payload.getBytes());
|
|
|
|
|
|
|
|
message.setQos(QOS);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
mqttClient.publish(downlinkTopic, message);
|
|
|
|
|
|
|
|
logger.info("参数同步指令已发送 - DeviceID: {}, Topic: {}, Payload: {}",
|
|
|
|
|
|
|
|
deviceId, downlinkTopic, payload);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
|
|
|
logger.error("发送参数同步指令异常: {}", e.getMessage(), e);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|