Skip to content

Commit

Permalink
Realign (#31)
Browse files Browse the repository at this point in the history
* Centralize state updates and make sure connection timer gets cancelled

* Enable CONNECTING and DISCONNECTING states. Some other alignments with java code

* Upgrade Gradle version

* Update dependencies
  • Loading branch information
weliem authored Aug 18, 2022
1 parent 3ab79d1 commit 678b4eb
Show file tree
Hide file tree
Showing 6 changed files with 69 additions and 33 deletions.
12 changes: 6 additions & 6 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'

android {
compileSdkVersion 31
compileSdkVersion 32

defaultConfig {
applicationId "com.welie.blessedexample"
minSdkVersion 26
targetSdkVersion 31
targetSdkVersion 32
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
Expand All @@ -31,13 +31,13 @@ android {

dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation 'androidx.appcompat:appcompat:1.4.1'
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
implementation 'androidx.appcompat:appcompat:1.5.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'com.jakewharton.timber:timber:5.0.1'

implementation "androidx.core:core-ktx:1.7.0"
implementation "androidx.core:core-ktx:1.8.0"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.0"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.1"


implementation project(':blessed')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,7 @@ internal class BluetoothHandler private constructor(context: Context) {
central = BluetoothCentralManager(context)

central.observeConnectionState { peripheral, state ->
Timber.i("Peripheral ${peripheral.name} has $state")
Timber.i("Peripheral '${peripheral.name}' is $state")
when (state) {
ConnectionState.CONNECTED -> handlePeripheral(peripheral)
ConnectionState.DISCONNECTED -> scope.launch {
Expand Down
10 changes: 5 additions & 5 deletions blessed/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ apply plugin: 'kotlin-android'
apply plugin: 'maven-publish'

android {
compileSdkVersion 31
compileSdkVersion 32

defaultConfig {
minSdkVersion 26
targetSdkVersion 31
targetSdkVersion 32

testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
Expand All @@ -30,9 +30,9 @@ android {
}

dependencies {
implementation "androidx.core:core-ktx:1.7.0"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.1-native-mt"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.1"
implementation "androidx.core:core-ktx:1.8.0"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.1"
implementation 'com.jakewharton.timber:timber:5.0.1'

testImplementation 'junit:junit:4.13.2'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,10 @@ class BluetoothCentralManager(private val context: Context) {

@JvmField
val internalCallback: InternalCallback = object : InternalCallback {
override fun connecting(peripheral: BluetoothPeripheral) {
scope.launch { connectionStateCallback.invoke(peripheral, ConnectionState.CONNECTING)}
}

override fun connected(peripheral: BluetoothPeripheral) {
connectionRetries.remove(peripheral.address)
unconnectedPeripherals.remove(peripheral.address)
Expand Down Expand Up @@ -195,6 +199,10 @@ class BluetoothCentralManager(private val context: Context) {
}
}

override fun disconnecting(peripheral: BluetoothPeripheral) {
scope.launch { connectionStateCallback.invoke(peripheral, ConnectionState.DISCONNECTING)}
}

override fun disconnected(peripheral: BluetoothPeripheral, status: HciStatus) {
connectedPeripherals.remove(peripheral.address)
unconnectedPeripherals.remove(peripheral.address)
Expand Down
66 changes: 47 additions & 19 deletions blessed/src/main/java/com/welie/blessed/BluetoothPeripheral.kt
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ class BluetoothPeripheral internal constructor(
*/
private val bluetoothGattCallback: BluetoothGattCallback = object : BluetoothGattCallback() {
override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) {
cancelConnectionTimer()
if (newState != BluetoothProfile.STATE_CONNECTING) cancelConnectionTimer()
val previousState = state
state = newState

Expand All @@ -103,8 +103,14 @@ class BluetoothPeripheral internal constructor(
when (newState) {
BluetoothProfile.STATE_CONNECTED -> successfullyConnected()
BluetoothProfile.STATE_DISCONNECTED -> successfullyDisconnected(previousState)
BluetoothProfile.STATE_DISCONNECTING -> Logger.d(TAG, "peripheral is disconnecting")
BluetoothProfile.STATE_CONNECTING -> Logger.d(TAG, "peripheral is connecting")
BluetoothProfile.STATE_DISCONNECTING -> {
Logger.d(TAG, "peripheral is disconnecting")
listener.disconnecting(this@BluetoothPeripheral)
}
BluetoothProfile.STATE_CONNECTING -> {
Logger.d(TAG, "peripheral is connecting")
listener.connecting(this@BluetoothPeripheral)
}
else -> Logger.e(TAG, "unknown state received")
}
} else {
Expand Down Expand Up @@ -142,9 +148,11 @@ class BluetoothPeripheral internal constructor(
)
}

val value = currentWriteBytes
currentWriteBytes = ByteArray(0)

if (descriptor.uuid == CCC_DESCRIPTOR_UUID) {
if (gattStatus == GattStatus.SUCCESS) {
val value = nonnullOf(descriptor.value)
if (value.contentEquals(ENABLE_NOTIFICATION_VALUE) ||
value.contentEquals(ENABLE_INDICATION_VALUE)
) {
Expand All @@ -155,7 +163,7 @@ class BluetoothPeripheral internal constructor(
}
callbackScope.launch { resultCallback.onNotificationStateUpdate(this@BluetoothPeripheral, parentCharacteristic, gattStatus) }
} else {
callbackScope.launch { resultCallback.onDescriptorWrite(this@BluetoothPeripheral, currentWriteBytes, descriptor, gattStatus) }
callbackScope.launch { resultCallback.onDescriptorWrite(this@BluetoothPeripheral, value, descriptor, gattStatus) }
}
completedCommand()
}
Expand Down Expand Up @@ -440,7 +448,6 @@ class BluetoothPeripheral internal constructor(
delay(DIRECT_CONNECTION_DELAY_IN_MS)
Logger.d(TAG, "connect to '%s' (%s) using TRANSPORT_LE", name, address)
registerBondingBroadcastReceivers()
state = BluetoothProfile.STATE_CONNECTING
discoveryStarted = false
bluetoothGatt = try {
device.connectGatt(context, false, bluetoothGattCallback, BluetoothDevice.TRANSPORT_LE)
Expand All @@ -449,6 +456,7 @@ class BluetoothPeripheral internal constructor(
null
}
bluetoothGatt?.let {
bluetoothGattCallback.onConnectionStateChange(it, HciStatus.SUCCESS.value, BluetoothProfile.STATE_CONNECTING)
connectTimestamp = SystemClock.elapsedRealtime()
startConnectionTimer(this@BluetoothPeripheral)
}
Expand All @@ -469,15 +477,15 @@ class BluetoothPeripheral internal constructor(
scope.launch {
Logger.d(TAG, "autoConnect to '%s' (%s) using TRANSPORT_LE", name, address)
registerBondingBroadcastReceivers()
state = BluetoothProfile.STATE_CONNECTING
discoveryStarted = false
bluetoothGatt = try {
device.connectGatt(context, true, bluetoothGattCallback, BluetoothDevice.TRANSPORT_LE)
} catch (e: SecurityException) {
Logger.d(TAG, "exception")
Logger.e(TAG, "connectGatt exception")
null
}
bluetoothGatt?.let {
bluetoothGattCallback.onConnectionStateChange(it, HciStatus.SUCCESS.value, BluetoothProfile.STATE_CONNECTING)
connectTimestamp = SystemClock.elapsedRealtime()
}
}
Expand Down Expand Up @@ -506,11 +514,12 @@ class BluetoothPeripheral internal constructor(
// Check if we have a Gatt object
if (bluetoothGatt == null) {
// No gatt object so no connection issued, do create bond immediately
registerBondingBroadcastReceivers()
return device.createBond()
}

// Enqueue the bond command because a connection has been issued or we are already connected
val result = commandQueue.add(Runnable {
return enqueue {
manuallyBonding = true
if (!device.createBond()) {
Logger.e(TAG, "bonding failed for %s", address)
Expand All @@ -519,13 +528,7 @@ class BluetoothPeripheral internal constructor(
Logger.d(TAG, "manually bonding %s", address)
nrTries++
}
})
if (result) {
nextCommand()
} else {
Logger.e(TAG, "could not enqueue bonding command")
}
return result
}

/**
Expand Down Expand Up @@ -557,7 +560,9 @@ class BluetoothPeripheral internal constructor(
// Since we will not get a callback on onConnectionStateChange for this, we issue the disconnect ourselves
scope.launch {
delay(50)
bluetoothGattCallback.onConnectionStateChange(bluetoothGatt, HciStatus.SUCCESS.value, BluetoothProfile.STATE_DISCONNECTED)
bluetoothGatt?.let {
bluetoothGattCallback.onConnectionStateChange(bluetoothGatt, HciStatus.SUCCESS.value, BluetoothProfile.STATE_DISCONNECTED)
}
}
} else {
// Cancel active connection and onConnectionStateChange will be called by Android
Expand All @@ -573,10 +578,14 @@ class BluetoothPeripheral internal constructor(
*/
private fun disconnect() {
if (state == BluetoothProfile.STATE_CONNECTED || state == BluetoothProfile.STATE_CONNECTING) {
state = BluetoothProfile.STATE_DISCONNECTING
bluetoothGatt?.let {
bluetoothGattCallback.onConnectionStateChange(it, HciStatus.SUCCESS.value, BluetoothProfile.STATE_DISCONNECTING)
}

scope.launch {
if (state == BluetoothProfile.STATE_DISCONNECTING && bluetoothGatt != null) {
bluetoothGatt?.disconnect()
Logger.i(TAG, "force disconnect '%s' (%s)", name, address)
}
}
} else {
Expand All @@ -585,7 +594,6 @@ class BluetoothPeripheral internal constructor(
}

fun disconnectWhenBluetoothOff() {
bluetoothGatt = null
completeDisconnect(true, HciStatus.SUCCESS)
}

Expand All @@ -600,6 +608,10 @@ class BluetoothPeripheral internal constructor(
commandQueue.clear()
commandQueueBusy = false
notifyingCharacteristics.clear()
currentMtu = DEFAULT_MTU
currentCommand = IDLE
manuallyBonding = false
discoveryStarted = false
try {
context.unregisterReceiver(bondStateReceiver)
context.unregisterReceiver(pairingRequestBroadcastReceiver)
Expand Down Expand Up @@ -1482,6 +1494,13 @@ class BluetoothPeripheral internal constructor(
}

interface InternalCallback {
/**
* Trying to connect to [BluetoothPeripheral]
*
* @param peripheral [BluetoothPeripheral] the peripheral.
*/
fun connecting(peripheral: BluetoothPeripheral)

/**
* [BluetoothPeripheral] has successfully connected.
*
Expand All @@ -1496,6 +1515,13 @@ class BluetoothPeripheral internal constructor(
*/
fun connectFailed(peripheral: BluetoothPeripheral, status: HciStatus)

/**
* Trying to disconnect to [BluetoothPeripheral]
*
* @param peripheral [BluetoothPeripheral] the peripheral.
*/
fun disconnecting(peripheral: BluetoothPeripheral)

/**
* [BluetoothPeripheral] has disconnected.
*
Expand All @@ -1515,7 +1541,9 @@ class BluetoothPeripheral internal constructor(
disconnect()
scope.launch {
delay(50)
bluetoothGattCallback.onConnectionStateChange(bluetoothGatt, HciStatus.CONNECTION_FAILED_ESTABLISHMENT.value, BluetoothProfile.STATE_DISCONNECTED)
bluetoothGatt?.let {
bluetoothGattCallback.onConnectionStateChange(bluetoothGatt, HciStatus.CONNECTION_FAILED_ESTABLISHMENT.value, BluetoothProfile.STATE_DISCONNECTED)
}
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.

buildscript {
ext.kotlin_version = '1.6.10'
ext.kotlin_version = '1.7.10'
repositories {
google()
mavenCentral()
}

dependencies {
classpath 'com.android.tools.build:gradle:7.2.1'
classpath 'com.android.tools.build:gradle:7.2.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"

// NOTE: Do not place your application dependencies here; they belong
Expand Down

0 comments on commit 678b4eb

Please sign in to comment.