Android BLE Gatt 连接状态改变

Android BLE Gatt connection change statuses

我有一个 android 应用程序可以连接到 BLE 设备并向其写入数据。我可以成功连接、读取和写入它。作为测试的一部分,我们正在尝试不同的断开连接场景。

有时,如果 BLE 设备断开连接,我将连接更改为断开连接,状态值为 19。此外,如果有任何绑定错误,状态等于 22。如果我以编程方式断开连接,此状态会给我0. 但是 none 这些状态中除了 0 在 android documentation.

中指定

发布示例 BluetoothGattCallback

private BluetoothGattCallback bluetoothGattCallback = new BluetoothGattCallback() {
    @Override
    public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
        Log.i(TAG, "onConnectionStateChange status: "+status+", newState: "+newState);
        /*i need to know the possible values for this status variable*/
        if(newState == BluetoothProfile.STATE_CONNECTED) {
            gatt.discoverServices();
        } else {
            gatt.close();
        }
    }

    @Override
    public void onServicesDiscovered(BluetoothGatt gatt, int status) {
        Log.i(TAG, "onServicesDiscovered service discovered");
    }
};

有没有人遇到同样的问题并整理出状态列表。我需要知道 onConnectionStateChange 方法中状态变量的可能值

这是我拥有的代码列表

  • 以编程方式断开连接 - 0
  • 设备超出范围 - 8
  • 已被设备断开连接 - 19
  • 债券发行 - 22
  • 未找到设备 - 133(有些 phone 它给出 62)

我已经在 5.0.2、5.1、6.0 和 6.0.1 中测试了断开连接的情况。但是在6.0.1android版本才找到这个债券发行代码。

很抱歉提出一个老问题,但这里是我在使用蓝牙 (BLE) 4.0 时遇到的许多问题的解决方案。再次对下面的大 类 感到抱歉,但请确保它们是必需的,并且没有不相关或未使用的方法。

public abstract class AbstractBluetoothBroadcaster extends BroadcastReceiver {
    protected static final String LOG_TAG = BluetoothLowEnergy.LOG_TAG;

    protected BluetoothLowEnergy bluetoothLowEnergy;

    public AbstractBluetoothBroadcaster(BluetoothLowEnergy bluetoothLowEnergy, String action){
        super();
        this.bluetoothLowEnergy = bluetoothLowEnergy;

        IntentFilter intentFilterStateChange = new IntentFilter(action);
        intentFilterStateChange.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
        this.bluetoothLowEnergy.getActivity().registerReceiver(this, intentFilterStateChange);
    }

    public void onDestroy(){
        this.bluetoothLowEnergy.getActivity().unregisterReceiver(this);
    }
}


public class BluetoothBondStateBroadcaster extends AbstractBluetoothBroadcaster {

    private BluetoothLowEnergy bluetoothLowEnergy;
    private boolean deviceBonded;

    public BluetoothBondStateBroadcaster(BluetoothLowEnergy bluetoothLowEnergy) {
        super(bluetoothLowEnergy, BluetoothDevice.ACTION_BOND_STATE_CHANGED);
        this.bluetoothLowEnergy = bluetoothLowEnergy;
        this.deviceBonded = false;
    }

    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        if (action == null){
            return;
        }
        BluetoothDevice bluetoothDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
        if (action.equals(BluetoothDevice.ACTION_BOND_STATE_CHANGED) &&
                bluetoothDevice != null &&
                bluetoothDevice.getAddress().equals(bluetoothLowEnergy.getDeviceUUID())) {
            int state = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, -1);
            switch (state) {
                case BluetoothDevice.BOND_NONE:
                    Log.d(LOG_TAG, "  NOT BONDED - dev " + bluetoothDevice.getAddress());
                    this.deviceBonded = false;
                    break;
                case BluetoothDevice.BOND_BONDING:
                    Log.d(LOG_TAG, " BONDING ... - dev " + bluetoothDevice.getAddress());
                    break;
                case BluetoothDevice.BOND_BONDED:
                    Log.d(LOG_TAG, " BONDED - dev " + bluetoothDevice.getAddress());
                    deviceBonded = true;
                    bluetoothLowEnergy.onBluetoothBonded();
                    break;
                default:
                    break;
            }
        }
    }

    public void resetDeviceBonded(){
        this.deviceBonded = false;
    }

    public boolean isDeviceBonded() {
        return deviceBonded;
    }
}


public class BluetoothPairingBroadcaster extends AbstractBluetoothBroadcaster {

    private String devicePIN;

