use only async io when talking to the radio

pull/42/head
geeksville 2020-06-11 16:22:20 -07:00
rodzic 73c2c8def1
commit e20f7c5943
2 zmienionych plików z 54 dodań i 38 usunięć

Wyświetl plik

@ -12,7 +12,6 @@ import com.geeksville.concurrent.handledLaunch
import com.geeksville.util.anonymize import com.geeksville.util.anonymize
import com.geeksville.util.exceptionReporter import com.geeksville.util.exceptionReporter
import com.geeksville.util.ignoreException import com.geeksville.util.ignoreException
import com.geeksville.util.toRemoteExceptions
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import java.lang.reflect.Method import java.lang.reflect.Method
import java.util.* 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) { /// Send a packet/command out the radio link
isFirstSend = false override fun handleSendToRadio(a: ByteArray) {
doReadFromRadio(false) 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() startWatchingFromNum()
} }
} catch (ex: BLEException) { } catch (ex: BLEException) {
errormsg( warn(
"error during doReadFromRadio", "error during doReadFromRadio - disconnecting, ${ex.message}"
ex
) )
service.serviceScope.handledLaunch { retryDueToException() } 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 /// 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 private var hasForcedRefresh = false
@Volatile
var fromNumChanged = false
private fun startWatchingFromNum() { private fun startWatchingFromNum() {
safe!!.setNotify(fromNum, true) { safe!!.setNotify(fromNum, true) {
debug("fromNum changed, so we are reading new messages") // We might get multiple notifies before we get around to reading from the radio - so just set one flag
doReadFromRadio(false) 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 /// We only try to set MTU once, because some buggy implementations fail
@Volatile
private var shouldSetMtu = true private var shouldSetMtu = true
/// For testing /// For testing
@Volatile
private var isFirstTime = true private var isFirstTime = true
private fun doDiscoverServicesAndInit() { private fun doDiscoverServicesAndInit() {
@ -410,22 +431,6 @@ class BluetoothInterface(val service: RadioInterfaceService, val address: String
lostConnectCb = { service.onDisconnect(isPermanent = false) }) 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 * Get a chracteristic, but in a safe manner because some buggy BLE implementations might return null

Wyświetl plik

@ -58,12 +58,18 @@ class SafeBluetooth(private val context: Context, private val device: BluetoothD
/// Users can access the GATT directly as needed /// Users can access the GATT directly as needed
var gatt: BluetoothGatt? = null var gatt: BluetoothGatt? = null
@Volatile
var state = BluetoothProfile.STATE_DISCONNECTED var state = BluetoothProfile.STATE_DISCONNECTED
@Volatile
private var currentWork: BluetoothContinuation? = null private var currentWork: BluetoothContinuation? = null
private val workQueue = mutableListOf<BluetoothContinuation>() private val workQueue = mutableListOf<BluetoothContinuation>()
// Called for reconnection attemps // Called for reconnection attemps
@Volatile
private var connectionCallback: ((Result<Unit>) -> Unit)? = null private var connectionCallback: ((Result<Unit>) -> Unit)? = null
@Volatile
private var lostConnectCallback: (() -> Unit)? = null private var lostConnectCallback: (() -> Unit)? = null
/// from characteristic UUIDs to the handler function for notfies /// 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") debug("Starting work: $tag")
return startWorkFn() return startWorkFn()
} }
override fun toString(): String {
return super.toString()
}
} }
/** /**
* skanky hack to restart BLE if it says it is hosed * skanky hack to restart BLE if it says it is hosed
* https://stackoverflow.com/questions/35103701/ble-android-onconnectionstatechange-not-being-called * https://stackoverflow.com/questions/35103701/ble-android-onconnectionstatechange-not-being-called
*/ */
var mHandler: Handler = Handler() private val mHandler: Handler = Handler()
fun restartBle() { fun restartBle() {
GeeksvilleApplication.analytics.track("ble_restart") // record # of times we needed to use this nasty hack 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 lostConnectCb: () -> Unit
) { ) {
logAssert(workQueue.isEmpty()) 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 lostConnectCallback = lostConnectCb
connectionCallback = if (autoConnect) 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 // Cancel any notifications - because when the device comes back it might have forgotten about us
notifyHandlers.clear() notifyHandlers.clear()
ignoreException { ignoreException {
// Hmm - sometimes the "Connection closing" exception comes back to us - ignore it // Hmm - sometimes the "Connection closing" exception comes back to us - ignore it
failAllWork(BLEException("Connection closing")) failAllWork(BLEException("Connection closing"))