如何进行蓝牙连接

场景说明

蓝牙技术是一种无线数据和语音通信开放的全球规范,它是基于低成本的近距离无线连接,为固定和移动设备建立通信环境的一种特殊的连接。本示例通过@ohos.bluetoothManager接口实现蓝牙设备发现、配对、取消配对功能。

效果呈现

本示例最终效果如下:

发现设备 连接设备 断开连接

运行环境

本例基于以下环境开发,开发者也可以基于其他适配的版本进行开发。

  • IDE:DevEco Studio 3.1.1 Release
  • SDK:Ohos_sdk_full 4.0.8.5(API Version 10 Beta1)

实现思路

本文涉及到蓝牙的设备发现、配对、取消配对三个功能特性,实现思路如下:

  • 启动和关闭蓝牙:在Index页面中通过Toggle组件的onChange函数控制蓝牙的开关,开关打开的情况下执行initBluetooth函数,关闭的情况下执行bluetooth.disableBluetooth()方法来断开蓝牙;
  • 验证蓝牙是否处于连接状态:蓝牙打开的时候通过bluetooth.on('stateChange')方法监听蓝牙连接状态改变事件,如确认已打开,执行foundDevices()函数来查找设备接口,确认已关闭则执行bluetooth.stopBluetoothDiscovery()方法停止查询接口。
  • 发现设备:在foundDevices()函数中通过bluetooth.on('bluetoothDeviceFind')方法监听设备发现事件,通过bluetooth.getPairedDevices()方法更新已配对蓝牙地址,然后通过bluetooth.startBluetoothDiscovery()方法开启蓝牙扫描发现远端设备,并且通过bluetooth.setBluetoothScanMode()方法来被远端设备发现。
  • 蓝牙配对:通过bluetooth.on('pinRequired')方法监听远端蓝牙设备的配对请求事件,点击配对执行bluetooth.setDevicePairingConfirmation(this.data.deviceId, true)方法,点击取消执行bluetooth.setDevicePairingConfirmation(this.data.deviceId, false)方法。
  • 取消配对:使用bluetooth.cancelPairedDevice()断开指定的远端设备连接。

