From 8ce5a13cf830eb967fe99fa656d2f084195845c2 Mon Sep 17 00:00:00 2001 From: geeksville Date: Tue, 4 Feb 2020 17:27:10 -0800 Subject: [PATCH] treat disabiling bluetooth as loss of connection --- TODO.md | 4 +- .../geeksville/mesh/RadioInterfaceService.kt | 15 ++++-- .../java/com/geeksville/mesh/SafeBluetooth.kt | 52 ++++++++++++++++--- .../geeksville/mesh/SoftwareUpdateService.kt | 4 +- 4 files changed, 59 insertions(+), 16 deletions(-) diff --git a/TODO.md b/TODO.md index f1800864f..a9be6fb06 100644 --- a/TODO.md +++ b/TODO.md @@ -1,9 +1,8 @@ # High priority -* fix startup race conditions in services, allow reads to block as needed * if radio disconnects, we need to requeue a new connect attempt in RadioService * when notified phone should download messages -* have phone use our local node number as its node number (instead of hardwired) +* fix startup race conditions in services, allow reads to block as needed * investigate the Signal SMS message flow path, see if I could just make Mesh a third peer to signal & sms? * make signal work when there is no internet up * make Signal rx path work @@ -69,3 +68,4 @@ Don't leave device discoverable. Don't let unpaired users do things with device * investigate a 16 bit node number. If possible it would make collisions super rare. Much easier to just pick a nodenum and go. * remove example code boilerplate from the service * switch from protobuf-java to protobuf-javalite - much faster and smaller, just no JSON debug printing +* have phone use our local node number as its node number (instead of hardwired) \ No newline at end of file diff --git a/app/src/main/java/com/geeksville/mesh/RadioInterfaceService.kt b/app/src/main/java/com/geeksville/mesh/RadioInterfaceService.kt index 9d5ec0c9e..3c2660aae 100644 --- a/app/src/main/java/com/geeksville/mesh/RadioInterfaceService.kt +++ b/app/src/main/java/com/geeksville/mesh/RadioInterfaceService.kt @@ -132,11 +132,12 @@ class RadioInterfaceService : Service(), Logging { private lateinit var device: BluetoothDevice private lateinit var safe: SafeBluetooth - val service get() = safe.gatt.services.find { it.uuid == BTM_SERVICE_UUID }!! + val service get() = safe.gatt!!.services.find { it.uuid == BTM_SERVICE_UUID }!! private lateinit var fromRadio: BluetoothGattCharacteristic private lateinit var fromNum: BluetoothGattCharacteristic + private val logSends = false lateinit var sentPacketsLog: BinaryLogFile // inited in onCreate private var isConnected = false @@ -160,8 +161,10 @@ class RadioInterfaceService : Service(), Logging { debug("sending to radio") doWrite(BTM_TORADIO_CHARACTER, p) - sentPacketsLog.write(p) - sentPacketsLog.flush() + if (logSends) { + sentPacketsLog.write(p) + sentPacketsLog.flush() + } } // Handle an incoming packet from the radio, broadcasts it as an android intent @@ -239,12 +242,14 @@ class RadioInterfaceService : Service(), Logging { } } - sentPacketsLog = BinaryLogFile(this, "sent_log.pb") + if (logSends) + sentPacketsLog = BinaryLogFile(this, "sent_log.pb") } override fun onDestroy() { info("Destroying radio interface service") - sentPacketsLog.close() + if (logSends) + sentPacketsLog.close() safe.disconnect() super.onDestroy() } diff --git a/app/src/main/java/com/geeksville/mesh/SafeBluetooth.kt b/app/src/main/java/com/geeksville/mesh/SafeBluetooth.kt index 40de79567..a4c0cdacd 100644 --- a/app/src/main/java/com/geeksville/mesh/SafeBluetooth.kt +++ b/app/src/main/java/com/geeksville/mesh/SafeBluetooth.kt @@ -1,11 +1,15 @@ package com.geeksville.mesh import android.bluetooth.* +import android.content.BroadcastReceiver import android.content.Context +import android.content.Intent +import android.content.IntentFilter import com.geeksville.android.Logging import com.geeksville.concurrent.CallbackContinuation import com.geeksville.concurrent.Continuation import com.geeksville.concurrent.SyncContinuation +import com.geeksville.util.exceptionReporter import java.io.IOException @@ -25,12 +29,39 @@ class SafeBluetooth(private val context: Context, private val device: BluetoothD var timeoutMsec = 5 * 1000L /// Users can access the GATT directly as needed - lateinit var gatt: BluetoothGatt + var gatt: BluetoothGatt? = null var state = BluetoothProfile.STATE_DISCONNECTED private var currentWork: BluetoothContinuation? = null private val workQueue = mutableListOf() + /// 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 { + if (intent.action == BluetoothAdapter.ACTION_STATE_CHANGED) { + 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_ON -> { + warn("FIXME - requeue a connect anytime bluetooth is reenabled") + } + } + } + } + } + + init { + context.registerReceiver( + btStateReceiver, + IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED) + ) + } + /** * a schedulable bit of bluetooth work, includes both the closure to call to start the operation * and the completion (either async or sync) to call when it completes @@ -48,10 +79,11 @@ class SafeBluetooth(private val context: Context, private val device: BluetoothD } } + private val gattCallback = object : BluetoothGattCallback() { override fun onConnectionStateChange( - gatt: BluetoothGatt, + g: BluetoothGatt, status: Int, newState: Int ) { @@ -66,6 +98,8 @@ class SafeBluetooth(private val context: Context, private val device: BluetoothD BluetoothProfile.STATE_DISCONNECTED -> { // cancel any queued ops? for now I think it is best to keep them around // failAllWork(IOException("Lost connection")) + + gatt = null; } } } @@ -167,6 +201,7 @@ class SafeBluetooth(private val context: Context, private val device: BluetoothD // more info. // 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) { + assert(gatt == null); queueWork("connect", cont) { val g = device.connectGatt(context, autoConnect, gattCallback) if (g != null) @@ -185,7 +220,7 @@ class SafeBluetooth(private val context: Context, private val device: BluetoothD private fun queueReadCharacteristic( c: BluetoothGattCharacteristic, cont: Continuation - ) = queueWork("readc", cont) { gatt.readCharacteristic(c) } + ) = queueWork("readc", cont) { gatt!!.readCharacteristic(c) } fun asyncReadCharacteristic( c: BluetoothGattCharacteristic, @@ -197,7 +232,7 @@ class SafeBluetooth(private val context: Context, private val device: BluetoothD private fun queueDiscoverServices(cont: Continuation) { queueWork("discover", cont) { - gatt.discoverServices() + gatt!!.discoverServices() } } @@ -211,7 +246,7 @@ class SafeBluetooth(private val context: Context, private val device: BluetoothD private fun queueRequestMtu( len: Int, cont: Continuation - ) = queueWork("reqMtu", cont) { gatt.requestMtu(len) } + ) = queueWork("reqMtu", cont) { gatt!!.requestMtu(len) } fun asyncRequestMtu( len: Int, @@ -227,7 +262,7 @@ class SafeBluetooth(private val context: Context, private val device: BluetoothD private fun queueWriteCharacteristic( c: BluetoothGattCharacteristic, cont: Continuation - ) = queueWork("writec", cont) { gatt.writeCharacteristic(c) } + ) = queueWork("writec", cont) { gatt!!.writeCharacteristic(c) } fun asyncWriteCharacteristic( c: BluetoothGattCharacteristic, @@ -238,7 +273,10 @@ class SafeBluetooth(private val context: Context, private val device: BluetoothD makeSync { queueWriteCharacteristic(c, it) } fun disconnect() { - gatt.disconnect() + if (gatt != null) + gatt!!.disconnect() + + context.unregisterReceiver(btStateReceiver) failAllWork(Exception("SafeBluetooth disconnected")) } } diff --git a/app/src/main/java/com/geeksville/mesh/SoftwareUpdateService.kt b/app/src/main/java/com/geeksville/mesh/SoftwareUpdateService.kt index 4841c065f..56b00a765 100644 --- a/app/src/main/java/com/geeksville/mesh/SoftwareUpdateService.kt +++ b/app/src/main/java/com/geeksville/mesh/SoftwareUpdateService.kt @@ -53,8 +53,8 @@ class SoftwareUpdateService : JobIntentService(), Logging { // we begin by setting our MTU size as high as it can go sync.requestMtu(512) - - val service = sync.gatt.services.find { it.uuid == SW_UPDATE_UUID }!! + + val service = sync.gatt!!.services.find { it.uuid == SW_UPDATE_UUID }!! val totalSizeDesc = service.getCharacteristic(SW_UPDATE_TOTALSIZE_CHARACTER) val dataDesc = service.getCharacteristic(SW_UPDATE_DATA_CHARACTER)