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

Check bluetooth state prior to connecting #291

Merged
merged 11 commits into from
Apr 9, 2022
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.")
}
}