开发步骤

  1. 申请蓝牙权限。 使用蓝牙需要先申请对应的权限,在module.json5文件中添加以下配置:

    "requestPermissions": [
          {
            //允许应用查看蓝牙的配置
            "name": "ohos.permission.USE_BLUETOOTH",
            "reason": "$string:grant_use_bluetooth",
            "usedScene": {
              "abilities": [
                "MainAbility"
              ],
              "when": "inuse"
            }
          },
          {
            //允许应用配置本地蓝牙,查找远端设备且与之配对连接
            "name": "ohos.permission.DISCOVER_BLUETOOTH",
            "reason": "$string:grant_discovery_bluetooth",
            "usedScene": {
              "abilities": [
                "MainAbility"
              ],
              "when": "inuse"
            }
          },
          {
            //允许应用获取设备位置信息
            "name": "ohos.permission.LOCATION",
            "reason": "$string:grant_location",
            "usedScene": {
              "abilities": [
                "MainAbility"
              ],
              "when": "inuse"
            }
          },
          {
            //允许应用获取设备模糊位置信息
            "name": "ohos.permission.APPROXIMATELY_LOCATION",
            "reason": "$string:grant_location",
            "usedScene": {
              "abilities": [
                "MainAbility"
              ],
              "when": "inuse"
            }
          },
          {
            //允许应用配对蓝牙设备,并对设备的电话簿或消息进行访问
            "name": "ohos.permission.MANAGE_BLUETOOTH",
            "reason": "$string:grant_manage_bluetooth",
            "usedScene": {
              "abilities": [
                "MainAbility"
              ],
              "when": "inuse"
            }
          }
        ]
    
  2. 构建UI框架,整体的UI框架分为TitleBar(页面名称),PinDialog(配对蓝牙弹框),index(主页面)三个部分。

    //Common/TitleBar.ets
    @Component
    export struct TitleBar {
      private handlerClickButton: () => void
    
      build() {
        Row() {
          Image($r('app.media.ic_back'))
            .width(40)
            .height(30)
            .onClick(() => {
              this.handlerClickButton()
            })
          Text($r('app.string.bluetooth'))
            .fontSize(30)
            .width(150)
            .height(50)
            .margin({ left: 15 })
            .fontColor('#ffa2a3a4')
        }
        .width('100%')
        .height(60)
        .padding({ left: 20, top: 10 })
        .backgroundColor('#ff2d30cb')
        .constraintSize({ minHeight: 50 })
      }
    }
    
    //Common/PinDalog.ets
     ...
     aboutToAppear() {
        this.titleText = `"${this.data.deviceId}"要与您配对。请确认此配对码已在"${this.data.deviceId}"上直接显示,且不是手动输入的。`
        this.pinCode = JSON.stringify(this.data.pinCode)
      }
      build() {
        //配对弹框描述文字
        Column({ space: 10 }) {
          Text($r('app.string.match_request'))
            .fontSize(30)
            .alignSelf(ItemAlign.Start)
          Text(this.titleText)
            .alignSelf(ItemAlign.Start)
            .margin({ top: 20 })
            .fontSize(21)
          Text(this.pinCode)
            .fontSize(40)
            .fontWeight(FontWeight.Bold)
            .margin({ top: 20 })
          Flex({ direction: FlexDirection.Row, alignItems: ItemAlign.Center }) {
            Checkbox({ name: 'checkbox' })
              .select(false)
              .selectedColor('#ff3d6fb8')
              .key('checkBox')
            Text($r('app.string.grant_permission'))
              .fontSize(15)
              .margin({ left: 3, top: 6 })
          }
          .alignSelf(ItemAlign.Start)
          .width('95%')
          .margin({ top: 5 })
    
          Row() {
            //配对选择UI,取消or配对
            this.choiceText($r('app.string.cancel'), () => {
              bluetooth.setDevicePairingConfirmation(this.data.deviceId, false)
              logger.info(TAG, `setDevicePairingConfirmation = ${bluetooth.setDevicePairingConfirmation(this.data.deviceId, false)}`)
              this.controller.close()
            })
    
            Divider()
              .vertical(true)
              .height(32)
    
            this.choiceText($r('app.string.match'), () => {
              bluetooth.setDevicePairingConfirmation(this.data.deviceId, true)
              logger.info(TAG, `setDevicePairingConfirmation = ${bluetooth.setDevicePairingConfirmation(this.data.deviceId, true)}`)
              this.controller.close()
            })
          }
          .margin({ top: 20 })
        }
        .width('100%')
        .padding(15)
      }
    ...
    
    //pages/index.ets
    @Entry
    @Component
    struct Index {
     build() {
        Column() {
          TitleBar({ handlerClickButton: this.handlerClickButton })
          Scroll() {
            Column() {
              Row() {
                //蓝牙开关
                Column() {
                  Text($r('app.string.bluetooth'))
                    .fontSize(30)
                    .margin({ top: 20 })
                    .alignSelf(ItemAlign.Start)
                  if (true === this.isOn) {
                    Text($r('app.string.discovery'))
                      .fontSize(20)
                      .alignSelf(ItemAlign.Start)
                  }
                }
    
                Blank()
    
                Column() {
                  Toggle({ type: ToggleType.Switch, isOn: this.isOn })
                    .selectedColor('#ff2982ea')
                    .onChange((isOn: boolean) => {
                      if (isOn) {
                        this.isOn = true
                        this.initBluetooth()
                      } else {
                        this.isOn = false
                        bluetooth.disableBluetooth()
                        this.deviceList = []
                        this.discoveryList = []
                      }
                    })
                }
                .id('toggleBtn')
              }
              .width('90%')
    
              if (this.isOn) {
                Divider()
                  .vertical(false)
                  .strokeWidth(10)
                  .color('#ffece7e7')
                  .lineCap(LineCapStyle.Butt)
                  .margin('1%')
                //已配对的设备
                Text($r('app.string.paired_device'))
                  .fontSize(25)
                  .fontColor('#ff565555')
                  .margin({ left: '5%' })
                  .alignSelf(ItemAlign.Start)
    
                ForEach(this.deviceList, (item, index) => {
                  Row() {
                    Text(item)
                      .fontSize(20)
                  }
                  .alignSelf(ItemAlign.Start)
                  .width('100%')
                  .height(50)
                  .margin({ left: '5%', top: '1%' })
                  .id(`pairedDevice${index}`)
                  .onClick(() => {
                    AlertDialog.show({
                      //取消配对
                      title: $r('app.string.disconnect'),
                      message: '此操作将会断开您与以下设备的连接:' + item,
                      primaryButton: {
                        value: $r('app.string.cancel'),
                        action: () => {
                        }
                      },
                      //确认取消
                      secondaryButton: {
                        value: $r('app.string.confirm'),
                        action: () => {
                          try {
                            bluetooth.cancelPairedDevice(item);
                            this.deviceList = bluetooth.getPairedDevices()
                            this.discoveryList = []
                            bluetooth.startBluetoothDiscovery()
                          } catch (err) {
                            console.error("errCode:" + err.code + ",errMessage:" + err.message);
                          }
                        }
                      }
                    })
                  })
                })
    
                Divider()
                  .vertical(false)
                  .strokeWidth(10)
                  .color('#ffece7e7')
                  .lineCap(LineCapStyle.Butt)
                  .margin('1%')
    
                Text($r('app.string.available_device'))
                  .fontSize(25)
                  .fontColor('#ff565555')
                  .margin({ left: '5%', bottom: '2%' })
                  .alignSelf(ItemAlign.Start)
                //可用设备列表
                ForEach(this.discoveryList, (item) => {
                  Row() {
                    Text(item)
                      .fontSize(20)
                  }
                  .alignSelf(ItemAlign.Start)
                  .width('100%')
                  .height(50)
                  .margin({ left: '5%', top: '1%' })
                  //进行配对操作点击
                  .onClick(() => {
                    logger.info(TAG, `start bluetooth.pairDevice,item = ${item}`)
                    let pairStatus = bluetooth.pairDevice(item)
                    logger.info(TAG, `pairStatus = ${pairStatus}`)
                  })
    
                  Divider()
                    .vertical(false)
                    .color('#ffece7e7')
                    .lineCap(LineCapStyle.Butt)
                    .margin('1%')
                })
              }
            }
          }
          .constraintSize({ maxHeight: '85%' })
        }
      }
    }
    
  3. 蓝牙开关打开关闭操作,需要打开蓝牙开关才能进行后续操作:

    initBluetooth() {
        bluetooth.on('stateChange', (data) => {
          logger.info(TAG, `enter on stateChange`)
          //判断蓝牙开关状态
          if (data === bluetooth.BluetoothState.STATE_ON) {
            logger.info(TAG, `enter BluetoothState.STATE_ON`)
            //蓝牙打开后的相关操作
            this.foundDevices()
          }
          if (data === bluetooth.BluetoothState.STATE_OFF) {
            logger.info(TAG, `enter BluetoothState.STATE_OFF`)
            bluetooth.off('bluetoothDeviceFind', (data) => {
              logger.info(TAG, `offBluetoothDeviceFindData = ${JSON.stringify(data)}`)
            })
            //关闭
            bluetooth.stopBluetoothDiscovery()
            this.discoveryList = []
          }
          logger.info(TAG, `BluetoothState = ${JSON.stringify(data)}`)
        })
        //开启蓝牙
        bluetooth.enableBluetooth()
      }
    
  4. 设置蓝牙扫描模式并开启扫描去发现设备,并订阅蓝牙设备发现上报时间获取设备列表

     foundDevices() {
        //订阅蓝牙设备发现上报事件
        bluetooth.on('bluetoothDeviceFind', (data) => {
          logger.info(TAG, `enter on bluetoothDeviceFind`)
          if (data !== null && data.length > 0) {
            if (this.discoveryList.indexOf(data[0]) === -1 && this.deviceList.indexOf(data[0]) === -1) {
              this.discoveryList.push(data[0])
            }
            logger.info(TAG, `discoveryList = ${JSON.stringify(this.discoveryList)}`)
          }
          let list = bluetooth.getPairedDevices()
          if (list !== null && list.length > 0) {
            this.deviceList = list
            logger.info(TAG, `deviceList =  ${JSON.stringify(this.deviceList)}`)
          }
        })
        //开启蓝牙扫描,可以发现远端设备
        bluetooth.startBluetoothDiscovery()
        //设置蓝牙扫描模式,可以被远端设备发现
        bluetooth.setBluetoothScanMode(bluetooth.ScanMode.SCAN_MODE_CONNECTABLE_GENERAL_DISCOVERABLE, TIME)
      }
    
  5. 设置蓝牙扫描模式并开启扫描去发现设备,并订阅蓝牙设备发现上报时间获取设备列表

    //配对确定和取消代码在PinDialog.ets文件中
    //setDevicePairingConfirmation(device: string, accept: boolean): void
    //device	string	表示远端设备地址,例如:"XX:XX:XX:XX:XX:XX
    //accept	boolean	接受配对请求设置为true,否则设置为false
    
    //订阅蓝牙配对状态改变事件,根据蓝牙状态更新设备列表
    bluetooth.on('bondStateChange', (data) => {
        logger.info(TAG, `enter bondStateChange`)
        logger.info(TAG, `data = ${JSON.stringify(data)}`)
        if (data.state === bluetooth.BondState.BOND_STATE_BONDED) {
            logger.info(TAG, `BOND_STATE_BONDED`)
            let index = this.discoveryList.indexOf(data.deviceId)
            this.discoveryList.splice(index, 1)
            this.deviceList = bluetooth.getPairedDevices()
        }
        if (data.state === bluetooth.BondState.BOND_STATE_INVALID) {
            logger.info(TAG, `BOND_STATE_INVALID`)
            this.deviceList = bluetooth.getPairedDevices()
        }
        logger.info(TAG, `bondStateChange,data = ${JSON.stringify(data)}`)
    })
    

完整代码

本例完整代码sample示例链接:蓝牙Sample

参考