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.最終效果展示

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章