Android低功耗蓝牙(BLE)随笔(二)

作者: CrazyJesse | 来源:发表于2017-08-28 00:49 被阅读189次

    Android中的实现

    1. 扫描广播包

    • 首先获取 BluetoothManagerBluetoothAdapter
      private BluetoothAdapter mBluetoothAdapter;
      BluetoothManager bluetoothManager = (BluetoothManager)   context.getSystemService(Context.BLUETOOTH_SERVICE);
      mBluetoothAdapter= bluetoothManager.getAdapter();
    
    • 开始扫描
        /**
         * 开始扫描,扫描结果在 BluetoothAdapter.LeScanCallback 中返回
         * @param deviceName 扫描的设备名
         */
        public void startScan(String deviceName) {
            if (mBluetoothAdapter == null) {
                // error
                return;
            } 
            BluetoothLeScanner scanner = mBluetoothAdapter.getBluetoothLeScanner();
            this.mScanning = true;
            List<ScanFilter> filters = new ArrayList<>();
            ScanFilter filter = new ScanFilter.Builder()
                    .setDeviceName(deviceName)
                    .build();
            filters.add(filter);
            scanner.startScan(filters, new ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build(), mScanCallback);
        }
    

    这里的扫描设置了过滤的参数,只返回设备名为传入参数的广播。如果要扫描特定的设备,也可以根据设备的Mac地址过滤。或者也可以用不过滤方式扫描,过滤逻辑可以在扫描结果中处理。

        private ScanCallback mScanCallback = new ScanCallback() {
            @Override
            public void onScanResult(int callbackType, ScanResult result) {
                // 广播的信息,可以在result中获取
                Log.e(TAG, "onScanResult: name: " + result.getDevice().getName() +
                                    ", address: " + result.getDevice().getAddress() +
                                    ", rssi: " + result.getRssi() + ", scanRecord: " + result.getScanRecord());
            }
        };
    

    开启蓝牙扫描会增加手机功耗,Android官方也不建议一直进行扫描,因此无论有没有扫描到目标设备,都应该设置一个超时时间,避免长时间扫描。停止扫描指令如下:

        public void stopScan() {
            BluetoothLeScanner scanner = mBluetoothAdapter.getBluetoothLeScanner();
            scanner.stopScan(mScanCallback);
        }
    

    2. 建立蓝牙连接

    如果发现需要进行蓝牙连接的设备,就可以发起建立蓝牙连接请求:

        /**
         * 建立蓝牙连接
         * @param address 设备的Mac地址
         * @result 连接请求是否成功
         */
        public boolean connect(Context context, String address) {
            if (mBluetoothAdapter == null || address == null) {
                Log.w(TAG, "BluetoothAdapter not initialized or unspecified address.");
                error();
                return false;
            }
            Log.i(TAG, "connecting ");
            // Previously connected device.  Try to reconnect.
            if (mBluetoothDeviceAddress != null && address.equals(mBluetoothDeviceAddress)
                    && mBluetoothGatt != null) {
                Log.i(TAG, "尝试使用已有的GATT连接");
                if (mBluetoothGatt.connect()) {
                    return true;
                } else {
                    return false;
                }
            }
    
            final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);
            if (device == null) {
                Log.i(TAG, "Device not found.  Unable to connect.");
                return false;
            }
            // 连接的核心代码
            mBluetoothGatt = device.connectGatt(context, false, mGattCallback);
            Log.d(TAG, "Trying to create a new connection.");
            return true;
        }
    

    连接是否成功可以在回调函数中获取:

    private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
            @Override
            public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
                if (newState == BluetoothProfile.STATE_CONNECTED) {
                    mConnectionState = STATE_CONNECTED;
                    Log.i(TAG, "Connected to GATT server.");
                    // 连接成功
                    ...
                } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
                    mConnectionState = STATE_DISCONNECTED;
                    // 断开连接
                    ...
                } else {
                    // 其他错误情况
                }
            }
        };
    

    onConnectionStateChange方法在连接状态改变后被调用,status是之前状态,newState是改变后的状态。类BluetoothGattCallback 除了得到连接状态的变化,还可以得到其他信息。稍后会逐步介绍。

    3. 发现服务

    如果蓝牙连接成功,就可以通过GATT协议进行通讯。前面介绍过,蓝牙的信息是通过特征值(characteristics)和服务(services)的传输。因此首先得获取设备的服务:

        public void discoverServices() {
            mBluetoothGatt.discoverServices();
        }
    

    发现服务的结果在BluetoothGattCallback的另一个方法中返回:

        private BleDeviceService  mBleDeviceService ;
        private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
            @Override
            public void onServicesDiscovered(BluetoothGatt gatt, int status) {
                if (status == BluetoothGatt.GATT_SUCCESS) {
                    //获取服务成功
                    //可用gatt.getServices()获取Service,并用BleDeviceService缓存起来,供访问使用。
                    mBleDeviceService = new BleDeviceService(gatt.getServices());
                } else {
                    //获取服务失败
                }
            }
        };
    

    其中BleDeviceService 用List缓存了相关的Service,并提供了用UUID查询的方法:

    public class BleDeviceService {
        private final List<BluetoothGattService> bluetoothGattServices;
    
        public BleDeviceService(List<BluetoothGattService> bluetoothGattServices) {
            this.bluetoothGattServices = bluetoothGattServices;
        }
    
        /**
         * 获取全部 BluetoothGattService
         */
        public List<BluetoothGattService> getBluetoothGattServices() {
            return bluetoothGattServices;
        }
    
        /**
         * 获取特定 BluetoothGattService
         * @param serviceUuid UUID service的标识
         * @return BluetoothGattService 查找不到返回null
         */
        public BluetoothGattService getService(@NonNull final UUID serviceUuid){
            for (BluetoothGattService bluetoothGattService : bluetoothGattServices) {
                if (bluetoothGattService.getUuid().equals(serviceUuid)){
                    return bluetoothGattService;
                }
            }
            return null;
        }
    
        /**
         * 获取特定特征值
         * @param characteristicUuid 特征值UUID
         * @return BluetoothGattCharacteristic 特征值,查找不到返回null
         */
        public BluetoothGattCharacteristic getCharacteristic(@NonNull UUID serviceUuid,@NonNull UUID characteristicUuid) {
            BluetoothGattService bluetoothGattService = getService(serviceUuid);
            if (bluetoothGattService != null){
                BluetoothGattCharacteristic characteristic = bluetoothGattService.getCharacteristic(characteristicUuid);
                if (characteristic != null){
                    return characteristic;
                }
            }
            return null;
        }
    }
    

    4. 读写特征值

    有了Service的信息,就可以进行读写特征值了,读特征值是获取设备的信息,写特征值是,发送信息给设备。
    读特征值:

    /**
         * 读特征值
         *
         * @param serviceUUID        服务 UUID
         * @param characteristicUUID 特征值 UUID
         */
        public void readCharacteristic(String serviceUUID, String characteristicUUID) {
            if (mBluetoothAdapter == null || mBluetoothGatt == null) {
                Log.w(TAG, "BluetoothAdapter not initialized");
                return;
            }
            boolean isError = false;
            if (mBleDeviceService != null) {
                BluetoothGattCharacteristic characteristic = mBleDeviceService.getCharacteristic(UUID.fromString(serviceUUID),
                        UUID.fromString(characteristicUUID));
                if (characteristic != null) {
                    int permission = characteristic.getProperties();
                    if ((permission & BleConstants.AT_BLE_PERMISSION_CHAR_READ)
                            == BleConstants.AT_BLE_PERMISSION_CHAR_READ) {
                        Log.i(TAG, "reading characteristic");
                        mBluetoothGatt.readCharacteristic(characteristic);
                    } else {
                        Log.i(TAG, "read permission denied");
                        isError = true;
                    }
                } else {
                    Log.i(TAG, "characteristic is null");
                    isError = true;
                }
            } else {
                Log.i(TAG, "mBleDeviceService is null");
                isError = true;
            }
            if (isError) {
                // 处理错误
            }
        }
    

    写特征值:

        private void write(String serviceUUID, String characteristicUUID, byte[] data) {
            if (mBluetoothAdapter == null || mBluetoothGatt == null) {
                //错误
                return;
            }
            if (mBleDeviceService != null) {
                BluetoothGattCharacteristic characteristic = mBleDeviceService.getCharacteristic(UUID.fromString(serviceUUID),
                        UUID.fromString(characteristicUUID));
                if (characteristic != null) {
                    int permission = (byte) characteristic.getProperties();
                    if ((permission & BleConstants.AT_BLE_PERMISSION_CHAR_WRITE)
                            == BleConstants.AT_BLE_PERMISSION_CHAR_WRITE ||
                            (permission & BleConstants.AT_BLE_PERMISSION_CHAR_SIGNED_WRITE)
                                    == BleConstants.AT_BLE_PERMISSION_CHAR_SIGNED_WRITE ||
                            (permission & BleConstants.AT_BLE_PERMISSION_CHAR_WRITE_WITHOUT_RESPONSE)
                                    == BleConstants.AT_BLE_PERMISSION_CHAR_WRITE_WITHOUT_RESPONSE ||
                            (permission & BleConstants.AT_BLE_PERMISSION_CHAR_RELIABLE_WRITE)
                                    == BleConstants.AT_BLE_PERMISSION_CHAR_RELIABLE_WRITE) {
                     //可以设置特征值的类型,默认为有应答。
                    //characteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE);   
                        characteristic.setValue(data);
                        mBluetoothGatt.writeCharacteristic(characteristic);
                        Log.i(TAG, "writing characteristic done");
                    } else {
                        Log.i(TAG, "writing permission denied");
                        //error
                    }
                } else {
                    Log.i(TAG, "null characteristic");
                   //error
                }
            }
        }
    

    这里注释的这一行(characteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE);)需要说明一下,如果不设置,默认的写特征值类型是有应答的。有无应答体现在底层通信上,不会影响到写特征值的回调函数的调用。目前发现区别是:对某些蓝牙芯片,无应答的通讯速率会略快,而且对调大的最大MTU(Maximum Transmission Unit,传输单元),也会减少出错的几率。
    读写是否成功的结果同样也是在回调函数中获得:

    private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
            @Override
            public void onCharacteristicRead(BluetoothGatt gatt,
                                             BluetoothGattCharacteristic characteristic,
                                             int status) {
                if (status == BluetoothGatt.GATT_SUCCESS) {
                    Log.i(TAG, "read characteristic success");
                    // 通过characteristic.getValue()获取信息
                    ...
                } else {
                    Log.i(TAG, "read characteristic fail " + status);
                }
    
            }
    
            @Override
            public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
                super.onCharacteristicWrite(gatt, characteristic, status);
                if (status == BluetoothGatt.GATT_SUCCESS) {
                    Log.i(TAG, "characteristic write success ");
                    ...
    
                } else {
                    Log.i(TAG, "characteristic write fail " + status);
                    ...
    
                }
            }
        };
    

    5. 使能和接收通知

    读特征值的方式只能是手机去读取设备的信息,而不能设备主动发送信息过来。如果想要实现设备主动发送信息给手机,必须手机端先使能一个特征值,之后设备就可以通过这个特征值发送信息。
    使能的方法:

    /**
         * 使能通知
         *
         * @param serviceUUID        服务UUID
         * @param characteristicUUID 特征值UUID
         * @param notificationFlag   是否开启
         */
        public void toggleNotification(String serviceUUID, String characteristicUUID, boolean notificationFlag) {
            if ( mBluetoothAdapter == null || mBluetoothGatt == null) {
                Log.w(TAG, "BluetoothAdapter not initialized");
                 // error
                return;
            }
            if (mBleDeviceService != null) {
                BluetoothGattCharacteristic characteristic = mBleDeviceService.getCharacteristic(UUID.fromString(serviceUUID),
                        UUID.fromString(characteristicUUID));
                if (characteristic != null) {
                    int permission = (byte) characteristic.getProperties();
                    if ((permission & BluetoothGattCharacteristic.PROPERTY_NOTIFY) == BluetoothGattCharacteristic.PROPERTY_NOTIFY) {
                        UUID CCC = UUID.fromString(BleConstants.CONFIG_DESCRIPTOR);
                        mBluetoothGatt.setCharacteristicNotification(characteristic, notificationFlag); //Enabled locally
                        BluetoothGattDescriptor config = characteristic.getDescriptor(CCC);
                        config.setValue(notificationFlag ? BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE :
                                BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE);
                        mBluetoothGatt.writeDescriptor(config); //Enabled remotely
                    } else {
                        Log.i(TAG, "characteristic has no notification property");
                        // error
                    }
                } else {
                    Log.i(TAG, "null characteristic");
                     // error
                }
            }
        }
    

    获取使能是否成功的结果以及接收设备发送来的信息的回调方法:

    private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
            @Override
            public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
                super.onCharacteristicChanged(gatt, characteristic);
                Log.i(TAG, "onCharacteristicChanged: " + Arrays.toString(characteristic.getValue()) + " " +
                        characteristic.getUuid().toString());
                // 可以用characteristic.getValue()读取信息。
            }
    
            @Override
            public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
                super.onDescriptorWrite(gatt, descriptor, status);
                if (status == BluetoothGatt.GATT_SUCCESS) {
                    Log.i(TAG, "Notification/indication  enabled successfully");
                    ...
                } else {
                    Log.i(TAG, "Notification/indication enabled failed");
                    // error
                }
            }
    
        };
    

    代码中用的是Notification,还有另一个方法是关于Indication的(将toggleNotification方法内的BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE替换为BluetoothGattDescriptor.ENABLE_INDICATION_VALUE),二者的区别也是前者类似TCP后者类似UDP。

    5. 断开连接

    最后,就是通讯结束后断开连接的方法:

        public void disconnect() {
            if (mBluetoothAdapter == null || mBluetoothGatt == null) {
                Log.i(TAG, "BluetoothAdapter not initialized");
                return;
            }
            mBluetoothGatt.disconnect();
        }
    

    相关文章

      网友评论

        本文标题:Android低功耗蓝牙(BLE)随笔(二)

        本文链接:https://www.haomeiwen.com/subject/mpsqdxtx.html