【HarmonyOS】鸿蒙蓝牙连接与通信技术

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



一、蓝牙权限申请

  • 在使用蓝牙功能之前,须在module.json5中配置蓝牙权限。
typescript 复制代码
"requestPermissions": [
  {
    "name": "ohos.permission.ACCESS_BLUETOOTH",
    "reason": "$string:open_bluetooth",
    "usedScene": {
      "abilities": ["EntryAbility"],
      "when": "inuse"
    }
  }
]
  • 同时向用户申请蓝牙权限
typescript 复制代码
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. 判断蓝牙是否开启

typescript 复制代码
isBluetoothEnabled(): boolean {
  const state: access.BluetoothState = access.getState(); // 获取蓝牙当前状态
  // 判断是否为开启或正在开启状态
  return state === access.BluetoothState.STATE_ON; // 返回蓝牙是否已开启的布尔值
}
  • 调用 access.getState() 获取当前蓝牙状态。
  • 检查状态是否为 access.BluetoothState.STATE_ON,如果是则表示蓝牙已打开。

2. 监听蓝牙状态变化

typescript 复制代码
//监听蓝牙变化
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)

三、蓝牙设备扫描

typescript 复制代码
// 蓝牙设备扫描
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. 连接设备

typescript 复制代码
// 蓝牙设备连接
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. 断开设备连接

typescript 复制代码
//断开蓝牙设备
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. 发送数据

typescript 复制代码
//数据发送
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 开头
  • 编码数据 :使用 util.TextEncoder 将 JSON 对象转换为 Uint8Array
  • 写入特征值 :调用 writeCharacteristicValue 发送数据,参数包括:
    • serviceUuid
    • characteristicUuid
    • characteristicValue(即编码后的数据)
    • descriptors
    • writeType(指定为 WRITE

2. 监听特征值变化

typescript 复制代码
//监听特征值变化
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)

六、接口定义说明

typescript 复制代码
interface BlueData {
  status?: 200 | 400 // 200 表示成功,400 表示失败
  msg?: string // 状态消息
  command?: 'open' | 'wifi' // 命令类型:开门或配置Wi-Fi
  data?: string[] // 数据内容,如 Wi-Fi 的 [ssid, pwd]
}

该接口用于封装蓝牙通信过程中发送和接收的数据结构。


七、完整代码

typescript 复制代码
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()
相关推荐
coder_pig8 小时前
跟🤡杰哥一起学Flutter (三十四、玩转Flutter手势✋)
前端·flutter·harmonyos
前端世界9 小时前
HarmonyOS开发实战:鸿蒙分布式生态构建与多设备协同发布全流程详解
分布式·华为·harmonyos
Jalor10 小时前
Flutter + 鸿蒙 | Flutter 跳转鸿蒙原生界面
flutter·harmonyos
zhanshuo11 小时前
开发者必看!如何在HarmonyOS中快速调用摄像头功能
harmonyos
HMSCore11 小时前
借助HarmonyOS SDK,《NBA巅峰对决》实现“分钟级启动”到“秒级进场”
harmonyos
zhanshuo11 小时前
鸿蒙UI开发全解:JS与Java双引擎实战指南
前端·javascript·harmonyos
HarmonyOS小助手12 小时前
闯入鸿蒙:浪漫、理想与「草台班子」
harmonyos·鸿蒙·harmonyos next·鸿蒙生态
xq952712 小时前
flutter 鸿蒙化插件开发横空出世
harmonyos
HarmonyOS_SDK12 小时前
借助HarmonyOS SDK,《NBA巅峰对决》实现“分钟级启动”到“秒级进场”
harmonyos