Monday 7 December 2020

Questions on creating a service that connects automatically to a BLE device on android

I am implementing a service that uses the autoconnect feature of bluetoothGatt to connect to the device and monitor it while it is being connected.

I work on the assumption that the device is already bonded (a coworker is responsible for that part) so autoconnect should not have any problems

my code is as follows:

//the callback is for the class I have created that actually does the connection
class BTService: Service(), CoroutineScope, BTConnection.Callback {
    private val btReceiver by lazy { BluetoothStateReceiver(this::btStateChange) } //receiver for bt adapter changes

    private var connection:BTConnection? = null
    private var readJob:Job? = null

    override fun onCreate() {
        buildNotificationChannels()
        registerReceiver(btReceiver, IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED)) //since I can't register this receiver in AndroidManifest any more I did it here
    }

    private fun btStateChange(enabled: Boolean) {
        if (enabled)
            startConnecting()
        else
            stopConnection()
    }

    private fun startConnecting() {
        
        val address = prefs.address //get the current saved address
        val current = connection //get the current connection

        //try to stop the current connection if it is different than the one we want to set up
        if (current != null && !current.address.equals(address, true))
            current.stop()

        if (address.isNullOrBlank())
            return
        //then we create a new connection if needed
        val new = if (current == null || !current.address.equals(address, true)) {
            Injections.buildConnection(application, address, this)
        } else {
            current
        }
        connection = new
        new.connect()
    }

    //this is one of the callbacks from BTConnection.Callback
    override fun connected(address: String) {
        if (address != connection?.address) return
        val cn = connection ?: return
        showConnectionNotification()
        val notification = buildForegroundNotification()
        startForeground(FOREGROUND_ID, notification)
        readJob?.cancel()
        readJob = launch {
             cn.dataFlow //this is a flow that will be emmitting read data
             .cancellable() 
             .flowOn(Dispatchers.IO)
             .buffer()
             .onEach(this@BTService::parseData)
             .flowOn(Dispatchers.Default)
        }
    }


    private suspend fun parseData(bytes:ByteArray) { //this is where the parsing and storage etc happens
}

private fun stopConnection() {
    val cn = connection
    connection = null
    cn?.stop()
}
 
override fun disconnected(address: String) { //another callback from the connection class
    showDisconnectNotification()
    stopForeground(true)
}

my code that stops the connection is

fun stop() {
    canceled = true
    if (connected)
        gatt?.disconnect()
    launch(Dispatchers.IO) {
        delay(1000)
        gatt?.close()
        gatt = null
    }
}

my code is based (and affected) by this really good article I read:

https://medium.com/@martijn.van.welie/making-android-ble-work-part-2-47a3cdaade07

I have also created a receiver for boot events that will call

 context.startService(Intent(context, BTService::class.java))

just to make sure that the service is created at least once and the bt receiver is registered

my questions are:

a) is there a chance that my service will be destroyed while it is not in foreground mode? i.e. when the device is not near by and bluetoothGat.connect is suspending while autoconnecting? is it enough for me to return START_STICKY from onStartCommand() to make sure that even when my service is destroyed it will start again?

b) if there is such a case, is there a way to at least recreate the service so the btReceiver is at least registered?

c) when should close() be called on bluetoothGatt in case of autoconnect = true? only when creating a new connection (in my example where I call Injections.buildConnection)? do I also call it when the bluetoothadapter is disabled? or can I reuse the same connection and bluetoothGatt if the user turns the bluetooth adapter off and on again?

d) is there a way to find out if autoconnect has failed and will not try again? and is there a way to actually test and reproduce such an effect? the article mentioned above says it can happen when the batteries of the peripheral are almost empty, or when you are on the edge of the Bluetooth range

thanks in advance for any help you can provide



from Questions on creating a service that connects automatically to a BLE device on android

No comments:

Post a Comment