#HarmonyOS 鸿蒙Next 体验官#开发一个BLE低功耗蓝牙调试助手(二)实现BLE广播

发布于 1周前 作者 htzhanglong 来自 鸿蒙OS

#HarmonyOS 鸿蒙Next 体验官#开发一个BLE低功耗蓝牙调试助手(二)实现BLE广播
<markdown _ngcontent-dql-c237="" class="markdownPreContainer">

1 简介

上一篇文章(#HarmonyOS NEXT 体验官#开发一个BLE低功耗蓝牙调试助手(一)连接蓝牙服务设备-华为开发者论坛)介绍了如何实现调试助手连接BLE服务端,本篇将介绍如何使用HarmonyOS NEXT原生能力实现BLE广播,以及介绍BLE调试助手的页面和整体功能优化。

通过本篇文章你将学到:

  • 如何在HarmonyOS NEXT中实现BLE广播。
  • 如何在HarmonyOS NEXT中连接BEL设备进行数据交互。
  • 学习Navigation、List、Slider等常用容器和组件的用法。
  • 学习Consum、Provide状态管理。

目前BLE调试助手的功能效果为:

  • 支持扫描BLE设备
    • 能扫描周围的BLE设备,列出设备名称以及MAC地址。
  • 支持连接、订阅、发送BLE消息:
    • 在扫描列表中选择期望连接的设备,点击连接按钮即可与BLE设备建立连接。
    • 支持设置需要订阅的BLE服务(默认9011)及其特征值(默认9012)。
  • 支持设置广播参数
    • 能扫描周围的BLE设备,列出设备名称以及MAC地址。
  • 支持扫描响应、广播数据:
    • 能对外广播数据,BLE客户端能扫描到调试助手配置的广播信息和服务。

目前APP可在客户端与服务端之间切换和配置,功能基本完善,APP效果如下:

2 环境搭建

我们首先需要完成HarmonyOS开发环境搭建,可参考(开发一个BLE低功耗蓝牙调试助手(一)连接蓝牙服务设备-华为开发者论坛)中的第二章进行操作。

3 代码结构解读

本篇文档只对新增功能的核心代码进行讲解,对于作为BLE客户端的实现见上一篇文章。

. entry/src
|-- common
|   |-- CommonConstants.ets
|   |-- Logger.ets
|   `-- PermissionUtil.ets
|-- entryability
|   `-- EntryAbility.ets
|-- pages
|   |-- Advertising.ets  // BLE广播管理子页面
|   |-- DeviceDetails.ets// BLE客户端管理子页面
|   |-- Index.ets        // 主页面
|   `-- ScanDevices.ets  // 设备扫描子页面
`-- servers
    |-- BLEAdvertising.ets      // BLE 广播接口
    |-- BLEGattClientManager.ets// BLE 通用属性开发接口
    `-- BLEScanManager.ets         // BLE 扫描接口
<button style="position: absolute; padding: 4px 8px 0px; cursor: pointer; top: 4px; right: 8px; font-size: 14px;">复制</button>

4 构建应用主界面

调试助手入口页面使用Navigation组件实现布局,将BLE客户端管理页面放置到List Item点击后的层级,使用Navigation自带的toolbarConfiguration属性(可自定义控键和触发事件)实现广播和扫描页面导航:触发不同的按钮时设定显示状态。

 // Index.ets
[@State](/user/State) ShowBLEClient:boolean = true
[@State](/user/State) BLEClientTool: ToolbarItem = {'value': "BLE客户端", 'icon': $r('app.media.ble_client'), 'action': ()=> {
    this.ShowBLEClient = true
  }}
  [@State](/user/State) BLEAdvertisingTool: ToolbarItem = {'value': "BLE广播", 'icon': $r('app.media.ble_adv'), 'action': ()=> {
    this.ShowBLEClient = false
  }}
<button style="position: absolute; padding: 4px 8px 0px; cursor: pointer; top: 4px; right: 8px; font-size: 14px;">复制</button>

Navigation组件其他常用的属性有: .title("设置标题") .mode(可自动分栏、单页) .navDestination(可指定) ,可看出使用Navigation组件开发页面更方便,页面管理和导航一举两得。Index整体的布局框架实现如下:

// Index.ets
[@Entry](/user/Entry)
[@Component](/user/Component)
struct Index {
  build() {
    Column({space:20}) {
      Navigation(this.pageInfos) {
        if(this.ShowBLEClient){
          BleScan() // 扫描管理子页面
        }else {
          BleAdvertising() // 广播管理子页面
        }
      }
      .title("BLE调试助手")
      .mode(NavigationMode.Auto)
      .navDestination(this.PageMap) // 工具栏实现切换广播和扫描
      .toolbarConfiguration([this.BLEClientTool, this.BLEAdvertisingTool]) 
    }.justifyContent(FlexAlign.Center)
    .height('100%')
    .width('100%')
  }
}
<button style="position: absolute; padding: 4px 8px 0px; cursor: pointer; top: 4px; right: 8px; font-size: 14px;">复制</button>

在Index.ets页面中使用provide提供导航页面栈NavPathStack,在ScanDevices.ets设备扫描子页面使用Consume绑定Navigation的NavPathStack,点击扫描设备后跳转到BLE 客户端管理页面(DeviceDetails.ets)。

// Index.ets
[@Provide](/user/Provide)('pageInfos') pageInfos: NavPathStack = new NavPathStack()
  [@Builder](/user/Builder)
  PageMap(name: string) {
    if (name === "DeviceDetails") {
      DeviceDetails()
    }
  }

// ScanDevices.ets // 设备扫描子页面 @Consume (‘pageInfos’) pageInfos: NavPathStack ListItem() { …扫描到的Device… } .onClick(() => { // 点击对应设备,高亮并获取设备ID与设备名,用于后续连接 this.deviceId = data.deviceId; this.deviceName = data.deviceName; this.clickNum = index; this.pageInfos.pushPath({ name: “DeviceDetails”}) // 跳转到BLE客户端管理页面 }) <button style="position: absolute; padding: 4px 8px 0px; cursor: pointer; top: 4px; right: 8px; font-size: 14px;">复制</button>

5 BLE功能开发

https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V5/js-apis-bluetooth-ble-V5)

5.1 设置广播参数

在文件BLEAdvertising.ets 中详细介绍了BLE 设备广播的实现,手机广播之前需要了解BLE广播主要的参数,一个BLE广播主要包含的信息整理如下:

  • 广播设备的名称(Name,显示名字时不能超过31字节)
  • 设备厂商名(manufactureName)与ID(manufactureId)
  • 广播数据advData
  • 广播服务UUID、和广播数值数据
  • 广播发送的参数,有三个:
    • 广播间隔:间隔越长越不容易被扫描到,160个slot表示100ms,最大值设置16384个slot,
    • 广播功率:发送功率 默认-7 推荐值:高档(1),中档(-7),低档(-15)。
    • 广播是否可连接:设置能否被客户端连接
 //设置广播发送参数,见Advertising.ets 
[@State](/user/State) setting_: ble.AdvertiseSetting = {
    interval: 160, 
    txPower: -7,  
    connectable: true 
    };
// 可使用Slider组件设置有限定范围的变量,如使用Slider设置txPower
 Slider({value:this.txPower,min:-15,max:1,step:1,style:SliderStyle.InSet})
            .onChange((value:number)=>{
              this.setting_.txPower = value
              bleAdvertisingManager.SetAdvertiseSetting(this.setting_)
            }).width('60%')
<button style="position: absolute; padding: 4px 8px 0px; cursor: pointer; top: 4px; right: 8px; font-size: 14px;">复制</button>

除设置广播发送的参数外,下面看如何构造广播数据ble.AdvertisingParams。

  • 1.设置厂商参数,并且在Advertising.ets 页面中能使用提供的接口该配置
// 初始化参数,需要注意使用Uint8Array
manufactureValueBuffer = new Uint8Array(4);
this.manufactureValueBuffer[0] = 1;
this.manufactureValueBuffer[1] = 2;
this.manufactureValueBuffer[2] = 3;
this.manufactureValueBuffer[3] = 4;
manufactureDataUnit: ble.ManufactureData = {
    manufactureId: 4567,
    manufactureValue: this.manufactureValueBuffer.buffer
 };
// 提供的设置接口:输入厂商ID信息ManuID与数值manufactureValueBuffer
  SetManufactureValueBuffer(ManuID:number,ManuBuff:Array<number>){
    let manufactureValueBuffer = new Uint8Array(4);
    manufactureValueBuffer[0] = ManuBuff[0];
    ...
    this.manufactureDataUnit = manufactureDataUnit
  }
<button style="position: absolute; padding: 4px 8px 0px; cursor: pointer; top: 4px; right: 8px; font-size: 14px;">复制</button>
  • 2.构建服务参数,包括服义UUID和serviceValue,同样提供了修改接口。
// 默认参数
serviceValueBuffer = new Uint8Array(4);
this.serviceValueBuffer[0] = 5;
this.serviceValueBuffer[1] = 6;
this.serviceValueBuffer[2] = 7;
this.serviceValueBuffer[3] = 8;
 let serviceDataUnit: ble.ServiceData = {
      serviceUuid: "00001888-0000-1000-8000-00805f9b34fb",
      serviceValue: this.serviceValueBuffer.buffer
    };
// 提供的修改接口,输入广播的服务UUID与值ServerValBuff
SetServiceValueBuffer(ServerUUID:string,ServerValBuff:Array<number>){...}
<button style="position: absolute; padding: 4px 8px 0px; cursor: pointer; top: 4px; right: 8px; font-size: 14px;">复制</button>

  • 3.广播数据构造,与1、2设置的厂商和服务参数相关,修改1、2即可。
let advData: ble.AdvertiseData = {
      serviceUuids: ["0000"+ServerUUID+"-0000-1000-8000-00805f9b34fb"],
      manufactureData: [this.manufactureDataUnit],
      serviceData: [this.serviceDataUnit],
      includeDeviceName: true // 表示是否携带设备名,可选参数。注意带上设备名时广播包长度不能超出31个字节。
    };
    this.advData = advData
<button style="position: absolute; padding: 4px 8px 0px; cursor: pointer; top: 4px; right: 8px; font-size: 14px;">复制</button>
  • 4.广播时响应的参数构造,同样与1、2设置的厂商和服务参数相关。
    let advResponse: ble.AdvertiseData = {
      serviceUuids: ["0000"+ServerUUID+"-0000-1000-8000-00805f9b34fb"],
      manufactureData: [this.manufactureDataUnit],
      serviceData: [this.serviceDataUnit]
    };
    this.advResponse  = advResponse
<button style="position: absolute; padding: 4px 8px 0px; cursor: pointer; top: 4px; right: 8px; font-size: 14px;">复制</button>
  • 5.使用1、2、3、4步的参数构造广播启动需要的完整参数AdvertisingParams

     let advertisingParams: ble.AdvertisingParams = {
          advertisingSettings: setting,
          advertisingData: advData,
          advertisingResponse: advResponse,
          duration: 0 // 可选参数,若大于0,则广播发送一段时间后,则会临时停止,可重新启动发送
        }
    <button style="position: absolute; padding: 4px 8px 0px; cursor: pointer; top: 4px; right: 8px; font-size: 14px;">复制</button>

5.2 广播管理

完成广播参数构造后,下一步需要管理广播,主要有以下步骤:

  • 0.监听广播变化,可随时获取广播状态,可能的状态有

    enum AdvertisingState {
        STARTED = 1, // 开启广播
        ENABLED = 2, // 继续广播
        DISABLED = 3,// 暂停广播
        STOPPED = 4  // 停止广播
    }
    <button style="position: absolute; padding: 4px 8px 0px; cursor: pointer; top: 4px; right: 8px; font-size: 14px;">复制</button>
 public onAdvertisingStateChange() {
    try {
      ble.on('advertisingStateChange', (data: ble.AdvertisingStateChangeInfo) => {
        console.info(TAG, 'bluetooth advertising state = ' + JSON.stringify(data));
      });
    } catch (err) {
          ....
    }
  }
<button style="position: absolute; padding: 4px 8px 0px; cursor: pointer; top: 4px; right: 8px; font-size: 14px;">复制</button>
  • 1.开启广播,使用用户设置的参数构造广播参数Param,然后使用ble.startAdvertising(Param)开启广播。
Button("开启广播").onClick(()=>{
    bleAdvertisingManager.startAdvertising(bleAdvertisingManager.advertisingParams)
  })

public async startAdvertising(Param:ble.AdvertisingParams) { // 首次启动广播,且获取所启动广播的标识ID try { this.onAdvertisingStateChange(); this.advHandle = await ble.startAdvertising(Param); } catch (err) { … } } <button style="position: absolute; padding: 4px 8px 0px; cursor: pointer; top: 4px; right: 8px; font-size: 14px;">复制</button>

开启广播后,可以使用BLE客户端设备扫描,可以看到广播的信息,可以核对是否与5.1节配置的参数一致。

  • 2.暂停广播,当临时关闭广播时可以使用 ble.disableAdvertising(advertisingDisableParams)实现
  public async disableAdvertising() {
    //构造临时停止广播参数
    let advertisingDisableParams: ble.AdvertisingDisableParams = {
      advertisingId: this.advHandle // 使用首次启动广播时获取到的广播标识ID
    }
    try {
      await ble.disableAdvertising(advertisingDisableParams);
    } catch (err) {
     ....
    }
  }
<button style="position: absolute; padding: 4px 8px 0px; cursor: pointer; top: 4px; right: 8px; font-size: 14px;">复制</button>
  • 3、继续广播,临时暂停广播后继续广播调用ble.enableAdvertising(advertisingEnableParams)
public async enableAdvertising(enableDuration: number) {
    let advertisingEnableParams: ble.AdvertisingEnableParams = {
      advertisingId: this.advHandle, // 使用首次启动广播时获取到的广播标识ID
      duration: enableDuration
    }
    try {
      await ble.enableAdvertising(advertisingEnableParams);  // 再次启动
    } catch (err) {
        ...
    }
<button style="position: absolute; padding: 4px 8px 0px; cursor: pointer; top: 4px; right: 8px; font-size: 14px;">复制</button>
  • 4、停止广播,与临时暂停广播不同,直接关闭广播会释放资源,不能够继续使用首次广播的advHandle进行再次广播,需要重新开启新的广播流程。
  public async stopAdvertising() {
    try {
      await ble.stopAdvertising(this.advHandle);
      ble.off('advertisingStateChange', (data: ble.AdvertisingStateChangeInfo) => {
        console.info(TAG, 'bluetooth advertising state = ' + JSON.stringify(data));
      });
    } catch (err) {
   ....
    }
  }
<button style="position: absolute; padding: 4px 8px 0px; cursor: pointer; top: 4px; right: 8px; font-size: 14px;">复制</button>

6.总结

至此BLE调试助手APP开发基本完成,实现了BLE广播与BLE服务端连接交互。感兴趣可访问仓库地址HelloKun - Gitee.com,欢迎大家提意见,等待星闪API开放继续添加其调试功能!

</markdown>

关于#HarmonyOS 鸿蒙Next 体验官#开发一个BLE低功耗蓝牙调试助手(二)实现BLE广播的问题,您也可以访问:https://www.itying.com/category-93-b0.html 联系官网客服。
2 回复

mac上的蓝牙距离锁屏软件正好需要这个,Mate 60的蓝牙广播,手机息屏后就没有了😘,用这个感觉可以搞定

哪个仓库是呢
回到顶部