Files
uni-pda/utils/BluetoothManager.js
2026-06-02 15:28:25 +08:00

383 lines
11 KiB
JavaScript
Raw Permalink 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.
/**
* 蓝牙多设备管理模块(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<void>}
*/
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<object>} 返回设备信息 { 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();