最近随项目接触了下小程序的BLE开发。体会颇多。
小程序BLE开发前的准备操作:
1、熟读小程序开发文档,文档链接。
2、微信开发者工具。
3、对应的BLE蓝牙设备,我们需要对BLE设备进行读写操作。
一、小程序BLE开发API简介
微信小程序目前有蓝牙 API 共 18 个,其中操作蓝牙适配器的共有 4 个,分别是
wx.openBluetoothAdapter 初始化蓝牙适配器
wx.closeBluetoothAdapter 关闭蓝牙模块
wx.getBluetoothAdapterState 获取本机蓝牙适配器状态
wx.onBluetoothAdapterStateChange 监听蓝牙适配器状态变化事件
其中,扫描和获取周围BLE设备的有4个(这4个操作方式与普通蓝牙一样):
wx.startBluetoothDevicesDiscovery 开始搜寻附近的蓝牙外围设备
wx.stopBluetoothDevicesDiscovery 停止搜寻附近的蓝牙外围设备
wx.getBluetoothDevices 获取所有已发现的蓝牙设备
wx.onBluetoothDeviceFound 监听寻找到新设备的事件
连接BLE设备的2个:
wx.createBLEConnection 连接低功耗蓝牙设备
wx.closeBLEConnection 断开与低功耗蓝牙设备的连接
连接成功后,读写BLE对应特征对象的数据:
wx.getConnectedBluetoothDevices 根据 uuid 获取处于已连接状态的设备
wx.getBLEDeviceServices 获取蓝牙设备所有 service(服务)
wx.getBLEDeviceCharacteristics 获取蓝牙设备所有 characteristic(特征值)
wx.readBLECharacteristicValue 读取低功耗蓝牙设备的特征值的二进制数据值
wx.writeBLECharacteristicValue 向低功耗蓝牙设备特征值中写入二进制数据
wx.notifyBLECharacteristicValueChange 启用低功耗蓝牙设备特征值变化时的 notify 功能
wx.onBLECharacteristicValueChange 监听低功耗蓝牙设备的特征值变化
wx.onBLEConnectionStateChange 监听低功耗蓝牙连接的错误事件
二、微信小程序开发前需要注意的事项
android微信支持BLE蓝牙的微信版本为:6.5.7;
android 6.0 以上的手机未打开系统定位服务时,搜索不到蓝牙设备;
ios微信支持BLE蓝牙的微信版本为:6.5.6。
所以做BLE蓝牙开发,为了提高用户的小程序体验感,需要对用户使用的微信版本以及android版本进行判断,并做合理的解决方式。
下面是获取手机当前微信版本和手机系统版本的封装方法,将方法放入apps.js全局js文件中:
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
}
}
所以,在实际的操作中,比如点击了按钮需要调用手机系统蓝牙去扫描操作之前,需要校验以上的各项参数。整体代码:
//判断系统版本、微信版本、定位服务等权限和信息
function checkPhoneInfo(obj){
//Android 从微信 6.5.7 开始支持,iOS 从微信 6.5.6 开始支持
//第一项,如果手机是android系统,需要判断版本信息
if (apps.getPlatform() == "android" && versionCompare("6.5.7", apps.getVersion())) {
wx.showModal({
title: '提示',
content: '当前微信版本过低,请更新至最新版本',
showCancel: false
});
obj.setData({
btnDisabled: false,
});
//执行quit机制
quit(obj);
return;
}
//第二项,如果手机是ios系统,需要判断版本信息
if (apps.getPlatform() == "ios" && versionCompare("6.5.6", apps.getVersion())) {
wx.showModal({
title: '提示',
content: '当前微信版本过低,请更新至最新版本',
showCancel: false
});
obj.setData({
btnDisabled: false,
});
//执行quit机制
quit(obj);
return;
}
//版本 以及 平台校验完毕后 需要判断蓝牙的相关信息
//微信小程序 android6.0手机需要开启位置服务,否则扫描不到设备
console.log("当前系统版本:" + apps.getSystem());//Android 8.1.0
console.log("当前微信版本:" + apps.getVersion());
if (apps.getPlatform() == "android") {
console.log("android手机 当前系统版本号:" + apps.getSystem().replace("Android", "").replace(" ", ""));
//android版本高于6.0.0
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机制
quit(obj);
}
}
})
}else{
console.log("点击了取消操作");
obj.setData({
btnDisabled: false,
});
//执行quit机制
quit(obj);
}
}
})
}else {
//存在权限,调用封装的蓝牙方式继续进行
bleOperateFun(obj);
}
},
fail: function (res) {
wx.showToast({
title: '调用授权窗口失败',
icon: 'success',
duration: 1000
});
obj.setData({
btnDisabled: false,
});
//执行quit机制
quit(obj);
}
})
} else if (!versionCompare(apps.getSystem().replace("Android", "").replace(" ", "")), "4.3.0") {
//系统版本低于4.3的,使用不了ble蓝牙
wx.showModal({
title: '温馨提示',
content: '您的手机系统版本较低,无法操作BLE蓝牙设备',
showCancel:false,
});
obj.setData({
btnDisabled: false,
});
//执行quit机制
quit(obj);
}else {
console.log("系统版本低于6.0.0但高于4.3.0");
//除去android系统的手机(由于最开始过滤了能支持蓝牙的最低微信版本 所以此处无需再判断)
bleOperateFun(obj);
}
}
//除android以外的ios或者其他系统
bleOperateFun(obj);
}
三、BLE蓝牙开发,开启当前手机ble蓝牙适配器,注册相关的监听事件
function bleOperateFun(obj){
//1、判断手机蓝牙是否开启
wx.openBluetoothAdapter({
success: function(res) {
console.log("初始化蓝牙适配器成功");
//1、开启蓝牙适配器的状态变化监听
wx.onBluetoothAdapterStateChange(function(res){
console.log("蓝牙适配器状态变化",res);
});
//2、开启搜索周围设备的情况监听事件 (扫描 开锁 两次按钮点击 针对同一个设备只执行了一次)
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));
});
});
// 3、连接状态变化监听 --监听低功耗蓝牙连接状态的改变事件。包括开发者主动连接或断开连接,设备丢失,连接异常断开等等
wx.onBLEConnectionStateChange(function(res){
console.log("蓝牙设备连接状态监听回调:\n"+JSON.stringify(res));
});
// 4、读取特征值数据监听事件 ---- wx.readBLECharacteristicValue(Object object)调用后,若要取得其中的数据,需要再此回调中获取
wx.onBLECharacteristicValueChange(function(res){
console.log("wx.onBLECharacteristicValueChange 监听事件:\n"+JSON.stringify(res));
})
//开启扫描周围设备模式
startScanAroundDevice(obj);
},
fail:function(res){
console.log("初始化蓝牙适配器失败")
wx.showModal({
title: '温馨提示',
content: '请检查手机蓝牙是否打开',
showCancel:false,
});
obj.setData({
btnDisabled: false,
});
//执行quit机制
quit(obj);
}
})
}
在开启扫描操作后,会触发设置好的 wx.onBluetoothDeviceFound 回调函数,当发现了设备,则会显示设备的mac,uuid,广播数据等。这里有一个重点:
获取广播数据,小程序中读取 BLE 广播数据使用 wx.onBluetoothDeviceFound 接口中的 advertisData,对应上面兼容问题的 devices 格式,如 devices.advertisData,这个数据是 ArrayBuffer类型的数据,如果需要显示至日志中,需要对其进行相关的转换处理操作,使用如下代码:
//广播数据为Array Buffer类型 ArrayBuffer转16进度字符
ab2hex: function(buffer) {
var hexArr = Array.prototype.map.call(
new Uint8Array(buffer),
function(bit) {
return ('00' + bit.toString(16)).slice(-2)
}
)
return hexArr.join('');
},
四、小程序BLE开发,扫描到指定设备后,需要对其进行连接操作
连接成功后,为了节省手机端的资源,需要对扫描进行关闭处理。(假设设置扫描开始,10秒后会有关闭操作流程;但可能在2秒的时候已经扫描到指定设备,我们知道,扫描操作及其耗电,所以为了避免过多的资源浪费,需要提前进行关闭操作)。
五、读写操作
1、按照微信提供的api,可以进行读写操作,读操作成功后会触发之前(二)中的设置监听事件 wx.onBLECharacteristicValueChange ,而写操作只有成功和失败没有回调。
2、写操作的api为
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));
},
fail(res){
console.log("写数据失败回调"+res);
wx.showModal({
title: '温馨提示',
content: '发送开锁命令失败!',
showCancel:false,
})
quit(obj);
}
})
其中的各项参数在api文档中都有充分的介绍,这里不说其他废话,但其中的 value 属性为 ArrayBuffer 类型,所以需要进行数据格式的转换操作
strToArrayBuffer:function(aes,str){
//首先将str转化为数组
var strtoHexArray = aes.hex_to_bytes(str);
//取一半数据的长度作为arraybuffer的长度
var buffer = new ArrayBuffer(str.length >> 1);
var bufView = new DataView(buffer);
//保存数据至arraybuffer中
for(var i = 0,len = (str.length >> 1);i
其中 aes.hex_to_bytes(str) 是我调用的库的方式,是将 十六进制的字符串(如:"6E"),转换成十六进制的数组类型。
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;
}
六、最后写操作中有几个潜在的重点
1、BLE 4.0 中发送一个数据包只能包含 20 字节的数据,大于 20 字节只能分包发送。没做ios的测试(人穷用不起苹果),就拿android来说,每次最大发送20字节的数据,之前就说过了写操作没有可设置的回调,所以不需要操作过于麻烦。只需要在写入20字节成功之后,延迟一段时间(这个时间不好控制,我用的小米8测试设置200ms无压力,不同手机设置值肯定不同,需要找到一个合理的参数做为一个统一的处理),再进行下一个包的发送。
2、当你扫描操作执行后,假如未扫描到设备,再次点击按钮进行扫描,即使有BLE设备在广播,也不会再扫描到设备。遇到这样的情况,需要释放手机端蓝牙的资源,代码如下(我的代码放于quit机制中):
//如果扫描操作执行了的 需要释放手机蓝牙的资源
if (isStartScan) {
wx.closeBluetoothAdapter({
success: function (res) {
console.log("quit wx.closeBluetoothAdapter 成功回调 清理手机蓝牙资源");
console.log(JSON.stringify(res));
},
fail(res) {
console.log("清理手机端 蓝牙 资源失败回调 " + res);
}
})
}
3、2019.03.04新增一个问题总结。android手机使用小程序的BLE模块,广播中的deviceId表示设备的mac信息,ios系统则是手机mac和设备mac加密产生的uuid值!连接设备也如此:安卓直接使用mac进行连接操作;但ios使用广播中读取到的UUID进行连接。
简单而言,全是扫描到设备后的deviceId信息
wx.onBluetoothDeviceFound(function (res) {
console.log("扫描周围设备详情返回:" + JSON.stringify(res));
//判断是否为 "创想物联" 的设备 若是则需要进行连接操作和关闭扫描操作(节省手机资源)
res.devices.forEach(device => {
console.log(device.name + "/" + device.localName);
})
})
obj.setData({
lockMac: device.deviceId,//2019.07.31在判断是当前设备后,使用扫描得到的deviceId进行连接操作(androd和ios)
});
wx.createBLEConnection({
deviceId: obj.data.lockMac,
success: function (res) {
}
})
4、2019.03.05新增一个问题。Android手机使用小程序操作BLE设备,连接成功后可以直接进行特征数据的获取;但ios直接调用时,会出现10004的报错 官方文档报错信息连接。
如何解决ios手机使用小程序BLE报错问题。
只需要在连接成功和读设备特征数据之间,进行一项开启通信服务操作即可,按照java android开发ble规范来说,连接成功后是需要优先开启服务的,所以android和ios都统一开启服务!!
wx.createBLEConnection({
deviceId: obj.data.lockMac,
success: function(res) {
wx.hideLoading();
wx.showToast({
title: '连接成功',
icon: 'success',
duration: 1000
})
console.log("连接success回调:"+JSON.stringify(res));
//关闭扫描
stopScanAroundDevice(obj);
//连接成功后 进行开锁流程操作(读 --- 写 -- 读)
//判断是android还是ios(流程不同)
if (apps.getPlatform() == "android"){
firstReadCharacVal(obj);
return;
}
//ios
wx.getBLEDeviceServices({
// 这里的 deviceId 需要已经通过 createBLEConnection 与对应设备建立链接
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(JSON.stringify(res))
console.log("wx.getBLEDeviceCharacteristics ---->\n" + apps.ab2hex(res));
firstReadCharacVal(obj);
},
fail:function(){
wx.showModal({
title: '温馨提示',
content: '获取特征对象失败!',
showCancel: false
});
quit(obj);
}
})
},
fail:function(){
wx.showModal({
title: '温馨提示',
content: '获取服务失败!',
showCancel:false
});
quit(obj);
}
})
},
fail: function (res) {
wx.hideLoading();
wx.showToast({
title: '连接设备失败',
icon: 'success',
duration: 1000
})
console.log("连接设备失败")
console.log("连接fail回调:"+res);
obj.setData({
btnDisabled: false,
});
//执行 quit 机制
quit(obj);
}
})
也就是说连接成功后,需要多进行一次wx.getBLEDeviceServices 和 wx.getBLEDeviceCharacteristics !!