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>
: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;