diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/MedtrumPlugin.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/MedtrumPlugin.kt index e264320ba5..66833b1f94 100644 --- a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/MedtrumPlugin.kt +++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/MedtrumPlugin.kt @@ -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 { diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/MedtrumPump.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/MedtrumPump.kt index 6bda29514f..0536be865c 100644 --- a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/MedtrumPump.kt +++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/MedtrumPump.kt @@ -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,29 +30,37 @@ 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 = _pumpState - // Pump state and parameters - var pumpState = MedtrumPumpState.NONE // TODO save in SP - var patchActivationState = PatchActivationState.NONE // TODO save in SP + var pumpState: MedtrumPumpState + get() = _pumpState.value + set(value) { + _pumpState.value = value + } + // Prime progress as state flow + private val _primeProgress = MutableStateFlow(0) + val primeProgressFlow: StateFlow = _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! var suspendTime = 0L // Time in seconds! var patchStartTime = 0L // Time in seconds! 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 } diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/code/PatchStep.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/code/PatchStep.kt index 3b2c973ae6..8d615dd713 100644 --- a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/code/PatchStep.kt +++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/code/PatchStep.kt @@ -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, diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/enums/MedtrumPumpState.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/enums/MedtrumPumpState.kt index e861c8866a..b52b9c1793 100644 --- a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/enums/MedtrumPumpState.kt +++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/enums/MedtrumPumpState.kt @@ -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), diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/ActivatePacket.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/ActivatePacket.kt index 5911d09525..4e70df0638 100644 --- a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/ActivatePacket.kt +++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/ActivatePacket.kt @@ -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) } diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/MedtrumPacket.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/MedtrumPacket.kt index 605c071fad..6386e988ef 100644 --- a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/MedtrumPacket.kt +++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/MedtrumPacket.kt @@ -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 } } diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/NotificationPacket.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/NotificationPacket.kt index 6cb5a5bfa6..88f9f2bf70 100644 --- a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/NotificationPacket.kt +++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/NotificationPacket.kt @@ -14,19 +14,19 @@ import kotlin.experimental.and class NotificationPacket(val injector: HasAndroidInjector) { - /** + /** * 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 * when the pump has a notification to send. - * + * * Notifications are sent regualary, regardless of the pump state. - * + * * 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 2-3: FieldMask (BitMask which tells the fields present in the message) * Byte 4-end : status data - * + * * When multiple fields are in the message, the data is concatenated. * This kind of message can also come as a response of SynchronizePacket, * and can be handled here by handleMaskedMessage() as well. @@ -36,9 +36,10 @@ 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 - + private const val MASK_SUSPEND = 0x01 private const val MASK_NORMAL_BOLUS = 0x02 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 */ - fun handleMaskedMessage(data: ByteArray) { + fun handleMaskedMessage(data: ByteArray) { val fieldMask = data.copyOfRange(0, 2).toInt() var offset = 2 @@ -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 } @@ -172,7 +173,7 @@ class NotificationPacket(val injector: HasAndroidInjector) { medtrumPump.alarmFlags = data.copyOfRange(offset, offset + 2).toInt() medtrumPump.alarmParameter = data.copyOfRange(offset + 2, offset + 4).toInt() aapsLogger.debug(LTag.PUMPCOMM, "Alarm flags: ${medtrumPump.alarmFlags}, alarm parameter: ${medtrumPump.alarmParameter}") - offset += 4 + offset += 4 } if (fieldMask and MASK_START_TIME != 0) { @@ -202,5 +203,5 @@ class NotificationPacket(val injector: HasAndroidInjector) { if (fieldMask and MASK_UNUSED_LEGACY != 0) { aapsLogger.debug(LTag.PUMPCOMM, "Unused legacy notification received, not handled!") } - } + } } diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/SetBasalProfilePacket.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/SetBasalProfilePacket.kt index 186aa0f463..6023a2fafb 100644 --- a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/SetBasalProfilePacket.kt +++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/SetBasalProfilePacket.kt @@ -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 + } } diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/SynchronizePacket.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/SynchronizePacket.kt index 3c7f077dc2..71b022e848 100644 --- a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/SynchronizePacket.kt +++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/SynchronizePacket.kt @@ -37,7 +37,7 @@ class SynchronizePacket(injector: HasAndroidInjector) : MedtrumPacket(injector) var state = MedtrumPumpState.fromByte(data[RESP_STATE_START]) medtrumPump.pumpState = state - + var fieldMask = data.copyOfRange(RESP_FIELDS_START, RESP_FIELDS_END).toInt() var syncData = data.copyOfRange(RESP_SYNC_DATA_START, data.size) var offset = 0 @@ -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 } diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/di/MedtrumModule.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/di/MedtrumModule.kt index 18f4fb7419..9b5548b746 100644 --- a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/di/MedtrumModule.kt +++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/di/MedtrumModule.kt @@ -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 + } diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/services/MedtrumService.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/services/MedtrumService.kt index 864c4f7c82..68fe0fd3a8 100644 --- a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/services/MedtrumService.kt +++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/services/MedtrumService.kt @@ -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,11 +178,11 @@ 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? + 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 + } } } diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/MedtrumActivateFragment.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/MedtrumActivateFragment.kt new file mode 100644 index 0000000000..ab788b9d4b --- /dev/null +++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/MedtrumActivateFragment.kt @@ -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() { + + @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() + } + } + } +} diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/MedtrumActivity.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/MedtrumActivity.kt index 7b49049a6e..1c0edd9f94 100644 --- a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/MedtrumActivity.kt +++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/MedtrumActivity.kt @@ -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() { 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 } diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/MedtrumAttachPatchFragment.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/MedtrumAttachPatchFragment.kt new file mode 100644 index 0000000000..b8741413b7 --- /dev/null +++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/MedtrumAttachPatchFragment.kt @@ -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() { + + @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) + } + } + } + } + } + } +} diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/MedtrumOverviewFragment.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/MedtrumOverviewFragment.kt index 25aad5ce89..3ee8141993 100644 --- a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/MedtrumOverviewFragment.kt +++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/MedtrumOverviewFragment.kt @@ -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 private lateinit var resultLauncherForPause: ActivityResultLauncher @@ -40,7 +43,12 @@ class MedtrumOverviewFragment : MedtrumBaseFragment 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 } } diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/MedtrumPreparePatchFragment.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/MedtrumPreparePatchFragment.kt index 650d103f6e..d044e82073 100644 --- a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/MedtrumPreparePatchFragment.kt +++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/MedtrumPreparePatchFragment.kt @@ -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 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() diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/MedtrumPrimeFragment.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/MedtrumPrimeFragment.kt index 42f27d3105..d0903226b0 100644 --- a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/MedtrumPrimeFragment.kt +++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/MedtrumPrimeFragment.kt @@ -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() 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() + } } } } diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/viewmodel/BaseViewModel.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/viewmodel/BaseViewModel.kt index c3b39693b3..62a214ae3a 100644 --- a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/viewmodel/BaseViewModel.kt +++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/viewmodel/BaseViewModel.kt @@ -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 : 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 + } } \ No newline at end of file diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/viewmodel/MedtrumOverviewViewModel.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/viewmodel/MedtrumOverviewViewModel.kt index d270275c03..2aaddfb663 100644 --- a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/viewmodel/MedtrumOverviewViewModel.kt +++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/viewmodel/MedtrumOverviewViewModel.kt @@ -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() { private var disposable: CompositeDisposable = CompositeDisposable() + private val scope = CoroutineScope(Dispatchers.Default) private val _eventHandler = SingleLiveEvent>() val eventHandler: LiveData> @@ -36,11 +43,12 @@ class MedtrumOverviewViewModel @Inject constructor( val bleStatus: LiveData get() = _bleStatus - // TODO make these livedata - val isPatchActivated: Boolean - get() = false // TODO + private val _isPatchActivated = SingleLiveEvent() + val isPatchActivated: LiveData + 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() { diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/viewmodel/MedtrumViewModel.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/viewmodel/MedtrumViewModel.kt index 328b42a89a..1be28aa66a 100644 --- a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/viewmodel/MedtrumViewModel.kt +++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/ui/viewmodel/MedtrumViewModel.kt @@ -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() { val patchStep = MutableLiveData() - val title = "Activation" - val medtrumService: MedtrumService? get() = medtrumPlugin.getService() - private var disposable: CompositeDisposable = CompositeDisposable() + private val scope = CoroutineScope(Dispatchers.Default) + + private val _title = MutableLiveData(R.string.step_prepare_patch) + val title: LiveData + get() = _title private val _eventHandler = SingleLiveEvent>() val eventHandler: LiveData> @@ -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 -> {} + 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.CONNECTED - -> if (patchStep.value == PatchStep.PREPARE_PATCH) setupStep.postValue(SetupStep.CONNECTED) else { - } + MedtrumPumpState.FILLED -> { + setupStep.postValue(SetupStep.FILLED) + } - EventPumpStatusChanged.Status.DISCONNECTED -> {} + MedtrumPumpState.PRIMING -> { + // setupStep.postValue(SetupStep.PRIMING) + // TODO: What to do here? start prime counter? + } - else -> {} - } - }, fabricPrivacy::logException) + MedtrumPumpState.PRIMED, MedtrumPumpState.EJECTED -> { + setupStep.postValue(SetupStep.PRIMED) + } + + MedtrumPumpState.ACTIVE, MedtrumPumpState.ACTIVE_ALT -> { + setupStep.postValue(SetupStep.ACTIVATED) + } + + else -> { + setupStep.postValue(SetupStep.ERROR) + } + } + } + } } 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() diff --git a/pump/medtrum/src/main/res/layout/fragment_medtrum_activate.xml b/pump/medtrum/src/main/res/layout/fragment_medtrum_activate.xml new file mode 100644 index 0000000000..dbf6522fe1 --- /dev/null +++ b/pump/medtrum/src/main/res/layout/fragment_medtrum_activate.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + +