mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-05-30 09:37:13 -04:00
refactor: Naming cleanup for clarity and Kotlin conventions
Audit-driven renames: - DirectRadioControllerImpl -> RadioControllerImpl: the "Direct" prefix was vestigial (it once contrasted with the AIDL-routed AndroidRadioControllerImpl, now deleted); it is the sole impl and now matches its parts (AdminControllerImpl, etc.). - RadioController.getPacketId() -> generatePacketId(): a `get`-prefixed function that generates a fresh id each call violates the getter-is-idempotent convention; also aligns with CommandSender.generatePacketId(). - RequestController -> QueryController (+ Impl, + the `requestController` params/mocks): clearer intent for the pull/query surface; "request" was generic. - RequestTimer param `label` -> `logLabel`. - AdminControllerImpl DEFAULT_REBOOT_DELAY -> DEFAULT_DELAY_SECONDS (shared by reboot + shutdown; conveys the unit). Interface-consumer-safe; docs/READMEs/architecture guide updated to match. Koin graph verifies on Android + desktop; affected test suites green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -42,14 +42,14 @@ internal class RequestTimer {
|
||||
|
||||
/**
|
||||
* Consumes the start time recorded for [requestId] and appends a `Duration: N s` line to [text], logging completion
|
||||
* under [label]. Returns [text] unchanged when no start time was recorded for the id.
|
||||
* under [logLabel]. Returns [text] unchanged when no start time was recorded for the id.
|
||||
*/
|
||||
fun appendDuration(requestId: Int, text: String, label: String): String {
|
||||
fun appendDuration(requestId: Int, text: String, logLabel: String): String {
|
||||
val start = startTimes.value[requestId]
|
||||
startTimes.update { it.remove(requestId) }
|
||||
if (start == null) return text
|
||||
val seconds = (nowMillis - start) / MILLIS_PER_SECOND
|
||||
Logger.i { "$label $requestId complete in $seconds s" }
|
||||
Logger.i { "$logLabel $requestId complete in $seconds s" }
|
||||
return "$text\n\nDuration: ${NumberFormatter.format(seconds, 1)} s"
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ class RequestTimerTest {
|
||||
fun appendDuration_withoutStart_returnsTextUnchanged() {
|
||||
val timer = RequestTimer()
|
||||
|
||||
assertEquals("base", timer.appendDuration(requestId = 1, text = "base", label = "Test"))
|
||||
assertEquals("base", timer.appendDuration(requestId = 1, text = "base", logLabel = "Test"))
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -34,7 +34,7 @@ class RequestTimerTest {
|
||||
val timer = RequestTimer()
|
||||
timer.start(requestId = 7)
|
||||
|
||||
val result = timer.appendDuration(requestId = 7, text = "base", label = "Test")
|
||||
val result = timer.appendDuration(requestId = 7, text = "base", logLabel = "Test")
|
||||
|
||||
assertTrue(result.startsWith("base\n\nDuration: "), "expected a duration suffix, got: $result")
|
||||
assertTrue(result.endsWith(" s"))
|
||||
@@ -45,9 +45,9 @@ class RequestTimerTest {
|
||||
val timer = RequestTimer()
|
||||
timer.start(requestId = 7)
|
||||
|
||||
timer.appendDuration(requestId = 7, text = "first", label = "Test")
|
||||
timer.appendDuration(requestId = 7, text = "first", logLabel = "Test")
|
||||
// The start time is single-use; a second response for the same id gets no duration.
|
||||
assertEquals("second", timer.appendDuration(requestId = 7, text = "second", label = "Test"))
|
||||
assertEquals("second", timer.appendDuration(requestId = 7, text = "second", logLabel = "Test"))
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -57,7 +57,7 @@ class RequestTimerTest {
|
||||
timer.start(requestId = 2)
|
||||
|
||||
// Consuming one id must not affect the other.
|
||||
timer.appendDuration(requestId = 1, text = "a", label = "Test")
|
||||
assertTrue(timer.appendDuration(requestId = 2, text = "b", label = "Test").contains("Duration: "))
|
||||
timer.appendDuration(requestId = 1, text = "a", logLabel = "Test")
|
||||
assertTrue(timer.appendDuration(requestId = 2, text = "b", logLabel = "Test").contains("Duration: "))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ constructor(
|
||||
* @return The packet ID of the request.
|
||||
*/
|
||||
open suspend fun reboot(destNum: Int): Int {
|
||||
val packetId = radioController.getPacketId()
|
||||
val packetId = radioController.generatePacketId()
|
||||
radioController.reboot(destNum, packetId)
|
||||
return packetId
|
||||
}
|
||||
@@ -51,7 +51,7 @@ constructor(
|
||||
* @return The packet ID of the request.
|
||||
*/
|
||||
open suspend fun shutdown(destNum: Int): Int {
|
||||
val packetId = radioController.getPacketId()
|
||||
val packetId = radioController.generatePacketId()
|
||||
radioController.shutdown(destNum, packetId)
|
||||
return packetId
|
||||
}
|
||||
@@ -64,7 +64,7 @@ constructor(
|
||||
* @return The packet ID of the request.
|
||||
*/
|
||||
open suspend fun factoryReset(destNum: Int, isLocal: Boolean): Int {
|
||||
val packetId = radioController.getPacketId()
|
||||
val packetId = radioController.generatePacketId()
|
||||
radioController.factoryReset(destNum, packetId)
|
||||
|
||||
if (isLocal) {
|
||||
@@ -84,7 +84,7 @@ constructor(
|
||||
* @return The packet ID of the request.
|
||||
*/
|
||||
open suspend fun nodedbReset(destNum: Int, preserveFavorites: Boolean, isLocal: Boolean): Int {
|
||||
val packetId = radioController.getPacketId()
|
||||
val packetId = radioController.generatePacketId()
|
||||
radioController.nodedbReset(destNum, packetId, preserveFavorites)
|
||||
|
||||
if (isLocal) {
|
||||
|
||||
@@ -58,7 +58,7 @@ constructor(
|
||||
|
||||
nodeRepository.deleteNodes(nodeNums)
|
||||
for (nodeNum in nodeNums) {
|
||||
val packetId = radioController.getPacketId()
|
||||
val packetId = radioController.generatePacketId()
|
||||
radioController.removeByNodenum(packetId, nodeNum)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ open class RadioConfigUseCase constructor(private val radioController: RadioCont
|
||||
* @return The packet ID of the request.
|
||||
*/
|
||||
open suspend fun setOwner(destNum: Int, user: User): Int {
|
||||
val packetId = radioController.getPacketId()
|
||||
val packetId = radioController.generatePacketId()
|
||||
radioController.setOwner(destNum, user, packetId)
|
||||
return packetId
|
||||
}
|
||||
@@ -47,7 +47,7 @@ open class RadioConfigUseCase constructor(private val radioController: RadioCont
|
||||
* @return The packet ID of the request.
|
||||
*/
|
||||
open suspend fun getOwner(destNum: Int): Int {
|
||||
val packetId = radioController.getPacketId()
|
||||
val packetId = radioController.generatePacketId()
|
||||
radioController.getOwner(destNum, packetId)
|
||||
return packetId
|
||||
}
|
||||
@@ -60,7 +60,7 @@ open class RadioConfigUseCase constructor(private val radioController: RadioCont
|
||||
* @return The packet ID of the request.
|
||||
*/
|
||||
open suspend fun setConfig(destNum: Int, config: Config): Int {
|
||||
val packetId = radioController.getPacketId()
|
||||
val packetId = radioController.generatePacketId()
|
||||
radioController.setConfig(destNum, config, packetId)
|
||||
return packetId
|
||||
}
|
||||
@@ -73,7 +73,7 @@ open class RadioConfigUseCase constructor(private val radioController: RadioCont
|
||||
* @return The packet ID of the request.
|
||||
*/
|
||||
open suspend fun getConfig(destNum: Int, configType: Int): Int {
|
||||
val packetId = radioController.getPacketId()
|
||||
val packetId = radioController.generatePacketId()
|
||||
radioController.getConfig(destNum, configType, packetId)
|
||||
return packetId
|
||||
}
|
||||
@@ -86,7 +86,7 @@ open class RadioConfigUseCase constructor(private val radioController: RadioCont
|
||||
* @return The packet ID of the request.
|
||||
*/
|
||||
open suspend fun setModuleConfig(destNum: Int, config: ModuleConfig): Int {
|
||||
val packetId = radioController.getPacketId()
|
||||
val packetId = radioController.generatePacketId()
|
||||
radioController.setModuleConfig(destNum, config, packetId)
|
||||
return packetId
|
||||
}
|
||||
@@ -99,7 +99,7 @@ open class RadioConfigUseCase constructor(private val radioController: RadioCont
|
||||
* @return The packet ID of the request.
|
||||
*/
|
||||
open suspend fun getModuleConfig(destNum: Int, moduleConfigType: Int): Int {
|
||||
val packetId = radioController.getPacketId()
|
||||
val packetId = radioController.generatePacketId()
|
||||
radioController.getModuleConfig(destNum, moduleConfigType, packetId)
|
||||
return packetId
|
||||
}
|
||||
@@ -112,7 +112,7 @@ open class RadioConfigUseCase constructor(private val radioController: RadioCont
|
||||
* @return The packet ID of the request.
|
||||
*/
|
||||
open suspend fun getChannel(destNum: Int, index: Int): Int {
|
||||
val packetId = radioController.getPacketId()
|
||||
val packetId = radioController.generatePacketId()
|
||||
radioController.getChannel(destNum, index, packetId)
|
||||
return packetId
|
||||
}
|
||||
@@ -125,7 +125,7 @@ open class RadioConfigUseCase constructor(private val radioController: RadioCont
|
||||
* @return The packet ID of the request.
|
||||
*/
|
||||
open suspend fun setRemoteChannel(destNum: Int, channel: org.meshtastic.proto.Channel): Int {
|
||||
val packetId = radioController.getPacketId()
|
||||
val packetId = radioController.generatePacketId()
|
||||
radioController.setRemoteChannel(destNum, channel, packetId)
|
||||
return packetId
|
||||
}
|
||||
@@ -152,7 +152,7 @@ open class RadioConfigUseCase constructor(private val radioController: RadioCont
|
||||
* @return The packet ID of the request.
|
||||
*/
|
||||
open suspend fun getRingtone(destNum: Int): Int {
|
||||
val packetId = radioController.getPacketId()
|
||||
val packetId = radioController.generatePacketId()
|
||||
radioController.getRingtone(destNum, packetId)
|
||||
return packetId
|
||||
}
|
||||
@@ -169,7 +169,7 @@ open class RadioConfigUseCase constructor(private val radioController: RadioCont
|
||||
* @return The packet ID of the request.
|
||||
*/
|
||||
open suspend fun getCannedMessages(destNum: Int): Int {
|
||||
val packetId = radioController.getPacketId()
|
||||
val packetId = radioController.generatePacketId()
|
||||
radioController.getCannedMessages(destNum, packetId)
|
||||
return packetId
|
||||
}
|
||||
@@ -181,7 +181,7 @@ open class RadioConfigUseCase constructor(private val radioController: RadioCont
|
||||
* @return The packet ID of the request.
|
||||
*/
|
||||
open suspend fun getDeviceConnectionStatus(destNum: Int): Int {
|
||||
val packetId = radioController.getPacketId()
|
||||
val packetId = radioController.generatePacketId()
|
||||
radioController.getDeviceConnectionStatus(destNum, packetId)
|
||||
return packetId
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ src/
|
||||
│ ├── AdminController.kt ← config, channels, owner, device lifecycle, editSettings
|
||||
│ ├── MessagingController.kt ← send packets, reactions, contacts
|
||||
│ ├── NodeController.kt ← favorite, ignore, mute, remove nodes
|
||||
│ ├── RequestController.kt ← telemetry, traceroute, position queries
|
||||
│ ├── QueryController.kt ← telemetry, traceroute, position queries
|
||||
│ ├── CommandSender.kt
|
||||
│ ├── AdminPacketHandler.kt
|
||||
│ ├── FromRadioPacketHandler.kt
|
||||
@@ -130,7 +130,7 @@ connection state). Handlers inject `ServiceStateWriter` for mutations. The full
|
||||
`ServiceRepository` union is still available for backward compatibility.
|
||||
|
||||
Radio commands are issued through `RadioController` (a composite of `AdminController`,
|
||||
`MessagingController`, `NodeController`, `RequestController`) rather than an action/intent bus.
|
||||
`MessagingController`, `NodeController`, `QueryController`) rather than an action/intent bus.
|
||||
|
||||
### `NodeRepository`
|
||||
|
||||
|
||||
@@ -19,14 +19,14 @@ package org.meshtastic.core.repository
|
||||
import org.meshtastic.core.model.Position
|
||||
|
||||
/**
|
||||
* Mesh request operations — position, traceroute, telemetry, user info, and metadata queries.
|
||||
* Mesh query operations — position, traceroute, telemetry, user info, and metadata.
|
||||
*
|
||||
* These are "pull" operations that request data from remote nodes. When the SDK is adopted, implementations delegate to
|
||||
* `RadioClient.telemetry` and `RadioClient.routing` sub-APIs.
|
||||
*
|
||||
* @see RadioController which extends this interface for backward compatibility
|
||||
*/
|
||||
interface RequestController {
|
||||
interface QueryController {
|
||||
|
||||
/** Requests device metadata from a remote node. */
|
||||
suspend fun refreshMetadata(destNum: Int)
|
||||
@@ -30,7 +30,7 @@ import org.meshtastic.proto.ClientNotification
|
||||
* - [AdminController] — config, channels, owner, device lifecycle (→ SDK `AdminApi`)
|
||||
* - [MessagingController] — send packets, reactions, contacts (→ SDK `RadioClient.send*`)
|
||||
* - [NodeController] — favorite, ignore, mute, remove nodes (→ SDK `AdminApi` node ops)
|
||||
* - [RequestController] — telemetry, traceroute, position queries (→ SDK `TelemetryApi` / `RoutingApi`)
|
||||
* - [QueryController] — telemetry, traceroute, position queries (→ SDK `TelemetryApi` / `RoutingApi`)
|
||||
*
|
||||
* When migrating to the SDK, each sub-interface becomes a thin adapter over the corresponding SDK API. The composite
|
||||
* [RadioController] can then be deprecated and consumers migrated to the narrower interfaces one at a time.
|
||||
@@ -39,7 +39,7 @@ interface RadioController :
|
||||
AdminController,
|
||||
MessagingController,
|
||||
NodeController,
|
||||
RequestController,
|
||||
QueryController,
|
||||
ConnectionStateProvider {
|
||||
/**
|
||||
* Flow of notifications from the radio client.
|
||||
@@ -56,7 +56,7 @@ interface RadioController :
|
||||
*
|
||||
* @return A unique 32-bit integer.
|
||||
*/
|
||||
fun getPacketId(): Int
|
||||
fun generatePacketId(): Int
|
||||
|
||||
/** Starts providing the phone's location to the mesh. */
|
||||
fun startProvideLocation()
|
||||
|
||||
@@ -17,8 +17,8 @@ A high-level repository that wraps the service connection and exposes reactive `
|
||||
### 3. `ConnectionState`
|
||||
Represents the current state of the radio connection (`Connected`, `Disconnected`, `DeviceSleep`, etc.).
|
||||
|
||||
### 4. `DirectRadioControllerImpl`
|
||||
The in-process `RadioController` composition root (Desktop, iOS, and single-process Android). It assembles four focused sub-controllers — `AdminControllerImpl`, `MessagingControllerImpl`, `NodeControllerImpl`, `RequestControllerImpl` — via Kotlin interface delegation, and owns the cross-cutting concerns (connection state, packet-id, location, device-address switching). Commands are direct suspend calls to `CommandSender`; admin sends are fire-and-forget (the device is the source of truth). Config writes use the `editSettings { }` transaction.
|
||||
### 4. `RadioControllerImpl`
|
||||
The in-process `RadioController` composition root (Desktop, iOS, and single-process Android). It assembles four focused sub-controllers — `AdminControllerImpl`, `MessagingControllerImpl`, `NodeControllerImpl`, `QueryControllerImpl` — via Kotlin interface delegation, and owns the cross-cutting concerns (connection state, packet-id, location, device-address switching). Commands are direct suspend calls to `CommandSender`; admin sends are fire-and-forget (the device is the source of truth). Config writes use the `editSettings { }` transaction.
|
||||
|
||||
|
||||
## Dependency Graph
|
||||
|
||||
@@ -36,14 +36,14 @@ import org.meshtastic.core.repository.NodeRepository
|
||||
import org.meshtastic.core.repository.NotificationManager
|
||||
import org.meshtastic.core.repository.PacketRepository
|
||||
import org.meshtastic.core.repository.PlatformAnalytics
|
||||
import org.meshtastic.core.repository.QueryController
|
||||
import org.meshtastic.core.repository.RadioConfigRepository
|
||||
import org.meshtastic.core.repository.RadioController
|
||||
import org.meshtastic.core.repository.RadioInterfaceService
|
||||
import org.meshtastic.core.repository.RequestController
|
||||
import org.meshtastic.core.repository.ServiceRepository
|
||||
import org.meshtastic.core.repository.UiPrefs
|
||||
import org.meshtastic.core.service.DirectRadioControllerImpl
|
||||
import org.meshtastic.core.service.MeshService
|
||||
import org.meshtastic.core.service.RadioControllerImpl
|
||||
import org.meshtastic.core.service.startService
|
||||
|
||||
@Module
|
||||
@@ -57,7 +57,7 @@ class CoreServiceAndroidModule {
|
||||
AdminController::class,
|
||||
MessagingController::class,
|
||||
NodeController::class,
|
||||
RequestController::class,
|
||||
QueryController::class,
|
||||
],
|
||||
)
|
||||
fun radioController(
|
||||
@@ -78,7 +78,7 @@ class CoreServiceAndroidModule {
|
||||
messageProcessor: Lazy<MeshMessageProcessor>,
|
||||
radioConfigRepository: RadioConfigRepository,
|
||||
@Named("ServiceScope") scope: CoroutineScope,
|
||||
): RadioController = DirectRadioControllerImpl(
|
||||
): RadioController = RadioControllerImpl(
|
||||
serviceRepository = serviceRepository,
|
||||
nodeRepository = nodeRepository,
|
||||
commandSender = commandSender,
|
||||
|
||||
@@ -38,10 +38,10 @@ import org.meshtastic.proto.User
|
||||
* [AdminController] implementation: local/remote configuration, channels, owner, device lifecycle, and the
|
||||
* [editSettings] transaction.
|
||||
*
|
||||
* Focused collaborator of [DirectRadioControllerImpl]. Builds [AdminMessage] protos directly and delegates to
|
||||
* [CommandSender] for transport, mirroring the SDK's `AdminApiImpl` pattern. Config/channel writes use fire-and-forget
|
||||
* optimistic local persistence ([handledLaunch]): the device is the source of truth and re-sends its full config on
|
||||
* every connection, so persistence is a cache optimization, not a correctness requirement.
|
||||
* Focused collaborator of [RadioControllerImpl]. Builds [AdminMessage] protos directly and delegates to [CommandSender]
|
||||
* for transport, mirroring the SDK's `AdminApiImpl` pattern. Config/channel writes use fire-and-forget optimistic local
|
||||
* persistence ([handledLaunch]): the device is the source of truth and re-sends its full config on every connection, so
|
||||
* persistence is a cache optimization, not a correctness requirement.
|
||||
*/
|
||||
@Suppress("TooManyFunctions")
|
||||
internal class AdminControllerImpl(
|
||||
@@ -159,7 +159,7 @@ internal class AdminControllerImpl(
|
||||
|
||||
override suspend fun reboot(destNum: Int, packetId: Int) {
|
||||
Logger.i { "Reboot requested for node $destNum" }
|
||||
commandSender.sendAdmin(destNum, packetId) { AdminMessage(reboot_seconds = DEFAULT_REBOOT_DELAY) }
|
||||
commandSender.sendAdmin(destNum, packetId) { AdminMessage(reboot_seconds = DEFAULT_DELAY_SECONDS) }
|
||||
}
|
||||
|
||||
override suspend fun rebootToDfu(nodeNum: Int) {
|
||||
@@ -174,7 +174,7 @@ internal class AdminControllerImpl(
|
||||
}
|
||||
|
||||
override suspend fun shutdown(destNum: Int, packetId: Int) {
|
||||
commandSender.sendAdmin(destNum, packetId) { AdminMessage(shutdown_seconds = DEFAULT_REBOOT_DELAY) }
|
||||
commandSender.sendAdmin(destNum, packetId) { AdminMessage(shutdown_seconds = DEFAULT_DELAY_SECONDS) }
|
||||
}
|
||||
|
||||
override suspend fun factoryReset(destNum: Int, packetId: Int) {
|
||||
@@ -211,6 +211,6 @@ internal class AdminControllerImpl(
|
||||
}
|
||||
|
||||
private companion object {
|
||||
private const val DEFAULT_REBOOT_DELAY = 5
|
||||
private const val DEFAULT_DELAY_SECONDS = 5
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ import org.meshtastic.proto.User
|
||||
/**
|
||||
* [MessagingController] implementation: sends data packets, reactions, and shared contacts.
|
||||
*
|
||||
* Focused collaborator of [DirectRadioControllerImpl]. Mirrors the SDK's `RadioClient.send*` surface — when the SDK is
|
||||
* Focused collaborator of [RadioControllerImpl]. Mirrors the SDK's `RadioClient.send*` surface — when the SDK is
|
||||
* adopted this becomes a thin adapter over `RadioClient`.
|
||||
*/
|
||||
internal class MessagingControllerImpl(
|
||||
|
||||
@@ -27,8 +27,8 @@ import org.meshtastic.proto.AdminMessage
|
||||
/**
|
||||
* [NodeController] implementation: favorite, ignore, mute, and remove nodes.
|
||||
*
|
||||
* Focused collaborator of [DirectRadioControllerImpl]. Favorite/ignore are idempotent (no-op when already in the
|
||||
* requested state), mirroring the SDK's `AdminApi.setFavorite`/`setIgnored`.
|
||||
* Focused collaborator of [RadioControllerImpl]. Favorite/ignore are idempotent (no-op when already in the requested
|
||||
* state), mirroring the SDK's `AdminApi.setFavorite`/`setIgnored`.
|
||||
*/
|
||||
internal class NodeControllerImpl(
|
||||
private val commandSender: CommandSender,
|
||||
|
||||
@@ -19,20 +19,20 @@ package org.meshtastic.core.service
|
||||
import org.meshtastic.core.model.Position
|
||||
import org.meshtastic.core.repository.CommandSender
|
||||
import org.meshtastic.core.repository.NodeManager
|
||||
import org.meshtastic.core.repository.RequestController
|
||||
import org.meshtastic.core.repository.QueryController
|
||||
import org.meshtastic.core.repository.UiPrefs
|
||||
import org.meshtastic.proto.AdminMessage
|
||||
|
||||
/**
|
||||
* [RequestController] implementation: position, traceroute, telemetry, user info, and metadata "pull" queries.
|
||||
* [QueryController] implementation: position, traceroute, telemetry, user info, and metadata "pull" queries.
|
||||
*
|
||||
* Focused collaborator of [DirectRadioControllerImpl]. Mirrors the SDK's `TelemetryApi`/`RoutingApi` surface.
|
||||
* Focused collaborator of [RadioControllerImpl]. Mirrors the SDK's `TelemetryApi`/`RoutingApi` surface.
|
||||
*/
|
||||
internal class RequestControllerImpl(
|
||||
internal class QueryControllerImpl(
|
||||
private val commandSender: CommandSender,
|
||||
private val nodeManager: NodeManager,
|
||||
private val uiPrefs: UiPrefs,
|
||||
) : RequestController {
|
||||
) : QueryController {
|
||||
|
||||
private val myNodeNum: Int
|
||||
get() = nodeManager.myNodeNum.value ?: 0
|
||||
@@ -34,10 +34,10 @@ import org.meshtastic.core.repository.NodeRepository
|
||||
import org.meshtastic.core.repository.NotificationManager
|
||||
import org.meshtastic.core.repository.PacketRepository
|
||||
import org.meshtastic.core.repository.PlatformAnalytics
|
||||
import org.meshtastic.core.repository.QueryController
|
||||
import org.meshtastic.core.repository.RadioConfigRepository
|
||||
import org.meshtastic.core.repository.RadioController
|
||||
import org.meshtastic.core.repository.RadioInterfaceService
|
||||
import org.meshtastic.core.repository.RequestController
|
||||
import org.meshtastic.core.repository.ServiceRepository
|
||||
import org.meshtastic.core.repository.UiPrefs
|
||||
import org.meshtastic.proto.ClientNotification
|
||||
@@ -48,7 +48,7 @@ import org.meshtastic.proto.ClientNotification
|
||||
*
|
||||
* Rather than implementing every command itself, this class **assembles** four focused collaborators — one per
|
||||
* sub-interface — and delegates to them via Kotlin interface delegation, mirroring the SDK's layered API design
|
||||
* ([AdminController] → `AdminApi`, [MessagingController] → `RadioClient.send*`, [NodeController]/[RequestController] →
|
||||
* ([AdminController] → `AdminApi`, [MessagingController] → `RadioClient.send*`, [NodeController]/[QueryController] →
|
||||
* `AdminApi`/`TelemetryApi`/`RoutingApi`). When the SDK is adopted, each collaborator becomes a thin adapter and this
|
||||
* class is the seam where they are wired together.
|
||||
*
|
||||
@@ -56,7 +56,7 @@ import org.meshtastic.proto.ClientNotification
|
||||
* surfacing, packet-id generation, location provisioning, and device-address switching.
|
||||
*/
|
||||
@Suppress("LongParameterList")
|
||||
class DirectRadioControllerImpl(
|
||||
class RadioControllerImpl(
|
||||
private val serviceRepository: ServiceRepository,
|
||||
nodeRepository: NodeRepository,
|
||||
private val commandSender: CommandSender,
|
||||
@@ -85,7 +85,7 @@ class DirectRadioControllerImpl(
|
||||
packetRepository,
|
||||
),
|
||||
NodeController by NodeControllerImpl(commandSender, nodeManager, packetRepository, scope),
|
||||
RequestController by RequestControllerImpl(commandSender, nodeManager, uiPrefs) {
|
||||
QueryController by QueryControllerImpl(commandSender, nodeManager, uiPrefs) {
|
||||
|
||||
// ── Connection State ────────────────────────────────────────────────────
|
||||
|
||||
@@ -101,7 +101,7 @@ class DirectRadioControllerImpl(
|
||||
|
||||
// ── Packet ID & Location ────────────────────────────────────────────────
|
||||
|
||||
override fun getPacketId(): Int = commandSender.generatePacketId()
|
||||
override fun generatePacketId(): Int = commandSender.generatePacketId()
|
||||
|
||||
override fun startProvideLocation() {
|
||||
locationManager.restart()
|
||||
@@ -58,7 +58,7 @@ import kotlin.test.assertNull
|
||||
import kotlin.test.assertSame
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class DirectRadioControllerImplTest {
|
||||
class RadioControllerImplTest {
|
||||
|
||||
private val nodeRepository: NodeRepository = mock(MockMode.autofill)
|
||||
private val commandSender: CommandSender = mock(MockMode.autofill)
|
||||
@@ -80,10 +80,10 @@ class DirectRadioControllerImplTest {
|
||||
private fun createController(
|
||||
serviceRepository: ServiceRepository = ServiceRepositoryImpl(),
|
||||
myNodeNum: Int? = 1234,
|
||||
): DirectRadioControllerImpl {
|
||||
): RadioControllerImpl {
|
||||
every { nodeManager.myNodeNum } returns MutableStateFlow(myNodeNum)
|
||||
every { meshPrefs.deviceAddress } returns MutableStateFlow(null)
|
||||
return DirectRadioControllerImpl(
|
||||
return RadioControllerImpl(
|
||||
serviceRepository = serviceRepository,
|
||||
nodeRepository = nodeRepository,
|
||||
commandSender = commandSender,
|
||||
@@ -153,14 +153,15 @@ class FakeRadioController :
|
||||
editSettingsCalled = true
|
||||
val scope =
|
||||
object : AdminEditScope {
|
||||
override suspend fun setOwner(user: User) = setOwner(destNum, user, getPacketId())
|
||||
override suspend fun setOwner(user: User) = setOwner(destNum, user, generatePacketId())
|
||||
|
||||
override suspend fun setConfig(config: Config) = setConfig(destNum, config, getPacketId())
|
||||
override suspend fun setConfig(config: Config) = setConfig(destNum, config, generatePacketId())
|
||||
|
||||
override suspend fun setModuleConfig(config: ModuleConfig) =
|
||||
setModuleConfig(destNum, config, getPacketId())
|
||||
setModuleConfig(destNum, config, generatePacketId())
|
||||
|
||||
override suspend fun setChannel(channel: Channel) = setRemoteChannel(destNum, channel, getPacketId())
|
||||
override suspend fun setChannel(channel: Channel) =
|
||||
setRemoteChannel(destNum, channel, generatePacketId())
|
||||
|
||||
override suspend fun setFixedPosition(position: Position) =
|
||||
this@FakeRadioController.setFixedPosition(destNum, position)
|
||||
@@ -168,7 +169,7 @@ class FakeRadioController :
|
||||
scope.block()
|
||||
}
|
||||
|
||||
override fun getPacketId(): Int = 1
|
||||
override fun generatePacketId(): Int = 1
|
||||
|
||||
override fun startProvideLocation() {
|
||||
startProvideLocationCalled = true
|
||||
|
||||
@@ -58,13 +58,13 @@ import org.meshtastic.core.repository.NeighborInfoResponseProvider
|
||||
import org.meshtastic.core.repository.NodeController
|
||||
import org.meshtastic.core.repository.NotificationManager
|
||||
import org.meshtastic.core.repository.PlatformAnalytics
|
||||
import org.meshtastic.core.repository.QueryController
|
||||
import org.meshtastic.core.repository.RadioController
|
||||
import org.meshtastic.core.repository.RadioTransportFactory
|
||||
import org.meshtastic.core.repository.RequestController
|
||||
import org.meshtastic.core.repository.ServiceRepository
|
||||
import org.meshtastic.core.repository.ServiceStateWriter
|
||||
import org.meshtastic.core.repository.TracerouteResponseProvider
|
||||
import org.meshtastic.core.service.DirectRadioControllerImpl
|
||||
import org.meshtastic.core.service.RadioControllerImpl
|
||||
import org.meshtastic.core.service.ServiceRepositoryImpl
|
||||
import org.meshtastic.desktop.DesktopBuildConfig
|
||||
import org.meshtastic.desktop.DesktopNotificationManager
|
||||
@@ -177,7 +177,7 @@ private fun desktopPlatformStubsModule() = module {
|
||||
)
|
||||
}
|
||||
single<RadioController> {
|
||||
DirectRadioControllerImpl(
|
||||
RadioControllerImpl(
|
||||
serviceRepository = get(),
|
||||
nodeRepository = get(),
|
||||
commandSender = get(),
|
||||
@@ -199,7 +199,7 @@ private fun desktopPlatformStubsModule() = module {
|
||||
single<AdminController> { get<RadioController>() }
|
||||
single<MessagingController> { get<RadioController>() }
|
||||
single<NodeController> { get<RadioController>() }
|
||||
single<RequestController> { get<RadioController>() }
|
||||
single<QueryController> { get<RadioController>() }
|
||||
single<NativeNotificationSender> {
|
||||
when (DesktopOS.current()) {
|
||||
DesktopOS.Linux -> LinuxNotificationSender()
|
||||
|
||||
@@ -130,9 +130,9 @@ focused sub-interfaces so callers can depend on just the slice they need:
|
||||
| `AdminController` | Config, channels, owner, device lifecycle, `editSettings { }` transactions |
|
||||
| `MessagingController` | Send packets, reactions, shared contacts |
|
||||
| `NodeController` | Favorite, ignore, mute, remove nodes |
|
||||
| `RequestController` | Telemetry, traceroute, position/user-info queries |
|
||||
| `QueryController` | Telemetry, traceroute, position/user-info queries |
|
||||
|
||||
`DirectRadioControllerImpl` (`core:service`) is the in-process composition root for all targets
|
||||
`RadioControllerImpl` (`core:service`) is the in-process composition root for all targets
|
||||
(Desktop, iOS, single-process Android). It assembles the four sub-controllers via Kotlin interface
|
||||
delegation and adds the cross-cutting concerns (connection state, packet-id, location,
|
||||
device-address switching). Commands are direct suspend calls; admin writes are fire-and-forget
|
||||
|
||||
@@ -190,7 +190,7 @@ class Esp32OtaUpdateHandler(
|
||||
val myInfo = nodeRepository.myNodeInfo.value ?: return
|
||||
val myNodeNum = myInfo.myNodeNum
|
||||
Logger.i { "ESP32 OTA: Triggering reboot OTA mode $mode with hash" }
|
||||
radioController.requestRebootOta(radioController.getPacketId(), myNodeNum, mode, hash)
|
||||
radioController.requestRebootOta(radioController.generatePacketId(), myNodeNum, mode, hash)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -162,7 +162,7 @@ open class BaseMapViewModel(
|
||||
safeLaunch(context = ioDispatcher, tag = "sendDataPacket") { radioController.sendMessage(p) }
|
||||
}
|
||||
|
||||
fun generatePacketId(): Int = radioController.getPacketId()
|
||||
fun generatePacketId(): Int = radioController.generatePacketId()
|
||||
|
||||
data class MapFilterState(
|
||||
val onlyFavorites: Boolean,
|
||||
|
||||
@@ -67,7 +67,7 @@ constructor(
|
||||
|
||||
override suspend fun requestNeighborInfo(destNum: Int, longName: String) {
|
||||
Logger.i { "Requesting NeighborInfo for '$destNum'" }
|
||||
val packetId = radioController.getPacketId()
|
||||
val packetId = radioController.generatePacketId()
|
||||
radioController.requestNeighborInfo(packetId, destNum)
|
||||
_lastRequestNeighborTimes.update { it + (destNum to nowMillis) }
|
||||
showFeedback(UiText.Resource(Res.string.requesting_from, Res.string.neighbor_info, longName))
|
||||
@@ -81,7 +81,7 @@ constructor(
|
||||
|
||||
override suspend fun requestTelemetry(destNum: Int, longName: String, type: TelemetryType) {
|
||||
Logger.i { "Requesting telemetry for '$destNum'" }
|
||||
val packetId = radioController.getPacketId()
|
||||
val packetId = radioController.generatePacketId()
|
||||
radioController.requestTelemetry(packetId, destNum, type.ordinal)
|
||||
|
||||
val typeRes =
|
||||
@@ -100,7 +100,7 @@ constructor(
|
||||
|
||||
override suspend fun requestTraceroute(destNum: Int, longName: String) {
|
||||
Logger.i { "Requesting traceroute for '$destNum'" }
|
||||
val packetId = radioController.getPacketId()
|
||||
val packetId = radioController.generatePacketId()
|
||||
radioController.requestTraceroute(packetId, destNum)
|
||||
_lastTracerouteTime.value = nowMillis
|
||||
showFeedback(UiText.Resource(Res.string.requesting_from, Res.string.traceroute, longName))
|
||||
|
||||
@@ -39,7 +39,7 @@ import org.meshtastic.core.model.NodeAddress
|
||||
import org.meshtastic.core.model.SessionStatus
|
||||
import org.meshtastic.core.navigation.Route
|
||||
import org.meshtastic.core.navigation.SettingsRoute
|
||||
import org.meshtastic.core.repository.RequestController
|
||||
import org.meshtastic.core.repository.QueryController
|
||||
import org.meshtastic.core.resources.Res
|
||||
import org.meshtastic.core.resources.UiText
|
||||
import org.meshtastic.core.resources.connect_radio_for_remote_admin
|
||||
@@ -81,7 +81,7 @@ class NodeDetailViewModel(
|
||||
private val savedStateHandle: SavedStateHandle,
|
||||
private val nodeManagementActions: NodeManagementActions,
|
||||
private val nodeRequestActions: NodeRequestActions,
|
||||
private val requestController: RequestController,
|
||||
private val queryController: QueryController,
|
||||
private val getNodeDetailsUseCase: GetNodeDetailsUseCase,
|
||||
private val ensureRemoteAdminSession: EnsureRemoteAdminSessionUseCase,
|
||||
private val observeRemoteAdminSessionStatus: ObserveRemoteAdminSessionStatusUseCase,
|
||||
@@ -174,7 +174,7 @@ class NodeDetailViewModel(
|
||||
/**
|
||||
* Re-fetch device metadata (firmware/edition/role) for [destNum]. Refreshes the session passkey as a side effect.
|
||||
*/
|
||||
fun refreshMetadata(destNum: Int) = viewModelScope.launch { requestController.refreshMetadata(destNum) }
|
||||
fun refreshMetadata(destNum: Int) = viewModelScope.launch { queryController.refreshMetadata(destNum) }
|
||||
|
||||
/**
|
||||
* Ensure a remote-admin session passkey is fresh, then request navigation to the remote-admin screen. Surfaces a
|
||||
|
||||
@@ -60,7 +60,7 @@ constructor(
|
||||
|
||||
open suspend fun removeNode(nodeNum: Int) {
|
||||
Logger.i { "Removing node '$nodeNum'" }
|
||||
val packetId = radioController.getPacketId()
|
||||
val packetId = radioController.generatePacketId()
|
||||
radioController.removeByNodenum(packetId, nodeNum)
|
||||
nodeRepository.deleteNode(nodeNum)
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ import org.meshtastic.core.domain.usecase.session.EnsureRemoteAdminSessionUseCas
|
||||
import org.meshtastic.core.domain.usecase.session.ObserveRemoteAdminSessionStatusUseCase
|
||||
import org.meshtastic.core.model.Node
|
||||
import org.meshtastic.core.model.SessionStatus
|
||||
import org.meshtastic.core.repository.RequestController
|
||||
import org.meshtastic.core.repository.QueryController
|
||||
import org.meshtastic.core.ui.util.SnackbarManager
|
||||
import org.meshtastic.feature.node.component.NodeMenuAction
|
||||
import org.meshtastic.feature.node.domain.usecase.GetNodeDetailsUseCase
|
||||
@@ -51,7 +51,7 @@ class HandleNodeActionTest {
|
||||
private val testDispatcher = UnconfinedTestDispatcher()
|
||||
private val nodeManagementActions: NodeManagementActions = mock()
|
||||
private val nodeRequestActions: NodeRequestActions = mock()
|
||||
private val requestController: RequestController = mock()
|
||||
private val queryController: QueryController = mock()
|
||||
private val getNodeDetailsUseCase: GetNodeDetailsUseCase = mock()
|
||||
private val ensureRemoteAdminSession: EnsureRemoteAdminSessionUseCase = mock()
|
||||
private val observeRemoteAdminSessionStatus: ObserveRemoteAdminSessionStatusUseCase = mock()
|
||||
@@ -93,7 +93,7 @@ class HandleNodeActionTest {
|
||||
savedStateHandle = SavedStateHandle(mapOf("destNum" to 1234)),
|
||||
nodeManagementActions = nodeManagementActions,
|
||||
nodeRequestActions = nodeRequestActions,
|
||||
requestController = requestController,
|
||||
queryController = queryController,
|
||||
getNodeDetailsUseCase = getNodeDetailsUseCase,
|
||||
ensureRemoteAdminSession = ensureRemoteAdminSession,
|
||||
observeRemoteAdminSessionStatus = observeRemoteAdminSessionStatus,
|
||||
|
||||
@@ -42,7 +42,7 @@ import org.meshtastic.core.domain.usecase.session.ObserveRemoteAdminSessionStatu
|
||||
import org.meshtastic.core.model.Node
|
||||
import org.meshtastic.core.model.SessionStatus
|
||||
import org.meshtastic.core.navigation.SettingsRoute
|
||||
import org.meshtastic.core.repository.RequestController
|
||||
import org.meshtastic.core.repository.QueryController
|
||||
import org.meshtastic.core.resources.Res
|
||||
import org.meshtastic.core.resources.UiText
|
||||
import org.meshtastic.core.resources.connect_radio_for_remote_admin
|
||||
@@ -64,7 +64,7 @@ class NodeDetailViewModelTest {
|
||||
private lateinit var viewModel: NodeDetailViewModel
|
||||
private val nodeManagementActions: NodeManagementActions = mock()
|
||||
private val nodeRequestActions: NodeRequestActions = mock()
|
||||
private val requestController: RequestController = mock()
|
||||
private val queryController: QueryController = mock()
|
||||
private val getNodeDetailsUseCase: GetNodeDetailsUseCase = mock()
|
||||
private val ensureRemoteAdminSession: EnsureRemoteAdminSessionUseCase = mock()
|
||||
private val observeRemoteAdminSessionStatus: ObserveRemoteAdminSessionStatusUseCase = mock()
|
||||
@@ -97,7 +97,7 @@ class NodeDetailViewModelTest {
|
||||
savedStateHandle = SavedStateHandle(if (nodeId != null) mapOf("destNum" to nodeId) else emptyMap()),
|
||||
nodeManagementActions = nodeManagementActions,
|
||||
nodeRequestActions = nodeRequestActions,
|
||||
requestController = requestController,
|
||||
queryController = queryController,
|
||||
getNodeDetailsUseCase = getNodeDetailsUseCase,
|
||||
ensureRemoteAdminSession = ensureRemoteAdminSession,
|
||||
observeRemoteAdminSessionStatus = observeRemoteAdminSessionStatus,
|
||||
|
||||
Reference in New Issue
Block a user