Skip to content

Commit

Permalink
Merge pull request #70 from Reedyuk/js-support
Browse files Browse the repository at this point in the history
Javascript support for bluetooth
  • Loading branch information
Reedyuk authored Mar 20, 2021
2 parents 65decc1 + 6fb88c0 commit e3917f0
Show file tree
Hide file tree
Showing 16 changed files with 355 additions and 34 deletions.
28 changes: 21 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
# ![Blue Falcon](bluefalcon.png) Blue-Falcon ![CI](https://github.com/Reedyuk/blue-falcon/workflows/CI/badge.svg) [![Kotlin](https://img.shields.io/badge/kotlin-1.4.31-blue.svg)](http://kotlinlang.org) ![badge][badge-android] ![badge][badge-native] ![badge][badge-mac] ![badge][badge-rpi]
# ![Blue Falcon](bluefalcon.png) Blue-Falcon ![CI](https://github.com/Reedyuk/blue-falcon/workflows/CI/badge.svg) [![Kotlin](https://img.shields.io/badge/kotlin-1.4.31-blue.svg)](http://kotlinlang.org) ![badge][badge-android] ![badge][badge-native] ![badge][badge-mac] ![badge][badge-rpi] ![badge][badge-js]

A Bluetooth "Cross Platform" Kotlin Multiplatform library for iOS, Android, MacOS and Raspberry Pi.
A Bluetooth "Cross Platform" Kotlin Multiplatform library for iOS, Android, MacOS, Raspberry Pi and Javascript.

Bluetooth in general has the same functionality for all platforms, e.g. connect to device, fetch services, fetch characteristics.

This library is the glue that brings those together so that mobile developers can use one common api to perform the bluetooth actions.

The idea is to have a common api for using bluetooth as the principle of bluetooth is the same but each platform ios and android has different apis which means you have to duplicate the logic for each platform.

What this library isn't? It is not a cross platform library, this is a multiplatform library. The difference? each platform is compiled down to the native code, so when you use the library in iOS, you are consuming an obj-c library and same principle for Android.
What this library isn't? It is not a cross platform library, this is a multiplatform library. The difference? each platform is compiled down to the native code, so when you use the library in iOS, you are consuming an obj-c library and same principle for Android and so on.

## Basic Usage

Expand All @@ -28,13 +28,13 @@ blueFalcon.scan()
#### Install

```kotlin
implementation 'dev.bluefalcon:blue-falcon-android:0.9.3'
implementation 'dev.bluefalcon:blue-falcon-android:0.9.4'
```

And if you are using the debug variant:

```kotlin
implementation 'dev.bluefalcon:blue-falcon-android-debug:0.9.3'
implementation 'dev.bluefalcon:blue-falcon-android-debug:0.9.4'
```

The Android sdk requires an Application context, we do this by passing in on the BlueFalcon constructor, in this example we are calling the code from an activity(this).
Expand All @@ -57,15 +57,23 @@ try {
The Raspberry Pi library is using Java.

```kotlin
implementation 'dev.bluefalcon:blue-falcon-rpi:0.9.3'
implementation 'dev.bluefalcon:blue-falcon-rpi:0.9.4'
```

### Javascript

#### Install

Simply copy the compiled javascript file (blue-falcon.js) to your web directory.

See the JS-Example for details on how to use.

### Kotlin Multiplatform

### Install

```kotlin
implementation 'dev.bluefalcon:blue-falcon:0.9.3'
implementation 'dev.bluefalcon:blue-falcon:0.9.4'
```

Please look at the Kotlin Multiplatform example in the Examples folder.
Expand Down Expand Up @@ -122,6 +130,10 @@ Open the root directory of the project in Android Studio and run the Android app

This example can only be ran on a Raspberry pi, it will crash otherwise.

### Javascript

Open the index.html file in a web browser.

## Support

For a **bug, feature request, or cool idea**, please [file a Github issue](https://github.com/Reedyuk/blue-falcon/issues/new).
Expand All @@ -130,6 +142,8 @@ For a **bug, feature request, or cool idea**, please [file a Github issue](https

Keep in mind that Blue-Falcon is maintained by volunteers. Please be patient if you don’t immediately get an answer to your question; we all have jobs, families, obligations, and lives beyond this project.

Many thanks to everyone so far who has contributed to the project, it really means alot.


[badge-android]: http://img.shields.io/badge/platform-android-brightgreen.svg?style=flat
[badge-native]: http://img.shields.io/badge/platform-native-lightgrey.svg?style=flat
Expand Down
2 changes: 2 additions & 0 deletions examples/JS-Example/blue-falcon.js

Large diffs are not rendered by default.

76 changes: 76 additions & 0 deletions examples/JS-Example/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
<!DOCTYPE html>
<html>

<head>
<title>Javascript External Script</title>
<!-- Latest compiled and minified JavaScript -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css" integrity="sha384-HSMxcRTRxnN+Bdg0JdbxYKrThecOKuH5zCYotlSAcp1+c8xmyTe9GYg1l9a69psu" crossorigin="anonymous">
<script src = "blue-falcon.js" type = "text/javascript"/></script>
</head>

<body style="padding: 5%;">
<script>
var device = undefined;
var characteristics = new Array();
const blueFalcon = new this['blue-falcon'].dev.bluefalcon.BlueFalcon();
var Delegate = {
didDiscoverDevice: function(bluetoothPeripheral) {
console.log(`didDiscover ${bluetoothPeripheral.name}`);
blueFalcon.connect(bluetoothPeripheral);
},
didConnect: function(bluetoothPeripheral) {
console.log(`didConnect ${bluetoothPeripheral.name}`);
device = bluetoothPeripheral;
document.getElementById('connectedDevice').style.display = "block";
document.getElementById('device').innerHTML = bluetoothPeripheral.name;
blueFalcon.readService(bluetoothPeripheral, document.getElementById('serviceId').value);
},
didDiscoverServices: function(bluetoothPeripheral) {
console.log(`didDiscoverServices ${bluetoothPeripheral.serviceArray}`);
var serviceHtml = ""
bluetoothPeripheral.serviceArray.forEach(service => {
serviceHtml += `<p>${service.name}</p>`;
});
document.getElementById('services').innerHTML = serviceHtml
},
didDiscoverCharacteristics: function(bluetoothPeripheral) {
console.log(`didDiscoverCharacteristics`);
var characteristicHtml = ""
characteristics = new Array();
bluetoothPeripheral.serviceArray.forEach(service => {
service.characteristicArray.forEach(characteristic => {
characteristics.push(characteristic);
characteristicHtml += `<p>${characteristic.name} <button type="button" class="btn btn-default" onclick="blueFalcon.readCharacteristic(device, characteristics[${characteristics.length-1}])" >Read</button>
<button type="button" class="btn btn-default" onclick="blueFalcon.writeCharacteristic(device, characteristics[${characteristics.length-1}], new TextEncoder().encode(document.getElementById('writeValue').value), undefined)" >Write</button>
<input type="text" class="form-control" id="writeValue" placeholder="Value to write" aria-describedby="basic-addon1">
</p>`;
})
});
document.getElementById('characteristics').innerHTML = characteristicHtml
},
didCharacteristcValueChanged: function(bluetoothPeripheral, bluetoothCharacteristic) {
console.log(`didCharacteristcValueChanged ${bluetoothCharacteristic.stringValue}`);
document.getElementById('characteristicValue').innerHTML = bluetoothCharacteristic.stringValue;
}
};
blueFalcon.addDelegate(Object.create(Delegate));
</script>

<h1>Blue Falcon JS Example</h1>

<p>Enter a bluetooth service uuid and press the scan button, for example: 000000ee-0000-1000-8000-00805f9b34fb</p>
<div class="input-group">
<input type="text" class="form-control" id="serviceId" placeholder="Service UUID" aria-describedby="basic-addon1">
<button type="button" class="btn btn-default" onclick="blueFalcon.scan([document.getElementById('serviceId').value])" >Scan</button>
</div>

<div id="connectedDevice" class="input-group" style="display: none;">
<h2>Connected Device</h2>
<p><b>Device:</b> <span id="device"></span></p>
<p><b>Services:</b> <span id="services"></span></p>
<p><b>Characteristics:</b> <span id="characteristics"></span></p>
<p><b>Last Characteristic Value:</b> <span id="characteristicValue"></span></p>
</div>
</body>

</html>
9 changes: 4 additions & 5 deletions library/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,12 @@ kotlin {
kotlinOptions.jvmTarget = "1.8"
}
}
js(LEGACY) {
js {
browser {
testTask {
useKarma {
useChromeHeadless()
}
webpackTask {
output.libraryTarget = "umd"
}
binaries.executable()
}
}
iosX64 {
Expand Down
2 changes: 1 addition & 1 deletion library/gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ org.gradle.configureondemand = false
android.useAndroidX=true
android.enableJetifier=true

version=0.9.3
version=0.9.4
group=dev.bluefalcon
libraryName=blue-falcon

Expand Down
12 changes: 12 additions & 0 deletions library/src/commonMain/kotlin/dev/bluefalcon/BlueFalconDelegate.kt
Original file line number Diff line number Diff line change
@@ -1,18 +1,30 @@
package dev.bluefalcon

import kotlin.js.JsName

@JsName("BlueFalconDelegate")
interface BlueFalconDelegate {

@JsName("didDiscoverDevice")
fun didDiscoverDevice(bluetoothPeripheral: BluetoothPeripheral)
@JsName("didConnect")
fun didConnect(bluetoothPeripheral: BluetoothPeripheral)
@JsName("didDisconnect")
fun didDisconnect(bluetoothPeripheral: BluetoothPeripheral)
@JsName("didDiscoverServices")
fun didDiscoverServices(bluetoothPeripheral: BluetoothPeripheral)
@JsName("didDiscoverCharacteristics")
fun didDiscoverCharacteristics(bluetoothPeripheral: BluetoothPeripheral)
@JsName("didCharacteristcValueChanged")
fun didCharacteristcValueChanged(
bluetoothPeripheral: BluetoothPeripheral,
bluetoothCharacteristic: BluetoothCharacteristic
)
@JsName("didRssiUpdate")
fun didRssiUpdate(bluetoothPeripheral: BluetoothPeripheral)
@JsName("didUpdateMTU")
fun didUpdateMTU(bluetoothPeripheral: BluetoothPeripheral)
@JsName("didReadDescriptor")
fun didReadDescriptor(
bluetoothPeripheral: BluetoothPeripheral,
bluetoothCharacteristicDescriptor: BluetoothCharacteristicDescriptor
Expand Down
88 changes: 78 additions & 10 deletions library/src/jsMain/kotlin/dev/bluefalcon/BlueFalcon.kt
Original file line number Diff line number Diff line change
@@ -1,51 +1,115 @@
package dev.bluefalcon

import dev.bluefalcon.external.Bluetooth
import dev.bluefalcon.external.BluetoothOptions
import kotlinx.browser.window
import org.w3c.dom.Navigator

@JsName("blueFalcon")
val blueFalcon = BlueFalcon(ApplicationContext(), null)

actual class BlueFalcon actual constructor(context: ApplicationContext, serviceUUID: String?) {

actual val delegates: MutableSet<BlueFalconDelegate> = mutableSetOf()
actual var isScanning: Boolean = false

private inline val Navigator.bluetooth: Bluetooth get() = asDynamic().bluetooth as Bluetooth
private var optionalServices: Array<String> = emptyArray()

@JsName("addDelegate")
fun addDelegate(blueFalconDelegate: BlueFalconDelegate) { delegates.add(blueFalconDelegate) }

@JsName("removeDelegate")
fun removeDelegate(blueFalconDelegate: BlueFalconDelegate) { delegates.remove(blueFalconDelegate) }

@JsName("connect")
actual fun connect(bluetoothPeripheral: BluetoothPeripheral, autoConnect: Boolean) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
log("connect -> ${bluetoothPeripheral.device}:${bluetoothPeripheral.device.gatt} gatt connected? ${bluetoothPeripheral.device.gatt?.connected}")
if(bluetoothPeripheral.device.gatt?.connected == true) {
delegates.forEach { it.didConnect(bluetoothPeripheral) }
} else {
bluetoothPeripheral.device.gatt?.connect()?.then { gatt ->
connect(bluetoothPeripheral, autoConnect)
}
}
}

@JsName("disconnect")
actual fun disconnect(bluetoothPeripheral: BluetoothPeripheral) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
bluetoothPeripheral.device.gatt?.disconnect()
}

actual fun stopScanning() {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
actual fun stopScanning() {}

@JsName("scan")
fun scan(optionalServices: Array<String>) {
this.optionalServices = optionalServices
scan()
}

@JsName("rescan")
actual fun scan() {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
window.navigator.bluetooth.requestDevice(BluetoothOptions(false, arrayOf(BluetoothOptions.Filter.Services(optionalServices)), optionalServices))
.then { bluetoothDevice ->
val device = BluetoothPeripheral(bluetoothDevice)
delegates.forEach {
it.didDiscoverDevice(device)
}
}
}

@JsName("readService")
fun readService(
bluetoothPeripheral: BluetoothPeripheral,
serviceUUID: String?
) {
bluetoothPeripheral.device.gatt?.getPrimaryServices(serviceUUID)?.then { services ->
bluetoothPeripheral.deviceServices.addAll(services.map { BluetoothService(it) })
delegates.forEach {
it.didDiscoverServices(bluetoothPeripheral)
}
bluetoothPeripheral.deviceServices.forEach { service ->
service.service.getCharacteristics(undefined).then { characteristics ->
service.deviceCharacteristics = characteristics.map { BluetoothCharacteristic(it) }.toMutableSet()
delegates.forEach {
it.didDiscoverCharacteristics(bluetoothPeripheral)
}
}
}
}
}

@JsName("readCharacteristic")
actual fun readCharacteristic(
bluetoothPeripheral: BluetoothPeripheral,
bluetoothCharacteristic: BluetoothCharacteristic
) {
TODO("not implemented")
bluetoothCharacteristic.characteristic.readValue().then { _ ->
delegates.forEach {
it.didCharacteristcValueChanged(bluetoothPeripheral, bluetoothCharacteristic)
}
}
}


actual fun writeCharacteristic(
bluetoothPeripheral: BluetoothPeripheral,
bluetoothCharacteristic: BluetoothCharacteristic,
value: String,
writeType: Int?
) {
TODO("not implemented")
}
) {}

@JsName("writeCharacteristic")
actual fun writeCharacteristic(
bluetoothPeripheral: BluetoothPeripheral,
bluetoothCharacteristic: BluetoothCharacteristic,
value: ByteArray,
writeType: Int?
){
TODO("not implemented")
bluetoothCharacteristic.characteristic.writeValue(value)
}

@JsName("notifyCharacteristic")
actual fun notifyCharacteristic(
bluetoothPeripheral: BluetoothPeripheral,
bluetoothCharacteristic: BluetoothCharacteristic,
Expand All @@ -54,6 +118,7 @@ actual class BlueFalcon actual constructor(context: ApplicationContext, serviceU
TODO("not implemented")
}

@JsName("indicateCharacteristic")
actual fun indicateCharacteristic(
bluetoothPeripheral: BluetoothPeripheral,
bluetoothCharacteristic: BluetoothCharacteristic,
Expand All @@ -62,6 +127,7 @@ actual class BlueFalcon actual constructor(context: ApplicationContext, serviceU
TODO("not implemented")
}

@JsName("notifyAndIndicateCharacteristic")
actual fun notifyAndIndicateCharacteristic(
bluetoothPeripheral: BluetoothPeripheral,
bluetoothCharacteristic: BluetoothCharacteristic,
Expand All @@ -70,6 +136,7 @@ actual class BlueFalcon actual constructor(context: ApplicationContext, serviceU
TODO("not implemented")
}

@JsName("readDescriptor")
actual fun readDescriptor(
bluetoothPeripheral: BluetoothPeripheral,
bluetoothCharacteristic: BluetoothCharacteristic,
Expand All @@ -78,6 +145,7 @@ actual class BlueFalcon actual constructor(context: ApplicationContext, serviceU
TODO("not implemented")
}

@JsName("changeMTU")
actual fun changeMTU(bluetoothPeripheral: BluetoothPeripheral, mtuSize: Int) {
TODO("not implemented")
}
Expand Down
Loading

0 comments on commit e3917f0

Please sign in to comment.