Receiving BluetoothGattCallback multiple times after foreground Service restart - android

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.

Related

How to receive data from BLE device using greenbot EventBus in my code?

I have an application that sends and receives data using BLE. Originally it was classical bluetooth but I was tasked with changing the project to BLE.
So far I have succeeded in sending data but not receiving it. The main activity contains multiple fragments. On of those is responsible for sending data where as the other sends a request and then receives a response with the data from the BLE device.
one fragment is called Parameter and the other Memory. Each fragment has a viewmodel and repository as the architecture is based on MVVM. the flow is as follows:
Parameter fragment -> View model -> repository -> DataStore class -> DataStore uses instance from BLEConnectionManager class to send the data of the corresponding parameter. Example of a function in DataStore:
fun sendToolAddressParam(data: Int){
toolAddress = data
var value = Integer.toHexString(data)
if (value.length == 1) value = "0$value"
val message = WriteCommandCodes.TOOL_ADDRESS.value + " " + value + " " + WriteCommandCodes.EXTRA2.value
BleConnectionManager.sendMessage(message)
Timber.i("Payload: $message")
}
There are also functions that request data:
fun requestToolAddress(){
BleConnectionManager.requestReadValues(ReadRequestCodes.TOOL_ADDRESS.value)
}
in the BLE class the functions are written as the following:
fun write(message:String){
val bytes = BigInteger(message.replace("\\s".toRegex(), ""), 16).toByteArray()
Timber.i("Bytes value ---> ${bytes.toHexString()}")
val device = getBleDevice()
// val characteristicRX = getBleCharacteristic()
val characteristicRX = bluetoothGattRef.getService(XpressStreamingServiceUUID).getCharacteristic(
peripheralRX)
writeCharacteristic(device, characteristicRX, bytes)
}
fun requestReadValues(requestCode:String){
if(isConnected.value!!){
write(requestCode)
}else{
Timber.e("Make sure that you connected and paired with the desired device.")
}
}
fun sendMessage(message:String){
Timber.i("Check if isConnected = true --> ${isConnected.value}")
if(isConnected.value == true){
write(message)
}else{
Timber.e("Make sure that you connected and paired with the desired device.")
}
}
Now here is my issue I want to receive data from the BLE device after I send the request, the device's documentation when it comes to BLE data exchange is here: https://docs.silabs.com/gecko-os/1/bgx/latest/ble-services
Now I have a function that supposedly receives the incoming messages but this was when classical bluetooth was used.
fun readIncomingMessages(message: String){
when{
message.startsWith(com.brainsocket.milink.data.bluetooth.ReadResponseCodes.KEY_ADDRESS.value) ->{
EventBus.getDefault().post(
ReadKeyAddressEvent(message.substring(com.brainsocket.milink.data.bluetooth.ReadResponseCodes.KEY_ADDRESS.value.length+1, com.brainsocket.milink.data.bluetooth.ReadResponseCodes.KEY_ADDRESS.value.length+3))
)
Timber.i("Message received: $message")
}
message.startsWith(com.brainsocket.milink.data.bluetooth.ReadResponseCodes.TOOL_ADDRESS.value) ->{
EventBus.getDefault().post(
ReadToolAddressEvent(message.substring(com.brainsocket.milink.data.bluetooth.ReadResponseCodes.TOOL_ADDRESS.value.length+1, com.brainsocket.milink.data.bluetooth.ReadResponseCodes.TOOL_ADDRESS.value.length+3))
)
Timber.i("Message received: $message")}
message.startsWith(com.brainsocket.milink.data.bluetooth.ReadResponseCodes.RPM_THRESHOLD.value) ->{
EventBus.getDefault().post(
ReadRPMThresholdEvent(message.substring(com.brainsocket.milink.data.bluetooth.ReadResponseCodes.RPM_THRESHOLD.value.length+1, com.brainsocket.milink.data.bluetooth.ReadResponseCodes.RPM_THRESHOLD.value.length+3))
)
Timber.i("Message received: $message")}
message.startsWith(com.brainsocket.milink.data.bluetooth.ReadResponseCodes.BACKLASH.value) ->{
EventBus.getDefault().post(
ReadBacklashEvent(message.substring(com.brainsocket.milink.data.bluetooth.ReadResponseCodes.BACKLASH.value.length+1, com.brainsocket.milink.data.bluetooth.ReadResponseCodes.BACKLASH.value.length+6))
)
Timber.i("Message received: $message")}
As you can see the Event Bus is used here, it is also used here in the DataStore:
#Subscribe(threadMode = ThreadMode.MAIN)
fun onKeyAddressEvent(event: ReadKeyAddressEvent) {
Timber.i("onKeyAddressEvent: data:${event.data}")
keyAddress = Integer.parseInt(event.data , 16)
EventBus.getDefault().post(ReadMemoryItemsEvent())
}
#Subscribe(threadMode = ThreadMode.MAIN)
fun onToolAddressEvent(event: ReadToolAddressEvent) {
Log.d(LOG_TAG, "onToolAddressEvent: data:${event.data}")
when(Integer.parseInt(event.data , 16)){
0 -> toolAddress = 1
1 -> toolAddress = 2
}
EventBus.getDefault().post(ReadMemoryItemsEvent())
}
#Subscribe(threadMode = ThreadMode.MAIN)
fun onRPMThresholdEvent(event: ReadRPMThresholdEvent) {
Log.d(LOG_TAG, "onRPMThresholdEvent: data:${event.data}")
rpmThreshold = Integer.parseInt(event.data , 16)
EventBus.getDefault().post(ReadMemoryItemsEvent())
}
#Subscribe(threadMode = ThreadMode.MAIN)
fun onReadBacklashEvent(event: ReadBacklashEvent) {
Log.d(LOG_TAG, "onReadBacklashEvent: data:${event.data}")
val data = event.data
backlash = parseGotoPos(data)
EventBus.getDefault().post(ReadMemoryItemsEvent())
}
This is in the repository:
fun getMemoryItems() : List<ModesPosItem> = listOf(
ModesPosItem(value = btDataStore.keyAddress, title = context.getString(R.string.key_address_string)),
ModesPosItem(value = btDataStore.toolAddress, title = context.getString(R.string.tool_address_string)),
ModesPosItem(value = btDataStore.rpmThreshold, title = context.getString(R.string.rpm_threshold_string)),
ModesPosItem(value = btDataStore.backlash, title = context.getString(R.string.backlash_string))
)
This is in the viewmodel:
#Subscribe(threadMode = ThreadMode.MAIN)
fun onReadMemoryItemsEvent(event: ReadMemoryItemsEvent) {
memoryItems.value = repository.getMemoryItems()
Timber.i("Memory Items [tool address, keyAddress, RPM threshold, backlash]: ${memoryItems.value.toString()}")
}
This is in the fragment:
override fun onStart() {
super.onStart()
EventBus.getDefault().register(this)
}
override fun onStop() {
EventBus.getDefault().unregister(this)
super.onStop()
}
What exactly am I supposed to do to acquire the data from the BLE device?
I made an app that write and read continous stream data throw BLE connection from a bluetooth device.
The Flow i follow is the following:
Connect Gatt;
Discover Services;
Write To Characteristic;
Subscribe to Notification;
Read Characteristic from notification --> Here the EventBus post() with your data package;
Going deeper into the connection and using some code:
After you connect to the GATT you call onConnectionStateChange to listen for changes in the gatt connection:
private val gattCallback = object : BluetoothGattCallback() {
override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) {
val deviceAddress = gatt.device.address
if (status == BluetoothGatt.GATT_SUCCESS) {
if (newState == BluetoothProfile.STATE_CONNECTED) {
Log.w("BluetoothGattCallback", "Successfully connected to $deviceAddress")
// NOW DISCOVER SERVICES
gatt.discoverServices()
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
Log.w("BluetoothGattCallback", "Successfully disconnected from $deviceAddress")
}
} else {
Log.w(
"BluetoothGattCallback",
"Error $status encountered for $deviceAddress! Disconnecting..."
)
}
If the GATT is connected succesfully it will discover services.
At this step you can write to the characteristic as follow:
override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) {
with(gatt) {
Log.w(
"BluetoothGattCallback",
"Discovered ${services.size} services for ${device.address}"
)
val msg = byteArrayOf(0x00.toByte())
val newcharacteristic = gatt!!.getService(dataUUID_service).getCharacteristic(
dataUUID_characteristic
)
newcharacteristic!!.value = msg
gatt!!.writeCharacteristic(newcharacteristic)
}
}
This will let you go on the next step, the onCharacteristicWrite listener:
override fun onCharacteristicWrite(
gatt: BluetoothGatt,
characteristic: BluetoothGattCharacteristic,
status: Int
) {
val characteristic = gatt.getService(dataUUID_service).getCharacteristic(
dataUUID_characteristic
)
gatt.setCharacteristicNotification(characteristic, true)
val descriptor = characteristic!!.getDescriptor(descriptor_UUID)
descriptor.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE;
if (descriptor != null) {
descriptor.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
gatt.writeDescriptor(descriptor)
}
}
Writing the characteristic will let you go into the onCharacteristicChanged listener that will give you back the data from the ble device and in which you can use the event bus to use your data.
override fun onCharacteristicChanged(
gatt: BluetoothGatt,
charac: BluetoothGattCharacteristic
) {
super.onCharacteristicChanged(gatt, charac)
// Log.d("CHARAC", "Characteristic Changed")
onCharacteristicRead(gatt, charac, BluetoothGatt.GATT_SUCCESS)
}
Where onCharacteristicRead should look like:
override fun onCharacteristicRead(
gatt: BluetoothGatt,
characteristic: BluetoothGattCharacteristic,
status: Int
) {
with(characteristic) {
when (status) {
BluetoothGatt.GATT_SUCCESS -> {
Log.i("BluetoothGattCallback","Read characteristic $uuid:\n${value.toHexString()}" )
// value is the read value from ble device
// HERE YOU HANDE YOUR EVENT BUS, example:
val eventData: deviceListener = deviceListener(value)
EventBus.getDefault().post(eventData)
}
BluetoothGatt.GATT_READ_NOT_PERMITTED -> {
Log.e("BluetoothGattCallback", "Read not permitted for $uuid!")
}
else -> {
Log.e(
"BluetoothGattCallback",
"Characteristic read failed for $uuid, error: $status"
)
}
}
}
}
Maybe it is not the most efficent way and not the clearest code but it works like a charm.

Android, BLE continuously writing a characteristic value disconnects the gatt server

I notice that if i write fast and continuously a characteristic value the gatt server disconnect.
I know that I have to wait until onCharacteristicWrite callback, so that's not the problem I think.
This my queue implementation, I'm using a kotlin Channel to syncronize write and read.
private var continuation: CancellableContinuation<BluetoothGattCharacteristic>? = null
private val channel = Channel<WriteOp>(1)
private suspend fun processBluetoothWrite() {
do {
val writeOp = channel.receiveOrNull()
writeOp?.apply {
try {
suspendCancellableCoroutine<BluetoothGattCharacteristic> { cont ->
continuation = cont
characteristic.value = writeOp?.value
Log.d(TAG, "Write to ${characteristic?.uuid} value ${writeOp?.value?.toHexString()}...")
if (gatt?.writeCharacteristic(characteristic) == false) {
cont.resumeWithException(Exception("Write to ${characteristic?.uuid} fails."))
}
}
} catch (ex: Exception) {
Log.e(TAG, ex.message, ex)
}
}
} while (writeOp != null)
}
override fun onCharacteristicWrite(
gatt: BluetoothGatt?,
characteristic: BluetoothGattCharacteristic?,
status: Int
) {
Log.d(TAG, "Write to ${characteristic?.uuid} value ${characteristic?.value?.toHexString()} | ${status}")
characteristic?.apply {
if (status == BluetoothGatt.GATT_SUCCESS) {
continuation?.resume(this)
} else {
continuation?.resumeWithException(Exception("Write to ${characteristic?.uuid} value ${characteristic?.value?.toHexString()} | ${status}"))
}
}
}
I need to add a delay of about 100ms in the queue processing to avoid disconnection.
UPDATE
After setting writeType as default, it seems that onCharacteristicWrite is more realistic (I used to get GATT_SUCCESS even when the device stopped communicating, so I guess it was a "virtual" state), now when the device stopped communicating it didn't get the onCharacteristicWrite callback, though after a while it is fired with status = 133.
characteristic.writeType = BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT
What does it mean?

Listening to network changes using Connectivity Manager

I am trying to listen to network changes using method registerDefaultNetworkCallback() of conenctivityManager
Using the code below from this answer
val connectivityManager = cotnext.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
connectivityManager?.let {
it.registerDefaultNetworkCallback(object : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) {
//take action when network connection is gained
}
override fun onLost(network: Network) {
//take action when network connection is lost
}
})
}
but I have a few questions about this method:
what if the phone is connected to wifi but the wifi is not connected to Internet
In the method documentation I read this which I don't understand, when exactly will the limit will hit? If the callback is called 100 times then an Exception will be thrown? And how to handle this?
To avoid performance issues due to apps leaking callbacks, the system will limit the number of outstanding requests to 100 per app (identified by their UID), shared with all variants of this method, of requestNetwork as well as ConnectivityDiagnosticsManager.registerConnectivityDiagnosticsCallback. Requesting a network with this method will count toward this limit. If this limit is exceeded, an exception will be thrown. To avoid hitting this issue and to conserve resources, make sure to unregister the callbacks with unregisterNetworkCallback(ConnectivityManager.NetworkCallback).
what if the phone is connected to wifi but the wifi is not connected
to Internet
The answer, this method will return false
In the method documentation I read this which I don't
understand, when exactly will the limit will hit? If the callback is
called 100 times then an Exception will be thrown? And how to handle
this?
I think it means if you cant register more than 100 callback
At first, add the ConnectivityReceiver class:
class ConnectivityReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (connectivityReceiverListener != null) {
connectivityReceiverListener!!.onNetworkConnectionChanged(
isConnectedOrConnecting(
context
)
)
}
}
private fun isConnectedOrConnecting(context: Context): Boolean {
val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
if (cm != null) {
if (Build.VERSION.SDK_INT < 23) {
val ni = cm.activeNetworkInfo
if (ni != null) {
return ni.isConnected && (ni.type == ConnectivityManager.TYPE_WIFI || ni.type == ConnectivityManager.TYPE_MOBILE)
}
} else {
val n = cm.activeNetwork
if (n != null) {
val nc = cm.getNetworkCapabilities(n)
return nc!!.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) || nc!!.hasTransport(
NetworkCapabilities.TRANSPORT_WIFI
)
}
}
}
return false
}
interface ConnectivityReceiverListener {
fun onNetworkConnectionChanged(isConnected: Boolean)
}
companion object {
var connectivityReceiverListener: ConnectivityReceiverListener? = null
}
}
Then In your BaseActivity or MainActivity add these lines:
abstract class BaseActivity:AppCompatActivity(),
ConnectivityReceiver.ConnectivityReceiverListener {
var receiver: ConnectivityReceiver? = null
override fun onResume() {
super.onResume()
try {
receiver = ConnectivityReceiver()
registerReceiver(
receiver!!,
IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)
)
connectivityReceiverListener = this
} catch (ex: Exception) {
//Timber.d("Base ex ${ex.localizedMessage}")
}
}
override fun onPause() {
try {
unregisterReceiver(receiver!!)
receiver = null
} catch (ex: Exception) {
}
super.onPause()
}
override fun onNetworkConnectionChanged(isConnected: Boolean) {
showMessage(isConnected)
}
private fun showMessage(isConnected: Boolean) {
try {
if (!isConnected) {
Log.d("Connection state"," disconnected")
} else {
Log.d("Connection state"," connected")
}
} catch (ex: Exception) {
}
}
}
You should register the receiver in the OnResume method and unregister it in theOnPause method

