blue-falcon

Project Url: Reedyuk/blue-falcon
Introduction: A Bluetooth kotlin multiplatform "Cross-Platform" library for iOS and Android
More: Author   ReportBugs   OfficialWebsite   
Tags:

CI Maven Central Kotlin License

Android iOS macOS Raspberry Pi JavaScript Windows

A 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.

✨ Features

  • 🔌 Plugin Architecture - Extensible engine system with official and community plugins
  • 📱 Cross-Platform - Single API for iOS, Android, macOS, JavaScript, Windows, and Raspberry Pi
  • 🔄 Legacy Support - 2.x API available via compatibility layer (see Migration Guide)
  • ⚡ Native Performance - Compiles to platform-native code (Obj-C, JVM, JS, etc.)
  • 🔧 Flexible APIs - Choose between Flow-based reactive API or delegate callbacks
  • 🎯 Type-Safe - Full Kotlin type safety across all platforms
  • Peripheral / GATT Server - Advertising, local services, multi-central sessions, and targeted notifications on Android, iOS, and macOS (3.7.0+)

📦 Installation

Upgrading from 2.x? See the Migration Guide — most apps require zero code changes.

Core + Engine

commonMain.dependencies {
    implementation("dev.bluefalcon:blue-falcon-core:3.7.12")
}

// Add platform-specific engines
androidMain.dependencies {
    implementation("dev.bluefalcon:blue-falcon-engine-android:3.7.12")
}

iosMain.dependencies {
    implementation("dev.bluefalcon:blue-falcon-engine-ios:3.7.12")
}

Optional Plugins

commonMain.dependencies {
    // Logging support
    implementation("dev.bluefalcon:blue-falcon-plugin-logging:3.7.12")
    
    // Automatic retry with exponential backoff
    implementation("dev.bluefalcon:blue-falcon-plugin-retry:3.7.12")
    
    // Service/characteristic caching
    implementation("dev.bluefalcon:blue-falcon-plugin-caching:3.7.12")
    
    // Connection success/failure counts, operation latency, and throughput metrics
    implementation("dev.bluefalcon:blue-falcon-plugin-metrics:3.7.12")

    // Bounded, observable central GATT command queue
    implementation("dev.bluefalcon:blue-falcon-plugin-command-queue:3.7.12")
}

🚀 Quick Start

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()

Peripheral / GATT Server (3.7.0+)

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.12")
    // Optional bounded notification queue with fair per-session scheduling
    implementation("dev.bluefalcon:blue-falcon-plugin-queue:3.7.12")
}

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 QueuePlugin 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:

import 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,
                    ),
                )),
            )),
        ),
    ))
}

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.

📚 Documentation

Architecture

  • ADR 0001 - Windows Platform Support
  • ADR 0002 - Plugin-Based Engine Architecture

🎯 Platform Support

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)

Platform Requirements

Android

  • Minimum SDK: 24 (Android 7.0)
  • Target SDK: 33 (Android 13)

iOS

  • Minimum: iOS 13.0+
  • Xcode 14.0+

macOS

  • Minimum: macOS 10.15+

JavaScript / Wasm (browser)

  • Modern browsers with Web Bluetooth support
  • HTTPS required (security policy)
  • The blue-falcon-engine-js artifact ships both js and wasmJs browser variants; Gradle resolves the right one for your target automatically
  • scan() opens the browser's device chooser; dismissing it without picking a device is a no-op (no peripheral added), not an error

Windows

  • Windows 10 version 1803 (April 2018 Update) or later
  • JDK 11+
Building bluefalcon-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.md
  • library/src/windowsMain/cpp/README.md

Raspberry Pi

  • Linux with BlueZ 5.0+
  • JDK 11+

🏗️ Architecture

Blue 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)                         │
└─────────────────────────────────────────┘

Official Plugins

  • LoggingPlugin - Configurable logging with custom loggers
  • RetryPlugin - Automatic retry with exponential backoff
  • CachingPlugin - Service/characteristic discovery caching
  • MetricsPlugin - Connection success/failure counts, operation latency histograms, and read/write throughput (ADR 0012)

See the Plugin Development Guide to create your own!

🤝 Contributing

We welcome contributions! Blue Falcon follows a structured decision-making process:

Proposing Major Changes

For significant architectural changes or new features:

  1. Create an Architecture Decision Record (ADR)

    # Use the ADR template
    cp docs/adr/ADR-TEMPLATE.md docs/adr/XXXX-your-proposal.md
    
  2. Let AI help you write it

    • Use GitHub Copilot or your preferred AI assistant
    • Reference existing ADRs for context
    • See Contributing Guide for details
  3. Submit a Pull Request

    • Link to your ADR
    • Discuss with maintainers
    • Implement once approved

Quick Contributions

For bug fixes, docs, or small improvements:

  1. Fork the repository
  2. Create a feature branch
  3. Make your changes
  4. Submit a Pull Request

See CONTRIBUTING.md for detailed guidelines.

📖 Examples

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 */ }
    }
}

Connection Management

// 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 after connect(). BLE connections are asynchronous. Polling connectionState() right after initiating a connection will return Disconnected because the platform callback has not fired yet. Use connectionStateUpdates instead 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")

Service & Characteristic Discovery

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()
)

Reading & Writing Characteristics

// Read a characteristic value - suspends until the platform actually delivers a result (ADR 0014)
when (val result = blueFalcon.readCharacteristic(bluetoothPeripheral, bluetoothCharacteristic)) {
    is CharacteristicReadResult.Success -> println("Read ${result.value?.size} bytes")
    is CharacteristicReadResult.Failed -> println("Read failed: ${result.cause?.message}")
    CharacteristicReadResult.Disconnected -> println("Peripheral disconnected mid-read")
    CharacteristicReadResult.Unsupported -> println("Read not supported on this platform")
}

// Compatibility overload: no typed delivery outcome
blueFalcon.writeCharacteristic(
    bluetoothPeripheral,
    bluetoothCharacteristic,
    "Hello BLE",
)

// Typed central write
val payload = "Hello BLE".encodeToByteArray()
val 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("queued=${snapshot.queuedCount}, sending=${snapshot.inFlightCount}")
    }
}

val result = commandQueue.send(
    peripheral = bluetoothPeripheral,
    characteristic = bluetoothCharacteristic,
    value = payload,
    writeType = CharacteristicWriteType.WithoutResponse,
)

val reading = commandQueue.read(bluetoothPeripheral, bluetoothCharacteristic)
val services = commandQueue.discoverServices(bluetoothPeripheral)
val characteristics = commandQueue.discoverCharacteristics(
    bluetoothPeripheral,
    bluetoothService,
)
val mtuRequest = commandQueue.changeMtu(bluetoothPeripheral, 247)
val subscription = commandQueue.setNotificationSubscription(
    bluetoothPeripheral,
    bluetoothCharacteristic,
    enabled = true,
)

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.

📄 License

Blue Falcon is released under the MIT License.

🙏 Acknowledgments

📞 Support


Made with ❤️ by the Blue Falcon community

Apps
About Me
GitHub: Trinea
Facebook: Dev Tools
AI Daily Digest