Skip to content

feat: Desktop USB serial transport#4836

Merged
jamesarich merged 41 commits intomainfrom
feat/serial
Mar 18, 2026
Merged

feat: Desktop USB serial transport#4836
jamesarich merged 41 commits intomainfrom
feat/serial

Conversation

@jamesarich
Copy link
Collaborator

This pull request introduces support for direct Serial/USB communication with Meshtastic devices on the Desktop (JVM) target, using the jSerialComm library. It implements a new SerialTransport class that integrates with the existing multiplatform architecture, updates documentation and build scripts, and adds tests to ensure correctness. The desktop app can now scan for, connect to, and communicate with devices over USB serial, achieving transport parity with Android.

Desktop Serial/USB Transport Feature:

  • Added SerialTransport implementation in core/network using jSerialComm for the JVM target. This class discovers available serial ports, manages connections, and integrates with the existing RadioTransport interface and stream framing logic. (core/network/src/jvmMain/kotlin/org/meshtastic/core/network/SerialTransport.kt)
  • Updated the desktop radio interface service to support USB device connections, including logic to handle serial transport alongside TCP and BLE. (desktop/src/main/kotlin/org/meshtastic/desktop/radio/DesktopRadioInterfaceService.kt) [1] [2] [3] [4] [5]

Build and Dependency Updates:

  • Added jSerialComm as a dependency for the JVM target in the network module, and included mockk for JVM tests. (core/network/build.gradle.kts) [1] [2]

