Merge pull request #26 from 0pen-dash/bart/create-responses

Parse responses
This commit is contained in:
bartsopers 2021-03-31 11:27:24 +02:00 committed by GitHub
commit 210872d6c6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
59 changed files with 906 additions and 400 deletions

View file

@ -23,7 +23,10 @@ abstract class OmnipodWizardModule {
@Provides @Provides
@OmnipodPluginQualifier @OmnipodPluginQualifier
fun providesViewModelFactory(@OmnipodPluginQualifier viewModels: MutableMap<Class<out ViewModel>, @JvmSuppressWildcards Provider<ViewModel>>): ViewModelProvider.Factory { fun providesViewModelFactory(
@OmnipodPluginQualifier
viewModels: MutableMap<Class<out ViewModel>, @JvmSuppressWildcards Provider<ViewModel>>
): ViewModelProvider.Factory {
return ViewModelFactory(viewModels) return ViewModelFactory(viewModels)
} }
} }

View file

@ -41,7 +41,11 @@ class AttachPodFragment : InfoFragmentBase() {
.setIcon(android.R.drawable.ic_dialog_alert) .setIcon(android.R.drawable.ic_dialog_alert)
.setTitle(getString(getTitleId())) .setTitle(getString(getTitleId()))
.setMessage(getString(R.string.omnipod_common_pod_activation_wizard_attach_pod_confirm_insert_cannula_text)) .setMessage(getString(R.string.omnipod_common_pod_activation_wizard_attach_pod_confirm_insert_cannula_text))
.setPositiveButton(getString(R.string.omnipod_common_ok)) { _, _ -> findNavController().navigate(getNextPageActionId()) } .setPositiveButton(getString(R.string.omnipod_common_ok)) { _, _ ->
findNavController().navigate(
getNextPageActionId()
)
}
.setNegativeButton(getString(R.string.omnipod_common_cancel), null) .setNegativeButton(getString(R.string.omnipod_common_cancel), null)
.show() .show()
} }

View file