Android ble: Unable to send large data

I'm trying to implement an app for transfer some strings between ble devices (for now one device act as central and the other one as pheripheral) but without success.
This is how my peripheral (server) is set up.
Characteristic build
fun buildCharacteristic(
characteristicUUID: UUID,
): BluetoothGattCharacteristic {
var properties = BluetoothGattCharacteristic.PROPERTY_READ or
BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE or
BluetoothGattCharacteristic.PROPERTY_NOTIFY
var permission = BluetoothGattCharacteristic.PERMISSION_READ or
BluetoothGattCharacteristic.PERMISSION_WRITE
var characteristic = BluetoothGattCharacteristic(
characteristicUUID,
properties,
permission
)
return characteristic
}
service build
fun buildService(
serviceUUID: UUID,
serviceType: Int,
characteristics: List<BluetoothGattCharacteristic>
) {
bluetoothGattService = BluetoothGattService(
serviceUUID,
BluetoothGattService.SERVICE_TYPE_PRIMARY
)
for (characteristic in characteristics) {
bluetoothGattService.addCharacteristic(characteristic)
}
}
and this is how i start ble server (i omit implementation of callbacks)
fun startServer(
bleAdapter: BluetoothAdapter,
btManager: BluetoothManager,
context: Context
) {
bleAdvertiser = bleAdapter.bluetoothLeAdvertiser
bleGattServer = btManager.openGattServer(context, gattServerCallback)
bleGattServer.addService(bluetoothGattService)
var settings = AdvertiseSettings.Builder().apply {
setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY)
setConnectable(true)
setTimeout(0)
setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_HIGH)
}
var data = AdvertiseData.Builder().apply {
setIncludeDeviceName(true)
}
bleAdvertiser.startAdvertising(settings.build(), data.build(), advertiseCallback)
}
On central (client) side, when onScanResult is triggered, i try to connect with device:
fun connectToDevice(device: BluetoothDevice) {
device.connectGatt(
context,
false,
createGattCallback()
)
}
where createGattCallback() is a function return a BluetoothGattCallback object. Inside this callback, when onConnectionStateChange is called, i call service discover, and when service is discovered i try do write data to peripheral
override fun onServicesDiscovered(gatt: BluetoothGatt?, status: Int) {
super.onServicesDiscovered(gatt, status)
if (gatt?.services != null) {
var serviceFound = false
for (service in gatt.services) {
if (service.uuid == Consts.SERVICE_UUID) {
serviceFound = true
var bluetoothGattCharacteristic = service.getCharacteristic(Consts.CHARACTERISTIC_UUID)
writeCharacteristic(
gatt,
bluetoothGattCharacteristic
)
}
}
if (!serviceFound) {
gatt.disconnect()
}
}
}
fun writeCharacteristic(
gatt: BluetoothGatt,
characteristic: BluetoothGattCharacteristic
) {
var toSendString = "A random string for testing purpose only"
var toSendByteArray = toSendString.toByteArray(Charsets.UTF_8)
val chunkSize = 18
val numberOfPackets = ceil(
(toSendByteArray.size).toDouble() / chunkSize.toDouble()
)
for (i in 0 until numberOfPackets.toInt()) {
var startIndex = i * chunkSize
var endIndex = if (startIndex + chunkSize <= toSendByteArray.size) {
startIndex + chunkSize
} else {
toSendByteArray.size
}
var packet = toSendByteArray.copyOfRange(startIndex, endIndex)
characteristic.value = packet
gatt.writeCharacteristic(characteristic)
Thread.sleep(250)
}
}
My code seems not workin, on peripheral i don't receive entire string, but only the first 18 bytes. Where i'm wrong?
You need to wait for onCharacteristicWrite before you can send the next value. See Android BLE BluetoothGatt.writeDescriptor() return sometimes false.
And your sleep won't solve anything.

