Files
James Rich 4f57e65097 refactor: remove AIDL API and modernize radio architecture
Remove the deprecated AIDL/IPC API surface and perform deep architectural
modernization of the radio command pipeline, aligning with the meshtastic-sdk
AdminApiImpl pattern for future SDK migration.

Key changes:

1. AIDL Removal & Infrastructure Cleanup
   - Delete core:api module and all AIDL interfaces
   - Remove ServiceBroadcasts + CommonParcelable infrastructure
   - Remove core:api from CI workflow lint/publish steps

2. Model Modernization
   - Introduce NodeAddress sealed class with type-safe addressing
   - Remove deprecated DataPacket constants in favor of NodeAddress
   - Consolidate dual node maps into single source with getNodeById
   - Split large model files, deduplicate NodeEntity, flatten RadioController

3. Service Layer Refactoring (SDK-aligned)
   - Remove ServiceAction sealed class, use direct suspend calls
   - Convert CommandSender & MeshActionHandler to suspend APIs
   - Merge MeshActionHandler into DirectRadioControllerImpl
     (ViewModel → RadioController → CommandSender, no intermediate layer)
   - Build AdminMessage protos directly with typed protos end-to-end
   - Apply structured concurrency to NodeRequestActions/NodeManagementActions
   - Fix CancellationException handling throughout

Architecture (before → after):
  ViewModel → RadioController → Handler → CommandSender → PacketHandler
  ViewModel → RadioController → CommandSender → PacketHandler

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-23 15:00:49 -05:00
..

:core:domain

Overview

The :core:domain module is the business-logic layer of the KMP architecture. It contains exclusively use-case classes — no UI, no platform code, no mutable state. Each use case is a thin orchestrator that coordinates one or more repository/model dependencies to fulfil a single application action.

Targets: Android · JVM · iOS (via meshtastic.kmp.library convention plugin)

Key Responsibilities

  • Orchestrate radio configuration reads/writes (config, module config, channels, owner, position)
  • Manage remote-admin session lifecycle (per-node passkey negotiation)
  • Application settings toggles (theme, locale, analytics, notifications, location sharing)
  • Data export (CSV mesh log, profile .zip, security config)
  • Profile and config import/install
  • Node database maintenance (clean, reset, selective purge)

Source Structure

src/commonMain/kotlin/org/meshtastic/core/domain/
├── di/
│   └── CoreDomainModule.kt          ← Koin @Module + component scan
└── usecase/
    ├── session/
    │   ├── EnsureRemoteAdminSessionUseCase.kt
    │   ├── EnsureSessionResult.kt
    │   └── ObserveRemoteAdminSessionStatusUseCase.kt
    └── settings/
        ├── AdminActionsUseCase.kt
        ├── CleanNodeDatabaseUseCase.kt
        ├── ExportDataUseCase.kt
        ├── ExportProfileUseCase.kt
        ├── ExportSecurityConfigUseCase.kt
        ├── ImportProfileUseCase.kt
        ├── InstallProfileUseCase.kt
        ├── IsOtaCapableUseCase.kt
        ├── ProcessRadioResponseUseCase.kt
        ├── RadioConfigUseCase.kt
        ├── SetAppIntroCompletedUseCase.kt
        ├── SetDatabaseCacheLimitUseCase.kt
        ├── SetLocaleUseCase.kt
        ├── SetMeshLogSettingsUseCase.kt
        ├── SetNotificationSettingsUseCase.kt
        ├── SetProvideLocationUseCase.kt
        ├── SetThemeUseCase.kt
        ├── ToggleAnalyticsUseCase.kt
        └── ToggleHomoglyphEncodingUseCase.kt

Notable APIs

EnsureRemoteAdminSessionUseCase

Ensures a per-node remote-admin passkey session exists before entering the remote admin UI. Uses a Mutex-guarded inFlight map so that double-taps coalesce onto a single Deferred.

sealed interface EnsureSessionResult {
    data object AlreadyActive   : EnsureSessionResult  // passkey already fresh
    data object Refreshed       : EnsureSessionResult  // metadata response arrived
    data object Timeout         : EnsureSessionResult  // no response within 10 s
    data object Disconnected    : EnsureSessionResult  // radio not connected
}

RadioConfigUseCase

Radio configuration read/write operations, all returning the packetId for async tracking:

Method Description
setOwner / getOwner Node owner info
setConfig / getConfig Config proto (device, position, power, …)
setModuleConfig / getModuleConfig ModuleConfig proto
getChannel / setRemoteChannel Channel configuration
setFixedPosition / removeFixedPosition Fixed GPS position
setRingtone / getRingtone External notification ringtone
setCannedMessages / getCannedMessages Canned message slots

AdminActionsUseCase

reboot(destNum)
shutdown(destNum)
factoryReset(destNum, isLocal)   // also clears local NodeDB when isLocal = true
nodedbReset(destNum, preserveFavorites, isLocal)

ExportDataUseCase

Streams all mesh log packets to a CSV BufferedSink. Columns: date, time, from, sender name/location, received location/elevation, SNR, distance, hop limit, payload.

Dependency Graph

core:domain
  ├── core:repository   (use-case interfaces & contracts)
  ├── core:model        (domain models)
  ├── core:proto        (Meshtastic protobuf types)
  ├── core:common
  ├── core:database
  ├── core:datastore
  └── core:resources

DI

All use cases are registered via Koin component scan on org.meshtastic.core.domain. No manual binding is needed — annotate a new use case with @Single and it is picked up automatically.

Dependency Graph

graph TB
  :core:domain[domain]:::kmp-library
  :core:domain -.-> :core:repository
  :core:domain -.-> :core:model
  :core:domain -.-> :core:proto
  :core:domain -.-> :core:common
  :core:domain -.-> :core:database
  :core:domain -.-> :core:datastore
  :core:domain -.-> :core:resources
  :core:domain -.-> :core:testing

classDef android-application fill:#CAFFBF,stroke:#000,stroke-width:2px,color:#000;
classDef android-application-compose fill:#CAFFBF,stroke:#000,stroke-width:2px,color:#000;
classDef compose-desktop-application fill:#CAFFBF,stroke:#000,stroke-width:2px,color:#000;
classDef android-feature fill:#FFD6A5,stroke:#000,stroke-width:2px,color:#000;
classDef android-library fill:#9BF6FF,stroke:#000,stroke-width:2px,color:#000;
classDef android-library-compose fill:#9BF6FF,stroke:#000,stroke-width:2px,color:#000;
classDef android-test fill:#A0C4FF,stroke:#000,stroke-width:2px,color:#000;
classDef jvm-library fill:#BDB2FF,stroke:#000,stroke-width:2px,color:#000;
classDef kmp-feature fill:#FFD6A5,stroke:#000,stroke-width:2px,color:#000;
classDef kmp-library-compose fill:#FFC1CC,stroke:#000,stroke-width:2px,color:#000;
classDef kmp-library fill:#FFC1CC,stroke:#000,stroke-width:2px,color:#000;
classDef unknown fill:#FFADAD,stroke:#000,stroke-width:2px,color:#000;