1# 如何进行蓝牙连接
2
3## 场景说明
4蓝牙技术是一种无线数据和语音通信开放的全球规范,它是基于低成本的近距离无线连接,为固定和移动设备建立通信环境的一种特殊的连接。本示例通过[@ohos.bluetoothManager](../application-dev/reference/apis-connectivity-kit/js-apis-bluetoothManager.md)接口实现蓝牙设备发现、配对、取消配对功能。
5
6## 效果呈现
7
8本示例最终效果如下:
9
10| 发现设备                        | 连接设备                          | 断开连接                          |
11| ------------------------------- | --------------------------------- | --------------------------------- |
12| ![](figures/bluetooth_list.png) | ![](figures/bluetooth_dialog.png) | ![](figures/bluetooth_delete.png) |
13
14## 运行环境
15
16本例基于以下环境开发,开发者也可以基于其他适配的版本进行开发。
17
18- IDE:DevEco Studio 3.1.1 Release
19- SDK:Ohos_sdk_full 4.0.8.5(API Version 10 Beta1)
20
21## 实现思路
22本文涉及到蓝牙的设备发现、配对、取消配对三个功能特性,实现思路如下:
23
24- 启动和关闭蓝牙:在Index页面中通过Toggle组件的onChange函数控制蓝牙的开关,开关打开的情况下执行initBluetooth函数,关闭的情况下执行bluetooth.disableBluetooth()方法来断开蓝牙;
25- 验证蓝牙是否处于连接状态:蓝牙打开的时候通过bluetooth.on('stateChange')方法监听蓝牙连接状态改变事件,如确认已打开,执行foundDevices()函数来查找设备接口,确认已关闭则执行bluetooth.stopBluetoothDiscovery()方法停止查询接口。
26- 发现设备:在foundDevices()函数中通过bluetooth.on('bluetoothDeviceFind')方法监听设备发现事件,通过bluetooth.getPairedDevices()方法更新已配对蓝牙地址,然后通过bluetooth.startBluetoothDiscovery()方法开启蓝牙扫描发现远端设备,并且通过bluetooth.setBluetoothScanMode()方法来被远端设备发现。
27- 蓝牙配对:通过bluetooth.on('pinRequired')方法监听远端蓝牙设备的配对请求事件,点击配对执行bluetooth.setDevicePairingConfirmation(this.data.deviceId, true)方法,点击取消执行bluetooth.setDevicePairingConfirmation(this.data.deviceId, false)方法。
28- 取消配对:使用bluetooth.cancelPairedDevice()断开指定的远端设备连接。
29
30## 开发步骤
311. 申请蓝牙权限。
32   使用蓝牙需要先申请对应的权限,在module.json5文件中添加以下配置:
33
34    ```json
35    "requestPermissions": [
36          {
37            //允许应用查看蓝牙的配置
38            "name": "ohos.permission.USE_BLUETOOTH",
39            "reason": "$string:grant_use_bluetooth",
40            "usedScene": {
41              "abilities": [
42                "MainAbility"
43              ],
44              "when": "inuse"
45            }
46          },
47          {
48            //允许应用配置本地蓝牙,查找远端设备且与之配对连接
49            "name": "ohos.permission.DISCOVER_BLUETOOTH",
50            "reason": "$string:grant_discovery_bluetooth",
51            "usedScene": {
52              "abilities": [
53                "MainAbility"
54              ],
55              "when": "inuse"
56            }
57          },
58          {
59            //允许应用获取设备位置信息
60            "name": "ohos.permission.LOCATION",
61            "reason": "$string:grant_location",
62            "usedScene": {
63              "abilities": [
64                "MainAbility"
65              ],
66              "when": "inuse"
67            }
68          },
69          {
70            //允许应用获取设备模糊位置信息
71            "name": "ohos.permission.APPROXIMATELY_LOCATION",
72            "reason": "$string:grant_location",
73            "usedScene": {
74              "abilities": [
75                "MainAbility"
76              ],
77              "when": "inuse"
78            }
79          },
80          {
81            //允许应用配对蓝牙设备,并对设备的电话簿或消息进行访问
82            "name": "ohos.permission.MANAGE_BLUETOOTH",
83            "reason": "$string:grant_manage_bluetooth",
84            "usedScene": {
85              "abilities": [
86                "MainAbility"
87              ],
88              "when": "inuse"
89            }
90          }
91        ]
92    ```
93
942. 构建UI框架,整体的UI框架分为TitleBar(页面名称),PinDialog(配对蓝牙弹框),index(主页面)三个部分。
95
96    ```ts
97    //Common/TitleBar.ets
98    @Component
99    export struct TitleBar {
100      private handlerClickButton: () => void
101
102      build() {
103        Row() {
104          Image($r('app.media.ic_back'))
105            .width(40)
106            .height(30)
107            .onClick(() => {
108              this.handlerClickButton()
109            })
110          Text($r('app.string.bluetooth'))
111            .fontSize(30)
112            .width(150)
113            .height(50)
114            .margin({ left: 15 })
115            .fontColor('#ffa2a3a4')
116        }
117        .width('100%')
118        .height(60)
119        .padding({ left: 20, top: 10 })
120        .backgroundColor('#ff2d30cb')
121        .constraintSize({ minHeight: 50 })
122      }
123    }
124
125    //Common/PinDalog.ets
126     ...
127     aboutToAppear() {
128        this.titleText = `"${this.data.deviceId}"要与您配对。请确认此配对码已在"${this.data.deviceId}"上直接显示,且不是手动输入的。`
129        this.pinCode = JSON.stringify(this.data.pinCode)
130      }
131      build() {
132        //配对弹框描述文字
133        Column({ space: 10 }) {
134          Text($r('app.string.match_request'))
135            .fontSize(30)
136            .alignSelf(ItemAlign.Start)
137          Text(this.titleText)
138            .alignSelf(ItemAlign.Start)
139            .margin({ top: 20 })
140            .fontSize(21)
141          Text(this.pinCode)
142            .fontSize(40)
143            .fontWeight(FontWeight.Bold)
144            .margin({ top: 20 })
145          Flex({ direction: FlexDirection.Row, alignItems: ItemAlign.Center }) {
146            Checkbox({ name: 'checkbox' })
147              .select(false)
148              .selectedColor('#ff3d6fb8')
149              .key('checkBox')
150            Text($r('app.string.grant_permission'))
151              .fontSize(15)
152              .margin({ left: 3, top: 6 })
153          }
154          .alignSelf(ItemAlign.Start)
155          .width('95%')
156          .margin({ top: 5 })
157
158          Row() {
159            //配对选择UI,取消or配对
160            this.choiceText($r('app.string.cancel'), () => {
161              bluetooth.setDevicePairingConfirmation(this.data.deviceId, false)
162              logger.info(TAG, `setDevicePairingConfirmation = ${bluetooth.setDevicePairingConfirmation(this.data.deviceId, false)}`)
163              this.controller.close()
164            })
165
166            Divider()
167              .vertical(true)
168              .height(32)
169
170            this.choiceText($r('app.string.match'), () => {
171              bluetooth.setDevicePairingConfirmation(this.data.deviceId, true)
172              logger.info(TAG, `setDevicePairingConfirmation = ${bluetooth.setDevicePairingConfirmation(this.data.deviceId, true)}`)
173              this.controller.close()
174            })
175          }
176          .margin({ top: 20 })
177        }
178        .width('100%')
179        .padding(15)
180      }
181    ...
182
183    //pages/index.ets
184    @Entry
185    @Component
186    struct Index {
187     build() {
188        Column() {
189          TitleBar({ handlerClickButton: this.handlerClickButton })
190          Scroll() {
191            Column() {
192              Row() {
193                //蓝牙开关
194                Column() {
195                  Text($r('app.string.bluetooth'))
196                    .fontSize(30)
197                    .margin({ top: 20 })
198                    .alignSelf(ItemAlign.Start)
199                  if (true === this.isOn) {
200                    Text($r('app.string.discovery'))
201                      .fontSize(20)
202                      .alignSelf(ItemAlign.Start)
203                  }
204                }
205
206                Blank()
207
208                Column() {
209                  Toggle({ type: ToggleType.Switch, isOn: this.isOn })
210                    .selectedColor('#ff2982ea')
211                    .onChange((isOn: boolean) => {
212                      if (isOn) {
213                        this.isOn = true
214                        this.initBluetooth()
215                      } else {
216                        this.isOn = false
217                        bluetooth.disableBluetooth()
218                        this.deviceList = []
219                        this.discoveryList = []
220                      }
221                    })
222                }
223                .id('toggleBtn')
224              }
225              .width('90%')
226
227              if (this.isOn) {
228                Divider()
229                  .vertical(false)
230                  .strokeWidth(10)
231                  .color('#ffece7e7')
232                  .lineCap(LineCapStyle.Butt)
233                  .margin('1%')
234                //已配对的设备
235                Text($r('app.string.paired_device'))
236                  .fontSize(25)
237                  .fontColor('#ff565555')
238                  .margin({ left: '5%' })
239                  .alignSelf(ItemAlign.Start)
240
241                ForEach(this.deviceList, (item, index) => {
242                  Row() {
243                    Text(item)
244                      .fontSize(20)
245                  }
246                  .alignSelf(ItemAlign.Start)
247                  .width('100%')
248                  .height(50)
249                  .margin({ left: '5%', top: '1%' })
250                  .id(`pairedDevice${index}`)
251                  .onClick(() => {
252                    AlertDialog.show({
253                      //取消配对
254                      title: $r('app.string.disconnect'),
255                      message: '此操作将会断开您与以下设备的连接:' + item,
256                      primaryButton: {
257                        value: $r('app.string.cancel'),
258                        action: () => {
259                        }
260                      },
261                      //确认取消
262                      secondaryButton: {
263                        value: $r('app.string.confirm'),
264                        action: () => {
265                          try {
266                            bluetooth.cancelPairedDevice(item);
267                            this.deviceList = bluetooth.getPairedDevices()
268                            this.discoveryList = []
269                            bluetooth.startBluetoothDiscovery()
270                          } catch (err) {
271                            console.error("errCode:" + err.code + ",errMessage:" + err.message);
272                          }
273                        }
274                      }
275                    })
276                  })
277                })
278
279                Divider()
280                  .vertical(false)
281                  .strokeWidth(10)
282                  .color('#ffece7e7')
283                  .lineCap(LineCapStyle.Butt)
284                  .margin('1%')
285
286                Text($r('app.string.available_device'))
287                  .fontSize(25)
288                  .fontColor('#ff565555')
289                  .margin({ left: '5%', bottom: '2%' })
290                  .alignSelf(ItemAlign.Start)
291                //可用设备列表
292                ForEach(this.discoveryList, (item) => {
293                  Row() {
294                    Text(item)
295                      .fontSize(20)
296                  }
297                  .alignSelf(ItemAlign.Start)
298                  .width('100%')
299                  .height(50)
300                  .margin({ left: '5%', top: '1%' })
301                  //进行配对操作点击
302                  .onClick(() => {
303                    logger.info(TAG, `start bluetooth.pairDevice,item = ${item}`)
304                    let pairStatus = bluetooth.pairDevice(item)
305                    logger.info(TAG, `pairStatus = ${pairStatus}`)
306                  })
307
308                  Divider()
309                    .vertical(false)
310                    .color('#ffece7e7')
311                    .lineCap(LineCapStyle.Butt)
312                    .margin('1%')
313                })
314              }
315            }
316          }
317          .constraintSize({ maxHeight: '85%' })
318        }
319      }
320    }
321    ```
322
3233. 蓝牙开关打开关闭操作,需要打开蓝牙开关才能进行后续操作:
324    ```ts
325    initBluetooth() {
326        bluetooth.on('stateChange', (data) => {
327          logger.info(TAG, `enter on stateChange`)
328          //判断蓝牙开关状态
329          if (data === bluetooth.BluetoothState.STATE_ON) {
330            logger.info(TAG, `enter BluetoothState.STATE_ON`)
331            //蓝牙打开后的相关操作
332            this.foundDevices()
333          }
334          if (data === bluetooth.BluetoothState.STATE_OFF) {
335            logger.info(TAG, `enter BluetoothState.STATE_OFF`)
336            bluetooth.off('bluetoothDeviceFind', (data) => {
337              logger.info(TAG, `offBluetoothDeviceFindData = ${JSON.stringify(data)}`)
338            })
339            //关闭
340            bluetooth.stopBluetoothDiscovery()
341            this.discoveryList = []
342          }
343          logger.info(TAG, `BluetoothState = ${JSON.stringify(data)}`)
344        })
345        //开启蓝牙
346        bluetooth.enableBluetooth()
347      }
348    ```
349
3504. 设置蓝牙扫描模式并开启扫描去发现设备,并订阅蓝牙设备发现上报时间获取设备列表
351    ```ts
352     foundDevices() {
353        //订阅蓝牙设备发现上报事件
354        bluetooth.on('bluetoothDeviceFind', (data) => {
355          logger.info(TAG, `enter on bluetoothDeviceFind`)
356          if (data !== null && data.length > 0) {
357            if (this.discoveryList.indexOf(data[0]) === -1 && this.deviceList.indexOf(data[0]) === -1) {
358              this.discoveryList.push(data[0])
359            }
360            logger.info(TAG, `discoveryList = ${JSON.stringify(this.discoveryList)}`)
361          }
362          let list = bluetooth.getPairedDevices()
363          if (list !== null && list.length > 0) {
364            this.deviceList = list
365            logger.info(TAG, `deviceList =  ${JSON.stringify(this.deviceList)}`)
366          }
367        })
368        //开启蓝牙扫描,可以发现远端设备
369        bluetooth.startBluetoothDiscovery()
370        //设置蓝牙扫描模式,可以被远端设备发现
371        bluetooth.setBluetoothScanMode(bluetooth.ScanMode.SCAN_MODE_CONNECTABLE_GENERAL_DISCOVERABLE, TIME)
372      }
373    ```
374
3755. 设置蓝牙扫描模式并开启扫描去发现设备,并订阅蓝牙设备发现上报时间获取设备列表
376
377   ```ts
378   //配对确定和取消代码在PinDialog.ets文件中
379   //setDevicePairingConfirmation(device: string, accept: boolean): void
380   //device	string	表示远端设备地址,例如:"XX:XX:XX:XX:XX:XX
381   //accept	boolean	接受配对请求设置为true,否则设置为false
382
383   //订阅蓝牙配对状态改变事件,根据蓝牙状态更新设备列表
384   bluetooth.on('bondStateChange', (data) => {
385       logger.info(TAG, `enter bondStateChange`)
386       logger.info(TAG, `data = ${JSON.stringify(data)}`)
387       if (data.state === bluetooth.BondState.BOND_STATE_BONDED) {
388           logger.info(TAG, `BOND_STATE_BONDED`)
389           let index = this.discoveryList.indexOf(data.deviceId)
390           this.discoveryList.splice(index, 1)
391           this.deviceList = bluetooth.getPairedDevices()
392       }
393       if (data.state === bluetooth.BondState.BOND_STATE_INVALID) {
394           logger.info(TAG, `BOND_STATE_INVALID`)
395           this.deviceList = bluetooth.getPairedDevices()
396       }
397       logger.info(TAG, `bondStateChange,data = ${JSON.stringify(data)}`)
398   })
399   ```
400
401402
403## 完整代码
404
405本例完整代码sample示例链接:[蓝牙Sample](https://gitee.com/openharmony/applications_app_samples/tree/master/code/SystemFeature/Connectivity/Bluetooth)
406
407
408
409## 参考
410- [权限申请指导](../application-dev/security/AccessToken/declare-permissions.md)
411- [@ohos.bluetooth](../application-dev/reference/apis-connectivity-kit/js-apis-bluetooth.md)
412
413