Skip to content

feat: Add firmware update module for Nordic nRF devices#3782

Merged
jamesarich merged 10 commits into
meshtastic:mainfrom
jamesarich:feat/dfu-firmware-update
Nov 24, 2025
Merged

feat: Add firmware update module for Nordic nRF devices#3782
jamesarich merged 10 commits into
meshtastic:mainfrom
jamesarich:feat/dfu-firmware-update

Conversation

@jamesarich

@jamesarich jamesarich commented Nov 22, 2025

Copy link
Copy Markdown
Collaborator

This commit introduces a new feature module (feature:firmware) to handle Device Firmware Updates (DFU) for Nordic nRF52-based devices over Bluetooth LE.

  • Module & Dependencies:

    • Added the :feature:firmware module and integrated it into the main app.
    • Included the Nordic DFU library (no.nordicsemi.android:dfu:2.10.1) and other necessary dependencies like Coil for image loading.
  • DFU Logic & ViewModel:

    • Implemented FirmwareUpdateViewModel to manage the entire update flow: - Fetches stable or alpha releases from FirmwareReleaseRepository. - Downloads the release artifact, showing progress. - Extracts the correct device-specific firmware from the downloaded zip archive. - Initiates the Nordic DFU process. - Supports updating from a local file (.zip).
    • Added a FirmwareFileHandler for downloading, extracting, and cleaning up firmware files.
    • Implemented a FirmwareDfuService as required by the Nordic DFU library, including a notification channel.
  • UI & Navigation:

    • Created FirmwareUpdateScreen with a comprehensive UI to guide the user through the update process, showing different states (checking, downloading, updating, success, error).
    • Added a Firmware Update entry in the device configuration screen, which is conditionally displayed for DFU-capable (nRF52) devices.
    • Implemented FirmwareRoutes and integrated the navigation graph into the application.
  • Resources & Configuration:

    • Added extensive string resources for all steps and states of the firmware update process.
    • Configured the Android Manifest with necessary permissions and the DFU service declaration.
Screen_recording_20251123_164232.mp4

This commit introduces a new feature module (`feature:firmware`) to handle Device Firmware Updates (DFU) for Nordic nRF52-based devices over Bluetooth LE.

- **Module & Dependencies**:
    - Added the `:feature:firmware` module and integrated it into the main app.
    - Included the Nordic DFU library (`no.nordicsemi.android:dfu:2.10.1`) and other necessary dependencies like Coil for image loading.

- **DFU Logic & ViewModel**:
    - Implemented `FirmwareUpdateViewModel` to manage the entire update flow:
        - Fetches stable or alpha releases from `FirmwareReleaseRepository`.
        - Downloads the release artifact, showing progress.
        - Extracts the correct device-specific firmware from the downloaded zip archive.
        - Initiates the Nordic DFU process.
        - Supports updating from a local file (`.zip`).
    - Added a `FirmwareFileHandler` for downloading, extracting, and cleaning up firmware files.
    - Implemented a `FirmwareDfuService` as required by the Nordic DFU library, including a notification channel.

- **UI & Navigation**:
    - Created `FirmwareUpdateScreen` with a comprehensive UI to guide the user through the update process, showing different states (checking, downloading, updating, success, error).
    - Added a `Firmware Update` entry in the device configuration screen, which is conditionally displayed for DFU-capable (nRF52) devices.
    - Implemented `FirmwareRoutes` and integrated the navigation graph into the application.

