【HarmonyOS】鸿蒙蓝牙连接与通信技术
在鸿蒙系统中,蓝牙功能的实现依赖于 HarmonyOS 提供的ConnectivityKit
蓝牙模块、AbilityKit
权限控制模块和ArkTS
工具模块。本文详细讲解蓝牙连接、数据传输等核心流程。


一、蓝牙权限申请
- 在使用蓝牙功能之前,须在
module.json5
中配置蓝牙权限。
"requestPermissions": [
{
"name": "ohos.permission.ACCESS_BLUETOOTH",
"reason": "$string:open_bluetooth",
"usedScene": {
"abilities": ["EntryAbility"],
"when": "inuse"
}
}
]
- 同时向用户申请蓝牙权限
async getBluetoothRequestion(callback: (res: abilityAccessCtrl.GrantStatus) => void) {
const ctx = AppStorage.get<Context>(CONTEXT_GLOB) // 获取全局上下文
const ctrl = abilityAccessCtrl.createAtManager() // 创建权限管理器
const res = (ctrl.requestPermissionsFromUser(ctx, ['ohos.permission.ACCESS_BLUETOOTH'])) // 请求蓝牙权限
res.then(async (promise) => {
if (!promise.authResults.every(t => t == abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED)) {
const res = await ctrl.requestPermissionOnSetting(ctx, ['ohos.permission.ACCESS_BLUETOOTH'])
callback(res[0])
} else {
callback(promise.authResults[0]) // 调用回调函数,返回权限状态
}
})
}
-
引入权限控制模块:使用
abilityAccessCtrl.createAtManager()
创建权限管理器。 -
请求权限:调用
requestPermissionsFromUser
向用户请求ohos.permission.ACCESS_BLUETOOTH
权限。 -
权限未授予处理:若用户拒绝权限,则尝试跳转到设置页面进行手动授权,调用
requestPermissionOnSetting
。 -
回调返回结果:最终通过
callback(res[0])
或callback(promise.authResults[0])
返回权限状态。
二、蓝牙开关状态判断与监听
1. 判断蓝牙是否开启
isBluetoothEnabled(): boolean {
const state: access.BluetoothState = access.getState(); // 获取蓝牙当前状态
// 判断是否为开启或正在开启状态
return state === access.BluetoothState.STATE_ON; // 返回蓝牙是否已开启的布尔值
}
- 调用
access.getState()
获取当前蓝牙状态。 - 检查状态是否为
access.BluetoothState.STATE_ON
,如果是则表示蓝牙已打开。
2. 监听蓝牙状态变化
//监听蓝牙变化
onBluetoothState(callback: (state: boolean) => void) {
// 监听蓝牙状态变化事件
access.on('stateChange', (status) => {
// 判断蓝牙是否关闭,若关闭则调用回调函数并传入false
status == access.BluetoothState.STATE_OFF && callback(false)
// 判断蓝牙是否开启,若开启则调用回调函数并传入true
status == access.BluetoothState.STATE_ON && callback(true)
})
}
- 使用
access.on('stateChange')
监听蓝牙开关状态变化事件。 - 当状态变为
STATE_OFF
时,调用callback(false)
; - 当状态变为
STATE_ON
时,调用callback(true)
。
三、蓝牙设备扫描
// 蓝牙设备扫描
findBluetoothDecice(callback: (device: ble.ScanResult[]) => void) {
try {
ble.startBLEScan(null, {
interval: 500, // 设置扫描间隔为500ms
dutyMode: ble.ScanDuty.SCAN_MODE_LOW_POWER, // 设置扫描模式为低功耗模式
matchMode: ble.MatchMode.MATCH_MODE_AGGRESSIVE // 设置匹配模式为积极模式
})
// 初始化设备列表数组
let deviceList: ble.ScanResult[] = []
ble.on("BLEDeviceFind", (res: ble.ScanResult[]) => { // 监听蓝牙设备发现事件
res.forEach((dev: ble.ScanResult) => { // 遍历所有发现的设备
// 如果设备有名称
if (dev.deviceName) {
// 将新设备添加到列表中,避免重复
deviceList.push(...res.filter(v => !deviceList.some(vv => vv.deviceId === v.deviceId)))
}
// 调用回调函数,返回当前设备列表
callback(deviceList)
})
})
} catch (err) { // 捕获异常
logger.info(err) // 记录错误信息
}
}
-
启动蓝牙扫描:调用
ble.startBLEScan
开始低功耗扫描,参数如下:-
interval
: 扫描间隔(500ms) -
dutyMode
: 扫描模式(低功耗模式SCAN_MODE_LOW_POWER
) -
matchMode
: 匹配模式(积极匹配MATCH_MODE_AGGRESSIVE
)
-
-
设备发现监听:注册
BLEDeviceFind
事件,每次发现新设备后,遍历并去重添加至deviceList
。 -
回调返回设备列表:通过
callback(deviceList)
返回当前扫描到的所有设备。
四、蓝牙连接与断开
1. 连接设备
// 蓝牙设备连接
connectDevice(device: ble.ScanResult, callback: (clientDevice: ble.ScanResult) => void) {
try {
// 创建GATT客户端设备实例,传入目标设备的ID
this.currentClient = ble.createGattClientDevice(device.deviceId)
// 建立与设备的蓝牙连接
this.currentClient.connect()
// 监听蓝牙连接状态变化事件
this.currentClient.on("BLEConnectionStateChange", (result) => {
// 判断连接状态是否为已连接
if (result.state === constant.ProfileConnectionState.STATE_CONNECTED) {
// 调用回调函数,传入已连接的设备
callback(device)
}
})
} catch (err) {
logger.info(err)
}
}
-
创建 GATT 客户端:调用
ble.createGattClientDevice(device.deviceId)
创建客户端实例。 -
建立连接:调用
connect()
方法发起连接。 -
监听连接状态变化:注册
BLEConnectionStateChange
事件,当连接状态为STATE_CONNECTED
时,调用callback(device)
返回连接成功的设备对象。
2. 断开设备连接
//断开蓝牙设备
disconnectDevice(callBack: () => void) {
if (this.currentClient) { // 检查当前是否已连接蓝牙设备
this.currentClient.disconnect() // 断开与蓝牙设备的连接
this.currentClient.close(); // 关闭蓝牙客户端资源
this.currentClient = null // 将当前客户端设为null,表示已断开连接
callBack() // 调用回调函数,通知断开完成
}
}
-
检查连接状态:判断
this.currentClient
是否存在。 -
断开连接:调用
disconnect()
关闭连接,并调用close()
清理资源。 -
清空引用:将
currentClient
设为null
。 -
回调通知完成:调用
callBack()
告知上层断开操作已完成。
五、蓝牙数据发送与监听
1. 发送数据
//数据发送
async bluetoothSendMsg(data: BlueData) {
try {
// 检查当前是否存在已连接的蓝牙设备
if (this.currentClient) {
// 获取蓝牙设备的所有服务列表
const list = await this.currentClient.getServices()
// 查找服务UUID以0000AE30开头的服务
const doorService = list.find(v => v.serviceUuid.startsWith("0000AE30"))
// 查找特征UUID以0000AE10开头的特征
const message = doorService?.characteristics.find(v => v.characteristicUuid.startsWith("0000AE10"))
// 创建文本编码器实例
const encoder = new util.TextEncoder()
// 将数据对象编码为Uint8Array
const u8a = encoder.encodeInto(JSON.stringify(data))
// 向蓝牙设备发送特征值数据
await this.currentClient.writeCharacteristicValue({
serviceUuid: message?.serviceUuid, // 服务UUID
characteristicUuid: message?.characteristicUuid, // 特征UUID
characteristicValue: u8a.buffer, // 特征值数据
descriptors: [], // 描述符列表
}, ble.GattWriteType.WRITE) // 设置写入类型为WRITE
}
} catch (err) {
logger.info(err)
}
}
-
检查连接状态:确保
currentClient
不为空。 -
获取服务列表:调用
getServices()
获取设备支持的服务。 -
查找目标服务和特征:
- 服务 UUID 以
0000AE30
开头 - 特征 UUID 以
0000AE10
开头
- 服务 UUID 以
-
编码数据:使用
util.TextEncoder
将 JSON 对象转换为Uint8Array
。 -
写入特征值:调用
writeCharacteristicValue
发送数据,参数包括:serviceUuid
characteristicUuid
-
characteristicValue
(即编码后的数据) descriptors
-
writeType
(指定为WRITE
)
2. 监听特征值变化
//监听特征值变化
async listenInDeviceDataChange(callBack: (message: number | void) => void) {
// 检查当前是否存在已连接的蓝牙设备
if (this.currentClient) {
// 监听蓝牙特征值变化事件
this.currentClient?.on("BLECharacteristicChange", (res) => {
// 创建文本解码器实例
const decoder = util.TextDecoder.create()
// 将特征值数据转换为Uint8Array
const buffer = new Uint8Array(res.characteristicValue)
// 将二进制数据解码并解析为BlueData对象
const result = JSON.parse(decoder.decodeToString(buffer)) as BlueData
// 如果命令类型为'wifi'
if (result.command === 'wifi') {
// 再次调用回调函数,传递状态码
callBack(result.status)
}
})
// 获取蓝牙设备的所有服务列表
const serviceList = await this.currentClient?.getServices()
// 查找服务UUID以0000AE30开头的服务
const doorService = serviceList?.find(v => v.serviceUuid.startsWith("0000AE30"))
// 查找特征UUID以0000AE04开头的特征
const message = doorService?.characteristics.find(v => v.characteristicUuid.startsWith("0000AE04"))
// 设置特征值变化通知,启用通知功能
await this.currentClient?.setCharacteristicChangeNotification(message, true)
}
}
-
监听特征值变化:注册
BLECharacteristicChange
事件,每当设备有数据更新时触发。 -
解码数据:使用
util.TextDecoder.create()
创建解码器,将characteristicValue
转换为字符串并解析为BlueData
对象。 -
特殊命令处理:如果命令是
'wifi'
,调用callBack(result.status)
。
六、接口定义说明
interface BlueData {
status?: 200 | 400 // 200 表示成功,400 表示失败
msg?: string // 状态消息
command?: 'open' | 'wifi' // 命令类型:开门或配置Wi-Fi
data?: string[] // 数据内容,如 Wi-Fi 的 [ssid, pwd]
}
该接口用于封装蓝牙通信过程中发送和接收的数据结构。
七、完整代码
import { abilityAccessCtrl } from "@kit.AbilityKit" // 导入AbilityKit中的权限控制模块
import { logger } from "../../../../Index" // 导入自定义日志工具
import { CONTEXT_GLOB } from "../constants/ConstantEvent" // 导入全局上下文常量
import { access, ble, constant } from "@kit.ConnectivityKit" // 导入蓝牙相关模块
import { util } from "@kit.ArkTS" // 导入ArkTS工具模块,用于文本编码解码等操作
// 1. 蓝牙开门 2. 配置设备 wifi 连网
interface BlueData {
status?: 200 | 400 // 200 成功 400 失败
msg?: string // 消息提示
command?: 'open' | 'wifi' // 命令类型:开门或配置Wi-Fi
data?: string[] // 例如配置Wi-Fi时的数据:[ssid, pwd]
}
class BluetoothManager {
// 当前已连接的设备
currentClient: ble.GattClientDevice | null = null
// 获取蓝牙权限
async getBluetoothRequestion(callback: (res: abilityAccessCtrl.GrantStatus) => void) {
const ctx = AppStorage.get<Context>(CONTEXT_GLOB) // 获取全局上下文
const ctrl = abilityAccessCtrl.createAtManager() // 创建权限管理器
const res = (ctrl.requestPermissionsFromUser(ctx, ['ohos.permission.ACCESS_BLUETOOTH'])) // 请求蓝牙权限
res.then(async (promise) => {
if (!promise.authResults.every(t => t == abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED)) {
const res = await ctrl.requestPermissionOnSetting(ctx, ['ohos.permission.ACCESS_BLUETOOTH'])
callback(res[0])
} else {
callback(promise.authResults[0]) // 调用回调函数,返回权限状态
}
})
}
// 判断本机蓝牙是否打开
isBluetoothEnabled(): boolean {
const state: access.BluetoothState = access.getState(); // 获取蓝牙当前状态
// 判断是否为开启或正在开启状态
return state === access.BluetoothState.STATE_ON; // 返回蓝牙是否已开启的布尔值
}
//监听蓝牙变化
onBluetoothState(callback: (state: boolean) => void) {
// 监听蓝牙状态变化事件
access.on('stateChange', (status) => {
// 判断蓝牙是否关闭,若关闭则调用回调函数并传入false
status == access.BluetoothState.STATE_OFF && callback(false)
// 判断蓝牙是否开启,若开启则调用回调函数并传入true
status == access.BluetoothState.STATE_ON && callback(true)
})
}
// 蓝牙设备扫描
findBluetoothDecice(callback: (device: ble.ScanResult[]) => void) {
try {
ble.startBLEScan(null, {
interval: 500, // 设置扫描间隔为500ms
dutyMode: ble.ScanDuty.SCAN_MODE_LOW_POWER, // 设置扫描模式为低功耗模式
matchMode: ble.MatchMode.MATCH_MODE_AGGRESSIVE // 设置匹配模式为积极模式
})
// 初始化设备列表数组
let deviceList: ble.ScanResult[] = []
ble.on("BLEDeviceFind", (res: ble.ScanResult[]) => { // 监听蓝牙设备发现事件
res.forEach((dev: ble.ScanResult) => { // 遍历所有发现的设备
// 如果设备有名称
if (dev.deviceName) {
// 将新设备添加到列表中,避免重复
deviceList.push(...res.filter(v => !deviceList.some(vv => vv.deviceId === v.deviceId)))
}
// 调用回调函数,返回当前设备列表
callback(deviceList)
})
})
} catch (err) { // 捕获异常
logger.info(err) // 记录错误信息
}
}
// 蓝牙设备连接
connectDevice(device: ble.ScanResult, callback: (clientDevice: ble.ScanResult) => void) {
try {
// 创建GATT客户端设备实例,传入目标设备的ID
this.currentClient = ble.createGattClientDevice(device.deviceId)
// 建立与设备的蓝牙连接
this.currentClient.connect()
// 监听蓝牙连接状态变化事件
this.currentClient.on("BLEConnectionStateChange", (result) => {
// 判断连接状态是否为已连接
if (result.state === constant.ProfileConnectionState.STATE_CONNECTED) {
// 调用回调函数,传入已连接的设备
callback(device)
}
})
} catch (err) {
logger.info(err)
}
}
//断开蓝牙设备
disconnectDevice(callBack: () => void) {
if (this.currentClient) { // 检查当前是否已连接蓝牙设备
this.currentClient.disconnect() // 断开与蓝牙设备的连接
this.currentClient.close(); // 关闭蓝牙客户端资源
this.currentClient = null // 将当前客户端设为null,表示已断开连接
callBack() // 调用回调函数,通知断开完成
}
}
//数据发送
async bluetoothSendMsg(data: BlueData) {
try {
// 检查当前是否存在已连接的蓝牙设备
if (this.currentClient) {
// 获取蓝牙设备的所有服务列表
const list = await this.currentClient.getServices()
// 查找服务UUID以0000AE30开头的服务
const doorService = list.find(v => v.serviceUuid.startsWith("0000AE30"))
// 查找特征UUID以0000AE10开头的特征
const message = doorService?.characteristics.find(v => v.characteristicUuid.startsWith("0000AE10"))
// 创建文本编码器实例
const encoder = new util.TextEncoder()
// 将数据对象编码为Uint8Array
const u8a = encoder.encodeInto(JSON.stringify(data))
// 向蓝牙设备发送特征值数据
await this.currentClient.writeCharacteristicValue({
serviceUuid: message?.serviceUuid, // 服务UUID
characteristicUuid: message?.characteristicUuid, // 特征UUID
characteristicValue: u8a.buffer, // 特征值数据
descriptors: [], // 描述符列表
}, ble.GattWriteType.WRITE) // 设置写入类型为WRITE
}
} catch (err) {
logger.info(err)
}
}
//监听特征值变化
async listenInDeviceDataChange(callBack: (message: number | void) => void) {
// 检查当前是否存在已连接的蓝牙设备
if (this.currentClient) {
// 监听蓝牙特征值变化事件
this.currentClient?.on("BLECharacteristicChange", (res) => {
// 创建文本解码器实例
const decoder = util.TextDecoder.create()
// 将特征值数据转换为Uint8Array
const buffer = new Uint8Array(res.characteristicValue)
// 将二进制数据解码并解析为BlueData对象
const result = JSON.parse(decoder.decodeToString(buffer)) as BlueData
// 调用回调函数,传递状态码
callBack(result.status)
// 如果命令类型为'wifi'
if (result.command === 'wifi') {
// 再次调用回调函数,传递状态码
callBack(result.status)
}
})
// 获取蓝牙设备的所有服务列表
const serviceList = await this.currentClient?.getServices()
// 查找服务UUID以0000AE30开头的服务
const doorService = serviceList?.find(v => v.serviceUuid.startsWith("0000AE30"))
// 查找特征UUID以0000AE04开头的特征
const message = doorService?.characteristics.find(v => v.characteristicUuid.startsWith("0000AE04"))
// 设置特征值变化通知,启用通知功能
await this.currentClient?.setCharacteristicChangeNotification(message, true)
}
}
}
export const bluetoothManager = new BluetoothManager()