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"
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