diff --git a/buildSrc/src/main/kotlin/Dependencies.kt b/buildSrc/src/main/kotlin/Dependencies.kt index c1b173b36..4b22f78fc 100644 --- a/buildSrc/src/main/kotlin/Dependencies.kt +++ b/buildSrc/src/main/kotlin/Dependencies.kt @@ -13,11 +13,6 @@ fun uuid( version: String = "0.3.1" ): String = "com.benasher44:$artifact:$version" -fun stately( - module: String, - version: String = "1.1.10-a1" -): String = "co.touchlab:stately-$module:$version" - fun wrappers( version: String = "1.0.1-pre.264-kotlin-1.5.31" ) = "org.jetbrains.kotlin-wrappers:kotlin-extensions:$version" diff --git a/core/api/core.api b/core/api/core.api index 1462f7f6f..41a906e99 100644 --- a/core/api/core.api +++ b/core/api/core.api @@ -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; @@ -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 ()V } @@ -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 ()V +} + +public final class com/juul/kable/NotReadyException : com/juul/kable/NotConnectedException { public fun ()V } @@ -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; diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 7062217c8..447247dfc 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -73,9 +73,6 @@ kotlin { val appleMain by creating { dependsOn(commonMain) - dependencies { - implementation(stately("isolate")) - } } val appleTest by creating diff --git a/core/src/androidMain/kotlin/Connection.kt b/core/src/androidMain/kotlin/Connection.kt index 50079a31a..f9e93640a 100644 --- a/core/src/androidMain/kotlin/Connection.kt +++ b/core/src/androidMain/kotlin/Connection.kt @@ -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 diff --git a/core/src/androidMain/kotlin/Observations.kt b/core/src/androidMain/kotlin/Observations.kt new file mode 100644 index 000000000..889313af3 --- /dev/null +++ b/core/src/androidMain/kotlin/Observations.kt @@ -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) + } +} diff --git a/core/src/androidMain/kotlin/Peripheral.kt b/core/src/androidMain/kotlin/Peripheral.kt index de60248c9..c1e4e7462 100644 --- a/core/src/androidMain/kotlin/Peripheral.kt +++ b/core/src/androidMain/kotlin/Peripheral.kt @@ -128,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, @@ -137,8 +137,10 @@ 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.Disconnected()) - public override val state: Flow = _state.asStateFlow() + public override val state: StateFlow = _state.asStateFlow() private val receiver = registerBluetoothStateBroadcastReceiver { state -> if (state == STATE_OFF) { @@ -168,7 +170,7 @@ public class AndroidPeripheral internal constructor( */ public val mtu: StateFlow = _mtu.asStateFlow() - private val observers = Observers(this, _state, logging) + private val observers = Observers(this, logging) @Volatile private var _platformServices: List? = null @@ -509,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 diff --git a/core/src/appleMain/kotlin/Observations.kt b/core/src/appleMain/kotlin/Observations.kt new file mode 100644 index 000000000..9585dda2d --- /dev/null +++ b/core/src/appleMain/kotlin/Observations.kt @@ -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) + } +} diff --git a/core/src/appleMain/kotlin/Observers.kt b/core/src/appleMain/kotlin/Observers.kt deleted file mode 100644 index 9dba77ce1..000000000 --- a/core/src/appleMain/kotlin/Observers.kt +++ /dev/null @@ -1,158 +0,0 @@ -package com.juul.kable - -import co.touchlab.stately.ensureNeverFrozen -import co.touchlab.stately.isolate.IsolateState -import com.juul.kable.logs.Logger -import com.juul.kable.logs.detail -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableSharedFlow -import kotlinx.coroutines.flow.filter -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.onCompletion -import kotlinx.coroutines.flow.onSubscription -import platform.Foundation.NSData -import kotlin.coroutines.cancellation.CancellationException - -internal sealed class AppleObservationEvent { - - abstract val characteristic: Characteristic - - data class CharacteristicChange( - override val characteristic: Characteristic, - val data: NSData, - ) : AppleObservationEvent() - - data class Error( - override val characteristic: Characteristic, - val cause: Throwable, - ) : AppleObservationEvent() -} - -/** - * Manages observations for the specified [peripheral]. - * - * The [characteristicChanges] property is expected to be fed with all characteristic changes associated with the - * [peripheral]. The changes are then fanned-out to individual [Flow]s created via [acquire] (associated with a specific - * characteristic). - * - * For example, if you have a sequence of characteristic changes represented by characteristic A, B and C with their - * corresponding change uniquely identified by a sequence number postfix (in other words: characteristic A emitting 3 - * different changes would be represented as A1, A2 and A3): - * - * ``` - * .--- acquire(A) --> A1, A2, A3 - * .-----------------------. / - * A1, B1, C1, A2, A3, B2 --> | characteristicChanges | ----- acquire(B) --> B1, B2 - * '-----------------------' \ - * '--- acquire(C) --> C1 - * ``` - * - * @param peripheral to perform notification actions against to enable/disable the observations. - */ -internal class Observers( - private val peripheral: ApplePeripheral, - private val logger: Logger, -) { - - val characteristicChanges = MutableSharedFlow() - private val observations = Observations() - - fun acquire( - characteristic: Characteristic, - onSubscription: OnSubscriptionAction, - ): Flow { - return characteristicChanges - .onSubscription { - peripheral.suspendUntilAtLeast() - if (observations.add(characteristic, onSubscription) == 1) { - peripheral.startNotifications(characteristic) - } - onSubscription() - } - .filter { - it.characteristic.characteristicUuid == characteristic.characteristicUuid && - it.characteristic.serviceUuid == characteristic.serviceUuid - } - .map { - when (it) { - is AppleObservationEvent.Error -> throw it.cause - is AppleObservationEvent.CharacteristicChange -> it.data - } - } - .onCompletion { - if (observations.remove(characteristic, onSubscription) == 0) { - try { - peripheral.stopNotifications(characteristic) - } catch (e: NotReadyException) { - // Silently ignore as it is assumed that failure is due to connection drop, in which case the - // system will clear the notifications. - logger.warn(e) { - message = "Stop notification failure ignored." - detail(characteristic) - } - } - } - } - } - - suspend fun rewire() { - observations.entries.forEach { (characteristic, observationStartedActions) -> - try { - peripheral.startNotifications(characteristic) - observationStartedActions.forEach { it() } - } catch (cancellation: CancellationException) { - throw cancellation - } catch (t: Throwable) { - characteristicChanges.emit(AppleObservationEvent.Error(characteristic, t)) - } - } - } -} - -private class Observations : IsolateState>>( - producer = { mutableMapOf() } -) { - - val entries: Map> - get() = access { - mutableMapOf>().also { copy -> - it.forEach { (key, value) -> - copy[key] = value.toList() - } - }.toMap() - } - - fun add( - characteristic: Characteristic, - onSubscription: OnSubscriptionAction - ): Int = access { - val actions = it[characteristic] - if (actions == null) { - val newActions = mutableListOf(onSubscription) - newActions.ensureNeverFrozen() - it[characteristic] = newActions - 1 - } else { - actions += onSubscription - actions.count() - } - } - - fun remove( - characteristic: Characteristic, - onSubscription: OnSubscriptionAction - ): Int = access { - val actions = it[characteristic] - when { - actions == null -> -1 // No previous observation existed for characteristic. - actions.count() == 1 -> { - it -= characteristic - 0 - } - else -> { - actions -= onSubscription - actions.count() - } - } - } -} diff --git a/core/src/appleMain/kotlin/Peripheral.kt b/core/src/appleMain/kotlin/Peripheral.kt index bd29c3278..cb2db3c13 100644 --- a/core/src/appleMain/kotlin/Peripheral.kt +++ b/core/src/appleMain/kotlin/Peripheral.kt @@ -39,6 +39,7 @@ import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.async import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.first @@ -100,7 +101,11 @@ public class ApplePeripheral internal constructor( private val logger = Logger(logging, identifier = cbPeripheral.identifier.UUIDString) private val _state = MutableStateFlow(State.Disconnected()) - override val state: Flow = _state.asStateFlow() + override val state: StateFlow = _state.asStateFlow() + + private val observers = Observers(this, logging) + + internal val platformIdentifier = cbPeripheral.identifier.UUIDString init { centralManager.delegate @@ -112,13 +117,14 @@ public class ApplePeripheral internal constructor( detail("state", event.toString()) } } + .onEach { event -> + if (event is DidFailToConnect || event is DidDisconnect) observers.onConnectionLost() + } .map { event -> event.toState() } .onEach { _state.value = it } .launchIn(scope) } - private val observers = Observers(this, logger) - private val _platformServices = atomic?>(null) private val platformServices: List get() = checkNotNull(_platformServices.value) { @@ -164,7 +170,7 @@ public class ApplePeripheral internal constructor( .takeWhile { it !== Closed } .mapNotNull { it as? Data } .map { - AppleObservationEvent.CharacteristicChange( + ObservationEvent.CharacteristicChange( characteristic = it.cbCharacteristic.toLazyCharacteristic(), data = it.data ) @@ -180,7 +186,7 @@ public class ApplePeripheral internal constructor( _state.value = State.Connecting.Observes logger.verbose { message = "Configuring characteristic observations" } - observers.rewire() + observers.onConnected() } catch (t: Throwable) { logger.error(t) { message = "Failed to connect" } withContext(NonCancellable) { @@ -408,3 +414,6 @@ private fun NSError.toStatus(): State.Disconnected.Status = when (code) { CBErrorEncryptionTimedOut -> EncryptionTimedOut else -> Unknown(code.toInt()) } + +internal actual val Peripheral.identifier: String + get() = (this as ApplePeripheral).platformIdentifier diff --git a/core/src/commonMain/kotlin/Observations.kt b/core/src/commonMain/kotlin/Observation.kt similarity index 100% rename from core/src/commonMain/kotlin/Observations.kt rename to core/src/commonMain/kotlin/Observation.kt diff --git a/core/src/commonMain/kotlin/ObservationEvent.kt b/core/src/commonMain/kotlin/ObservationEvent.kt new file mode 100644 index 000000000..c066ff22f --- /dev/null +++ b/core/src/commonMain/kotlin/ObservationEvent.kt @@ -0,0 +1,25 @@ +package com.juul.kable + +internal sealed class ObservationEvent { + + abstract val characteristic: Characteristic + + data class CharacteristicChange( + override val characteristic: Characteristic, + val data: T, + ) : ObservationEvent() + + data class Error( + override val characteristic: Characteristic, + val cause: Throwable, + ) : ObservationEvent() +} + +internal fun dematerialize(event: ObservationEvent): T = when (event) { + is ObservationEvent.Error -> throw event.cause + is ObservationEvent.CharacteristicChange -> event.data +} + +internal fun ObservationEvent.isAssociatedWith(characteristic: Characteristic): Boolean = + this.characteristic.characteristicUuid == characteristic.characteristicUuid && + this.characteristic.serviceUuid == characteristic.serviceUuid diff --git a/core/src/androidMain/kotlin/Observers.kt b/core/src/commonMain/kotlin/Observers.kt similarity index 52% rename from core/src/androidMain/kotlin/Observers.kt rename to core/src/commonMain/kotlin/Observers.kt index f08120d75..e4fe33516 100644 --- a/core/src/androidMain/kotlin/Observers.kt +++ b/core/src/commonMain/kotlin/Observers.kt @@ -4,27 +4,13 @@ import com.juul.kable.logs.Logging import com.juul.tuulbox.collections.synchronizedMapOf import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow -import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onCompletion import kotlinx.coroutines.flow.onSubscription import kotlin.coroutines.cancellation.CancellationException -internal sealed class AndroidObservationEvent { - - abstract val characteristic: Characteristic - - data class CharacteristicChange( - override val characteristic: Characteristic, - val data: ByteArray, - ) : AndroidObservationEvent() - - data class Error( - override val characteristic: Characteristic, - val cause: Throwable, - ) : AndroidObservationEvent() -} +internal expect fun Peripheral.observationHandler(): Observation.Handler /** * Manages observations for the specified [peripheral]. @@ -38,42 +24,38 @@ internal sealed class AndroidObservationEvent { * different changes would be represented as A1, A2 and A3): * * ``` - * .--- acquire(A) --> A1, A2, A3 - * .----------------------. / - * A1, B1, C1, A2, A3, B2 --> | characteristicChange | ----- acquire(B) --> B1, B2 - * '----------------------' \ - * '--- acquire(C) --> C1 + * .--- acquire(A) --> A1, A2, A3 + * .-----------------------. / + * A1, B1, C1, A2, A3, B2 --> | characteristicChanges | ----- acquire(B) --> B1, B2 + * '-----------------------' \ + * '--- acquire(C) --> C1 * ``` * * @param peripheral to perform notification actions against to enable/disable the observations. */ -internal class Observers( - private val peripheral: AndroidPeripheral, - private val state: StateFlow, +internal class Observers( + private val peripheral: Peripheral, private val logging: Logging, + extraBufferCapacity: Int = 0, ) { - val characteristicChanges = MutableSharedFlow() + val characteristicChanges = MutableSharedFlow>(extraBufferCapacity = extraBufferCapacity) private val observations = synchronizedMapOf() fun acquire( characteristic: Characteristic, onSubscription: OnSubscriptionAction, - ): Flow { + ): Flow { val handler = peripheral.observationHandler() - val identifier = peripheral.bluetoothDevice.address val observation = observations.synchronized { getOrPut(characteristic) { - Observation(state, handler, characteristic, logging, identifier) + Observation(peripheral.state, handler, characteristic, logging, peripheral.identifier) } } return characteristicChanges .onSubscription { observation.onSubscription(onSubscription) } - .filter { - it.characteristic.characteristicUuid == characteristic.characteristicUuid && - it.characteristic.serviceUuid == characteristic.serviceUuid - } + .filter { event -> event.isAssociatedWith(characteristic) } .map(::dematerialize) .onCompletion { observation.onCompletion(onSubscription) } } @@ -85,7 +67,7 @@ internal class Observers( } catch (cancellation: CancellationException) { throw cancellation } catch (e: Exception) { - characteristicChanges.emit(AndroidObservationEvent.Error(characteristic, e)) + characteristicChanges.emit(ObservationEvent.Error(characteristic, e)) } } } @@ -96,18 +78,3 @@ internal class Observers( } } } - -private fun dematerialize(event: AndroidObservationEvent): ByteArray = when (event) { - is AndroidObservationEvent.Error -> throw event.cause - is AndroidObservationEvent.CharacteristicChange -> event.data -} - -private fun AndroidPeripheral.observationHandler(): Observation.Handler = object : Observation.Handler { - override suspend fun startObservation(characteristic: Characteristic) { - this@observationHandler.startObservation(characteristic) - } - - override suspend fun stopObservation(characteristic: Characteristic) { - this@observationHandler.stopObservation(characteristic) - } -} diff --git a/core/src/commonMain/kotlin/Peripheral.kt b/core/src/commonMain/kotlin/Peripheral.kt index e2c7167b4..19048a8c8 100644 --- a/core/src/commonMain/kotlin/Peripheral.kt +++ b/core/src/commonMain/kotlin/Peripheral.kt @@ -6,6 +6,7 @@ package com.juul.kable import com.juul.kable.WriteType.WithoutResponse import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.onEach import kotlin.coroutines.cancellation.CancellationException @@ -58,7 +59,7 @@ public interface Peripheral { * '---------------' '--------------' * ``` */ - public val state: Flow + public val state: StateFlow /** * Initiates a connection, suspending until connected, or failure occurs. Multiple concurrent invocations will all @@ -184,3 +185,5 @@ internal suspend inline fun Peripheral.suspendUntilOrThrow() .onEach { if (it is State.Disconnected) throw ConnectionLostException() } .first { it is T } } + +internal expect val Peripheral.identifier: String diff --git a/core/src/jsMain/kotlin/Observations.kt b/core/src/jsMain/kotlin/Observations.kt new file mode 100644 index 000000000..71e6d030f --- /dev/null +++ b/core/src/jsMain/kotlin/Observations.kt @@ -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 JsPeripheral).startObservation(characteristic) + } + + override suspend fun stopObservation(characteristic: Characteristic) { + (this@observationHandler as JsPeripheral).stopObservation(characteristic) + } +} diff --git a/core/src/jsMain/kotlin/Observers.kt b/core/src/jsMain/kotlin/Observers.kt deleted file mode 100644 index f4035279d..000000000 --- a/core/src/jsMain/kotlin/Observers.kt +++ /dev/null @@ -1,112 +0,0 @@ -package com.juul.kable - -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableSharedFlow -import kotlinx.coroutines.flow.filter -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.onCompletion -import kotlinx.coroutines.flow.onSubscription -import org.khronos.webgl.DataView -import kotlin.coroutines.cancellation.CancellationException - -internal sealed class JsObservationEvent { - - abstract val characteristic: Characteristic - - data class CharacteristicChange( - override val characteristic: Characteristic, - val data: DataView, - ) : JsObservationEvent() - - data class Error( - override val characteristic: Characteristic, - val cause: Throwable, - ) : JsObservationEvent() -} - -internal class Observers( - private val peripheral: JsPeripheral, -) { - - val characteristicChanges = MutableSharedFlow(extraBufferCapacity = 64) - private val observations = Observations() - - fun acquire( - characteristic: Characteristic, - onSubscription: OnSubscriptionAction, - ): Flow = characteristicChanges - .onSubscription { - peripheral.suspendUntilAtLeast() - if (observations.add(characteristic, onSubscription) == 1) { - peripheral.startObservation(characteristic) - } - onSubscription() - } - .filter { - it.characteristic.characteristicUuid == characteristic.characteristicUuid && - it.characteristic.serviceUuid == characteristic.serviceUuid - } - .map { - when (it) { - is JsObservationEvent.Error -> throw it.cause - is JsObservationEvent.CharacteristicChange -> it.data - } - } - .onCompletion { - if (observations.remove(characteristic, onSubscription) == 0) { - peripheral.stopObservation(characteristic) - } - } - - suspend fun rewire() { - observations.entries.forEach { (characteristic, onSubscriptionActions) -> - try { - peripheral.startObservation(characteristic) - onSubscriptionActions.forEach { it() } - } catch (cancellation: CancellationException) { - throw cancellation - } catch (t: Throwable) { - characteristicChanges.emit(JsObservationEvent.Error(characteristic, t)) - } - } - } -} - -private class Observations { - - private val observations = mutableMapOf>() - val entries get() = observations.entries - - fun add( - characteristic: Characteristic, - onSubscription: OnSubscriptionAction, - ): Int { - val actions = observations[characteristic] - return if (actions == null) { - val newActions = mutableListOf(onSubscription) - observations[characteristic] = newActions - 1 - } else { - actions += onSubscription - actions.count() - } - } - - fun remove( - characteristic: Characteristic, - onSubscription: OnSubscriptionAction, - ): Int { - val actions = observations[characteristic] - return when { - actions == null -> -1 // No previous observation existed for characteristic. - actions.count() == 1 -> { - observations -= characteristic - 0 - } - else -> { - actions -= onSubscription - actions.count() - } - } - } -} diff --git a/core/src/jsMain/kotlin/Peripheral.kt b/core/src/jsMain/kotlin/Peripheral.kt index 624313781..12a1280bd 100644 --- a/core/src/jsMain/kotlin/Peripheral.kt +++ b/core/src/jsMain/kotlin/Peripheral.kt @@ -21,6 +21,7 @@ import kotlinx.coroutines.async import kotlinx.coroutines.await import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.update @@ -75,8 +76,10 @@ public class JsPeripheral internal constructor( private val ioLock = Mutex() + internal val platformIdentifier = bluetoothDevice.id + private val _state = MutableStateFlow(State.Disconnected()) - public override val state: Flow = _state.asStateFlow() + public override val state: StateFlow = _state.asStateFlow() private var _platformServices: List? = null private val platformServices: List @@ -138,7 +141,7 @@ public class JsPeripheral internal constructor( onServicesDiscovered(ServicesDiscoveredPeripheral(this@JsPeripheral)) _state.value = State.Connecting.Observes logger.verbose { message = "Configuring characteristic observations" } - observers.rewire() + observers.onConnected() } catch (t: Throwable) { logger.error(t) { message = "Failed to connect" } disconnectGatt() @@ -261,7 +264,7 @@ public class JsPeripheral internal constructor( .buffer .toByteArray() - private val observers = Observers(this) + private val observers = Observers(this, logging, extraBufferCapacity = 64) public fun observeDataView( characteristic: Characteristic, @@ -345,7 +348,7 @@ public class JsPeripheral internal constructor( detail(this@createListener) detail(data) } - val characteristicChange = JsObservationEvent.CharacteristicChange(this, data) + val characteristicChange = ObservationEvent.CharacteristicChange(this, data) if (!observers.characteristicChanges.tryEmit(characteristicChange)) console.error("Failed to emit $characteristicChange") @@ -372,3 +375,6 @@ public class JsPeripheral internal constructor( override fun toString(): String = "Peripheral(bluetoothDevice=${bluetoothDevice.string()})" } + +internal actual val Peripheral.identifier: String + get() = (this as JsPeripheral).platformIdentifier