Documentation and Architecture:

  • Updated documentation to reflect the use of Kable (not Nordic) for BLE and to describe the new serial transport feature, including architecture, specification, and implementation plan. (AGENTS.md, GEMINI.md, conductor/tech-stack.md, conductor/archive/desktop_serial_transport_20260317/*) [1] [2] [3] [4] [5] [6] [7]

Testing:

  • Added a new test suite for SerialTransport to verify interface compliance, port discovery, and connection error handling. (core/network/src/jvmTest/kotlin/org/meshtastic/core/network/SerialTransportTest.kt)

This commit updates the project documentation and technical stack to reflect the implementation progress of the Desktop target and refined Kotlin Multiplatform (KMP) development guidelines. Key updates include the roadmap for Desktop serial/USB transport, the transition to Kable for BLE, and expanded testing/interop strategies.

Specific changes include:
- **Project Tracking:** Introduced a new track for "Desktop Serial/USB Transport via jSerialComm," including specification, implementation plan, and metadata.
- **Roadmap Updates:**
    - Marked Desktop BLE (via Kable JVM) and Native Notifications/System Tray as completed.
    - Added MenuBar integration to the completed Tier 2 tasks.
    - Updated transport status table to reflect current KMP progress across Android, Desktop, and iOS.
    - Added detailed long-term plans for platform-native UI interop (Maps, Camera, Web) and logic/UI splitting for iOS.
- **Technical Stack & Guidelines:**
    - Documented the KMP testing strategy, recommending `Mokkery`/`Mockative` for mocking and `Turbine` for Flow assertions.
    - Formally updated `AGENTS.md` and `GEMINI.md` to specify Kable as the primary BLE stack, replacing Nordic-specific libraries.
    - Added design principles regarding zero platform leaks in `commonMain` and preferred DI patterns over `expect`/`actual`.
- **Status Reporting:** Updated `kmp-status.md` to include native notifications and system tray icon support for the Desktop target.

Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
… modules

This commit adds the standard GPLv3 license headers to Kotlin files in the `feature:connections` module and applies minor code formatting to `DesktopRadioInterfaceService.kt`.

Specific changes:
- Added 2026 copyright and license headers to `JvmUsbDeviceData.kt` and `UsbScanner.kt`.
- Applied code formatting to the `supportedDeviceTypes` list in `DesktopRadioInterfaceService.kt`.

Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
@jamesarich jamesarich requested a review from Copilot March 18, 2026 11:37
@github-actions github-actions bot added the enhancement New feature or request label Mar 18, 2026
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds Desktop (JVM) Serial/USB transport support using jSerialComm, integrating it into device discovery/connection flows and documenting the updated architecture.

Changes:

  • Added a JVM SerialTransport implementation (jSerialComm-backed) plus a basic JVM test suite.
  • Integrated USB/serial device scanning into the connections feature and enabled serial connect/send paths in the desktop radio service.
  • Updated version catalog/build config and refreshed KMP/transport documentation/roadmap.

Reviewed changes

Copilot reviewed 18 out of 18 changed files in this pull request and generated 12 comments.

Show a summary per file
File Description
gradle/libs.versions.toml Adds jSerialComm to the version catalog for JVM serial transport.
feature/connections/src/jvmMain/kotlin/org/meshtastic/feature/connections/model/JvmUsbDeviceData.kt Introduces JVM USB device model wrapping a serial port name.
feature/connections/src/jvmMain/kotlin/org/meshtastic/feature/connections/domain/usecase/JvmUsbScanner.kt Implements desktop polling scanner for available serial ports.
feature/connections/src/commonMain/kotlin/org/meshtastic/feature/connections/domain/usecase/UsbScanner.kt Adds a common interface to abstract USB/serial scanning across platforms.
feature/connections/src/commonMain/kotlin/org/meshtastic/feature/connections/domain/usecase/CommonGetDiscoveredDevicesUseCase.kt Plumbs USB discovery flow into the discovered-devices use case.
docs/roadmap.md Updates transport matrix and desktop feature gap tracking.
docs/kmp-status.md Notes desktop notifications/system tray support in the status page.
desktop/src/main/kotlin/org/meshtastic/desktop/radio/DesktopRadioInterfaceService.kt Adds USB device type support and serial connect/send/cleanup handling.
core/network/src/jvmTest/kotlin/org/meshtastic/core/network/SerialTransportTest.kt Adds JVM tests to validate jSerialComm availability and basic failure behavior.
core/network/src/jvmMain/kotlin/org/meshtastic/core/network/SerialTransport.kt Implements the jSerialComm-backed serial transport with read loop + framing integration.
core/network/build.gradle.kts Adds jSerialComm for jvmMain and mockk for jvmTest.
conductor/tech-stack.md Documents jSerialComm and expands KMP testing guidance.
conductor/archive/desktop_serial_transport_20260317/spec.md Archives spec for the desktop serial transport feature.
conductor/archive/desktop_serial_transport_20260317/plan.md Archives implementation plan/checkpoints for the feature.
conductor/archive/desktop_serial_transport_20260317/metadata.json Adds archived track metadata for the feature work.
conductor/archive/desktop_serial_transport_20260317/index.md Adds index linking the archived spec/plan/metadata.
GEMINI.md Updates BLE documentation to reflect Kable usage.
AGENTS.md Updates BLE documentation to reflect Kable usage.

You can also share your feedback on Copilot code review. Take the survey.

Comment on lines +43 to +45
val usbFlow = usbScanner?.scanUsbDevices() ?: kotlinx.coroutines.flow.flowOf(emptyList())

return combine(nodeDb, recentAddressesDataSource.recentAddresses) { db, recentList ->
return combine(nodeDb, recentAddressesDataSource.recentAddresses, usbFlow) { db, recentList, usbList ->
listOf(DeviceListEntry.Mock(demoModeLabel))
} else {
emptyList()
usbList
}
}

private fun startSerialConnection(portName: String) {
Comment on lines +197 to +199
if (serial.startConnection()) {
onConnect()
} else {
return try {
val port = SerialPort.getCommPort(portName) ?: return false
port.setComPortParameters(baudRate, DATA_BITS, SerialPort.ONE_STOP_BIT, SerialPort.NO_PARITY)
port.setComPortTimeouts(SerialPort.TIMEOUT_READ_SEMI_BLOCKING, 0, 0)
Comment on lines +90 to +92
close()
}
}
Comment on lines +107 to +113
override fun close() {
readJob?.cancel()
readJob = null
serialPort?.takeIf { it.isOpen }?.closePort()
serialPort = null
super.close()
}
Comment on lines +31 to +34
override fun scanUsbDevices(): Flow<List<DeviceListEntry.Usb>> = flow {
while (coroutineContext.isActive) {
val ports =
SerialTransport.getAvailablePorts().map { portName ->
Comment on lines +43 to +46
emit(ports)
delay(POLL_INTERVAL_MS)
}
}
docs/roadmap.md Outdated
@@ -55,9 +55,10 @@ here| **Migrate to JetBrains Compose Multiplatform dependencies** | High | Low |
| TCP | Desktop (JVM) | ✅ Done — shared `StreamFrameCodec` + `TcpTransport` in `core:network` |
| Serial/USB | Desktop (JVM) | ❌ Next — jSerialComm |
@codecov
Copy link

codecov bot commented Mar 18, 2026

Codecov Report

❌ Patch coverage is 22.97297% with 57 lines in your changes missing coverage. Please review.
✅ Project coverage is 13.44%. Comparing base (06c9900) to head (130f088).
⚠️ Report is 1 commits behind head on main.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
...lin/org/meshtastic/core/network/SerialTransport.kt 20.00% 38 Missing and 2 partials ⚠️
...eature/connections/domain/usecase/JvmUsbScanner.kt 0.00% 14 Missing ⚠️
...omain/usecase/CommonGetDiscoveredDevicesUseCase.kt 77.77% 0 Missing and 2 partials ⚠️
...stic/feature/connections/model/JvmUsbDeviceData.kt 0.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4836      +/-   ##
==========================================
+ Coverage   13.38%   13.44%   +0.06%     
==========================================
  Files         539      542       +3     
  Lines       17753    17822      +69     
  Branches     2651     2667      +16     
==========================================
+ Hits         2376     2396      +20     
- Misses      15058    15104      +46     
- Partials      319      322       +3     
Flag Coverage Δ
host-unit 13.44% <22.97%> (+0.06%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

This commit ensures that coroutine cancellations are properly propagated in the `SerialTransport` read loop and prevents redundant disconnect calls during scope teardown.

Specific changes include:
- Added explicit catch blocks for `kotlinx.coroutines.CancellationException` in both the inner and outer serial read loops to ensure exceptions are rethrown and not swallowed by generic handlers.
- Updated the `finally` block to only invoke `onDeviceDisconnect(true)` if the coroutine scope is still active (`isActive`).
- Maintained existing error logging for non-cancellation exceptions.

Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
…tic complexity

- Suppress CyclomaticComplexMethod to satisfy detekt rules.
- Log SerialTransport IO exceptions at debug level if the coroutine was gracefully cancelled, avoiding noise during planned disconnects.
- Maintain error logging for unhandled/unexpected IO exceptions.
- Mark Serial/USB on Desktop as 'Done' across roadmap and kmp-status
- Update AGENTS.md and GEMINI.md descriptions to list SerialTransport
- Check off the desktop README todo item
@jamesarich jamesarich marked this pull request as ready for review March 18, 2026 12:29
@jamesarich jamesarich added this pull request to the merge queue Mar 18, 2026
Merged via the queue into main with commit 59408ef Mar 18, 2026
7 checks passed
@jamesarich jamesarich deleted the feat/serial branch March 18, 2026 12:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants