1. 權(quán)限配置
在 module.json5 文件中添加藍(lán)牙相關(guān)權(quán)限:
{
"module": {
"requestPermissions": [
{
"name": "ohos.permission.ACCESS_BLUETOOTH",
"reason": "需要使用藍(lán)牙功能"
}
]
}
}
2. 導(dǎo)入藍(lán)牙模塊
import bluetooth from '@ohos.bluetooth';
3. 藍(lán)牙狀態(tài)檢查與開啟
//用戶申請藍(lán)牙權(quán)限
async requestBlueToothPermission() {
let grantStatus = await this.reqPermissionsFromUser();
for (let i = 0; i < grantStatus.length; i++) {
if (grantStatus[i] === 0) {
//查看當(dāng)前藍(lán)牙開啟狀態(tài)
this.isStartBlueTooth = BleState.getBluetoothState();
if (!this.isStartBlueTooth) {
showConfirmAlertDialog('藍(lán)牙未開啟,請滑出系統(tǒng)控制中心,打開藍(lán)牙進(jìn)行連接', () => {})
}
} else {
showConfirmAlertDialog('藍(lán)牙授權(quán)失敗,請打開設(shè)置進(jìn)行授權(quán)', () => {
WantUtil.toAppSetting()
})
console.info(CommonConstants.log_prefix + '藍(lán)牙授權(quán)失??!')
}
}
}
//用戶申請權(quán)限
async reqPermissionsFromUser(): Promise<number[]> {
// 申請權(quán)限
let context = getContext() as common.UIAbilityContext;
// 獲取AbilityAccessCtrl對象
let atManager = abilityAccessCtrl.createAtManager();
// 請求權(quán)限,傳入上下文和需要申請的權(quán)限列表
let grantStatus = await atManager.requestPermissionsFromUser(context, ['ohos.permission.ACCESS_BLUETOOTH']);
// 返回授權(quán)結(jié)果列表
return grantStatus.authResults;
}
4. 設(shè)備掃描
try {
const scanOptions: ble.ScanOptions = {
interval: 1000,
dutyMode: ble.ScanDuty.SCAN_MODE_LOW_POWER,
matchMode: ble.MatchMode.MATCH_MODE_AGGRESSIVE,
};
// 訂閱 BLEDeviceFind 事件
ble.on('BLEDeviceFind', (data: Array<ble.ScanResult>) => {
//解析藍(lán)牙廣播數(shù)據(jù)
});
// 開始掃描
ble.startBLEScan(filters, scanOptions);
this.isScanning = true;
} catch (err) {
throw new Error('Failed to start BLE scan');
}
5. 設(shè)備連接、數(shù)據(jù)接收
const clientDevice = ble.createGattClientDevice(deviceId)
//連接狀態(tài)
clientDevice.on('BLEConnectionStateChange', (state: ble.BLEConnectionChangeState) => {
const connectState = state.state;
switch (connectState) {
case constant.ProfileConnectionState.STATE_DISCONNECTED: // 設(shè)備已斷開連接
break;
case constant.ProfileConnectionState.STATE_CONNECTING: // 設(shè)備正在連接
break;
case constant.ProfileConnectionState.STATE_CONNECTED: // 設(shè)備已連接
//發(fā)現(xiàn)服務(wù)
this.getServices();
break;
case constant.ProfileConnectionState.STATE_DISCONNECTING: // 設(shè)備正在斷開連接
break;
default:
break;
}
})
//連接
try {
clientDevice.connect()
} catch (err) {
}
//獲取服務(wù)
private async getServices() {
if (this.gattClientDevice) {
const result: Array<ble.GattService> = await this.gattClientDevice.getServices();
console.info('getServices successfully:' + JSON.stringify(result));
let gatt = this.gattClientDevice;
LogUtils.debug('發(fā)現(xiàn)藍(lán)牙服務(wù)>>>' + JSON.stringify(result))
for (const item of result) {
console.info('getServices item= ' + JSON.stringify(item));
//服務(wù)UUID
if (item.serviceUuid === ‘讀寫特征UUID'’) {
this.gattServiceInfo = item;
const writeCharacteristic = item.characteristics.find(c => c.characteristicUuid == '讀寫特征UUID');
const notyCharacteristic = item.characteristics.find(c => c.characteristicUuid == '通知特征UUID');
// setNotification
if (writeCharacteristic) {
try {
const success = await this.setNotificationChannel(gatt, item, writeCharacteristic, true);
if (success) {
console.log(`Notification set successfully for characteristic(通道1) ${writeCharacteristic.characteristicUuid}`);
} else {
console.log(`Failed to set notification for characteristic(通道1) ${writeCharacteristic.characteristicUuid}`);
}
}catch (err){
}
}
// setNotification
if (notyCharacteristic) {
try {
const success = await this.setNotificationChannel(gatt, item, notyCharacteristic, true);
if (success) {
console.log(`Notification set successfully for characteristic(通道1) ${notyCharacteristic.characteristicUuid}`);
} else {
console.log(`Failed to set notification for characteristic(通道1) ${notyCharacteristic.characteristicUuid}`);
}
}catch (err){
}
}
}
}
//接收設(shè)備數(shù)據(jù)
this.onBleCharacteristicChange()
}
}
//向下位機(jī)發(fā)送設(shè)置通知此indicate征值請求
private async setNotificationChannel(
gatt: ble.GattClientDevice | null ,
gattService: ble.GattService | undefined,
characteristic: ble.BLECharacteristic,
enable: boolean
): Promise<boolean>{
if (!gattService) {
return false; // 返回失敗
}
if (gatt == null){
return false; // 返回失敗
}
try {
if (gatt) {
// 向下位機(jī)發(fā)送設(shè)置通知此indicate征值請求
await gatt?.setCharacteristicChangeNotification(characteristic, enable)
return true; // 返回成功
}
} catch (err) {
return false; // 返回失敗
}
return false; // 如果沒有g(shù)att,返回失敗
}
//訂閱藍(lán)牙低功耗設(shè)備的特征值變化事件 (接收消息)
private onBleCharacteristicChange() {
LogUtils.debug('開始監(jiān)聽特征值變化')
try {
if (this.gattClientDevice) {
//監(jiān)聽
this.gattClientDevice.on('BLECharacteristicChange', (characteristicChangeReq: ble.BLECharacteristic) => {
let serviceUuid: string = characteristicChangeReq.serviceUuid;
let characteristicUuid: string = characteristicChangeReq.characteristicUuid;
const characteristicValue = characteristicChangeReq.characteristicValue
//服務(wù)UUID
if(serviceUuid == '讀寫特征UUID'){
//回調(diào)數(shù)據(jù)
} else {
}
})
}
} catch (err) {
}
}
6. 數(shù)據(jù)發(fā)送
export class TBBleCommandManager {
private gatt: ble.GattClientDevice | null
private gattServiceInfo: ble.GattService | null
private data: ArrayBuffer;//特征數(shù)據(jù)
/**
* 構(gòu)造函數(shù)
* @param gatt GattClientDevice實例
* @param gattServiceInfo GattService實例
* @param data 特征數(shù)據(jù)
*/
constructor(
gatt: ble.GattClientDevice | null,
gattServiceInfo: ble.GattService | null,
data: ArrayBuffer,
) {
this.gatt = gatt
this.gattServiceInfo = gattServiceInfo
this.data = data
}
//寫入特征值
writeBrushCharacteristicValue(){
//查找讀寫特征
const characteristic = this.gattServiceInfo?.characteristics.find(c => c.characteristicUuid === BrushConst.WRITE_CHARACTERISTIC_UUID);
if (!characteristic) {
return
}
try {
if (this.gatt) {
// 設(shè)置特征值
characteristic.characteristicValue = this.data
// 寫入特征值到設(shè)備中
this.gatt.writeCharacteristicValue(characteristic, ble.GattWriteType.WRITE)
} else {
}
}
catch (err) {
const errorCode = (err as BusinessError).code;
const errorMessage = (err as BusinessError).message;
}
}
}
數(shù)據(jù)粘包的問題,我們可以采用隊列的方式處理
import { BluetoothCommand } from "./BluetoothCommand";
import { BusinessError } from "@kit.BasicServicesKit";
import { TBBleCommandManager } from "../../toothbrush/model/TBBleCommandManager";
export class BluetoothCommandQueue {
// 隊列存儲指令,根據(jù)優(yōu)先級排序
private queue: BluetoothCommand[] = [];
// 標(biāo)記是否正在處理指令
private isProcessing: boolean = false;
// 隊列最大長度限制,可根據(jù)實際情況調(diào)整
private maxSize: number = 999;
// 添加指令到隊列
enqueue(command: BluetoothCommand): boolean {
if (this.queue.length >= this.maxSize) {
return false;
}
// 根據(jù)優(yōu)先級插入隊列
let inserted = false;
for (let i = 0; i < this.queue.length; i++) {
if (this.queue[i].priority < command.priority) {
this.queue.splice(i, 0, command);
inserted = true;
break;
}
}
if (!inserted) {
this.queue.push(command);
}
this.processQueue();
return true;
}
// 從隊列取出指令
dequeue(): BluetoothCommand | undefined {
return this.queue.shift();
}
// 處理隊列中的指令
private async processQueue(): Promise<void> {
if (this.isProcessing || this.queue.length === 0) {
return;
}
this.isProcessing = true;
while (this.queue.length > 0) {
const command:BluetoothCommand = this.dequeue() as BluetoothCommand;
if (command) {
try {
// 這里調(diào)用實際的藍(lán)牙發(fā)送接口
await this.sendBluetoothCommand(command);
command.callback?.(null);
} catch (error) {
command.callback?.(error as BusinessError);
}
}
}
// 處理完成后,標(biāo)記為非處理狀態(tài)
this.isProcessing = false;
}
// 發(fā)送藍(lán)牙指令的方法
private sendBluetoothCommand(command: BluetoothCommand): Promise<void> {
return new Promise((resolve, reject) => {
// 發(fā)送藍(lán)牙指令
new TBBleCommandManager(command.gatt, command.gattServiceInfo, command.command).writeBrushCharacteristicValue()
setTimeout(() => {
console.info('Bluetooth command sent successfully');
// 成功回調(diào)
resolve();
}, 100);
});
}
// 清空隊列
clear(): void {
this.queue = [];
this.isProcessing = false;
}
// 獲取隊列大小
size(): number {
return this.queue.length;
}
}
import { BusinessError } from "@kit.BasicServicesKit";
import { ble } from "@kit.ConnectivityKit";
export class BluetoothCommand {
command: ArrayBuffer;
priority: number;
timestamp: number;
callback?: (result: BusinessError | null) => void;
gatt: ble.GattClientDevice | null;
gattServiceInfo: ble.GattService | null;
/**
* 構(gòu)造函數(shù)
* @param command 命令
* @param priority 優(yōu)先級
* @param gatt GattClientDevice實例
* @param gattServiceInfo GattService實例
* @param callback 回調(diào)函數(shù)
*/
constructor(command: ArrayBuffer, priority: number = 0, gatt: ble.GattClientDevice , gattServiceInfo: ble.GattService , callback?: (result: BusinessError | null) => void) {
this.command = command;
this.gatt = gatt;
this.gattServiceInfo = gattServiceInfo;
this.priority = priority;
this.timestamp = Date.now();
this.callback = callback;
}
}
隊列的調(diào)用方法如下
private commandQueue: BluetoothCommandQueue = new BluetoothCommandQueue();//指令發(fā)送隊列
// 寫入BLE特征值
// 創(chuàng)建藍(lán)牙指令對象
const command = new BluetoothCommand( buff, 0, this.gattClientDevice!, this.gattServiceInfo!,
(error: BusinessError | null) => {
if (error) {
// `發(fā)送失敗: ${error.message}`;
} else {
// '發(fā)送成功';
}
}
);
this.commandQueue.enqueue(command)
注意事項
- 權(quán)限申請:必須在配置文件中聲明藍(lán)牙權(quán)限,并在運行時檢查權(quán)限狀態(tài)
- 異步操作:藍(lán)牙操作多為異步,需正確處理回調(diào)
- 狀態(tài)監(jiān)聽:建議監(jiān)聽藍(lán)牙狀態(tài)變化,及時響應(yīng)開關(guān)變化
- 資源釋放:使用完畢后及時斷開連接,釋放藍(lán)牙資源
- 異常處理:做好異常捕獲和錯誤處理機(jī)制
- 兼容性:注意不同設(shè)備型號和系統(tǒng)版本的兼容性問題
- 安全性:配對和數(shù)據(jù)傳輸時注意安全加密
- 功耗控制:合理控制掃描頻率,避免過度耗電