Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android BLE Gatt connection change statuses

I have an android app to connect to a BLE device and write to it. I can successfully connect, read and write to it. As a part of testing, we are trying different disconnection scenarios.

Sometimes, if BLE device disconnects the connection, I get the connection change as disconnect with status value as 19. Also if there is any bond error, status equals 22. If I programmatically disconnect the connection, this status gives me 0. But none of these states except 0 are specified in android documentation.

Posting a sample 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");
    }
};

Does anyone face this same problem and sorted out the list of statuses. I need to know the possible values for status variable in onConnectionStateChange method

like image 951
Ijas Ahamed N Avatar asked Jul 12 '17 11:07

Ijas Ahamed N


2 Answers

Here is the list of codes i have

  • Programmatically disconnected - 0
  • Device went out of range - 8
  • Disconnected by device - 19
  • Issue with bond - 22
  • Device not found - 133(some phone it gives 62)

I have tested disconnect scenario's in 5.0.2, 5.1, 6.0 and 6.0.1. But only found this bond issue code in 6.0.1 android version.

like image 181
Ijas Ahamed N Avatar answered Oct 01 '22 15:10

Ijas Ahamed N


Sorry to bring up an old question, but here is the solution for many of the problems i've had with Bluetooth (BLE) 4.0. Sorry again for the big classes below but be sure they are needed and no method there is irrelevant or unused.

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();
        }
    }

}

The key methods and functions to avoid problems used above are:

  • Thread.sleep(600)
  • removeBond(device)
  • refresh(gatt)
  • gatt.disconnect()
  • gatt.close()
like image 36
Andre Madureira Avatar answered Oct 01 '22 15:10

Andre Madureira