Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
8b45988
feat: integrate meshtastic-sdk POC vertical slice
jamesarich May 3, 2026
dd67120
refactor(radio): use SDK BleTransport(address) factory (Gap F resolved)
jamesarich May 3, 2026
384746c
feat: enrich Android POC with SDK integration improvements
jamesarich May 4, 2026
f5e2aab
feat: add SdkRadioControllerImpl and SdkStateBridge for SDK hard cutover
jamesarich May 4, 2026
9319b0c
refactor: tighten SDK integration — eliminate old pipeline indirection
jamesarich May 4, 2026
2162e0a
feat: full SDK integration — drop AIDL, eliminate pipeline, SDK-backe…
jamesarich May 5, 2026
2c438b9
docs: add MIGRATION-REMAINING.md tracking clean-break progress
jamesarich May 5, 2026
74ba959
feat: DRY SDK integration — shared bridge, Desktop cutover, dead infr…
jamesarich May 5, 2026
3cdad0d
refactor: delete transport layer and dead intermediaries, slim RadioI…
jamesarich May 5, 2026
f0aa99e
refactor: delete POC ViewModels and stale references
jamesarich May 5, 2026
10425e8
refactor: delete dead broadcast Constants.kt and unused actionReceived
jamesarich May 5, 2026
a0ee9e8
docs: update MIGRATION-REMAINING.md with latest cleanup status
jamesarich May 5, 2026
1dd3637
refactor: eliminate NodeInfoReadDataSource, use NodeRepository directly
jamesarich May 5, 2026
19eba72
chore(database): Room migration 39→40 — drop legacy node tables
jamesarich May 5, 2026
352cce6
docs: update MIGRATION-REMAINING.md — Room cleanup complete (97%)
jamesarich May 5, 2026
551d441
refactor: merge NodeManager into SdkNodeRepositoryImpl + restore Mesh…
jamesarich May 5, 2026
a12aa26
docs: update MIGRATION-REMAINING to ~100% complete
jamesarich May 5, 2026
e7d1767
chore: remove ~220 LOC dead code from node layer
jamesarich May 5, 2026
296f27d
refactor: merge NodeManager into NodeRepository
jamesarich May 5, 2026
2db6db5
fix: add error handling to SDK bridge and radio controller
jamesarich May 5, 2026
43ecd2e
chore: rename stale files and update migration doc
jamesarich May 5, 2026
e9cb439
feat: rearchitect around SDK — decompose RadioController, simplify Da…
jamesarich May 5, 2026
27b2c19
refactor: narrow ViewModel injections, add ConnectionAware, delete de…
jamesarich May 5, 2026
7683db0
feat: deep SDK integration — retry delivery, Store-and-Forward API, c…
jamesarich May 5, 2026
6446419
feat: SFPP delegation to SDK, NeighborInfo SDK model, congestion + S&…
jamesarich May 5, 2026
c712d7e
chore: remove dead SfppHasher + unused NodeRepository injection
jamesarich May 5, 2026
31f792c
fix: remove SFPP vestige, resource-back badge strings, add bridge tests
jamesarich May 5, 2026
b874873
refactor: delete 6 dead packet handlers post-SDK cutover (-1420 lines)
jamesarich May 5, 2026
35f0373
refactor: use SDK remote admin API, eliminate sendRemoteAdmin
jamesarich May 6, 2026
6e5b159
refactor: eliminate MeshPacket/Data imports from radio layer
jamesarich May 6, 2026
140e062
refactor: eliminate ProcessRadioResponseUseCase and packet-ID correla…
jamesarich May 6, 2026
3fb45e0
fix: architecture review P0/P1 fixes
jamesarich May 6, 2026
f4c6cee
feat: typed telemetry dispatch + MeshTopology service
jamesarich May 6, 2026
0b5791a
refactor: remove getPacketId() from public interface
jamesarich May 6, 2026
0b6d8ec
chore: remove unused core.service dependencies from 5 feature modules
jamesarich May 6, 2026
6487d13
refactor: remove dead deps + add Compose stability annotations
jamesarich May 6, 2026
5dd5ebc
chore: remove more dead dependencies + stability annotations
jamesarich May 6, 2026
b3542c7
fix: remove circular StateFlow observation in RadioConfigViewModel
jamesarich May 6, 2026
07214bd
refactor: decouple feature/connections from feature/settings
jamesarich May 6, 2026
44b2a8c
refactor: extract MqttProbeCoordinator and ProfileCoordinator from Ra…
jamesarich May 6, 2026
046efd5
refactor: replace string-based route with typed Enum in RadioConfigState
jamesarich May 6, 2026
52aaa4d
feat: handle AdminResult.RateLimited from SDK, update MeshTopology API
jamesarich May 6, 2026
fa6d625
perf: Phase 4 Android performance optimizations
jamesarich May 6, 2026
604ce68
feat: set_ignored_node side effects mirror firmware behavior
jamesarich May 6, 2026
4340fc5
feat: handle NODE_STATUS_APP packets to populate node status string
jamesarich May 6, 2026
839d43d
Refactor SdkStateBridge bridges
jamesarich May 6, 2026
6450c69
Add distance-based node filter
jamesarich May 6, 2026
6490fc3
test: comprehensive bridge unit tests
jamesarich May 6, 2026
37be51b
test: MeshTopologyService and MessageDeliveryTracker tests
jamesarich May 6, 2026
2d299f7
test: SdkRadioController and PacketRepository coverage
jamesarich May 6, 2026
aacd789
Add integration tests for SDK→Bridge→Repository chain
jamesarich May 6, 2026
8133161
chore: KDoc cleanup, stale comments, and cruft removal
jamesarich May 6, 2026
d35dc5d
fix: update MqttManagerImplTest for nodeRepository constructor param
jamesarich May 6, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
162 changes: 162 additions & 0 deletions MIGRATION-REMAINING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
# SDK Migration — Status & Remaining Work

> Tracks progress of the Meshtastic-Android clean-break migration to meshtastic-sdk.
> Updated: 2026-05-05

---

## Summary

**Completed:** ~100% of the Clean Break migration. AIDL dropped, SDK is sole radio path,
transport layer fully deleted, Desktop uses shared SDK bridge, dead infrastructure gone,
POC ViewModels removed, NodeInfoReadDataSource eliminated, Room legacy tables dropped,
NodeManager merged into SdkNodeRepositoryImpl, MeshActivity restored.

**Remaining:** Optional VM parameter slimming and test coverage for new bridge code.

**Net change:** 170 files changed, +4,601 / -16,963 lines (net -12,362 LOC removed)

---

## Architecture (current state)

```
┌──────────────────────────────────────────────────────────────┐
│ Feature VMs (NodeList, Settings, RadioConfig, Messages...) │
│ inject: RadioController, NodeRepository, ServiceRepository │
└───────────────────────────────┬──────────────────────────────┘
┌───────────────────────────────▼──────────────────────────────┐
│ SDK-Backed Adapters (core/data) │
│ SdkRadioController, SdkStateBridge, SdkNodeRepositoryImpl │
│ SdkPacketHandler, SdkRadioInterfaceService │
│ MessagePersistenceHandler │
└───────────────────────────────┬──────────────────────────────┘
┌───────────────────────────────▼──────────────────────────────┐
│ RadioClientAccessor (platform-specific providers) │
│ Android: RadioClientProvider (app/) │
│ Desktop: DesktopRadioClientProvider (desktop/) │
└───────────────────────────────┬──────────────────────────────┘
┌───────────────────────────────▼──────────────────────────────┐
│ meshtastic-sdk │
│ RadioClient → MeshEngine → Transport (BLE/TCP/Serial) │
└──────────────────────────────────────────────────────────────┘
```

---

## What's Done

### Infrastructure ✅
- AIDL completely removed
- SDK composite build integrated
- `RadioClientProvider` (Android) + `DesktopRadioClientProvider` (Desktop)
- `SdkClientLifecycle` bridges to service layer
- SDK `sendRaw(ToRadio)` API added for MQTT/XModem

### Transport Layer ✅
- **Fully deleted:** BleRadioTransport, TcpRadioTransport, SerialRadioTransport, StreamTransport, HeartbeatSender, StreamFrameCodec, all transport factories, BleReconnectPolicy, TcpTransport
- `RadioInterfaceService` slimmed to device-address surface only
- `SdkRadioInterfaceService` created (thin adapter over RadioPrefs + RadioClientAccessor)
- `NoopRadioInterfaceService` deleted (superseded by SdkRadioInterfaceService)
- `JvmUsbScanner` migrated to SDK's `JvmSerialPorts.list()`

### Pipeline ✅
- **Fully deleted:** CommandSender, MeshRouter, MeshActionHandler, PacketHandlerImpl, MeshDataHandlerImpl, MeshConnectionManager, MeshConfigFlowManager, ServiceBroadcasts, DirectRadioControllerImpl, broadcast Constants.kt
- `SdkRadioController` is sole RadioController impl
- `SdkStateBridge` bridges SDK events → repositories
- `SdkPacketHandler` routes MeshPackets via `client.send()`, raw ToRadio via `client.sendRaw()`

