Skip to content

feat: hard cutover to meshtastic-sdk — eliminate AIDL pipeline#5379

Draft
jamesarich wants to merge 53 commits intomeshtastic:mainfrom
jamesarich:feat/meshtastic-sdk
Draft

feat: hard cutover to meshtastic-sdk — eliminate AIDL pipeline#5379
jamesarich wants to merge 53 commits intomeshtastic:mainfrom
jamesarich:feat/meshtastic-sdk

Conversation

@jamesarich
Copy link
Copy Markdown
Collaborator

Summary

Complete hard cutover from the legacy AIDL/MeshService pipeline to meshtastic-sdk. The app now connects to radios via the SDK's RadioClient directly, eliminating ~20k lines of intermediary code.

Architecture (52 commits, 305 files, +11.4k / −20.5k lines)

What changed:

  • SdkRadioControllerImpl + SdkStateBridge replace the old AIDL IMeshService binding
  • RadioClientSdkStateBridgeRepository — three-layer data flow
  • All ViewModels narrowed to typed interfaces (ConnectionAware, NodeProvider, etc.)
  • Room migration 39→40: dropped legacy node tables
  • Deleted transport layer, dead intermediaries, POC ViewModels, broadcast Constants

What was removed (~20.5k lines):

  • AIDL IMeshService + MeshService pipeline
  • ProcessRadioResponseUseCase and packet-ID correlation
  • 6 dead packet handlers
  • SfppHasher, dead NodeManager, unused NodeRepository injection
  • MeshDataMapper, CommandSender indirection
  • String-based radio config routing → typed enum

What was added (~11.4k lines):

  • SDK bridge layer: SdkStateBridge, SdkRadioControllerImpl, MeshTopologyService
  • MessageDeliveryTracker with SDK retry policy
  • Congestion + S&F server UI badges
  • NeighborInfo SDK model delegation
  • Distance-based node filter
  • SFPP delegation to SDK
  • Performance optimizations (Phase 4)

Tests (400+ tests, 0 failures)

  • SdkRadioController, PacketRepository, MeshTopologyService, MessageDeliveryTracker
  • Bridge unit tests, integration tests (SDK→Bridge→Repository chain)
  • MqttManagerImpl, UIViewModel, ScannedQrCodeViewModel
  • Repository impls: AppMetadata, DeviceHardware, FirmwareRelease (Robolectric)

KDoc & Cleanup

  • KDoc on all datasource interfaces and repository implementations
  • Removed stale manifest comments, IndoorAirQuality stubs, redundant DAO comments

⚠️ Depends on: meshtastic/meshtastic-sdk#1 — SDK must merge and publish 0.1.0-SNAPSHOT first.
During development, the app resolves SDK via includeBuild("../meshtastic-sdk") composite build.
For CI, update libs.versions.toml meshtastic-sdk version to the published snapshot/release after SDK PR merges.

@github-actions github-actions Bot added enhancement New feature or request refactor no functional changes labels May 6, 2026
jamesarich and others added 28 commits May 6, 2026 17:52
Wires core flows to the meshtastic-sdk (0.1.0-SNAPSHOT) while keeping the
legacy path alive. Goal: prove the SDK works with a real Android app and
surface API deficiencies.

Build:
- settings.gradle.kts: composite build inclusion for meshtastic-sdk
  (../meshtastic-sdk) with dependency substitution for all SDK artifacts
- libs.versions.toml: sdk = "0.1.0-SNAPSHOT", mavenCentral snapshots repo
- app/build.gradle.kts: sdk-core, sdk-proto, sdk-transport-ble,
  sdk-storage-sqldelight dependencies

Bootstrap:
- MeshUtilApplication: AndroidContextHolder.context set in onCreate()
  before startKoin so SqlDelightStorageProvider can locate app files
- RadioClientProvider (@single, binds SdkClientLifecycle): mutex-serialized
  rebuildAndConnect(), strips 'x' prefix from BLE devAddr, holds
  RadioClient StateFlow
