mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-05-12 00:28:20 -04:00
refactor(firmware): Simplify ESP32 firmware check (#4272)
Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
This commit is contained in:
@@ -17,151 +17,6 @@
|
||||
"hwModelSlug": "NOMADSTAR_METEOR_PRO",
|
||||
"requiresBootloaderUpgradeForOta": true,
|
||||
"infoUrl": "https://meshtastic.org/docs/getting-started/flashing-firmware/nrf52/update-nrf52-bootloader/"
|
||||
},
|
||||
{
|
||||
"hwModel": 12,
|
||||
"hwModelSlug": "LILYGO_TBEAM_S3_CORE",
|
||||
"supportsUnifiedOta": true
|
||||
},
|
||||
{
|
||||
"hwModel": 16,
|
||||
"hwModelSlug": "TLORA_T3_S3",
|
||||
"supportsUnifiedOta": true
|
||||
},
|
||||
{
|
||||
"hwModel": 28,
|
||||
"hwModelSlug": "SENSELORA_S3",
|
||||
"supportsUnifiedOta": true
|
||||
},
|
||||
{
|
||||
"hwModel": 43,
|
||||
"hwModelSlug": "HELTEC_V3",
|
||||
"supportsUnifiedOta": true
|
||||
},
|
||||
{
|
||||
"hwModel": 44,
|
||||
"hwModelSlug": "HELTEC_WSL_V3",
|
||||
"supportsUnifiedOta": true
|
||||
},
|
||||
{
|
||||
"hwModel": 45,
|
||||
"hwModelSlug": "BETAFPV_2400_TX",
|
||||
"supportsUnifiedOta": true
|
||||
},
|
||||
{
|
||||
"hwModel": 46,
|
||||
"hwModelSlug": "BETAFPV_900_NANO_TX",
|
||||
"supportsUnifiedOta": true
|
||||
},
|
||||
{
|
||||
"hwModel": 48,
|
||||
"hwModelSlug": "HELTEC_WIRELESS_TRACKER",
|
||||
"supportsUnifiedOta": true
|
||||
},
|
||||
{
|
||||
"hwModel": 49,
|
||||
"hwModelSlug": "HELTEC_WIRELESS_PAPER",
|
||||
"supportsUnifiedOta": true
|
||||
},
|
||||
{
|
||||
"hwModel": 50,
|
||||
"hwModelSlug": "T_DECK",
|
||||
"supportsUnifiedOta": true
|
||||
},
|
||||
{
|
||||
"hwModel": 51,
|
||||
"hwModelSlug": "T_WATCH_S3",
|
||||
"supportsUnifiedOta": true
|
||||
},
|
||||
{
|
||||
"hwModel": 52,
|
||||
"hwModelSlug": "PICOMPUTER_S3",
|
||||
"supportsUnifiedOta": true
|
||||
},
|
||||
{
|
||||
"hwModel": 53,
|
||||
"hwModelSlug": "HELTEC_HT62",
|
||||
"supportsUnifiedOta": true
|
||||
},
|
||||
{
|
||||
"hwModel": 54,
|
||||
"hwModelSlug": "EBYTE_ESP32_S3",
|
||||
"supportsUnifiedOta": true
|
||||
},
|
||||
{
|
||||
"hwModel": 55,
|
||||
"hwModelSlug": "ESP32_S3_PICO",
|
||||
"supportsUnifiedOta": true
|
||||
},
|
||||
{
|
||||
"hwModel": 56,
|
||||
"hwModelSlug": "CHATTER_2",
|
||||
"supportsUnifiedOta": true
|
||||
},
|
||||
{
|
||||
"hwModel": 57,
|
||||
"hwModelSlug": "HELTEC_WIRELESS_PAPER_V1_0",
|
||||
"supportsUnifiedOta": true
|
||||
},
|
||||
{
|
||||
"hwModel": 58,
|
||||
"hwModelSlug": "HELTEC_WIRELESS_TRACKER_V1_0",
|
||||
"supportsUnifiedOta": true
|
||||
},
|
||||
{
|
||||
"hwModel": 59,
|
||||
"hwModelSlug": "UNPHONE",
|
||||
"supportsUnifiedOta": true
|
||||
},
|
||||
{
|
||||
"hwModel": 61,
|
||||
"hwModelSlug": "CDEBYTE_EORA_S3",
|
||||
"supportsUnifiedOta": true
|
||||
},
|
||||
{
|
||||
"hwModel": 64,
|
||||
"hwModelSlug": "RADIOMASTER_900_BANDIT_NANO",
|
||||
"supportsUnifiedOta": true
|
||||
},
|
||||
{
|
||||
"hwModel": 65,
|
||||
"hwModelSlug": "HELTEC_CAPSULE_SENSOR_V3",
|
||||
"supportsUnifiedOta": true
|
||||
},
|
||||
{
|
||||
"hwModel": 66,
|
||||
"hwModelSlug": "HELTEC_VISION_MASTER_T190",
|
||||
"supportsUnifiedOta": true
|
||||
},
|
||||
{
|
||||
"hwModel": 67,
|
||||
"hwModelSlug": "HELTEC_VISION_MASTER_E213",
|
||||
"supportsUnifiedOta": true
|
||||
},
|
||||
{
|
||||
"hwModel": 68,
|
||||
"hwModelSlug": "HELTEC_VISION_MASTER_E290",
|
||||
"supportsUnifiedOta": true
|
||||
},
|
||||
{
|
||||
"hwModel": 70,
|
||||
"hwModelSlug": "SENSECAP_INDICATOR",
|
||||
"supportsUnifiedOta": true
|
||||
},
|
||||
{
|
||||
"hwModel": 74,
|
||||
"hwModelSlug": "RADIOMASTER_900_BANDIT",
|
||||
"supportsUnifiedOta": true
|
||||
},
|
||||
{
|
||||
"hwModel": 107,
|
||||
"hwModelSlug": "THINKNODE_M5",
|
||||
"supportsUnifiedOta": true
|
||||
},
|
||||
{
|
||||
"hwModel": 110,
|
||||
"hwModelSlug": "HELTEC_V4",
|
||||
"supportsUnifiedOta": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -236,7 +236,6 @@ constructor(
|
||||
}
|
||||
base.copy(
|
||||
requiresBootloaderUpgradeForOta = matchedQuirk.requiresBootloaderUpgradeForOta,
|
||||
supportsUnifiedOta = matchedQuirk.supportsUnifiedOta,
|
||||
bootloaderInfoUrl = matchedQuirk.infoUrl,
|
||||
)
|
||||
} else {
|
||||
|
||||
@@ -96,6 +96,19 @@ class DeviceHardwareRepositoryTest {
|
||||
assertEquals("tdeck-pro", result?.platformioTarget)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getDeviceHardwareByModel correctly sets isEsp32Arc for ESP32 devices`() = runTest(testDispatcher) {
|
||||
val hwModel = 50
|
||||
val entities = listOf(createEntity(hwModel, "t-deck", "T-Deck").copy(architecture = "esp32-s3"))
|
||||
|
||||
coEvery { localDataSource.getByHwModel(hwModel) } returns entities
|
||||
every { bootloaderOtaQuirksJsonDataSource.loadBootloaderOtaQuirksFromJsonAsset() } returns emptyList()
|
||||
|
||||
val result = repository.getDeviceHardwareByModel(hwModel).getOrNull()
|
||||
|
||||
assertEquals(true, result?.isEsp32Arc)
|
||||
}
|
||||
|
||||
private fun createEntity(hwModel: Int, target: String, displayName: String) = DeviceHardwareEntity(
|
||||
activelySupported = true,
|
||||
architecture = "esp32-s3",
|
||||
|
||||
@@ -30,11 +30,6 @@ 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,
|
||||
)
|
||||
|
||||
@@ -38,7 +38,10 @@ 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,
|
||||
)
|
||||
) {
|
||||
/** Returns true if the device architecture is ESP32-based. */
|
||||
val isEsp32Arc: Boolean
|
||||
get() = architecture.startsWith("esp32", ignoreCase = true)
|
||||
}
|
||||
|
||||
@@ -53,25 +53,19 @@ class FirmwareRetriever @Inject constructor(private val fileHandler: FirmwareFil
|
||||
hardware: DeviceHardware,
|
||||
onProgress: (Float) -> Unit,
|
||||
): File? {
|
||||
// Try MCU-generic Unified OTA binary first, as it's the fastest and newest standard.
|
||||
// However, we skip the generic binary for devices with specialized UI requirements (MUI/TFT/E-Ink)
|
||||
// because the generic binary often lacks the necessary drivers.
|
||||
val hasSpecificUi = hardware.hasMui == true || hardware.hasInkHud == true
|
||||
if (hardware.supportsUnifiedOta && !hasSpecificUi) {
|
||||
val mcu = hardware.architecture.replace("-", "")
|
||||
val otaFilename = "mt-$mcu-ota.bin"
|
||||
retrieve(
|
||||
release = release,
|
||||
hardware = hardware,
|
||||
onProgress = onProgress,
|
||||
fileSuffix = ".bin",
|
||||
internalFileExtension = ".bin",
|
||||
preferredFilename = otaFilename,
|
||||
)
|
||||
?.let {
|
||||
return it
|
||||
}
|
||||
}
|
||||
val mcu = hardware.architecture.replace("-", "")
|
||||
val otaFilename = "mt-$mcu-ota.bin"
|
||||
retrieve(
|
||||
release = release,
|
||||
hardware = hardware,
|
||||
onProgress = onProgress,
|
||||
fileSuffix = ".bin",
|
||||
internalFileExtension = ".bin",
|
||||
preferredFilename = otaFilename,
|
||||
)
|
||||
?.let {
|
||||
return it
|
||||
}
|
||||
|
||||
// Fallback to board-specific binary using the now-accurate platformioTarget.
|
||||
return retrieve(
|
||||
|
||||
@@ -31,60 +31,6 @@ class FirmwareRetrieverTest {
|
||||
private val fileHandler: FirmwareFileHandler = mockk()
|
||||
private val retriever = FirmwareRetriever(fileHandler)
|
||||
|
||||
@Test
|
||||
fun `retrieveEsp32Firmware uses mt-arch-ota bin when Unified OTA is supported and no screen`() = runTest {
|
||||
val release = FirmwareRelease(id = "v2.5.0", zipUrl = "https://example.com/esp32.zip")
|
||||
val hardware =
|
||||
DeviceHardware(
|
||||
hwModelSlug = "HELTEC_V3",
|
||||
platformioTarget = "heltec-v3",
|
||||
architecture = "esp32-s3",
|
||||
supportsUnifiedOta = true,
|
||||
hasMui = false,
|
||||
hasInkHud = false,
|
||||
)
|
||||
val expectedFile = File("mt-esp32s3-ota.bin")
|
||||
|
||||
coEvery { fileHandler.checkUrlExists(any()) } returns true
|
||||
coEvery { fileHandler.downloadFile(any(), any(), any()) } returns expectedFile
|
||||
|
||||
val result = retriever.retrieveEsp32Firmware(release, hardware) {}
|
||||
|
||||
assertEquals(expectedFile, result)
|
||||
coVerify {
|
||||
fileHandler.checkUrlExists(
|
||||
"https://raw.githubusercontent.com/meshtastic/meshtastic.github.io/master/firmware-2.5.0/mt-esp32s3-ota.bin",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `retrieveEsp32Firmware skips mt-arch-ota bin for devices with MUI`() = runTest {
|
||||
val release = FirmwareRelease(id = "v2.5.0", zipUrl = "https://example.com/esp32.zip")
|
||||
val hardware =
|
||||
DeviceHardware(
|
||||
hwModelSlug = "T_DECK",
|
||||
platformioTarget = "tdeck-tft",
|
||||
architecture = "esp32-s3",
|
||||
supportsUnifiedOta = true,
|
||||
hasMui = true,
|
||||
)
|
||||
val expectedFile = File("firmware-tdeck-tft-2.5.0.bin")
|
||||
|
||||
coEvery { fileHandler.checkUrlExists(any()) } returns true
|
||||
coEvery { fileHandler.downloadFile(any(), any(), any()) } returns expectedFile
|
||||
|
||||
val result = retriever.retrieveEsp32Firmware(release, hardware) {}
|
||||
|
||||
assertEquals(expectedFile, result)
|
||||
coVerify(exactly = 0) { fileHandler.checkUrlExists(match { it.contains("mt-esp32s3-ota.bin") }) }
|
||||
coVerify {
|
||||
fileHandler.checkUrlExists(
|
||||
"https://raw.githubusercontent.com/meshtastic/meshtastic.github.io/master/firmware-2.5.0/firmware-tdeck-tft-2.5.0.bin",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `retrieveEsp32Firmware falls back to board-specific bin when mt-arch-ota bin is missing`() = runTest {
|
||||
val release = FirmwareRelease(id = "v2.5.0", zipUrl = "https://example.com/esp32.zip")
|
||||
@@ -93,7 +39,6 @@ class FirmwareRetrieverTest {
|
||||
hwModelSlug = "HELTEC_V3",
|
||||
platformioTarget = "heltec-v3",
|
||||
architecture = "esp32-s3",
|
||||
supportsUnifiedOta = true,
|
||||
hasMui = false,
|
||||
)
|
||||
val expectedFile = File("firmware-heltec-v3-2.5.0.bin")
|
||||
@@ -122,16 +67,10 @@ class FirmwareRetrieverTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `retrieveEsp32Firmware uses legacy filename for devices without Unified OTA`() = runTest {
|
||||
fun `retrieveEsp32Firmware uses Unified OTA path for ESP32`() = runTest {
|
||||
val release = FirmwareRelease(id = "v2.5.0", zipUrl = "https://example.com/esp32.zip")
|
||||
val hardware =
|
||||
DeviceHardware(
|
||||
hwModelSlug = "TLORA_V2",
|
||||
platformioTarget = "tlora-v2",
|
||||
architecture = "esp32",
|
||||
supportsUnifiedOta = false,
|
||||
)
|
||||
val expectedFile = File("firmware-tlora-v2-2.5.0.bin")
|
||||
val hardware = DeviceHardware(hwModelSlug = "TLORA_V2", platformioTarget = "tlora-v2", architecture = "esp32")
|
||||
val expectedFile = File("mt-esp32-ota.bin")
|
||||
|
||||
coEvery { fileHandler.checkUrlExists(any()) } returns true
|
||||
coEvery { fileHandler.downloadFile(any(), any(), any()) } returns expectedFile
|
||||
@@ -141,23 +80,15 @@ class FirmwareRetrieverTest {
|
||||
assertEquals(expectedFile, result)
|
||||
coVerify {
|
||||
fileHandler.checkUrlExists(
|
||||
"https://raw.githubusercontent.com/meshtastic/meshtastic.github.io/master/firmware-2.5.0/firmware-tlora-v2-2.5.0.bin",
|
||||
"https://raw.githubusercontent.com/meshtastic/meshtastic.github.io/master/firmware-2.5.0/mt-esp32-ota.bin",
|
||||
)
|
||||
}
|
||||
// Verify we DID NOT check for mt-esp32-ota.bin
|
||||
coVerify(exactly = 0) { fileHandler.checkUrlExists(match { it.contains("mt-esp32-ota.bin") }) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `retrieveOtaFirmware uses correct zip extension for NRF52`() = runTest {
|
||||
val release = FirmwareRelease(id = "v2.5.0", zipUrl = "https://example.com/nrf52.zip")
|
||||
val hardware =
|
||||
DeviceHardware(
|
||||
hwModelSlug = "RAK4631",
|
||||
platformioTarget = "rak4631",
|
||||
architecture = "nrf52840",
|
||||
supportsUnifiedOta = false, // OTA via DFU zip
|
||||
)
|
||||
val hardware = DeviceHardware(hwModelSlug = "RAK4631", platformioTarget = "rak4631", architecture = "nrf52840")
|
||||
val expectedFile = File("firmware-rak4631-2.5.0-ota.zip")
|
||||
|
||||
coEvery { fileHandler.checkUrlExists(any()) } returns true
|
||||
|
||||
@@ -133,7 +133,7 @@ constructor(
|
||||
|
||||
// ESP32 Unified OTA is only supported via BLE or WiFi (TCP), not USB Serial.
|
||||
val isEsp32OtaSupported =
|
||||
hw?.supportsUnifiedOta == true && capabilities.supportsEsp32Ota && !radioPrefs.isSerial()
|
||||
hw?.isEsp32Arc == true && capabilities.supportsEsp32Ota && !radioPrefs.isSerial()
|
||||
|
||||
flow { emit(hw?.requiresDfu == true || isEsp32OtaSupported) }
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user