diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/OmnipodDashBleManagerImpl.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/OmnipodDashBleManagerImpl.kt index 26e5475b1b..17bf919381 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/OmnipodDashBleManagerImpl.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/OmnipodDashBleManagerImpl.kt @@ -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> = - 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 = 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 = 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 = 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. } } diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/callbacks/BleCommCallbacks.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/callbacks/BleCommCallbacks.kt index 735c7ea67d..cf0ca067ec 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/callbacks/BleCommCallbacks.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/callbacks/BleCommCallbacks.kt @@ -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> + private val incomingPackets: IncomingPackets, ) : BluetoothGattCallback() { private val serviceDiscoveryComplete: CountDownLatch = CountDownLatch(1) private val connected: CountDownLatch = CountDownLatch(1) - private val writeQueue: BlockingQueue = LinkedBlockingQueue(1) - private val descriptorWriteQueue: BlockingQueue = LinkedBlockingQueue(1) + private val writeQueue: BlockingQueue = 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() } } diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/callbacks/CharacteristicWriteConfirmation.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/callbacks/CharacteristicWriteConfirmation.kt deleted file mode 100644 index 575bd19c0f..0000000000 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/callbacks/CharacteristicWriteConfirmation.kt +++ /dev/null @@ -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() diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/callbacks/DescriptorWriteConfirmation.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/callbacks/DescriptorWriteConfirmation.kt deleted file mode 100644 index 83fc9d41c8..0000000000 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/callbacks/DescriptorWriteConfirmation.kt +++ /dev/null @@ -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() diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/callbacks/WriteConfirmation.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/callbacks/WriteConfirmation.kt new file mode 100644 index 0000000000..776639d3e8 --- /dev/null +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/callbacks/WriteConfirmation.kt @@ -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() diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/command/BleCommand.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/command/BleCommand.kt index 5c3e5a4450..ac09c468c6 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/command/BleCommand.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/command/BleCommand.kt @@ -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) + } + } + } } diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/command/BleCommandHello.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/command/BleCommandHello.kt deleted file mode 100644 index d10ae85aa1..0000000000 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/command/BleCommandHello.kt +++ /dev/null @@ -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() -) diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/command/BleCommandNack.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/command/BleCommandNack.kt deleted file mode 100644 index 14e0705c2b..0000000000 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/command/BleCommandNack.kt +++ /dev/null @@ -1,3 +0,0 @@ -package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.command - -class BleCommandNack(idx: Byte) : BleCommand(BleCommandType.NACK, byteArrayOf(idx)) diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/command/BleCommandType.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/command/BleCommandType.kt index eb8b6bdb47..2a85db74d7 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/command/BleCommandType.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/command/BleCommandType.kt @@ -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 { diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/CouldNotConfirmCommandException.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/CouldNotConfirmCommandException.kt new file mode 100644 index 0000000000..e2a8520fb9 --- /dev/null +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/CouldNotConfirmCommandException.kt @@ -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) diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/CouldNotConfirmDescriptorWriteException.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/CouldNotConfirmDescriptorWriteException.kt deleted file mode 100644 index ffc37a29da..0000000000 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/CouldNotConfirmDescriptorWriteException.kt +++ /dev/null @@ -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") -} diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/CouldNotConfirmWriteException.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/CouldNotConfirmWriteException.kt deleted file mode 100644 index 85b1fb0c0c..0000000000 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/CouldNotConfirmWriteException.kt +++ /dev/null @@ -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") -} diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/CouldNotEnableNotifications.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/CouldNotEnableNotifications.kt deleted file mode 100644 index 1e982d21a0..0000000000 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/CouldNotEnableNotifications.kt +++ /dev/null @@ -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) diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/CouldNotInitiateConnection.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/CouldNotInitiateConnection.kt new file mode 100644 index 0000000000..84ab11d5df --- /dev/null +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/CouldNotInitiateConnection.kt @@ -0,0 +1,3 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions + +class CouldNotInitiateConnection(msg: String) : Exception(msg) diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/CouldNotSendCommandException.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/CouldNotSendCommandException.kt new file mode 100644 index 0000000000..9b48b0cd74 --- /dev/null +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/CouldNotSendCommandException.kt @@ -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){ +} \ No newline at end of file diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/FailedToConnectException.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/FailedToConnectException.kt index 07aba43b33..28584aec75 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/FailedToConnectException.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/FailedToConnectException.kt @@ -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 ?: ""}") } + diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/MessageIOException.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/MessageIOException.kt index 1deed2f63c..eca2f077c9 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/MessageIOException.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/MessageIOException.kt @@ -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) } diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/CouldNotSendBleException.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/NotConnectedException.kt similarity index 57% rename from omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/CouldNotSendBleException.kt rename to omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/NotConnectedException.kt index 83680ec002..c3675f4167 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/CouldNotSendBleException.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/NotConnectedException.kt @@ -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) \ No newline at end of file diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/BleIOBusyException.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/PairingException.kt similarity index 60% rename from omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/BleIOBusyException.kt rename to omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/PairingException.kt index 1fa748a391..da65b08fec 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/BleIOBusyException.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/PairingException.kt @@ -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) \ No newline at end of file diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/ScanFailException.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/ScanException.kt similarity index 82% rename from omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/ScanFailException.kt rename to omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/ScanException.kt index 15b3432ec3..4640eba00e 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/ScanFailException.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/ScanException.kt @@ -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") } diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/ScanFailFoundTooManyException.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/ScanFailFoundTooManyException.kt index 88d678ab88..c88528b3f0 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/ScanFailFoundTooManyException.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/ScanFailFoundTooManyException.kt @@ -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) : ScanFailException("Found more than one Pod") { +class ScanFailFoundTooManyException(devices: List) : ScanException("Found more than one Pod") { private val devices: List = ArrayList(devices) val discoveredDevices: List diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/ScanFailNotFoundException.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/ScanFailNotFoundException.kt index 848331257a..0f9883093c 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/ScanFailNotFoundException.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/ScanFailNotFoundException.kt @@ -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") diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/UnexpectedCommandException.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/UnexpectedCommandException.kt deleted file mode 100644 index 82a2950f2d..0000000000 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/UnexpectedCommandException.kt +++ /dev/null @@ -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") diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/io/BleIO.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/io/BleIO.kt index 0a939805dd..186537835f 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/io/BleIO.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/io/BleIO.kt @@ -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, - private val incomingPackets: Map>, - 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, + 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() } } + + diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/io/CharacteristicType.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/io/CharacteristicType.kt index b09eb72bb2..c5d60d5a30 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/io/CharacteristicType.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/io/CharacteristicType.kt @@ -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") diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/io/CmdBleIO.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/io/CmdBleIO.kt new file mode 100644 index 0000000000..4988f1b919 --- /dev/null +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/io/CmdBleIO.kt @@ -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, + 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 + } + } + } +} + diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/io/DataBleIO.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/io/DataBleIO.kt new file mode 100644 index 0000000000..39b1798c6c --- /dev/null +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/io/DataBleIO.kt @@ -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, + gatt: BluetoothGatt, + bleCommCallbacks: BleCommCallbacks +) : BleIO( + logger, + characteristic, + incomingPackets, + gatt, + bleCommCallbacks, + CharacteristicType.DATA +) diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/io/IncomingPackets.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/io/IncomingPackets.kt new file mode 100644 index 0000000000..6ff1b8762a --- /dev/null +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/io/IncomingPackets.kt @@ -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 = LinkedBlockingDeque() + val dataQueue: BlockingQueue = LinkedBlockingDeque() + + fun byCharacteristicType(char: CharacteristicType): BlockingQueue { + return when (char) { + CharacteristicType.DATA -> cmdQueue + CharacteristicType.CMD -> dataQueue + } + } +} \ No newline at end of file diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/message/MessageIO.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/message/MessageIO.kt index 2890ee31d5..be3be3debd 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/message/MessageIO.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/message/MessageIO.kt @@ -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() - 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) { - 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): 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): 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() } } diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/pair/LTKExchanger.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/pair/LTKExchanger.kt index f30f7e5c6a..6865ff4f43 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/pair/LTKExchanger.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/pair/LTKExchanger.kt @@ -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()}") } } diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/scan/PodScanner.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/scan/PodScanner.kt index b4a32b8827..e9a436d17b 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/scan/PodScanner.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/scan/PodScanner.kt @@ -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() diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/scan/ScanCollector.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/scan/ScanCollector.kt index 82c249d492..5087d32141 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/scan/ScanCollector.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/scan/ScanCollector.kt @@ -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 { + @Throws(ScanException::class) fun collect(): List { val ret: MutableList = ArrayList() if (scanFailed != 0) { - throw ScanFailException(scanFailed) + throw ScanException(scanFailed) } logger.debug(LTag.PUMPBTCOMM, "ScanCollector looking for podID: $podID") for (result in found.values) { diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/session/CertainFailureException.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/session/CertainFailureException.kt deleted file mode 100644 index b6c3eb0739..0000000000 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/session/CertainFailureException.kt +++ /dev/null @@ -1,3 +0,0 @@ -package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.session - -class CertainFailureException(msg: String) : Exception(msg) \ No newline at end of file diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/session/Connection.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/session/Connection.kt new file mode 100644 index 0000000000..53fb2f3409 --- /dev/null +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/session/Connection.kt @@ -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 + } +} \ No newline at end of file diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/session/Session.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/session/Session.kt index 9d95703a35..5dbc8ba4c9 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/session/Session.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/session/Session.kt @@ -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] diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/session/SessionEstablisher.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/session/SessionEstablisher.kt index 41c7e9855b..2e0cc1e365 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/session/SessionEstablisher.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/session/SessionEstablisher.kt @@ -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( diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/session/UncertainFailureException.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/session/UncertainFailureException.kt deleted file mode 100644 index 190a0eb096..0000000000 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/session/UncertainFailureException.kt +++ /dev/null @@ -1,3 +0,0 @@ -package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.session - -class UncertainFailureException(msg: String) : Exception(msg) \ No newline at end of file diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/event/PodEvent.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/event/PodEvent.kt index 367393292e..ea7316020c 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/event/PodEvent.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/event/PodEvent.kt @@ -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() }