Files
Meshtastic-Android/core/ble
James RichandClaude Sonnet 5 e95cc396c5 feat(network): enable wasmJs with WebSocket-only MQTT and real HTTP client
core:network is the largest module tackled so far in this effort:
BLE radio transport, TCP radio transport, MQTT (TCP + WebSocket
transports), USB/Serial, mDNS/NSD discovery, and HTTP data sources
for api.meshtastic.org all live here.

BLE and Mock/Replay radio transports already work unchanged --
BaseRadioTransportFactory handles BLE directly via core:ble's Web
Bluetooth actuals, and Mock/Replay are already commonMain-portable.
A new WasmJsRadioTransportFactory implements the platform seam,
failing loudly for TCP and Serial/USB addresses (no browser
equivalent for raw sockets; WebSerial/WebUSB out of scope this pass).

Raw TCP sockets are a permanent browser sandbox limitation, not a
library gap (confirmed via Ktor's own docs/KTOR-6690, recorded
earlier this session). TcpTransport/TcpRadioTransport move to
nonWebMain. MQTT's transport composition becomes a real per-platform
expect/actual (MqttTransportSelection.kt): nonWebMain composes
TCP+WebSocket as before, wasmJs registers WebSocket only --
mqtt-client-transport-ws publishes a wasmJs Gradle variant,
mqtt-client-transport-tcp does not (confirmed via Maven Central's
module metadata directly, not assumed).

New wasmJs actuals: WebNetworkMonitor (real navigator.onLine plus
window online/offline events, via kotlinx-browser -- no custom JS
interop needed), MqttTlsTrust.wasmJs.kt (null -- a browser page
cannot influence TLS trust at all, the platform decision is the only
one available), ConnectionFailures.wasmJs.kt (false -- Ktor's Js
engine surfaces failures through the same IOException family the
shared predicate already covers). CoreNetworkWasmJsModule wires a
real HttpClient(Js) engine for the HTTP data sources; deliberately no
@ComponentScan since commonMain's CoreNetworkModule scan already
reaches this target's @Single classes. Like core:database's
SingleDatabaseProvider and core:prefs's CorePrefsWasmJsModule, it's
not registered anywhere yet -- no webApp module exists this pass.

core:network's own applyHierarchyTemplate call (needed for the
nonWeb/wasmJs split) can't coexist with the separate
meshtastic.kmp.jvm.android convention plugin, which makes its own
call -- Gradle only allows one per project. Replaced with a nested
jvmAndroid group inside the custom nonWeb group; ConnectionFailures's
existing jvmAndroidMain source set needed no changes.

One small upstream fix: core:ble's classifyBleException() had no
wasmJs counterpart, blocking core:network's commonMain
BleRadioTransport.kt. Added as an honest `= null` (Kable doesn't
exist on wasmJs at all, so nothing is ever a recognized Kable
exception there) -- same disjoint-compilation shape as
BleServiceExtensions.kt.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-30 22:17:51 -05:00
..

:core:ble

Overview

The :core:ble module contains the foundation for Bluetooth Low Energy (BLE) communication in the Meshtastic Android app. It uses the Kable multiplatform BLE library to provide a unified, Coroutine-based architecture across all supported targets (Android, Desktop, and future iOS).

This module abstracts platform-specific BLE operations behind common Kotlin interfaces (BleDevice, BleScanner, BleConnection, BleConnectionFactory), ensuring that business logic in commonMain remains platform-agnostic and testable.

Key Components

1. BleConnection

A robust wrapper around Kable's Peripheral that simplifies the connection lifecycle and service discovery using modern Coroutine APIs.

  • Features:
    • Connection & Await: Provides suspend functions to connect and wait for a terminal state (Connected or Disconnected).
    • Unified Profile Helper: A profile function that manages service discovery, characteristic setup, and lifecycle in a single block, with automatic timeout and error handling.
    • Observability: Exposes connectionState as a Flow for reactive UI and service updates.
    • Platform Setup: Seamlessly handles platform-specific configuration (like MTU negotiation on Android or direct connections on Desktop) via platformConfig() extensions.

2. BluetoothRepository

A Singleton repository responsible for the global state of Bluetooth on the device.

  • Features:
    • State Management: Exposes a StateFlow<BluetoothState> reflecting whether Bluetooth is enabled, permissions are granted, and which devices are bonded.
    • Permission Handling: Centralizes logic for checking Bluetooth and Location permissions across different platforms.
    • Bonding: Simplifies the process of creating and validating bonds with peripherals.

3. BleScanner

A wrapper around Kable's Scanner to provide a consistent and easy-to-use API for BLE scanning with built-in peripheral mapping.

4. BleRetry

A utility for executing BLE operations with retry logic, essential for handling the inherent unreliability of wireless communication.

Integration

The :core:ble module is used by BleRadioTransport in :core:network to implement the RadioTransport contract for Bluetooth devices.

Usage

Dependencies are managed via the version catalog (libs.versions.toml).

[versions]
kable = "0.44.2"

[libraries]
kable-core = { module = "com.juul.kable:kable-core", version.ref = "kable" }

Architecture

The module follows a clean multiplatform architecture approach:

  • Repository Pattern: BluetoothRepository mediates data access.
  • Coroutines & Flow: All asynchronous operations use Kotlin Coroutines and Flows.
  • Dependency Injection: Koin is used for dependency injection.

Testing

The module includes unit tests for key components, utilizing Kable's architecture and standard coroutine testing tools to ensure logic correctness.

Dependency Graph

graph TB
  :core:ble[ble]:::kmp-library
  :core:ble --> :core:common
  :core:ble -.-> :core:di
  :core:ble -.-> :core:model
  :core:ble -.-> :core:testing

classDef android-application fill:#CAFFBF,stroke:#000,stroke-width:2px,color:#000;
classDef android-application-compose fill:#CAFFBF,stroke:#000,stroke-width:2px,color:#000;
classDef compose-desktop-application fill:#CAFFBF,stroke:#000,stroke-width:2px,color:#000;
classDef android-feature fill:#FFD6A5,stroke:#000,stroke-width:2px,color:#000;
classDef android-library fill:#9BF6FF,stroke:#000,stroke-width:2px,color:#000;
classDef android-library-compose fill:#9BF6FF,stroke:#000,stroke-width:2px,color:#000;
classDef android-test fill:#A0C4FF,stroke:#000,stroke-width:2px,color:#000;
classDef jvm-library fill:#BDB2FF,stroke:#000,stroke-width:2px,color:#000;
classDef kmp-feature fill:#FFD6A5,stroke:#000,stroke-width:2px,color:#000;
classDef kmp-library-compose fill:#FFC1CC,stroke:#000,stroke-width:2px,color:#000;
classDef kmp-library fill:#FFC1CC,stroke:#000,stroke-width:2px,color:#000;
classDef unknown fill:#FFADAD,stroke:#000,stroke-width:2px,color:#000;