All Projects → JuulLabs → Kable

JuulLabs / Kable

Licence: apache-2.0
Kotlin Asynchronous Bluetooth Low-Energy

Programming Languages

javascript
184084 projects - #8 most used programming language
kotlin
9241 projects

Projects that are alternatives of or similar to Kable

Continuity
Apple Continuity Protocol Reverse Engineering and Dissector
Stars: ✭ 180 (-7.22%)
Mutual labels:  apple, bluetooth-low-energy
Blegattcoroutines
Functional Bluetooth GATT for Android (all the meanings)
Stars: ✭ 343 (+76.8%)
Mutual labels:  kotlin-coroutines, bluetooth-low-energy
Rapidpayload
Framework RapidPayload - Metasploit Payload Generator | Crypter FUD AntiVirus Evasion
Stars: ✭ 174 (-10.31%)
Mutual labels:  apple
Macos Egpu Cuda Guide
Set up CUDA for machine learning (and gaming) on macOS using a NVIDIA eGPU
Stars: ✭ 187 (-3.61%)
Mutual labels:  apple
Quotefixformac
QuoteFix for Apple Mail —
Stars: ✭ 181 (-6.7%)
Mutual labels:  apple
Apple Runtime Headers
Objective-C runtime headers for Apple's iOS, macOS, tvOS and watchOS frameworks
Stars: ✭ 174 (-10.31%)
Mutual labels:  apple
Macos Cookbook
A Chef cookbook used to provision macOS
Stars: ✭ 183 (-5.67%)
Mutual labels:  apple
Reactivebeacons
Android library scanning BLE beacons nearby with RxJava
Stars: ✭ 171 (-11.86%)
Mutual labels:  bluetooth-low-energy
Vector
Kotlin Coroutines based MVI architecture library for Android
Stars: ✭ 194 (+0%)
Mutual labels:  kotlin-coroutines
Objc Sdk
LeanCloud Objective-C SDK
Stars: ✭ 186 (-4.12%)
Mutual labels:  apple
Homebridge Mi Hygrothermograph
Homebridge plugin for the Xiaomi Mi Bluetooth Temperature and Humidity Sensor
Stars: ✭ 179 (-7.73%)
Mutual labels:  bluetooth-low-energy
Droid Feed
Aggregated Android news, articles, podcasts and conferences about Android Development
Stars: ✭ 174 (-10.31%)
Mutual labels:  kotlin-coroutines
Watchshaker
Simple motion detector for ⌚️ (watchOS) shake gesture.
Stars: ✭ 184 (-5.15%)
Mutual labels:  apple
Android Clean Arch Coroutines Koin
Implemented by Clean Architecture, MVVM, Koin, Coroutines, Moshi, Mockk, LiveData & DataBinding
Stars: ✭ 173 (-10.82%)
Mutual labels:  kotlin-coroutines
Dotnet Passbook
A .Net Library for generating Apple Passbook (Wallet) files for iOS. Please get involved by creating pull requests and opening issues!
Stars: ✭ 188 (-3.09%)
Mutual labels:  apple
Androidbluetoothlibrary
A Library for easy implementation of Serial Bluetooth Classic and Low Energy on Android. 💙
Stars: ✭ 171 (-11.86%)
Mutual labels:  bluetooth-low-energy
Ultratabsaver
The open source Tab Manager Extension for Safari.
Stars: ✭ 178 (-8.25%)
Mutual labels:  apple
Wwdc Notes
WWDCNotes.com content ✨
Stars: ✭ 183 (-5.67%)
Mutual labels:  apple
Apns Http2
A Java library for sending notifications via APNS using Apple's HTTP/2 API.
Stars: ✭ 194 (+0%)
Mutual labels:  apple
Iboot64helper
IDAPython loader to help with AArch64 iBoot, iBEC, and SecureROM reverse engineering
Stars: ✭ 189 (-2.58%)
Mutual labels:  apple

badge badge badge badge Slack

Kable

Kotlin Asynchronous Bluetooth Low Energy provides a simple Coroutines-powered API for interacting with Bluetooth Low Energy devices.

Usage is demonstrated with the SensorTag sample app.

Scanning

To scan for nearby peripherals, the Scanner provides an advertisements Flow which is a stream of Advertisement objects representing advertisements seen from nearby peripherals. Advertisement objects contain information such as the peripheral's name and RSSI (signal strength).

Scanning begins when the advertisements Flow is collected and stops when the Flow collection is terminated. A Flow terminal operator (such as first) may be used to scan until an advertisement is found that matches a desired predicate.

val advertisement = Scanner()
    .advertisements
    .first { it.name?.startsWith("Example") }

JavaScript: Scanning for nearby peripherals is supported, but only available on Chrome 79+ with "Experimental Web Platform features" enabled via: chrome://flags/#enable-experimental-web-platform-features

Peripheral

Once an Advertisement is obtained, it can be converted to a Peripheral via the CoroutineScope.peripheral extension function. Peripheral objects represent actions that can be performed against a remote peripheral, such as connection handling and I/O operations.

val peripheral = scope.peripheral(advertisement)

JavaScript

On JavaScript, rather than processing a stream of advertisements, a specific peripheral can be requested using the CoroutineScope.requestPeripheral extension function. Criteria (Options) such as expected service UUIDs on the peripheral and/or the peripheral's name may be specified. When requestPeripheral is called with the specified options, the browser shows the user a list of peripherals matching the criteria. The peripheral chosen by the user is then returned (as a Peripheral object).

