feat/decoupling (#4685)

Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
This commit is contained in:
James Rich 2026-03-03 07:15:28 -06:00 committed by GitHub
parent 40244f8337
commit 2c49db8041
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
254 changed files with 5132 additions and 2666 deletions

View file

@ -37,19 +37,20 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withTimeoutOrNull
import org.jetbrains.compose.resources.StringResource
import org.jetbrains.compose.resources.getString
import org.meshtastic.core.data.repository.DeviceHardwareRepository
import org.meshtastic.core.data.repository.FirmwareReleaseRepository
import org.meshtastic.core.data.repository.NodeRepository
import org.meshtastic.core.database.entity.FirmwareRelease
import org.meshtastic.core.database.entity.FirmwareReleaseType
import org.meshtastic.core.database.entity.MyNodeEntity
import org.meshtastic.core.datastore.BootloaderWarningDataSource
import org.meshtastic.core.model.ConnectionState
import org.meshtastic.core.model.DeviceHardware
import org.meshtastic.core.model.MyNodeInfo
import org.meshtastic.core.model.RadioController
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.radio.isTcp
import org.meshtastic.core.repository.DeviceHardwareRepository
import org.meshtastic.core.repository.NodeRepository
import org.meshtastic.core.resources.Res
import org.meshtastic.core.resources.firmware_update_battery_low
import org.meshtastic.core.resources.firmware_update_copying
@ -72,7 +73,6 @@ import org.meshtastic.core.resources.firmware_update_unknown_hardware
import org.meshtastic.core.resources.firmware_update_updating
import org.meshtastic.core.resources.firmware_update_validating
import org.meshtastic.core.resources.unknown
import org.meshtastic.core.service.ServiceRepository
import java.io.File
import javax.inject.Inject
@ -95,7 +95,7 @@ constructor(
private val firmwareReleaseRepository: FirmwareReleaseRepository,
private val deviceHardwareRepository: DeviceHardwareRepository,
private val nodeRepository: NodeRepository,
private val serviceRepository: ServiceRepository,
private val radioController: RadioController,
private val radioPrefs: RadioPrefs,
private val bootloaderWarningDataSource: BootloaderWarningDataSource,
private val firmwareUpdateManager: FirmwareUpdateManager,
@ -106,6 +106,8 @@ constructor(
private val _state = MutableStateFlow<FirmwareUpdateState>(FirmwareUpdateState.Idle)
val state: StateFlow<FirmwareUpdateState> = _state.asStateFlow()
val connectionState = radioController.connectionState
private val _selectedReleaseType = MutableStateFlow(FirmwareReleaseType.STABLE)
val selectedReleaseType: StateFlow<FirmwareReleaseType> = _selectedReleaseType.asStateFlow()
@ -429,14 +431,14 @@ constructor(
// Trigger a fresh connection attempt by MeshService
address?.let { currentAddr ->
Logger.i { "Post-update: Requesting MeshService to reconnect to $currentAddr" }
serviceRepository.meshService?.setDeviceAddress("$DFU_RECONNECT_PREFIX$currentAddr")
radioController.setDeviceAddress("$DFU_RECONNECT_PREFIX$currentAddr")
}
// Wait for device to reconnect and settle
val result =
withTimeoutOrNull(VERIFY_TIMEOUT) {
// Wait for both Connected state and node info to be present
serviceRepository.connectionState.first { it is ConnectionState.Connected }
connectionState.first { it is ConnectionState.Connected }
nodeRepository.ourNodeInfo.filterNotNull().first()
delay(VERIFY_DELAY) // Extra buffer for initial config sync
true
@ -462,7 +464,7 @@ constructor(
return !isBatteryLow
}
private suspend fun getDeviceHardware(ourNode: MyNodeEntity): DeviceHardware? {
private suspend fun getDeviceHardware(ourNode: MyNodeInfo): DeviceHardware? {
val nodeInfo = nodeRepository.ourNodeInfo.value
val hwModelInt = nodeInfo?.user?.hw_model?.value
val target = ourNode.pioEnv

View file

@ -33,12 +33,12 @@ import no.nordicsemi.android.dfu.DfuServiceListenerHelper
import org.jetbrains.compose.resources.getString
import org.meshtastic.core.database.entity.FirmwareRelease
import org.meshtastic.core.model.DeviceHardware
import org.meshtastic.core.model.RadioController
import org.meshtastic.core.resources.Res
import org.meshtastic.core.resources.firmware_update_downloading_percent
import org.meshtastic.core.resources.firmware_update_nordic_failed
import org.meshtastic.core.resources.firmware_update_not_found_in_release
import org.meshtastic.core.resources.firmware_update_starting_service
import org.meshtastic.core.service.ServiceRepository
import java.io.File
import javax.inject.Inject
@ -53,7 +53,7 @@ class NordicDfuHandler
constructor(
private val firmwareRetriever: FirmwareRetriever,
@ApplicationContext private val context: Context,
private val serviceRepository: ServiceRepository,
private val radioController: RadioController,
) : FirmwareUpdateHandler {
override suspend fun startUpdate(
@ -113,7 +113,7 @@ constructor(
updateState(FirmwareUpdateState.Processing(ProgressState(startingMsg)))
// n = Nordic (Legacy prefix handling in mesh service)
serviceRepository.meshService?.setDeviceAddress("n")
radioController.setDeviceAddress("n")
DfuServiceInitiator(address)
.setDeviceName(deviceHardware.displayName)

View file

@ -23,12 +23,13 @@ import kotlinx.coroutines.delay
import org.jetbrains.compose.resources.getString
import org.meshtastic.core.database.entity.FirmwareRelease
import org.meshtastic.core.model.DeviceHardware
import org.meshtastic.core.model.RadioController
import org.meshtastic.core.repository.NodeRepository
import org.meshtastic.core.resources.Res
import org.meshtastic.core.resources.firmware_update_downloading_percent
import org.meshtastic.core.resources.firmware_update_rebooting
import org.meshtastic.core.resources.firmware_update_retrieval_failed
import org.meshtastic.core.resources.firmware_update_usb_failed
import org.meshtastic.core.service.ServiceRepository
import java.io.File
import javax.inject.Inject
@ -40,7 +41,8 @@ class UsbUpdateHandler
@Inject
constructor(
private val firmwareRetriever: FirmwareRetriever,
private val serviceRepository: ServiceRepository,
private val radioController: RadioController,
private val nodeRepository: NodeRepository,
) : FirmwareUpdateHandler {
override suspend fun startUpdate(
@ -62,8 +64,8 @@ constructor(
if (firmwareUri != null) {
updateState(FirmwareUpdateState.Processing(ProgressState(rebootingMsg)))
val myNodeNum = serviceRepository.meshService?.getMyNodeInfo()?.myNodeNum ?: 0
serviceRepository.meshService?.rebootToDfu(myNodeNum)
val myNodeNum = nodeRepository.myNodeInfo.value?.myNodeNum ?: 0
radioController.rebootToDfu(myNodeNum)
delay(REBOOT_DELAY)
updateState(FirmwareUpdateState.AwaitingFileSave(null, "firmware.uf2", firmwareUri))
@ -85,8 +87,8 @@ constructor(
null
} else {
updateState(FirmwareUpdateState.Processing(ProgressState(rebootingMsg)))
val myNodeNum = serviceRepository.meshService?.getMyNodeInfo()?.myNodeNum ?: 0
serviceRepository.meshService?.rebootToDfu(myNodeNum)
val myNodeNum = nodeRepository.myNodeInfo.value?.myNodeNum ?: 0
radioController.rebootToDfu(myNodeNum)
delay(REBOOT_DELAY)
updateState(FirmwareUpdateState.AwaitingFileSave(firmwareFile, firmwareFile.name))

View file

@ -21,14 +21,18 @@ import android.net.Uri
import co.touchlab.kermit.Logger
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import no.nordicsemi.kotlin.ble.client.android.CentralManager
import org.jetbrains.compose.resources.getString
import org.meshtastic.core.common.util.nowMillis
import org.meshtastic.core.database.entity.FirmwareRelease
import org.meshtastic.core.model.DeviceHardware
import org.meshtastic.core.model.RadioController
import org.meshtastic.core.repository.NodeRepository
import org.meshtastic.core.resources.Res
import org.meshtastic.core.resources.firmware_update_connecting_attempt
import org.meshtastic.core.resources.firmware_update_downloading_percent
@ -40,7 +44,6 @@ import org.meshtastic.core.resources.firmware_update_retrieval_failed
import org.meshtastic.core.resources.firmware_update_starting_ota
import org.meshtastic.core.resources.firmware_update_uploading
import org.meshtastic.core.resources.firmware_update_waiting_reboot
import org.meshtastic.core.service.ServiceRepository
import org.meshtastic.feature.firmware.FirmwareRetriever
import org.meshtastic.feature.firmware.FirmwareUpdateHandler
import org.meshtastic.feature.firmware.FirmwareUpdateState
@ -68,7 +71,8 @@ class Esp32OtaUpdateHandler
@Inject
constructor(
private val firmwareRetriever: FirmwareRetriever,
private val serviceRepository: ServiceRepository,
private val radioController: RadioController,
private val nodeRepository: NodeRepository,
private val centralManager: CentralManager,
@ApplicationContext private val context: Context,
) : FirmwareUpdateHandler {
@ -201,13 +205,11 @@ constructor(
}
private fun triggerRebootOta(mode: Int, hash: ByteArray?) {
val service = serviceRepository.meshService ?: return
try {
val myInfo = service.getMyNodeInfo() ?: return
Logger.i { "ESP32 OTA: Triggering reboot OTA mode $mode with hash" }
service.requestRebootOta(service.getPacketId(), myInfo.myNodeNum, mode, hash)
} catch (@Suppress("TooGenericExceptionCaught") e: Exception) {
Logger.e(e) { "ESP32 OTA: Failed to trigger reboot OTA" }
val myInfo = nodeRepository.myNodeInfo.value ?: return
val myNodeNum = myInfo.myNodeNum
Logger.i { "ESP32 OTA: Triggering reboot OTA mode $mode with hash" }
CoroutineScope(Dispatchers.IO).launch {
radioController.requestRebootOta(radioController.getPacketId(), myNodeNum, mode, hash)
}
}
@ -216,12 +218,8 @@ constructor(
* interface) cleanly disconnects without reconnection attempts.
*/
private fun disconnectMeshService() {
try {
Logger.i { "ESP32 OTA: Disconnecting mesh service for OTA" }
serviceRepository.meshService?.setDeviceAddress("n")
} catch (@Suppress("TooGenericExceptionCaught") e: Exception) {
Logger.w(e) { "ESP32 OTA: Error disconnecting mesh service" }
}
Logger.i { "ESP32 OTA: Disconnecting mesh service for OTA" }
radioController.setDeviceAddress("n")
}
private suspend fun obtainFirmwareFile(

View file

@ -33,7 +33,8 @@ import org.junit.Before
import org.junit.Test
import org.meshtastic.core.database.entity.FirmwareRelease
import org.meshtastic.core.model.DeviceHardware
import org.meshtastic.core.service.ServiceRepository
import org.meshtastic.core.model.RadioController
import org.meshtastic.core.repository.NodeRepository
import org.meshtastic.feature.firmware.FirmwareRetriever
import org.meshtastic.feature.firmware.FirmwareUpdateState
import java.io.IOException
@ -42,12 +43,14 @@ import java.io.IOException
class Esp32OtaUpdateHandlerTest {
private val firmwareRetriever: FirmwareRetriever = mockk()
private val serviceRepository: ServiceRepository = mockk()
private val radioController: RadioController = mockk()
private val nodeRepository: NodeRepository = mockk()
private val centralManager: CentralManager = mockk()
private val context: Context = mockk()
private val contentResolver: ContentResolver = mockk()
private val handler = Esp32OtaUpdateHandler(firmwareRetriever, serviceRepository, centralManager, context)
private val handler =
Esp32OtaUpdateHandler(firmwareRetriever, radioController, nodeRepository, centralManager, context)
@Before
fun setUp() {