OpenHarmony经典蓝牙之配对详解 原创 精华
作者:马魁
1. 简介
要使用设备中的蓝牙功能,需要先进行设备扫描,然后对扫描到的设备进行配对连接。
蓝牙配对就像是两个蓝牙设备间的注册。配对类似于两个设备互相交换电话号码或其他信息来保证互相的信息确认。且在第一次对设备进行配对后,设备已经保存了必要的信息,因此不需要重复这个配对过程就可以再次轻松地连接。
在OpenHarmony设备中,进入蓝牙设置打卡蓝牙开关后,就会进行蓝牙扫描,我们可以通过点击扫描到的某个设备来进行配对请求。
2. 配对请求流程
2.1 应用侧配对请求
对扫描到的某个设备进行点击操作,请求配对,应用侧代码如下( 代码路径:applications\standard\settings\product\phone\src\main\ets\MainAbility\pages\bluetooth.ets):
...
if (this.availableDevices && this.availableDevices.length >= 1) {
List() {
// paired devices list
ForEach(this.availableDevices, (item: BluetoothDevice) => {
ListItem() {
Row() {
EntryComponent({
settingIcon: getDeviceIconPath(item.deviceType),
settingTitle: item.deviceName ? item.deviceName : item.deviceId,
settingSummary: getConnectionStateText(item),
settingValue: '',
settingArrow: '',
settingArrowStyle: '',
settingUri: '',
image_wh: $r('app.float.wh_value_24'),
height: getConnectionStateText(item) == '' ? $r('app.float.wh_value_56') : ($r('app.float.wh_value_64')),
fontSize: ($r('app.float.font_16')),
});
}
.width(ConfigData.WH_100_100)
.borderRadius($r("app.float.radius_24"))
.padding($r('app.float.distance_4'))
.backgroundColor($r("app.color.white_bg_color"))
//点击当前设备,请求配对
.onClick(() => {
LogUtil.info(this.TAG_PAGE + 'item on click : ' + JSON.stringify(item));
this.pairDevice(item)
})
}
}, item => JSON.stringify(item));
}
.backgroundColor($r("app.color.white_bg_color"))
.borderRadius($r("app.float.radius_24"))
.divider({
strokeWidth: 1,
color: $r('app.color.color_E3E3E3_grey'),
startMargin: $r('app.float.wh_value_52'),
endMargin: $r('app.float.wh_value_12')
})
} else {
...
点击事件的处理在onClick函数中,通过调用this.pairDevice(item)来进行对选中的设备进行配对请求。
pairDevice(item)函数接口如下:
/**
* Pair device
* @param device
*/
@Log
pairDevice(device: BluetoothDevice) {
this.controller.pair(device.deviceId, (pinCode: string) => {
LogUtil.info(this.TAG_PAGE + 'pairDevice success callback : pinCode = ' + pinCode);
// show pair pin dialog
this.pairPinCode = pinCode
this.pairingDevice = device;
this.pairDialog.open();
}, () => {
LogUtil.info(this.TAG_PAGE + 'pairDevice error callback');
this.showPairFailedDialog()
})
}
该接口通过继续调用this.controller.pair()方法请求配对,该方法如下(代码路径:applications\standard\settings\product\phone\src\main\ets\MainAbility\controller\bluetooth\BluetoothDeviceController.ts):
/**
* Pair device.
*
* @param deviceId device id
* @param success success callback
* @param error error callback
*/
pair(deviceId: string, success?: (pinCode: string) => void, error?: () => void): void {
const device: BluetoothDevice = this.getAvailableDevice(deviceId);
if (device && device.connectionState === DeviceState.STATE_PAIRING) {
LogUtil.log(this.TAG + `bluetooth no Aavailable device or device(${deviceId}) is already pairing.`)
return;
}
// start listening pinCode
BluetoothModel.subscribePinRequired((pinRequiredParam: {
deviceId: string;
pinCode: string;
}) => {
LogUtil.log(this.TAG + 'bluetooth subscribePinRequired callback. pinRequiredParam = ' + JSON.stringify(pinRequiredParam));
this.pairPinCode = pinRequiredParam.pinCode;
BluetoothModel.unsubscribePinRequired(() => LogUtil.log(this.TAG + 'available pinRequired unsubscribed.'));
if (success) {
success(this.pairPinCode);
}
})
// start pairing
const result = BluetoothModel.pairDevice(deviceId);
LogUtil.log(this.TAG + 'bluetooth pairDevice result = ' + result);
if (!result) {
BluetoothModel.unsubscribePinRequired(() => LogUtil.log(this.TAG + 'available pinRequired unsubscribed.'));
if (error) {
error();
}
}
}
通过代码可以看到,先是监听了扫描设备的配对pin码并拿到其设备地址,再通过BluetoothModel.pairDevice(deviceId)方法传入设备地址进行配对请求。
其中pairDevice方法最终是调用NAPI的pairDevice(deviceId)接口来实现。
/**
* Pair device
*/
pairDevice(deviceId: string): boolean {
return bluetooth.pairDevice(deviceId);
}
2.2 NAPI侧到C++侧的配对请求流程
NAPI侧到c++侧的主要流程如下图所示:
主要的流程分为三个阶段:
- napi_bluetooth_host.cpp的NAPI侧接口,通过IPC调用bluetooth SA的接口
- server到adapter的调用
- adapter到stack的调用,最终在stack中向下调用蓝牙驱动外围子系统的接口
3. 配对响应流程
3.1 C++到NAPI侧的配对响应流程
配对成功后的响应流程如下图所示:
该流程反向对应请求阶段三个流程,从协议栈stack到adapter,再到NAPI侧的observer。
3.2 应用设置侧的响应监听接口
应用设置侧对配对成功的监听主要代码如下:
/**
* Subscribe bond state change
*/
private subscribeBondStateChange() {
BluetoothModel.subscribeBondStateChange((data: {
deviceId: string;
bondState: number;
}) => {
LogUtil.log(this.TAG + 'bluetooth subscribeBondStateChange callback.');
//paired devices
if (data.bondState !== BondState.BOND_STATE_BONDING) {
this.refreshPairedDevices();
}
//available devices
if (data.bondState == BondState.BOND_STATE_BONDING) {
// case bonding
// do nothing and still listening
LogUtil.log(this.TAG + 'bluetooth continue listening bondStateChange.');
} else if (data.bondState == BondState.BOND_STATE_INVALID) {
// case failed
this.getAvailableDevice(data.deviceId).connectionState = DeviceState.STATE_DISCONNECTED;
this.forceRefresh(this.availableDevices);
AppStorage.SetOrCreate('bluetoothAvailableDevices', this.availableDevices);
this.showConnectFailedDialog();
} else if (data.bondState == BondState.BOND_STATE_BONDED) {
// case success
LogUtil.log(this.TAG + 'bluetooth bonded : remove device.');
this.removeAvailableDevice(data.deviceId);
BluetoothModel.connectDevice(data.deviceId);
}
});
}
subscribeBondStateChange函数用来监听配对状态。当配对成功后,会接着调用BluetoothModel.connectDevice(data.deviceId)接口来进行profile的链接。
4. 应用侧配对log流程
从配对请求到配对成功响应的settings应用的主要log如下:
//请求配对
08-05 09:11:29.753 1683 1692 D 03b00/JSApp: app Log: Settings BluetoothDeviceController bluetooth pairDevice result = true
//配对状态为正在配对
08-05 09:11:29.754 1683 1692 I 03b00/JSApp: app Log: Settings BluetoothModel subscribeBondStateChange->bondStateChange data:{"deviceId":"18:95:52:80:E2:3E","state":1}
//配对pin码
08-05 09:11:30.451 1683 1692 I 03b00/JSApp: app Log: Settings BluetoothModel subscribePinRequired->pinRequired return:{"deviceId":"18:95:52:80:E2:3E","pinCode":897679}
//pin码对话框
08-05 09:11:30.472 1683 1692 D 03b00/JSApp: app Log: Settings bluetooth PairDialog aboutToAppear pinCode = 897679
//配对状态为已经配对
08-05 09:11:30.870 1683 1692 I 03b00/JSApp: app Log: Settings BluetoothModel subscribeBondStateChange->bondStateChange data:{"deviceId":"18:95:52:80:E2:3E","state":2}
更多原创内容请关注:深开鸿技术团队
入门到精通、技巧到案例,系统化分享HarmonyOS开发技术,欢迎投稿和订阅,让我们一起携手前行共建鸿蒙生态。
厉害,每天都能学到新知识!
大佬,图片模糊看不清,请求上传附近