342 lines
9.1 KiB
JavaScript
342 lines
9.1 KiB
JavaScript
export class BluetoothManager {
|
|
constructor() {
|
|
this.isInitialized = false
|
|
this.deviceFoundCallback = null
|
|
this.onReceiveDataCallback = null
|
|
this.isConnected = false
|
|
this.retryTimes = 3 // 重试次数
|
|
this.retryDelay = 1000 // 重试延迟(ms)
|
|
|
|
// 添加常用的服务和特征值UUID
|
|
this.SERVICE_UUID = "000000FF-0000-1000-8000-00805F9B34FB"
|
|
this.WRITE_CHARACTERISTIC_UUID = "0000FF01-0000-1000-8000-00805F9B34FB"
|
|
this.NOTIFY_CHARACTERISTIC_UUID = "0000FF02-0000-1000-8000-00805F9B34FB"
|
|
}
|
|
|
|
// 延迟函数
|
|
delay(ms) {
|
|
return new Promise(resolve => setTimeout(resolve, ms))
|
|
}
|
|
|
|
// 带重试的异步操作
|
|
async withRetry(operation, errorMessage) {
|
|
let lastError
|
|
for(let i = 0; i < this.retryTimes; i++) {
|
|
try {
|
|
return await operation()
|
|
} catch(error) {
|
|
console.log(`${errorMessage} 尝试第 ${i + 1} 次失败:`, error)
|
|
lastError = error
|
|
if(i < this.retryTimes - 1) {
|
|
await this.delay(this.retryDelay)
|
|
}
|
|
}
|
|
}
|
|
throw lastError
|
|
}
|
|
|
|
async init() {
|
|
if (this.isInitialized) return
|
|
|
|
try {
|
|
// 先关闭蓝牙适配器再重新打开
|
|
try {
|
|
await my.closeBluetoothAdapter()
|
|
await this.delay(1000)
|
|
} catch(e) {
|
|
console.log('关闭蓝牙适配器失败,可能本来就是关闭状态')
|
|
}
|
|
|
|
await this.withRetry(
|
|
() => this.openBluetoothAdapter(),
|
|
'初始化蓝牙适配器失败'
|
|
)
|
|
|
|
this.isInitialized = true
|
|
|
|
// 监听蓝牙连接状态变化
|
|
my.onBLEConnectionStateChanged((res) => {
|
|
this.isConnected = res.connected
|
|
if (!res.connected) {
|
|
console.log('蓝牙连接已断开')
|
|
}
|
|
})
|
|
|
|
// 监听特征值变化
|
|
my.onBLECharacteristicValueChange((res) => {
|
|
if(this.onReceiveDataCallback) {
|
|
const value = this.ab2str(res.value)
|
|
this.onReceiveDataCallback(value)
|
|
}
|
|
})
|
|
|
|
} catch (error) {
|
|
this.isInitialized = false
|
|
throw new Error('蓝牙初始化失败: ' + error.message)
|
|
}
|
|
}
|
|
|
|
async openBluetoothAdapter() {
|
|
return new Promise((resolve, reject) => {
|
|
my.openBluetoothAdapter({
|
|
success: () => {
|
|
resolve()
|
|
},
|
|
fail: (error) => {
|
|
if(error.error === 10015) {
|
|
reject(new Error('蓝牙未打开,请打开手机蓝牙'))
|
|
} else {
|
|
reject(error)
|
|
}
|
|
}
|
|
})
|
|
})
|
|
}
|
|
|
|
// ArrayBuffer转字符串
|
|
ab2str(buf) {
|
|
return String.fromCharCode.apply(null, new Uint8Array(buf))
|
|
}
|
|
|
|
// 字符串转ArrayBuffer
|
|
str2ab(str) {
|
|
let buf = new ArrayBuffer(str.length)
|
|
let bufView = new Uint8Array(buf)
|
|
for (let i = 0, strLen = str.length; i < strLen; i++) {
|
|
bufView[i] = str.charCodeAt(i)
|
|
}
|
|
return buf
|
|
}
|
|
|
|
async startSearch() {
|
|
if(!this.isInitialized) {
|
|
await this.init()
|
|
}
|
|
|
|
await this.withRetry(async () => {
|
|
return new Promise((resolve, reject) => {
|
|
my.startBluetoothDevicesDiscovery({
|
|
allowDuplicatesKey: false,
|
|
success: () => {
|
|
this.startListeningForDevices()
|
|
resolve()
|
|
},
|
|
fail: (error) => {
|
|
reject(error)
|
|
}
|
|
})
|
|
})
|
|
}, '开始搜索设备失败')
|
|
}
|
|
|
|
startListeningForDevices() {
|
|
my.onBluetoothDeviceFound((res) => {
|
|
if (this.deviceFoundCallback) {
|
|
res.devices.forEach(device => {
|
|
const deviceName = device.name || device.localName
|
|
// 只处理前缀为BBLE的设备
|
|
if (deviceName && deviceName.startsWith('BBLE')) {
|
|
this.deviceFoundCallback({
|
|
name: deviceName,
|
|
deviceId: device.deviceId,
|
|
RSSI: device.RSSI,
|
|
advertisData: device.advertisData,
|
|
connected: false,
|
|
mac: deviceName.substring(4)
|
|
})
|
|
}
|
|
})
|
|
}
|
|
})
|
|
}
|
|
|
|
async stopSearch() {
|
|
return new Promise((resolve, reject) => {
|
|
my.stopBluetoothDevicesDiscovery({
|
|
success: () => {
|
|
resolve()
|
|
},
|
|
fail: (error) => {
|
|
reject(error)
|
|
}
|
|
})
|
|
})
|
|
}
|
|
|
|
async connect(deviceId) {
|
|
console.log('开始连接设备:', deviceId)
|
|
try {
|
|
// 先断开可能存在的连接
|
|
try {
|
|
await this.disconnect(deviceId)
|
|
await this.delay(1000)
|
|
} catch(e) {
|
|
console.log('断开旧连接失败,可能本来就未连接')
|
|
}
|
|
|
|
// 重试连接流程
|
|
await this.withRetry(async () => {
|
|
// 1. 建立连接
|
|
await this.createConnection(deviceId)
|
|
|
|
// 2. 获取服务
|
|
const services = await this.getServices(deviceId)
|
|
const targetService = services.find(s => s.uuid.toLowerCase() === this.SERVICE_UUID.toLowerCase())
|
|
if (!targetService) throw new Error('未找到目标服务')
|
|
|
|
// 3. 获取特征值
|
|
const characteristics = await this.getCharacteristics(deviceId, targetService.uuid)
|
|
|
|
// 4. 启用通知
|
|
await this.enableNotify(deviceId, targetService.uuid, this.NOTIFY_CHARACTERISTIC_UUID)
|
|
|
|
this.isConnected = true
|
|
return true
|
|
}, '连接设备失败')
|
|
|
|
} catch (error) {
|
|
this.isConnected = false
|
|
console.error('连接失败:', error)
|
|
throw error
|
|
}
|
|
}
|
|
|
|
async createConnection(deviceId) {
|
|
return new Promise((resolve, reject) => {
|
|
my.connectBLEDevice({
|
|
deviceId,
|
|
timeout: 15000,
|
|
success: (res) => {
|
|
resolve(res)
|
|
},
|
|
fail: (error) => {
|
|
if(error.error === 10015) {
|
|
reject(new Error('蓝牙连接失败,请检查设备是否在范围内'))
|
|
} else {
|
|
reject(error)
|
|
}
|
|
}
|
|
})
|
|
})
|
|
}
|
|
|
|
async getServices(deviceId) {
|
|
return new Promise((resolve, reject) => {
|
|
my.getBLEDeviceServices({
|
|
deviceId,
|
|
success: (res) => {
|
|
console.log('获取服务成功:', res.services)
|
|
resolve(res.services)
|
|
},
|
|
fail: (error) => {
|
|
console.error('获取服务失败:', error)
|
|
reject(error)
|
|
}
|
|
})
|
|
})
|
|
}
|
|
|
|
async getCharacteristics(deviceId, serviceId) {
|
|
return new Promise((resolve, reject) => {
|
|
my.getBLEDeviceCharacteristics({
|
|
deviceId,
|
|
serviceId,
|
|
success: (res) => {
|
|
console.log('获取特征值成功:', res.characteristics)
|
|
resolve(res.characteristics)
|
|
},
|
|
fail: (error) => {
|
|
console.error('获取特征值失败:', error)
|
|
reject(error)
|
|
}
|
|
})
|
|
})
|
|
}
|
|
|
|
async enableNotify(deviceId, serviceId, characteristicId) {
|
|
return new Promise((resolve, reject) => {
|
|
my.notifyBLECharacteristicValueChange({
|
|
deviceId,
|
|
serviceId,
|
|
characteristicId,
|
|
state: true,
|
|
success: (res) => {
|
|
resolve(res)
|
|
},
|
|
fail: (error) => {
|
|
reject(error)
|
|
}
|
|
})
|
|
})
|
|
}
|
|
|
|
async disconnect(deviceId) {
|
|
return new Promise((resolve, reject) => {
|
|
my.disconnectBLEDevice({
|
|
deviceId,
|
|
success: () => {
|
|
this.isConnected = false
|
|
resolve()
|
|
},
|
|
fail: (error) => {
|
|
reject(error)
|
|
}
|
|
})
|
|
})
|
|
}
|
|
|
|
// 发送数据
|
|
async sendData(deviceId, data) {
|
|
if (!this.isConnected) {
|
|
throw new Error('蓝牙未连接')
|
|
}
|
|
|
|
const buffer = this.str2ab(data)
|
|
return this.writeBLECharacteristicValue(
|
|
deviceId,
|
|
this.SERVICE_UUID,
|
|
this.WRITE_CHARACTERISTIC_UUID,
|
|
buffer
|
|
)
|
|
}
|
|
|
|
async writeBLECharacteristicValue(deviceId, serviceId, characteristicId, value) {
|
|
return new Promise((resolve, reject) => {
|
|
my.writeBLECharacteristicValue({
|
|
deviceId,
|
|
serviceId,
|
|
characteristicId,
|
|
value,
|
|
success: () => {
|
|
resolve()
|
|
},
|
|
fail: (error) => {
|
|
reject(error)
|
|
}
|
|
})
|
|
})
|
|
}
|
|
|
|
onDeviceFound(callback) {
|
|
this.deviceFoundCallback = callback
|
|
}
|
|
|
|
// 注册数据接收回调
|
|
onReceiveData(callback) {
|
|
this.onReceiveDataCallback = callback
|
|
}
|
|
|
|
async destroy() {
|
|
if (this.isInitialized) {
|
|
try {
|
|
await this.stopSearch()
|
|
await this.delay(1000)
|
|
await my.closeBluetoothAdapter()
|
|
this.isInitialized = false
|
|
this.isConnected = false
|
|
} catch (error) {
|
|
console.error('销毁蓝牙实例失败:', error)
|
|
}
|
|
}
|
|
}
|
|
} |