Omnipod Dash: provide and verify expected response type
This commit is contained in:
parent
aff38851e1
commit
10c316edd9
8 changed files with 92 additions and 51 deletions
|
@ -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<PodEvent> {
|
||||
// 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
|
||||
) //
|
||||
}
|
||||
|
||||
|
|
|
@ -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<PodEvent>
|
||||
fun sendCommand(cmd: Command, responseType: KClass<out Response>): Observable<PodEvent>
|
||||
|
||||
fun getStatus(): ConnectionStatus
|
||||
|
||||
|
|
|
@ -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<PodEvent> = 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<out Response>): Observable<PodEvent> =
|
||||
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
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions
|
||||
|
||||
class CouldNotParseResponseException(message: String?) : Exception(message)
|
|
@ -1,3 +1,9 @@
|
|||
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions
|
||||
|
||||
class IllegalResponseException(message: String?) : Exception(message)
|
||||
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.response.Response
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
class IllegalResponseException(
|
||||
expectedResponseType: KClass<out Response>,
|
||||
actualResponse: Response
|
||||
) : Exception("Illegal response: expected ${expectedResponseType.simpleName} but got $actualResponse")
|
|
@ -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}")
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<out Response>): 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]
|
||||
|
|
Loading…
Reference in a new issue