吐槽:

先说一下心路历程,因为个人开发的一个APP,需要连接蓝牙模块进行设备控制和双向的数据通信,所以尝试用uni-app开发一个手机程序对购买的蓝牙模块进行连接,emm.......怎么说呢,理论上过程都是通的,但坑还是太多了。今天过程跑通了,特来总结一下。说明下,代码太长了,所以我准备分段说明展示,完整代码到时候我上传到github上,地址最后我写在评论里哈。进入正题...........


1.蓝牙通信整体流程

上图一共九个步骤就是创建uni-app/微信小程序连接蓝牙设备并进行通信的基本步骤。具体每个模块是怎么回事,请继续阅读。也可以直接转向官网查看。

微信小程序:

wx.readBLECharacteristicValue(Object object) | 微信开放文档 (qq.com)

uni-app:

https://uniapp.dcloud.io/api/system/ble?id=getbledeviceservices

说明:uni和小程序在API接口上基本是一毛一样的,所以在开发的时候可以互相参考着看下。因为微信小程序开发工具扫码真机测试速度快。当然uni自家的HbuilderX自带的真机运行基座也不错。就我本人来讲,我是两个一起参考的。需要注意的就是语法的细微差别。因为uni是基于Vue开发的,用的是Vue的写法,而小程序并不是(其实都差不多)

2.打开蓝牙适配器状态openBluetoothAdapter

 //开启蓝牙适配器初始化蓝牙模块openBluetoothAdapter() {//刷新蓝牙设备this.devices=[]uni.openBluetoothAdapter({success: (res) => {console.log("开启蓝牙适配器成功(openBluetoothAdapter success)", res);this.startBluetoothDevicesDiscovery();uni.showToast({title: "开始扫描设备",icon: "success",});},fail: (res) => {uni.showToast({title: "请开启蓝牙",icon: "none",});if (res.errCode === 10001) {uni.onBluetoothAdapterStateChange(function (res) {//监听蓝牙适配器是否打开,若打开则自动搜索蓝牙设备(onBluetoothAdapterStateChange)if (res.available) {this.startBluetoothDevicesDiscovery();}});}},});},

当用户打开了蓝牙的时候就会进入下一步查找蓝牙设备。如果用户没有打开蓝牙,可以通过onBluetoothAdapterStateChange进行判断提示。打开后进入设备搜索。

这里说明下,不论是微信小程序还是uni-app,调用方式都是这种(uni.某某/wx.某某),其都包含有三个回调函数,success,fail,complete。所以都可以直接使用就好了

3.开始搜寻附近的蓝牙外围设备startBluetoothDevicesDiscovery

此操作比较耗费系统资源,请在搜索并连接到设备后调用 stopBluetoothDevicesDiscovery 方法停止搜索,后期熟练了也可以直接写上对应的设备id,直接连他就ok了。

//开启蓝牙设备搜索startBluetoothDevicesDiscovery() {// 关闭蓝牙适配器的时候将其打开if (this._discoveryStarted) {return;}this._discoveryStarted = true;uni.startBluetoothDevicesDiscovery({allowDuplicatesKey: true,success: (res) => {console.log( "开始搜寻蓝牙设备成功(startBluetoothDevicesDiscovery success)", res);uni.showLoading({title: "正在搜索设备",});this.onBluetoothDeviceFound();},});},

4. 监听寻找到新设备的事件onBluetoothDeviceFound

//监听寻找到新设备的事件onBluetoothDeviceFound() {uni.onBluetoothDeviceFound((res)=>{if(res){uni.hideLoading();}res.devices.forEach(device=>{//过滤掉没有名字的设备if (!device.name && !device.localName) {return};   //这么操作是为了去除重复const foundDevices = this.devices//将数据中的数组复制一份,利用动态绑定方式,不断复制最新的数组const idx = this.inArray(foundDevices, 'deviceId', device.deviceId)if (idx === -1) {this.devices.push(device);//数组里没有的的就向里面添加数据,保证没有重复[uni写法]}})console.log(this.devices);});},

这里要注意的是要对搜索到的设备进行去重复操作,因为蓝牙搜索貌似是这样的,你只要没关闭它,他就一直搜索他,会出现大量的重复设备。这些设备你只需要向你的设备数组中放入一个就行了。

