前言
之前BLE蓝牙通信博客中,重点说明了使用读 -- 写 的模式,实现手机端和设备端的BLE信息交互操作。
但在一般的BLE设备中,存在可以使用notify 通信的方式。
本篇博客重点说明小程序 BLE 使用notify 实现数据交互的方式,以及部分测试时碰见的坑!
准备
在开始写具体代码之前,需要准备以下材料:
- 微信小程序开发者工具
- 带有BLE的设备,且某个特征值对象支持notify
- 一款支持BLE的手机
注意事项
android微信支持BLE蓝牙的微信版本为:6.5.7;
ios微信支持BLE蓝牙的微信版本为:6.5.6。
所以做BLE蓝牙开发,为了提高用户的小程序体验感,需要对用户使用的微信版本以及android版本进行判断,并做合理的解决方式。
开发文档概述
在使用BLE的notify功能之前,还需要熟读微信小程序开发文档 。特别注意以下几个重要的API:
-
1、手机蓝牙端接口 关于手机蓝牙端,需要注意以下几个接口: wx.openBluetoothAdapter 初始化蓝牙适配器
wx.getBluetoothAdapterState 获取本机蓝牙适配器状态
wx.onBluetoothAdapterStateChange 监听蓝牙适配器状态变化事件
其次,在使用这些接口后,需要保证资源释放: wx.closeBluetoothAdapter(Object object); //释放手机蓝牙端资源
-
2、扫描周围设备接口 扫描周围蓝牙设备等操作,需要使用到以下几种接口: wx.startBluetoothDevicesDiscovery 开始搜寻附近的蓝牙外围设备
wx.getBluetoothDevices 获取所有已发现的蓝牙设备
wx.onBluetoothDeviceFound 监听寻找到新设备的事件
当然这些资源使用后,也需要在最后阶段进行释放。 wx.stopBluetoothDevicesDiscovery 停止搜寻附近的蓝牙外围设备
-
3、连接操作接口 当扫描到指定的设备后,需要执行连接操作,此时需要使用下列接口: wx.createBLEConnection 连接低功耗蓝牙设备
wx.onBLEConnectionStateChange 监听低功耗蓝牙连接的状态事件
当然连接操作也是一种占用资源的操作,需要在最后进行资源的释放 wx.closeBLEConnection 断开与低功耗蓝牙设备的连接
wx.offBLEConnectionStateChange(function callback) 关闭连接状态变化的监听事件
-
4、获取服务和特征值对象接口 连接成功后,在android 系统中,可以直接进行数据的交互操作,但在ios 系统中,直接进行数据交互时,会产生10004 报错 。此时则需要保证使用下列代码。在之前博客中也做了详细说明介绍。 wx.getConnectedBluetoothDevices 根据 uuid 获取处于已连接状态的设备
wx.getBLEDeviceServices 获取蓝牙设备所有 service(服务)
wx.getBLEDeviceCharacteristics 获取蓝牙设备所有 characteristic(特征值)
-
5、通信接口 之前博客中重大使用的读-写 操作,本篇博客使用notify 实现交互,需要使用到下列几种接口: wx.writeBLECharacteristicValue 向低功耗蓝牙设备特征值中写入二进制数据
wx.notifyBLECharacteristicValueChange 启用低功耗蓝牙设备特征值变化时的 notify 功能
wx.onBLECharacteristicValueChange 监听低功耗蓝牙设备的特征值变化
当然,也需要对监听进行资源释放: wx.offBLECharacteristicValueChange(function callback) 关闭对特征值数据变化的监听
扩展
由于小程序需要兼容android 和ios ,所以需要使用到以下几种自定义的api接口:
onLaunch: function() {
this.globalData.sysinfo = wx.getSystemInfoSync()
},
getModel: function () {
return this.globalData.sysinfo["model"]
},
getVersion: function () {
return this.globalData.sysinfo["version"]
},
getSystem: function () {
return this.globalData.sysinfo["system"]
},
getPlatform: function () {
return this.globalData.sysinfo["platform"]
},
getSDKVersion: function () {
return this.globalData.sysinfo["SDKVersion"]
}
对手机微信版本和系统版本做比较,封装有一个版本比较的方法:
versionCompare: function (ver1, ver2) {
var version1pre = parseFloat(ver1)
var version2pre = parseFloat(ver2)
var version1next = parseInt(ver1.replace(version1pre + ".", ""))
var version2next = parseInt(ver2.replace(version2pre + ".", ""))
if (version1pre > version2pre)
return true
else if (version1pre < version2pre)
return false
else {
if (version1next > version2next)
return true
else
return false
}
}
代码开发
-
1、android 6.0 以上的手机未打开系统定位服务时,搜索不到蓝牙设备; 首先需要在app.json 中对位置信息 进行注册: "permission": {
"scope.userLocation": {
"desc": "您的位置信息将用于设备定位和蓝牙操作"
}
},
其次在代码调用中也需要进行权限校验:
function checkPhoneInfo(obj){
if (apps.getPlatform() == "android" && versionCompare("6.5.7", apps.getVersion())) {
wx.showModal({
title: '提示',
content: '当前微信版本过低,请更新至最新版本',
showCancel: false
});
obj.setData({
btnDisabled: false,
});
quit(obj);
return;
}
if (apps.getPlatform() == "ios" && versionCompare("6.5.6", apps.getVersion())) {
wx.showModal({
title: '提示',
content: '当前微信版本过低,请更新至最新版本',
showCancel: false
});
obj.setData({
btnDisabled: false,
});
quit(obj);
return;
}
console.log("当前系统版本:" + apps.getSystem());
console.log("当前微信版本:" + apps.getVersion());
if (apps.getPlatform() == "android") {
console.log("android手机 当前系统版本号:" + apps.getSystem().replace("Android", "").replace(" ", ""));
if (!versionCompare("6.0.0", apps.getSystem().replace("Android","").replace(" ",""))) {
console.log("当前系统版本高于6.0.0");
wx.getSetting({
success: function (res) {
var statu = res.authSetting;
if (!statu['scope.userLocation']) {
wx.showModal({
title: '温馨提示',
content: '请授予位置服务权限,以便更好的搜索周围设备',
success: function (tip) {
if (tip.confirm) {
wx.openSetting({
success: function (data) {
if (data.authSetting["scope.userLocation"] === true) {
wx.showToast({
title: '授权成功',
icon: 'success',
duration: 1000
})
bleOperateFun(obj);
} else {
wx.showToast({
title: '授权失败',
icon: 'none',
duration: 1000
});
obj.setData({
btnDisabled: false,
});
quit(obj);
}
}
})
}else{
console.log("点击了取消操作");
obj.setData({
btnDisabled: false,
});
quit(obj);
}
}
})
}else {
bleOperateFun(obj);
}
},
fail: function (res) {
wx.showToast({
title: '调用授权窗口失败',
icon: 'success',
duration: 1000
});
obj.setData({
btnDisabled: false,
});
quit(obj);
}
})
} else if (!versionCompare(apps.getSystem().replace("Android", "").replace(" ", "")), "4.3.0") {
wx.showModal({
title: '温馨提示',
content: '您的手机系统版本较低,无法操作BLE蓝牙设备',
showCancel:false,
});
obj.setData({
btnDisabled: false,
});
quit(obj);
}else {
console.log("系统版本低于6.0.0但高于4.3.0");
bleOperateFun(obj);
}
}
bleOperateFun(obj);
}
-
2、判断蓝牙状态,提示开启蓝牙等
function bleOperateFun(obj){
wx.openBluetoothAdapter({
success: function(res) {
console.log("初始化蓝牙适配器成功");
wx.onBluetoothAdapterStateChange(function(res){
console.log("蓝牙适配器状态变化",res);
});
wx.onBluetoothDeviceFound(function(res){
console.log("扫描周围设备详情返回:"+JSON.stringify(res));
res.devices.forEach(device=>{
console.log(device.name +"/"+device.localName);
console.log("广播数据:" + apps.ab2hex(device.advertisData));
});
});
wx.onBLEConnectionStateChange(function(res){
console.log("蓝牙设备连接状态监听回调:\n"+JSON.stringify(res));
});
startScanAroundDevice(obj);
},
fail:function(res){
console.log("初始化蓝牙适配器失败")
wx.showModal({
title: '温馨提示',
content: '请检查手机蓝牙是否打开',
showCancel:false,
});
obj.setData({
btnDisabled: false,
});
quit(obj);
}
})
}
function startScanAroundDevice(obj) {
if (isScaning) {
console.log("正在扫描设备。。。。");
return;
}
wx.startBluetoothDevicesDiscovery({
success: function(res) {
console.log("成功开启扫描:" + res);
isStartScan = true;
isScaning = true;
},
fail: function(res) {
console.log("扫描操作失败回调");
obj.setData({
btnDisabled: false,
});
quit(obj);
}
});
if (obj.data.btnText == "扫描") {
setTimeout(function() {
stopScanAroundDevice(obj);
}, apps.bleProperties.scanToConnectTimes);
} else {
setTimeout(function() {
stopScanAroundDevice(obj);
}, apps.bleProperties.scanTimes);
}
}
自定义函数中,扫描操作也添加了一个定时任务操作,就是关闭扫描,释放资源。
function stopScanAroundDevice(obj) {
wx.stopBluetoothDevicesDiscovery({
success(res) {
console.log(new Date() + "定时关闭扫描返回:" + JSON.stringify(res));
isScaning = false;
},
fail: function(res) {
console.log("关闭扫描失败回调:" + JSON.stringify(res));
obj.setData({
btnDisabled: false,
});
quit(obj);
}
})
}
- 5、识别设备
在扫描操作执行后 ,当有设备在手机上识别到时,会触发wx.onBluetoothDeviceFound 监听 。
但是,wx.onBluetoothDeviceFound 接口中的 advertisData 数据是一种ArrayBuffer 格式。解析此类数据时,需要使用以下的函数进行操作。
ab2hex: function(buffer) {
var hexArr = Array.prototype.map.call(
new Uint8Array(buffer),
function(bit) {
return ('00' + bit.toString(16)).slice(-2)
}
)
return hexArr.join('');
},
wx.onBluetoothDeviceFound(function(res) {
res.devices.forEach(device => {
})
})
【注意:】Android系统 deviceId 就是指定设备的 mac 信息;ios系统则是一串随机uuid!
可以将设备mac保存至设备的蓝牙广播字段中。
- 6、当识别到设备后,此时需要进行连接操作
连接操作使用下列方式:
function startConnectTo(obj) {
wx.showLoading({
title: '连接蓝牙设备中...',
});
wx.createBLEConnection({
deviceId: obj.data.lockMac,
success: function(res) {
wx.hideLoading();
wx.showToast({
title: '连接成功',
icon: 'success',
duration: 1000
})
isConnected = true;
console.log("连接设备成功");
console.log("连接success回调:" + JSON.stringify(res));
stopScanAroundDevice(obj);
startCommitServices(obj);
},
fail: function(res) {
wx.hideLoading();
wx.showToast({
title: '连接设备失败',
icon: 'success',
duration: 1000
})
console.log("连接设备失败")
console.log("连接fail回调:" + res);
obj.setData({
btnDisabled: false,
});
quit(obj);
}
})
}
- 7、开启服务(兼容ios),开启notify和特征值数据变化监听
function startCommitServices(obj) {
wx.getBLEDeviceServices({
deviceId: obj.data.lockMac,
success(res) {
console.log(JSON.stringify(res))
console.log('device services:', res.services)
console.log('device services:', apps.ab2hex(res))
console.log('device services:', apps.ab2hex(res.services))
wx.getBLEDeviceCharacteristics({
deviceId: obj.data.lockMac,
serviceId: apps.bleProperties.bleServiceUUID,
success: function(res) {
console.log("wx.getBLEDeviceCharacteristics ---->\n" + JSON.stringify(res))
setTimeout(() => {
wx.notifyBLECharacteristicValueChange({
characteristicId: apps.bleProperties.readCharacUUID,
deviceId: obj.data.lockMac,
serviceId: apps.bleProperties.bleServiceUUID,
state: true,
type:"notification",
success:function(res){
console.log("wx.notifyBLECharacteristicValueChange success回执:\n"+JSON.stringify(res));
console.log("###################################### 11111111");
wx.onBLECharacteristicValueChange(function(res) {
console.log("###################################### 22222222");
console.log("-----> wx.onBLECharacteristicValueChange 监听事件:\n" + JSON.stringify(res));
isOpenCharacValueChange = true;
setTimeout(() => {
sendOpenlockKeys(obj);
}, apps.bleProperties.writeSuccessBegainReadTime);
},
fail:function(){
wx.showModal({
title: '温馨提示',
content: '开启notify失败!',
showCancel: false
});
obj.setData({
btnDisabled: false,
});
quit(obj);
}
})
}, apps.bleProperties.bleNotifyTimeout);
},
fail: function() {
wx.showModal({
title: '温馨提示',
content: '获取特征对象失败!',
showCancel: false
});
obj.setData({
btnDisabled: false,
});
quit(obj);
}
})
},
fail: function() {
wx.showModal({
title: '温馨提示',
content: '获取服务失败!',
showCancel: false
});
obj.setData({
btnDisabled: false,
});
quit(obj);
}
})
}
【注意:】开启通信服务后,不要立即去开启notify,不然几率报错!
function sendOpenlockKeys(obj) {
console.log("sendOpenlockKeys");
writeCharacVal(obj, buffer);
}
function writeCharacVal(obj, sendValue) {
console.log("准备向设备发送信息");
wx.writeBLECharacteristicValue({
deviceId: obj.data.lockMac,
serviceId: apps.bleProperties.bleServiceUUID,
characteristicId: apps.bleProperties.writeCharacUUID,
value: sendValue,
success: function(res) {
console.log("发送开锁命令成功回调:\n" + JSON.stringify(res));
isFirstWrite = true;
},
fail(res) {
console.log("写数据失败回调" + res);
console.log("发送命令次数:" + isFirstWriteNum);
wx.showModal({
title: '温馨提示',
content: '发送开锁命令失败!',
showCancel: false,
})
quit(obj);
}
})
}
注意发送的数据类型为ArrayBuffer ,需要对字符串数据进行转换:
strToArrayBuffer: function(aes, str) {
var strtoHexArray = aes.hex_to_bytes(str);
var buffer = new ArrayBuffer(str.length >> 1);
var bufView = new DataView(buffer);
for (var i = 0, len = (str.length >> 1); i < len; i++) {
bufView.setUint8(i, strtoHexArray[i]);
}
return buffer;
},
这里的aes.hex_to_bytes 是使用AES加解密 库中自带的方式。
将字符串类型的数据转换为十六进制的数组数据。
function hex_to_bytes(str) {
var len = str.length;
if (len & 1) {
str = '0' + str;
len++;
}
var bytes = new Uint8Array(len >> 1);
for (var i = 0; i < len; i += 2) {
bytes[i >> 1] = parseInt(str.substr(i, 2), 16);
}
return bytes;
}
function quit(obj) {
if(isOpenCharacValueChange){
wx.offBLECharacteristicValueChange({
success: function(res) {
console.log("quit wx.offBLECharacteristicValueChange 成功回调 清理手机蓝牙资源");
console.log(JSON.stringify(res));
},
fail(res) {
console.log("wx.offBLECharacteristicValueChange 资源失败回调 " + res);
}
})
}
if (isConnected) {
wx.closeBLEConnection({
deviceId: obj.data.lockMac,
success(res) {
console.log("quit() 断开连接操作成功回调");
isConnected = false;
},
fail(res) {
console.log("quit() 断开连接操作失败回调");
}
});
}
if (isStartScan) {
wx.closeBluetoothAdapter({
success: function(res) {
console.log("quit wx.closeBluetoothAdapter 成功回调 清理手机蓝牙资源");
console.log(JSON.stringify(res));
},
fail(res) {
console.log("清理手机端 蓝牙 资源失败回调 " + res);
}
})
}
obj.setData({
btnDisabled: false,
});
}
关于notify开启成功后,onBLECharacteristicValueChange无法监听的问题
 【解决方式】
可以和设备开发工程师商讨兼容性解决方式。 比如:写一次其他信息,再写一次真实数据信息。
|