鸿蒙设备如何与低功耗蓝牙设备通讯

大家好,我是simple。我的理想是利用科技手段来解决生活中遇到的各种问题

1. 概念

1.1 蓝牙种类

蓝牙是短距离无线通信技术,核心是让设备无线互联。它分传统蓝牙和低功耗蓝牙(BLE)两大分支。

传统蓝牙适合传音频、文件等大数据,功耗较高;BLE 则主打超低功耗,续航能达数月甚至数年,适合小数据传输,是蓝牙设备的 "常客"。

在开发之前我们需要分清楚自己需要开发的设备是哪个类型(在这里我们主要是讲低功耗设备的通讯)。

1.2 中心设备和外围设备

在低功耗蓝牙(BLE)通信中,中心设备和外围设备是两种核心角色,分工明确:

中心设备是 主动方 ,负责扫描、发起连接并获取数据,比如鸿蒙手机、平板等。它像 指挥官,主动搜索周围的外围设备,建立连接后读取或控制数据(例如手机连接智能手环时,手机就是中心设备)。

外围设备是 被动方 ,通过广播信号 告知 自身存在,等待被连接,比如智能手环、温湿度传感器。它就像 喇叭,不断向外播报少量数据(如步数、温度),被中心设备连接后才进一步交互。

1.3 广播与扫描

  • 广播(Advertising) :外围设备(如传感器)通过广播包向外发送自身信息(如设备名称、支持的服务 UUID),让中心设备能发现它。广播每次发出都会有时间间隔,因而在扫描的时候会时不时出现一个设备。

  • 扫描(Scanning) :中心设备(如手机)主动扫描周围的广播包,筛选出目标设备后发起连接。

1.4 客户端和服务端

在低功耗蓝牙(BLE)通信中,服务端(Server)客户端(Client) 是从 "数据交互模式" 定义的角色,与 "中心设备 / 外围设备"(连接发起模式)是两个不同维度的概念。

  • 服务端(Server) :是 "数据提供者",负责定义通信的数据结构(如 "温度服务""步数服务"),并对外提供数据读写接口。可以理解为 "存有数据的仓库",等待被访问。
  • 客户端(Client) :是 "数据请求者",通过服务端提供的接口读取数据或发送指令。可以理解为 "访问仓库的人",主动发起数据交互。

中心设备可以充当服务端或客户端,外围设备也可以充当服务端或客户端。

1.5 层级关系:设备 → 服务 → 特征

当客户端(如鸿蒙手机应用)需要和 BLE 设备交互时,流程通常是:

  1. 连接设备后,先 "发现服务"(获取设备包含哪些服务,如找到0x180F电池服务);
  2. 在目标服务中 "发现特征"(如找到电池服务下的0x2A19电池电量特征);
  3. 检查特征的 Properties(如是否支持READ);
  4. 根据权限操作:支持READ就调用读 API 获取数据(如读取电池电量),支持NOTIFY就注册通知(如实时接收心率变化)。

2. 流程

了解了上述概念之后,那我们写代码实现就简单多了。

这里我们主要举例子鸿蒙手机为中心设备建立客户端,与外围设备建立联系的案例。

  1. 开启蓝牙
  2. 搜索周边蓝牙设备
  3. 连接蓝牙设备
  4. 建立client客户端并指定服务和指定特征
  5. 读取或写入数据

3. 代码实现

  1. 开启蓝牙
ts 复制代码
import { access, ble } from '@kit.ConnectivityKit';

// 当前蓝牙状态:开启、关闭、开启中、关闭中
get state() {
  return access.getState()
}

// 开启蓝牙开启关闭监听
onStateChange(callback: Callback<access.BluetoothState>) {
  access.on('stateChange', callback)
}

offStateChange(callback?: Callback<access.BluetoothState>) {
  access.off('stateChange', callback)
}
  1. 搜索周边蓝牙设备
