This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

OTA DFU in background with no notification?

Hi, I have implemented OTA DFU on Android with your official DFU library. We want the DFU to happen in background at a certain trigger (car driving, triggered from device = user will stay near device) and without bothering the user that this is happening. My implementation was all good and working fine when the app was in the foreground while this was happening, but since we don't expect the user to be staring at his phone while driving - this needs to happen in the background of course. Problem is, when I tested this the following happend:

SDK 15.3 softdevice s132.

starter created
controller initiated
listener registred
onDfuProcessStarting FA:11:1B:B1:D7:C4
onEnablingDfuMode
onDeviceConnecting
DfuBaseService: Connection state change error: 133 newState: 0
DfuBaseService: Device not reachable. Check if the device with address FA:11:1B:B1:D7:C4 is in range, is advertising and is connectable
DfuBaseService: Connection state change error: 133 newState: 0
DfuBaseService: Device not reachable. Check if the device with address FA:11:1B:B1:D7:C4 is in range, is advertising and is connectable
DfuBaseService: Connection state change error: 133 newState: 0
DfuBaseService: Device not reachable. Check if the device with address FA:11:1B:B1:D7:C4 is in range, is advertising and is

onError 133 type 1 message GATT ERROR

It is implemented in the following way.

a Presenter class contains the function scheduleDFU

private fun scheduleDFU() {
        val fwFile = File(context.getExternalFilesDir(null).toString() + File.separator.toString() + "fw.zip")
        Log.d("DFUTEST","scheduleDFU with " + fwFile.absolutePath + " " + fwFile.absolutePath)

        val starter = deviceComm.getConnectedDevice()?.address?.let {
            DfuServiceInitiator(it)
                .setDeviceName(deviceComm.getConnectedDevice()?.name)
                .setKeepBond(true) // keep bond after dfu
                .setForceDfu(true)
                .setPacketsReceiptNotificationsEnabled(false)
                .setDisableNotification(true) // no foreground notification
                .setForeground(false)
                .setPrepareDataObjectDelay(400L)
        }

        starter!!.setZip(fwFile.toUri(), fwFile.absolutePath)
        Log.d("DFUTEST","starter created")
        
        dfuServiceController = starter!!.start(context, DfuService::class.java)
        Log.d("DFUTEST","controller initiated")
        
        DfuServiceListenerHelper.registerProgressListener(context, dfuProgressListener)
        Log.d("DFUTEST","listener registred")

    }

I didn't want any notifications, and disabled that as you can see above and so the implementation of DfuBaseService (as pr your examples) is very bare bones:

class DfuService : DfuBaseService() {
    @Override
    protected override fun getNotificationTarget(): Class<out Activity> {
        Log.d("DFUTEST","getNotificationTarget")
        return NotificationActivity::class.java
    }

    @Override
    protected override fun isDebug(): Boolean {
        return super.isDebug()
    }

    @Override
    protected override fun updateForegroundNotification(builder: NotificationCompat.Builder) {
        Log.d("DFUTEST","updateForegroundNot")
        super.updateForegroundNotification(builder)
    }
}

The progress listener is also pretty simple, all it does is log and unsubscribe itself

private val dfuProgressListener: DfuProgressListener = object : DfuProgressListenerAdapter() {
        override fun onDeviceConnecting(deviceAddress: String) {
            Log.d("DFUTEST","onDeviceConnecting")
        }

        override fun onDfuProcessStarting(deviceAddress: String) {
            App.dfuInProgress = true
            Log.d("DFUTEST","onDfuProcessStarting " + deviceAddress )
        }

        override fun onEnablingDfuMode(deviceAddress: String) {
            Log.d("DFUTEST","onEnablingDfuMode")
        }

        override fun onFirmwareValidating(deviceAddress: String) {
            Log.d("DFUTEST","onFirmwareValidating")
        }

        override fun onDeviceDisconnecting(deviceAddress: String) {
            Log.d("DFUTEST","onDeviceDisconnecting " + deviceAddress)
            //DfuServiceListenerHelper.unregisterProgressListener(context, this)
        }

        override fun onDfuCompleted(deviceAddress: String) {
            Log.d("DFUTEST","onDfuCompleted")
            App.dfuInProgress = false
            DfuServiceListenerHelper.unregisterProgressListener(context, this)
        }

        override fun onDfuAborted(deviceAddress: String) {
            Log.d("DFUTEST","onDfuAborted")
            DfuServiceListenerHelper.unregisterProgressListener(context, this)
        }
        override fun onProgressChanged(
            deviceAddress: String,
            percent: Int,
            speed: Float,
            avgSpeed: Float,
            currentPart: Int,
            partsTotal: Int
        ) {
            Log.d("DFUTEST","onProgressChanged / " + percent)
        }
        override fun onError(deviceAddress: String, error: Int, errorType: Int, message: String) {
            Log.d("DFUTEST","onError " + error + " type " + errorType + " message " + message)
        }
    }

So my question:

I assume this has to do with me disabling the foreground notification when setting up the DfuServiceInitiator, but I was under the impression that it was possible to do this without a notification?

Best

Søren

Parents Reply Children
  • I see... our app normally reconnects to a user's device when he gets near it (while the phone is in the pocket and screen off) but I guess that must be the autoconnect that handles that, and not any scan results.. couldn't the DFU process be able to reconnect back to the device during DFU instead of using a Scan ? I mean, it's a known device which it had a connection to just seconds ago.. might be stupid questions here, I don't know

  • No question is stupid Slight smile. The reason we don't use autoconnect is that when the device goes into bootloader mode the address of the device changes, hence the scan and connect. Usually address is incremented by 1 when it goes in to bootloader mode. In addition the address of a device can change during every power recycle causing the address to remain unknown.

  • Ok.. can I ask also, how does a Service and the state of the display affect each other? Shouldn't a foreground service care less about the state of the screen?

    and lastly, are you saying that we simply need to make sure that OTA DFU happens in an approach where the user keeps the screen on ? It is simply not possible with the screen turned off, is what I'm hearing you say?

  • Seems like an android limitation from what I read from the documentation, if the screen is turned off your scanning will not return scan results irrespective of the service type.

    Once the connection is established it doesn't matter if the screen is or not and as long as the service is on the foreground android will not try to kill it.

    Yours is a very specific case which needs some research. There is one more issue you might face in your approach of background DFU upload is that, user needs to grant location permission for scanning, with the new permission scheme, a user might select grant location permission "while app in use" option. I'm not entirely sure how it will behave in this scenario and you will definitely need background location permission. Oh and btw have you granted background location permission ? Could you just try with that and see if that gives you any scan result? After all that could be a reason. I assumed if this is in place already. You may refer this.

  • Thanks for your support Roshan, it's very kind of you. 

    Again, I don't understand however, how this should affect our app since we do not use unfiltered scans. We are filtering all scans with the name of our device, always. So this doesn't really apply to our case. It should still receive scan results in screen-off mode. 

    Regarding the permission, this has been granted by the user during onboarding (and also on the phones that I'm testing on here at the office) but I don't think this is relevant since we are running a foreground service for the app (with a notification). All scanning is handled in this service. 

Related