BLE蓝牙开发流程

219 阅读6分钟

最近项目中需要使用到BLE通信,用来实现车机端和另外一个设备的信息交互(称为从设备)。在做了一番了解之后发现,Android对于BLE的封装已经很到位了,使得BLE的功能相对于整个Android蓝牙SDK来说就是一个Profile,使用起来已经很方便。

一、BLE基本概念:
1.概述

BLE全称为Bluetooth Low Energy,低功耗蓝牙技术,用以解决传统蓝牙(BR)功耗过高的问题。其中定义了两个角色,一个是主设备(Master),如手机、车机等处理能力较强的设备;一个是从设备(Slave),通常是智能手环、心跳仪等外围设备。一个主设备可以同时连接多个从设备,而一个从设备也可以同时连接多个主设备。

2.广播

在主设备跟从设备建立连接之前,从设备需要每隔一段时间就发送一条广播信号,实际上是在37(2402MHz) /38(2426MHz) /39(2480MHz)这三个信道上发送相同的广播,广播间隔(Advertising interval )在20ms ~ 10.28s之间。一条广播信息中包含设备地址(device address)、设备名、数据类型、自定义数据字段等,长度不能超过31个字节。
当主设备打开扫描功能之后,会在三个信道上分时开启接收窗口,一旦在某个信道上的接收到广播数据,那么就可以扫描到从设备了。

3.连接

由于广播携带的数据有限,如果两个设备间需要交互数据,那么就需要建立连接。所谓建立连接,其实就是两个设备之间达成收发数据的物理信道一致、时间原点一致、基于时间原点的收发时间间隔一致,从而实现一对一的通信。
当主设备端收到从设备的广播之后,就可以对从设备发起连接请求。从设备在每次广播之后,会开启一段时间的信息接收窗口,这个时候从设备收到连接请求,就会给主设备反馈连接成功的信号。后面两个设备就会定时发送数据和接收数据,完成数据的交互。
当然,以上是链路层的逻辑,而上层协议栈中,实际上是建立了GATT协议的连接。

4.GATT协议

GATT协议中定义了两个角色,一个是Service,一个是Characteristic,每个Service可以包含多个Characteristic,且他们都有特定的UUID,类似于 0000ff00-0000-1000-8000-00805f9b34fb 这样的字符串。每个Service代表提供某种服务的能力,比如跟心率有关的Service;Characteristic代表的是一个键值对,Service就是通过这个一个个的键值对达到传输数据的目的。他们的关系如下图所示:

当主设备和从设备连接上GATT协议之后,就可以询问从设备可以提供哪些服务,在得到从设备的反馈报文后,就可以通过双方协商好的UUID获取到Service服务,然后再从Service中根据UUID获取到可读的Characteristic和可写的Characteristic,操作Characteristic就可以实现具体的数据通信。

二、Android中的BLE
1.扫描设备

连接BLE设备,首先需要扫描。Android SDK中使用BluetoothLeScanner对象来执行扫描的动作:

private BluetoothLeScanner bluetoothLeScanner;

bluetoothLeScanner = bluetoothAdapter.getBluetoothLeScanner();

bluetoothLeScanner.startScan(scanCallback);

由于扫描是一个异步的过程,所以这里需要传入一个回调接口,我们在回调接口去获取扫描的结果:

public ScanCallback scanCallback = new ScanCallback() {
        @Override
        public void onScanResult(int callbackType, ScanResult result) {
            super.onScanResult(callbackType, result);
            ScanRecord record = result.getScanRecord();
            BluetoothDevice device = result.getDevice();
            String deviceName = record.getDeviceName();
            Log.d(TAG, "record name:" + deviceName);
            Log.d(TAG, "ServiceUuids:" + record.getServiceUuids());

            if (TextUtils.isEmpty(deviceName)) {
                return;
            }

            if (deviceName.startsWith("XX")) {    //这里我们可以找出设备名以XX开头的BLE设备
              
                byte[] bytes = record.getBytes();    //这里可以获取整个广播的完整数据,包括协议头等
                for (int i = 0; i < bytes.length; i++) {
                    Log.d(TAG, "[" + i + "]:" + bytes[i]);
                }
                
                bluetoothLeScanner.stopScan(scanCallback);     //需要停止扫描
                              
            }
        }

    };

2.连接设备

在找到了“XX”名称的设备后,我们就可以发起GATT协议的连接了:

device.connectGatt(context, false, gattCallback);

第二个参数,如果是false代表仅发起本次连接,如果连接不上则会反馈连接失败;如果是true则表示只要这个远程的设备可用,那么底层协议栈就会自动去连接,并且第一次连接不上,也会继续去连接。

第三个参数,是一个关于GATT协议相关的回调接口,主要有GATT连接状态的回调、发现Service服务的回调、特征值(Characteristic)发生改变的回调、最大传输单元(MTU)改变的回调、物理层发送模式(PHY)改变回调等,如下:

BluetoothGattCallback gattCallback = new BluetoothGattCallback() {

        @Override
        public void onPhyUpdate(BluetoothGatt gatt, int txPhy, int rxPhy, int status) {
            super.onPhyRead(gatt, txPhy, rxPhy, status);
            Log.d(TAG, "onPhyUpdate txPhy:" + txPhy + "; rxPhy:" + rxPhy);
        }

        @Override
        public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
            super.onMtuChanged(gatt, mtu, status);
            Log.d(TAG, "onMtuChanged mtu:" + mtu + "; status:" + status);
        }

        @Override
        public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
            super.onConnectionStateChange(gatt, status, newState);
            Log.d(TAG, "onConnectionStateChange newState:" + newState);
            if (newState == BluetoothProfile.STATE_CONNECTED) {  //协议连接成功
                Log.d(TAG, "STATE_CONNECTED");
                bluetoothGatt = gatt;             
                bluetoothGatt.discoverServices();   //发现service服务
            } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {         //协议连接失败
                Log.d(TAG, "STATE_DISCONNECTED");
            }

        }
}

