mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
refactor(data): replace lateinit var scope + start() with constructor injection (#5075)
This commit is contained in:
parent
172680fd46
commit
174315b21f
43 changed files with 188 additions and 301 deletions
|
|
@ -24,6 +24,7 @@ import kotlinx.coroutines.flow.launchIn
|
|||
import kotlinx.coroutines.flow.onEach
|
||||
import okio.ByteString
|
||||
import okio.ByteString.Companion.toByteString
|
||||
import org.koin.core.annotation.Named
|
||||
import org.koin.core.annotation.Single
|
||||
import org.meshtastic.core.common.util.nowMillis
|
||||
import org.meshtastic.core.model.DataPacket
|
||||
|
|
@ -59,8 +60,8 @@ class CommandSenderImpl(
|
|||
private val radioConfigRepository: RadioConfigRepository,
|
||||
private val tracerouteHandler: TracerouteHandler,
|
||||
private val neighborInfoHandler: NeighborInfoHandler,
|
||||
@Named("ServiceScope") private val scope: CoroutineScope,
|
||||
) : CommandSender {
|
||||
private lateinit var scope: CoroutineScope
|
||||
private val currentPacketId = atomic(Random(nowMillis).nextLong().absoluteValue)
|
||||
private val sessionPasskey = atomic(ByteString.EMPTY)
|
||||
|
||||
|
|
@ -71,8 +72,7 @@ class CommandSenderImpl(
|
|||
// maybe via ServiceRepository or similar.
|
||||
// For now I'll assume it's injected or available.
|
||||
|
||||
override fun start(scope: CoroutineScope) {
|
||||
this.scope = scope
|
||||
init {
|
||||
radioConfigRepository.localConfigFlow.onEach { localConfig.value = it }.launchIn(scope)
|
||||
radioConfigRepository.channelSetFlow.onEach { channelSet.value = it }.launchIn(scope)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ package org.meshtastic.core.data.manager
|
|||
import co.touchlab.kermit.Logger
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import okio.ByteString.Companion.toByteString
|
||||
import org.koin.core.annotation.Named
|
||||
import org.koin.core.annotation.Single
|
||||
import org.meshtastic.core.common.database.DatabaseManager
|
||||
import org.meshtastic.core.common.util.handledLaunch
|
||||
|
|
@ -64,12 +65,8 @@ class MeshActionHandlerImpl(
|
|||
private val notificationManager: NotificationManager,
|
||||
private val messageProcessor: Lazy<MeshMessageProcessor>,
|
||||
private val radioConfigRepository: RadioConfigRepository,
|
||||
@Named("ServiceScope") private val scope: CoroutineScope,
|
||||
) : MeshActionHandler {
|
||||
private lateinit var scope: CoroutineScope
|
||||
|
||||
override fun start(scope: CoroutineScope) {
|
||||
this.scope = scope
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val DEFAULT_REBOOT_DELAY = 5
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import kotlinx.atomicfu.atomic
|
|||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.delay
|
||||
import okio.IOException
|
||||
import org.koin.core.annotation.Named
|
||||
import org.koin.core.annotation.Single
|
||||
import org.meshtastic.core.common.util.handledLaunch
|
||||
import org.meshtastic.core.model.ConnectionState
|
||||
|
|
@ -56,17 +57,13 @@ class MeshConfigFlowManagerImpl(
|
|||
private val analytics: PlatformAnalytics,
|
||||
private val commandSender: CommandSender,
|
||||
private val packetHandler: PacketHandler,
|
||||
@Named("ServiceScope") private val scope: CoroutineScope,
|
||||
) : MeshConfigFlowManager {
|
||||
private lateinit var scope: CoroutineScope
|
||||
private val wantConfigDelay = 100L
|
||||
|
||||
/** Monotonically increasing generation so async clears from a stale handshake are discarded. */
|
||||
private val handshakeGeneration = atomic(0L)
|
||||
|
||||
override fun start(scope: CoroutineScope) {
|
||||
this.scope = scope
|
||||
}
|
||||
|
||||
/**
|
||||
* Type-safe handshake state machine. Each state carries exactly the data that is valid during that phase,
|
||||
* eliminating the possibility of accessing stale or uninitialized fields.
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
|||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import org.koin.core.annotation.Named
|
||||
import org.koin.core.annotation.Single
|
||||
import org.meshtastic.core.common.util.handledLaunch
|
||||
import org.meshtastic.core.repository.MeshConfigHandler
|
||||
|
|
@ -40,8 +41,8 @@ class MeshConfigHandlerImpl(
|
|||
private val radioConfigRepository: RadioConfigRepository,
|
||||
private val serviceRepository: ServiceRepository,
|
||||
private val nodeManager: NodeManager,
|
||||
@Named("ServiceScope") private val scope: CoroutineScope,
|
||||
) : MeshConfigHandler {
|
||||
private lateinit var scope: CoroutineScope
|
||||
|
||||
private val _localConfig = MutableStateFlow(LocalConfig())
|
||||
override val localConfig = _localConfig.asStateFlow()
|
||||
|
|
@ -49,8 +50,7 @@ class MeshConfigHandlerImpl(
|
|||
private val _moduleConfig = MutableStateFlow(LocalModuleConfig())
|
||||
override val moduleConfig = _moduleConfig.asStateFlow()
|
||||
|
||||
override fun start(scope: CoroutineScope) {
|
||||
this.scope = scope
|
||||
init {
|
||||
radioConfigRepository.localConfigFlow.onEach { _localConfig.value = it }.launchIn(scope)
|
||||
radioConfigRepository.moduleConfigFlow.onEach { _moduleConfig.value = it }.launchIn(scope)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,13 +19,13 @@ package org.meshtastic.core.data.manager
|
|||
import co.touchlab.kermit.Logger
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.FlowPreview
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.core.annotation.Named
|
||||
import org.koin.core.annotation.Single
|
||||
import org.meshtastic.core.common.util.handledLaunch
|
||||
import org.meshtastic.core.common.util.nowMillis
|
||||
|
|
@ -81,17 +81,15 @@ class MeshConnectionManagerImpl(
|
|||
private val packetRepository: PacketRepository,
|
||||
private val workerManager: MeshWorkerManager,
|
||||
private val appWidgetUpdater: AppWidgetUpdater,
|
||||
@Named("ServiceScope") private val scope: CoroutineScope,
|
||||
) : MeshConnectionManager {
|
||||
private lateinit var scope: CoroutineScope
|
||||
private var sleepTimeout: Job? = null
|
||||
private var locationRequestsJob: Job? = null
|
||||
private var handshakeTimeout: Job? = null
|
||||
private var connectTimeMsec = 0L
|
||||
private var connectionRestored = false
|
||||
|
||||
@OptIn(FlowPreview::class)
|
||||
override fun start(scope: CoroutineScope) {
|
||||
this.scope = scope
|
||||
init {
|
||||
radioInterfaceService.connectionState.onEach(::onRadioConnectionState).launchIn(scope)
|
||||
|
||||
// Ensure notification title and content stay in sync with state changes
|
||||
|
|
@ -302,8 +300,7 @@ class MeshConnectionManagerImpl(
|
|||
// Start MQTT if enabled
|
||||
scope.handledLaunch {
|
||||
val moduleConfig = radioConfigRepository.moduleConfigFlow.first()
|
||||
mqttManager.start(
|
||||
scope,
|
||||
mqttManager.startProxy(
|
||||
moduleConfig.mqtt?.enabled == true,
|
||||
moduleConfig.mqtt?.proxy_to_client_enabled == true,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import kotlinx.coroutines.CoroutineScope
|
|||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.core.annotation.Named
|
||||
import org.koin.core.annotation.Single
|
||||
import org.meshtastic.core.common.util.handledLaunch
|
||||
import org.meshtastic.core.common.util.nowMillis
|
||||
|
|
@ -94,14 +95,8 @@ class MeshDataHandlerImpl(
|
|||
private val storeForwardHandler: StoreForwardPacketHandler,
|
||||
private val telemetryHandler: TelemetryPacketHandler,
|
||||
private val adminPacketHandler: AdminPacketHandler,
|
||||
@Named("ServiceScope") private val scope: CoroutineScope,
|
||||
) : MeshDataHandler {
|
||||
private lateinit var scope: CoroutineScope
|
||||
|
||||
override fun start(scope: CoroutineScope) {
|
||||
this.scope = scope
|
||||
storeForwardHandler.start(scope)
|
||||
telemetryHandler.start(scope)
|
||||
}
|
||||
|
||||
private val rememberDataType =
|
||||
setOf(
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import kotlinx.coroutines.flow.onEach
|
|||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import org.koin.core.annotation.Named
|
||||
import org.koin.core.annotation.Single
|
||||
import org.meshtastic.core.common.util.handledLaunch
|
||||
import org.meshtastic.core.common.util.nowMillis
|
||||
|
|
@ -53,8 +54,8 @@ class MeshMessageProcessorImpl(
|
|||
private val meshLogRepository: Lazy<MeshLogRepository>,
|
||||
private val router: Lazy<MeshRouter>,
|
||||
private val fromRadioDispatcher: FromRadioPacketHandler,
|
||||
@Named("ServiceScope") private val scope: CoroutineScope,
|
||||
) : MeshMessageProcessor {
|
||||
private lateinit var scope: CoroutineScope
|
||||
|
||||
private val mapsMutex = Mutex()
|
||||
private val logUuidByPacketId = mutableMapOf<Int, String>()
|
||||
|
|
@ -75,8 +76,7 @@ class MeshMessageProcessorImpl(
|
|||
scope.launch { earlyMutex.withLock { earlyReceivedPackets.clear() } }
|
||||
}
|
||||
|
||||
override fun start(scope: CoroutineScope) {
|
||||
this.scope = scope
|
||||
init {
|
||||
nodeManager.isNodeDbReady
|
||||
.onEach { ready ->
|
||||
if (ready) {
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@
|
|||
*/
|
||||
package org.meshtastic.core.data.manager
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import org.koin.core.annotation.Single
|
||||
import org.meshtastic.core.repository.MeshActionHandler
|
||||
import org.meshtastic.core.repository.MeshConfigFlowManager
|
||||
|
|
@ -64,13 +63,4 @@ class MeshRouterImpl(
|
|||
|
||||
override val xmodemManager: XModemManager
|
||||
get() = xmodemManagerLazy.value
|
||||
|
||||
override fun start(scope: CoroutineScope) {
|
||||
dataHandler.start(scope)
|
||||
configHandler.start(scope)
|
||||
tracerouteHandler.start(scope)
|
||||
neighborInfoHandler.start(scope)
|
||||
configFlowManager.start(scope)
|
||||
actionHandler.start(scope)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import kotlinx.coroutines.Job
|
|||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import org.koin.core.annotation.Named
|
||||
import org.koin.core.annotation.Single
|
||||
import org.meshtastic.core.network.repository.MQTTRepository
|
||||
import org.meshtastic.core.repository.MqttManager
|
||||
|
|
@ -36,12 +37,11 @@ class MqttManagerImpl(
|
|||
private val mqttRepository: MQTTRepository,
|
||||
private val packetHandler: PacketHandler,
|
||||
private val serviceRepository: ServiceRepository,
|
||||
@Named("ServiceScope") private val scope: CoroutineScope,
|
||||
) : MqttManager {
|
||||
private lateinit var scope: CoroutineScope
|
||||
private var mqttMessageFlow: Job? = null
|
||||
|
||||
override fun start(scope: CoroutineScope, enabled: Boolean, proxyToClientEnabled: Boolean) {
|
||||
this.scope = scope
|
||||
override fun startProxy(enabled: Boolean, proxyToClientEnabled: Boolean) {
|
||||
if (mqttMessageFlow?.isActive == true) return
|
||||
if (enabled && proxyToClientEnabled) {
|
||||
mqttMessageFlow =
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ import co.touchlab.kermit.Logger
|
|||
import kotlinx.atomicfu.atomic
|
||||
import kotlinx.atomicfu.update
|
||||
import kotlinx.collections.immutable.persistentMapOf
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import org.koin.core.annotation.Single
|
||||
import org.meshtastic.core.common.util.NumberFormatter
|
||||
import org.meshtastic.core.common.util.nowMillis
|
||||
|
|
@ -37,16 +36,11 @@ class NeighborInfoHandlerImpl(
|
|||
private val serviceRepository: ServiceRepository,
|
||||
private val serviceBroadcasts: ServiceBroadcasts,
|
||||
) : NeighborInfoHandler {
|
||||
private lateinit var scope: CoroutineScope
|
||||
|
||||
private val startTimes = atomic(persistentMapOf<Int, Long>())
|
||||
|
||||
override var lastNeighborInfo: NeighborInfo? = null
|
||||
|
||||
override fun start(scope: CoroutineScope) {
|
||||
this.scope = scope
|
||||
}
|
||||
|
||||
override fun recordStartTime(requestId: Int) {
|
||||
startTimes.update { it.put(requestId, nowMillis) }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import kotlinx.coroutines.CoroutineScope
|
|||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.first
|
||||
import okio.ByteString
|
||||
import org.koin.core.annotation.Named
|
||||
import org.koin.core.annotation.Single
|
||||
import org.meshtastic.core.common.util.handledLaunch
|
||||
import org.meshtastic.core.model.DataPacket
|
||||
|
|
@ -59,8 +60,8 @@ class NodeManagerImpl(
|
|||
private val nodeRepository: NodeRepository,
|
||||
private val serviceBroadcasts: ServiceBroadcasts,
|
||||
private val notificationManager: NotificationManager,
|
||||
@Named("ServiceScope") private val scope: CoroutineScope,
|
||||
) : NodeManager {
|
||||
private lateinit var scope: CoroutineScope
|
||||
|
||||
private val _nodeDBbyNodeNum = atomic(persistentMapOf<Int, Node>())
|
||||
private val _nodeDBbyID = atomic(persistentMapOf<String, Node>())
|
||||
|
|
@ -88,10 +89,6 @@ class NodeManagerImpl(
|
|||
myNodeNum.value = num
|
||||
}
|
||||
|
||||
override fun start(scope: CoroutineScope) {
|
||||
this.scope = scope
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TIME_MS_TO_S = 1000L
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ import kotlinx.coroutines.sync.Mutex
|
|||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.coroutines.withTimeout
|
||||
import kotlinx.coroutines.withTimeoutOrNull
|
||||
import org.koin.core.annotation.Named
|
||||
import org.koin.core.annotation.Single
|
||||
import org.meshtastic.core.common.util.handledLaunch
|
||||
import org.meshtastic.core.common.util.nowMillis
|
||||
|
|
@ -60,6 +61,7 @@ class PacketHandlerImpl(
|
|||
private val radioInterfaceService: RadioInterfaceService,
|
||||
private val meshLogRepository: Lazy<MeshLogRepository>,
|
||||
private val serviceRepository: ServiceRepository,
|
||||
@Named("ServiceScope") private val scope: CoroutineScope,
|
||||
) : PacketHandler {
|
||||
|
||||
companion object {
|
||||
|
|
@ -67,7 +69,6 @@ class PacketHandlerImpl(
|
|||
}
|
||||
|
||||
private var queueJob: Job? = null
|
||||
private lateinit var scope: CoroutineScope
|
||||
|
||||
private val queueMutex = Mutex()
|
||||
private val queuedPackets = mutableListOf<MeshPacket>()
|
||||
|
|
@ -79,11 +80,6 @@ class PacketHandlerImpl(
|
|||
private val responseMutex = Mutex()
|
||||
private val queueResponse = mutableMapOf<Int, CompletableDeferred<Boolean>>()
|
||||
|
||||
override fun start(scope: CoroutineScope) {
|
||||
this.scope = scope
|
||||
queueStopped = false // Safe: called before any concurrent operations on this scope.
|
||||
}
|
||||
|
||||
override fun sendToRadio(p: ToRadio) {
|
||||
Logger.d { "Sending to radio ${p.toPIIString()}" }
|
||||
val b = p.encode()
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import co.touchlab.kermit.Logger
|
|||
import kotlinx.coroutines.CoroutineScope
|
||||
import okio.ByteString.Companion.toByteString
|
||||
import okio.IOException
|
||||
import org.koin.core.annotation.Named
|
||||
import org.koin.core.annotation.Single
|
||||
import org.meshtastic.core.common.util.handledLaunch
|
||||
import org.meshtastic.core.model.DataPacket
|
||||
|
|
@ -45,12 +46,8 @@ class StoreForwardPacketHandlerImpl(
|
|||
private val serviceBroadcasts: ServiceBroadcasts,
|
||||
private val historyManager: HistoryManager,
|
||||
private val dataHandler: Lazy<MeshDataHandler>,
|
||||
@Named("ServiceScope") private val scope: CoroutineScope,
|
||||
) : StoreForwardPacketHandler {
|
||||
private lateinit var scope: CoroutineScope
|
||||
|
||||
override fun start(scope: CoroutineScope) {
|
||||
this.scope = scope
|
||||
}
|
||||
|
||||
override fun handleStoreAndForward(packet: MeshPacket, dataPacket: DataPacket, myNodeNum: Int) {
|
||||
val payload = packet.decoded?.payload ?: return
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import kotlinx.coroutines.CoroutineScope
|
|||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import org.koin.core.annotation.Named
|
||||
import org.koin.core.annotation.Single
|
||||
import org.meshtastic.core.common.util.nowSeconds
|
||||
import org.meshtastic.core.model.DataPacket
|
||||
|
|
@ -49,16 +50,12 @@ class TelemetryPacketHandlerImpl(
|
|||
private val nodeManager: NodeManager,
|
||||
private val connectionManager: Lazy<MeshConnectionManager>,
|
||||
private val notificationManager: NotificationManager,
|
||||
@Named("ServiceScope") private val scope: CoroutineScope,
|
||||
) : TelemetryPacketHandler {
|
||||
private lateinit var scope: CoroutineScope
|
||||
|
||||
private val batteryMutex = Mutex()
|
||||
private val batteryPercentCooldowns = mutableMapOf<Int, Long>()
|
||||
|
||||
override fun start(scope: CoroutineScope) {
|
||||
this.scope = scope
|
||||
}
|
||||
|
||||
@Suppress("LongMethod", "CyclomaticComplexMethod")
|
||||
override fun handleTelemetry(packet: MeshPacket, dataPacket: DataPacket, myNodeNum: Int) {
|
||||
val payload = packet.decoded?.payload ?: return
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import kotlinx.atomicfu.update
|
|||
import kotlinx.collections.immutable.persistentMapOf
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Job
|
||||
import org.koin.core.annotation.Named
|
||||
import org.koin.core.annotation.Single
|
||||
import org.meshtastic.core.common.util.NumberFormatter
|
||||
import org.meshtastic.core.common.util.handledLaunch
|
||||
|
|
@ -42,15 +43,11 @@ class TracerouteHandlerImpl(
|
|||
private val serviceRepository: ServiceRepository,
|
||||
private val tracerouteSnapshotRepository: TracerouteSnapshotRepository,
|
||||
private val nodeRepository: NodeRepository,
|
||||
@Named("ServiceScope") private val scope: CoroutineScope,
|
||||
) : TracerouteHandler {
|
||||
private lateinit var scope: CoroutineScope
|
||||
|
||||
private val startTimes = atomic(persistentMapOf<Int, Long>())
|
||||
|
||||
override fun start(scope: CoroutineScope) {
|
||||
this.scope = scope
|
||||
}
|
||||
|
||||
override fun recordStartTime(requestId: Int) {
|
||||
startTimes.update { it.put(requestId, nowMillis) }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import dev.mokkery.mock
|
|||
import dev.mokkery.verify
|
||||
import dev.mokkery.verify.VerifyMode.Companion.not
|
||||
import dev.mokkery.verifySuspend
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
import kotlinx.coroutines.test.UnconfinedTestDispatcher
|
||||
|
|
@ -89,28 +90,28 @@ class MeshActionHandlerImplTest {
|
|||
every { nodeManager.myNodeNum } returns myNodeNumFlow
|
||||
every { nodeManager.getMyId() } returns "!12345678"
|
||||
every { nodeManager.nodeDBbyNodeNum } returns emptyMap()
|
||||
|
||||
handler =
|
||||
MeshActionHandlerImpl(
|
||||
nodeManager = nodeManager,
|
||||
commandSender = commandSender,
|
||||
packetRepository = lazy { packetRepository },
|
||||
serviceBroadcasts = serviceBroadcasts,
|
||||
dataHandler = lazy { dataHandler },
|
||||
analytics = analytics,
|
||||
meshPrefs = meshPrefs,
|
||||
databaseManager = databaseManager,
|
||||
notificationManager = notificationManager,
|
||||
messageProcessor = lazy { messageProcessor },
|
||||
radioConfigRepository = radioConfigRepository,
|
||||
)
|
||||
}
|
||||
|
||||
private fun createHandler(scope: CoroutineScope): MeshActionHandlerImpl = MeshActionHandlerImpl(
|
||||
nodeManager = nodeManager,
|
||||
commandSender = commandSender,
|
||||
packetRepository = lazy { packetRepository },
|
||||
serviceBroadcasts = serviceBroadcasts,
|
||||
dataHandler = lazy { dataHandler },
|
||||
analytics = analytics,
|
||||
meshPrefs = meshPrefs,
|
||||
databaseManager = databaseManager,
|
||||
notificationManager = notificationManager,
|
||||
messageProcessor = lazy { messageProcessor },
|
||||
radioConfigRepository = radioConfigRepository,
|
||||
scope = scope,
|
||||
)
|
||||
|
||||
// ---- handleUpdateLastAddress (device-switch path — P0 critical) ----
|
||||
|
||||
@Test
|
||||
fun handleUpdateLastAddress_differentAddress_switchesDatabaseAndClearsState() = runTest(testDispatcher) {
|
||||
handler.start(backgroundScope)
|
||||
handler = createHandler(backgroundScope)
|
||||
|
||||
every { meshPrefs.deviceAddress } returns MutableStateFlow("old_addr")
|
||||
everySuspend { databaseManager.switchActiveDatabase(any()) } returns Unit
|
||||
|
|
@ -128,7 +129,7 @@ class MeshActionHandlerImplTest {
|
|||
|
||||
@Test
|
||||
fun handleUpdateLastAddress_sameAddress_noOp() = runTest(testDispatcher) {
|
||||
handler.start(backgroundScope)
|
||||
handler = createHandler(backgroundScope)
|
||||
|
||||
every { meshPrefs.deviceAddress } returns MutableStateFlow("same_addr")
|
||||
|
||||
|
|
@ -141,7 +142,7 @@ class MeshActionHandlerImplTest {
|
|||
|
||||
@Test
|
||||
fun handleUpdateLastAddress_nullAddress_switchesIfDifferent() = runTest(testDispatcher) {
|
||||
handler.start(backgroundScope)
|
||||
handler = createHandler(backgroundScope)
|
||||
|
||||
every { meshPrefs.deviceAddress } returns MutableStateFlow("old_addr")
|
||||
everySuspend { databaseManager.switchActiveDatabase(any()) } returns Unit
|
||||
|
|
@ -156,7 +157,7 @@ class MeshActionHandlerImplTest {
|
|||
|
||||
@Test
|
||||
fun handleUpdateLastAddress_nullToNull_noOp() = runTest(testDispatcher) {
|
||||
handler.start(backgroundScope)
|
||||
handler = createHandler(backgroundScope)
|
||||
|
||||
every { meshPrefs.deviceAddress } returns MutableStateFlow(null)
|
||||
|
||||
|
|
@ -168,7 +169,7 @@ class MeshActionHandlerImplTest {
|
|||
|
||||
@Test
|
||||
fun handleUpdateLastAddress_executesStepsInOrder() = runTest(testDispatcher) {
|
||||
handler.start(backgroundScope)
|
||||
handler = createHandler(backgroundScope)
|
||||
|
||||
every { meshPrefs.deviceAddress } returns MutableStateFlow("old")
|
||||
everySuspend { databaseManager.switchActiveDatabase(any()) } returns Unit
|
||||
|
|
@ -187,7 +188,7 @@ class MeshActionHandlerImplTest {
|
|||
|
||||
@Test
|
||||
fun onServiceAction_nullMyNodeNum_doesNothing() = runTest(testDispatcher) {
|
||||
handler.start(backgroundScope)
|
||||
handler = createHandler(backgroundScope)
|
||||
myNodeNumFlow.value = null
|
||||
|
||||
val node = createTestNode(REMOTE_NODE_NUM)
|
||||
|
|
@ -201,7 +202,7 @@ class MeshActionHandlerImplTest {
|
|||
|
||||
@Test
|
||||
fun onServiceAction_favorite_sendsSetFavoriteWhenNotFavorite() = runTest(testDispatcher) {
|
||||
handler.start(backgroundScope)
|
||||
handler = createHandler(backgroundScope)
|
||||
val node = createTestNode(REMOTE_NODE_NUM, isFavorite = false)
|
||||
|
||||
handler.onServiceAction(ServiceAction.Favorite(node))
|
||||
|
|
@ -213,7 +214,7 @@ class MeshActionHandlerImplTest {
|
|||
|
||||
@Test
|
||||
fun onServiceAction_favorite_sendsRemoveFavoriteWhenAlreadyFavorite() = runTest(testDispatcher) {
|
||||
handler.start(backgroundScope)
|
||||
handler = createHandler(backgroundScope)
|
||||
val node = createTestNode(REMOTE_NODE_NUM, isFavorite = true)
|
||||
|
||||
handler.onServiceAction(ServiceAction.Favorite(node))
|
||||
|
|
@ -227,7 +228,7 @@ class MeshActionHandlerImplTest {
|
|||
|
||||
@Test
|
||||
fun onServiceAction_ignore_togglesAndUpdatesFilteredBySender() = runTest(testDispatcher) {
|
||||
handler.start(backgroundScope)
|
||||
handler = createHandler(backgroundScope)
|
||||
val node = createTestNode(REMOTE_NODE_NUM, isIgnored = false)
|
||||
|
||||
handler.onServiceAction(ServiceAction.Ignore(node))
|
||||
|
|
@ -242,7 +243,7 @@ class MeshActionHandlerImplTest {
|
|||
|
||||
@Test
|
||||
fun onServiceAction_mute_togglesMutedState() = runTest(testDispatcher) {
|
||||
handler.start(backgroundScope)
|
||||
handler = createHandler(backgroundScope)
|
||||
val node = createTestNode(REMOTE_NODE_NUM, isMuted = false)
|
||||
|
||||
handler.onServiceAction(ServiceAction.Mute(node))
|
||||
|
|
@ -256,7 +257,7 @@ class MeshActionHandlerImplTest {
|
|||
|
||||
@Test
|
||||
fun onServiceAction_getDeviceMetadata_sendsAdminRequest() = runTest(testDispatcher) {
|
||||
handler.start(backgroundScope)
|
||||
handler = createHandler(backgroundScope)
|
||||
|
||||
handler.onServiceAction(ServiceAction.GetDeviceMetadata(REMOTE_NODE_NUM))
|
||||
advanceUntilIdle()
|
||||
|
|
@ -268,7 +269,7 @@ class MeshActionHandlerImplTest {
|
|||
|
||||
@Test
|
||||
fun onServiceAction_sendContact_completesWithTrueOnSuccess() = runTest(testDispatcher) {
|
||||
handler.start(backgroundScope)
|
||||
handler = createHandler(backgroundScope)
|
||||
everySuspend { commandSender.sendAdminAwait(any(), any(), any(), any()) } returns true
|
||||
|
||||
val action = ServiceAction.SendContact(SharedContact())
|
||||
|
|
@ -281,7 +282,7 @@ class MeshActionHandlerImplTest {
|
|||
|
||||
@Test
|
||||
fun onServiceAction_sendContact_completesWithFalseOnFailure() = runTest(testDispatcher) {
|
||||
handler.start(backgroundScope)
|
||||
handler = createHandler(backgroundScope)
|
||||
everySuspend { commandSender.sendAdminAwait(any(), any(), any(), any()) } returns false
|
||||
|
||||
val action = ServiceAction.SendContact(SharedContact())
|
||||
|
|
@ -296,7 +297,7 @@ class MeshActionHandlerImplTest {
|
|||
|
||||
@Test
|
||||
fun onServiceAction_importContact_sendsAdminAndUpdatesNode() = runTest(testDispatcher) {
|
||||
handler.start(backgroundScope)
|
||||
handler = createHandler(backgroundScope)
|
||||
|
||||
val contact =
|
||||
SharedContact(node_num = REMOTE_NODE_NUM, user = User(id = "!abcdef12", long_name = "TestUser"))
|
||||
|
|
@ -311,7 +312,7 @@ class MeshActionHandlerImplTest {
|
|||
|
||||
@Test
|
||||
fun handleSetOwner_sendsAdminAndUpdatesLocalNode() {
|
||||
handler.start(testScope)
|
||||
handler = createHandler(testScope)
|
||||
val meshUser =
|
||||
MeshUser(
|
||||
id = "!12345678",
|
||||
|
|
@ -331,7 +332,7 @@ class MeshActionHandlerImplTest {
|
|||
|
||||
@Test
|
||||
fun handleSend_sendsDataAndBroadcastsStatus() {
|
||||
handler.start(testScope)
|
||||
handler = createHandler(testScope)
|
||||
val packet = DataPacket(to = "!deadbeef", dataType = 1, bytes = null, channel = 0)
|
||||
|
||||
handler.handleSend(packet, MY_NODE_NUM)
|
||||
|
|
@ -345,7 +346,7 @@ class MeshActionHandlerImplTest {
|
|||
|
||||
@Test
|
||||
fun handleRequestPosition_sameNode_doesNothing() {
|
||||
handler.start(testScope)
|
||||
handler = createHandler(testScope)
|
||||
|
||||
handler.handleRequestPosition(MY_NODE_NUM, Position(0.0, 0.0, 0), MY_NODE_NUM)
|
||||
|
||||
|
|
@ -354,7 +355,7 @@ class MeshActionHandlerImplTest {
|
|||
|
||||
@Test
|
||||
fun handleRequestPosition_provideLocation_validPosition_usesGivenPosition() {
|
||||
handler.start(testScope)
|
||||
handler = createHandler(testScope)
|
||||
every { meshPrefs.shouldProvideNodeLocation(MY_NODE_NUM) } returns MutableStateFlow(true)
|
||||
|
||||
val validPosition = Position(37.7749, -122.4194, 10)
|
||||
|
|
@ -365,7 +366,7 @@ class MeshActionHandlerImplTest {
|
|||
|
||||
@Test
|
||||
fun handleRequestPosition_provideLocation_invalidPosition_fallsBackToNodeDB() {
|
||||
handler.start(testScope)
|
||||
handler = createHandler(testScope)
|
||||
every { meshPrefs.shouldProvideNodeLocation(MY_NODE_NUM) } returns MutableStateFlow(true)
|
||||
every { nodeManager.nodeDBbyNodeNum } returns emptyMap()
|
||||
|
||||
|
|
@ -378,7 +379,7 @@ class MeshActionHandlerImplTest {
|
|||
|
||||
@Test
|
||||
fun handleRequestPosition_doNotProvide_sendsZeroPosition() {
|
||||
handler.start(testScope)
|
||||
handler = createHandler(testScope)
|
||||
every { meshPrefs.shouldProvideNodeLocation(MY_NODE_NUM) } returns MutableStateFlow(false)
|
||||
|
||||
val validPosition = Position(37.7749, -122.4194, 10)
|
||||
|
|
@ -392,7 +393,7 @@ class MeshActionHandlerImplTest {
|
|||
|
||||
@Test
|
||||
fun handleSetConfig_decodesAndSendsAdmin_thenPersistsLocally() = runTest(testDispatcher) {
|
||||
handler.start(backgroundScope)
|
||||
handler = createHandler(backgroundScope)
|
||||
everySuspend { radioConfigRepository.setLocalConfig(any()) } returns Unit
|
||||
|
||||
val config = Config(lora = Config.LoRaConfig(hop_limit = 5))
|
||||
|
|
@ -409,7 +410,7 @@ class MeshActionHandlerImplTest {
|
|||
|
||||
@Test
|
||||
fun handleSetModuleConfig_ownNode_persistsLocally() = runTest(testDispatcher) {
|
||||
handler.start(backgroundScope)
|
||||
handler = createHandler(backgroundScope)
|
||||
myNodeNumFlow.value = MY_NODE_NUM
|
||||
everySuspend { radioConfigRepository.setLocalModuleConfig(any()) } returns Unit
|
||||
|
||||
|
|
@ -425,7 +426,7 @@ class MeshActionHandlerImplTest {
|
|||
|
||||
@Test
|
||||
fun handleSetModuleConfig_remoteNode_doesNotPersistLocally() = runTest(testDispatcher) {
|
||||
handler.start(backgroundScope)
|
||||
handler = createHandler(backgroundScope)
|
||||
myNodeNumFlow.value = MY_NODE_NUM
|
||||
|
||||
val moduleConfig = ModuleConfig(mqtt = ModuleConfig.MQTTConfig(enabled = true))
|
||||
|
|
@ -442,7 +443,7 @@ class MeshActionHandlerImplTest {
|
|||
|
||||
@Test
|
||||
fun handleSetChannel_nonNullPayload_decodesAndPersists() = runTest(testDispatcher) {
|
||||
handler.start(backgroundScope)
|
||||
handler = createHandler(backgroundScope)
|
||||
everySuspend { radioConfigRepository.updateChannelSettings(any()) } returns Unit
|
||||
|
||||
val channel = Channel(index = 1)
|
||||
|
|
@ -457,7 +458,7 @@ class MeshActionHandlerImplTest {
|
|||
|
||||
@Test
|
||||
fun handleSetChannel_nullPayload_doesNothing() {
|
||||
handler.start(testScope)
|
||||
handler = createHandler(testScope)
|
||||
|
||||
handler.handleSetChannel(null, MY_NODE_NUM)
|
||||
|
||||
|
|
@ -468,7 +469,7 @@ class MeshActionHandlerImplTest {
|
|||
|
||||
@Test
|
||||
fun handleRemoveByNodenum_removesAndSendsAdmin() {
|
||||
handler.start(testScope)
|
||||
handler = createHandler(testScope)
|
||||
|
||||
handler.handleRemoveByNodenum(REMOTE_NODE_NUM, 99, MY_NODE_NUM)
|
||||
|
||||
|
|
@ -480,7 +481,7 @@ class MeshActionHandlerImplTest {
|
|||
|
||||
@Test
|
||||
fun handleSetRemoteOwner_decodesAndSendsAdmin() {
|
||||
handler.start(testScope)
|
||||
handler = createHandler(testScope)
|
||||
|
||||
val user = User(id = "!remote01", long_name = "Remote", short_name = "RM")
|
||||
val payload = User.ADAPTER.encode(user)
|
||||
|
|
@ -495,7 +496,7 @@ class MeshActionHandlerImplTest {
|
|||
|
||||
@Test
|
||||
fun handleGetRemoteConfig_sessionkeyConfig_sendsDeviceMetadataRequest() {
|
||||
handler.start(testScope)
|
||||
handler = createHandler(testScope)
|
||||
|
||||
handler.handleGetRemoteConfig(1, REMOTE_NODE_NUM, AdminMessage.ConfigType.SESSIONKEY_CONFIG.value)
|
||||
|
||||
|
|
@ -504,7 +505,7 @@ class MeshActionHandlerImplTest {
|
|||
|
||||
@Test
|
||||
fun handleGetRemoteConfig_regularConfig_sendsConfigRequest() {
|
||||
handler.start(testScope)
|
||||
handler = createHandler(testScope)
|
||||
|
||||
handler.handleGetRemoteConfig(1, REMOTE_NODE_NUM, AdminMessage.ConfigType.LORA_CONFIG.value)
|
||||
|
||||
|
|
@ -515,7 +516,7 @@ class MeshActionHandlerImplTest {
|
|||
|
||||
@Test
|
||||
fun handleSetRemoteChannel_nullPayload_doesNothing() {
|
||||
handler.start(testScope)
|
||||
handler = createHandler(testScope)
|
||||
|
||||
handler.handleSetRemoteChannel(1, REMOTE_NODE_NUM, null)
|
||||
|
||||
|
|
@ -524,7 +525,7 @@ class MeshActionHandlerImplTest {
|
|||
|
||||
@Test
|
||||
fun handleSetRemoteChannel_nonNullPayload_decodesAndSendsAdmin() {
|
||||
handler.start(testScope)
|
||||
handler = createHandler(testScope)
|
||||
|
||||
val channel = Channel(index = 2)
|
||||
val payload = Channel.ADAPTER.encode(channel)
|
||||
|
|
@ -538,7 +539,7 @@ class MeshActionHandlerImplTest {
|
|||
|
||||
@Test
|
||||
fun handleRequestRebootOta_withNullHash_sendsAdmin() {
|
||||
handler.start(testScope)
|
||||
handler = createHandler(testScope)
|
||||
|
||||
handler.handleRequestRebootOta(1, REMOTE_NODE_NUM, 0, null)
|
||||
|
||||
|
|
@ -547,7 +548,7 @@ class MeshActionHandlerImplTest {
|
|||
|
||||
@Test
|
||||
fun handleRequestRebootOta_withHash_sendsAdmin() {
|
||||
handler.start(testScope)
|
||||
handler = createHandler(testScope)
|
||||
|
||||
val hash = byteArrayOf(0x01, 0x02, 0x03)
|
||||
handler.handleRequestRebootOta(1, REMOTE_NODE_NUM, 1, hash)
|
||||
|
|
@ -559,7 +560,7 @@ class MeshActionHandlerImplTest {
|
|||
|
||||
@Test
|
||||
fun handleRequestNodedbReset_sendsAdminWithPreserveFavorites() {
|
||||
handler.start(testScope)
|
||||
handler = createHandler(testScope)
|
||||
|
||||
handler.handleRequestNodedbReset(1, REMOTE_NODE_NUM, preserveFavorites = true)
|
||||
|
||||
|
|
|
|||
|
|
@ -98,8 +98,8 @@ class MeshConfigFlowManagerImplTest {
|
|||
analytics = analytics,
|
||||
commandSender = commandSender,
|
||||
packetHandler = packetHandler,
|
||||
scope = testScope,
|
||||
)
|
||||
manager.start(testScope)
|
||||
}
|
||||
|
||||
// ---------- handleMyInfo ----------
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import dev.mokkery.matcher.any
|
|||
import dev.mokkery.mock
|
||||
import dev.mokkery.verify
|
||||
import dev.mokkery.verifySuspend
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.test.UnconfinedTestDispatcher
|
||||
|
|
@ -60,20 +61,20 @@ class MeshConfigHandlerImplTest {
|
|||
fun setUp() {
|
||||
every { radioConfigRepository.localConfigFlow } returns localConfigFlow
|
||||
every { radioConfigRepository.moduleConfigFlow } returns moduleConfigFlow
|
||||
|
||||
handler =
|
||||
MeshConfigHandlerImpl(
|
||||
radioConfigRepository = radioConfigRepository,
|
||||
serviceRepository = serviceRepository,
|
||||
nodeManager = nodeManager,
|
||||
)
|
||||
}
|
||||
|
||||
private fun createHandler(scope: CoroutineScope): MeshConfigHandlerImpl = MeshConfigHandlerImpl(
|
||||
radioConfigRepository = radioConfigRepository,
|
||||
serviceRepository = serviceRepository,
|
||||
nodeManager = nodeManager,
|
||||
scope = scope,
|
||||
)
|
||||
|
||||
// ---------- start and flow wiring ----------
|
||||
|
||||
@Test
|
||||
fun `start wires localConfig flow from repository`() = runTest(testDispatcher) {
|
||||
handler.start(backgroundScope)
|
||||
handler = createHandler(backgroundScope)
|
||||
val config = LocalConfig(device = Config.DeviceConfig(role = Config.DeviceConfig.Role.ROUTER))
|
||||
localConfigFlow.value = config
|
||||
advanceUntilIdle()
|
||||
|
|
@ -83,7 +84,7 @@ class MeshConfigHandlerImplTest {
|
|||
|
||||
@Test
|
||||
fun `start wires moduleConfig flow from repository`() = runTest(testDispatcher) {
|
||||
handler.start(backgroundScope)
|
||||
handler = createHandler(backgroundScope)
|
||||
val config = LocalModuleConfig(mqtt = ModuleConfig.MQTTConfig(enabled = true))
|
||||
moduleConfigFlow.value = config
|
||||
advanceUntilIdle()
|
||||
|
|
@ -95,7 +96,7 @@ class MeshConfigHandlerImplTest {
|
|||
|
||||
@Test
|
||||
fun `handleDeviceConfig persists config and updates progress`() = runTest(testDispatcher) {
|
||||
handler.start(backgroundScope)
|
||||
handler = createHandler(backgroundScope)
|
||||
val config = Config(device = Config.DeviceConfig(role = Config.DeviceConfig.Role.CLIENT))
|
||||
handler.handleDeviceConfig(config)
|
||||
advanceUntilIdle()
|
||||
|
|
@ -106,7 +107,7 @@ class MeshConfigHandlerImplTest {
|
|||
|
||||
@Test
|
||||
fun `handleDeviceConfig handles all config variants`() = runTest(testDispatcher) {
|
||||
handler.start(backgroundScope)
|
||||
handler = createHandler(backgroundScope)
|
||||
val configs =
|
||||
listOf(
|
||||
Config(position = Config.PositionConfig()),
|
||||
|
|
@ -131,7 +132,7 @@ class MeshConfigHandlerImplTest {
|
|||
|
||||
@Test
|
||||
fun `handleModuleConfig persists config and updates progress`() = runTest(testDispatcher) {
|
||||
handler.start(backgroundScope)
|
||||
handler = createHandler(backgroundScope)
|
||||
val config = ModuleConfig(mqtt = ModuleConfig.MQTTConfig(enabled = true))
|
||||
handler.handleModuleConfig(config)
|
||||
advanceUntilIdle()
|
||||
|
|
@ -142,7 +143,7 @@ class MeshConfigHandlerImplTest {
|
|||
|
||||
@Test
|
||||
fun `handleModuleConfig with statusmessage updates node status`() = runTest(testDispatcher) {
|
||||
handler.start(backgroundScope)
|
||||
handler = createHandler(backgroundScope)
|
||||
val myNum = 123
|
||||
every { nodeManager.myNodeNum } returns MutableStateFlow<Int?>(myNum)
|
||||
|
||||
|
|
@ -155,7 +156,7 @@ class MeshConfigHandlerImplTest {
|
|||
|
||||
@Test
|
||||
fun `handleModuleConfig with statusmessage skipped when myNodeNum is null`() = runTest(testDispatcher) {
|
||||
handler.start(backgroundScope)
|
||||
handler = createHandler(backgroundScope)
|
||||
every { nodeManager.myNodeNum } returns MutableStateFlow<Int?>(null)
|
||||
|
||||
val config = ModuleConfig(statusmessage = ModuleConfig.StatusMessageConfig(node_status = "Active"))
|
||||
|
|
@ -168,7 +169,7 @@ class MeshConfigHandlerImplTest {
|
|||
|
||||
@Test
|
||||
fun `handleChannel persists channel settings`() = runTest(testDispatcher) {
|
||||
handler.start(backgroundScope)
|
||||
handler = createHandler(backgroundScope)
|
||||
val channel = Channel(index = 0)
|
||||
handler.handleChannel(channel)
|
||||
advanceUntilIdle()
|
||||
|
|
@ -178,7 +179,7 @@ class MeshConfigHandlerImplTest {
|
|||
|
||||
@Test
|
||||
fun `handleChannel shows progress with max channels when myNodeInfo available`() = runTest(testDispatcher) {
|
||||
handler.start(backgroundScope)
|
||||
handler = createHandler(backgroundScope)
|
||||
every { nodeManager.getMyNodeInfo() } returns
|
||||
MyNodeInfo(
|
||||
myNodeNum = 123,
|
||||
|
|
@ -206,7 +207,7 @@ class MeshConfigHandlerImplTest {
|
|||
|
||||
@Test
|
||||
fun `handleChannel shows progress without max channels when myNodeInfo unavailable`() = runTest(testDispatcher) {
|
||||
handler.start(backgroundScope)
|
||||
handler = createHandler(backgroundScope)
|
||||
every { nodeManager.getMyNodeInfo() } returns null
|
||||
|
||||
val channel = Channel(index = 0)
|
||||
|
|
@ -220,7 +221,7 @@ class MeshConfigHandlerImplTest {
|
|||
|
||||
@Test
|
||||
fun `handleDeviceUIConfig persists config`() = runTest(testDispatcher) {
|
||||
handler.start(backgroundScope)
|
||||
handler = createHandler(backgroundScope)
|
||||
val config = DeviceUIConfig()
|
||||
handler.handleDeviceUIConfig(config)
|
||||
advanceUntilIdle()
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ import dev.mokkery.everySuspend
|
|||
import dev.mokkery.matcher.any
|
||||
import dev.mokkery.mock
|
||||
import dev.mokkery.verify
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.test.UnconfinedTestDispatcher
|
||||
|
|
@ -60,7 +60,7 @@ import kotlin.test.BeforeTest
|
|||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class)
|
||||
class MeshConnectionManagerImplTest {
|
||||
private val radioInterfaceService = mock<RadioInterfaceService>(MockMode.autofill)
|
||||
private val serviceRepository = mock<ServiceRepository>(MockMode.autofill)
|
||||
|
|
@ -108,29 +108,29 @@ class MeshConnectionManagerImplTest {
|
|||
every { mqttManager.stop() } returns Unit
|
||||
every { nodeManager.nodeDBbyNodeNum } returns emptyMap<Int, Node>()
|
||||
every { packetHandler.sendToRadio(any<org.meshtastic.proto.ToRadio>()) } returns Unit
|
||||
|
||||
manager =
|
||||
MeshConnectionManagerImpl(
|
||||
radioInterfaceService,
|
||||
serviceRepository,
|
||||
serviceBroadcasts,
|
||||
serviceNotifications,
|
||||
uiPrefs,
|
||||
packetHandler,
|
||||
nodeRepository,
|
||||
locationManager,
|
||||
mqttManager,
|
||||
historyManager,
|
||||
radioConfigRepository,
|
||||
commandSender,
|
||||
nodeManager,
|
||||
analytics,
|
||||
packetRepository,
|
||||
workerManager,
|
||||
appWidgetUpdater,
|
||||
)
|
||||
}
|
||||
|
||||
private fun createManager(scope: CoroutineScope): MeshConnectionManagerImpl = MeshConnectionManagerImpl(
|
||||
radioInterfaceService,
|
||||
serviceRepository,
|
||||
serviceBroadcasts,
|
||||
serviceNotifications,
|
||||
uiPrefs,
|
||||
packetHandler,
|
||||
nodeRepository,
|
||||
locationManager,
|
||||
mqttManager,
|
||||
historyManager,
|
||||
radioConfigRepository,
|
||||
commandSender,
|
||||
nodeManager,
|
||||
analytics,
|
||||
packetRepository,
|
||||
workerManager,
|
||||
appWidgetUpdater,
|
||||
scope,
|
||||
)
|
||||
|
||||
@AfterTest fun tearDown() {}
|
||||
|
||||
@Test
|
||||
|
|
@ -138,7 +138,7 @@ class MeshConnectionManagerImplTest {
|
|||
every { packetHandler.sendToRadio(any<org.meshtastic.proto.ToRadio>()) } returns Unit
|
||||
every { serviceNotifications.updateServiceStateNotification(any(), any()) } returns Unit
|
||||
|
||||
manager.start(backgroundScope)
|
||||
manager = createManager(backgroundScope)
|
||||
radioConnectionState.value = ConnectionState.Connected
|
||||
advanceUntilIdle()
|
||||
|
||||
|
|
@ -163,7 +163,7 @@ class MeshConnectionManagerImplTest {
|
|||
every { locationManager.stop() } returns Unit
|
||||
every { mqttManager.stop() } returns Unit
|
||||
every { nodeManager.nodeDBbyNodeNum } returns emptyMap()
|
||||
manager.start(backgroundScope)
|
||||
manager = createManager(backgroundScope)
|
||||
// Transition to Connected first so that Disconnected actually does something
|
||||
radioConnectionState.value = ConnectionState.Connected
|
||||
advanceUntilIdle()
|
||||
|
|
@ -197,7 +197,7 @@ class MeshConnectionManagerImplTest {
|
|||
every { mqttManager.stop() } returns Unit
|
||||
every { nodeManager.nodeDBbyNodeNum } returns emptyMap()
|
||||
|
||||
manager.start(backgroundScope)
|
||||
manager = createManager(backgroundScope)
|
||||
advanceUntilIdle()
|
||||
|
||||
radioConnectionState.value = ConnectionState.DeviceSleep
|
||||
|
|
@ -221,7 +221,7 @@ class MeshConnectionManagerImplTest {
|
|||
every { locationManager.stop() } returns Unit
|
||||
every { mqttManager.stop() } returns Unit
|
||||
|
||||
manager.start(backgroundScope)
|
||||
manager = createManager(backgroundScope)
|
||||
advanceUntilIdle()
|
||||
|
||||
radioConnectionState.value = ConnectionState.DeviceSleep
|
||||
|
|
@ -236,7 +236,7 @@ class MeshConnectionManagerImplTest {
|
|||
|
||||
@Test
|
||||
fun `onRadioConfigLoaded enqueues queued packets and sets time`() = runTest(testDispatcher) {
|
||||
manager.start(backgroundScope)
|
||||
manager = createManager(backgroundScope)
|
||||
val packetId = 456
|
||||
everySuspend { packetRepository.getQueuedPackets() } returns listOf(dataPacket)
|
||||
every { workerManager.enqueueSendMessage(any()) } returns Unit
|
||||
|
|
@ -257,15 +257,15 @@ class MeshConnectionManagerImplTest {
|
|||
moduleConfigFlow.value = moduleConfig
|
||||
every { commandSender.requestTelemetry(any(), any(), any()) } returns Unit
|
||||
every { nodeManager.myNodeNum } returns MutableStateFlow(123)
|
||||
every { mqttManager.start(any(), any(), any()) } returns Unit
|
||||
every { mqttManager.startProxy(any(), any()) } returns Unit
|
||||
every { historyManager.requestHistoryReplay(any(), any(), any(), any()) } returns Unit
|
||||
every { nodeManager.getMyNodeInfo() } returns null
|
||||
|
||||
manager.start(backgroundScope)
|
||||
manager = createManager(backgroundScope)
|
||||
manager.onNodeDbReady()
|
||||
advanceUntilIdle()
|
||||
|
||||
verify { mqttManager.start(any(), true, true) }
|
||||
verify { mqttManager.startProxy(true, true) }
|
||||
verify { historyManager.requestHistoryReplay(any(), any(), any(), any()) }
|
||||
}
|
||||
|
||||
|
|
@ -286,7 +286,7 @@ class MeshConnectionManagerImplTest {
|
|||
every { mqttManager.stop() } returns Unit
|
||||
every { nodeManager.nodeDBbyNodeNum } returns emptyMap()
|
||||
|
||||
manager.start(backgroundScope)
|
||||
manager = createManager(backgroundScope)
|
||||
advanceUntilIdle()
|
||||
|
||||
// Transition to Connected then DeviceSleep
|
||||
|
|
|
|||
|
|
@ -108,8 +108,8 @@ class MeshDataHandlerTest {
|
|||
storeForwardHandler = storeForwardHandler,
|
||||
telemetryHandler = telemetryHandler,
|
||||
adminPacketHandler = adminPacketHandler,
|
||||
scope = testScope,
|
||||
)
|
||||
handler.start(testScope)
|
||||
|
||||
// Default: mapper returns null for empty packets, which is the safe default
|
||||
every { dataMapper.toDataPacket(any()) } returns null
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import dev.mokkery.matcher.any
|
|||
import dev.mokkery.mock
|
||||
import dev.mokkery.verify
|
||||
import dev.mokkery.verifySuspend
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.test.UnconfinedTestDispatcher
|
||||
|
|
@ -65,22 +66,22 @@ class MeshMessageProcessorImplTest {
|
|||
every { nodeManager.isNodeDbReady } returns isNodeDbReady
|
||||
every { nodeManager.myNodeNum } returns MutableStateFlow<Int?>(myNodeNum)
|
||||
every { router.dataHandler } returns dataHandler
|
||||
|
||||
processor =
|
||||
MeshMessageProcessorImpl(
|
||||
nodeManager = nodeManager,
|
||||
serviceRepository = serviceRepository,
|
||||
meshLogRepository = lazy { meshLogRepository },
|
||||
router = lazy { router },
|
||||
fromRadioDispatcher = fromRadioDispatcher,
|
||||
)
|
||||
}
|
||||
|
||||
private fun createProcessor(scope: CoroutineScope): MeshMessageProcessorImpl = MeshMessageProcessorImpl(
|
||||
nodeManager = nodeManager,
|
||||
serviceRepository = serviceRepository,
|
||||
meshLogRepository = lazy { meshLogRepository },
|
||||
router = lazy { router },
|
||||
fromRadioDispatcher = fromRadioDispatcher,
|
||||
scope = scope,
|
||||
)
|
||||
|
||||
// ---------- handleFromRadio: non-packet variants ----------
|
||||
|
||||
@Test
|
||||
fun `handleFromRadio dispatches non-packet variants to fromRadioDispatcher`() = runTest(testDispatcher) {
|
||||
processor.start(backgroundScope)
|
||||
processor = createProcessor(backgroundScope)
|
||||
val logRecord = LogRecord(message = "test log")
|
||||
val fromRadio = FromRadio(log_record = logRecord)
|
||||
val bytes = FromRadio.ADAPTER.encode(fromRadio)
|
||||
|
|
@ -93,7 +94,7 @@ class MeshMessageProcessorImplTest {
|
|||
|
||||
@Test
|
||||
fun `handleFromRadio falls back to LogRecord parsing when FromRadio fails`() = runTest(testDispatcher) {
|
||||
processor.start(backgroundScope)
|
||||
processor = createProcessor(backgroundScope)
|
||||
// Encode a raw LogRecord (not wrapped in FromRadio) — first decode as FromRadio fails,
|
||||
// fallback decode as LogRecord succeeds
|
||||
val logRecord = LogRecord(message = "fallback log")
|
||||
|
|
@ -108,7 +109,7 @@ class MeshMessageProcessorImplTest {
|
|||
|
||||
@Test
|
||||
fun `handleFromRadio with completely invalid bytes does not crash`() = runTest(testDispatcher) {
|
||||
processor.start(backgroundScope)
|
||||
processor = createProcessor(backgroundScope)
|
||||
// Invalid protobuf bytes — both parses should fail
|
||||
val garbage = byteArrayOf(0xFF.toByte(), 0xFE.toByte(), 0xFD.toByte())
|
||||
|
||||
|
|
@ -121,7 +122,7 @@ class MeshMessageProcessorImplTest {
|
|||
|
||||
@Test
|
||||
fun `packets are buffered when node DB is not ready`() = runTest(testDispatcher) {
|
||||
processor.start(backgroundScope)
|
||||
processor = createProcessor(backgroundScope)
|
||||
isNodeDbReady.value = false
|
||||
|
||||
val packet =
|
||||
|
|
@ -141,7 +142,7 @@ class MeshMessageProcessorImplTest {
|
|||
|
||||
@Test
|
||||
fun `buffered packets are flushed when node DB becomes ready`() = runTest(testDispatcher) {
|
||||
processor.start(backgroundScope)
|
||||
processor = createProcessor(backgroundScope)
|
||||
isNodeDbReady.value = false
|
||||
|
||||
val packet =
|
||||
|
|
@ -165,7 +166,7 @@ class MeshMessageProcessorImplTest {
|
|||
|
||||
@Test
|
||||
fun `early buffer overflow drops oldest packet`() = runTest(testDispatcher) {
|
||||
processor.start(backgroundScope)
|
||||
processor = createProcessor(backgroundScope)
|
||||
isNodeDbReady.value = false
|
||||
|
||||
// The maxEarlyPacketBuffer is 10240 — we won't actually fill it in this test,
|
||||
|
|
@ -195,7 +196,7 @@ class MeshMessageProcessorImplTest {
|
|||
|
||||
@Test
|
||||
fun `packets with rx_time 0 get current time`() = runTest(testDispatcher) {
|
||||
processor.start(backgroundScope)
|
||||
processor = createProcessor(backgroundScope)
|
||||
isNodeDbReady.value = true
|
||||
|
||||
val packet =
|
||||
|
|
@ -214,7 +215,7 @@ class MeshMessageProcessorImplTest {
|
|||
|
||||
@Test
|
||||
fun `packets with non-zero rx_time keep their time`() = runTest(testDispatcher) {
|
||||
processor.start(backgroundScope)
|
||||
processor = createProcessor(backgroundScope)
|
||||
isNodeDbReady.value = true
|
||||
|
||||
val packet =
|
||||
|
|
@ -235,7 +236,7 @@ class MeshMessageProcessorImplTest {
|
|||
|
||||
@Test
|
||||
fun `processReceivedMeshPacket updates myNode lastHeard`() = runTest(testDispatcher) {
|
||||
processor.start(backgroundScope)
|
||||
processor = createProcessor(backgroundScope)
|
||||
isNodeDbReady.value = true
|
||||
|
||||
val packet =
|
||||
|
|
@ -255,7 +256,7 @@ class MeshMessageProcessorImplTest {
|
|||
|
||||
@Test
|
||||
fun `processReceivedMeshPacket updates sender node`() = runTest(testDispatcher) {
|
||||
processor.start(backgroundScope)
|
||||
processor = createProcessor(backgroundScope)
|
||||
isNodeDbReady.value = true
|
||||
|
||||
val senderNode = 999
|
||||
|
|
@ -279,7 +280,7 @@ class MeshMessageProcessorImplTest {
|
|||
|
||||
@Test
|
||||
fun `packet with null decoded is skipped`() = runTest(testDispatcher) {
|
||||
processor.start(backgroundScope)
|
||||
processor = createProcessor(backgroundScope)
|
||||
isNodeDbReady.value = true
|
||||
|
||||
val packet = MeshPacket(id = 1, from = 999, decoded = null)
|
||||
|
|
@ -293,7 +294,7 @@ class MeshMessageProcessorImplTest {
|
|||
|
||||
@Test
|
||||
fun `processReceivedMeshPacket with null myNodeNum skips node updates`() = runTest(testDispatcher) {
|
||||
processor.start(backgroundScope)
|
||||
processor = createProcessor(backgroundScope)
|
||||
isNodeDbReady.value = true
|
||||
|
||||
val packet =
|
||||
|
|
@ -315,7 +316,7 @@ class MeshMessageProcessorImplTest {
|
|||
|
||||
@Test
|
||||
fun `clearEarlyPackets empties the buffer`() = runTest(testDispatcher) {
|
||||
processor.start(backgroundScope)
|
||||
processor = createProcessor(backgroundScope)
|
||||
isNodeDbReady.value = false
|
||||
|
||||
val packet =
|
||||
|
|
@ -342,7 +343,7 @@ class MeshMessageProcessorImplTest {
|
|||
|
||||
@Test
|
||||
fun `FromRadio log_record variant is logged as MeshLog`() = runTest(testDispatcher) {
|
||||
processor.start(backgroundScope)
|
||||
processor = createProcessor(backgroundScope)
|
||||
val logRecord = LogRecord(message = "device log")
|
||||
val fromRadio = FromRadio(log_record = logRecord)
|
||||
val bytes = FromRadio.ADAPTER.encode(fromRadio)
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ package org.meshtastic.core.data.manager
|
|||
|
||||
import dev.mokkery.MockMode
|
||||
import dev.mokkery.mock
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
import okio.ByteString
|
||||
import okio.ByteString.Companion.toByteString
|
||||
import org.meshtastic.core.model.DataPacket
|
||||
|
|
@ -44,12 +45,13 @@ class NodeManagerImplTest {
|
|||
private val nodeRepository: NodeRepository = mock(MockMode.autofill)
|
||||
private val serviceBroadcasts: ServiceBroadcasts = mock(MockMode.autofill)
|
||||
private val notificationManager: NotificationManager = mock(MockMode.autofill)
|
||||
private val testScope = TestScope()
|
||||
|
||||
private lateinit var nodeManager: NodeManagerImpl
|
||||
|
||||
@BeforeTest
|
||||
fun setUp() {
|
||||
nodeManager = NodeManagerImpl(nodeRepository, serviceBroadcasts, notificationManager)
|
||||
nodeManager = NodeManagerImpl(nodeRepository, serviceBroadcasts, notificationManager, testScope)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
|||
|
|
@ -70,8 +70,8 @@ class PacketHandlerImplTest {
|
|||
radioInterfaceService,
|
||||
lazy { meshLogRepository },
|
||||
serviceRepository,
|
||||
testScope,
|
||||
)
|
||||
handler.start(testScope)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
|||
|
|
@ -72,8 +72,8 @@ class StoreForwardPacketHandlerImplTest {
|
|||
serviceBroadcasts = serviceBroadcasts,
|
||||
historyManager = historyManager,
|
||||
dataHandler = lazy { dataHandler },
|
||||
scope = testScope,
|
||||
)
|
||||
handler.start(testScope)
|
||||
}
|
||||
|
||||
private fun makeSfPacket(from: Int, sf: StoreAndForward): MeshPacket {
|
||||
|
|
|
|||
|
|
@ -62,8 +62,8 @@ class TelemetryPacketHandlerImplTest {
|
|||
nodeManager = nodeManager,
|
||||
connectionManager = lazy { connectionManager },
|
||||
notificationManager = notificationManager,
|
||||
scope = testScope,
|
||||
)
|
||||
handler.start(testScope)
|
||||
}
|
||||
|
||||
private fun makeTelemetryPacket(from: Int, telemetry: Telemetry): MeshPacket {
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@
|
|||
*/
|
||||
package org.meshtastic.core.repository
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import okio.ByteString
|
||||
import org.meshtastic.core.model.DataPacket
|
||||
import org.meshtastic.core.model.Position
|
||||
|
|
@ -27,9 +26,6 @@ import org.meshtastic.proto.LocalConfig
|
|||
/** Interface for sending commands and packets to the mesh network. */
|
||||
@Suppress("TooManyFunctions")
|
||||
interface CommandSender {
|
||||
/** Starts the command sender with the given coroutine scope. */
|
||||
fun start(scope: CoroutineScope)
|
||||
|
||||
/** Returns the current packet ID. */
|
||||
fun getCurrentPacketId(): Long
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@
|
|||
*/
|
||||
package org.meshtastic.core.repository
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import org.meshtastic.core.model.DataPacket
|
||||
import org.meshtastic.core.model.MeshUser
|
||||
import org.meshtastic.core.model.Position
|
||||
|
|
@ -25,9 +24,6 @@ import org.meshtastic.core.model.service.ServiceAction
|
|||
/** Interface for handling UI-triggered actions and administrative commands for the mesh. */
|
||||
@Suppress("TooManyFunctions")
|
||||
interface MeshActionHandler {
|
||||
/** Starts the handler with the given coroutine scope. */
|
||||
fun start(scope: CoroutineScope)
|
||||
|
||||
/** Processes a service action from the UI. */
|
||||
suspend fun onServiceAction(action: ServiceAction)
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@
|
|||
*/
|
||||
package org.meshtastic.core.repository
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import org.meshtastic.proto.DeviceMetadata
|
||||
import org.meshtastic.proto.FileInfo
|
||||
import org.meshtastic.proto.MyNodeInfo
|
||||
|
|
@ -24,9 +23,6 @@ import org.meshtastic.proto.NodeInfo
|
|||
|
||||
/** Interface for managing the configuration flow, including local node info and metadata. */
|
||||
interface MeshConfigFlowManager {
|
||||
/** Starts the manager with the given coroutine scope. */
|
||||
fun start(scope: CoroutineScope)
|
||||
|
||||
/** Handles received local node information. */
|
||||
fun handleMyInfo(myInfo: MyNodeInfo)
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@
|
|||
*/
|
||||
package org.meshtastic.core.repository
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import org.meshtastic.proto.Channel
|
||||
import org.meshtastic.proto.Config
|
||||
|
|
@ -27,9 +26,6 @@ import org.meshtastic.proto.ModuleConfig
|
|||
|
||||
/** Interface for handling device and module configuration updates. */
|
||||
interface MeshConfigHandler {
|
||||
/** Starts the handler with the given coroutine scope. */
|
||||
fun start(scope: CoroutineScope)
|
||||
|
||||
/** Reactive local configuration. */
|
||||
val localConfig: StateFlow<LocalConfig>
|
||||
|
||||
|
|
|
|||
|
|
@ -16,14 +16,10 @@
|
|||
*/
|
||||
package org.meshtastic.core.repository
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import org.meshtastic.proto.Telemetry
|
||||
|
||||
/** Interface for managing the connection lifecycle and status with the mesh radio. */
|
||||
interface MeshConnectionManager {
|
||||
/** Starts the connection manager with the given coroutine scope. */
|
||||
fun start(scope: CoroutineScope)
|
||||
|
||||
/** Called when the radio configuration has been fully loaded. */
|
||||
fun onRadioConfigLoaded()
|
||||
|
||||
|
|
|
|||
|
|
@ -16,16 +16,12 @@
|
|||
*/
|
||||
package org.meshtastic.core.repository
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Job
|
||||
import org.meshtastic.core.model.DataPacket
|
||||
import org.meshtastic.proto.MeshPacket
|
||||
|
||||
/** Interface for handling incoming mesh data packets and routing them to the appropriate handlers. */
|
||||
interface MeshDataHandler {
|
||||
/** Starts the handler with the given coroutine scope. */
|
||||
fun start(scope: CoroutineScope)
|
||||
|
||||
/**
|
||||
* Processes a received mesh packet.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -16,14 +16,10 @@
|
|||
*/
|
||||
package org.meshtastic.core.repository
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import org.meshtastic.proto.MeshPacket
|
||||
|
||||
/** Interface for processing incoming radio messages and mesh packets. */
|
||||
interface MeshMessageProcessor {
|
||||
/** Starts the processor with the given coroutine scope. */
|
||||
fun start(scope: CoroutineScope)
|
||||
|
||||
/** Handles a raw message received from the radio. */
|
||||
fun handleFromRadio(bytes: ByteArray, myNodeNum: Int?)
|
||||
|
||||
|
|
|
|||
|
|
@ -16,13 +16,8 @@
|
|||
*/
|
||||
package org.meshtastic.core.repository
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
|
||||
/** Interface for the central router that orchestrates specialized mesh packet handlers. */
|
||||
interface MeshRouter {
|
||||
/** Starts the router and its sub-components with the given coroutine scope. */
|
||||
fun start(scope: CoroutineScope)
|
||||
|
||||
/** Access to the data handler. */
|
||||
val dataHandler: MeshDataHandler
|
||||
|
||||
|
|
|
|||
|
|
@ -16,13 +16,12 @@
|
|||
*/
|
||||
package org.meshtastic.core.repository
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import org.meshtastic.proto.MqttClientProxyMessage
|
||||
|
||||
/** Interface for managing MQTT proxy communication. */
|
||||
interface MqttManager {
|
||||
/** Starts the MQTT manager with the given coroutine scope and settings. */
|
||||
fun start(scope: CoroutineScope, enabled: Boolean, proxyToClientEnabled: Boolean)
|
||||
/** Starts the MQTT proxy with the given settings. */
|
||||
fun startProxy(enabled: Boolean, proxyToClientEnabled: Boolean)
|
||||
|
||||
/** Stops the MQTT manager. */
|
||||
fun stop()
|
||||
|
|
|
|||
|
|
@ -16,15 +16,11 @@
|
|||
*/
|
||||
package org.meshtastic.core.repository
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import org.meshtastic.proto.MeshPacket
|
||||
import org.meshtastic.proto.NeighborInfo
|
||||
|
||||
/** Interface for handling neighbor info responses from the mesh. */
|
||||
interface NeighborInfoHandler {
|
||||
/** Starts the neighbor info handler with the given coroutine scope. */
|
||||
fun start(scope: CoroutineScope)
|
||||
|
||||
/** Records the start time for a neighbor info request. */
|
||||
fun recordStartTime(requestId: Int)
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@
|
|||
*/
|
||||
package org.meshtastic.core.repository
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import org.meshtastic.core.model.MyNodeInfo
|
||||
import org.meshtastic.core.model.Node
|
||||
|
|
@ -51,9 +50,6 @@ interface NodeManager : NodeIdLookup {
|
|||
/** Sets whether node database writes are allowed. */
|
||||
fun setAllowNodeDbWrites(allowed: Boolean)
|
||||
|
||||
/** Starts the node manager with the given coroutine scope. */
|
||||
fun start(scope: CoroutineScope)
|
||||
|
||||
/** The local node number as a thread-safe [StateFlow]. */
|
||||
val myNodeNum: StateFlow<Int?>
|
||||
|
||||
|
|
|
|||
|
|
@ -16,16 +16,12 @@
|
|||
*/
|
||||
package org.meshtastic.core.repository
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import org.meshtastic.proto.MeshPacket
|
||||
import org.meshtastic.proto.QueueStatus
|
||||
import org.meshtastic.proto.ToRadio
|
||||
|
||||
/** Interface for handling the transmission of packets to the radio and managing the packet queue. */
|
||||
interface PacketHandler {
|
||||
/** Starts the packet handler with the given coroutine scope. */
|
||||
fun start(scope: CoroutineScope)
|
||||
|
||||
/** Sends a command/packet directly to the radio. */
|
||||
fun sendToRadio(p: ToRadio)
|
||||
|
||||
|
|
|
|||
|
|
@ -16,15 +16,11 @@
|
|||
*/
|
||||
package org.meshtastic.core.repository
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import org.meshtastic.core.model.DataPacket
|
||||
import org.meshtastic.proto.MeshPacket
|
||||
|
||||
/** Interface for handling Store & Forward (legacy) and SF++ packets. */
|
||||
interface StoreForwardPacketHandler {
|
||||
/** Starts the handler with the given coroutine scope. */
|
||||
fun start(scope: CoroutineScope)
|
||||
|
||||
/**
|
||||
* Handles a legacy Store & Forward packet.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -16,15 +16,11 @@
|
|||
*/
|
||||
package org.meshtastic.core.repository
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import org.meshtastic.core.model.DataPacket
|
||||
import org.meshtastic.proto.MeshPacket
|
||||
|
||||
/** Interface for handling telemetry packets from the mesh, including battery notifications. */
|
||||
interface TelemetryPacketHandler {
|
||||
/** Starts the handler with the given coroutine scope. */
|
||||
fun start(scope: CoroutineScope)
|
||||
|
||||
/**
|
||||
* Processes a telemetry packet.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -16,15 +16,11 @@
|
|||
*/
|
||||
package org.meshtastic.core.repository
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Job
|
||||
import org.meshtastic.proto.MeshPacket
|
||||
|
||||
/** Interface for handling traceroute responses from the mesh. */
|
||||
interface TracerouteHandler {
|
||||
/** Starts the traceroute handler with the given coroutine scope. */
|
||||
fun start(scope: CoroutineScope)
|
||||
|
||||
/** Records the start time for a traceroute request. */
|
||||
fun recordStartTime(requestId: Int)
|
||||
|
||||
|
|
|
|||
|
|
@ -22,16 +22,14 @@ import kotlinx.coroutines.CoroutineScope
|
|||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import org.koin.core.annotation.Named
|
||||
import org.koin.core.annotation.Single
|
||||
import org.meshtastic.core.common.database.DatabaseManager
|
||||
import org.meshtastic.core.common.util.handledLaunch
|
||||
import org.meshtastic.core.repository.CommandSender
|
||||
import org.meshtastic.core.repository.MeshConnectionManager
|
||||
import org.meshtastic.core.repository.MeshMessageProcessor
|
||||
import org.meshtastic.core.repository.MeshRouter
|
||||
import org.meshtastic.core.repository.MeshServiceNotifications
|
||||
import org.meshtastic.core.repository.NodeManager
|
||||
import org.meshtastic.core.repository.PacketHandler
|
||||
import org.meshtastic.core.repository.RadioInterfaceService
|
||||
import org.meshtastic.core.repository.ServiceRepository
|
||||
import org.meshtastic.core.repository.TakPrefs
|
||||
|
|
@ -51,25 +49,22 @@ import org.meshtastic.core.takserver.TAKServerManager
|
|||
class MeshServiceOrchestrator(
|
||||
private val radioInterfaceService: RadioInterfaceService,
|
||||
private val serviceRepository: ServiceRepository,
|
||||
private val packetHandler: PacketHandler,
|
||||
private val nodeManager: NodeManager,
|
||||
private val messageProcessor: MeshMessageProcessor,
|
||||
private val commandSender: CommandSender,
|
||||
private val connectionManager: MeshConnectionManager,
|
||||
private val router: MeshRouter,
|
||||
private val serviceNotifications: MeshServiceNotifications,
|
||||
private val takServerManager: TAKServerManager,
|
||||
private val takMeshIntegration: TAKMeshIntegration,
|
||||
private val takPrefs: TakPrefs,
|
||||
private val dispatchers: org.meshtastic.core.di.CoroutineDispatchers,
|
||||
private val databaseManager: DatabaseManager,
|
||||
@Named("ServiceScope") private val scope: CoroutineScope,
|
||||
) {
|
||||
private var serviceJob: Job? = null
|
||||
private var takJob: Job? = null
|
||||
|
||||
/** The coroutine scope for the service. Available after [start] is called. */
|
||||
var serviceScope: CoroutineScope? = null
|
||||
private set
|
||||
/** The coroutine scope for the service. */
|
||||
val serviceScope: CoroutineScope
|
||||
get() = scope
|
||||
|
||||
/** Whether the orchestrator is currently running. */
|
||||
val isRunning: Boolean
|
||||
|
|
@ -78,8 +73,8 @@ class MeshServiceOrchestrator(
|
|||
/**
|
||||
* Starts the mesh service components and wires up data flows.
|
||||
*
|
||||
* This is the KMP equivalent of `MeshService.onCreate()`. It starts all managers, connects to the radio, and wires
|
||||
* incoming radio data to the message processor and service actions to the router's action handler.
|
||||
* This is the KMP equivalent of `MeshService.onCreate()`. It connects to the radio and wires incoming radio data to
|
||||
* the message processor and service actions to the router's action handler.
|
||||
*/
|
||||
fun start() {
|
||||
if (isRunning) {
|
||||
|
|
@ -90,18 +85,9 @@ class MeshServiceOrchestrator(
|
|||
Logger.i { "Starting mesh service orchestrator" }
|
||||
val job = Job()
|
||||
serviceJob = job
|
||||
val scope = CoroutineScope(dispatchers.default + job)
|
||||
serviceScope = scope
|
||||
|
||||
serviceNotifications.initChannels()
|
||||
|
||||
packetHandler.start(scope)
|
||||
router.start(scope)
|
||||
nodeManager.start(scope)
|
||||
connectionManager.start(scope)
|
||||
messageProcessor.start(scope)
|
||||
commandSender.start(scope)
|
||||
|
||||
// Observe TAK server pref to start/stop
|
||||
takJob =
|
||||
takPrefs.isTakServerEnabled
|
||||
|
|
@ -161,6 +147,5 @@ class MeshServiceOrchestrator(
|
|||
}
|
||||
serviceJob?.cancel()
|
||||
serviceJob = null
|
||||
serviceScope = null
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,9 +16,19 @@
|
|||
*/
|
||||
package org.meshtastic.core.service.di
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import org.koin.core.annotation.ComponentScan
|
||||
import org.koin.core.annotation.Module
|
||||
import org.koin.core.annotation.Named
|
||||
import org.koin.core.annotation.Single
|
||||
import org.meshtastic.core.di.CoroutineDispatchers
|
||||
|
||||
@Module
|
||||
@ComponentScan("org.meshtastic.core.service")
|
||||
class CoreServiceModule
|
||||
class CoreServiceModule {
|
||||
@Single
|
||||
@Named("ServiceScope")
|
||||
fun provideServiceScope(dispatchers: CoroutineDispatchers): CoroutineScope =
|
||||
CoroutineScope(dispatchers.default + SupervisorJob())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,23 +25,21 @@ import dev.mokkery.mock
|
|||
import dev.mokkery.verify
|
||||
import dev.mokkery.verify.VerifyMode.Companion.exactly
|
||||
import dev.mokkery.verifySuspend
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.test.UnconfinedTestDispatcher
|
||||
import org.meshtastic.core.common.database.DatabaseManager
|
||||
import org.meshtastic.core.di.CoroutineDispatchers
|
||||
import org.meshtastic.core.model.Node
|
||||
import org.meshtastic.core.model.service.ServiceAction
|
||||
import org.meshtastic.core.repository.CommandSender
|
||||
import org.meshtastic.core.repository.MeshActionHandler
|
||||
import org.meshtastic.core.repository.MeshConfigHandler
|
||||
import org.meshtastic.core.repository.MeshConnectionManager
|
||||
import org.meshtastic.core.repository.MeshMessageProcessor
|
||||
import org.meshtastic.core.repository.MeshRouter
|
||||
import org.meshtastic.core.repository.MeshServiceNotifications
|
||||
import org.meshtastic.core.repository.NodeManager
|
||||
import org.meshtastic.core.repository.NodeRepository
|
||||
import org.meshtastic.core.repository.PacketHandler
|
||||
import org.meshtastic.core.repository.RadioInterfaceService
|
||||
import org.meshtastic.core.repository.ServiceRepository
|
||||
import org.meshtastic.core.repository.TakPrefs
|
||||
|
|
@ -57,12 +55,10 @@ class MeshServiceOrchestratorTest {
|
|||
|
||||
private val radioInterfaceService: RadioInterfaceService = mock(MockMode.autofill)
|
||||
private val serviceRepository: ServiceRepository = mock(MockMode.autofill)
|
||||
private val packetHandler: PacketHandler = mock(MockMode.autofill)
|
||||
private val nodeManager: NodeManager = mock(MockMode.autofill)
|
||||
private val nodeRepository: NodeRepository = mock(MockMode.autofill)
|
||||
private val messageProcessor: MeshMessageProcessor = mock(MockMode.autofill)
|
||||
private val commandSender: CommandSender = mock(MockMode.autofill)
|
||||
private val connectionManager: MeshConnectionManager = mock(MockMode.autofill)
|
||||
private val router: MeshRouter = mock(MockMode.autofill)
|
||||
private val actionHandler: MeshActionHandler = mock(MockMode.autofill)
|
||||
private val meshConfigHandler: MeshConfigHandler = mock(MockMode.autofill)
|
||||
|
|
@ -73,7 +69,7 @@ class MeshServiceOrchestratorTest {
|
|||
private val databaseManager: DatabaseManager = mock(MockMode.autofill)
|
||||
|
||||
private val testDispatcher = UnconfinedTestDispatcher()
|
||||
private val dispatchers = CoroutineDispatchers(testDispatcher, testDispatcher, testDispatcher)
|
||||
private val testScope = CoroutineScope(testDispatcher)
|
||||
|
||||
/** Stubs the shared flow dependencies used by every test and returns an orchestrator. */
|
||||
private fun createOrchestrator(
|
||||
|
|
@ -107,18 +103,15 @@ class MeshServiceOrchestratorTest {
|
|||
return MeshServiceOrchestrator(
|
||||
radioInterfaceService = radioInterfaceService,
|
||||
serviceRepository = serviceRepository,
|
||||
packetHandler = packetHandler,
|
||||
nodeManager = nodeManager,
|
||||
messageProcessor = messageProcessor,
|
||||
commandSender = commandSender,
|
||||
connectionManager = connectionManager,
|
||||
router = router,
|
||||
serviceNotifications = serviceNotifications,
|
||||
takServerManager = takServerManager,
|
||||
takMeshIntegration = takMeshIntegration,
|
||||
takPrefs = takPrefs,
|
||||
dispatchers = dispatchers,
|
||||
databaseManager = databaseManager,
|
||||
scope = testScope,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -131,7 +124,6 @@ class MeshServiceOrchestratorTest {
|
|||
assertTrue(orchestrator.isRunning)
|
||||
|
||||
verify { serviceNotifications.initChannels() }
|
||||
verify { packetHandler.start(any()) }
|
||||
verify { nodeManager.loadCachedNodeDB() }
|
||||
|
||||
orchestrator.stop()
|
||||
|
|
@ -217,7 +209,6 @@ class MeshServiceOrchestratorTest {
|
|||
|
||||
// Components should only be initialized once
|
||||
verify(exactly(1)) { serviceNotifications.initChannels() }
|
||||
verify(exactly(1)) { packetHandler.start(any()) }
|
||||
verify(exactly(1)) { nodeManager.loadCachedNodeDB() }
|
||||
|
||||
orchestrator.stop()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue