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

大家好,我是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)
    }
  });
}
相关推荐
EnCi Zheng8 分钟前
M5-markconv自定义CSS样式指南 [特殊字符]
前端·css·python
kyriewen12 分钟前
你的网页慢,用户不说直接走——前端性能监控教你“读心术”
前端·性能优化·监控
广州华水科技12 分钟前
北斗GNSS变形监测在大坝安全监测中的应用与优势分析
前端
前端老石人23 分钟前
前端开发中的 URL 完全指南
开发语言·前端·javascript·css·html
CAE虚拟与现实24 分钟前
五一假期闲来无事,来个前段、后端的说明吧
前端·后端·vtk·three.js·前后端
Sarvartha35 分钟前
三目运算符
linux·服务器·前端
晓晨的博客42 分钟前
ROS1录制的bag包转换为ROS2格式
前端·chrome
Wect1 小时前
LeetCode 72. 编辑距离:动态规划经典题解
前端·算法·typescript
donecoding1 小时前
别再让 pnpm 跟着 nvm 跑了!独立安装终极指南
前端·node.js·前端工程化
GISer_Jing1 小时前
AI全栈转型_TS后端学习路线
前端·人工智能·后端·学习