From 5b10ad13ec254acc23c1eb5b7d3fbedf9db52bc9 Mon Sep 17 00:00:00 2001 From: Andrei Vereha Date: Sun, 4 Apr 2021 12:40:28 +0200 Subject: [PATCH] dash ble: handle disconnects --- .../driver/comm/OmnipodDashBleManagerImpl.kt | 6 +- .../driver/comm/callbacks/BleCommCallbacks.kt | 14 ++- .../dash/driver/comm/session/Connection.kt | 90 +++++++++++-------- .../driver/comm/session/DisconnectHandler.kt | 5 ++ .../dash/driver/comm/session/EapSqn.kt | 4 + .../driver/comm/session/SessionEstablisher.kt | 2 +- .../dash/driver/comm/session/SessionKeys.kt | 4 +- 7 files changed, 80 insertions(+), 45 deletions(-) create mode 100644 omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/session/DisconnectHandler.kt diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/OmnipodDashBleManagerImpl.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/OmnipodDashBleManagerImpl.kt index 69ec21f599..9712f43fac 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/OmnipodDashBleManagerImpl.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/OmnipodDashBleManagerImpl.kt @@ -87,6 +87,7 @@ class OmnipodDashBleManagerImpl @Inject constructor( override fun getStatus(): ConnectionStatus { + // TODO is this used? var s: ConnectionStatus synchronized(status) { s = status @@ -113,7 +114,6 @@ class OmnipodDashBleManagerImpl @Inject constructor( emitter.onNext(PodEvent.EstablishingSession) establishSession(1.toByte()) emitter.onNext(PodEvent.Connected) - } else { emitter.onNext(PodEvent.AlreadyConnected(podAddress)) } @@ -217,10 +217,8 @@ class OmnipodDashBleManagerImpl @Inject constructor( } override fun disconnect() { - if (connection == null) { - aapsLogger.info(LTag.PUMPBTCOMM, "Trying to disconnect a null connection") - } connection?.disconnect() + ?: aapsLogger.info(LTag.PUMPBTCOMM, "Trying to disconnect a null connection") } companion object { diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/callbacks/BleCommCallbacks.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/callbacks/BleCommCallbacks.kt index 5866408a4a..8bdd998020 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/callbacks/BleCommCallbacks.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/callbacks/BleCommCallbacks.kt @@ -9,6 +9,7 @@ import info.nightscout.androidaps.logging.AAPSLogger import info.nightscout.androidaps.logging.LTag import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.io.CharacteristicType.Companion.byValue import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.io.IncomingPackets +import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.session.DisconnectHandler import info.nightscout.androidaps.utils.extensions.toHex import java.util.* import java.util.concurrent.BlockingQueue @@ -19,11 +20,19 @@ import java.util.concurrent.TimeUnit class BleCommCallbacks( private val aapsLogger: AAPSLogger, private val incomingPackets: IncomingPackets, + private val disconnectHandler: DisconnectHandler, ) : BluetoothGattCallback() { + // Synchronized because they can be: + // - read from various callbacks + // - written from resetConnection that is called onConnectionLost private var serviceDiscoveryComplete: CountDownLatch = CountDownLatch(1) + @Synchronized get + @Synchronized set private var connected: CountDownLatch = CountDownLatch(1) - private val writeQueue: BlockingQueue = LinkedBlockingQueue(1) + @Synchronized get + @Synchronized set + private val writeQueue: BlockingQueue = LinkedBlockingQueue() override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) { aapsLogger.debug(LTag.PUMPBTCOMM, "OnConnectionStateChange with status/state: $status/$newState") @@ -31,6 +40,9 @@ class BleCommCallbacks( if (newState == BluetoothProfile.STATE_CONNECTED && status == BluetoothGatt.GATT_SUCCESS) { connected.countDown() } + if (newState == BluetoothProfile.STATE_DISCONNECTED) { + disconnectHandler.onConnectionLost(status) + } } override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) { diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/session/Connection.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/session/Connection.kt index dd25d2e142..ee2a98abc7 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/session/Connection.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/session/Connection.kt @@ -5,6 +5,7 @@ import android.bluetooth.BluetoothGatt import android.bluetooth.BluetoothManager import android.bluetooth.BluetoothProfile import android.content.Context +import android.provider.ContactsContract import info.nightscout.androidaps.logging.AAPSLogger import info.nightscout.androidaps.logging.LTag import info.nightscout.androidaps.plugins.pump.omnipod.dash.BuildConfig @@ -13,7 +14,6 @@ import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.ServiceD import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.callbacks.BleCommCallbacks import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.endecrypt.EnDecrypt import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions.FailedToConnectException -import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions.SessionEstablishmentException import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.io.BleSendSuccess import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.io.CharacteristicType import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.io.CmdBleIO @@ -27,48 +27,51 @@ sealed class ConnectionState object Connected : ConnectionState() object NotConnected : ConnectionState() -class Connection(val podDevice: BluetoothDevice, private val aapsLogger: AAPSLogger, context: Context) { +class Connection(private val podDevice: BluetoothDevice, private val aapsLogger: AAPSLogger, context: Context) + : DisconnectHandler { private val incomingPackets = IncomingPackets() - private val bleCommCallbacks = BleCommCallbacks(aapsLogger, incomingPackets) + private val bleCommCallbacks = BleCommCallbacks(aapsLogger, incomingPackets, this) private val gattConnection: BluetoothGatt private val bluetoothManager: BluetoothManager = context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager + // The session is Synchronized because we can lose the connection right when establishing it + var session: Session? = null + @Synchronized get + @Synchronized set + private val cmdBleIO: CmdBleIO + private val dataBleIO: DataBleIO init { aapsLogger.debug(LTag.PUMPBTCOMM, "Connecting to ${podDevice.address}") val autoConnect = false + gattConnection = podDevice.connectGatt(context, autoConnect, bleCommCallbacks, BluetoothDevice.TRANSPORT_LE) + // OnDisconnect can be called after this point!!! val state = waitForConnection() if (state !is Connected) { throw FailedToConnectException(podDevice.address) } - } - - private val discoverer = ServiceDiscoverer(aapsLogger, gattConnection, bleCommCallbacks) - private val discoveredCharacteristics = discoverer.discoverServices() - private val cmdBleIO = CmdBleIO( - aapsLogger, - discoveredCharacteristics[CharacteristicType.CMD]!!, - incomingPackets - .cmdQueue, - gattConnection, - bleCommCallbacks - ) - private val dataBleIO = DataBleIO( - aapsLogger, - discoveredCharacteristics[CharacteristicType.DATA]!!, - incomingPackets - .dataQueue, - gattConnection, - bleCommCallbacks - ) - val msgIO = MessageIO(aapsLogger, cmdBleIO, dataBleIO) - var session: Session? = null - - init { + val discoverer = ServiceDiscoverer(aapsLogger, gattConnection, bleCommCallbacks) + val discoveredCharacteristics = discoverer.discoverServices() + cmdBleIO = CmdBleIO( + aapsLogger, + discoveredCharacteristics[CharacteristicType.CMD]!!, + incomingPackets + .cmdQueue, + gattConnection, + bleCommCallbacks + ) + dataBleIO = DataBleIO( + aapsLogger, + discoveredCharacteristics[CharacteristicType.DATA]!!, + incomingPackets + .dataQueue, + gattConnection, + bleCommCallbacks + ) val sendResult = cmdBleIO.hello() if (sendResult !is BleSendSuccess) { throw FailedToConnectException("Could not send HELLO command to ${podDevice.address}") @@ -77,8 +80,9 @@ class Connection(val podDevice: BluetoothDevice, private val aapsLogger: AAPSLog dataBleIO.readyToRead() } + val msgIO = MessageIO(aapsLogger, cmdBleIO, dataBleIO) + fun connect() { - // forces reconnection disconnect() if (!gattConnection.connect()) { @@ -88,9 +92,12 @@ class Connection(val podDevice: BluetoothDevice, private val aapsLogger: AAPSLog if (waitForConnection() is NotConnected) { throw FailedToConnectException(podDevice.address) } + + val discoverer = ServiceDiscoverer(aapsLogger, gattConnection, bleCommCallbacks) val discovered = discoverer.discoverServices() - dataBleIO.characteristic = discovered[CharacteristicType.DATA]!! - cmdBleIO.characteristic = discovered[CharacteristicType.CMD]!! + dataBleIO.characteristic = discovered[CharacteristicType.DATA]!! + cmdBleIO.characteristic = discovered[CharacteristicType.CMD]!! + cmdBleIO.hello() cmdBleIO.readyToRead() dataBleIO.readyToRead() @@ -98,8 +105,8 @@ class Connection(val podDevice: BluetoothDevice, private val aapsLogger: AAPSLog fun disconnect() { aapsLogger.debug(LTag.PUMPBTCOMM, "Disconnecting") - bleCommCallbacks.resetConnection() gattConnection.disconnect() + bleCommCallbacks.resetConnection() session = null } @@ -108,7 +115,7 @@ class Connection(val podDevice: BluetoothDevice, private val aapsLogger: AAPSLog bleCommCallbacks.waitForConnection(CONNECT_TIMEOUT_MS) } catch (e: InterruptedException) { // We are still going to check if connection was successful - aapsLogger.info(LTag.PUMPBTCOMM, "Interruped while waiting for connection") + aapsLogger.info(LTag.PUMPBTCOMM, "Interrupted while waiting for connection") } return connectionState() } @@ -123,11 +130,14 @@ class Connection(val podDevice: BluetoothDevice, private val aapsLogger: AAPSLog } fun establishSession(ltk: ByteArray, msgSeq: Byte, myId: Id, podID: Id, eapSqn: ByteArray): EapSqn? { - var eapAkaExchanger = SessionEstablisher(aapsLogger, msgIO, ltk, eapSqn, myId, podID, msgSeq) - var keys = eapAkaExchanger.negotiateSessionKeys() - return when (keys) { - is SessionNegotiationResynchronization -> - keys.syncronizedEapSqn + val eapAkaExchanger = SessionEstablisher(aapsLogger, msgIO, ltk, eapSqn, myId, podID, msgSeq) + return when (val keys = eapAkaExchanger.negotiateSessionKeys()) { + is SessionNegotiationResynchronization -> { + if (BuildConfig.DEBUG) { + aapsLogger.info(LTag.PUMPCOMM, "EAP AKA resynchronization: ${keys.synchronizedEapSqn}") + } + keys.synchronizedEapSqn + } is SessionKeys -> { if (BuildConfig.DEBUG) { aapsLogger.info(LTag.PUMPCOMM, "CK: ${keys.ck.toHex()}") @@ -145,6 +155,12 @@ class Connection(val podDevice: BluetoothDevice, private val aapsLogger: AAPSLog } } + // This will be called from a different thread !!! + override fun onConnectionLost(status: Int) { + aapsLogger.info(LTag.PUMPBTCOMM, "Lost connection with status: $status") + disconnect() + } + companion object { private const val CONNECT_TIMEOUT_MS = 7000 diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/session/DisconnectHandler.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/session/DisconnectHandler.kt new file mode 100644 index 0000000000..e307b2e224 --- /dev/null +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/session/DisconnectHandler.kt @@ -0,0 +1,5 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.session + +interface DisconnectHandler { + fun onConnectionLost(status: Int) +} \ No newline at end of file diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/session/EapSqn.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/session/EapSqn.kt index f1e8f0e118..b2e4f6769e 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/session/EapSqn.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/session/EapSqn.kt @@ -21,6 +21,10 @@ class EapSqn(val value: ByteArray) { ).long } + override fun toString(): String { + return "EapSqn(value=${toLong()})" + } + companion object { private const val SIZE = 6 private fun fromLong(v: Long): ByteArray { diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/session/SessionEstablisher.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/session/SessionEstablisher.kt index 2f50ea9149..ab425b9dcf 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/session/SessionEstablisher.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/session/SessionEstablisher.kt @@ -50,7 +50,7 @@ class SessionEstablisher( val newSqn = processChallengeResponse(challengeResponse) if (newSqn != null) { return SessionNegotiationResynchronization( - syncronizedEapSqn = newSqn, + synchronizedEapSqn = newSqn, msgSequenceNumber = msgSeq ) } diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/session/SessionKeys.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/session/SessionKeys.kt index cfd0be4c38..4aa1a4b481 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/session/SessionKeys.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/session/SessionKeys.kt @@ -10,5 +10,5 @@ data class SessionKeys(val ck: ByteArray, val nonce: Nonce, var msgSequenceNumbe } } -data class SessionNegotiationResynchronization(val syncronizedEapSqn: EapSqn?, val msgSequenceNumber: Byte) - :SessionNegotiationResponse() \ No newline at end of file +data class SessionNegotiationResynchronization(val synchronizedEapSqn: EapSqn, val msgSequenceNumber: Byte) + : SessionNegotiationResponse() \ No newline at end of file