appleMain.CentralManagerDelegate.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of kable-core Show documentation
Show all versions of kable-core Show documentation
Kotlin Asynchronous Bluetooth Low Energy
package com.juul.kable
import com.juul.kable.CentralManagerDelegate.ConnectionEvent.DidConnect
import com.juul.kable.CentralManagerDelegate.ConnectionEvent.DidDisconnect
import com.juul.kable.CentralManagerDelegate.ConnectionEvent.DidFailToConnect
import com.juul.kable.CentralManagerDelegate.Response.DidDiscoverPeripheral
import kotlinx.cinterop.ObjCSignatureOverride
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import platform.CoreBluetooth.CBCentralManager
import platform.CoreBluetooth.CBCentralManagerDelegateProtocol
import platform.CoreBluetooth.CBManagerState
import platform.CoreBluetooth.CBManagerStateUnknown
import platform.CoreBluetooth.CBPeripheral
import platform.Foundation.NSError
import platform.Foundation.NSNumber
import platform.Foundation.NSUUID
import platform.darwin.NSObject
// https://developer.apple.com/documentation/corebluetooth/cbcentralmanagerdelegate
internal class CentralManagerDelegate : NSObject(), CBCentralManagerDelegateProtocol {
private val _onDisconnected = MutableSharedFlow()
internal val onDisconnected = _onDisconnected.asSharedFlow()
private val _state = MutableStateFlow(CBManagerStateUnknown)
val state: StateFlow = _state.asStateFlow()
sealed class Response {
data class DidDiscoverPeripheral(
val cbPeripheral: CBPeripheral,
val rssi: NSNumber,
val advertisementData: Map,
) : Response()
}
private val _response = MutableSharedFlow(extraBufferCapacity = 64)
val response: Flow = _response.asSharedFlow()
sealed class ConnectionEvent {
abstract val identifier: NSUUID
data class DidConnect(
override val identifier: NSUUID,
) : ConnectionEvent()
data class DidFailToConnect(
override val identifier: NSUUID,
val error: NSError?,
) : ConnectionEvent()
data class DidDisconnect(
override val identifier: NSUUID,
val error: NSError?,
) : ConnectionEvent()
}
// `SharedFlow` (instead of `StateFlow`) as downstream needs non-distinct items, as it feeds individual `Peripheral`
// states. If, for example, this flow emits `Disconnected` then downstream `Peripheral` connects and updates its own
// state to `Connected`, this flow may still hold `Disconnected` but we'll need to emit another `Disconnected` to
// update the `Peripheral` state with.
private val _connectionState = MutableSharedFlow()
val connectionState: Flow = _connectionState.asSharedFlow()
/* Monitoring Connections with Peripherals */
override fun centralManager(
central: CBCentralManager,
didConnectPeripheral: CBPeripheral,
) {
_connectionState.emitBlocking(DidConnect(didConnectPeripheral.identifier))
}
@ObjCSignatureOverride
override fun centralManager(
central: CBCentralManager,
didDisconnectPeripheral: CBPeripheral,
error: NSError?,
) {
_onDisconnected.emitBlocking(didDisconnectPeripheral.identifier) // Used to notify `Peripheral` of disconnect.
_connectionState.emitBlocking(DidDisconnect(didDisconnectPeripheral.identifier, error))
}
@ObjCSignatureOverride
override fun centralManager(
central: CBCentralManager,
didFailToConnectPeripheral: CBPeripheral,
error: NSError?,
) {
_connectionState.emitBlocking(DidFailToConnect(didFailToConnectPeripheral.identifier, error))
}
// todo: func centralManager(CBCentralManager, connectionEventDidOccur: CBConnectionEvent, for: CBPeripheral)
/* Discovering and Retrieving Peripherals */
override fun centralManager(
central: CBCentralManager,
didDiscoverPeripheral: CBPeripheral,
advertisementData: Map,
RSSI: NSNumber,
) {
// Per Apple documentation, `advertisementData` is defined as dictionary of `[String : Any]`.
// https://developer.apple.com/documentation/corebluetooth/cbcentralmanagerdelegate/1518937-centralmanager
val data = advertisementData as Map
_response.emitBlocking(DidDiscoverPeripheral(didDiscoverPeripheral, RSSI, data))
}
/* Monitoring the Central Manager’s State */
override fun centralManagerDidUpdateState(
central: CBCentralManager,
) {
_state.value = central.state
}
override fun centralManager(central: CBCentralManager, willRestoreState: Map) {
// No-op: From the documentation: Tells the delegate the system is about to restore the
// central manager, as part of relaunching the app into the background. Use this method to
// synchronize the state of your app with the state of the Bluetooth system. Since the rest
// of Kable is handling the "synchronize," there's nothing to do here.
}
/* Monitoring the Central Manager’s Authorization */
// todo: func centralManager(CBCentralManager, didUpdateANCSAuthorizationFor: CBPeripheral)
}