uni-app使用蓝牙的坑
前言
在以前的一篇博客中,我记载了给公司写蓝牙锁连接的代码,然后后面我优化了一下代码,想节省一下时间,而这个节省时间让我遇到了问题。
https://www.cnblogs.com/weiyanei/p/17039919.html
问题点
服务发现保存蓝牙缓存
用户扫码获取车锁信息,然后点击开锁按钮,车锁打开。
这里的开锁按钮就是需要连接蓝牙,然后告诉车锁开锁,那么问题来了,我设计了如下这个表,其中保存了serviceid,characterid,deviceid,这些都是uni-app中连接蓝牙需要的信息。
CREATE TABLE `ims_app_bluetooth_lock_info` (
`info_id` int NOT NULL AUTO_INCREMENT,
`qrcode` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '锁表面的二维码',
`mac` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '锁对应的蓝牙mac',
`write_serviceid` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '写服务ID',
`write_characterid` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '写特征值',
`notify_serviceid` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '监听返回服务ID',
`notify_characterid` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '监听返回特征值',
`deviceid` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
PRIMARY KEY (`info_id`)
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='蓝牙锁信息';
我在想,既然我最终的目的是朝蓝牙锁写入指令,然后蓝牙锁告诉我返回数据,那么最重要的就是获取serviceid,characterid,deviceid这三个上了,我能节省时间的也就是在获取这三个上面的。
在之前那篇博客中,我定义了整个流程的顺序,打开蓝牙适配器--> 监听搜索--> 开始搜索--> (发现目标蓝牙后) 关闭搜索--> 连接蓝牙--> 发送蓝牙指令 --> 断开蓝牙--> 关闭蓝牙适配器
在发送蓝牙指令后,其实是要接收返回数据,这一步是在notify中实现的,uni.onBLECharacteristicValueChange 使用这个api监听蓝牙返回数据,
uni.notifyBLECharacteristicValueChange({
state: true, // 启用 notify 功能
// 这里的 deviceId 需要已经通过 createBLEConnection 与对应设备建立链接
deviceId: ths.deviceInfo.deviceId,
// 这里的 serviceId 需要在 getBLEDeviceServices 接口中获取
// serviceId:"6E400001-E6AC-A7E7-B1B3-E699BAE8D000",
serviceId: serviceId,
// 这里的 characteristicId 需要在 getBLEDeviceCharacteristics 接口中获取
// characteristicId:"6E400003-E6AC-A7E7-B1B3-E699BAE8D000",
characteristicId: characteristicId,
success(res) {
// 必须在这里的回调才能获取
uni.onBLECharacteristicValueChange(function(res) {
var receiveValue = ths.ab2hex(res.value) //2进制数据转为16进制字符串
// console.log("蓝牙返回数据为:"+receiveValue)
ths.showDebugInfo("蓝牙返回数据为:"+receiveValue)
if (ths.nowOper == ths.operType.getKey) { //获取秘钥
ths.key = getKeyInfo(receiveValue);
if (ths.key) {
ths.unlock();
}
} else if (ths.nowOper == ths.operType.unlock) { //解锁
var lockState = unlockInfo(receiveValue);
if (lockState) { //开锁成功
ths.disConnectBle();
//开锁成功后需要将定时器关闭
// clearTimeout(ths.timeOutValue)
ths.stopAll()
ths.hasUnLock = true;
//新增骑行开始
var param = {
info_id:ths.lockInfo.info_id,
qrcode:ths.lockInfo.qrcode,
bike_id:ths.bike.bike_id,
area_id:ths.bike.areaInfo.area_id
}
addBikeUseInfo(param).then(res=>{
if(res?.result_code=="0"){
}
})
ths.hideMyLoading(true);
}
}
})
//获取key 此处一定要延迟,要等Notify成功后才能发送
setTimeout(function(){
if(ths.nowOper==ths.operType.getKey){
ths.getKey();
}
},2000);
},
fail(res) {
ths.showDebugInfo(res)
ths.startNotify = false;
ths.stopAll()
ths.hideMyLoading(false);
},
})
好,扯了一大堆还没说到问题点上,看下面的代码,
我在第一次蓝牙发现后,获得了serviceid,characterid,deviceid保存到表里后,就不再所搜附近的蓝牙,直接根据deviceid去连接蓝牙,走的if逻辑,当然连接蓝牙是能连上。
uni.openBluetoothAdapter({
success(res) {
ths.openAdapterFlag = true;
if(ths.deviceInfo.deviceId){//一荣俱荣,一损俱损
//有deviceId,说明已经存在各种连接信息,而且扫码那么就在锁附近了,就直接连接,不用在去发现蓝牙操作
ths.connectBle()
}else{
//监听附近的蓝牙
ths.findBluetooth(ths.deviceInfo.deviceId);
//开始搜索蓝牙
ths.startFindBluetooth();
}
},fail(res) {
ths.showDebugInfo(res)
ths.hideMyLoading(false,"请打开蓝牙");
},
});
当我连接上蓝牙后,如果有serviceid和characterid的话,那么说明已经不用调用getService和getCharacter了,
看到下面的代码了么,表里保存了数据,则不调用getServices()方法,也就是uni.getBLEDeviceServices 和 uni.getBLEDeviceCharacteristics 这两个API的调用。
直接就进入notify方法了,也就是调用 uni.notifyBLECharacteristicValueChange 这个API。
connectBle() {
var ths = this;
uni.createBLEConnection({
// 这里的 deviceId 需要已经通过 createBLEConnection 与对应设备建立链接
deviceId:ths.deviceInfo.deviceId,
success(res) {
ths.deviceInfo.connect = true;
if(ths.deviceInfo.write.serviceId&&ths.deviceInfo.write.characteristicId&&
ths.deviceInfo.notify.serviceId&&ths.deviceInfo.notify.characteristicId){//已经有信息了,则不再获取
setTimeout(function(){
ths.notify();
},ths.notifyTimeoutValue)
}else{
//此处也需要延迟,刚连接上时,无法获取到特征值
setTimeout(function(){
ths.getServices();
},ths.serviceTimeoutValue)
}
},
fail(res) {
ths.showDebugInfo(res)
ths.stopAll()
ths.hideMyLoading(false);
},
complete(res) { //无论有没有连上蓝牙,都要停止搜索
ths.stopFindBluetooth(); //停止搜索蓝牙
}
})
},
结果它会报错 fail no connection , 1004;
诈一看这个报错描述,会以为是不是没有连接上蓝牙,但其实压根不是这么回事,就是没有调用 uni.startBluetoothDevicesDiscovery 和 uni.onBluetoothDeviceFound 导致的。
看后面的1004,这个对应官网错误描述是,没有找到指定服务,这个指定服务其实就是没有找到对应蓝牙缓存。
当调用 uni.startBluetoothDevicesDiscovery 后,我猜其实是把发现的蓝牙服务保存在手机蓝牙缓存中,然后调用notify时传递的 serviceId,characterid,deviceId其实就是根据这三个key,去蓝牙列表中寻找对应服务,找到了就是有服务,没找到就是没有服务。
所以这个过程应该是如下图所示,标注为黄色的都是需要从缓存中获取蓝牙信息,也就是说需要uni.startBluetoothDevicesDiscovery缓存的信息,否则会报 1004错误。
而由于我将serviceid保存在表里面,然后不再开启服务发现,自然也没有蓝牙缓存,结果就是如下流程,报1004错误,
但是每次调用 uni.getBLEDeviceServices获取serviceIds 和 uni.getBLEDeviceCharacteristics获取characterids 确实会浪费一些时间,所以整个流程就可以优化成如下所示,
这是我碰到的第一个坑。
这个也好还原,只需要把手机蓝牙打开,运行成功后,数据库有serviceid,characterid和deviceid后,第二次执行前把手机蓝牙关闭,再打开,就个清空了缓存。这样照着流程2走的时候就会GG。
延迟问题
这个问题在上一篇博客中也写到了,估计还是官网API的问题,在连接蓝牙后要延迟一段时间,否则会出现getService, notify都会出问题的情况。
这玩意没啥好解决的方法,我其实是设定了几个全局的超时时间,每点击一次按钮失败就增加几秒超时,理论上说,用户第一次失败10s算超时,第二次失败15s算超时,这样用来解决问题,也没啥好办法。
其实这过程中还遇到连接蓝牙后写入数据很长时间,导致超时失败,然后再点击写入数据又很快写入。
还有蓝牙连接成功,notify偶尔失败的情况。
不知道是官网这个蓝牙的API不完善还是工厂生产的蓝牙锁有问题。