5.连接低功耗蓝牙设备createBLEConnection

官方目前只有低功耗蓝牙设备的接口,就是BLE蓝牙,和传统手机蓝牙有区别。所以你搜索的时候是找不到开着蓝牙的其他手机的。目前我了解的是智能家居设备,小米手环,华为手表等这类的是用的低功耗蓝牙设备。也可以购买BLE低功耗蓝牙模块进行测试,总之低功耗蓝牙设备将来的使用会更广,通过蓝牙模块也可以集成进其他硬件设备中进行控制。

我这个代码是做在了button按钮上,当用户点击某个设备想要连接时,这个设备的信息就传入参数e中,界面图最后展示。

createBLEConnection(e) {const ds = e.currentTarget.datasetthis.deviceId = ds.deviceId//将设备名称也传递给全局变量this.deviceName = ds.nameuni.createBLEConnection({deviceId:this.deviceId,success: (res) => {this.connected=true,console.log('连接时获取设备id',this.deviceId);setTimeout(() => {this.getBLEDeviceServices(this.deviceId);}, 1000);},fail:(err)=>{uni.showToast({title:'建立连接失败',icon:'none'})return}})this.stopBluetoothDevicesDiscovery()//此时停止蓝牙搜索},

这里面有一个巨坑:在uni-app中调用getBLEDeviceServices的时候,不要直接调,直接调是没有任何服务的!!!!!!!!!!!!!!!!一定要设置时间间隔,延迟调用。这个问题在微信小程序中没有,在uni中一定要延迟调用,延迟调用,延迟调用,延迟调用,延迟调用,延迟调用,延迟调用,延迟调用,延迟调用。

6.获取蓝牙设备所有服务getBLEDeviceServices

这里需要说明下,刚开始我也是对各种id云里雾里,其实就三个。简单的说:每个设备都有一个唯一的设备id(deviceId),每个设备中又有不同的服务,他们通过服务id区分(serviceId),有的是只读的,有的是只写的,有的是可读可写,有的可以监听,有的不可以。。。。。所以你要搞清楚那个服务是你要的,要不然后面所有操作都对了就是不行。每个服务id中又有不同的特征值id(characteristicId)也就是uuid,uuid(universally unique Identifier)通用唯一识别码。用来标识蓝牙设备所提供的服务,比如(音频传输、串口通信、打印服务、传真服务、网络服务、文件传输服务、信息同步服务等)。下面就是我当时做的时候的一个截图。

可以看到,我这个服务中所有的特征值id/uuid只有2和3支持读写,但是也不能用,因为他们的notify与indicate都是false,这意味着到时候传递数据时候,你写的程序无法获取数据的变化情况,说白了就是没法通信。所以你得从新换一个服务id,之后再看他里面的特征值id情况。

这个是我候选的一个服务id中的特征值id,发现他的notify是true的,证明可以用这个进行通信。notify和indicate只要有一个为true就行,不用都为true。

getBLEDeviceServices(deviceId) {uni.getBLEDeviceServices({deviceId:deviceId,success: (res) => {//serviceId固定死了this.getBLEDeviceCharacteristics(deviceId, this.serviceId)},fail:(err)=>{uni.showToast({title:'获取服务失败',icon:'none'})return}})},

这里我的服务id我写死了 所以就不在上res中找了,如果不想写死,可以上res中找,找到后存起来供下面使用。

7.获取蓝牙设备某个服务中所有特征值(characteristic)getBLEDeviceCharacteristics

这里面特征值id我也是之前console出来找到后直接写死了,就不要每次获取了。当监听到notify或者indicate为true是就要启动notifyBLECharacteristicValueChange。

启用低功耗蓝牙设备特征值变化时的 notify 功能,订阅特征值。注意:必须设备的特征值支持 notify 或者 indicate 才可以成功调用。

另外,必须先启用 notifyBLECharacteristicValueChange 才能监听到设备 characteristicValueChange 事件

uni.getBLEDeviceCharacteristics({//设备id与服务id必须要给,特征值id先不用获取,直接写死deviceId,serviceId,success: (res) => {if(res.characteristics[0].properties.read){console.log('该特征值可读');uni.readBLECharacteristicValue({deviceId,serviceId,characteristicId:this.characteristicId,});}if(res.characteristics[0].properties.write){console.log('该特征值可写');this.canWrite=true;//调用写this.writeBLECharacteristicValue()}//确保对应服务id下的特征值id具备监听数据变化的特性if (res.characteristics[0].properties.notify || res.characteristics[2].properties.indicate) {uni.notifyBLECharacteristicValueChange({deviceId,serviceId,characteristicId: this.characteristicId,state: true,//是否启用notify通知success:(res)=>{console.log('通知启用(notifyBLECharacteristicValueChange)',res);}})}},fail(res) {console.error('获取蓝牙设备特征值失败(getBLEDeviceCharacteristics)', res)}})

这里面我在判断出设备可读的时候就调用了readBLECharacteristicValue。

读取低功耗蓝牙设备的特征值的二进制数据值。注意:必须设备的特征值支持 read 才可以成功调用

8.监听低功耗蓝牙设备的特征值变化事件onBLECharacteristicValueChange

监听低功耗蓝牙设备的特征值变化事件。必须先启用 notifyBLECharacteristicValueChange 接口才能接收到设备推送的 notification。

这样设备发送数据给手机,手机才会获得。传递的值就是value(硬件设备向手机传递数据)。

uni.onBLECharacteristicValueChange((characteristic) => {//记录手机接受的数据this.myDataMeasure.push(this.ab2hex(characteristic.value));//记录目前通信的对象(和谁通信,特征值是多少,初始值00)const idx = this.inArray(this.chs, 'uuid', characteristic.characteristicId)const data = {}if (idx === -1) {this.chs.push({uuid: characteristic.characteristicId,value: this.ab2hex(characteristic.value)})} else {this.chs[idx] = {uuid: characteristic.characteristicId,value: this.ab2hex(characteristic.value)}}})

9.写入蓝牙特征值writeBLECharacteristicValue

这是手机向硬件设备进行写入写入二进制数据。注意:必须设备的特征值支持 write 才可以成功调用

writeBLECharacteristicValue() {//向蓝牙设备发送一个0x00的16进制数据let buffer = new ArrayBuffer(1)//可以自定义复合格式的视图let dataView = new DataView(buffer)dataView.setUint8(0, this.sendData)uni.writeBLECharacteristicValue({deviceId: this.deviceId,serviceId: this.serviceId,characteristicId: this.characteristicId,value: buffer,complete:()=>{//buffer本身是arraybuffer(arraybuffer要转换为16进制)console.log('十六进制',this.ab2hex(buffer));let sixNumber=this.ab2hex(buffer);console.log('十进制',this.hex2int(sixNumber));}})},

这里我对手机发送的数据进行了一个进制转换。到目前为止就可以实现手机与蓝牙设备的通信了。当然自己实际操作肯定不能这么顺利。各种问题还是会有,目前我把我遇到的坑都写了出来,后续也欢迎补充。

10.最终效果展示

uni-app(微信小程序)连接HC系列蓝牙模块并进行双向通信采坑总结相关推荐

  1. 微信小程序地图插件系列(二):微信小程序使用百度地图(不定期补充知识点)

    前言 当我们在使用一些地图相关软件的情况下,他会显示选择那种.一般都有高德地图,百度地图等.那么我们在微信小程序中也想使用这种功能需要怎么办呢? 本文地址链接 微信小程序-百度地图SDK官方文档 微信 ...

  2. [转]微信小程序之购物车 —— 微信小程序实战商城系列(5)

    本文转自:http://blog.csdn.net/michael_ouyang/article/details/70755892 续上一篇的文章:微信小程序之商品属性分类  -- 微信小程序实战商城 ...

  3. 微信小程序之仿淘宝分类入口 —— 微信小程序实战商城系列(2)

    分类入口,已经成为了商城项目必须的布局之一,这里以仿照淘宝的分类入口来做案例 下图红框部分,就是本文重点讲解部分,另外本文并没有写点击某个入口跳转页面. 如需学习页面跳转的同学,可以参考此文 微信小程 ...

  4. 微信小程序之图表系列——canvas绘制饼状图,带点击效果

    一图胜千言,相信很多开发者都没有绕开过图表制作这个坑,在小程序中也是,当然可以用第三方echart等制图插件来做,但小程序要求代码量最大12M,还得分好几个包,一个echart插件就将近1M,要是只做 ...

  5. uni开发微信小程序解决全局分享分销问题

    uni开发微信小程序解决全局分享分销问题 1. 需求 1.小程序内每个页面都要打开胶囊分享按钮并实现分销 2.分享功能应该是在用户登录之后才予以打开 3.不想做在每个页面都写分享钩子的傻逼操作 2.实 ...

  6. 微信小程序:商城系列专辑(开发指南+精品Demo)

    商城开发指南: 微信小程序商城模块 | 链接 使用zanui开发小程序微商城(模板组件的开发规范)| 链接 ecshop商城开发:用户信息的获取和缓存,地址信息的缓存 | 链接 微信小程序商城 - 基 ...

  7. 微信小程序之购物车 —— 微信小程序实战商城系列(5)

    续上一篇的文章:微信小程序之商品属性分类 -- 微信小程序实战商城系列(4) 自从认识某人后,我收获了两个成功.登录成功.付款成功,而且还拥有了自己的一辆车: 购物车 也发现了自己的不足之处: 余额不 ...

  8. 微信小程序 连接云数据库(不使用云函数)进行 登录、注册、查询(包括模糊查询)快速实现 亲测可用

    当连接MySQL的时候总是出现各种各样的小问题,可以选用微信小程序自带的云数据库 目 录 建立云数据库 建表 导入MySQL中的表 导出 导入 云数据库初始化 登录注册 注册功能 登录功能 查询(模糊 ...

  9. uniapp之APP/微信小程序/公众号支付

    涉及到微信支付的都需要从后端接口获取支付配置信息: await this.$http({apiName:"wxPayConfig",type:"POST",da ...

  10. 微信小程序-京东购物系列一

    一.node-js后台服务器创建 https://mlab.com/  monggoDb数据库 https://www.heroku.com node后台服务器 二.后台关联 1.keys.js mo ...

最新文章

  1. Spring Cloud微服务笔记(一)微服务与云概念
  2. JAVA 设计的七大原则
  3. Ant Design Pro入门之部署安装
  4. ORA-12899: value too large for column (actual: 27, maximum: 20)错误解决
  5. 《11.02-构建之法:现代软件工程-阅读笔记》
  6. 安装了opencv,怎么一直报错:找不到CAP_PROP_FRAME_COUNT
  7. Pytorch:GPU版代码改成CPU版(RuntimeError: torch.cuda.FloatTensor is not enabled.)
  8. php大小写转换,php中字母大小写转换函数
  9. 心电电路算法滤波_简述心电信号采集原理及电路设计
  10. c语言使用CodeBlocks软件,使用CodeBlocks学习C语言
  11. 酒桌上的那些礼仪规矩~
  12. 35个优秀的电子商务网站设计案例
  13. 国内产线 OLED 良率低,产能释放缓慢
  14. 强化学习中的基础概念和术语——spinning up文档翻译
  15. Moon Player正式登陆爱奇艺-奇遇VR应用商店
  16. 手机兼容性测试--testin云测
  17. 基于android 的影院售票
  18. UML-九种基本图形
  19. HashSet和HashMap
  20. renren_login_urllib

热门文章

  1. 你为什么遇不到好公司
  2. Unity_AR制作_80
  3. 淘宝网上卖F22飞机,一群强人提问
  4. 70-0001 Poisson Matting【泊松抠图】
  5. 消逝的光芒 Dying Light for Mac 跑酷僵尸游戏 动作生存游戏
  6. CGAL 4.11 官方文档 软件包概述 ——多边形类
  7. Java高级工程师必备知识!kafka消费topic命令
  8. UESTC 1639 云中谁寄锦书来?雁字回时,月满西楼
  9. leetcode1480.一维数组的动态和
  10. 虚拟服务器 被劫持,服务器DNS被劫持,服务器dns被劫持怎么解决?