diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/OmnipodDashManagerImpl.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/OmnipodDashManagerImpl.kt index 08b79f9d79..53f86bc677 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/OmnipodDashManagerImpl.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/OmnipodDashManagerImpl.kt @@ -10,7 +10,6 @@ import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.definitio import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.response.* import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.state.OmnipodDashPodStateManager import info.nightscout.androidaps.utils.rx.AapsSchedulers -import info.nightscout.androidaps.utils.rx.retryWithBackoff import io.reactivex.Observable import io.reactivex.functions.Action import io.reactivex.functions.Consumer @@ -72,19 +71,29 @@ class OmnipodDashManagerImpl @Inject constructor( .setNumberOfUnits(units) .setDelayBetweenPulsesInEighthSeconds(rateInEighthPulsesPerSeconds) .setProgramReminder(ProgramReminder(confirmationBeeps, completionBeeps, 0)) - .build() + .build(), + DefaultStatusResponse::class ) } } private fun observeSendGetPodStatusCommand(type: ResponseType.StatusResponseType = ResponseType.StatusResponseType.DEFAULT_STATUS_RESPONSE): Observable { + // TODO move somewhere else + val expectedResponseType = when (type) { + ResponseType.StatusResponseType.DEFAULT_STATUS_RESPONSE -> DefaultStatusResponse::class + ResponseType.StatusResponseType.ALARM_STATUS -> AlarmStatusResponse::class + + else -> return Observable.error(UnsupportedOperationException("No response type to class mapping for ${type.name}")) + } + return Observable.defer { bleManager.sendCommand( GetStatusCommand.Builder() .setUniqueId(podStateManager.uniqueId!!.toInt()) .setSequenceNumber(podStateManager.messageSequenceNumber) .setStatusResponseType(type) - .build() + .build(), + expectedResponseType ) } } @@ -115,7 +124,8 @@ class OmnipodDashManagerImpl @Inject constructor( .setSequenceNumber(podStateManager.messageSequenceNumber) .setNonce(1229869870) // TODO .setAlertConfigurations(alertConfigurations) - .build() + .build(), + DefaultStatusResponse::class ) } } @@ -130,7 +140,8 @@ class OmnipodDashManagerImpl @Inject constructor( .setProgramReminder(ProgramReminder(atStart = false, atEnd = false, atInterval = 0)) .setBasalProgram(basalProgram) .setCurrentTime(Date()) - .build() + .build(), + DefaultStatusResponse::class ) } } @@ -159,7 +170,8 @@ class OmnipodDashManagerImpl @Inject constructor( .setLotNumber(podStateManager.lotNumber!!.toInt()) // .setPodSequenceNumber(podStateManager.podSequenceNumber!!.toInt()) .setInitializationTime(Date()) - .build() + .build(), + SetUniqueIdResponse::class ) // } @@ -169,7 +181,8 @@ class OmnipodDashManagerImpl @Inject constructor( GetVersionCommand.Builder() // .setSequenceNumber(podStateManager.messageSequenceNumber) // .setUniqueId(DEFAULT_UNIQUE_ID) // - .build() + .build(), + VersionResponse::class ) // } diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/OmnipodDashBleManager.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/OmnipodDashBleManager.kt index 814685ccd0..f3708c8031 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/OmnipodDashBleManager.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/OmnipodDashBleManager.kt @@ -3,11 +3,13 @@ package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm 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.response.Response import io.reactivex.Observable +import kotlin.reflect.KClass interface OmnipodDashBleManager { - fun sendCommand(cmd: Command): Observable + fun sendCommand(cmd: Command, responseType: KClass): Observable fun getStatus(): ConnectionStatus 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 b5f794a946..333cb5a9f7 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 @@ -24,6 +24,7 @@ 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.response.Response import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.state.OmnipodDashPodStateManager import info.nightscout.androidaps.utils.extensions.toHex import io.reactivex.Observable @@ -32,6 +33,7 @@ import java.util.concurrent.LinkedBlockingDeque import java.util.concurrent.TimeoutException import javax.inject.Inject import javax.inject.Singleton +import kotlin.reflect.KClass @Singleton class OmnipodDashBleManagerImpl @Inject constructor( @@ -90,40 +92,39 @@ class OmnipodDashBleManagerImpl @Inject constructor( return bleIO } - @Throws(IllegalResponseException::class, UnsupportedOperationException::class) - 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") + override fun sendCommand(cmd: Command, responseType: KClass): Observable = + Observable.create { emitter -> + try { + val keys = sessionKeys + val mIO = msgIO + if (keys == null || mIO == null) { + throw Exception("Not connected") + } + 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, responseType) + emitter.onNext(PodEvent.ResponseReceived(response)) + emitter.onComplete() + } catch (ex: Exception) { + emitter.tryOnError(ex) } - 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) } - } override fun getStatus(): ConnectionStatus { var s: ConnectionStatus diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/CouldNotParseResponseException.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/CouldNotParseResponseException.kt new file mode 100644 index 0000000000..7279052046 --- /dev/null +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/CouldNotParseResponseException.kt @@ -0,0 +1,3 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions + +class CouldNotParseResponseException(message: String?) : Exception(message) \ No newline at end of file diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/IllegalResponseException.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/IllegalResponseException.kt index 76f0b639ec..10b21a9355 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/IllegalResponseException.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/IllegalResponseException.kt @@ -1,3 +1,9 @@ package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions -class IllegalResponseException(message: String?) : Exception(message) \ No newline at end of file +import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.response.Response +import kotlin.reflect.KClass + +class IllegalResponseException( + expectedResponseType: KClass, + actualResponse: Response +) : Exception("Illegal response: expected ${expectedResponseType.simpleName} but got $actualResponse") \ No newline at end of file diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/PodAlarmException.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/PodAlarmException.kt new file mode 100644 index 0000000000..4d6cd067b9 --- /dev/null +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/exceptions/PodAlarmException.kt @@ -0,0 +1,5 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions + +import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.response.AlarmStatusResponse + +class PodAlarmException(val response: AlarmStatusResponse) : Exception("Pod is in alarm: ${response.alarmType.value} ${response.alarmType.name}") \ No newline at end of file diff --git a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/session/ResponseUtil.kt b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/session/ResponseUtil.kt index 225cd5516a..ec2599b56d 100644 --- a/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/session/ResponseUtil.kt +++ b/omnipod-dash/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/dash/driver/comm/session/ResponseUtil.kt @@ -1,32 +1,32 @@ package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.session -import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions.IllegalResponseException +import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions.CouldNotParseResponseException import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.response.* import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.util.byValue object ResponseUtil { - @Throws(IllegalResponseException::class, UnsupportedOperationException::class) + @Throws(CouldNotParseResponseException::class, UnsupportedOperationException::class) fun parseResponse(payload: ByteArray): Response { return when (val responseType = byValue(payload[0], ResponseType.UNKNOWN)) { ResponseType.ACTIVATION_RESPONSE -> parseActivationResponse(payload) ResponseType.DEFAULT_STATUS_RESPONSE -> DefaultStatusResponse(payload) ResponseType.ADDITIONAL_STATUS_RESPONSE -> parseAdditionalStatusResponse(payload) ResponseType.NAK_RESPONSE -> NakResponse(payload) - ResponseType.UNKNOWN -> throw IllegalResponseException("Unrecognized message type: $responseType") + ResponseType.UNKNOWN -> throw CouldNotParseResponseException("Unrecognized message type: $responseType") } } - @Throws(IllegalResponseException::class) + @Throws(CouldNotParseResponseException::class) private fun parseActivationResponse(payload: ByteArray): Response { return when (val activationResponseType = byValue(payload[1], ResponseType.ActivationResponseType.UNKNOWN)) { ResponseType.ActivationResponseType.GET_VERSION_RESPONSE -> VersionResponse(payload) ResponseType.ActivationResponseType.SET_UNIQUE_ID_RESPONSE -> SetUniqueIdResponse(payload) - ResponseType.ActivationResponseType.UNKNOWN -> throw IllegalResponseException("Unrecognized activation response type: $activationResponseType") + ResponseType.ActivationResponseType.UNKNOWN -> throw CouldNotParseResponseException("Unrecognized activation response type: $activationResponseType") } } - @Throws(IllegalResponseException::class, UnsupportedOperationException::class) + @Throws(CouldNotParseResponseException::class, UnsupportedOperationException::class) private fun parseAdditionalStatusResponse(payload: ByteArray): Response { return when (val additionalStatusResponseType = byValue(payload[2], ResponseType.StatusResponseType.UNKNOWN)) { ResponseType.StatusResponseType.DEFAULT_STATUS_RESPONSE -> DefaultStatusResponse(payload) // Unreachable; this response type is only used for requesting a default status response @@ -38,7 +38,7 @@ object ResponseUtil { ResponseType.StatusResponseType.STATUS_RESPONSE_PAGE_70 -> throw UnsupportedOperationException("Status response page 70 is not (yet) implemented") ResponseType.StatusResponseType.STATUS_RESPONSE_PAGE_80 -> throw UnsupportedOperationException("Status response page 80 is not (yet) implemented") ResponseType.StatusResponseType.STATUS_RESPONSE_PAGE_81 -> throw UnsupportedOperationException("Status response page 81 is not (yet) implemented") - ResponseType.StatusResponseType.UNKNOWN -> throw IllegalResponseException("Unrecognized additional status response type: $additionalStatusResponseType") + ResponseType.StatusResponseType.UNKNOWN -> throw CouldNotParseResponseException("Unrecognized additional status response type: $additionalStatusResponseType") } } } \ 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 ac04803ea0..ab096579e2 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,15 +4,19 @@ 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.exceptions.CouldNotParseResponseException import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions.IllegalResponseException +import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions.PodAlarmException 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.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.AlarmStatusResponse import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.response.Response import info.nightscout.androidaps.utils.extensions.toHex +import kotlin.reflect.KClass class Session( private val aapsLogger: AAPSLogger, @@ -29,8 +33,8 @@ class Session( * <- response, ACK TODO: retries? * -> ACK */ - @Throws(IllegalResponseException::class, UnsupportedOperationException::class) - fun sendCommand(cmd: Command): Response { + @Throws(CouldNotParseResponseException::class, UnsupportedOperationException::class) + fun sendCommand(cmd: Command, responseType: KClass): Response { sessionKeys.msgSequenceNumber++ aapsLogger.debug(LTag.PUMPBTCOMM, "Sending command: ${cmd.encoded.toHex()} in packet $cmd") @@ -43,6 +47,13 @@ class Session( aapsLogger.debug(LTag.PUMPBTCOMM, "Received response: $decrypted") val response = parseResponse(decrypted) + if (!responseType.isInstance(response)) { + if (response is AlarmStatusResponse) { + throw PodAlarmException(response) + } + throw IllegalResponseException(responseType, response) + } + sessionKeys.msgSequenceNumber++ val ack = getAck(responseMsg) aapsLogger.debug(LTag.PUMPBTCOMM, "Sending ACK: ${ack.payload.toHex()} in packet $ack") @@ -50,7 +61,7 @@ class Session( return response } - @Throws(IllegalResponseException::class, UnsupportedOperationException::class) + @Throws(CouldNotParseResponseException::class, UnsupportedOperationException::class) private fun parseResponse(decrypted: MessagePacket): Response { val payload = parseKeys(arrayOf(RESPONSE_PREFIX), decrypted.payload)[0]