蓝牙(Bluetooth)是一种无线通信技术,被广泛应用于各种电子设备之间的短距离数据传输和连接。而低功耗蓝牙(Bluetooth Low Energy,简称 BLE)是一种能够在低功耗情况下进行通信的蓝牙技术。与传统蓝牙相比,BLE的功耗更低,适用于需要长时间运行的低功耗设备,如智能手表、健康监测设备、智能家居等。
本文适用于低功耗蓝牙通信相关的开发,主要介绍了基于BLE进行蓝牙扫描管理、蓝牙连接状态管理、蓝牙设备特征值同步三个场景,并分别从服务端和客户端描述其相关实现。
实现低功耗蓝牙通信的能力主要步骤如下:
首先服务端需要创建实例,成功创建后,需要添加对应服务,才能开启BLE广播,向客户端传递相关信息。当服务端开启BLE广播后,还需要订阅BLE连接状态,即对连接状态进行监听。当连接状态变化时,如断开连接,则此时需要关闭BLE广播。在服务端接收到客户端发起的连接请求和发起的特征值订阅请求后,才能向客户端传递特征值信息。
而客户端同样需要创建实例,并获取相关服务,才能通过蓝牙扫描到服务端,并发起连接请求和特征值订阅请求。客户端也需要监听连接状态,当断开连接时,关闭对应的监听能力。
实现低功耗蓝牙通信能力需要使用@ohos.bluetooth.ble (蓝牙ble模块)中提供的API能力,服务端使用的API主要包括创建服务器实例、发送BLE广播、特征值变化通知等;客户端使用的API主要包括蓝牙连接、订阅蓝牙低功耗设备的特征值变化、订阅蓝牙低功耗设备的连接状态变化事件等。
在需要使用低功耗蓝牙进行通信的场景中:
服务端
客户端
BLE扫描主要分为被动扫描和主动扫描两种模式。被动扫描是指设备监听周围的广播信息,不主动发起连接请求;而主动扫描则会在接收到广播的同时,向广播设备发送扫描请求,以获取更详尽的设备信息。
蓝牙扫描管理是在主动扫描中,对扫描出的结果进行管理。主动扫描时,不仅可以设置扫描参数,还能处理扫描结果。如扫描时设置扫描时间间隔、扫描持续时间等;扫描出结果后,可对设备列表更新,并过滤相关设备信息。
蓝牙扫描步骤如下:
首先服务端需要检查蓝牙状态,如未开启蓝牙,则需要向用户申请开启蓝牙;如已开启,则开启广播。然后客户端也需要检查蓝牙状态,如未开启蓝牙,则需要向用户申请开启蓝牙;如已开启,则设置扫描参数,并启动蓝牙扫描。根据扫描结果来处理相关设备信息并展示,然后主动发起蓝牙连接请求,连接远端设备。
关键技术
服务端:
客户端:
服务端
import { ble } from '@kit.ConnectivityKit'; import { access } from '@kit.ConnectivityKit';
isBluetoothEnabled(): boolean { const state: access.BluetoothState = access.getState(); if (state === access.BluetoothState.STATE_ON || state === access.BluetoothState.STATE_TURNING_ON) { return true; } return false; }
access.enableBluetooth();
startAdvertiser(): boolean { if (!this.isBluetoothEnabled()) { this.enableBluetooth(); promptAction.showToast({ message: 'Bluetooth enabled, please wait...', duration: 2000 }) return false; } // Create a GattServer instance this.mGattServer = ble.createGattServer(); // ... // Define the heart rate beating service const service: ble.GattService = { serviceUuid: '0000180D-0000-1000-8000-00805F9B34FB', isPrimary: true, characteristics: characteristics, includeServices: [] } // Add a service this.mGattServer.addService(service); try { // The status of the subscription connection service this.onConnectStateChange(); // ... // Start advertising ble.startAdvertising(setting, advData, advResponse); return true; } catch (err) { Logger.error(`startAdvertiser: err = ${JSON.stringify(err)}`); } return false }
客户端
import { ble } from '@kit.ConnectivityKit';
startBLEScan(): boolean { if (!this.isBluetoothEnabled()) { this.enableBluetooth(); promptAction.showToast({ message: 'Bluetooth enabled, please wait...', duration: 2000 }) return false; } this.onBLEDeviceFind(); const ret = this.startBLEScanInner(); return ret; }
onBLEDeviceFind() { try { ble.on('BLEDeviceFind', (arr: Array<ble.ScanResult>) => { let deviceId: string = arr[0].deviceId; let deviceName: string = arr[0].deviceName; let device = this.mAvailableDevices.find((availableDevice => { return availableDevice.deviceId === deviceId; })) if (!device) { let data: ArrayBuffer = arr[0].data; deviceName = util.TextDecoder.create('utf-8', { ignoreBOM: true }).decodeToString(new Uint8Array(data), { stream: false }); this.mAvailableDevices = []; this.mAvailableDevices.push(this.getDevice(deviceId, deviceName)); AppStorage.setOrCreate('availableDevices', this.mAvailableDevices); } }) } catch (err) { Logger.info(`onBLEDeviceFind: err = ${JSON.stringify(err)}`); } }
connectInner(gattClientDevice: ble.GattClientDevice): boolean { try { if (!gattClientDevice) { return false; } // The subscription connection status changes this.onBLEConnectionStateChange(); // Subscription feature value changes this.onBLECharacteristicChange(); // Start connecting gattClientDevice.connect(); this.mConnectBluetoothDevice.connectionState = ConnectionState.STATE_CONNECTING; AppStorage.setOrCreate('connectBluetoothDevice', this.mConnectBluetoothDevice); return true; } catch (err) { Logger.error(`connectInner: err = ${JSON.stringify(err)}`); } return false; }
蓝牙连接状态管理可实时监测蓝牙连接的质量,包括信号强度、数据传输速率、误码率等指标。在蓝牙设备连接后,经常会遇到服务端、客户端连接状态改变的情况,例如客户端连接后,服务端关闭广播导致连接断开。针对此问题,在进行蓝牙设备开发的时候,需要对蓝牙的连接进行管理。
在服务端与客户端进行连接的相关操作时,注册对应的监听事件。如监听蓝牙设备开关状态、监听蓝牙状态变化等。当对应状态发生变化时,即可通过回调函数来处理相应逻辑。
关键技术
服务端:
1.调用on('stateChange')方法订阅蓝牙设备开关状态事件。
2.调用on('connectionStateChange')方法订阅蓝牙状态变化事件。
客户端:
1.订阅蓝牙设备开关状态事件,与服务端步骤1一致。
2.调用on('BLEConnectionStateChange')订阅蓝牙低功耗设备的连接状态变化事件。
服务端
import { ble } from '@kit.ConnectivityKit'; import { access } from '@kit.ConnectivityKit';
(1)根据蓝牙的状态BluetoothState进行处理,如果当前蓝牙开启,AppStorage存储当前蓝牙状态为true,表示蓝牙可用,并开启广播。
onBTStateChange() { try { access.on('stateChange', (data: access.BluetoothState) => { if (data === access.BluetoothState.STATE_ON) { AppStorage.setOrCreate('bluetoothEnable', true); } else if (data === access.BluetoothState.STATE_OFF) { AppStorage.setOrCreate('bluetoothEnable', false); } }) } catch (err) { Logger.error(`onBTSateChange: err = ${JSON.stringify(err)}`); } }
(1)根据连接状态设置连接设备ID。
onConnectStateChange() { if (!this.mGattServer) { return; } try { this.mGattServer.on('connectionStateChange', (data: ble.BLEConnectionChangeState) => { if (data) { if (data.state === constant.ProfileConnectionState.STATE_CONNECTED) { let deviceId = data.deviceId; AppStorage.setOrCreate('deviceId', deviceId); } else if (data.state === constant.ProfileConnectionState.STATE_DISCONNECTED) { AppStorage.setOrCreate('deviceId', ''); this.stopAdvertiser(); } } }) } catch (err) { Logger.error(`connectInner: err = ${JSON.stringify(err)}`); } }
客户端
import { ble } from '@kit.ConnectivityKit'; import { access } from '@kit.ConnectivityKit';
(1)根据蓝牙的状态BluetoothState进行处理,如果当前蓝牙开启,则发起BLE扫描流程。
onBTStateChange() { try { access.on('stateChange', (data: access.BluetoothState) => { if (data === access.BluetoothState.STATE_ON) { this.startBLEScan(); } else if (data === access.BluetoothState.STATE_OFF) { this.mAvailableDevices = []; AppStorage.setOrCreate('availableDevices', this.mAvailableDevices); } }) } catch (err) { Logger.error(`onBTSateChange: err = ${JSON.stringify(err)}`); } }
onBLEConnectionStateChange() { if (!this.mGattClientDevice) { return; } try { this.mGattClientDevice.on('BLEConnectionStateChange', async (data: ble.BLEConnectionChangeState) => { let state: constant.ProfileConnectionState = data.state; if (data) { if (state === constant.ProfileConnectionState.STATE_CONNECTED) { this.mConnectBluetoothDevice.connectionState = ConnectionState.STATE_CONNECTED; AppStorage.setOrCreate('availableDevices', this.mAvailableDevices); // The connection is successful, and the service is obtained let services: Array<ble.GattService> | undefined = await this.mGattClientDevice?.getServices(); let service: ble.GattService | undefined = services?.find(item => item.serviceUuid === '0000180D-0000-1000-8000-00805F9B34FB') let characteristics: Array<ble.BLECharacteristic> | undefined = service?.characteristics; let characteristic: ble.BLECharacteristic | undefined = characteristics?.find(item => item.characteristicUuid === '00002A37-0000-1000-8000-00805F9B34FB'); // Sends a setup notification request for this feature value to the server this.mGattClientDevice?.setCharacteristicChangeNotification(characteristic, true); let descriptors: Array<ble.BLEDescriptor> | undefined = characteristic?.descriptors; let descriptor = descriptors?.find(item => item.descriptorUuid === '00002902-0000-1000-8000-00805F9B34FB') let descriptorValue = ArrayBufferUtils.byteArray2ArrayBuffer([0x01, 0x00]); let descriptorObj: BLEDescriptor = { serviceUuid: descriptor?.serviceUuid, characteristicUuid: descriptor?.characteristicUuid, descriptorValue: descriptorValue, descriptorUuid: descriptor?.descriptorUuid } // Writes binary data to a Bluetooth Low energy device-specific descriptor this.mGattClientDevice?.writeDescriptorValue(descriptorObj); } else if (state === constant.ProfileConnectionState.STATE_DISCONNECTED) { this.mConnectBluetoothDevice.connectionState = ConnectionState.STATE_DISCONNECTED; AppStorage.setOrCreate('availableDevices', []); if (this.getConnectionState() === ConnectionState.STATE_CONNECTING) { this.close(); } else if (this.getConnectionState() === ConnectionState.STATE_CONNECTED || this.getConnectionState() === ConnectionState.STATE_DISCONNECTING) { this.close(); } } } }) } catch (err) { Logger.error(`onBLEConnectionStateChange: err = ${JSON.stringify(err)}`); } }
在低功耗蓝牙通信中,特征值是数据的基本单元,它是一种用于存储或传输特定类型数据的实体。许多蓝牙设备需要实时传输数据,如运动传感器将加速度、陀螺仪等数据传输到手机应用。如果特征值不同步,数据可能会出现延迟、丢失或者错误的顺序,导致接收端(如手机应用)无法准确地对运动状态进行分析,像在运动步数统计中,可能会少计或多计步数。
在服务端与客户端连接成功后,服务端通过调用相关接口,主动通知客户端特征值发生变化;而客户端需要调用接口向服务端来订阅特征值变化,当特征值变化时,客户端即可接收到相关变化信息。
关键技术
服务端:
1.调用notifyCharacteristicChanged方法主动通知已连接的客户端设备,特征值变化。
客户端:
1.调用setCharacteristicChangeNotification接口向服务端发送设置通知此特征值请求。
2.调用on('BLECharacteristicChange')订阅蓝牙低功耗设备的特征值变化事件。
(1)根据UUID对服务进行过滤,获取服务特征值配置。
(2)调用setCharacteristicChangeNotification,向服务端发送设置通知此特征值请求。
(3)调用writeDescriptorValue,向低功耗蓝牙设备特定的描述符写入二进制数据。
服务端
import { ble } from '@kit.ConnectivityKit';
notifyCharacteristicChanged(deviceId: string, heartRate: number) { if (!deviceId) { return; } if (this.mGattServer) { try { let descriptors: Array<ble.BLEDescriptor> = []; let arrayBuffer = ArrayBufferUtils.byteArray2ArrayBuffer([11]); // Define descriptors let descriptor: ble.BLEDescriptor = { serviceUuid: '0000180D-0000-1000-8000-00805F9B34FB', characteristicUuid: '00002A37-0000-1000-8000-00805F9B34FB', descriptorUuid: '00002902-0000-1000-8000-00805F9B34FB', descriptorValue: arrayBuffer } descriptors[0] = descriptor; let arrayBufferC = ArrayBufferUtils.byteArray2ArrayBuffer([0x00, heartRate]); // Define characteristic let characteristic: CharacteristicModel = { serviceUuid: '0000180D-0000-1000-8000-00805F9B34FB', characteristicUuid: '00002A37-0000-1000-8000-00805F9B34FB', characteristicValue: arrayBufferC, descriptors: descriptors } // Define notifyCharacteristic let notifyCharacteristic: NotifyCharacteristicModel = { serviceUuid: '0000180D-0000-1000-8000-00805F9B34FB', characteristicUuid: '00002A37-0000-1000-8000-00805F9B34FB', characteristicValue: characteristic.characteristicValue, confirm: false } // Notifies the connected client of the change of the feature value this.mGattServer.notifyCharacteristicChanged(deviceId, notifyCharacteristic, (err: BusinessError) => { if (err) { Logger.error(`notifyCharacteristicChanged callback failed: err = ${JSON.stringify(err)}`); } else { Logger.info('notifyCharacteristicChanged callback success') } }) } catch (err) { Logger.error(`notifyCharacteristicChanged: err = ${JSON.stringify(err)}`); } } }
客户端
import { ble } from '@kit.ConnectivityKit';
onBLEConnectionStateChange() { if (!this.mGattClientDevice) { return; } try { this.mGattClientDevice.on('BLEConnectionStateChange', async (data: ble.BLEConnectionChangeState) => { let state: constant.ProfileConnectionState = data.state; if (data) { if (state === constant.ProfileConnectionState.STATE_CONNECTED) { this.mConnectBluetoothDevice.connectionState = ConnectionState.STATE_CONNECTED; AppStorage.setOrCreate('availableDevices', this.mAvailableDevices); // The connection is successful, and the service is obtained let services: Array<ble.GattService> | undefined = await this.mGattClientDevice?.getServices(); let service: ble.GattService | undefined = services?.find(item => item.serviceUuid === '0000180D-0000-1000-8000-00805F9B34FB') let characteristics: Array<ble.BLECharacteristic> | undefined = service?.characteristics; let characteristic: ble.BLECharacteristic | undefined = characteristics?.find(item => item.characteristicUuid === '00002A37-0000-1000-8000-00805F9B34FB'); // Sends a setup notification request for this feature value to the server this.mGattClientDevice?.setCharacteristicChangeNotification(characteristic, true); let descriptors: Array<ble.BLEDescriptor> | undefined = characteristic?.descriptors; let descriptor = descriptors?.find(item => item.descriptorUuid === '00002902-0000-1000-8000-00805F9B34FB') let descriptorValue = ArrayBufferUtils.byteArray2ArrayBuffer([0x01, 0x00]); let descriptorObj: BLEDescriptor = { serviceUuid: descriptor?.serviceUuid, characteristicUuid: descriptor?.characteristicUuid, descriptorValue: descriptorValue, descriptorUuid: descriptor?.descriptorUuid } // Writes binary data to a Bluetooth Low energy device-specific descriptor this.mGattClientDevice?.writeDescriptorValue(descriptorObj); } else if (state === constant.ProfileConnectionState.STATE_DISCONNECTED) { this.mConnectBluetoothDevice.connectionState = ConnectionState.STATE_DISCONNECTED; AppStorage.setOrCreate('availableDevices', []); if (this.getConnectionState() === ConnectionState.STATE_CONNECTING) { this.close(); } else if (this.getConnectionState() === ConnectionState.STATE_CONNECTED || this.getConnectionState() === ConnectionState.STATE_DISCONNECTING) { this.close(); } } } }) } catch (err) { Logger.error(`onBLEConnectionStateChange: err = ${JSON.stringify(err)}`); } }
onBLECharacteristicChange() { if (!this.mGattClientDevice) { return; } this.mGattClientDevice.on('BLECharacteristicChange', (data: ble.BLECharacteristic) => { let characteristicValue: ArrayBuffer = data.characteristicValue; let byteArr = ArrayBufferUtils.arrayBuffer2ByteArray(characteristicValue); let hearRate = byteArr[1]; AppStorage.setOrCreate('heartRate', hearRate); }) }
通过on('BLEConnectionStateChange')接口订阅连接状态,连接状态订阅中返回状态值为2的时候,表示连接成功,之后再调用getServices()获取数据。
基于信息安全考虑,此处获取的设备地址为随机MAC地址。配对成功后,该地址不会变更;已配对设备取消配对后重新扫描或蓝牙服务下电时,该随机地址会变更。