Network Service Discovery Wrong IP resolved

I want to connect multiple Android devices via Network Service Discovery. I want to use a star topology. so on device should discover and the others a registering a service which should found by the discoverer. For this I took the NSD Chat (https://github.com/gauravcanon/NsdChat ) NsdHelper and changed it only a bit, so a callback gets called after resolving a service which establish a connection. This works in mostly if I am using three devices. two advertiser and one discoverer. The services getting resolved and a connection estabished. If I get an additional third advertiser it is crashing in 80% of all attempts. The reason is that the serviceInfo which is passed in the onServiceResolved function in the ResolveListener contains the ip address of the resolving phone and not of the advertising one. The port is correct and the servicename also. That is such a strange behaviour, I don't know how to debug this. I'm using the bonjour browser to see all registered services and the registration of all services is fine. All service infos containing the right ip address and port. I also tried the the pure discovering process without establishing a connection. Same failure. Sometimes the ip address of the resolver is in the serviceinfo. It can also happen at the first discovery and the second, but most likely it is on the third one.
I will post my NsdHelper code below. I made some edits right now because i tried to start the discovery process again after resolving, so there are more differences to the NSD Chat, but the error persists.
Is someone using the NSD implementation of Android via the NSDManager with multiple devices? Is it working for you? What are you doing different?
This topic is related there someone had the same problems ( Android, NSD/DNS-SD: NsdManager unreliable discovery and IP resolution ). I cant imagine that this error is still a thing 3 years ago?
I'm thankful for every hint!
class NsdHelper(private var mContext: Context, private val createClient: (InetAddress, Int)->Unit, val mHandler: Handler) {
internal var mNsdManager: NsdManager = mContext.getSystemService(Context.NSD_SERVICE) as NsdManager
internal lateinit var mResolveListener: NsdManager.ResolveListener
private var mDiscoveryListener: NsdManager.DiscoveryListener? = null
private var mRegistrationListener: NsdManager.RegistrationListener? = null
var mServiceName = "Wizard"
var chosenServiceInfo: NsdServiceInfo? = null
internal set
val mServices = mutableListOf<NsdServiceInfo>()
fun initializeNsd() {
stopDiscovery()
tearDown()
initializeResolveListener()
}
fun reset(){
initializeResolveListener()
discoverServices()
}
fun initializeDiscoveryListener() {
mDiscoveryListener = object : NsdManager.DiscoveryListener {
override fun onDiscoveryStarted(regType: String) {
Log.d(TAG, "Service discovery started")
}
override fun onServiceFound(service: NsdServiceInfo) {
Log.d(TAG, "Service discovery success$service")
when {
service.serviceType != SERVICE_TYPE -> Log.d(TAG, "Unknown Service Type: " + service.serviceType)
service.serviceName == mServiceName -> Log.d(TAG, "Same machine: $mServiceName")
service.serviceName.contains("Verzauberte") -> {
if (mServices.none { it.serviceName == service.serviceName })
mNsdManager.resolveService(service, mResolveListener)
}
}
}
override fun onServiceLost(service: NsdServiceInfo) {
Log.e(TAG, "service lost$service")
}
override fun onDiscoveryStopped(serviceType: String) {
Log.i(TAG, "Discovery stopped: $serviceType")
}
override fun onStartDiscoveryFailed(serviceType: String, errorCode: Int) {
Log.e(TAG, "Discovery failed: Error code:$errorCode")
}
override fun onStopDiscoveryFailed(serviceType: String, errorCode: Int) {
Log.e(TAG, "Discovery failed: Error code:$errorCode")
}
}
}
fun initializeResolveListener() {
mResolveListener = object : NsdManager.ResolveListener {
override fun onResolveFailed(serviceInfo: NsdServiceInfo, errorCode: Int) {
Log.e(TAG, "Resolve failed$errorCode")
}
override fun onServiceResolved(serviceInfo: NsdServiceInfo) {
Log.e(TAG, "Resolve Succeeded. $serviceInfo")
if (serviceInfo.serviceName == mServiceName) {
Log.d(TAG, "Same IP.")
return
}
chosenServiceInfo = serviceInfo
mHandler.post(Runnable {
createClient(
serviceInfo.host,
serviceInfo.port
)
})
mServices.add(serviceInfo)
reset()
}
}
}
fun initializeRegistrationListener() {
mRegistrationListener = object : NsdManager.RegistrationListener {
override fun onServiceRegistered(NsdServiceInfo: NsdServiceInfo) {
mServiceName = NsdServiceInfo.serviceName
Log.d(TAG, "Service registered: $mServiceName")
}
override fun onRegistrationFailed(arg0: NsdServiceInfo, arg1: Int) {
Log.d(TAG, "Service registration failed: $arg1")
}
override fun onServiceUnregistered(arg0: NsdServiceInfo) {
Log.d(TAG, "Service unregistered: " + arg0.serviceName)
}
override fun onUnregistrationFailed(serviceInfo: NsdServiceInfo, errorCode: Int) {
Log.d(TAG, "Service unregistration failed: $errorCode")
}
}
}
fun registerService(port: Int) {
tearDown() // Cancel any previous registration request
initializeRegistrationListener()
val serviceInfo = NsdServiceInfo().apply {
serviceType = SERVICE_TYPE
serviceName = "Verzauberte[$port]"
setPort(port)
}
mNsdManager.registerService(
serviceInfo, NsdManager.PROTOCOL_DNS_SD, mRegistrationListener
)
}
fun discoverServices() {
stopDiscovery() // Cancel any existing discovery request
initializeDiscoveryListener()
Log.d(this.toString(), "Start discovering")
mNsdManager.discoverServices(
SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, mDiscoveryListener
)
}
fun stopDiscovery() {
if (mDiscoveryListener != null) {
try {
mNsdManager.stopServiceDiscovery(mDiscoveryListener)
} finally {
}
mDiscoveryListener = null
}
}
fun tearDown() {
if (mRegistrationListener != null) {
try {
mNsdManager.unregisterService(mRegistrationListener)
} finally {
}
mRegistrationListener = null
}
}
companion object {
val SERVICE_TYPE = "_votinginteractions._tcp."
val TAG = "NsdHelper"
}
}

Categories

Resources