#HarmonyOS 鸿蒙Next 体验官#开发一个BLE低功耗蓝牙调试助手(二)实现BLE广播
#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 联系官网客服。
mac上的蓝牙距离锁屏软件正好需要这个,Mate 60的蓝牙广播,手机息屏后就没有了😘,用这个感觉可以搞定