val options = Options(
    optionalServices = arrayOf(
        "f000aa80-0451-4000-b000-000000000000",
        "f000aa81-0451-4000-b000-000000000000"
    ),
    filters = arrayOf(
        NamePrefix("Example")
    )
)
val peripheral = scope.requestPeripheral(options).await()

Connectivity

Once a Peripheral object is acquired, a connection can be established via the connect function. The connect method suspends until a connection is established and ready (or a failure occurs). A connection is considered ready when connected, services have been discovered, and observations (if any) have been re-wired. Service discovery occurs automatically upon connection.

Multiple concurrent calls to connect will all suspend until connection is ready.

peripheral.connect()

To disconnect, the disconnect function will disconnect an active connection, or cancel an in-flight connection attempt. The disconnect function suspends until the peripheral has settled on a disconnected state.

peripheral.disconnect()

If the underlying subsystem fails to deliver the disconnected state then the disconnect call could potentially stall indefinitely. To prevent this (and ensure underlying resources are cleaned up in a timely manner) it is recommended that disconnect be wrapped with a timeout, for example:

// Allow 5 seconds for graceful disconnect before forcefully closing `Peripheral`.
withTimeoutOrNull(5_000L) {
    peripheral.disconnect()
}

State

The connection state of a Peripheral can be monitored via its state Flow.

peripheral.state.collect { state ->
    // Display and/or process the connection state.
}

The state will typically transition through the following States:

Connection states

Disconnecting state only occurs on Android platform. JavaScript and Apple-based platforms transition directly from Connected to Disconnected (upon calling disconnect function, or when a connection is dropped).

I/O

Bluetooth Low Energy devices are organized into a tree-like structure of services, characteristics and descriptors; whereas characteristics and descriptors have the capability of being read from, or written to.

For example, a peripheral might have the following structure:

  • Service S1 (00001815-0000-1000-8000-00805f9b34fb)
    • Characteristic C1
      • Descriptor D1
      • Descriptor D2
    • Characteristic C2 (00002a56-0000-1000-8000-00805f9b34fb)
      • Descriptor D3 (00002902-0000-1000-8000-00805f9b34fb)
  • Service S2
    • Characteristic C3

To access a characteristic or descriptor, use the charactisticOf or descriptorOf functions, respectively.

In the above example, to access "Descriptor D2":

val descriptor = descriptorOf(
    service = "00001815-0000-1000-8000-00805f9b34fb",
    characteristic = "00002a56-0000-1000-8000-00805f9b34fb",
    descriptor = "00002902-0000-1000-8000-00805f9b34fb"
)

Once connected, data can be read from, or written to, characteristics and/or descriptors via read and write functions.

The read and write functions throw NotReadyException until a connection is established.

val data = peripheral.read(characteristic)

peripheral.write(descriptor, byteArrayOf(1, 2, 3))

Notifications

Bluetooth Low Energy provides the capability of subscribing to characteristic changes by means of notifications, whereas a characteristic change on a connected peripheral is "pushed" to the central via a characteristic notification which carries the new value of the characteristic.

Characteristic change notifications can be observed/subscribed to via the observe function which returns a Flow of the new characteristic data.

val observation = peripheral.observe(characteristic)
observation.collect { data ->
    // Process data.
}

The observe function can be called (and its returned Flow can be collected) prior to a connection being established. Once a connection is established then characteristic changes will stream from the Flow. If the connection drops, the Flow will remain active, and upon reconnecting it will resume streaming characteristic changes.

Failures related to notifications are propagated via connect if the observe Flow is collected prior to a connection being established. If a connection is already established when an observe Flow is beginning to be collected, then notification failures are propagated via the observe Flow.

Structured Concurrency

Peripheral objects/connections are scoped to a Coroutine scope. When creating a Peripheral, the CoroutineScope.peripheral extension function is used, which scopes the returned Peripheral to the CoroutineScope receiver. If the CoroutineScope receiver is cancelled then the Peripheral will disconnect and be disposed.

Scanner()
    .advertisements
    .filter { advertisement -> advertisement.name?.startsWith("Example") }
    .map { advertisement -> scope.peripheral(advertisement) }
    .onEach { peripheral -> peripheral.connect() }
    .launchIn(scope)

delay(60_000L)
scope.cancel() // All `peripherals` will implicitly disconnect and be disposed.

Peripheral.disconnect is the preferred method of disconnecting peripherals, but disposal via Coroutine scope cancellation is provided to prevent connection leaks.

Setup

Gradle

Maven Central

Kable can be configured via Gradle Kotlin DSL as follows:

Multiplatform

plugins {
    id("com.android.application") // or id("com.android.library")
    kotlin("multiplatform")
}

repositories {
    mavenCentral()
}

kotlin {
    android()
    js().browser() // and/or js().node()
    macosX64()
    iosX64()
    iosArm64()

    sourceSets {
        val commonMain by getting {
            dependencies {
                implementation("com.juul.kable:core:$version")
            }
        }
    }
}

android {
    // ...
}

Note that Apple-based targets (e.g. macosX64) require Coroutines with multithread support for Kotlin/Native (more specifically: Coroutines library artifacts that are suffixed with -native-mt). Kable is configured to use -native-mt as a transitive dependency for Apple-based targets.

Platform-specific

repositories {
    mavenCentral()
}

dependencies {
    implementation("com.juul.kable:core:$version")
}

License

Copyright 2020 JUUL Labs, Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].