ts 复制代码
// 扫描设备
onDeviceFind(callback: (list: Array<ble.ScanResult>) => void) {
    ble.on("BLEDeviceFind", this.findDeviceCallback)
}

offDeviceFind() {
    ble.off("BLEDeviceFind")
}

// 停止扫描
stopScan() {
  ble.stopBLEScan()
}

// 开启扫描
async scan(callback: (data: Array<ble.ScanResult>) => void) {
  this.onDeviceFind(callback)
  // tips: 这里扫描容易报错,是因为不能开启扫描,所以在开启新的扫描之前必须关闭上一个扫描
  this.stopScan()
  let scanOptions: ble.ScanOptions = {
    interval: 500,
    dutyMode: ble.ScanDuty.SCAN_MODE_LOW_POWER,
    matchMode: ble.MatchMode.MATCH_MODE_AGGRESSIVE
  }
  ble.startBLEScan([{}], scanOptions);
}
  1. 连接蓝牙设备
ts 复制代码
async connect(deviceId: string) {
  if (this.currentDeviceId === deviceId) {
    return
  }
  this.client = ble.createGattClientDevice(deviceId)
  this.client.connect()
}
  1. 建立client客户端并指定服务和指定特征
ts 复制代码
async getCharacter(characterId: string): Promise<ble.BLECharacteristic | void> {
  if (this.client) {
    const serves = await this.client.getServices()
    const service = serves.find(item => item.serviceUuid.startsWith(MoonLiteManager.SERVICE_ID))
    if (service) {
      return service.characteristics.find(item => item.characteristicUuid.startsWith(characterId))
    }
  }
  return null
}
  1. 读取或写入数据
ts 复制代码
// 写入数据
async writeMessage(characteristicValue: string) {
  const writeCharacter = await this.getCharacter(MoonLiteManager.WRITE_CHARACTERISTIC_ID)
  if (writeCharacter && this.client) {
    try {
      this.client.writeCharacteristicValue({
        serviceUuid: writeCharacter.serviceUuid,
        characteristicValue: hexStringToBytes(characteristicValue),
        characteristicUuid: writeCharacter.characteristicUuid,
        descriptors: writeCharacter.descriptors
      }, ble.GattWriteType.WRITE)
    } catch (e) {
      e
    }
  }
}

// 监听写入消息是否成功的回调
// 回调数据由服务端返回
async onMessage() {
  const readCharacter = await this.getCharacter(MoonLiteManager.READ_CHARACTERISTIC_ID)
  if (!readCharacter || !this.client) {
    return
  }
  await this.client.setCharacteristicChangeNotification(readCharacter, true);
  this.client.on('BLECharacteristicChange', (data) => {
    const arrayBuffer = new Uint8Array(data.characteristicValue)
    const key = formatArrayBuffer(arrayBuffer).toUpperCase()
    if (key.startsWith(MoonLiteManager.PRESSURE_COMMAND)) {
      this.pressureManager.emitPressure(arrayBuffer)
    }
  });
}
相关推荐
AntBlack11 分钟前
闲谈 :AI 生成视频哪家强 ,掘友们有没有推荐的工具?
前端·后端·aigc
花菜会噎住1 小时前
Vue3核心语法进阶(computed与监听)
前端·javascript·vue.js
花菜会噎住1 小时前
Vue3核心语法基础
前端·javascript·vue.js·前端框架
全宝1 小时前
echarts5实现地图过渡动画
前端·javascript·echarts
vjmap1 小时前
MCP协议:CAD地图应用的AI智能化解决方案(唯杰地图MCP)
前端·人工智能·gis
啃火龙果的兔子3 小时前
解决 Node.js 托管 React 静态资源的跨域问题
前端·react.js·前端框架
ttyyttemo3 小时前
Compose生命周期---Lifecycle of composables
前端
以身入局3 小时前
FragmentManager 之 addToBackStack 作用
前端·面试
sophie旭3 小时前
《深入浅出react》总结之 10.7 scheduler 异步调度原理
前端·react.js·源码