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

吐槽:

先说一下心路历程,因为个人开发的一个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.dataset
		this.deviceId = ds.deviceId
		//将设备名称也传递给全局变量
		this.deviceName = ds.name
		uni.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.最终效果展示

猜你喜欢

转载自blog.csdn.net/qq_42539194/article/details/111118416
今日推荐