Files
smartDriveEEUniApp/common/bluetooth.js
2026-01-09 19:41:18 +08:00

517 lines
14 KiB
JavaScript
Raw 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.

/**
* 蓝牙录音设备管理工具类
* 支持蓝牙设备搜索、连接、数据接收等功能
*/
class BluetoothRecorder {
constructor() {
this.adapterState = false // 蓝牙适配器状态
this.isScanning = false // 是否正在扫描
this.connectedDeviceId = null // 已连接的设备ID
this.deviceList = [] // 发现的设备列表
this.services = [] // 蓝牙服务列表
this.characteristics = [] // 特征值列表
this.notifyCharacteristicId = null // 用于接收数据的特征值ID
this.writeCharacteristicId = null // 用于发送数据的特征值ID
// 音频数据缓冲区
this.audioBuffer = []
this.isReceiving = false
// 回调函数
this.onDeviceFound = null // 发现设备回调
this.onDeviceConnected = null // 设备连接回调
this.onDeviceDisconnected = null // 设备断开回调
this.onAudioDataReceived = null // 音频数据接收回调
this.onError = null // 错误回调
}
/**
* 初始化蓝牙适配器
*/
async initBluetoothAdapter() {
return new Promise((resolve, reject) => {
uni.openBluetoothAdapter({
success: (res) => {
console.log('蓝牙适配器初始化成功', res)
this.adapterState = true
this._listenAdapterState()
resolve(res)
},
fail: (err) => {
console.error('蓝牙适配器初始化失败', err)
this.adapterState = false
this._handleError('蓝牙适配器初始化失败', err)
reject(err)
}
})
})
}
/**
* 监听蓝牙适配器状态变化
*/
_listenAdapterState() {
uni.onBluetoothAdapterStateChange((res) => {
console.log('蓝牙适配器状态变化', res)
this.adapterState = res.available
if (!res.available) {
this.connectedDeviceId = null
this._handleError('蓝牙适配器不可用', res)
}
})
}
/**
* 开始搜索蓝牙设备
* @param {Object} options - 搜索选项
* @param {Array} options.services - 服务UUID列表可选
* @param {number} options.interval - 上报间隔(毫秒)
* @param {boolean} options.allowDuplicatesKey - 是否允许重复上报
*/
async startScan(options = {}) {
if (this.isScanning) {
console.warn('已经在扫描中')
return
}
if (!this.adapterState) {
await this.initBluetoothAdapter()
}
return new Promise((resolve, reject) => {
// 监听设备发现
uni.onBluetoothDeviceFound((res) => {
const devices = res.devices || []
devices.forEach(device => {
// 过滤已存在的设备
const exists = this.deviceList.find(d => d.deviceId === device.deviceId)
if (!exists) {
this.deviceList.push({
deviceId: device.deviceId,
name: device.name || '未知设备',
RSSI: device.RSSI,
advertisData: device.advertisData,
advertisServiceUUIDs: device.advertisServiceUUIDs,
localName: device.localName
})
// 触发设备发现回调
if (this.onDeviceFound) {
this.onDeviceFound(device)
}
}
})
})
// 开始搜索
uni.startBluetoothDevicesDiscovery({
services: options.services || [],
allowDuplicatesKey: options.allowDuplicatesKey || false,
interval: options.interval || 0,
success: (res) => {
console.log('开始搜索蓝牙设备', res)
this.isScanning = true
resolve(res)
},
fail: (err) => {
console.error('搜索蓝牙设备失败', err)
this.isScanning = false
this._handleError('搜索蓝牙设备失败', err)
reject(err)
}
})
})
}
/**
* 停止搜索蓝牙设备
*/
async stopScan() {
if (!this.isScanning) {
return
}
return new Promise((resolve, reject) => {
uni.stopBluetoothDevicesDiscovery({
success: (res) => {
console.log('停止搜索蓝牙设备', res)
this.isScanning = false
resolve(res)
},
fail: (err) => {
console.error('停止搜索失败', err)
this._handleError('停止搜索失败', err)
reject(err)
}
})
})
}
/**
* 连接蓝牙设备
* @param {string} deviceId - 设备ID
*/
async connectDevice(deviceId) {
if (this.connectedDeviceId === deviceId) {
console.warn('设备已连接')
return
}
return new Promise((resolve, reject) => {
uni.createBLEConnection({
deviceId: deviceId,
success: async (res) => {
console.log('蓝牙设备连接成功', res)
this.connectedDeviceId = deviceId
// 监听连接状态
this._listenConnectionState(deviceId)
try {
// 获取服务列表
await this.getServices(deviceId)
// 触发连接成功回调
if (this.onDeviceConnected) {
this.onDeviceConnected(deviceId)
}
resolve(res)
} catch (err) {
reject(err)
}
},
fail: (err) => {
console.error('蓝牙设备连接失败', err)
this._handleError('蓝牙设备连接失败', err)
reject(err)
}
})
})
}
/**
* 监听连接状态
*/
_listenConnectionState(deviceId) {
uni.onBLEConnectionStateChange((res) => {
console.log('蓝牙连接状态变化', res)
if (res.deviceId === deviceId) {
if (!res.connected) {
// 连接断开
this.connectedDeviceId = null
this.isReceiving = false
if (this.onDeviceDisconnected) {
this.onDeviceDisconnected(deviceId)
}
}
}
})
}
/**
* 断开蓝牙设备连接
*/
async disconnectDevice() {
if (!this.connectedDeviceId) {
return
}
return new Promise((resolve, reject) => {
uni.closeBLEConnection({
deviceId: this.connectedDeviceId,
success: (res) => {
console.log('蓝牙设备断开成功', res)
this.connectedDeviceId = null
this.isReceiving = false
this.audioBuffer = []
resolve(res)
},
fail: (err) => {
console.error('蓝牙设备断开失败', err)
this._handleError('蓝牙设备断开失败', err)
reject(err)
}
})
})
}
/**
* 获取蓝牙设备服务列表
* @param {string} deviceId - 设备ID
*/
async getServices(deviceId) {
return new Promise((resolve, reject) => {
uni.getBLEDeviceServices({
deviceId: deviceId,
success: async (res) => {
console.log('获取服务列表成功', res)
this.services = res.services || []
// 遍历服务,获取特征值
for (const service of this.services) {
try {
await this.getCharacteristics(deviceId, service.uuid)
} catch (err) {
console.warn('获取特征值失败', service.uuid, err)
}
}
resolve(res)
},
fail: (err) => {
console.error('获取服务列表失败', err)
this._handleError('获取服务列表失败', err)
reject(err)
}
})
})
}
/**
* 获取特征值列表
* @param {string} deviceId - 设备ID
* @param {string} serviceId - 服务UUID
*/
async getCharacteristics(deviceId, serviceId) {
return new Promise((resolve, reject) => {
uni.getBLEDeviceCharacteristics({
deviceId: deviceId,
serviceId: serviceId,
success: (res) => {
console.log('获取特征值成功', serviceId, res)
const chars = res.characteristics || []
this.characteristics.push(...chars)
// 查找可用于通知和写入的特征值
chars.forEach(char => {
// 查找可通知的特征值(用于接收音频数据)
if (char.properties.notify || char.properties.indicate) {
if (!this.notifyCharacteristicId) {
this.notifyCharacteristicId = {
deviceId: deviceId,
serviceId: serviceId,
characteristicId: char.uuid
}
// 启用通知
this.enableNotify(deviceId, serviceId, char.uuid)
}
}
// 查找可写入的特征值(用于发送命令)
if (char.properties.write || char.properties.writeNoResponse) {
if (!this.writeCharacteristicId) {
this.writeCharacteristicId = {
deviceId: deviceId,
serviceId: serviceId,
characteristicId: char.uuid
}
}
}
})
resolve(res)
},
fail: (err) => {
console.error('获取特征值失败', err)
reject(err)
}
})
})
}
/**
* 启用特征值通知(用于接收音频数据)
* @param {string} deviceId - 设备ID
* @param {string} serviceId - 服务UUID
* @param {string} characteristicId - 特征值UUID
*/
async enableNotify(deviceId, serviceId, characteristicId) {
return new Promise((resolve, reject) => {
uni.notifyBLECharacteristicValueChange({
deviceId: deviceId,
serviceId: serviceId,
characteristicId: characteristicId,
state: true, // 启用通知
success: (res) => {
console.log('启用通知成功', res)
// 监听特征值变化(接收音频数据)
uni.onBLECharacteristicValueChange((res) => {
this._handleAudioData(res.value)
})
this.isReceiving = true
resolve(res)
},
fail: (err) => {
console.error('启用通知失败', err)
this._handleError('启用通知失败', err)
reject(err)
}
})
})
}
/**
* 处理接收到的音频数据
* @param {ArrayBuffer} data - 音频数据
*/
_handleAudioData(data) {
// 将 ArrayBuffer 转换为 Base64 或保存到缓冲区
const base64 = uni.arrayBufferToBase64(data)
// 添加到缓冲区
this.audioBuffer.push({
data: base64,
timestamp: Date.now()
})
// 触发回调
if (this.onAudioDataReceived) {
this.onAudioDataReceived({
data: base64,
arrayBuffer: data,
timestamp: Date.now()
})
}
}
/**
* 读取特征值
* @param {string} deviceId - 设备ID
* @param {string} serviceId - 服务UUID
* @param {string} characteristicId - 特征值UUID
*/
async readCharacteristic(deviceId, serviceId, characteristicId) {
return new Promise((resolve, reject) => {
uni.readBLECharacteristicValue({
deviceId: deviceId,
serviceId: serviceId,
characteristicId: characteristicId,
success: (res) => {
console.log('读取特征值成功', res)
resolve(res)
},
fail: (err) => {
console.error('读取特征值失败', err)
this._handleError('读取特征值失败', err)
reject(err)
}
})
})
}
/**
* 写入特征值(发送命令到设备)
* @param {ArrayBuffer} value - 要写入的数据
*/
async writeCharacteristic(value) {
if (!this.writeCharacteristicId) {
throw new Error('未找到可写入的特征值')
}
const { deviceId, serviceId, characteristicId } = this.writeCharacteristicId
return new Promise((resolve, reject) => {
uni.writeBLECharacteristicValue({
deviceId: deviceId,
serviceId: serviceId,
characteristicId: characteristicId,
value: value,
success: (res) => {
console.log('写入特征值成功', res)
resolve(res)
},
fail: (err) => {
console.error('写入特征值失败', err)
this._handleError('写入特征值失败', err)
reject(err)
}
})
})
}
/**
* 获取已缓存的音频数据
*/
getAudioBuffer() {
return this.audioBuffer
}
/**
* 清空音频缓冲区
*/
clearAudioBuffer() {
this.audioBuffer = []
}
/**
* 合并音频数据为完整文件Base64格式
*/
mergeAudioData() {
return this.audioBuffer.map(item => item.data).join('')
}
/**
* 处理错误
*/
_handleError(message, error) {
console.error(`[BluetoothRecorder] ${message}`, error)
if (this.onError) {
this.onError({
message,
error
})
}
}
/**
* 关闭蓝牙适配器
*/
async closeBluetoothAdapter() {
// 先断开连接
if (this.connectedDeviceId) {
await this.disconnectDevice()
}
// 停止扫描
if (this.isScanning) {
await this.stopScan()
}
return new Promise((resolve, reject) => {
uni.closeBluetoothAdapter({
success: (res) => {
console.log('关闭蓝牙适配器成功', res)
this.adapterState = false
resolve(res)
},
fail: (err) => {
console.error('关闭蓝牙适配器失败', err)
reject(err)
}
})
})
}
/**
* 获取设备列表
*/
getDeviceList() {
return this.deviceList
}
/**
* 清空设备列表
*/
clearDeviceList() {
this.deviceList = []
}
}
// 导出单例
export default new BluetoothRecorder()