### POC Code ✅
- **Deleted:** SdkConfigViewModel, SdkMessagingViewModel, SdkTelemetryViewModel, RadioClientViewModel, SdkNodeListViewModel
- All POC diagnostic logging removed from Main.kt
- Dead test fakes removed (app/test/Fakes.kt)

### Data Layer ✅
- Room migration 38→39: NodeMetadata persistence
- Room migration 39→40: DROP legacy `nodes`, `my_node`, `metadata` tables
- `SdkNodeRepositoryImpl` implements unified NodeRepository + NodeIdLookup
- SDK storage (SqlDelight) is source of truth for node data
- `AppMetadataRepository` provides firmware/hardware/model info
- NodeManagerImpl deleted — logic merged into SdkNodeRepositoryImpl

### Desktop ✅
- Fully cut over to SDK via shared KMP bridge
- `DesktopRadioClientProvider` manages TCP/Serial transport
- No transport stubs needed — SDK handles everything

### NodeManager Merge ✅
- NodeManager interface eliminated — all methods merged into NodeRepository
- `SdkNodeRepositoryImpl` now binds [NodeRepository, NodeIdLookup]
- Single in-memory StateFlow — no duplicate maps
- Metadata enrichment on every write (favorites, notes, ignore, mute)
- `NodeManagerImpl.kt` deleted (377 LOC)
- `NodeManager.kt` interface deleted (82 LOC)

### MeshActivity Restoration ✅
- `meshActivityFlow` added to ServiceRepository interface
- Emit `Send` from SdkPacketHandler.sendToRadio() and SdkRadioController.sendMessage()
- Emit `Receive` from ServiceRepositoryImpl.emitMeshPacket()
- UIViewModel.meshActivity wired to serviceRepository.meshActivityFlow
- Connection icon animation fully functional

### Dead Code Removal ✅
- Removed 7 dead methods from NodeManager/NodeRepository interfaces (~220 LOC)
- Deleted `NodeInfo` data class (kept MeshUser, Position, DeviceMetrics, EnvironmentMetrics)
- Renamed `NodeInfo.kt` → `MeshModels.kt`
- Removed dead `nodeManager` parameter from MeshServiceOrchestrator

### Error Handling ✅
- SdkStateBridge: ServiceAction dispatch wrapped in try/catch (prevents bridge death)
- Favorite/Ignore/Mute: local state update only applied on admin call success
- SdkRadioController: sendMessage + sendRemoteAdmin log errors before re-throwing
- ImportContact: guarded with runCatching

### UseCases Deleted ✅
- ProcessRadioResponse (tests only — impl kept, has real packet parsing logic)
- AdminActions (tests only — impl kept, has real reboot/reset logic)
- SetMeshLogSettings (tests only — impl kept)
- CleanNodeDatabase (tests only — impl kept)
- IsOtaCapable (tests only — impl kept)
- EnsureRemoteAdminSession (tests only — impl kept, complex concurrency)

---

## What Remains (optional, quality-of-life)

### 1. VM Parameter Slimming
VMs currently inject SDK-backed adapters (RadioController, NodeRepository, etc.)
which work correctly. Direct SDK injection would reduce params but isn't required:

| VM | Current Params | Could Be |
|----|---------------|----------|
| RadioConfigVM | 15 | 8-10 |
| SettingsVM | 12 | 8-10 |
| MessageVM | 12 | 6-8 |
| NodeListVM | 9 | 5-6 |
| NodeDetailVM | 7 | 4-5 |

### 2. Test Coverage
- New code (`SdkRadioInterfaceService`, `SdkPacketHandler`, `MessagePersistenceHandler`)
has no dedicated tests yet (existing integration tests cover happy paths)
- UseCase tests were deleted with the impls — should add back for kept impls

---

## What STAYS (permanent architecture)

These components are NOT migration candidates:

- `PacketRepository` — message persistence (SDK doesn't persist chat history)
- `MeshLogRepository` — debug logging (app-local)
- `QuickChatActionRepository` — quick-chat templates
- `DeviceHardwareRepository` / `FirmwareReleaseRepository` — GitHub API
- `NodeMetadataDao` / `AppMetadataRepository` — favorites, notes, ignore, mute
- `MeshServiceOrchestrator` — TAK lifecycle, notifications, DB init, widget
- `SdkStateBridge` — SDK → repository bridging, notifications, TAK dispatch
- `MqttManager` / `HistoryManager` / `XModemManager` — real features
- `TelemetryPacketHandler` / `NeighborInfoHandler` / `TracerouteHandler` — packet processors
- `ContactSettings` — per-contact mute/read state
- `SessionManager` — per-node admin session passkey management
11 changes: 11 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,17 @@ dependencies {
implementation(projects.feature.wifiProvision)
implementation(projects.feature.widget)

// Meshtastic SDK (composite build — sourced from ../meshtastic-sdk)
implementation(libs.sdk.core)
implementation(libs.sdk.proto)
implementation(libs.sdk.transport.ble)
implementation(libs.sdk.transport.tcp)
implementation(libs.sdk.transport.serial)
implementation(libs.sdk.storage.sqldelight)
// Kable Peripheral type is used directly in RadioClientProvider via BlePeripheralFactory
implementation(libs.kable.core)
testImplementation(libs.sdk.testing)

implementation(libs.jetbrains.compose.material3.adaptive)
implementation(libs.jetbrains.compose.material3.adaptive.layout)
implementation(libs.jetbrains.compose.material3.adaptive.navigation)
Expand Down
4 changes: 2 additions & 2 deletions app/src/fdroid/kotlin/org/meshtastic/app/map/MapView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -431,10 +431,10 @@ fun MapView(
}
}

fun getUsername(id: String?) = if (id == DataPacket.ID_LOCAL || (myId != null && id == myId)) {
fun getUsername(id: Int) = if (id == DataPacket.LOCAL || id == mapViewModel.myNodeNum) {
getString(Res.string.you)
} else {
mapViewModel.getUser(id).long_name
mapViewModel.getUser(DataPacket.nodeNumToId(id)).long_name
}

@Suppress("MagicNumber")
Expand Down
4 changes: 2 additions & 2 deletions app/src/fdroid/kotlin/org/meshtastic/app/map/MapViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -37,7 +37,7 @@ class MapViewModel(
mapPrefs: MapPrefs,
packetRepository: PacketRepository,
nodeRepository: NodeRepository,
radioController: RadioController,
radioController: MessageSender,
radioConfigRepository: RadioConfigRepository,
buildConfigProvider: BuildConfigProvider,
savedStateHandle: SavedStateHandle,
Expand Down
6 changes: 3 additions & 3 deletions app/src/google/kotlin/org/meshtastic/app/map/MapViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -89,7 +89,7 @@ class MapViewModel(
nodeRepository: NodeRepository,
packetRepository: PacketRepository,
radioConfigRepository: RadioConfigRepository,
radioController: RadioController,
radioController: MessageSender,
private val customTileProviderRepository: CustomTileProviderRepository,
uiPrefs: UiPrefs,
savedStateHandle: SavedStateHandle,
Expand Down Expand Up @@ -671,7 +671,7 @@ class MapViewModel(
}

override fun getUser(userId: String?) =
nodeRepository.getUser(userId ?: org.meshtastic.core.model.DataPacket.ID_BROADCAST)
nodeRepository.getUser(userId ?: org.meshtastic.core.model.DataPacket.nodeNumToId(org.meshtastic.core.model.DataPacket.BROADCAST))
}

enum class LayerType {
Expand Down
6 changes: 1 addition & 5 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,7 @@
-->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

<!-- Only for debug log writing, disable for production
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
-->

<!-- We run our mesh code as a foreground service - FIXME, find a way to stop doing this -->
<!-- Required: foreground service keeps mesh connection alive per Android 14+ requirements -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
Expand Down
5 changes: 5 additions & 0 deletions app/src/main/kotlin/org/meshtastic/app/MeshUtilApplication.kt
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import org.meshtastic.core.database.DatabaseManager
import org.meshtastic.core.repository.MeshPrefs
import org.meshtastic.core.service.worker.MeshLogCleanupWorker
import org.meshtastic.feature.widget.LocalStatsWidgetReceiver
import org.meshtastic.sdk.storage.sqldelight.AndroidContextHolder
import kotlin.time.Duration.Companion.hours
import kotlin.time.Duration.Companion.seconds
import kotlin.time.toJavaDuration
Expand All @@ -63,6 +64,10 @@ open class MeshUtilApplication :
super.onCreate()
ContextServices.app = this

// Must be set before startKoin so SqlDelightStorageProvider (used by RadioClientProvider)
// can resolve applicationContext internally.
AndroidContextHolder.context = applicationContext

startKoin<AndroidKoinApp> {
androidContext(this@MeshUtilApplication)
workManagerFactory()
Expand Down
Loading
Loading