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

Android Kotlin Sample code for DFU

I need a solution for doing a DFU from an Android phone to a device with a nRF52832 chip in it

I currently have a working version in IOS Swift but cannot get the Android sample code to trigger a DFU.

I have created the following code which triggers a DFU call when you click an item in the list.

Looking for someone with experience doing this who can help me get this up and running and suggestions for ways to get it working

If you have done this before and help, I have consulting funds available

Code I have put together for the activity and the onItemClickListener contains the code that I think is supposed to trigger a DFU

package com.athleteintelligence.aiteam.ui

import android.app.AlertDialog
import android.app.LoaderManager
import android.app.NotificationManager
import android.content.Context
import android.content.CursorLoader
import android.content.Loader
import android.database.Cursor
import android.net.Uri
import android.os.Bundle
import android.os.Handler
import android.provider.MediaStore
import android.support.v7.app.AppCompatActivity
import android.text.TextUtils
import android.util.Log
import android.view.LayoutInflater
import android.widget.*
import com.athleteintelligence.aiteam.R
import com.athleteintelligence.aiteam.adapter.FirmwareListAdapter
import com.athleteintelligence.aiteam.ble.BLEManager
import com.athleteintelligence.aiteam.ble.BLEManagerListener
import com.athleteintelligence.aiteam.ble.CueInspector
import com.athleteintelligence.aiteam.common.Common
import com.athleteintelligence.aiteam.model.Cue
import com.athleteintelligence.aiteam.model.Device
import com.athleteintelligence.aiteam.service.DeviceService
import com.athleteintelligence.aiteam.service.DfuService
import no.nordicsemi.android.dfu.DfuBaseService
import no.nordicsemi.android.dfu.DfuProgressListenerAdapter
import no.nordicsemi.android.dfu.DfuServiceInitiator
import no.nordicsemi.android.dfu.DfuServiceListenerHelper
import java.io.File
import java.util.*

/*
* Firmware update where user sees list of devices that need to be updated
*/
class FirmwareActivity : AppCompatActivity(), BLEManagerListener, LoaderManager.LoaderCallbacks<Cursor> {

//region Declarations

private var mTag = "${Common.applicationTag} GatherFragment"

private lateinit var listView: ListView
private lateinit var listViewAdapter: FirmwareListAdapter
private lateinit var backButton: ImageButton
private lateinit var refreshButton: ImageButton
private lateinit var updateAllButton: Button

private var mDevices = Array(0) { Device() }
private var mCues = Array(0) { Cue() }

private var mScanDialog: AlertDialog? = null
private var mTimer: Timer? = null

private var mResumed: Boolean = false

private val mScope: Int? = null
private val mFileType = 0

private var mFileStreamUri: Uri? = Uri.fromFile(File("cueupdate.zip"))
private var mFilePath: String? = null

private val mInitFileStreamUri: Uri? = null
private val mInitFilePath: String? = null

// Currently selected device from which
// impacts are being gathered
private var mCurrentDevice = Device()

//endregion

//region Initialization


override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_firmware)

listView = this.findViewById(R.id.firmware_listView)

listView.setOnItemClickListener { _, _, _, id ->
onItemClickListener(id)
}

refreshButton = findViewById(R.id.activityfirmware_button_refresh)
refreshButton.setOnClickListener {
onRefreshClick()
}

updateAllButton = findViewById(R.id.activityfirmware_updateall)
updateAllButton.setOnClickListener {
onUpdateAllClick()
}

backButton = findViewById(R.id.activityfirmware_back)
backButton.setOnClickListener {
finish()
}

listViewAdapter = FirmwareListAdapter(this, mDevices)
listView.adapter = listViewAdapter

BLEManager.instance.bleManagerListener = this

}

override fun onResume() {
super.onResume()

mDevices = DeviceService.instance.allDevices.value

scanDevices()

}

//endregion

//region Navigation Event Handlers

