354 lines
8.7 KiB
JavaScript
354 lines
8.7 KiB
JavaScript
/**
|
||
* 设备详情页 - 实时数据
|
||
*/
|
||
const ble = require('../../../services/ble.js');
|
||
const mqtt = require('../../../services/mqtt-client.js');
|
||
const storage = require('../../../utils/storage.js');
|
||
const auth = require('../../../utils/auth.js');
|
||
|
||
const app = getApp();
|
||
|
||
Page({
|
||
data: {
|
||
device: null,
|
||
bleConnected: false,
|
||
mqttConnected: false,
|
||
connecting: false,
|
||
mqttConnecting: false,
|
||
|
||
// 数据列表
|
||
dataList: [],
|
||
maxDataCount: 50,
|
||
|
||
// 统计
|
||
stats: {
|
||
received: 0,
|
||
uploaded: 0,
|
||
failed: 0,
|
||
},
|
||
},
|
||
|
||
onLoad(options) {
|
||
const sn = options.sn;
|
||
if (!sn) {
|
||
wx.showToast({ title: '设备信息缺失', icon: 'none' });
|
||
setTimeout(() => wx.navigateBack(), 500);
|
||
return;
|
||
}
|
||
|
||
// 从全局或存储获取设备信息
|
||
let device = app.globalData.currentDevice;
|
||
if (!device || device.serialNumber !== sn) {
|
||
device = storage.getDevice(sn);
|
||
}
|
||
|
||
if (!device) {
|
||
wx.showToast({ title: '设备不存在', icon: 'none' });
|
||
setTimeout(() => wx.navigateBack(), 500);
|
||
return;
|
||
}
|
||
|
||
this.setData({ device });
|
||
|
||
// 监听 BLE 状态变化
|
||
ble.bleService.onStateChange((state) => {
|
||
const connected = state === ble.BLE_STATE.READY || state === ble.BLE_STATE.CONNECTED;
|
||
this.setData({ bleConnected: connected });
|
||
});
|
||
|
||
// 监听 BLE 数据
|
||
ble.bleService.onData((data) => {
|
||
this._handleBleData(data);
|
||
});
|
||
|
||
// 监听 MQTT 状态变化
|
||
mqtt.mqttClient.onStateChange((state) => {
|
||
const connected = state === mqtt.MQTT_STATE.CONNECTED;
|
||
this.setData({ mqttConnected: connected });
|
||
});
|
||
},
|
||
|
||
onUnload() {
|
||
// 清除回调,防止页面销毁后 setData 报错
|
||
ble.bleService.clearCallbacks();
|
||
mqtt.mqttClient.clearCallbacks();
|
||
|
||
// 断开连接
|
||
ble.bleService.disconnect();
|
||
mqtt.mqttClient.disconnect();
|
||
},
|
||
|
||
/**
|
||
* 处理 BLE 数据
|
||
*/
|
||
_handleBleData(data) {
|
||
const now = new Date();
|
||
const time = this._formatTime(now);
|
||
|
||
// 使用唯一 ID 标识数据项(防止重复数据更新错乱)
|
||
const dataItem = {
|
||
id: `${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
|
||
hex: data.hex,
|
||
time: time,
|
||
uploaded: false,
|
||
};
|
||
|
||
let dataList = this.data.dataList;
|
||
dataList.unshift(dataItem);
|
||
|
||
// 限制数量
|
||
if (dataList.length > this.data.maxDataCount) {
|
||
dataList = dataList.slice(0, this.data.maxDataCount);
|
||
}
|
||
|
||
this.setData({
|
||
dataList,
|
||
'stats.received': this.data.stats.received + 1,
|
||
});
|
||
|
||
// 如果 MQTT 已连接且设备已启用,自动上报
|
||
if (this.data.mqttConnected && this.data.device.enabled) {
|
||
this._uploadData(data.hex, dataItem.id);
|
||
} else {
|
||
// MQTT 未连接,缓存数据(离线缓存)
|
||
storage.cachePendingData({
|
||
deviceId: this.data.device.serialNumber,
|
||
productId: this.data.device.productId,
|
||
hex: data.hex,
|
||
dataId: dataItem.id,
|
||
});
|
||
}
|
||
},
|
||
|
||
/**
|
||
* 上报数据到 yack
|
||
* @param {string} hexData - 十六进制数据
|
||
* @param {string} [dataId] - 数据项 ID(用于更新列表状态)
|
||
* @throws 上报失败时抛出异常
|
||
*/
|
||
async _uploadData(hexData, dataId) {
|
||
const device = this.data.device;
|
||
if (!device) return;
|
||
|
||
const topic = auth.buildPropertyPostTopic(device.productId, device.serialNumber);
|
||
|
||
try {
|
||
await mqtt.mqttClient.publish(topic, hexData);
|
||
console.log('[Detail] 上报成功:', hexData);
|
||
|
||
// 更新状态
|
||
this.setData({
|
||
'stats.uploaded': this.data.stats.uploaded + 1,
|
||
});
|
||
|
||
// 更新 dataList 中的状态(按唯一 ID 查找)
|
||
if (dataId) {
|
||
const dataList = this.data.dataList;
|
||
const index = dataList.findIndex(d => d.id === dataId);
|
||
if (index >= 0) {
|
||
this.setData({
|
||
[`dataList[${index}].uploaded`]: true,
|
||
});
|
||
}
|
||
}
|
||
|
||
} catch (err) {
|
||
console.error('[Detail] 上报失败:', err);
|
||
this.setData({
|
||
'stats.failed': this.data.stats.failed + 1,
|
||
});
|
||
// 抛出异常,供调用方统计失败
|
||
throw err;
|
||
}
|
||
},
|
||
|
||
/**
|
||
* 连接/断开 BLE
|
||
*/
|
||
async handleConnect() {
|
||
const { device, bleConnected } = this.data;
|
||
if (!device) return;
|
||
|
||
if (bleConnected) {
|
||
// 断开连接
|
||
await ble.bleService.disconnect();
|
||
this.setData({ bleConnected: false });
|
||
return;
|
||
}
|
||
|
||
this.setData({ connecting: true });
|
||
|
||
try {
|
||
// 初始化蓝牙
|
||
await ble.bleService.initBluetooth();
|
||
// 连接设备
|
||
await ble.bleService.connectDevice(device.deviceId, device.serviceUUID);
|
||
this.setData({ bleConnected: true });
|
||
wx.showToast({ title: 'BLE 已连接', icon: 'success' });
|
||
} catch (err) {
|
||
console.error('[Detail] BLE 连接失败:', err);
|
||
wx.showToast({
|
||
title: err.message || 'BLE 连接失败',
|
||
icon: 'none',
|
||
});
|
||
} finally {
|
||
this.setData({ connecting: false });
|
||
}
|
||
},
|
||
|
||
/**
|
||
* 连接/断开 MQTT
|
||
*/
|
||
async handleMqttConnect() {
|
||
const { device, mqttConnected } = this.data;
|
||
if (!device) return;
|
||
|
||
if (mqttConnected) {
|
||
// 断开连接
|
||
await mqtt.mqttClient.disconnect();
|
||
this.setData({ mqttConnected: false });
|
||
return;
|
||
}
|
||
|
||
this.setData({ mqttConnecting: true });
|
||
|
||
try {
|
||
// 获取 MQTT 凭证
|
||
const credentials = auth.getMqttCredentialsFromDevice(device);
|
||
if (!credentials) {
|
||
throw new Error('获取 MQTT 凭证失败');
|
||
}
|
||
|
||
// 连接 MQTT
|
||
await mqtt.mqttClient.connect(credentials);
|
||
this.setData({ mqttConnected: true });
|
||
wx.showToast({ title: 'MQTT 已连接', icon: 'success' });
|
||
|
||
// 上传缓存的离线数据
|
||
this._uploadPendingData();
|
||
|
||
} catch (err) {
|
||
console.error('[Detail] MQTT 连接失败:', err);
|
||
wx.showToast({
|
||
title: err.message || 'MQTT 连接失败',
|
||
icon: 'none',
|
||
});
|
||
} finally {
|
||
this.setData({ mqttConnecting: false });
|
||
}
|
||
},
|
||
|
||
/**
|
||
* 上传缓存的离线数据
|
||
*/
|
||
async _uploadPendingData() {
|
||
const pendingList = storage.getPendingData();
|
||
if (pendingList.length === 0) return;
|
||
|
||
console.log('[Detail] 开始上传离线缓存数据:', pendingList.length, '条');
|
||
const device = this.data.device;
|
||
|
||
let uploaded = 0;
|
||
let failed = 0;
|
||
|
||
for (const item of pendingList) {
|
||
// 只上传当前设备的数据
|
||
if (item.deviceId === device.serialNumber) {
|
||
try {
|
||
const topic = auth.buildPropertyPostTopic(device.productId, device.serialNumber);
|
||
await mqtt.mqttClient.publish(topic, item.hex);
|
||
uploaded++;
|
||
} catch (err) {
|
||
console.error('[Detail] 离线数据上传失败:', item.hex, err);
|
||
failed++;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 清除已处理的缓存数据
|
||
storage.clearPendingData();
|
||
|
||
if (uploaded > 0 || failed > 0) {
|
||
console.log('[Detail] 离线数据上传完成:', uploaded, '成功,', failed, '失败');
|
||
}
|
||
},
|
||
|
||
/**
|
||
* 上报单条数据
|
||
*/
|
||
async handleUploadSingle(e) {
|
||
const index = e.currentTarget.dataset.index;
|
||
const data = this.data.dataList[index];
|
||
if (!data || data.uploaded) return;
|
||
|
||
if (!this.data.mqttConnected) {
|
||
wx.showToast({ title: 'MQTT 未连接', icon: 'none' });
|
||
return;
|
||
}
|
||
|
||
try {
|
||
await this._uploadData(data.hex, data.id);
|
||
} catch (err) {
|
||
wx.showToast({ title: err.message || '上报失败', icon: 'none' });
|
||
}
|
||
},
|
||
|
||
/**
|
||
* 全部上报
|
||
*/
|
||
async handleUploadAll() {
|
||
const { dataList, mqttConnected } = this.data;
|
||
if (!mqttConnected || dataList.length === 0) return;
|
||
|
||
wx.showLoading({ title: '上报中...', mask: true });
|
||
|
||
let uploaded = 0;
|
||
let failed = 0;
|
||
|
||
for (const data of dataList) {
|
||
if (!data.uploaded) {
|
||
try {
|
||
await this._uploadData(data.hex, data.id);
|
||
uploaded++;
|
||
} catch (err) {
|
||
failed++;
|
||
}
|
||
}
|
||
}
|
||
|
||
wx.hideLoading();
|
||
|
||
wx.showToast({
|
||
title: `上报完成: ${uploaded} 成功, ${failed} 失败`,
|
||
icon: 'none',
|
||
});
|
||
},
|
||
|
||
/**
|
||
* 清空数据
|
||
*/
|
||
handleClearData() {
|
||
wx.showModal({
|
||
title: '清空数据',
|
||
content: '确定清空所有接收到的数据吗?',
|
||
success: (res) => {
|
||
if (res.confirm) {
|
||
this.setData({
|
||
dataList: [],
|
||
stats: { received: 0, uploaded: 0, failed: 0 },
|
||
});
|
||
}
|
||
},
|
||
});
|
||
},
|
||
|
||
/**
|
||
* 格式化时间
|
||
*/
|
||
_formatTime(date) {
|
||
const h = date.getHours().toString().padStart(2, '0');
|
||
const m = date.getMinutes().toString().padStart(2, '0');
|
||
const s = date.getSeconds().toString().padStart(2, '0');
|
||
return `${h}:${m}:${s}`;
|
||
},
|
||
}); |