连接步骤
-
初始化蓝牙
// 蓝牙初始化 openBluetooth() { let that = this wx.openBluetoothAdapter({ success: (res) => { console.log(res); that.getBluetoothState(); }, fail: (err) => { console.log(err); } }) }
-
停止搜索附近的蓝牙外围设备
// 停止搜索附近的蓝牙外围设备 stopBluetoothDevicesDiscovery() { wx.stopBluetoothDevicesDiscovery({ success: (res) => { console.log('停止搜索蓝牙设备:', res.errMsg); wx.offBluetoothDeviceFound() }, fail: (res) => { console.log('停止搜索蓝牙设备失败:', res.errMsg); } }) }
-
获取本机蓝牙适配器状态
// 获取本机蓝牙适配器状态 getBluetoothState() { let that = this wx.getBluetoothAdapterState({ success: (res) => { console.log(res); that.startBluetoothDevices() }, fail: (err) => { console.log(err); } }) }
-
开始搜寻附近的蓝牙外围设备
// 开始搜寻附近的蓝牙外围设备 startBluetoothDevices() { let that = this wx.startBluetoothDevicesDiscovery({ powerLevel: 'high', services: ['主服务UUID'], // 不填则搜索周围所有设备 success: (res) => { console.log("开始搜寻附近的蓝牙外围设备", res); that.onBluetoothDeviceFound() }, fail: (err) => { console.log("开始搜寻附近的蓝牙外围设备", err); } }) }
其中services属性是蓝牙设备的主服务UUID,需要设备能够广播出自己的主服务才能正常搜索到这类设备,不然就无法搜索到只能使用搜索周围设备的方案然后手动筛选。
-
开启监听搜索到新设备的事件
// 发现外围设备 onBluetoothDeviceFound() { let that = this console.log("发现外围设备"); wx.onBluetoothDeviceFound(function (res) { console.log("开锁", res); // that.getBluetoothDevices() }) }
onBluetoothDeviceFound这个方法是个事件监听,如果在筛选阶段找到的目标设备需要手动调用停止监听事件也就是上方定义的 “停止搜索附近的蓝牙外围设备”这个方法。
-
获取在蓝牙模块生效期间所有搜索到的蓝牙设备(包括以搜索到的)
// 获取在蓝牙模块生效期间所有已发现的蓝牙设备 getBluetoothDevices() { let that = this wx.getBluetoothDevices({ success: (res) => { // console.log(res); that.getOtherDevices(res) // 手动筛选出目标数据 }, fail: (err) => { console.log(err); } }) },
在上述“开启监听搜索到新设备的事件”中为什么会将这个方法其注掉呢?因为是事件监听所以会一直被执行,如果嵌套调用会一直执行这个方法,一直获取周围的设备有设备进来就能被监听到获取到看似是没有问题,但是如果需要手动筛选符合条件的设备,就会一直执行这个方法,就会导致开启设备很慢。 既然这么的麻烦为什么还要用这个方法而不直接在监听中写逻辑呢?这个方式也是可行的,但是会存在搜索过的设备再次去搜索获取不到的情况。监听获取周围设备是一次只获取到单个设备,而getBluetoothDevices这个获取的是多个设备。
-
找到符合条件的设备
// 找到符合条件的设备 clearOtherDevices(data) { let that = this let devicesLength = data.devices.length // 这里怎么去获取目标设备都行 我这里用的是for 也可以使用.find() for (var i = 0; i < devicesLength; i++) { if(条件){ that.createBLEConnection() break; } } },
这里是寻找目标设备,但是如果我以知晓连接设备需要初始化蓝牙适配器并且也知道连接设备需要mac地址并填写在createBLEConnection这个API中的deviceId属性中,那为什么不能直接在初始化蓝牙适配器中直接去连接设备呢?这个方法是可行的,是个不错的想法,这边获取的deviceId是D4:AD:20:57:3F:E6这种的,安卓中这样使用是没有问题,但是IOS系统的deviceId是UUID的形式去连接而这中deviceId只有在搜索设备中才能获取到这中UUID形式的deviceId。既然IOS与安卓的deviceId不同那么我改如何去寻找目标设备呢?寻找目标设备可以按照固定格式的蓝牙名称匹配搜索,若蓝牙名称相同可以用已知设备的mac值匹配搜索,两个系统的deviceId不同可以根据搜索到设备列表的advertisData这个属性进行转化成D4:AD:20:57:3F:E6这种格式的mac值。
const ArrayBufferToArray = (arrayBuffer) => { return Array.prototype.slice.call(new Uint8Array(arrayBuffer)); }; let advertisData = ArrayBufferToArray(device.advertisData).slice(2, 8); console.log(advertisData.join(','));
ps:同一个蓝牙设备不同的IOS设备获取的deviceId是不一样的!!
-
连接蓝牙低功耗设备
// 蓝牙链接 createBLEConnection() { let that = this; wx.createBLEConnection({ deviceId: that.data.mac, success: (res) => { //停止搜索 that.stopBluetoothDevicesDiscovery(); that.getBLEDeviceServices() }, fail: (err) => { console.log('连接低功耗蓝牙失败,错误码:', err); } }) }
-
获取蓝牙低功耗设备所有服务
// 获取服务 getBLEDeviceServices() { let that = this let services = []; wx.getBLEDeviceServices({ deviceId: that.data.mac, success: (res) => { console.log("获取服务成功", res); services = res.services; // 找到自己需要的主服务UUID this.setData({ serviceId: res.services[1].serviceId }) }, fail: (err) => { console.log("获取服务失败", err); } }) }
-
获取蓝牙低功耗设备某个服务中所有特征
// 获取某个服务下的所有特征值 getBLEDeviceCharacteristics() { let charactArray = []; let that = this wx.getBLEDeviceCharacteristics({ deviceId: that.data.mac, serviceId: that.data.serviceId, // 主服务UUID success: (res) => { console.log("服务下的所有特征值成功", res); charactArray = res.characteristics; if (charactArray.length <= 0) { console.log('获取特征值失败,请重试!'); return; } for (var i = 0; i < charactArray.length; i++) { // 获取可写的characteristicId if (charactArray[i].properties.write === true) { // console.log(charactArray[i].uuid); that.setData({ characteristicId: charactArray[i].characteristicId }) } // 获取可监听的characteristicId if (charactArray[i].properties.notify === true) { // console.log(charactArray[i].uuid); that.setData({ notifyId: charactArray[i].characteristicId }) } } // 开启监听 that.notifyBLECharacteristicValueChange() that.sendMy() }, fail: (err) => { console.log("服务下的所有特征值失败", err); } }) },
-
启用蓝牙低功耗设备特征值变化时的 notify 功能,订阅特征
// 读数据 notifyBLECharacteristicValueChange() { let that = this wx.notifyBLECharacteristicValueChange({ state: true, //是否启用notify deviceId: that.data.devicesId, serviceId: that.data.serviceId, characteristicId: that.data.notifyId, //这个characteristicId是上面方法存的能读数据的uuid state: true, success: (res) => { console.log('notifyBLECharacteristicValueChange success:', res.errMsg, res); // console.log(JSON.stringify(res)); that.onBLECharacteristicValueChange(); }, }) },
-
监听蓝牙低功耗设备的特征值变化事件
// 监听蓝牙特征值变化 onBLECharacteristicValueChange() { let that = this wx.onBLECharacteristicValueChange(function (res) { let arrayBuffer = getApp().hex2ArrayBuffer(res.value) let arr = getApp().ArrayBufferToArray(arrayBuffer); let command = String(getApp().aesDecrypt(arr)).split(','); // 根据方法解析的数据判断设备的状态 if (判断) { } }); },
-
向蓝牙低功耗设备特征值中写入二进制数据
// 写入 writeToken() { let that = this; // 向蓝牙低功耗设备特征值中写入二进制数据 wx.writeBLECharacteristicValue({ deviceId: that.data.devicesId, serviceId: that.data.serviceId, characteristicId: that.data.characteristicId, value: that.data.value, writeType: that.data.writeType, success: (res) => { console.log("写入成功"); }, fail: (fail) => { console.log("写入失败", fail); }, }); },
需要将写入的值转为ArrayBuffer类型的数据,如果是数组需要将数组中的复杂格式的值转为普通格式的然后再将数组转为ArrayBuffer类型。
工具类
将字符串转换成ArrayBufer
/**
* 将字符串转换成ArrayBufer
* @param {字符串数据} str
*/
const string2buffer = (str) => {
let val = ""
if (!str) return;
let length = str.length;
let index = 0;
let array = []
while (index < length) {
array.push(str.substring(index, index + 2));
index = index + 2;
}
val = array.join(",");
// 将16进制转化为ArrayBuffer
return new Uint8Array(val.match(/[\da-f]{2}/gi).map(function (h) {
return parseInt(h, 16)
})).buffer
}
将ArrayBuffer转换成字符串
/**
* 将ArrayBuffer转换成字符串
* @param {buffer数据} buffer
* @return {字符串数据}
*/
const ab2hex = (buffer) => {
var hexArr = Array.prototype.map.call(
new Uint8Array(buffer),
function (bit) {
return ('00' + bit.toString(16)).slice(-2)
}
)
return hexArr.join('');
}
数组转ArrayBuffer
/**
* 数组转ArrayBuffer
* @param {数组} array
*/
const ArrayToArrayBuffer = (array) => {
return new Uint8Array(array).buffer;
};
ArrayBuffer转数组
/**
* ArrayBuffer转数组
* @param {arrayBuffer数组} arrayBuffer
* @return {数组}
*/
const ArrayBufferToArray = (arrayBuffer) => {
return Array.prototype.slice.call(new Uint8Array(arrayBuffer));
};
字符串转byte
// 字符串转byte
function stringToBytes(str) {
var array = new Int8Array(str.length)
for (var i = 0; i < str.length; i++) {
array[i] = str.charCodeAt(i)
}
return array.buffer
}
ArrayBuffer转Str
// ArrayBuffer转Str
function ArrayBufferToStr(value) {
var buffer = value
var dataview = new DataView(buffer)
var ints = new Uint8Array(buffer.byteLength)
var str = ''
for (var i = 0; i < ints.length; i++) {
str += String.fromCharCode(dataview.getUint8(i))
}
return str
}
结语
在整个流程中的监听在使用完毕后记得关闭,会有相应的关闭监听事件的API。此外在调试过程中会有遇到监听事件没有响应的情况,请查看写法与是否开启响应的前置条件,例如写入数据时成功写入数据才会有监听结果,箭头函数好像会有问题所以用的function。