【HarmonyOS】鸿蒙应用低功耗蓝牙BLE的使用心得 (二)

【HarmonyOS】鸿蒙应用低功耗蓝牙BLE的使用心得 (二)

一、前言

目前鸿蒙应用的实现逻辑,基本都是参考和移植Android端来实现。针对BLE低功耗蓝牙来说,在鸿蒙化的实现过程中。我们发现了,鸿蒙独有的优秀点,也发现了不一致带来的问题。

并且因为鸿蒙系统还在迭代中,难免有一些bug的存在。目前BLE低功耗蓝牙主流程环节是没有问题的。

鸿蒙整体的实现流程与Android基本是一致。但是在BLE中有一个点需要特别注意。

当周边设备(外围设备)开启广播时,其实没有前置条件,不需要先开启GATT服务器和注册相关服务。但是有些业务逻辑是如此,导致我们开发的时候,惯性思维。实际上鸿蒙BLE在广播流程设计上,并不需要前置条件。

当周边设备持续发送广播,例如智能腕表发送广播,这时候中心设备(中央设备),例如手机才能通过BLE扫描去去识别到该设备。然后才是连接和信息传递的过程。

二、目前已知鸿蒙和Android不一致的实现点梳理:

1.安卓中设置低功耗蓝牙广播,其中有参数可以设置超时时间:

AdvertiseSettings.Builder.SetTimeout()。

但是对标鸿蒙中设置广播,文档中并没有该参数。鸿蒙的低功耗蓝牙广播,无法设置超时时间。

2.鸿蒙中的低功耗蓝牙广播设置对象,ble.AdvertiseSetting中只能设置txPower广播强度。设置广播的模式,对标Android的三个广播模式:

在均衡电源模式下执行蓝牙LE广播:

AdvertiseSettings#ADVERTISE_MODE_BALANCED

在低延迟,高功率模式下执行蓝牙LE广播:

AdvertiseSettings#ADVERTISE_MODE_LOW_LATENCY

在低功耗模式下执行蓝牙LE广播

:AdvertiseSettings#ADVERTISE_MODE_LOW_POWER

鸿蒙这边可以使用interval参数来设置广播频率

ADVERTISE_MODE_LOW_LATENCY(低延迟,广播间隔100ms)对应interval的值为 160

ADVERTISE_MODE_BALANCED(均衡模式,广播间隔250ms)对应interval的值为 400

ADVERTISE_MODE_LOW_POWER(低功耗,广播间隔1s)对应interval的值为 1600

三、BLE低功耗蓝牙DEMO项目示例参考:

此项目实际上是从官方文档基础上进行了扩展。文档所示例的样本代码比较割裂,只是针对API功能的讲解,在不知道BLE完成业务流程的基础上,我们是不清楚如何调用的。所以我们开玩笑的角度说,基本上当你会这个技术点了,官方文档也就能看懂了。

项目整个框架采用管理对象进行BLE功能的封装:

GattServermanager

作为GATT服务器(周边设备)的管理类,实现服务器的初始化和服务的注册。

GattClientManager

作为周边设备客户端管理类,实现链接和数据读写传递。

BleScanManager

作为BLE扫描管理类,实现扫描和结果的处理。

BleAdvertisingManager

作为BLE广播类,实现广播的处理,特征值和描述符的生成。

BLEMgr

作为总管理类,业务逻辑的感知层,业务直接调用BLEMgr实现低功耗蓝牙接口的调用。

Index.ets 启动页

dart 复制代码
import { CommonTextModifier } from '../common/CommonTextModifier'
import { BLEMgr } from '../mgr/BLEMgr';
import { promptAction } from '@kit.ArkUI';
import { PermissionsUtil } from '../utils/PermissionsUtil';

@Entry
@Component
struct Index {

  @State isOpenBluetooth: boolean = false;

