242 lines
7.7 KiB
JavaScript
242 lines
7.7 KiB
JavaScript
/**
|
||
* 扫描设备页面
|
||
*
|
||
* 新测点链路下此处只负责「连接蓝牙设备」:
|
||
* 扫描 → 点击设备 → 询问设备名称(选填)→ 登记保存 → 交由常驻网关 gateway 接管 BLE 连接。
|
||
* 设备不再需要配置弹窗(产品/MQTT 凭证统一在“我的”页配置),
|
||
* 设备编号自动取自 BLE 广播名(无则退化为 BLE 地址归一化),
|
||
* 设备名称仅作本地展示标签(可选填,留空沿用上次或退化为广播名/编号),不参与上报身份。
|
||
*/
|
||
const ble = require('../../../services/ble.js');
|
||
const storage = require('../../../utils/storage.js');
|
||
const { gateway } = require('../../../services/gateway.js');
|
||
const { LIMITS } = require('../../../utils/constants.js');
|
||
|
||
Page({
|
||
data: {
|
||
scanning: false,
|
||
connecting: false,
|
||
scannedDevices: [],
|
||
statusText: '准备扫描',
|
||
statusHint: '点击下方按钮开始扫描 BLE 设备',
|
||
},
|
||
|
||
onLoad() {
|
||
// 暂停正在跑的网关,避免常驻会话与扫描抢占同一 BLE 目标;
|
||
// 返回首页后 index.onShow → gateway.ensureStarted() 会自动恢复。
|
||
gateway.stop().then(() => this._initBluetooth());
|
||
},
|
||
|
||
onUnload() {
|
||
// 停止扫描
|
||
if (this.data.scanning) {
|
||
ble.bleService.stopScan();
|
||
}
|
||
},
|
||
|
||
/**
|
||
* 初始化蓝牙
|
||
*/
|
||
async _initBluetooth() {
|
||
try {
|
||
await ble.bleService.initBluetooth();
|
||
this.setData({
|
||
statusText: '蓝牙已就绪',
|
||
statusHint: '点击下方按钮开始扫描 BLE 设备',
|
||
});
|
||
} catch (err) {
|
||
console.error('[Scan] 初始化蓝牙失败:', err);
|
||
this.setData({
|
||
statusText: '蓝牙初始化失败',
|
||
statusHint: err.message || '请确保已开启蓝牙',
|
||
});
|
||
wx.showModal({
|
||
title: '蓝牙错误',
|
||
content: err.message || '请确保已开启蓝牙权限',
|
||
showCancel: false,
|
||
success: () => {
|
||
wx.navigateBack();
|
||
},
|
||
});
|
||
}
|
||
},
|
||
|
||
/**
|
||
* 开始扫描
|
||
*/
|
||
async handleScan() {
|
||
if (this.data.scanning) return;
|
||
|
||
this.setData({
|
||
scanning: true,
|
||
scannedDevices: [],
|
||
statusText: '正在扫描...',
|
||
statusHint: '请确保设备已开机且在附近',
|
||
});
|
||
|
||
try {
|
||
const devices = await ble.bleService.scanDevices();
|
||
this.setData({
|
||
scanning: false,
|
||
scannedDevices: devices,
|
||
statusText: devices.length > 0 ? `发现 ${devices.length} 个设备` : '扫描完成',
|
||
statusHint: devices.length > 0 ? '点击设备连接,名称可选填' : '未发现设备,请确保设备已开机',
|
||
});
|
||
} catch (err) {
|
||
console.error('[Scan] 扫描失败:', err);
|
||
this.setData({
|
||
scanning: false,
|
||
statusText: '扫描失败',
|
||
statusHint: err.message || '请重试',
|
||
});
|
||
}
|
||
},
|
||
|
||
/**
|
||
* 停止扫描
|
||
*/
|
||
async handleStopScan() {
|
||
await ble.bleService.stopScan();
|
||
this.setData({
|
||
scanning: false,
|
||
statusText: '已停止扫描',
|
||
});
|
||
},
|
||
|
||
/**
|
||
* 点击设备 - 询问设备名称(选填)后登记并交由网关连接
|
||
*
|
||
* 名称只是本地展示标签,serialNumber(上报身份)仍由 `_buildSerialNumber` 决定,
|
||
* 所以留空也能直接连;这里用二次确认弹窗替代「点一下就连上」,
|
||
* 便于顺手命名,也让误触有反悔余地。
|
||
*/
|
||
async handleSelectDevice(e) {
|
||
if (this.data.connecting) return;
|
||
const index = e.currentTarget.dataset.index;
|
||
const device = this.data.scannedDevices[index];
|
||
if (!device) return;
|
||
|
||
const serialNumber = this._buildSerialNumber(device);
|
||
if (!serialNumber) {
|
||
wx.showToast({ title: '无法识别该设备', icon: 'none' });
|
||
return;
|
||
}
|
||
|
||
const existing = storage.getDevice(serialNumber);
|
||
const inputName = await this._askDeviceName(device, serialNumber, existing);
|
||
// 取消(或弹窗未能弹出)则不连接,否则用户没有退出路径
|
||
if (inputName === null) return;
|
||
|
||
this.setData({ connecting: true });
|
||
wx.showLoading({ title: '连接中...', mask: true });
|
||
|
||
try {
|
||
// 登记设备(同名按 serialNumber 覆盖刷新 BLE 地址与信号)
|
||
const deviceConfig = {
|
||
deviceId: device.deviceId,
|
||
serialNumber: serialNumber,
|
||
name: this._resolveDeviceName(inputName, existing, device.name, serialNumber),
|
||
bleName: device.name,
|
||
RSSI: device.RSSI,
|
||
enabled: true,
|
||
connected: true,
|
||
createTime: Date.now(),
|
||
};
|
||
|
||
// 必须检查返回值:数量达上限时这里是失败的,继续往下会变成
|
||
// "提示已连接、实际没保存也没连"的静默失败
|
||
if (!storage.addDevice(deviceConfig)) {
|
||
wx.hideLoading();
|
||
wx.showToast({
|
||
title: `最多添加 ${LIMITS.MAX_DEVICE_COUNT} 个设备`,
|
||
icon: 'none',
|
||
duration: 2500,
|
||
});
|
||
return;
|
||
}
|
||
// 成为唯一启用设备(单 enabled 不变量)
|
||
storage.activateDevice(serialNumber);
|
||
|
||
const target = storage.getDevice(serialNumber);
|
||
const hasMqtt = !!storage.getMqttConfig();
|
||
|
||
wx.hideLoading();
|
||
wx.showToast({
|
||
title: hasMqtt ? '已连接设备' : '已连接,请在"我的"配置MQTT',
|
||
icon: 'none',
|
||
duration: hasMqtt ? 1500 : 2500,
|
||
});
|
||
|
||
// 由常驻网关接管 BLE 连接与采集
|
||
gateway.start(target || deviceConfig);
|
||
wx.navigateBack();
|
||
} catch (err) {
|
||
wx.hideLoading();
|
||
console.error('[Scan] 连接设备失败:', err);
|
||
wx.showToast({
|
||
title: err.message || '连接失败',
|
||
icon: 'none',
|
||
});
|
||
} finally {
|
||
this.setData({ connecting: false });
|
||
}
|
||
},
|
||
|
||
/**
|
||
* 弹窗询问设备名称(选填)
|
||
* @returns {Promise<string|null>} 确认返回输入内容(可能为空串);取消/弹窗失败返回 null
|
||
*/
|
||
_askDeviceName(device, serialNumber, existing) {
|
||
const fallback = this._resolveDeviceName('', existing, device.name, serialNumber);
|
||
const customized = this._isCustomizedName(existing);
|
||
return new Promise((resolve) => {
|
||
wx.showModal({
|
||
title: '设置设备名称',
|
||
editable: true,
|
||
// 已自定义过则回填,方便改名;仍是广播名/编号说明用户没命名过,留空不干扰
|
||
content: customized ? existing.name : '',
|
||
placeholderText: `选填,留空用:${fallback}`,
|
||
confirmText: '连接',
|
||
cancelText: '取消',
|
||
success: (res) => resolve(res.confirm ? (res.content || '').trim() : null),
|
||
// 弹窗都没弹出来时不静默连接,按取消处理,避免「点一下就莫名连上了」
|
||
fail: () => resolve(null),
|
||
});
|
||
});
|
||
},
|
||
|
||
/**
|
||
* 解析最终展示名称:本次输入 > 既有名称 > BLE 广播名 > 设备编号
|
||
* @returns {string}
|
||
*/
|
||
_resolveDeviceName(inputName, existing, bleName, serialNumber) {
|
||
return (inputName || '').trim()
|
||
|| (existing && existing.name)
|
||
|| (bleName || '').trim()
|
||
|| serialNumber;
|
||
},
|
||
|
||
/**
|
||
* 判断既有名称是否为用户自定义(而非广播名/编号的自动兜底值)
|
||
* @returns {boolean}
|
||
*/
|
||
_isCustomizedName(existing) {
|
||
const name = existing && existing.name;
|
||
if (!name) return false;
|
||
return name !== existing.bleName && name !== existing.serialNumber;
|
||
},
|
||
|
||
/**
|
||
* 生成设备编号:优先 BLE 广播名,无名称时退化为 BLE 地址归一化(去冒号取后12位)
|
||
* @param {Object} device - 扫描到的 BLE 设备
|
||
* @returns {string}
|
||
*/
|
||
_buildSerialNumber(device) {
|
||
const rawName = (device.name || device.localName || '').trim();
|
||
if (rawName) {
|
||
return rawName;
|
||
}
|
||
const rawId = (device.deviceId || '').replace(/[:\-\s]/g, '').toUpperCase();
|
||
return rawId ? rawId.slice(-12) : '';
|
||
},
|
||
}); |