Uniapp蓝牙模块功能开发笔记。
功能主要是初始化蓝牙模块、搜索蓝牙设备、连接蓝牙设备、获取蓝牙设备所有服务、获取蓝牙设备某个服务中所有特征值、向蓝牙设备特征值中写入二进制数据、读取或监听低功耗蓝牙设备的特征值变化。
1、初始化蓝牙模块
由于Uniapp的初始化蓝牙模块api并不会拉起系统打开蓝牙开关,需要手动开启蓝牙后再进行初始化。
但是可以使用html5plus来实现请求打开蓝牙开关,适用于Andriod
pages/scan/index.vue
// 安卓系统请求开启蓝牙
initBluetoothAdapter() {
let BluetoothAdapter;
this.main = plus.android.runtimeMainActivity();
BluetoothAdapter = plus.android.importClass("android.bluetooth.BluetoothAdapter");
this.BAdapter = BluetoothAdapter.getDefaultAdapter();
if(this.BAdapter && !this.BAdapter.isEnabled()) {
this.BAdapter.enable();
}
},
scopeBluetoothAdapter() {
return new Promise(resolve => {
this.BAdapter.enable();
const receiver = plus.android.implements('io.dcloud.android.content.BroadcastReceiver', {
onReceive: function(context, intent) { //实现onReceiver回调函数
plus.android.importClass(intent);
this.main.unregisterReceiver(receiver);
}
});
const IntentFilter = plus.android.importClass('android.content.IntentFilter');
const filter = new IntentFilter();
filter.addAction(BAdapter.ACTION_STATE_CHANGED); //监听蓝牙开关
this.main.registerReceiver(receiver, filter); //注册监听
resolve()
})
},
// 初始化蓝牙设备
openBluetoothAdapter() {
return new Promise((resolve, reject) => {
uni.openBluetoothAdapter({
success: e => {
resolve()
},
fail: e => {
reject(e)
}
});
})
},
// 关闭蓝牙模块。调用该方法将断开所有已建立的连接并释放系统资源。
closeBluetoothAdapter() {
return new Promise((resolve, reject) => {
uni.closeBluetoothAdapter({
success: e => {
resolve()
},
fail: e => {
reject(e)
}
});
})
}
2、扫描蓝牙设备
只搜索广播包有对应 uuid 的主服务的蓝牙设备:使用services属性,或者自己写逻辑过滤掉周边不需要处理的其他蓝牙设备
可以使用vuex存储扫描到的蓝牙设备。
store/modules/bluetooth.js
uni.startBluetoothDevicesDiscovery({
allowDuplicatesKey: true, //是否允许重复上报同一设备
interval: 200,
// services: ['FFF0', 'FFB0'],
success: e => {
this.searchLoad = true;
!this.isListenFound && this.onBluetoothDeviceFound(); // 是否已经监听发现外围设备事件
},
fail: e => {
console.log('搜索蓝牙设备失败,错误码:' + e.errCode);、
}
});
uni.onBluetoothDeviceFound(({ devices }) => {
const { deviceId, RSSI, advertisServiceUUIDs } = devices[0]
const source = JSON.parse(JSON.stringify(state.bluetoothList))
const advertisServiceUUIDsStr = advertisServiceUUIDs.join('~')
if(advertisServiceUUIDsStr.includes('FFF0') && advertisServiceUUIDsStr.includes('FFB0')){ // 过滤主服务
const row = source.find(element => element.deviceId === deviceId)
if(row) {
row.RSSI = RSSI
} else {
source.push(lists)
}
commit('SET_BLELIST', source)
}
});
uni.stopBluetoothDevicesDiscovery({
success: e => {
console.log('停止搜索蓝牙设备:' + e.errMsg);
},
fail: e => {
console.log('停止搜索蓝牙设备失败,错误码:' + e.errCode);
}
});
3、连接蓝牙设备
使用vuex存储已连接的蓝牙设备uuid,断开连接失败可以尝试重新初始化蓝牙模块~~
store/modules/bluetooth.js
/**
* 连接低功耗蓝牙
* @param {Object} device 设备uuid/名称
*/
createBLEConnection({ commit, dispatch }, deviceId){
return new Promise((resolve, reject) => {
uni.createBLEConnection({
// 这里的 deviceId 需要已经通过 createBLEConnection 与对应设备建立链接
deviceId,
timeout: 10000, // 连接超时时间
success: res => {
commit('SET_DEVICE', deviceId)
resolve()
},
fail: error => {
commit('SET_DEVICE', '')
reject(error)
}
});
})
},
// 断开与低功耗蓝牙设备的连接
closeBLEConnection({ commit, dispatch }){
return new Promise((resolve, reject) => {
if(!state.deviceId) {
resolve()
return
}
uni.closeBLEConnection({
deviceId: state.deviceId,
success: res => {
console.log(state.deviceId + '断开连接')
commit('SET_DEVICE', '')
resolve()
},
fail: async(error) => {
await dispatch('closeBluetoothAdapter').catch(() => {})
await dispatch('openBluetoothAdapter').catch(() => {})
reject(error)
}
});
})
},
3.1、获取蓝牙设备服务及其特征值
需要获取到蓝牙设备可以读、写、订阅的服务及其对应的特征值,才能向设备发送或接收设备发来的信息
有可能读和订阅都在一个服务里面,也可能分开,一般蓝牙设备文档里面会有说明,如果没有那就只能去试每个服务的特征值是否可读、可写、可订阅咯ヾ§  ̄▽)ゞ2333333
像下面的示例,写服务为FFE5,特征值为FFE9。订阅服务为FFE0,特征值为FFE4。
store/modules/bluetooth.js
/**
* 获取所有服务
* @description 12秒内未获取所需的主服务及特征值则返回失败
*/
getBLEDeviceServices({ commit, dispatch }){
return new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
reject({errCode: 10004})
}, 12000);
// 使用定时器获取服务
// 处理连接设备后获取服务,services列表为空的问题 @see https://ask.dcloud.net.cn/question/66347
clearInterval(timer)
const timer = setInterval(() => {
uni.getBLEDeviceServices({
// 这里的 deviceId 需要已经通过 createBLEConnection 与对应设备建立链接
deviceId: state.deviceId,
success: async(res) => {
const { services } = res
if(services.length){
clearInterval(timer)
const writeService = services.find(item => item.uuid.indexOf('FFE5') > -1)
const notifyService = services.find(item => item.uuid.indexOf('FFE0') > -1)
commit('SET_WRITESERVICE', writeService.uuid)
commit('SET_NOTIFYSERVCE', notifyService.uuid)
await dispatch('getBLEDeviceCharacteristics', { serviceId: writeService.uuid, type: 'write'}).catch(() => {})
await dispatch('getBLEDeviceCharacteristics', { serviceId: notifyService.uuid, type: 'notify'}).catch(() => {})
clearTimeout(timeout)
if(state.writeCharacteristic && state.notifyCharacteristic) {
resolve()
} else {
reject({errCode: 10005})
}
}
},
fail: error => {
reject(error)
}
});
}, 200);
})
},
/**
* 获取某个服务下的所有特征值
* @param {String} serviceId 服务的uuid
* @param {String} type 服务类型
*/
getBLEDeviceCharacteristics({ commit, dispatch }, data){
const { serviceId, type } = data
return new Promise((resolve, reject) => {
uni.getBLEDeviceCharacteristics({
// 这里的 deviceId 需要已经通过 createBLEConnection 与对应设备建立链接
deviceId: state.deviceId,
// 这里的 serviceId 需要在 getBLEDeviceServices 接口中获取
serviceId,
success: res => {
const { characteristics } = res
if(type === 'write') {
const writeCharacteristic = characteristics.find(item => item.uuid.indexOf('FFE9') > -1)
const writeCharacteristicId = writeCharacteristic ? writeCharacteristic.uuid : ''
commit('SET_WRITECHARACTERISTIC', writeCharacteristicId)
} else {
const notifyCharacteristic = characteristics.find(item => item.uuid.indexOf('FFE4') > -1)
const notifyCharacteristicId = notifyCharacteristic ? notifyCharacteristic.uuid : ''
commit('SET_NOTIFYCHARACTERISTIC', notifyCharacteristicId)
}
resolve()
},
fail: error => {
reject(error)
}
});
})
}
3.2、断线重连机制
连接成功后,就监听低功耗蓝牙连接状态的改变事件了。包括开发者主动连接或断开连接,设备丢失,连接异常断开等等
store/modules/bluetooth.js
onBLEConnectionStateChange({ commit, dispatch }) {
uni.onBLEConnectionStateChange(res => {
// 断线重连
if(!res.connected && res.deviceId === state.deviceId){
uni.showModal({
title: '提示',
content: '连接断开,是否要重连',
success: async(res) => {
if (res.confirm) {
uni.showLoading({
title: '尝试重连中...',
mask: true
})
try {
await dispatch('createBLEConnection', state.deviceId)
uni.showToast({
title: '重连成功',
icon: 'none'
})
} catch(error) {
uni.hideLoading()
dispatch('closeBLEConnection')
}
} else if (res.cancel) {
uni.hideLoading()
dispatch('closeBLEConnection')
}
}
})
}
});
},
4、向蓝牙设备发送
蓝牙最大传输单元默认为20,所以需要把要发送的数据拆成1包20字节去发送,可以尝试使用uni.setBLEMTU
api与设备协商,把传输单元设置大一点,分包就可以传输更多数据。
参考文档:https://uniapp.dcloud.io/api/system/ble?id=setblemtu
utils/bluetooth.js
/**
* 向蓝牙设备发送数据
* @param {Hex} cmd 命令
* @param {String} sn 设备编号
* @param {Object} data 信息内容
* @param {Function} callback 回调函数
*/
export function writeBLECharacteristicValue(data , callback){
return new Promise(async(resolve, reject) => {
try{
const msg = JSON.stringify(data)
if(!store.getters.isNotify){
await notifyBLECharacteristicValue().catch(error => {
reject(error)
})
await sleep(1000) // 在调用 notifyBLECharacteristicValueChange 成功后立即调用 writeBLECharacteristicValue 接口,在部分机型上会发生 10008 系统错误
}
onBLECharacteristicValueChange().then(res => {
resolve(res)
})
sendDataPacket(msg, 0, callback).catch(error => {
reject(error)
})
}catch(e){
//TODO handle the exception
reject(e)
}
})
}
/**
* 数据分包发送,每个包20字节
* @param {Array} data 总的数据
* @param {Number} index 当前分包数据起始下标
* @param {Function} callback 回调函数
*/
const sendDataPacket = async(data, index, callback) => {
const partArray = data.slice(index, index + 20)
const buffer = new Uint8Array(partArray).buffer;
return new Promise((resolve, reject) => {
uni.writeBLECharacteristicValue({
// 这里的 deviceId 需要在 getBluetoothDevices 或 onBluetoothDeviceFound 接口中获取
deviceId: store.getters.deviceId,
// 这里的 serviceId 需要在 getBLEDeviceServices 接口中获取
serviceId: store.getters.writeService,
// 这里的 characteristicId 需要在 getBLEDeviceCharacteristics 接口中获取
characteristicId: store.getters.writeCharacteristic,
// 这里的value是ArrayBuffer类型
value: buffer,
success: res => {
index += 20
if(typeof callback === 'function'){
const progress = index / data.length
callback(progress)
}
if(index < data.length){
sendDataPacket(data, index, callback)
}else{
resolve(res)
}
console.log('writeBLECharacteristicValue success', res.errMsg)
},
fail: error => {
console.log(error)
reject(error)
}
})
})
}
/**
* 字符串转二进制数组
* @param {String} str 源字符串
* @return {Array}
*/
export function str2hex(str) {
const target = []
for(let i=0; i<str.length; i++){
target.push(str.charCodeAt(i))
}
return target
}
/**
* 10进制转16进制字符串
* @param {Number} dec 10进制
* @param {Number} len 目标字符串长度
* @return {String}
*/
export function dec2hex(dec, len){
const hex = (dec).toString(16)
return (Array(len).join(0) + hex).slice(-len); // 16进制不及目标长度前导补0
}
5、接收蓝牙设备发来的数据
先启用 notifyBLECharacteristicValueChange 接口才能接收到设备推送的 notification。
如上一节所说,不协商蓝牙最大传输单元,接收到的数据也是1包20字节,所以需要将接收到的数据进行拼包,根据结束标识判断是完整的一条信息
如下示例,结束标识是'\n\r'
utils/bluetooth.js
/**
* 订阅操作成功后需要设备主动更新特征值的 value,才会触发 uni.onBLECharacteristicValueChange 回调。
*/
notifyBLECharacteristicValue() {
return new Promise((resolve, reject) => {
uni.notifyBLECharacteristicValueChange({
state: true, // 启用 notify 功能
// 这里的 deviceId 需要已经通过 createBLEConnection 与对应设备建立链接
deviceId: state.deviceId,
// 这里的 serviceId 需要在 getBLEDeviceServices 接口中获取
serviceId: state.notifyService,
// 这里的 characteristicId 需要在 getBLEDeviceCharacteristics 接口中获取
characteristicId: state.notifyCharacteristic,
success: res => {
resolve(res)
},
fail: error => {
reject(error)
}
});
})
}
/**
* 监听低功耗蓝牙设备的特征值变化事件。必须先启用 notifyBLECharacteristicValueChange 接口才能接收到设备推送的 notification。
*/
const onBLECharacteristicValueChange = function(){
let partData = ''
return new Promise((resolve, reject) => {
// 必须在这里的回调才能获取
uni.onBLECharacteristicValueChange(characteristic => {
console.log('监听低功耗蓝牙设备的特征值变化事件成功');
const { value } = characteristic
partData += ab2hex(value) + ','
if(partData.indexOf(',13,10,') > -1){
resolve(result )
}
});
})
}
/**
* ArrayBuffer转字符串
* @param {ArrayBuffer} buffer
* @return {String}
*/
export function ab2hex(buffer) {
const dataView = new Uint8Array(buffer)
const hexArr = dataView.map(item => item)
return hexArr.join(',')
}
还没有评论,快来抢第一吧