From 78e6be2b3897c85eefb2f95ab47e5938ff71dc69 Mon Sep 17 00:00:00 2001 From: geeksville Date: Sun, 9 Feb 2020 03:40:13 -0800 Subject: [PATCH] get notifies for BLE packet arrival --- .../geeksville/mesh/RadioInterfaceService.kt | 12 +- .../java/com/geeksville/mesh/SafeBluetooth.kt | 103 +++++++++++++++++- 2 files changed, 107 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/com/geeksville/mesh/RadioInterfaceService.kt b/app/src/main/java/com/geeksville/mesh/RadioInterfaceService.kt index 4e6897b49..73a59d2b4 100644 --- a/app/src/main/java/com/geeksville/mesh/RadioInterfaceService.kt +++ b/app/src/main/java/com/geeksville/mesh/RadioInterfaceService.kt @@ -14,6 +14,7 @@ import com.geeksville.concurrent.DeferredExecution import com.geeksville.util.toRemoteExceptions import java.util.* + /* Info for the esp32 device side code. See that source for the 'gold' standard docs on this interface. MeshBluetoothService UUID 6ba1b218-15a8-461f-9fa8-5dcae273eafd @@ -134,7 +135,6 @@ class RadioInterfaceService : Service(), Logging { 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 @@ -176,7 +176,8 @@ class RadioInterfaceService : Service(), Logging { private fun doReadFromRadio() { if (!isConnected) warn("Abandoning fromradio read because we are not connected") - else + else { + val fromRadio = service.getCharacteristic(BTM_FROMRADIO_CHARACTER) safe.asyncReadCharacteristic(fromRadio) { val b = it.getOrThrow().value @@ -190,6 +191,7 @@ class RadioInterfaceService : Service(), Logging { debug("Done reading from radio, fromradio is empty") } } + } } @@ -214,9 +216,13 @@ class RadioInterfaceService : Service(), Logging { 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) + safe.setNotify(fromNum, true) { + debug("fromNum changed, so we are reading new messages") + doReadFromRadio() + } + // Now tell clients they can (finally use the api) broadcastConnectionChanged(true) isConnected = true diff --git a/app/src/main/java/com/geeksville/mesh/SafeBluetooth.kt b/app/src/main/java/com/geeksville/mesh/SafeBluetooth.kt index 58c8adcb9..0553ec742 100644 --- a/app/src/main/java/com/geeksville/mesh/SafeBluetooth.kt +++ b/app/src/main/java/com/geeksville/mesh/SafeBluetooth.kt @@ -11,6 +11,7 @@ import com.geeksville.concurrent.Continuation import com.geeksville.concurrent.SyncContinuation import com.geeksville.util.exceptionReporter import java.io.IOException +import java.util.* /** @@ -26,7 +27,7 @@ class SafeBluetooth(private val context: Context, private val device: BluetoothD Logging { /// Timeout before we declare a bluetooth operation failed - var timeoutMsec = 5 * 1000L + var timeoutMsec = 30 * 1000L /// Users can access the GATT directly as needed var gatt: BluetoothGatt? = null @@ -39,6 +40,9 @@ class SafeBluetooth(private val context: Context, private val device: BluetoothD private var connectionCallback: ((Result) -> Unit)? = null private var lostConnectCallback: (() -> Unit)? = null + /// from characteristic UUIDs to the handler function for notfies + private val notifyHandlers = mutableMapOf Unit>() + /// 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 { @@ -64,6 +68,10 @@ class SafeBluetooth(private val context: Context, private val device: BluetoothD } } + // 0x2902 org.bluetooth.descriptor.gatt.client_characteristic_configuration.xml + private val configurationDescriptorUUID = + UUID.fromString("00002902-0000-1000-8000-00805f9b34fb") + init { context.registerReceiver( btStateReceiver, @@ -88,7 +96,6 @@ class SafeBluetooth(private val context: Context, private val device: BluetoothD } } - private val gattCallback = object : BluetoothGattCallback() { override fun onConnectionStateChange( @@ -112,7 +119,6 @@ class SafeBluetooth(private val context: Context, private val device: BluetoothD if (oldstate == BluetoothProfile.STATE_CONNECTED) { info("Lost connection - aborting current work") - /* Supposedly this reconnect attempt happens automatically "If the connection was established through an auto connect, Android will @@ -125,6 +131,9 @@ class SafeBluetooth(private val context: Context, private val device: BluetoothD */ failAllWork(IOException("Lost connection")) + // Cancel any notifications - because when the device comes back it might have forgotten about us + notifyHandlers.clear() + debug("calling lostConnect handler") lostConnectCallback?.invoke() @@ -167,6 +176,59 @@ class SafeBluetooth(private val context: Context, private val device: BluetoothD override fun onMtuChanged(gatt: BluetoothGatt, mtu: Int, status: Int) { completeWork(status, mtu) } + + /** + * Callback triggered as a result of a remote characteristic notification. + * + * @param gatt GATT client the characteristic is associated with + * @param characteristic Characteristic that has been updated as a result of a remote + * notification event. + */ + override fun onCharacteristicChanged( + gatt: BluetoothGatt, + characteristic: BluetoothGattCharacteristic + ) { + val handler = notifyHandlers.get(characteristic.uuid) + if (handler == null) + warn("Received notification from $characteristic, but no handler registered") + else { + exceptionReporter { + handler(characteristic) + } + } + } + + /** + * Callback indicating the result of a descriptor write operation. + * + * @param gatt GATT client invoked [BluetoothGatt.writeDescriptor] + * @param descriptor Descriptor that was writte to the associated remote device. + * @param status The result of the write operation [BluetoothGatt.GATT_SUCCESS] if the + * operation succeeds. + */ + override fun onDescriptorWrite( + gatt: BluetoothGatt, + descriptor: BluetoothGattDescriptor, + status: Int + ) { + completeWork(status, descriptor) + } + + /** + * Callback reporting the result of a descriptor read operation. + * + * @param gatt GATT client invoked [BluetoothGatt.readDescriptor] + * @param descriptor Descriptor that was read from the associated remote device. + * @param status [BluetoothGatt.GATT_SUCCESS] if the read operation was completed + * successfully + */ + override fun onDescriptorRead( + gatt: BluetoothGatt, + descriptor: BluetoothGattDescriptor, + status: Int + ) { + completeWork(status, descriptor) + } } @@ -251,7 +313,6 @@ class SafeBluetooth(private val context: Context, private val device: BluetoothD } } - /** * start a connection attempt. * @@ -322,7 +383,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, @@ -332,6 +393,17 @@ class SafeBluetooth(private val context: Context, private val device: BluetoothD fun writeCharacteristic(c: BluetoothGattCharacteristic): BluetoothGattCharacteristic = makeSync { queueWriteCharacteristic(c, it) } + private fun queueWriteDescriptor( + c: BluetoothGattDescriptor, + cont: Continuation + ) = queueWork("writeD", cont) { gatt!!.writeDescriptor(c) } + + fun asyncWriteDescriptor( + c: BluetoothGattDescriptor, + cb: (Result) -> Unit + ) = queueWriteDescriptor(c, CallbackContinuation(cb)) + + private fun closeConnection() { failAllWork(IOException("Connection closing")) @@ -348,5 +420,26 @@ class SafeBluetooth(private val context: Context, private val device: BluetoothD context.unregisterReceiver(btStateReceiver) } + + + /// asyncronously turn notification on/off for a characteristic + fun setNotify( + c: BluetoothGattCharacteristic, + enable: Boolean, + onChanged: (BluetoothGattCharacteristic) -> Unit + ) { + debug("starting setNotify(${c.uuid}, $enable)") + notifyHandlers[c.uuid] = onChanged + // c.writeType = BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT + gatt!!.setCharacteristicNotification(c, enable) + + // per https://stackoverflow.com/questions/27068673/subscribe-to-a-ble-gatt-notification-android + val descriptor: BluetoothGattDescriptor = c.getDescriptor(configurationDescriptorUUID)!! + descriptor.value = + if (enable) BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE else BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE + asyncWriteDescriptor(descriptor) { + debug("Notify enable=$enable completed") + } + } }