    public BluetoothPairingBroadcaster(BluetoothLowEnergy bluetoothLowEnergy){
        super(bluetoothLowEnergy, BluetoothDevice.ACTION_PAIRING_REQUEST);
        this.devicePIN = "";
    }

    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        if (action == null){
            return;
        }
        BluetoothDevice bluetoothDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
        int pairingType = intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, BluetoothDevice.ERROR);
        if (action.equals(BluetoothDevice.ACTION_PAIRING_REQUEST) &&
                bluetoothDevice != null &&
                bluetoothDevice.getAddress().equals(bluetoothLowEnergy.getDeviceUUID()) &&
                !getDevicePIN().isEmpty()) {
            if (pairingType == BluetoothDevice.PAIRING_VARIANT_PIN){
                bluetoothDevice.setPin(getDevicePIN().getBytes());
                Log.d(LOG_TAG," Auto-entering pin - " + getDevicePIN());
                bluetoothDevice.createBond();
                Log.d(LOG_TAG," pin entered and request sent...");
                abortBroadcast();
            }
        }
    }

    public void setDevicePIN(String pin){
        this.devicePIN = pin;
    }

    public String getDevicePIN(){
        return this.devicePIN ;
    }
}


public class BluetoothLowEnergy extends BluetoothGattCallback {

    // listener that has the methods that the application (activity)
    // will use to send / receive data, or to reflect the system state 
    // in the UI
    public interface BluetoothListener {
        /**
         * Triggered when the scanning has started successfully
         */
        void onBluetoothStartScan();

        /**
         * Triggered when the scanning stops
         * @param scanResults results of the scanning
         */
        void onBluetoothStopScan(Collection<BluetoothDevice> scanResults);

        /**
         * Triggered when the device is ready to send/receive data
         */
        void onBluetoothConnectionReady();

        /**
         * Triggered when a bluetooth msg is received
         * @param msg message received
         */
        void onBluetoothReceiveMsg(String msg);

        /**
         * Triggered whenever data is send
         * @param success true means data was sent fine to the remote device, false otherwise
         */
        void onBluetoothSend(String data, boolean success);

        /**
         * Triggered if no bluetooth is connected, and we need a connection
         * to send / receive / discover services
         */
        void onBluetoothNotConnected();

    }

    // custom exceptions
    public class BluetoothNotEnabledException extends Exception { }
    public class BluetoothLowEnergyNotSupported extends Exception { }
    public class BluetoothDeviceNotFound extends Exception { }

    // service and characteristic uuids that are going to be used to
    // send / receive data between central and peripheral GATTs
    private static final String SERVICE_UUID = "FFE0-";
    private static final String CHARACTERISTIC_UUID = "FFE1-";

    // timeout for bluetooth scan (in ms)
    public static final int SCAN_TIMEOUT = 5000;

    // BLE LOG TAG
    public static final String LOG_TAG = "BLUETOOTH_BLE";

    // model
    private boolean bluetoothScanning;
    private boolean bluetoothConnected;
    private Map<String, BluetoothDevice> bluetoothScanResults;

    // gui
    private Activity activity;

    // bluetooth
    private BluetoothAdapter bluetoothAdapter;
    private BluetoothLeScanner bluetoothLeScanner;
    private ScanCallback bluetoothScanCallback;
    private BluetoothGatt bluetoothGatt;
    private BluetoothGattCharacteristic characteristic;


    public BluetoothLowEnergy(Activity activity, BluetoothListener bluetoothListener){
        this.activity = activity;
        this.bluetoothListener = bluetoothListener;

        // this keeps track of the scanning and connection states
        this.bluetoothScanning = this.bluetoothConnected = false;

        // keeps track of the scanning results
        this.bluetoothScanResults = new HashMap<>();

        // set bluetooth pairing request and bonded callback
        // these broadcasters will be responsible to detect and validate
        // the bonded state of your device
        this.pairingRequestBroadcaster = new BluetoothPairingBroadcaster(this);
        this.bondedBroadcaster = new BluetoothBondStateBroadcaster(this);

        // set the scan callback methods that will add results to 
        // this.bluetoothScanResults map
        this.bluetoothScanCallback = new ScanCallback() {
            @Override
            public void onScanResult(int callbackType, ScanResult result) {
                super.onScanResult(callbackType, result);
                addScanResult(result);
            }

            @Override
            public void onBatchScanResults(List<ScanResult> results) {
                super.onBatchScanResults(results);
                for (ScanResult result: results) {
                    addScanResult(result);
                }
            }

            @Override
            public void onScanFailed(int errorCode) {
                super.onScanFailed(errorCode);
                Log.e(LOG_TAG, "Scan Failed with code " + errorCode);
            }

            private void addScanResult(ScanResult result) {
                BluetoothDevice device = result.getDevice();
                String deviceAddress = device.getAddress();
                bluetoothScanResults.put(deviceAddress, device);
                Log.d(LOG_TAG, "Found device " + deviceAddress);
            }
        };

        // Use this to determine whether BLE is supported on the device.
        if (!this.activity.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
            throw new BluetoothLowEnergyNotSupported();
        }
    }

