Skip to content

Commit

Permalink
Check bluetooth state prior to connecting (#291)
Browse files Browse the repository at this point in the history
  • Loading branch information
burnhamd authored Apr 9, 2022
1 parent daa9f99 commit aea9303
Show file tree
Hide file tree
Showing 2 changed files with 31 additions and 2 deletions.
7 changes: 5 additions & 2 deletions core/src/appleMain/kotlin/CentralManagerDelegate.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,13 @@ import com.juul.kable.CentralManagerDelegate.Response.DidDiscoverPeripheral
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 kotlinx.coroutines.flow.filterNotNull
import platform.CoreBluetooth.CBCentralManager
import platform.CoreBluetooth.CBCentralManagerDelegateProtocol
import platform.CoreBluetooth.CBCentralManagerStateUnknown
import platform.CoreBluetooth.CBManagerState
import platform.CoreBluetooth.CBPeripheral
import platform.Foundation.NSError
Expand All @@ -24,8 +27,8 @@ internal class CentralManagerDelegate : NSObject(), CBCentralManagerDelegateProt
private val _onDisconnected = MutableSharedFlow<NSUUID>()
internal val onDisconnected = _onDisconnected.asSharedFlow()

private val _state = MutableStateFlow<CBManagerState?>(null)
val state: Flow<CBManagerState> = _state.filterNotNull()
private val _state = MutableStateFlow(CBCentralManagerStateUnknown)
val state: StateFlow<CBManagerState> = _state.asStateFlow()

sealed class Response {

Expand Down
26 changes: 26 additions & 0 deletions core/src/appleMain/kotlin/Peripheral.kt
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,13 @@ import kotlinx.coroutines.flow.takeWhile
import kotlinx.coroutines.job
import kotlinx.coroutines.sync.withPermit
import kotlinx.coroutines.withContext
import platform.CoreBluetooth.CBCentralManagerState
import platform.CoreBluetooth.CBCentralManagerStatePoweredOff
import platform.CoreBluetooth.CBCentralManagerStatePoweredOn
import platform.CoreBluetooth.CBCentralManagerStateResetting
import platform.CoreBluetooth.CBCentralManagerStateUnauthorized
import platform.CoreBluetooth.CBCentralManagerStateUnknown
import platform.CoreBluetooth.CBCentralManagerStateUnsupported
import platform.CoreBluetooth.CBCharacteristicWriteType
import platform.CoreBluetooth.CBCharacteristicWriteWithResponse
import platform.CoreBluetooth.CBCharacteristicWriteWithoutResponse
Expand Down Expand Up @@ -224,6 +230,8 @@ public class ApplePeripheral internal constructor(
}

public override suspend fun connect() {
// Check CBCentral State since connecting can result in an api misuse message
centralManager.checkBluetoothState(CBCentralManagerStatePoweredOn)
connectJob.updateAndGet { it ?: connectAsync() }!!.await()
}

Expand Down Expand Up @@ -430,3 +438,21 @@ private fun NSError.toStatus(): State.Disconnected.Status = when (code) {
CBErrorEncryptionTimedOut -> EncryptionTimedOut
else -> Unknown(code.toInt())
}

private fun CentralManager.checkBluetoothState(expected: CBCentralManagerState) {
val actual = delegate.state.value
if (expected != actual) {
fun nameFor(value: Number) = when (value) {
CBCentralManagerStatePoweredOff -> "PoweredOff"
CBCentralManagerStatePoweredOn -> "PoweredOn"
CBCentralManagerStateResetting -> "Resetting"
CBCentralManagerStateUnauthorized -> "Unauthorized"
CBCentralManagerStateUnknown -> "Unknown"
CBCentralManagerStateUnsupported -> "Unsupported"
else -> "Unknown"
}
val actualName = nameFor(actual)
val expectedName = nameFor(expected)
throw BluetoothDisabledException("Bluetooth state is $actualName ($actual), but $expectedName ($expected) was required.")
}
}

0 comments on commit aea9303

Please sign in to comment.