  private mBLEMgr: BLEMgr = new BLEMgr();
  txtModifier: CommonTextModifier = new CommonTextModifier()

  async aboutToAppear() {
    let isHave: boolean = await PermissionsUtil.requestPermission();
    if(isHave){
      this.isOpenBluetooth = this.mBLEMgr.getBluetoothState();
    }else{
      this.toSysSettingPage();
    }
  }

  private toSysSettingPage(){
    globalThis.sysContext.startAbility({
      bundleName: 'com.huawei.hmos.settings',
      abilityName: 'com.huawei.hmos.settings.MainAbility',// com.huawei.hmos.settings.AppInfoAbility
      uri: 'application_info_entry', //application_settings   application_info_entry
      parameters: {
        pushParams: globalThis.sysContext.abilityInfo.bundleName // 应用包名com.example.tosettingdemo  'uiAbilityContext.abilityInfo.bundleName'
      }
    });
  }

  onClickStart = async ()=>{
    let isHave: boolean = await PermissionsUtil.requestPermission();
    if(isHave){
      this.mBLEMgr.startBluetooth((str: string)=>{
        let content: string = "";
        if (str == 'STATE_ON') {
          content = "蓝牙已开启";
        }else{
          content = "开启错误:" + str;
        }
        promptAction.showToast({
          message: content
        });
      });
    }else{
      this.toSysSettingPage();
    }
  }

  onClickClose = async ()=>{
    let isHave: boolean = await PermissionsUtil.requestPermission();
    if(isHave){
      this.mBLEMgr.closeBluetooth((str: string)=>{
        let content: string = "";
        if (str == 'STATE_OFF') {
          content = "蓝牙已关闭";
        }else{
          content = "关闭错误:" + str;
        }
        promptAction.showToast({
          message: content
        });
      });
    }else{
      this.toSysSettingPage();
    }
  }

  onClickStartAdv = ()=>{
    this.mBLEMgr.startAdvertising((advState: string)=>{
      let content: string = "";
      if(advState == "STARTED"){
        content = "广播已开启";
      }else{
        content = "广播错误:" + advState;
      }
      promptAction.showToast({
        message: content
      });
    });
  }

  onClickCloseAdv = ()=>{
    this.mBLEMgr.stopAdvertising((str: string)=>{
      promptAction.showToast({
        message: str
      });
    });
  }

  onClickStartScan = ()=>{
    this.mBLEMgr.startScan();
  }

  onClickCloseScan = ()=>{
    this.mBLEMgr.stopScan();
  }

  onClickStartServer = ()=>{
    this.mBLEMgr.registerServer((res: string)=>{
      promptAction.showToast({
        message: res
      });
    });
  }

  onClickCloseServer = ()=>{
    this.mBLEMgr.unRegisterServer((res: string)=>{
      promptAction.showToast({
        message: res
      });
    });
  }

  @Builder LineView(){
    Line().width("100%").height(px2vp(2)).backgroundColor(Color.Black).margin({
      top: px2vp(100)
    })
  }

  build() {
    Column() {

      Column(){
        Text(this.isOpenBluetooth ? "蓝牙状态: 已开启" : "蓝牙状态: 已关闭")
        Text("蓝牙设备名:" + this.mBLEMgr.getCurrentDeviceName())
      }

      Text("开启蓝牙")
        .attributeModifier(this.txtModifier)
        .onClick(this.onClickStart)

      Text("关闭蓝牙")
        .attributeModifier(this.txtModifier)
        .onClick(this.onClickClose)

      this.LineView()

      Text("启动服务")
        .attributeModifier(this.txtModifier)
        .onClick(this.onClickStartServer)

      Text("关闭服务")
        .attributeModifier(this.txtModifier)
        .onClick(this.onClickCloseServer)

      Text("开启广播")
        .attributeModifier(this.txtModifier)
        .onClick(this.onClickStartAdv)

      Text("关闭广播")
        .attributeModifier(this.txtModifier)
        .onClick(this.onClickCloseAdv)

      this.LineView()

      Text("开启扫描")
        .attributeModifier(this.txtModifier)
        .onClick(this.onClickStartScan)

      Text("关闭扫描")
        .attributeModifier(this.txtModifier)
        .onClick(this.onClickCloseScan)

    }
    .height('100%')
    .width('100%')
  }
}

