/** * 蓝牙多设备管理模块(Android 优化版) * 功能: * - 统一初始化蓝牙适配器 * - 扫描设备(可指定服务 UUID) * - 直接连接已知设备(通过 deviceId/MAC) * - 管理多个设备(打印机、电子秤等) * - 写入数据、接收数据、连接状态监听 * - 自动重连(可选) * * 使用方法: * import bluetoothManager from '@/utils/BluetoothManager.js'; * await bluetoothManager.init(); * bluetoothManager.setDeviceFoundCallback(...); * await bluetoothManager.connectKnownDevice(deviceId, serviceUUID, writeUUID); */ class BluetoothManager { constructor() { this.adapterOpen = false; // 蓝牙适配器是否已打开 this.discovering = false; // 是否正在扫描 this.devices = new Map(); // 存储已连接设备的信息:key=deviceId, value={ serviceId, writeCharId, notifyCharId, name } // 全局回调(只注册一次) this.globalListeners = { onDeviceFound: null, onConnectionChange: null, onDataReceived: null, }; } /** * 初始化蓝牙适配器(全局只需调用一次) * @returns {Promise} */ async init() { if (this.adapterOpen) return; return new Promise((resolve, reject) => { uni.openBluetoothAdapter({ success: () => { this.adapterOpen = true; this._registerGlobalListeners(); resolve(); }, fail: (err) => { console.error('蓝牙初始化失败', err); reject(err); }, }); }); } /** * 注册全局唯一的监听器(避免覆盖) */ _registerGlobalListeners() { // 设备发现监听 uni.onBluetoothDeviceFound((res) => { const { devices } = res; if (this.globalListeners.onDeviceFound) { devices.forEach(device => this.globalListeners.onDeviceFound(device)); } }); // 连接状态变化监听 uni.onBLEConnectionStateChange((res) => { const { deviceId, connected } = res; if (this.globalListeners.onConnectionChange) { this.globalListeners.onConnectionChange(deviceId, connected); } // 如果连接断开,从 devices Map 中移除(可选,保留以便重连) if (!connected && this.devices.has(deviceId)) { // 注意:不断开 Map 中的记录,因为可能用于重连;但可以标记一个状态 // 这里不做自动删除,由业务层决定是否移除 } }); // 特征值数据接收监听 uni.onBLECharacteristicValueChange((res) => { const { deviceId, value } = res; if (this.globalListeners.onDataReceived) { this.globalListeners.onDataReceived(deviceId, value); } }); } /** * 设置设备发现回调 * @param {Function} callback (device) => {} */ setDeviceFoundCallback(callback) { this.globalListeners.onDeviceFound = callback; } /** * 设置连接状态变化回调 * @param {Function} callback (deviceId, connected) => {} */ setConnectionChangeCallback(callback) { this.globalListeners.onConnectionChange = callback; } /** * 设置数据接收回调 * @param {Function} callback (deviceId, arrayBuffer) => {} */ setDataReceivedCallback(callback) { this.globalListeners.onDataReceived = callback; } /** * 开始扫描设备 * @param {Array} services 要过滤的服务 UUID 数组(可选) * @returns {Promise} */ async startScan(services = []) { if (!this.adapterOpen) { throw new Error('蓝牙未初始化,请先调用 init()'); } if (this.discovering) { await this.stopScan(); } return new Promise((resolve, reject) => { uni.startBluetoothDevicesDiscovery({ services, success: () => { this.discovering = true; resolve(); }, fail: reject, }); }); } /** * 停止扫描 */ stopScan() { if (!this.discovering) return Promise.resolve(); return new Promise((resolve, reject) => { uni.stopBluetoothDevicesDiscovery({ success: () => { this.discovering = false; resolve(); }, fail: reject, }); }); } /** * 直接连接已知设备(不依赖扫描结果) * @param {string} deviceId 设备 MAC 地址(Android) * @param {string} targetServiceUUID 目标服务的 UUID * @param {string} writeUUID 写入特征值 UUID(可选,自动查找第一个可写) * @param {string} notifyUUID 通知特征值 UUID(可选,自动查找第一个可通知) * @returns {Promise} 返回设备信息 { deviceId, serviceId, writeCharId, notifyCharId } */ async connectKnownDevice(deviceId, targetServiceUUID, writeUUID = null, notifyUUID = null) { if (!this.adapterOpen) { throw new Error('蓝牙未初始化,请先调用 init()'); } // 如果已经连接且 Map 中有记录,直接返回(可选,但为了避免重复连接,先断开) if (this.devices.has(deviceId)) { await this.disconnectDevice(deviceId); } return new Promise((resolve, reject) => { uni.createBLEConnection({ deviceId, success: async () => { // 等待服务发现稳定(重要:解决连接成功但查不到服务的问题) await this._delay(800); try { // 1. 获取服务列表 const { services } = await this._getBLEDeviceServices(deviceId); const targetService = services.find(s => s.uuid.toLowerCase().includes(targetServiceUUID.toLowerCase())); if (!targetService) { throw new Error(`未找到服务 ${targetServiceUUID}`); } // 2. 获取特征值 const characteristics = await this._getBLEDeviceCharacteristics(deviceId, targetService.uuid); let writeChar = null; if (writeUUID) { writeChar = characteristics.find(c => c.uuid.toLowerCase() === writeUUID.toLowerCase()); } else { writeChar = characteristics.find(c => c.properties.write || c.properties.writeNoResponse); } if (!writeChar) { throw new Error('未找到可写入的特征值'); } let notifyChar = null; if (notifyUUID) { notifyChar = characteristics.find(c => c.uuid.toLowerCase() === notifyUUID.toLowerCase()); } else { notifyChar = characteristics.find(c => c.properties.notify || c.properties.indicate); } // 3. 启用通知(如果有 notify 特征值) if (notifyChar && (notifyChar.properties.notify || notifyChar.properties.indicate)) { await this._notifyBLECharacteristicValueChange(deviceId, targetService.uuid, notifyChar.uuid, true); } // 4. 存储设备信息 this.devices.set(deviceId, { serviceId: targetService.uuid, writeCharId: writeChar.uuid, notifyCharId: notifyChar ? notifyChar.uuid : null, }); resolve({ deviceId, serviceId: targetService.uuid, writeCharId: writeChar.uuid, notifyCharId: notifyChar ? notifyChar.uuid : null, }); } catch (err) { reject(err); } }, fail: reject, }); }); } /** * 向指定设备写入数据 * @param {string} deviceId * @param {ArrayBuffer} buffer * @returns {Promise} */ async writeData(deviceId, buffer) { const device = this.devices.get(deviceId); if (!device) { throw new Error('设备未连接或未初始化,请先调用 connectKnownDevice'); } return new Promise((resolve, reject) => { uni.writeBLECharacteristicValue({ deviceId, serviceId: device.serviceId, characteristicId: device.writeCharId, value: buffer, success: resolve, fail: reject, }); }); } /** * 断开指定设备 * @param {string} deviceId * @returns {Promise} */ disconnectDevice(deviceId) { if (!this.devices.has(deviceId)) return Promise.resolve(); return new Promise((resolve, reject) => { uni.closeBLEConnection({ deviceId, success: () => { this.devices.delete(deviceId); resolve(); }, fail: reject, }); }); } /** * 断开所有已连接设备并关闭蓝牙适配器 */ async closeAll() { const disconnectPromises = Array.from(this.devices.keys()).map(id => this.disconnectDevice(id)); await Promise.all(disconnectPromises); if (this.adapterOpen) { return new Promise((resolve) => { uni.closeBluetoothAdapter({ success: () => { this.adapterOpen = false; this.discovering = false; resolve(); }, fail: () => { this.adapterOpen = false; resolve(); }, }); }); } } // ---------- 内部辅助方法 ---------- _delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } _getBLEDeviceServices(deviceId) { return new Promise((resolve, reject) => { uni.getBLEDeviceServices({ deviceId, success: resolve, fail: reject, }); }); } _getBLEDeviceCharacteristics(deviceId, serviceId) { return new Promise((resolve, reject) => { uni.getBLEDeviceCharacteristics({ deviceId, serviceId, success: resolve, fail: reject, }); }); } _notifyBLECharacteristicValueChange(deviceId, serviceId, characteristicId, state = true) { return new Promise((resolve, reject) => { uni.notifyBLECharacteristicValueChange({ deviceId, serviceId, characteristicId, state, success: resolve, fail: reject, }); }); } /** * 仅连接设备并获取服务列表(不预设任何特征值) * 用于设备配置阶段,让用户手动选择服务和特征值 * @param {string} deviceId * @returns {Promise<{services: Array}>} 返回服务列表 */ async connectForDiscovery(deviceId) { if (!this.adapterOpen) { throw new Error('蓝牙未初始化'); } // 如果已连接,先断开 if (this.devices.has(deviceId)) { await this.disconnectDevice(deviceId); } return new Promise((resolve, reject) => { uni.createBLEConnection({ deviceId, success: async () => { await this._delay(800); try { const { services } = await this._getBLEDeviceServices(deviceId); // 暂不存储到 devices Map,因为还没确定特征值 resolve({ services, deviceId }); } catch (err) { reject(err); } }, fail: reject, }); }); } /** * 保存用户选择的配置,并完成最终连接(启用通知等) * @param {string} deviceId * @param {string} serviceId * @param {string} writeCharId * @param {string} notifyCharId (可选) * @returns {Promise} */ async saveAndConnect(deviceId, serviceId, writeCharId, notifyCharId = null) { if (!this.adapterOpen) { throw new Error('蓝牙未初始化'); } // 启用通知(如果需要) if (notifyCharId) { await this._notifyBLECharacteristicValueChange(deviceId, serviceId, notifyCharId, true); } // 存储设备信息 this.devices.set(deviceId, { serviceId: serviceId, writeCharId: writeCharId, notifyCharId: notifyCharId, }); return { deviceId, serviceId, writeCharId, notifyCharId }; } } export default new BluetoothManager();