【最新】鴻蒙Next 藍(lán)牙開發(fā)流程

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ù)傳輸時注意安全加密
  • 功耗控制:合理控制掃描頻率,避免過度耗電
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容