Commit Graph

6572 Commits

Author SHA1 Message Date
James Rich
2d299f7e21 test: SdkRadioController and PacketRepository coverage
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-06 17:52:06 -05:00
James Rich
37be51bed9 test: MeshTopologyService and MessageDeliveryTracker tests
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-06 17:52:06 -05:00
James Rich
6490fc30e8 test: comprehensive bridge unit tests
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-06 17:52:06 -05:00
James Rich
6450c69820 Add distance-based node filter
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-06 17:52:06 -05:00
James Rich
839d43ddf1 Refactor SdkStateBridge bridges
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-06 17:52:06 -05:00
James Rich
4340fc5045 feat: handle NODE_STATUS_APP packets to populate node status string
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>
2026-05-06 17:52:06 -05:00
James Rich
604ce68415 feat: set_ignored_node side effects mirror firmware behavior
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>
2026-05-06 17:52:06 -05:00
James Rich
fa6d625d90 perf: Phase 4 Android performance optimizations
- 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>
2026-05-06 17:52:06 -05:00
James Rich
52aaa4d926 feat: handle AdminResult.RateLimited from SDK, update MeshTopology API
- 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>
2026-05-06 17:52:06 -05:00
James Rich
046efd50dd refactor: replace string-based route with typed Enum in RadioConfigState
- 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>
2026-05-06 17:52:06 -05:00
James Rich
44b2a8c98a refactor: extract MqttProbeCoordinator and ProfileCoordinator from RadioConfigViewModel
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>
2026-05-06 17:52:06 -05:00
James Rich
07214bd307 refactor: decouple feature/connections from feature/settings
- 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>
2026-05-06 17:52:06 -05:00
James Rich
b3542c76aa fix: remove circular StateFlow observation in RadioConfigViewModel
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>
2026-05-06 17:52:06 -05:00
James Rich
5dd5ebc074 chore: remove more dead dependencies + stability annotations
- 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>
2026-05-06 17:52:05 -05:00
James Rich
6487d1316d refactor: remove dead deps + add Compose stability annotations
- 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>
2026-05-06 17:52:05 -05:00
James Rich
0b6d8eca08 chore: remove unused core.service dependencies from 5 feature modules
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>
2026-05-06 17:52:05 -05:00
James Rich
0b5791a61d refactor: remove getPacketId() from public interface
- 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>
2026-05-06 17:52:05 -05:00
James Rich
f4c6cee332 feat: typed telemetry dispatch + MeshTopology service
- 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>
2026-05-06 17:52:05 -05:00
James Rich
3fb45e05bd fix: architecture review P0/P1 fixes
- 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>
2026-05-06 17:52:05 -05:00
James Rich
140e062eee refactor: eliminate ProcessRadioResponseUseCase and packet-ID correlation
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>
2026-05-06 17:52:05 -05:00
James Rich
6e5b159014 refactor: eliminate MeshPacket/Data imports from radio layer
- 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>
2026-05-06 17:52:05 -05:00
James Rich
35f03735ab refactor: use SDK remote admin API, eliminate sendRemoteAdmin
- 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>
2026-05-06 17:52:05 -05:00
James Rich
b874873f10 refactor: delete 6 dead packet handlers post-SDK cutover (-1420 lines)
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>
2026-05-06 17:52:05 -05:00
James Rich
31f792c71e fix: remove SFPP vestige, resource-back badge strings, add bridge tests
- 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>
2026-05-06 17:52:05 -05:00
James Rich
c712d7ef68 chore: remove dead SfppHasher + unused NodeRepository injection
- 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>
2026-05-06 17:52:05 -05:00
James Rich
64464196b0 feat: SFPP delegation to SDK, NeighborInfo SDK model, congestion + S&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>
2026-05-06 17:52:05 -05:00
James Rich
7683db0c57 feat: deep SDK integration — retry delivery, Store-and-Forward API, congestion 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>
2026-05-06 17:52:05 -05:00
James Rich
27b2c19e69 refactor: narrow ViewModel injections, add ConnectionAware, delete dead 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>
2026-05-06 17:52:05 -05:00
James Rich
e9cb439849 feat: rearchitect around SDK — decompose RadioController, simplify DataPacket, 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>
2026-05-06 17:52:05 -05:00
James Rich
43ecd2eb73 chore: rename stale files and update migration doc
- 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>
2026-05-06 17:52:05 -05:00
James Rich
2db6db5ed9 fix: add error handling to SDK bridge and radio controller
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>
2026-05-06 17:52:05 -05:00
James Rich
296f27dc73 refactor: merge NodeManager into NodeRepository
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>
2026-05-06 17:52:05 -05:00
James Rich
e7d1767527 chore: remove ~220 LOC dead code from node layer
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>
2026-05-06 17:52:05 -05:00
James Rich
a12aa2691e docs: update MIGRATION-REMAINING to ~100% complete
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>
2026-05-06 17:52:05 -05:00
James Rich
551d4419a4 refactor: merge NodeManager into SdkNodeRepositoryImpl + restore MeshActivity
- 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>
2026-05-06 17:52:05 -05:00
James Rich
352cce6603 docs: update MIGRATION-REMAINING.md — Room cleanup complete (97%)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-06 17:52:05 -05:00
James Rich
19eba729f7 chore(database): Room migration 39→40 — drop legacy node tables
- 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>
2026-05-06 17:52:05 -05:00
James Rich
1dd3637b50 refactor: eliminate NodeInfoReadDataSource, use NodeRepository directly
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>
2026-05-06 17:52:05 -05:00
James Rich
a0ee9e8399 docs: update MIGRATION-REMAINING.md with latest cleanup status
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-06 17:52:05 -05:00
James Rich
10425e8c4e refactor: delete dead broadcast Constants.kt and unused actionReceived
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>
2026-05-06 17:52:05 -05:00
James Rich
f0aa99eaff refactor: delete POC ViewModels and stale references
- 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>
2026-05-06 17:52:05 -05:00
James Rich
3cdad0da28 refactor: delete transport layer and dead intermediaries, slim RadioInterfaceService
- 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>
2026-05-06 17:52:05 -05:00
James Rich
74ba959b24 feat: DRY SDK integration — shared bridge, Desktop cutover, dead infra 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>
2026-05-06 17:52:05 -05:00
James Rich
2c438b9a3f docs: add MIGRATION-REMAINING.md tracking clean-break progress
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>
2026-05-06 17:52:05 -05:00
James Rich
2162e0a340 feat: full SDK integration — drop AIDL, eliminate pipeline, SDK-backed 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>
2026-05-06 17:52:05 -05:00
James Rich
9319b0c3a3 refactor: tighten SDK integration — eliminate old pipeline indirection
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>
2026-05-06 17:52:05 -05:00
James Rich
f5e2aabc00 feat: add SdkRadioControllerImpl and SdkStateBridge for SDK hard cutover
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>
2026-05-06 17:52:05 -05:00
James Rich
384746cc75 feat: enrich Android POC with SDK integration improvements
- 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>
2026-05-06 17:52:05 -05:00
James Rich
dd67120b5c refactor(radio): use SDK BleTransport(address) factory (Gap F resolved)
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>
2026-05-06 17:52:05 -05:00
James Rich
8b459881e0 feat: integrate meshtastic-sdk POC vertical slice
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>
2026-05-06 17:52:05 -05:00