- RadioClientViewModel: exposes RadioClientProvider to UI layer

SDK ViewModels (POC quality, compile-verified):
- SdkNodeListViewModel: NodeChange.Snapshot/Added/Updated/Removed → UiNode
- SdkMessagingViewModel: sendText() via client.sendText(), incomingText
  via client.textMessages (Gap B — now fixed in SDK)
- SdkConfigViewModel: configBundle reads, setConfig/setOwner writes,
  loadChannels() via admin, Gap G workaround (local override map)
- SdkTelemetryViewModel: TelemetryApi.observe(NodeId), requestDeviceMetrics

Service lifecycle:
- SdkClientLifecycle interface in core:service (avoids reverse dep from
  service → app); RadioClientProvider implements it
- MeshService.onDestroy: calls sdkClientLifecycle.disconnect() before
  serviceJob.cancel()
- BlePeripheralFactory.kt in core:ble: public buildPeripheralForAddress()
  wrapper (Gap F workaround; proper fix needed in SDK transport-ble)

SDK gaps discovered and logged:
  Gap B - textMessages flow (FIXED in SDK feat/meshtastic-android-integration-gaps)
  Gap C - channels StateFlow (no reactive cache, only admin.listChannels())
  Gap F - BleTransport MAC string factory (requires live Peripheral today)
  Gap G - configBundle not refreshed after editSettings writes

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace the two-step buildPeripheralForSavedAddress() + BleTransport(peripheral, address)
with the new SDK factory BleTransport(address) { autoConnectIf { true } }.

The SDK's transport-ble androidMain now exposes BleTransport(address: String,
builderAction: PeripheralBuilder.() -> Unit) directly, removing the need for
the core:ble workaround. The autoConnectIf { true } flag preserves bonded-device
behavior (avoids GATT 133 on reconnect without a fresh advertisement).

Gap F note removed from RadioClientProvider KDoc — gap is resolved in SDK.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- RadioClientProvider: support BLE, TCP, and Serial transports via
  InterfaceId routing; handle MOCK/NOP gracefully
- SdkNodeListViewModel: use SDK's MeshNode model for 20+ enriched
  fields (isOnline, battery, position, signal quality, etc.)
- SdkConfigViewModel: remove Gap G workaround (_localConfigOverrides),
  add reactive channels StateFlow from SDK

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace the legacy AIDL-based AndroidRadioControllerImpl with an SDK-backed
implementation that delegates all operations through RadioClient:

SdkRadioControllerImpl:
- Full RadioController interface impl (~35 methods)
- Local admin ops → SDK AdminApi/TelemetryApi/RoutingApi
- Remote admin ops → raw MeshPacket via RadioClient.send()
- Registered as @single(binds = [RadioController::class]) in Koin

SdkStateBridge:
- Bridges SDK reactive flows (connection, nodes, packets, events) into
  ServiceRepository and NodeManager for legacy UI compatibility
- Connection state mapping (SDK states → app ConnectionState enum)
- Node snapshot/added/updated/removed → NodeManager
- Inbound telemetry/position/user packets → NodeManager handlers
- Events (reboot, security warnings, drops) → notification layer

AndroidRadioControllerImpl:
- @single annotation disabled (commented out) to prevent Koin conflict
- Class retained for reference/fallback

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
SdkStateBridge:
- Remove redundant manual proto decode (telemetry/position/user) — SDK's
  NodeChange flow already provides updated NodeInfo with all fields
- Forward raw packets to meshPacketFlow for RadioConfigViewModel + TAK
- Handle ServiceActions directly via SDK AdminApi (Favorite, Ignore, Mute,
  Reaction, ImportContact, SendContact, GetDeviceMetadata)
- Bypasses entire CommandSender → MeshActionHandler → MeshRouter chain

MeshServiceOrchestrator:
- Remove radioInterfaceService.connect() — SDK owns transport now
- Remove receivedData → messageProcessor wiring — SDK handles packets
- Remove serviceAction → actionHandler subscription — SdkStateBridge handles
- Remove resetReceivedBuffer — no raw byte pipeline to drain
- Retain: TAK integration, database init, service notifications
- Remove unused constructor params (serviceRepository, messageProcessor, router)

