Skip to main content

DFU SDK

The dfu module performs Device Firmware Upgrades on a Corsano bracelet. The firmware images ship inside the module itself, so an upgrade needs no download and no network connection — you tell the SDK which device to upgrade and it uploads the bundled image over BLE.

implementation "com.corsano.sdk:dfu:$corsanoSdkVersion"

The module declares its own DfuService and the Bluetooth and foreground-service permissions it needs, and these are merged into your app at build time. You do not have to declare the service yourself.

Setting up

Pass a CorsanoDfu.Config when you initialise the SDK. See Getting Started.

CorsanoDfu.Config

ParameterTypeDefaultDescription
notificationTargetClass<out Activity>?nullThe activity opened when the user taps the DFU notification.
loggerSdkLogger?nullModule-specific logger. Falls back to the logger on CorsanoSdkConfig.

Once initialised, everything goes through the singleton:

val dfu = CorsanoDfu.getInstance()

Note: getInstance() throws IllegalArgumentException if the DFU module was not initialised — that is, if no CorsanoDfu.Config was added to CorsanoSdkConfig.

The hardwareId parameter

Every method below takes a hardwareId as an Int, not as the enum. Read it from the connected device and pass its .value:

val hardwareId = currentDevice.deviceInfo.hardwareId?.value ?: HardwareId.UNKNOWN.value
HardwareIdValueDevice
WATCH_284N38Watch 284N
WATCH_287N50Watch 287N
BRACELET_287N51CardioWatch 287-1B ("B1")
BRACELET_287N_2B54CardioWatch 287-2B ("B2")
UNKNOWN-1Unrecognised

Warning: Firmware images are bundled only for B1 (51) and B2 (54). Any other value — including UNKNOWN — makes needDfu, getLatestFirmwareVersion and getAvailableVersion throw. Always check the hardware ID is one of the two supported bracelets before calling them.

Methods

needDfu

fun needDfu(hardwareId: Int, currentVersion: FirmwareVersion): Boolean

Whether a newer firmware than currentVersion is bundled for this hardware. Equivalent to currentVersion < getLatestFirmwareVersion(hardwareId).

ParameterTypeDescription
hardwareIdIntHardware ID of the bracelet
currentVersionFirmwareVersionFirmware currently on the bracelet

Returns Booleantrue if an upgrade is available. Throws Error if hardwareId is not a supported bracelet.

val needsUpgrade = CorsanoDfu.getInstance().needDfu(
currentDevice.deviceInfo.hardwareId!!.value,
currentDevice.deviceInfo.firmwareVersion!!
)

getLatestFirmwareVersion

fun getLatestFirmwareVersion(hardwareId: Int): FirmwareVersion

The newest firmware version bundled for this hardware.

Returns FirmwareVersion. Throws Error if hardwareId is not a supported bracelet.

val latest = CorsanoDfu.getInstance().getLatestFirmwareVersion(hardwareId)
Log.d("Corsano-SDK", "Latest available: ${latest.text}")

getAvailableVersion

@JvmStatic
fun getAvailableVersion(hardwareId: Int): FirmwareVersion

Identical result to getLatestFirmwareVersion, but static — usable before the DFU module has been initialised.

Returns FirmwareVersion. Throws Error if hardwareId is not a supported bracelet.

val latest = CorsanoDfu.getAvailableVersion(hardwareId)

startDfu

fun startDfu(address: String, hardwareId: Int)

Uploads the latest bundled firmware for this hardware.

ParameterTypeDescription
addressStringMAC address of the bracelet
hardwareIdIntHardware ID of the bracelet

Returns nothing. Progress and completion arrive through DfuListener. Throws IllegalStateException if a DFU is already in progress; Error if hardwareId is not a supported bracelet.

CorsanoDfu.getInstance().startDfu(address, hardwareId)

Sample app: DfuFragment.kt in the sample module (sdk/sample/src/main/java/com/corsano/sdk/sample/screens/dfu/) is a complete working example. It calls startDfu(address, hardwareId) from the start button's click listener, and shows the surrounding lifecycle you need:

override fun onStart() {
super.onStart()

val corsanoDfu = CorsanoDfu.getInstance()

// restore the UI from the current state, in case a DFU is already running
renderState(corsanoDfu.getDfuState())

corsanoDfu.addDfuListener(this)

binding.startButton.setOnClickListener {
corsanoDfu.startDfu(args.address, args.hardwareId)
}
}

override fun onStop() {
CorsanoDfu.getInstance().removeDfuStatusListener(this)
super.onStop()
}

The fragment implements DfuListener and every callback simply re-reads getDfuState() and re-renders — a single rendering path for both live updates and state restored after the screen is recreated.

startDfu (specific version)

fun startDfu(address: String, hardwareId: Int, specificVersion: FirmwareVersion)

Uploads a specific bundled firmware version instead of the latest — useful for pinning a fleet to a validated release, or for downgrading.

ParameterTypeDescription
addressStringMAC address of the bracelet
hardwareIdIntHardware ID of the bracelet
specificVersionFirmwareVersionThe exact version to install

Returns nothing. Throws IllegalStateException if a DFU is already in progress; IllegalArgumentException if that version is not bundled for that hardware.

Warning: Specific versions are available for B2 only. Calling this for a B1 always throws IllegalArgumentException, whatever version you ask for.

Bundled B2 versions: 5.39, 5.72, 5.82, 6.45, 6.65, 6.87, 7.14, 7.20, 7.23, 7.34, 7.48, 7.65, 7.97, 8.12, 8.26, 8.48.

CorsanoDfu.getInstance().startDfu(address, hardwareId, FirmwareVersion(8, 26))

Sample app: the same DfuFragment.kt carries this overload as a commented-out alternative next to the call to the latest-version one.

getDfuState

fun getDfuState(): DfuState

The current state, for when you need to poll rather than listen — for example to restore your UI after a configuration change.

Returns DfuState. Before any DFU has run, its status is DfuStatus.NONE.

val state = CorsanoDfu.getInstance().getDfuState()
if (state.isInProgress) showProgress(state.progressPercentage)

addDfuListener / removeDfuStatusListener

fun addDfuListener(listener: DfuListener)
fun removeDfuStatusListener(listener: DfuListener)

Register and unregister a DfuListener. Note the asymmetric names: you add a DfuListener but remove a DfuStatusListener.

override fun onStart() {
super.onStart()
CorsanoDfu.getInstance().addDfuListener(this)
}

override fun onStop() {
CorsanoDfu.getInstance().removeDfuStatusListener(this)
super.onStop()
}

extractDfuDeviceAddress

@JvmStatic
fun extractDfuDeviceAddress(intent: Intent?): String?

Reads the bracelet's MAC address out of the intent delivered when the user taps the DFU notification, so the activity named by notificationTarget can tell which device the notification referred to.

Returns the MAC address, or null if the intent carries none.

val address = CorsanoDfu.extractDfuDeviceAddress(intent)

DfuListener

interface DfuListener {
fun onDfuStatusChanged(address: String, status: DfuStatus)
fun onUploadProgress(address: String, percentage: Int, part: Int, totalParts: Int)
fun onDfuCompleted(address: String)
fun onDfuFailed(address: String, error: DfuError)
fun onDfuAborted(address: String)
}
CallbackParametersWhen
onDfuStatusChangedaddress, statusEvery time the status changes. Fires for each transition in the table below.
onUploadProgressaddress, percentage (0–100), part, totalPartsRepeatedly while the image uploads.
onDfuCompletedaddressThe upgrade finished successfully.
onDfuFailedaddress, errorThe upgrade failed.
onDfuAbortedaddressSee the warning below.

Warning: In the current SDK version onDfuAborted is never called. When a DFU is aborted the SDK notifies listeners through onDfuCompleted instead, even though the upgrade did not succeed. To detect an abort reliably, check the status rather than trusting the callback: either onDfuStatusChanged reporting DfuStatus.DFU_ABORTED, or getDfuState().isAborted. Do not treat onDfuCompleted alone as proof of success.

DfuState

data class DfuState(
val status: DfuStatus,
val address: String? = null,
val error: DfuError? = null,
val progressPercentage: Int = 0
)
MemberTypeDescription
statusDfuStatusWhere the process currently is
addressString?MAC address of the bracelet being upgraded
errorDfuError?Set when status is DFU_FAILED
progressPercentageIntUpload progress, 0–100
isInProgressBooleanThe process is still running
isSuccessfulBooleanstatus == DFU_COMPLETED
isFailedBooleanstatus == DFU_FAILED
isAbortedBooleanstatus == DFU_ABORTED

DfuStatus

Each value carries inProgress, which is what DfuState.isInProgress reports.

StatusIn progressMeaning
NONEnoNo DFU has been started
INITIATEDyesstartDfu accepted the request
CONNECTINGyesConnecting to the bracelet
CONNECTEDyesConnected
ENABLING_DFU_MODEyesSwitching the bracelet into DFU mode
DFU_STARTINGyesUpgrade starting
DFU_STARTEDyesUpgrade started
DFU_UPLOADINGyesUploading the image; progressPercentage updates
VALIDATING_FIRMWAREyesBracelet validating the uploaded image
DISCONNECTINGyesDisconnecting
DISCONNECTEDyesDisconnected
DFU_COMPLETEDnoFinished successfully
DFU_FAILEDnoFailed; see DfuError
DFU_ABORTEDnoAborted

DfuError

data class DfuError(val errorCode: Int, val message: String)
MemberTypeDescription
errorCodeIntError code from the underlying Nordic DFU library
messageStringHuman-readable description

FirmwareVersion

FirmwareVersion comes from the ble module and is Comparable, so versions can be compared directly.

MemberTypeDescription
majorIntMajor number
minorIntMinor number
numberIntmajor * 10000 + minor, used for ordering
textString"major.minor", e.g. "8.48"
if (currentVersion < FirmwareVersion(8, 26)) {
// firmware predates 8.26
}

Upload behaviour

The upload is configured by the SDK and is not adjustable:

  • The existing bond is kept, so the bracelet does not need re-pairing afterwards.
  • Failed uploads are retried up to 3 times before onDfuFailed.
  • DFU runs as a background service; a notification channel is created for it.

DFU recovery

A recovery process is necessary if a DFU started but was interrupted midway (Bluetooth switched off, app killed). It is rare but it happens. The bracelet will appear dead — lights off, even on the charger. Restarting the DFU from the app restores it.

Scenarios:

  • DFU interrupted, or app killed. Prompt the user to restart the DFU.
  • The patient uninstalled the app mid-upgrade. During scanning, look for a bracelet in recovery mode and prompt to restart the DFU.

A bracelet needing recovery is reported during the scan with ScanDeviceType.BRACELET_1B_NEED_RECOVERY or ScanDeviceType.BRACELET_2B_NEED_RECOVERY, and ScanDeviceType.needsRecovery is true for both. See Pairing for the scanning flow.

Recovery is an ordinary startDfu call against the recovered bracelet's address.