The reconnect policy previously capped at 10 consecutive failures and emitted a permanent disconnect, which terminated the reconnect loop and required the user to manually re-select the device. BleRadioTransport is only ever instantiated for the user-selected address (verified via SharedRadioInterfaceService.startTransportLocked), so the only legitimate permanent-disconnect path is explicit close() owned by the service layer. - BleRadioTransport: pass maxFailures = Int.MAX_VALUE; backoff still caps at 60 s so battery impact remains bounded. - BleExceptionClassifier: flip UnmetRequirementException (BT off / permission missing) to non-permanent — both can resolve without the user re-selecting the device. - Test: replace the old 'gives up after DEFAULT_MAX_FAILURES' test with an inverted contract test that runs past the legacy threshold and asserts the policy never emits isPermanent=true on its own. |
||
|---|---|---|
| .. | ||
| src | ||
| build.gradle.kts | ||
| detekt-baseline.xml | ||
| README.md | ||
:core:ble
Module dependency graph
graph TB
:core:ble[ble]:::kmp-library
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;
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
profilefunction that manages service discovery, characteristic setup, and lifecycle in a single block, with automatic timeout and error handling. - Observability: Exposes
connectionStateas 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.
- State Management: Exposes a
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 in app
The :core:ble module is used by BleRadioInterface in the main application module to implement the RadioTransport interface for Bluetooth devices.
Usage
Dependencies are managed via the version catalog (libs.versions.toml).
[versions]
kable = "0.42.0"
[libraries]
kable-core = { module = "com.juul.kable:kable-core", version.ref = "kable" }
Architecture
The module follows a clean multiplatform architecture approach:
- Repository Pattern:
BluetoothRepositorymediates 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.