吐槽:
先说一下心路历程,因为个人开发的一个APP,需要连接蓝牙模块进行设备控制和双向的数据通信,所以尝试用uni-app开发一个手机程序对购买的蓝牙模块进行连接,emm.......怎么说呢,理论上过程都是通的,但坑还是太多了。今天过程跑通了,特来总结一下。说明下,代码太长了,所以我准备分段说明展示,完整代码到时候我上传到github上,地址最后我写在评论里哈。进入正题...........
1.蓝牙通信整体流程
上图一共九个步骤就是创建uni-app/微信小程序连接蓝牙设备并进行通信的基本步骤。具体每个模块是怎么回事,请继续阅读。也可以直接转向官网查看。
微信小程序:
wx.readBLECharacteristicValue(Object object) | 微信开放文档 (qq.com)
uni-app:
https://uniapp.dcloud.io/api/system/ble?id=getbledeviceservices
说明:uni和小程序在API接口上基本是一毛一样的,所以在开发的时候可以互相参考着看下。因为微信小程序开发工具扫码真机测试速度快。当然uni自家的HbuilderX自带的真机运行基座也不错。就我本人来讲,我是两个一起参考的。需要注意的就是语法的细微差别。因为uni是基于Vue开发的,用的是Vue的写法,而小程序并不是(其实都差不多)
2.打开蓝牙适配器状态openBluetoothAdapter
//开启蓝牙适配器初始化蓝牙模块
openBluetoothAdapter() {
//刷新蓝牙设备
this.devices=[]
uni.openBluetoothAdapter({
success: (res) => {
console.log("开启蓝牙适配器成功(openBluetoothAdapter success)", res);
this.startBluetoothDevicesDiscovery();
uni.showToast({
title: "开始扫描设备",
icon: "success",
});
},
fail: (res) => {
uni.showToast({
title: "请开启蓝牙",
icon: "none",
});
if (res.errCode === 10001) {
uni.onBluetoothAdapterStateChange(function (res) {
//监听蓝牙适配器是否打开,若打开则自动搜索蓝牙设备(onBluetoothAdapterStateChange)
if (res.available) {
this.startBluetoothDevicesDiscovery();
}
});
}
},
});
},
当用户打开了蓝牙的时候就会进入下一步查找蓝牙设备。如果用户没有打开蓝牙,可以通过onBluetoothAdapterStateChange进行判断提示。打开后进入设备搜索。
这里说明下,不论是微信小程序还是uni-app,调用方式都是这种(uni.某某/wx.某某),其都包含有三个回调函数,success,fail,complete。所以都可以直接使用就好了
3.开始搜寻附近的蓝牙外围设备startBluetoothDevicesDiscovery
此操作比较耗费系统资源,请在搜索并连接到设备后调用 stopBluetoothDevicesDiscovery 方法停止搜索,后期熟练了也可以直接写上对应的设备id,直接连他就ok了。
//开启蓝牙设备搜索
startBluetoothDevicesDiscovery() {
// 关闭蓝牙适配器的时候将其打开
if (this._discoveryStarted) {
return;
}
this._discoveryStarted = true;
uni.startBluetoothDevicesDiscovery({
allowDuplicatesKey: true,
success: (res) => {
console.log( "开始搜寻蓝牙设备成功(startBluetoothDevicesDiscovery success)", res);
uni.showLoading({
title: "正在搜索设备",
});
this.onBluetoothDeviceFound();
},
});
},
4. 监听寻找到新设备的事件onBluetoothDeviceFound
//监听寻找到新设备的事件
onBluetoothDeviceFound() {
uni.onBluetoothDeviceFound((res)=>{
if(res){
uni.hideLoading();
}
res.devices.forEach(device=>{
//过滤掉没有名字的设备
if (!device.name && !device.localName) {
return
};
//这么操作是为了去除重复
const foundDevices = this.devices//将数据中的数组复制一份,利用动态绑定方式,不断复制最新的数组
const idx = this.inArray(foundDevices, 'deviceId', device.deviceId)
if (idx === -1) {
this.devices.push(device);//数组里没有的的就向里面添加数据,保证没有重复[uni写法]
}
})
console.log(this.devices);
});
},
这里要注意的是要对搜索到的设备进行去重复操作,因为蓝牙搜索貌似是这样的,你只要没关闭它,他就一直搜索他,会出现大量的重复设备。这些设备你只需要向你的设备数组中放入一个就行了。
5.连接低功耗蓝牙设备createBLEConnection
官方目前只有低功耗蓝牙设备的接口,就是BLE蓝牙,和传统手机蓝牙有区别。所以你搜索的时候是找不到开着蓝牙的其他手机的。目前我了解的是智能家居设备,小米手环,华为手表等这类的是用的低功耗蓝牙设备。也可以购买BLE低功耗蓝牙模块进行测试,总之低功耗蓝牙设备将来的使用会更广,通过蓝牙模块也可以集成进其他硬件设备中进行控制。
我这个代码是做在了button按钮上,当用户点击某个设备想要连接时,这个设备的信息就传入参数e中,界面图最后展示。
createBLEConnection(e) {
const ds = e.currentTarget.dataset
this.deviceId = ds.deviceId
//将设备名称也传递给全局变量
this.deviceName = ds.name
uni.createBLEConnection({
deviceId:this.deviceId,
success: (res) => {
this.connected=true,
console.log('连接时获取设备id',this.deviceId);
setTimeout(() => {
this.getBLEDeviceServices(this.deviceId);
}, 1000);
},
fail:(err)=>{
uni.showToast({
title:'建立连接失败',
icon:'none'
})
return
}
})
this.stopBluetoothDevicesDiscovery()//此时停止蓝牙搜索
},
这里面有一个巨坑:在uni-app中调用getBLEDeviceServices的时候,不要直接调,直接调是没有任何服务的!!!!!!!!!!!!!!!!一定要设置时间间隔,延迟调用。这个问题在微信小程序中没有,在uni中一定要延迟调用,延迟调用,延迟调用,延迟调用,延迟调用,延迟调用,延迟调用,延迟调用,延迟调用。
6.获取蓝牙设备所有服务getBLEDeviceServices
这里需要说明下,刚开始我也是对各种id云里雾里,其实就三个。简单的说:每个设备都有一个唯一的设备id(deviceId),每个设备中又有不同的服务,他们通过服务id区分(serviceId),有的是只读的,有的是只写的,有的是可读可写,有的可以监听,有的不可以。。。。。所以你要搞清楚那个服务是你要的,要不然后面所有操作都对了就是不行。每个服务id中又有不同的特征值id(characteristicId)也就是uuid,uuid(universally unique Identifier)通用唯一识别码。用来标识蓝牙设备所提供的服务,比如(音频传输、串口通信、打印服务、传真服务、网络服务、文件传输服务、信息同步服务等)。下面就是我当时做的时候的一个截图。
可以看到,我这个服务中所有的特征值id/uuid只有2和3支持读写,但是也不能用,因为他们的notify与indicate都是false,这意味着到时候传递数据时候,你写的程序无法获取数据的变化情况,说白了就是没法通信。所以你得从新换一个服务id,之后再看他里面的特征值id情况。
这个是我候选的一个服务id中的特征值id,发现他的notify是true的,证明可以用这个进行通信。notify和indicate只要有一个为true就行,不用都为true。
getBLEDeviceServices(deviceId) {
uni.getBLEDeviceServices({
deviceId:deviceId,
success: (res) => {
//serviceId固定死了
this.getBLEDeviceCharacteristics(deviceId, this.serviceId)
},
fail:(err)=>{
uni.showToast({
title:'获取服务失败',
icon:'none'
})
return
}
})
},
这里我的服务id我写死了 所以就不在上res中找了,如果不想写死,可以上res中找,找到后存起来供下面使用。
7.获取蓝牙设备某个服务中所有特征值(characteristic)getBLEDeviceCharacteristics
这里面特征值id我也是之前console出来找到后直接写死了,就不要每次获取了。当监听到notify或者indicate为true是就要启动notifyBLECharacteristicValueChange。
启用低功耗蓝牙设备特征值变化时的 notify 功能,订阅特征值。注意:必须设备的特征值支持 notify 或者 indicate 才可以成功调用。
另外,必须先启用 notifyBLECharacteristicValueChange
才能监听到设备 characteristicValueChange
事件
uni.getBLEDeviceCharacteristics({
//设备id与服务id必须要给,特征值id先不用获取,直接写死
deviceId,
serviceId,
success: (res) => {
if(res.characteristics[0].properties.read)
{
console.log('该特征值可读');
uni.readBLECharacteristicValue({
deviceId,
serviceId,
characteristicId:this.characteristicId,
});
}
if(res.characteristics[0].properties.write)
{
console.log('该特征值可写');
this.canWrite=true;
//调用写
this.writeBLECharacteristicValue()
}
//确保对应服务id下的特征值id具备监听数据变化的特性
if (res.characteristics[0].properties.notify || res.characteristics[2].properties.indicate) {
uni.notifyBLECharacteristicValueChange({
deviceId,
serviceId,
characteristicId: this.characteristicId,
state: true,//是否启用notify通知
success:(res)=>{
console.log('通知启用(notifyBLECharacteristicValueChange)',res);
}
})
}
},
fail(res) {
console.error('获取蓝牙设备特征值失败(getBLEDeviceCharacteristics)', res)
}
})
这里面我在判断出设备可读的时候就调用了readBLECharacteristicValue。
读取低功耗蓝牙设备的特征值的二进制数据值。注意:必须设备的特征值支持 read 才可以成功调用
8.监听低功耗蓝牙设备的特征值变化事件onBLECharacteristicValueChange
监听低功耗蓝牙设备的特征值变化事件。必须先启用 notifyBLECharacteristicValueChange
接口才能接收到设备推送的 notification。
这样设备发送数据给手机,手机才会获得。传递的值就是value(硬件设备向手机传递数据)。
uni.onBLECharacteristicValueChange((characteristic) => {
//记录手机接受的数据
this.myDataMeasure.push(this.ab2hex(characteristic.value));
//记录目前通信的对象(和谁通信,特征值是多少,初始值00)
const idx = this.inArray(this.chs, 'uuid', characteristic.characteristicId)
const data = {}
if (idx === -1) {
this.chs.push({
uuid: characteristic.characteristicId,
value: this.ab2hex(characteristic.value)
})
} else {
this.chs[idx] = {
uuid: characteristic.characteristicId,
value: this.ab2hex(characteristic.value)
}
}
})
9.写入蓝牙特征值writeBLECharacteristicValue
这是手机向硬件设备进行写入写入二进制数据。注意:必须设备的特征值支持 write 才可以成功调用
writeBLECharacteristicValue() {
//向蓝牙设备发送一个0x00的16进制数据
let buffer = new ArrayBuffer(1)
//可以自定义复合格式的视图
let dataView = new DataView(buffer)
dataView.setUint8(0, this.sendData)
uni.writeBLECharacteristicValue({
deviceId: this.deviceId,
serviceId: this.serviceId,
characteristicId: this.characteristicId,
value: buffer,
complete:()=>{
//buffer本身是arraybuffer(arraybuffer要转换为16进制)
console.log('十六进制',this.ab2hex(buffer));
let sixNumber=this.ab2hex(buffer);
console.log('十进制',this.hex2int(sixNumber));
}
})
},
这里我对手机发送的数据进行了一个进制转换。到目前为止就可以实现手机与蓝牙设备的通信了。当然自己实际操作肯定不能这么顺利。各种问题还是会有,目前我把我遇到的坑都写了出来,后续也欢迎补充。