Search code examples
androidarduinobluetooth-lowenergygatt

BLE GATT onCharacteristicChanged not called after subscribing to notification


this is my first post on SO.

I have some problems subscribing to GATT notifications on android 5.0.2 .

What I aim to do is to connect an Arduino with a BLE Shield to my Android phone. I have a sensor connected to the Arduino and want to send the data from the Arduino to my phone by using the BLE shield. There is a nRF8001 on the shield which is the server, my phone/app is the client.

What I did so far was to create an Android app which scans for BLE devices. It can connect to a device and read or write characteristics. So, I can "manually" read the characteristic by calling gatt.readCharacteristic(mCharacteristic);. This allows me to get the sensor values from the Arduino. I also created a custom Service by using nRFGo Studio. I know that this part is working as I am able to discover, connect and even to be notified about changes of the characteristic by using the BLE Scanner app which is available on Google Play. But subscribing to the notifications in my own app won't work. Well, at least the subscription works, but onCharacteristicChanged(...) is never called. What's funny is the fact that if I subscribe to the characteristic in my app and afterwards subscribe to it with the BLE scanner app suddenly onCharacteristicChanged(...) is called until I unsubscribe again through the BLE scanner app. (I can see this by the Log)

My android code is as follows: The GATT callback:

private final BluetoothGattCallback mGattCallback =
        new BluetoothGattCallback() {
            @Override
            public void onConnectionStateChange(BluetoothGatt gatt, int status,
                                                int newState) {
                if (newState == BluetoothProfile.STATE_CONNECTED) {
                    sendBroadcastConnected();

                    Log.i("BLE", "Connected to GATT server.");
                    Log.i("BLE", "Attempting to start service discovery:" + bleManager.startServiceDiscovery());

                } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
                    sendBroadcastDisconnected();
                    Log.i("BLE", "Disconnected from GATT server.");
                }
            }

            @Override
            public void onServicesDiscovered(BluetoothGatt gatt, int status) {
                if (status == BluetoothGatt.GATT_SUCCESS) {
                    Log.w("BLE", "onServicesDiscovered ");
                    setNotifySensor(gatt);     
                }
            }

            private void setNotifySensor(BluetoothGatt gatt) {
                BluetoothGattCharacteristic characteristic = gatt.getService(Globals.MPU_SERVICE_UUID).getCharacteristic(Globals.X_ACCEL_CHARACTERISTICS_UUID);
                gatt.setCharacteristicNotification(characteristic, true);

                BluetoothGattDescriptor desc = characteristic.getDescriptor(Globals.X_ACCEL_DESCRIPTOR_UUID);
                Log.i("BLE", "Descriptor is " + desc); // this is not null
                desc.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
                Log.i("BLE", "Descriptor write: " + gatt.writeDescriptor(desc)); // returns true

            }

            @Override
            public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
                               if(Globals.X_ACCEL_CHARACTERISTICS_UUID.equals(characteristic.getUuid())){
                    Log.w("BLE", "CharacteristicRead - xaccel service uuid: " + characteristic.getService().getUuid());
                    Log.w("BLE", "CharacteristicRead - xaccel value: " + characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8,0));
                }
            }

            @Override
            public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
                Log.i("BLE", "Received characteristics changed event : "+characteristic.getUuid());
                if(Globals.X_ACCEL_CHARACTERISTICS_UUID.equals(characteristic.getUuid())){
                    Log.i("BLE", "Received new value for xAccel.");
                }


            }

            @Override
            public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
            }

            @Override
            public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
            }

            @Override
            public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
            }
        };

This is how I connect to GATT:

        bt_device = createBluetoothDevice(DEVICE_ADRESS);
        BluetoothGatt mBluetoothGatt = bt_device.connectGatt(context, false, mGattCallback);

All of this code runs in a background service which is started after a BLE device is selected.

What I tried was to implement things like they are demonstrated in the Google Dev API Guide and I also tried this solution (without success). Neither searching on Google, reading questions already asked on SO (For example this one ) or having a look on the Nordic Developer Zone did help much in this case.

Finally, my question is: What have I done wrong? Am I missing something? I just can't figure it out and it's driving me crazy for two days now. I don't know where else I could search for a solution, so I hope you can help me..

EDIT Globals Class:

// BLE Services
public static final UUID MPU_SERVICE_UUID = UUID.fromString("3f540001-1ee0-4245-a7ef-35885ccae141");
// BLE Characteristics
public static final UUID X_ACCEL_CHARACTERISTICS_UUID = UUID.fromString("3f540002-1ee0-4245-a7ef-35885ccae141");
// BLE Descriptors
public static final UUID X_ACCEL_DESCRIPTOR_UUID = UUID.fromString("3f542902-1ee0-4245-a7ef-35885ccae141");

EDIT What i do in BLE Scanner: I simply scan for BLE devices. It finds my device and after tapping on it it shows me all my services which I set up on the Arduino board. After selecting a service it shows me all my chatacteristics which I specified for this Service. And when I tap on a characteristic, it shows me its UUID and its Service's UUID. Also, the BLE Scanner app allows me to subscribe to the notification or to read the characteristic. When I subscribe, the value is continously updated.


Solution

  • So, I finnally figured out my mistake :) As you can see above, I'm usinng a UUID with the same base for my descriptor as my characteristics (starting with 3f54XXXX-....)

    I changed it to public static final UUID X_ACCEL_DESCRIPTOR_UUID = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"); and now everything is working as expected.

    I did not do this before as i thought that there should be a descriptor for each characteristic. But in fact, according to the Client Characteristic Configuration the ...

    [...] descriptor shall be persistent across connections for bonded devices. The Client Characteristic Configuration descriptor is unique for each client.

    So I checked the RedBearLab Android App example and saw that the descriptor's UUID equals the ones posted on other SO answers.

    This also explains why my App received Notifications after I enabled them in the BLE Scanner App: As the descriptor shall be persistent across connections for bonded devices, the BLE Scanner App used also this UUID for the descriptor and thus enabled notifications for the client (= my phone).