Architecture after this commit:
- All in-app flows: Feature VM → RadioController → SdkRadioControllerImpl → SDK
- ServiceActions: UI → ServiceRepository.onServiceAction → SdkStateBridge → SDK
- AIDL binder: only remaining path to old CommandSender/MeshActionHandler (for
  external 3rd-party app compat only)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…d nodes

Phase 1: Drop AIDL
- Delete all 6 .aidl files (core/api/src/main/aidl/)
- Strip binder object from MeshService (onBind returns null)
- Simplify MeshServiceClient to lifecycle observer (no AIDL binding)
- Remove meshService field from AndroidServiceRepository
- Delete ServiceClient, AndroidRadioControllerImpl, FakeIMeshService,
  IMeshServiceContractTest

Phase 2: Migrate TAK to RadioController
- GenericCoTHandler: RadioController replaces CommandSender
- TAKMeshIntegration: RadioController replaces CommandSender
- CoreTakServerModule: DI updated

Phase 3: Port remaining consumers
- MeshConnectionManagerImpl: RadioController, remove time sync/passkey
  (SDK handles internally), wrap telemetry in coroutine
- MeshConfigFlowManagerImpl: remove CommandSender dependency, use 0L
  for currentPacketId
- RefreshLocalStatsAction: RadioController for widget telemetry
- EnsureRemoteAdminSessionUseCase: serviceRepository.onServiceAction()
  replaces meshActionHandler

Phase 4: Delete dead pipeline
- Delete FromRadioPacketHandler interface + FromRadioPacketHandlerImpl
- Delete MeshMessageProcessor interface + MeshMessageProcessorImpl
- Delete related tests
- Remove MeshMessageProcessor dep from MeshActionHandlerImpl
- Keep CommandSender/MeshRouter/MeshActionHandler for
  DirectRadioControllerImpl (desktop target)

Phase 5: SDK-backed NodeRepository
- New SdkNodeRepositoryImpl: in-memory StateFlow backed by NodeManager
- SDK handles persistence via its own SqlDelight storage
- Deactivate Room-backed NodeRepositoryImpl (@single removed)
- NodeManagerImpl propagates myNodeNum to SdkNodeRepositoryImpl
- Cold start: brief empty state until SDK emits snapshot (<1s)
- Node notes: in-memory for POC (does not survive process death)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Documents completed phases (A-G partial), remaining blockers,
and recommended execution order for full SDK migration.

Key remaining items:
- Desktop SDK migration (unblocks ~2,000 LOC deletion)
- Module restructuring (unblocks VM direct-binding)
- 22 VM migrations to RadioClient
- 4 deferred UseCase deletions
- Phase C completion (packet flow cleanup)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…a deletion

Major architectural changes:

1. SHARED KMP BRIDGE (core/data/radio/):
   - RadioClientAccessor: Platform-agnostic interface for SDK RadioClient
   - SdkRadioController: Shared RadioController impl (replaces per-platform copies)
   - SdkStateBridge: Shared SDK→repository bridge (event dispatch, node sync)
   - SdkPacketHandler: Thin SDK-backed PacketHandler for MQTT/XModem/History

2. DESKTOP SDK CUTOVER:
   - DesktopRadioClientProvider: TCP + Serial transport support
   - Removed DirectRadioControllerImpl (old desktop radio path)
   - Desktop now shares the same SDK bridge code as Android

3. DEAD INFRASTRUCTURE DELETION (~5,100 LOC removed):
   - PacketHandlerImpl, MeshDataHandlerImpl, MeshRouterImpl
   - CommandSenderImpl, MeshActionHandlerImpl, MeshConfigFlowManagerImpl
   - MeshConnectionManagerImpl, AdminPacketHandlerImpl
   - ServiceBroadcasts (Android intent-based pub/sub)
   - NodeRepositoryImpl (Room-backed, replaced by SdkNodeRepositoryImpl)
   - 8 trivial UseCases (SetLocale, SetTheme, ToggleAnalytics, etc.)
   - All associated test files for deleted impls
   - Deleted interfaces: AdminPacketHandler, CommandSender, MeshActionHandler,
     MeshConfigFlowManager, MeshConnectionManager, MeshRouter, ServiceBroadcasts

