Patch activation flow

This commit is contained in:
jbr7rr 2023-04-11 15:43:29 +02:00
parent af6445a3dc
commit fff833dae3
26 changed files with 720 additions and 116 deletions

View file

@ -8,6 +8,7 @@ import android.os.IBinder
import dagger.android.HasAndroidInjector
import info.nightscout.core.ui.toast.ToastUtils
import info.nightscout.core.utils.fabric.FabricPrivacy
import info.nightscout.interfaces.notifications.Notification
import info.nightscout.interfaces.plugin.PluginDescription
import info.nightscout.interfaces.plugin.PluginType
import info.nightscout.interfaces.profile.Profile
@ -33,6 +34,7 @@ import info.nightscout.rx.AapsSchedulers
import info.nightscout.rx.bus.RxBus
import info.nightscout.rx.events.EventAppExit
import info.nightscout.rx.events.EventAppInitialized
import info.nightscout.rx.events.EventDismissNotification
import info.nightscout.rx.events.EventOverviewBolusProgress
import info.nightscout.rx.events.EventPreferenceChange
import info.nightscout.rx.logging.AAPSLogger
@ -170,11 +172,33 @@ class MedtrumPlugin @Inject constructor(
}
override fun setNewBasalProfile(profile: Profile): PumpEnactResult {
return PumpEnactResult(injector).success(true).enacted(true) // TODO
// New profile will be set when patch is activated
if (!isInitialized()) return PumpEnactResult(injector).success(true).enacted(true)
return if (medtrumService?.updateBasalsInPump(profile) == true) {
rxBus.send(EventDismissNotification(Notification.FAILED_UPDATE_PROFILE))
uiInteraction.addNotificationValidFor(Notification.PROFILE_SET_OK, rh.gs(info.nightscout.core.ui.R.string.profile_set_ok), Notification.INFO, 60)
PumpEnactResult(injector).success(true).enacted(true)
} else {
uiInteraction.addNotification(Notification.FAILED_UPDATE_PROFILE, rh.gs(info.nightscout.core.ui.R.string.failed_update_basal_profile), Notification.URGENT)
PumpEnactResult(injector)
}
}
override fun isThisProfileSet(profile: Profile): Boolean {
return false // TODO
if (!isInitialized()) return true
var result = false
val profileBytes = medtrumPump.buildMedtrumProfileArray(profile)
if (profileBytes?.size == medtrumPump.actualBasalProfile.size) {
result = true
for (i in profileBytes.indices) {
if (profileBytes[i] != medtrumPump.actualBasalProfile[i]) {
result = false
break
}
}
}
return result
}
override fun lastDataTime(): Long {
@ -204,12 +228,14 @@ class MedtrumPlugin @Inject constructor(
override fun setTempBasalPercent(percent: Int, durationInMinutes: Int, profile: Profile, enforceNew: Boolean, tbrType: PumpSync.TemporaryBasalType): PumpEnactResult {
aapsLogger.info(LTag.PUMP, "setTempBasalPercent - percent: $percent, durationInMinutes: $durationInMinutes, enforceNew: $enforceNew")
return PumpEnactResult(injector) // TODO
return PumpEnactResult(injector).success(false).enacted(false)
.comment("Medtrum driver does not support percentage temp basals")
}
override fun setExtendedBolus(insulin: Double, durationInMinutes: Int): PumpEnactResult {
aapsLogger.info(LTag.PUMP, "setExtendedBolus - insulin: $insulin, durationInMinutes: $durationInMinutes")
return PumpEnactResult(injector) // TODO
return PumpEnactResult(injector).success(false).enacted(false)
.comment("Medtrum driver does not support extended boluses")
}
override fun cancelTempBasal(enforceNew: Boolean): PumpEnactResult {

View file

@ -1,5 +1,7 @@
package info.nightscout.pump.medtrum
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import info.nightscout.interfaces.Constants
import info.nightscout.interfaces.profile.Instantiator
import info.nightscout.interfaces.profile.Profile
@ -14,6 +16,8 @@ import info.nightscout.rx.logging.LTag
import info.nightscout.shared.sharedPreferences.SP
import info.nightscout.shared.utils.DateUtil
import info.nightscout.shared.utils.T
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import javax.inject.Inject
import javax.inject.Singleton
import kotlin.math.round
@ -26,20 +30,29 @@ class MedtrumPump @Inject constructor(
private val instantiator: Instantiator
) {
enum class PatchActivationState(val state: Int) {
NONE(0),
IDLE(1),
ACTIVATING(2),
ACTIVATED(3),
DEACTIVATING(4),
DEACTIVATED(5),
ERROR(6)
// Pump state flow
// TODO We might want to save this in SP, or at least get activated state from SP
private val _pumpState = MutableStateFlow(MedtrumPumpState.NONE)
val pumpStateFlow: StateFlow<MedtrumPumpState> = _pumpState
var pumpState: MedtrumPumpState
get() = _pumpState.value
set(value) {
_pumpState.value = value
}
// Pump state and parameters
var pumpState = MedtrumPumpState.NONE // TODO save in SP
var patchActivationState = PatchActivationState.NONE // TODO save in SP
// Prime progress as state flow
private val _primeProgress = MutableStateFlow(0)
val primeProgressFlow: StateFlow<Int> = _primeProgress
var primeProgress: Int
get() = _primeProgress.value
set(value) {
_primeProgress.value = value
}
// TODO: Save this in SP? This might be a bit tricky as we only know what we have set, not what the pump has set but the pump should not change it, addtionally we should track the active basal profile in pump e.g. Basal patern A, B etc
var actualBasalProfile = byteArrayOf(0)
var patchId = 0L
var lastKnownSequenceNumber = 0
var lastTimeReceivedFromPump = 0L // Time in seconds!
@ -48,7 +61,6 @@ class MedtrumPump @Inject constructor(
var patchAge = 0L // Time in seconds!
var reservoir = 0.0
var primeProgress = 0
var batteryVoltage_A = 0.0
var batteryVoltage_B = 0.0
@ -67,7 +79,6 @@ class MedtrumPump @Inject constructor(
var lastStopSequence = 0
var lastStopPatchId = 0
// TODO set these setting on init
// User settings (desired values, to be set on pump)
var desiredPatchExpiration = false
@ -75,7 +86,6 @@ class MedtrumPump @Inject constructor(
var desiredHourlyMaxInsulin: Int = 40
var desiredDailyMaxInsulin: Int = 180
fun buildMedtrumProfileArray(nsProfile: Profile): ByteArray? {
val list = nsProfile.getBasalValues()
var basals = byteArrayOf()
@ -87,7 +97,7 @@ class MedtrumPump @Inject constructor(
return null
}
basals += ((rate shl 12) + time).toByteArray(3)
aapsLogger.debug(LTag.PUMP, "buildMedtrumProfileArray: value: ${item.value} time: ${item.timeAsSeconds}")
aapsLogger.debug(LTag.PUMP, "buildMedtrumProfileArray: value: ${item.value} time: ${item.timeAsSeconds}, converted: $rate, $time")
}
return (list.size).toByteArray(1) + basals
}

View file

@ -8,9 +8,8 @@ enum class PatchStep {
DISCARDED_FROM_ALARM,
PREPARE_PATCH,
PRIME,
ATTACH_INSERT_NEEDLE,
BASAL_SCHEDULE,
CHECK_CONNECTION,
ATTACH_PATCH,
ACTIVATE,
CANCEL,
COMPLETE,
BACK_TO_HOME,

View file

@ -3,7 +3,7 @@ package info.nightscout.pump.medtrum.comm.enums
enum class MedtrumPumpState(val state: Byte) {
NONE(0),
IDLE(1),
FILL(2),
FILLED(2),
PRIMING(3),
PRIMED(4),
EJECTING(5),

View file

@ -57,14 +57,23 @@ class ActivatePacket(injector: HasAndroidInjector, private val basalProfile: Byt
* bytes 15 - end // Basal profile > see MedtrumPump
*/
val autoSuspendEnable: Byte = 0
val autoSuspendTime: Byte = 12 // Not sure why, but pump needs this in order to activate
val patchExpiration: Byte = medtrumPump.desiredPatchExpiration.toByte()
val alarmSetting: Byte = medtrumPump.desiredAlarmSetting
val lowSuspend: Byte = 0
val predictiveLowSuspend: Byte = 0
val predictiveLowSuspendRange: Byte = 30 // Not sure why, but pump needs this in order to activate
val hourlyMaxInsulin: Int = round(medtrumPump.desiredHourlyMaxInsulin / 0.05).toInt()
val dailyMaxInsulin: Int = round(medtrumPump.desiredDailyMaxInsulin / 0.05).toInt()
val currentTDD: Double = tddCalculator.calculateToday()?.totalAmount?.div(0.05) ?: 0.0
return byteArrayOf(opCode) + 0.toByteArray(2) + patchExpiration + alarmSetting + 0.toByteArray(3) + hourlyMaxInsulin.toByteArray(2) + dailyMaxInsulin.toByteArray(2) + currentTDD.toInt()
.toByteArray(2) + 1.toByte() + basalProfile
return byteArrayOf(opCode) + autoSuspendEnable + autoSuspendTime + patchExpiration + alarmSetting + lowSuspend + predictiveLowSuspend + predictiveLowSuspendRange + hourlyMaxInsulin.toByteArray(
2
) + dailyMaxInsulin.toByteArray(2) + currentTDD.toInt().toByteArray(2) + 1.toByte() + basalProfile
}
override fun handleResponse(data: ByteArray): Boolean {
@ -82,7 +91,9 @@ class ActivatePacket(injector: HasAndroidInjector, private val basalProfile: Byt
medtrumPump.patchId = patchId
medtrumPump.lastTimeReceivedFromPump = time
// TODO: Handle basal here, and report to AAPS directly
// Update the actual basal profile
medtrumPump.actualBasalProfile = basalProfile
// TODO: Handle history entry
medtrumPump.handleBasalStatusUpdate(basalType, basalValue, basalSequence, basalPatchId, basalStartTime, time)
}

View file

@ -30,13 +30,13 @@ open class MedtrumPacket(protected var injector: HasAndroidInjector) {
}
open fun getRequest(): ByteArray {
aapsLogger.debug(LTag.PUMPCOMM, "Get REQUEST TEST")
return byteArrayOf(opCode)
}
/** handles a response from the Medtrum pump, returns true if command was successfull, returns false if command failed or waiting for response */
open fun handleResponse(data: ByteArray): Boolean {
if (expectedMinRespLength > data.size) {
// Check for broken packets
if (RESP_RESULT_END > data.size) {
failed = true
aapsLogger.debug(LTag.PUMPCOMM, "handleResponse: Unexpected response length, expected: $expectedMinRespLength got: ${data.size}")
return false
@ -48,11 +48,17 @@ open class MedtrumPacket(protected var injector: HasAndroidInjector) {
return when {
incomingOpCode != opCode -> {
failed = true
aapsLogger.debug(LTag.PUMPCOMM, "handleResponse: Unexpected command, expected: $opCode got: $incomingOpCode")
aapsLogger.error(LTag.PUMPCOMM, "handleResponse: Unexpected command, expected: $opCode got: $incomingOpCode")
false
}
responseCode == 0 -> {
// Check if length is what is expected from this type of packet
if (expectedMinRespLength > data.size) {
failed = true
aapsLogger.debug(LTag.PUMPCOMM, "handleResponse: Unexpected response length, expected: $expectedMinRespLength got: ${data.size}")
return false
}
aapsLogger.debug(LTag.PUMPCOMM, "handleResponse: Happy command: $opCode response: $responseCode")
true
}
@ -65,7 +71,7 @@ open class MedtrumPacket(protected var injector: HasAndroidInjector) {
else -> {
failed = true
aapsLogger.debug(LTag.PUMPCOMM, "handleResponse: Error in command: $opCode response: $responseCode")
aapsLogger.warn(LTag.PUMPCOMM, "handleResponse: Error in command: $opCode response: $responseCode")
false
}
}

View file

@ -36,6 +36,7 @@ class NotificationPacket(val injector: HasAndroidInjector) {
@Inject lateinit var medtrumPump: MedtrumPump
companion object {
private const val NOTIF_STATE_START = 0
private const val NOTIF_STATE_END = NOTIF_STATE_START + 1
@ -96,13 +97,13 @@ class NotificationPacket(val injector: HasAndroidInjector) {
aapsLogger.debug(LTag.PUMPCOMM, "Normal bolus notification received")
var bolusData = data.copyOfRange(offset, offset + 1).toInt()
var bolusType = bolusData and 0x7F
var bolusCompleted = (bolusData shr 7) and 0x01
var bolusCompleted = (bolusData shr 7) and 0x01 // TODO: Check for other flags here :)
var bolusDelivered = data.copyOfRange(offset + 1, offset + 3).toInt() / 0.05
// TODO Sync bolus flow:
// If bolus is known add status
// If bolus is not known start read bolus
// When bolus is completed, remove bolus from medtrumPump
aapsLogger.debug(LTag.PUMPCOMM, "Bolus type: $bolusType, bolus completed: $bolusCompleted, bolus delivered: $bolusDelivered")
aapsLogger.debug(LTag.PUMPCOMM, "Bolus type: $bolusType, bolusData: $bolusData bolus completed: $bolusCompleted, bolus delivered: $bolusDelivered")
offset += 3
}

View file

@ -3,18 +3,56 @@ package info.nightscout.pump.medtrum.comm.packets
import dagger.android.HasAndroidInjector
import info.nightscout.pump.medtrum.MedtrumPump
import info.nightscout.pump.medtrum.comm.enums.CommandType.SET_BASAL_PROFILE
import info.nightscout.pump.medtrum.extension.toInt
import info.nightscout.pump.medtrum.extension.toLong
import info.nightscout.pump.medtrum.util.MedtrumTimeUtil
import javax.inject.Inject
class SetBasalProfilePacket(injector: HasAndroidInjector, private val basalProfile: ByteArray) : MedtrumPacket(injector) {
@Inject lateinit var medtrumPump: MedtrumPump
companion object {
private const val RESP_BASAL_TYPE_START = 6
private const val RESP_BASAL_TYPE_END = RESP_BASAL_TYPE_START + 1
private const val RESP_BASAL_VALUE_START = 7
private const val RESP_BASAL_VALUE_END = RESP_BASAL_VALUE_START + 2
private const val RESP_BASAL_SEQUENCE_START = 9
private const val RESP_BASAL_SEQUENCE_END = RESP_BASAL_SEQUENCE_START + 2
private const val RESP_BASAL_PATCH_ID_START = 11
private const val RESP_BASAL_PATCH_ID_END = RESP_BASAL_PATCH_ID_START + 2
private const val RESP_BASAL_START_TIME_START = 13
private const val RESP_BASAL_START_TIME_END = RESP_BASAL_START_TIME_START + 4
}
init {
opCode = SET_BASAL_PROFILE.code
expectedMinRespLength = RESP_BASAL_START_TIME_END
}
override fun getRequest(): ByteArray {
val basalType: Byte = 1 // Fixed to normal basal
return byteArrayOf(opCode) + basalType + basalProfile
}
override fun handleResponse(data: ByteArray): Boolean {
val success = super.handleResponse(data)
if (success) {
val medtrumTimeUtil = MedtrumTimeUtil()
val basalType = data.copyOfRange(RESP_BASAL_TYPE_START, RESP_BASAL_TYPE_END).toInt()
val basalValue = data.copyOfRange(RESP_BASAL_VALUE_START, RESP_BASAL_VALUE_END).toInt() * 0.05
val basalSequence = data.copyOfRange(RESP_BASAL_SEQUENCE_START, RESP_BASAL_SEQUENCE_END).toInt()
val basalPatchId = data.copyOfRange(RESP_BASAL_PATCH_ID_START, RESP_BASAL_PATCH_ID_END).toInt()
val basalStartTime = medtrumTimeUtil.convertPumpTimeToSystemTimeSeconds(data.copyOfRange(RESP_BASAL_START_TIME_START, RESP_BASAL_START_TIME_END).toLong())
// Update the actual basal profile
medtrumPump.actualBasalProfile = basalProfile
// TODO: Do we need to let AAPS know? Maybe depends on where we cancel TBR if we need to
// TODO: Handle history entry
medtrumPump.handleBasalStatusUpdate(basalType, basalValue, basalSequence, basalPatchId, basalStartTime)
}
return success
}
}

View file

@ -46,8 +46,7 @@ class SynchronizePacket(injector: HasAndroidInjector) : MedtrumPacket(injector)
aapsLogger.debug(LTag.PUMPCOMM, "SynchronizePacket: fieldMask: $fieldMask")
}
// Remove bolus fields from fieldMask if fields are present
// TODO: Test if this workaround is needed (hence the warning log)
// Remove bolus fields from fieldMask if fields are present (we sync bolus trough other commands)
if (fieldMask and MASK_SUSPEND != 0) {
offset += 4 // If field is present, skip 4 bytes
}

View file

@ -7,6 +7,8 @@ import dagger.Module
import dagger.Provides
import dagger.android.ContributesAndroidInjector
import dagger.multibindings.IntoMap
import info.nightscout.androidaps.plugins.pump.eopatch.ui.MedtrumActivateFragment
import info.nightscout.androidaps.plugins.pump.eopatch.ui.MedtrumAttachPatchFragment
import info.nightscout.androidaps.plugins.pump.eopatch.ui.MedtrumPreparePatchFragment
import info.nightscout.androidaps.plugins.pump.eopatch.ui.MedtrumPrimeFragment
import info.nightscout.pump.medtrum.services.MedtrumService
@ -56,6 +58,14 @@ abstract class MedtrumModule {
@ContributesAndroidInjector
internal abstract fun contributesPrimeFragment(): MedtrumPrimeFragment
@FragmentScope
@ContributesAndroidInjector
internal abstract fun contributesAttachPatchFragment(): MedtrumAttachPatchFragment
@FragmentScope
@ContributesAndroidInjector
internal abstract fun contributesActivateFragment(): MedtrumActivateFragment
// ACTIVITIES
@ContributesAndroidInjector
abstract fun contributesMedtrumActivity(): MedtrumActivity
@ -63,4 +73,5 @@ abstract class MedtrumModule {
// SERVICE
@ContributesAndroidInjector
abstract fun contributesMedtrumService(): MedtrumService
}

View file

@ -34,6 +34,7 @@ import info.nightscout.rx.logging.LTag
import info.nightscout.shared.interfaces.ResourceHelper
import info.nightscout.shared.sharedPreferences.SP
import info.nightscout.shared.utils.DateUtil
import info.nightscout.shared.utils.T
import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.kotlin.plusAssign
import java.util.*
@ -75,29 +76,6 @@ class MedtrumService : DaggerService(), BLECommCallback {
// TODO: Stuff like this in a settings class?
private var mLastDeviceTime: Long = 0
companion object {
private val MASK_SUSPEND = 0x01
private val MASK_NORMAL_BOLUS = 0x02
private val MASK_EXTENDED_BOLUS = 0x04
private val MASK_BASAL = 0x08
private val MASK_SETUP = 0x10
private val MASK_RESERVOIR = 0x20
private val MASK_LIFE_TIME = 0x40
private val MASK_BATTERY = 0x80
private val MASK_STORAGE = 0x100
private val MASK_ALARM = 0x200
private val MASK_START_TIME = 0x400
private val MASK_UNKNOWN_1 = 0x800
private val MASK_UNUSED_CGM = 0x1000
private val MASK_UNUSED_COMMAND_CONFIRM = 0x2000
private val MASK_UNUSED_AUTO_STATUS = 0x4000
private val MASK_UNUSED_LEGACY = 0x8000
}
override fun onCreate() {
super.onCreate()
bleComm.setCallback(this)
@ -135,6 +113,18 @@ class MedtrumService : DaggerService(), BLECommCallback {
}
}
fun startPrime(): Boolean {
val packet = PrimePacket(injector)
return sendPacketAndGetResponse(packet)
}
fun startActivate(): Boolean {
// TODO not sure this is the correct way lol, We might need to tell AAPS which profile is active
val profile = profileFunction.getProfile()?.let { medtrumPump.buildMedtrumProfileArray(it) }
val packet = profile?.let { ActivatePacket(injector, it) }
return packet?.let { sendPacketAndGetResponse(it) } == true
}
fun stopConnecting() {
// TODO proper way for this might need send commands etc
bleComm.stopConnecting()
@ -177,9 +167,8 @@ class MedtrumService : DaggerService(), BLECommCallback {
}
fun updateBasalsInPump(profile: Profile): Boolean {
if (!isConnected) return false
// TODO
return false
val packet = medtrumPump.buildMedtrumProfileArray(profile)?.let { SetBasalProfilePacket(injector, it) }
return packet?.let { sendPacketAndGetResponse(it) } == true
}
fun changePump() {
@ -189,9 +178,9 @@ class MedtrumService : DaggerService(), BLECommCallback {
} catch (e: NumberFormatException) {
aapsLogger.debug(LTag.PUMP, "changePump: Invalid input!")
}
// TODO: What do we do with active patch here?
// TODO: What do we do with active patch here? Getting status should be enough?
when (currentState) {
// is IdleState -> connect("changePump")
is IdleState -> connect("changePump")
// is ReadyState -> disconnect("changePump")
else -> null // TODO: What to do here? Abort stuff?
}
@ -244,6 +233,19 @@ class MedtrumService : DaggerService(), BLECommCallback {
currentState.onEnter()
}
private fun sendPacketAndGetResponse(packet: MedtrumPacket): Boolean {
var result = false
if (currentState is ReadyState) {
toState(CommandState())
mPacket = packet
mPacket?.getRequest()?.let { bleComm.sendMessage(it) }
result = currentState.waitForResponse()
} else {
aapsLogger.error(LTag.PUMPCOMM, "Send packet attempt when in non Ready state")
}
return result
}
// State class, Can we move this to different file?
private abstract inner class State {
@ -265,11 +267,18 @@ class MedtrumService : DaggerService(), BLECommCallback {
// TODO: Check flow for this
toState(IdleState())
}
open fun waitForResponse(): Boolean {
return false
}
}
private inner class IdleState : State() {
override fun onEnter() {}
override fun onEnter() {
aapsLogger.debug(LTag.PUMPCOMM, "Medtrum Service reached IdleState")
connect("IdleState onEnter")
}
override fun onConnected() {
super.onConnected()
@ -278,10 +287,11 @@ class MedtrumService : DaggerService(), BLECommCallback {
override fun onDisconnected() {
super.onDisconnected()
// TODO replace this by proper connecting state where we can retry
connect("IdleState onDisconnected")
}
}
// State for connect flow, could be replaced by commandState and steps in connect()
private inner class AuthState : State() {
override fun onEnter() {
@ -306,6 +316,7 @@ class MedtrumService : DaggerService(), BLECommCallback {
}
}
// State for connect flow, could be replaced by commandState and steps in connect()
private inner class GetDeviceTypeState : State() {
override fun onEnter() {
@ -330,6 +341,7 @@ class MedtrumService : DaggerService(), BLECommCallback {
}
}
// State for connect flow, could be replaced by commandState and steps in connect()
private inner class GetTimeState : State() {
override fun onEnter() {
@ -361,6 +373,7 @@ class MedtrumService : DaggerService(), BLECommCallback {
}
}
// State for connect flow, could be replaced by commandState and steps in connect()
private inner class SetTimeState : State() {
override fun onEnter() {
@ -381,6 +394,7 @@ class MedtrumService : DaggerService(), BLECommCallback {
}
}
// State for connect flow, could be replaced by commandState and steps in connect()
private inner class SetTimeZoneState : State() {
override fun onEnter() {
@ -401,6 +415,7 @@ class MedtrumService : DaggerService(), BLECommCallback {
}
}
// State for connect flow, could be replaced by commandState and steps in connect()
private inner class SynchronizeState : State() {
override fun onEnter() {
@ -422,6 +437,7 @@ class MedtrumService : DaggerService(), BLECommCallback {
}
}
// State for connect flow, could be replaced by commandState and steps in connect()
private inner class SubscribeState : State() {
override fun onEnter() {
@ -442,6 +458,7 @@ class MedtrumService : DaggerService(), BLECommCallback {
}
}
// This state is reached when the patch is ready to receive commands (Activation, Bolus, temp basal and whatever)
private inner class ReadyState : State() {
override fun onEnter() {
@ -451,6 +468,53 @@ class MedtrumService : DaggerService(), BLECommCallback {
isConnected = true
rxBus.send(EventPumpStatusChanged(EventPumpStatusChanged.Status.CONNECTED))
}
// Just a placeholder, this state is reached when the patch is ready to receive commands (Bolus, temp basal and whatever)
}
// This state is when a command is send and we wait for a response for that command
private inner class CommandState : State() {
private var responseHandled = false
private var responseSuccess = false
override fun onEnter() {
aapsLogger.debug(LTag.PUMPCOMM, "Medtrum Service reached CommandState")
}
override fun onIndication(data: ByteArray) {
if (mPacket?.handleResponse(data) == true) {
// Succes!
responseHandled = true
responseSuccess = true
toState(ReadyState())
} else if (mPacket?.failed == true) {
// Failure
responseHandled = true
responseSuccess = false
toState(ReadyState())
}
}
override fun onDisconnected() {
super.onDisconnected()
responseHandled = true
responseSuccess = false
}
override fun waitForResponse(): Boolean {
val startTime = System.currentTimeMillis()
val timeoutMillis = T.secs(45).msecs()
while (!responseHandled) {
if (System.currentTimeMillis() - startTime > timeoutMillis) {
// If we haven't received a response in the specified time, assume the command failed
aapsLogger.debug(LTag.PUMPCOMM, "Medtrum Service CommandState timeout")
// Disconnect to cancel any outstanding commands and go back to ready state
bleComm.disconnect("Timeout")
toState(ReadyState())
return false
}
Thread.sleep(100)
}
return responseSuccess
}
}
}

View file

@ -0,0 +1,51 @@
package info.nightscout.androidaps.plugins.pump.eopatch.ui
import android.os.Bundle
import android.view.View
import androidx.lifecycle.ViewModelProvider
import info.nightscout.core.ui.toast.ToastUtils
import info.nightscout.pump.medtrum.R
import info.nightscout.pump.medtrum.code.PatchStep
import info.nightscout.pump.medtrum.databinding.FragmentMedtrumActivateBinding
import info.nightscout.pump.medtrum.ui.MedtrumBaseFragment
import info.nightscout.pump.medtrum.ui.viewmodel.MedtrumViewModel
import info.nightscout.rx.logging.AAPSLogger
import info.nightscout.rx.logging.LTag
import javax.inject.Inject
class MedtrumActivateFragment : MedtrumBaseFragment<FragmentMedtrumActivateBinding>() {
@Inject lateinit var aapsLogger: AAPSLogger
companion object {
fun newInstance(): MedtrumActivateFragment = MedtrumActivateFragment()
}
override fun getLayoutId(): Int = R.layout.fragment_medtrum_activate
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.apply {
viewModel = ViewModelProvider(requireActivity(), viewModelFactory).get(MedtrumViewModel::class.java)
viewModel?.apply {
setupStep.observe(viewLifecycleOwner) {
when (it) {
MedtrumViewModel.SetupStep.PRIMED -> Unit // Nothing to do here, previous state
MedtrumViewModel.SetupStep.ACTIVATED -> btnPositive.visibility = View.VISIBLE
MedtrumViewModel.SetupStep.ERROR -> {
ToastUtils.errorToast(requireContext(), "Error Activating") // TODO: String resource and show error message
moveStep(PatchStep.CANCEL)
}
else -> {
ToastUtils.errorToast(requireContext(), "Unexpected state: $it") // TODO: String resource and show error message
moveStep(PatchStep.CANCEL)
}
}
}
startActivate()
}
}
}
}

View file

@ -9,6 +9,8 @@ import android.os.Bundle
import android.view.MotionEvent
import androidx.appcompat.app.AlertDialog
import androidx.lifecycle.ViewModelProvider
import info.nightscout.androidaps.plugins.pump.eopatch.ui.MedtrumActivateFragment
import info.nightscout.androidaps.plugins.pump.eopatch.ui.MedtrumAttachPatchFragment
import info.nightscout.androidaps.plugins.pump.eopatch.ui.MedtrumPreparePatchFragment
import info.nightscout.androidaps.plugins.pump.eopatch.ui.MedtrumPrimeFragment
import info.nightscout.core.utils.extensions.safeGetSerializableExtra
@ -42,6 +44,9 @@ class MedtrumActivity : MedtrumBaseActivity<ActivityMedtrumBinding>() {
when (it) {
PatchStep.PREPARE_PATCH -> setupViewFragment(MedtrumPreparePatchFragment.newInstance())
PatchStep.PRIME -> setupViewFragment(MedtrumPrimeFragment.newInstance())
PatchStep.ATTACH_PATCH -> setupViewFragment(MedtrumAttachPatchFragment.newInstance())
PatchStep.ACTIVATE -> setupViewFragment(MedtrumActivateFragment.newInstance())
PatchStep.COMPLETE -> this@MedtrumActivity.finish() // TODO proper finish
PatchStep.CANCEL -> this@MedtrumActivity.finish()
else -> Unit
}

View file

@ -0,0 +1,49 @@
package info.nightscout.androidaps.plugins.pump.eopatch.ui
import android.os.Bundle
import android.view.View
import androidx.lifecycle.ViewModelProvider
import info.nightscout.core.ui.toast.ToastUtils
import info.nightscout.pump.medtrum.R
import info.nightscout.pump.medtrum.code.PatchStep
import info.nightscout.pump.medtrum.databinding.FragmentMedtrumAttachPatchBinding
import info.nightscout.pump.medtrum.ui.MedtrumBaseFragment
import info.nightscout.pump.medtrum.ui.viewmodel.MedtrumViewModel
import info.nightscout.rx.logging.AAPSLogger
import info.nightscout.rx.logging.LTag
import javax.inject.Inject
class MedtrumAttachPatchFragment : MedtrumBaseFragment<FragmentMedtrumAttachPatchBinding>() {
@Inject lateinit var aapsLogger: AAPSLogger
companion object {
fun newInstance(): MedtrumAttachPatchFragment = MedtrumAttachPatchFragment()
}
override fun getLayoutId(): Int = R.layout.fragment_medtrum_attach_patch
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
aapsLogger.debug(LTag.PUMP, "MedtrumAttachPatchFragment onViewCreated")
binding.apply {
viewModel = ViewModelProvider(requireActivity(), viewModelFactory).get(MedtrumViewModel::class.java)
viewModel?.apply {
setupStep.observe(viewLifecycleOwner) {
when (it) {
MedtrumViewModel.SetupStep.PRIMED -> Unit // Nothing to do here, previous state
MedtrumViewModel.SetupStep.ERROR -> {
ToastUtils.errorToast(requireContext(), "Error attach patch") // TODO: String resource and show error message
moveStep(PatchStep.CANCEL)
}
else -> {
ToastUtils.errorToast(requireContext(), "Unexpected state: $it") // TODO: String resource and show error message
moveStep(PatchStep.CANCEL)
}
}
}
}
}
}
}

View file

@ -6,6 +6,7 @@ import android.view.View
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.lifecycle.ViewModelProvider
import info.nightscout.pump.medtrum.MedtrumPump
import info.nightscout.pump.medtrum.databinding.FragmentMedtrumOverviewBinding
import info.nightscout.pump.medtrum.ui.viewmodel.MedtrumOverviewViewModel
import info.nightscout.pump.medtrum.R
@ -13,6 +14,7 @@ import info.nightscout.pump.medtrum.code.EventType
import info.nightscout.pump.medtrum.code.PatchStep
import info.nightscout.rx.AapsSchedulers
import info.nightscout.rx.logging.AAPSLogger
import info.nightscout.rx.logging.LTag
import io.reactivex.rxjava3.disposables.CompositeDisposable
import javax.inject.Inject
@ -20,6 +22,7 @@ class MedtrumOverviewFragment : MedtrumBaseFragment<FragmentMedtrumOverviewBindi
@Inject lateinit var aapsSchedulers: AapsSchedulers
@Inject lateinit var aapsLogger: AAPSLogger
@Inject lateinit var medtrumPump: MedtrumPump
private lateinit var resultLauncherForResume: ActivityResultLauncher<Intent>
private lateinit var resultLauncherForPause: ActivityResultLauncher<Intent>
@ -40,7 +43,12 @@ class MedtrumOverviewFragment : MedtrumBaseFragment<FragmentMedtrumOverviewBindi
viewmodel?.apply {
eventHandler.observe(viewLifecycleOwner) { evt ->
when (evt.peekContent()) {
EventType.ACTIVATION_CLICKED -> requireContext().apply { startActivity(MedtrumActivity.createIntentFromMenu(this, PatchStep.PREPARE_PATCH)) }
EventType.ACTIVATION_CLICKED -> requireContext().apply {
val step = convertToPatchStep(medtrumPump.pumpState)
if (step != PatchStep.PREPARE_PATCH) {
aapsLogger.warn(LTag.PUMP, "MedtrumOverviewFragment: Patch already in activation process, going to $step")
}
startActivity(MedtrumActivity.createIntentFromMenu(this, step)) }
else -> Unit
}
}

View file

@ -3,7 +3,9 @@ package info.nightscout.androidaps.plugins.pump.eopatch.ui
import android.os.Bundle
import android.view.View
import androidx.lifecycle.ViewModelProvider
import info.nightscout.core.ui.toast.ToastUtils
import info.nightscout.pump.medtrum.R
import info.nightscout.pump.medtrum.code.PatchStep
import info.nightscout.pump.medtrum.databinding.FragmentMedtrumPreparePatchBinding
import info.nightscout.pump.medtrum.ui.MedtrumBaseFragment
import info.nightscout.pump.medtrum.ui.viewmodel.MedtrumViewModel
@ -30,9 +32,19 @@ class MedtrumPreparePatchFragment : MedtrumBaseFragment<FragmentMedtrumPreparePa
viewModel?.apply {
setupStep.observe(viewLifecycleOwner) {
when (it) {
MedtrumViewModel.SetupStep.INITIAL -> btnPositive.visibility = View.GONE
// TODO: Confirmation dialog
MedtrumViewModel.SetupStep.CONNECTED -> btnPositive.visibility = View.VISIBLE
else -> Unit
MedtrumViewModel.SetupStep.FILLED -> btnPositive.visibility = View.VISIBLE
MedtrumViewModel.SetupStep.ERROR -> {
ToastUtils.errorToast(requireContext(), "Error preparing patch") // TODO: String resource and show error message
moveStep(PatchStep.CANCEL)
}
else -> {
ToastUtils.errorToast(requireContext(), "Unexpected state: $it") // TODO: String resource and show error message
moveStep(PatchStep.CANCEL)
}
}
}
preparePatch()

View file

@ -3,7 +3,9 @@ package info.nightscout.androidaps.plugins.pump.eopatch.ui
import android.os.Bundle
import android.view.View
import androidx.lifecycle.ViewModelProvider
import info.nightscout.core.ui.toast.ToastUtils
import info.nightscout.pump.medtrum.R
import info.nightscout.pump.medtrum.code.PatchStep
import info.nightscout.pump.medtrum.databinding.FragmentMedtrumPrimeBinding
import info.nightscout.pump.medtrum.ui.MedtrumBaseFragment
import info.nightscout.pump.medtrum.ui.viewmodel.MedtrumViewModel
@ -26,7 +28,24 @@ class MedtrumPrimeFragment : MedtrumBaseFragment<FragmentMedtrumPrimeBinding>()
super.onViewCreated(view, savedInstanceState)
binding.apply {
viewModel = ViewModelProvider(requireActivity(), viewModelFactory).get(MedtrumViewModel::class.java)
// TODO do stuff
viewModel?.apply {
setupStep.observe(viewLifecycleOwner) {
when (it) {
MedtrumViewModel.SetupStep.FILLED -> Unit // Nothing to do here, previous state
MedtrumViewModel.SetupStep.PRIMED -> btnPositive.visibility = View.VISIBLE
MedtrumViewModel.SetupStep.ERROR -> {
ToastUtils.errorToast(requireContext(), "Error priming") // TODO: String resource and show error message
moveStep(PatchStep.CANCEL)
}
else -> {
ToastUtils.errorToast(requireContext(), "Unexpected state: $it") // TODO: String resource and show error message
moveStep(PatchStep.CANCEL)
}
}
}
startPrime()
}
}
}
}

View file

@ -1,6 +1,8 @@
package info.nightscout.pump.medtrum.ui.viewmodel
import androidx.lifecycle.ViewModel
import info.nightscout.pump.medtrum.code.PatchStep
import info.nightscout.pump.medtrum.comm.enums.MedtrumPumpState
import info.nightscout.pump.medtrum.ui.MedtrumBaseNavigator
import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.disposables.Disposable
@ -28,4 +30,12 @@ abstract class BaseViewModel<N : MedtrumBaseNavigator> : ViewModel() {
fun Disposable.addTo() = apply { compositeDisposable.add(this) }
fun convertToPatchStep(pumpState: MedtrumPumpState) = when (pumpState) {
MedtrumPumpState.NONE, MedtrumPumpState.IDLE -> PatchStep.PREPARE_PATCH
MedtrumPumpState.FILLED -> PatchStep.PREPARE_PATCH
MedtrumPumpState.PRIMING -> PatchStep.PRIME
MedtrumPumpState.PRIMED, MedtrumPumpState.EJECTED -> PatchStep.ATTACH_PATCH
MedtrumPumpState.ACTIVE, MedtrumPumpState.ACTIVE_ALT -> PatchStep.COMPLETE
else -> PatchStep.CANCEL
}
}

View file

@ -9,6 +9,8 @@ import info.nightscout.pump.medtrum.ui.event.SingleLiveEvent
import info.nightscout.pump.medtrum.ui.event.UIEvent
import info.nightscout.pump.medtrum.ui.viewmodel.BaseViewModel
import info.nightscout.interfaces.profile.ProfileFunction
import info.nightscout.pump.medtrum.MedtrumPump
import info.nightscout.pump.medtrum.comm.enums.MedtrumPumpState
import info.nightscout.rx.AapsSchedulers
import info.nightscout.rx.bus.RxBus
import info.nightscout.rx.events.EventPumpStatusChanged
@ -16,6 +18,9 @@ import info.nightscout.rx.logging.AAPSLogger
import info.nightscout.rx.logging.LTag
import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.kotlin.plusAssign
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import javax.inject.Inject
class MedtrumOverviewViewModel @Inject constructor(
@ -24,9 +29,11 @@ class MedtrumOverviewViewModel @Inject constructor(
private val aapsSchedulers: AapsSchedulers,
private val fabricPrivacy: FabricPrivacy,
private val profileFunction: ProfileFunction,
private val medtrumPump: MedtrumPump
) : BaseViewModel<MedtrumBaseNavigator>() {
private var disposable: CompositeDisposable = CompositeDisposable()
private val scope = CoroutineScope(Dispatchers.Default)
private val _eventHandler = SingleLiveEvent<UIEvent<EventType>>()
val eventHandler: LiveData<UIEvent<EventType>>
@ -36,11 +43,12 @@ class MedtrumOverviewViewModel @Inject constructor(
val bleStatus: LiveData<String>
get() = _bleStatus
// TODO make these livedata
val isPatchActivated: Boolean
get() = false // TODO
private val _isPatchActivated = SingleLiveEvent<Boolean>()
val isPatchActivated: LiveData<Boolean>
get() = _isPatchActivated
init {
// TODO proper connection state from medtrumPump
disposable += rxBus
.toObservable(EventPumpStatusChanged::class.java)
.observeOn(aapsSchedulers.main)
@ -59,6 +67,16 @@ class MedtrumOverviewViewModel @Inject constructor(
""
}
}, fabricPrivacy::logException)
scope.launch {
medtrumPump.pumpStateFlow.collect { state ->
aapsLogger.debug(LTag.PUMP, "MedtrumViewModel pumpStateFlow: $state")
if (state > MedtrumPumpState.EJECTED) {
_isPatchActivated.postValue(true)
} else {
_isPatchActivated.postValue(false)
}
}
}
}
fun onClickActivation() {

View file

@ -4,9 +4,12 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import info.nightscout.core.utils.fabric.FabricPrivacy
import info.nightscout.pump.medtrum.MedtrumPlugin
import info.nightscout.pump.medtrum.MedtrumPump
import info.nightscout.pump.medtrum.R
import info.nightscout.pump.medtrum.services.MedtrumService
import info.nightscout.pump.medtrum.code.EventType
import info.nightscout.pump.medtrum.code.PatchStep
import info.nightscout.pump.medtrum.comm.enums.MedtrumPumpState
import info.nightscout.pump.medtrum.ui.MedtrumBaseNavigator
import info.nightscout.pump.medtrum.ui.event.SingleLiveEvent
import info.nightscout.pump.medtrum.ui.event.UIEvent
@ -17,8 +20,9 @@ import info.nightscout.rx.events.EventPumpStatusChanged
import info.nightscout.rx.logging.AAPSLogger
import info.nightscout.rx.logging.LTag
import info.nightscout.shared.sharedPreferences.SP
import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.kotlin.plusAssign
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import javax.inject.Inject
class MedtrumViewModel @Inject constructor(
@ -28,17 +32,20 @@ class MedtrumViewModel @Inject constructor(
private val aapsSchedulers: AapsSchedulers,
private val fabricPrivacy: FabricPrivacy,
private val medtrumPlugin: MedtrumPlugin,
private val medtrumPump: MedtrumPump,
private val sp: SP
) : BaseViewModel<MedtrumBaseNavigator>() {
val patchStep = MutableLiveData<PatchStep>()
val title = "Activation"
val medtrumService: MedtrumService?
get() = medtrumPlugin.getService()
private var disposable: CompositeDisposable = CompositeDisposable()
private val scope = CoroutineScope(Dispatchers.Default)
private val _title = MutableLiveData<Int>(R.string.step_prepare_patch)
val title: LiveData<Int>
get() = _title
private val _eventHandler = SingleLiveEvent<UIEvent<EventType>>()
val eventHandler: LiveData<UIEvent<EventType>>
@ -47,22 +54,37 @@ class MedtrumViewModel @Inject constructor(
private var mInitPatchStep: PatchStep? = null
init {
disposable += rxBus
.toObservable(EventPumpStatusChanged::class.java)
.observeOn(aapsSchedulers.main)
.subscribe({
when (it.status) {
EventPumpStatusChanged.Status.CONNECTING -> {}
EventPumpStatusChanged.Status.CONNECTED
-> if (patchStep.value == PatchStep.PREPARE_PATCH) setupStep.postValue(SetupStep.CONNECTED) else {
scope.launch {
medtrumPump.pumpStateFlow.collect { state ->
aapsLogger.debug(LTag.PUMP, "MedtrumViewModel pumpStateFlow: $state")
when (state) {
MedtrumPumpState.NONE, MedtrumPumpState.IDLE -> {
setupStep.postValue(SetupStep.INITIAL)
}
EventPumpStatusChanged.Status.DISCONNECTED -> {}
else -> {}
MedtrumPumpState.FILLED -> {
setupStep.postValue(SetupStep.FILLED)
}
MedtrumPumpState.PRIMING -> {
// setupStep.postValue(SetupStep.PRIMING)
// TODO: What to do here? start prime counter?
}
MedtrumPumpState.PRIMED, MedtrumPumpState.EJECTED -> {
setupStep.postValue(SetupStep.PRIMED)
}
MedtrumPumpState.ACTIVE, MedtrumPumpState.ACTIVE_ALT -> {
setupStep.postValue(SetupStep.ACTIVATED)
}
else -> {
setupStep.postValue(SetupStep.ERROR)
}
}
}
}
}, fabricPrivacy::logException)
}
fun moveStep(newPatchStep: PatchStep) {
@ -71,8 +93,8 @@ class MedtrumViewModel @Inject constructor(
if (oldPatchStep != newPatchStep) {
when (newPatchStep) {
PatchStep.CANCEL -> {
if (medtrumService?.isConnected == true || medtrumService?.isConnecting == true) medtrumService?.disconnect("Cancel") else {
}
// if (medtrumService?.isConnected == true || medtrumService?.isConnecting == true) medtrumService?.disconnect("Cancel") else {
// }
}
else -> null
@ -91,20 +113,49 @@ class MedtrumViewModel @Inject constructor(
}
fun preparePatch() {
// TODO: Decide if we want to connect already when user is still filling, or if we want to wait after user is done filling
// TODO When we dont need to connect what needs to be done here?
medtrumService?.connect("PreparePatch")
}
fun startPrime() {
// TODO: Get result from service
if (medtrumPump.pumpState == MedtrumPumpState.PRIMING) {
aapsLogger.info(LTag.PUMP, "startPrime: already priming!")
} else {
if (medtrumService?.startPrime() == true) {
aapsLogger.info(LTag.PUMP, "startPrime: success!")
} else {
aapsLogger.info(LTag.PUMP, "startPrime: failure!")
setupStep.postValue(SetupStep.ERROR)
}
}
}
fun startActivate() {
if (medtrumService?.startActivate() == true) {
aapsLogger.info(LTag.PUMP, "startActivate: success!")
} else {
aapsLogger.info(LTag.PUMP, "startActivate: failure!")
setupStep.postValue(SetupStep.ERROR)
}
}
private fun prepareStep(step: PatchStep?): PatchStep {
// TODO Title per screen :) And proper sync with patchstate
// (step ?: convertToPatchStep(patchConfig.lifecycleEvent.lifeCycle)).let { newStep ->
(step ?: PatchStep.SAFE_DEACTIVATION).let { newStep ->
(step ?: convertToPatchStep(medtrumPump.pumpState)).let { newStep ->
when (newStep) {
else -> ""
PatchStep.PREPARE_PATCH -> R.string.step_prepare_patch
PatchStep.PRIME -> R.string.step_prime
PatchStep.ATTACH_PATCH -> R.string.step_attach
PatchStep.ACTIVATE -> R.string.step_activate
PatchStep.COMPLETE -> R.string.step_complete
else -> _title.value
}.let {
aapsLogger.info(LTag.PUMP, "prepareStep: title before cond: $it")
if (_title.value != it) {
aapsLogger.info(LTag.PUMP, "prepareStep: title: $it")
_title.postValue(it)
}
}
patchStep.postValue(newStep)
@ -114,9 +165,11 @@ class MedtrumViewModel @Inject constructor(
}
enum class SetupStep {
CONNECTED,
PRIME_READY,
ACTIVATED
INITIAL,
FILLED,
PRIMED,
ACTIVATED,
ERROR
}
val setupStep = MutableLiveData<SetupStep>()

View file

@ -0,0 +1,66 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<import type="info.nightscout.pump.medtrum.code.PatchStep" />
<variable
name="viewModel"
type="info.nightscout.pump.medtrum.ui.viewmodel.MedtrumViewModel" />
</data>
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginStart="30dp"
android:layout_marginEnd="30dp"
android:fillViewport="true"
android:scrollbars="none">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:context=".ui.MedtrumActivity">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/layout_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
<Button
android:id="@+id/btn_negative"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:contentDescription="@string/cancel"
android:text="@string/cancel"
app:layout_constraintEnd_toStartOf="@id/btn_positive"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:onSafeClick="@{() -> viewModel.moveStep(PatchStep.CANCEL)}" />
<Button
android:id="@+id/btn_positive"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="6dp"
android:contentDescription="@string/string_start_complete"
android:text="@string/string_start_complete"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/btn_negative"
app:layout_constraintTop_toTopOf="parent"
app:onSafeClick="@{() -> viewModel.moveStep(PatchStep.COMPLETE)}"
tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>
</layout>

View file

@ -0,0 +1,66 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<import type="info.nightscout.pump.medtrum.code.PatchStep" />
<variable
name="viewModel"
type="info.nightscout.pump.medtrum.ui.viewmodel.MedtrumViewModel" />
</data>
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginStart="30dp"
android:layout_marginEnd="30dp"
android:fillViewport="true"
android:scrollbars="none">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:context=".ui.MedtrumActivity">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/layout_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
<Button
android:id="@+id/btn_negative"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:contentDescription="@string/cancel"
android:text="@string/cancel"
app:layout_constraintEnd_toStartOf="@id/btn_positive"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:onSafeClick="@{() -> viewModel.moveStep(PatchStep.CANCEL)}" />
<Button
android:id="@+id/btn_positive"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="6dp"
android:contentDescription="@string/string_start_activate"
android:text="@string/string_start_activate"
android:visibility="visible"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/btn_negative"
app:layout_constraintTop_toTopOf="parent"
app:onSafeClick="@{() -> viewModel.moveStep(PatchStep.ACTIVATE)}"
tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>
</layout>

View file

@ -25,7 +25,40 @@
android:layout_height="wrap_content"
tools:context=".ui.MedtrumActivity">
<!-- TODO some stuff here that we are waiting :) -->
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/layout_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
<Button
android:id="@+id/btn_negative"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:contentDescription="@string/cancel"
android:text="@string/cancel"
app:layout_constraintEnd_toStartOf="@id/btn_positive"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:onSafeClick="@{() -> viewModel.moveStep(PatchStep.CANCEL)}" />
<Button
android:id="@+id/btn_positive"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="6dp"
android:contentDescription="@string/string_next"
android:text="@string/string_next"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/btn_negative"
app:layout_constraintTop_toTopOf="parent"
app:onSafeClick="@{() -> viewModel.moveStep(PatchStep.ATTACH_PATCH)}"
tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>

View file

@ -16,7 +16,16 @@
<!-- wizard-->
<string name="string_change_patch">Discard/Change Patch</string> <!-- TODO check-->
<string name="string_next">Next</string>
<string name="string_start_prime">Start priming</string>
<string name="string_start_activate">Start activation</string>
<string name="string_start_complete">Complete</string>
<string name ="step_prepare_patch">Prepare patch</string>
<string name ="step_prime">Priming</string>
<string name ="step_attach">Attach patch</string>
<string name ="step_activate">Activation</string>
<string name ="step_complete">Complete</string>
<!-- settings-->
<string name="snInput_title">SN</string>

View file

@ -70,6 +70,7 @@ class ActivatePacketTest : MedtrumTestBase() {
assertEquals(expectedBasalSequence, medtrumPump.lastBasalSequence)
assertEquals(expectedBasalPatchId, medtrumPump.lastBasalPatchId)
assertEquals(expectedBasalStart, medtrumPump.lastBasalStartTime)
assertEquals(basalProfile, medtrumPump.actualBasalProfile)
}
@Test fun handleResponseGivenResponseWhenMessageTooShortThenResultFalse() {

View file

@ -32,4 +32,43 @@ class SetBasalProfilePacketTest : MedtrumTestBase() {
val expected = byteArrayOf(opCode.toByte()) + 1.toByte() + basalProfile
assertEquals(expected.contentToString(), result.contentToString())
}
@Test fun handleResponseGivenPacketWhenValuesSetThenReturnCorrectValues() {
// Inputs
val repsonse = byteArrayOf(18, 21, 16, 0, 0, 0, 1, 22, 0, 3, 0, -110, 0, -32, -18, 88, 17)
val basalProfile = byteArrayOf(8, 2, 3, 4, -1, 0, 0, 0, 0)
// Call
val packet = SetBasalProfilePacket(packetInjector, basalProfile)
val result = packet.handleResponse(repsonse)
// Expected values
val expectedBasalType = 1
val expectedBasalRate = 1.1
val expectedBasalSequence = 3
val expectedStartTime = 1679575392L
val expectedPatchId = 146
assertTrue(result)
assertEquals(expectedBasalType, medtrumPump.lastBasalType)
assertEquals(expectedBasalRate, medtrumPump.lastBasalRate, 0.01)
assertEquals(expectedBasalSequence, medtrumPump.lastBasalSequence)
assertEquals(expectedStartTime, medtrumPump.lastBasalStartTime)
assertEquals(expectedPatchId, medtrumPump.lastBasalPatchId)
assertEquals(basalProfile, medtrumPump.actualBasalProfile)
}
@Test fun handleResponseGivenResponseWhenMessageTooShortThenResultFalse() {
// Inputs
val response = byteArrayOf(18, 21, 16, 0, 0, 0, 1, 22, 0, 3, 0, -110, 0, -32, -18, 88)
val basalProfile = byteArrayOf(8, 2, 3, 4, -1, 0, 0, 0, 0)
// Call
val packet = SetBasalProfilePacket(packetInjector, basalProfile)
val result = packet.handleResponse(response)
// Expected values
assertFalse(result)
assertTrue(packet.failed)
}
}