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 dagger.android.HasAndroidInjector
import info.nightscout.core.ui.toast.ToastUtils import info.nightscout.core.ui.toast.ToastUtils
import info.nightscout.core.utils.fabric.FabricPrivacy import info.nightscout.core.utils.fabric.FabricPrivacy
import info.nightscout.interfaces.notifications.Notification
import info.nightscout.interfaces.plugin.PluginDescription import info.nightscout.interfaces.plugin.PluginDescription
import info.nightscout.interfaces.plugin.PluginType import info.nightscout.interfaces.plugin.PluginType
import info.nightscout.interfaces.profile.Profile 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.bus.RxBus
import info.nightscout.rx.events.EventAppExit import info.nightscout.rx.events.EventAppExit
import info.nightscout.rx.events.EventAppInitialized import info.nightscout.rx.events.EventAppInitialized
import info.nightscout.rx.events.EventDismissNotification
import info.nightscout.rx.events.EventOverviewBolusProgress import info.nightscout.rx.events.EventOverviewBolusProgress
import info.nightscout.rx.events.EventPreferenceChange import info.nightscout.rx.events.EventPreferenceChange
import info.nightscout.rx.logging.AAPSLogger import info.nightscout.rx.logging.AAPSLogger
@ -170,11 +172,33 @@ class MedtrumPlugin @Inject constructor(
} }
override fun setNewBasalProfile(profile: Profile): PumpEnactResult { 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 { 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 { 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 { 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") 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 { override fun setExtendedBolus(insulin: Double, durationInMinutes: Int): PumpEnactResult {
aapsLogger.info(LTag.PUMP, "setExtendedBolus - insulin: $insulin, durationInMinutes: $durationInMinutes") 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 { override fun cancelTempBasal(enforceNew: Boolean): PumpEnactResult {

View file

@ -1,5 +1,7 @@
package info.nightscout.pump.medtrum package info.nightscout.pump.medtrum
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import info.nightscout.interfaces.Constants import info.nightscout.interfaces.Constants
import info.nightscout.interfaces.profile.Instantiator import info.nightscout.interfaces.profile.Instantiator
import info.nightscout.interfaces.profile.Profile 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.sharedPreferences.SP
import info.nightscout.shared.utils.DateUtil import info.nightscout.shared.utils.DateUtil
import info.nightscout.shared.utils.T import info.nightscout.shared.utils.T
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
import kotlin.math.round import kotlin.math.round
@ -26,29 +30,37 @@ class MedtrumPump @Inject constructor(
private val instantiator: Instantiator private val instantiator: Instantiator
) { ) {
enum class PatchActivationState(val state: Int) { // Pump state flow
NONE(0), // TODO We might want to save this in SP, or at least get activated state from SP
IDLE(1), private val _pumpState = MutableStateFlow(MedtrumPumpState.NONE)
ACTIVATING(2), val pumpStateFlow: StateFlow<MedtrumPumpState> = _pumpState
ACTIVATED(3),
DEACTIVATING(4),
DEACTIVATED(5),
ERROR(6)
}
// Pump state and parameters var pumpState: MedtrumPumpState
var pumpState = MedtrumPumpState.NONE // TODO save in SP get() = _pumpState.value
var patchActivationState = PatchActivationState.NONE // TODO save in SP set(value) {
_pumpState.value = value
}
// 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 patchId = 0L
var lastKnownSequenceNumber = 0 var lastKnownSequenceNumber = 0
var lastTimeReceivedFromPump = 0L // Time in seconds! var lastTimeReceivedFromPump = 0L // Time in seconds!
var suspendTime = 0L // Time in seconds! var suspendTime = 0L // Time in seconds!
var patchStartTime = 0L // Time in seconds! var patchStartTime = 0L // Time in seconds!
var patchAge = 0L // Time in seconds! var patchAge = 0L // Time in seconds!
var reservoir = 0.0 var reservoir = 0.0
var primeProgress = 0
var batteryVoltage_A = 0.0 var batteryVoltage_A = 0.0
var batteryVoltage_B = 0.0 var batteryVoltage_B = 0.0
@ -67,7 +79,6 @@ class MedtrumPump @Inject constructor(
var lastStopSequence = 0 var lastStopSequence = 0
var lastStopPatchId = 0 var lastStopPatchId = 0
// TODO set these setting on init // TODO set these setting on init
// User settings (desired values, to be set on pump) // User settings (desired values, to be set on pump)
var desiredPatchExpiration = false var desiredPatchExpiration = false
@ -75,7 +86,6 @@ class MedtrumPump @Inject constructor(
var desiredHourlyMaxInsulin: Int = 40 var desiredHourlyMaxInsulin: Int = 40
var desiredDailyMaxInsulin: Int = 180 var desiredDailyMaxInsulin: Int = 180
fun buildMedtrumProfileArray(nsProfile: Profile): ByteArray? { fun buildMedtrumProfileArray(nsProfile: Profile): ByteArray? {
val list = nsProfile.getBasalValues() val list = nsProfile.getBasalValues()
var basals = byteArrayOf() var basals = byteArrayOf()
@ -87,7 +97,7 @@ class MedtrumPump @Inject constructor(
return null return null
} }
basals += ((rate shl 12) + time).toByteArray(3) 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 return (list.size).toByteArray(1) + basals
} }

View file

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

View file

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

View file

@ -57,14 +57,23 @@ class ActivatePacket(injector: HasAndroidInjector, private val basalProfile: Byt
* bytes 15 - end // Basal profile > see MedtrumPump * 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 patchExpiration: Byte = medtrumPump.desiredPatchExpiration.toByte()
val alarmSetting: Byte = medtrumPump.desiredAlarmSetting 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 hourlyMaxInsulin: Int = round(medtrumPump.desiredHourlyMaxInsulin / 0.05).toInt()
val dailyMaxInsulin: Int = round(medtrumPump.desiredDailyMaxInsulin / 0.05).toInt() val dailyMaxInsulin: Int = round(medtrumPump.desiredDailyMaxInsulin / 0.05).toInt()
val currentTDD: Double = tddCalculator.calculateToday()?.totalAmount?.div(0.05) ?: 0.0 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() return byteArrayOf(opCode) + autoSuspendEnable + autoSuspendTime + patchExpiration + alarmSetting + lowSuspend + predictiveLowSuspend + predictiveLowSuspendRange + hourlyMaxInsulin.toByteArray(
.toByteArray(2) + 1.toByte() + basalProfile 2
) + dailyMaxInsulin.toByteArray(2) + currentTDD.toInt().toByteArray(2) + 1.toByte() + basalProfile
} }
override fun handleResponse(data: ByteArray): Boolean { override fun handleResponse(data: ByteArray): Boolean {
@ -82,7 +91,9 @@ class ActivatePacket(injector: HasAndroidInjector, private val basalProfile: Byt
medtrumPump.patchId = patchId medtrumPump.patchId = patchId
medtrumPump.lastTimeReceivedFromPump = time 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) 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 { open fun getRequest(): ByteArray {
aapsLogger.debug(LTag.PUMPCOMM, "Get REQUEST TEST")
return byteArrayOf(opCode) 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 */ /** 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 { open fun handleResponse(data: ByteArray): Boolean {
if (expectedMinRespLength > data.size) { // Check for broken packets
if (RESP_RESULT_END > data.size) {
failed = true failed = true
aapsLogger.debug(LTag.PUMPCOMM, "handleResponse: Unexpected response length, expected: $expectedMinRespLength got: ${data.size}") aapsLogger.debug(LTag.PUMPCOMM, "handleResponse: Unexpected response length, expected: $expectedMinRespLength got: ${data.size}")
return false return false
@ -48,11 +48,17 @@ open class MedtrumPacket(protected var injector: HasAndroidInjector) {
return when { return when {
incomingOpCode != opCode -> { incomingOpCode != opCode -> {
failed = true 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 false
} }
responseCode == 0 -> { 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") aapsLogger.debug(LTag.PUMPCOMM, "handleResponse: Happy command: $opCode response: $responseCode")
true true
} }
@ -65,7 +71,7 @@ open class MedtrumPacket(protected var injector: HasAndroidInjector) {
else -> { else -> {
failed = true 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 false
} }
} }

View file

@ -14,19 +14,19 @@ import kotlin.experimental.and
class NotificationPacket(val injector: HasAndroidInjector) { class NotificationPacket(val injector: HasAndroidInjector) {
/** /**
* This is a bit of a special packet, as it is not a command packet * This is a bit of a special packet, as it is not a command packet
* but a notification packet. It is sent by the pump to the phone * but a notification packet. It is sent by the pump to the phone
* when the pump has a notification to send. * when the pump has a notification to send.
* *
* Notifications are sent regualary, regardless of the pump state. * Notifications are sent regualary, regardless of the pump state.
* *
* There can be multiple messages in one packet, this is noted by the fieldMask. * There can be multiple messages in one packet, this is noted by the fieldMask.
* *
* Byte 1: State (Handle a state change directly? before analyzing further?) * Byte 1: State (Handle a state change directly? before analyzing further?)
* Byte 2-3: FieldMask (BitMask which tells the fields present in the message) * Byte 2-3: FieldMask (BitMask which tells the fields present in the message)
* Byte 4-end : status data * Byte 4-end : status data
* *
* When multiple fields are in the message, the data is concatenated. * When multiple fields are in the message, the data is concatenated.
* This kind of message can also come as a response of SynchronizePacket, * This kind of message can also come as a response of SynchronizePacket,
* and can be handled here by handleMaskedMessage() as well. * and can be handled here by handleMaskedMessage() as well.
@ -36,9 +36,10 @@ class NotificationPacket(val injector: HasAndroidInjector) {
@Inject lateinit var medtrumPump: MedtrumPump @Inject lateinit var medtrumPump: MedtrumPump
companion object { companion object {
private const val NOTIF_STATE_START = 0 private const val NOTIF_STATE_START = 0
private const val NOTIF_STATE_END = NOTIF_STATE_START + 1 private const val NOTIF_STATE_END = NOTIF_STATE_START + 1
private const val MASK_SUSPEND = 0x01 private const val MASK_SUSPEND = 0x01
private const val MASK_NORMAL_BOLUS = 0x02 private const val MASK_NORMAL_BOLUS = 0x02
private const val MASK_EXTENDED_BOLUS = 0x04 private const val MASK_EXTENDED_BOLUS = 0x04
@ -79,7 +80,7 @@ class NotificationPacket(val injector: HasAndroidInjector) {
/** /**
* Handle a message with a field mask, can be used by other packets as well * Handle a message with a field mask, can be used by other packets as well
*/ */
fun handleMaskedMessage(data: ByteArray) { fun handleMaskedMessage(data: ByteArray) {
val fieldMask = data.copyOfRange(0, 2).toInt() val fieldMask = data.copyOfRange(0, 2).toInt()
var offset = 2 var offset = 2
@ -96,13 +97,13 @@ class NotificationPacket(val injector: HasAndroidInjector) {
aapsLogger.debug(LTag.PUMPCOMM, "Normal bolus notification received") aapsLogger.debug(LTag.PUMPCOMM, "Normal bolus notification received")
var bolusData = data.copyOfRange(offset, offset + 1).toInt() var bolusData = data.copyOfRange(offset, offset + 1).toInt()
var bolusType = bolusData and 0x7F 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 var bolusDelivered = data.copyOfRange(offset + 1, offset + 3).toInt() / 0.05
// TODO Sync bolus flow: // TODO Sync bolus flow:
// If bolus is known add status // If bolus is known add status
// If bolus is not known start read bolus // If bolus is not known start read bolus
// When bolus is completed, remove bolus from medtrumPump // 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 offset += 3
} }
@ -172,7 +173,7 @@ class NotificationPacket(val injector: HasAndroidInjector) {
medtrumPump.alarmFlags = data.copyOfRange(offset, offset + 2).toInt() medtrumPump.alarmFlags = data.copyOfRange(offset, offset + 2).toInt()
medtrumPump.alarmParameter = data.copyOfRange(offset + 2, offset + 4).toInt() medtrumPump.alarmParameter = data.copyOfRange(offset + 2, offset + 4).toInt()
aapsLogger.debug(LTag.PUMPCOMM, "Alarm flags: ${medtrumPump.alarmFlags}, alarm parameter: ${medtrumPump.alarmParameter}") aapsLogger.debug(LTag.PUMPCOMM, "Alarm flags: ${medtrumPump.alarmFlags}, alarm parameter: ${medtrumPump.alarmParameter}")
offset += 4 offset += 4
} }
if (fieldMask and MASK_START_TIME != 0) { if (fieldMask and MASK_START_TIME != 0) {
@ -202,5 +203,5 @@ class NotificationPacket(val injector: HasAndroidInjector) {
if (fieldMask and MASK_UNUSED_LEGACY != 0) { if (fieldMask and MASK_UNUSED_LEGACY != 0) {
aapsLogger.debug(LTag.PUMPCOMM, "Unused legacy notification received, not handled!") aapsLogger.debug(LTag.PUMPCOMM, "Unused legacy notification received, not handled!")
} }
} }
} }

View file

@ -3,18 +3,56 @@ package info.nightscout.pump.medtrum.comm.packets
import dagger.android.HasAndroidInjector import dagger.android.HasAndroidInjector
import info.nightscout.pump.medtrum.MedtrumPump import info.nightscout.pump.medtrum.MedtrumPump
import info.nightscout.pump.medtrum.comm.enums.CommandType.SET_BASAL_PROFILE 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 import javax.inject.Inject
class SetBasalProfilePacket(injector: HasAndroidInjector, private val basalProfile: ByteArray) : MedtrumPacket(injector) { class SetBasalProfilePacket(injector: HasAndroidInjector, private val basalProfile: ByteArray) : MedtrumPacket(injector) {
@Inject lateinit var medtrumPump: MedtrumPump @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 { init {
opCode = SET_BASAL_PROFILE.code opCode = SET_BASAL_PROFILE.code
expectedMinRespLength = RESP_BASAL_START_TIME_END
} }
override fun getRequest(): ByteArray { override fun getRequest(): ByteArray {
val basalType: Byte = 1 // Fixed to normal basal val basalType: Byte = 1 // Fixed to normal basal
return byteArrayOf(opCode) + basalType + basalProfile 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

@ -37,7 +37,7 @@ class SynchronizePacket(injector: HasAndroidInjector) : MedtrumPacket(injector)
var state = MedtrumPumpState.fromByte(data[RESP_STATE_START]) var state = MedtrumPumpState.fromByte(data[RESP_STATE_START])
medtrumPump.pumpState = state medtrumPump.pumpState = state
var fieldMask = data.copyOfRange(RESP_FIELDS_START, RESP_FIELDS_END).toInt() var fieldMask = data.copyOfRange(RESP_FIELDS_START, RESP_FIELDS_END).toInt()
var syncData = data.copyOfRange(RESP_SYNC_DATA_START, data.size) var syncData = data.copyOfRange(RESP_SYNC_DATA_START, data.size)
var offset = 0 var offset = 0
@ -46,8 +46,7 @@ class SynchronizePacket(injector: HasAndroidInjector) : MedtrumPacket(injector)
aapsLogger.debug(LTag.PUMPCOMM, "SynchronizePacket: fieldMask: $fieldMask") aapsLogger.debug(LTag.PUMPCOMM, "SynchronizePacket: fieldMask: $fieldMask")
} }
// Remove bolus fields from fieldMask if fields are present // Remove bolus fields from fieldMask if fields are present (we sync bolus trough other commands)
// TODO: Test if this workaround is needed (hence the warning log)
if (fieldMask and MASK_SUSPEND != 0) { if (fieldMask and MASK_SUSPEND != 0) {
offset += 4 // If field is present, skip 4 bytes offset += 4 // If field is present, skip 4 bytes
} }

View file

@ -7,6 +7,8 @@ import dagger.Module
import dagger.Provides import dagger.Provides
import dagger.android.ContributesAndroidInjector import dagger.android.ContributesAndroidInjector
import dagger.multibindings.IntoMap 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.MedtrumPreparePatchFragment
import info.nightscout.androidaps.plugins.pump.eopatch.ui.MedtrumPrimeFragment import info.nightscout.androidaps.plugins.pump.eopatch.ui.MedtrumPrimeFragment
import info.nightscout.pump.medtrum.services.MedtrumService import info.nightscout.pump.medtrum.services.MedtrumService
@ -56,6 +58,14 @@ abstract class MedtrumModule {
@ContributesAndroidInjector @ContributesAndroidInjector
internal abstract fun contributesPrimeFragment(): MedtrumPrimeFragment internal abstract fun contributesPrimeFragment(): MedtrumPrimeFragment
@FragmentScope
@ContributesAndroidInjector
internal abstract fun contributesAttachPatchFragment(): MedtrumAttachPatchFragment
@FragmentScope
@ContributesAndroidInjector
internal abstract fun contributesActivateFragment(): MedtrumActivateFragment
// ACTIVITIES // ACTIVITIES
@ContributesAndroidInjector @ContributesAndroidInjector
abstract fun contributesMedtrumActivity(): MedtrumActivity abstract fun contributesMedtrumActivity(): MedtrumActivity
@ -63,4 +73,5 @@ abstract class MedtrumModule {
// SERVICE // SERVICE
@ContributesAndroidInjector @ContributesAndroidInjector
abstract fun contributesMedtrumService(): MedtrumService 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.interfaces.ResourceHelper
import info.nightscout.shared.sharedPreferences.SP import info.nightscout.shared.sharedPreferences.SP
import info.nightscout.shared.utils.DateUtil import info.nightscout.shared.utils.DateUtil
import info.nightscout.shared.utils.T
import io.reactivex.rxjava3.disposables.CompositeDisposable import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.kotlin.plusAssign import io.reactivex.rxjava3.kotlin.plusAssign
import java.util.* import java.util.*
@ -75,29 +76,6 @@ class MedtrumService : DaggerService(), BLECommCallback {
// TODO: Stuff like this in a settings class? // TODO: Stuff like this in a settings class?
private var mLastDeviceTime: Long = 0 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() { override fun onCreate() {
super.onCreate() super.onCreate()
bleComm.setCallback(this) 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() { fun stopConnecting() {
// TODO proper way for this might need send commands etc // TODO proper way for this might need send commands etc
bleComm.stopConnecting() bleComm.stopConnecting()
@ -177,9 +167,8 @@ class MedtrumService : DaggerService(), BLECommCallback {
} }
fun updateBasalsInPump(profile: Profile): Boolean { fun updateBasalsInPump(profile: Profile): Boolean {
if (!isConnected) return false val packet = medtrumPump.buildMedtrumProfileArray(profile)?.let { SetBasalProfilePacket(injector, it) }
// TODO return packet?.let { sendPacketAndGetResponse(it) } == true
return false
} }
fun changePump() { fun changePump() {
@ -189,11 +178,11 @@ class MedtrumService : DaggerService(), BLECommCallback {
} catch (e: NumberFormatException) { } catch (e: NumberFormatException) {
aapsLogger.debug(LTag.PUMP, "changePump: Invalid input!") 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) { when (currentState) {
// is IdleState -> connect("changePump") is IdleState -> connect("changePump")
// is ReadyState -> disconnect("changePump") // is ReadyState -> disconnect("changePump")
else -> null // TODO: What to do here? Abort stuff? else -> null // TODO: What to do here? Abort stuff?
} }
} }
@ -244,6 +233,19 @@ class MedtrumService : DaggerService(), BLECommCallback {
currentState.onEnter() 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? // State class, Can we move this to different file?
private abstract inner class State { private abstract inner class State {
@ -265,11 +267,18 @@ class MedtrumService : DaggerService(), BLECommCallback {
// TODO: Check flow for this // TODO: Check flow for this
toState(IdleState()) toState(IdleState())
} }
open fun waitForResponse(): Boolean {
return false
}
} }
private inner class IdleState : State() { 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() { override fun onConnected() {
super.onConnected() super.onConnected()
@ -278,10 +287,11 @@ class MedtrumService : DaggerService(), BLECommCallback {
override fun onDisconnected() { override fun onDisconnected() {
super.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() { private inner class AuthState : State() {
override fun onEnter() { 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() { private inner class GetDeviceTypeState : State() {
override fun onEnter() { 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() { private inner class GetTimeState : State() {
override fun onEnter() { 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() { private inner class SetTimeState : State() {
override fun onEnter() { 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() { private inner class SetTimeZoneState : State() {
override fun onEnter() { 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() { private inner class SynchronizeState : State() {
override fun onEnter() { 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() { private inner class SubscribeState : State() {
override fun onEnter() { 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() { private inner class ReadyState : State() {
override fun onEnter() { override fun onEnter() {
@ -451,6 +468,53 @@ class MedtrumService : DaggerService(), BLECommCallback {
isConnected = true isConnected = true
rxBus.send(EventPumpStatusChanged(EventPumpStatusChanged.Status.CONNECTED)) 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 android.view.MotionEvent
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.lifecycle.ViewModelProvider 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.MedtrumPreparePatchFragment
import info.nightscout.androidaps.plugins.pump.eopatch.ui.MedtrumPrimeFragment import info.nightscout.androidaps.plugins.pump.eopatch.ui.MedtrumPrimeFragment
import info.nightscout.core.utils.extensions.safeGetSerializableExtra import info.nightscout.core.utils.extensions.safeGetSerializableExtra
@ -42,6 +44,9 @@ class MedtrumActivity : MedtrumBaseActivity<ActivityMedtrumBinding>() {
when (it) { when (it) {
PatchStep.PREPARE_PATCH -> setupViewFragment(MedtrumPreparePatchFragment.newInstance()) PatchStep.PREPARE_PATCH -> setupViewFragment(MedtrumPreparePatchFragment.newInstance())
PatchStep.PRIME -> setupViewFragment(MedtrumPrimeFragment.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() PatchStep.CANCEL -> this@MedtrumActivity.finish()
else -> Unit 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.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import info.nightscout.pump.medtrum.MedtrumPump
import info.nightscout.pump.medtrum.databinding.FragmentMedtrumOverviewBinding import info.nightscout.pump.medtrum.databinding.FragmentMedtrumOverviewBinding
import info.nightscout.pump.medtrum.ui.viewmodel.MedtrumOverviewViewModel import info.nightscout.pump.medtrum.ui.viewmodel.MedtrumOverviewViewModel
import info.nightscout.pump.medtrum.R 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.pump.medtrum.code.PatchStep
import info.nightscout.rx.AapsSchedulers import info.nightscout.rx.AapsSchedulers
import info.nightscout.rx.logging.AAPSLogger import info.nightscout.rx.logging.AAPSLogger
import info.nightscout.rx.logging.LTag
import io.reactivex.rxjava3.disposables.CompositeDisposable import io.reactivex.rxjava3.disposables.CompositeDisposable
import javax.inject.Inject import javax.inject.Inject
@ -20,6 +22,7 @@ class MedtrumOverviewFragment : MedtrumBaseFragment<FragmentMedtrumOverviewBindi
@Inject lateinit var aapsSchedulers: AapsSchedulers @Inject lateinit var aapsSchedulers: AapsSchedulers
@Inject lateinit var aapsLogger: AAPSLogger @Inject lateinit var aapsLogger: AAPSLogger
@Inject lateinit var medtrumPump: MedtrumPump
private lateinit var resultLauncherForResume: ActivityResultLauncher<Intent> private lateinit var resultLauncherForResume: ActivityResultLauncher<Intent>
private lateinit var resultLauncherForPause: ActivityResultLauncher<Intent> private lateinit var resultLauncherForPause: ActivityResultLauncher<Intent>
@ -40,7 +43,12 @@ class MedtrumOverviewFragment : MedtrumBaseFragment<FragmentMedtrumOverviewBindi
viewmodel?.apply { viewmodel?.apply {
eventHandler.observe(viewLifecycleOwner) { evt -> eventHandler.observe(viewLifecycleOwner) { evt ->
when (evt.peekContent()) { 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 else -> Unit
} }
} }

View file

@ -3,7 +3,9 @@ package info.nightscout.androidaps.plugins.pump.eopatch.ui
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import info.nightscout.core.ui.toast.ToastUtils
import info.nightscout.pump.medtrum.R 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.databinding.FragmentMedtrumPreparePatchBinding
import info.nightscout.pump.medtrum.ui.MedtrumBaseFragment import info.nightscout.pump.medtrum.ui.MedtrumBaseFragment
import info.nightscout.pump.medtrum.ui.viewmodel.MedtrumViewModel import info.nightscout.pump.medtrum.ui.viewmodel.MedtrumViewModel
@ -30,9 +32,19 @@ class MedtrumPreparePatchFragment : MedtrumBaseFragment<FragmentMedtrumPreparePa
viewModel?.apply { viewModel?.apply {
setupStep.observe(viewLifecycleOwner) { setupStep.observe(viewLifecycleOwner) {
when (it) { when (it) {
MedtrumViewModel.SetupStep.INITIAL -> btnPositive.visibility = View.GONE
// TODO: Confirmation dialog // TODO: Confirmation dialog
MedtrumViewModel.SetupStep.CONNECTED -> btnPositive.visibility = View.VISIBLE MedtrumViewModel.SetupStep.FILLED -> btnPositive.visibility = View.VISIBLE
else -> Unit
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() preparePatch()

View file

@ -3,7 +3,9 @@ package info.nightscout.androidaps.plugins.pump.eopatch.ui
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import info.nightscout.core.ui.toast.ToastUtils
import info.nightscout.pump.medtrum.R 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.databinding.FragmentMedtrumPrimeBinding
import info.nightscout.pump.medtrum.ui.MedtrumBaseFragment import info.nightscout.pump.medtrum.ui.MedtrumBaseFragment
import info.nightscout.pump.medtrum.ui.viewmodel.MedtrumViewModel import info.nightscout.pump.medtrum.ui.viewmodel.MedtrumViewModel
@ -26,7 +28,24 @@ class MedtrumPrimeFragment : MedtrumBaseFragment<FragmentMedtrumPrimeBinding>()
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
binding.apply { binding.apply {
viewModel = ViewModelProvider(requireActivity(), viewModelFactory).get(MedtrumViewModel::class.java) 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 package info.nightscout.pump.medtrum.ui.viewmodel
import androidx.lifecycle.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 info.nightscout.pump.medtrum.ui.MedtrumBaseNavigator
import io.reactivex.rxjava3.disposables.CompositeDisposable import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.disposables.Disposable import io.reactivex.rxjava3.disposables.Disposable
@ -28,4 +30,12 @@ abstract class BaseViewModel<N : MedtrumBaseNavigator> : ViewModel() {
fun Disposable.addTo() = apply { compositeDisposable.add(this) } 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.event.UIEvent
import info.nightscout.pump.medtrum.ui.viewmodel.BaseViewModel import info.nightscout.pump.medtrum.ui.viewmodel.BaseViewModel
import info.nightscout.interfaces.profile.ProfileFunction 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.AapsSchedulers
import info.nightscout.rx.bus.RxBus import info.nightscout.rx.bus.RxBus
import info.nightscout.rx.events.EventPumpStatusChanged import info.nightscout.rx.events.EventPumpStatusChanged
@ -16,6 +18,9 @@ import info.nightscout.rx.logging.AAPSLogger
import info.nightscout.rx.logging.LTag import info.nightscout.rx.logging.LTag
import io.reactivex.rxjava3.disposables.CompositeDisposable import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.kotlin.plusAssign import io.reactivex.rxjava3.kotlin.plusAssign
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import javax.inject.Inject import javax.inject.Inject
class MedtrumOverviewViewModel @Inject constructor( class MedtrumOverviewViewModel @Inject constructor(
@ -24,9 +29,11 @@ class MedtrumOverviewViewModel @Inject constructor(
private val aapsSchedulers: AapsSchedulers, private val aapsSchedulers: AapsSchedulers,
private val fabricPrivacy: FabricPrivacy, private val fabricPrivacy: FabricPrivacy,
private val profileFunction: ProfileFunction, private val profileFunction: ProfileFunction,
private val medtrumPump: MedtrumPump
) : BaseViewModel<MedtrumBaseNavigator>() { ) : BaseViewModel<MedtrumBaseNavigator>() {
private var disposable: CompositeDisposable = CompositeDisposable() private var disposable: CompositeDisposable = CompositeDisposable()
private val scope = CoroutineScope(Dispatchers.Default)
private val _eventHandler = SingleLiveEvent<UIEvent<EventType>>() private val _eventHandler = SingleLiveEvent<UIEvent<EventType>>()
val eventHandler: LiveData<UIEvent<EventType>> val eventHandler: LiveData<UIEvent<EventType>>
@ -36,11 +43,12 @@ class MedtrumOverviewViewModel @Inject constructor(
val bleStatus: LiveData<String> val bleStatus: LiveData<String>
get() = _bleStatus get() = _bleStatus
// TODO make these livedata private val _isPatchActivated = SingleLiveEvent<Boolean>()
val isPatchActivated: Boolean val isPatchActivated: LiveData<Boolean>
get() = false // TODO get() = _isPatchActivated
init { init {
// TODO proper connection state from medtrumPump
disposable += rxBus disposable += rxBus
.toObservable(EventPumpStatusChanged::class.java) .toObservable(EventPumpStatusChanged::class.java)
.observeOn(aapsSchedulers.main) .observeOn(aapsSchedulers.main)
@ -59,6 +67,16 @@ class MedtrumOverviewViewModel @Inject constructor(
"" ""
} }
}, fabricPrivacy::logException) }, 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() { fun onClickActivation() {

View file

@ -4,9 +4,12 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import info.nightscout.core.utils.fabric.FabricPrivacy import info.nightscout.core.utils.fabric.FabricPrivacy
import info.nightscout.pump.medtrum.MedtrumPlugin 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.services.MedtrumService
import info.nightscout.pump.medtrum.code.EventType import info.nightscout.pump.medtrum.code.EventType
import info.nightscout.pump.medtrum.code.PatchStep 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.MedtrumBaseNavigator
import info.nightscout.pump.medtrum.ui.event.SingleLiveEvent import info.nightscout.pump.medtrum.ui.event.SingleLiveEvent
import info.nightscout.pump.medtrum.ui.event.UIEvent 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.AAPSLogger
import info.nightscout.rx.logging.LTag import info.nightscout.rx.logging.LTag
import info.nightscout.shared.sharedPreferences.SP import info.nightscout.shared.sharedPreferences.SP
import io.reactivex.rxjava3.disposables.CompositeDisposable import kotlinx.coroutines.CoroutineScope
import io.reactivex.rxjava3.kotlin.plusAssign import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import javax.inject.Inject import javax.inject.Inject
class MedtrumViewModel @Inject constructor( class MedtrumViewModel @Inject constructor(
@ -28,17 +32,20 @@ class MedtrumViewModel @Inject constructor(
private val aapsSchedulers: AapsSchedulers, private val aapsSchedulers: AapsSchedulers,
private val fabricPrivacy: FabricPrivacy, private val fabricPrivacy: FabricPrivacy,
private val medtrumPlugin: MedtrumPlugin, private val medtrumPlugin: MedtrumPlugin,
private val medtrumPump: MedtrumPump,
private val sp: SP private val sp: SP
) : BaseViewModel<MedtrumBaseNavigator>() { ) : BaseViewModel<MedtrumBaseNavigator>() {
val patchStep = MutableLiveData<PatchStep>() val patchStep = MutableLiveData<PatchStep>()
val title = "Activation"
val medtrumService: MedtrumService? val medtrumService: MedtrumService?
get() = medtrumPlugin.getService() 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>>() private val _eventHandler = SingleLiveEvent<UIEvent<EventType>>()
val eventHandler: LiveData<UIEvent<EventType>> val eventHandler: LiveData<UIEvent<EventType>>
@ -47,22 +54,37 @@ class MedtrumViewModel @Inject constructor(
private var mInitPatchStep: PatchStep? = null private var mInitPatchStep: PatchStep? = null
init { init {
disposable += rxBus scope.launch {
.toObservable(EventPumpStatusChanged::class.java) medtrumPump.pumpStateFlow.collect { state ->
.observeOn(aapsSchedulers.main) aapsLogger.debug(LTag.PUMP, "MedtrumViewModel pumpStateFlow: $state")
.subscribe({ when (state) {
when (it.status) { MedtrumPumpState.NONE, MedtrumPumpState.IDLE -> {
EventPumpStatusChanged.Status.CONNECTING -> {} setupStep.postValue(SetupStep.INITIAL)
}
EventPumpStatusChanged.Status.CONNECTED MedtrumPumpState.FILLED -> {
-> if (patchStep.value == PatchStep.PREPARE_PATCH) setupStep.postValue(SetupStep.CONNECTED) else { setupStep.postValue(SetupStep.FILLED)
} }
EventPumpStatusChanged.Status.DISCONNECTED -> {} MedtrumPumpState.PRIMING -> {
// setupStep.postValue(SetupStep.PRIMING)
// TODO: What to do here? start prime counter?
}
else -> {} MedtrumPumpState.PRIMED, MedtrumPumpState.EJECTED -> {
} setupStep.postValue(SetupStep.PRIMED)
}, fabricPrivacy::logException) }
MedtrumPumpState.ACTIVE, MedtrumPumpState.ACTIVE_ALT -> {
setupStep.postValue(SetupStep.ACTIVATED)
}
else -> {
setupStep.postValue(SetupStep.ERROR)
}
}
}
}
} }
fun moveStep(newPatchStep: PatchStep) { fun moveStep(newPatchStep: PatchStep) {
@ -71,8 +93,8 @@ class MedtrumViewModel @Inject constructor(
if (oldPatchStep != newPatchStep) { if (oldPatchStep != newPatchStep) {
when (newPatchStep) { when (newPatchStep) {
PatchStep.CANCEL -> { 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 else -> null
@ -91,20 +113,49 @@ class MedtrumViewModel @Inject constructor(
} }
fun preparePatch() { 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") 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 { private fun prepareStep(step: PatchStep?): PatchStep {
// TODO Title per screen :) And proper sync with patchstate // TODO Title per screen :) And proper sync with patchstate
// (step ?: convertToPatchStep(patchConfig.lifecycleEvent.lifeCycle)).let { newStep -> (step ?: convertToPatchStep(medtrumPump.pumpState)).let { newStep ->
(step ?: PatchStep.SAFE_DEACTIVATION).let { newStep ->
when (newStep) { when (newStep) {
PatchStep.PREPARE_PATCH -> R.string.step_prepare_patch
else -> "" 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 { }.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) patchStep.postValue(newStep)
@ -114,9 +165,11 @@ class MedtrumViewModel @Inject constructor(
} }
enum class SetupStep { enum class SetupStep {
CONNECTED, INITIAL,
PRIME_READY, FILLED,
ACTIVATED PRIMED,
ACTIVATED,
ERROR
} }
val setupStep = MutableLiveData<SetupStep>() 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" android:layout_height="wrap_content"
tools:context=".ui.MedtrumActivity"> 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> </androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView> </ScrollView>

View file

@ -16,7 +16,16 @@
<!-- wizard--> <!-- wizard-->
<string name="string_change_patch">Discard/Change Patch</string> <!-- TODO check--> <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_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--> <!-- settings-->
<string name="snInput_title">SN</string> <string name="snInput_title">SN</string>

View file

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

View file

@ -32,4 +32,43 @@ class SetBasalProfilePacketTest : MedtrumTestBase() {
val expected = byteArrayOf(opCode.toByte()) + 1.toByte() + basalProfile val expected = byteArrayOf(opCode.toByte()) + 1.toByte() + basalProfile
assertEquals(expected.contentToString(), result.contentToString()) 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)
}
} }