/*
* Triggers when user clicks one of the devices in the list
*/
private fun onItemClickListener(id: Long) {

mCurrentDevice = mDevices[id.toInt()]
BLEManager.instance.connect(mCurrentDevice)

DfuServiceListenerHelper.registerProgressListener(this, mDfuProgressListener)

var numberOfPackets: Int
try {
numberOfPackets = 12
} catch (e: NumberFormatException) {
numberOfPackets = DfuServiceInitiator.DEFAULT_PRN_VALUE
}


val starter = DfuServiceInitiator(mCurrentDevice.cue?.peripheral?.address)
.setDeviceName(mCurrentDevice.cue?.peripheral?.name)
.setKeepBond(false)
.setForceDfu(false)
.setPacketsReceiptNotificationsEnabled(false)
.setPacketsReceiptNotificationsValue(numberOfPackets)
.setUnsafeExperimentalButtonlessServiceInSecureDfuEnabled(true)

starter.setZip(R.raw.cueupdate)

if (mFileType == DfuBaseService.TYPE_AUTO) {

starter.setZip(R.raw.cueupdate)
if (mScope != null)
starter.setScope(mScope)
} else {
starter.setBinOrHex(mFileType, mFileStreamUri, mFilePath).setInitFile(mInitFileStreamUri, mInitFilePath)
}

starter.start(this, DfuService::class.java)
}

/*
* Finds the CUEs that are broadcasting
*/
private fun onRefreshClick() {

scanDevices()

}

/*
* Triggers a firmware update of the available CUEs
*/
private fun onUpdateAllClick() {

}

//endregion

//region DfuProgressListener

/**
* The progress listener receives events from the DFU Service.
* If is registered in onCreate() and unregistered in onDestroy() so methods here may also be called
* when the screen is locked or the app went to the background. This is because the UI needs to have the
* correct information after user comes back to the activity and this information can't be read from the service
* as it might have been killed already (DFU completed or finished with error).
*/
private val mDfuProgressListener = object : DfuProgressListenerAdapter() {

override fun onDeviceConnecting(deviceAddress: String?) {

Log.i(mTag, "Connecting")

}

override fun onDfuProcessStarting(deviceAddress: String?) {

Log.i(mTag, "Starting Firmware Update")

}

override fun onEnablingDfuMode(deviceAddress: String?) {

Log.i(mTag, "Starting Bootloader")
}

override fun onFirmwareValidating(deviceAddress: String?) {

Log.i(mTag, "Validating")
}

override fun onDeviceDisconnecting(deviceAddress: String?) {

Log.i(mTag, "Disconnecting")
}

override fun onDfuCompleted(deviceAddress: String?) {

Log.i(mTag, "Completed")

if (mResumed) {
// let's wait a bit until we cancel the notification. When canceled immediately it will be recreated by service again.
Handler().postDelayed({
// onTransferCompleted();

// if this activity is still open and upload process was completed, cancel the notification
val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
manager.cancel(DfuBaseService.NOTIFICATION_ID)
}, 200)
} else {

// Save that the DFU process has finished
}
}

override fun onDfuAborted(deviceAddress: String?) {

Log.e(mTag, "Aborted")

// let's wait a bit until we cancel the notification. When canceled immediately it will be recreated by service again.
Handler().postDelayed({
// onUploadCanceled();

// if this activity is still open and upload process was completed, cancel the notification
val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
manager.cancel(DfuBaseService.NOTIFICATION_ID)
}, 200)

}

override fun onProgressChanged(deviceAddress: String?, percent: Int, speed: Float, avgSpeed: Float, currentPart: Int, partsTotal: Int) {

if (partsTotal > 1)
Log.i(mTag, "Uploading part ${currentPart}/${partsTotal}")
else {
Log.i(mTag, "Uploading")
}
}

override fun onError(deviceAddress: String?, error: Int, errorType: Int, message: String?) {
if (mResumed) {
// showErrorMessage(message);

// We have to wait a bit before canceling notification. This is called before DfuService creates the last notification.
Handler().postDelayed({
// if this activity is still open and upload process was completed, cancel the notification
val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
manager.cancel(DfuBaseService.NOTIFICATION_ID)
}, 200)
} else {
Log.e(mTag, message)
}
}
}
//endregion

//region LoaderManager Handlers

override fun onCreateLoader(id: Int, args: Bundle?): Loader<Cursor> {
val uri = args?.getParcelable<Uri>("uri")
// final String[] projection = new String[] { MediaStore.MediaColumns.DISPLAY_NAME, MediaStore.MediaColumns.SIZE, MediaStore.MediaColumns.DATA };
return CursorLoader(this, uri, null, null, null, null)/* all columns, instead of projection */
}

