Android蓝牙知识点专题首页投稿(暂停使用,暂停投稿)

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

2017-08-28  本文已影响189人  CrazyJesse

Android中的实现

1. 扫描广播包

  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();
    }
上一篇下一篇

猜你喜欢

热点阅读