4. NEW FEATURES:
   - NodeMetadataEntity + Room migration 38→39 (persistent favorites/notes)
   - AppMetadataRepository (clean access to node metadata)
   - MessagePersistenceHandler (focused rememberDataPacket for StoreForward)

All three targets compile clean: :app:compileGoogleDebugKotlin,
:desktop:compileKotlin, :core:data:jvmTest passes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…nterfaceService

- Delete entire transport layer: BleRadioTransport, TcpRadioTransport,
  SerialRadioTransport, StreamTransport, HeartbeatSender, StreamFrameCodec,
  AndroidRadioTransportFactory, BaseRadioTransportFactory, MockRadioTransport,
  NopRadioTransport, BleReconnectPolicy, TcpTransport, SerialTransport
- Delete MeshConfigHandler interface + impl (replaced by RadioConfigRepository)
- Delete RadioTransportCallback, RadioTransport, RadioTransportFactory interfaces
- Delete FakeRadioTransport, RadioTransportTest, MeshConfigHandlerImplTest
- Delete UseCase tests (impls restored, tests for deleted patterns removed)
- Slim RadioInterfaceService interface: remove transport internals, keep only
  device-address/connection surface needed by Scanner and connections UIs
- Create SdkRadioInterfaceService: thin SDK-backed impl delegating to
  RadioPrefs + RadioClientAccessor
- Update NoopRadioInterfaceService to match slimmed interface
- Update JvmUsbScanner to use SDK's JvmSerialPorts.list() instead of
  deleted SerialTransport.getAvailablePorts()
- Remove DesktopRadioTransportFactory from desktop DI module
- Fix NodeListViewModel: replace RadioInterfaceService with RadioPrefs
- Fix MeshServiceOrchestratorTest: align with updated constructor params
- Fix UIViewModel: use emptyFlow() for meshActivity (SDK doesn't emit
  raw transport-level activity events)

All targets compile clean, all JVM + Android unit tests pass.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Delete SdkConfigViewModel, SdkMessagingViewModel, SdkTelemetryViewModel (unused POC)
- Delete RadioClientViewModel, SdkNodeListViewModel (POC logging only)
- Remove POC VM instantiation from Main.kt
- Delete NoopRadioInterfaceService (superseded by SdkRadioInterfaceService)
- Delete dead app/test/Fakes.kt (both classes unused)
- Fix stale KDoc references to MeshConnectionManager, RadioInterfaceService.connectionState

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Constants.kt re-exported MeshtasticIntent constants for the deleted
ServiceBroadcasts system. No consumers remain after broadcast removal.
Also remove MeshService.actionReceived() companion method (no callers).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
MeshLogRepositoryImpl was the sole external consumer of
NodeInfoReadDataSource — it only needed myNodeNum to identify the
local node in logs. Replace with NodeRepository.myNodeInfo (already
SDK-backed via SdkNodeRepositoryImpl).

PacketRepositoryImpl referenced NodeInfoDao.MAX_BIND_PARAMS — inlined
as a private constant since it's just the SQLite bind-param limit.

With zero external consumers remaining, delete:
- NodeInfoReadDataSource interface
- SwitchingNodeInfoReadDataSource implementation

The Room NodeInfoDao/NodeEntity/MyNodeEntity remain in the database
module for now (internal tests + migration history) but have no
external dependents — ready for a future Room migration 40 to DROP.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Remove my_node, nodes, metadata tables via AutoMigration with @deletetable
- Parameterize all 34 PacketDao queries to accept myNodeNum directly
- Delete NodeInfoDao, NodeEntity, MyNodeEntity, MetadataEntity
- Delete CommonNodeInfoDaoTest (dead)
- Rewrite PacketRepositoryImpl to inject NodeRepository for myNodeNum
- Rewrite CommonPacketDaoTest and CommonPacketRepositoryTest
- Update MigrationTest to remove nodeInfoDao usage
- Update core/database README to reflect current schema