BLEMgr.ets

dart 复制代码
import bleAdvertisingManager from "./BleAdvertisingManager";
import bleScanManager from "./BleScanManager";
import { access } from '@kit.ConnectivityKit';
import gattServerManager from "./GattServerManager";
import { connection } from '@kit.ConnectivityKit';
import { BusinessError } from "@kit.BasicServicesKit";

const TAG: string = "BLEMgr";

export class BLEMgr {

  public getBluetoothState(): boolean {
    let state = access.getState();
    return this.getStateName(state) == "STATE_ON" ? true : false;
  }

  /**
   * 当前设备蓝牙设备名称
   */
  public getCurrentDeviceName(){
    let localName: string = "";
    try {
      localName = connection.getLocalName();
      console.info(TAG, 'getCurrentDeviceName localName: ' + localName);
    } catch (err) {
      console.error(TAG, 'getCurrentDeviceName errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message);
    }
    return localName;
  }

  /**
   * 蓝牙广播状态
   * @param state
   * @returns
   */
  public getAdvState(state: number){
    switch (state) {
      case 1:
        // 首次启动广播后的状态。
        return 'STARTED';
      case 2:
        // 临时启动广播后的状态。
        return 'ENABLED';
      case 3:
        // 临时停止广播后的状态。
        return 'DISABLED';
      case 4:
        // 完全停止广播后的状态。
        return 'STOPPED';
      default:
        return 'unknown status';
    }
  }

  /**
   * 蓝牙开启状态
   * @param state
   * @returns
   */
  private getStateName(state: number): string {
    switch (state) {
      case 0:
        // 蓝牙已关闭。
        return 'STATE_OFF'; ;
      case 1:
        // 蓝牙正在打开。
        return 'STATE_TURNING_ON';
      case 2:
        // 蓝牙已打开。
        return 'STATE_ON';
      case 3:
        // 蓝牙正在关闭。
        return 'STATE_TURNING_OFF';
      case 4:
        // 蓝牙正在打开LE-only模式。
        return 'STATE_BLE_TURNING_ON';
      case 5:
        // 蓝牙正处于LE-only模式。
        return 'STATE_BLE_ON';
      case 6:
        // 蓝牙正在关闭LE-only模式。
        return 'STATE_BLE_TURNING_OFF';
      default:
        return 'unknown status';
    }
  }

  /**
   * 开启蓝牙
   */
  public startBluetooth(callback: (str: string)=> void){
    try {
      access.enableBluetooth();
    } catch (err) {
      let errStr: string = JSON.stringify(err);
      console.info(TAG, 'startBluetooth enableBluetooth err: ' + errStr);
      callback(errStr);
    }
    access.on('stateChange', (data) => {
      let btStateMessage = this.getStateName(data);
      callback(btStateMessage);
      if (btStateMessage == 'STATE_ON') {
        access.off('stateChange');
      }
      console.info('bluetooth statues: ' + btStateMessage);
    });
  }

  /**
   * 关闭蓝牙
   */
  public closeBluetooth(callback: (str: string)=> void){
    access.disableBluetooth();
    access.on('stateChange', (data) => {
      let btStateMessage = this.getStateName(data);
      callback(btStateMessage);
      if (btStateMessage == 'STATE_OFF') {
        access.off('stateChange');
      }
      console.info("bluetooth statues: " + btStateMessage);
    })
  }

  /**
   * 创建GATT服务器,注册服务
   */
  public registerServer(callBack: (str: string)=> void){
    gattServerManager.registerServer(callBack);
  }

  /**
   * 删除服务,关闭GATT服务器
   */
  public unRegisterServer(callBack: (str: string)=> void){
    gattServerManager.unRegisterServer(callBack);
  }

  /**
   * 开启广播
   */
  public async startAdvertising(callBack: (state: string)=> void) {
    await bleAdvertisingManager.startAdvertising((state: number)=>{
      let advState: string = this.getAdvState(state);
      callBack(advState);
    });
  }

  /**
   * 关闭广播
   */
  public async stopAdvertising(callBack: (str: string)=> void) {
    await bleAdvertisingManager.stopAdvertising(callBack);
  }

  /**
   * 开始扫描
   */
  public startScan() {
    bleScanManager.startScan();
  }

  /**
   * 关闭扫描
   */
  public stopScan() {
    bleScanManager.stopScan();
  }


}

GattServermanager.ets

dart 复制代码
import { ble } from '@kit.ConnectivityKit';
import { constant } from '@kit.ConnectivityKit';
import { BusinessError } from '@kit.BasicServicesKit';

const TAG: string = 'GattServerManager';

export class GattServerManager {
  gattServer: ble.GattServer | undefined = undefined;
  connectState: ble.ProfileConnectionState = constant.ProfileConnectionState.STATE_DISCONNECTED;
  myServiceUuid: string = '00001810-0000-1000-8000-00805F9B34FB';
  myCharacteristicUuid: string = '00001820-0000-1000-8000-00805F9B34FB';
  myFirstDescriptorUuid: string = '00002902-0000-1000-8000-00805F9B34FB'; // 2902一般用于notification或者indication
  mySecondDescriptorUuid: string = '00002903-0000-1000-8000-00805F9B34FB';

