dash ble: handle disconnects

This commit is contained in:
Andrei Vereha 2021-04-04 12:40:28 +02:00
parent 547454de6c
commit 5b10ad13ec
7 changed files with 80 additions and 45 deletions

View file

@ -87,6 +87,7 @@ class OmnipodDashBleManagerImpl @Inject constructor(
override fun getStatus(): ConnectionStatus { override fun getStatus(): ConnectionStatus {
// TODO is this used?
var s: ConnectionStatus var s: ConnectionStatus
synchronized(status) { synchronized(status) {
s = status s = status
@ -113,7 +114,6 @@ class OmnipodDashBleManagerImpl @Inject constructor(
emitter.onNext(PodEvent.EstablishingSession) emitter.onNext(PodEvent.EstablishingSession)
establishSession(1.toByte()) establishSession(1.toByte())
emitter.onNext(PodEvent.Connected) emitter.onNext(PodEvent.Connected)
} else { } else {
emitter.onNext(PodEvent.AlreadyConnected(podAddress)) emitter.onNext(PodEvent.AlreadyConnected(podAddress))
} }
@ -217,10 +217,8 @@ class OmnipodDashBleManagerImpl @Inject constructor(
} }
override fun disconnect() { override fun disconnect() {
if (connection == null) {
aapsLogger.info(LTag.PUMPBTCOMM, "Trying to disconnect a null connection")
}
connection?.disconnect() connection?.disconnect()
?: aapsLogger.info(LTag.PUMPBTCOMM, "Trying to disconnect a null connection")
} }
companion object { companion object {

View file

@ -9,6 +9,7 @@ import info.nightscout.androidaps.logging.AAPSLogger
import info.nightscout.androidaps.logging.LTag 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.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.io.IncomingPackets
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.session.DisconnectHandler
import info.nightscout.androidaps.utils.extensions.toHex import info.nightscout.androidaps.utils.extensions.toHex
import java.util.* import java.util.*
import java.util.concurrent.BlockingQueue import java.util.concurrent.BlockingQueue
@ -19,11 +20,19 @@ import java.util.concurrent.TimeUnit
class BleCommCallbacks( class BleCommCallbacks(
private val aapsLogger: AAPSLogger, private val aapsLogger: AAPSLogger,
private val incomingPackets: IncomingPackets, private val incomingPackets: IncomingPackets,
private val disconnectHandler: DisconnectHandler,
) : BluetoothGattCallback() { ) : BluetoothGattCallback() {
// Synchronized because they can be:
// - read from various callbacks
// - written from resetConnection that is called onConnectionLost
private var serviceDiscoveryComplete: CountDownLatch = CountDownLatch(1) private var serviceDiscoveryComplete: CountDownLatch = CountDownLatch(1)
@Synchronized get
@Synchronized set
private var connected: CountDownLatch = CountDownLatch(1) private var connected: CountDownLatch = CountDownLatch(1)
private val writeQueue: BlockingQueue<WriteConfirmation> = LinkedBlockingQueue(1) @Synchronized get
@Synchronized set
private val writeQueue: BlockingQueue<WriteConfirmation> = LinkedBlockingQueue()
override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) { override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) {
aapsLogger.debug(LTag.PUMPBTCOMM, "OnConnectionStateChange with status/state: $status/$newState") 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) { if (newState == BluetoothProfile.STATE_CONNECTED && status == BluetoothGatt.GATT_SUCCESS) {
connected.countDown() connected.countDown()
} }
if (newState == BluetoothProfile.STATE_DISCONNECTED) {
disconnectHandler.onConnectionLost(status)
}
} }
override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) { override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) {

View file

@ -5,6 +5,7 @@ import android.bluetooth.BluetoothGatt
import android.bluetooth.BluetoothManager import android.bluetooth.BluetoothManager
import android.bluetooth.BluetoothProfile import android.bluetooth.BluetoothProfile
import android.content.Context import android.content.Context
import android.provider.ContactsContract
import info.nightscout.androidaps.logging.AAPSLogger import info.nightscout.androidaps.logging.AAPSLogger
import info.nightscout.androidaps.logging.LTag import info.nightscout.androidaps.logging.LTag
import info.nightscout.androidaps.plugins.pump.omnipod.dash.BuildConfig 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.callbacks.BleCommCallbacks
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.endecrypt.EnDecrypt 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.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.BleSendSuccess
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.io.CharacteristicType import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.io.CharacteristicType
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.io.CmdBleIO import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.io.CmdBleIO
@ -27,29 +27,36 @@ sealed class ConnectionState
object Connected : ConnectionState() object Connected : ConnectionState()
object NotConnected : 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 incomingPackets = IncomingPackets()
private val bleCommCallbacks = BleCommCallbacks(aapsLogger, incomingPackets) private val bleCommCallbacks = BleCommCallbacks(aapsLogger, incomingPackets, this)
private val gattConnection: BluetoothGatt private val gattConnection: BluetoothGatt
private val bluetoothManager: BluetoothManager = private val bluetoothManager: BluetoothManager =
context.getSystemService(Context.BLUETOOTH_SERVICE) as 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 { init {
aapsLogger.debug(LTag.PUMPBTCOMM, "Connecting to ${podDevice.address}") aapsLogger.debug(LTag.PUMPBTCOMM, "Connecting to ${podDevice.address}")
val autoConnect = false val autoConnect = false
gattConnection = podDevice.connectGatt(context, autoConnect, bleCommCallbacks, BluetoothDevice.TRANSPORT_LE) gattConnection = podDevice.connectGatt(context, autoConnect, bleCommCallbacks, BluetoothDevice.TRANSPORT_LE)
// OnDisconnect can be called after this point!!!
val state = waitForConnection() val state = waitForConnection()
if (state !is Connected) { if (state !is Connected) {
throw FailedToConnectException(podDevice.address) throw FailedToConnectException(podDevice.address)
} }
} val discoverer = ServiceDiscoverer(aapsLogger, gattConnection, bleCommCallbacks)
val discoveredCharacteristics = discoverer.discoverServices()
private val discoverer = ServiceDiscoverer(aapsLogger, gattConnection, bleCommCallbacks) cmdBleIO = CmdBleIO(
private val discoveredCharacteristics = discoverer.discoverServices()
private val cmdBleIO = CmdBleIO(
aapsLogger, aapsLogger,
discoveredCharacteristics[CharacteristicType.CMD]!!, discoveredCharacteristics[CharacteristicType.CMD]!!,
incomingPackets incomingPackets
@ -57,7 +64,7 @@ class Connection(val podDevice: BluetoothDevice, private val aapsLogger: AAPSLog
gattConnection, gattConnection,
bleCommCallbacks bleCommCallbacks
) )
private val dataBleIO = DataBleIO( dataBleIO = DataBleIO(
aapsLogger, aapsLogger,
discoveredCharacteristics[CharacteristicType.DATA]!!, discoveredCharacteristics[CharacteristicType.DATA]!!,
incomingPackets incomingPackets
@ -65,10 +72,6 @@ class Connection(val podDevice: BluetoothDevice, private val aapsLogger: AAPSLog
gattConnection, gattConnection,
bleCommCallbacks bleCommCallbacks
) )
val msgIO = MessageIO(aapsLogger, cmdBleIO, dataBleIO)
var session: Session? = null
init {
val sendResult = cmdBleIO.hello() val sendResult = cmdBleIO.hello()
if (sendResult !is BleSendSuccess) { if (sendResult !is BleSendSuccess) {
throw FailedToConnectException("Could not send HELLO command to ${podDevice.address}") 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() dataBleIO.readyToRead()
} }
val msgIO = MessageIO(aapsLogger, cmdBleIO, dataBleIO)
fun connect() { fun connect() {
// forces reconnection
disconnect() disconnect()
if (!gattConnection.connect()) { if (!gattConnection.connect()) {
@ -88,9 +92,12 @@ class Connection(val podDevice: BluetoothDevice, private val aapsLogger: AAPSLog
if (waitForConnection() is NotConnected) { if (waitForConnection() is NotConnected) {
throw FailedToConnectException(podDevice.address) throw FailedToConnectException(podDevice.address)
} }
val discoverer = ServiceDiscoverer(aapsLogger, gattConnection, bleCommCallbacks)
val discovered = discoverer.discoverServices() val discovered = discoverer.discoverServices()
dataBleIO.characteristic = discovered[CharacteristicType.DATA]!! dataBleIO.characteristic = discovered[CharacteristicType.DATA]!!
cmdBleIO.characteristic = discovered[CharacteristicType.CMD]!! cmdBleIO.characteristic = discovered[CharacteristicType.CMD]!!
cmdBleIO.hello() cmdBleIO.hello()
cmdBleIO.readyToRead() cmdBleIO.readyToRead()
dataBleIO.readyToRead() dataBleIO.readyToRead()
@ -98,8 +105,8 @@ class Connection(val podDevice: BluetoothDevice, private val aapsLogger: AAPSLog
fun disconnect() { fun disconnect() {
aapsLogger.debug(LTag.PUMPBTCOMM, "Disconnecting") aapsLogger.debug(LTag.PUMPBTCOMM, "Disconnecting")
bleCommCallbacks.resetConnection()
gattConnection.disconnect() gattConnection.disconnect()
bleCommCallbacks.resetConnection()
session = null session = null
} }
@ -108,7 +115,7 @@ class Connection(val podDevice: BluetoothDevice, private val aapsLogger: AAPSLog
bleCommCallbacks.waitForConnection(CONNECT_TIMEOUT_MS) bleCommCallbacks.waitForConnection(CONNECT_TIMEOUT_MS)
} catch (e: InterruptedException) { } catch (e: InterruptedException) {
// We are still going to check if connection was successful // 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() 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? { fun establishSession(ltk: ByteArray, msgSeq: Byte, myId: Id, podID: Id, eapSqn: ByteArray): EapSqn? {
var eapAkaExchanger = SessionEstablisher(aapsLogger, msgIO, ltk, eapSqn, myId, podID, msgSeq) val eapAkaExchanger = SessionEstablisher(aapsLogger, msgIO, ltk, eapSqn, myId, podID, msgSeq)
var keys = eapAkaExchanger.negotiateSessionKeys() return when (val keys = eapAkaExchanger.negotiateSessionKeys()) {
return when (keys) { is SessionNegotiationResynchronization -> {
is SessionNegotiationResynchronization -> if (BuildConfig.DEBUG) {
keys.syncronizedEapSqn aapsLogger.info(LTag.PUMPCOMM, "EAP AKA resynchronization: ${keys.synchronizedEapSqn}")
}
keys.synchronizedEapSqn
}
is SessionKeys -> { is SessionKeys -> {
if (BuildConfig.DEBUG) { if (BuildConfig.DEBUG) {
aapsLogger.info(LTag.PUMPCOMM, "CK: ${keys.ck.toHex()}") 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 { companion object {
private const val CONNECT_TIMEOUT_MS = 7000 private const val CONNECT_TIMEOUT_MS = 7000

View file

@ -0,0 +1,5 @@
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.session
interface DisconnectHandler {
fun onConnectionLost(status: Int)
}

View file

@ -21,6 +21,10 @@ class EapSqn(val value: ByteArray) {
).long ).long
} }
override fun toString(): String {
return "EapSqn(value=${toLong()})"
}
companion object { companion object {
private const val SIZE = 6 private const val SIZE = 6
private fun fromLong(v: Long): ByteArray { private fun fromLong(v: Long): ByteArray {

View file

@ -50,7 +50,7 @@ class SessionEstablisher(
val newSqn = processChallengeResponse(challengeResponse) val newSqn = processChallengeResponse(challengeResponse)
if (newSqn != null) { if (newSqn != null) {
return SessionNegotiationResynchronization( return SessionNegotiationResynchronization(
syncronizedEapSqn = newSqn, synchronizedEapSqn = newSqn,
msgSequenceNumber = msgSeq msgSequenceNumber = msgSeq
) )
} }

View file

@ -10,5 +10,5 @@ data class SessionKeys(val ck: ByteArray, val nonce: Nonce, var msgSequenceNumbe
} }
} }
data class SessionNegotiationResynchronization(val syncronizedEapSqn: EapSqn?, val msgSequenceNumber: Byte) data class SessionNegotiationResynchronization(val synchronizedEapSqn: EapSqn, val msgSequenceNumber: Byte)
: SessionNegotiationResponse() : SessionNegotiationResponse()