From e20f7c5943c2a00c1930379746750c097a3b6a46 Mon Sep 17 00:00:00 2001 From: geeksville Date: Thu, 11 Jun 2020 16:22:20 -0700 Subject: [PATCH] use only async io when talking to the radio --- .../mesh/service/BluetoothInterface.kt | 75 ++++++++++--------- .../geeksville/mesh/service/SafeBluetooth.kt | 17 ++++- 2 files changed, 54 insertions(+), 38 deletions(-) diff --git a/app/src/main/java/com/geeksville/mesh/service/BluetoothInterface.kt b/app/src/main/java/com/geeksville/mesh/service/BluetoothInterface.kt index 5153fc50f..16d7fe453 100644 --- a/app/src/main/java/com/geeksville/mesh/service/BluetoothInterface.kt +++ b/app/src/main/java/com/geeksville/mesh/service/BluetoothInterface.kt @@ -12,7 +12,6 @@ import com.geeksville.concurrent.handledLaunch import com.geeksville.util.anonymize import com.geeksville.util.exceptionReporter import com.geeksville.util.ignoreException -import com.geeksville.util.toRemoteExceptions import kotlinx.coroutines.delay import java.lang.reflect.Method import java.util.* @@ -208,21 +207,31 @@ class BluetoothInterface(val service: RadioInterfaceService, val address: String } } - /// Send a packet/command out the radio link - override fun handleSendToRadio(p: ByteArray) { - try { - debug("sending to radio") - doWrite( - BTM_TORADIO_CHARACTER, - p - ) // Do a synchronous write, so that we can then do our reads if needed - if (isFirstSend) { - isFirstSend = false - doReadFromRadio(false) + /// Send a packet/command out the radio link + override fun handleSendToRadio(a: ByteArray) { + safe?.let { s -> + val uuid = BTM_TORADIO_CHARACTER + debug("queuing ${a.size} bytes to $uuid") + + // Note: we generate a new characteristic each time, because we are about to + // change the data and we want the data stored in the closure + val toRadio = getCharacteristic(uuid) + toRadio.value = a + + s.asyncWriteCharacteristic(toRadio) { r -> + try { + r.getOrThrow() + debug("write of ${a.size} bytes completed") + + if (isFirstSend) { + isFirstSend = false + doReadFromRadio(false) + } + } catch (ex: Exception) { + errormsg("Ignoring sendToRadio exception: $ex") + } } - } catch (ex: Exception) { - errormsg("Ignoring sendToRadio exception: $ex") } } @@ -248,9 +257,8 @@ class BluetoothInterface(val service: RadioInterfaceService, val address: String startWatchingFromNum() } } catch (ex: BLEException) { - errormsg( - "error during doReadFromRadio", - ex + warn( + "error during doReadFromRadio - disconnecting, ${ex.message}" ) service.serviceScope.handledLaunch { retryDueToException() } } @@ -274,10 +282,21 @@ class BluetoothInterface(val service: RadioInterfaceService, val address: String /// We only force service refresh the _first_ time we connect to the device. Thereafter it is assumed the firmware didn't change private var hasForcedRefresh = false + @Volatile + var fromNumChanged = false + private fun startWatchingFromNum() { safe!!.setNotify(fromNum, true) { - debug("fromNum changed, so we are reading new messages") - doReadFromRadio(false) + // We might get multiple notifies before we get around to reading from the radio - so just set one flag + fromNumChanged = true + debug("fromNum changed") + service.serviceScope.handledLaunch { + if (fromNumChanged) { + fromNumChanged = false + debug("fromNum changed, so we are reading new messages") + doReadFromRadio(false) + } + } } } @@ -309,9 +328,11 @@ class BluetoothInterface(val service: RadioInterfaceService, val address: String } /// We only try to set MTU once, because some buggy implementations fail + @Volatile private var shouldSetMtu = true /// For testing + @Volatile private var isFirstTime = true private fun doDiscoverServicesAndInit() { @@ -410,22 +431,6 @@ class BluetoothInterface(val service: RadioInterfaceService, val address: String lostConnectCb = { service.onDisconnect(isPermanent = false) }) } - /** - * do a synchronous write operation - */ - private fun doWrite(uuid: UUID, a: ByteArray) = toRemoteExceptions { - safe?.let { s -> - debug("queuing ${a.size} bytes to $uuid") - - // Note: we generate a new characteristic each time, because we are about to - // change the data and we want the data stored in the closure - val toRadio = getCharacteristic(uuid) - toRadio.value = a - - s.writeCharacteristic(toRadio) - debug("write of ${a.size} bytes completed") - } - } /** * Get a chracteristic, but in a safe manner because some buggy BLE implementations might return null 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 049d3789c..8be3e8933 100644 --- a/app/src/main/java/com/geeksville/mesh/service/SafeBluetooth.kt +++ b/app/src/main/java/com/geeksville/mesh/service/SafeBluetooth.kt @@ -58,12 +58,18 @@ class SafeBluetooth(private val context: Context, private val device: BluetoothD /// Users can access the GATT directly as needed var gatt: BluetoothGatt? = null + @Volatile var state = BluetoothProfile.STATE_DISCONNECTED + + @Volatile private var currentWork: BluetoothContinuation? = null private val workQueue = mutableListOf() // Called for reconnection attemps + @Volatile private var connectionCallback: ((Result) -> Unit)? = null + + @Volatile private var lostConnectCallback: (() -> Unit)? = null /// from characteristic UUIDs to the handler function for notfies @@ -123,13 +129,17 @@ class SafeBluetooth(private val context: Context, private val device: BluetoothD debug("Starting work: $tag") return startWorkFn() } + + override fun toString(): String { + return super.toString() + } } /** * skanky hack to restart BLE if it says it is hosed * https://stackoverflow.com/questions/35103701/ble-android-onconnectionstatechange-not-being-called */ - var mHandler: Handler = Handler() + private val mHandler: Handler = Handler() fun restartBle() { GeeksvilleApplication.analytics.track("ble_restart") // record # of times we needed to use this nasty hack @@ -533,7 +543,8 @@ class SafeBluetooth(private val context: Context, private val device: BluetoothD lostConnectCb: () -> Unit ) { logAssert(workQueue.isEmpty()) - logAssert(currentWork == null) // I don't think anything should be able to sneak in front + if (currentWork != null) + throw AssertionError("currentWork was not null: $currentWork") lostConnectCallback = lostConnectCb connectionCallback = if (autoConnect) @@ -663,7 +674,7 @@ class SafeBluetooth(private val context: Context, private val device: BluetoothD // Cancel any notifications - because when the device comes back it might have forgotten about us notifyHandlers.clear() - + ignoreException { // Hmm - sometimes the "Connection closing" exception comes back to us - ignore it failAllWork(BLEException("Connection closing"))