I am working with BLE enabled hardware and communicating with the hardware using Foreground Service of the Android. Foreground service is responsible for handling the BLE related events and it works quite good as per requirements for a while but somehow if the Foreground service is got killed or BLE connection is broken due to any reason then app tries to reconnect to the BLE again and then BLE callbacks start getting duplicate events from the BluetoothGattCallback, that is even though hardware sends a single event to Bluetooth but Android BluetoothGattCallback receives multiple callbacks for the same which leads to a lot of errors in our implementations.
For reference please go through Logs as follows,
Following are methods and callbacks from my foreground service,
BLEManagerService: *****onDescriptorWrite: 0*****<br>
BLEManagerService: *****onDescriptorWrite: 0*****<br>
BLEManagerService: *****onDescriptorWrite: 0*****<br>
BLEManagerService: Firmware: onCharacteristicRead true<br>
BLEManagerService: *****onDescriptorWrite: 0*****<br>
BLEManagerService: Firmware: onCharacteristicRead true<br>
BLEManagerService: *****onCharacteristicRead: 0*****<br>
BLEManagerService: *****onCharacteristicRead: 0*****<br>
override fun onCreate() {
super.onCreate()
mBluetoothGatt?.let { refreshDeviceCache(it) }
registerReceiver(btStateBroadcastReceiver, IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED))
}
/**
* Start BLE scan
*/
private fun scanLeDevice(enable: Boolean) {
if (enable && bleConnectionState == DISCONNECTED) {
//initialize scanning BLE
startScan()
scanTimer = scanTimer()
} else {
stopScan("scanLeDevice: (Enable: $enable)")
}
}
private fun scanTimer(): CountDownTimer {
return object : CountDownTimer(SCAN_PERIOD, 1000) {
override fun onTick(millisUntilFinished: Long) {
//Nothing to do
}
override fun onFinish() {
if (SCAN_PERIOD > 10000 && bleConnectionState == DISCONNECTED) {
stopScan("restart scanTimer")
Thread.sleep(200)
scanLeDevice(true)
SCAN_PERIOD -= 5000
if (null != scanTimer) {
scanTimer!!.cancel()
scanTimer = null
}
scanTimer = scanTimer()
} else {
stopScan("stop scanTimer")
SCAN_PERIOD = 60000
}
}
}
}
//Scan callbacks for more that LOLLIPOP versions
private val mScanCallback = object : ScanCallback() {
override fun onScanResult(callbackType: Int, result: ScanResult) {
val btDevice = result.device
if (null != btDevice) {
val scannedDeviceName: String? = btDevice.name
scannedDeviceName?.let {
if (it == mBluetoothFemurDeviceName) {
stopScan("ScanCallback: Found device")
//Disconnect from current connection if any
mBluetoothGatt?.let {it1 ->
it1.close()
mBluetoothGatt = null
}
connectToDevice(btDevice)
}
}
}
}
override fun onBatchScanResults(results: List<ScanResult>) {
//Not Required
}
override fun onScanFailed(errorCode: Int) {
Log.e(TAG, "*****onScanFailed->Error Code: $errorCode*****")
}
}
/**
* Connect to BLE device
* @param device
*/
fun connectToDevice(device: BluetoothDevice) {
scanLeDevice(false)// will stop after first device detection
//Stop Scanning before connect attempt
try {
if (null != scanTimer) {
scanTimer!!.cancel()
}
} catch (e: Exception) {
//Just handle exception if something
// goes wrong while canceling the scan timer
}
//Stop scan if still BLE scanner is running
stopScan("connectToDevice")
if (mBluetoothGatt == null) {
connectedDevice = device
if (Build.VERSION.SDK_INT >= 26)
connectedDevice?.connectGatt(this, false, mGattCallback)
}else{
disconnectDevice()
connectedDevice = device
connectedDevice?.connectGatt(this, false, mGattCallback)
}
}
/**
* Disconnect from BLE device
*/
private fun disconnectDevice() {
mBluetoothGatt?.close()
mBluetoothGatt = null
bleConnectionState = DISCONNECTED
mBluetoothManager = null
mBluetoothAdapter = null
mBluetoothFemurDeviceName = null
mBluetoothTibiaDeviceName = null
connectedDevice = null
}
/****************************************
* BLE Related Callbacks starts *
* Implements callback methods for GATT *
****************************************/
// Implements callback methods for GATT events that the app cares about. For example,
// connection change and services discovered.
private val mGattCallback = object : BluetoothGattCallback() {
/**
* Connection state changed callback
*/
override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) {
if (newState == BluetoothProfile.STATE_CONNECTED) {
mBluetoothGatt = gatt
//Stop Scanning before connect attempt
try {
if (null != scanTimer) {
scanTimer!!.cancel()
}
} catch (e: Exception) {
//Just handle exception if something
// goes wrong while canceling the scan timer
}
stopScan("onConnectionStateChange")// will stop after first device detection
} else if (newState == BluetoothProfile.STATE_DISCONNECTED || status == 8) {
disconnectDevice()
Handler(Looper.getMainLooper()).postDelayed({
initialize()
}, 500)
}
}
/**
* On services discovered
* @param gatt
* @param status
*/
override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) {
super.onServicesDiscovered(gatt, status)
}
override fun onDescriptorWrite(gatt: BluetoothGatt, descriptor: BluetoothGattDescriptor, status: Int) {
super.onDescriptorWrite(gatt, descriptor, status)
}
/**
* On characteristic read operation complete
* @param gatt
* @param characteristic
* @param status
*/
override fun onCharacteristicRead(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, status: Int) {
super.onCharacteristicRead(gatt, characteristic, status)
}
/**
* On characteristic write operation complete
* @param gatt
* @param characteristic
* @param status
*/
override fun onCharacteristicWrite(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, status: Int) {
super.onCharacteristicWrite(gatt, characteristic, status)
val data = characteristic.value
val dataHex = byteToHexStringJava(data)
}
/**
* On Notification/Data received from the characteristic
* @param gatt
* @param characteristic
*/
override fun onCharacteristicChanged(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic) {
super.onCharacteristicChanged(gatt, characteristic)
val data = characteristic.value
val dataHex = byteToHexStringJava(data)
}
override fun onReadRemoteRssi(gatt: BluetoothGatt, rssi: Int, status: Int) {
super.onReadRemoteRssi(gatt, rssi, status)
val b = Bundle()
b.putInt(BT_RSSI_VALUE_READ, rssi)
receiver?.send(APP_RESULT_CODE_BT_RSSI, b)
}
}
/**
* Bluetooth state receiver to handle the ON/OFF states
*/
private val btStateBroadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1)
when (state) {
BluetoothAdapter.STATE_OFF -> {
//STATE OFF
}
BluetoothAdapter.STATE_ON -> {
//STATE ON
btState = BT_ON
val b = Bundle()
receiver?.send(APP_RESULT_CODE_BT_ON, b)
initialize()
}
BluetoothAdapter.STATE_TURNING_OFF -> {
//Not Required
}
BluetoothAdapter.STATE_TURNING_ON -> {
//Not Required
}
}
}
}
private fun handleBleDisconnectedState() {
mBluetoothGatt?.let {
it.close()
receiver?.send(DISCONNECTED, b)
Handler(Looper.getMainLooper()).postDelayed({
mBluetoothManager = null
mBluetoothAdapter = null
mBluetoothFemurDeviceName = null
mBluetoothTibiaDeviceName = null
mBluetoothGatt = null
}, 1000)
}
}
/****************************************
* BLE Related Callbacks End ***
****************************************/
/****************************************************
* Register Receivers to handle calbacks to UI ***
****************************************************/
override fun onDestroy() {
super.onDestroy()
try {
mBluetoothGatt?.let {
it.close()
mBluetoothGatt = null
}
unregisterReceivers()
scanTimer?.cancel()
} catch (e: Exception) {
e.printStackTrace()
}
}
override fun onTaskRemoved(rootIntent: Intent?) {
super.onTaskRemoved(rootIntent)
Log.e(TAG, "onTaskRemoved")
stopSelf()
}
/**
* Unregister the receivers before destroying the service
*/
private fun unregisterReceivers() {
unregisterReceiver(btStateBroadcastReceiver)
}
companion object {
private val TAG = BLEManagerService::class.java.simpleName
private var mBluetoothGatt: BluetoothGatt? = null
var bleConnectionState: Int = DISCONNECTED
}
}
Don't set mBluetoothGatt = gatt in onConnectionStateChange. Instead set it from the return value of connectGatt. Otherwise you might create multiple BluetoothGatt objects without closing previous ones and therefore get multiple callbacks.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With