- **Resources & Configuration**:
    - Added extensive string resources for all steps and states of the firmware update process.
    - Configured the Android Manifest with necessary permissions and the DFU service declaration.

Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
@jamesarich jamesarich added this to the 2.7.8 milestone Nov 22, 2025
@github-actions github-actions Bot added the enhancement New feature or request label Nov 22, 2025
@jamesarich jamesarich marked this pull request as draft November 22, 2025 02:39
try {
// 1. Download
_state.value = FirmwareUpdateState.Downloading(0f)
val zipUrl = release.zipUrl.replace("esp32", "nrf52840")

@mdecourcy mdecourcy Nov 22, 2025

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couple questions here :

  • What if the URL doesn't contain "esp32"? It may silently continues with the wrong firmware
  • What if "esp32" appears multiple times in the URL?
  • This assumes the release structure for nrf52840 matches esp32, which may not be true

Users could flash completely wrong firmware to their devices

May need to add some defensive checks here

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May need to use the actual device hardware model to construct the correct firmware URL, and validate the firmware is for the correct hardware before flashing.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had some similar thought about how much do we need to hand-hold during the process.

_state.value = FirmwareUpdateState.Downloading(0f)
val zipUrl = release.zipUrl.replace("esp32", "nrf52840")

val downloadedZip =

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Few things here:

  • No checksum/hash verification of downloaded firmware
  • No signature validation
  • No verification that the firmware file is actually for the target device
  • A corrupted download or man-in-the-middle attack could brick the device

* 3. Initiates the DFU process.
*/
@Suppress("TooGenericExceptionCaught")
fun startUpdate() {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe add battery/RSSI validation before starting

.setDeviceName(deviceHardware.displayName)
.setKeepBond(true)
.setZip(Uri.fromFile(firmwareFile))
.setUnsafeExperimentalButtonlessServiceInSecureDfuEnabled(true)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

name leads me to think this isn't prod ready, I haven't read through nordic yet so unsure of implications

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The docs are more about how this dfu triggering method is "unsafe" overall (on the device side).

}
}

private fun cleanupTemporaryFiles() {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if app crashes during update, files remain

@DaneEvans

DaneEvans commented Nov 22, 2025

Copy link
Copy Markdown
Collaborator
  • Strings show "Downloading 0 %%"

  • and got a "could not find firmware for seeeeeeeed card tracker t1000E in release" error

    • for both stable and alpha builds.
  • Local firmware selection remains greyed out and can't select anything - at least with .uf2's (t1000E)

…itectures

This commit replaces a hardcoded firmware URL replacement with a dynamic function, `getDeviceFirmwareUrl`, to correctly construct download URLs for various hardware architectures.

Previously, the firmware download logic incorrectly assumed all non-ESP32 devices were `nrf52840`. The new implementation intelligently detects the architecture from the release URL and substitutes it with the target device's architecture.

- Implemented `getDeviceFirmwareUrl` to handle multiple architectures like `esp32-s3`, `esp32-c3`, `esp32-c6`, `nrf52840`, `rp2040`, and `stm32`.
- Updated `FirmwareUpdateViewModel` to use this function, ensuring the correct firmware package is downloaded based on the connected device's hardware.

Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
@jamesarich

Copy link
Copy Markdown
Collaborator Author
  • for both stable and alpha builds.
  • Local firmware selection remains greyed out and can't select anything - at least with .uf2's (t1000E)

needs to be a .zip - probably should have some instructions along with warnings

This commit enhances the firmware update process by adding download integrity checks and refining the firmware extraction logic to prevent mismatches.

- Added a check to verify that the downloaded file size matches the `content-length` header, throwing an `IOException` on mismatch.
- Improved the firmware extraction logic to prevent incorrect flashing due to partial name matches (e.g., preventing "tbeam" from matching "tbeam-s3").
- The new logic finds all potential firmware files for a device and selects the one with the shortest name as the best match.
- A comment was added to note the current lack of checksums in the API.

Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
The `firmware_update_updating` and `firmware_update_downloading` string resources already included a percentage sign (`%`), and the view model was appending an additional one. This resulted in a double percentage sign (e.g., "Updating... 50%%") in the UI.

This commit removes the redundant `%` from the string formatting in `FirmwareUpdateViewModel.kt` and the string resource definitions to correct the display.

Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
This commit introduces a confirmation dialog that appears before a firmware update can be initiated. The dialog warns the user about the potential risks associated with flashing new firmware and requires them to acknowledge these risks before proceeding.

- Added an `AlertDialog` to `FirmwareUpdateScreen` to act as a disclaimer.
- The "Update" button now triggers this disclaimer dialog instead of starting the update directly.
- Added new string resources for the disclaimer's title and text content.

Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
…U improvements

This commit enhances the firmware update process by prioritizing direct downloads of device-specific firmware and refining the DFU (Device Firmware Update) configuration for better reliability.

- Implemented logic to first attempt downloading a specific firmware build for the target device, falling back to the full release zip only if the direct download fails.
- Adjusted DFU initiator settings for increased stability:
    - Enabled `setForceScanningForNewAddressInLegacyDfu(true)` to improve device rediscovery.
    - Set a 2-second scan timeout (`setScanTimeout`).
    - Configured packet receipt notifications to reduce overhead.
    - Disabled DFU session resumption (`disableResume`).
- Refactored `downloadFirmware` into a more generic `downloadFile` function.
- Added a `checkUrlExists` utility to verify direct download links before attempting to fetch them.
- Updated file cleanup logic to include the new direct download firmware file.

Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
@codecov

codecov Bot commented Nov 22, 2025

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 0% with 560 lines in your changes missing coverage. Please review.
✅ Project coverage is 0.54%. Comparing base (f9575a2) to head (6cd7437).
⚠️ Report is 11 commits behind head on main.

Files with missing lines Patch % Lines
...eshtastic/feature/firmware/FirmwareUpdateScreen.kt 0.00% 288 Missing ⚠️
...tastic/feature/firmware/FirmwareUpdateViewModel.kt 0.00% 229 Missing and 1 partial ⚠️
.../meshtastic/feature/firmware/FirmwareDfuService.kt 0.00% 11 Missing ⚠️
...meshtastic/feature/firmware/FirmwareUpdateState.kt 0.00% 8 Missing ⚠️
...g/meshtastic/feature/settings/SettingsViewModel.kt 0.00% 8 Missing ⚠️
...g/meshtastic/feature/settings/radio/RadioConfig.kt 0.00% 7 Missing ⚠️
...in/kotlin/org/meshtastic/core/navigation/Routes.kt 0.00% 3 Missing ⚠️
...m/geeksville/mesh/navigation/FirmwareNavigation.kt 0.00% 2 Missing ⚠️
.../org/meshtastic/feature/settings/SettingsScreen.kt 0.00% 2 Missing ⚠️
app/src/main/java/com/geeksville/mesh/ui/Main.kt 0.00% 1 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff            @@
##            main   #3782      +/-   ##
========================================
- Coverage   0.56%   0.54%   -0.02%     
========================================
  Files        381     386       +5     
  Lines      21733   22421     +688     
  Branches    2684    2810     +126     
========================================
  Hits         122     122              
- Misses     21591   22278     +687     
- Partials      20      21       +1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

The firmware download URL was incorrect due to a missing `-ota` suffix in the filename. This change updates the filename construction in `FirmwareUpdateViewModel` to `firmware-$target-$version-ota.zip` to ensure the correct OTA firmware package is downloaded.

Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
This commit enhances the firmware update disclaimer dialog by adding a visual and textual element featuring "Chirpy," the Meshtastic mascot.

- Added a `Card` within the `DisclaimerDialog` to display an image of Chirpy and a related message.
- Introduced a new vector drawable for Chirpy (`core/ui/src/main/res/drawable/chirpy.xml`).
- Added new string resources for the disclaimer message ("Chirpy says, 'Keep your ladder handy!'") and for accessibility.

Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
@jamesarich jamesarich marked this pull request as ready for review November 23, 2025 22:34
This commit refines the firmware update functionality by enforcing stricter conditions for its availability.

- The firmware update option is now only displayed for nodes that are both DFU-capable and locally connected (`state.isLocal`).
- The logic to determine DFU capability has been updated to rely on a `requiresDfu` property from the hardware model, replacing a check for the "nrf52" architecture.
- The device address validation for DFU eligibility has been simplified.

Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
This commit refactors the `FirmwareFileHandler` to improve the management of temporary firmware files and enhance robustness.

- **Centralized Temp Directory:** All temporary files for firmware updates are now stored in a dedicated `firmware_update` subdirectory within the cache, preventing clutter in the main cache directory.
- **Robust Cleanup:** Implemented `deleteRecursively()` to reliably clean up all temporary files and the dedicated directory, such as after a crash. This replaces the previous method of deleting individual files.
- **Lowercase URL Replacement:** Ensures that the target architecture in firmware URLs is always lowercase for consistency.
- **Directory Creation:** The temporary directory is now proactively created before any file operations to prevent potential `FileNotFoundException` errors.

Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
@jamesarich jamesarich enabled auto-merge November 24, 2025 18:55
@jamesarich jamesarich added this pull request to the merge queue Nov 24, 2025
Merged via the queue into meshtastic:main with commit 4b93065 Nov 24, 2025
5 checks passed
@jamesarich jamesarich deleted the feat/dfu-firmware-update branch November 24, 2025 19:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants