diff --git a/app/src/fdroid/kotlin/org/meshtastic/app/map/MapViewModel.kt b/app/src/fdroid/kotlin/org/meshtastic/app/map/MapViewModel.kt
index eefd9df43..90ea43d40 100644
--- a/app/src/fdroid/kotlin/org/meshtastic/app/map/MapViewModel.kt
+++ b/app/src/fdroid/kotlin/org/meshtastic/app/map/MapViewModel.kt
@@ -22,7 +22,7 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import org.koin.core.annotation.KoinViewModel
import org.meshtastic.core.common.BuildConfigProvider
-import org.meshtastic.core.model.RadioController
+import org.meshtastic.core.model.MessageSender
import org.meshtastic.core.repository.MapPrefs
import org.meshtastic.core.repository.NodeRepository
import org.meshtastic.core.repository.PacketRepository
@@ -37,7 +37,7 @@ class MapViewModel(
mapPrefs: MapPrefs,
packetRepository: PacketRepository,
nodeRepository: NodeRepository,
- radioController: RadioController,
+ radioController: MessageSender,
radioConfigRepository: RadioConfigRepository,
buildConfigProvider: BuildConfigProvider,
savedStateHandle: SavedStateHandle,
diff --git a/app/src/google/kotlin/org/meshtastic/app/map/MapViewModel.kt b/app/src/google/kotlin/org/meshtastic/app/map/MapViewModel.kt
index 5acd0b1a0..9a05e6d87 100644
--- a/app/src/google/kotlin/org/meshtastic/app/map/MapViewModel.kt
+++ b/app/src/google/kotlin/org/meshtastic/app/map/MapViewModel.kt
@@ -50,7 +50,7 @@ import org.meshtastic.app.map.model.CustomTileProviderConfig
import org.meshtastic.app.map.prefs.map.GoogleMapsPrefs
import org.meshtastic.app.map.repository.CustomTileProviderRepository
import org.meshtastic.core.di.CoroutineDispatchers
-import org.meshtastic.core.model.RadioController
+import org.meshtastic.core.model.MessageSender
import org.meshtastic.core.repository.MapPrefs
import org.meshtastic.core.repository.NodeRepository
import org.meshtastic.core.repository.PacketRepository
@@ -89,7 +89,7 @@ class MapViewModel(
nodeRepository: NodeRepository,
packetRepository: PacketRepository,
radioConfigRepository: RadioConfigRepository,
- radioController: RadioController,
+ radioController: MessageSender,
private val customTileProviderRepository: CustomTileProviderRepository,
uiPrefs: UiPrefs,
savedStateHandle: SavedStateHandle,
diff --git a/core/data/src/commonMain/kotlin/org/meshtastic/core/data/di/CoreDataModule.kt b/core/data/src/commonMain/kotlin/org/meshtastic/core/data/di/CoreDataModule.kt
index a2407008b..308a821f0 100644
--- a/core/data/src/commonMain/kotlin/org/meshtastic/core/data/di/CoreDataModule.kt
+++ b/core/data/src/commonMain/kotlin/org/meshtastic/core/data/di/CoreDataModule.kt
@@ -19,14 +19,10 @@ package org.meshtastic.core.data.di
import org.koin.core.annotation.ComponentScan
import org.koin.core.annotation.Module
import org.koin.core.annotation.Single
-import org.meshtastic.core.model.util.MeshDataMapper
-import org.meshtastic.core.model.util.NodeIdLookup
import kotlin.time.Clock
@Module
@ComponentScan("org.meshtastic.core.data")
class CoreDataModule {
- @Single fun provideMeshDataMapper(nodeIdLookup: NodeIdLookup): MeshDataMapper = MeshDataMapper(nodeIdLookup)
-
@Single fun provideClock(): Clock = Clock.System
}
diff --git a/core/data/src/commonTest/kotlin/org/meshtastic/core/data/radio/SdkStateBridgeTest.kt b/core/data/src/commonTest/kotlin/org/meshtastic/core/data/radio/SdkStateBridgeTest.kt
new file mode 100644
index 000000000..bbd361708
--- /dev/null
+++ b/core/data/src/commonTest/kotlin/org/meshtastic/core/data/radio/SdkStateBridgeTest.kt
@@ -0,0 +1,189 @@
+/*
+ * Copyright (c) 2026 Meshtastic LLC
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package org.meshtastic.core.data.radio
+
+import dev.mokkery.MockMode
+import dev.mokkery.mock
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.advanceTimeBy
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.meshtastic.core.di.CoroutineDispatchers
+import org.meshtastic.core.model.Node
+import org.meshtastic.core.repository.MeshLocationManager
+import org.meshtastic.core.repository.PacketRepository
+import org.meshtastic.core.testing.FakeNodeRepository
+import org.meshtastic.core.testing.FakeRadioController
+import org.meshtastic.core.testing.FakeServiceRepository
+import org.meshtastic.core.testing.FakeUiPrefs
+import org.meshtastic.proto.Data
+import org.meshtastic.proto.MeshPacket
+import org.meshtastic.proto.PortNum
+import org.meshtastic.proto.Position
+import org.meshtastic.proto.User
+import org.meshtastic.sdk.DeviceStorage
+import org.meshtastic.sdk.NodeId
+import org.meshtastic.sdk.RadioClient
+import org.meshtastic.sdk.StorageProvider
+import org.meshtastic.sdk.TransportIdentity
+import org.meshtastic.sdk.testing.FakeRadioTransport
+import org.meshtastic.sdk.testing.InMemoryStorage
+import kotlin.test.Test
+import kotlin.test.assertFalse
+import kotlin.test.assertTrue
+import kotlin.time.Clock
+import kotlin.time.Duration
+import kotlin.time.Duration.Companion.seconds
+
+@OptIn(ExperimentalCoroutinesApi::class)
+class SdkStateBridgeTest {
+
+ @Test
+ fun `went offline marks node offline in repository`() = runTest {
+ val remoteNode = NodeId(0x22222222)
+ val staleHeartbeatMs = Clock.System.now().toEpochMilliseconds() - 5.seconds.inWholeMilliseconds
+ val nodeRepository =
+ FakeNodeRepository().apply {
+ setNodes(
+ listOf(
+ Node(
+ num = remoteNode.raw,
+ user = User(id = "!22222222", long_name = "Test Node"),
+ lastHeard = (Clock.System.now().toEpochMilliseconds() / 1000).toInt(),
+ ),
+ ),
+ )
+ }
+ val (_, client) = connectedClient(SeededHeartbeatStorageProvider(mapOf(remoteNode to staleHeartbeatMs)))
+ buildBridge(client, nodeRepository)
+
+ client.connect()
+ runCurrent()
+ advanceTimeBy(30.seconds)
+ runCurrent()
+
+ val updated = nodeRepository.nodeDBbyNum.value.getValue(remoteNode.raw)
+ assertTrue(updated.lastHeard <= (staleHeartbeatMs / 1000).toInt())
+ assertFalse(updated.isOnline)
+
+ client.disconnect()
+ }
+
+ @Test
+ fun `came online marks node online in repository`() = runTest {
+ val remoteNode = NodeId(0x33333333)
+ val staleHeartbeatMs = Clock.System.now().toEpochMilliseconds() - 5.seconds.inWholeMilliseconds
+ val nodeRepository =
+ FakeNodeRepository().apply {
+ setNodes(
+ listOf(
+ Node(
+ num = remoteNode.raw,
+ user = User(id = "!33333333", long_name = "Test Node"),
+ lastHeard = (staleHeartbeatMs / 1000).toInt(),
+ ),
+ ),
+ )
+ }
+ val (transport, client) = connectedClient(SeededHeartbeatStorageProvider(mapOf(remoteNode to staleHeartbeatMs)))
+ buildBridge(client, nodeRepository)
+
+ client.connect()
+ runCurrent()
+ advanceTimeBy(30.seconds)
+ runCurrent()
+
+ transport.injectPacket(
+ MeshPacket(
+ from = remoteNode.raw,
+ to = 0,
+ decoded = Data(portnum = PortNum.TEXT_MESSAGE_APP),
+ ),
+ )
+ runCurrent()
+
+ val updated = nodeRepository.nodeDBbyNum.value.getValue(remoteNode.raw)
+ assertTrue(updated.lastHeard > (staleHeartbeatMs / 1000).toInt())
+ assertTrue(updated.isOnline)
+
+ client.disconnect()
+ }
+
+ private fun TestScope.connectedClient(
+ storage: StorageProvider,
+ myNodeNum: Int = 0x11111111,
+ presenceTimeout: Duration = 1.seconds,
+ ): Pair {
+ val transport = FakeRadioTransport(identity = TransportIdentity("fake:state-bridge"), autoHandshake = true, nodeNum = myNodeNum)
+ val client =
+ RadioClient.Builder()
+ .transport(transport)
+ .storage(storage)
+ .coroutineContext(backgroundScope.coroutineContext)
+ .autoSyncTimeOnConnect(false)
+ .presenceTimeout(presenceTimeout)
+ .build()
+ return transport to client
+ }
+
+ private fun TestScope.buildBridge(
+ client: RadioClient,
+ nodeRepository: FakeNodeRepository,
+ ): SdkStateBridge =
+ SdkStateBridge(
+ accessor = TestRadioClientAccessor(client),
+ serviceRepository = FakeServiceRepository(),
+ nodeRepository = nodeRepository,
+ packetRepository = lazyOf(mock(MockMode.autofill)),
+ locationManager = NoOpLocationManager,
+ uiPrefs = FakeUiPrefs(),
+ radioController = FakeRadioController(),
+ dispatchers = CoroutineDispatchers(
+ io = backgroundScope.coroutineContext[kotlin.coroutines.ContinuationInterceptor] as kotlinx.coroutines.CoroutineDispatcher,
+ main = backgroundScope.coroutineContext[kotlin.coroutines.ContinuationInterceptor] as kotlinx.coroutines.CoroutineDispatcher,
+ default = backgroundScope.coroutineContext[kotlin.coroutines.ContinuationInterceptor] as kotlinx.coroutines.CoroutineDispatcher,
+ ),
+ )
+
+ private class TestRadioClientAccessor(client: RadioClient) : RadioClientAccessor {
+ override val client = MutableStateFlow(client)
+
+ override fun rebuildAndConnectAsync() = Unit
+
+ override fun disconnect() = Unit
+ }
+
+ private object NoOpLocationManager : MeshLocationManager {
+ override fun start(scope: CoroutineScope, sendPositionFn: (Position) -> Unit) = Unit
+
+ override fun stop() = Unit
+ }
+}
+
+private class SeededHeartbeatStorageProvider(
+ private val heartbeats: Map,
+) : StorageProvider {
+ override suspend fun activate(identity: TransportIdentity): DeviceStorage =
+ InMemoryStorage().also { storage ->
+ heartbeats.forEach { (nodeId, heartbeatMs) ->
+ storage.saveHeartbeat(nodeId, heartbeatMs)
+ }
+ }
+}
diff --git a/core/model/src/commonMain/kotlin/org/meshtastic/core/model/ConnectionAware.kt b/core/model/src/commonMain/kotlin/org/meshtastic/core/model/ConnectionAware.kt
new file mode 100644
index 000000000..5e5cb3bc3
--- /dev/null
+++ b/core/model/src/commonMain/kotlin/org/meshtastic/core/model/ConnectionAware.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2026 Meshtastic LLC
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package org.meshtastic.core.model
+
+import kotlinx.coroutines.flow.StateFlow
+
+/** Provides read-only access to the app's connection state. */
+interface ConnectionAware {
+ val connectionState: StateFlow
+}
diff --git a/core/model/src/commonMain/kotlin/org/meshtastic/core/model/DeviceAdmin.kt b/core/model/src/commonMain/kotlin/org/meshtastic/core/model/DeviceAdmin.kt
index 6554a7da6..c12979e21 100644
--- a/core/model/src/commonMain/kotlin/org/meshtastic/core/model/DeviceAdmin.kt
+++ b/core/model/src/commonMain/kotlin/org/meshtastic/core/model/DeviceAdmin.kt
@@ -20,7 +20,7 @@ import org.meshtastic.proto.Channel
import org.meshtastic.proto.Config
/** Focused interface for local device configuration and edit sessions. */
-interface DeviceAdmin {
+interface DeviceAdmin : ConnectionAware {
suspend fun setLocalConfig(config: Config)
suspend fun setLocalChannel(channel: Channel)
suspend fun beginEditSettings(destNum: Int)
diff --git a/core/model/src/commonMain/kotlin/org/meshtastic/core/model/DeviceControl.kt b/core/model/src/commonMain/kotlin/org/meshtastic/core/model/DeviceControl.kt
index 1e42ec820..8cd849205 100644
--- a/core/model/src/commonMain/kotlin/org/meshtastic/core/model/DeviceControl.kt
+++ b/core/model/src/commonMain/kotlin/org/meshtastic/core/model/DeviceControl.kt
@@ -17,7 +17,7 @@
package org.meshtastic.core.model
/** Focused interface for device lifecycle control. */
-interface DeviceControl {
+interface DeviceControl : ConnectionAware {
suspend fun reboot(destNum: Int, packetId: Int)
suspend fun rebootToDfu(nodeNum: Int)
suspend fun requestRebootOta(requestId: Int, destNum: Int, mode: Int, hash: ByteArray?)
diff --git a/core/model/src/commonMain/kotlin/org/meshtastic/core/model/MessageSender.kt b/core/model/src/commonMain/kotlin/org/meshtastic/core/model/MessageSender.kt
index 897c2501c..1eaf22ce6 100644
--- a/core/model/src/commonMain/kotlin/org/meshtastic/core/model/MessageSender.kt
+++ b/core/model/src/commonMain/kotlin/org/meshtastic/core/model/MessageSender.kt
@@ -17,7 +17,7 @@
package org.meshtastic.core.model
/** Focused interface for sending messages over the mesh. */
-interface MessageSender {
+interface MessageSender : ConnectionAware {
suspend fun sendMessage(packet: DataPacket)
fun getPacketId(): Int
}
diff --git a/core/model/src/commonMain/kotlin/org/meshtastic/core/model/RadioController.kt b/core/model/src/commonMain/kotlin/org/meshtastic/core/model/RadioController.kt
index a79c745ba..8913df374 100644
--- a/core/model/src/commonMain/kotlin/org/meshtastic/core/model/RadioController.kt
+++ b/core/model/src/commonMain/kotlin/org/meshtastic/core/model/RadioController.kt
@@ -26,18 +26,6 @@ import org.meshtastic.proto.ClientNotification
* This super-interface remains for backward compatibility with existing injections.
*/
interface RadioController : MessageSender, DeviceAdmin, RemoteAdmin, DeviceControl, DataRequester {
- /**
- * Canonical app-level connection state, delegated from [ServiceRepository][connectionState].
- *
- * This exposes the same single source of truth as `ServiceRepository.connectionState`, surfaced through the
- * controller interface for convenience in feature modules and ViewModels that depend on [RadioController] rather
- * than [ServiceRepository] directly.
- *
- * This is **not** the transport-level state — it reflects the fully reconciled app-level state including handshake
- * progress and device sleep policy.
- */
- val connectionState: StateFlow
-
/**
* Flow of notifications from the radio client.
*
diff --git a/core/model/src/commonMain/kotlin/org/meshtastic/core/model/util/MeshDataMapper.kt b/core/model/src/commonMain/kotlin/org/meshtastic/core/model/util/MeshDataMapper.kt
deleted file mode 100644
index 7d35a8e31..000000000
--- a/core/model/src/commonMain/kotlin/org/meshtastic/core/model/util/MeshDataMapper.kt
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright (c) 2026 Meshtastic LLC
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-@file:Suppress("MagicNumber")
-
-package org.meshtastic.core.model.util
-
-import okio.ByteString.Companion.toByteString
-import org.meshtastic.core.model.DataPacket
-import org.meshtastic.proto.MeshPacket
-
-/**
- * Utility class to map [MeshPacket] protobufs to [DataPacket] domain models.
- *
- * This class is platform-agnostic and can be used in shared logic.
- */
-open class MeshDataMapper(private val nodeIdLookup: NodeIdLookup) {
-
- /** Maps a [MeshPacket] to a [DataPacket], or returns null if the packet has no decoded data. */
- open fun toDataPacket(packet: MeshPacket): DataPacket? {
- val decoded = packet.decoded ?: return null
- return DataPacket(
- from = packet.from,
- to = packet.to,
- time = packet.rx_time * 1000L,
- id = packet.id,
- dataType = decoded.portnum.value,
- bytes = decoded.payload.toByteArray().toByteString(),
- hopLimit = packet.hop_limit,
- channel = if (packet.pki_encrypted == true) DataPacket.PKC_CHANNEL_INDEX else packet.channel,
- wantAck = packet.want_ack == true,
- hopStart = packet.hop_start,
- snr = packet.rx_snr,
- rssi = packet.rx_rssi,
- replyId = decoded.reply_id,
- relayNode = packet.relay_node,
- viaMqtt = packet.via_mqtt == true,
- emoji = decoded.emoji,
- transportMechanism = packet.transport_mechanism.value,
- )
- }
-}
diff --git a/core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/qr/ScannedQrCodeViewModel.kt b/core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/qr/ScannedQrCodeViewModel.kt
index 5198bd3a1..2df0a6c2b 100644
--- a/core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/qr/ScannedQrCodeViewModel.kt
+++ b/core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/qr/ScannedQrCodeViewModel.kt
@@ -18,7 +18,7 @@ package org.meshtastic.core.ui.qr
import androidx.lifecycle.ViewModel
import org.koin.core.annotation.KoinViewModel
-import org.meshtastic.core.model.RadioController
+import org.meshtastic.core.model.DeviceAdmin
import org.meshtastic.core.repository.RadioConfigRepository
import org.meshtastic.core.ui.util.getChannelList
import org.meshtastic.core.ui.viewmodel.safeLaunch
@@ -31,7 +31,7 @@ import org.meshtastic.proto.LocalConfig
@KoinViewModel
class ScannedQrCodeViewModel(
private val radioConfigRepository: RadioConfigRepository,
- private val radioController: RadioController,
+ private val radioController: DeviceAdmin,
) : ViewModel() {
val channels = radioConfigRepository.channelSetFlow.stateInWhileSubscribed(initialValue = ChannelSet())
diff --git a/feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/BaseMapViewModel.kt b/feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/BaseMapViewModel.kt
index 455236e19..153c91a21 100644
--- a/feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/BaseMapViewModel.kt
+++ b/feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/BaseMapViewModel.kt
@@ -28,7 +28,7 @@ import org.meshtastic.core.common.util.ioDispatcher
import org.meshtastic.core.common.util.nowSeconds
import org.meshtastic.core.model.DataPacket
import org.meshtastic.core.model.Node
-import org.meshtastic.core.model.RadioController
+import org.meshtastic.core.model.MessageSender
import org.meshtastic.core.model.TracerouteOverlay
import org.meshtastic.core.repository.MapPrefs
import org.meshtastic.core.repository.NodeRepository
@@ -55,7 +55,7 @@ open class BaseMapViewModel(
protected val mapPrefs: MapPrefs,
protected val nodeRepository: NodeRepository,
private val packetRepository: PacketRepository,
- private val radioController: RadioController,
+ private val radioController: MessageSender,
) : ViewModel() {
val myNodeInfo = nodeRepository.myNodeInfo
diff --git a/feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/SharedMapViewModel.kt b/feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/SharedMapViewModel.kt
index bcebdabf6..6d9017f6b 100644
--- a/feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/SharedMapViewModel.kt
+++ b/feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/SharedMapViewModel.kt
@@ -17,7 +17,7 @@
package org.meshtastic.feature.map
import org.koin.core.annotation.KoinViewModel
-import org.meshtastic.core.model.RadioController
+import org.meshtastic.core.model.MessageSender
import org.meshtastic.core.repository.MapPrefs
import org.meshtastic.core.repository.NodeRepository
import org.meshtastic.core.repository.PacketRepository
@@ -27,5 +27,5 @@ class SharedMapViewModel(
mapPrefs: MapPrefs,
nodeRepository: NodeRepository,
packetRepository: PacketRepository,
- radioController: RadioController,
+ radioController: MessageSender,
) : BaseMapViewModel(mapPrefs, nodeRepository, packetRepository, radioController)
diff --git a/feature/node/src/commonMain/kotlin/org/meshtastic/feature/node/detail/CommonNodeRequestActions.kt b/feature/node/src/commonMain/kotlin/org/meshtastic/feature/node/detail/CommonNodeRequestActions.kt
index 1ea463685..19f6843c5 100644
--- a/feature/node/src/commonMain/kotlin/org/meshtastic/feature/node/detail/CommonNodeRequestActions.kt
+++ b/feature/node/src/commonMain/kotlin/org/meshtastic/feature/node/detail/CommonNodeRequestActions.kt
@@ -26,8 +26,9 @@ import kotlinx.coroutines.launch
import org.koin.core.annotation.Single
import org.meshtastic.core.common.util.ioDispatcher
import org.meshtastic.core.common.util.nowMillis
+import org.meshtastic.core.model.DataRequester
+import org.meshtastic.core.model.MessageSender
import org.meshtastic.core.model.Position
-import org.meshtastic.core.model.RadioController
import org.meshtastic.core.model.TelemetryType
import org.meshtastic.core.resources.Res
import org.meshtastic.core.resources.UiText
@@ -48,7 +49,8 @@ import org.meshtastic.core.ui.util.SnackbarManager
@Single(binds = [NodeRequestActions::class])
class CommonNodeRequestActions
constructor(
- private val radioController: RadioController,
+ private val dataRequester: DataRequester,
+ private val messageSender: MessageSender,
private val snackbarManager: SnackbarManager,
) : NodeRequestActions {
@@ -65,7 +67,7 @@ constructor(
override fun requestUserInfo(scope: CoroutineScope, destNum: Int, longName: String) {
scope.launch(ioDispatcher) {
Logger.i { "Requesting UserInfo for '$destNum'" }
- radioController.requestUserInfo(destNum)
+ dataRequester.requestUserInfo(destNum)
showFeedback(UiText.Resource(Res.string.requesting_from, Res.string.user_info, longName))
}
}
@@ -73,8 +75,8 @@ constructor(
override fun requestNeighborInfo(scope: CoroutineScope, destNum: Int, longName: String) {
scope.launch(ioDispatcher) {
Logger.i { "Requesting NeighborInfo for '$destNum'" }
- val packetId = radioController.getPacketId()
- radioController.requestNeighborInfo(packetId, destNum)
+ val packetId = messageSender.getPacketId()
+ dataRequester.requestNeighborInfo(packetId, destNum)
_lastRequestNeighborTimes.update { it + (destNum to nowMillis) }
showFeedback(UiText.Resource(Res.string.requesting_from, Res.string.neighbor_info, longName))
}
@@ -83,7 +85,7 @@ constructor(
override fun requestPosition(scope: CoroutineScope, destNum: Int, longName: String, position: Position) {
scope.launch(ioDispatcher) {
Logger.i { "Requesting position for '$destNum'" }
- radioController.requestPosition(destNum, position)
+ dataRequester.requestPosition(destNum, position)
showFeedback(UiText.Resource(Res.string.requesting_from, Res.string.position, longName))
}
}
@@ -91,8 +93,8 @@ constructor(
override fun requestTelemetry(scope: CoroutineScope, destNum: Int, longName: String, type: TelemetryType) {
scope.launch(ioDispatcher) {
Logger.i { "Requesting telemetry for '$destNum'" }
- val packetId = radioController.getPacketId()
- radioController.requestTelemetry(packetId, destNum, type.ordinal)
+ val packetId = messageSender.getPacketId()
+ dataRequester.requestTelemetry(packetId, destNum, type.ordinal)
val typeRes =
when (type) {
@@ -112,8 +114,8 @@ constructor(
override fun requestTraceroute(scope: CoroutineScope, destNum: Int, longName: String) {
scope.launch(ioDispatcher) {
Logger.i { "Requesting traceroute for '$destNum'" }
- val packetId = radioController.getPacketId()
- radioController.requestTraceroute(packetId, destNum)
+ val packetId = messageSender.getPacketId()
+ dataRequester.requestTraceroute(packetId, destNum)
_lastTracerouteTime.value = nowMillis
showFeedback(UiText.Resource(Res.string.requesting_from, Res.string.traceroute, longName))
}
diff --git a/feature/node/src/commonMain/kotlin/org/meshtastic/feature/node/detail/NodeManagementActions.kt b/feature/node/src/commonMain/kotlin/org/meshtastic/feature/node/detail/NodeManagementActions.kt
index 17046f3a7..1c48540f1 100644
--- a/feature/node/src/commonMain/kotlin/org/meshtastic/feature/node/detail/NodeManagementActions.kt
+++ b/feature/node/src/commonMain/kotlin/org/meshtastic/feature/node/detail/NodeManagementActions.kt
@@ -22,8 +22,9 @@ import kotlinx.coroutines.launch
import org.jetbrains.compose.resources.getString
import org.koin.core.annotation.Single
import org.meshtastic.core.common.util.ioDispatcher
+import org.meshtastic.core.model.DeviceControl
+import org.meshtastic.core.model.MessageSender
import org.meshtastic.core.model.Node
-import org.meshtastic.core.model.RadioController
import org.meshtastic.core.model.service.ServiceAction
import org.meshtastic.core.repository.NodeRepository
import org.meshtastic.core.repository.ServiceRepository
@@ -47,7 +48,8 @@ open class NodeManagementActions
constructor(
private val nodeRepository: NodeRepository,
private val serviceRepository: ServiceRepository,
- private val radioController: RadioController,
+ private val deviceControl: DeviceControl,
+ private val messageSender: MessageSender,
private val alertManager: AlertManager,
) {
open fun requestRemoveNode(scope: CoroutineScope, node: Node, onAfterRemove: () -> Unit = {}) {
@@ -64,8 +66,8 @@ constructor(
open fun removeNode(scope: CoroutineScope, nodeNum: Int) {
scope.launch(ioDispatcher) {
Logger.i { "Removing node '$nodeNum'" }
- val packetId = radioController.getPacketId()
- radioController.removeByNodenum(packetId, nodeNum)
+ val packetId = messageSender.getPacketId()
+ deviceControl.removeByNodenum(packetId, nodeNum)
nodeRepository.deleteNode(nodeNum)
}
}
diff --git a/feature/node/src/commonMain/kotlin/org/meshtastic/feature/node/list/NodeListViewModel.kt b/feature/node/src/commonMain/kotlin/org/meshtastic/feature/node/list/NodeListViewModel.kt
index 0d54dfead..2f0ab1c80 100644
--- a/feature/node/src/commonMain/kotlin/org/meshtastic/feature/node/list/NodeListViewModel.kt
+++ b/feature/node/src/commonMain/kotlin/org/meshtastic/feature/node/list/NodeListViewModel.kt
@@ -29,7 +29,7 @@ import org.koin.core.annotation.KoinViewModel
import org.meshtastic.core.model.DeviceType
import org.meshtastic.core.model.Node
import org.meshtastic.core.model.NodeSortOption
-import org.meshtastic.core.model.RadioController
+import org.meshtastic.core.model.DeviceAdmin
import org.meshtastic.core.repository.NodeRepository
import org.meshtastic.core.repository.RadioConfigRepository
import org.meshtastic.core.repository.RadioPrefs
@@ -47,7 +47,7 @@ class NodeListViewModel(
private val nodeRepository: NodeRepository,
private val radioConfigRepository: RadioConfigRepository,
private val serviceRepository: ServiceRepository,
- private val radioController: RadioController,
+ private val radioController: DeviceAdmin,
private val radioPrefs: RadioPrefs,
val nodeManagementActions: NodeManagementActions,
private val getFilteredNodesUseCase: GetFilteredNodesUseCase,
diff --git a/feature/node/src/commonTest/kotlin/org/meshtastic/feature/node/detail/NodeManagementActionsTest.kt b/feature/node/src/commonTest/kotlin/org/meshtastic/feature/node/detail/NodeManagementActionsTest.kt
index 4e65cf290..3233747da 100644
--- a/feature/node/src/commonTest/kotlin/org/meshtastic/feature/node/detail/NodeManagementActionsTest.kt
+++ b/feature/node/src/commonTest/kotlin/org/meshtastic/feature/node/detail/NodeManagementActionsTest.kt
@@ -46,7 +46,8 @@ class NodeManagementActionsTest {
NodeManagementActions(
nodeRepository = nodeRepository,
serviceRepository = serviceRepository,
- radioController = radioController,
+ deviceControl = radioController,
+ messageSender = radioController,
alertManager = alertManager,
)
@@ -78,7 +79,8 @@ class NodeManagementActionsTest {
NodeManagementActions(
nodeRepository = nodeRepository,
serviceRepository = serviceRepository,
- radioController = radioController,
+ deviceControl = radioController,
+ messageSender = radioController,
alertManager = realAlertManager,
)
val node = Node(num = 123, user = User(long_name = "Test Node"))
diff --git a/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/channel/ChannelViewModel.kt b/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/channel/ChannelViewModel.kt
index 136131241..5c8369cfe 100644
--- a/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/channel/ChannelViewModel.kt
+++ b/feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/channel/ChannelViewModel.kt
@@ -22,7 +22,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import org.koin.core.annotation.KoinViewModel
import org.meshtastic.core.common.util.CommonUri
-import org.meshtastic.core.model.RadioController
+import org.meshtastic.core.model.DeviceAdmin
import org.meshtastic.core.model.util.toChannelSet
import org.meshtastic.core.repository.DataPair
import org.meshtastic.core.repository.PlatformAnalytics
@@ -37,7 +37,7 @@ import org.meshtastic.proto.LocalConfig
@KoinViewModel
class ChannelViewModel(
- private val radioController: RadioController,
+ private val radioController: DeviceAdmin,
private val radioConfigRepository: RadioConfigRepository,
private val analytics: PlatformAnalytics,
) : ViewModel() {