  // 构造BLEDescriptor
  private initDescriptor(des: string, value: ArrayBuffer): ble.BLEDescriptor {
    let descriptor: ble.BLEDescriptor = {
      serviceUuid: this.myServiceUuid,
      characteristicUuid: this.myCharacteristicUuid,
      descriptorUuid: des,
      descriptorValue: value
    };
    return descriptor;
  }

  // 构造BLECharacteristic
  private initCharacteristic(): ble.BLECharacteristic {
    let descriptors: Array<ble.BLEDescriptor> = [];
    let descBuffer = new ArrayBuffer(2);
    let descValue = new Uint8Array(descBuffer);
    descValue[0] = 31;
    descValue[1] = 32;
    descriptors[0] = this.initDescriptor(this.myFirstDescriptorUuid, new ArrayBuffer(2));
    descriptors[1] = this.initDescriptor(this.mySecondDescriptorUuid, descBuffer);
    let charBuffer = new ArrayBuffer(2);
    let charValue = new Uint8Array(charBuffer);
    charValue[0] = 21;
    charValue[1] = 22;
    let characteristic: ble.BLECharacteristic = {
      serviceUuid: this.myServiceUuid,
      characteristicUuid: this.myCharacteristicUuid,
      characteristicValue: charBuffer,
      descriptors: descriptors
    };
    return characteristic;
  }