    /**
     * This method should be called when the activity is destroyed
     */
    public void onDestroy(){
        this.bondedBroadcaster.onDestroy();
        this.pairingRequestBroadcaster.onDestroy();
        this.disconnect();
    }

    /**
     * This method is called when we finish pairing/bonding to the device
     */
    public void onBluetoothBonded(){
        // if we have the services already discovered, then we can 
        // send/receive data, to do so we call the bluetooth listener below
        if (servicesDiscovered){
            this.bluetoothListener.onBluetoothConnectionReady();
        // if we know we have a connection established, then we can 
        // discover services
        } else if (bluetoothConnected){
            bluetoothGatt.discoverServices();
        }
    }

    /**
     * This method is called whenever a connection is established or a disconnection happens
     */
    @Override        
    public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
        super.onConnectionStateChange(gatt, status, newState);

        BluetoothDevice bluetoothDevice = gatt.getDevice();

        // if these conditions == true, then we have a disconnect
        if ( status == BluetoothGatt.GATT_FAILURE ||
                status != BluetoothGatt.GATT_SUCCESS ||
                newState == BluetoothProfile.STATE_DISCONNECTED) {
            Log.d(LOG_TAG, String.format(Locale.getDefault(),
                    "Disconnected from %s (%s) - status %d - state %d",
                    bluetoothDevice.getName(),
                    bluetoothDevice.getAddress(),
                    status,
                    newState
            ));
            this.disconnect();
        // if these conditions == true, then we have a successful connection
        } else if (newState == BluetoothProfile.STATE_CONNECTED) {
            bluetoothConnected = true;
            Log.d(LOG_TAG, String.format(Locale.getDefault(),
                    "Connected to %s (%s) - status %d - state %d",
                    bluetoothDevice.getName(),
                    bluetoothDevice.getAddress(),
                    status,
                    newState
            ));
            // this sleep is here to avoid TONS of problems in BLE, that occur whenever we start 
            // service discovery immediately after the connection is established 
            try {
                Thread.sleep(600);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            gatt.discoverServices();
        }
    }

