feat(firmware): Implement USB DFU updates for supported devices (#3901)

Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
This commit is contained in:
James Rich 2025-12-06 06:36:54 -06:00 committed by GitHub
parent f322eb31a0
commit 499ed58311
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 860 additions and 483 deletions

View file

@ -54,6 +54,7 @@ import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
@ -134,7 +135,7 @@ fun SettingsScreen(
val ourNode by settingsViewModel.ourNodeInfo.collectAsStateWithLifecycle()
val isConnected by settingsViewModel.isConnected.collectAsStateWithLifecycle(false)
val isDfuCapable by settingsViewModel.isDfuCapable.collectAsStateWithLifecycle()
val destNode by viewModel.destNode.collectAsState()
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
var isWaiting by remember { mutableStateOf(false) }
if (isWaiting) {
@ -228,7 +229,7 @@ fun SettingsScreen(
if (state.isLocal) {
ourNode?.user?.longName
} else {
val remoteName = viewModel.destNode.value?.user?.longName ?: ""
val remoteName = destNode?.user?.longName ?: ""
stringResource(Res.string.remotely_administrating, remoteName)
},
ourNode = ourNode,
@ -244,7 +245,7 @@ fun SettingsScreen(
RadioConfigItemList(
state = state,
isManaged = localConfig.security.isManaged,
node = viewModel.destNode.value,
node = destNode,
excludedModulesUnlocked = excludedModulesUnlocked,
isDfuCapable = isDfuCapable,
onPreserveFavoritesToggle = { viewModel.setPreserveFavorites(it) },

View file

@ -48,6 +48,8 @@ import org.meshtastic.core.datastore.UiPreferencesDataSource
import org.meshtastic.core.model.Position
import org.meshtastic.core.model.util.positionToMeter
import org.meshtastic.core.prefs.radio.RadioPrefs
import org.meshtastic.core.prefs.radio.isBle
import org.meshtastic.core.prefs.radio.isSerial
import org.meshtastic.core.prefs.ui.UiPrefs
import org.meshtastic.core.service.IMeshService
import org.meshtastic.core.service.ServiceRepository
@ -120,19 +122,12 @@ constructor(
.flatMapLatest { (node, connectionState) ->
if (node == null || !connectionState.isConnected()) {
flowOf(false)
} else if (radioPrefs.isBle() || radioPrefs.isSerial()) {
val hwModel = node.user.hwModel.number
val hw = deviceHardwareRepository.getDeviceHardwareByModel(hwModel).getOrNull()
flow { emit(hw?.requiresDfu == true) }
} else {
// Check BLE address
val address = radioPrefs.devAddr
if (address == null || !address.startsWith("x")) {
flowOf(false)
} else {
// Check hardware
val hwModel = node.user.hwModel.number
flow {
val hw = deviceHardwareRepository.getDeviceHardwareByModel(hwModel).getOrNull()
emit(hw?.requiresDfu == true)
}
}
flowOf(false)
}
}
.stateInWhileSubscribed(initialValue = false)