简体   繁体   中英

BleGattException when writing to a particular characteristic

Writing to a specific characteristic crashes the application and throws the following exception:

Caused by: BleGattException{status=8, bleGattOperation=BleGattOperation{description='CHARACTERISTIC_WRITE'}}
      at com.polidea.rxandroidble.internal.connection.RxBleGattCallback.propagateStatusErrorIfGattErrorOccurred(RxBleGattCallback.java:245)
      at com.polidea.rxandroidble.internal.connection.RxBleGattCallback.access$100(RxBleGattCallback.java:26)
      at com.polidea.rxandroidble.internal.connection.RxBleGattCallback$1.onCharacteristicWrite(RxBleGattCallback.java:110)
      at android.bluetooth.BluetoothGatt$1.onCharacteristicWrite(BluetoothGatt.java:407)
      at android.bluetooth.IBluetoothGattCallback$Stub.onTransact(IBluetoothGattCallback.java:279)

A connection is established to the device, and other methods that both read and write seem to work fine.

Code being used:

mConnectoin.writeCharacteristic(UUID, bytes)
    .observeOn(AndroidSchedulers.mainThread());

My first thought was that perhaps the characteristic does not have a write permission enabled, but the following log statement for characteristic.getProperties() returns 8, indicating it does in fact have write permissions:

.getCharacteristic(CharacteristicUUID)
                    .subscribe(new Action1<BluetoothGattCharacteristic>() {
                        @Override
                        public void call(BluetoothGattCharacteristic characteristic) {
                            Log.d(TAG, "characteristic permissions: " + characteristic.getPermissions());
                            Log.d(TAG, "characteristic properties: " + characteristic.getProperties());
                        }
                    });

So what might the issue be?

BleGattException is emitted in BluetoothGattCallback class onCharacteristicWrite() callback method. (it is inside the RxBleGattCallback class).

status comes from the Android OS BLE stack. These are described for instance here: https://android.googlesource.com/platform/external/bluetooth/bluedroid/+/android-5.1.0_r1/stack/include/gatt_api.h

status=8 indicates #define GATT_INSUF_AUTHORIZATION 0x08 - so it seems that you're trying to write a BluetoothCharacteristic that needs an encrypted connection (a paired device).

Unfortunately as for the moment RxAndroidBle doesn't help with pairing the devices.

As @s_noopy has pointed out, there's no specific support for pairing the device inside the library, but there are options for creating your little helper that can assist you before connecting.

RxBluetooth library has support for doing bonds in a reactive way.

I extracted the particular pieces of code and I created a small gist to share (that only does the bonding process). Feel free to fork it or improve it (the code is based on RxBluetooth). Also if someone finds a better way or there's something wrong, just point it out!

The original gist :

Update

I modified the source code of the RxBluetooth (previously named RxBluetoothHelper ) as there were a bug when doing unbonding. In Android when you bond you have to listen for the bonding process in a intent (but when you unbond is not necessary, as the system is only removing the stored keys). This updated version addresses this that the previous not. Also if for some reason when you call bond the method returns false, the observable will emit onError. The gist is also updated!


public class BluetoothCompat {

    public static boolean createBondCompat(final BluetoothDevice device)
    throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            return device.createBond();
            } else {
            Method method = device.getClass().getMethod("createBond", (Class[]) null);
            final Object invoke = method.invoke(device, (Object[]) null);
            return (Boolean) invoke;
        }
    }

    public static boolean removeBondCompat(final BluetoothDevice device) throws NoSuchMethodException, InvocationTargetException,
    IllegalAccessException {
        Method method = device.getClass().getMethod("removeBond", (Class[]) null);
        final Object invoke = method.invoke(device, (Object[]) null);
        return (Boolean) invoke;
    }

    private BluetoothCompat() {
        throw new AssertionError("No instances!");
    }

}

public class RxBluetooth {

    private final Context context;
    private final BluetoothAdapter adapter;

    private static final Observable.Transformer BOND_STATUS_TRANSFORMER = statusObservable -> statusObservable.map(status -> {
        switch (status) {
            case BluetoothDevice.BOND_NONE:
            default:
            return BondStatus.NONE;
            case BluetoothDevice.BOND_BONDING:
            return BondStatus.BONDING;
            case BluetoothDevice.BOND_BONDED:
            return BondStatus.BONDED;
        }
    });

    public enum BondStatus {
        NONE,
        BONDING,
        BONDED
    }

    public RxBluetooth(Context context, BluetoothAdapter bluetoothAdapter) {
        this.context = context.getApplicationContext();
        this.adapter = bluetoothAdapter;
    }

    public Observable bondStatus(@NonNull final BluetoothDevice device) {
        return Observable.defer(() -> Observable.just(device.getBondState()).compose(BOND_STATUS_TRANSFORMER));
    }

    public Observable bond(@NonNull final BluetoothDevice device) {
        return Observable.create(subscriber -> {
            bondStatus(device).subscribe(bondStatus -> {
                switch (bondStatus) {
                    case NONE:
                    observeDeviceBonding(context, device).compose(BOND_STATUS_TRANSFORMER).subscribe(subscriber);
                    try {
                        final boolean bonding = BluetoothCompat.createBondCompat(device);
                        if (!bonding) {
                            subscriber.onError(new BluetoothBondingException("Can't initiate a bonding operation!"));
                        }
                        } catch (Exception e) {
                        subscriber.onError(new BluetoothIncompatibleBondingException(e));
                    }
                    break;
                    case BONDING:
                    subscriber.onError(new BluetoothBondingException("device is already in the process of bonding"));
                    break;
                    case BONDED:
                    subscriber.onNext(BondStatus.BONDED);
                    subscriber.onCompleted();
                    break;
                }
            });
        });
    }

    public Observable removeBond(@NonNull final BluetoothDevice device) {
        return Observable.defer(() -> {

            for (BluetoothDevice bondedDevice : adapter.getBondedDevices()) {
                if (bondedDevice.getAddress().equals(device.getAddress())) {
                    try {
                        final boolean removeBond = BluetoothCompat.removeBondCompat(device);
                        if (!removeBond) {
                            return Observable.error(new BluetoothBondingException("Can't delete the bonding for this device!"));
                        }
                        } catch (Exception e) {
                        return Observable.error(new BluetoothIncompatibleBondingException(e));
                    }
                }
            }

            return Observable.just(BondStatus.NONE);
        });
    }

    private static Observable observeDeviceBonding(@NonNull final Context context, @NonNull final BluetoothDevice device) {
        return observeBroadcast(context, new IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED)).filter(pair -> {
            BluetoothDevice bondingDevice = pair.getValue1().getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
            return bondingDevice.equals(device);
        })
        .map(pair1 -> pair1.getValue1().getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, -1))
        .skipWhile(state -> state != BluetoothDevice.BOND_BONDING)
        .takeUntil(state -> state == BluetoothDevice.BOND_BONDED || state == BluetoothDevice.BOND_NONE);
    }

    private static Observable> observeBroadcast(final Context context, final IntentFilter filter) {
        return Observable.create(new Observable.OnSubscribe>() {

            @Override public void call(Subscriber> subscriber) {
                Enforcer.onMainThread();

                final BroadcastReceiver receiver = new BroadcastReceiver() {
                    @Override public void onReceive(Context context, Intent intent) {
                        subscriber.onNext(Pair.with(context, intent));
                    }
                };

                context.registerReceiver(receiver, filter);

                subscriber.add(Subscriptions.create(() -> context.unregisterReceiver(receiver)));
            }
        });
    }

}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM