fastbee/yack-mini/miniprogram/pages/device/detail/detail.js
2026-09-05 10:19:07 +08:00

354 lines
8.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 设备详情页 - 实时数据
*/
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}`;
},
});