https://developer.huawei.com/consumer/cn/doc/best-practices/bpta-bluetooth-low-energy
蓝牙权限
module.json5
bash
{
"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
bash
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-Fi
data?: 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 // 重置当前客户端设备为null
promptAction.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()
// 将特征值数据转换为Uint8Array
const buffer = new Uint8Array(res.characteristicValue)
// 将Uint8Array解码为字符串并解析为JSON对象
const result = JSON.parse(decoder.decodeToString(buffer)) as BlueData
// 判断命令类型是否为wifi
if (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
bash
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%')
}
}