@ -24,7 +24,8 @@ abstract class ActionFragmentBase : WizardFragmentBase() {
binding.navButtonsLayout.buttonNext.isEnabled = false binding.navButtonsLayout.buttonNext.isEnabled = false
view.findViewById<TextView>(R.id.omnipod_wizard_action_page_text).setText(getTextId()) view.findViewById<TextView>(R.id.omnipod_wizard_action_page_text).setText(getTextId())
view.findViewById<Button>(R.id.omnipod_wizard_button_retry).setOnClickListener { actionViewModel.executeAction() } view.findViewById<Button>(R.id.omnipod_wizard_button_retry)
.setOnClickListener { actionViewModel.executeAction() }
actionViewModel.isActionExecutingLiveData.observe(viewLifecycleOwner, { isExecuting -> actionViewModel.isActionExecutingLiveData.observe(viewLifecycleOwner, { isExecuting ->
if (isExecuting) { if (isExecuting) {
@ -33,7 +34,8 @@ abstract class ActionFragmentBase : WizardFragmentBase() {
view.findViewById<Button>(R.id.omnipod_wizard_button_discard_pod).visibility = View.GONE view.findViewById<Button>(R.id.omnipod_wizard_button_discard_pod).visibility = View.GONE
view.findViewById<Button>(R.id.omnipod_wizard_button_retry).visibility = View.GONE view.findViewById<Button>(R.id.omnipod_wizard_button_retry).visibility = View.GONE
} }
view.findViewById<ProgressBar>(R.id.omnipod_wizard_action_progress_indication).visibility = isExecuting.toVisibility() view.findViewById<ProgressBar>(R.id.omnipod_wizard_action_progress_indication).visibility =
isExecuting.toVisibility()
view.findViewById<Button>(R.id.button_cancel).isEnabled = !isExecuting view.findViewById<Button>(R.id.button_cancel).isEnabled = !isExecuting
}) })
@ -42,9 +44,12 @@ abstract class ActionFragmentBase : WizardFragmentBase() {
val isExecuting = isActionExecuting() val isExecuting = isActionExecuting()
view.findViewById<Button>(R.id.button_next).isEnabled = result.success view.findViewById<Button>(R.id.button_next).isEnabled = result.success
view.findViewById<ImageView>(R.id.omnipod_wizard_action_success).visibility = result.success.toVisibility() view.findViewById<ImageView>(R.id.omnipod_wizard_action_success).visibility =
view.findViewById<TextView>(R.id.omnipod_wizard_action_error).visibility = (!isExecuting && !result.success).toVisibility() result.success.toVisibility()
view.findViewById<Button>(R.id.omnipod_wizard_button_retry).visibility = (!isExecuting && !result.success).toVisibility() view.findViewById<TextView>(R.id.omnipod_wizard_action_error).visibility =
(!isExecuting && !result.success).toVisibility()
view.findViewById<Button>(R.id.omnipod_wizard_button_retry).visibility =
(!isExecuting && !result.success).toVisibility()
if (!result.success) { if (!result.success) {
view.findViewById<TextView>(R.id.omnipod_wizard_action_error).text = result.comment view.findViewById<TextView>(R.id.omnipod_wizard_action_error).text = result.comment

View file

@ -47,7 +47,8 @@ abstract class WizardFragmentBase : DaggerFragment() {
if (nextPage == null) { if (nextPage == null) {
binding.navButtonsLayout.buttonNext.text = getString(R.string.omnipod_common_wizard_button_finish) binding.navButtonsLayout.buttonNext.text = getString(R.string.omnipod_common_wizard_button_finish)
binding.navButtonsLayout.buttonNext.backgroundTintList = ColorStateList.valueOf(resources.getColor(R.color.omnipod_wizard_finish_button, context?.theme)) binding.navButtonsLayout.buttonNext.backgroundTintList =
ColorStateList.valueOf(resources.getColor(R.color.omnipod_wizard_finish_button, context?.theme))
} }
updateProgressIndication() updateProgressIndication()

View file

@ -33,13 +33,13 @@ class OmnipodDashPumpPlugin @Inject constructor(
companion object { companion object {
private val pluginDescription = PluginDescription() // private val pluginDescription = PluginDescription()
.mainType(PluginType.PUMP) // .mainType(PluginType.PUMP)
.fragmentClass(OmnipodDashOverviewFragment::class.java.name) // .fragmentClass(OmnipodDashOverviewFragment::class.java.name)
.pluginIcon(R.drawable.ic_pod_128) .pluginIcon(R.drawable.ic_pod_128)
.pluginName(R.string.omnipod_dash_name) // .pluginName(R.string.omnipod_dash_name)
.shortName(R.string.omnipod_dash_name_short) // .shortName(R.string.omnipod_dash_name_short)
.preferencesId(R.xml.omnipod_dash_preferences) // .preferencesId(R.xml.omnipod_dash_preferences)
.description(R.string.omnipod_dash_pump_description) .description(R.string.omnipod_dash_pump_description)
private val pumpDescription = PumpDescription(PumpType.Omnipod_Dash) private val pumpDescription = PumpDescription(PumpType.Omnipod_Dash)

View file

@ -5,6 +5,8 @@ import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.definitio
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.definition.AlertTrigger import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.definition.AlertTrigger
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.definition.AlertType import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.definition.AlertType
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.definition.BasalProgram import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.definition.BasalProgram
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.definition.BeepType
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.response.ResponseType
import io.reactivex.Observable import io.reactivex.Observable
import java.util.* import java.util.*
@ -14,9 +16,9 @@ interface OmnipodDashManager {
fun activatePodPart2(basalProgram: BasalProgram): Observable<PodEvent> fun activatePodPart2(basalProgram: BasalProgram): Observable<PodEvent>
fun getStatus(): Observable<PodEvent> fun getStatus(type: ResponseType.StatusResponseType): Observable<PodEvent>
fun setBasalProgram(program: BasalProgram): Observable<PodEvent> fun setBasalProgram(basalProgram: BasalProgram): Observable<PodEvent>
fun suspendDelivery(): Observable<PodEvent> fun suspendDelivery(): Observable<PodEvent>
@ -26,15 +28,15 @@ interface OmnipodDashManager {
fun cancelTempBasal(): Observable<PodEvent> fun cancelTempBasal(): Observable<PodEvent>
fun bolus(amount: Double): Observable<PodEvent> fun bolus(units: Double, confirmationBeeps: Boolean, completionBeeps: Boolean): Observable<PodEvent>
fun cancelBolus(): Observable<PodEvent> fun cancelBolus(): Observable<PodEvent>
fun programBeeps(): Observable<PodEvent> fun playBeep(beepType: BeepType): Observable<PodEvent>
fun programAlerts(alertConfigurations: List<AlertConfiguration>): Observable<PodEvent> fun programAlerts(alertConfigurations: List<AlertConfiguration>): Observable<PodEvent>
fun silenceAlerts(alerts: EnumSet<AlertType>): Observable<PodEvent> fun silenceAlerts(alertTypes: EnumSet<AlertType>): Observable<PodEvent>
fun deactivatePod(): Observable<PodEvent> fun deactivatePod(): Observable<PodEvent>
} }

View file

@ -26,11 +26,17 @@ class OmnipodDashManagerImpl @Inject constructor(
private val aapsSchedulers: AapsSchedulers private val aapsSchedulers: AapsSchedulers
) : OmnipodDashManager { ) : OmnipodDashManager {
companion object {
const val NONCE = 1229869870 // The Omnipod Dash seems to use a fixed nonce
}
private val observePodReadyForActivationPart1: Observable<PodEvent> private val observePodReadyForActivationPart1: Observable<PodEvent>
get() = Observable.defer { get() = Observable.defer {
if (podStateManager.activationProgress.isBefore(ActivationProgress.PHASE_1_COMPLETED)) { if (podStateManager.activationProgress.isBefore(ActivationProgress.PHASE_1_COMPLETED)) {
Observable.empty() Observable.empty()
} else { } else {
// TODO introduce specialized Exception
Observable.error(IllegalStateException("Pod is in an incorrect state")) Observable.error(IllegalStateException("Pod is in an incorrect state"))
} }
} }
@ -42,6 +48,27 @@ class OmnipodDashManagerImpl @Inject constructor(
) { ) {
Observable.empty() Observable.empty()
} else { } else {
// TODO introduce specialized Exception
Observable.error(IllegalStateException("Pod is in an incorrect state"))
}
}
private val observeUniqueIdSet: Observable<PodEvent>
get() = Observable.defer {
if (podStateManager.activationProgress.isAtLeast(ActivationProgress.SET_UNIQUE_ID)) {
Observable.empty()
} else {
// TODO introduce specialized Exception
Observable.error(IllegalStateException("Pod is in an incorrect state"))
}
}
private val observePodRunning: Observable<PodEvent>
get() = Observable.defer {
if (podStateManager.activationProgress == ActivationProgress.COMPLETED && podStateManager.podStatus!!.isRunning()) {
Observable.empty()
} else {
// TODO introduce specialized Exception
Observable.error(IllegalStateException("Pod is in an incorrect state")) Observable.error(IllegalStateException("Pod is in an incorrect state"))
} }
} }
@ -67,32 +94,40 @@ class OmnipodDashManagerImpl @Inject constructor(
ProgramBolusCommand.Builder() ProgramBolusCommand.Builder()
.setUniqueId(podStateManager.uniqueId!!.toInt()) .setUniqueId(podStateManager.uniqueId!!.toInt())
.setSequenceNumber(podStateManager.messageSequenceNumber) .setSequenceNumber(podStateManager.messageSequenceNumber)
.setNonce(1229869870) // TODO .setNonce(NONCE)
.setNumberOfUnits(units) .setNumberOfUnits(units)
.setDelayBetweenPulsesInEighthSeconds(rateInEighthPulsesPerSeconds) .setDelayBetweenPulsesInEighthSeconds(rateInEighthPulsesPerSeconds)
.setProgramReminder(ProgramReminder(confirmationBeeps, completionBeeps, 0)) .setProgramReminder(ProgramReminder(confirmationBeeps, completionBeeps, 0))
.build() .build(),
DefaultStatusResponse::class
) )
} }
} }
private fun observeSendGetPodStatusCommand(type: ResponseType.StatusResponseType = ResponseType.StatusResponseType.DEFAULT_STATUS_RESPONSE): Observable<PodEvent> { 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 { return Observable.defer {
bleManager.sendCommand( bleManager.sendCommand(
GetStatusCommand.Builder() GetStatusCommand.Builder()
.setUniqueId(podStateManager.uniqueId!!.toInt()) .setUniqueId(podStateManager.uniqueId!!.toInt())
.setSequenceNumber(podStateManager.messageSequenceNumber) .setSequenceNumber(podStateManager.messageSequenceNumber)
.setStatusResponseType(type) .setStatusResponseType(type)
.build() .build(),
expectedResponseType
) )
} }
} }
private val observeVerifyCannulaInsertion: Observable<PodEvent> private val observeVerifyCannulaInsertion: Observable<PodEvent>
get() = Observable.defer { get() = Observable.concat(
observeSendGetPodStatusCommand() observeSendGetPodStatusCommand(),
.ignoreElements() //
.andThen(
Observable.defer { Observable.defer {
if (podStateManager.podStatus == PodStatus.RUNNING_ABOVE_MIN_VOLUME) { if (podStateManager.podStatus == PodStatus.RUNNING_ABOVE_MIN_VOLUME) {
Observable.empty() Observable.empty()
@ -101,7 +136,6 @@ class OmnipodDashManagerImpl @Inject constructor(
} }
} }
) )
}
private fun observeSendProgramAlertsCommand( private fun observeSendProgramAlertsCommand(
alertConfigurations: List<AlertConfiguration>, alertConfigurations: List<AlertConfiguration>,
@ -112,74 +146,79 @@ class OmnipodDashManagerImpl @Inject constructor(
ProgramAlertsCommand.Builder() ProgramAlertsCommand.Builder()
.setUniqueId(podStateManager.uniqueId!!.toInt()) .setUniqueId(podStateManager.uniqueId!!.toInt())
.setSequenceNumber(podStateManager.messageSequenceNumber) .setSequenceNumber(podStateManager.messageSequenceNumber)
.setNonce(1229869870) // TODO .setNonce(NONCE)
.setAlertConfigurations(alertConfigurations) .setAlertConfigurations(alertConfigurations)
.build() .setMultiCommandFlag(multiCommandFlag)
.build(),
DefaultStatusResponse::class
) )
} }
} }
private fun observeProgramBasalCommand(basalProgram: BasalProgram): Observable<PodEvent> { private fun observeSendProgramBasalCommand(basalProgram: BasalProgram): Observable<PodEvent> {
return Observable.defer { return Observable.defer {
val currentTime = Date()
logger.debug(LTag.PUMPCOMM, "Programming basal. currentTime={}, basalProgram={}", currentTime, basalProgram)
bleManager.sendCommand( bleManager.sendCommand(
ProgramBasalCommand.Builder() ProgramBasalCommand.Builder()
.setUniqueId(podStateManager.uniqueId!!.toInt()) .setUniqueId(podStateManager.uniqueId!!.toInt())
.setSequenceNumber(podStateManager.messageSequenceNumber) .setSequenceNumber(podStateManager.messageSequenceNumber)
.setNonce(1229869870) // TODO .setNonce(NONCE)
.setProgramReminder(ProgramReminder(atStart = false, atEnd = false, atInterval = 0)) .setProgramReminder(ProgramReminder(atStart = false, atEnd = false, atInterval = 0))
.setBasalProgram(basalProgram) .setBasalProgram(basalProgram)
.setCurrentTime(Date()) .setCurrentTime(currentTime)
.build() .build(),
DefaultStatusResponse::class
) )
} }
} }
private val observeVerifyPrime: Observable<PodEvent> private val observeVerifyPrime: Observable<PodEvent>
get() = Observable.defer { get() = Observable.concat(
observeSendGetPodStatusCommand() observeSendGetPodStatusCommand(ResponseType.StatusResponseType.DEFAULT_STATUS_RESPONSE),
.ignoreElements() //
.andThen(
Observable.defer { Observable.defer {
if (podStateManager.podStatus == PodStatus.CLUTCH_DRIVE_ENGAGED) { if (podStateManager.podStatus == PodStatus.CLUTCH_DRIVE_ENGAGED) {
Observable.empty() Observable.empty()
} else { } else {
Observable.error(IllegalStateException("Unexpected Pod status")) Observable.error(IllegalStateException("Unexpected Pod status: got ${podStateManager.podStatus}, expected CLUTCH_DRIVE_ENGAGED"))
} }
} }
) )
}
private val observeSendSetUniqueIdCommand: Observable<PodEvent> private val observeSendSetUniqueIdCommand: Observable<PodEvent>
get() = Observable.defer { get() = Observable.defer {
bleManager.sendCommand( bleManager.sendCommand(
SetUniqueIdCommand.Builder() // SetUniqueIdCommand.Builder()
.setSequenceNumber(podStateManager.messageSequenceNumber) // .setSequenceNumber(podStateManager.messageSequenceNumber)
.setUniqueId(podStateManager.uniqueId!!.toInt()) // .setUniqueId(podStateManager.uniqueId!!.toInt())
.setLotNumber(podStateManager.lotNumber!!.toInt()) // .setLotNumber(podStateManager.lotNumber!!.toInt())
.setPodSequenceNumber(podStateManager.podSequenceNumber!!.toInt()) .setPodSequenceNumber(podStateManager.podSequenceNumber!!.toInt())
.setInitializationTime(Date()) .setInitializationTime(Date())
.build() .build(),
) // SetUniqueIdResponse::class
)
} }
private val observeSendGetVersionCommand: Observable<PodEvent> private val observeSendGetVersionCommand: Observable<PodEvent>
get() = Observable.defer { get() = Observable.defer {
bleManager.sendCommand( bleManager.sendCommand(
GetVersionCommand.Builder() // GetVersionCommand.Builder()
.setSequenceNumber(podStateManager.messageSequenceNumber) // .setSequenceNumber(podStateManager.messageSequenceNumber)
.setUniqueId(DEFAULT_UNIQUE_ID) // .setUniqueId(DEFAULT_UNIQUE_ID)
.build() .build(),
) // VersionResponse::class
)
} }
override fun activatePodPart1(lowReservoirAlertTrigger: AlertTrigger.ReservoirVolumeTrigger?): Observable<PodEvent> { override fun activatePodPart1(lowReservoirAlertTrigger: AlertTrigger.ReservoirVolumeTrigger?): Observable<PodEvent> {
return Observable.concat( return Observable.concat(
observePodReadyForActivationPart1, observePodReadyForActivationPart1,
observePairNewPod, observePairNewPod,
observeConnectToPod, // FIXME needed after disconnect; observePairNewPod does not connect in that case.
observeActivationPart1Commands(lowReservoirAlertTrigger) observeActivationPart1Commands(lowReservoirAlertTrigger)
).doOnComplete(ActivationProgressUpdater(ActivationProgress.PHASE_1_COMPLETED)) ).doOnComplete(ActivationProgressUpdater(ActivationProgress.PHASE_1_COMPLETED))
// TODO these would be common for any observable returned in a public function in this class // TODO these would be common for any observable returned in a public function in this class
.doOnNext(PodEventInterceptor()) // .doOnNext(PodEventInterceptor())
.doOnError(ErrorInterceptor()) .doOnError(ErrorInterceptor())
.subscribeOn(aapsSchedulers.io) .subscribeOn(aapsSchedulers.io)
} }
@ -204,12 +243,24 @@ class OmnipodDashManagerImpl @Inject constructor(
} }
if (podStateManager.activationProgress.isBefore(ActivationProgress.PRIMING)) { if (podStateManager.activationProgress.isBefore(ActivationProgress.PRIMING)) {
observables.add( observables.add(
observeSendProgramBolusCommand( Observable.defer {
podStateManager.firstPrimeBolusVolume!! * 0.05, Observable.timer(podStateManager.firstPrimeBolusVolume!!.toLong(), TimeUnit.SECONDS)
podStateManager.primePulseRate!!.toByte(), .flatMap { Observable.empty() }
confirmationBeeps = false, })
completionBeeps = false observables.add(
).doOnComplete(ActivationProgressUpdater(ActivationProgress.PRIMING)) Observable.defer {
bleManager.sendCommand(
ProgramBolusCommand.Builder()
.setUniqueId(podStateManager.uniqueId!!.toInt())
.setSequenceNumber(podStateManager.messageSequenceNumber)
.setNonce(NONCE)
.setNumberOfUnits(podStateManager.firstPrimeBolusVolume!! * 0.05)
.setDelayBetweenPulsesInEighthSeconds(podStateManager.primePulseRate!!.toByte())
.setProgramReminder(ProgramReminder(atStart = false, atEnd = false, atInterval = 0))
.build(),
DefaultStatusResponse::class
)
}.doOnComplete(ActivationProgressUpdater(ActivationProgress.PRIMING))
) )
} }
if (podStateManager.activationProgress.isBefore(ActivationProgress.REPROGRAMMED_LUMP_OF_COAL_ALERT)) { if (podStateManager.activationProgress.isBefore(ActivationProgress.REPROGRAMMED_LUMP_OF_COAL_ALERT)) {
@ -267,7 +318,7 @@ class OmnipodDashManagerImpl @Inject constructor(
observeActivationPart2Commands(basalProgram) observeActivationPart2Commands(basalProgram)
).doOnComplete(ActivationProgressUpdater(ActivationProgress.COMPLETED)) ).doOnComplete(ActivationProgressUpdater(ActivationProgress.COMPLETED))
// TODO these would be common for any observable returned in a public function in this class // TODO these would be common for any observable returned in a public function in this class
.doOnNext(PodEventInterceptor()) // .doOnNext(PodEventInterceptor())
.doOnError(ErrorInterceptor()) .doOnError(ErrorInterceptor())
.subscribeOn(aapsSchedulers.io) .subscribeOn(aapsSchedulers.io)
} }
@ -292,6 +343,11 @@ class OmnipodDashManagerImpl @Inject constructor(
) )
} }
if (podStateManager.activationProgress.isBefore(ActivationProgress.INSERTING_CANNULA)) { if (podStateManager.activationProgress.isBefore(ActivationProgress.INSERTING_CANNULA)) {
observables.add(
Observable.defer {
Observable.timer(podStateManager.secondPrimeBolusVolume!!.toLong(), TimeUnit.SECONDS)
.flatMap { Observable.empty() }
})
observables.add( observables.add(
observeSendProgramBolusCommand( observeSendProgramBolusCommand(
podStateManager.secondPrimeBolusVolume!! * 0.05, podStateManager.secondPrimeBolusVolume!! * 0.05,
@ -335,7 +391,7 @@ class OmnipodDashManagerImpl @Inject constructor(
} }
if (podStateManager.activationProgress.isBefore(ActivationProgress.PROGRAMMED_BASAL)) { if (podStateManager.activationProgress.isBefore(ActivationProgress.PROGRAMMED_BASAL)) {
observables.add( observables.add(
observeProgramBasalCommand(basalProgram) observeSendProgramBasalCommand(basalProgram)
.doOnComplete(ActivationProgressUpdater(ActivationProgress.PROGRAMMED_BASAL)) .doOnComplete(ActivationProgressUpdater(ActivationProgress.PROGRAMMED_BASAL))
) )
} }
@ -343,70 +399,233 @@ class OmnipodDashManagerImpl @Inject constructor(
return observables.reversed() return observables.reversed()
} }
override fun getStatus(): Observable<PodEvent> { override fun getStatus(type: ResponseType.StatusResponseType): Observable<PodEvent> {
// TODO return Observable.concat(
return Observable.empty() observeUniqueIdSet,
observeConnectToPod,
observeSendGetPodStatusCommand(type)
)
// TODO these would be common for any observable returned in a public function in this class
.doOnNext(PodEventInterceptor())
.doOnError(ErrorInterceptor())
.subscribeOn(aapsSchedulers.io)
} }
override fun setBasalProgram(program: BasalProgram): Observable<PodEvent> { override fun setBasalProgram(basalProgram: BasalProgram): Observable<PodEvent> {
// TODO return Observable.concat(
return Observable.empty() observePodRunning,
observeConnectToPod,
observeSendProgramBasalCommand(basalProgram)
)
// TODO these would be common for any observable returned in a public function in this class
.doOnNext(PodEventInterceptor())
.doOnError(ErrorInterceptor())
.subscribeOn(aapsSchedulers.io)
}
private fun observeSendStopDeliveryCommand(deliveryType: StopDeliveryCommand.DeliveryType): Observable<PodEvent> {
return Observable.defer {
bleManager.sendCommand(
StopDeliveryCommand.Builder()
.setSequenceNumber(podStateManager.messageSequenceNumber)
.setUniqueId(podStateManager.uniqueId!!.toInt())
.setNonce(NONCE)
.setDeliveryType(deliveryType)
.build(),
DefaultStatusResponse::class
)
}
} }
override fun suspendDelivery(): Observable<PodEvent> { override fun suspendDelivery(): Observable<PodEvent> {
// TODO return Observable.concat(
return Observable.empty() observePodRunning,
observeConnectToPod,
observeSendStopDeliveryCommand(StopDeliveryCommand.DeliveryType.ALL)
)
// TODO these would be common for any observable returned in a public function in this class
.doOnNext(PodEventInterceptor())
.doOnError(ErrorInterceptor())
.subscribeOn(aapsSchedulers.io)
} }
override fun setTime(): Observable<PodEvent> { override fun setTime(): Observable<PodEvent> {
// TODO // TODO
logger.error(LTag.PUMPCOMM, "NOT IMPLEMENTED: setTime()")
return Observable.empty() return Observable.empty()
} }
private fun observeSendProgramTempBasalCommand(rate: Double, durationInMinutes: Short): Observable<PodEvent> {
return Observable.defer {
// TODO cancel current temp basal (if active)
bleManager.sendCommand(
ProgramTempBasalCommand.Builder()
.setSequenceNumber(podStateManager.messageSequenceNumber)
.setUniqueId(podStateManager.uniqueId!!.toInt())
.setNonce(NONCE)
.setRateInUnitsPerHour(rate)
.setDurationInMinutes(durationInMinutes)
.build(),
DefaultStatusResponse::class
)
}
}
override fun setTempBasal(rate: Double, durationInMinutes: Short): Observable<PodEvent> { override fun setTempBasal(rate: Double, durationInMinutes: Short): Observable<PodEvent> {
// TODO return Observable.concat(
return Observable.empty() observePodRunning,
observeConnectToPod,
observeSendProgramTempBasalCommand(rate, durationInMinutes)
)
// TODO these would be common for any observable returned in a public function in this class
.doOnNext(PodEventInterceptor())
.doOnError(ErrorInterceptor())
.subscribeOn(aapsSchedulers.io)
} }
override fun cancelTempBasal(): Observable<PodEvent> { override fun cancelTempBasal(): Observable<PodEvent> {
// TODO return Observable.concat(
return Observable.empty() observePodRunning,
observeConnectToPod,
observeSendStopDeliveryCommand(StopDeliveryCommand.DeliveryType.TEMP_BASAL)
)
// TODO these would be common for any observable returned in a public function in this class
.doOnNext(PodEventInterceptor())
.doOnError(ErrorInterceptor())
.subscribeOn(aapsSchedulers.io)
} }
override fun bolus(amount: Double): Observable<PodEvent> { override fun bolus(units: Double, confirmationBeeps: Boolean, completionBeeps: Boolean): Observable<PodEvent> {
// TODO return Observable.concat(
return Observable.empty() observePodRunning,
observeConnectToPod,
observeSendProgramBolusCommand(
units,
podStateManager.pulseRate!!.toByte(),
confirmationBeeps,
completionBeeps
)
)
// TODO these would be common for any observable returned in a public function in this class
.doOnNext(PodEventInterceptor())
.doOnError(ErrorInterceptor())
.subscribeOn(aapsSchedulers.io)
} }
override fun cancelBolus(): Observable<PodEvent> { override fun cancelBolus(): Observable<PodEvent> {
// TODO return Observable.concat(
return Observable.empty() observePodRunning,
observeConnectToPod,
observeSendStopDeliveryCommand(StopDeliveryCommand.DeliveryType.BOLUS)
)
// TODO these would be common for any observable returned in a public function in this class
.doOnNext(PodEventInterceptor())
.doOnError(ErrorInterceptor())
.subscribeOn(aapsSchedulers.io)
} }
override fun programBeeps(): Observable<PodEvent> { private fun observeSendConfigureBeepsCommand(
// TODO basalReminder: ProgramReminder = ProgramReminder(atStart = false, atEnd = false, atInterval = 0),
return Observable.empty() tempBasalReminder: ProgramReminder = ProgramReminder(atStart = false, atEnd = false, atInterval = 0),
bolusReminder: ProgramReminder = ProgramReminder(atStart = false, atEnd = false, atInterval = 0),
immediateBeepType: BeepType = BeepType.SILENT
): Observable<PodEvent> {
return Observable.defer {
bleManager.sendCommand(
ProgramBeepsCommand.Builder()
.setSequenceNumber(podStateManager.messageSequenceNumber)
.setUniqueId(podStateManager.uniqueId!!.toInt())
.setBasalReminder(basalReminder)
.setTempBasalReminder(tempBasalReminder)
.setBolusReminder(bolusReminder)
.setImmediateBeepType(immediateBeepType)
.build(),
DefaultStatusResponse::class
)
}
}
override fun playBeep(beepType: BeepType): Observable<PodEvent> {
return Observable.concat(
observePodRunning,
observeConnectToPod,
observeSendConfigureBeepsCommand(immediateBeepType = beepType)
)
// TODO these would be common for any observable returned in a public function in this class
.doOnNext(PodEventInterceptor())
.doOnError(ErrorInterceptor())
.subscribeOn(aapsSchedulers.io)
} }
override fun programAlerts(alertConfigurations: List<AlertConfiguration>): Observable<PodEvent> { override fun programAlerts(alertConfigurations: List<AlertConfiguration>): Observable<PodEvent> {
// TODO return Observable.concat(
return Observable.empty() observePodRunning,
observeConnectToPod,
observeSendProgramAlertsCommand(alertConfigurations)
)
// TODO these would be common for any observable returned in a public function in this class
.doOnNext(PodEventInterceptor())
.doOnError(ErrorInterceptor())
.subscribeOn(aapsSchedulers.io)
} }
override fun silenceAlerts(alerts: EnumSet<AlertType>): Observable<PodEvent> { private fun observeSendSilenceAlertsCommand(alertTypes: EnumSet<AlertType>): Observable<PodEvent> {
// TODO return Observable.defer {
return Observable.empty() bleManager.sendCommand(
SilenceAlertsCommand.Builder()
.setSequenceNumber(podStateManager.messageSequenceNumber)
.setUniqueId(podStateManager.uniqueId!!.toInt())
.setNonce(NONCE)
.setAlertTypes(alertTypes)
.build(),
DefaultStatusResponse::class
)
}
}
override fun silenceAlerts(alertTypes: EnumSet<AlertType>): Observable<PodEvent> {
return Observable.concat(
observePodRunning,
observeConnectToPod,
observeSendSilenceAlertsCommand(alertTypes)
)
// TODO these would be common for any observable returned in a public function in this class
.doOnNext(PodEventInterceptor())
.doOnError(ErrorInterceptor())
.subscribeOn(aapsSchedulers.io)
}
private val observeSendDeactivateCommand: Observable<PodEvent>
get() = Observable.defer {
bleManager.sendCommand(
DeactivateCommand.Builder()
.setSequenceNumber(podStateManager.messageSequenceNumber)
.setUniqueId(podStateManager.uniqueId!!.toInt())
.setNonce(NONCE)
.build(),
DefaultStatusResponse::class
)
} }
override fun deactivatePod(): Observable<PodEvent> { override fun deactivatePod(): Observable<PodEvent> {
// TODO return Observable.concat(
return Observable.empty() observePodRunning,
observeConnectToPod,
observeSendDeactivateCommand
)
// TODO these would be common for any observable returned in a public function in this class
.doOnNext(PodEventInterceptor())
.doOnError(ErrorInterceptor())
.subscribeOn(aapsSchedulers.io)
//
.doOnComplete(podStateManager::reset)
} }
inner class PodEventInterceptor : Consumer<PodEvent> { inner class PodEventInterceptor : Consumer<PodEvent> {
override fun accept(event: PodEvent) { override fun accept(event: PodEvent) {
logger.debug(LTag.PUMP, "Intercepted PodEvent in OmnipodDashManagerImpl: ${event.javaClass.simpleName}") logger.debug(LTag.PUMP, "Intercepted PodEvent in OmnipodDashManagerImpl: ${event.javaClass.simpleName}")
when (event) { when (event) {
is PodEvent.AlreadyConnected -> { is PodEvent.AlreadyConnected -> {
podStateManager.bluetoothAddress = event.bluetoothAddress podStateManager.bluetoothAddress = event.bluetoothAddress

View file

@ -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.comm.status.ConnectionStatus
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.event.PodEvent import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.event.PodEvent
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command.base.Command import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command.base.Command
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.response.Response
import io.reactivex.Observable import io.reactivex.Observable
import kotlin.reflect.KClass
interface OmnipodDashBleManager { interface OmnipodDashBleManager {
fun sendCommand(cmd: Command): Observable<PodEvent> fun sendCommand(cmd: Command, responseType: KClass<out Response>): Observable<PodEvent>
fun getStatus(): ConnectionStatus fun getStatus(): ConnectionStatus

View file

@ -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.comm.status.ConnectionStatus
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.event.PodEvent import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.event.PodEvent
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command.base.Command import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command.base.Command
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.response.Response
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.state.OmnipodDashPodStateManager import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.state.OmnipodDashPodStateManager
import info.nightscout.androidaps.utils.extensions.toHex import info.nightscout.androidaps.utils.extensions.toHex
import io.reactivex.Observable import io.reactivex.Observable
@ -32,6 +33,7 @@ import java.util.concurrent.LinkedBlockingDeque
import java.util.concurrent.TimeoutException import java.util.concurrent.TimeoutException
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
import kotlin.reflect.KClass
@Singleton @Singleton
class OmnipodDashBleManagerImpl @Inject constructor( class OmnipodDashBleManagerImpl @Inject constructor(
@ -90,7 +92,8 @@ class OmnipodDashBleManagerImpl @Inject constructor(
return bleIO return bleIO
} }
override fun sendCommand(cmd: Command): Observable<PodEvent> = Observable.create { emitter -> override fun sendCommand(cmd: Command, responseType: KClass<out Response>): Observable<PodEvent> =
Observable.create { emitter ->
try { try {
val keys = sessionKeys val keys = sessionKeys
val mIO = msgIO val mIO = msgIO
@ -115,9 +118,8 @@ class OmnipodDashBleManagerImpl @Inject constructor(
sessionKeys = keys, sessionKeys = keys,
enDecrypt = enDecrypt enDecrypt = enDecrypt
) )
val response = session.sendCommand(cmd) val response = session.sendCommand(cmd, responseType)
emitter.onNext(PodEvent.ResponseReceived(response)) emitter.onNext(PodEvent.ResponseReceived(cmd, response))
emitter.onComplete() emitter.onComplete()
} catch (ex: Exception) { } catch (ex: Exception) {
emitter.tryOnError(ex) emitter.tryOnError(ex)

View file

@ -0,0 +1,3 @@
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions
class CouldNotParseResponseException(message: String?) : Exception(message)

View file

@ -0,0 +1,9 @@
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions
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")

View file

@ -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.NakResponse
class NakResponseException(val response: NakResponse) : Exception("Received NAK response: ${response.nakErrorType.value} ${response.nakErrorType.name}")

View file

@ -0,0 +1,13 @@
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.exceptions
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.response.AlarmStatusResponse
import java.util.*
class PodAlarmException(val response: AlarmStatusResponse) : Exception(
String.format(
Locale.getDefault(),
"Pod is in alarm: %03d %s",
response.alarmType.value.toInt() and 0xff,
response.alarmType.name
)
)

View file

@ -0,0 +1,44 @@
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.session
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(CouldNotParseResponseException::class, UnsupportedOperationException::class)
fun parseResponse(payload: ByteArray): Response {
return when (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 CouldNotParseResponseException("Unrecognized message type: ${payload[0]}")
}
}
@Throws(CouldNotParseResponseException::class)
private fun parseActivationResponse(payload: ByteArray): Response {
return when (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 CouldNotParseResponseException("Unrecognized activation response type: ${payload[1]}")
}
}
@Throws(CouldNotParseResponseException::class, UnsupportedOperationException::class)
private fun parseAdditionalStatusResponse(payload: ByteArray): Response {
return when (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
ResponseType.StatusResponseType.STATUS_RESPONSE_PAGE_1 -> throw UnsupportedOperationException("Status response page 1 is not (yet) implemented")
ResponseType.StatusResponseType.ALARM_STATUS -> AlarmStatusResponse(payload)
ResponseType.StatusResponseType.STATUS_RESPONSE_PAGE_3 -> throw UnsupportedOperationException("Status response page 3 is not (yet) implemented")
ResponseType.StatusResponseType.STATUS_RESPONSE_PAGE_5 -> throw UnsupportedOperationException("Status response page 5 is not (yet) implemented")
ResponseType.StatusResponseType.STATUS_RESPONSE_PAGE_6 -> throw UnsupportedOperationException("Status response page 6 is not (yet) implemented")
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 CouldNotParseResponseException("Unrecognized additional status response type: ${payload[2]}")
}
}
}

View file

@ -4,15 +4,21 @@ import info.nightscout.androidaps.logging.AAPSLogger
import info.nightscout.androidaps.logging.LTag import info.nightscout.androidaps.logging.LTag
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.Id import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.Id
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.endecrypt.EnDecrypt import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.endecrypt.EnDecrypt
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.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.NakResponseException
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.MessageIO
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.message.MessagePacket import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.message.MessagePacket
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.message.MessageType 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
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.message.StringLengthPrefixEncoding.Companion.parseKeys import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.message.StringLengthPrefixEncoding.Companion.parseKeys
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command.base.Command import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command.base.Command
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.response.AlarmStatusResponse
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.response.NakResponse import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.response.NakResponse
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.response.Response import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.response.Response
import info.nightscout.androidaps.utils.extensions.toHex import info.nightscout.androidaps.utils.extensions.toHex
import kotlin.reflect.KClass
class Session( class Session(
private val aapsLogger: AAPSLogger, private val aapsLogger: AAPSLogger,
@ -29,9 +35,13 @@ class Session(
* <- response, ACK TODO: retries? * <- response, ACK TODO: retries?
* -> ACK * -> ACK
*/ */
fun sendCommand(cmd: Command): Response { @Throws(CouldNotParseResponseException::class, UnsupportedOperationException::class)
fun sendCommand(cmd: Command, responseType: KClass<out Response>): Response {
sessionKeys.msgSequenceNumber++ sessionKeys.msgSequenceNumber++
aapsLogger.debug(LTag.PUMPBTCOMM, "Sending command: ${cmd.encoded.toHex()} in packet $cmd") aapsLogger.debug(
LTag.PUMPBTCOMM,
"Sending command: ${cmd.javaClass.simpleName}: ${cmd.encoded.toHex()} in packet $cmd"
)
val msg = getCmdMessage(cmd) val msg = getCmdMessage(cmd)
aapsLogger.debug(LTag.PUMPBTCOMM, "Sending command(wrapped): ${msg.payload.toHex()}") aapsLogger.debug(LTag.PUMPBTCOMM, "Sending command(wrapped): ${msg.payload.toHex()}")
@ -40,8 +50,19 @@ class Session(
val responseMsg = msgIO.receiveMessage() val responseMsg = msgIO.receiveMessage()
val decrypted = enDecrypt.decrypt(responseMsg) val decrypted = enDecrypt.decrypt(responseMsg)
aapsLogger.debug(LTag.PUMPBTCOMM, "Received response: $decrypted") aapsLogger.debug(LTag.PUMPBTCOMM, "Received response: $decrypted")
val response = parseResponse(decrypted) val response = parseResponse(decrypted)
if (!responseType.isInstance(response)) {
if (response is AlarmStatusResponse) {
throw PodAlarmException(response)
}
if (response is NakResponse) {
throw NakResponseException(response)
}
throw IllegalResponseException(responseType, response)
}
sessionKeys.msgSequenceNumber++ sessionKeys.msgSequenceNumber++
val ack = getAck(responseMsg) val ack = getAck(responseMsg)
aapsLogger.debug(LTag.PUMPBTCOMM, "Sending ACK: ${ack.payload.toHex()} in packet $ack") aapsLogger.debug(LTag.PUMPBTCOMM, "Sending ACK: ${ack.payload.toHex()} in packet $ack")
@ -49,11 +70,22 @@ class Session(
return response return response
} }
@Throws(CouldNotParseResponseException::class, UnsupportedOperationException::class)
private fun parseResponse(decrypted: MessagePacket): Response { private fun parseResponse(decrypted: MessagePacket): Response {
val payload = parseKeys(arrayOf(RESPONSE_PREFIX), decrypted.payload)[0] val data = parseKeys(arrayOf(RESPONSE_PREFIX), decrypted.payload)[0]
aapsLogger.info(LTag.PUMPBTCOMM, "Received decrypted response: ${payload.toHex()} in packet: $decrypted") aapsLogger.info(LTag.PUMPBTCOMM, "Received decrypted response: ${data.toHex()} in packet: $decrypted")
return NakResponse(payload)
// TODO verify length
val uniqueId = data.copyOfRange(0, 4)
val lenghtAndSequenceNumber = data.copyOfRange(4, 6)
val payload = data.copyOfRange(6, data.size - 2)
val crc = data.copyOfRange(data.size - 2, data.size)
// TODO validate uniqueId, sequenceNumber and crc
return ResponseUtil.parseResponse(payload)
} }
private fun getAck(response: MessagePacket): MessagePacket { private fun getAck(response: MessagePacket): MessagePacket {

View file

@ -7,18 +7,57 @@ import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.response.
sealed class PodEvent { sealed class PodEvent {
/* BT connection events */ /* BT connection events */
class AlreadyConnected(val bluetoothAddress: String) : PodEvent() class AlreadyConnected(val bluetoothAddress: String) : PodEvent() {
override fun toString(): String {
return "AlreadyConnected(bluetoothAddress='$bluetoothAddress')"
}
}
object AlreadyPaired : PodEvent() object AlreadyPaired : PodEvent()
object Scanning : PodEvent() object Scanning : PodEvent()
object BluetoothConnecting : PodEvent() object BluetoothConnecting : PodEvent()
class BluetoothConnected(val bluetoothAddress: String) : PodEvent() class BluetoothConnected(val bluetoothAddress: String) : PodEvent() {
override fun toString(): String {
return "BluetoothConnected(bluetoothAddress='$bluetoothAddress')"
}
}
object Pairing : PodEvent() object Pairing : PodEvent()
class Paired(val uniqueId: Id) : PodEvent() class Paired(val uniqueId: Id) : PodEvent() {
override fun toString(): String {
return "Paired(uniqueId=$uniqueId)"
}
}
object EstablishingSession : PodEvent() object EstablishingSession : PodEvent()
object Connected : PodEvent() object Connected : PodEvent()
/* Message exchange events */ /* Message exchange events */
class CommandSending(val command: Command) : PodEvent() class CommandSending(val command: Command) : PodEvent() {
class CommandSent(val command: Command) : PodEvent()
class ResponseReceived(val response: Response) : PodEvent() override fun toString(): String {
return "CommandSending(command=$command)"
}
}
class CommandSent(val command: Command) : PodEvent() {
override fun toString(): String {
return "CommandSent(command=$command)"
}
}
class ResponseReceived(
val command: Command,
val response: Response
) : PodEvent() {
override fun toString(): String {
return "ResponseReceived(command=$command, response=$response)"
}
}
} }

View file

@ -14,11 +14,11 @@ class DeactivateCommand private constructor(
override val encoded: ByteArray override val encoded: ByteArray
get() = appendCrc( get() = appendCrc(
ByteBuffer.allocate(LENGTH + HEADER_LENGTH) // ByteBuffer.allocate(LENGTH + HEADER_LENGTH)
.put(encodeHeader(uniqueId, sequenceNumber, LENGTH, multiCommandFlag)) // .put(encodeHeader(uniqueId, sequenceNumber, LENGTH, multiCommandFlag))
.put(commandType.value) // .put(commandType.value)
.put(BODY_LENGTH) // .put(BODY_LENGTH)
.putInt(nonce) // .putInt(nonce)
.array() .array()
) )

View file

@ -15,11 +15,11 @@ class GetStatusCommand private constructor(
override val encoded: ByteArray override val encoded: ByteArray
get() = appendCrc( get() = appendCrc(
ByteBuffer.allocate(LENGTH + HEADER_LENGTH) // ByteBuffer.allocate(LENGTH + HEADER_LENGTH)
.put(encodeHeader(uniqueId, sequenceNumber, LENGTH, multiCommandFlag)) // .put(encodeHeader(uniqueId, sequenceNumber, LENGTH, multiCommandFlag))
.put(commandType.value) // .put(commandType.value)
.put(BODY_LENGTH) // .put(BODY_LENGTH)
.put(statusResponseType.value) // .put(statusResponseType.value)
.array() .array()
) )

View file

@ -13,11 +13,11 @@ class GetVersionCommand private constructor(
override val encoded: ByteArray override val encoded: ByteArray
get() = appendCrc( get() = appendCrc(
ByteBuffer.allocate(LENGTH + HEADER_LENGTH) // ByteBuffer.allocate(LENGTH + HEADER_LENGTH)
.put(encodeHeader(uniqueId, sequenceNumber, LENGTH, multiCommandFlag)) // .put(encodeHeader(uniqueId, sequenceNumber, LENGTH, multiCommandFlag))
.put(commandType.value) // .put(commandType.value)
.put(BODY_LENGTH) // .put(BODY_LENGTH)
.putInt(uniqueId) // .putInt(uniqueId)
.array() .array()
) )

View file

@ -27,10 +27,10 @@ class ProgramAlertsCommand private constructor(
override val encoded: ByteArray override val encoded: ByteArray
get() { get() {
val byteBuffer: ByteBuffer = ByteBuffer.allocate(getLength() + HEADER_LENGTH) // val byteBuffer: ByteBuffer = ByteBuffer.allocate(getLength() + HEADER_LENGTH)
.put(encodeHeader(uniqueId, sequenceNumber, getLength(), multiCommandFlag)) // .put(encodeHeader(uniqueId, sequenceNumber, getLength(), multiCommandFlag))
.put(commandType.value) // .put(commandType.value)
.put(getBodyLength()) // .put(getBodyLength())
.putInt(nonce) .putInt(nonce)
for (configuration in alertConfigurations) { for (configuration in alertConfigurations) {
byteBuffer.put(configuration.encoded) byteBuffer.put(configuration.encoded)

View file

@ -36,12 +36,12 @@ class ProgramBasalCommand private constructor(
override val encoded: ByteArray override val encoded: ByteArray
get() { get() {
val buffer = ByteBuffer.allocate(length.toInt()) // val buffer = ByteBuffer.allocate(length.toInt())
.put(commandType.value) // .put(commandType.value)
.put(bodyLength) // .put(bodyLength)
.put(programReminder.encoded) // .put(programReminder.encoded)
.put(currentInsulinProgramElementIndex) // .put(currentInsulinProgramElementIndex)
.putShort(remainingTenthPulsesInCurrentInsulinProgramElement) // .putShort(remainingTenthPulsesInCurrentInsulinProgramElement)
.putInt(delayUntilNextTenthPulseInUsec) .putInt(delayUntilNextTenthPulseInUsec)
for (insulinProgramElement in insulinProgramElements) { for (insulinProgramElement in insulinProgramElements) {
buffer.put(insulinProgramElement.encoded) buffer.put(insulinProgramElement.encoded)
@ -55,10 +55,10 @@ class ProgramBasalCommand private constructor(
multiCommandFlag multiCommandFlag
) )
return appendCrc( return appendCrc(
ByteBuffer.allocate(basalCommand.size + interlockCommand.size + header.size) // ByteBuffer.allocate(basalCommand.size + interlockCommand.size + header.size)
.put(header) // .put(header)
.put(interlockCommand) // .put(interlockCommand)
.put(basalCommand) // .put(basalCommand)
.array() .array()
) )
} }

View file

@ -19,14 +19,14 @@ class ProgramBeepsCommand private constructor(
override val encoded: ByteArray override val encoded: ByteArray
get() = appendCrc( get() = appendCrc(
ByteBuffer.allocate(LENGTH + HEADER_LENGTH) // ByteBuffer.allocate(LENGTH + HEADER_LENGTH)
.put(encodeHeader(uniqueId, sequenceNumber, LENGTH, multiCommandFlag)) // .put(encodeHeader(uniqueId, sequenceNumber, LENGTH, multiCommandFlag))
.put(commandType.value) // .put(commandType.value)
.put(BODY_LENGTH) // .put(BODY_LENGTH)
.put(immediateBeepType.value) // .put(immediateBeepType.value)
.put(basalReminder.encoded) // .put(basalReminder.encoded)
.put(tempBasalReminder.encoded) // .put(tempBasalReminder.encoded)
.put(bolusReminder.encoded) // .put(bolusReminder.encoded)
.array() .array()
) )

View file

@ -21,12 +21,12 @@ class ProgramBolusCommand private constructor(
override val encoded: ByteArray override val encoded: ByteArray
get() { get() {
val bolusCommand = ByteBuffer.allocate(LENGTH.toInt()) // val bolusCommand = ByteBuffer.allocate(LENGTH.toInt())
.put(commandType.value) // .put(commandType.value)
.put(BODY_LENGTH) // .put(BODY_LENGTH)
.put(programReminder.encoded) // .put(programReminder.encoded)
.putShort(numberOfTenthPulses) // .putShort(numberOfTenthPulses)
.putInt(delayUntilFirstTenthPulseInUsec) // .putInt(delayUntilFirstTenthPulseInUsec)
.putShort(0.toShort()) // Extended bolus pulses .putShort(0.toShort()) // Extended bolus pulses
.putInt(0) // Delay between tenth extended pulses in usec .putInt(0) // Delay between tenth extended pulses in usec
.array() .array()
@ -38,10 +38,10 @@ class ProgramBolusCommand private constructor(
multiCommandFlag multiCommandFlag
) )
return appendCrc( return appendCrc(
ByteBuffer.allocate(header.size + interlockCommand.size + bolusCommand.size) // ByteBuffer.allocate(header.size + interlockCommand.size + bolusCommand.size)
.put(header) // .put(header)
.put(interlockCommand) // .put(interlockCommand)
.put(bolusCommand) // .put(bolusCommand)
.array() .array()
) )
} }
@ -120,11 +120,11 @@ class ProgramBolusCommand private constructor(
private const val BODY_LENGTH: Byte = 13 private const val BODY_LENGTH: Byte = 13
private fun calculateChecksum(numberOfSlots: Byte, byte10And11: Short, numberOfPulses: Short): Short { private fun calculateChecksum(numberOfSlots: Byte, byte10And11: Short, numberOfPulses: Short): Short {
return MessageUtil.calculateChecksum( return MessageUtil.calculateChecksum(
ByteBuffer.allocate(7) // ByteBuffer.allocate(7)
.put(numberOfSlots) // .put(numberOfSlots)
.putShort(byte10And11) // .putShort(byte10And11)
.putShort(numberOfPulses) // .putShort(numberOfPulses)
.putShort(numberOfPulses) // .putShort(numberOfPulses)
.array() .array()
) )
} }

View file

@ -37,12 +37,12 @@ class ProgramInsulinCommand internal constructor(
override val encoded: ByteArray override val encoded: ByteArray
get() { get() {
val buffer = ByteBuffer.allocate(getLength().toInt()) // val buffer = ByteBuffer.allocate(getLength().toInt())
.put(commandType.value) // .put(commandType.value)
.put(getBodyLength()) // .put(getBodyLength())
.putInt(nonce) // .putInt(nonce)
.put(deliveryType.getValue()) // .put(deliveryType.getValue())
.putShort(checksum) // .putShort(checksum)
.put(byte9) // BASAL: currentSlot // BOLUS: number of ShortInsulinProgramElements .put(byte9) // BASAL: currentSlot // BOLUS: number of ShortInsulinProgramElements
.putShort(byte10And11) // BASAL: remainingEighthSecondsInCurrentSlot // BOLUS: immediate pulses multiplied by delay between pulses in eighth seconds .putShort(byte10And11) // BASAL: remainingEighthSecondsInCurrentSlot // BOLUS: immediate pulses multiplied by delay between pulses in eighth seconds
.putShort(byte12And13) // BASAL: remainingPulsesInCurrentSlot // BOLUS: immediate pulses .putShort(byte12And13) // BASAL: remainingPulsesInCurrentSlot // BOLUS: immediate pulses

View file

@ -103,12 +103,12 @@ class ProgramTempBasalCommand private constructor(
delayUntilNextTenthPulseInUsec = delayUntilNextTenthPulseInUsec =
(firstProgramElement.numberOfSlots.toLong() * 1800.0 / remainingTenthPulsesInFirstElement * 1000000).toInt() (firstProgramElement.numberOfSlots.toLong() * 1800.0 / remainingTenthPulsesInFirstElement * 1000000).toInt()
} }
val buffer = ByteBuffer.allocate(getLength().toInt()) // val buffer = ByteBuffer.allocate(getLength().toInt())
.put(commandType.value) // .put(commandType.value)
.put(getBodyLength()) // .put(getBodyLength())
.put(programReminder.encoded) // .put(programReminder.encoded)
.put(0x00.toByte()) // Current slot index .put(0x00.toByte()) // Current slot index
.putShort(remainingTenthPulsesInFirstElement) // .putShort(remainingTenthPulsesInFirstElement)
.putInt(delayUntilNextTenthPulseInUsec) .putInt(delayUntilNextTenthPulseInUsec)
for (element in insulinProgramElements) { for (element in insulinProgramElements) {
buffer.put(element.encoded) buffer.put(element.encoded)
@ -122,10 +122,10 @@ class ProgramTempBasalCommand private constructor(
multiCommandFlag multiCommandFlag
) )
return appendCrc( return appendCrc(
ByteBuffer.allocate(header.size + interlockCommand.size + tempBasalCommand.size) // ByteBuffer.allocate(header.size + interlockCommand.size + tempBasalCommand.size)
.put(header) // .put(header)
.put(interlockCommand) // .put(interlockCommand)
.put(tempBasalCommand) // .put(tempBasalCommand)
.array() .array()
) )
} }

View file

@ -17,16 +17,16 @@ class SetUniqueIdCommand private constructor(
override val encoded: ByteArray override val encoded: ByteArray
get() = appendCrc( get() = appendCrc(
ByteBuffer.allocate(LENGTH + HEADER_LENGTH) // ByteBuffer.allocate(LENGTH + HEADER_LENGTH)
.put(encodeHeader(DEFAULT_UNIQUE_ID, sequenceNumber, LENGTH, multiCommandFlag)) // .put(encodeHeader(DEFAULT_UNIQUE_ID, sequenceNumber, LENGTH, multiCommandFlag))
.put(commandType.value) // .put(commandType.value)
.put(BODY_LENGTH) // .put(BODY_LENGTH)
.putInt(uniqueId) // .putInt(uniqueId)
.put(0x14.toByte()) // FIXME ?? .put(0x14.toByte()) // FIXME ??
.put(0x04.toByte()) // FIXME ?? .put(0x04.toByte()) // FIXME ??
.put(encodeInitializationTime(initializationTime)) // .put(encodeInitializationTime(initializationTime))
.putInt(lotNumber) // .putInt(lotNumber)
.putInt(podSequenceNumber) // .putInt(podSequenceNumber)
.array() .array()
) )
@ -86,12 +86,12 @@ class SetUniqueIdCommand private constructor(
private fun encodeInitializationTime(date: Date): ByteArray { private fun encodeInitializationTime(date: Date): ByteArray {
val instance = Calendar.getInstance() val instance = Calendar.getInstance()
instance.time = date instance.time = date
return byteArrayOf( // return byteArrayOf(
(instance[Calendar.MONTH] + 1).toByte(), // (instance[Calendar.MONTH] + 1).toByte(),
instance[Calendar.DATE].toByte(), // instance[Calendar.DATE].toByte(),
(instance[Calendar.YEAR] % 100).toByte(), // (instance[Calendar.YEAR] % 100).toByte(),
instance[Calendar.HOUR_OF_DAY].toByte(), // instance[Calendar.HOUR_OF_DAY].toByte(),
instance[Calendar.MINUTE].toByte() // instance[Calendar.MINUTE].toByte()
) )
} }
} }

View file

@ -19,12 +19,12 @@ class SilenceAlertsCommand private constructor(
override val encoded: ByteArray override val encoded: ByteArray
get() = get() =
appendCrc( appendCrc(
ByteBuffer.allocate(LENGTH + HEADER_LENGTH) // ByteBuffer.allocate(LENGTH + HEADER_LENGTH)
.put(encodeHeader(uniqueId, sequenceNumber, LENGTH, multiCommandFlag)) // .put(encodeHeader(uniqueId, sequenceNumber, LENGTH, multiCommandFlag))
.put(commandType.value) // .put(commandType.value)
.put(BODY_LENGTH) // .put(BODY_LENGTH)
.putInt(nonce) // .putInt(nonce)
.put(AlertUtil.encodeAlertSet(alertTypes)) // .put(AlertUtil.encodeAlertSet(alertTypes))
.array() .array()
) )

View file

@ -20,12 +20,12 @@ class StopDeliveryCommand private constructor(
override val encoded: ByteArray override val encoded: ByteArray
get() { get() {
return appendCrc( return appendCrc(
ByteBuffer.allocate(LENGTH + HEADER_LENGTH) // ByteBuffer.allocate(LENGTH + HEADER_LENGTH)
.put(encodeHeader(uniqueId, sequenceNumber, LENGTH, multiCommandFlag)) // .put(encodeHeader(uniqueId, sequenceNumber, LENGTH, multiCommandFlag))
.put(commandType.value) // .put(commandType.value)
.put(BODY_LENGTH) // .put(BODY_LENGTH)
.putInt(nonce) // .putInt(nonce)
.put((beepType.value.toInt() shl 4 or deliveryType.encoded[0].toInt()).toByte()) // .put((beepType.value.toInt() shl 4 or deliveryType.encoded[0].toInt()).toByte())
.array() .array()
) )
} }

View file

@ -13,9 +13,9 @@ abstract class HeaderEnabledCommand protected constructor(
companion object { companion object {
internal fun appendCrc(command: ByteArray): ByteArray = internal fun appendCrc(command: ByteArray): ByteArray =
ByteBuffer.allocate(command.size + 2) // ByteBuffer.allocate(command.size + 2)
.put(command) // .put(command)
.putShort(MessageUtil.createCrc(command)) // .putShort(MessageUtil.createCrc(command))
.array() .array()
internal fun encodeHeader( internal fun encodeHeader(
@ -24,9 +24,9 @@ abstract class HeaderEnabledCommand protected constructor(
length: Short, length: Short,
multiCommandFlag: Boolean multiCommandFlag: Boolean
): ByteArray = ): ByteArray =
ByteBuffer.allocate(6) // ByteBuffer.allocate(6)
.putInt(uniqueId) // .putInt(uniqueId)
.putShort((sequenceNumber.toInt() and 0x0f shl 10 or length.toInt() or ((if (multiCommandFlag) 1 else 0) shl 15)).toShort()) // .putShort((sequenceNumber.toInt() and 0x0f shl 10 or length.toInt() or ((if (multiCommandFlag) 1 else 0) shl 15)).toShort())
.array() .array()
internal const val HEADER_LENGTH: Short = 6 internal const val HEADER_LENGTH: Short = 6

View file

@ -12,9 +12,9 @@ open class BasalInsulinProgramElement(
) : Encodable, Serializable { ) : Encodable, Serializable {
override val encoded: ByteArray override val encoded: ByteArray
get() = ByteBuffer.allocate(6) // get() = ByteBuffer.allocate(6)
.putShort(totalTenthPulses) // .putShort(totalTenthPulses)
.putInt(if (totalTenthPulses.toInt() == 0) Int.MIN_VALUE or delayBetweenTenthPulsesInUsec else delayBetweenTenthPulsesInUsec) // .putInt(if (totalTenthPulses.toInt() == 0) Int.MIN_VALUE or delayBetweenTenthPulsesInUsec else delayBetweenTenthPulsesInUsec)
.array() .array()
val durationInSeconds: Short val durationInSeconds: Short
get() = (numberOfSlots * 1800).toShort() get() = (numberOfSlots * 1800).toShort()

View file

@ -12,13 +12,13 @@ class BasalShortInsulinProgramElement(
override val encoded: ByteArray override val encoded: ByteArray
get() { get() {
val firstByte = ( val firstByte = (
numberOfSlots - 1 and 0x0f shl 4 // numberOfSlots - 1 and 0x0f shl 4
or ((if (extraAlternatePulse) 1 else 0) shl 3) // or ((if (extraAlternatePulse) 1 else 0) shl 3)
or (pulsesPerSlot.toInt() ushr 8 and 0x03) or (pulsesPerSlot.toInt() ushr 8 and 0x03)
).toByte() ).toByte()
return ByteBuffer.allocate(2) // return ByteBuffer.allocate(2)
.put(firstByte) // .put(firstByte)
.put((pulsesPerSlot and 0xff).toByte()) // .put((pulsesPerSlot and 0xff).toByte())
.array() .array()
} }

View file

@ -13,10 +13,10 @@ class TempBasalInsulinProgramElement(
val buffer = ByteBuffer.allocate(6) val buffer = ByteBuffer.allocate(6)
if (totalTenthPulses.toInt() == 0) { if (totalTenthPulses.toInt() == 0) {
val i = (durationInSeconds.toDouble() * 1000000.0 / numberOfSlots.toDouble()).toInt() or Int.MIN_VALUE val i = (durationInSeconds.toDouble() * 1000000.0 / numberOfSlots.toDouble()).toInt() or Int.MIN_VALUE
buffer.putShort(numberOfSlots.toShort()) // buffer.putShort(numberOfSlots.toShort())
.putInt(i) .putInt(i)
} else { } else {
buffer.putShort(totalTenthPulses) // buffer.putShort(totalTenthPulses)
.putInt(delayBetweenTenthPulsesInUsec) .putInt(delayBetweenTenthPulsesInUsec)
} }
return buffer.array() return buffer.array()

View file

@ -56,6 +56,7 @@ object ProgramBasalUtil {
fun mapPulsesPerSlotToShortInsulinProgramElements(pulsesPerSlot: ShortArray?): List<ShortInsulinProgramElement> { fun mapPulsesPerSlotToShortInsulinProgramElements(pulsesPerSlot: ShortArray?): List<ShortInsulinProgramElement> {
require(pulsesPerSlot!!.size <= NUMBER_OF_BASAL_SLOTS) { "Basal program must contain at most 48 slots" } require(pulsesPerSlot!!.size <= NUMBER_OF_BASAL_SLOTS) { "Basal program must contain at most 48 slots" }
val elements: MutableList<ShortInsulinProgramElement> = ArrayList() val elements: MutableList<ShortInsulinProgramElement> = ArrayList()
var extraAlternatePulse = false var extraAlternatePulse = false
var previousPulsesPerSlot: Short = 0 var previousPulsesPerSlot: Short = 0
@ -84,7 +85,7 @@ object ProgramBasalUtil {
extraAlternatePulse = false extraAlternatePulse = false
} }
currentTotalNumberOfSlots++ currentTotalNumberOfSlots++
} else if (numberOfSlotsInCurrentElement.toInt() == 1 && pulsesPerSlot[currentTotalNumberOfSlots.toInt()].toInt() == previousPulsesPerSlot + 1) { } else if (numberOfSlotsInCurrentElement.toInt() == 1 && !extraAlternatePulse && pulsesPerSlot[currentTotalNumberOfSlots.toInt()].toInt() == previousPulsesPerSlot + 1) {
// Second slot of segment with extra alternate pulse // Second slot of segment with extra alternate pulse
var expectAlternatePulseForNextSegment = false var expectAlternatePulseForNextSegment = false
currentTotalNumberOfSlots++ currentTotalNumberOfSlots++
@ -94,10 +95,10 @@ object ProgramBasalUtil {
// Loop rest alternate pulse segment // Loop rest alternate pulse segment
if (pulsesPerSlot[currentTotalNumberOfSlots.toInt()].toInt() == previousPulsesPerSlot + (if (expectAlternatePulseForNextSegment) 1 else 0)) { if (pulsesPerSlot[currentTotalNumberOfSlots.toInt()].toInt() == previousPulsesPerSlot + (if (expectAlternatePulseForNextSegment) 1 else 0)) {
// Still in alternate pulse segment // Still in alternate pulse segment
currentTotalNumberOfSlots++
expectAlternatePulseForNextSegment = !expectAlternatePulseForNextSegment expectAlternatePulseForNextSegment = !expectAlternatePulseForNextSegment
if (numberOfSlotsInCurrentElement < MAX_NUMBER_OF_SLOTS_IN_INSULIN_PROGRAM_ELEMENT) { if (numberOfSlotsInCurrentElement < MAX_NUMBER_OF_SLOTS_IN_INSULIN_PROGRAM_ELEMENT) {
numberOfSlotsInCurrentElement++ numberOfSlotsInCurrentElement++
currentTotalNumberOfSlots++
} else { } else {
// End of alternate pulse segment (no slots left in element) // End of alternate pulse segment (no slots left in element)
elements.add( elements.add(
@ -110,6 +111,7 @@ object ProgramBasalUtil {
previousPulsesPerSlot = pulsesPerSlot[currentTotalNumberOfSlots.toInt()] previousPulsesPerSlot = pulsesPerSlot[currentTotalNumberOfSlots.toInt()]
numberOfSlotsInCurrentElement = 1 numberOfSlotsInCurrentElement = 1
extraAlternatePulse = false extraAlternatePulse = false
currentTotalNumberOfSlots++
break break
} }
} else { } else {
@ -193,6 +195,7 @@ object ProgramBasalUtil {
val hourOfDay = instance[Calendar.HOUR_OF_DAY] val hourOfDay = instance[Calendar.HOUR_OF_DAY]
val minuteOfHour = instance[Calendar.MINUTE] val minuteOfHour = instance[Calendar.MINUTE]
val secondOfMinute = instance[Calendar.SECOND] val secondOfMinute = instance[Calendar.SECOND]
val index = ((hourOfDay * 60 + minuteOfHour) / 30).toByte() val index = ((hourOfDay * 60 + minuteOfHour) / 30).toByte()
val secondOfDay = secondOfMinute + hourOfDay * 3600 + minuteOfHour * 60 val secondOfDay = secondOfMinute + hourOfDay * 3600 + minuteOfHour * 60
val secondsRemaining = ((index + 1) * 1800 - secondOfDay).toShort() val secondsRemaining = ((index + 1) * 1800 - secondOfDay).toShort()
@ -245,9 +248,9 @@ object ProgramBasalUtil {
} }
fun calculateChecksum(pulsesPerSlot: ShortArray?, currentSlot: CurrentSlot): Short { fun calculateChecksum(pulsesPerSlot: ShortArray?, currentSlot: CurrentSlot): Short {
val buffer = ByteBuffer.allocate(1 + 2 + 2 + NUMBER_OF_BASAL_SLOTS * 2) // val buffer = ByteBuffer.allocate(1 + 2 + 2 + NUMBER_OF_BASAL_SLOTS * 2)
.put(currentSlot.index) // .put(currentSlot.index)
.putShort(currentSlot.pulsesRemaining) // .putShort(currentSlot.pulsesRemaining)
.putShort(currentSlot.eighthSecondsRemaining) .putShort(currentSlot.eighthSecondsRemaining)
for (pulses in pulsesPerSlot!!) { for (pulses in pulsesPerSlot!!) {
buffer.putShort(pulses) buffer.putShort(pulses)

View file

@ -53,9 +53,9 @@ object ProgramTempBasalUtil {
} }
fun calculateChecksum(totalNumberOfSlots: Byte, pulsesInFirstSlot: Short, pulsesPerSlot: ShortArray?): Short { fun calculateChecksum(totalNumberOfSlots: Byte, pulsesInFirstSlot: Short, pulsesPerSlot: ShortArray?): Short {
val buffer = ByteBuffer.allocate(1 + 2 + 2 + 2 * pulsesPerSlot!!.size) // val buffer = ByteBuffer.allocate(1 + 2 + 2 + 2 * pulsesPerSlot!!.size)
.put(totalNumberOfSlots) // .put(totalNumberOfSlots)
.putShort(0x3840.toShort()) // .putShort(0x3840.toShort())
.putShort(pulsesInFirstSlot) .putShort(pulsesInFirstSlot)
for (pulses in pulsesPerSlot) { for (pulses in pulsesPerSlot) {
buffer.putShort(pulses) buffer.putShort(pulses)

View file

@ -26,9 +26,9 @@ class AlertConfiguration(
firstByte = firstByte or (1 shl 1) firstByte = firstByte or (1 shl 1)
} }
firstByte = firstByte or ((durationInMinutes.toInt() shr 8 and 0x01).toByte()) firstByte = firstByte or ((durationInMinutes.toInt() shr 8 and 0x01).toByte())
return ByteBuffer.allocate(6) // return ByteBuffer.allocate(6)
.put(firstByte) .put(firstByte)
.put(durationInMinutes.toByte()) // .put(durationInMinutes.toByte())
.putShort( .putShort(
when (trigger) { when (trigger) {
is AlertTrigger.ReservoirVolumeTrigger -> { is AlertTrigger.ReservoirVolumeTrigger -> {
@ -39,9 +39,9 @@ class AlertConfiguration(
trigger.offsetInMinutes trigger.offsetInMinutes
} }
} }
) // )
.put(beepRepetition.value) // .put(beepRepetition.value)
.put(beepType.value) // .put(beepType.value)
.array() .array()
} }

View file

@ -17,4 +17,8 @@ class ProgramReminder(
or ((atInterval and 0x3f).toInt()) or ((atInterval and 0x3f).toInt())
).toByte() ).toByte()
) )
override fun toString(): String {
return "ProgramReminder(atStart=$atStart, atEnd=$atEnd, atInterval=$atInterval)"
}
} }

View file

@ -216,8 +216,8 @@ class OmnipodDashPodStateManagerImpl @Inject constructor(
override fun updateFromSetUniqueIdResponse(response: SetUniqueIdResponse) { override fun updateFromSetUniqueIdResponse(response: SetUniqueIdResponse) {
podState.pulseRate = response.pumpRate podState.pulseRate = response.pumpRate
podState.primePulseRate = response.primePumpRate podState.primePulseRate = response.primePumpRate
podState.firstPrimeBolusVolume = response.numberOfPrimePulses podState.firstPrimeBolusVolume = response.numberOfEngagingClutchDrivePulses
podState.secondPrimeBolusVolume = response.numberOfEngagingClutchDrivePulses podState.secondPrimeBolusVolume = response.numberOfPrimePulses
podState.podLifeInHours = response.podExpirationTimeInHours podState.podLifeInHours = response.podExpirationTimeInHours
podState.bleVersion = SoftwareVersion( podState.bleVersion = SoftwareVersion(
response.bleVersionMajor, response.bleVersionMajor,

View file

@ -25,9 +25,6 @@ import io.reactivex.disposables.CompositeDisposable
import io.reactivex.rxkotlin.plusAssign import io.reactivex.rxkotlin.plusAssign
import javax.inject.Inject import javax.inject.Inject
/**
* Created by andy on 30/08/2019
*/
class DashPodManagementActivity : NoSplashAppCompatActivity() { class DashPodManagementActivity : NoSplashAppCompatActivity() {
@Inject lateinit var rxBus: RxBusWrapper @Inject lateinit var rxBus: RxBusWrapper
@ -70,7 +67,7 @@ class DashPodManagementActivity : NoSplashAppCompatActivity() {
this, this,
resourceHelper.gs(R.string.omnipod_common_pod_management_discard_pod_confirmation), resourceHelper.gs(R.string.omnipod_common_pod_management_discard_pod_confirmation),
Thread { Thread {
// TODO discard Pod podStateManager.reset()
} }
) )
} }

View file

@ -295,7 +295,10 @@ class OmnipodDashOverviewFragment : DaggerFragment() {
// total delivered // total delivered
podInfoBinding.totalDelivered.text = podInfoBinding.totalDelivered.text =
if (podStateManager.isActivationCompleted && podStateManager.pulsesDelivered != null) { if (podStateManager.isActivationCompleted && podStateManager.pulsesDelivered != null) {
resourceHelper.gs(R.string.omnipod_common_overview_total_delivered_value, podStateManager.pulseRate) resourceHelper.gs(
R.string.omnipod_common_overview_total_delivered_value,
podStateManager.pulsesDelivered!! * 0.05
)
} else { } else {
PLACEHOLDER PLACEHOLDER
} }
@ -313,7 +316,7 @@ class OmnipodDashOverviewFragment : DaggerFragment() {
podInfoBinding.reservoir.text = resourceHelper.gs( podInfoBinding.reservoir.text = resourceHelper.gs(
R.string.omnipod_common_overview_reservoir_value, R.string.omnipod_common_overview_reservoir_value,
podStateManager.pulsesRemaining (podStateManager.pulsesRemaining!! * 0.05)
) )
podInfoBinding.reservoir.setTextColor( podInfoBinding.reservoir.setTextColor(
if (podStateManager.pulsesRemaining!! < lowReservoirThreshold) { if (podStateManager.pulsesRemaining!! < lowReservoirThreshold) {

View file

@ -32,7 +32,14 @@ class DashInsertCannulaViewModel @Inject constructor(
if (profile == null) { if (profile == null) {
source.onError(IllegalStateException("No profile set")) source.onError(IllegalStateException("No profile set"))
} else { } else {
val disposable = omnipodManager.activatePodPart2(mapProfileToBasalProgram(profile)).subscribeBy( val basalProgram = mapProfileToBasalProgram(profile)
logger.debug(
LTag.PUMPCOMM,
"Mapped profile to basal program. profile={}, basalProgram={}",
profile,
basalProgram
)
val disposable = omnipodManager.activatePodPart2(basalProgram).subscribeBy(
onNext = { podEvent -> onNext = { podEvent ->
logger.debug( logger.debug(
LTag.PUMP, LTag.PUMP,

View file

@ -4,24 +4,43 @@ import androidx.annotation.StringRes
import dagger.android.HasAndroidInjector import dagger.android.HasAndroidInjector
import info.nightscout.androidaps.data.PumpEnactResult import info.nightscout.androidaps.data.PumpEnactResult
import info.nightscout.androidaps.logging.AAPSLogger import info.nightscout.androidaps.logging.AAPSLogger
import info.nightscout.androidaps.logging.LTag
import info.nightscout.androidaps.plugins.pump.omnipod.common.R import info.nightscout.androidaps.plugins.pump.omnipod.common.R
import info.nightscout.androidaps.plugins.pump.omnipod.common.ui.wizard.deactivation.viewmodel.action.DeactivatePodViewModel import info.nightscout.androidaps.plugins.pump.omnipod.common.ui.wizard.deactivation.viewmodel.action.DeactivatePodViewModel
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.OmnipodDashManager import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.OmnipodDashManager
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.state.OmnipodDashPodStateManager
import io.reactivex.Single import io.reactivex.Single
import io.reactivex.rxkotlin.subscribeBy
import javax.inject.Inject import javax.inject.Inject
class DashDeactivatePodViewModel @Inject constructor( class DashDeactivatePodViewModel @Inject constructor(
private val omnipodManager: OmnipodDashManager, private val omnipodManager: OmnipodDashManager,
private val podStateManager: OmnipodDashPodStateManager,
injector: HasAndroidInjector, injector: HasAndroidInjector,
logger: AAPSLogger logger: AAPSLogger
) : DeactivatePodViewModel(injector, logger) { ) : DeactivatePodViewModel(injector, logger) {
override fun doExecuteAction(): Single<PumpEnactResult> = Single.just( override fun doExecuteAction(): Single<PumpEnactResult> = Single.create { source ->
PumpEnactResult(injector).success(true).comment("TODO") omnipodManager.deactivatePod().subscribeBy(
) // TODO onNext = { podEvent ->
logger.debug(
LTag.PUMP,
"Received PodEvent in Pod deactivation: $podEvent"
)
},
onError = { throwable ->
logger.error(LTag.PUMP, "Error in Pod deactivation", throwable)
source.onSuccess(PumpEnactResult(injector).success(false).comment(throwable.message))
},
onComplete = {
logger.debug("Pod deactivation completed")
source.onSuccess(PumpEnactResult(injector).success(true))
}
)
}
override fun discardPod() { override fun discardPod() {
// TODO podStateManager.reset()
} }
@StringRes @StringRes

View file

@ -41,7 +41,7 @@ fun mapProfileToBasalProgram(profile: Profile): BasalProgram {
} }
if (entries.size == 0 && basalValue.timeAsSeconds != 0) { if (entries.size == 0 && basalValue.timeAsSeconds != 0) {
throw java.lang.IllegalArgumentException("First basal segment start time should be 0") throw IllegalArgumentException("First basal segment start time should be 0")
} }
if (entries.size > 0 && entries[entries.size - 1].endSlotIndex != startSlotIndex) { if (entries.size > 0 && entries[entries.size - 1].endSlotIndex != startSlotIndex) {

View file

@ -119,6 +119,7 @@
android:orientation="vertical" android:orientation="vertical"
app:layout_constraintGuide_percent="0.5" /> app:layout_constraintGuide_percent="0.5" />
<!-- FIXME visible for development -->
<info.nightscout.androidaps.utils.ui.SingleClickButton <info.nightscout.androidaps.utils.ui.SingleClickButton
android:id="@+id/button_discard_pod" android:id="@+id/button_discard_pod"
style="?android:attr/buttonStyle" style="?android:attr/buttonStyle"
@ -127,7 +128,7 @@
android:drawableTop="@drawable/ic_pod_management_discard_pod" android:drawableTop="@drawable/ic_pod_management_discard_pod"
android:text="@string/omnipod_common_pod_management_button_discard_pod" android:text="@string/omnipod_common_pod_management_button_discard_pod"
android:textAllCaps="false" android:textAllCaps="false"
android:visibility="gone" android:visibility="visible"
app:layout_constrainedHeight="@+id/Actions_Row_2_horizontal_guideline" app:layout_constrainedHeight="@+id/Actions_Row_2_horizontal_guideline"
app:layout_constraintHorizontal_bias="0.0" app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintLeft_toRightOf="@+id/Actions_Col_1_Row_2_vertical_guideline" app:layout_constraintLeft_toRightOf="@+id/Actions_Col_1_Row_2_vertical_guideline"

View file

@ -4,7 +4,7 @@ import com.google.crypto.tink.subtle.Hex
import info.nightscout.androidaps.logging.AAPSLoggerTest import info.nightscout.androidaps.logging.AAPSLoggerTest
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.Id import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.Id
import info.nightscout.androidaps.utils.extensions.toHex import info.nightscout.androidaps.utils.extensions.toHex
import org.junit.Assert.* import org.junit.Assert.assertEquals
import org.junit.Test import org.junit.Test
class MessagePacketTest { class MessagePacketTest {

View file

@ -7,6 +7,7 @@ import org.junit.Test
import java.util.* import java.util.*
class PayloadSplitJoinTest { class PayloadSplitJoinTest {
private val random = Random(42) private val random = Random(42)
@Test fun testSplitAndJoinBack() { @Test fun testSplitAndJoinBack() {

View file

@ -7,6 +7,7 @@ import org.junit.Assert.assertTrue
import org.junit.Test import org.junit.Test
class PayloadSplitterTest { class PayloadSplitterTest {
@Test fun testSplitter() { @Test fun testSplitter() {
val f1 = "00,01,54,57,10,23,03,00,00,c0,ff,ff,ff,fe,08,20,2e,a8,50,30".replace(",", "") val f1 = "00,01,54,57,10,23,03,00,00,c0,ff,ff,ff,fe,08,20,2e,a8,50,30".replace(",", "")
val f2 = "01,04,bc,20,1f,f6,3d,00,01,a5,ff,ff,ff,fe,08,20,2e,a8,50,30".replace(",", "") val f2 = "01,04,bc,20,1f,f6,3d,00,01,a5,ff,ff,ff,fe,08,20,2e,a8,50,30".replace(",", "")

View file

@ -2,10 +2,11 @@ package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.message
import com.google.crypto.tink.subtle.Hex import com.google.crypto.tink.subtle.Hex
import info.nightscout.androidaps.utils.extensions.toHex import info.nightscout.androidaps.utils.extensions.toHex
import org.junit.Assert.* import org.junit.Assert.assertEquals
import org.junit.Test import org.junit.Test
class StringLengthPrefixEncodingTest { class StringLengthPrefixEncodingTest {
private val p0Payload = Hex.decode("50,30,3d,00,01,a5".replace(",", "")) // from logs private val p0Payload = Hex.decode("50,30,3d,00,01,a5".replace(",", "")) // from logs
private val p0Content = Hex.decode("a5") private val p0Content = Hex.decode("a5")

View file

@ -3,7 +3,6 @@ package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.comm.session
import info.nightscout.androidaps.logging.AAPSLoggerTest import info.nightscout.androidaps.logging.AAPSLoggerTest
import info.nightscout.androidaps.utils.extensions.toHex import info.nightscout.androidaps.utils.extensions.toHex
import org.junit.Assert import org.junit.Assert
import org.junit.Assert.*
import org.junit.Test import org.junit.Test
import org.spongycastle.util.encoders.Hex import org.spongycastle.util.encoders.Hex
@ -11,7 +10,8 @@ class EapMessageTest {
@Test fun testParseAndBack() { @Test fun testParseAndBack() {
val aapsLogger = AAPSLoggerTest() val aapsLogger = AAPSLoggerTest()
val payload = Hex.decode("01bd0038170100000205000000c55c78e8d3b9b9e935860a7259f6c001050000c2cd1248451103bd77a6c7ef88c441ba7e0200006cff5d18") val payload =
Hex.decode("01bd0038170100000205000000c55c78e8d3b9b9e935860a7259f6c001050000c2cd1248451103bd77a6c7ef88c441ba7e0200006cff5d18")
val eapMsg = EapMessage.parse(aapsLogger, payload) val eapMsg = EapMessage.parse(aapsLogger, payload)
val back = eapMsg.toByteArray() val back = eapMsg.toByteArray()
Assert.assertEquals(back.toHex(), payload.toHex()) Assert.assertEquals(back.toHex(), payload.toHex())

View file

@ -8,11 +8,11 @@ import org.junit.Test
class DeactivateCommandTest { class DeactivateCommandTest {
@Test @Throws(DecoderException::class) fun testEncoding() { @Test @Throws(DecoderException::class) fun testEncoding() {
val encoded = DeactivateCommand.Builder() // val encoded = DeactivateCommand.Builder()
.setUniqueId(37879809) // .setUniqueId(37879809)
.setSequenceNumber(5.toShort()) // .setSequenceNumber(5.toShort())
.setNonce(1229869870) // .setNonce(1229869870)
.build() // .build()
.encoded .encoded
Assert.assertArrayEquals(Hex.decodeHex("0242000114061C04494E532E001C"), encoded) Assert.assertArrayEquals(Hex.decodeHex("0242000114061C04494E532E001C"), encoded)

View file

@ -9,11 +9,11 @@ import org.junit.Test
class GetStatusCommandTest { class GetStatusCommandTest {
@Test @Throws(DecoderException::class) fun testGetDefaultStatusResponse() { @Test @Throws(DecoderException::class) fun testGetDefaultStatusResponse() {
val encoded = GetStatusCommand.Builder() // val encoded = GetStatusCommand.Builder()
.setUniqueId(37879810) // .setUniqueId(37879810)
.setSequenceNumber(15.toShort()) // .setSequenceNumber(15.toShort())
.setStatusResponseType(ResponseType.StatusResponseType.DEFAULT_STATUS_RESPONSE) // .setStatusResponseType(ResponseType.StatusResponseType.DEFAULT_STATUS_RESPONSE)
.build() // .build()
.encoded .encoded
Assert.assertArrayEquals(Hex.decodeHex("024200023C030E0100024C"), encoded) Assert.assertArrayEquals(Hex.decodeHex("024200023C030E0100024C"), encoded)

View file

@ -8,10 +8,10 @@ import org.junit.Test
class GetVersionCommandTest { class GetVersionCommandTest {
@Test @Throws(DecoderException::class) fun testEncoding() { @Test @Throws(DecoderException::class) fun testEncoding() {
val encoded = GetVersionCommand.Builder() // val encoded = GetVersionCommand.Builder()
.setSequenceNumber(0.toShort()) // .setSequenceNumber(0.toShort())
.setUniqueId(GetVersionCommand.DEFAULT_UNIQUE_ID) // .setUniqueId(GetVersionCommand.DEFAULT_UNIQUE_ID)
.build() // .build()
.encoded .encoded
Assert.assertArrayEquals(Hex.decodeHex("FFFFFFFF00060704FFFFFFFF82B2"), encoded) Assert.assertArrayEquals(Hex.decodeHex("FFFFFFFF00060704FFFFFFFF82B2"), encoded)

View file

@ -15,16 +15,36 @@ class ProgramAlertsCommandTest {
@Test @Throws(DecoderException::class) fun testExpirationAlerts() { @Test @Throws(DecoderException::class) fun testExpirationAlerts() {
val configurations: MutableList<AlertConfiguration> = ArrayList() val configurations: MutableList<AlertConfiguration> = ArrayList()
configurations.add(AlertConfiguration(AlertType.EXPIRATION, true, 420.toShort(), false, AlertTrigger.TimerTrigger(4305.toShort()), BeepType.FOUR_TIMES_BIP_BEEP, BeepRepetitionType.XXX3)) configurations.add(
configurations.add(AlertConfiguration(AlertType.EXPIRATION_IMMINENT, true, 0.toShort(), false, AlertTrigger.TimerTrigger(4725.toShort()), BeepType.FOUR_TIMES_BIP_BEEP, BeepRepetitionType.XXX4)) AlertConfiguration(
AlertType.EXPIRATION,
true,
420.toShort(),
false,
AlertTrigger.TimerTrigger(4305.toShort()),
BeepType.FOUR_TIMES_BIP_BEEP,
BeepRepetitionType.XXX3
)
)
configurations.add(
AlertConfiguration(
AlertType.EXPIRATION_IMMINENT,
true,
0.toShort(),
false,
AlertTrigger.TimerTrigger(4725.toShort()),
BeepType.FOUR_TIMES_BIP_BEEP,
BeepRepetitionType.XXX4
)
)
val encoded = ProgramAlertsCommand.Builder() // val encoded = ProgramAlertsCommand.Builder()
.setUniqueId(37879811) // .setUniqueId(37879811)
.setSequenceNumber(3.toShort()) // .setSequenceNumber(3.toShort())
.setMultiCommandFlag(true) // .setMultiCommandFlag(true)
.setNonce(1229869870) // .setNonce(1229869870)
.setAlertConfigurations(configurations) // .setAlertConfigurations(configurations)
.build() // .build()
.encoded .encoded
Assert.assertArrayEquals(Hex.decodeHex("024200038C121910494E532E79A410D1050228001275060280F5"), encoded) Assert.assertArrayEquals(Hex.decodeHex("024200038C121910494E532E79A410D1050228001275060280F5"), encoded)
@ -32,14 +52,24 @@ class ProgramAlertsCommandTest {
@Test @Throws(DecoderException::class) fun testLowReservoirAlert() { @Test @Throws(DecoderException::class) fun testLowReservoirAlert() {
val configurations: MutableList<AlertConfiguration> = ArrayList() val configurations: MutableList<AlertConfiguration> = ArrayList()
configurations.add(AlertConfiguration(AlertType.LOW_RESERVOIR, true, 0.toShort(), false, AlertTrigger.ReservoirVolumeTrigger(200.toShort()), BeepType.FOUR_TIMES_BIP_BEEP, BeepRepetitionType.XXX)) configurations.add(
AlertConfiguration(
AlertType.LOW_RESERVOIR,
true,
0.toShort(),
false,
AlertTrigger.ReservoirVolumeTrigger(200.toShort()),
BeepType.FOUR_TIMES_BIP_BEEP,
BeepRepetitionType.XXX
)
)
val encoded = ProgramAlertsCommand.Builder() // val encoded = ProgramAlertsCommand.Builder()
.setUniqueId(37879811) // .setUniqueId(37879811)
.setSequenceNumber(8.toShort()) // .setSequenceNumber(8.toShort())
.setNonce(1229869870) // .setNonce(1229869870)
.setAlertConfigurations(configurations) // .setAlertConfigurations(configurations)
.build() // .build()
.encoded .encoded
Assert.assertArrayEquals(Hex.decodeHex("02420003200C190A494E532E4C0000C801020149"), encoded) Assert.assertArrayEquals(Hex.decodeHex("02420003200C190A494E532E4C0000C801020149"), encoded)
@ -47,14 +77,24 @@ class ProgramAlertsCommandTest {
@Test @Throws(DecoderException::class) fun testUserExpirationAlert() { @Test @Throws(DecoderException::class) fun testUserExpirationAlert() {
val configurations: MutableList<AlertConfiguration> = ArrayList() val configurations: MutableList<AlertConfiguration> = ArrayList()
configurations.add(AlertConfiguration(AlertType.USER_SET_EXPIRATION, true, 0.toShort(), false, AlertTrigger.TimerTrigger(4079.toShort()), BeepType.FOUR_TIMES_BIP_BEEP, BeepRepetitionType.XXX2)) configurations.add(
AlertConfiguration(
AlertType.USER_SET_EXPIRATION,
true,
0.toShort(),
false,
AlertTrigger.TimerTrigger(4079.toShort()),
BeepType.FOUR_TIMES_BIP_BEEP,
BeepRepetitionType.XXX2
)
)
val encoded = ProgramAlertsCommand.Builder() // val encoded = ProgramAlertsCommand.Builder()
.setUniqueId(37879811) // .setUniqueId(37879811)
.setSequenceNumber(15.toShort()) // .setSequenceNumber(15.toShort())
.setNonce(1229869870) // .setNonce(1229869870)
.setAlertConfigurations(configurations) // .setAlertConfigurations(configurations)
.build() // .build()
.encoded .encoded
Assert.assertArrayEquals(Hex.decodeHex("024200033C0C190A494E532E38000FEF030203E2"), encoded) Assert.assertArrayEquals(Hex.decodeHex("024200033C0C190A494E532E38000FEF030203E2"), encoded)
@ -62,15 +102,25 @@ class ProgramAlertsCommandTest {
@Test @Throws(DecoderException::class) fun testLumpOfCoalAlert() { @Test @Throws(DecoderException::class) fun testLumpOfCoalAlert() {
val configurations: MutableList<AlertConfiguration> = ArrayList() val configurations: MutableList<AlertConfiguration> = ArrayList()
configurations.add(AlertConfiguration(AlertType.EXPIRATION, true, 55.toShort(), false, AlertTrigger.TimerTrigger(5.toShort()), BeepType.FOUR_TIMES_BIP_BEEP, BeepRepetitionType.XXX5)) configurations.add(
AlertConfiguration(
AlertType.EXPIRATION,
true,
55.toShort(),
false,
AlertTrigger.TimerTrigger(5.toShort()),
BeepType.FOUR_TIMES_BIP_BEEP,
BeepRepetitionType.XXX5
)
)
val encoded = ProgramAlertsCommand.Builder() // val encoded = ProgramAlertsCommand.Builder()
.setUniqueId(37879811) // .setUniqueId(37879811)
.setSequenceNumber(10.toShort()) // .setSequenceNumber(10.toShort())
.setMultiCommandFlag(false) // .setMultiCommandFlag(false)
.setNonce(1229869870) // .setNonce(1229869870)
.setAlertConfigurations(configurations) // .setAlertConfigurations(configurations)
.build() // .build()
.encoded .encoded
Assert.assertArrayEquals(Hex.decodeHex("02420003280C190A494E532E7837000508020356"), encoded) Assert.assertArrayEquals(Hex.decodeHex("02420003280C190A494E532E7837000508020356"), encoded)

View file

@ -15,18 +15,45 @@ class ProgramBasalCommandTest {
BasalProgram.Segment(0.toShort(), 48.toShort(), 300) BasalProgram.Segment(0.toShort(), 48.toShort(), 300)
) )
val basalProgram = BasalProgram(segments) val basalProgram = BasalProgram(segments)
val date = Date(2021, 1, 17, 14, 47, 43) val date = Date(121, 1, 17, 14, 47, 43)
val encoded = ProgramBasalCommand.Builder() // val encoded = ProgramBasalCommand.Builder()
.setUniqueId(37879809) // .setUniqueId(37879809)
.setNonce(1229869870) // .setNonce(1229869870)
.setSequenceNumber(10.toShort()) // .setSequenceNumber(10.toShort())
.setBasalProgram(basalProgram) // .setBasalProgram(basalProgram)
.setCurrentTime(date) // .setCurrentTime(date)
.setProgramReminder(ProgramReminder(false, true, 0.toByte())) // .setProgramReminder(ProgramReminder(false, true, 0.toByte()))
.build() // .build()
.encoded .encoded
Assert.assertArrayEquals(Hex.decodeHex("0242000128241A12494E532E0005E81D1708000CF01EF01EF01E130E40001593004C4B403840005B8D80827C"), encoded) Assert.assertArrayEquals(
Hex.decodeHex("0242000128241A12494E532E0005E81D1708000CF01EF01EF01E130E40001593004C4B403840005B8D80827C"),
encoded
)
} }
@Test @Throws(DecoderException::class) fun testProgramBasalCommandWithExtraAlternateSegmentPulse() {
val segments = listOf(
BasalProgram.Segment(0.toShort(), 48.toShort(), 5)
)
val basalProgram = BasalProgram(segments)
val date = Date(121, 0, 30, 23, 21, 46)
val encoded = ProgramBasalCommand.Builder()
.setUniqueId(4241)
.setNonce(1229869870)
.setSequenceNumber(12.toShort())
.setBasalProgram(basalProgram)
.setCurrentTime(date)
.setProgramReminder(ProgramReminder(atStart = false, atEnd = false, atInterval = 0.toByte()))
.build()
.encoded
Assert.assertArrayEquals(
Hex.decodeHex("0000109130241a12494e532e0000c52e0f700000f800f800f800130e0000000707fcad8000f015752a00033b"),
encoded
)
}
} }

View file

@ -10,14 +10,14 @@ import org.junit.Test
class ProgramBeepsCommandTest { class ProgramBeepsCommandTest {
@Test @Throws(DecoderException::class) fun testPlayTestBeep() { @Test @Throws(DecoderException::class) fun testPlayTestBeep() {
val encoded = ProgramBeepsCommand.Builder() // val encoded = ProgramBeepsCommand.Builder()
.setUniqueId(37879810) // .setUniqueId(37879810)
.setSequenceNumber(11.toShort()) // .setSequenceNumber(11.toShort())
.setImmediateBeepType(BeepType.FOUR_TIMES_BIP_BEEP) // .setImmediateBeepType(BeepType.FOUR_TIMES_BIP_BEEP)
.setBasalReminder(ProgramReminder(false, false, 0.toByte())) // .setBasalReminder(ProgramReminder(false, false, 0.toByte()))
.setTempBasalReminder(ProgramReminder(false, false, 0.toByte())) // .setTempBasalReminder(ProgramReminder(false, false, 0.toByte()))
.setBolusReminder(ProgramReminder(false, false, 0.toByte())) // .setBolusReminder(ProgramReminder(false, false, 0.toByte()))
.build() // .build()
.encoded .encoded
Assert.assertArrayEquals(Hex.decodeHex("024200022C061E0402000000800F"), encoded) Assert.assertArrayEquals(Hex.decodeHex("024200022C061E0402000000800F"), encoded)

View file

@ -9,16 +9,19 @@ import org.junit.Test
class ProgramBolusCommandTest { class ProgramBolusCommandTest {
@Test @Throws(DecoderException::class) fun testProgramBolusCommand() { @Test @Throws(DecoderException::class) fun testProgramBolusCommand() {
val encoded = ProgramBolusCommand.Builder() // val encoded = ProgramBolusCommand.Builder()
.setNumberOfUnits(5.0) // .setNumberOfUnits(5.0)
.setProgramReminder(ProgramReminder(false, true, 0.toByte())) // .setProgramReminder(ProgramReminder(false, true, 0.toByte()))
.setDelayBetweenPulsesInEighthSeconds(16.toByte()) // .setDelayBetweenPulsesInEighthSeconds(16.toByte())
.setUniqueId(37879809) // .setUniqueId(37879809)
.setSequenceNumber(14.toShort()) // .setSequenceNumber(14.toShort())
.setNonce(1229869870) // .setNonce(1229869870)
.build() // .build()
.encoded .encoded
Assert.assertArrayEquals(Hex.decodeHex("02420001381F1A0E494E532E02010F01064000640064170D4003E800030D4000000000000080F6"), encoded) Assert.assertArrayEquals(
Hex.decodeHex("02420001381F1A0E494E532E02010F01064000640064170D4003E800030D4000000000000080F6"),
encoded
)
} }
} }

View file

@ -9,28 +9,34 @@ import org.junit.Test
class ProgramTempBasalCommandTest { class ProgramTempBasalCommandTest {
@Test @Throws(DecoderException::class) fun testExtraAlternateSegmentPulseTempBasal() { @Test @Throws(DecoderException::class) fun testExtraAlternateSegmentPulseTempBasal() {
val command = ProgramTempBasalCommand.Builder() // val command = ProgramTempBasalCommand.Builder()
.setUniqueId(37879809) // .setUniqueId(37879809)
.setNonce(1229869870) // .setNonce(1229869870)
.setSequenceNumber(15.toShort()) // .setSequenceNumber(15.toShort())
.setRateInUnitsPerHour(5.05) // .setRateInUnitsPerHour(5.05)
.setDurationInMinutes(60.toShort()) // .setDurationInMinutes(60.toShort())
.setProgramReminder(ProgramReminder(false, true, 0.toByte())) // .setProgramReminder(ProgramReminder(false, true, 0.toByte()))
.build() .build()
Assert.assertArrayEquals(Hex.decodeHex("024200013C201A0E494E532E01011102384000321832160E400003F20036634403F20036634482A6"), command.encoded) Assert.assertArrayEquals(
Hex.decodeHex("024200013C201A0E494E532E01011102384000321832160E400003F20036634403F20036634482A6"),
command.encoded
)
} }
@Test @Throws(DecoderException::class) fun testZeroTempBasal() { @Test @Throws(DecoderException::class) fun testZeroTempBasal() {
val command = ProgramTempBasalCommand.Builder() // val command = ProgramTempBasalCommand.Builder()
.setUniqueId(37879809) // .setUniqueId(37879809)
.setNonce(1229869870) // .setNonce(1229869870)
.setSequenceNumber(7.toShort()) // .setSequenceNumber(7.toShort())
.setRateInUnitsPerHour(0.0) // .setRateInUnitsPerHour(0.0)
.setDurationInMinutes(300.toShort()) // .setDurationInMinutes(300.toShort())
.setProgramReminder(ProgramReminder(true, true, 0.toByte())) // .setProgramReminder(ProgramReminder(true, true, 0.toByte()))
.build() .build()
Assert.assertArrayEquals(Hex.decodeHex("024200011C201A0E494E532E0100820A384000009000160EC000000A6B49D200000AEB49D20001DE"), command.encoded) Assert.assertArrayEquals(
Hex.decodeHex("024200011C201A0E494E532E0100820A384000009000160EC000000A6B49D200000AEB49D20001DE"),
command.encoded
)
} }
} }

View file

@ -9,13 +9,13 @@ import java.util.*
class SetUniqueIdCommandTest { class SetUniqueIdCommandTest {
@Test @Throws(DecoderException::class) fun testEncoding() { @Test @Throws(DecoderException::class) fun testEncoding() {
val encoded = SetUniqueIdCommand.Builder() // val encoded = SetUniqueIdCommand.Builder()
.setUniqueId(37879811) // .setUniqueId(37879811)
.setSequenceNumber(6.toShort()) // .setSequenceNumber(6.toShort())
.setLotNumber(135556289) // .setLotNumber(135556289)
.setPodSequenceNumber(681767) // .setPodSequenceNumber(681767)
.setInitializationTime(Date(2021, 1, 10, 14, 41)) // .setInitializationTime(Date(2021, 1, 10, 14, 41))
.build() // .build()
.encoded .encoded
Assert.assertArrayEquals(Hex.decodeHex("FFFFFFFF18150313024200031404020A150E2908146CC1000A67278344"), encoded) Assert.assertArrayEquals(Hex.decodeHex("FFFFFFFF18150313024200031404020A150E2908146CC1000A67278344"), encoded)

View file

@ -10,12 +10,12 @@ import java.util.*
class SilenceAlertsCommandTest { class SilenceAlertsCommandTest {
@Test @Throws(DecoderException::class) fun testSilenceLowReservoirAlert() { @Test @Throws(DecoderException::class) fun testSilenceLowReservoirAlert() {
val encoded = SilenceAlertsCommand.Builder() // val encoded = SilenceAlertsCommand.Builder()
.setUniqueId(37879811) // .setUniqueId(37879811)
.setSequenceNumber(1.toShort()) // .setSequenceNumber(1.toShort())
.setNonce(1229869870) // .setNonce(1229869870)
.setAlertTypes(EnumSet.of(AlertType.LOW_RESERVOIR)) // .setAlertTypes(EnumSet.of(AlertType.LOW_RESERVOIR))
.build() // .build()
.encoded .encoded
Assert.assertArrayEquals(Hex.decodeHex("0242000304071105494E532E1081CE"), encoded) Assert.assertArrayEquals(Hex.decodeHex("0242000304071105494E532E1081CE"), encoded)

View file

@ -9,26 +9,26 @@ import org.junit.Test
class StopDeliveryCommandTest { class StopDeliveryCommandTest {
@Test @Throws(DecoderException::class) fun testStopTempBasal() { @Test @Throws(DecoderException::class) fun testStopTempBasal() {
val encoded = StopDeliveryCommand.Builder() // val encoded = StopDeliveryCommand.Builder()
.setUniqueId(37879811) // .setUniqueId(37879811)
.setSequenceNumber(0.toShort()) // .setSequenceNumber(0.toShort())
.setNonce(1229869870) // .setNonce(1229869870)
.setDeliveryType(StopDeliveryCommand.DeliveryType.TEMP_BASAL) // .setDeliveryType(StopDeliveryCommand.DeliveryType.TEMP_BASAL)
.setBeepType(BeepType.LONG_SINGLE_BEEP) // .setBeepType(BeepType.LONG_SINGLE_BEEP)
.build() // .build()
.encoded .encoded
Assert.assertArrayEquals(Hex.decodeHex("0242000300071F05494E532E6201B1"), encoded) Assert.assertArrayEquals(Hex.decodeHex("0242000300071F05494E532E6201B1"), encoded)
} }
@Test @Throws(DecoderException::class) fun testSuspendDelivery() { @Test @Throws(DecoderException::class) fun testSuspendDelivery() {
val encoded = StopDeliveryCommand.Builder() // val encoded = StopDeliveryCommand.Builder()
.setUniqueId(37879811) // .setUniqueId(37879811)
.setSequenceNumber(2.toShort()) // .setSequenceNumber(2.toShort())
.setNonce(1229869870) // .setNonce(1229869870)
.setDeliveryType(StopDeliveryCommand.DeliveryType.ALL) // .setDeliveryType(StopDeliveryCommand.DeliveryType.ALL)
.setBeepType(BeepType.SILENT) // .setBeepType(BeepType.SILENT)
.build() // .build()
.encoded .encoded
Assert.assertArrayEquals(Hex.decodeHex("0242000308071F05494E532E078287"), encoded) Assert.assertArrayEquals(Hex.decodeHex("0242000308071F05494E532E078287"), encoded)