Skip to content

About

No description, website, or topics provided.

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

gpt-ble-manager

Maven Central

A Kotlin Multiplatform BLE client for Windows x64 (JVM + C++/WinRT) and Android 8.0+ (API 26+). The shared API is in the gpt.ble.manager package.

This project is created and developed with the help of ChatGPT. ChatGPT assists with implementation, refactoring, tests, and documentation. Project maintainers remain responsible for accepting changes and releasing versions.

The main code is licensed under MIT. The project is actively developed.

This working tree contains 0.3.0 in development (0.3.0-local). See the 0.3.0 API guide for local integration, migration notes, and examples of the new APIs. Maven dependency examples below refer to the 0.2.3 coordinates; they do not provide the new 0.3.0 functionality.

Features

  • BLE scanning with exact/prefix names, address, RSSI, UUID, manufacturer and service-data filters.
  • Merging advertised, system, and GATT device names.
  • Connections and discovery of services, characteristics, and descriptors.
  • Reads and writes, with or without a response.
  • Per-characteristic notification flows, indications, and shared CCCD ownership.
  • Pairing state, pair/unpair subject to OS capabilities.
  • State through StateFlow, notifications through SharedFlow.
  • Serialized GATT requests, independent queue/execution timeouts, and structured diagnostics.
  • Managed sessions with bounded reconnection, fresh discovery and automatic resubscription.
  • Service data, last-seen timestamps, scan events and optional disappearance detection.
  • Connection registry, lookup and disconnect-all.
  • Opt-in chunked transfers with progress, pacing and protocol framing.
  • Capability queries, Android RSSI/PHY/priority and Windows 11 connection preferences.
  • Separate Android background/companion integration and a hardware-free testing module.
Platform Implementation Notes
Windows x64 Kotlin/JVM, JNI, C++20/WinRT DLL bundled in the JAR; Windows negotiates MTU
Android API 26+ Android Bluetooth API The application requests runtime permissions

Peripheral/GATT server mode, iOS, Linux, and macOS are outside the current API's scope.

Building from source

Tool and dependency versions are defined in gradle/libs.versions.toml: Kotlin 2.4.20, Android Gradle Plugin 9.4.1, JDK 21, and compile SDK 37. The Gradle version is set in gradle-wrapper.properties.

A complete Windows and Android build requires:

  1. Windows x64 and JDK 21.
  2. Android SDK platform 37. Set its location through ANDROID_HOME or sdk.dir in your local local.properties.
  3. Visual Studio 2022 with C++ tools, a Windows SDK containing C++/WinRT, and CMake 3.20+.

From the repository root in PowerShell:

.\gradlew.bat allTests assemble

Gradle searches for CMake in PATH, its standard installation, and Visual Studio. To override its location:

.\gradlew.bat :gpt-ble-manager:buildWindowsNative -PcmakeExecutable="C:/tools/cmake/bin/cmake.exe"

Android can be built separately on a system supported by AGP: bash ./gradlew :gpt-ble-manager:assembleAndroidMain :gpt-ble-manager:testAndroidHostTest. The Windows artifact requires its DLL to be built on Windows.

Adding the dependency

Add Maven Central to your application's settings:

dependencyResolutionManagement {
    repositories {
        google()
        mavenCentral()
    }
}

Kotlin Multiplatform:

kotlin {
    sourceSets {
        commonMain.dependencies {
            implementation("io.github.graycatdeveloper:gpt-ble-manager:0.2.3")
        }
    }
}

A regular JVM application on Windows:

dependencies {
    implementation("io.github.graycatdeveloper:gpt-ble-manager-windows:0.2.3")
}

The Windows dependency includes the native DLL. Visual Studio, CMake, and the Windows SDK are only needed when building the library from source.

Creating a manager

In the Windows source set:

import gpt.ble.manager.BleManager
import gpt.ble.manager.windows.WindowsBleManager

val manager: BleManager = WindowsBleManager()

In the Android source set:

import gpt.ble.manager.android.AndroidBleManager

val manager = AndroidBleManager(context.applicationContext)
val permissions = manager.requiredPermissions()
// The Activity requests missing permissions before startScan/connect.

Android 12+ requires BLUETOOTH_SCAN and BLUETOOTH_CONNECT; Android 8–11 requires ACCESS_FINE_LOCATION and location services to be enabled. The library does not request runtime permissions itself. The OS may display its own pairing UI.

