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 681ec6a4d..1c56ad35a 100644 --- a/app/src/main/java/com/geeksville/mesh/service/SafeBluetooth.kt +++ b/app/src/main/java/com/geeksville/mesh/service/SafeBluetooth.kt @@ -32,8 +32,8 @@ fun longBLEUUID(hexFour: String): UUID = UUID.fromString("0000$hexFour-0000-1000 class SafeBluetooth(private val context: Context, private val device: BluetoothDevice) : Logging, Closeable { - /// Timeout before we declare a bluetooth operation failed - var timeoutMsec = 15 * 1000L + /// Timeout before we declare a bluetooth operation failed (used for synchronous API operations only) + var timeoutMsec = 20 * 1000L /// Users can access the GATT directly as needed @Volatile @@ -361,7 +361,7 @@ class SafeBluetooth(private val context: Context, private val device: BluetoothD private fun queueWork( tag: String, cont: Continuation, - timeout: Long = 0, + timeout: Long, initFn: () -> Boolean ) { val btCont = @@ -455,7 +455,7 @@ class SafeBluetooth(private val context: Context, private val device: BluetoothD private fun makeSync(wrappedFn: (SyncContinuation) -> Unit): T { val cont = SyncContinuation() wrappedFn(cont) - return cont.await(timeoutMsec) + return cont.await() // was timeoutMsec but we now do the timeout at the lower BLE level } // Is the gatt trying to repeatedly connect as needed? @@ -495,12 +495,13 @@ class SafeBluetooth(private val context: Context, private val device: BluetoothD // Otherwise if you pass in false, it will try to connect now and will timeout and fail in 30 seconds. private fun queueConnect( autoConnect: Boolean = false, - cont: Continuation + cont: Continuation, + timeout: Long = 0 ) { this.autoConnect = autoConnect // assert(gatt == null) this now might be !null with our new reconnect support - queueWork("connect", cont) { + queueWork("connect", cont, timeout) { // Note: To workaround https://issuetracker.google.com/issues/36995652 // Always call BluetoothDevice#connectGatt() with autoConnect=false @@ -585,7 +586,7 @@ class SafeBluetooth(private val context: Context, private val device: BluetoothD } // note - we don't need an init fn (because that would normally redo the connectGatt call - which we don't need) - queueWork("reconnect", CallbackContinuation(cb)) { -> true } + queueWork("reconnect", CallbackContinuation(cb), 0) { -> true } } else { debug("No connectionCallback registered") } @@ -596,19 +597,22 @@ class SafeBluetooth(private val context: Context, private val device: BluetoothD private fun queueReadCharacteristic( c: BluetoothGattCharacteristic, - cont: Continuation - ) = queueWork("readC ${c.uuid}", cont) { gatt!!.readCharacteristic(c) } + cont: Continuation, timeout: Long = 0 + ) = queueWork("readC ${c.uuid}", cont, timeout) { gatt!!.readCharacteristic(c) } fun asyncReadCharacteristic( c: BluetoothGattCharacteristic, cb: (Result) -> Unit ) = queueReadCharacteristic(c, CallbackContinuation(cb)) - fun readCharacteristic(c: BluetoothGattCharacteristic): BluetoothGattCharacteristic = - makeSync { queueReadCharacteristic(c, it) } + fun readCharacteristic( + c: BluetoothGattCharacteristic, + timeout: Long = timeoutMsec + ): BluetoothGattCharacteristic = + makeSync { queueReadCharacteristic(c, it, timeout) } - private fun queueDiscoverServices(cont: Continuation) { - queueWork("discover", cont) { + private fun queueDiscoverServices(cont: Continuation, timeout: Long = 0) { + queueWork("discover", cont, timeout) { gatt?.discoverServices() ?: throw BLEException("GATT is null") } } @@ -649,8 +653,8 @@ class SafeBluetooth(private val context: Context, private val device: BluetoothD private fun queueWriteCharacteristic( c: BluetoothGattCharacteristic, v: ByteArray, - cont: Continuation - ) = queueWork("writeC ${c.uuid}", cont) { + cont: Continuation, timeout: Long = 0 + ) = queueWork("writeC ${c.uuid}", cont, timeout) { currentReliableWrite = null c.value = v gatt?.writeCharacteristic(c) ?: false @@ -664,17 +668,18 @@ class SafeBluetooth(private val context: Context, private val device: BluetoothD fun writeCharacteristic( c: BluetoothGattCharacteristic, - v: ByteArray + v: ByteArray, + timeout: Long = timeoutMsec ): BluetoothGattCharacteristic = - makeSync { queueWriteCharacteristic(c, v, it) } + makeSync { queueWriteCharacteristic(c, v, it, timeout) } /** Like write, but we use the extra reliable flow documented here: * https://stackoverflow.com/questions/24485536/what-is-reliable-write-in-ble */ private fun queueWriteReliable( c: BluetoothGattCharacteristic, - cont: Continuation - ) = queueWork("rwriteC ${c.uuid}", cont) { + cont: Continuation, timeout: Long = 0 + ) = queueWork("rwriteC ${c.uuid}", cont, timeout) { logAssert(gatt!!.beginReliableWrite()) currentReliableWrite = c.value.clone() gatt?.writeCharacteristic(c) ?: false @@ -690,8 +695,8 @@ class SafeBluetooth(private val context: Context, private val device: BluetoothD private fun queueWriteDescriptor( c: BluetoothGattDescriptor, - cont: Continuation - ) = queueWork("writeD", cont) { gatt?.writeDescriptor(c) ?: false } + cont: Continuation, timeout: Long = 0 + ) = queueWork("writeD", cont, timeout) { gatt?.writeDescriptor(c) ?: false } fun asyncWriteDescriptor( c: BluetoothGattDescriptor, @@ -765,7 +770,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() - + closeGatt() failAllWork(BLEException("Connection closing")) diff --git a/app/src/main/java/com/geeksville/mesh/service/SoftwareUpdateService.kt b/app/src/main/java/com/geeksville/mesh/service/SoftwareUpdateService.kt index 6a132e20c..ebeafe75d 100644 --- a/app/src/main/java/com/geeksville/mesh/service/SoftwareUpdateService.kt +++ b/app/src/main/java/com/geeksville/mesh/service/SoftwareUpdateService.kt @@ -205,7 +205,7 @@ class SoftwareUpdateService : JobIntentService(), Logging { verStringToInt(if (swVer.isEmpty() || swVer == "unset") "99.99.99" else swVer) (curVer > deviceVersion) && (deviceVersion >= minVer) - // true + true } catch (ex: Exception) { errormsg("Error finding swupdate info", ex) false // If we fail parsing our update info @@ -313,25 +313,30 @@ class SoftwareUpdateService : JobIntentService(), Logging { firmwareNumSent += blockSize } - // We have finished sending all our blocks, so post the CRC so our state machine can advance - val c = firmwareCrc.value - info("Sent all blocks, crc is $c") - sync.writeCharacteristic( - crc32Desc, - toNetworkByteArray(c.toInt(), BluetoothGattCharacteristic.FORMAT_UINT32) - ) + try { + // We have finished sending all our blocks, so post the CRC so our state machine can advance + val c = firmwareCrc.value + info("Sent all blocks, crc is $c") + sync.writeCharacteristic( + crc32Desc, + toNetworkByteArray(c.toInt(), BluetoothGattCharacteristic.FORMAT_UINT32) + ) - // we just read the update result if !0 we have an error - val updateResult = - sync.readCharacteristic(updateResultDesc) - .getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, 0) - if (updateResult != 0) { - progress = -2 - throw Exception("Device update failed, reason=$updateResult") + // we just read the update result if !0 we have an error + val updateResult = + sync.readCharacteristic(updateResultDesc) + .getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, 0) + if (updateResult != 0) { + progress = -2 + throw Exception("Device update failed, reason=$updateResult") + } + + // Device will now reboot + } catch (ex: BLEException) { + // We might get SyncContinuation timeout on the final write, assume the device simply rebooted to run the new load and we missed it + errormsg("Assuming successful update", ex) } - // Device will now reboot - progress = -1 // success } } catch (ex: BLEException) { diff --git a/geeksville-androidlib b/geeksville-androidlib index 80d207cb5..775502e99 160000 --- a/geeksville-androidlib +++ b/geeksville-androidlib @@ -1 +1 @@ -Subproject commit 80d207cb51c79601ceaf2b09bc369e5babf06495 +Subproject commit 775502e999b06c1e8effbfd631b1924d998b0570