kopia lustrzana https://github.com/meshtastic/Meshtastic-Android
Fix 27: add timeouts for BLE operations, to protect against buggy drivers
rodzic
bba9def20e
commit
28023b8f42
|
@ -13,6 +13,7 @@ import com.geeksville.concurrent.CallbackContinuation
|
||||||
import com.geeksville.concurrent.Continuation
|
import com.geeksville.concurrent.Continuation
|
||||||
import com.geeksville.concurrent.SyncContinuation
|
import com.geeksville.concurrent.SyncContinuation
|
||||||
import com.geeksville.util.exceptionReporter
|
import com.geeksville.util.exceptionReporter
|
||||||
|
import kotlinx.coroutines.*
|
||||||
import java.io.Closeable
|
import java.io.Closeable
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
|
@ -67,6 +68,8 @@ class SafeBluetooth(private val context: Context, private val device: BluetoothD
|
||||||
/// from characteristic UUIDs to the handler function for notfies
|
/// from characteristic UUIDs to the handler function for notfies
|
||||||
private val notifyHandlers = mutableMapOf<UUID, (BluetoothGattCharacteristic) -> Unit>()
|
private val notifyHandlers = mutableMapOf<UUID, (BluetoothGattCharacteristic) -> Unit>()
|
||||||
|
|
||||||
|
private val serviceScope = CoroutineScope(Dispatchers.IO)
|
||||||
|
|
||||||
/// When we see the BT stack getting disabled/renabled we handle that as a connect/disconnect event
|
/// When we see the BT stack getting disabled/renabled we handle that as a connect/disconnect event
|
||||||
private val btStateReceiver = BluetoothStateReceiver { enabled ->
|
private val btStateReceiver = BluetoothStateReceiver { enabled ->
|
||||||
if (!enabled) {
|
if (!enabled) {
|
||||||
|
@ -107,7 +110,8 @@ class SafeBluetooth(private val context: Context, private val device: BluetoothD
|
||||||
private class BluetoothContinuation(
|
private class BluetoothContinuation(
|
||||||
val tag: String,
|
val tag: String,
|
||||||
val completion: com.geeksville.concurrent.Continuation<*>,
|
val completion: com.geeksville.concurrent.Continuation<*>,
|
||||||
val startWorkFn: () -> Boolean
|
val timeoutMillis: Long = 0, // If we want to timeout this operation at a certain time, use a non zero value
|
||||||
|
private val startWorkFn: () -> Boolean
|
||||||
) : Logging {
|
) : Logging {
|
||||||
|
|
||||||
/// Start running a queued bit of work, return true for success or false for fatal bluetooth error
|
/// Start running a queued bit of work, return true for success or false for fatal bluetooth error
|
||||||
|
@ -146,6 +150,10 @@ class SafeBluetooth(private val context: Context, private val device: BluetoothD
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Our own custom BLE status codes
|
||||||
|
private val STATUS_RELIABLE_WRITE_FAILED = 4403
|
||||||
|
private val STATUS_TIMEOUT = 4403
|
||||||
|
|
||||||
private val gattCallback = object : BluetoothGattCallback() {
|
private val gattCallback = object : BluetoothGattCallback() {
|
||||||
|
|
||||||
override fun onConnectionStateChange(
|
override fun onConnectionStateChange(
|
||||||
|
@ -240,7 +248,10 @@ class SafeBluetooth(private val context: Context, private val device: BluetoothD
|
||||||
if (!characteristic.value.contentEquals(reliable)) {
|
if (!characteristic.value.contentEquals(reliable)) {
|
||||||
errormsg("A reliable write failed!")
|
errormsg("A reliable write failed!")
|
||||||
gatt.abortReliableWrite();
|
gatt.abortReliableWrite();
|
||||||
completeWork(42, characteristic) // skanky code to indicate failure
|
completeWork(
|
||||||
|
STATUS_RELIABLE_WRITE_FAILED,
|
||||||
|
characteristic
|
||||||
|
) // skanky code to indicate failure
|
||||||
} else {
|
} else {
|
||||||
logAssert(gatt.executeReliableWrite())
|
logAssert(gatt.executeReliableWrite())
|
||||||
// After this execute reliable completes - we can continue with normal operations (see onReliableWriteCompleted)
|
// After this execute reliable completes - we can continue with normal operations (see onReliableWriteCompleted)
|
||||||
|
@ -310,6 +321,8 @@ class SafeBluetooth(private val context: Context, private val device: BluetoothD
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private var activeTimeout: Job? = null
|
||||||
|
|
||||||
/// If we have work we can do, start doing it.
|
/// If we have work we can do, start doing it.
|
||||||
private fun startNewWork() {
|
private fun startNewWork() {
|
||||||
logAssert(currentWork == null)
|
logAssert(currentWork == null)
|
||||||
|
@ -317,15 +330,35 @@ class SafeBluetooth(private val context: Context, private val device: BluetoothD
|
||||||
if (workQueue.isNotEmpty()) {
|
if (workQueue.isNotEmpty()) {
|
||||||
val newWork = workQueue.removeAt(0)
|
val newWork = workQueue.removeAt(0)
|
||||||
currentWork = newWork
|
currentWork = newWork
|
||||||
|
|
||||||
|
if (newWork.timeoutMillis != 0L) {
|
||||||
|
|
||||||
|
activeTimeout = serviceScope.launch {
|
||||||
|
debug("Starting failsafe timer ${newWork.timeoutMillis}")
|
||||||
|
delay(newWork.timeoutMillis)
|
||||||
|
errormsg("Failsafe BLE timer expired!")
|
||||||
|
completeWork(
|
||||||
|
STATUS_TIMEOUT,
|
||||||
|
Unit
|
||||||
|
) // Throw an exception in that work
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
logAssert(newWork.startWork())
|
logAssert(newWork.startWork())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun <T> queueWork(tag: String, cont: Continuation<T>, initFn: () -> Boolean) {
|
private fun <T> queueWork(
|
||||||
|
tag: String,
|
||||||
|
cont: Continuation<T>,
|
||||||
|
timeout: Long = 0,
|
||||||
|
initFn: () -> Boolean
|
||||||
|
) {
|
||||||
val btCont =
|
val btCont =
|
||||||
BluetoothContinuation(
|
BluetoothContinuation(
|
||||||
tag,
|
tag,
|
||||||
cont,
|
cont,
|
||||||
|
timeout,
|
||||||
initFn
|
initFn
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -339,6 +372,17 @@ class SafeBluetooth(private val context: Context, private val device: BluetoothD
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop any current work
|
||||||
|
*/
|
||||||
|
private fun stopCurrentWork() {
|
||||||
|
activeTimeout?.let {
|
||||||
|
it.cancel()
|
||||||
|
activeTimeout = null
|
||||||
|
}
|
||||||
|
currentWork = null
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called from our big GATT callback, completes the current job and then schedules a new one
|
* Called from our big GATT callback, completes the current job and then schedules a new one
|
||||||
*/
|
*/
|
||||||
|
@ -352,7 +396,7 @@ class SafeBluetooth(private val context: Context, private val device: BluetoothD
|
||||||
val w =
|
val w =
|
||||||
currentWork
|
currentWork
|
||||||
?: throw Exception("currentWork was null") // will throw if null, which is helpful (FIXME - throws in the field)
|
?: throw Exception("currentWork was null") // will throw if null, which is helpful (FIXME - throws in the field)
|
||||||
currentWork = null // We are now no longer working on anything
|
stopCurrentWork() // We are now no longer working on anything
|
||||||
|
|
||||||
startNewWork()
|
startNewWork()
|
||||||
w
|
w
|
||||||
|
@ -380,7 +424,7 @@ class SafeBluetooth(private val context: Context, private val device: BluetoothD
|
||||||
it.completion.resumeWithException(ex)
|
it.completion.resumeWithException(ex)
|
||||||
}
|
}
|
||||||
workQueue.clear()
|
workQueue.clear()
|
||||||
currentWork = null
|
stopCurrentWork()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -481,10 +525,13 @@ class SafeBluetooth(private val context: Context, private val device: BluetoothD
|
||||||
|
|
||||||
fun discoverServices() = makeSync<Unit> { queueDiscoverServices(it) }
|
fun discoverServices() = makeSync<Unit> { queueDiscoverServices(it) }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* mtu operations seem to hang sometimes. To cope with this we have a 5 second timeout before throwing an exception and cancelling the work
|
||||||
|
*/
|
||||||
private fun queueRequestMtu(
|
private fun queueRequestMtu(
|
||||||
len: Int,
|
len: Int,
|
||||||
cont: Continuation<Unit>
|
cont: Continuation<Unit>
|
||||||
) = queueWork("reqMtu", cont) { gatt!!.requestMtu(len) }
|
) = queueWork("reqMtu", cont, 5 * 1000) { gatt!!.requestMtu(len) }
|
||||||
|
|
||||||
fun asyncRequestMtu(
|
fun asyncRequestMtu(
|
||||||
len: Int,
|
len: Int,
|
||||||
|
|
Ładowanie…
Reference in New Issue