The manifest uses neverForLocation. Applications that derive location from BLE must configure their merged manifest accordingly. This flag may also restrict discovery of some beacons. See Android Bluetooth permissions.

Scanning and connecting

Example for commonMain; pass your device's name prefix:

import gpt.ble.manager.AdapterState
import gpt.ble.manager.BleConnection
import gpt.ble.manager.BleManager
import gpt.ble.manager.ScanOptions
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.withTimeout

suspend fun connectToDevice(manager: BleManager, namePrefix: String): BleConnection {
    check(manager.refreshAdapterState() == AdapterState.Ready)
    manager.startScan(ScanOptions(namePrefix = namePrefix))
    val device = try {
        withTimeout(30_000) {
            manager.devices.mapNotNull { devices -> devices.firstOrNull() }.first()
        }
    } finally {
        manager.stopScan()
    }
    return manager.connect(device, timeoutMillis = 20_000)
}

Use an existing manager from a suspend function:

try {
    val connection = connectToDevice(manager, "ExampleSensor")
    try {
        println(connection.readDeviceName())
        connection.discoverServices().forEach { service ->
            println("${service.uuid}: ${service.characteristics.size} characteristics")
        }
    } finally {
        connection.close()
    }
} finally {
    manager.close()
}

Create a new manager after manager.close(). Remote disconnection is reflected in connection.state and connection.disconnectReason.

Device names

A name may arrive in a separate scan response after the first unnamed packet. namePrefix is case-sensitive and is applied after merging the data. Source priority: Advertisement → GATT → System.

device.name remains null until a name is known; only displayName falls back to the address for display. An address returned by the OS as a name is discarded.

With ScanOptions(includeKnownDevices = true), results also include Windows-known devices or bonded Android LE/dual-mode devices. Such a record may be unreachable: check seenInCurrentScan and nullable rssi. Read the GATT name using readDeviceName() after connecting; scanning does not automatically connect to every unnamed device.

GATT and notifications

Obtain characteristics from connection.discoverServices(). UUIDs may repeat: id, serviceId, and connectionId establish identity. Objects from a previous connection cannot be reused.

val services = connection.discoverServices()
val readable = services.flatMap { it.characteristics }.first { it.canRead }
val bytes = connection.read(readable).toByteArray()

Start collecting connection.notifications before calling subscribe(characteristic). Select a characteristic with canNotify or canIndicate; disable the subscription with SubscriptionMode.Disabled. Use subscribe for CCCD 2902. See GattExample.kt for listener/CCCD ordering.

A write accepts at most MTU - 3 bytes. Command fragmentation depends on the device protocol. Windows returns the actually negotiated MTU; on Android, requestMtu asks the OS to change it.

Pairing and errors

val state = manager.getPairingState(device)
val paired = manager.pair(device)
val unpaired = manager.unpair(device)

Close this manager's connection to the device before pair/unpair. Windows supports ConfirmOnly; use system settings for PIN/passkey scenarios. Android unpair requires API 36+ and a CompanionDeviceManager association owned by the application; otherwise it returns BleError.Unsupported. No hidden Android APIs are used.

Paired does not guarantee access to every GATT service. BleException.code holds a portable error category, while cause retains the platform cause. A GATT timeout or cancellation terminates the session to prevent a late response from completing the next request. Cancelling pair/unpair does not guarantee cancellation of an OS operation already in progress.

Windows console example

.\gradlew.bat :sample-windows:run
.\gradlew.bat :sample-windows:run --args="ExampleSensor --known --name-only"
.\gradlew.bat :sample-windows:run --args="AA:BB:CC:DD:EE:01 --direct --inspect"

--known includes system records, --prefix=<name> filters the scan, and --inspect reads the catalog and standard attributes. --notify=<uuid> checks notifications for five seconds. --pairing-state only reads the state; --pair and --unpair change it. The example name and address are placeholders; replace them with your device's values.

Development and support

Include the OS, library version, reproduction steps, BleException.code, and stack trace when reporting an issue. Remove device addresses and other personal data before sharing logs.

License

The main source code and documentation are distributed under the MIT License. Gradle Wrapper retains its Apache-2.0 license; see NOTICE and the Wrapper license text. Dependencies retain their own licenses.

Windows console issues: encoding and native error diagnostics.

About

No description, website, or topics provided.

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages