mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
bluetooth reconnection works
This commit is contained in:
parent
8ce5a13cf8
commit
0719b62a22
3 changed files with 125 additions and 49 deletions
|
|
@ -193,6 +193,40 @@ class RadioInterfaceService : Service(), Logging {
|
|||
}
|
||||
|
||||
|
||||
private fun onDisconnect() {
|
||||
broadcastConnectionChanged(false)
|
||||
isConnected = false
|
||||
}
|
||||
|
||||
private fun onConnect(connRes: Result<Unit>) {
|
||||
// This callback is invoked after we are connected
|
||||
|
||||
connRes.getOrThrow() // FIXME, instead just try to reconnect?
|
||||
info("Connected to radio!")
|
||||
|
||||
// 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?
|
||||
debug("Discovered services!")
|
||||
|
||||
// we begin by setting our MTU size as high as it can go
|
||||
safe.asyncRequestMtu(512) { mtuRes ->
|
||||
debug("requested MTU result=$mtuRes")
|
||||
mtuRes.getOrThrow() // FIXME - why sometimes is the result Unit!?!
|
||||
|
||||
fromRadio = service.getCharacteristic(BTM_FROMRADIO_CHARACTER)
|
||||
fromNum = service.getCharacteristic(BTM_FROMNUM_CHARACTER)
|
||||
|
||||
// Now tell clients they can (finally use the api)
|
||||
broadcastConnectionChanged(true)
|
||||
isConnected = true
|
||||
|
||||
// Immediately broadcast any queued packets sitting on the device
|
||||
doReadFromRadio()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
||||
|
|
@ -212,35 +246,7 @@ class RadioInterfaceService : Service(), Logging {
|
|||
// comes in range (even if we made this connect call long ago when we got powered on)
|
||||
// see https://stackoverflow.com/questions/40156699/which-correct-flag-of-autoconnect-in-connectgatt-of-ble for
|
||||
// more info
|
||||
// FIXME, broadcast connection lost/gained via broadcastConnecionChanged
|
||||
safe.asyncConnect(true) { connRes ->
|
||||
// This callback is invoked after we are connected
|
||||
|
||||
connRes.getOrThrow() // FIXME, instead just try to reconnect?
|
||||
info("Connected to radio!")
|
||||
|
||||
// FIXME - no need to discover services, instead just hardwire the characteristics (like we do for toRadio)
|
||||
safe.asyncDiscoverServices { discRes ->
|
||||
discRes.getOrThrow() // FIXME, instead just try to reconnect?
|
||||
debug("Discovered services!")
|
||||
|
||||
// we begin by setting our MTU size as high as it can go
|
||||
safe.asyncRequestMtu(512) { mtuRes ->
|
||||
debug("requested MTU result=$mtuRes")
|
||||
mtuRes.getOrThrow()
|
||||
|
||||
fromRadio = service.getCharacteristic(BTM_FROMRADIO_CHARACTER)
|
||||
fromNum = service.getCharacteristic(BTM_FROMNUM_CHARACTER)
|
||||
|
||||
// Now tell clients they can (finally use the api)
|
||||
broadcastConnectionChanged(true)
|
||||
isConnected = true
|
||||
|
||||
// Immediately broadcast any queued packets sitting on the device
|
||||
doReadFromRadio()
|
||||
}
|
||||
}
|
||||
}
|
||||
safe.asyncConnect(true, ::onConnect, ::onDisconnect)
|
||||
|
||||
if (logSends)
|
||||
sentPacketsLog = BinaryLogFile(this, "sent_log.pb")
|
||||
|
|
|
|||
|
|
@ -35,6 +35,10 @@ class SafeBluetooth(private val context: Context, private val device: BluetoothD
|
|||
private var currentWork: BluetoothContinuation? = null
|
||||
private val workQueue = mutableListOf<BluetoothContinuation>()
|
||||
|
||||
// Called for reconnection attemps
|
||||
private var connectionCallback: ((Result<Unit>) -> Unit)? = null
|
||||
private var lostConnectCallback: (() -> Unit)? = null
|
||||
|
||||
/// When we see the BT stack getting disabled/renabled we handle that as a connect/disconnect event
|
||||
private val btStateReceiver = object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context, intent: Intent) = exceptionReporter {
|
||||
|
|
@ -42,13 +46,18 @@ class SafeBluetooth(private val context: Context, private val device: BluetoothD
|
|||
val newstate = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1)
|
||||
when (newstate) {
|
||||
// Simulate a disconnection if the user disables bluetooth entirely
|
||||
BluetoothAdapter.STATE_OFF -> if (gatt != null) gattCallback.onConnectionStateChange(
|
||||
gatt!!,
|
||||
0,
|
||||
BluetoothProfile.STATE_DISCONNECTED
|
||||
)
|
||||
BluetoothAdapter.STATE_OFF -> {
|
||||
if (state == BluetoothProfile.STATE_CONNECTED)
|
||||
gattCallback.onConnectionStateChange(
|
||||
gatt!!,
|
||||
0,
|
||||
BluetoothProfile.STATE_DISCONNECTED
|
||||
)
|
||||
else
|
||||
debug("We were not connected, so ignoring bluetooth shutdown")
|
||||
}
|
||||
BluetoothAdapter.STATE_ON -> {
|
||||
warn("FIXME - requeue a connect anytime bluetooth is reenabled")
|
||||
warn("FIXME - requeue a connect anytime bluetooth is reenabled?")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -86,20 +95,51 @@ class SafeBluetooth(private val context: Context, private val device: BluetoothD
|
|||
g: BluetoothGatt,
|
||||
status: Int,
|
||||
newState: Int
|
||||
) {
|
||||
info("new bluetooth connection state $newState")
|
||||
state = newState
|
||||
) = exceptionReporter {
|
||||
info("new bluetooth connection state $newState, status $status")
|
||||
when (newState) {
|
||||
BluetoothProfile.STATE_CONNECTED -> {
|
||||
state =
|
||||
newState // we only care about connected/disconnected - not the transitional states
|
||||
//logAssert(workQueue.isNotEmpty())
|
||||
//val work = workQueue.removeAt(0)
|
||||
completeWork(status, Unit)
|
||||
}
|
||||
BluetoothProfile.STATE_DISCONNECTED -> {
|
||||
// cancel any queued ops? for now I think it is best to keep them around
|
||||
// failAllWork(IOException("Lost connection"))
|
||||
// cancel any queued ops if we were already connected
|
||||
val oldstate = state
|
||||
state = newState
|
||||
if (oldstate == BluetoothProfile.STATE_CONNECTED) {
|
||||
info("Lost connection - aborting current work")
|
||||
|
||||
gatt = null;
|
||||
|
||||
/*
|
||||
Supposedly this reconnect attempt happens automatically
|
||||
"If the connection was established through an auto connect, Android will
|
||||
automatically try to reconnect to the remote device when it gets disconnected
|
||||
until you manually call disconnect() or close(). Once a connection established
|
||||
through direct connect disconnects, no attempt is made to reconnect to the remote device."
|
||||
https://stackoverflow.com/questions/37965337/what-exactly-does-androids-bluetooth-autoconnect-parameter-do?rq=1
|
||||
|
||||
closeConnection()
|
||||
*/
|
||||
failAllWork(IOException("Lost connection"))
|
||||
|
||||
debug("calling lostConnect handler")
|
||||
lostConnectCallback?.invoke()
|
||||
|
||||
// Queue a new connection attempt
|
||||
val cb = connectionCallback
|
||||
if (cb != null) {
|
||||
debug("queuing a reconnection callback")
|
||||
assert(currentWork == null)
|
||||
|
||||
// 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 }
|
||||
} else {
|
||||
debug("No connectionCallback registered")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -169,7 +209,7 @@ class SafeBluetooth(private val context: Context, private val device: BluetoothD
|
|||
w
|
||||
}
|
||||
|
||||
debug("work ${work.tag} is completed, resuming with status $status")
|
||||
debug("work ${work.tag} is completed, resuming status=$status, res=$res")
|
||||
if (status != 0)
|
||||
work.completion.resumeWithException(IOException("Bluetooth status=$status"))
|
||||
else
|
||||
|
|
@ -185,6 +225,7 @@ class SafeBluetooth(private val context: Context, private val device: BluetoothD
|
|||
it.completion.resumeWithException(ex)
|
||||
}
|
||||
workQueue.clear()
|
||||
currentWork = null
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -210,8 +251,27 @@ class SafeBluetooth(private val context: Context, private val device: BluetoothD
|
|||
}
|
||||
}
|
||||
|
||||
fun asyncConnect(autoConnect: Boolean = false, cb: (Result<Unit>) -> Unit) {
|
||||
|
||||
/**
|
||||
* start a connection attempt.
|
||||
*
|
||||
* Note: if autoConnect is true, the callback you provide will be kept around _even after the connection is complete.
|
||||
* If we ever lose the connection, this class will immediately requque the attempt (after canceling
|
||||
* any outstanding queued operations).
|
||||
*
|
||||
* So you should expect your callback might be called multiple times, each time to reestablish a new connection.
|
||||
*/
|
||||
fun asyncConnect(
|
||||
autoConnect: Boolean = false,
|
||||
cb: (Result<Unit>) -> Unit,
|
||||
lostConnectCb: () -> Unit
|
||||
) {
|
||||
logAssert(workQueue.isEmpty() && currentWork == null) // I don't think anything should be able to sneak in front
|
||||
lostConnectCallback = lostConnectCb
|
||||
connectionCallback = if (autoConnect)
|
||||
cb
|
||||
else
|
||||
null
|
||||
queueConnect(autoConnect, CallbackContinuation(cb))
|
||||
}
|
||||
|
||||
|
|
@ -272,12 +332,21 @@ class SafeBluetooth(private val context: Context, private val device: BluetoothD
|
|||
fun writeCharacteristic(c: BluetoothGattCharacteristic): BluetoothGattCharacteristic =
|
||||
makeSync { queueWriteCharacteristic(c, it) }
|
||||
|
||||
fun disconnect() {
|
||||
if (gatt != null)
|
||||
private fun closeConnection() {
|
||||
failAllWork(IOException("Connection closing"))
|
||||
|
||||
if (gatt != null) {
|
||||
info("Closing our GATT connection")
|
||||
gatt!!.disconnect()
|
||||
gatt!!.close()
|
||||
gatt = null
|
||||
}
|
||||
}
|
||||
|
||||
fun disconnect() {
|
||||
closeConnection()
|
||||
|
||||
context.unregisterReceiver(btStateReceiver)
|
||||
failAllWork(Exception("SafeBluetooth disconnected"))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue