Search code examples
androidbluetooth-lowenergybluetooth-gatt

Android BLE onCharacteristicChanged callback never gets invoked


I have a client BLE Android application that Successfully scans for devices and then connects to the selected device. As far as I can tell I have set up the characteristics correctly. The server is a simple echo server and receives the message from my Android client. It writes the data back to a characteristic the client has marked as notify. I can see the server knows it's a notify and sends the data accordingly. But my Android client never fires the callback.

When the device connects and the onServicesDiscovered() callback fires. I request an MTU of 512 then wait for that callback to fire. WHen onMtuChanged() fires I get the characteristic I need to subscribe to and set setCharacteristicNotification to true. Then I updated the configuration descriptor with ENABLE_NOTIFICATION_VALUE, write it, and then wait for the onDescriptorWrite(). The server responds to all of this and is ready to echo back data.

I would expect the onCharacteristicChanged() callback to fire each time. I have a Xamarin BLE client that works fine with the exact same sequence of events, so I know the server is working.

This is my callback class.

private class GattClientCallback : BluetoothGattCallback() {
        override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) {
            super.onConnectionStateChange(gatt, status, newState)
            val isSuccess = status == BluetoothGatt.GATT_SUCCESS
            val isConnected = newState == BluetoothProfile.STATE_CONNECTED
            Log.d("TAG", "onConnectionStateChange: Client $gatt  success: $isSuccess connected: $isConnected")
            // try to send a message to the other device as a test
            if (isSuccess && isConnected) {
                // discover services
                gatt.discoverServices()
            }
        }

        override fun onServicesDiscovered(discoveredGatt: BluetoothGatt, status: Int) {
            super.onServicesDiscovered(discoveredGatt, status)
            if (status == BluetoothGatt.GATT_SUCCESS) {
                Log.d("TAG", "onServicesDiscovered: Have gatt $discoveredGatt")
                gatt = discoveredGatt

                gatt?.requestMtu(512)
            }
        }

        override fun onCharacteristicWrite(
            gatt: BluetoothGatt?,
            characteristic: BluetoothGattCharacteristic?,
            status: Int
        ) {
            super.onCharacteristicWrite(gatt, characteristic, status)
        }

        override fun onCharacteristicRead(
            gatt: BluetoothGatt,
            characteristic: BluetoothGattCharacteristic,
            value: ByteArray,
            status: Int
        ) {
            Log.d("TAG", "onCharacteristicRead")
            super.onCharacteristicRead(gatt, characteristic, value, status)
        }

        override fun onMtuChanged(gatt: BluetoothGatt?, mtu: Int, status: Int) {
            Log.d("TAG", "onMtuChanged")
            super.onMtuChanged(gatt, mtu, status)

            mService = BLE_Object.gatt?.getService(SERVICE_UUID)
            mOut = mService?.getCharacteristic(OUT_UUID)
            mIn = mService?.getCharacteristic(IN_UUID)

            gatt?.setCharacteristicNotification(mIn, true)
            val clientConfig: BluetoothGattDescriptor? = mIn?.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"))

            if (clientConfig != null) {
                Log.d("TAG", "descriptor found")

                if (mIn?.getProperties()?.and(BluetoothGattCharacteristic.PROPERTY_NOTIFY) != 0)
                {
                    Log.d("TAG", "PROPERTY_NOTIFY found")
                    clientConfig?.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE)
                }

            }

            var b: Boolean?

            b = gatt?.writeDescriptor(clientConfig)

            Log.d("TAG", "writeDescriptor: " + b.toString())
        }

        override fun onCharacteristicChanged(
            gatt: BluetoothGatt,
            characteristic: BluetoothGattCharacteristic,
            value: ByteArray
        ) {
            Log.d("TAG", "onCharacteristicChanged")
            super.onCharacteristicChanged(gatt, characteristic, value)
        }

        override fun onDescriptorWrite(
            gatt: BluetoothGatt?,
            descriptor: BluetoothGattDescriptor?,
            status: Int
        ) {
            super.onDescriptorWrite(gatt, descriptor, status)

            Log.d(
                "TAG",
                "onDescriptorWrite :" + if (status === BluetoothGatt.GATT_SUCCESS) "Sucess" else "false"
            )
        }
    }

This is the Android LogCat after connection.

BluetoothGatt     com.example.nav3    D  onClientConnectionState() - status=0 clientIf=8 device=94:E6:86:6F:58:B2
TAG               com.example.nav3    D  onConnectionStateChange: Client android.bluetooth.BluetoothGatt@9c745cd  success: true connected: true
BluetoothGatt     com.example.nav3    D  discoverServices() - device: 94:E6:86:6F:58:B2
BluetoothGatt     com.example.nav3    D  onConnectionUpdated() - Device=94:E6:86:6F:58:B2 interval=6 latency=0 timeout=500 status=0
BluetoothGatt     com.example.nav3    D  onSearchComplete() = Device=94:E6:86:6F:58:B2 Status=0
TAG               com.example.nav3    D  onServicesDiscovered: Have gatt android.bluetooth.BluetoothGatt@9c745cd
BluetoothGatt     com.example.nav3    D  configureMTU() - device: 94:E6:86:6F:58:B2 mtu: 512
BluetoothGatt     com.example.nav3    D  onConfigureMTU() - Device=94:E6:86:6F:58:B2 mtu=512 status=0
TAG               com.example.nav3    D  onMtuChanged
BluetoothGatt     com.example.nav3    D  setCharacteristicNotification() - uuid: b76018e2-b400-11eb-8529-0242ac130003 enable: true
TAG               com.example.nav3    D  descriptor found
TAG               com.example.nav3    D  PROPERTY_NOTIFY found
TAG               com.example.nav3    D  writeDescriptor: true
TAG               com.example.nav3    D  onDescriptorWrite :Sucess
TAG               com.example.nav3    D  Send a message
TAG               com.example.nav3    D  onServicesDiscovered: message send: true

This is the server log (it's an ESP32) which shows the message received and the return notification.

I NimBLEServer: "mtu update event; conn_handle=0 mtu=512"
D NimBLEServer: ">> handleGapEvent: "
I NimBLEServer: "subscribe event; attr_handle=12, subscribed: true"
I NimBLECharacteristic: "New subscribe value for conn: 0 val: 1"
D NimBLECharacteristicCallbacks: "onSubscribe: default"
D NimBLEServer: ">> handleGapEvent: "
D NimBLEServer: "Connection parameters updated."
D NimBLECharacteristic: "Characteristic b76019c8-b400-11eb-8529-0242ac130003 Write event"
D NimBLECharacteristic: ">> setValue: length=10, data=4543483a313233343536, characteristic UUID=b76019c8-b400-11eb-8529-0242ac130003"
D NimBLECharacteristic: "<< setValue"
10, ECH: : 123456
D NimBLECharacteristic: ">> setValue: length=6, data=313233343536, characteristic UUID=b76018e2-b400-11eb-8529-0242ac130003"
D NimBLECharacteristic: "<< setValue"
D NimBLECharacteristic: ">> notify: length: 6"
D NimBLECharacteristicCallbacks: "onNotify: default"
D NimBLEServer: ">> handleGapEvent: "
D NimBLECharacteristicCallbacks: "onStatus: default"
D NimBLECharacteristic: "<< notify"
Echo: ECH:123456
D NimBLECharacteristicCallbacks: "onWrite: default"

Solution

  • So, I found the problem. After looking at the GitHub project BLEProof-collection, I found that the auto-complete callback that is inserted by Android Studio is:

            override fun onCharacteristicChanged(
            gatt: BluetoothGatt,
            characteristic: BluetoothGattCharacteristic,
            value: ByteArray) 
    

    But the one that works is:

    override fun onCharacteristicChanged(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic)
    

    You get the value from characteristic.value