Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optimize observation handling #193

Merged
merged 28 commits into from
Jan 18, 2022
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
defa3a1
Begin refactor of `observe` handling
twyatt Nov 19, 2021
340576b
Move observation handling to common source set
twyatt Nov 22, 2021
dc35f99
Spin up/down observations when at least `Connecting.Observes` state
twyatt Dec 4, 2021
0f04e36
Suppress `BluetoothException`s during observation spin up/down
twyatt Dec 4, 2021
7034218
Clean up
twyatt Dec 4, 2021
c318228
Merge branch 'main' into twyatt/observe
twyatt Dec 8, 2021
f46d1e3
Add missing dependency definition
twyatt Dec 8, 2021
1cd5f49
Merge branch 'main' into twyatt/observe
twyatt Jan 7, 2022
ff08901
Fix Tuulbox version that was trampled in merge
twyatt Jan 7, 2022
dfb3bec
Merge branch 'main' into twyatt/observe
twyatt Jan 11, 2022
b9bb5a8
Merge branch 'main' into twyatt/observe
twyatt Jan 11, 2022
e593c65
Add unit tests and fix re-observe on reconnect
twyatt Jan 12, 2022
167a1ad
Additional tests and fixes
twyatt Jan 12, 2022
20ff24c
Fix test on JS legacy
twyatt Jan 12, 2022
b8fd68a
Add comment re: piping failures
twyatt Jan 12, 2022
f3edff6
Merge branch 'main' into twyatt/observe
twyatt Jan 13, 2022
7fc2dbb
Update tests to have states more aligned with actual
twyatt Jan 13, 2022
cd46759
Code clean up
twyatt Jan 13, 2022
cbf28d5
Simplify observation logic
twyatt Jan 14, 2022
2498d40
Removed need for monitoring connection loss for observations
twyatt Jan 14, 2022
16ee31f
Remove unused import
twyatt Jan 14, 2022
15bbce4
Fix freeze crash on Apple
twyatt Jan 14, 2022
2726b2f
Merge branch 'main' into twyatt/observe
twyatt Jan 14, 2022
608fdfe
Merge branch 'main' into twyatt/observe
twyatt Jan 14, 2022
4f4f4e4
Swap boolean check order in `onConnected`
twyatt Jan 15, 2022
875c98a
Use Stately for observation collection
twyatt Jan 15, 2022
ff426d1
Fix freeze/mutability failures on Native
twyatt Jan 15, 2022
bc8c464
Fix unit tests on Native
twyatt Jan 15, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions core/api/core.api
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public final class com/juul/kable/AndroidPeripheral : com/juul/kable/Peripheral
public fun disconnect (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public final fun getMtu ()Lkotlinx/coroutines/flow/StateFlow;
public fun getServices ()Ljava/util/List;
public fun getState ()Lkotlinx/coroutines/flow/Flow;
public fun getState ()Lkotlinx/coroutines/flow/StateFlow;
public fun observe (Lcom/juul/kable/Characteristic;Lkotlin/jvm/functions/Function1;)Lkotlinx/coroutines/flow/Flow;
public fun read (Lcom/juul/kable/Characteristic;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun read (Lcom/juul/kable/Descriptor;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
Expand Down Expand Up @@ -58,7 +58,7 @@ public final class com/juul/kable/CharacteristicKt {
public static final fun characteristicOf (Ljava/lang/String;Ljava/lang/String;)Lcom/juul/kable/Characteristic;
}

public final class com/juul/kable/ConnectionLostException : java/io/IOException {
public final class com/juul/kable/ConnectionLostException : com/juul/kable/NotConnectedException {
public fun <init> ()V
}

Expand Down Expand Up @@ -154,7 +154,11 @@ public final class com/juul/kable/ManufacturerData {
public final fun getData ()[B
}

public final class com/juul/kable/NotReadyException : java/io/IOException {
public class com/juul/kable/NotConnectedException : java/io/IOException {
public fun <init> ()V
}

public final class com/juul/kable/NotReadyException : com/juul/kable/NotConnectedException {
public fun <init> ()V
}

Expand All @@ -168,7 +172,7 @@ public abstract interface class com/juul/kable/Peripheral {
public abstract fun connect (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public abstract fun disconnect (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public abstract fun getServices ()Ljava/util/List;
public abstract fun getState ()Lkotlinx/coroutines/flow/Flow;
public abstract fun getState ()Lkotlinx/coroutines/flow/StateFlow;
public abstract fun observe (Lcom/juul/kable/Characteristic;Lkotlin/jvm/functions/Function1;)Lkotlinx/coroutines/flow/Flow;
public abstract fun read (Lcom/juul/kable/Characteristic;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public abstract fun read (Lcom/juul/kable/Descriptor;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
Expand Down
4 changes: 1 addition & 3 deletions core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ kotlin {
dependencies {
api(libs.kotlinx.coroutines.core)
api(libs.uuid)
implementation(libs.tuulbox.collections)
}
}

Expand Down Expand Up @@ -72,9 +73,6 @@ kotlin {

val appleMain by creating {
dependsOn(commonMain)
dependencies {
implementation(libs.stately)
}
}

val appleTest by creating
Expand Down
2 changes: 1 addition & 1 deletion core/src/androidMain/kotlin/Connection.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package com.juul.kable

import android.bluetooth.BluetoothGatt
import android.bluetooth.BluetoothGatt.GATT_SUCCESS
import com.juul.kable.AndroidObservationEvent.CharacteristicChange
import com.juul.kable.ObservationEvent.CharacteristicChange
import com.juul.kable.gatt.Callback
import com.juul.kable.gatt.GattStatus
import kotlinx.coroutines.CoroutineDispatcher
Expand Down
11 changes: 11 additions & 0 deletions core/src/androidMain/kotlin/Observations.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.juul.kable

internal actual fun Peripheral.observationHandler(): Observation.Handler = object : Observation.Handler {
override suspend fun startObservation(characteristic: Characteristic) {
(this@observationHandler as AndroidPeripheral).startObservation(characteristic)
}

override suspend fun stopObservation(characteristic: Characteristic) {
(this@observationHandler as AndroidPeripheral).stopObservation(characteristic)
}
}
153 changes: 0 additions & 153 deletions core/src/androidMain/kotlin/Observers.kt

This file was deleted.

38 changes: 23 additions & 15 deletions core/src/androidMain/kotlin/Peripheral.kt
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.withContext
Expand Down Expand Up @@ -127,7 +128,7 @@ public enum class Priority { Low, Balanced, High }

public class AndroidPeripheral internal constructor(
parentCoroutineContext: CoroutineContext,
internal val bluetoothDevice: BluetoothDevice,
private val bluetoothDevice: BluetoothDevice,
private val transport: Transport,
private val phy: Phy,
private val onServicesDiscovered: ServicesDiscoveredAction,
Expand All @@ -136,12 +137,15 @@ public class AndroidPeripheral internal constructor(

private val logger = Logger(logging, tag = "Kable/Peripheral", identifier = bluetoothDevice.address)

internal val platformIdentifier = bluetoothDevice.address

private val _state = MutableStateFlow<State>(State.Disconnected())
public override val state: Flow<State> = _state.asStateFlow()
public override val state: StateFlow<State> = _state.asStateFlow()

private val receiver = registerBluetoothStateBroadcastReceiver { state ->
if (state == STATE_OFF) {
closeConnection()
observers.onConnectionLost()
_state.value = State.Disconnected()
}
}
Expand All @@ -166,7 +170,7 @@ public class AndroidPeripheral internal constructor(
*/
public val mtu: StateFlow<Int?> = _mtu.asStateFlow()

private val observers = Observers(this, logging)
private val observers = Observers<ByteArray>(this, logging)

@Volatile
private var _platformServices: List<PlatformService>? = null
Expand Down Expand Up @@ -205,14 +209,15 @@ public class AndroidPeripheral internal constructor(
connection
.characteristicChanges
.onEach(observers.characteristicChanges::emit)
.onCompletion { observers.onConnectionLost() }
.launchIn(scope, start = UNDISPATCHED)

suspendUntilOrThrow<State.Connecting.Services>()
discoverServices()
onServicesDiscovered(ServicesDiscoveredPeripheral(this@AndroidPeripheral))
_state.value = State.Connecting.Observes
logger.verbose { message = "Configuring characteristic observations" }
observers.rewire()
observers.onConnected()
} catch (t: Throwable) {
closeConnection()
logger.error(t) { message = "Failed to connect" }
Expand All @@ -226,6 +231,9 @@ public class AndroidPeripheral internal constructor(
private fun closeConnection() {
_connection?.close()
_connection = null

observers.onConnectionLost()

// Avoid trampling existing `Disconnected` state (and its properties) by only updating if not already `Disconnected`.
_state.update { previous -> previous as? State.Disconnected ?: State.Disconnected() }
}
Expand Down Expand Up @@ -381,19 +389,16 @@ public class AndroidPeripheral internal constructor(

internal suspend fun stopObservation(characteristic: Characteristic) {
val platformCharacteristic = platformServices.findCharacteristic(characteristic)
setConfigDescriptor(platformCharacteristic, enable = false)

try {
setConfigDescriptor(platformCharacteristic, enable = false)
} finally {
logger.debug {
message = "setCharacteristicNotification"
detail(characteristic)
detail("value", "false")
}
connection
.bluetoothGatt
.setCharacteristicNotification(platformCharacteristic, false)
logger.debug {
message = "setCharacteristicNotification"
detail(characteristic)
detail("value", "false")
}
connection
.bluetoothGatt
.setCharacteristicNotification(platformCharacteristic, false)
}

private suspend fun setConfigDescriptor(
Expand Down Expand Up @@ -506,3 +511,6 @@ private fun checkBluetoothAdapterState(
throw BluetoothDisabledException("Bluetooth adapter state is $actualName ($actual), but $expectedName ($expected) was required.")
}
}

internal actual val Peripheral.identifier: String
get() = (this as AndroidPeripheral).platformIdentifier
11 changes: 11 additions & 0 deletions core/src/appleMain/kotlin/Observations.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.juul.kable

internal actual fun Peripheral.observationHandler(): Observation.Handler = object : Observation.Handler {
override suspend fun startObservation(characteristic: Characteristic) {
(this@observationHandler as ApplePeripheral).startNotifications(characteristic)
}

override suspend fun stopObservation(characteristic: Characteristic) {
(this@observationHandler as ApplePeripheral).stopNotifications(characteristic)
}
}
Loading