SDK is now sole source of truth for all node data.
Room only stores messages, logs, and user annotations.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…Activity

- Merge NodeManagerImpl logic into SdkNodeRepositoryImpl (single source
  of truth for all node state — eliminates duplicate in-memory maps)
- SdkNodeRepositoryImpl now binds NodeRepository, NodeManager, NodeIdLookup
- Delete NodeManagerImpl.kt (377 LOC)
- Add meshActivityFlow to ServiceRepository for nav-bar icon animation
- Emit MeshActivity.Send from SdkPacketHandler and SdkRadioController
- Emit MeshActivity.Receive from ServiceRepositoryImpl.emitMeshPacket()
- Wire UIViewModel.meshActivity to serviceRepository.meshActivityFlow
- Align insertMetadata signature (remove unnecessary suspend)
- Adapt NodeManagerImplTest to test SdkNodeRepositoryImpl directly
- Update FakeServiceRepository with meshActivityFlow stub

All targets compile clean (Android + Desktop), all tests pass.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
NodeManager merge and MeshActivity restoration are done.
Only optional VM param slimming and test coverage remain.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Delete dead methods from NodeManager/NodeRepository interfaces:
- allowNodeDbWrites, setAllowNodeDbWrites (never read)
- loadCachedNodeDB (no-op, zero-value call in orchestrator)
- getNodes(): List<NodeInfo> (deprecated, unused)
- handleReceivedPaxcounter (zero callers)
- handleReceivedNodeStatus, updateNodeStatus (zero callers)
- insertMetadata (zero production callers)

Delete NodeInfo data class (85 LOC):
- All consumers now use Node domain model directly
- Retained MeshUser, Position, DeviceMetrics, EnvironmentMetrics
  which have active consumers across feature modules

Remove corresponding implementations from SdkNodeRepositoryImpl,
FakeNodeRepository, and test verifications.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Eliminate the vestigial NodeManager/NodeRepository interface split.
All runtime node state management methods (handleReceivedUser,
handleReceivedPosition, handleReceivedTelemetry, updateNode, etc.)
now live directly on NodeRepository alongside the query surface.

- Delete NodeManager.kt (82 LOC)
- Extend NodeRepository with NodeIdLookup and add all manager methods
- Update 8 consumers to inject NodeRepository instead of NodeManager
- Remove dead nodeManager param from MeshServiceOrchestrator
- Add NodeManager methods to FakeNodeRepository test double
- Update all tests (mocks, constructor params, verifications)
- SdkNodeRepositoryImpl now binds [NodeRepository, NodeIdLookup]

Build: assembleDebug ✅, desktop:compileKotlin ✅, all jvmTests ✅

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
SdkStateBridge:
- Wrap handleServiceAction in try/catch to prevent bridge death
- Favorite/Ignore/Mute: only apply local state update on admin
  success (eliminates optimistic state inconsistency)
- ImportContact: guard with runCatching, log failures
- Extract dispatchAction for clean separation of concerns

SdkRadioController:
- Wrap sendMessage with try/catch + logging before re-throw
- Wrap sendRemoteAdmin with try/catch + logging before re-throw
- Ensures BLE disconnect errors are visible in logs

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- NodeInfo.kt → MeshModels.kt (no longer contains NodeInfo class)
- NodeManagerImplTest.kt → SdkNodeRepositoryImplTest.kt (tests SdkNodeRepositoryImpl)
- Update MIGRATION-REMAINING.md with dead code removal, error handling,
  and NodeManager merge status

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…taPacket, integrate SDK utilities

Android rearchitecture consuming meshtastic-sdk improvements:

