小程序连接BLE蓝牙设备的流程及遇到的常见问题
前言
- 我用的是
uni-app
开发,uni-app
提供了 蓝牙 和 低功耗蓝牙 的api
,和微信小程序提供的api
是一样的
需要用到的api
- 初始化蓝牙适配器:
uni.openBluetoothAdapter()
- 监听蓝牙适配器的状态
uni.onBluetoothAdapterStateChange()
- 关闭监听蓝牙适配器的状态的事件
uni.offBluetoothAdapterStateChange()
- 监听寻找到新设备的事件:
uni.onBluetoothDeviceFound()
- 开始搜索附近蓝牙设备:
uni.startBluetoothDevicesDiscovery()
- 停止搜寻附近的蓝牙设备:
uni.stopBluetoothDevicesDiscovery()
- 获取在搜索期间搜索到的所有蓝牙设备:
uni.getBluetoothDevices()
- 连接蓝牙设备:
uni.createBLEConnection()
- 获取蓝牙设备所有服务:
uni.getBLEDeviceServices()
- 获取蓝牙设备服务中的特征值:
uni.getBLEDeviceCharacteristics()
- 监听蓝牙连接状态的改变:
uni.onBLEConnectionStateChange()
- 启用蓝牙设备特征值变化时的订阅(notify)功能:
uni.notifyBLECharacteristicValueChange()
- 接收消息监听传来的数据:
uni.onBLECharacteristicValueChange()
相关文档
思路
初始化适配器 --> 搜索蓝牙 --> 连接蓝牙 --> 拿服务和特征值的uuid --> 开启监听 --> 收发内容 --> 关闭模块
详细流程及注意事项
初始化蓝牙适配器
uni.openBluetoothAdapter()
错误代码可以查阅 《错误码文档》。complete(){}
接口调用结束后会执行(不管成功或者失败),考虑到用户会在打开蓝牙适配器后,手动把手机的蓝牙开关给关闭了,然后又打开,所以在这调用监听适配器状态的api,对用户的操作作出反应。uni.offBluetoothAdapterStateChange()
把这个api用上就可以避免openBluetooth()
方法重复回调,uni.onBluetoothAdapterStateChange()
造成蓝牙设备提示连接超时。uni.onBluetoothDeviceFound()
在这里调用监听就可以监听到后面搜索蓝牙搜到的每一个蓝牙设备的地址啦。
openBluetooth() {
uni.openBluetoothAdapter({
success: (e) => {
if (e.errMsg == "openBluetoothAdapter:ok") {
//初始化蓝牙成功,蓝牙可用
console.log("初始化蓝牙成功,蓝牙可用");
}
},
fail: () => {
uni.showModal({
title: "温馨提示",
content: "请您打开蓝牙或检查微信是否授权蓝牙",
showCancel: false,
});
},
complete: () => {
//开始监听蓝牙开关的变化
uni.onBluetoothAdapterStateChange((res) => {
uni.offBluetoothAdapterStateChange();
if (!res.available) {
//蓝牙适配器正常变为异常 (即手机蓝牙被手动关闭)
uni.showModal({
title: "手机蓝牙不可用",
content: "您手机蓝牙已关闭",
showCancel: false,
});
}else{
//蓝牙适配器异常变为正常 (即手机蓝牙被关闭后再手动打开)
this. openBluetooth()
}
});
//开始监听扫描到新设备事件
uni.onBluetoothDeviceFound((res) => {
res.devices.forEach((device) => {
// 这里可以做一些过滤,
console.log("Device Found", device.deviceId);
});
});
},
});
},
搜索蓝牙
uni.startBluetoothDevicesDiscovery()
错误代码可以查阅 《错误码文档》。uni.startBluetoothDevicesDiscovery()
此操作比较耗费系统资源,请在搜索到需要的设备后及时调用uni.stopBluetoothDevicesDiscovery()
停止搜索。- 如果是对某个已知蓝牙地址连接蓝牙可以在
uni.onBluetoothDeviceFound()
监听中直接连接蓝牙,并停止搜索。 - 如果想展示搜索到的所有蓝牙,应该使用
uni.getBluetoothDevices()
获取搜索结束后在搜索期间搜索到的所有蓝牙,而不是在uni.onBluetoothDeviceFound()
监听中一一获取蓝牙并加入数组中,因为这样加会造成蓝牙丢失,亲测。
searchBluetooth() {
uni.startBluetoothDevicesDiscovery({
success: (res) => {
console.log("正在搜索");
},
fail: () => {
uni.showToast({title: "搜索失败",icon: "error",duration: 2000,});
},
complete: () => {
// 扫描十秒后, 超时停止搜索;
this.timeOut = setTimeout(() => {
uni.stopBluetoothDevicesDiscovery();
//应该使用此函数获取搜索结束后,在搜索期间搜索到的所有蓝牙,而不是在uni.onBluetoothDeviceFound()中一一获取蓝牙并加入表单,因为这样加会造成蓝牙丢失
//uni.getBluetoothDevices({
// success: (res) => {
// console.log(res);
// this.bluetoothList = res.devices.map((item) => item.deviceId);
// },
// });
uni.showToast({ title: "搜索超时,请检查蓝牙设备是否被连接", icon: "error", duration: 2000, });
console.log("搜索结束");
}, 10 * 1000);
},
});
},
连接蓝牙 获取服务
- 坑来了!坑来了!
uni.getBLEDeviceServices()
可能会拿到好几条uuid
,一定要问清楚和你对接的嵌入式工程师要用哪个!!获取特征值uuid
同理。
- 如果你像下图那样read的权限是false的话,不是咱们前端的问题,是嵌入式那边设备属性没开。
- 如果接口返回成功却拿不到
uuid
,可以加个setTimeout()
延时访问接口,听部分小伙伴说在app上需要延时,小程序就不用,或者有些是要调用几次接口才能拿到服务,可以循环调用一下,service.length>0
停止,应该是官方接口的问题,大佬知道的话评论区指导一下。 - 我之前获取不到设备发送的心跳包就是拿错了uuid😭😭,百度也搜不到,搜的都是让我改这个。
connectBluetooth(deviceId) {
//连接蓝牙
uni.createBLEConnection({
deviceId, // 搜索到蓝牙设备地址
success: () => {
this.connectingDeviceId = deviceId;
clearTimeout(this.timeOut); //在监听中直连,记得连上后清除定时器,就不会提示超时啦
this.$refs.uToast.show({
title: "连接成功",
type: "success",
});
// 连接成功,获取服务,拿到主服务uuid
uni.getBLEDeviceServices({
deviceId,
success: (res) => {
// 获取读写服务的特征值
uni.getBLEDeviceCharacteristics({
// 这里的 deviceId 需要已经通过 uni.createBLEConnection 与对应设备建立链接
deviceId,
// 这里的 serviceId 需要在 uni.getBLEDeviceServices 接口中获取
serviceId: this.services_UUID,
success: (res) => {
//保存一下特征值的uuid
for (let i = 0; i < res.characteristics.length; i++) {
let item = res.characteristics[i];
this.characteristic_UUID = item.uuid;
}
//开始监听特征值uuid的变化
this.listenCharacterUuid();
},
});
},
});
},
});
//随时监听蓝牙连接状态,断开就重连
uni.onBLEConnectionStateChange((res) => {
if (!res.connected) {
uni.offBLEConnectionStateChange();
this.connectBluetooth(res.deviceId);
}
});
},
开启监听
- 接收回来的值是一个十六进制的伪数组,可以用下面那个
ab2hex()
方法转字符串,复制即用 - 安卓平台上,在
uni.notifyBLECharacteristicValueChange
调用成功后立即调用uni.writeBLECharacteristicValue
接口,在部分机型上会发生 10008 系统错误
// 监听蓝牙发送的内容
listenCharacterUuid() {
// 监听特征值的变化
uni.notifyBLECharacteristicValueChange({
state: true, // 启用 notify 功能
// 这里的 deviceId 需要在 getBluetoothDevices 或 onBluetoothDeviceFound 接口中获取
deviceId: this.connectingDeviceId,
// 这里的 serviceId 需要在 getBLEDeviceServices 接口中获取
serviceId: this.services_UUID,
// 这里的 characteristicId 需要在 getBLEDeviceCharacteristics 接口中获取
characteristicId: this.characteristic_UUID,
success:(res)=> {
console.log("订阅监听特征值", res.errMsg);
},
});
uni.onBLECharacteristicValueChange(async (res) => {
console.log("接收回来的值", this.ab2hex(res.value));
this.receiptHeartbeat = this.ab2hex(res.value);
});
},
//伪数组转字符串
ab2hex(buffer) {
let hexArr = Array.prototype.map.call(
new Uint8Array(buffer),
function (bit) {
return ("00" + bit.toString(16)).slice(-2);
}
);
return hexArr.join("");
},
收发数据
- 不管是
uni.writeBLECharacteristicValue()
还是uni.readBLECharacteristicValue()
都是向蓝牙设备发送指令,一个是给蓝牙设备发送数据,一个是通知蓝牙设备给我发送数据。 success
和fail
只是返回你本次发送请求的动作是否成功,至于对面的蓝牙设备有没有收到这个指令你是不知道的,所以要和嵌入式工程师一起联调。- 读写订阅接收从蓝牙设备来的数据都是在
uni.onBLECharacteristicValueChange()
接口中接收。
HeartSubmit(data) {
//将字符串再转回伪数组
let hex = data;
let typedArray = new Uint8Array(
hex.match(/[\da-f]{2}/gi).map((h)=>{
return parseInt(h, 16);
})
);
let buffer = typedArray.buffer;
//发指令给设备
uni.writeBLECharacteristicValue({
// 这里的 deviceId 需要在 getBluetoothDevices 或 onBluetoothDeviceFound 接口中获取
deviceId: this.connectingDeviceId,
// 这里的 serviceId 需要在 getBLEDeviceServices 接口中获取
serviceId: this.services_UUID,
// 这里的 characteristicId 需要在 getBLEDeviceCharacteristics 接口中获取
characteristicId: this.characteristic_UUID,
// 这里的 value 是ArrayBuffer类型
value: buffer,
success:(res)=> {
//这里输出代表你已经发送成功啦(只是发送成功并不代表设备一定收到啦)
console.log("writeBLECharacteristicValue success", res.errMsg);
},
});
},
uni.readBLECharacteristicValue({
deviceId: deviceId.value,
serviceId: serviceId.value,
characteristicId: characteristicId.value,
success:(res)=> {
console.log('读取指令发送成功',res)
},
fail:(err)=> {
console.log('读取指令发送失败',err)
}
})
转载自:https://juejin.cn/post/7207702606646476855