/** * 蓝牙 BLE 采集服务 */ const hex = require('../utils/hex.js'); // BLE 状态常量 const BLE_STATE = { DISCONNECTED: 'disconnected', CONNECTING: 'connecting', CONNECTED: 'connected', DISCOVERING: 'discovering', READY: 'ready', }; // 默认配置 const DEFAULT_CONFIG = { // 扫描超时时间(毫秒) scanTimeout: 10000, // 连接超时时间(毫秒) connectTimeout: 10000, // 默认服务 UUID(需根据实际设备配置) serviceUUIDs: [], }; class BleService { constructor() { this.state = BLE_STATE.DISCONNECTED; this.deviceId = null; this.serviceId = null; this.notifyCharacteristicId = null; this.writeCharacteristicId = null; this.onDataCallback = null; this.onStateChangeCallback = null; this.config = { ...DEFAULT_CONFIG }; // 监听器注册标志(防止叠加) this._listenersRegistered = false; } /** * 设置配置 * @param {Object} config */ setConfig(config) { this.config = { ...DEFAULT_CONFIG, ...config }; } /** * 设置数据回调 * @param {Function} callback - (data: {hex, buffer}) => void */ onData(callback) { this.onDataCallback = callback; } /** * 设置状态变化回调 * @param {Function} callback - (state) => void */ onStateChange(callback) { this.onStateChangeCallback = callback; } /** * 更新状态并通知 * @param {string} state */ _setState(state) { this.state = state; if (this.onStateChangeCallback) { this.onStateChangeCallback(state); } console.log('[BLE] 状态变更:', state); } /** * 初始化蓝牙适配器 * @returns {Promise} */ initBluetooth() { return new Promise((resolve, reject) => { // 移除旧监听器,防止叠加(微信 API 支持注册多个回调) if (this._listenersRegistered) { try { wx.offBLEConnectionStateChange(); wx.offBLECharacteristicValueChange(); } catch (e) { // 忽略移除失败 } } wx.openBluetoothAdapter({ success: () => { console.log('[BLE] 初始化成功'); // 监听连接状态变化(只注册一次) wx.onBLEConnectionStateChange((res) => { console.log('[BLE] 连接状态变化:', res); if (!res.connected) { this._setState(BLE_STATE.DISCONNECTED); this.deviceId = null; } }); // 监听特征值变化(只注册一次) wx.onBLECharacteristicValueChange((res) => { const hexData = hex.arrayBufferToHex(res.value); console.log('[BLE] 收到数据:', hexData); if (this.onDataCallback) { this.onDataCallback({ hex: hexData, buffer: res.value, deviceId: res.deviceId, serviceId: res.serviceId, characteristicId: res.characteristicId, }); } }); this._listenersRegistered = true; resolve(); }, fail: (err) => { console.error('[BLE] 初始化失败:', err); reject(new Error('蓝牙初始化失败: ' + (err.errMsg || '未知错误'))); }, }); }); } /** * 扫描蓝牙设备 * @param {Array} serviceUUIDs - 服务 UUID 列表(可选) * @param {number} timeout - 超时时间(毫秒) * @returns {Promise} 设备列表 */ scanDevices(serviceUUIDs = null, timeout = null) { return new Promise((resolve, reject) => { const uuids = serviceUUIDs || this.config.serviceUUIDs; const scanTimeout = timeout || this.config.scanTimeout; const devices = []; const deviceMap = new Map(); // 移除旧的扫描监听器,防止叠加 try { wx.offBluetoothDeviceFound(); } catch (e) { // 忽略移除失败 } // 开始扫描 wx.startBluetoothDevicesDiscovery({ services: uuids.length > 0 ? uuids : undefined, allowDuplicatesKey: false, success: () => { console.log('[BLE] 开始扫描, 超时:', scanTimeout); // 监听扫描结果 wx.onBluetoothDeviceFound((res) => { res.devices.forEach((device) => { // 去重 if (!deviceMap.has(device.deviceId)) { deviceMap.set(device.deviceId, true); devices.push({ deviceId: device.deviceId, name: device.name || '未知设备', localName: device.localName, advertisData: device.advertisData, RSSI: device.RSSI, }); } }); }); // 设置超时 setTimeout(() => { wx.stopBluetoothDevicesDiscovery({ success: () => { console.log('[BLE] 扫描完成, 发现设备:', devices.length); // 移除扫描监听器 try { wx.offBluetoothDeviceFound(); } catch (e) { // 忽略 } resolve(devices); }, fail: () => { try { wx.offBluetoothDeviceFound(); } catch (e) { // 忽略 } resolve(devices); }, }); }, scanTimeout); }, fail: (err) => { console.error('[BLE] 扫描失败:', err); reject(new Error('扫描失败: ' + (err.errMsg || '未知错误'))); }, }); }); } /** * 停止扫描 */ stopScan() { return new Promise((resolve) => { wx.stopBluetoothDevicesDiscovery({ success: () => { console.log('[BLE] 已停止扫描'); resolve(); }, fail: () => { resolve(); }, }); }); } /** * 连接设备 * @param {string} deviceId - 设备 ID * @param {string} serviceId - 服务 UUID(可选,不传则自动发现) * @returns {Promise} */ connectDevice(deviceId, serviceId = null) { return new Promise((resolve, reject) => { this._setState(BLE_STATE.CONNECTING); this.deviceId = deviceId; const connectTimeout = this.config.connectTimeout; let timeoutId = null; // 设置连接超时 timeoutId = setTimeout(() => { wx.closeBLEConnection({ deviceId: deviceId, complete: () => { this._setState(BLE_STATE.DISCONNECTED); reject(new Error('连接超时')); }, }); }, connectTimeout); // 发起连接 wx.createBLEConnection({ deviceId: deviceId, success: () => { clearTimeout(timeoutId); console.log('[BLE] 连接成功:', deviceId); this._setState(BLE_STATE.CONNECTED); // 获取 MTU(提高传输效率,iOS 最大 185,Android 建议 247) wx.setBLEMTU({ deviceId: deviceId, mtu: 247, complete: () => { // 发现服务 this._discoverServices(deviceId, serviceId) .then(resolve) .catch(reject); }, }); }, fail: (err) => { clearTimeout(timeoutId); this._setState(BLE_STATE.DISCONNECTED); console.error('[BLE] 连接失败:', err); reject(new Error('连接失败: ' + (err.errMsg || '未知错误'))); }, }); }); } /** * 发现服务和特征值 * @param {string} deviceId * @param {string} serviceId * @returns {Promise} */ _discoverServices(deviceId, serviceId = null) { return new Promise((resolve, reject) => { this._setState(BLE_STATE.DISCOVERING); wx.getBLEDeviceServices({ deviceId: deviceId, success: (res) => { console.log('[BLE] 发现服务:', res.services); const services = res.services; // 选择目标服务 let targetService = null; if (serviceId) { // 指定了服务 UUID targetService = services.find(s => s.uuid.toLowerCase().includes(serviceId.toLowerCase())); } else { // 自动选择:排除通用服务(GAP 0x1800, GATT 0x1801 等) const GENERIC_SERVICE_PREFIXES = ['00001800', '00001801', '0000180a', '0000180f']; targetService = services.find(s => !GENERIC_SERVICE_PREFIXES.some(p => s.uuid.toLowerCase().startsWith(p)) ); // 如果没有非通用服务,选择第一个主服务 if (!targetService) { targetService = services.find(s => s.isPrimary) || services[0]; } } if (!targetService) { reject(new Error('未找到目标服务')); return; } this.serviceId = targetService.uuid; console.log('[BLE] 目标服务:', this.serviceId); // 获取特征值 this._discoverCharacteristics(deviceId, this.serviceId) .then(resolve) .catch(reject); }, fail: (err) => { console.error('[BLE] 获取服务失败:', err); reject(new Error('获取服务失败')); }, }); }); } /** * 发现特征值 * @param {string} deviceId * @param {string} serviceId * @returns {Promise} */ _discoverCharacteristics(deviceId, serviceId) { return new Promise((resolve, reject) => { wx.getBLEDeviceCharacteristics({ deviceId: deviceId, serviceId: serviceId, success: (res) => { console.log('[BLE] 发现特征值:', res.characteristics); const characteristics = res.characteristics; // 查找通知特征值(通常具有 notify 或 indicate 属性) const notifyChar = characteristics.find(c => c.properties.notify || c.properties.indicate); // 查找写入特征值 const writeChar = characteristics.find(c => c.properties.write || c.properties.writeNoResponse); if (notifyChar) { this.notifyCharacteristicId = notifyChar.uuid; console.log('[BLE] 通知特征值:', this.notifyCharacteristicId); // 启用通知 wx.notifyBLECharacteristicValueChange({ deviceId: deviceId, serviceId: serviceId, characteristicId: this.notifyCharacteristicId, state: true, success: () => { console.log('[BLE] 已启用通知'); if (writeChar) { this.writeCharacteristicId = writeChar.uuid; console.log('[BLE] 写入特征值:', this.writeCharacteristicId); } this._setState(BLE_STATE.READY); resolve({ serviceId: this.serviceId, notifyCharacteristicId: this.notifyCharacteristicId, writeCharacteristicId: this.writeCharacteristicId, }); }, fail: (err) => { console.error('[BLE] 启用通知失败:', err); reject(new Error('启用通知失败')); }, }); } else { // 没有通知特征值,但可能有写入特征值 if (writeChar) { this.writeCharacteristicId = writeChar.uuid; console.log('[BLE] 写入特征值:', this.writeCharacteristicId); } this._setState(BLE_STATE.READY); resolve({ serviceId: this.serviceId, notifyCharacteristicId: null, writeCharacteristicId: this.writeCharacteristicId, }); } }, fail: (err) => { console.error('[BLE] 获取特征值失败:', err); reject(new Error('获取特征值失败')); }, }); }); } /** * 写入数据 * @param {string} hexData - 十六进制字符串 * @returns {Promise} */ writeCommand(hexData) { return new Promise((resolve, reject) => { if (!this.deviceId || !this.serviceId || !this.writeCharacteristicId) { reject(new Error('设备未连接或无写入特征值')); return; } const buffer = hex.hexToArrayBuffer(hexData); wx.writeBLECharacteristicValue({ deviceId: this.deviceId, serviceId: this.serviceId, characteristicId: this.writeCharacteristicId, value: buffer, success: () => { console.log('[BLE] 写入成功:', hexData); resolve(); }, fail: (err) => { console.error('[BLE] 写入失败:', err); reject(new Error('写入失败: ' + (err.errMsg || '未知错误'))); }, }); }); } /** * 断开连接 */ disconnect() { return new Promise((resolve) => { if (!this.deviceId) { resolve(); return; } wx.closeBLEConnection({ deviceId: this.deviceId, success: () => { console.log('[BLE] 已断开连接'); }, complete: () => { this._setState(BLE_STATE.DISCONNECTED); this.deviceId = null; this.serviceId = null; this.notifyCharacteristicId = null; this.writeCharacteristicId = null; resolve(); }, }); }); } /** * 关闭蓝牙适配器 */ closeBluetooth() { return new Promise((resolve) => { this.disconnect().then(() => { wx.closeBluetoothAdapter({ success: () => { console.log('[BLE] 已关闭蓝牙适配器'); }, complete: () => { resolve(); }, }); }); }); } /** * 获取当前状态 * @returns {string} */ getState() { return this.state; } /** * 是否已连接 * @returns {boolean} */ isConnected() { return this.state === BLE_STATE.READY || this.state === BLE_STATE.CONNECTED; } /** * 清除数据回调(页面卸载时调用) */ clearCallbacks() { this.onDataCallback = null; this.onStateChangeCallback = null; console.log('[BLE] 已清除回调'); } } // 导出单例 const bleService = new BleService(); module.exports = { BLE_STATE, BleService, bleService, };