A1 — ConnectionState Enrichment:
- Rich sealed interface with Connecting(attempt), Configuring(phase, progress), Reconnecting(attempt)
- SdkStateBridge maps SDK states preserving metadata

A2 — MessageHandle Integration:
- MessageDeliveryTracker: tracks delivery via SDK MessageHandle
- SdkRadioController captures handles on send

A3 — RadioController Decomposition:
- Split into 5 focused interfaces: MessageSender, DeviceAdmin, RemoteAdmin, DeviceControl, DataRequester
- RadioController extends all; SdkRadioController binds all via Koin

A4 — DataPacket Simplification:
- to/from fields changed from String? to Int (node numbers directly)
- Removed string ID parsing layer; added BROADCAST/LOCAL constants
- Updated ~40 consumer files across feature modules

A5 — SDK Utility Consumption:
- DeviceVersion, Capabilities, SfppHasher, LocationUtils delegate to SDK
- Removed duplicated protocol logic

A6 — Presence Events:
- SdkStateBridge handles NodeChange.WentOffline/CameOnline
- Updates node online status via repository

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ad code, integration tests

ViewModel Narrowing:
- V1: Created ConnectionAware interface; MessageSender, DeviceAdmin, DeviceControl extend it
- V2: Narrowed 6 ViewModels/actions to focused sub-interfaces (DeviceAdmin, MessageSender, DataRequester, DeviceControl)

Cleanup:
- C1: Deleted dead MeshDataMapper and its DI registration

Integration Tests:
- T2: SdkStateBridgeTest verifying WentOffline/CameOnline presence handling
- Verified Koin resolution, full test suite passes

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ongestion surfacing

I1 — RetryPolicy in MessageDeliveryTracker:
- track() now accepts optional RetryPolicy (default: ExponentialBackoff 3 attempts)
- Failed sends automatically retry before marking ERROR
- UI sees ENROUTE during retry attempts

I2 — Store-and-Forward SDK consumption:
- SdkStateBridge observes storeForward.events and servers
- ServiceRepository exposes storeForwardServers StateFlow
- SdkRadioController.requestStoreForwardHistory() delegates to SDK
- HistoryManagerImpl uses SDK path instead of manual packet construction

I3 — Congestion level surfacing:
- SdkStateBridge handles MeshEvent.CongestionWarning
- ServiceRepository.congestionLevel StateFlow exposed to UI
- Cleared on disconnect

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…F badges

- SdkStateBridge: handle SfppLinkProvided/SfppCanonAnnounced events from SDK
- StoreForwardPacketHandlerImpl: SFPP parsing removed (SDK-owned)
- NeighborInfoHandlerImpl: delegate formatting to SDK NeighborInfo.fromProto()
- NodeStatusIcons: CongestionBadge (yellow/orange/red for MEDIUM/HIGH/CRITICAL)
- NodeStatusIcons: StoreForwardBadge (blue cloud icon for S&F servers)
- NodeListViewModel: expose congestionLevel + storeForwardServers flows
- Tests updated for SFPP bridge coverage

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Delete SfppHasher wrapper (SDK SfppHash handles SFPP hashing)
- Remove unused NodeRepository from StoreForwardPacketHandlerImpl
- Update tests to match constructor change

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Remove handleStoreForwardPlusPlus() from interface + impl (dead code post-SDK cutover)
- Move StoreForwardBadge/CongestionBadge hardcoded strings to string resources (i18n)
- Add SdkStateBridge tests: congestion warning → ServiceRepository, S&F server propagation

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Remove handler interfaces, implementations, and tests that have zero production
callers after the SDK became authoritative for protocol handling:

- TelemetryPacketHandler/Impl — SDK owns telemetry via NodeChange.Updated
- StoreForwardPacketHandler/Impl — SDK owns S&F lifecycle + SFPP
- NeighborInfoHandler/Impl — SDK owns NeighborInfo model
- TracerouteHandler/Impl — SDK owns traceroute via AdminResult flow
- MeshDataHandler/MessagePersistenceHandler — handleReceivedData was no-op
- HistoryManager/Impl — only caller was deleted StoreForwardPacketHandlerImpl

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
jamesarich and others added 24 commits May 6, 2026 17:52
- Replace all isLocalNode/sendRemoteAdmin patterns with
  client.admin.forNode(NodeId(destNum)).method()
- Use client.sendReaction() instead of manual MeshPacket construction
- Use client.admin.forNode(dest).getDeviceMetadata() for remote metadata
- Delete sendRemoteAdmin() and isLocalNode() helpers
- Remove unused imports (AdminMessage, Data, MeshPacket from SdkStateBridge)

Net: -131 lines, all admin ops now go through SDK's typed API with
proper ACK tracking and session-key retry.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- sendMessage() now uses client.send(portnum, payload, to, channel, ...)
- requestUserInfo() now uses client.requestNodeInfo(NodeId)
- SdkStateBridge position publishing calls SDK directly (removes
  intermediate DataPacket + RadioController dependency)
- Remove unused imports: MeshPacket, Data, okio.toByteString
- Remove unused radioController dependency from SdkStateBridge

Net: SdkRadioController no longer imports MeshPacket or Data proto.
Only AdminMessage remains (for beginEditSettings/commitEditSettings).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…tion

Replace the manual packet-ID tracking and meshPacketFlow subscription in
RadioConfigViewModel with direct typed returns from the SDK via
RadioConfigUseCase. The ViewModel now awaits typed results (User, Config,
ModuleConfig, channels, etc.) from suspend calls and maps AdminException
to UI error states.

Key changes:
- Delete ProcessRadioResponseUseCase (130 lines of manual proto decode)
- Remove requestIds state, registerRequestId, processPacketResponse,
  sendAdminRequest, and meshPacketFlow subscription from ViewModel
- Rewrite setResponseStateLoading to use direct coroutine calls
- Admin actions (reboot/shutdown/etc.) fire directly without session key
  preflight (SDK handles retryOnSessionExpiry transparently)
- All setters (setConfig, setModuleConfig, setOwner, updateChannels)
  no longer return/track packetIds
- Remove messageSender dependency from NodeManagementActions
- Update InstallProfileUseCase to use editSettings {} receiver pattern
- Update all callers: CleanNodeDatabaseUseCase, Esp32OtaUpdateHandler,
  NodeManagementActions
- Rewrite RadioConfigViewModelTest for new direct-await semantics
- Update RadioConfigUseCaseTest and InstallProfileUseCaseTest

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- P0: setDeviceAddress now persists address to RadioPrefs before reconnect
- P0: Document nodedbReset firmware limitation (preserveFavorites is local-only)
- P1: Add writeAction helper for AdminException → sendError in RadioConfigViewModel
- P1: Wrap CommonNodeRequestActions scope.launch with runCatching for crash safety
- P1: Create CongestionLevel typealias in core/model to decouple feature modules from SDK
- P1: Cancel prior loadJob in setResponseStateLoading to prevent stale results

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Replace magic int dispatch in requestTelemetry with TelemetryType enum
- Update DataRequester interface: remove requestId param, use TelemetryType directly
- Add HEALTH and TRAFFIC_MANAGEMENT to TelemetryType, remove stale PAX variant
- Create MeshTopologyService wrapping SDK's MeshTopology with thread-safe Mutex
- Wire NeighborInfo packet ingestion in SdkStateBridge → topology graph
- Clear topology on node snapshot (reconnect)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Remove getPacketId() from MessageSender interface (SDK owns packet IDs)
- Remove requestId parameter from requestTraceroute/requestNeighborInfo
- Make getPacketId() private in SdkRadioController (still used internally
  for delivery tracking correlation)
- Replace radioController.getPacketId() in map with local Random.nextInt()
  for waypoint ID generation (only needs uniqueness, not SDK correlation)
