feat: Add ESP32 Unified OTA update support (#4095)

Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
This commit is contained in:
James Rich 2026-01-14 21:22:30 -06:00 committed by GitHub
parent 6b5dd24249
commit 2a60480bd9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
40 changed files with 3410 additions and 717 deletions

View file

@ -179,6 +179,7 @@ constructor(
}
base.copy(
requiresBootloaderUpgradeForOta = quirk.requiresBootloaderUpgradeForOta,
supportsUnifiedOta = quirk.supportsUnifiedOta,
bootloaderInfoUrl = quirk.infoUrl,
)
} else {

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2025 Meshtastic LLC
* Copyright (c) 2025-2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -14,7 +14,6 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.meshtastic.core.database.entity
import androidx.room.ColumnInfo
@ -73,4 +72,5 @@ fun FirmwareRelease.asDeviceVersion(): DeviceVersion = DeviceVersion(id.substrin
enum class FirmwareReleaseType {
STABLE,
ALPHA,
LOCAL,
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2025 Meshtastic LLC
* Copyright (c) 2025-2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -14,7 +14,6 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.meshtastic.core.model
import kotlinx.serialization.SerialName
@ -31,6 +30,11 @@ data class BootloaderOtaQuirk(
* one-time bootloader upgrade (typically via USB) before DFU updates from the app work.
*/
@SerialName("requiresBootloaderUpgradeForOta") val requiresBootloaderUpgradeForOta: Boolean = false,
/**
* Indicates that the device supports the ESP32 Unified OTA protocol. When true, the app will use the unified OTA
* handler instead of Nordic DFU.
*/
@SerialName("supportsUnifiedOta") val supportsUnifiedOta: Boolean = false,
/** Optional URL pointing to documentation on how to update the bootloader. */
@SerialName("infoUrl") val infoUrl: String? = null,
)

View file

@ -55,4 +55,8 @@ data class Capabilities(val firmwareVersion: String?, internal val forceEnableAl
/** Support for sharing contact information via QR codes. Supported since firmware v2.6.8. */
val supportsQrCodeSharing: Boolean
get() = isSupported("2.6.8")
/** Support for ESP32 Unified OTA. Supported since firmware v2.7.18. */
val supportsEsp32Ota: Boolean
get() = isSupported("2.7.18")
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2025 Meshtastic LLC
* Copyright (c) 2025-2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -14,7 +14,6 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.meshtastic.core.model
import kotlinx.serialization.Serializable
@ -39,6 +38,7 @@ data class DeviceHardware(
val requiresBootloaderUpgradeForOta: Boolean? = null,
/** Optional URL pointing to documentation for upgrading the bootloader. */
val bootloaderInfoUrl: String? = null,
val supportsUnifiedOta: Boolean = false,
val supportLevel: Int? = null,
val tags: List<String>? = null,
)

View file

@ -182,4 +182,11 @@ interface IMeshService {
/// Send request for telemetry to nodeNum
void requestTelemetry(in int requestId, in int destNum, in int type);
/**
* Tell the node to reboot into OTA mode for firmware update via BLE or WiFi (ESP32 only)
* mode is 1 for BLE, 2 for WiFi
* hash is the 32-byte firmware SHA256 hash (optional, can be null)
*/
void requestRebootOta(in int requestId, in int destNum, in int mode, in byte []hash);
}

View file

@ -562,7 +562,7 @@
<string name="triple_click_adhoc_ping">Triple Click Ad Hoc Ping</string>
<string name="time_zone">Time Zone</string>
<string name="led_heartbeat">LED Heartbeat</string>
<string name="display_config">Display</string>
<string name="display_config">Device Display</string>
<string name="screen_on_for">Screen on for</string>
<string name="carousel_interval">Carousel interval</string>
<string name="compass_north_top">Compass north top</string>
@ -1022,17 +1022,20 @@
<string name="firmware_update_checking">Checking for updates...</string>
<string name="firmware_update_device">Device: %1$s</string>
<string name="firmware_update_currently_installed">Currently Installed: %1$s</string>
<string name="firmware_update_latest">Latest Release: %1$s</string>
<string name="firmware_update_latest">Update To: %1$s</string>
<string name="firmware_update_stable">Stable</string>
<string name="firmware_update_alpha">Alpha</string>
<string name="firmware_update_disconnect_warning">Note: This will temporarily disconnect your device during the update.</string>
<string name="firmware_update_downloading">Downloading firmware... %1$d%</string>
<string name="firmware_update_downloading_percent">Downloading firmware... %1$d%%</string>
<string name="firmware_update_error">Error: %1$s</string>
<string name="firmware_update_retry">Retry</string>
<string name="firmware_update_success">Update Successful!</string>
<string name="firmware_update_done">Done</string>
<string name="firmware_update_starting_dfu">Starting DFU...</string>
<string name="firmware_update_updating">Updating... %1$s%</string>
<string name="firmware_update_updating">Updating... %1$s</string>
<string name="firmware_update_enabling_dfu">Enabling DFU mode...</string>
<string name="firmware_update_validating">Validating firmware...</string>
<string name="firmware_update_disconnecting">Disconnecting...</string>
<string name="firmware_update_unknown_hardware">Unknown hardware model: %1$d</string>
<string name="firmware_update_invalid_address">Connected device is not a valid BLE device or address is unknown (%1$s).</string>
<string name="firmware_update_no_device">No device connected</string>
@ -1046,6 +1049,8 @@
<string name="firmware_update_almost_there">Almost there...</string>
<string name="firmware_update_taking_a_while">This might take a minute...</string>
<string name="firmware_update_select_file">Select Local File</string>
<string name="firmware_update_local_file">Local File</string>
<string name="firmware_update_source_local">Source: Local File</string>
<string name="firmware_update_unknown_release">Unknown remote release</string>
<string name="firmware_update_disclaimer_title">Update Warning</string>
<string name="firmware_update_disclaimer_text">You are about to flash new firmware to your device. This process carries risks.\n\n• Ensure your device is charged.\n• Keep the device close to your phone.\n• Do not close the app during the update.\n\nVerify you have selected the correct firmware for your hardware.</string>
@ -1058,9 +1063,39 @@
<string name="firmware_update_flashing">Flashing device, please wait...</string>
<string name="firmware_update_method_usb">USB File Transfer</string>
<string name="firmware_update_method_ble">BLE OTA</string>
<string name="firmware_update_method_wifi">WiFi OTA</string>
<string name="firmware_update_method_detail">Update via %1$s</string>
<string name="firmware_update_usb_instruction_title">Select DFU USB Drive</string>
<string name="firmware_update_usb_instruction_text">Your device has rebooted into DFU mode and should appear as a USB drive (e.g., RAK4631).\n\nWhen the file picker opens, please select the root of that drive to save the firmware file.</string>
<string name="firmware_update_verifying">Verifying update...</string>
<string name="firmware_update_verification_failed">Verification timed out. Device did not reconnect in time.</string>
<string name="firmware_update_waiting_reconnect">Waiting for device to reconnect...</string>
<string name="firmware_update_target">Target: %1$s</string>
<string name="firmware_update_release_notes">Release Notes</string>
<string name="firmware_update_unknown_error">Unknown error</string>
<string name="firmware_update_local_failed">Local update failed</string>
<string name="firmware_update_dfu_error">DFU Error: %1$s</string>
<string name="firmware_update_dfu_aborted">DFU Aborted</string>
<string name="firmware_update_node_info_missing">Node user information is missing.</string>
<string name="firmware_update_battery_low">Battery too low (%1$d%%). Please charge your device before updating.</string>
<string name="firmware_update_retrieval_failed">Could not retrieve firmware file.</string>
<string name="firmware_update_nordic_failed">Nordic DFU Update failed</string>
<string name="firmware_update_usb_failed">USB Update failed</string>
<string name="firmware_update_hash_rejected">Firmware hash rejected. Device may require hash provisioning or bootloader update.</string>
<string name="firmware_update_ota_failed">OTA update failed: %1$s</string>
<string name="firmware_update_loading">Loading firmware...</string>
<string name="firmware_update_waiting_reboot">Waiting for device to reboot into OTA mode...</string>
<string name="firmware_update_connecting_attempt">Connecting to device (attempt %1$d/%2$d)...</string>
<string name="firmware_update_checking_version">Checking device version...</string>
<string name="firmware_update_starting_ota">Starting OTA update...</string>
<string name="firmware_update_uploading">Uploading firmware...</string>
<string name="firmware_update_uploading_progress">Uploading firmware... %1$d%% (%2$s)</string>
<string name="firmware_update_rebooting_device">Rebooting device...</string>
<string name="firmware_update_channel_name">Firmware Update</string>
<string name="firmware_update_channel_description">Firmware update status</string>
<string name="firmware_update_erasing">Erasing...</string>
<string name="back">Back</string>
<string name="interval_unset">Unset</string>
<string name="interval_always_on">Always On</string>
<plurals name="plurals_seconds">