OTA Firmware Updates for IoT Devices via Mobile Applications
OTA (Over-The-Air) firmware update is a critical function for any IoT product. Firmware bugs, new protocols, security patches — all must be delivered to devices without physical access. The mobile application either initiates the update or serves as the transport for transmitting firmware directly via BLE.
Two OTA Scenarios
Cloud OTA: The device downloads firmware from the server when it connects to Wi-Fi. The mobile app only notifies the user of available updates and shows progress. Update logic is device-side (ESP-IDF OTA, Mender, Hawkbit).
BLE OTA: Firmware is downloaded to the phone, then transmitted to the device via BLE. Used when the device has no direct internet access or when strict control over the update process is needed.
BLE OTA: DFU for Nordic nRF
For nRF51/nRF52 devices — Nordic DFU (Device Firmware Update). Official library from Nordic Semiconductor:
// build.gradle
implementation 'no.nordicsemi.android:dfu:2.3.0'
// Start DFU
val starter = DfuServiceInitiator(deviceAddress)
.setDeviceName(deviceName)
.setKeepBond(true)
.setForceDfu(false)
.setPacketsReceiptNotificationsEnabled(true)
.setNumberOfPackets(12) // PRN - balance speed and reliability
.setZip(firmwareUri) // .zip with firmware and init packet
val controller = starter.start(context, DfuService::class.java)
setPacketsReceiptNotificationsEnabled(true) + setNumberOfPackets(12) — device acknowledges every 12 packets. Without PRN, losing one packet means restarting everything. With PRN — resume from the last confirmed position.
The DFU library starts DfuService as a foreground service — user can minimize the app, update continues. Track progress via DfuProgressListenerHelper:
DfuProgressListenerHelper.registerProgressListener(this, object : DfuProgressListener {
override fun onDfuProgressChanged(deviceAddress: String, percent: Int,
speed: Float, avgSpeed: Float,
currentPart: Int, partsTotal: Int) {
updateProgress(percent)
}
override fun onDfuCompleted(deviceAddress: String) { onUpdateSuccess() }
override fun onError(deviceAddress: String, error: Int, errorType: Int, message: String) {
onUpdateFailed(message)
}
})
Typical DFU speed: 50–80 KB/s for nRF52840. 200 KB firmware — about 3 minutes.
ESP32 OTA via BLE
For ESP32 — esp_ota_ops on device side + custom BLE service for data reception. Espressif doesn't provide ready-made BLE DFU SDK (unlike Nordic), so the protocol must be implemented yourself or use esp-idf-ble-ota library.
Basic scheme: phone sends firmware in chunks by MTU-3 bytes. Device collects firmware in OTA buffer (esp_ota_begin, esp_ota_write, esp_ota_end), then reboots with new image. On error — rollback to previous version via esp_ota_mark_app_invalid_rollback_and_reboot().
// Split firmware into chunks and send
val chunkSize = mtu - 3
val chunks = firmware.toList().chunked(chunkSize)
chunks.forEachIndexed { index, chunk ->
writeCharacteristic(firmwareDataCharacteristic, chunk.toByteArray())
// Wait for ACK from device before next chunk
awaitAck()
updateProgress((index + 1) * 100 / chunks.size)
}
Important: never start OTA with phone battery below 20% or weak BLE signal. Interruption mid-firmware could brick the device if it lacks rollback mechanism.
Cloud OTA: Mobile App Role
With cloud OTA, the phone is UI only. User sees "Update 2.1.0 available", hits "Update", tracks progress.
Device sends update progress via MQTT or WebSocket. Statuses: idle → downloading (with percent) → applying → rebooting → updated / failed.
Don't show endless spinner. Update can take 5–15 minutes (download + flash write). Show concrete progress with stages. After reboot, device appears online with new firmware version — reflect this in UI immediately.
OTA Security
Firmware must be signed — device verifies signature before applying. RSA-2048 or ECDSA-256. With cloud OTA — HTTPS with certificate pinning against MITM. For BLE OTA — Nordic DFU init packet already contains hash and signature.
Without signature verification, any attacker with BLE access can flash malicious firmware.
Implementing BLE OTA with Nordic DFU: 2–3 weeks. Cloud OTA UI with progress monitoring: 1–2 weeks. Custom ESP32 BLE OTA protocol: 3–5 weeks.