override fun onLoadFinished(loader: Loader<Cursor>?, data: Cursor?) {
if (data != null && data.moveToNext()) {
/*
* Here we have to check the column indexes by name as we have requested for all. The order may be different.
*/
val fileName = data.getString(data.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME)/* 0 DISPLAY_NAME */)
val fileSize = data.getInt(data.getColumnIndex(MediaStore.MediaColumns.SIZE) /* 1 SIZE */)
var filePath: String? = null
val dataIndex = data.getColumnIndex(MediaStore.MediaColumns.DATA)
if (dataIndex != -1)
filePath = data.getString(dataIndex /* 2 DATA */)
if (!TextUtils.isEmpty(filePath))
mFilePath = filePath

// updateFileInfo(fileName, fileSize, mFileType);
// if file is not a cue update file then don't allow further dfu progress
if (!fileName.contains("Cue")) {
Log.d(mTag, "Not a valid cue update file")
mFilePath = null
mFileStreamUri = null
}

} else {
mFilePath = null
mFileStreamUri = null
}

}

override fun onLoaderReset(loader: Loader<Cursor>?) {
mFileStreamUri = null
}

//endregion

//region BLEListener Event Handlers

/*
* Result of calling BLEManager.shared.startScanning()
* @cue: The discovered device
*/
override fun didDiscoverCue(cue: Cue) {

mCues = BLEManager.instance.discoveredCues()

}

/*
* Result of calling connectToDevice:
* @cueInspector: Associated device inspector for handling ble events
* @device: The connected cue
*/
override fun didConnect(cueInspector: CueInspector, device: Device) {


}

/*
* Result of disconnecting from a cue :
* @cue: The disconnected cue
*/
override fun didDisconnect(device: Device) {

}

//endregion

//region Devices and Scan

/*
* Arranges list of CUEs according to mActiveLast flag with false meaning active first, and false active last
*/
private fun sortDevices() {


mDevices.sortWith(compareByDescending(Device::IsActive))
listViewAdapter.updateData(mDevices)

}

/*
* Triggers a scan of available devices
*/
private fun scanDevices() {

if(!BLEManager.instance.Enabled) {

Toast.makeText(this, "You must have bluetooth enabled", Toast.LENGTH_SHORT).show()

} else {

BLEManager.instance.startScan()

mDevices.forEach {
it.cue = null
it.IsActive = false
}

var factory = LayoutInflater.from(this)
var view = factory.inflate(R.layout.loading_dialog, null)
mScanDialog = AlertDialog.Builder(this).create()
mScanDialog?.setView(view)

var messageView = view.findViewById<TextView>(R.id.loadingDialog_textView_message)
messageView.text = getString(R.string.Looking_for_your_sensors)

var cancelButton: Button = view.findViewById(R.id.loadingDialog_button_cancel)
cancelButton.setOnClickListener {

mTimer?.cancel()
mTimer?.purge()

BLEManager.instance.stopScan()
mScanDialog?.dismiss()


}

mScanDialog?.show()

var handler = Handler()
handler.postDelayed(handleScanResults(handler), 15000)

}

}

/*
* Provides logic for handling closing down a scan showing results
*/
private fun handleScanResults(handler: Handler) : Runnable {

return Runnable {

try {

BLEManager.instance.stopScan()
mCues = BLEManager.instance.discoveredCues()

Log.i(mTag, "******************************** ${mCues.size} found ********************************")

mCues.forEach {

val device: Device? = mDevices.firstOrNull {
d -> d.VendorId?.toLowerCase() == it.vendorId?.toLowerCase()
}

if(device == null ) {
Log.e(mTag, "Could not find ${it.vendorId}")
} else {
device.cue = it
device.IsActive = true
}
}

mDevices = mDevices.filter { i -> i.DeviceTypeId == Common.CUEDeviceTypeId && !(i.FirmwareVersion?.currentVersion(Common.firmwareVersion)!!) }.toTypedArray()

sortDevices()


mScanDialog?.dismiss()


} catch(e: Exception) {
Log.e(mTag, e.toString())
mScanDialog?.dismiss()
}
}

}

//endregion

}
Related