    @Override
    public void onServicesDiscovered(BluetoothGatt gatt, int status) {
        super.onServicesDiscovered(gatt, status);
        if (status != BluetoothGatt.GATT_SUCCESS) {
            return;
        }
        // BEGIN - find the service and characteristic that we want (defined as a static attribute 
        // of the BluetoothLowEnergy class)
        Log.d(LOG_TAG, "Discovering services ...");
        BluetoothGattService service = null;
        for (BluetoothGattService serv: gatt.getServices()){
            Log.d(LOG_TAG, "Found service " + serv.getUuid().toString());
            if (serv.getUuid().toString().toUpperCase().contains(SERVICE_UUID)){
                service = serv;
                Log.d(LOG_TAG, "---> Selected service " + serv.getUuid().toString());
                break;
            }
        }
        if (service == null){
            return;
        }
        for (BluetoothGattCharacteristic charac: service.getCharacteristics()){
            Log.d(LOG_TAG, "Found characteristic " + charac.getUuid().toString());
            if (charac.getUuid().toString().toUpperCase().contains(CHARACTERISTIC_UUID)){
                this.characteristic = charac;
                Log.d(LOG_TAG, "---> Selected characteristic " + charac.getUuid().toString());
                break;
            }
        }
        if (this.characteristic == null){
            return;
        }
        Log.d(LOG_TAG, "Setting write and notification to the characteristic ...");
        bluetoothAdapter.cancelDiscovery();
        // END - find the service and characteristic 
        // set that we want to write to the selected characteristic and be notified if
        // it changes (the remote GATT peripheral sends data to the Android's GATT Center)
        this.characteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT);
        gatt.setCharacteristicNotification(this.characteristic, true);
        // we finished service discovery
        this.servicesDiscovered = true;
        // if we have paired/bonded then we are ready to send/receive data            
        if (pairingRequestBroadcaster.getDevicePIN().isEmpty() || bondedBroadcaster.isDeviceBonded()) {
            this.bluetoothListener.onBluetoothConnectionReady();
        }
    }

    @Override
    public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic charac, int status) {
        super.onCharacteristicRead(gatt, charac, status);
        restartDisconnectTimeout();
        if (status != BluetoothGatt.GATT_SUCCESS) {
            return;
        }
        try {
            String characValue = new String(charac.getValue(), CHARSET)
                    .replaceAll(DATA_FILTER_REGEX,"");
            Log.i(LOG_TAG, String.format(Locale.getDefault(),
                    "Characteristic Read - %s",
                    characValue
            ));
            if (charac.getUuid().equals(this.characteristic.getUuid())) {
                this.bluetoothListener.onBluetoothReceiveMsg(characValue);
            }
        } catch (UnsupportedEncodingException e) {
            Log.e(LOG_TAG, "Characteristic Read - Failed to convert message string to byte array");
        }
    }

    @Override
    public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic charac, int status) {
        super.onCharacteristicWrite(gatt, charac, status);
        restartDisconnectTimeout();
        try {
            String characValue = new String(charac.getValue(), CHARSET);
            Log.i(LOG_TAG, String.format(Locale.getDefault(),
                    "Characteristic Write - SUCCESS - %s",
                    characValue
            ));
            bluetoothListener.onBluetoothSend( characValue, (status == BluetoothGatt.GATT_SUCCESS) );
        } catch (UnsupportedEncodingException e) {
            Log.e(LOG_TAG, "Characteristic Write - Failed to convert message string to byte array");
        }
    }


    @Override
    public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic charac) {
        super.onCharacteristicChanged(gatt, charac);
        Log.d(LOG_TAG,"Characteristic Changed");
        onCharacteristicRead(gatt, charac, BluetoothGatt.GATT_SUCCESS);
    }

    /**
     * Remove pairing/bonding of the device 
     * @param device Device to remove bonding
     */
    public static void removeBond(BluetoothDevice device){
        try {
            if (device == null){
                throw new Exception();
            }
            Method method = device.getClass().getMethod("removeBond", (Class[]) null);
            method.invoke(device, (Object[]) null);
            Log.d(LOG_TAG, "removeBond() called");
            Thread.sleep(600);
            Log.d(LOG_TAG, "removeBond() - finished method");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * Clears the GATT services cache, so that new services can be discovered 
     * @param bluetoothGatt GATT Client to clear service's discovery cache
     */
    public static void refresh(BluetoothGatt bluetoothGatt){
        try {
            Method method = bluetoothGatt.getClass().getMethod("refresh", (Class[]) null);
            method.invoke(bluetoothGatt, (Object[]) null);
        } catch (Exception e){
            e.printStackTrace();
        }
    }

    /**
     * Connect to the GATT Peripheral device
     * @param uuid GATT Peripheral address / mac / uuid to connect to
     * @param pin PIN to authenticate and pair to the device
     */
    public void connect(String uuid, String pin) throws BluetoothNotEnabledException, BluetoothDeviceNotFound {
        checkBluetooth();
        // do not connect twice
        if (this.isConnected()){
            return;
        }
        // get device
        BluetoothDevice device = this.bluetoothScanResults.get(uuid);
        if (device == null){
            throw new BluetoothDeviceNotFound();
        }
        this.deviceUUID = uuid;
        pairingRequestBroadcaster.setDevicePIN(pin);
        removeBond(device);
        // create connection to the bluetooth device
        bluetoothGatt = device.connectGatt(activity, false, this);
        refresh(bluetoothGatt);
    }

    /**
     * Disconnect from BLE device. This method should be called whenever we want to 
     * close the APP, or the BLE connection.
     */
    public void disconnect() {            
        Log.d(LOG_TAG, "disconnect() - executed");
        if (bluetoothGatt != null) {
            if (characteristic != null) {
                bluetoothGatt.setCharacteristicNotification(characteristic, false);
            }
            //remove device authorization/ bond/ pairing
            removeBond(bluetoothGatt);
            // disconnect now
            bluetoothGatt.disconnect();
            bluetoothGatt.close();
            Log.d(LOG_TAG, "disconnect() - bluetoothGatt disconnect happened");
        }
        bluetoothGatt = null;
        characteristic = null;
        bluetoothConnected = false;
        servicesDiscovered = false;
        // set device as not bonded anymore
        bondedBroadcaster.resetDeviceBonded();
    }

    /**
     * bluetooth nearby devices scan is on
     * @return true if scanning is on, false otherwise
     */
    public boolean isScanning(){
        return (this.bluetoothScanning);
    }

    /**
     * Check bluetooth system state (on or off)
     * @return true if system is on, false otherwise
     */
    public boolean isEnabled(){
        try {
            checkBluetooth();
            return bluetoothAdapter.isEnabled();
        } catch (BluetoothNotEnabledException e) {
            return false;
        }
    }

    /**
     * Check bluetooth connection
     * @return true if connected, false otherwise
     */
    public boolean isConnected(){
        return (this.bluetoothConnected);
    }

    /**
     * Start bluetooth scan for nearby devices
     * @param filters Scan filters that define what devices to scan for
     */
    public void startScan(List<ScanFilter> filters)
            throws BluetoothNotEnabledException{
        checkBluetooth();
        // dont run two scans simultaneously
        if (isScanning()) {
            return;
        }
        // disconnect previously connected devices
        if (isConnected()) {
            this.disconnect();
            return;
        }
        // setup bluetooth scanning settings
        ScanSettings settings = new ScanSettings.Builder()
                .setScanMode(ScanSettings.SCAN_MODE_LOW_POWER)
                .build();

        // start scanning
        this.bluetoothScanning = true;
        this.bluetoothScanResults.clear();
        this.bluetoothLeScanner = bluetoothAdapter.getBluetoothLeScanner();

        // Stops scanning after a pre-defined scan period.
        Handler bluetoothHandler = new Handler();
        bluetoothHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                stopScan();
            }
        }, SCAN_TIMEOUT);

        // start scan with default scan callback
        this.bluetoothLeScanner.startScan(filters, settings, bluetoothScanCallback);
        // we have started successfully the BLE scanning
        bluetoothListener.onBluetoothStartScan();
    }

    /**
     * Stop bluetooth scan for nearby devices
     */
    public void stopScan(){
        if (!bluetoothScanning) {
            return;
        }
        // set app scan state to false
        bluetoothScanning = false;
        if (bluetoothLeScanner != null) {
            bluetoothLeScanner.stopScan(bluetoothScanCallback);
            bluetoothLeScanner = null;
        }
        // we have stopped BLE scanning, call the user's callback
        bluetoothListener.onBluetoothStopScan(bluetoothScanResults.values());
    }

    /**
     * Send a message via bluetooth
     * @param msg message to send
     */
    public void send(String msg) {
        if (!bluetoothConnected || characteristic == null){
            bluetoothListener.onBluetoothNotConnected();
            return;
        }
        try {
            msg = msg.replaceAll(DATA_FILTER_REGEX, "") + TERMINATION_CHAR;
            Log.d(LOG_TAG, String.format(Locale.getDefault(),
                    "Sending message: %s",
                    msg));
            characteristic.setValue(msg.getBytes(CHARSET));
            bluetoothGatt.writeCharacteristic(characteristic);
        } catch (UnsupportedEncodingException e) {
            Log.e(LOG_TAG,
                "BluetoothLowEnergy.send: Failed to convert message string to byte array");
        }
    }

    public String getDeviceUUID(){
        return deviceUUID;
    }

    public Activity getActivity(){
        return activity;
    }

    /**
     * Check if bluetooth is enabled and working properly
     */
    private void checkBluetooth() throws BluetoothNotEnabledException{
        if (bluetoothAdapter == null) {
            final BluetoothManager bluetoothManager =
                    (BluetoothManager) activity.getSystemService(Context.BLUETOOTH_SERVICE);
            if (bluetoothManager == null){
                throw new BluetoothNotEnabledException();
            }
            bluetoothAdapter = bluetoothManager.getAdapter();
        }

        // Ensures Bluetooth is available on the device and it is enabled. If not,
        // displays a dialog requesting user permission to enable Bluetooth.
        if (bluetoothAdapter == null || !bluetoothAdapter.isEnabled()) {
            throw new BluetoothNotEnabledException();
        }
    }

}

上面使用的避免问题的关键方法和函数是:

  • Thread.sleep(600)
  • removeBond(device)
  • refresh(gatt)
  • gatt.disconnect()
  • gatt.close()

在我的例子中,我从蓝牙堆栈得到了这个响应,因为该设备已经与我的 phone 绑定。我将其从我的设置中删除,错误 22 消失了。

在 aosp 中(android 源代码)。您可以在蓝牙源代码中找到任何错误,并了解状态代码的含义。 文件路径是 system/bt/stack/include/gatt_api.h

这是 link:https://android.googlesource.com/platform/system/bt/+/master/stack/include/gatt_api.h。但它全部以十六进制显示。

例如:

hex Decimal reason
0x08 8 connection timeout
0x13 19 connection terminate by peer user
0x16 22 connectionterminated by local host
0x22 34 connection fail for LMP response tout
0x85 133 gatt_error