https://developer.huawei.com/consumer/cn/doc/best-practices/bpta-bluetooth-low-energy
藍牙權限
module.json5
{"module": {"requestPermissions": [{"name": "ohos.permission.ACCESS_BLUETOOTH","reason": "$string:app_name","usedScene": {"when": "inuse"}}],}
}
藍牙管理工具類
- 申請藍牙權限 (requestPermission)
○ 使用@kit.AbilityKit中的權限管理模塊創建AtManager實例。
○ 請求用戶授權ohos.permission.ACCESS_BLUETOOTH權限。
○ 如果用戶拒絕,則跳轉到設置界面讓用戶授權。
○ 異常時通過promptAction顯示錯誤信息。 - 檢查藍牙權限 (checkPermission)
○ 獲取當前應用的Bundle信息。
○ 檢查是否已授予藍牙權限。
○ 返回布爾值,表示權限是否被授予。 - 檢查藍牙開關狀態 (checkOpen)
○ 使用@kit.ConnectivityKit的access模塊獲取藍牙狀態。
○ 返回布爾值,表示藍牙是否開啟(返回access.BluetoothState.STATE_ON即開啟)。 - 掃描藍牙設備 (scanDevice)
○ 啟動藍牙掃描,設置掃描間隔、掃描模式等參數。
○ 監聽BLEDeviceFind事件,收集掃描到的設備并去重。
○ 通過回調函數將掃描結果數組返回給調用者。
○ 設置10秒超時自動停止掃描。 - 連接藍牙設備 (connectDevice)
○ 根據設備ID創建GATT客戶端設備實例。
○ 連接設備,并監聽連接狀態變化事件。
○ 當連接成功(狀態變為STATE_CONNECTED)時,執行回調函數并顯示成功提示,同時調用listenChange方法開啟通知監聽。 - 斷開藍牙連接 (disconnectDevice)
○ 如果存在連接,斷開并關閉GATT客戶端設備,重置引用。
○ 顯示斷開提示。 - 發送數據 (sendData)
○ 向已連接設備寫入數據。
○ 查找UUID以0000AE30開頭的服務,然后在該服務下查找UUID以0000AE10開頭的特征。
○ 將JSON格式的數據編碼為Uint8Array并寫入特征值。 - 開啟通知監聽 (listenChange方法)
○ 在連接成功后調用,用于監聽特征值變化(通知)。
○ 監聽BLECharacteristicChange事件,接收到數據時解析為BlueData結構。
○ 如果數據命令為wifi,則顯示接收到的數據。
○ 查找UUID以0000AE30開頭的服務,然后查找UUID以0000AE04開頭的特征,并啟用該特征的通知。
blueManager.ets
import { abilityAccessCtrl, bundleManager } from '@kit.AbilityKit' // 導入能力訪問控制相關模塊
import { promptAction } from '@kit.ArkUI' // 導入用戶界面交互模塊
import { access, ble, constant } from '@kit.ConnectivityKit' // 導入藍牙相關功能模塊
import { util } from '@kit.ArkTS' // 導入文本編碼工具
import { emitter } from '@kit.BasicServicesKit'// 1. 藍牙開門 2. 配置設備 wifi 連網
interface BlueData {status?: 200 | 400 // 200 成功 400 失敗msg?: string // 消息提示command?: 'open' | 'wifi' // 命令類型:開門或配置Wi-Fidata?: string[] // 例如配置Wi-Fi時的數據:[ssid, pwd]
}class BlueManager {// 1. 申請藍牙權限async requestPermission() {try {const AtManager = abilityAccessCtrl.createAtManager() // 創建權限管理器實例const res =await AtManager.requestPermissionsFromUser(getContext(), ['ohos.permission.ACCESS_BLUETOOTH']) // 向用戶請求藍牙權限if (res.authResults[0] === abilityAccessCtrl.GrantStatus.PERMISSION_DENIED) { // 如果權限被拒絕await AtManager.requestPermissionOnSetting(getContext(), ['ohos.permission.ACCESS_BLUETOOTH']) // 在設置中請求權限}} catch (e) {promptAction.showToast({ message: JSON.stringify(e) }) // 顯示錯誤信息}}// 2. 檢測藍牙權限checkPermission() {const bundleInfo =bundleManager.getBundleInfoForSelfSync(bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION) // 獲取當前應用的bundle信息const AtManager = abilityAccessCtrl.createAtManager() // 創建權限管理器實例const grantStatus =AtManager.checkAccessTokenSync(bundleInfo.appInfo.accessTokenId, 'ohos.permission.ACCESS_BLUETOOTH') // 檢查藍牙權限狀態return grantStatus === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED // 返回權限是否被授予}// 3. 檢測藍牙開關checkOpen() {return access.getState() === access.BluetoothState.STATE_ON // 檢查藍牙是否開啟}timeoutID: number = 0 // 掃描超時ID,用于控制掃描時長// 4. 掃描藍牙設備scanDevice(callback: (res: ble.ScanResult[]) => void) {try {clearTimeout(this.timeoutID) // 清除之前的超時設置const scanList: ble.ScanResult[] = [] // 初始化掃描結果列表// 監聽藍牙設備發現事件ble.on('BLEDeviceFind', (res: ble.ScanResult[]) => {scanList.push(...res.filter(v => !scanList.some(vv => v.deviceId === vv.deviceId))) // 過濾重復設備并更新掃描列表callback(scanList) // 調用回調函數傳遞掃描結果})// 開始藍牙掃描ble.startBLEScan(null, {interval: 500, // 設置掃描間隔時間為500毫秒dutyMode: ble.ScanDuty.SCAN_MODE_LOW_POWER, // 設置低功耗掃描模式matchMode: ble.MatchMode.MATCH_MODE_AGGRESSIVE // 設置積極匹配模式})this.timeoutID = setTimeout(() => {ble.stopBLEScan() // 停止掃描ble.off('BLEDeviceFind') // 停止監聽藍牙設備發現事件}, 10000)} catch (e) {promptAction.showToast({ message: JSON.stringify(e) }) // 顯示錯誤信息}}currentClient: ble.GattClientDevice | null = null // 當前連接的藍牙客戶端設備// 5. 連接藍牙設備connectDevice(deviceId: string, callback?: () => void) {if (deviceId) {this.currentClient = ble.createGattClientDevice(deviceId) // 創建Gatt客戶端設備實例this.currentClient.connect() // 連接到藍牙設備this.currentClient.on('BLEConnectionStateChange', async (res) => {if (res.state === constant.ProfileConnectionState.STATE_CONNECTED) { // 如果連接狀態為已連接callback?.() // 調用回調函數promptAction.showToast({ message: '藍牙連接成功' }) // 顯示連接成功提示// 監聽特征值變化this.listenChange()}})}}// 6. 斷開藍牙連接disconnectDevice() {if (this.currentClient) {this.currentClient.disconnect() // 斷開藍牙連接this.currentClient.close() // 關閉客戶端設備this.currentClient = null // 重置當前客戶端設備為nullpromptAction.showToast({ message: '藍牙已斷開' }) // 顯示斷開連接提示}}// 7. 發送數據async sendData(data: BlueData) {try {if (this.currentClient) {const list = await this.currentClient.getServices() // 獲取藍牙設備的所有服務const doorService = list.find(v => v.serviceUuid.startsWith('0000AE30')) // 查找門控服務const message = doorService?.characteristics.find(v => v.characteristicUuid.startsWith('0000AE10')) // 查找門控特征值const encoder = new util.TextEncoder() // 創建文本編碼器const u8a = encoder.encodeInto(JSON.stringify(data)) // 將數據編碼為Uint8Array// 寫入特征值await this.currentClient.writeCharacteristicValue({serviceUuid: message?.serviceUuid,characteristicUuid: message?.characteristicUuid,characteristicValue: u8a.buffer,descriptors: [],}, ble.GattWriteType.WRITE)}} catch (e) {promptAction.showToast({ message: JSON.stringify(e) }) // 顯示錯誤信息}}// 8. 監聽特征值變化async listenChange() {if (this.currentClient) {// 注冊特征值變化事件監聽器this.currentClient?.on('BLECharacteristicChange', (res) => {// 創建文本解碼器const decoder = util.TextDecoder.create()// 將特征值數據轉換為Uint8Arrayconst buffer = new Uint8Array(res.characteristicValue)// 將Uint8Array解碼為字符串并解析為JSON對象const result = JSON.parse(decoder.decodeToString(buffer)) as BlueData// 判斷命令類型是否為wifiif (result.command === 'wifi') {// 觸發wifi連接事件,并傳遞狀態信息promptAction.showToast({ message: JSON.stringify(result, null, 2) })}})// 獲取藍牙設備的服務列表const serviceList = await this.currentClient?.getServices()// 查找門控服務const doorService = serviceList?.find(v => v.serviceUuid.startsWith('0000AE30'))// 查找門控特征值const message = doorService?.characteristics.find(v => v.characteristicUuid.startsWith('0000AE04'))// 啟用特征值變化通知await this.currentClient?.setCharacteristicChangeNotification(message, true)}}
}export const blueManager = new BlueManager() // 導出BlueManager實例
藍牙測試頁面
Test.ets
import { blueManager } from '../utils/blueManager'
import { ble } from '@kit.ConnectivityKit'@Entry
@Component
struct Index {@State isPermission: boolean = false@State isOpen: boolean = false@State list: ble.ScanResult[] = []aboutToAppear(): void {this.checkPermission()this.checkOpen()}checkPermission() {this.isPermission = blueManager.checkPermission()}checkOpen() {this.isOpen = blueManager.checkOpen()}build() {Column({ space: 10 }) {Button('申請藍牙權限').onClick(async () => {await blueManager.requestPermission()this.checkPermission()})Button('檢測藍牙權限是否開啟:' + this.isPermission).onClick(() => {this.isPermission = blueManager.checkPermission()})Button('檢測藍牙開關是否開啟:' + this.isOpen).onClick(() => {this.isOpen = blueManager.checkOpen()})Button('掃描藍牙設備').onClick(() => {blueManager.scanDevice((res) => {this.list = res})})List({ space: 5 }) {ForEach(this.list, (item: ble.ScanResult) => {if (item.deviceName) {ListItem() {Row() {Text(item.deviceName)Button('連接藍牙').size({ height: 30 }).onClick(() => {blueManager.connectDevice(item.deviceId)})}}}})}.width('100%').height(300)Button('斷開藍牙連接').onClick(() => {blueManager.disconnectDevice()})Button('發送數據-解鎖開門').onClick(() => {blueManager.sendData({command: 'open'})})Button('發送數據-配網').onClick(() => {blueManager.sendData({command: 'wifi',data: ['Megasu_iPhone', 'nideshengri'] // 寫入自己的 WIFI 和 密碼})})}.height('100%').width('100%')}
}