blue-falcon
3.4.5indexedLibrary unifies Bluetooth functionality across various platforms, offering a common API for actions like device connection, service fetching, and characteristic handling, eliminating platform-specific code duplication.
Library unifies Bluetooth functionality across various platforms, offering a common API for actions like device connection, service fetching, and characteristic handling, eliminating platform-specific code duplication.
Blue-FalconA Bluetooth Low Energy (BLE) Kotlin Multiplatform library for iOS, Android, MacOS, Raspberry Pi, Windows, and JavaScript.
Blue Falcon provides a unified API for Bluetooth LE operations across all platforms. Each platform implementation compiles to native code, ensuring optimal performance and seamless integration with platform-specific APIs.
🎉 Version 3.0+ introduces a plugin-based engine architecture inspired by Ktor, enabling extensibility. The 2.x API is still available via a compatibility layer — see the Migration Guide.
Upgrading from 2.x? See the Migration Guide — most apps require zero code changes.
commonMain.dependencies {
implementation("dev.bluefalcon:blue-falcon-core:3.7.13")
}
// Add platform-specific engines
androidMain.dependencies {
implementation("dev.bluefalcon:blue-falcon-engine-android:3.7.13")
}
iosMain.dependencies {
implementation("dev.bluefalcon:blue-falcon-engine-ios:3.7.13")
}
commonMain.dependencies {
// Logging support
implementation("dev.bluefalcon:blue-falcon-plugin-logging:3.7.13")
// Automatic retry with exponential backoff
implementation("dev.bluefalcon:blue-falcon-plugin-retry:3.7.13")
// Service/characteristic caching
implementation("dev.bluefalcon:blue-falcon-plugin-caching:3.7.13")
// Connection success/failure counts, operation latency, and throughput metrics
implementation("dev.bluefalcon:blue-falcon-plugin-metrics:3.7.13")
// Bounded, observable central GATT command queue
implementation("dev.bluefalcon:blue-falcon-plugin-command-queue:3.7.13")
}
import dev.bluefalcon.core.*
import dev.bluefalcon.plugins.logging.*
// Configure with DSL
val blueFalcon = BlueFalcon {
engine = AndroidEngine(context) // or iOSEngine(), macOSEngine(), etc.
install(LoggingPlugin) {
level = LogLevel.DEBUG
}
install(RetryPlugin) {
maxAttempts = 3
initialDelay = 500
}
}
// Reactive Flow API
launch {
blueFalcon.peripherals.collect { devices ->
devices.forEach { device ->
println("Device: ${device.name}")
}
}
}
// Start scanning
blueFalcon.scan()
The blue-falcon-peripheral module provides the BLE Peripheral role alongside the
Central engines. Production GATT-server backends are available on Android, iOS,
and macOS; the other Central platforms do not currently provide this server API.
commonMain.dependencies {
implementation("dev.bluefalcon:blue-falcon-peripheral:3.7.13")
// Optional bounded notification queue with fair per-session scheduling
implementation("dev.bluefalcon:blue-falcon-plugin-queue:3.7.13")
}
The module supports a configurable local service/characteristic/descriptor tree,
advertising, independent PeripheralSession instances for connected centrals,
subscription tracking, per-session maximumUpdateValueLength, and targeted
session.notify() calls. Requests include reads, writes, descriptor operations,
and prepared-write batches (GattCharacteristicWriteBatchRequest). Applications
must validate requests and send the appropriate ATT responses. Extend the manager
through PeripheralPluginRegistry, or install for bounded FIFO
notification queues with aggregate byte limits and fair round-robin scheduling.
Create the manager in platform code:
// Android: dev.bluefalcon.peripheral.android.createBlueFalconPeripheral
val peripheral = createBlueFalconPeripheral(applicationContext)
// iOS/macOS: dev.bluefalcon.peripheral.apple.createBlueFalconPeripheral
val peripheral = createBlueFalconPeripheral()
Configure and start it from an application-owned coroutine scope:
Use PeripheralConfig.restorationIdentifier for Apple state restoration and
declare bluetooth-peripheral in UIBackgroundModes when enabling it on iOS.
Advertisement fields are platform-dependent: iOS does not advertise manufacturer
data. Inspect peripheral.capabilities and typed notification results before
relying on platform-specific behavior. stop() is restartable; close() is
terminal and should run before cancelling the owning scope.
See the Peripheral Echo Server example
for complete request routing, targeted notifications through QueuePlugin,
platform permissions, lifecycle ownership, and restoration setup.
blue-falcon-engine-js artifact ships both js and wasmJs browser variants; Gradle resolves the right one for your target automaticallyscan() opens the browser's device chooser; dismissing it without picking a device is a no-op (no peripheral added), not an errorbluefalcon-windows.dll (from source)If you are building Blue Falcon's Windows implementation from source, build the native DLL with CMake:
cd library\src\windowsMain\cpp
mkdir build
cd build
cmake .. -G "Visual Studio 16 2019" -A x64
cmake --build . --config Release
The resulting bluefalcon-windows.dll is generated in the Release directory. Copy it to your Java library path or to library/src/windowsMain/resources/.
For full Windows setup and troubleshooting, see:
library/src/windowsMain/WINDOWS.mdlibrary/src/windowsMain/cpp/README.mdBlue Falcon 3.0 uses a three-layer architecture:
┌─────────────────────────────────────────┐
│ Your Application Code │
└─────────────────────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ Core (Interfaces + Plugin System) │
│ • BlueFalcon API │
│ • Plugin Registry │
│ • Type Definitions │
└─────────────────────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ Platform Engines (6 implementations) │
│ • AndroidEngine │
│ • iOSEngine, macOSEngine │
│ • JSEngine │
│ • WindowsEngine │
│ • RPiEngine │
└─────────────────────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ Native Platform APIs │
│ • Android Bluetooth │
│ • CoreBluetooth (iOS/macOS) │
│ • Web Bluetooth │
│ • Windows WinRT │
│ • BlueZ (Linux) │
└─────────────────────────────────────────┘
See the Plugin Development Guide to create your own!
We welcome contributions! Blue Falcon follows a structured decision-making process:
For significant architectural changes or new features:
For bug fixes, docs, or small improvements:
See CONTRIBUTING.md for detailed guidelines.
blueFalcon.clearPeripherals()
// Check scanning state val scanning: Boolean = blueFalcon.isScanning
#### Observing Discovered Devices
```kotlin
// Observe discovered peripherals via StateFlow
blueFalcon.peripherals.collect { peripherals: Set<BluetoothPeripheral> ->
// update your UI with the discovered devices
}
// Observe Bluetooth manager state (Ready / NotReady)
blueFalcon.managerState.collect { state: BluetoothManagerState ->
when (state) {
BluetoothManagerState.Ready -> { /* Bluetooth is available */ }
BluetoothManagerState.NotReady -> { /* Bluetooth is unavailable */ }
}
}
// Connect to a peripheral (autoConnect = false for direct connection)
blueFalcon.connect(bluetoothPeripheral, autoConnect = false)
// Disconnect from a peripheral
blueFalcon.disconnect(bluetoothPeripheral)
⚠️ Do not call
connectionState()immediately afterconnect(). BLE connections are asynchronous. PollingconnectionState()right after initiating a connection will returnDisconnectedbecause the platform callback has not fired yet. UseconnectionStateUpdatesinstead to react to the actual state change:
// ✅ Reactive — subscribe BEFORE calling connect()
launch {
blueFalcon.connectionStateUpdates.collect { update ->
when (update.state) {
BluetoothPeripheralState.Connected -> println("${update.peripheral.name} connected")
BluetoothPeripheralState.Disconnected -> println("${update.peripheral.name} disconnected")
else -> Unit
}
}
}
blueFalcon.connect(bluetoothPeripheral)
// ❌ Avoid — connectionState() is a snapshot and will return Disconnected if called too early
val state: BluetoothPeripheralState = blueFalcon.connectionState(bluetoothPeripheral)
// Request connection priority (Android-specific, no-op on other platforms)
blueFalcon.requestConnectionPriority(bluetoothPeripheral, ConnectionPriority.High)
// Options: ConnectionPriority.Balanced, ConnectionPriority.High, ConnectionPriority.Low
// Retrieve a previously known peripheral by identifier
val peripheral: BluetoothPeripheral? = blueFalcon.retrievePeripheral("device-identifier")
// Android: MAC address format (e.g., "00:11:22:33:44:55")
// iOS/Native: UUID format (e.g., "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX")
When autoDiscoverAllServicesAndCharacteristics is true (default), services and characteristics are discovered automatically after connection. You can also trigger discovery manually:
// Discover services (optionally filter by service UUIDs)
blueFalcon.discoverServices(bluetoothPeripheral, serviceUUIDs = emptyList())
// Discover characteristics for a specific service (optionally filter by UUIDs)
blueFalcon.discoverCharacteristics(
bluetoothPeripheral,
bluetoothService,
characteristicUUIDs = emptyList()
)
// Read a characteristic value - suspends until the platform actually delivers a result (ADR 0014)
when (val result = blueFalcon.readCharacteristic(bluetoothPeripheral, bluetoothCharacteristic)) {
CharacteristicReadResult.Success -> println()
CharacteristicReadResult.Failed -> println()
CharacteristicReadResult.Disconnected -> println()
CharacteristicReadResult.Unsupported -> println()
}
blueFalcon.writeCharacteristic(
bluetoothPeripheral,
bluetoothCharacteristic,
,
)
payload = .encodeToByteArray()
result = blueFalcon.writeCharacteristic(
bluetoothPeripheral,
bluetoothCharacteristic,
payload,
CharacteristicWriteType.WithoutResponse,
)
The typed overload is implemented by Android, iOS, and native macOS. A Backpressured result means
the payload was not retained; wait until the matching entry in
characteristicWriteCapabilities is ready and submit it again. characteristicWriteReady is only
an edge-triggered optimization and may be missed by a late collector. Other engines currently return
Unsupported.
For applications that want bounded buffering and automatic readiness handling, install the optional command queue plugin:
val commandQueue = CommandQueuePlugin.create {
maxPendingItemsPerPeripheral = 64
maxPendingBytes = 64 * 1024
}
val blueFalcon = BlueFalcon {
engine = platformEngine
install(commandQueue)
}
launch {
commandQueue.state.collect { snapshot ->
println()
}
}
result = commandQueue.send(
peripheral = bluetoothPeripheral,
characteristic = bluetoothCharacteristic,
value = payload,
writeType = CharacteristicWriteType.WithoutResponse,
)
reading = commandQueue.read(bluetoothPeripheral, bluetoothCharacteristic)
services = commandQueue.discoverServices(bluetoothPeripheral)
characteristics = commandQueue.discoverCharacteristics(
bluetoothPeripheral,
bluetoothService,
)
mtuRequest = commandQueue.changeMtu(bluetoothPeripheral, )
subscription = commandQueue.setNotificationSubscription(
bluetoothPeripheral,
bluetoothCharacteristic,
enabled = ,
)
The plugin places writes, reads, discovery, MTU requests, and subscription changes into one FIFO per
peripheral while allowing different peripherals to progress concurrently. It waits for confirmed
read, discovery, and subscription outcomes and for durable write readiness after backpressure. MTU
APIs do not expose a portable negotiated result, so a successful command reports that the change was
requested. The plugin does not fragment, persist, reconnect, or retry terminal failures. Call
commandQueue.close() when its owning client is no longer used.
For the complete API including descriptors, MTU, L2CAP, and bonding, see the API Reference.
Blue Falcon is released under the MIT License.
Made with ❤️ by the Blue Falcon community
QueuePluginimport dev.bluefalcon.peripheral.*
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.launch
val serviceUuid = "84f7e120-63fd-4f79-8b08-5b9780a36a94"
val characteristicUuid = "84f7e121-63fd-4f79-8b08-5b9780a36a94"
// Install the request collector before advertising. This minimal example accepts
// ordinary writes and explicitly rejects other operations.
applicationScope.launch(start = CoroutineStart.UNDISPATCHED) {
peripheral.requests.collect { request ->
val status = if (request is GattCharacteristicWriteRequest &&
!request.preparedWrite && request.offset == 0
) {
println("Received ${request.value.size} bytes from ${request.session.id}")
GattResponseStatus.Success
} else {
GattResponseStatus.RequestNotSupported
}
request.response?.respond(status)
}
}
applicationScope.launch {
peripheral.sessions.collect { sessions ->
println("Connected centrals: ${sessions.size}")
}
}
applicationScope.launch {
peripheral.start(PeripheralConfig(
advertiseConfig = AdvertiseConfig(
localName = "Blue Falcon Peripheral",
serviceUuids = listOf(serviceUuid),
services = listOf(GattServiceConfig(
uuid = serviceUuid,
characteristics = listOf(GattCharacteristicConfig(
uuid = characteristicUuid,
properties = setOf(
CharacteristicProperty.WRITE,
CharacteristicProperty.NOTIFY,
),
)),
)),
),
))
}
WindowsEngine supports adapters() and selectAdapter(identifier) for multi-radio hosts| Platform | Engine Module | Status | Notes |
|---|
| Android | blue-falcon-engine-android | ✅ Stable | Full BLE support including L2CAP, bonding |
| iOS | blue-falcon-engine-ios | ✅ Stable | CoreBluetooth wrapper |
| macOS | blue-falcon-engine-macos | ✅ Stable | CoreBluetooth wrapper |
| JavaScript | blue-falcon-engine-js | ✅ Stable | Web Bluetooth API (js browser target) |
| Wasm (browser) | blue-falcon-engine-js | ✅ Stable | Web Bluetooth API (wasmJs browser target) |
| Windows | blue-falcon-engine-windows | ✅ Stable | WinRT via JNI (Windows 10 1803+) |
| Raspberry Pi | blue-falcon-engine-rpi | ✅ Stable | Blessed library (BlueZ) |
Create an Architecture Decision Record (ADR)
# Use the ADR template
cp docs/adr/ADR-TEMPLATE.md docs/adr/XXXX-your-proposal.md
Let AI help you write it
Submit a Pull Request
Surfaced from shared tags and platforms — no rankings paid for.