From 23ebe474227a9f72304e4c4b054e1d76ad4a7a7b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 18 Apr 2026 19:16:04 +0000 Subject: [PATCH] Gate firmware update features by minimum firmware version MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - NRF DFU (BLE OTA via Nordic DFU) and UF2 mass-storage work with the app's existing minimum firmware (2.5.18) — both use enterDfuModeRequest which has been in the firmware long before that version, no extra gate needed. - ESP32 BIN OTA (BLE/WiFi) requires AdminMessage.OTAEvent with otaHash, which was added to the Meshtastic firmware in 2.7.10. Gate these features at 2.7.10: • FirmwareRow: show an orange "Requires 2.7.10+" tag and disable the Install button for .bin files when the connected device is below 2.7.10. • ESP32OTAIntroSheet: show a firmware-update-required warning banner and disable the "I Know What I'm Doing" BLE/WiFi OTA buttons when firmware < 2.7.10. Agent-Logs-Url: https://github.com/meshtastic/Meshtastic-Apple/sessions/ac79356b-8f0b-4297-973b-ee00f5a960d6 Co-authored-by: garthvh <1795163+garthvh@users.noreply.github.com> --- .../ESP32 OTA/ESP32OTAIntroSheet.swift | 68 +++++++++++++------ .../Views/Settings/Firmware/Firmware.swift | 25 +++++-- 2 files changed, 66 insertions(+), 27 deletions(-) diff --git a/Meshtastic/Views/Settings/Firmware/ESP32 OTA/ESP32OTAIntroSheet.swift b/Meshtastic/Views/Settings/Firmware/ESP32 OTA/ESP32OTAIntroSheet.swift index 3a360ac8..bf6b9b49 100644 --- a/Meshtastic/Views/Settings/Firmware/ESP32 OTA/ESP32OTAIntroSheet.swift +++ b/Meshtastic/Views/Settings/Firmware/ESP32 OTA/ESP32OTAIntroSheet.swift @@ -14,18 +14,26 @@ struct ESP32OTAIntroSheet: View { case intro case updater } - + + /// ESP32 OTA (BLE/WiFi) requires AdminMessage.OTAEvent with otaHash for authentication, + /// added to Meshtastic firmware in 2.7.10. + private let minimumOTAVersion = "2.7.10" + @EnvironmentObject var accessoryManager: AccessoryManager @Environment(\.dismiss) var dismiss @Environment(\.managedObjectContext) var context - + let binFileURL: URL - - + @State var showWifiUpdater = false @State var debugHost: String = "" @State var showBLEUpdater = false - + + /// True when the connected device's firmware supports the OTAEvent protocol. + private var firmwareSupportsOTA: Bool { + accessoryManager.checkIsVersionSupported(forVersion: minimumOTAVersion) + } + var body: some View { NavigationStack { List { @@ -33,14 +41,14 @@ struct ESP32OTAIntroSheet: View { VStack(alignment: .leading, spacing: 12) { Label("Desktop Recommended", systemImage: "desktopcomputer") .font(.headline) - + Text("The recommended way to update ESP32 devices is using the **Web Flasher** on a desktop computer (Chrome-based browser).") .fixedSize(horizontal: false, vertical: true) - + Text("The **Web Flasher** does not support updating on this device or over USB or BLE.") .font(.caption) .foregroundStyle(.secondary) - + Link(destination: URL(string: "https://flash.meshtastic.org")!) { HStack { Text("Open Web Flasher") @@ -51,31 +59,49 @@ struct ESP32OTAIntroSheet: View { .buttonStyle(.bordered) .controlSize(.regular) }.listRowBackground(Color(UIColor.tertiarySystemBackground)) - + } footer: { Color.clear.frame(height: 5) } - + + if !firmwareSupportsOTA { + Section { + HStack(spacing: 12) { + Image(systemName: "exclamationmark.triangle.fill") + .foregroundStyle(.orange) + VStack(alignment: .leading, spacing: 4) { + Text("Firmware Update Required") + .font(.subheadline.bold()) + Text("ESP32 OTA updating requires firmware \(minimumOTAVersion) or later. Update your device firmware using the Web Flasher first.") + .font(.caption) + .foregroundStyle(.secondary) + .fixedSize(horizontal: false, vertical: true) + } + } + .padding(.vertical, 4) + } + } + switch OTAMode { case .wifi: Section { VStack(alignment: .leading, spacing: 12) { Label("WiFi OTA Updating", systemImage: "wifi") .font(.headline) - + HStack(alignment: .top, spacing: 12) { Image(systemName: "lock.shield") .font(.title2) .foregroundStyle(.blue) - + Text("Advanced Users Only.") .font(.callout) } - + Text("If you device has the proper updater loaded into the OTA_1 partition, you can attempt to use the WiFi update process.") .font(.caption) .foregroundStyle(.secondary) - + Button(role: .destructive) { self.showWifiUpdater = true } label: { @@ -84,7 +110,8 @@ struct ESP32OTAIntroSheet: View { .buttonStyle(.borderedProminent) .controlSize(.large) .frame(maxWidth: .infinity) - .cornerRadius(10).disabled(accessoryManager.activeDeviceNum == nil) + .cornerRadius(10) + .disabled(accessoryManager.activeDeviceNum == nil || !firmwareSupportsOTA) } .padding() .listRowBackground(Color(UIColor.tertiarySystemBackground)) @@ -93,20 +120,20 @@ struct ESP32OTAIntroSheet: View { VStack(alignment: .leading, spacing: 12) { Label("BLE OTA Updating", systemImage: "wifi") .font(.headline) - + HStack(alignment: .top, spacing: 12) { Image(systemName: "lock.shield") .font(.title2) .foregroundStyle(.blue) - + Text("Advanced Users Only.") .font(.callout) } - + Text("If you device has the proper updater loaded into the OTA_1 partition, you can attempt to use the BLE update process.") .font(.caption) .foregroundStyle(.secondary) - + Button(role: .destructive) { self.showBLEUpdater = true } label: { @@ -115,7 +142,8 @@ struct ESP32OTAIntroSheet: View { .buttonStyle(.borderedProminent) .controlSize(.large) .frame(maxWidth: .infinity) - .cornerRadius(10).disabled(accessoryManager.activeDeviceNum == nil) + .cornerRadius(10) + .disabled(accessoryManager.activeDeviceNum == nil || !firmwareSupportsOTA) } .padding() .listRowBackground(Color(UIColor.tertiarySystemBackground)) diff --git a/Meshtastic/Views/Settings/Firmware/Firmware.swift b/Meshtastic/Views/Settings/Firmware/Firmware.swift index b48996ec..31ae8db1 100644 --- a/Meshtastic/Views/Settings/Firmware/Firmware.swift +++ b/Meshtastic/Views/Settings/Firmware/Firmware.swift @@ -333,12 +333,18 @@ struct FirmwareTagView: View { } private struct FirmwareRow: View { - + + @EnvironmentObject var accessoryManager: AccessoryManager + @ObservedObject var firmwareFile: FirmwareFile - + @State var unsupporedInstallationMessage: Bool = false @State var showInstallationSheet: FirmwareFile.FirmwareType? - + + /// ESP32 OTA (BLE/WiFi) requires the AdminMessage.OTAEvent protocol with otaHash, + /// which was added to Meshtastic firmware in 2.7.10. + private let minimumESP32OTAVersion = "2.7.10" + var body: some View { VStack { HStack { @@ -350,11 +356,11 @@ private struct FirmwareRow: View { case .otaZip: Text("ZIP").font(.caption2) } - + Text("\(firmwareFile.versionId)") .font(.caption2) .foregroundColor(.secondary) - + switch firmwareFile.releaseType { case .stable: FirmwareTagView("STABLE", color: Color.green) @@ -363,13 +369,17 @@ private struct FirmwareRow: View { case .unlisted: FirmwareTagView("UNLISTED", color: Color.orange) } - + + if firmwareFile.firmwareType == .bin && !accessoryManager.checkIsVersionSupported(forVersion: minimumESP32OTAVersion) { + FirmwareTagView("Requires \(minimumESP32OTAVersion)+", color: .orange) + } + Spacer() switch firmwareFile.status { case .downloading: ProgressView() - + case .downloaded: Button { switch firmwareFile.firmwareType { @@ -390,6 +400,7 @@ private struct FirmwareRow: View { .buttonStyle(.bordered) .buttonBorderShape(.capsule) .controlSize(UIDevice.current.userInterfaceIdiom == .phone ? .small : .regular) + .disabled(firmwareFile.firmwareType == .bin && !accessoryManager.checkIsVersionSupported(forVersion: minimumESP32OTAVersion)) case .notDownloaded: Button {