注意事项
最近在重构微信小程序项目,因为业务涉及蓝牙连接相关,就重新整理一遍蓝牙连接的封装方法。
需要注意到是:
- 蓝牙配适器开启与初始化会在对象实例化的构造函数中执行,如果需要单独控制初始化时间点(涉及蓝牙授权弹窗的初次出现时机),可以将
initialize()
方法从构造函数中移除并改为public
方法,并在外部独立控制实例化的时间点; - 连接蓝牙设备的方法,是在实例化后使用
connect()
方法传入蓝牙设备名,再在wx.onBluetoothDeviceFound
中不断匹配,再用匹配的deviceId
连接。这种方法会有诸多隐患,包括但不限于蓝牙设备重名、搜索方法功耗较大等,这部分可以按照自己的需求自行调整优化; - 获取特征值、发送握手、校验位计算等方法各不相同,可以按照自己板子的需求调整内容与规则。
公共方法
// utils/bluetooth.ts
export default class Bluetooth {
/** 是否初始化 */
#initialized = false;
/** 连接状态 */
#connection = false;
/** 设备ID */
#deviceId = '';
/** 服务ID */
#serviceId = '';
/** 特征值 */
#characteristics = [] as WechatMiniprogram.BLECharacteristic[];
constructor () {
// 初始化蓝牙模块
this.initialize()
}
/**
* @private 开启蓝牙模块
*/
private openBluetoothAdapter () {
return new Promise((resolve, reject) => {
wx.openBluetoothAdapter({
success: () => {
this.#initialized = true;
resolve(true);
},
fail: () => {
this.#initialized = false;
reject(false);
},
complete: (res) => {
console.log('[Bluetooth] 开启蓝牙模块', res.errMsg)
}
})
})
}
/**
*@private 获取蓝牙模块状态
*/
private getBluetoothAdapterState () {
return new Promise((resolve, reject) => {
wx.getBluetoothAdapterState({
success: () => {
this.#initialized = true;
resolve(true);
},
fail: () => {
this.#initialized = false;
reject(false);
},
complete: (res) => {
console.log('[Bluetooth] 获取蓝牙模块状态', res.errMsg)
}
})
})
}
/**
* @private 搜索设备
* @returns {Array} 设备列表
*/
private getBluetoothDevicesList (deviceName: string): Promise<string> {
console.log('[Bluetooth] 开始搜索蓝牙设备')
return new Promise((resolve, reject) => {
wx.startBluetoothDevicesDiscovery({
services: [],
success: () => {
wx.onBluetoothDeviceFound((res) => {
if (res.devices[0].localName === deviceName) {
wx.stopBluetoothDevicesDiscovery()
resolve(res.devices[0]['deviceId'])
}
})
},
fail: (err) => {
console.log('[Bluetooth] 蓝牙设备搜索失败', err)
reject()
}
})
})
}
/**
* @private 连接设备
* @param deviceId
* @returns
*/
private createBLEConnection (deviceId: string) {
wx.showLoading({ title: '设备连接中' })
return new Promise((resolve, reject) => {
wx.createBLEConnection({
deviceId,
success: () => {
console.log('[Bluetooth] 设备连接成功')
resolve(true);
},
fail: (err) => {
wx.showToast({ title: '设备连接失败', icon: 'none' })
console.log('[Bluetooth] 设备连接失败', err.errMsg)
reject(false);
},
complete: () => {
wx.stopBluetoothDevicesDiscovery()
wx.hideLoading()
}
})
})
}
/**
* @private 获取蓝牙设备所有服务
* @param {string} deviceId 蓝牙设备 id
*/
private getBLEDeviceServices (deviceId: string): Promise<string> {
const _this = this
return new Promise((resolve, reject) => {
wx.getBLEDeviceServices({
deviceId,
success: (res) => {
_this.#serviceId = res.services[1].uuid
resolve(res.services[1].uuid)
},
fail: () => {
reject(null)
},
complete: (res) => {
console.log('[Bluetooth] 获取蓝牙设备的所有服务', res.errMsg)
}
})
})
}
/**
* @private 获取所有特征值
* @param deviceId
* @param serviceId
* @returns
*/
private getBLEDeviceCharacteristics (deviceId: string, serviceId: string): Promise<WechatMiniprogram.BLECharacteristic[]> {
return new Promise((resolve, reject) => {
wx.getBLEDeviceCharacteristics({
deviceId,
serviceId,
success: (res) => {
resolve(res.characteristics)
},
fail: () => {
reject(null)
},
complete: (res) => {
console.log('[Bluetooth] 获取设备特征值', res.errMsg)
}
})
})
}
/**
* @private 启用蓝牙低功耗设备特征值变化时的 notify 功能
* @param deviceId 蓝牙设备 id
* @param serviceId 蓝牙服务 uuid
* @returns
*/
private notifyBLECharacteristicValueChange (deviceId: string, serviceId: string): Promise<boolean> {
const characteristicId = this.#characteristics[1]?.uuid || ''
return new Promise((resolve, reject) => {
wx.notifyBLECharacteristicValueChange({
deviceId,
serviceId,
characteristicId,
state: true,
success: () => {
resolve(true)
},
fail: () => {
reject(false)
},
complete: (res) => {
console.log('[Bluetooth] 订阅特征值变化特征', res.errMsg)
}
})
})
}
/**
* @private 计算校验位
* @param arr
* @returns
*/
private handleCheckBit (arr: any[]) {
// ...
return arr
}
private hexStringToArrayBuffer (arr: Array<any>) {
// ArrayBuffer 对象代表原始的二进制数据,不能直接读写,只能通过视图(TypeArray和DataView)
// DataView视图 用来读写复杂类型的二进制数据
let str = this.handleCheckBit(arr)
let buffer = new ArrayBuffer(str.length / 2)
let dataView = new DataView(buffer)
let ind = 0
for (var i = 0, len = str.length; i < len; i += 2) {
let code = parseInt(str.substr(i, 2), 16)
dataView.setUint8(ind, code)
ind++
}
return buffer
}
private handshake () {
return setTimeout(() => {
this.sendBleData(...)
}, 300)
}
/**
* @private 字符串转为数组
* @param str
* @param num
* @returns
*/
private handleSplitStr (str: string, num: number) {
let arr = []
for (let i = 0, len = str.length / num; i < len; i++) {
let subStr = str.substr(0, num)
arr.push(subStr)
str = str.replace(subStr, '')
}
return arr
}
/**
* @private ArrayBuffer解析出16进制
* @param buffer
* @returns
*/
private ab2hex (buffer: ArrayBuffer) {
let hexArr = Array.prototype.map.call(
new Uint8Array(buffer),
function (bit) {
return ('00' + bit.toString(16)).slice(-2)
}
)
return hexArr.join('')
}
/** 蓝牙初始化 */
private async initialize() {
if (this.#initialized) return;
if (await this.openBluetoothAdapter() && await this.getBluetoothAdapterState()) {
this.#initialized = true;
console.log('[Bluetooth] 蓝牙初始化成功')
} else {
wx.showToast({ title: '蓝牙初始化失败', icon: 'none' })
}
}
/** 连接蓝牙 */
public async connect(deviceName: string) {
// 0. 初始化蓝牙
if (!this.#initialized) await this.initialize();
// 1. 获取设备列表
this.#deviceId = await this.getBluetoothDevicesList(deviceName)
// 2. 连接设备
if (!await this.createBLEConnection(this.#deviceId)) return
// 3. 获取服务
this.#serviceId = await this.getBLEDeviceServices(this.#deviceId)
// 4. 获取特征值
this.#characteristics = await this.getBLEDeviceCharacteristics(this.#deviceId, this.#serviceId)
// 5. 监听特征值
this.#connection = await this.notifyBLECharacteristicValueChange(this.#deviceId, this.#serviceId)
// 6. 开始握手
this.handshake()
}
/** 断开蓝牙连接 */
public disconnect(): void {
if (!this.#connection) return
setTimeout(() => {
wx.closeBLEConnection({
deviceId: this.#deviceId,
success: () => {
this.#connection = false
console.log('[Bluetooth] 蓝牙断开连接成功')
},
fail: (err) => {
console.log('[Bluetooth] 蓝牙断开连接失败', err)
}
})
}, 300);
}
/**
* 发送蓝牙数据
* @param msg
* @returns
*/
public sendBleData (msg: any[]) {
const deviceId = this.#deviceId
const serviceId = this.#serviceId
const characteristicId = this.#characteristics[0].uuid || ''
const value = this.hexStringToArrayBuffer(msg)
return new Promise((resolve, reject) => {
setTimeout(() => {
wx.writeBLECharacteristicValue({
deviceId,
serviceId,
characteristicId,
value,
success: () => {
resolve(true)
},
fail: (err) => {
reject(err)
},
complete: (res) => {
console.log('[Bluetooth] 发送蓝牙消息', msg, res)
}
})
}, 300);
})
}
/**
* 接收蓝牙数据
* @param {function} callback 回调函数
*/
public receiveBleData (callback?: Function) {
wx.onBLECharacteristicValueChange(res => {
let data = this.handleSplitStr(this.ab2hex(res.value).toUpperCase(), 2)
callback && callback(data)
console.log('[Bluetooth] 接收蓝牙消息', data)
})
}
/** 关闭蓝牙 */
public async close () {
if (!this.#initialized) return
if (await this.getBluetoothAdapterState()) {
wx.closeBluetoothAdapter({
success: () => {
console.log('[Bluetooth] 蓝牙配适器已关闭')
}
})
}
}
}
使用方法
// index.ts
import Bluetooth from '/utils/bluetooth'
const bluetooth = new Bluetooth()
// ...
// 连接蓝牙
await bluetooth.connect(deviceName)
// 发送消息
bluetooth.sendBleData([...])
// 接收消息
bluetooth.receiveBleData(msg => {
// ...
})
// 断开连接
bluetooth.disconnect()
// 关闭蓝牙
bluetooth.close()
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。