在物聯網應用場景中,低功耗藍牙(BLE)憑借其低能耗、連接便捷的特點,成為設備間數據交互的重要方式。Uniapp 作為一款跨平臺開發框架,提供了豐富的 API 支持,使得在多個端實現低功耗藍牙功能變得輕松高效。本文將結合示例代碼,詳細講解如何在 Uniapp 中實現低功耗藍牙的連接、數據讀取與寫入操作。
一、開發準備
在開始編碼前,需確保開發環境已配置好 Uniapp 開發工具,同時要了解低功耗藍牙的基本概念,如設備、服務、特征值等。設備是藍牙連接的主體,服務是設備提供功能的集合,特征值則是具體的數據交互點,包含可讀、可寫等屬性 。
二、初始化藍牙模塊
在 Uniapp 中,使用uni.openBluetoothAdapter方法初始化藍牙模塊,同時通過監聽相關事件獲取藍牙狀態變化和搜索到的設備信息。
const openBluetooth = () => {uni.openBluetoothAdapter({success: (e) => {console.log('藍牙適配器打開成功');// 監聽藍牙適配器狀態變化uni.onBluetoothAdapterStateChange((res) => {console.log('藍牙適配器狀態變化:', res);});// 監聽搜索到新設備uni.onBluetoothDeviceFound((res) => {const devices = res.devices;console.log('搜索到新設備數量:', devices.length);// 處理搜索到的設備數據});},fail: (e) => {console.log('藍牙適配器打開失敗:', e);}});}
三、搜索藍牙設備
調用uni.startBluetoothDevicesDiscovery方法開始搜索周圍的藍牙設備,可通過傳入參數篩選特定設備。搜索完成后,及時調用uni.stopBluetoothDevicesDiscovery停止搜索,避免資源浪費。
const startDiscovery = () => {uni.startBluetoothDevicesDiscovery({success: (e) => {console.log('開始搜索藍牙設備成功');},fail: (e) => {console.log('開始搜索藍牙設備失敗:', e);}});}const stopDiscovery = () => {uni.stopBluetoothDevicesDiscovery({success: (e) => {console.log('停止搜索藍牙設備成功');},fail: (e) => {console.log('停止搜索藍牙設備失敗:', e);}});}
?
四、連接藍牙設備
在獲取到目標設備的deviceId后,使用uni.createBLEConnection方法連接設備,并監聽連接狀態變化。
const connectDevice = (deviceId) => {uni.createBLEConnection({deviceId: deviceId,success: (e) => {console.log('連接藍牙設備成功');},fail: (e) => {console.log('連接藍牙設備失敗:', e);}});// 監聽連接狀態變化uni.onBLEConnectionStateChange((res) => {if (res.connected) {console.log('藍牙設備已連接');} else {console.log('藍牙設備已斷開');}});}
五、獲取設備服務與特征值
連接設備后,通過uni.getBLEDeviceServices獲取設備服務列表,再利用uni.getBLEDeviceCharacteristics獲取指定服務的特征值列表,區分出可讀和可寫的特征值。
const getServices = (deviceId) => {uni.getBLEDeviceServices({deviceId: deviceId,success: (res) => {const services = res.services;console.log('獲取服務成功,服務數量:', services.length);// 處理服務數據},fail: (e) => {console.log('獲取服務失敗:', e);}});}const getCharacteristics = (deviceId, serviceId) => {uni.getBLEDeviceCharacteristics({deviceId: deviceId,serviceId: serviceId,success: (res) => {const characteristics = res.characteristics;console.log('獲取特征值成功,特征值數量:', characteristics.length);// 處理特征值數據,區分可讀可寫},fail: (e) => {console.log('獲取特征值失敗:', e);}});}
六、數據讀取與寫入
6.1 讀取數據
獲取到可讀特征值的characteristicId后,調用uni.readBLECharacteristicValue讀取數據。
const readValue = (deviceId, serviceId, characteristicId) => {uni.readBLECharacteristicValue({deviceId: deviceId,serviceId: serviceId,characteristicId: characteristicId,success: (res) => {const data = res.value;console.log('讀取數據成功:', data);},fail: (e) => {console.log('讀取數據失敗:', e);}});}
6.2 寫入數據
對于可寫特征值,將數據轉換為合適格式后,使用uni.writeBLECharacteristicValue寫入。
const writeValue = (deviceId, serviceId, characteristicId, data) => {// 數據格式轉換,如將字符串轉換為ArrayBufferconst buffer = new TextEncoder('utf - 8').encode(data);uni.writeBLECharacteristicValue({deviceId: deviceId,serviceId: serviceId,characteristicId: characteristicId,value: buffer,success: (res) => {console.log('寫入數據成功');},fail: (e) => {console.log('寫入數據失敗:', e);}});}
七、斷開連接與關閉藍牙
使用uni.closeBLEConnection斷開與設備的連接,通過uni.closeBluetoothAdapter關閉藍牙模塊。
const disconnectDevice = (deviceId) => {uni.closeBLEConnection({deviceId: deviceId,success: (e) => {console.log('斷開藍牙設備連接成功');},fail: (e) => {console.log('斷開藍牙設備連接失敗:', e);}});}const closeBluetooth = () => {uni.closeBluetoothAdapter({success: (e) => {console.log('關閉藍牙適配器成功');},fail: (e) => {console.log('關閉藍牙適配器失敗:', e);}});}
八、完整代碼
<template><view class="container"><button @click="openBluetooth">初始化藍牙模塊</button><button @click="startDiscovery">開始搜索藍牙設備</button><button @click="stopDiscovery">停止搜索藍牙設備</button><view class="input-group"><text>設備:</text><input v-model="selectedDeviceName" disabled /><button @click="selectDevice">選擇設備</button></view><button @click="connectDevice">連接藍牙設備</button><button @click="getServices">獲取設備服務</button><view class="input-group"><text>服務:</text><input v-model="selectedServiceId" disabled /><button @click="selectService">選擇服務</button></view><button @click="getCharacteristics">獲取服務的特征值</button><view class="input-group"><text>讀取特征值:</text><input v-model="selectedCharacteristicId" disabled /><button @click="selectCharacteristic">選擇</button></view><button @click="readValue">讀取特征值數據</button><view class="input-group"><text>讀取數據:</text><input v-model="readValueData" disabled style="width:60%" /></view><hr /><view class="input-group"><text>寫入特征值:</text><input v-model="selectedWCharacteristicId" disabled /><button @click="selectwCharacteristic">選擇</button></view><button @click="writeValue">寫入特征值數據</button><view class="input-group"><text>寫入數據:</text><input v-model="writeValueData" style="width:60%" /></view><button @click="disconnectDevice">斷開藍牙設備</button><button @click="closeBluetooth">關閉藍牙模塊</button><view class="output"><text>{{ outputText }}</text></view></view>
</template><script setup>import {ref} from 'vue'import {onLoad,onUnload} from '@dcloudio/uni-app'// 數據狀態const bds = ref([]) // 可連接設備列表const deviceId = ref(null)const bconnect = ref(false)const bss = ref([]) // 連接設備服務列表const serviceId = ref(null)const bscs = ref([]) // 連接設備服務對應的特征值列表const characteristicId = ref(null)const bscws = ref([]) // 可寫特征值列表const wcharacteristicId = ref(null)// UI綁定數據const selectedDeviceName = ref('')const selectedServiceId = ref('')const selectedCharacteristicId = ref('')const selectedWCharacteristicId = ref('')const readValueData = ref('')const writeValueData = ref('test')const outputText = ref('Bluetooth用于管理藍牙設備,搜索附近藍牙設備、連接實現數據通信等。')// 工具函數const buffer2hex = (value) => {let t = ''if (value) {const v = new Uint8Array(value)for (const i in v) {t += '0x' + v[i].toString(16) + ' '}} else {t = '無效值'}return t}const str2ArrayBuffer = (s, f) => {const b = new Blob([s], {type: 'text/plain'})const r = new FileReader()r.readAsArrayBuffer(b)r.onload = () => {if (f) f.call(null, r.result)}}// 重設數據const resetDevices = (d, s) => {if (!d) {bds.value = []deviceId.value = nullselectedDeviceName.value = ''}if (!s) {bss.value = []serviceId.value = nullselectedServiceId.value = ''}bscs.value = []bscws.value = []characteristicId.value = nullwcharacteristicId.value = nullselectedCharacteristicId.value = ''selectedWCharacteristicId.value = ''}// 輸出日志const outLine = (text) => {outputText.value += '\n' + text}const outSet = (text) => {outputText.value = text}// 藍牙操作函數const openBluetooth = () => {outSet('打開藍牙適配器:')uni.openBluetoothAdapter({success: (e) => {outLine('打開成功!')// 監聽藍牙適配器狀態變化uni.onBluetoothAdapterStateChange((res) => {outLine('onBluetoothAdapterStateChange: ' + JSON.stringify(res))})// 監聽搜索到新設備uni.onBluetoothDeviceFound((res) => {const devices = res.devicesoutLine('onBluetoothDeviceFound: ' + devices.length)for (const i in devices) {outLine(JSON.stringify(devices[i]))const device = devices[i]if (device.deviceId) {bds.value.push(device)}}if (!bconnect.value && bds.value.length > 0) {const n = bds.value[bds.value.length - 1].nameselectedDeviceName.value = n || bds.value[bds.value.length - 1].deviceIddeviceId.value = bds.value[bds.value.length - 1].deviceId}})// 監聽低功耗藍牙設備連接狀態變化uni.onBLEConnectionStateChange((res) => {outLine('onBLEConnectionStateChange: ' + JSON.stringify(res))if (deviceId.value === res.deviceId) {bconnect.value = res.connected}})// 監聽低功耗藍牙設備的特征值變化uni.onBLECharacteristicValueChange((res) => {outLine('onBLECharacteristicValueChange: ' + JSON.stringify(res))const value = buffer2hex(res.value)console.log(value)outLine('value(hex) = ' + value)if (characteristicId.value === res.characteristicId) {readValueData.value = value} else if (wcharacteristicId.value === res.characteristicId) {uni.showToast({title: value,icon: 'none'})}})},fail: (e) => {outLine('打開失敗! ' + JSON.stringify(e))}})}const startDiscovery = () => {outSet('開始搜索藍牙設備:')resetDevices()uni.startBluetoothDevicesDiscovery({success: (e) => {outLine('開始搜索成功!')},fail: (e) => {outLine('開始搜索失敗! ' + JSON.stringify(e))}})}const stopDiscovery = () => {outSet('停止搜索藍牙設備:')uni.stopBluetoothDevicesDiscovery({success: (e) => {outLine('停止搜索成功!')},fail: (e) => {outLine('停止搜索失敗! ' + JSON.stringify(e))}})}const selectDevice = () => {if (bds.value.length <= 0) {uni.showToast({title: '未搜索到有效藍牙設備!',icon: 'none'})return}const buttons = bds.value.map(device => {return device.name || device.deviceId})uni.showActionSheet({title: '選擇藍牙設備',itemList: buttons,success: (res) => {selectedDeviceName.value = bds.value[res.tapIndex].name || bds.value[res.tapIndex].deviceIddeviceId.value = bds.value[res.tapIndex].deviceIdoutLine('選擇了"' + (bds.value[res.tapIndex].name || bds.value[res.tapIndex].deviceId) + '"')}})}const connectDevice = () => {if (!deviceId.value) {uni.showToast({title: '未選擇設備!',icon: 'none'})return}outSet('連接設備: ' + deviceId.value)uni.createBLEConnection({deviceId: deviceId.value,success: (e) => {outLine('連接成功!')},fail: (e) => {outLine('連接失敗! ' + JSON.stringify(e))}})}const getServices = () => {if (!deviceId.value) {uni.showToast({title: '未選擇設備!',icon: 'none'})return}if (!bconnect.value) {uni.showToast({title: '未連接藍牙設備!',icon: 'none'})return}resetDevices(true)outSet('獲取藍牙設備服務:')uni.getBLEDeviceServices({deviceId: deviceId.value,success: (e) => {const services = e.servicesoutLine('獲取服務成功! ' + services.length)if (services.length > 0) {bss.value = servicesfor (const i in services) {outLine(JSON.stringify(services[i]))}if (bss.value.length > 0) {selectedServiceId.value = serviceId.value = bss.value[bss.value.length - 1].uuid}} else {outLine('獲取服務列表為空?')}},fail: (e) => {outLine('獲取服務失敗! ' + JSON.stringify(e))}})}const selectService = () => {if (bss.value.length <= 0) {uni.showToast({title: '未獲取到有效藍牙服務!',icon: 'none'})return}const buttons = bss.value.map(service => service.uuid)uni.showActionSheet({title: '選擇服務',itemList: buttons,success: (res) => {selectedServiceId.value = serviceId.value = bss.value[res.tapIndex].uuidoutLine('選擇了服務: "' + serviceId.value + '"')}})}const getCharacteristics = () => {if (!deviceId.value) {uni.showToast({title: '未選擇設備!',icon: 'none'})return}if (!bconnect.value) {uni.showToast({title: '未連接藍牙設備!',icon: 'none'})return}if (!serviceId.value) {uni.showToast({title: '未選擇服務!',icon: 'none'})return}resetDevices(true, true)outSet('獲取藍牙設備指定服務的特征值:')uni.getBLEDeviceCharacteristics({deviceId: deviceId.value,serviceId: serviceId.value,success: (e) => {const characteristics = e.characteristicsoutLine('獲取特征值成功! ' + characteristics.length)if (characteristics.length > 0) {bscs.value = []bscws.value = []for (const i in characteristics) {const characteristic = characteristics[i]outLine(JSON.stringify(characteristic))if (characteristic.properties) {if (characteristic.properties.read) {bscs.value.push(characteristic)}if (characteristic.properties.write) {bscws.value.push(characteristic)if (characteristic.properties.notify || characteristic.properties.indicate) {uni.notifyBLECharacteristicValueChange({deviceId: deviceId.value,serviceId: serviceId.value,characteristicId: characteristic.uuid,state: true,success: (e) => {outLine('notifyBLECharacteristicValueChange ' +characteristic.uuid + ' success.')},fail: (e) => {outLine('notifyBLECharacteristicValueChange ' +characteristic.uuid + ' failed! ' + JSON.stringify(e))}})}}}}if (bscs.value.length > 0) {selectedCharacteristicId.value = characteristicId.value = bscs.value[bscs.value.length - 1].uuid}if (bscws.value.length > 0) {selectedWCharacteristicId.value = wcharacteristicId.value = bscws.value[bscws.value.length - 1].uuid}} else {outLine('獲取特征值列表為空?')}},fail: (e) => {outLine('獲取特征值失敗! ' + JSON.stringify(e))}})}const selectCharacteristic = () => {if (bscs.value.length <= 0) {uni.showToast({title: '未獲取到有效可讀特征值!',icon: 'none'})return}const buttons = bscs.value.map(char => char.uuid)uni.showActionSheet({title: '選擇特征值',itemList: buttons,success: (res) => {selectedCharacteristicId.value = characteristicId.value = bscs.value[res.tapIndex].uuidoutLine('選擇了特征值: "' + characteristicId.value + '"')}})}let readInterval = nullconst readValue = () => {if (!deviceId.value) {uni.showToast({title: '未選擇設備!',icon: 'none'})return}if (!bconnect.value) {uni.showToast({title: '未連接藍牙設備!',icon: 'none'})return}if (!serviceId.value) {uni.showToast({title: '未選擇服務!',icon: 'none'})return}if (!characteristicId.value) {uni.showToast({title: '未選擇讀取的特征值!',icon: 'none'})return}outSet('讀取藍牙設備的特征值數據: ')uni.readBLECharacteristicValue({deviceId: deviceId.value,serviceId: serviceId.value,characteristicId: characteristicId.value,success: (e) => {outLine('讀取數據成功!')},fail: (e) => {outLine('讀取數據失敗! ' + JSON.stringify(e))}})}const selectwCharacteristic = () => {if (bscws.value.length <= 0) {uni.showToast({title: '未獲取到有效可寫特征值!',icon: 'none'})return}const buttons = bscws.value.map(char => char.uuid)uni.showActionSheet({title: '選擇特征值',itemList: buttons,success: (res) => {selectedWCharacteristicId.value = wcharacteristicId.value = bscws.value[res.tapIndex].uuidoutLine('選擇了特征值: "' + wcharacteristicId.value + '"')}})}const writeValue = () => {if (!deviceId.value) {uni.showToast({title: '未選擇設備!',icon: 'none'})return}if (!bconnect.value) {uni.showToast({title: '未連接藍牙設備!',icon: 'none'})return}if (!serviceId.value) {uni.showToast({title: '未選擇服務!',icon: 'none'})return}if (!wcharacteristicId.value) {uni.showToast({title: '未選擇寫入的特征值!',icon: 'none'})return}if (!writeValueData.value || writeValueData.value === '') {uni.showToast({title: '請輸入需要寫入的數據',icon: 'none'})return}str2ArrayBuffer(writeValueData.value, (buffer) => {outSet('寫入藍牙設備的特征值數據: ')uni.writeBLECharacteristicValue({deviceId: deviceId.value,serviceId: serviceId.value,characteristicId: wcharacteristicId.value,value: buffer,success: (e) => {outLine('寫入數據成功!')},fail: (e) => {outLine('寫入數據失敗! ' + JSON.stringify(e))}})})}const disconnectDevice = () => {if (!deviceId.value) {uni.showToast({title: '未選擇設備!',icon: 'none'})return}resetDevices(true)outSet('斷開藍牙設備連接:')uni.closeBLEConnection({deviceId: deviceId.value,success: (e) => {outLine('斷開連接成功!')},fail: (e) => {outLine('斷開連接失敗! ' + JSON.stringify(e))}})}const closeBluetooth = () => {outSet('關閉藍牙適配器:')resetDevices()uni.closeBluetoothAdapter({success: (e) => {outLine('關閉成功!')bconnect.value = false},fail: (e) => {outLine('關閉失敗! ' + JSON.stringify(e))}})}
</script><style>.container {padding: 20px;}button {margin: 10px 0;padding: 10px;background-color: #007aff;color: white;border-radius: 5px;border: none;}.input-group {display: flex;align-items: center;margin: 10px 0;}.input-group input {flex: 1;border: 1px solid #ccc;padding: 5px;margin: 0 5px;}.input-group button {margin: 0;padding: 5px 10px;}.output {margin-top: 20px;padding: 10px;background-color: #f5f5f5;border: 1px solid #ddd;white-space: pre-wrap;max-height: 200px;overflow-y: auto;}
</style>
?
九、注意事項
? ? ? 1、權限問題:在不同平臺上,需確保應用已獲取藍牙相關權限。例如在 Android 平臺,需在AndroidManifest.xml中添加藍牙權限聲明。
? ? ? 2、兼容性:不同設備的藍牙服務和特征值 UUID 可能不同,需根據實際設備文檔進行適配。
? ? ? 3、錯誤處理:完善各 API 調用的錯誤處理邏輯,及時向用戶反饋操作結果。
以上就是在 Uniapp 中實現低功耗藍牙連接并讀寫數據的完整流程。若你在實踐中有優化需求或遇到問題,歡迎隨時分享,我們一起探討解決。
?