  // 1. 订阅连接状态变化事件
  public onGattServerStateChange() {
    if (!this.gattServer) {
      console.error(TAG, 'no gattServer');
      return;
    }
    try {
      this.gattServer.on('connectionStateChange', (stateInfo: ble.BLEConnectionChangeState) => {
        let state = '';
        switch (stateInfo.state) {
          case 0:
            state = 'DISCONNECTED';
            break;
          case 1:
            state = 'CONNECTING';
            break;
          case 2:
            state = 'CONNECTED';
            break;
          case 3:
            state = 'DISCONNECTING';
            break;
          default:
            state = 'undefined';
            break;
        }
        console.info(TAG, 'onGattServerStateChange: device=' + stateInfo.deviceId + ', state=' + state);
      });
    } catch (err) {
      console.error(TAG, 'errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message);
    }
  }

  // 2. server端注册服务时调用
  public registerServer(callBack: (str: string)=> void) {
    let characteristics: Array<ble.BLECharacteristic> = [];
    let characteristic = this.initCharacteristic();
    characteristics.push(characteristic);
    let gattService: ble.GattService = {
      serviceUuid: this.myServiceUuid,
      isPrimary: true,
      characteristics: characteristics
    };

    console.info(TAG, 'registerServer ' + this.myServiceUuid);
    try {
      this.gattServer = ble.createGattServer(); // 2.1 构造gattServer,后续的交互都需要使用该实例
      this.onGattServerStateChange(); // 2.2 订阅连接状态
      this.gattServer.addService(gattService);
      callBack("服务成功");
    } catch (err) {
      callBack("服务失败:" + JSON.stringify(err));
      console.error(TAG, 'errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message);
    }
  }

  // 3. 订阅来自gattClient的读取特征值请求时调用
  public onCharacteristicRead() {
    if (!this.gattServer) {
      console.error(TAG, 'no gattServer');
      return;
    }

    console.info(TAG, 'onCharacteristicRead');
    try {
      this.gattServer.on('characteristicRead', (charReq: ble.CharacteristicReadRequest) => {
        let deviceId: string = charReq.deviceId;
        let transId: number = charReq.transId;
        let offset: number = charReq.offset;
        console.info(TAG, 'receive characteristicRead');
        let rspBuffer = new ArrayBuffer(2);
        let rspValue = new Uint8Array(rspBuffer);
        rspValue[0] = 21;
        rspValue[1] = 22;
        let serverResponse: ble.ServerResponse = {
          deviceId: deviceId,
          transId: transId,
          status: 0, // 0表示成功
          offset: offset,
          value: rspBuffer
        };

        try {
          this.gattServer?.sendResponse(serverResponse);
        } catch (err) {
          console.error(TAG, 'errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message);
        }
      });
    } catch (err) {
      console.error(TAG, 'errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message);
    }
  }

  // 4. 订阅来自gattClient的写入特征值请求时调用
  public onCharacteristicWrite() {
    if (!this.gattServer) {
      console.error(TAG, 'no gattServer');
      return;
    }

    console.info(TAG, 'onCharacteristicWrite');
    try {
      this.gattServer.on('characteristicWrite', (charReq: ble.CharacteristicWriteRequest) => {
        let deviceId: string = charReq.deviceId;
        let transId: number = charReq.transId;
        let offset: number = charReq.offset;
        console.info(TAG, 'receive characteristicWrite: needRsp=' + charReq.needRsp);
        if (!charReq.needRsp) {
          return;
        }
        let rspBuffer = new ArrayBuffer(0);
        let serverResponse: ble.ServerResponse = {
          deviceId: deviceId,
          transId: transId,
          status: 0, // 0表示成功
          offset: offset,
          value: rspBuffer
        };

        try {
          this.gattServer?.sendResponse(serverResponse);
        } catch (err) {
          console.error(TAG, 'errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message);
        }
      });
    } catch (err) {
      console.error(TAG, 'errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message);
    }
  }

  // 5. 订阅来自gattClient的读取描述符请求时调用
  public onDescriptorRead() {
    if (!this.gattServer) {
      console.error(TAG, 'no gattServer');
      return;
    }

    console.info(TAG, 'onDescriptorRead');
    try {
      this.gattServer.on('descriptorRead', (desReq: ble.DescriptorReadRequest) => {
        let deviceId: string = desReq.deviceId;
        let transId: number = desReq.transId;
        let offset: number = desReq.offset;
        console.info(TAG, 'receive descriptorRead');
        let rspBuffer = new ArrayBuffer(2);
        let rspValue = new Uint8Array(rspBuffer);
        rspValue[0] = 31;
        rspValue[1] = 32;
        let serverResponse: ble.ServerResponse = {
          deviceId: deviceId,
          transId: transId,
          status: 0, // 0表示成功
          offset: offset,
          value: rspBuffer
        };

        try {
          this.gattServer?.sendResponse(serverResponse);
        } catch (err) {
          console.error(TAG, 'errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message);
        }
      });
    } catch (err) {
      console.error(TAG, 'errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message);
    }
  }

  // 6. 订阅来自gattClient的写入描述符请求时调用
  public onDescriptorWrite() {
    if (!this.gattServer) {
      console.error(TAG, 'no gattServer');
      return;
    }

    console.info(TAG, 'onDescriptorWrite');
    try {
      this.gattServer.on('descriptorWrite', (desReq: ble.DescriptorWriteRequest) => {
        let deviceId: string = desReq.deviceId;
        let transId: number = desReq.transId;
        let offset: number = desReq.offset;
        console.info(TAG, 'receive descriptorWrite: needRsp=' + desReq.needRsp);
        if (!desReq.needRsp) {
          return;
        }
        let rspBuffer = new ArrayBuffer(0);
        let serverResponse: ble.ServerResponse = {
          deviceId: deviceId,
          transId: transId,
          status: 0, // 0表示成功
          offset: offset,
          value: rspBuffer
        };

        try {
          this.gattServer?.sendResponse(serverResponse);
        } catch (err) {
          console.error(TAG, 'errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message);
        }
      });
    } catch (err) {
      console.error(TAG, 'errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message);
    }
  }

  // 7. server端删除服务,不再使用时调用
  public unRegisterServer(callBack: (str: string)=> void) {
    if (!this.gattServer) {
      console.error(TAG, 'no gattServer');
      return;
    }

    console.info(TAG, 'unRegisterServer ' + this.myServiceUuid);
    try {
      this.gattServer.removeService(this.myServiceUuid); // 7.1 删除服务
      callBack("关闭服务");
      this.gattServer.off('connectionStateChange', (stateInfo: ble.BLEConnectionChangeState) => { // 7.2 取消订阅连接状态
      });
      this.gattServer.close() // 7.3 如果不再使用此gattServer,则需要close
    } catch (err) {
      callBack("关闭失败:" + JSON.stringify(err));
      console.error(TAG, 'errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message);
    }
  }
}

let gattServerManager = new GattServerManager();
export default gattServerManager as GattServerManager;

其余GattClientManager BleScanManager BleAdvertisingManager 类可以参考官方文档。

相关推荐
鸿蒙自习室2 小时前
鸿蒙多线程开发——线程间数据通信对象02
ui·harmonyos·鸿蒙
SuperHeroWu74 小时前
【HarmonyOS】鸿蒙应用接入微博分享
华为·harmonyos·鸿蒙·微博·微博分享·微博sdk集成·sdk集成
期待未来的男孩4 小时前
华为FusionCube 500-8.2.0SPC100 实施部署文档
华为
岳不谢6 小时前
VPN技术-VPN简介学习笔记
网络·笔记·学习·华为
zhangjr05757 小时前
【HarmonyOS Next】鸿蒙实用装饰器一览(一)
前端·harmonyos·arkts
诗歌难吟46413 小时前
初识ArkUI
harmonyos
SameX13 小时前
HarmonyOS Next 设备安全特性深度剖析学习
harmonyos
郭梧悠15 小时前
HarmonyOS(57) UI性能优化
ui·性能优化·harmonyos
郝晨妤1 天前
鸿蒙原生应用开发元服务 元服务是什么?和App的关系?(保姆级步骤)
android·ios·华为od·华为·华为云·harmonyos·鸿蒙
Peace*1 天前
HarmonyOs鸿蒙开发实战(16)=>沉浸式效果第一种方案一窗口全屏布局方案
harmonyos·鸿蒙·鸿蒙系统