3.获取服务和特征(Characteristic)

在GATT协议连接成功之后,就可以去发现从设备端提供了哪些Service服务,如上代码。
这是一个异步的过程,待从设备反馈了自己提供的服务之后,Android框架层会通过BluetoothGattCallback回调通知,如下:

BluetoothGattCallback gattCallback = new BluetoothGattCallback() {
      @Override
        public void onServicesDiscovered(BluetoothGatt gatt, int status) {
            super.onServicesDiscovered(gatt, status);
            List<BluetoothGattService> services = gatt.getServices();
            for (BluetoothGattService service : services) {
                Log.d(TAG, "UUID:" + service.getUuid().toString());
            }
            
            //1.根据UUID获取到服务
            mGattService = gatt.getService(UUID.fromString("0000ff00-0000-1000-8000-00805f9b34fb"));
           
            if (mGattService == null) {
                Log.w(TAG, "GattService is null!");
            } else {
                Log.i(TAG, "connect GattService");
                if(writeCharacteristic == null){
                //2.获取一个特征(Characteristic),这是从设备定义好的,我通过这个Characteristic去写从设备感兴趣的值
                    writeCharacteristic = mGattService
                            .getCharacteristic(UUID.fromString("0000ff02-0000-1000-8000-00805f9b34fb"));
                }
                if(readCharacteristic == null){
                //3.获取一个主设备需要去读的特征(Characteristic),获取从设备发送过来的数据
                    readCharacteristic = mGattService
                            .getCharacteristic(UUID.fromString("0000ff01-0000-1000-8000-00805f9b34fb"));

                 //4.注册特征(Characteristic)值改变的监听
                    bluetoothGatt.setCharacteristicNotification(readCharacteristic, true);
                    List<BluetoothGattDescriptor> descriptors = readCharacteristic.getDescriptors();
                    for (BluetoothGattDescriptor descriptor : descriptors) {
                        descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
                        bluetoothGatt.writeDescriptor(descriptor);
                    }
                }             

        }
}

经过上述代码中的四个步骤,两个设备间已经可以发送和接收数据了。

4.通过特征(Characteristic)发送数据

把需要发送的数据设置到writeCharacteristic,然后再调用BluetoothGatt的写入方法,即可完成数据的发送:

writeCharacteristic.setValue(datas);

bluetoothGatt.writeCharacteristic(writeCharacteristic);

5.读取数据

当从设备有数据发送到主设备之后,Android系统会回调BluetoothGattCallback的onCharacteristicChanged方法通知:

@Override
        public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
            super.onCharacteristicChanged(gatt, characteristic);
            UUID uuid = characteristic.getUuid();
            byte[] receiveData = characteristic.getValue();
            for (byte b : receiveData) {
                Log.d(TAG, "receiveData:" + Integer.toHexString(b));
            }
        }

三、注意事项

1.自动连接属性

connectGatt方法的自动连接参数设置为true之后,连接建立了,这个时候如果是断开连接,如下:

bluetoothGatt.disconnect();

虽然在Android层面的BluetoothGattCallback接口会立刻反馈一个STATE_DISCONNECTED信号值,但是在数据链路层却还是处于连接的状态,连接并没有断开。

2.开启定位功能

现在Android最新的版本,需要开启定位才能使用BLE功能。
判断定位功能是否开启:

private boolean isLocationEnable(Context context) {
        LocationManager locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
        boolean networkProvider = locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER);
        boolean gpsProvider = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
        if (networkProvider || gpsProvider) {
            return true;
        }
        return false;
    }

开启定位功能的方法:

LocationManager locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
            try {
                Field field = UserHandle.class.getDeclaredField("SYSTEM");
                field.setAccessible(true);
                UserHandle userHandle = (UserHandle) field.get(UserHandle.class);
                Method method = LocationManager.class.getDeclaredMethod(
                        "setLocationEnabledForUser",
                        boolean.class,
                        UserHandle.class);
                method.invoke(locationManager, true, userHandle);
            } catch (Exception e) {
            }

3.最大传输单元(MTU)的设置

Android默认的最大传输单元(MTU)是23个字节,除去报文头占用的3个字节,实际最大只能传递20个字节。当两个设备之间传递的数据长度超过20字节的时候,数据就会被截断,导致通信异常。
只有在GATT协议连接成功之后,才可以设置MTU值,最大MTU=512,如下:

bluetoothGatt.requestMtu(128);

4.从设备广播间隔影响连接

当Android协议栈(Host)给蓝牙芯片Chip发送一个连接的指令,芯片在收到之后,会在一定的时间内去接收从设备的广播,在收到广播之后才会发送连接请求给从设备;如果从设备的广播间隔设置不合理,就会导致芯片无法在限定的时间内收到广播,导致无法发送连接请求。BLE连接过程如下图:

2941a569c127253ca4933ace56b1a909_1674995829746-067fa99a-fa3b-411c-8255-c2f2d4d19de1_x-oss-process=image%2Fresize%2Cw_750%2Climit_0.png