diff --git a/app/build.gradle b/app/build.gradle index 712495a9a..17e02999b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -17,8 +17,8 @@ android { applicationId "com.geeksville.mesh" minSdkVersion 22 // The oldest emulator image I have tried is 22 (though 21 probably works) targetSdkVersion 29 - versionCode 154 - versionName "0.5.4" + versionCode 155 + versionName "0.5.5" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { diff --git a/app/src/main/java/com/geeksville/mesh/service/RadioInterfaceService.kt b/app/src/main/java/com/geeksville/mesh/service/RadioInterfaceService.kt index d0866c224..c7e43749f 100644 --- a/app/src/main/java/com/geeksville/mesh/service/RadioInterfaceService.kt +++ b/app/src/main/java/com/geeksville/mesh/service/RadioInterfaceService.kt @@ -387,6 +387,73 @@ class RadioInterfaceService : Service(), Logging { // private var isFirstTime = true + /** + * Some buggy BLE stacks can fail on initial connect, with either missing services or missing characteristics. If that happens we + * disconnect and try again when the device reenumerates. + */ + private suspend fun retryDueToException() { + // Track how often in the field we need this hack + GeeksvilleApplication.analytics.track( + "ble_reconnect_hack", + DataPair(1) + ) + + warn("Forcing disconnect and hopefully device will comeback (disabling forced refresh)") + hasForcedRefresh = true + ignoreException { + safe!!.closeConnection() + } + delay(1000) // Give some nasty time for buggy BLE stacks to shutdown (500ms was not enough) + warn("Attempting reconnect") + startConnect() + } + + /// We only try to set MTU once, because some buggy implementations fail + private var shouldSetMtu = true + + private fun doDiscoverServicesAndInit() { + // FIXME - no need to discover services more than once - instead use lazy() to use them in future attempts + safe!!.asyncDiscoverServices { discRes -> + discRes.getOrThrow() // FIXME, instead just try to reconnect? + + serviceScope.handledLaunch { + try { + debug("Discovered services!") + delay(500) // android BLE is buggy and needs a 500ms sleep before calling getChracteristic, or you might get back null + + // service could be null, test this by throwing BLEException and testing it on my machine + isOldApi = service.getCharacteristic(BTM_RADIO_CHARACTER) != null + warn("Use oldAPI = $isOldApi") + + /* if (isFirstTime) { + isFirstTime = false + throw BLEException("Faking a BLE failure") + } */ + + fromNum = getCharacteristic(BTM_FROMNUM_CHARACTER) + + // We must set this to true before broadcasting connectionChanged + isConnected = true + + // We treat the first send by a client as special + isFirstSend = true + + // Now tell clients they can (finally use the api) + broadcastConnectionChanged(true, isPermanent = false) + + // Immediately broadcast any queued packets sitting on the device + doReadFromRadio(true) + } catch (ex: BLEException) { + errormsg( + "Unexpected error in initial device enumeration, forcing disconnect", + ex + ) + retryDueToException() + } + } + } + } + private fun onConnect(connRes: Result) { // This callback is invoked after we are connected @@ -399,65 +466,27 @@ class RadioInterfaceService : Service(), Logging { forceServiceRefresh() } - // we begin by setting our MTU size as high as it can go - safe!!.asyncRequestMtu(512) { mtuRes -> - mtuRes.getOrThrow() // FIXME - why sometimes is the result Unit!?! - debug("MTU change attempted") + // we begin by setting our MTU size as high as it can go (if we can) + if (shouldSetMtu) + safe!!.asyncRequestMtu(512) { mtuRes -> + try { + mtuRes.getOrThrow() // FIXME - why sometimes is the result Unit!?! + debug("MTU change attempted") - // FIXME - no need to discover services more than once - instead use lazy() to use them in future attempts - safe!!.asyncDiscoverServices { discRes -> - discRes.getOrThrow() // FIXME, instead just try to reconnect? + // throw BLEException("Test MTU set failed") - serviceScope.handledLaunch { - try { - debug("Discovered services!") - delay(500) // android BLE is buggy and needs a 500ms sleep before calling getChracteristic, or you might get back null - - // service could be null, test this by throwing BLEException and testing it on my machine - isOldApi = service.getCharacteristic(BTM_RADIO_CHARACTER) != null - warn("Use oldAPI = $isOldApi") - - /* if (isFirstTime) { - isFirstTime = false - throw BLEException("Faking a BLE failure") - } */ - - fromNum = getCharacteristic(BTM_FROMNUM_CHARACTER) - - // We must set this to true before broadcasting connectionChanged - isConnected = true - - // We treat the first send by a client as special - isFirstSend = true - - // Now tell clients they can (finally use the api) - broadcastConnectionChanged(true, isPermanent = false) - - // Immediately broadcast any queued packets sitting on the device - doReadFromRadio(true) - } catch (ex: BLEException) { - // Track how often in the field we need this hack - GeeksvilleApplication.analytics.track( - "ble_reconnect_hack", - DataPair(1) - ) - - errormsg( - "Unexpected error in initial device enumeration, forcing disconnect", - ex - ) - warn("Forcing disconnect and hopefully device will comeback (disabling forced refresh)") - hasForcedRefresh = true - ignoreException { - safe!!.closeConnection() - } - delay(500) // Give some nasty time for buggy BLE stacks to shutdown - warn("Attempting reconnect") - startConnect() - } + doDiscoverServicesAndInit() + } catch (ex: BLEException) { + errormsg( + "Giving up on setting MTUs, forcing disconnect", + ex + ) + shouldSetMtu = false + serviceScope.handledLaunch { retryDueToException() } } } - } + else + doDiscoverServicesAndInit() } /** diff --git a/app/src/main/java/com/geeksville/mesh/service/SafeBluetooth.kt b/app/src/main/java/com/geeksville/mesh/service/SafeBluetooth.kt index dba53be2f..b00774a0e 100644 --- a/app/src/main/java/com/geeksville/mesh/service/SafeBluetooth.kt +++ b/app/src/main/java/com/geeksville/mesh/service/SafeBluetooth.kt @@ -84,6 +84,11 @@ class SafeBluetooth(private val context: Context, private val device: BluetoothD } } + /** + * A BLE status code based error + */ + class BLEStatusException(val status: Int, msg: String) : BLEException(msg) + // 0x2902 org.bluetooth.descriptor.gatt.client_characteristic_configuration.xml private val configurationDescriptorUUID = longBLEUUID("2902") @@ -339,7 +344,8 @@ class SafeBluetooth(private val context: Context, private val device: BluetoothD debug("work ${work.tag} is completed, resuming status=$status, res=$res") if (status != 0) work.completion.resumeWithException( - BLEException( + BLEStatusException( + status, "Bluetooth status=$status while doing ${work.tag}" ) ) diff --git a/build.gradle b/build.gradle index 3465f224d..424586b7c 100644 --- a/build.gradle +++ b/build.gradle @@ -10,7 +10,7 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:4.0.0-beta04' + classpath 'com.android.tools.build:gradle:4.0.0-beta05' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"