【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 类可以参考官方文档。

相关推荐
数语数行30 分钟前
华为OceanStor 5500 V3存储证书过期问题处理
华为·5500 v3·证书问题
IT小饕餮30 分钟前
华为设备MSTP
运维·华为
RUZHUA3 小时前
华为首款鸿蒙电脑正式亮相,开启国产操作系统新篇章
华为·电脑·harmonyos
yuanlaile10 小时前
HarmonyOS 鸿蒙操作物联网设备蓝牙模块、扫描蓝牙、连接蓝牙和蓝牙通信
物联网·华为·harmonyos·鸿蒙蓝牙·harmonyos 蓝牙模块
鸿蒙布道师15 小时前
鸿蒙NEXT开发动画案例2
android·ios·华为·harmonyos·鸿蒙系统·arkui·huawei
HMS Core20 小时前
【FAQ】HarmonyOS SDK 闭源开放能力 — PDF Kit
华为·pdf·harmonyos
二蛋和他的大花21 小时前
HarmonyOS运动开发:如何集成百度地图SDK、运动跟随与运动公里数记录
华为·harmonyos
SuperHeroWu71 天前
【HarmonyOS 5】鸿蒙页面和组件生命周期函数
华为·harmonyos·鸿蒙·自定义组件·页面·生命周期函数
HarmonyOS小助手1 天前
Flutter适配HarmonyOS 5开发知识地图
harmonyos·鸿蒙·harmonyos next·鸿蒙flutter
搞瓶可乐1 天前
鸿蒙ArkTs实战之截图保存图片到相册,详细教程,不使用SaveButton的方法,附上源码和效果图
华为·harmonyos·arkts·保存图片·操作沙箱·鸿蒙解决方案·媒体操作