- Remove messageSender dependency from CommonNodeRequestActions

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Only feature/messaging actually imports from core.service (SendMessageWorker).
The other 5 feature modules (connections, firmware, map, node, settings)
had vestigial dependency declarations from pre-migration.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Remove unused feature/node → feature/map dependency (nothing imported)
- Add @immutable to RadioConfigState, DiscoveredDevices, DeviceListEntry
  to prevent unnecessary Compose recompositions from unstable inference

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- feature/map: remove unused core.data, core.database, core.datastore;
  add missing core.common, core.repository (actual dependencies)
- feature/node: remove unused core.datastore
- feature/connections: remove unused core.database
- Add @immutable to NodesUiState, NodeFilterState

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The combine(serviceRepository.connectionState, radioConfigState) pattern
subscribed to the ViewModel's own output StateFlow, causing redundant
re-evaluations on every state change. Replace with direct observation
of the source connectionState flow.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Move ResponseState<T> to core/model
- Move PacketResponseStateDialog to core/ui/component
- Create RadioConfigStateProvider interface in core/model
- RadioConfigViewModel implements RadioConfigStateProvider
- ConnectionsScreen accepts interface instead of concrete VM
- ConnectionsNavigation receives provider via lambda from app/desktop
- Remove projects.feature.settings dependency from connections build

This eliminates a feature→feature dependency, improving build
parallelism and enforcing proper module boundaries.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…dioConfigViewModel

Extract self-contained logic into plain coordinator classes:
- MqttProbeCoordinator: MQTT broker probe state + cancellation
- ProfileCoordinator: import/export/install profile file I/O

RadioConfigViewModel delegates to coordinators while retaining
state ownership and config-loading orchestration (its core job).
Reduces VM body by ~60 lines of implementation detail.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Change RadioConfigState.route from String to Enum<*>?
- ChannelScreen resolves navigation target via typed cast instead of
  string lookup through getNavRouteFrom()
- Delete dead SettingsNavUtils.kt (getNavRouteFrom now unused)
- Eliminates fragile string→enum round-trip that could silently fail

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add AdminException.RateLimited to domain exception hierarchy
- Handle AdminResult.RateLimited in SdkRadioController.unwrap()
- Update MeshTopologyService for SDK's new suspend API:
  topology.nodes → topology.nodes(), topology.edgeCount → edgeCount()

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- frequentEmojis: computed List → cached StateFlow (avoids parse+sort on every recomposition)
- SdkNodeRepositoryImpl: SharingStarted.Eagerly → WhileSubscribed(5_000) for ourNodeInfo, myId, localStats
- PacketRepositoryImpl: deduplicate flatMapLatest chains via shared combine(myNodeNumFlow, currentDb)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
When ignoring a node, wipe position, deviceMetrics, and publicKey locally
to match what firmware does on the device side. Un-ignoring just clears
the isIgnored flag (device will re-populate on next heard packet).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Parse UTF-8 payload from NODE_STATUS_APP (PortNum=36) and update
the node's nodeStatus field. Already displayed in NodeItem and
NodeDetailsSection.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
12 integration tests covering:
- T4a: SDK packets/events propagate through bridges to repositories
- T4b: Connection lifecycle state transitions (connect/disconnect/reconnect)
- T4c: Error resilience (malformed protos, unknown ports, rapid fire, empty payloads)

Tests verify the full chain from FakeRadioTransport through RadioClient,
SdkStateBridge orchestration, and into service/node/topology repositories.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add KDoc to 3 JSON datasource interfaces and 7 repository implementations
- Remove IndoorAirQuality stub comments
- Remove commented-out WRITE_EXTERNAL_STORAGE permission from manifest
- Update foreground service comment to architecture note (Android 14+)
- Remove redundant inline comments in PacketDao.kt

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@jamesarich jamesarich force-pushed the feat/meshtastic-sdk branch from cb669c1 to 8133161 Compare May 6, 2026 22:52
After rebase, MqttManagerImpl gained a nodeRepository parameter.
Updated test to mock and pass the new dependency.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@jamesarich jamesarich added this to the 2.8.0 milestone May 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request refactor no functional changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant