吐槽:
先說一下心路歷程,因爲個人開發的一個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));
}
})
},
這裏我對手機發送的數據進行了一個進制轉換。到目前爲止就可以實現手機與藍牙設備的通信了。當然自己實際操作肯定不能這麼順利。各種問題還是會有,目前我把我遇到的坑都寫了出來,後續也歡迎補充。