微信小程序连接低功耗蓝牙总结 您所在的位置:网站首页 微信小程序连接蓝牙模块 微信小程序连接低功耗蓝牙总结

微信小程序连接低功耗蓝牙总结

2023-12-03 11:12| 来源: 网络整理| 查看: 265

前言

最近做完了一个项目(uniapp + vue2--uniapp中的蓝牙api与原生微信小程序api相同), 用微信小程序连接低功耗蓝牙(BLE)实现与硬件设备通信。期间根据微信开发文档进行蓝牙搜索,连接和通信并非一帆风顺,遇到了不少坑,闲下来时做一个总结。

注意:各API使用时,手机兼容问题和api下方要求的基础库版本

安卓

打开蓝牙 打开手机定位(才能搜索到蓝牙)

苹果手机

打开蓝牙 打开手机定位 文件目录结构

蓝牙初始化+搜索:【JR-Bluetooth.js】;

JR-Bluetooth.js export default class JRBluetooth { constructor(isIOS) { this.isIOS = isIOS; // 是否是iphone this.available = false; // 蓝牙是否可用/开启 this.discovering = false; // 是否正在搜索设备 this.discoveringTimer = null; // 搜索无设备提示延时器 this.isDiscovery = false; // 是否搜索到设备 this.discoveringInterval = 20000; // 搜索时长20s } // TODO 下面实现的方法 // ... } 1、蓝牙初始化和搜索蓝牙设备

初始化成功之后才能监听蓝牙适配器状态变化事件,两个回调函数根据自身项目的实现逻辑来。

/** JR-Bluetooth.js * 初始化蓝牙模块 * @param {function} discoveryCallback 搜索到设备后的回调 * @param {function} stateChangeCallback 状态变化的回调 */ initBluetooth(discoveryCallback,stateChangeCallback) { return new Promise((resolve, reject) => { uni.openBluetoothAdapter({ success: res => { console.log('初始化蓝牙模块成功!'); // 监听蓝牙适配器状态变化事件 this.onBluetoothStateChange(stateChangeCallback); // 开始搜寻附近的蓝牙外围设备 this.discoverBluetooth(discoveryCallback); resolve(); }, fail: errMsg => { console.log('初始化蓝牙模块失败'); // 监听蓝牙适配器状态变化事件 this.onBluetoothStateChange(stateChangeCallback); reject(); } }) }) }

搜索蓝牙设备,上报设备时间间隔设置之后,出现了拿到的设备列表混乱,不建议添加

坑1: 此项目中因为禁止搜索到其它的蓝牙,所以 services 中添加了主服务 UUID,这里UUID 必须写全才能只搜索此类型号的蓝牙设备。

/** JR-Bluetooth.js * 搜寻蓝牙设备 * @param {function} cb 搜索到设备后的回调 */ discoverBluetooth(cb) { if (!cb || typeof cb !== 'function') return; uni.showLoading({ title: '蓝牙搜索中' }); const self = this; uni.startBluetoothDevicesDiscovery({ services: [00001812], // 只搜索主服务 UUID 为 00001812 的设备 allowDuplicatesKey: false, // 不允许重复上报同一设备 success: () => { self.discoveringTimer = setTimeout(() => { // TODO // 进行超过指定时间还没搜到设备的处理 }, self.discoveringInterval); }, // interval: 200, // 上报设备的间隔 }) // 监听搜索蓝牙设备事件 self.onBluetoothDeviceFound().then(res => { cb && cb(res); }) }

下边的方法根据需要增删

/** JR-Bluetooth.js * 停止搜寻蓝牙设备 */ stopDiscovery() { uni.stopBluetoothDevicesDiscovery(); clearTimeout(this.discoveringTimer); } /** * close 本机蓝牙适配器 */ closeBluetooth() { // console.log('关闭蓝牙适配器'); uni.closeBluetoothAdapter(); clearTimeout(this.discoveringTimer); } /** * 卸载本机蓝牙的事件 */ unloadBluetooth() { this.closeBluetooth(); this.discovering && this.stopDiscovery(); } /** * 获取本机蓝牙适配器状态 */ getBluetoothState() { return new Promise((resolve, reject) => { uni.getBluetoothAdapterState({ success: res => { this.updateBluetoothState(res); // 更新蓝牙适配器状态 resolve(res); } }) }) } /** * 监听蓝牙适配器状态变化事件 * @param {function} cb 处理状态变化的函数 */ onBluetoothStateChange(cb) { uni.onBluetoothAdapterStateChange(res => { const { available, discovering } = res; this.updateBluetoothState(res); // 更新蓝牙适配器状态 if (!available && !discovering) { // 蓝牙断开 this.unloadBluetooth(); } cb && cb(res); }) } /** * 更新蓝牙适配器状态 */ updateBluetoothState(state) { // 是否可用 是否处于搜索状态 // boolean const { available, discovering } = state; this.available = available; this.discovering = discovering; }

重点:处理搜索到的蓝牙列表

坑2: 大家都知道,iPhone很安全,做过iPhone兼容的小伙伴应该深有体会,由于本项目中需要用到蓝牙mac地址,安卓手机可以直接获取,然而iphone手机只能拿到经过iphone处理后的 uuid,经过测试发现:IOS不同手机连接同一个蓝牙获取到的 UUID 是不一样的 。这里iphone如何得到蓝牙 mac地址呢:与硬件开发伙伴约定,他们将mac放入广播中,如下 小程序通过 解析 device.advertisData 拿到mac地址。

/** JR-Bluetooth.js * 监听搜索蓝牙设备事件 * @param {Array} devicesList 设备列表 */ onBluetoothDeviceFound(devicesList = []) { return new Promise((resolve, reject) => { uni.onBluetoothDeviceFound(res => { const devices = res.devices; const device = devices[0]; // 筛选有名称的设备 if (Utils.has(device['name'])) { if (this.isIOS) { // 如果是 iphone //重点 根据advertisData 取出mac进行拼接 if (device.advertisData) { // 此处0~6只针对本司低功耗蓝牙,各产品可能不同,需要经过测试确定 let bf = device.advertisData.slice(0, 6); let mac = Array.prototype.map.call(new Uint8Array(bf), x => ('00' + x.toString(16)).slice(-2)).join(':'); mac = mac.toUpperCase(); // 保存mac地址 device.advertisMacData = mac; } } devicesList.push(device); this.isDiscovery = true; } // 这里resolve只执行一次,不管监听到多少设备 resolve(devicesList); }) }) }

蓝牙连接:【JR-BLE.js】 serviceId 用于蓝牙连接成功后 向蓝牙写入(write)和订阅消息(notify).通过下方的 getBLEServices 方法获取

export default class JRBLE { constructor(deviceId, serviceId) { this.deviceId = deviceId; // 设备id this.serviceId = serviceId; // 主服务 uuid this.writeCharacteristicId = ""; // write 特征值uuid this.notifyCharacteristicId = ""; // notify 特征值uuid this.connectionTimeout = 20000; // 连接BLE timeout 时长 } // TODO 下面实现的方法 // ... } 2、低功耗蓝牙的连接

重点是获取到特征值(notifyCharacteristicId, writeCharacteristicId)

/** JR-BLE.js * 处理特征值,获取notifyCharacteristicId和writeCharacteristicId * @param {array} characteristics * @return {object} [notifyCharacteristicId, writeCharacteristicId] */ handleCharacteristics(characteristics) { const len = characteristics.length; for (let i = 0; i < len; i++) { const characteristic = characteristics[i]; // 如果根据 serviceId 匹配到 所用的蓝牙产品,直接把特征值写死,因为是唯一的。 if (this.serviceId == '6E400001-B5A3-F393-E0A9-E50E24DCCA9E' || this.serviceId.toLocaleUpperCase() == '6E400001-B5A3-F393-E0A9-E50E24DCCA9E') { this.notifyCharacteristicId = '6E400003-B5A3-F393-E0A9-E50E24DCCA9E' this.writeCharacteristicId = '6E400002-B5A3-F393-E0A9-E50E24DCCA9E' } else { // 获取notifyCharacteristicId if (characteristic.properties.notify) { this.notifyCharacteristicId = characteristic.uuid; } // 获取writeCharacteristicId if (characteristic.properties.write) { this.writeCharacteristicId = characteristic.uuid; } } } return { notifyCharacteristicId: this.notifyCharacteristicId, writeCharacteristicId: this.writeCharacteristicId } }

坑3: 注意 getBLEServices() 方法中一定要 延迟一段时间再调用 uni.getBLEDeviceServices() 方法。 为什么呢? 当调用完 createBLEConnection 方法,即BLE初始化成功后,不能立即去获取(即调用uni.getBLEDeviceServices API) BLE设备服务列表, 立即调用在有些手机中会获取服务失败

/** JR-BLE.js /** * 连接BLE * @param {function} cb 弹框回调 */ createBLEConnection() { return new Promise((resolve, reject) => { uni.createBLEConnection({ deviceId: this.deviceId, timeout: this.connectionTimeout, success: res => { // console.log("res:createBLEConnection " + JSON.stringify(res)); resolve(res); }, fail: err => { console.log(err); reject(err) // err.errCode == 10003 ? this.createBLEConnection() : reject(err) }, }) }) } /** * 获取某个型号BLE设备服务表 * 这里获取到的是一个列表,具体选取哪个serviceId 需要拿到每个serviceId后, * 去获取蓝牙设备对应服务中所有特征值(characteristic), 获取到的特征值能成功写入(write)和订阅(notify) * 消息, 就用这个serviceId, 此时serviceId 就可以写死到项目中***(只针对同型号的蓝牙).*** */ getBLEServices() { return new Promise((resolve, reject) => { // 连接成功之后需要延时,继续操作才不会出问题 setTimeout(() => { uni.getBLEDeviceServices({ deviceId: this.deviceId, success: res => { const serviceList = []; const services = res.services; const len = services.length; for (let i = 0; i < len; i++) { if (services[i].isPrimary) { serviceList.push(services[i]); } } // console.log(serviceList); resolve(serviceList); }, fail: err => { console.log(err); reject(err); } }) }, 600) }) } /** * 获取蓝牙设备某个服务中所有特征值(characteristic) * */ getBLEDeviceCharacteristics() { return new Promise((resolve, reject) => { uni.getBLEDeviceCharacteristics({ deviceId: this.deviceId, serviceId: this.serviceId, success: res => { // 处理特征值,获取notifyCharacteristicId和writeCharacteristicId const result = this.handleCharacteristics(res.characteristics); resolve(result); } }) }) } /** * 向低功耗蓝牙设备特征值中写入二进制数据 * @param {Object} buffer */ writeBLECharacteristicValue(buffer) { return new Promise((resolve, reject) => { uni.writeBLECharacteristicValue({ deviceId: this.deviceId, serviceId: this.serviceId, characteristicId: this.writeCharacteristicId, value: buffer, success: res => { resolve(res); }, fail: err => { console.log('message发送失败', JSON.stringify(err)); reject(err); } }); }); } /** * 启用低功耗蓝牙设备特征值变化时的 notify 功能,订阅特征值 */ notifyBLECharacteristicValue() { return new Promise((resolve, reject) => { uni.notifyBLECharacteristicValueChange({ state: true, // 启用 notify 功能 deviceId: this.deviceId, serviceId: this.serviceId, characteristicId: this.notifyCharacteristicId, success: res => { resolve(res) }, fail: err => { reject(err) console.log('notifyBLECharacteristicValueChange failed:' + err.errMsg); } }); }) } /** * 接收设备推送的 notification * @param {function} cb 处理回调 */ onBLECharacteristicValueChange(cb) { uni.onBLECharacteristicValueChange(function (res) { cb && cb(res.value) }) } /** * 监听低功耗蓝牙连接状态的改变事件。 * 包括开发者主动连接或断开连接,设备丢失,连接异常断开等等 */ onBLEConnectionStateChange() { return new Promise(resolve=>{ uni.onBLEConnectionStateChange(function (res) { // 该方法回调中可以用于处理连接意外断开等异常情况 // console.log(`device ${res.deviceId} state has changed, connected: ${res.connected}`) if (!res.connected) { resolve(res) } }) }) } /** * 断开联链接 */ closeBLEConnection() { uni.closeBLEConnection({ deviceId: this.deviceId, success: res => { // console.log(res) } }) }

调用: 【 test.vue 】。

import BluetoothJR from './JR-Bluetooth.js'; import BLEJR from './JR-BLE.js'; // serviceId 每种蓝牙产品不一样, 这里只是个例子,具体获取方法 JR-BLE.js中有说明 const serviceID = ['55535343-ad7d-2bc5-8fa9-9fafd205e455']; let JRBluetooth = new BluetoothJR(); // 初始化时的传入的回调函数根据自身需求实现. JRBluetooth.initBluetooth() .then(res => { // 初始化成功 createBLEConnect(deviceId); // 连接BLE }) .catch(() => { // 初始化失败 }) function createBLEConnect(deviceId) { JRBLE = new BLEJR(deviceId, serviceId); // 停止本机的蓝牙搜索 JRBluetooth.stopDiscovery(); // 等待连接BLE设备 JRBLE.createBLEConnection() .then(res => { JRBLE.getBLEServices() .then(res => { // 处理获取到的 service 列表 handleBLEDeviceServices(res); // 监听BLE连接状态 JRBLE.onBLEConnectionStateChange().then(res => { // 处理异常 // this.handleBLEStateChange(res); }); }) .catch(error => { // this.handleBLEConnectError(error, cb); }); }) .catch(error => { // this.handleBLEConnectError(error, cb); }); } /** * 处理BLE services * @param {Object} list */ function handleBLEDeviceServices(list) { if (!Array.isArray(list)) { return; } list.forEach(async item => { if (serviceId.indexOf(item.uuid) !== -1 || serviceId.indexOf(item.uuid.toLocaleUpperCase()) !== -1 || serviceId.indexOf(item.uuid.toLocaleLowerCase()) !== -1) { // 把当前服务 uuid 更新一下 JRBLE.serviceId = item.uuid; // 获取某个服务特征值 await JRBLE.getBLEDeviceCharacteristics(); // 启用 notify await JRBLE.notifyBLECharacteristicValue(); // 监听特征变化 这里接收BLE发来的数据,可传入一个函数(handleReceiveData)去处理 JRBLE.onBLECharacteristicValueChange(handleReceiveData); } }); } 结束语

wx小程序连接低功耗蓝牙基本就这些流程, 里边有不少坑, 从初始化到连接到写入数据, 比较麻烦的点是获取 serviceId, 即主服务 service, 还有主服务对应的 write 与 notify 特征值,会耗费一点时间去测试. 下一篇实现一下分包发送.



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有