improve error handling
This commit is contained in:
parent
ae08e43109
commit
4b49392200
|
@ -1,34 +1,23 @@
|
|||
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm
|
||||
|
||||
import android.bluetooth.BluetoothAdapter
|
||||
import android.bluetooth.BluetoothDevice
|
||||
import android.bluetooth.BluetoothGatt
|
||||
import android.bluetooth.BluetoothManager
|
||||
import android.bluetooth.BluetoothProfile
|
||||
import android.content.Context
|
||||
import android.os.Message
|
||||
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.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.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.pair.LTKExchanger
|
||||
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.SessionEstablisher
|
||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.session.SessionKeys
|
||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.session.*
|
||||
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.pod.command.base.Command
|
||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.state.OmnipodDashPodStateManager
|
||||
import info.nightscout.androidaps.utils.extensions.toHex
|
||||
import io.reactivex.Observable
|
||||
import java.util.concurrent.BlockingQueue
|
||||
import java.util.concurrent.LinkedBlockingDeque
|
||||
import java.util.concurrent.TimeoutException
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
@ -40,88 +29,50 @@ class OmnipodDashBleManagerImpl @Inject constructor(
|
|||
private val podState: OmnipodDashPodStateManager
|
||||
) : OmnipodDashBleManager {
|
||||
|
||||
// TODO: add busy AtomicBoolean
|
||||
private val bluetoothManager: BluetoothManager =
|
||||
context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
|
||||
private val bluetoothAdapter: BluetoothAdapter = bluetoothManager.adapter
|
||||
private var sessionKeys: SessionKeys? = null
|
||||
private var msgIO: MessageIO? = null
|
||||
private var gatt: BluetoothGatt? = null
|
||||
private var connection: Connection? = null
|
||||
private var status: ConnectionStatus = ConnectionStatus.IDLE
|
||||
private val myId = Id.fromInt(CONTROLLER_ID)
|
||||
private val uniqueId = podState.uniqueId
|
||||
private val podId = uniqueId?.let(Id::fromLong)
|
||||
?: 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 ->
|
||||
try {
|
||||
val keys = sessionKeys
|
||||
val mIO = msgIO
|
||||
if (keys == null || mIO == null) {
|
||||
throw Exception("Not connected")
|
||||
val conn = connection ?: throw NotConnectedException("Not connected")
|
||||
|
||||
val session = conn.session ?: throw NotConnectedException("Missing session")
|
||||
|
||||
emitter.onNext(PodEvent.CommandSending(cmd))
|
||||
|
||||
val sendResult = session.sendCommand(cmd)
|
||||
when(sendResult) {
|
||||
is CommandSendErrorSending -> {
|
||||
emitter.tryOnError(CouldNotSendCommandException())
|
||||
return@create
|
||||
}
|
||||
emitter.onNext(PodEvent.CommandSending(cmd))
|
||||
// TODO switch to RX
|
||||
emitter.onNext(PodEvent.CommandSent(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)
|
||||
is CommandSendSuccess ->
|
||||
emitter.onNext(PodEvent.CommandSent(cmd))
|
||||
is CommandSendErrorConfirming ->
|
||||
emitter.onNext(PodEvent.CommandSendNotConfirmed(cmd))
|
||||
}
|
||||
|
||||
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 {
|
||||
|
@ -132,42 +83,23 @@ class OmnipodDashBleManagerImpl @Inject constructor(
|
|||
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 ->
|
||||
try {
|
||||
emitter.onNext(PodEvent.BluetoothConnecting)
|
||||
|
||||
val podAddress =
|
||||
podState.bluetoothAddress
|
||||
?: throw FailedToConnectException("Missing bluetoothAddress, activate the pod first")
|
||||
// check if already connected
|
||||
val podDevice = bluetoothAdapter.getRemoteDevice(podAddress)
|
||||
val connectionState = bluetoothManager.getConnectionState(podDevice, BluetoothProfile.GATT)
|
||||
aapsLogger.debug(LTag.PUMPBTCOMM, "GATT connection state: $connectionState")
|
||||
if (connectionState == BluetoothProfile.STATE_CONNECTED) {
|
||||
emitter.onNext(PodEvent.AlreadyConnected(podAddress))
|
||||
val conn = connection
|
||||
?: Connection(podDevice, aapsLogger, context)
|
||||
connection = conn
|
||||
if (conn.connectionState() is Connected) {
|
||||
emitter.onNext(PodEvent.Connected)
|
||||
emitter.onComplete()
|
||||
return@create
|
||||
}
|
||||
|
||||
emitter.onNext(PodEvent.BluetoothConnecting)
|
||||
if (msgIO != null) {
|
||||
disconnect()
|
||||
}
|
||||
val bleIO = connect(podDevice)
|
||||
val mIO = MessageIO(aapsLogger, bleIO)
|
||||
msgIO = mIO
|
||||
conn.connect()
|
||||
emitter.onNext(PodEvent.BluetoothConnected(podAddress))
|
||||
|
||||
emitter.onNext(PodEvent.EstablishingSession)
|
||||
|
@ -182,23 +114,15 @@ class OmnipodDashBleManagerImpl @Inject constructor(
|
|||
}
|
||||
|
||||
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 uniqueId = podState.uniqueId
|
||||
val podId = uniqueId?.let { Id.fromLong(uniqueId) }
|
||||
?: myId.increment() // pod not activated
|
||||
|
||||
val eapSqn = podState.increaseEapAkaSequenceNumber()
|
||||
val eapAkaExchanger = SessionEstablisher(aapsLogger, mIO, ltk, eapSqn, myId, podId, msgSeq)
|
||||
val keys = eapAkaExchanger.negotiateSessionKeys()
|
||||
conn.establishSession(ltk, msgSeq, myId, podId, eapSqn)
|
||||
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 ->
|
||||
|
@ -209,7 +133,7 @@ class OmnipodDashBleManagerImpl @Inject constructor(
|
|||
emitter.onComplete()
|
||||
return@create
|
||||
}
|
||||
aapsLogger.info(LTag.PUMPBTCOMM, "starting new pod activation")
|
||||
aapsLogger.info(LTag.PUMPBTCOMM, "Starting new pod activation")
|
||||
|
||||
emitter.onNext(PodEvent.Scanning)
|
||||
val podScanner = PodScanner(aapsLogger, bluetoothAdapter)
|
||||
|
@ -221,13 +145,17 @@ class OmnipodDashBleManagerImpl @Inject constructor(
|
|||
|
||||
emitter.onNext(PodEvent.BluetoothConnecting)
|
||||
val podDevice = bluetoothAdapter.getRemoteDevice(podAddress)
|
||||
val bleIO = connect(podDevice)
|
||||
val mIO = MessageIO(aapsLogger, bleIO)
|
||||
msgIO = mIO
|
||||
val conn = Connection(podDevice, aapsLogger, context)
|
||||
connection = conn
|
||||
emitter.onNext(PodEvent.BluetoothConnected(podAddress))
|
||||
|
||||
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()
|
||||
emitter.onNext(PodEvent.Paired(podId))
|
||||
podState.updateFromPairing(podId, pairResult)
|
||||
|
@ -246,16 +174,14 @@ class OmnipodDashBleManagerImpl @Inject constructor(
|
|||
}
|
||||
|
||||
override fun disconnect() {
|
||||
val localGatt = gatt
|
||||
localGatt?.close() // TODO: use disconnect?
|
||||
gatt = null
|
||||
msgIO = null
|
||||
sessionKeys = null
|
||||
if (connection == null) {
|
||||
aapsLogger.info(LTag.PUMPBTCOMM, "Trying to disconnect a null connection")
|
||||
}
|
||||
connection?.disconnect()
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private const val CONNECT_TIMEOUT_MS = 7000
|
||||
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 info.nightscout.androidaps.logging.AAPSLogger
|
||||
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.IncomingPackets
|
||||
import info.nightscout.androidaps.utils.extensions.toHex
|
||||
import java.util.*
|
||||
import java.util.concurrent.BlockingQueue
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.LinkedBlockingQueue
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.concurrent.TimeoutException
|
||||
|
||||
class BleCommCallbacks(
|
||||
private val aapsLogger: AAPSLogger,
|
||||
private val incomingPackets: Map<CharacteristicType, BlockingQueue<ByteArray>>
|
||||
private val incomingPackets: IncomingPackets,
|
||||
) : BluetoothGattCallback() {
|
||||
|
||||
private val serviceDiscoveryComplete: CountDownLatch = CountDownLatch(1)
|
||||
private val connected: CountDownLatch = CountDownLatch(1)
|
||||
private val writeQueue: BlockingQueue<CharacteristicWriteConfirmation> = LinkedBlockingQueue(1)
|
||||
private val descriptorWriteQueue: BlockingQueue<DescriptorWriteConfirmation> = LinkedBlockingQueue(1)
|
||||
private val writeQueue: BlockingQueue<WriteConfirmation> = LinkedBlockingQueue(1)
|
||||
|
||||
override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) {
|
||||
super.onConnectionStateChange(gatt, status, newState)
|
||||
|
@ -54,52 +51,32 @@ class BleCommCallbacks(
|
|||
serviceDiscoveryComplete.await(timeoutMs.toLong(), TimeUnit.MILLISECONDS)
|
||||
}
|
||||
|
||||
@Throws(InterruptedException::class, TimeoutException::class, CouldNotConfirmWriteException::class)
|
||||
fun confirmWrite(expectedPayload: ByteArray, timeoutMs: Long) {
|
||||
val received: CharacteristicWriteConfirmation = writeQueue.poll(timeoutMs, TimeUnit.MILLISECONDS)
|
||||
?: throw TimeoutException()
|
||||
|
||||
when (received) {
|
||||
is CharacteristicWriteConfirmationPayload -> confirmWritePayload(expectedPayload, received)
|
||||
is CharacteristicWriteConfirmationError -> throw CouldNotConfirmWriteException(received.status)
|
||||
fun confirmWrite(expectedPayload: ByteArray, expectedUUID: String, timeoutMs: Long) : WriteConfirmation{
|
||||
try {
|
||||
return when(val received = writeQueue.poll(timeoutMs, TimeUnit.MILLISECONDS) ) {
|
||||
null -> return WriteConfirmationError("Timeout waiting for writeConfirmation")
|
||||
is WriteConfirmationSuccess ->
|
||||
if (expectedPayload.contentEquals(received.payload) &&
|
||||
expectedUUID == received.uuid) {
|
||||
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) {
|
||||
super.onCharacteristicWrite(gatt, characteristic, status)
|
||||
val writeConfirmation = if (status == BluetoothGatt.GATT_SUCCESS) {
|
||||
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")
|
||||
}
|
||||
onWrite(status, characteristic.uuid, characteristic.value)
|
||||
}
|
||||
|
||||
override fun onCharacteristicChanged(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic) {
|
||||
|
@ -112,57 +89,52 @@ class BleCommCallbacks(
|
|||
characteristicType + "/" +
|
||||
payload.toHex()
|
||||
)
|
||||
incomingPackets[characteristicType]!!.add(payload)
|
||||
}
|
||||
|
||||
@Throws(InterruptedException::class, CouldNotConfirmDescriptorWriteException::class)
|
||||
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)
|
||||
}
|
||||
}
|
||||
incomingPackets.byCharacteristicType(characteristicType).add(payload)
|
||||
}
|
||||
|
||||
override fun onDescriptorWrite(gatt: BluetoothGatt, descriptor: BluetoothGattDescriptor, status: Int) {
|
||||
super.onDescriptorWrite(gatt, descriptor, status)
|
||||
val writeConfirmation = if (status == BluetoothGatt.GATT_SUCCESS) {
|
||||
aapsLogger.debug(LTag.PUMPBTCOMM, "OnDescriptor value " + descriptor.value.toHex())
|
||||
DescriptorWriteConfirmationUUID(descriptor.uuid.toString())
|
||||
} else {
|
||||
DescriptorWriteConfirmationError(status)
|
||||
|
||||
onWrite(status, descriptor.uuid, descriptor.value)
|
||||
}
|
||||
|
||||
private fun onWrite(status: Int, uuid: UUID?, value: ByteArray?) {
|
||||
if (uuid == null || value == null) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
if (descriptorWriteQueue.size > 0) {
|
||||
aapsLogger.warn(
|
||||
LTag.PUMPBTCOMM,
|
||||
"Descriptor write queue should be empty, found: ${descriptorWriteQueue.size}"
|
||||
)
|
||||
descriptorWriteQueue.clear()
|
||||
val writeConfirmation = when {
|
||||
uuid == null || value == null ->
|
||||
WriteConfirmationError("onWrite received Null: UUID=$uuid, value=${value.toHex()} status=$status")
|
||||
status == BluetoothGatt.GATT_SUCCESS -> {
|
||||
aapsLogger.debug(LTag.PUMPBTCOMM, "OnWrite value " + value.toHex())
|
||||
WriteConfirmationSuccess(uuid.toString(), value)
|
||||
}
|
||||
val offered = descriptorWriteQueue.offer(
|
||||
else ->WriteConfirmationError("onDescriptorWrite status is not success: $status")
|
||||
}
|
||||
|
||||
try {
|
||||
flushConfirmationQueue()
|
||||
val offered = writeQueue.offer(
|
||||
writeConfirmation,
|
||||
WRITE_CONFIRM_TIMEOUT_MS.toLong(),
|
||||
TimeUnit.MILLISECONDS
|
||||
)
|
||||
if (!offered) {
|
||||
aapsLogger.warn(LTag.PUMPBTCOMM, "Received delayed descriptor write confirmation")
|
||||
aapsLogger.warn(LTag.PUMPBTCOMM, "Received delayed write confirmation")
|
||||
}
|
||||
} 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
|
||||
|
||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command.base.CommandType
|
||||
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))
|
||||
|
||||
|
@ -36,4 +62,35 @@ open class BleCommand(val data: ByteArray) {
|
|||
override fun hashCode(): Int {
|
||||
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()),
|
||||
SUCCESS(0x04.toByte()),
|
||||
FAIL(0x05.toByte()),
|
||||
HELLO(0x06.toByte());
|
||||
HELLO(0x06.toByte()),
|
||||
INCORRECT(0x09.toByte());
|
||||
|
||||
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
|
||||
|
||||
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 {
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
open class ScanFailException : Exception {
|
||||
open class ScanException : Exception {
|
||||
constructor(message: String) : super(message)
|
||||
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 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)
|
||||
val discoveredDevices: List<BleDiscoveredDevice>
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
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.LTag
|
||||
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.utils.extensions.toHex
|
||||
import java.util.concurrent.BlockingQueue
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.concurrent.TimeoutException
|
||||
|
||||
class BleIO(
|
||||
private val aapsLogger: AAPSLogger,
|
||||
private val chars: Map<CharacteristicType, BluetoothGattCharacteristic>,
|
||||
private val incomingPackets: Map<CharacteristicType, BlockingQueue<ByteArray>>,
|
||||
private val gatt: BluetoothGatt,
|
||||
private val bleCommCallbacks: BleCommCallbacks
|
||||
) {
|
||||
sealed class BleReceiveResult
|
||||
data class BleReceivePayload(val payload: ByteArray) : BleReceiveResult()
|
||||
data class BleReceiveError(val msg: String, val cause: Throwable? = null) : BleReceiveResult()
|
||||
|
||||
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)
|
||||
* @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(characteristic: CharacteristicType, timeoutMs:Long = DEFAULT_IO_TIMEOUT_MS): ByteArray {
|
||||
synchronized(state) {
|
||||
if (state != IOState.IDLE) {
|
||||
throw BleIOBusyException()
|
||||
}
|
||||
state = IOState.READING
|
||||
fun receivePacket(timeoutMs: Long = DEFAULT_IO_TIMEOUT_MS): BleReceiveResult {
|
||||
try {
|
||||
val ret = incomingPackets.poll(timeoutMs, TimeUnit.MILLISECONDS)
|
||||
?: return BleReceiveError("Timeout")
|
||||
return BleReceivePayload(ret)
|
||||
} catch (e: InterruptedException) {
|
||||
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 payload the data to send
|
||||
* @throws CouldNotSendBleException
|
||||
*/
|
||||
@Throws(
|
||||
CouldNotSendBleException::class,
|
||||
BleIOBusyException::class,
|
||||
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)
|
||||
fun sendAndConfirmPacket(payload: ByteArray): BleSendResult {
|
||||
aapsLogger.debug(LTag.PUMPBTCOMM, "BleIO: Sending data on ${payload.toHex()}")
|
||||
val set = characteristic.setValue(payload)
|
||||
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) {
|
||||
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.
|
||||
* The incoming queues should be empty, so we log when they are not.
|
||||
*/
|
||||
fun flushIncomingQueues() {
|
||||
synchronized(state) { state = IOState.IDLE }
|
||||
|
||||
for (char in CharacteristicType.values()) {
|
||||
do {
|
||||
val found = incomingPackets[char]?.poll()?.also {
|
||||
aapsLogger.warn(LTag.PUMPBTCOMM, "BleIO: ${char.name} queue not empty, flushing: {${it.toHex()}")
|
||||
}
|
||||
} while (found != null)
|
||||
}
|
||||
fun flushIncomingQueue() {
|
||||
do {
|
||||
val found = incomingPackets.poll()?.also {
|
||||
aapsLogger.warn(LTag.PUMPBTCOMM, "BleIO: 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
|
||||
* @return
|
||||
*/
|
||||
@Throws(
|
||||
CouldNotSendBleException::class,
|
||||
CouldNotEnableNotifications::class,
|
||||
DescriptorNotFoundException::class,
|
||||
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)
|
||||
fun readyToRead(): BleSendResult {
|
||||
val notificationSet = gatt.setCharacteristicNotification(characteristic, true)
|
||||
if (!notificationSet) {
|
||||
throw CouldNotInitiateConnection("Could not enable notifications")
|
||||
}
|
||||
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 {
|
||||
|
||||
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 {
|
||||
|
||||
@JvmStatic
|
||||
fun byValue(value: String): CharacteristicType =
|
||||
values().firstOrNull { it.value == 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.LTag
|
||||
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.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.io.*
|
||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.packet.BlePacket
|
||||
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>()
|
||||
var maxTries = 3
|
||||
var tries = 0
|
||||
var maxMessageReadTries = 3
|
||||
var messageReadTries = 0
|
||||
|
||||
private fun expectCommandType(actual: BleCommand, expected: BleCommand) {
|
||||
if (actual.data.isEmpty()) {
|
||||
throw UnexpectedCommandException(actual)
|
||||
fun sendMessage(msg: MessagePacket): MessageSendResult {
|
||||
cmdBleIO.flushIncomingQueue()
|
||||
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]) {
|
||||
return
|
||||
|
||||
val expectCTS = cmdBleIO.expectCommandType(BleCommandCTS)
|
||||
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()
|
||||
aapsLogger.debug(LTag.PUMPBTCOMM, "Sending message: ${payload.toHex()}")
|
||||
val splitter = PayloadSplitter(payload)
|
||||
val packets = splitter.splitInPackets()
|
||||
|
||||
for ((index, packet) in packets.withIndex()) {
|
||||
aapsLogger.debug(LTag.PUMPBTCOMM, "Sending DATA: ${packet.toByteArray().toHex()}")
|
||||
bleIO.sendAndConfirmPacket(CharacteristicType.DATA, packet.toByteArray())
|
||||
peekForNack(index, packets)
|
||||
// This is implementing the same logic as the PDM.
|
||||
// I think it wil not work in case of packet lost.
|
||||
// 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).
|
||||
// I don't worry too much about this because for commands we have retries implemented at MessagePacket level anyway
|
||||
// If this will be a problem in the future, the fix might be(pending testing with a real pod) to move back the index
|
||||
// at the value received in the NACK and make sure don't retry forever.
|
||||
}
|
||||
val expectSuccess = bleIO.receivePacket(CharacteristicType.CMD)
|
||||
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
|
||||
val sendResult = dataBleIO.sendAndConfirmPacket(packet.toByteArray())
|
||||
val ret = handleSendResult(sendResult, index, packets)
|
||||
if (ret !is MessageSendSuccess) {
|
||||
return ret
|
||||
}
|
||||
val peek = peekForNack(index, packets)
|
||||
if (peek !is MessageSendSuccess) {
|
||||
return if (index == packets.size - 1)
|
||||
MessageSendErrorConfirming(peek.toString())
|
||||
else
|
||||
MessageSendErrorSending(peek.toString())
|
||||
}
|
||||
}
|
||||
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() {
|
||||
maxTries = 3
|
||||
tries = 0
|
||||
receivedOutOfOrder.clear()
|
||||
}
|
||||
fun receiveMessage(): MesssageReceiveResult {
|
||||
cmdBleIO.expectCommandType(BleCommandRTS, MESSAGE_READ_TIMEOUT_MS)
|
||||
|
||||
fun receiveMessage(): MessagePacket {
|
||||
val expectRTS = bleIO.receivePacket(CharacteristicType.CMD, MESSAGE_READ_TIMEOUT_MS)
|
||||
expectCommandType(BleCommand(expectRTS), BleCommandRTS())
|
||||
bleIO.sendAndConfirmPacket(CharacteristicType.CMD, BleCommandCTS().data)
|
||||
val sendResult = cmdBleIO.sendAndConfirmPacket(BleCommandCTS.data)
|
||||
if (sendResult !is BleSendSuccess) {
|
||||
return MessageReceiveError("Error sending CTS: $sendResult")
|
||||
}
|
||||
readReset()
|
||||
var expected: Byte = 0
|
||||
try {
|
||||
val firstPacket = expectBlePacket(0)
|
||||
val joiner = PayloadJoiner(firstPacket)
|
||||
maxTries = joiner.fullFragments * 2 + 2
|
||||
if (firstPacket !is PacketReceiveSuccess) {
|
||||
return MessageReceiveError(firstPacket)
|
||||
}
|
||||
val joiner = PayloadJoiner(firstPacket.payload)
|
||||
maxMessageReadTries = joiner.fullFragments * 2 + 2
|
||||
for (i in 1 until joiner.fullFragments + 1) {
|
||||
expected++
|
||||
val packet = expectBlePacket(expected)
|
||||
joiner.accumulate(packet)
|
||||
if (packet !is PacketReceiveSuccess) {
|
||||
return MessageReceiveError(packet)
|
||||
}
|
||||
joiner.accumulate(packet.payload)
|
||||
}
|
||||
if (joiner.oneExtraPacket) {
|
||||
expected++
|
||||
joiner.accumulate(expectBlePacket(expected))
|
||||
val packet = expectBlePacket(expected)
|
||||
if (packet !is PacketReceiveSuccess) {
|
||||
return MessageReceiveError(packet)
|
||||
}
|
||||
joiner.accumulate(packet.payload)
|
||||
}
|
||||
val fullPayload = joiner.finalize()
|
||||
bleIO.sendAndConfirmPacket(CharacteristicType.CMD, BleCommandSuccess().data)
|
||||
return MessagePacket.parse(fullPayload)
|
||||
cmdBleIO.sendAndConfirmPacket(BleCommandSuccess.data)
|
||||
return MessageReceiveSuccess(MessagePacket.parse(fullPayload))
|
||||
} catch (e: IncorrectPacketException) {
|
||||
aapsLogger.warn(LTag.PUMPBTCOMM, "Could not read message: $e")
|
||||
bleIO.sendAndConfirmPacket(CharacteristicType.CMD, BleCommandAbort().data)
|
||||
throw MessageIOException(cause = e)
|
||||
cmdBleIO.sendAndConfirmPacket(BleCommandAbort.data)
|
||||
return MessageReceiveError("Received incorrect packet: $e", cause = e)
|
||||
} catch (e: CrcMismatchException) {
|
||||
aapsLogger.warn(LTag.PUMPBTCOMM, "CRC mismatch: $e")
|
||||
bleIO.sendAndConfirmPacket(CharacteristicType.CMD, BleCommandFail().data)
|
||||
throw MessageIOException(cause = e)
|
||||
cmdBleIO.sendAndConfirmPacket(BleCommandFail.data)
|
||||
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()
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private const val MAX_PACKET_READ_TRIES = 4
|
||||
private const val MESSAGE_READ_TIMEOUT_MS = 2500.toLong()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,17 +1,18 @@
|
|||
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.LTag
|
||||
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.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.StringLengthPrefixEncoding
|
||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions.PairingException
|
||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.message.*
|
||||
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.X25519KeyGenerator
|
||||
import info.nightscout.androidaps.utils.extensions.hexStringToByteArray
|
||||
import info.nightscout.androidaps.utils.extensions.toHex
|
||||
import info.nightscout.androidaps.utils.extensions.waitMillis
|
||||
|
||||
internal class LTKExchanger(
|
||||
private val aapsLogger: AAPSLogger,
|
||||
|
@ -25,42 +26,54 @@ internal class LTKExchanger(
|
|||
private var seq: Byte = 1
|
||||
|
||||
fun negotiateLTK(): PairResult {
|
||||
// send SP1, 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++
|
||||
val sps1 = sps1()
|
||||
msgIO.sendMessage(sps1.messagePacket)
|
||||
// send SPS1
|
||||
val sp1Result = msgIO.sendMessage(sps1.messagePacket)
|
||||
if (sp1Result !is MessageSendSuccess) {
|
||||
throw PairingException("Could not send SP1: $sp1Result")
|
||||
}
|
||||
|
||||
// read SPS1
|
||||
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
|
||||
|
||||
seq++
|
||||
// send SPS2
|
||||
val sps2 = sps2()
|
||||
msgIO.sendMessage(sps2.messagePacket)
|
||||
// read SPS2
|
||||
val sp2Result = msgIO.sendMessage(sps2.messagePacket)
|
||||
if (sp1Result !is MessageSendSuccess) {
|
||||
throw PairingException("Could not send sps2: ${sp2Result}")
|
||||
}
|
||||
|
||||
val podSps2 = msgIO.receiveMessage()
|
||||
validatePodSps2(podSps2)
|
||||
if (podSps2 !is MessageReceiveSuccess) {
|
||||
throw PairingException("Could not read SPS2: $podSps2")
|
||||
}
|
||||
validatePodSps2(podSps2.msg)
|
||||
|
||||
seq++
|
||||
// send SP0GP0
|
||||
msgIO.sendMessage(sp0gp0().messagePacket)
|
||||
// read P0
|
||||
val sp0gp0Result = msgIO.sendMessage(sp0gp0().messagePacket)
|
||||
if (sp0gp0Result is MessageSendErrorSending) {
|
||||
throw PairingException("Could not send SP0GP0: $sp0gp0Result")
|
||||
}
|
||||
|
||||
// TODO: failing to read or validate p0 will lead to undefined state
|
||||
// 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)
|
||||
// No exception throwing after this point. It is possible that the pod saved the LTK
|
||||
//
|
||||
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(
|
||||
ltk = keyExchange.ltk,
|
||||
msgSeq = seq
|
||||
|
@ -147,7 +160,7 @@ internal class LTKExchanger(
|
|||
val payload = parseKeys(arrayOf(P0), msg.payload)[0]
|
||||
aapsLogger.debug(LTag.PUMPBTCOMM, "P0 payload from pod: ${payload.toHex()}")
|
||||
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 info.nightscout.androidaps.logging.AAPSLogger
|
||||
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.ScanFailNotFoundException
|
||||
import java.util.*
|
||||
|
||||
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 {
|
||||
val scanner = bluetoothAdapter.bluetoothLeScanner
|
||||
val filter = ScanFilter.Builder()
|
||||
|
|
|
@ -5,7 +5,7 @@ import android.bluetooth.le.ScanResult
|
|||
import info.nightscout.androidaps.logging.AAPSLogger
|
||||
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.ScanFailException
|
||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions.ScanException
|
||||
import java.util.*
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
|
@ -25,10 +25,10 @@ class ScanCollector(private val logger: AAPSLogger, private val podID: Long) : S
|
|||
super.onScanFailed(errorCode)
|
||||
}
|
||||
|
||||
@Throws(ScanFailException::class) fun collect(): List<BleDiscoveredDevice> {
|
||||
@Throws(ScanException::class) fun collect(): List<BleDiscoveredDevice> {
|
||||
val ret: MutableList<BleDiscoveredDevice> = ArrayList()
|
||||
if (scanFailed != 0) {
|
||||
throw ScanFailException(scanFailed)
|
||||
throw ScanException(scanFailed)
|
||||
}
|
||||
logger.debug(LTag.PUMPBTCOMM, "ScanCollector looking for podID: $podID")
|
||||
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.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.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.MessageType
|
||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.message.StringLengthPrefixEncoding
|
||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.message.*
|
||||
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.response.NakResponse
|
||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.response.Response
|
||||
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(
|
||||
private val aapsLogger: AAPSLogger,
|
||||
|
@ -24,51 +33,63 @@ class Session(
|
|||
val enDecrypt: EnDecrypt
|
||||
) {
|
||||
|
||||
/**
|
||||
* Used for commands:
|
||||
* -> command with retries
|
||||
* <- response, ACK TODO: retries?
|
||||
* -> ACK
|
||||
*/
|
||||
fun sendCommand(cmd: Command): Response {
|
||||
fun sendCommand(cmd: Command): CommandSendResult {
|
||||
sessionKeys.msgSequenceNumber++
|
||||
aapsLogger.debug(LTag.PUMPBTCOMM, "Sending command: ${cmd.encoded.toHex()} in packet $cmd")
|
||||
var tries = 0
|
||||
var certainFailure = true
|
||||
val msg = getCmdMessage(cmd)
|
||||
var possiblyUnconfirmedCommand = false
|
||||
for (i in 0..MAX_TRIES) {
|
||||
try {
|
||||
val msg = getCmdMessage(cmd)
|
||||
aapsLogger.debug(LTag.PUMPBTCOMM, "Sending command(wrapped): ${msg.payload.toHex()}")
|
||||
msgIO.sendMessage(msg)
|
||||
} catch (e: TimeoutException) {
|
||||
aapsLogger.info(LTag.PUMPBTCOMM,"Exception trying to send command: $e. Try: $i/$MAX_TRIES")
|
||||
} // TODO filter out certain vs uncertain errors
|
||||
}
|
||||
certainFailure = false
|
||||
var response: Response?= null
|
||||
for (i in 0..MAX_TRIES) {
|
||||
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")
|
||||
aapsLogger.debug(LTag.PUMPBTCOMM, "Sending command(wrapped): ${msg.payload.toHex()}")
|
||||
|
||||
when (val sendResult = msgIO.sendMessage(msg)) {
|
||||
is MessageSendSuccess ->
|
||||
return CommandSendSuccess
|
||||
is MessageSendErrorConfirming -> {
|
||||
possiblyUnconfirmedCommand = true
|
||||
aapsLogger.debug(LTag.PUMPBTCOMM, "Error confirming command: $sendResult")
|
||||
}
|
||||
is MessageSendErrorSending ->
|
||||
aapsLogger.debug(LTag.PUMPBTCOMM, "Error sending command: $sendResult")
|
||||
}
|
||||
}
|
||||
response?.let{
|
||||
return it
|
||||
}
|
||||
if (certainFailure) {
|
||||
throw CertainFailureException("Could not send command")
|
||||
}
|
||||
throw UncertainFailureException("Possible failure to send commnd")
|
||||
|
||||
val errMsg = "Maximum number of tries reached. Could not send command\""
|
||||
return if (possiblyUnconfirmedCommand)
|
||||
CommandSendErrorConfirming(errMsg)
|
||||
else
|
||||
CommandSendErrorSending(errMsg)
|
||||
}
|
||||
|
||||
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 {
|
||||
|
||||
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.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.exceptions.CouldNotInitiateConnection
|
||||
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.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.utils.extensions.toHex
|
||||
import java.security.SecureRandom
|
||||
import java.util.*
|
||||
|
||||
class SessionEstablisher(
|
||||
private val aapsLogger: AAPSLogger,
|
||||
|
@ -23,7 +27,7 @@ class SessionEstablisher(
|
|||
|
||||
private val controllerIV = ByteArray(IV_SIZE)
|
||||
private var nodeIV = ByteArray(IV_SIZE)
|
||||
|
||||
private val identifier = Random().nextInt().toByte()
|
||||
private val milenage = Milenage(aapsLogger, ltk, eapSqn)
|
||||
|
||||
init {
|
||||
|
@ -36,13 +40,18 @@ class SessionEstablisher(
|
|||
}
|
||||
|
||||
fun negotiateSessionKeys(): SessionKeys {
|
||||
// send EAP-AKA challenge
|
||||
msgSeq++
|
||||
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()
|
||||
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++
|
||||
var success = eapSuccess()
|
||||
|
@ -67,7 +76,7 @@ class SessionEstablisher(
|
|||
|
||||
val eapMsg = EapMessage(
|
||||
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
|
||||
)
|
||||
return MessagePacket(
|
||||
|
@ -80,12 +89,14 @@ class SessionEstablisher(
|
|||
}
|
||||
|
||||
private fun processChallengeResponse(challengeResponse: MessagePacket) {
|
||||
// TODO verify that identifier matches identifier from the Challenge
|
||||
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) {
|
||||
aapsLogger.debug(LTag.PUMPBTCOMM, "EAP-AKA: got message: $eapMsg")
|
||||
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("Expecting two attributes, got: ${eapMsg.attributes.size}")
|
||||
|
@ -108,7 +119,7 @@ class SessionEstablisher(
|
|||
val eapMsg = EapMessage(
|
||||
code = EapCode.SUCCESS,
|
||||
attributes = arrayOf(),
|
||||
identifier = 189.toByte() // TODO: find what value we need here
|
||||
identifier = identifier.toByte()
|
||||
)
|
||||
|
||||
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 */
|
||||
class CommandSending(val command: Command) : PodEvent()
|
||||
class CommandSent(val command: Command) : PodEvent()
|
||||
class CommandSendNotConfirmed(val command: Command) : PodEvent()
|
||||
|
||||
class ResponseReceived(val response: Response) : PodEvent()
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue