improve error handling
This commit is contained in:
parent
ae08e43109
commit
4b49392200
38 changed files with 795 additions and 556 deletions
|
@ -1,34 +1,23 @@
|
||||||
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm
|
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm
|
||||||
|
|
||||||
import android.bluetooth.BluetoothAdapter
|
import android.bluetooth.BluetoothAdapter
|
||||||
import android.bluetooth.BluetoothDevice
|
|
||||||
import android.bluetooth.BluetoothGatt
|
|
||||||
import android.bluetooth.BluetoothManager
|
import android.bluetooth.BluetoothManager
|
||||||
import android.bluetooth.BluetoothProfile
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.os.Message
|
||||||
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
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.callbacks.BleCommCallbacks
|
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.command.BleCommandHello
|
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.endecrypt.EnDecrypt
|
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions.*
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions.*
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.io.BleIO
|
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.io.CharacteristicType
|
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.message.MessageIO
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.message.MessageIO
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.pair.LTKExchanger
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.pair.LTKExchanger
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.scan.PodScanner
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.scan.PodScanner
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.session.Session
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.session.*
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.session.SessionEstablisher
|
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.session.SessionKeys
|
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.status.ConnectionStatus
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.status.ConnectionStatus
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.event.PodEvent
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.event.PodEvent
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command.base.Command
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command.base.Command
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.state.OmnipodDashPodStateManager
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.state.OmnipodDashPodStateManager
|
||||||
import info.nightscout.androidaps.utils.extensions.toHex
|
import info.nightscout.androidaps.utils.extensions.toHex
|
||||||
import io.reactivex.Observable
|
import io.reactivex.Observable
|
||||||
import java.util.concurrent.BlockingQueue
|
|
||||||
import java.util.concurrent.LinkedBlockingDeque
|
|
||||||
import java.util.concurrent.TimeoutException
|
import java.util.concurrent.TimeoutException
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
@ -40,88 +29,50 @@ class OmnipodDashBleManagerImpl @Inject constructor(
|
||||||
private val podState: OmnipodDashPodStateManager
|
private val podState: OmnipodDashPodStateManager
|
||||||
) : OmnipodDashBleManager {
|
) : OmnipodDashBleManager {
|
||||||
|
|
||||||
|
// TODO: add busy AtomicBoolean
|
||||||
private val bluetoothManager: BluetoothManager =
|
private val bluetoothManager: BluetoothManager =
|
||||||
context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
|
context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
|
||||||
private val bluetoothAdapter: BluetoothAdapter = bluetoothManager.adapter
|
private val bluetoothAdapter: BluetoothAdapter = bluetoothManager.adapter
|
||||||
private var sessionKeys: SessionKeys? = null
|
private var connection: Connection? = null
|
||||||
private var msgIO: MessageIO? = null
|
|
||||||
private var gatt: BluetoothGatt? = null
|
|
||||||
private var status: ConnectionStatus = ConnectionStatus.IDLE
|
private var status: ConnectionStatus = ConnectionStatus.IDLE
|
||||||
private val myId = Id.fromInt(CONTROLLER_ID)
|
private val myId = Id.fromInt(CONTROLLER_ID)
|
||||||
private val uniqueId = podState.uniqueId
|
private val uniqueId = podState.uniqueId
|
||||||
private val podId = uniqueId?.let(Id::fromLong)
|
private val podId = uniqueId?.let(Id::fromLong)
|
||||||
?: myId.increment() // pod not activated
|
?: myId.increment() // pod not activated
|
||||||
|
|
||||||
@Throws(
|
|
||||||
FailedToConnectException::class,
|
|
||||||
CouldNotSendBleException::class,
|
|
||||||
InterruptedException::class,
|
|
||||||
BleIOBusyException::class,
|
|
||||||
TimeoutException::class,
|
|
||||||
CouldNotConfirmWriteException::class,
|
|
||||||
CouldNotEnableNotifications::class,
|
|
||||||
DescriptorNotFoundException::class,
|
|
||||||
CouldNotConfirmDescriptorWriteException::class
|
|
||||||
)
|
|
||||||
|
|
||||||
private fun connect(podDevice: BluetoothDevice): BleIO {
|
|
||||||
val incomingPackets: Map<CharacteristicType, BlockingQueue<ByteArray>> =
|
|
||||||
mapOf(
|
|
||||||
CharacteristicType.CMD to LinkedBlockingDeque(),
|
|
||||||
CharacteristicType.DATA to LinkedBlockingDeque()
|
|
||||||
)
|
|
||||||
val bleCommCallbacks = BleCommCallbacks(aapsLogger, incomingPackets)
|
|
||||||
aapsLogger.debug(LTag.PUMPBTCOMM, "Connecting to ${podDevice.address}")
|
|
||||||
val autoConnect = false // TODO: check what to use here
|
|
||||||
|
|
||||||
val gattConnection = podDevice.connectGatt(context, autoConnect, bleCommCallbacks, BluetoothDevice.TRANSPORT_LE)
|
|
||||||
bleCommCallbacks.waitForConnection(CONNECT_TIMEOUT_MS)
|
|
||||||
val connectionState = bluetoothManager.getConnectionState(podDevice, BluetoothProfile.GATT)
|
|
||||||
aapsLogger.debug(LTag.PUMPBTCOMM, "GATT connection state: $connectionState")
|
|
||||||
if (connectionState != BluetoothProfile.STATE_CONNECTED) {
|
|
||||||
throw FailedToConnectException(podDevice.address)
|
|
||||||
}
|
|
||||||
val discoverer = ServiceDiscoverer(aapsLogger, gattConnection, bleCommCallbacks)
|
|
||||||
val chars = discoverer.discoverServices()
|
|
||||||
val bleIO = BleIO(aapsLogger, chars, incomingPackets, gattConnection, bleCommCallbacks)
|
|
||||||
bleIO.sendAndConfirmPacket(CharacteristicType.CMD, BleCommandHello(CONTROLLER_ID).data)
|
|
||||||
bleIO.readyToRead()
|
|
||||||
gatt = gattConnection
|
|
||||||
return bleIO
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun sendCommand(cmd: Command): Observable<PodEvent> = Observable.create { emitter ->
|
override fun sendCommand(cmd: Command): Observable<PodEvent> = Observable.create { emitter ->
|
||||||
try {
|
val conn = connection ?: throw NotConnectedException("Not connected")
|
||||||
val keys = sessionKeys
|
|
||||||
val mIO = msgIO
|
val session = conn.session ?: throw NotConnectedException("Missing session")
|
||||||
if (keys == null || mIO == null) {
|
|
||||||
throw Exception("Not connected")
|
emitter.onNext(PodEvent.CommandSending(cmd))
|
||||||
|
|
||||||
|
val sendResult = session.sendCommand(cmd)
|
||||||
|
when(sendResult) {
|
||||||
|
is CommandSendErrorSending -> {
|
||||||
|
emitter.tryOnError(CouldNotSendCommandException())
|
||||||
|
return@create
|
||||||
}
|
}
|
||||||
emitter.onNext(PodEvent.CommandSending(cmd))
|
is CommandSendSuccess ->
|
||||||
// TODO switch to RX
|
emitter.onNext(PodEvent.CommandSent(cmd))
|
||||||
emitter.onNext(PodEvent.CommandSent(cmd))
|
is CommandSendErrorConfirming ->
|
||||||
|
emitter.onNext(PodEvent.CommandSendNotConfirmed(cmd))
|
||||||
val enDecrypt = EnDecrypt(
|
|
||||||
aapsLogger,
|
|
||||||
keys.nonce,
|
|
||||||
keys.ck
|
|
||||||
)
|
|
||||||
|
|
||||||
val session = Session(
|
|
||||||
aapsLogger = aapsLogger,
|
|
||||||
msgIO = mIO,
|
|
||||||
myId = myId,
|
|
||||||
podId = podId,
|
|
||||||
sessionKeys = keys,
|
|
||||||
enDecrypt = enDecrypt
|
|
||||||
)
|
|
||||||
val response = session.sendCommand(cmd)
|
|
||||||
emitter.onNext(PodEvent.ResponseReceived(response))
|
|
||||||
|
|
||||||
emitter.onComplete()
|
|
||||||
} catch (ex: Exception) {
|
|
||||||
emitter.tryOnError(ex)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val readResult = session.readAndAckCommandResponse()
|
||||||
|
when (readResult){
|
||||||
|
is CommandReceiveSuccess ->
|
||||||
|
emitter.onNext(PodEvent.ResponseReceived(readResult.result))
|
||||||
|
|
||||||
|
is CommandAckError ->
|
||||||
|
emitter.onNext(PodEvent.ResponseReceived(readResult.result))
|
||||||
|
|
||||||
|
is CommandReceiveError -> {
|
||||||
|
emitter.tryOnError(MessageIOException("Could not read response: $readResult"))
|
||||||
|
return@create
|
||||||
|
}
|
||||||
|
}
|
||||||
|
emitter.onComplete()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getStatus(): ConnectionStatus {
|
override fun getStatus(): ConnectionStatus {
|
||||||
|
@ -132,42 +83,23 @@ class OmnipodDashBleManagerImpl @Inject constructor(
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(
|
|
||||||
InterruptedException::class,
|
|
||||||
ScanFailException::class,
|
|
||||||
FailedToConnectException::class,
|
|
||||||
CouldNotSendBleException::class,
|
|
||||||
BleIOBusyException::class,
|
|
||||||
TimeoutException::class,
|
|
||||||
CouldNotConfirmWriteException::class,
|
|
||||||
CouldNotEnableNotifications::class,
|
|
||||||
DescriptorNotFoundException::class,
|
|
||||||
CouldNotConfirmDescriptorWriteException::class
|
|
||||||
)
|
|
||||||
|
|
||||||
override fun connect(): Observable<PodEvent> = Observable.create { emitter ->
|
override fun connect(): Observable<PodEvent> = Observable.create { emitter ->
|
||||||
try {
|
try {
|
||||||
|
emitter.onNext(PodEvent.BluetoothConnecting)
|
||||||
|
|
||||||
val podAddress =
|
val podAddress =
|
||||||
podState.bluetoothAddress
|
podState.bluetoothAddress
|
||||||
?: throw FailedToConnectException("Missing bluetoothAddress, activate the pod first")
|
?: throw FailedToConnectException("Missing bluetoothAddress, activate the pod first")
|
||||||
// check if already connected
|
|
||||||
val podDevice = bluetoothAdapter.getRemoteDevice(podAddress)
|
val podDevice = bluetoothAdapter.getRemoteDevice(podAddress)
|
||||||
val connectionState = bluetoothManager.getConnectionState(podDevice, BluetoothProfile.GATT)
|
val conn = connection
|
||||||
aapsLogger.debug(LTag.PUMPBTCOMM, "GATT connection state: $connectionState")
|
?: Connection(podDevice, aapsLogger, context)
|
||||||
if (connectionState == BluetoothProfile.STATE_CONNECTED) {
|
connection = conn
|
||||||
emitter.onNext(PodEvent.AlreadyConnected(podAddress))
|
if (conn.connectionState() is Connected) {
|
||||||
|
emitter.onNext(PodEvent.Connected)
|
||||||
emitter.onComplete()
|
emitter.onComplete()
|
||||||
return@create
|
return@create
|
||||||
}
|
}
|
||||||
|
conn.connect()
|
||||||
emitter.onNext(PodEvent.BluetoothConnecting)
|
|
||||||
if (msgIO != null) {
|
|
||||||
disconnect()
|
|
||||||
}
|
|
||||||
val bleIO = connect(podDevice)
|
|
||||||
val mIO = MessageIO(aapsLogger, bleIO)
|
|
||||||
msgIO = mIO
|
|
||||||
emitter.onNext(PodEvent.BluetoothConnected(podAddress))
|
emitter.onNext(PodEvent.BluetoothConnected(podAddress))
|
||||||
|
|
||||||
emitter.onNext(PodEvent.EstablishingSession)
|
emitter.onNext(PodEvent.EstablishingSession)
|
||||||
|
@ -182,23 +114,15 @@ class OmnipodDashBleManagerImpl @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun establishSession(msgSeq: Byte) {
|
private fun establishSession(msgSeq: Byte) {
|
||||||
val mIO = msgIO ?: throw FailedToConnectException("connection lost")
|
val conn = connection ?: throw FailedToConnectException("connection lost")
|
||||||
val ltk: ByteArray = podState.ltk ?: throw FailedToConnectException("Missing LTK, activate the pod first")
|
val ltk: ByteArray = podState.ltk ?: throw FailedToConnectException("Missing LTK, activate the pod first")
|
||||||
val uniqueId = podState.uniqueId
|
val uniqueId = podState.uniqueId
|
||||||
val podId = uniqueId?.let { Id.fromLong(uniqueId) }
|
val podId = uniqueId?.let { Id.fromLong(uniqueId) }
|
||||||
?: myId.increment() // pod not activated
|
?: myId.increment() // pod not activated
|
||||||
|
|
||||||
val eapSqn = podState.increaseEapAkaSequenceNumber()
|
val eapSqn = podState.increaseEapAkaSequenceNumber()
|
||||||
val eapAkaExchanger = SessionEstablisher(aapsLogger, mIO, ltk, eapSqn, myId, podId, msgSeq)
|
conn.establishSession(ltk, msgSeq, myId, podId, eapSqn)
|
||||||
val keys = eapAkaExchanger.negotiateSessionKeys()
|
|
||||||
podState.commitEapAkaSequenceNumber()
|
podState.commitEapAkaSequenceNumber()
|
||||||
|
|
||||||
if (BuildConfig.DEBUG) {
|
|
||||||
aapsLogger.info(LTag.PUMPCOMM, "CK: ${keys.ck.toHex()}")
|
|
||||||
aapsLogger.info(LTag.PUMPCOMM, "msgSequenceNumber: ${keys.msgSequenceNumber}")
|
|
||||||
aapsLogger.info(LTag.PUMPCOMM, "Nonce: ${keys.nonce}")
|
|
||||||
}
|
|
||||||
sessionKeys = keys
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun pairNewPod(): Observable<PodEvent> = Observable.create { emitter ->
|
override fun pairNewPod(): Observable<PodEvent> = Observable.create { emitter ->
|
||||||
|
@ -209,7 +133,7 @@ class OmnipodDashBleManagerImpl @Inject constructor(
|
||||||
emitter.onComplete()
|
emitter.onComplete()
|
||||||
return@create
|
return@create
|
||||||
}
|
}
|
||||||
aapsLogger.info(LTag.PUMPBTCOMM, "starting new pod activation")
|
aapsLogger.info(LTag.PUMPBTCOMM, "Starting new pod activation")
|
||||||
|
|
||||||
emitter.onNext(PodEvent.Scanning)
|
emitter.onNext(PodEvent.Scanning)
|
||||||
val podScanner = PodScanner(aapsLogger, bluetoothAdapter)
|
val podScanner = PodScanner(aapsLogger, bluetoothAdapter)
|
||||||
|
@ -221,13 +145,17 @@ class OmnipodDashBleManagerImpl @Inject constructor(
|
||||||
|
|
||||||
emitter.onNext(PodEvent.BluetoothConnecting)
|
emitter.onNext(PodEvent.BluetoothConnecting)
|
||||||
val podDevice = bluetoothAdapter.getRemoteDevice(podAddress)
|
val podDevice = bluetoothAdapter.getRemoteDevice(podAddress)
|
||||||
val bleIO = connect(podDevice)
|
val conn = Connection(podDevice, aapsLogger, context)
|
||||||
val mIO = MessageIO(aapsLogger, bleIO)
|
connection = conn
|
||||||
msgIO = mIO
|
|
||||||
emitter.onNext(PodEvent.BluetoothConnected(podAddress))
|
emitter.onNext(PodEvent.BluetoothConnected(podAddress))
|
||||||
|
|
||||||
emitter.onNext(PodEvent.Pairing)
|
emitter.onNext(PodEvent.Pairing)
|
||||||
val ltkExchanger = LTKExchanger(aapsLogger, mIO, myId, podId, Id.fromLong(PodScanner.POD_ID_NOT_ACTIVATED))
|
val ltkExchanger = LTKExchanger(
|
||||||
|
aapsLogger, conn.msgIO, myId, podId, Id.fromLong(
|
||||||
|
PodScanner
|
||||||
|
.POD_ID_NOT_ACTIVATED
|
||||||
|
)
|
||||||
|
)
|
||||||
val pairResult = ltkExchanger.negotiateLTK()
|
val pairResult = ltkExchanger.negotiateLTK()
|
||||||
emitter.onNext(PodEvent.Paired(podId))
|
emitter.onNext(PodEvent.Paired(podId))
|
||||||
podState.updateFromPairing(podId, pairResult)
|
podState.updateFromPairing(podId, pairResult)
|
||||||
|
@ -246,16 +174,14 @@ class OmnipodDashBleManagerImpl @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun disconnect() {
|
override fun disconnect() {
|
||||||
val localGatt = gatt
|
if (connection == null) {
|
||||||
localGatt?.close() // TODO: use disconnect?
|
aapsLogger.info(LTag.PUMPBTCOMM, "Trying to disconnect a null connection")
|
||||||
gatt = null
|
}
|
||||||
msgIO = null
|
connection?.disconnect()
|
||||||
sessionKeys = null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
private const val CONNECT_TIMEOUT_MS = 7000
|
|
||||||
const val CONTROLLER_ID = 4242 // TODO read from preferences or somewhere else.
|
const val CONTROLLER_ID = 4242 // TODO read from preferences or somewhere else.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,26 +7,23 @@ import android.bluetooth.BluetoothGattDescriptor
|
||||||
import android.bluetooth.BluetoothProfile
|
import android.bluetooth.BluetoothProfile
|
||||||
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.driver.comm.exceptions.CouldNotConfirmDescriptorWriteException
|
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions.CouldNotConfirmWriteException
|
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.io.CharacteristicType
|
|
||||||
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.utils.extensions.toHex
|
import info.nightscout.androidaps.utils.extensions.toHex
|
||||||
|
import java.util.*
|
||||||
import java.util.concurrent.BlockingQueue
|
import java.util.concurrent.BlockingQueue
|
||||||
import java.util.concurrent.CountDownLatch
|
import java.util.concurrent.CountDownLatch
|
||||||
import java.util.concurrent.LinkedBlockingQueue
|
import java.util.concurrent.LinkedBlockingQueue
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import java.util.concurrent.TimeoutException
|
|
||||||
|
|
||||||
class BleCommCallbacks(
|
class BleCommCallbacks(
|
||||||
private val aapsLogger: AAPSLogger,
|
private val aapsLogger: AAPSLogger,
|
||||||
private val incomingPackets: Map<CharacteristicType, BlockingQueue<ByteArray>>
|
private val incomingPackets: IncomingPackets,
|
||||||
) : BluetoothGattCallback() {
|
) : BluetoothGattCallback() {
|
||||||
|
|
||||||
private val serviceDiscoveryComplete: CountDownLatch = CountDownLatch(1)
|
private val serviceDiscoveryComplete: CountDownLatch = CountDownLatch(1)
|
||||||
private val connected: CountDownLatch = CountDownLatch(1)
|
private val connected: CountDownLatch = CountDownLatch(1)
|
||||||
private val writeQueue: BlockingQueue<CharacteristicWriteConfirmation> = LinkedBlockingQueue(1)
|
private val writeQueue: BlockingQueue<WriteConfirmation> = LinkedBlockingQueue(1)
|
||||||
private val descriptorWriteQueue: BlockingQueue<DescriptorWriteConfirmation> = LinkedBlockingQueue(1)
|
|
||||||
|
|
||||||
override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) {
|
override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) {
|
||||||
super.onConnectionStateChange(gatt, status, newState)
|
super.onConnectionStateChange(gatt, status, newState)
|
||||||
|
@ -54,52 +51,32 @@ class BleCommCallbacks(
|
||||||
serviceDiscoveryComplete.await(timeoutMs.toLong(), TimeUnit.MILLISECONDS)
|
serviceDiscoveryComplete.await(timeoutMs.toLong(), TimeUnit.MILLISECONDS)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(InterruptedException::class, TimeoutException::class, CouldNotConfirmWriteException::class)
|
fun confirmWrite(expectedPayload: ByteArray, expectedUUID: String, timeoutMs: Long) : WriteConfirmation{
|
||||||
fun confirmWrite(expectedPayload: ByteArray, timeoutMs: Long) {
|
try {
|
||||||
val received: CharacteristicWriteConfirmation = writeQueue.poll(timeoutMs, TimeUnit.MILLISECONDS)
|
return when(val received = writeQueue.poll(timeoutMs, TimeUnit.MILLISECONDS) ) {
|
||||||
?: throw TimeoutException()
|
null -> return WriteConfirmationError("Timeout waiting for writeConfirmation")
|
||||||
|
is WriteConfirmationSuccess ->
|
||||||
when (received) {
|
if (expectedPayload.contentEquals(received.payload) &&
|
||||||
is CharacteristicWriteConfirmationPayload -> confirmWritePayload(expectedPayload, received)
|
expectedUUID == received.uuid) {
|
||||||
is CharacteristicWriteConfirmationError -> throw CouldNotConfirmWriteException(received.status)
|
received
|
||||||
|
} else {
|
||||||
|
aapsLogger.warn(
|
||||||
|
LTag.PUMPBTCOMM,
|
||||||
|
"Could not confirm write. Got " + received.payload.toHex() + ".Excepted: " + expectedPayload.toHex()
|
||||||
|
)
|
||||||
|
WriteConfirmationError("Received incorrect writeConfirmation")
|
||||||
|
}
|
||||||
|
is WriteConfirmationError ->
|
||||||
|
received
|
||||||
|
}
|
||||||
|
}catch (e: InterruptedException) {
|
||||||
|
return WriteConfirmationError("Interrupted waiting for confirmation")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun confirmWritePayload(expectedPayload: ByteArray, received: CharacteristicWriteConfirmationPayload) {
|
|
||||||
if (!expectedPayload.contentEquals(received.payload)) {
|
|
||||||
aapsLogger.warn(
|
|
||||||
LTag.PUMPBTCOMM,
|
|
||||||
"Could not confirm write. Got " + received.payload.toHex() + ".Excepted: " + expectedPayload.toHex()
|
|
||||||
)
|
|
||||||
throw CouldNotConfirmWriteException(expectedPayload, received.payload)
|
|
||||||
}
|
|
||||||
aapsLogger.debug(LTag.PUMPBTCOMM, "Confirmed write with value: " + received.payload.toHex())
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCharacteristicWrite(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, status: Int) {
|
override fun onCharacteristicWrite(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, status: Int) {
|
||||||
super.onCharacteristicWrite(gatt, characteristic, status)
|
super.onCharacteristicWrite(gatt, characteristic, status)
|
||||||
val writeConfirmation = if (status == BluetoothGatt.GATT_SUCCESS) {
|
onWrite(status, characteristic.uuid, characteristic.value)
|
||||||
CharacteristicWriteConfirmationPayload(characteristic.value)
|
|
||||||
} else {
|
|
||||||
CharacteristicWriteConfirmationError(status)
|
|
||||||
}
|
|
||||||
aapsLogger.debug(
|
|
||||||
LTag.PUMPBTCOMM,
|
|
||||||
"OnCharacteristicWrite with status/char/value " +
|
|
||||||
status + "/" + byValue(characteristic.uuid.toString()) + "/" + characteristic.value.toHex()
|
|
||||||
)
|
|
||||||
try {
|
|
||||||
if (writeQueue.size > 0) {
|
|
||||||
aapsLogger.warn(LTag.PUMPBTCOMM, "Write confirm queue should be empty. found: " + writeQueue.size)
|
|
||||||
writeQueue.clear()
|
|
||||||
}
|
|
||||||
val offered = writeQueue.offer(writeConfirmation, WRITE_CONFIRM_TIMEOUT_MS.toLong(), TimeUnit.MILLISECONDS)
|
|
||||||
if (!offered) {
|
|
||||||
aapsLogger.warn(LTag.PUMPBTCOMM, "Received delayed write confirmation")
|
|
||||||
}
|
|
||||||
} catch (e: InterruptedException) {
|
|
||||||
aapsLogger.warn(LTag.PUMPBTCOMM, "Interrupted while sending write confirmation")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCharacteristicChanged(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic) {
|
override fun onCharacteristicChanged(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic) {
|
||||||
|
@ -112,57 +89,52 @@ class BleCommCallbacks(
|
||||||
characteristicType + "/" +
|
characteristicType + "/" +
|
||||||
payload.toHex()
|
payload.toHex()
|
||||||
)
|
)
|
||||||
incomingPackets[characteristicType]!!.add(payload)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Throws(InterruptedException::class, CouldNotConfirmDescriptorWriteException::class)
|
incomingPackets.byCharacteristicType(characteristicType).add(payload)
|
||||||
fun confirmWriteDescriptor(descriptorUUID: String, timeoutMs: Long) {
|
|
||||||
val confirmed: DescriptorWriteConfirmation = descriptorWriteQueue.poll(
|
|
||||||
timeoutMs,
|
|
||||||
TimeUnit.MILLISECONDS
|
|
||||||
)
|
|
||||||
?: throw TimeoutException()
|
|
||||||
when (confirmed) {
|
|
||||||
is DescriptorWriteConfirmationError -> throw CouldNotConfirmWriteException(confirmed.status)
|
|
||||||
is DescriptorWriteConfirmationUUID ->
|
|
||||||
if (confirmed.uuid != descriptorUUID) {
|
|
||||||
aapsLogger.warn(
|
|
||||||
LTag.PUMPBTCOMM,
|
|
||||||
"Could not confirm descriptor write. Got ${confirmed.uuid}. Expected: $descriptorUUID"
|
|
||||||
)
|
|
||||||
throw CouldNotConfirmDescriptorWriteException(descriptorUUID, confirmed.uuid)
|
|
||||||
} else {
|
|
||||||
aapsLogger.debug(LTag.PUMPBTCOMM, "Confirmed descriptor write : " + confirmed.uuid)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDescriptorWrite(gatt: BluetoothGatt, descriptor: BluetoothGattDescriptor, status: Int) {
|
override fun onDescriptorWrite(gatt: BluetoothGatt, descriptor: BluetoothGattDescriptor, status: Int) {
|
||||||
super.onDescriptorWrite(gatt, descriptor, status)
|
super.onDescriptorWrite(gatt, descriptor, status)
|
||||||
val writeConfirmation = if (status == BluetoothGatt.GATT_SUCCESS) {
|
|
||||||
aapsLogger.debug(LTag.PUMPBTCOMM, "OnDescriptor value " + descriptor.value.toHex())
|
onWrite(status, descriptor.uuid, descriptor.value)
|
||||||
DescriptorWriteConfirmationUUID(descriptor.uuid.toString())
|
}
|
||||||
} else {
|
|
||||||
DescriptorWriteConfirmationError(status)
|
private fun onWrite(status: Int, uuid: UUID?, value: ByteArray?) {
|
||||||
|
if (uuid == null || value == null) {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
try {
|
val writeConfirmation = when {
|
||||||
if (descriptorWriteQueue.size > 0) {
|
uuid == null || value == null ->
|
||||||
aapsLogger.warn(
|
WriteConfirmationError("onWrite received Null: UUID=$uuid, value=${value.toHex()} status=$status")
|
||||||
LTag.PUMPBTCOMM,
|
status == BluetoothGatt.GATT_SUCCESS -> {
|
||||||
"Descriptor write queue should be empty, found: ${descriptorWriteQueue.size}"
|
aapsLogger.debug(LTag.PUMPBTCOMM, "OnWrite value " + value.toHex())
|
||||||
)
|
WriteConfirmationSuccess(uuid.toString(), value)
|
||||||
descriptorWriteQueue.clear()
|
|
||||||
}
|
}
|
||||||
val offered = descriptorWriteQueue.offer(
|
else ->WriteConfirmationError("onDescriptorWrite status is not success: $status")
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
flushConfirmationQueue()
|
||||||
|
val offered = writeQueue.offer(
|
||||||
writeConfirmation,
|
writeConfirmation,
|
||||||
WRITE_CONFIRM_TIMEOUT_MS.toLong(),
|
WRITE_CONFIRM_TIMEOUT_MS.toLong(),
|
||||||
TimeUnit.MILLISECONDS
|
TimeUnit.MILLISECONDS
|
||||||
)
|
)
|
||||||
if (!offered) {
|
if (!offered) {
|
||||||
aapsLogger.warn(LTag.PUMPBTCOMM, "Received delayed descriptor write confirmation")
|
aapsLogger.warn(LTag.PUMPBTCOMM, "Received delayed write confirmation")
|
||||||
}
|
}
|
||||||
} catch (e: InterruptedException) {
|
} catch (e: InterruptedException) {
|
||||||
aapsLogger.warn(LTag.PUMPBTCOMM, "Interrupted while sending descriptor write confirmation")
|
aapsLogger.warn(LTag.PUMPBTCOMM, "Interrupted while sending write confirmation")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun flushConfirmationQueue() {
|
||||||
|
if (writeQueue.size > 0) {
|
||||||
|
aapsLogger.warn(
|
||||||
|
LTag.PUMPBTCOMM,
|
||||||
|
"Write queue should be empty, found: ${writeQueue.size}"
|
||||||
|
)
|
||||||
|
writeQueue.clear()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.callbacks
|
|
||||||
|
|
||||||
sealed class CharacteristicWriteConfirmation
|
|
||||||
|
|
||||||
data class CharacteristicWriteConfirmationPayload(val payload: ByteArray) : CharacteristicWriteConfirmation()
|
|
||||||
|
|
||||||
data class CharacteristicWriteConfirmationError(val status: Int) : CharacteristicWriteConfirmation()
|
|
|
@ -1,7 +0,0 @@
|
||||||
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.callbacks
|
|
||||||
|
|
||||||
sealed class DescriptorWriteConfirmation
|
|
||||||
|
|
||||||
data class DescriptorWriteConfirmationUUID(val uuid: String) : DescriptorWriteConfirmation()
|
|
||||||
|
|
||||||
data class DescriptorWriteConfirmationError(val status: Int) : DescriptorWriteConfirmation()
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.callbacks
|
||||||
|
|
||||||
|
sealed class WriteConfirmation
|
||||||
|
|
||||||
|
data class WriteConfirmationSuccess(val uuid: String, val payload: ByteArray) : WriteConfirmation()
|
||||||
|
|
||||||
|
data class WriteConfirmationError(
|
||||||
|
val msg: String,
|
||||||
|
val status: Int = 0
|
||||||
|
) : WriteConfirmation()
|
|
@ -1,18 +1,44 @@
|
||||||
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.command
|
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.command
|
||||||
|
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command.base.CommandType
|
||||||
import info.nightscout.androidaps.utils.extensions.toHex
|
import info.nightscout.androidaps.utils.extensions.toHex
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
|
||||||
class BleCommandRTS : BleCommand(BleCommandType.RTS)
|
object BleCommandRTS : BleCommand(BleCommandType.RTS)
|
||||||
|
|
||||||
class BleCommandCTS : BleCommand(BleCommandType.CTS)
|
object BleCommandCTS : BleCommand(BleCommandType.CTS)
|
||||||
|
|
||||||
class BleCommandAbort : BleCommand(BleCommandType.ABORT)
|
object BleCommandAbort : BleCommand(BleCommandType.ABORT)
|
||||||
|
|
||||||
class BleCommandSuccess : BleCommand(BleCommandType.SUCCESS)
|
object BleCommandSuccess : BleCommand(BleCommandType.SUCCESS)
|
||||||
|
|
||||||
class BleCommandFail : BleCommand(BleCommandType.FAIL)
|
object BleCommandFail : BleCommand(BleCommandType.FAIL)
|
||||||
|
|
||||||
open class BleCommand(val data: ByteArray) {
|
data class BleCommandNack(val idx: Byte) : BleCommand(BleCommandType.NACK, byteArrayOf(idx)) {
|
||||||
|
companion object {
|
||||||
|
fun parse(payload: ByteArray): BleCommand {
|
||||||
|
if (payload.size < 2) {
|
||||||
|
return BleCommandIncorrect("Incorrect NACK payload", payload)
|
||||||
|
}
|
||||||
|
if (payload[0] != BleCommandType.NACK.value) {
|
||||||
|
return BleCommandIncorrect("Incorrect NACK header", payload)
|
||||||
|
}
|
||||||
|
return BleCommandNack(payload[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class BleCommandHello(private val controllerId: Int) : BleCommand(
|
||||||
|
BleCommandType.HELLO,
|
||||||
|
ByteBuffer.allocate(6)
|
||||||
|
.put(1.toByte()) // TODO find the meaning of this constant
|
||||||
|
.put(4.toByte()) // TODO find the meaning of this constant
|
||||||
|
.putInt(controllerId).array()
|
||||||
|
)
|
||||||
|
|
||||||
|
data class BleCommandIncorrect(val msg:String, val payload: ByteArray): BleCommand(BleCommandType.INCORRECT)
|
||||||
|
|
||||||
|
sealed class BleCommand(val data: ByteArray) {
|
||||||
|
|
||||||
constructor(type: BleCommandType) : this(byteArrayOf(type.value))
|
constructor(type: BleCommandType) : this(byteArrayOf(type.value))
|
||||||
|
|
||||||
|
@ -36,4 +62,35 @@ open class BleCommand(val data: ByteArray) {
|
||||||
override fun hashCode(): Int {
|
override fun hashCode(): Int {
|
||||||
return data.contentHashCode()
|
return data.contentHashCode()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun parse(payload: ByteArray): BleCommand {
|
||||||
|
if (payload.isEmpty()) {
|
||||||
|
return BleCommandIncorrect("Incorrect command: empty payload", payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return when(BleCommandType.byValue(payload[0])) {
|
||||||
|
BleCommandType.RTS ->
|
||||||
|
BleCommandRTS
|
||||||
|
BleCommandType.CTS ->
|
||||||
|
BleCommandCTS
|
||||||
|
BleCommandType.NACK ->
|
||||||
|
BleCommandNack.parse(payload)
|
||||||
|
BleCommandType.ABORT ->
|
||||||
|
BleCommandAbort
|
||||||
|
BleCommandType.SUCCESS ->
|
||||||
|
BleCommandSuccess
|
||||||
|
BleCommandType.FAIL ->
|
||||||
|
BleCommandFail
|
||||||
|
BleCommandType.HELLO ->
|
||||||
|
BleCommandIncorrect("Incorrect hello command received", payload)
|
||||||
|
BleCommandType.INCORRECT ->
|
||||||
|
BleCommandIncorrect("Incorrect command received", payload)
|
||||||
|
}
|
||||||
|
} catch (e: IllegalArgumentException) {
|
||||||
|
return BleCommandIncorrect("Incorrect command payload", payload)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +0,0 @@
|
||||||
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.command
|
|
||||||
|
|
||||||
import java.nio.ByteBuffer
|
|
||||||
|
|
||||||
class BleCommandHello(controllerId: Int) : BleCommand(
|
|
||||||
BleCommandType.HELLO,
|
|
||||||
ByteBuffer.allocate(6)
|
|
||||||
.put(1.toByte()) // TODO find the meaning of this constant
|
|
||||||
.put(4.toByte()) // TODO find the meaning of this constant
|
|
||||||
.putInt(controllerId).array()
|
|
||||||
)
|
|
|
@ -1,3 +0,0 @@
|
||||||
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.command
|
|
||||||
|
|
||||||
class BleCommandNack(idx: Byte) : BleCommand(BleCommandType.NACK, byteArrayOf(idx))
|
|
|
@ -7,7 +7,8 @@ enum class BleCommandType(val value: Byte) {
|
||||||
ABORT(0x03.toByte()),
|
ABORT(0x03.toByte()),
|
||||||
SUCCESS(0x04.toByte()),
|
SUCCESS(0x04.toByte()),
|
||||||
FAIL(0x05.toByte()),
|
FAIL(0x05.toByte()),
|
||||||
HELLO(0x06.toByte());
|
HELLO(0x06.toByte()),
|
||||||
|
INCORRECT(0x09.toByte());
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions
|
||||||
|
|
||||||
|
class CouldNotConfirmCommandException(val msg: String="Could not confirm command") : Exception(msg)
|
|
@ -1,6 +0,0 @@
|
||||||
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions
|
|
||||||
|
|
||||||
class CouldNotConfirmDescriptorWriteException(override val message: String?) : Exception(message) {
|
|
||||||
constructor(sent: String, confirmed: String) : this("Could not confirm write. Sent: {$sent} .Received: $confirmed")
|
|
||||||
constructor(status: Int) : this("Could not confirm write. Write status: $status")
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions
|
|
||||||
|
|
||||||
class CouldNotConfirmWriteException(override val message: String?) : Exception(message) {
|
|
||||||
constructor(
|
|
||||||
sent: ByteArray,
|
|
||||||
confirmed: ByteArray
|
|
||||||
) : this("Could not confirm write. Sent: {$sent} .Received: $confirmed")
|
|
||||||
|
|
||||||
constructor(status: Int) : this("Could not confirm write. Write status: $status")
|
|
||||||
}
|
|
|
@ -1,5 +0,0 @@
|
||||||
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions
|
|
||||||
|
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.io.CharacteristicType
|
|
||||||
|
|
||||||
class CouldNotEnableNotifications(cmd: CharacteristicType) : Exception(cmd.value)
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions
|
||||||
|
|
||||||
|
class CouldNotInitiateConnection(msg: String) : Exception(msg)
|
|
@ -0,0 +1,4 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions
|
||||||
|
|
||||||
|
class CouldNotSendCommandException (val msg: String="Could not send command") : Exception(msg){
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions
|
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions
|
||||||
|
|
||||||
open class FailedToConnectException : Exception {
|
open class FailedToConnectException : Exception {
|
||||||
constructor(message: String?) : super("Failed to connect: ${message ?: ""}")
|
constructor(message: String?=null) : super("Failed to connect: ${message ?: ""}")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,5 +2,4 @@ package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.excepti
|
||||||
|
|
||||||
class MessageIOException : Exception {
|
class MessageIOException : Exception {
|
||||||
constructor(msg: String) : super(msg)
|
constructor(msg: String) : super(msg)
|
||||||
constructor(cause: Throwable) : super("Caught Exception during Message I/O", cause)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions
|
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions
|
||||||
|
|
||||||
class CouldNotSendBleException(msg: String?) : Exception(msg)
|
class NotConnectedException(val msg: String) : Exception(msg)
|
|
@ -1,3 +1,3 @@
|
||||||
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions
|
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions
|
||||||
|
|
||||||
class BleIOBusyException : Exception()
|
class PairingException(val msg: String) : Exception(msg)
|
|
@ -1,6 +1,6 @@
|
||||||
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions
|
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions
|
||||||
|
|
||||||
open class ScanFailException : Exception {
|
open class ScanException : Exception {
|
||||||
constructor(message: String) : super(message)
|
constructor(message: String) : super(message)
|
||||||
constructor(errorCode: Int) : super("errorCode$errorCode")
|
constructor(errorCode: Int) : super("errorCode$errorCode")
|
||||||
}
|
}
|
|
@ -3,7 +3,7 @@ package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.excepti
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.scan.BleDiscoveredDevice
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.scan.BleDiscoveredDevice
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
class ScanFailFoundTooManyException(devices: List<BleDiscoveredDevice>) : ScanFailException("Found more than one Pod") {
|
class ScanFailFoundTooManyException(devices: List<BleDiscoveredDevice>) : ScanException("Found more than one Pod") {
|
||||||
|
|
||||||
private val devices: List<BleDiscoveredDevice> = ArrayList(devices)
|
private val devices: List<BleDiscoveredDevice> = ArrayList(devices)
|
||||||
val discoveredDevices: List<BleDiscoveredDevice>
|
val discoveredDevices: List<BleDiscoveredDevice>
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions
|
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions
|
||||||
|
|
||||||
class ScanFailNotFoundException : ScanFailException("No Pod found")
|
class ScanFailNotFoundException : ScanException("No Pod found")
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions
|
|
||||||
|
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.command.BleCommand
|
|
||||||
|
|
||||||
class UnexpectedCommandException(val cmd: BleCommand) : Exception("Unexpected command: $cmd")
|
|
|
@ -8,127 +8,122 @@ import android.bluetooth.BluetoothGattDescriptor
|
||||||
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.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.callbacks.WriteConfirmation
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.callbacks.WriteConfirmationError
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.callbacks.WriteConfirmationSuccess
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions.*
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions.*
|
||||||
import info.nightscout.androidaps.utils.extensions.toHex
|
import info.nightscout.androidaps.utils.extensions.toHex
|
||||||
import java.util.concurrent.BlockingQueue
|
import java.util.concurrent.BlockingQueue
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import java.util.concurrent.TimeoutException
|
import java.util.concurrent.TimeoutException
|
||||||
|
|
||||||
class BleIO(
|
sealed class BleReceiveResult
|
||||||
private val aapsLogger: AAPSLogger,
|
data class BleReceivePayload(val payload: ByteArray) : BleReceiveResult()
|
||||||
private val chars: Map<CharacteristicType, BluetoothGattCharacteristic>,
|
data class BleReceiveError(val msg: String, val cause: Throwable? = null) : BleReceiveResult()
|
||||||
private val incomingPackets: Map<CharacteristicType, BlockingQueue<ByteArray>>,
|
|
||||||
private val gatt: BluetoothGatt,
|
|
||||||
private val bleCommCallbacks: BleCommCallbacks
|
|
||||||
) {
|
|
||||||
|
|
||||||
private var state: IOState = IOState.IDLE
|
|
||||||
|
sealed class BleSendResult
|
||||||
|
|
||||||
|
object BleSendSuccess : BleSendResult()
|
||||||
|
data class BleSendErrorSending(val msg: String, val cause: Throwable? = null) : BleSendResult()
|
||||||
|
data class BleSendErrorConfirming(val msg: String, val cause: Throwable? = null) : BleSendResult()
|
||||||
|
|
||||||
|
abstract class BleIO(
|
||||||
|
private val aapsLogger: AAPSLogger,
|
||||||
|
private val characteristic: BluetoothGattCharacteristic,
|
||||||
|
private val incomingPackets: BlockingQueue<ByteArray>,
|
||||||
|
private val gatt: BluetoothGatt,
|
||||||
|
private val bleCommCallbacks: BleCommCallbacks,
|
||||||
|
private val type: CharacteristicType
|
||||||
|
) {
|
||||||
|
|
||||||
/***
|
/***
|
||||||
*
|
*
|
||||||
* @param characteristic where to read from(CMD or DATA)
|
* @param characteristic where to read from(CMD or DATA)
|
||||||
* @return a byte array with the received data
|
* @return a byte array with the received data or error
|
||||||
*/
|
*/
|
||||||
@Throws(BleIOBusyException::class, InterruptedException::class, TimeoutException::class)
|
fun receivePacket(timeoutMs: Long = DEFAULT_IO_TIMEOUT_MS): BleReceiveResult {
|
||||||
fun receivePacket(characteristic: CharacteristicType, timeoutMs:Long = DEFAULT_IO_TIMEOUT_MS): ByteArray {
|
try {
|
||||||
synchronized(state) {
|
val ret = incomingPackets.poll(timeoutMs, TimeUnit.MILLISECONDS)
|
||||||
if (state != IOState.IDLE) {
|
?: return BleReceiveError("Timeout")
|
||||||
throw BleIOBusyException()
|
return BleReceivePayload(ret)
|
||||||
}
|
} catch (e: InterruptedException) {
|
||||||
state = IOState.READING
|
return BleReceiveError("Interrupted", cause = e)
|
||||||
}
|
}
|
||||||
val ret = incomingPackets[characteristic]?.poll(timeoutMs.toLong(), TimeUnit.MILLISECONDS)
|
|
||||||
?: throw TimeoutException()
|
|
||||||
synchronized(state) { state = IOState.IDLE }
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
fun peekCommand(): ByteArray? {
|
|
||||||
return incomingPackets[CharacteristicType.CMD]?.peek()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/***
|
/***
|
||||||
*
|
*
|
||||||
* @param characteristic where to write to(CMD or DATA)
|
* @param characteristic where to write to(CMD or DATA)
|
||||||
* @param payload the data to send
|
* @param payload the data to send
|
||||||
* @throws CouldNotSendBleException
|
|
||||||
*/
|
*/
|
||||||
@Throws(
|
fun sendAndConfirmPacket(payload: ByteArray): BleSendResult {
|
||||||
CouldNotSendBleException::class,
|
aapsLogger.debug(LTag.PUMPBTCOMM, "BleIO: Sending data on ${payload.toHex()}")
|
||||||
BleIOBusyException::class,
|
val set = characteristic.setValue(payload)
|
||||||
InterruptedException::class,
|
|
||||||
CouldNotConfirmWriteException::class,
|
|
||||||
TimeoutException::class
|
|
||||||
)
|
|
||||||
fun sendAndConfirmPacket(characteristic: CharacteristicType, payload: ByteArray) {
|
|
||||||
synchronized(state) {
|
|
||||||
if (state != IOState.IDLE) {
|
|
||||||
throw BleIOBusyException()
|
|
||||||
}
|
|
||||||
state = IOState.WRITING
|
|
||||||
}
|
|
||||||
aapsLogger.debug(LTag.PUMPBTCOMM, "BleIO: Sending data on " + characteristic.name + "/" + payload.toHex())
|
|
||||||
val ch = chars[characteristic]
|
|
||||||
val set = ch!!.setValue(payload)
|
|
||||||
if (!set) {
|
if (!set) {
|
||||||
throw CouldNotSendBleException("setValue")
|
return BleSendErrorSending("Could set setValue on ${type.name}")
|
||||||
}
|
}
|
||||||
val sent = gatt.writeCharacteristic(ch)
|
bleCommCallbacks.flushConfirmationQueue()
|
||||||
|
val sent = gatt.writeCharacteristic(characteristic)
|
||||||
if (!sent) {
|
if (!sent) {
|
||||||
throw CouldNotSendBleException("writeCharacteristic")
|
return BleSendErrorSending("Could not writeCharacteristic on {$type.name}")
|
||||||
|
}
|
||||||
|
|
||||||
|
return when (val confirmation = bleCommCallbacks.confirmWrite(
|
||||||
|
payload, type.value,
|
||||||
|
DEFAULT_IO_TIMEOUT_MS)){
|
||||||
|
is WriteConfirmationError ->
|
||||||
|
BleSendErrorConfirming(confirmation.msg)
|
||||||
|
is WriteConfirmationSuccess ->
|
||||||
|
BleSendSuccess
|
||||||
}
|
}
|
||||||
bleCommCallbacks.confirmWrite(payload, DEFAULT_IO_TIMEOUT_MS)
|
|
||||||
synchronized(state) { state = IOState.IDLE }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called before sending a new message.
|
* Called before sending a new message.
|
||||||
* The incoming queues should be empty, so we log when they are not.
|
* The incoming queues should be empty, so we log when they are not.
|
||||||
*/
|
*/
|
||||||
fun flushIncomingQueues() {
|
fun flushIncomingQueue() {
|
||||||
synchronized(state) { state = IOState.IDLE }
|
do {
|
||||||
|
val found = incomingPackets.poll()?.also {
|
||||||
for (char in CharacteristicType.values()) {
|
aapsLogger.warn(LTag.PUMPBTCOMM, "BleIO: queue not empty, flushing: {${it.toHex()}")
|
||||||
do {
|
}
|
||||||
val found = incomingPackets[char]?.poll()?.also {
|
} while (found != null)
|
||||||
aapsLogger.warn(LTag.PUMPBTCOMM, "BleIO: ${char.name} queue not empty, flushing: {${it.toHex()}")
|
|
||||||
}
|
|
||||||
} while (found != null)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enable intentions on the characteristics.
|
* Enable intentions on the characteristic
|
||||||
* This will signal the pod it can start sending back data
|
* This will signal the pod it can start sending back data
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
@Throws(
|
fun readyToRead(): BleSendResult {
|
||||||
CouldNotSendBleException::class,
|
val notificationSet = gatt.setCharacteristicNotification(characteristic, true)
|
||||||
CouldNotEnableNotifications::class,
|
if (!notificationSet) {
|
||||||
DescriptorNotFoundException::class,
|
throw CouldNotInitiateConnection("Could not enable notifications")
|
||||||
InterruptedException::class,
|
|
||||||
CouldNotConfirmDescriptorWriteException::class
|
|
||||||
)
|
|
||||||
fun readyToRead() {
|
|
||||||
for (type in CharacteristicType.values()) {
|
|
||||||
val ch = chars[type]
|
|
||||||
val notificationSet = gatt.setCharacteristicNotification(ch, true)
|
|
||||||
if (!notificationSet) {
|
|
||||||
throw CouldNotEnableNotifications(type)
|
|
||||||
}
|
|
||||||
val descriptors = ch!!.descriptors
|
|
||||||
if (descriptors.size != 1) {
|
|
||||||
throw DescriptorNotFoundException()
|
|
||||||
}
|
|
||||||
val descriptor = descriptors[0]
|
|
||||||
descriptor.value = BluetoothGattDescriptor.ENABLE_INDICATION_VALUE
|
|
||||||
gatt.writeDescriptor(descriptor)
|
|
||||||
bleCommCallbacks.confirmWriteDescriptor(descriptor.uuid.toString(), DEFAULT_IO_TIMEOUT_MS)
|
|
||||||
}
|
}
|
||||||
|
val descriptors = characteristic.descriptors
|
||||||
|
if (descriptors.size != 1) {
|
||||||
|
throw CouldNotInitiateConnection("Expecting one descriptor, found: ${descriptors.size}")
|
||||||
|
}
|
||||||
|
val descriptor = descriptors[0]
|
||||||
|
descriptor.value = BluetoothGattDescriptor.ENABLE_INDICATION_VALUE
|
||||||
|
val wrote = gatt.writeDescriptor(descriptor)
|
||||||
|
if (!wrote) {
|
||||||
|
throw CouldNotInitiateConnection("Could not enable indications on descriptor")
|
||||||
|
}
|
||||||
|
val confirmation = bleCommCallbacks.confirmWrite(BluetoothGattDescriptor.ENABLE_INDICATION_VALUE,
|
||||||
|
descriptor.uuid.toString(),
|
||||||
|
DEFAULT_IO_TIMEOUT_MS)
|
||||||
|
if (confirmation is WriteConfirmationError) {
|
||||||
|
throw CouldNotInitiateConnection(confirmation.msg)
|
||||||
|
}
|
||||||
|
return BleSendSuccess
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
private const val DEFAULT_IO_TIMEOUT_MS = 1000.toLong()
|
const val DEFAULT_IO_TIMEOUT_MS = 1000.toLong()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,6 @@ enum class CharacteristicType(val value: String) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
fun byValue(value: String): CharacteristicType =
|
fun byValue(value: String): CharacteristicType =
|
||||||
values().firstOrNull { it.value == value }
|
values().firstOrNull { it.value == value }
|
||||||
?: throw IllegalArgumentException("Unknown Characteristic Type: $value")
|
?: throw IllegalArgumentException("Unknown Characteristic Type: $value")
|
||||||
|
|
|
@ -0,0 +1,54 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.io
|
||||||
|
|
||||||
|
import android.bluetooth.BluetoothGatt
|
||||||
|
import android.bluetooth.BluetoothGattCharacteristic
|
||||||
|
import info.nightscout.androidaps.logging.AAPSLogger
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.OmnipodDashBleManagerImpl
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.callbacks.BleCommCallbacks
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.command.BleCommand
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.command.BleCommandHello
|
||||||
|
import java.util.concurrent.BlockingQueue
|
||||||
|
|
||||||
|
sealed class BleConfirmResult
|
||||||
|
|
||||||
|
object BleConfirmSuccess : BleConfirmResult()
|
||||||
|
data class BleConfirmIncorrectData(val payload: ByteArray) : BleConfirmResult()
|
||||||
|
data class BleConfirmError(val msg: String, val cause: Throwable? = null) : BleConfirmResult()
|
||||||
|
|
||||||
|
class CmdBleIO(
|
||||||
|
logger: AAPSLogger,
|
||||||
|
characteristic: BluetoothGattCharacteristic,
|
||||||
|
private val incomingPackets: BlockingQueue<ByteArray>,
|
||||||
|
gatt: BluetoothGatt,
|
||||||
|
bleCommCallbacks: BleCommCallbacks
|
||||||
|
) : BleIO(
|
||||||
|
logger,
|
||||||
|
characteristic,
|
||||||
|
incomingPackets,
|
||||||
|
gatt,
|
||||||
|
bleCommCallbacks,
|
||||||
|
CharacteristicType.CMD
|
||||||
|
) {
|
||||||
|
init {
|
||||||
|
}
|
||||||
|
|
||||||
|
fun peekCommand(): ByteArray? {
|
||||||
|
return incomingPackets.peek()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun hello() = sendAndConfirmPacket(BleCommandHello(OmnipodDashBleManagerImpl.CONTROLLER_ID).data)
|
||||||
|
|
||||||
|
|
||||||
|
fun expectCommandType(expected: BleCommand, timeoutMs: Long = DEFAULT_IO_TIMEOUT_MS): BleConfirmResult {
|
||||||
|
return when (val actual = receivePacket(timeoutMs)) {
|
||||||
|
is BleReceiveError -> BleConfirmError(actual.toString())
|
||||||
|
is BleReceivePayload ->
|
||||||
|
if (actual.payload.isEmpty() || actual.payload[0] != expected.data[0]) {
|
||||||
|
BleConfirmIncorrectData(actual.payload)
|
||||||
|
} else {
|
||||||
|
BleConfirmSuccess
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.io
|
||||||
|
|
||||||
|
import android.bluetooth.BluetoothGatt
|
||||||
|
import android.bluetooth.BluetoothGattCharacteristic
|
||||||
|
import info.nightscout.androidaps.logging.AAPSLogger
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.callbacks.BleCommCallbacks
|
||||||
|
import java.util.concurrent.BlockingQueue
|
||||||
|
|
||||||
|
class DataBleIO(
|
||||||
|
logger: AAPSLogger,
|
||||||
|
characteristic: BluetoothGattCharacteristic,
|
||||||
|
incomingPackets: BlockingQueue<ByteArray>,
|
||||||
|
gatt: BluetoothGatt,
|
||||||
|
bleCommCallbacks: BleCommCallbacks
|
||||||
|
) : BleIO(
|
||||||
|
logger,
|
||||||
|
characteristic,
|
||||||
|
incomingPackets,
|
||||||
|
gatt,
|
||||||
|
bleCommCallbacks,
|
||||||
|
CharacteristicType.DATA
|
||||||
|
)
|
|
@ -0,0 +1,17 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.io
|
||||||
|
|
||||||
|
import java.util.concurrent.BlockingQueue
|
||||||
|
import java.util.concurrent.LinkedBlockingDeque
|
||||||
|
|
||||||
|
class IncomingPackets {
|
||||||
|
|
||||||
|
val cmdQueue: BlockingQueue<ByteArray> = LinkedBlockingDeque()
|
||||||
|
val dataQueue: BlockingQueue<ByteArray> = LinkedBlockingDeque()
|
||||||
|
|
||||||
|
fun byCharacteristicType(char: CharacteristicType): BlockingQueue<ByteArray> {
|
||||||
|
return when (char) {
|
||||||
|
CharacteristicType.DATA -> cmdQueue
|
||||||
|
CharacteristicType.CMD -> dataQueue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,154 +3,221 @@ package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.message
|
||||||
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.driver.comm.command.*
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.command.*
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions.MessageIOException
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.io.*
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions.UnexpectedCommandException
|
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.io.BleIO
|
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.io.CharacteristicType
|
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.io.PayloadJoiner
|
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.packet.BlePacket
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.packet.BlePacket
|
||||||
import info.nightscout.androidaps.utils.extensions.toHex
|
import info.nightscout.androidaps.utils.extensions.toHex
|
||||||
import java.util.concurrent.TimeoutException
|
|
||||||
|
|
||||||
class MessageIO(private val aapsLogger: AAPSLogger, private val bleIO: BleIO) {
|
sealed class MesssageReceiveResult
|
||||||
|
data class MessageReceiveSuccess(val msg: MessagePacket) : MesssageReceiveResult()
|
||||||
|
data class MessageReceiveError(val msg: String, val cause: Throwable? = null) : MesssageReceiveResult() {
|
||||||
|
constructor(e: PacketReceiveResult) : this("Could not read DATA packet: $e")
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class MessageSendResult
|
||||||
|
object MessageSendSuccess : MessageSendResult()
|
||||||
|
data class MessageSendErrorSending(val msg: String, val cause: Throwable? = null) : MessageSendResult() {
|
||||||
|
constructor(e: BleSendResult): this("Could not send packet: $e")
|
||||||
|
}
|
||||||
|
|
||||||
|
data class MessageSendErrorConfirming(val msg: String, val cause: Throwable? = null) : MessageSendResult() {
|
||||||
|
constructor(e: BleSendResult): this("Could not confirm packet: $e")
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class PacketReceiveResult
|
||||||
|
data class PacketReceiveSuccess(val payload: ByteArray) : PacketReceiveResult()
|
||||||
|
data class PacketReceiveError(val msg: String) : PacketReceiveResult()
|
||||||
|
|
||||||
|
class MessageIO(
|
||||||
|
private val aapsLogger: AAPSLogger,
|
||||||
|
private val cmdBleIO: CmdBleIO,
|
||||||
|
private val dataBleIO: DataBleIO,
|
||||||
|
) {
|
||||||
|
|
||||||
val receivedOutOfOrder = LinkedHashMap<Byte, ByteArray>()
|
val receivedOutOfOrder = LinkedHashMap<Byte, ByteArray>()
|
||||||
var maxTries = 3
|
var maxMessageReadTries = 3
|
||||||
var tries = 0
|
var messageReadTries = 0
|
||||||
|
|
||||||
private fun expectCommandType(actual: BleCommand, expected: BleCommand) {
|
fun sendMessage(msg: MessagePacket): MessageSendResult {
|
||||||
if (actual.data.isEmpty()) {
|
cmdBleIO.flushIncomingQueue()
|
||||||
throw UnexpectedCommandException(actual)
|
dataBleIO.flushIncomingQueue()
|
||||||
|
|
||||||
|
val sendResult = cmdBleIO.sendAndConfirmPacket(BleCommandRTS.data)
|
||||||
|
if (sendResult is BleSendErrorSending) {
|
||||||
|
return MessageSendErrorSending(sendResult)
|
||||||
}
|
}
|
||||||
// first byte is the command type
|
|
||||||
if (actual.data[0] == expected.data[0]) {
|
val expectCTS = cmdBleIO.expectCommandType(BleCommandCTS)
|
||||||
return
|
if (expectCTS !is BleConfirmSuccess) {
|
||||||
|
return MessageSendErrorSending(sendResult)
|
||||||
}
|
}
|
||||||
throw UnexpectedCommandException(actual)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun peekForNack(index: Int, packets: List<BlePacket>) {
|
|
||||||
val peekCmd = bleIO.peekCommand() ?: return
|
|
||||||
|
|
||||||
if (peekCmd.isEmpty()) {
|
|
||||||
throw UnexpectedCommandException(BleCommand(peekCmd))
|
|
||||||
}
|
|
||||||
when (BleCommandType.byValue(peekCmd[0])) {
|
|
||||||
BleCommandType.NACK -> {
|
|
||||||
if (peekCmd.size < 2) {
|
|
||||||
throw UnexpectedCommandException(BleCommand(peekCmd))
|
|
||||||
}
|
|
||||||
val missingIdx = peekCmd[1]
|
|
||||||
if (missingIdx > packets.size) {
|
|
||||||
throw UnexpectedCommandException(BleCommand(peekCmd))
|
|
||||||
|
|
||||||
}
|
|
||||||
bleIO.receivePacket(CharacteristicType.CMD) //consume NACK
|
|
||||||
bleIO.sendAndConfirmPacket(CharacteristicType.DATA, packets[missingIdx.toInt()].toByteArray())
|
|
||||||
}
|
|
||||||
|
|
||||||
BleCommandType.SUCCESS -> {
|
|
||||||
if (index != packets.size - 1) {
|
|
||||||
throw UnexpectedCommandException(BleCommand(peekCmd))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
else ->
|
|
||||||
throw UnexpectedCommandException(BleCommand(peekCmd))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun sendMessage(msg: MessagePacket) {
|
|
||||||
bleIO.flushIncomingQueues()
|
|
||||||
bleIO.sendAndConfirmPacket(CharacteristicType.CMD, BleCommandRTS().data)
|
|
||||||
val expectCTS = bleIO.receivePacket(CharacteristicType.CMD)
|
|
||||||
expectCommandType(BleCommand(expectCTS), BleCommandCTS())
|
|
||||||
val payload = msg.asByteArray()
|
val payload = msg.asByteArray()
|
||||||
aapsLogger.debug(LTag.PUMPBTCOMM, "Sending message: ${payload.toHex()}")
|
aapsLogger.debug(LTag.PUMPBTCOMM, "Sending message: ${payload.toHex()}")
|
||||||
val splitter = PayloadSplitter(payload)
|
val splitter = PayloadSplitter(payload)
|
||||||
val packets = splitter.splitInPackets()
|
val packets = splitter.splitInPackets()
|
||||||
|
|
||||||
for ((index, packet) in packets.withIndex()) {
|
for ((index, packet) in packets.withIndex()) {
|
||||||
aapsLogger.debug(LTag.PUMPBTCOMM, "Sending DATA: ${packet.toByteArray().toHex()}")
|
aapsLogger.debug(LTag.PUMPBTCOMM, "Sending DATA: ${packet.toByteArray().toHex()}")
|
||||||
bleIO.sendAndConfirmPacket(CharacteristicType.DATA, packet.toByteArray())
|
val sendResult = dataBleIO.sendAndConfirmPacket(packet.toByteArray())
|
||||||
peekForNack(index, packets)
|
val ret = handleSendResult(sendResult, index, packets)
|
||||||
// This is implementing the same logic as the PDM.
|
if (ret !is MessageSendSuccess) {
|
||||||
// I think it wil not work in case of packet lost.
|
return ret
|
||||||
// This is because each lost packet, we will receive a NACK on the next packet.
|
}
|
||||||
// At the end, we will still be missing the last packet(s).
|
val peek = peekForNack(index, packets)
|
||||||
// I don't worry too much about this because for commands we have retries implemented at MessagePacket level anyway
|
if (peek !is MessageSendSuccess) {
|
||||||
// If this will be a problem in the future, the fix might be(pending testing with a real pod) to move back the index
|
return if (index == packets.size - 1)
|
||||||
// at the value received in the NACK and make sure don't retry forever.
|
MessageSendErrorConfirming(peek.toString())
|
||||||
}
|
else
|
||||||
val expectSuccess = bleIO.receivePacket(CharacteristicType.CMD)
|
MessageSendErrorSending(peek.toString())
|
||||||
expectCommandType(BleCommand(expectSuccess), BleCommandSuccess())
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun expectBlePacket(index: Byte): ByteArray {
|
|
||||||
receivedOutOfOrder[index]?.let {
|
|
||||||
return it
|
|
||||||
}
|
|
||||||
while (tries < maxTries) {
|
|
||||||
try {
|
|
||||||
tries++
|
|
||||||
val payload = bleIO.receivePacket(CharacteristicType.DATA)
|
|
||||||
if (payload.isEmpty()) {
|
|
||||||
throw IncorrectPacketException(payload, index)
|
|
||||||
}
|
|
||||||
if (payload[0] == index) {
|
|
||||||
return payload
|
|
||||||
}
|
|
||||||
receivedOutOfOrder[payload[0]] = payload
|
|
||||||
bleIO.sendAndConfirmPacket(CharacteristicType.CMD, BleCommandNack(index).data)
|
|
||||||
} catch (e: TimeoutException) {
|
|
||||||
bleIO.sendAndConfirmPacket(CharacteristicType.CMD, BleCommandNack(index).data)
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw TimeoutException()
|
|
||||||
|
return when (val expectSuccess = cmdBleIO.expectCommandType(BleCommandSuccess)) {
|
||||||
|
is BleConfirmSuccess ->
|
||||||
|
MessageSendSuccess
|
||||||
|
is BleConfirmError ->
|
||||||
|
MessageSendErrorConfirming("Error reading message confirmation: $expectSuccess")
|
||||||
|
is BleConfirmIncorrectData ->
|
||||||
|
when (val received = (BleCommand.parse((expectSuccess.payload)))) {
|
||||||
|
is BleCommandFail ->
|
||||||
|
// this can happen if CRC does not match
|
||||||
|
MessageSendErrorSending("Received FAIL after sending message")
|
||||||
|
else ->
|
||||||
|
MessageSendErrorConfirming("Received confirmation message: $received")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun readReset() {
|
fun receiveMessage(): MesssageReceiveResult {
|
||||||
maxTries = 3
|
cmdBleIO.expectCommandType(BleCommandRTS, MESSAGE_READ_TIMEOUT_MS)
|
||||||
tries = 0
|
|
||||||
receivedOutOfOrder.clear()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun receiveMessage(): MessagePacket {
|
val sendResult = cmdBleIO.sendAndConfirmPacket(BleCommandCTS.data)
|
||||||
val expectRTS = bleIO.receivePacket(CharacteristicType.CMD, MESSAGE_READ_TIMEOUT_MS)
|
if (sendResult !is BleSendSuccess) {
|
||||||
expectCommandType(BleCommand(expectRTS), BleCommandRTS())
|
return MessageReceiveError("Error sending CTS: $sendResult")
|
||||||
bleIO.sendAndConfirmPacket(CharacteristicType.CMD, BleCommandCTS().data)
|
}
|
||||||
readReset()
|
readReset()
|
||||||
var expected: Byte = 0
|
var expected: Byte = 0
|
||||||
try {
|
try {
|
||||||
val firstPacket = expectBlePacket(0)
|
val firstPacket = expectBlePacket(0)
|
||||||
val joiner = PayloadJoiner(firstPacket)
|
if (firstPacket !is PacketReceiveSuccess) {
|
||||||
maxTries = joiner.fullFragments * 2 + 2
|
return MessageReceiveError(firstPacket)
|
||||||
|
}
|
||||||
|
val joiner = PayloadJoiner(firstPacket.payload)
|
||||||
|
maxMessageReadTries = joiner.fullFragments * 2 + 2
|
||||||
for (i in 1 until joiner.fullFragments + 1) {
|
for (i in 1 until joiner.fullFragments + 1) {
|
||||||
expected++
|
expected++
|
||||||
val packet = expectBlePacket(expected)
|
val packet = expectBlePacket(expected)
|
||||||
joiner.accumulate(packet)
|
if (packet !is PacketReceiveSuccess) {
|
||||||
|
return MessageReceiveError(packet)
|
||||||
|
}
|
||||||
|
joiner.accumulate(packet.payload)
|
||||||
}
|
}
|
||||||
if (joiner.oneExtraPacket) {
|
if (joiner.oneExtraPacket) {
|
||||||
expected++
|
expected++
|
||||||
joiner.accumulate(expectBlePacket(expected))
|
val packet = expectBlePacket(expected)
|
||||||
|
if (packet !is PacketReceiveSuccess) {
|
||||||
|
return MessageReceiveError(packet)
|
||||||
|
}
|
||||||
|
joiner.accumulate(packet.payload)
|
||||||
}
|
}
|
||||||
val fullPayload = joiner.finalize()
|
val fullPayload = joiner.finalize()
|
||||||
bleIO.sendAndConfirmPacket(CharacteristicType.CMD, BleCommandSuccess().data)
|
cmdBleIO.sendAndConfirmPacket(BleCommandSuccess.data)
|
||||||
return MessagePacket.parse(fullPayload)
|
return MessageReceiveSuccess(MessagePacket.parse(fullPayload))
|
||||||
} catch (e: IncorrectPacketException) {
|
} catch (e: IncorrectPacketException) {
|
||||||
aapsLogger.warn(LTag.PUMPBTCOMM, "Could not read message: $e")
|
aapsLogger.warn(LTag.PUMPBTCOMM, "Could not read message: $e")
|
||||||
bleIO.sendAndConfirmPacket(CharacteristicType.CMD, BleCommandAbort().data)
|
cmdBleIO.sendAndConfirmPacket(BleCommandAbort.data)
|
||||||
throw MessageIOException(cause = e)
|
return MessageReceiveError("Received incorrect packet: $e", cause = e)
|
||||||
} catch (e: CrcMismatchException) {
|
} catch (e: CrcMismatchException) {
|
||||||
aapsLogger.warn(LTag.PUMPBTCOMM, "CRC mismatch: $e")
|
aapsLogger.warn(LTag.PUMPBTCOMM, "CRC mismatch: $e")
|
||||||
bleIO.sendAndConfirmPacket(CharacteristicType.CMD, BleCommandFail().data)
|
cmdBleIO.sendAndConfirmPacket(BleCommandFail.data)
|
||||||
throw MessageIOException(cause = e)
|
return MessageReceiveError("CRC mismatch: $e", cause = e)
|
||||||
|
} finally {
|
||||||
|
readReset()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleSendResult(sendResult: BleSendResult, index: Int, packets: List<BlePacket>): MessageSendResult {
|
||||||
|
return when {
|
||||||
|
sendResult is BleSendSuccess ->
|
||||||
|
MessageSendSuccess
|
||||||
|
index == packets.size - 1 && sendResult is BleSendErrorConfirming ->
|
||||||
|
return MessageSendErrorConfirming("Error confirming last DATA packet $sendResult")
|
||||||
|
else ->
|
||||||
|
return MessageSendErrorSending("Error sending DATA: $sendResult")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun peekForNack(index: Int, packets: List<BlePacket>): MessageSendResult {
|
||||||
|
val peekCmd = cmdBleIO.peekCommand()
|
||||||
|
?: return MessageSendSuccess
|
||||||
|
|
||||||
|
when (val receivedCmd = BleCommand.parse(peekCmd)) {
|
||||||
|
is BleCommandNack -> {
|
||||||
|
//// Consume NACK
|
||||||
|
val received = cmdBleIO.receivePacket()
|
||||||
|
if (received !is BleReceivePayload) {
|
||||||
|
return MessageSendErrorSending(received.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
val sendResult = dataBleIO.sendAndConfirmPacket(packets[receivedCmd.idx.toInt()].toByteArray())
|
||||||
|
return handleSendResult(sendResult, index, packets)
|
||||||
|
}
|
||||||
|
|
||||||
|
BleCommandSuccess -> {
|
||||||
|
if (index != packets.size) {
|
||||||
|
return MessageSendErrorSending("Received SUCCESS before sending all the data. $index")
|
||||||
|
}
|
||||||
|
return MessageSendSuccess
|
||||||
|
}
|
||||||
|
|
||||||
|
else ->
|
||||||
|
return MessageSendErrorSending("Received unexpected command: ${peekCmd.toHex()}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun expectBlePacket(index: Byte, nackOnTimeout: Boolean = false): PacketReceiveResult {
|
||||||
|
receivedOutOfOrder[index]?.let {
|
||||||
|
return PacketReceiveSuccess(it)
|
||||||
|
}
|
||||||
|
var packetTries = 0
|
||||||
|
while (messageReadTries < maxMessageReadTries && packetTries < MAX_PACKET_READ_TRIES) {
|
||||||
|
messageReadTries++
|
||||||
|
packetTries++
|
||||||
|
|
||||||
|
when (val received = dataBleIO.receivePacket()) {
|
||||||
|
is BleReceiveError -> {
|
||||||
|
if (nackOnTimeout)
|
||||||
|
cmdBleIO.sendAndConfirmPacket(BleCommandNack(index).data)
|
||||||
|
aapsLogger.info(LTag.PUMPBTCOMM, "Error receiving DATA packet: $received")
|
||||||
|
}
|
||||||
|
|
||||||
|
is BleReceivePayload -> {
|
||||||
|
val payload = received.payload
|
||||||
|
if (payload.isEmpty()) {
|
||||||
|
aapsLogger.info(LTag.PUMPBTCOMM, "Received empty payload at index $index")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (payload[0] == index) {
|
||||||
|
return PacketReceiveSuccess(payload)
|
||||||
|
}
|
||||||
|
receivedOutOfOrder[payload[0]] = payload
|
||||||
|
cmdBleIO.sendAndConfirmPacket(BleCommandNack(index).data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return PacketReceiveError("Reached the maximum number tries to read a packet")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun readReset() {
|
||||||
|
maxMessageReadTries = 3
|
||||||
|
messageReadTries = 0
|
||||||
receivedOutOfOrder.clear()
|
receivedOutOfOrder.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
|
private const val MAX_PACKET_READ_TRIES = 4
|
||||||
private const val MESSAGE_READ_TIMEOUT_MS = 2500.toLong()
|
private const val MESSAGE_READ_TIMEOUT_MS = 2500.toLong()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,18 @@
|
||||||
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.pair
|
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.pair
|
||||||
|
|
||||||
|
import android.app.Notification
|
||||||
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.driver.comm.Id
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.Id
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions.MessageIOException
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions.MessageIOException
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.message.MessageIO
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions.PairingException
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.message.MessagePacket
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.message.*
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.message.StringLengthPrefixEncoding
|
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.message.StringLengthPrefixEncoding.Companion.parseKeys
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.message.StringLengthPrefixEncoding.Companion.parseKeys
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.util.RandomByteGenerator
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.util.RandomByteGenerator
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.util.X25519KeyGenerator
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.util.X25519KeyGenerator
|
||||||
import info.nightscout.androidaps.utils.extensions.hexStringToByteArray
|
import info.nightscout.androidaps.utils.extensions.hexStringToByteArray
|
||||||
import info.nightscout.androidaps.utils.extensions.toHex
|
import info.nightscout.androidaps.utils.extensions.toHex
|
||||||
|
import info.nightscout.androidaps.utils.extensions.waitMillis
|
||||||
|
|
||||||
internal class LTKExchanger(
|
internal class LTKExchanger(
|
||||||
private val aapsLogger: AAPSLogger,
|
private val aapsLogger: AAPSLogger,
|
||||||
|
@ -25,42 +26,54 @@ internal class LTKExchanger(
|
||||||
private var seq: Byte = 1
|
private var seq: Byte = 1
|
||||||
|
|
||||||
fun negotiateLTK(): PairResult {
|
fun negotiateLTK(): PairResult {
|
||||||
// send SP1, SP2
|
|
||||||
val sp1sp2 = sp1sp2(podId.address, sp2())
|
val sp1sp2 = sp1sp2(podId.address, sp2())
|
||||||
msgIO.sendMessage(sp1sp2.messagePacket)
|
val sendSp1Sp2Result = msgIO.sendMessage(sp1sp2.messagePacket)
|
||||||
|
if (sendSp1Sp2Result !is MessageSendSuccess) {
|
||||||
|
throw PairingException("Could not send SP1SP2: $sendSp1Sp2Result")
|
||||||
|
}
|
||||||
|
|
||||||
seq++
|
seq++
|
||||||
val sps1 = sps1()
|
val sps1 = sps1()
|
||||||
msgIO.sendMessage(sps1.messagePacket)
|
val sp1Result = msgIO.sendMessage(sps1.messagePacket)
|
||||||
// send SPS1
|
if (sp1Result !is MessageSendSuccess) {
|
||||||
|
throw PairingException("Could not send SP1: $sp1Result")
|
||||||
|
}
|
||||||
|
|
||||||
// read SPS1
|
|
||||||
val podSps1 = msgIO.receiveMessage()
|
val podSps1 = msgIO.receiveMessage()
|
||||||
processSps1FromPod(podSps1)
|
if (podSps1 !is MessageReceiveSuccess) {
|
||||||
|
throw PairingException("Could not read SPS1: $podSps1")
|
||||||
|
}
|
||||||
|
processSps1FromPod(podSps1.msg)
|
||||||
// now we have all the data to generate: confPod, confPdm, ltk and noncePrefix
|
// now we have all the data to generate: confPod, confPdm, ltk and noncePrefix
|
||||||
|
|
||||||
seq++
|
seq++
|
||||||
// send SPS2
|
|
||||||
val sps2 = sps2()
|
val sps2 = sps2()
|
||||||
msgIO.sendMessage(sps2.messagePacket)
|
val sp2Result = msgIO.sendMessage(sps2.messagePacket)
|
||||||
// read SPS2
|
if (sp1Result !is MessageSendSuccess) {
|
||||||
|
throw PairingException("Could not send sps2: ${sp2Result}")
|
||||||
|
}
|
||||||
|
|
||||||
val podSps2 = msgIO.receiveMessage()
|
val podSps2 = msgIO.receiveMessage()
|
||||||
validatePodSps2(podSps2)
|
if (podSps2 !is MessageReceiveSuccess) {
|
||||||
|
throw PairingException("Could not read SPS2: $podSps2")
|
||||||
|
}
|
||||||
|
validatePodSps2(podSps2.msg)
|
||||||
|
|
||||||
seq++
|
seq++
|
||||||
// send SP0GP0
|
// send SP0GP0
|
||||||
msgIO.sendMessage(sp0gp0().messagePacket)
|
val sp0gp0Result = msgIO.sendMessage(sp0gp0().messagePacket)
|
||||||
// read P0
|
if (sp0gp0Result is MessageSendErrorSending) {
|
||||||
|
throw PairingException("Could not send SP0GP0: $sp0gp0Result")
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: failing to read or validate p0 will lead to undefined state
|
// No exception throwing after this point. It is possible that the pod saved the LTK
|
||||||
// It could be that:
|
//
|
||||||
// - the pod answered with p0 and we did not receive/could not process the answer
|
|
||||||
// - the pod answered with some sort of error. This is very unlikely, because we already received(and validated) SPS2 from the pod
|
|
||||||
// But if sps2 conf value is incorrect, then we would probablysee this when receiving the pod podSps2(to test)
|
|
||||||
val p0 = msgIO.receiveMessage()
|
val p0 = msgIO.receiveMessage()
|
||||||
validateP0(p0)
|
if (p0 is MessageReceiveSuccess) {
|
||||||
|
validateP0(p0.msg)
|
||||||
|
} else{
|
||||||
|
aapsLogger.warn(LTag.PUMPBTCOMM, "Could not read P0: $p0")
|
||||||
|
}
|
||||||
return PairResult(
|
return PairResult(
|
||||||
ltk = keyExchange.ltk,
|
ltk = keyExchange.ltk,
|
||||||
msgSeq = seq
|
msgSeq = seq
|
||||||
|
@ -147,7 +160,7 @@ internal class LTKExchanger(
|
||||||
val payload = parseKeys(arrayOf(P0), msg.payload)[0]
|
val payload = parseKeys(arrayOf(P0), msg.payload)[0]
|
||||||
aapsLogger.debug(LTag.PUMPBTCOMM, "P0 payload from pod: ${payload.toHex()}")
|
aapsLogger.debug(LTag.PUMPBTCOMM, "P0 payload from pod: ${payload.toHex()}")
|
||||||
if (!payload.contentEquals(UNKNOWN_P0_PAYLOAD)) {
|
if (!payload.contentEquals(UNKNOWN_P0_PAYLOAD)) {
|
||||||
throw MessageIOException("Invalid P0 payload received")
|
aapsLogger.warn(LTag.PUMPBTCOMM, "Reveived invalid P0 payload: ${payload.toHex()}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,14 +6,14 @@ import android.bluetooth.le.ScanSettings
|
||||||
import android.os.ParcelUuid
|
import android.os.ParcelUuid
|
||||||
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.driver.comm.exceptions.ScanFailException
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions.ScanException
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions.ScanFailFoundTooManyException
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions.ScanFailFoundTooManyException
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions.ScanFailNotFoundException
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions.ScanFailNotFoundException
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
class PodScanner(private val logger: AAPSLogger, private val bluetoothAdapter: BluetoothAdapter) {
|
class PodScanner(private val logger: AAPSLogger, private val bluetoothAdapter: BluetoothAdapter) {
|
||||||
|
|
||||||
@Throws(InterruptedException::class, ScanFailException::class)
|
@Throws(InterruptedException::class, ScanException::class)
|
||||||
fun scanForPod(serviceUUID: String?, podID: Long): BleDiscoveredDevice {
|
fun scanForPod(serviceUUID: String?, podID: Long): BleDiscoveredDevice {
|
||||||
val scanner = bluetoothAdapter.bluetoothLeScanner
|
val scanner = bluetoothAdapter.bluetoothLeScanner
|
||||||
val filter = ScanFilter.Builder()
|
val filter = ScanFilter.Builder()
|
||||||
|
|
|
@ -5,7 +5,7 @@ import android.bluetooth.le.ScanResult
|
||||||
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.driver.comm.exceptions.DiscoveredInvalidPodException
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions.DiscoveredInvalidPodException
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions.ScanFailException
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions.ScanException
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
|
||||||
|
@ -25,10 +25,10 @@ class ScanCollector(private val logger: AAPSLogger, private val podID: Long) : S
|
||||||
super.onScanFailed(errorCode)
|
super.onScanFailed(errorCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(ScanFailException::class) fun collect(): List<BleDiscoveredDevice> {
|
@Throws(ScanException::class) fun collect(): List<BleDiscoveredDevice> {
|
||||||
val ret: MutableList<BleDiscoveredDevice> = ArrayList()
|
val ret: MutableList<BleDiscoveredDevice> = ArrayList()
|
||||||
if (scanFailed != 0) {
|
if (scanFailed != 0) {
|
||||||
throw ScanFailException(scanFailed)
|
throw ScanException(scanFailed)
|
||||||
}
|
}
|
||||||
logger.debug(LTag.PUMPBTCOMM, "ScanCollector looking for podID: $podID")
|
logger.debug(LTag.PUMPBTCOMM, "ScanCollector looking for podID: $podID")
|
||||||
for (result in found.values) {
|
for (result in found.values) {
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.session
|
|
||||||
|
|
||||||
class CertainFailureException(msg: String) : Exception(msg)
|
|
|
@ -0,0 +1,122 @@
|
||||||
|
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.session
|
||||||
|
|
||||||
|
import android.bluetooth.BluetoothDevice
|
||||||
|
import android.bluetooth.BluetoothGatt
|
||||||
|
import android.bluetooth.BluetoothManager
|
||||||
|
import android.bluetooth.BluetoothProfile
|
||||||
|
import android.content.Context
|
||||||
|
import com.j256.ormlite.stmt.query.Not
|
||||||
|
import info.nightscout.androidaps.logging.AAPSLogger
|
||||||
|
import info.nightscout.androidaps.logging.LTag
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.BuildConfig
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.Id
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.ServiceDiscoverer
|
||||||
|
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.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
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.io.DataBleIO
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.io.IncomingPackets
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.message.MessageIO
|
||||||
|
import info.nightscout.androidaps.utils.extensions.toHex
|
||||||
|
import info.nightscout.androidaps.utils.extensions.wait
|
||||||
|
|
||||||
|
sealed class ConnectionState
|
||||||
|
|
||||||
|
object Connected : ConnectionState()
|
||||||
|
object NotConnected : ConnectionState()
|
||||||
|
|
||||||
|
class Connection(val podDevice: BluetoothDevice, private val aapsLogger: AAPSLogger, private val context: Context) {
|
||||||
|
|
||||||
|
private val incomingPackets = IncomingPackets()
|
||||||
|
private val bleCommCallbacks = BleCommCallbacks(aapsLogger, incomingPackets)
|
||||||
|
private val gattConnection: BluetoothGatt
|
||||||
|
|
||||||
|
private val bluetoothManager: BluetoothManager =
|
||||||
|
context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
|
||||||
|
|
||||||
|
init {
|
||||||
|
aapsLogger.debug(LTag.PUMPBTCOMM, "Connecting to ${podDevice.address}")
|
||||||
|
|
||||||
|
val autoConnect = false
|
||||||
|
gattConnection = podDevice.connectGatt(context, autoConnect, bleCommCallbacks, BluetoothDevice.TRANSPORT_LE)
|
||||||
|
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)
|
||||||
|
|
||||||
|
init {
|
||||||
|
val sendResult = cmdBleIO.hello()
|
||||||
|
if (sendResult !is BleSendSuccess) {
|
||||||
|
throw FailedToConnectException("Could not send HELLO command to ${podDevice.address}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val dataBleIO = DataBleIO(aapsLogger, discoveredCharacteristics[CharacteristicType.DATA]!!, incomingPackets
|
||||||
|
.dataQueue, gattConnection, bleCommCallbacks)
|
||||||
|
val msgIO = MessageIO(aapsLogger, cmdBleIO, dataBleIO)
|
||||||
|
var session: Session? = null
|
||||||
|
|
||||||
|
fun connect() {
|
||||||
|
if (!gattConnection.connect()) {
|
||||||
|
throw FailedToConnectException("connect() returned false")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (waitForConnection() is NotConnected){
|
||||||
|
throw FailedToConnectException(podDevice.address)
|
||||||
|
}
|
||||||
|
|
||||||
|
cmdBleIO.hello()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun disconnect() {
|
||||||
|
gattConnection.disconnect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun waitForConnection(): ConnectionState {
|
||||||
|
try {
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
return connectionState()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun connectionState(): ConnectionState {
|
||||||
|
val connectionState = bluetoothManager.getConnectionState(podDevice, BluetoothProfile.GATT)
|
||||||
|
aapsLogger.debug(LTag.PUMPBTCOMM, "GATT connection state: $connectionState")
|
||||||
|
if (connectionState != BluetoothProfile.STATE_CONNECTED) {
|
||||||
|
return NotConnected
|
||||||
|
}
|
||||||
|
return Connected
|
||||||
|
}
|
||||||
|
|
||||||
|
fun establishSession(ltk: ByteArray, msgSeq: Byte, myId: Id, podID: Id, eapSqn: ByteArray) {
|
||||||
|
val eapAkaExchanger = SessionEstablisher(aapsLogger, msgIO, ltk, eapSqn, myId, podID, msgSeq)
|
||||||
|
val keys = eapAkaExchanger.negotiateSessionKeys()
|
||||||
|
if (BuildConfig.DEBUG) {
|
||||||
|
aapsLogger.info(LTag.PUMPCOMM, "CK: ${keys.ck.toHex()}")
|
||||||
|
aapsLogger.info(LTag.PUMPCOMM, "msgSequenceNumber: ${keys.msgSequenceNumber}")
|
||||||
|
aapsLogger.info(LTag.PUMPCOMM, "Nonce: ${keys.nonce}")
|
||||||
|
}
|
||||||
|
val enDecrypt = EnDecrypt(
|
||||||
|
aapsLogger,
|
||||||
|
keys.nonce,
|
||||||
|
keys.ck
|
||||||
|
)
|
||||||
|
session = Session(aapsLogger, msgIO, myId, podID, sessionKeys = keys, enDecrypt = enDecrypt)
|
||||||
|
}
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
private const val CONNECT_TIMEOUT_MS = 7000
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,16 +4,25 @@ 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.Id
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.Id
|
||||||
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.message.MessageIO
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.message.*
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.message.MessagePacket
|
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.message.MessageType
|
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.message.StringLengthPrefixEncoding
|
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.message.StringLengthPrefixEncoding.Companion.parseKeys
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.message.StringLengthPrefixEncoding.Companion.parseKeys
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command.base.Command
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command.base.Command
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.response.NakResponse
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.response.NakResponse
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.response.Response
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.response.Response
|
||||||
import info.nightscout.androidaps.utils.extensions.toHex
|
import info.nightscout.androidaps.utils.extensions.toHex
|
||||||
import java.util.concurrent.TimeoutException
|
|
||||||
|
sealed class CommandSendResult
|
||||||
|
object CommandSendSuccess: CommandSendResult()
|
||||||
|
data class CommandSendErrorSending(val msg: String): CommandSendResult()
|
||||||
|
|
||||||
|
// This error marks the undefined state
|
||||||
|
data class CommandSendErrorConfirming(val msg: String): CommandSendResult()
|
||||||
|
|
||||||
|
sealed class CommandReceiveResult
|
||||||
|
data class CommandReceiveSuccess(val result: Response): CommandReceiveResult()
|
||||||
|
data class CommandReceiveError(val msg: String): CommandReceiveResult()
|
||||||
|
data class CommandAckError(val result: Response, val msg: String): CommandReceiveResult()
|
||||||
|
|
||||||
|
|
||||||
class Session(
|
class Session(
|
||||||
private val aapsLogger: AAPSLogger,
|
private val aapsLogger: AAPSLogger,
|
||||||
|
@ -24,51 +33,63 @@ class Session(
|
||||||
val enDecrypt: EnDecrypt
|
val enDecrypt: EnDecrypt
|
||||||
) {
|
) {
|
||||||
|
|
||||||
/**
|
fun sendCommand(cmd: Command): CommandSendResult {
|
||||||
* Used for commands:
|
|
||||||
* -> command with retries
|
|
||||||
* <- response, ACK TODO: retries?
|
|
||||||
* -> ACK
|
|
||||||
*/
|
|
||||||
fun sendCommand(cmd: Command): Response {
|
|
||||||
sessionKeys.msgSequenceNumber++
|
sessionKeys.msgSequenceNumber++
|
||||||
aapsLogger.debug(LTag.PUMPBTCOMM, "Sending command: ${cmd.encoded.toHex()} in packet $cmd")
|
aapsLogger.debug(LTag.PUMPBTCOMM, "Sending command: ${cmd.encoded.toHex()} in packet $cmd")
|
||||||
var tries = 0
|
var tries = 0
|
||||||
var certainFailure = true
|
val msg = getCmdMessage(cmd)
|
||||||
|
var possiblyUnconfirmedCommand = false
|
||||||
for (i in 0..MAX_TRIES) {
|
for (i in 0..MAX_TRIES) {
|
||||||
try {
|
aapsLogger.debug(LTag.PUMPBTCOMM, "Sending command(wrapped): ${msg.payload.toHex()}")
|
||||||
val msg = getCmdMessage(cmd)
|
|
||||||
aapsLogger.debug(LTag.PUMPBTCOMM, "Sending command(wrapped): ${msg.payload.toHex()}")
|
when (val sendResult = msgIO.sendMessage(msg)) {
|
||||||
msgIO.sendMessage(msg)
|
is MessageSendSuccess ->
|
||||||
} catch (e: TimeoutException) {
|
return CommandSendSuccess
|
||||||
aapsLogger.info(LTag.PUMPBTCOMM,"Exception trying to send command: $e. Try: $i/$MAX_TRIES")
|
is MessageSendErrorConfirming -> {
|
||||||
} // TODO filter out certain vs uncertain errors
|
possiblyUnconfirmedCommand = true
|
||||||
}
|
aapsLogger.debug(LTag.PUMPBTCOMM, "Error confirming command: $sendResult")
|
||||||
certainFailure = false
|
}
|
||||||
var response: Response?= null
|
is MessageSendErrorSending ->
|
||||||
for (i in 0..MAX_TRIES) {
|
aapsLogger.debug(LTag.PUMPBTCOMM, "Error sending command: $sendResult")
|
||||||
try {
|
|
||||||
val responseMsg = msgIO.receiveMessage()
|
|
||||||
val decrypted = enDecrypt.decrypt(responseMsg)
|
|
||||||
aapsLogger.debug(LTag.PUMPBTCOMM, "Received response: $decrypted")
|
|
||||||
response = parseResponse(decrypted)
|
|
||||||
sessionKeys.msgSequenceNumber++
|
|
||||||
val ack = getAck(responseMsg)
|
|
||||||
aapsLogger.debug(LTag.PUMPBTCOMM, "Sending ACK: ${ack.payload.toHex()} in packet $ack")
|
|
||||||
msgIO.sendMessage(ack)
|
|
||||||
} catch (e: TimeoutException) {
|
|
||||||
aapsLogger.info(LTag.PUMPBTCOMM,"Exception trying to send command: $e. Try: $i/$MAX_TRIES")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
response?.let{
|
|
||||||
return it
|
val errMsg = "Maximum number of tries reached. Could not send command\""
|
||||||
}
|
return if (possiblyUnconfirmedCommand)
|
||||||
if (certainFailure) {
|
CommandSendErrorConfirming(errMsg)
|
||||||
throw CertainFailureException("Could not send command")
|
else
|
||||||
}
|
CommandSendErrorSending(errMsg)
|
||||||
throw UncertainFailureException("Possible failure to send commnd")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun readAndAckCommandResponse(): CommandReceiveResult {
|
||||||
|
var responseMsgPacket: MessagePacket?= null
|
||||||
|
for (i in 0..MAX_TRIES) {
|
||||||
|
val responseMsg = msgIO.receiveMessage()
|
||||||
|
if (responseMsg !is MessageReceiveSuccess) {
|
||||||
|
aapsLogger.debug(LTag.PUMPBTCOMM, "Error receiving response: $responseMsg")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
responseMsgPacket = responseMsg.msg
|
||||||
|
}
|
||||||
|
if (responseMsgPacket == null) {
|
||||||
|
return CommandReceiveError("Could not read response")
|
||||||
|
}
|
||||||
|
|
||||||
|
val decrypted = enDecrypt.decrypt(responseMsgPacket)
|
||||||
|
aapsLogger.debug(LTag.PUMPBTCOMM, "Received response: $decrypted")
|
||||||
|
val response = parseResponse(decrypted)
|
||||||
|
|
||||||
|
sessionKeys.msgSequenceNumber++
|
||||||
|
val ack = getAck(responseMsgPacket)
|
||||||
|
aapsLogger.debug(LTag.PUMPBTCOMM, "Sending ACK: ${ack.payload.toHex()} in packet $ack")
|
||||||
|
val sendResult = msgIO.sendMessage(ack)
|
||||||
|
if (sendResult !is MessageSendSuccess) {
|
||||||
|
return CommandAckError(response, "Could not ACK the response: $sendResult")
|
||||||
|
}
|
||||||
|
return CommandReceiveSuccess(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun parseResponse(decrypted: MessagePacket): Response {
|
private fun parseResponse(decrypted: MessagePacket): Response {
|
||||||
|
|
||||||
val payload = parseKeys(arrayOf(RESPONSE_PREFIX), decrypted.payload)[0]
|
val payload = parseKeys(arrayOf(RESPONSE_PREFIX), decrypted.payload)[0]
|
||||||
|
|
|
@ -4,12 +4,16 @@ 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.Id
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.Id
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.endecrypt.Nonce
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.endecrypt.Nonce
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions.CouldNotInitiateConnection
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions.SessionEstablishmentException
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions.SessionEstablishmentException
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.message.MessageIO
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.message.MessageIO
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.message.MessagePacket
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.message.MessagePacket
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.message.MessageReceiveSuccess
|
||||||
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.message.MessageSendSuccess
|
||||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.message.MessageType
|
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.message.MessageType
|
||||||
import info.nightscout.androidaps.utils.extensions.toHex
|
import info.nightscout.androidaps.utils.extensions.toHex
|
||||||
import java.security.SecureRandom
|
import java.security.SecureRandom
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
class SessionEstablisher(
|
class SessionEstablisher(
|
||||||
private val aapsLogger: AAPSLogger,
|
private val aapsLogger: AAPSLogger,
|
||||||
|
@ -23,7 +27,7 @@ class SessionEstablisher(
|
||||||
|
|
||||||
private val controllerIV = ByteArray(IV_SIZE)
|
private val controllerIV = ByteArray(IV_SIZE)
|
||||||
private var nodeIV = ByteArray(IV_SIZE)
|
private var nodeIV = ByteArray(IV_SIZE)
|
||||||
|
private val identifier = Random().nextInt().toByte()
|
||||||
private val milenage = Milenage(aapsLogger, ltk, eapSqn)
|
private val milenage = Milenage(aapsLogger, ltk, eapSqn)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
@ -36,13 +40,18 @@ class SessionEstablisher(
|
||||||
}
|
}
|
||||||
|
|
||||||
fun negotiateSessionKeys(): SessionKeys {
|
fun negotiateSessionKeys(): SessionKeys {
|
||||||
// send EAP-AKA challenge
|
|
||||||
msgSeq++
|
msgSeq++
|
||||||
var challenge = eapAkaChallenge()
|
var challenge = eapAkaChallenge()
|
||||||
msgIO.sendMessage(challenge)
|
val sendResult = msgIO.sendMessage(challenge)
|
||||||
|
if (sendResult !is MessageSendSuccess) {
|
||||||
|
throw SessionEstablishmentException("Could not send the EAP AKA challenge: $sendResult")
|
||||||
|
}
|
||||||
val challengeResponse = msgIO.receiveMessage()
|
val challengeResponse = msgIO.receiveMessage()
|
||||||
processChallengeResponse(challengeResponse) // TODO: what do we have to answer if challenge response does not validate?
|
if (challengeResponse !is MessageReceiveSuccess) {
|
||||||
|
throw SessionEstablishmentException("Could not establish session: $challengeResponse")
|
||||||
|
}
|
||||||
|
|
||||||
|
processChallengeResponse(challengeResponse.msg)
|
||||||
|
|
||||||
msgSeq++
|
msgSeq++
|
||||||
var success = eapSuccess()
|
var success = eapSuccess()
|
||||||
|
@ -67,7 +76,7 @@ class SessionEstablisher(
|
||||||
|
|
||||||
val eapMsg = EapMessage(
|
val eapMsg = EapMessage(
|
||||||
code = EapCode.REQUEST,
|
code = EapCode.REQUEST,
|
||||||
identifier = 189.toByte(), // TODO: find what value we need here, it's probably random
|
identifier = identifier, // TODO: find what value we need here, it's probably random
|
||||||
attributes = attributes
|
attributes = attributes
|
||||||
)
|
)
|
||||||
return MessagePacket(
|
return MessagePacket(
|
||||||
|
@ -80,12 +89,14 @@ class SessionEstablisher(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun processChallengeResponse(challengeResponse: MessagePacket) {
|
private fun processChallengeResponse(challengeResponse: MessagePacket) {
|
||||||
// TODO verify that identifier matches identifier from the Challenge
|
|
||||||
val eapMsg = EapMessage.parse(aapsLogger, challengeResponse.payload)
|
val eapMsg = EapMessage.parse(aapsLogger, challengeResponse.payload)
|
||||||
|
if (eapMsg.identifier != identifier ) {
|
||||||
|
aapsLogger.debug(LTag.PUMPBTCOMM, "EAP-AKA: got incorrect identifier ${eapMsg.identifier} expected: $identifier")
|
||||||
|
throw SessionEstablishmentException("Received incorrect EAP identifier: ${eapMsg.identifier}")
|
||||||
|
}
|
||||||
if (eapMsg.attributes.size != 2) {
|
if (eapMsg.attributes.size != 2) {
|
||||||
aapsLogger.debug(LTag.PUMPBTCOMM, "EAP-AKA: got message: $eapMsg")
|
aapsLogger.debug(LTag.PUMPBTCOMM, "EAP-AKA: got message: $eapMsg")
|
||||||
if (eapMsg.attributes.size == 1 && eapMsg.attributes[0] is EapAkaAttributeClientErrorCode) {
|
if (eapMsg.attributes.size == 1 && eapMsg.attributes[0] is EapAkaAttributeClientErrorCode) {
|
||||||
// TODO: special exception for this
|
|
||||||
throw SessionEstablishmentException("Received CLIENT_ERROR_CODE for EAP-AKA challenge: ${eapMsg.attributes[0].toByteArray().toHex()}")
|
throw SessionEstablishmentException("Received CLIENT_ERROR_CODE for EAP-AKA challenge: ${eapMsg.attributes[0].toByteArray().toHex()}")
|
||||||
}
|
}
|
||||||
throw SessionEstablishmentException("Expecting two attributes, got: ${eapMsg.attributes.size}")
|
throw SessionEstablishmentException("Expecting two attributes, got: ${eapMsg.attributes.size}")
|
||||||
|
@ -108,7 +119,7 @@ class SessionEstablisher(
|
||||||
val eapMsg = EapMessage(
|
val eapMsg = EapMessage(
|
||||||
code = EapCode.SUCCESS,
|
code = EapCode.SUCCESS,
|
||||||
attributes = arrayOf(),
|
attributes = arrayOf(),
|
||||||
identifier = 189.toByte() // TODO: find what value we need here
|
identifier = identifier.toByte()
|
||||||
)
|
)
|
||||||
|
|
||||||
return MessagePacket(
|
return MessagePacket(
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.session
|
|
||||||
|
|
||||||
class UncertainFailureException(msg: String) : Exception(msg)
|
|
|
@ -20,5 +20,7 @@ sealed class PodEvent {
|
||||||
/* Message exchange events */
|
/* Message exchange events */
|
||||||
class CommandSending(val command: Command) : PodEvent()
|
class CommandSending(val command: Command) : PodEvent()
|
||||||
class CommandSent(val command: Command) : PodEvent()
|
class CommandSent(val command: Command) : PodEvent()
|
||||||
|
class CommandSendNotConfirmed(val command: Command) : PodEvent()
|
||||||
|
|
||||||
class ResponseReceived(val response: Response) : PodEvent()
|
class ResponseReceived(val response: Response) : PodEvent()
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue