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 9ab782dd7d..0d2870fe36 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 @@ -18,6 +18,7 @@ import info.nightscout.interfaces.pump.Pump import info.nightscout.interfaces.pump.PumpEnactResult import info.nightscout.interfaces.pump.PumpPluginBase import info.nightscout.interfaces.pump.PumpSync +import info.nightscout.interfaces.pump.TemporaryBasalStorage import info.nightscout.interfaces.pump.actions.CustomAction import info.nightscout.interfaces.pump.actions.CustomActionType import info.nightscout.interfaces.pump.defs.ManufacturerType @@ -51,6 +52,7 @@ import org.json.JSONException import org.json.JSONObject import javax.inject.Inject import javax.inject.Singleton +import kotlin.math.round @Singleton class MedtrumPlugin @Inject constructor( injector: HasAndroidInjector, @@ -63,10 +65,11 @@ import javax.inject.Singleton private val context: Context, private val fabricPrivacy: FabricPrivacy, private val dateUtil: DateUtil, - private val pumpSync: PumpSync, private val medtrumPump: MedtrumPump, private val uiInteraction: UiInteraction, - private val profileFunction: ProfileFunction + private val profileFunction: ProfileFunction, + private val pumpSync: PumpSync, + private val temporaryBasalStorage: TemporaryBasalStorage ) : PumpPluginBase( PluginDescription() .mainType(PluginType.PUMP) @@ -205,7 +208,7 @@ import javax.inject.Singleton } override val baseBasalRate: Double - get() = 0.0 // TODO + get() = medtrumPump.baseBasalRate override val reservoirLevel: Double get() = medtrumPump.reservoir @@ -222,7 +225,24 @@ import javax.inject.Singleton } override fun setTempBasalAbsolute(absoluteRate: Double, durationInMinutes: Int, profile: Profile, enforceNew: Boolean, tbrType: PumpSync.TemporaryBasalType): PumpEnactResult { - return PumpEnactResult(injector) // TODO + if (!isInitialized()) return PumpEnactResult(injector).success(false).enacted(false) + + aapsLogger.info(LTag.PUMP, "setTempBasalAbsolute - absoluteRate: $absoluteRate, durationInMinutes: $durationInMinutes, enforceNew: $enforceNew") + // round rate to 0.05 + val pumpRate = round(absoluteRate * 20) / 20 // TODO: Maybe replace by constraints thing + temporaryBasalStorage.add(PumpSync.PumpState.TemporaryBasal(dateUtil.now(), T.mins(durationInMinutes.toLong()).msecs(), pumpRate, true, tbrType, 0L, 0L)) + val connectionOk = medtrumService?.setTempBasal(pumpRate, durationInMinutes) ?: false + if (connectionOk + && medtrumPump.tempBasalInProgress + && Math.abs(medtrumPump.tempBasalAbsoluteRate - pumpRate) <= 0.05 + /*&& Math.abs(medtrumPump.tempBasalRemainingMinutes - durationInMinutes) <= 5*/) { + + return PumpEnactResult(injector).success(true).enacted(true).duration(/*medtrumPump.tempBasalRemainingMinutes*/durationInMinutes).absolute(medtrumPump.tempBasalAbsoluteRate).isPercent(false) + .isTempCancel(false) + } else { + aapsLogger.error(LTag.PUMP, "setTempBasalAbsolute failed, connectionOk: $connectionOk, tempBasalInProgress: ${medtrumPump.tempBasalInProgress}, tempBasalAbsoluteRate: ${medtrumPump.tempBasalAbsoluteRate}") //, tempBasalRemainingMinutes: ${medtrumPump.tempBasalRemainingMinutes}") + return PumpEnactResult(injector).success(false).enacted(false).comment("Medtrum setTempBasalAbsolute failed") + } } override fun setTempBasalPercent(percent: Int, durationInMinutes: Int, profile: Profile, enforceNew: Boolean, tbrType: PumpSync.TemporaryBasalType): PumpEnactResult { @@ -236,7 +256,16 @@ import javax.inject.Singleton } override fun cancelTempBasal(enforceNew: Boolean): PumpEnactResult { - return PumpEnactResult(injector) // TODO + if (!isInitialized()) return PumpEnactResult(injector).success(false).enacted(false) + + aapsLogger.info(LTag.PUMP, "cancelTempBasal - enforceNew: $enforceNew") + val connectionOk = medtrumService?.cancelTempBasal() ?: false + if (connectionOk && !medtrumPump.tempBasalInProgress) { + return PumpEnactResult(injector).success(true).enacted(true).isTempCancel(true) + } else { + aapsLogger.error(LTag.PUMP, "cancelTempBasal failed, connectionOk: $connectionOk, tempBasalInProgress: ${medtrumPump.tempBasalInProgress}") + return PumpEnactResult(injector).success(false).enacted(false).comment("Medtrum cancelTempBasal failed") + } } override fun cancelExtendedBolus(): PumpEnactResult { @@ -289,8 +318,4 @@ import javax.inject.Singleton override fun timezoneOrDSTChanged(timeChangeType: TimeChangeType) { } - - private fun readTBR(): PumpSync.PumpState.TemporaryBasal? { - return pumpSync.expectedPumpState().temporaryBasal // TODO - } } 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 6791190162..a365e8e3bd 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,16 +1,22 @@ package info.nightscout.pump.medtrum +import android.util.Base64 import info.nightscout.interfaces.profile.Instantiator import info.nightscout.interfaces.profile.Profile +import info.nightscout.interfaces.pump.PumpSync +import info.nightscout.interfaces.pump.TemporaryBasalStorage import info.nightscout.interfaces.pump.defs.PumpType import info.nightscout.pump.medtrum.code.ConnectionState import info.nightscout.pump.medtrum.comm.enums.AlarmSetting +import info.nightscout.pump.medtrum.comm.enums.BasalType import info.nightscout.pump.medtrum.comm.enums.MedtrumPumpState import info.nightscout.pump.medtrum.extension.toByteArray +import info.nightscout.pump.medtrum.extension.toInt import info.nightscout.rx.logging.AAPSLogger 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 @@ -21,7 +27,9 @@ import kotlin.math.round class MedtrumPump @Inject constructor( private val aapsLogger: AAPSLogger, private val sp: SP, - private val dateUtil: DateUtil + private val dateUtil: DateUtil, + private val pumpSync: PumpSync, + private val temporaryBasalStorage: TemporaryBasalStorage ) { // Connection state flow @@ -33,6 +41,14 @@ class MedtrumPump @Inject constructor( _connectionState.value = value } + /** Patch activated state, mainly for UI, but also controls the connection flow, + * if patch is not activated, AAPS cannot connect to the pump, we can then connect trough the activation flow. + * Note: this is also saved in SP, by the set functions + */ + private var _patchActivated = false + val patchActivated: Boolean + get() = _patchActivated + // Pump state flow private val _pumpState = MutableStateFlow(MedtrumPumpState.NONE) val pumpStateFlow: StateFlow = _pumpState @@ -42,10 +58,6 @@ class MedtrumPump @Inject constructor( _pumpState.value = value } - var _patchActivated = false - val patchActivated: Boolean - get() = _patchActivated - // Prime progress as state flow private val _primeProgress = MutableStateFlow(0) val primeProgressFlow: StateFlow = _primeProgress @@ -55,14 +67,62 @@ class MedtrumPump @Inject constructor( _primeProgress.value = value } - var pumpSN = 0L - val pumpType: PumpType = PumpType.MEDTRUM_NANO // TODO, type based on pumpSN or pump activation/connection - var patchSessionToken = 0L + /** Stuff stored in SP */ + private var _patchSessionToken = 0L + var patchSessionToken: Long + get() = _patchSessionToken + set(value) { + _patchSessionToken = value + sp.putLong(R.string.key_session_token, value) + } + + private var _patchId = 0L + var patchId: Long + get() = _patchId + set(value) { + _patchId = value + sp.putLong(R.string.key_patch_id, value) + } + + private var _currentSequenceNumber = 0 + var currentSequenceNumber: Int + get() = _currentSequenceNumber + set(value) { + _currentSequenceNumber = value + sp.putInt(R.string.key_current_sequence_number, value) + } + + private var _syncedSequenceNumber = 0 + var syncedSequenceNumber: Int + get() = _syncedSequenceNumber + set(value) { + _syncedSequenceNumber = value + sp.putInt(R.string.key_synced_sequence_number, value) + } + + private var _actualBasalProfile = byteArrayOf(0) + var actualBasalProfile: ByteArray + get() = _actualBasalProfile + set(value) { + _actualBasalProfile = value + val encodedString = Base64.encodeToString(value, Base64.DEFAULT) + sp.putString(R.string.key_actual_basal_profile, encodedString) + } + + private var _lastBasalType: BasalType = BasalType.NONE + var lastBasalType: BasalType + get() = _lastBasalType + set(value) { + _lastBasalType = value + sp.putInt(R.string.key_last_basal_type, value.ordinal) + } + + private var _pumpSN = 0L + val pumpSN: Long + get() = _pumpSN + + val pumpType: PumpType = PumpType.MEDTRUM_NANO // TODO, type based on pumpSN or pump activation/connection - // 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! @@ -76,16 +136,25 @@ class MedtrumPump @Inject constructor( var alarmFlags = 0 var alarmParameter = 0 - // Last basal status update - var lastBasalType = 0 + // Last basal status update + // TODO: Save this in SP? var lastBasalRate = 0.0 var lastBasalSequence = 0 - var lastBasalPatchId = 0 + var lastBasalPatchId = 0L var lastBasalStartTime = 0L + val baseBasalRate: Double + get() = getCurrentHourlyBasalFromMedtrumProfileArray(actualBasalProfile) + + // TBR status + val tempBasalInProgress: Boolean + get() = lastBasalType == BasalType.ABSOLUTE_TEMP || lastBasalType == BasalType.RELATIVE_TEMP + val tempBasalAbsoluteRate: Double + get() = if (tempBasalInProgress) lastBasalRate else 0.0 + // Last stop status update var lastStopSequence = 0 - var lastStopPatchId = 0 + var lastStopPatchId = 0L // TODO set these setting on init // User settings (desired values, to be set on pump) @@ -94,6 +163,45 @@ class MedtrumPump @Inject constructor( var desiredHourlyMaxInsulin: Int = 40 var desiredDailyMaxInsulin: Int = 180 + init { + // Load stuff from SP + _patchActivated = sp.getBoolean(R.string.key_patch_activated, false) + _patchSessionToken = sp.getLong(R.string.key_session_token, 0L) + _currentSequenceNumber = sp.getInt(R.string.key_current_sequence_number, 0) + _patchId = sp.getLong(R.string.key_patch_id, 0L) + _syncedSequenceNumber = sp.getInt(R.string.key_synced_sequence_number, 0) + _lastBasalType = enumValues()[sp.getInt(R.string.key_last_basal_type, 0)] + + val encodedString = sp.getString(R.string.key_actual_basal_profile, "0") + try { + _actualBasalProfile = Base64.decode(encodedString, Base64.DEFAULT) + } catch (e: Exception) { + aapsLogger.error(LTag.PUMP, "Error decoding basal profile from SP: $encodedString") + } + + if (patchActivated) { + aapsLogger.debug(LTag.PUMP, "changePump: Patch is already activated, setting as ACTIVE") + // Set inital status as active will be updated on first connection + pumpState = MedtrumPumpState.ACTIVE + } + + loadUserSettingsFromSP() + } + + fun loadUserSettingsFromSP() { + // TODO + // desiredPatchExpiration = sp.getBoolean(R.string.key_patch_expiration, false) + // desiredAlarmSetting = sp.getInt(R.string.key_alarm_setting, AlarmSetting.LIGHT_VIBRATE_AND_BEEP.code) + // desiredHourlyMaxInsulin = sp.getInt(R.string.key_hourly_max_insulin, 40) + // desiredDailyMaxInsulin = sp.getInt(R.string.key_daily_max_insulin, 180) + + try { + _pumpSN = sp.getString(info.nightscout.pump.medtrum.R.string.key_snInput, " ").toLong(radix = 16) + } catch (e: NumberFormatException) { + aapsLogger.debug(LTag.PUMP, "changePump: Invalid input!") + } + } + fun setPatchActivatedState(activated: Boolean) { aapsLogger.debug(LTag.PUMP, "setPatchActivatedState: $activated") _patchActivated = activated @@ -101,7 +209,8 @@ class MedtrumPump @Inject constructor( } /** When the activation/deactivation screen, and the connection flow needs to be controlled, - * this can be used to set the ActivatedState without saving to SP, So when app is force closed the state is still maintained */ + * this can be used to set the ActivatedState without saving to SP, So when app is force closed the state is still maintained + */ fun setPatchActivatedStateTemp(activated: Boolean) { aapsLogger.debug(LTag.PUMP, "setPatchActivatedStateTemp: $activated") _patchActivated = activated @@ -123,29 +232,109 @@ class MedtrumPump @Inject constructor( return (list.size).toByteArray(1) + basals } - fun handleBasalStatusUpdate(basalType: Int, basalValue: Double, basalSequence: Int, basalPatchId: Int, basalStartTime: Long) { + fun getCurrentHourlyBasalFromMedtrumProfileArray(basalProfile: ByteArray): Double { + val basalCount = basalProfile[0].toInt() + var basal = 0.0 + if (basalProfile.size < 4 || (basalProfile.size - 1) % 3 != 0 || basalCount > 24) { + aapsLogger.debug(LTag.PUMP, "getCurrentHourlyBasalFromMedtrumProfileArray: No valid basal profile set") + return basal + } + + val hourOfDayMinutes = dateUtil.dateAndTimeString(dateUtil.now()).substring(11, 13).toInt() * 60 + dateUtil.dateAndTimeString(dateUtil.now()).substring(14, 16).toInt() + + for (index in 0 until basalCount) { + val currentIndex = 1 + (index * 3) + val nextIndex = currentIndex + 3 + val rateAndTime = basalProfile.copyOfRange(currentIndex, nextIndex).toInt() + val rate = (rateAndTime shr 12) * 0.05 + val startMinutes = rateAndTime and 0xFFF + + val endMinutes = if (nextIndex < basalProfile.size) { + val nextRateAndTime = basalProfile.copyOfRange(nextIndex, nextIndex + 3).toInt() + nextRateAndTime and 0xFFF + } else { + 24 * 60 + } + + if (hourOfDayMinutes in startMinutes until endMinutes) { + basal = rate + aapsLogger.debug(LTag.PUMP, "getCurrentHourlyBasalFromMedtrumProfileArray: basal: $basal") + break + } + aapsLogger.debug(LTag.PUMP, "getCurrentHourlyBasalFromMedtrumProfileArray: rate: $rate, startMinutes: $startMinutes, endMinutes: $endMinutes") + } + return basal + } + + fun handleBasalStatusUpdate(basalType: BasalType, basalValue: Double, basalSequence: Int, basalPatchId: Long, basalStartTime: Long) { handleBasalStatusUpdate(basalType, basalValue, basalSequence, basalPatchId, basalStartTime, dateUtil.now()) } - fun handleBasalStatusUpdate(basalType: Int, basalRate: Double, basalSequence: Int, basalPatchId: Int, basalStartTime: Long, receivedTime: Long) { + fun handleBasalStatusUpdate(basalType: BasalType, basalRate: Double, basalSequence: Int, basalPatchId: Long, basalStartTime: Long, receivedTime: Long) { aapsLogger.debug( - LTag.PUMP, "handleBasalStatusUpdate: basalType: $basalType basalValue: $basalRate basalSequence: $basalSequence basalPatchId: $basalPatchId basalStartTime: $basalStartTime " + - "receivedTime: $receivedTime" + LTag.PUMP, + "handleBasalStatusUpdate: basalType: $basalType basalValue: $basalRate basalSequence: $basalSequence basalPatchId: $basalPatchId basalStartTime: $basalStartTime " + "receivedTime: $receivedTime" ) + if (basalType.isTempBasal()) { + // TODO: Is this the correct place to sync temporaryBasalInfo? Note: it will be removed after getting it once, So this would only apply when called in setTempBasalPacket, maybe first check if basal entry already exists and leave this here, then we can also let the onNotification stuff sync basal? + val temporaryBasalInfo = temporaryBasalStorage.findTemporaryBasal(basalStartTime, basalRate) + + // If duration is unknown, no way to get it now, set patch lifetime as duration + val duration = temporaryBasalInfo?.duration ?: T.mins(4800).msecs() + val newRecord = pumpSync.syncTemporaryBasalWithPumpId( + timestamp = basalStartTime, + rate = basalRate, // TODO: Support percent here, this will break things? Check if this is correct + duration = duration, + isAbsolute = (basalType == BasalType.ABSOLUTE_TEMP), + type = temporaryBasalInfo?.type, + pumpId = basalStartTime, + pumpType = pumpType, + pumpSerial = pumpSN.toString(radix = 16) + ) + aapsLogger.debug( + LTag.PUMPCOMM, + "handleBasalStatusUpdate: ${if (newRecord) "**NEW** " else ""}EVENT TEMP_START ($basalType) ${dateUtil.dateAndTimeString(basalStartTime)} ($basalStartTime) " + "Rate: $basalRate Duration: ${duration}min" + ) + } else if (basalType.isSuspendedByPump()) { + val newRecord = pumpSync.syncTemporaryBasalWithPumpId( + timestamp = basalStartTime, + rate = 0.0, + duration = T.mins(4800).msecs(), // TODO MAGIC NUMBER + isAbsolute = true, + type = PumpSync.TemporaryBasalType.PUMP_SUSPEND, + pumpId = basalStartTime, + pumpType = pumpType, + pumpSerial = pumpSN.toString(radix = 16) + ) + aapsLogger.debug( + LTag.PUMPCOMM, + "handleBasalStatusUpdate: ${if (newRecord) "**NEW** " else ""}EVENT TEMP_END ($basalType) ${dateUtil.dateAndTimeString(basalStartTime)} ($basalStartTime)" + ) + } + + // Update medtrum pump state lastBasalType = basalType lastBasalRate = basalRate lastBasalSequence = basalSequence - lastKnownSequenceNumber = basalSequence + if (basalSequence > currentSequenceNumber) { + currentSequenceNumber = basalSequence + } lastBasalPatchId = basalPatchId + if (basalPatchId != patchId) { + aapsLogger.error(LTag.PUMP, "handleBasalStatusUpdate: WTF? PatchId in status update does not match current patchId!") + } lastBasalStartTime = basalStartTime - // TODO Handle history } - fun handleStopStatusUpdate(stopSequence: Int, stopPatchId: Int) { + fun handleStopStatusUpdate(stopSequence: Int, stopPatchId: Long) { aapsLogger.debug(LTag.PUMP, "handleStopStatusUpdate: stopSequence: $stopSequence stopPatchId: $stopPatchId") lastStopSequence = stopSequence - lastKnownSequenceNumber = stopSequence + if (stopSequence > currentSequenceNumber) { + currentSequenceNumber = stopSequence + } lastStopPatchId = stopPatchId - // TODO Handle history + if (stopPatchId != patchId) { + aapsLogger.error(LTag.PUMP, "handleStopStatusUpdate: WTF? PatchId in status update does not match current patchId!") + } } } diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/enums/BasalType.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/enums/BasalType.kt new file mode 100644 index 0000000000..7379b03609 --- /dev/null +++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/enums/BasalType.kt @@ -0,0 +1,58 @@ +package info.nightscout.pump.medtrum.comm.enums + +enum class BasalType { + NONE, + STANDARD, + EXERCISE, + HOLIDAY, + PROGRAM_A, + PROGRAM_B, + ABSOLUTE_TEMP, + RELATIVE_TEMP, + PROGRAM_C, + PROGRAM_D, + SICK, + AUTO, + NEW, + SUSPEND_LOW_GLUCOSE, + SUSPEND_PREDICT_LOW_GLUCOSE, + SUSPEND_AUTO, + SUSPEND_MORE_THAN_MAX_PER_HOUR, + SUSPEND_MORE_THAN_MAX_PER_DAY, + SUSPEND_MANUAL, + SUSPEND_KEY_LOST, + STOP_OCCLUSION, + STOP_EXPIRED, + STOP_EMPTY, + STOP_PATCH_FAULT, + STOP_PATCH_FAULT2, + STOP_BASE_FAULT, + STOP_DISCARD, + STOP_BATTERY_EXHAUSTED, + STOP, + PAUSE_INTERRUPT, + PRIME, + AUTO_MODE_START, + AUTO_MODE_EXIT, + AUTO_MODE_TARGET_100, + AUTO_MODE_TARGET_110, + AUTO_MODE_TARGET_120, + AUTO_MODE_BREAKFAST, + AUTO_MODE_LUNCH, + AUTO_MODE_DINNER, + AUTO_MODE_SNACK, + AUTO_MODE_EXERCISE_START, + AUTO_MODE_EXERCISE_EXIT; + + fun getValue(): Int { + return ordinal + } + + fun isTempBasal(): Boolean { + return this == ABSOLUTE_TEMP || this == RELATIVE_TEMP + } + + fun isSuspendedByPump(): Boolean { + return this in SUSPEND_LOW_GLUCOSE..STOP + } +} 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 6b5c00d117..627c7f2344 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 @@ -5,6 +5,7 @@ import info.nightscout.interfaces.pump.DetailedBolusInfo import info.nightscout.interfaces.pump.PumpSync import info.nightscout.pump.medtrum.MedtrumPump import info.nightscout.pump.medtrum.comm.enums.CommandType.ACTIVATE +import info.nightscout.pump.medtrum.comm.enums.BasalType import info.nightscout.pump.medtrum.extension.toByteArray import info.nightscout.pump.medtrum.extension.toByte import info.nightscout.interfaces.stats.TddCalculator @@ -85,15 +86,17 @@ class ActivatePacket(injector: HasAndroidInjector, private val basalProfile: Byt val medtrumTimeUtil = MedtrumTimeUtil() val patchId = data.copyOfRange(RESP_PATCH_ID_START, RESP_PATCH_ID_END).toLong() - val time = medtrumTimeUtil.convertPumpTimeToSystemTimeSeconds(data.copyOfRange(RESP_TIME_START, RESP_TIME_END).toLong()) - val basalType = data.copyOfRange(RESP_BASAL_TYPE_START, RESP_BASAL_TYPE_END).toInt() + val time = medtrumTimeUtil.convertPumpTimeToSystemTimeMillis(data.copyOfRange(RESP_TIME_START, RESP_TIME_END).toLong()) + val basalType = enumValues()[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()) + val basalPatchId = data.copyOfRange(RESP_BASAL_PATCH_ID_START, RESP_BASAL_PATCH_ID_END).toLong() + val basalStartTime = medtrumTimeUtil.convertPumpTimeToSystemTimeMillis(data.copyOfRange(RESP_BASAL_START_TIME_START, RESP_BASAL_START_TIME_END).toLong()) medtrumPump.patchId = patchId medtrumPump.lastTimeReceivedFromPump = time + medtrumPump.currentSequenceNumber = basalSequence // We are activated, set the new seq nr + medtrumPump.syncedSequenceNumber = basalSequence // We are activated, reset the synced seq nr () // Update the actual basal profile medtrumPump.actualBasalProfile = basalProfile // TODO: Handle history entry diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/CancelTempBasalPacket.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/CancelTempBasalPacket.kt index 0f5aa165db..d5ca93c993 100644 --- a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/CancelTempBasalPacket.kt +++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/CancelTempBasalPacket.kt @@ -3,9 +3,11 @@ 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.CANCEL_TEMP_BASAL +import info.nightscout.pump.medtrum.comm.enums.BasalType import info.nightscout.pump.medtrum.extension.toInt import info.nightscout.pump.medtrum.extension.toLong import info.nightscout.pump.medtrum.util.MedtrumTimeUtil +import info.nightscout.rx.logging.LTag import javax.inject.Inject class CancelTempBasalPacket(injector: HasAndroidInjector) : MedtrumPacket(injector) { @@ -34,11 +36,11 @@ class CancelTempBasalPacket(injector: HasAndroidInjector) : MedtrumPacket(inject override fun handleResponse(data: ByteArray): Boolean { val success = super.handleResponse(data) if (success) { - val basalType = data.copyOfRange(RESP_BASAL_TYPE_START, RESP_BASAL_TYPE_END).toInt() + val basalType = enumValues()[data.copyOfRange(RESP_BASAL_TYPE_START, RESP_BASAL_TYPE_END).toInt()] val basalRate = data.copyOfRange(RESP_BASAL_RATE_START, RESP_BASAL_RATE_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()) + val basalPatchId = data.copyOfRange(RESP_BASAL_PATCH_ID_START, RESP_BASAL_PATCH_ID_END).toLong() + val basalStartTime = MedtrumTimeUtil().convertPumpTimeToSystemTimeMillis(data.copyOfRange(RESP_BASAL_START_TIME_START, RESP_BASAL_START_TIME_END).toLong()) medtrumPump.handleBasalStatusUpdate(basalType, basalRate, basalSequence, basalPatchId, basalStartTime) } diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/GetRecordPacket.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/GetRecordPacket.kt index 9aa9c2e2be..d817895fa5 100644 --- a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/GetRecordPacket.kt +++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/GetRecordPacket.kt @@ -1,16 +1,26 @@ package info.nightscout.pump.medtrum.comm.packets import dagger.android.HasAndroidInjector +import info.nightscout.interfaces.pump.PumpSync +import info.nightscout.interfaces.pump.TemporaryBasalStorage import info.nightscout.pump.medtrum.MedtrumPump import info.nightscout.pump.medtrum.comm.enums.CommandType.GET_RECORD +import info.nightscout.pump.medtrum.comm.enums.BasalType import info.nightscout.pump.medtrum.extension.toByteArray import info.nightscout.pump.medtrum.extension.toInt import info.nightscout.pump.medtrum.extension.toLong +import info.nightscout.pump.medtrum.util.MedtrumTimeUtil +import info.nightscout.rx.logging.LTag +import info.nightscout.shared.utils.DateUtil +import info.nightscout.shared.utils.T import javax.inject.Inject class GetRecordPacket(injector: HasAndroidInjector, private val recordIndex: Int) : MedtrumPacket(injector) { @Inject lateinit var medtrumPump: MedtrumPump + @Inject lateinit var pumpSync: PumpSync + @Inject lateinit var temporaryBasalStorage: TemporaryBasalStorage + @Inject lateinit var dateUtil: DateUtil companion object { @@ -20,11 +30,29 @@ class GetRecordPacket(injector: HasAndroidInjector, private val recordIndex: Int private const val RESP_RECORD_UNKNOWN_END = RESP_RECORD_UNKNOWN_START + 1 private const val RESP_RECORD_TYPE_START = RESP_RECORD_UNKNOWN_END private const val RESP_RECORD_TYPE_END = RESP_RECORD_TYPE_START + 1 - private const val RESP_RECORD_SERIAL_START = RESP_RECORD_TYPE_END + private const val RESP_RECORD_UNKNOWN1_START = RESP_RECORD_TYPE_END + private const val RESP_RECORD_UNKNOWN1_END = RESP_RECORD_UNKNOWN1_START + 1 + private const val RESP_RECORD_SERIAL_START = RESP_RECORD_UNKNOWN1_END private const val RESP_RECORD_SERIAL_END = RESP_RECORD_SERIAL_START + 4 private const val RESP_RECORD_PATCHID_START = RESP_RECORD_SERIAL_END private const val RESP_RECORD_PATCHID_END = RESP_RECORD_PATCHID_START + 2 - private const val RESP_RECORD_DATA_START = RESP_RECORD_PATCHID_END + private const val RESP_RECORD_SEQUENCE_START = RESP_RECORD_PATCHID_END + private const val RESP_RECORD_SEQUENCE_END = RESP_RECORD_SEQUENCE_START + 2 + private const val RESP_RECORD_DATA_START = RESP_RECORD_SEQUENCE_END + + private const val VALID_HEADER = 170 + private const val BOLUS_RECORD = 1 + private const val BOLUS_RECORD_ALT = 65 + private const val BASAL_RECORD = 2 + private const val BASAL_RECORD_ALT = 66 + private const val ALARM_RECORD = 3 + private const val AUTO_RECORD = 4 + private const val TIME_SYNC_RECORD = 5 + private const val AUTO1_RECORD = 6 + private const val AUTO2_RECORD = 7 + private const val AUTO3_RECORD = 8 + private const val TDD_RECORD = 9 + } init { @@ -44,8 +72,137 @@ class GetRecordPacket(injector: HasAndroidInjector, private val recordIndex: Int val recordType = data.copyOfRange(RESP_RECORD_TYPE_START, RESP_RECORD_TYPE_END).toInt() val recordSerial = data.copyOfRange(RESP_RECORD_SERIAL_START, RESP_RECORD_SERIAL_END).toLong() val recordPatchId = data.copyOfRange(RESP_RECORD_PATCHID_START, RESP_RECORD_PATCHID_END).toInt() + val recordSequence = data.copyOfRange(RESP_RECORD_SEQUENCE_START, RESP_RECORD_SEQUENCE_END).toInt() - // TODO Handle history records + aapsLogger.debug( + LTag.PUMPCOMM, + "GetRecordPacket HandleResponse: Record header: $recordHeader, unknown: $recordUnknown, type: $recordType, serial: $recordSerial, patchId: $recordPatchId, " + "sequence: $recordSequence" + ) + + medtrumPump.syncedSequenceNumber = recordSequence // Assume sync upwards + + if (recordHeader == VALID_HEADER) { + when (recordType) { + BOLUS_RECORD, BOLUS_RECORD_ALT -> { + aapsLogger.debug(LTag.PUMPCOMM, "GetRecordPacket HandleResponse: BOLUS_RECORD") + } + + BASAL_RECORD, BASAL_RECORD_ALT -> { + val medtrumTimeUtil = MedtrumTimeUtil() + val basalStartTime = medtrumTimeUtil.convertPumpTimeToSystemTimeMillis(data.copyOfRange(RESP_RECORD_DATA_START, RESP_RECORD_DATA_START + 4).toLong()) + val basalEndTime = medtrumTimeUtil.convertPumpTimeToSystemTimeMillis(data.copyOfRange(RESP_RECORD_DATA_START + 4, RESP_RECORD_DATA_START + 8).toLong()) + val basalType = enumValues()[data.copyOfRange(RESP_RECORD_DATA_START + 8, RESP_RECORD_DATA_START + 9).toInt()] + val basalEndReason = data.copyOfRange(RESP_RECORD_DATA_START + 9, RESP_RECORD_DATA_START + 10).toInt() + val basalRate = data.copyOfRange(RESP_RECORD_DATA_START + 10, RESP_RECORD_DATA_START + 12).toInt() * 0.05 + val basalDelivered = data.copyOfRange(RESP_RECORD_DATA_START + 12, RESP_RECORD_DATA_START + 14).toInt() * 0.05 + val basalPercent = data.copyOfRange(RESP_RECORD_DATA_START + 14, RESP_RECORD_DATA_START + 16).toInt() + + aapsLogger.debug( + LTag.PUMPCOMM, + "GetRecordPacket HandleResponse: BASAL_RECORD: Start: $basalStartTime, End: $basalEndTime, Type: $basalType, EndReason: $basalEndReason, Rate: $basalRate, Delivered: $basalDelivered, Percent: $basalPercent" + ) + + when (basalType) { + BasalType.STANDARD -> { + aapsLogger.debug(LTag.PUMPCOMM, "GetRecordPacket HandleResponse: BASAL_RECORD: Standard basal") + // If we are here it means the basal has ended + } + + BasalType.ABSOLUTE_TEMP, BasalType.RELATIVE_TEMP -> { + aapsLogger.debug(LTag.PUMPCOMM, "GetRecordPacket HandleResponse: BASAL_RECORD: Absolute temp basal") + val duration = (basalEndTime - basalStartTime) + if (duration > 0) { // Sync start and end + val newRecord = pumpSync.syncTemporaryBasalWithPumpId( + timestamp = basalStartTime, + rate = if (basalType == BasalType.ABSOLUTE_TEMP) basalRate else basalPercent.toDouble(), + duration = duration, + isAbsolute = (basalType == BasalType.ABSOLUTE_TEMP), + type = PumpSync.TemporaryBasalType.NORMAL, + pumpId = basalStartTime, + pumpType = medtrumPump.pumpType, + pumpSerial = medtrumPump.pumpSN.toString(radix = 16) + ) + aapsLogger.debug( + LTag.PUMPCOMM, + "handleBasalStatusUpdate from record: ${if (newRecord) "**NEW** " else ""}EVENT TEMP_SYNC: ($basalType) ${dateUtil.dateAndTimeString(basalStartTime)} ($basalStartTime) " + + "Rate: $basalRate Duration: ${duration}min" + ) + } else { // Sync only end ? + pumpSync.syncStopTemporaryBasalWithPumpId( + timestamp = basalEndTime, + endPumpId = basalEndTime, + pumpType = medtrumPump.pumpType, + pumpSerial = medtrumPump.pumpSN.toString(radix = 16) + ) + aapsLogger.warn( + LTag.PUMPCOMM, + "handleBasalStatusUpdate from record: EVENT TEMP_END ($basalType) ${dateUtil.dateAndTimeString(basalEndTime)} ($basalEndTime) " + "Rate: $basalRate Duration: ${duration}min" + ) + } + } + in BasalType.SUSPEND_LOW_GLUCOSE..BasalType.STOP -> { + aapsLogger.debug(LTag.PUMPCOMM, "GetRecordPacket HandleResponse: BASAL_RECORD: Suspend basal") + val duration = (basalEndTime - basalStartTime) + val newRecord = pumpSync.syncTemporaryBasalWithPumpId( + timestamp = basalEndTime, + rate = 0.0, + duration = duration, + isAbsolute = true, + type = PumpSync.TemporaryBasalType.PUMP_SUSPEND, + pumpId = basalStartTime, + pumpType = medtrumPump.pumpType, + pumpSerial = medtrumPump.pumpSN.toString(radix = 16) + ) + aapsLogger.debug( + LTag.PUMPCOMM, + "handleBasalStatusUpdate from record: ${if (newRecord) "**NEW** " else ""}EVENT SUSPEND: ($basalType) ${dateUtil.dateAndTimeString(basalStartTime)} ($basalStartTime) " + + "Rate: $basalRate Duration: ${duration}min" + ) + + } + else -> { + aapsLogger.error(LTag.PUMPCOMM, "GetRecordPacket HandleResponse: BASAL_RECORD: Unknown basal type: $basalType") + // TODO: Error warning + } + } + } + + ALARM_RECORD -> { + aapsLogger.debug(LTag.PUMPCOMM, "GetRecordPacket HandleResponse: ALARM_RECORD") + } + + AUTO_RECORD -> { + aapsLogger.debug(LTag.PUMPCOMM, "GetRecordPacket HandleResponse: AUTO_RECORD") + } + + TIME_SYNC_RECORD -> { + aapsLogger.debug(LTag.PUMPCOMM, "GetRecordPacket HandleResponse: TIME_SYNC_RECORD") + } + + AUTO1_RECORD -> { + aapsLogger.debug(LTag.PUMPCOMM, "GetRecordPacket HandleResponse: AUTO1_RECORD") + } + + AUTO2_RECORD -> { + aapsLogger.debug(LTag.PUMPCOMM, "GetRecordPacket HandleResponse: AUTO2_RECORD") + } + + AUTO3_RECORD -> { + aapsLogger.debug(LTag.PUMPCOMM, "GetRecordPacket HandleResponse: AUTO3_RECORD") + } + + TDD_RECORD -> { + aapsLogger.debug(LTag.PUMPCOMM, "GetRecordPacket HandleResponse: TDD_RECORD") + } + + else -> { + aapsLogger.debug(LTag.PUMPCOMM, "GetRecordPacket HandleResponse: Unknown record type: $recordType") + } + } + + } else { + aapsLogger.error(LTag.PUMPCOMM, "GetRecordPacket HandleResponse: Invalid record header") + } } return success diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/GetTimePacket.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/GetTimePacket.kt index cedaf6fdbe..45efb5c30b 100644 --- a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/GetTimePacket.kt +++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/GetTimePacket.kt @@ -25,7 +25,7 @@ class GetTimePacket(injector: HasAndroidInjector) : MedtrumPacket(injector) { override fun handleResponse(data: ByteArray): Boolean { val success = super.handleResponse(data) if (success) { - val time = MedtrumTimeUtil().convertPumpTimeToSystemTimeSeconds(data.copyOfRange(RESP_TIME_START, RESP_TIME_END).toLong()) + val time = MedtrumTimeUtil().convertPumpTimeToSystemTimeMillis(data.copyOfRange(RESP_TIME_START, RESP_TIME_END).toLong()) medtrumPump.lastTimeReceivedFromPump = time } 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 3898488ea4..37f9ec0465 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 @@ -2,6 +2,7 @@ package info.nightscout.pump.medtrum.comm.packets import dagger.android.HasAndroidInjector import info.nightscout.pump.medtrum.MedtrumPump +import info.nightscout.pump.medtrum.comm.enums.BasalType import info.nightscout.pump.medtrum.comm.enums.MedtrumPumpState import info.nightscout.pump.medtrum.extension.toByteArray import info.nightscout.pump.medtrum.extension.toInt @@ -98,7 +99,7 @@ class NotificationPacket(val injector: HasAndroidInjector) { var bolusData = data.copyOfRange(offset, offset + 1).toInt() var bolusType = bolusData and 0x7F 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: // If bolus is known add status // If bolus is not known start read bolus @@ -115,18 +116,20 @@ class NotificationPacket(val injector: HasAndroidInjector) { if (fieldMask and MASK_BASAL != 0) { aapsLogger.debug(LTag.PUMPCOMM, "Basal notification received") - var basalType = data.copyOfRange(offset, offset + 1).toInt() + val basalType = enumValues()[data.copyOfRange(offset, offset + 1).toInt()] var basalSequence = data.copyOfRange(offset + 1, offset + 3).toInt() - var basalPatchId = data.copyOfRange(offset + 3, offset + 5).toInt() - var basalInterval = data.copyOfRange(offset + 5, offset + 9).toInt() + var basalPatchId = data.copyOfRange(offset + 3, offset + 5).toLong() + var basalTime = MedtrumTimeUtil().convertPumpTimeToSystemTimeMillis(data.copyOfRange(offset + 5, offset + 9).toLong()) var basalRateAndDelivery = data.copyOfRange(offset + 9, offset + 12).toInt() var basalRate = (basalRateAndDelivery and 0xFFF) * 0.05 var basalDelivery = (basalRateAndDelivery shr 12) * 0.05 aapsLogger.debug( LTag.PUMPCOMM, - "Basal type: $basalType, basal sequence: $basalSequence, basal patch id: $basalPatchId, basal interval: $basalInterval, basal rate: $basalRate, basal delivery: $basalDelivery" + "Basal type: $basalType, basal sequence: $basalSequence, basal patch id: $basalPatchId, basal time: $basalTime, basal rate: $basalRate, basal delivery: $basalDelivery" ) - // TODO Sync basal flow + // TODO: Check if basal is known, if not add it + // medtrumPump.handleBasalStatusUpdate(basalType, basalRate, basalSequence, basalPatchId, basalTime) + // TODO: Handle basal delivery offset += 12 } @@ -165,9 +168,16 @@ class NotificationPacket(val injector: HasAndroidInjector) { if (fieldMask and MASK_STORAGE != 0) { aapsLogger.debug(LTag.PUMPCOMM, "Storage notification received") // TODO, trigger check for new sequence? - medtrumPump.lastKnownSequenceNumber = data.copyOfRange(offset, offset + 2).toInt() - medtrumPump.patchId = data.copyOfRange(offset + 2, offset + 4).toLong() - aapsLogger.debug(LTag.PUMPCOMM, "Last known sequence number: ${medtrumPump.lastKnownSequenceNumber}, patch id: ${medtrumPump.patchId}") + val sequence = data.copyOfRange(offset, offset + 2).toInt() + if (sequence > medtrumPump.currentSequenceNumber) { + medtrumPump.currentSequenceNumber = sequence + } + val patchId = data.copyOfRange(offset + 2, offset + 4).toLong() + if (patchId != medtrumPump.patchId) { + aapsLogger.error(LTag.PUMPCOMM, "handleMaskedMessage: WTF? We got wrong patch id!") + // TODO: We should terminate session or stop patch here? or at least throw error? THis can be thrown during activation process though + } + aapsLogger.debug(LTag.PUMPCOMM, "Last known sequence number: ${medtrumPump.currentSequenceNumber}, patch id: ${patchId}") offset += 4 } @@ -182,7 +192,7 @@ class NotificationPacket(val injector: HasAndroidInjector) { if (fieldMask and MASK_START_TIME != 0) { aapsLogger.debug(LTag.PUMPCOMM, "Start time notification received") - medtrumPump.patchStartTime = MedtrumTimeUtil().convertPumpTimeToSystemTimeSeconds(data.copyOfRange(offset, offset + 4).toLong()) + medtrumPump.patchStartTime = MedtrumTimeUtil().convertPumpTimeToSystemTimeMillis(data.copyOfRange(offset, offset + 4).toLong()) aapsLogger.debug(LTag.PUMPCOMM, "Patch start time: ${medtrumPump.patchStartTime}") offset += 4 } 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 6023a2fafb..8d7a400ea5 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,6 +3,7 @@ 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.comm.enums.BasalType import info.nightscout.pump.medtrum.extension.toInt import info.nightscout.pump.medtrum.extension.toLong import info.nightscout.pump.medtrum.util.MedtrumTimeUtil @@ -41,11 +42,11 @@ class SetBasalProfilePacket(injector: HasAndroidInjector, private val basalProfi val success = super.handleResponse(data) if (success) { val medtrumTimeUtil = MedtrumTimeUtil() - val basalType = data.copyOfRange(RESP_BASAL_TYPE_START, RESP_BASAL_TYPE_END).toInt() + val basalType = enumValues()[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()) + val basalPatchId = data.copyOfRange(RESP_BASAL_PATCH_ID_START, RESP_BASAL_PATCH_ID_END).toLong() + val basalStartTime = medtrumTimeUtil.convertPumpTimeToSystemTimeMillis(data.copyOfRange(RESP_BASAL_START_TIME_START, RESP_BASAL_START_TIME_END).toLong()) // Update the actual basal profile medtrumPump.actualBasalProfile = basalProfile diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/SetTempBasalPacket.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/SetTempBasalPacket.kt index 3d11c0ecba..f203f21a4d 100644 --- a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/SetTempBasalPacket.kt +++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/SetTempBasalPacket.kt @@ -3,10 +3,12 @@ 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_TEMP_BASAL +import info.nightscout.pump.medtrum.comm.enums.BasalType import info.nightscout.pump.medtrum.extension.toByteArray import info.nightscout.pump.medtrum.extension.toInt import info.nightscout.pump.medtrum.extension.toLong import info.nightscout.pump.medtrum.util.MedtrumTimeUtil +import info.nightscout.rx.logging.LTag import javax.inject.Inject import kotlin.math.round @@ -49,11 +51,19 @@ class SetTempBasalPacket(injector: HasAndroidInjector, private val absoluteRate: override fun handleResponse(data: ByteArray): Boolean { val success = super.handleResponse(data) if (success) { - val basalType = data.copyOfRange(RESP_BASAL_TYPE_START, RESP_BASAL_TYPE_END).toInt() + val basalType = enumValues()[data.copyOfRange(RESP_BASAL_TYPE_START, RESP_BASAL_TYPE_END).toInt()] val basalRate = data.copyOfRange(RESP_BASAL_RATE_START, RESP_BASAL_RATE_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()) + val basalPatchId = data.copyOfRange(RESP_BASAL_PATCH_ID_START, RESP_BASAL_PATCH_ID_END).toLong() + + val rawTime = data.copyOfRange(RESP_BASAL_START_TIME_START, RESP_BASAL_START_TIME_END).toLong() + val basalStartTime = MedtrumTimeUtil().convertPumpTimeToSystemTimeMillis(data.copyOfRange(RESP_BASAL_START_TIME_START, RESP_BASAL_START_TIME_END).toLong()) + aapsLogger.debug(LTag.PUMPCOMM, "Basal status update: type=$basalType, rate=$basalRate, sequence=$basalSequence, patchId=$basalPatchId, startTime=$basalStartTime, rawTime=$rawTime") + + // TODO: For debugging remove later + val pumpTime = MedtrumTimeUtil().getCurrentTimePumpSeconds() + val systemTime = System.currentTimeMillis() + aapsLogger.debug(LTag.PUMPCOMM, "Pump time: $pumpTime, System time: $systemTime") medtrumPump.handleBasalStatusUpdate(basalType, basalRate, basalSequence, basalPatchId, basalStartTime) } diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/StopPatchPacket.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/StopPatchPacket.kt index 9eb38417b0..dba47867a5 100644 --- a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/StopPatchPacket.kt +++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/packets/StopPatchPacket.kt @@ -4,6 +4,7 @@ import dagger.android.HasAndroidInjector import info.nightscout.pump.medtrum.MedtrumPump import info.nightscout.pump.medtrum.extension.toInt import info.nightscout.pump.medtrum.comm.enums.CommandType.STOP_PATCH +import info.nightscout.pump.medtrum.extension.toLong import javax.inject.Inject class StopPatchPacket(injector: HasAndroidInjector) : MedtrumPacket(injector) { @@ -27,7 +28,7 @@ class StopPatchPacket(injector: HasAndroidInjector) : MedtrumPacket(injector) { val success = super.handleResponse(data) if (success) { val stopSequence = data.copyOfRange(RESP_STOP_SEQUENCE_START, RESP_STOP_SEQUENCE_END).toInt() - val stopPatchId = data.copyOfRange(RESP_STOP_PATCH_ID_START, RESP_STOP_PATCH_ID_END).toInt() + val stopPatchId = data.copyOfRange(RESP_STOP_PATCH_ID_START, RESP_STOP_PATCH_ID_END).toLong() medtrumPump.handleStopStatusUpdate(stopSequence, stopPatchId) } 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 71c566ee38..19459453fc 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 @@ -116,8 +116,7 @@ class MedtrumService : DaggerService(), BLECommCallback { } fun startPrime(): Boolean { - val packet = PrimePacket(injector) - return sendPacketAndGetResponse(packet) + return sendPacketAndGetResponse(PrimePacket(injector)) } fun startActivate(): Boolean { @@ -141,18 +140,19 @@ class MedtrumService : DaggerService(), BLECommCallback { } fun readPumpStatus() { - // TODO read pump history - } + // TODO decide what we need to do here + var result = false - fun loadEvents(): PumpEnactResult { - if (!medtrumPlugin.isInitialized()) { - val result = PumpEnactResult(injector).success(false) - result.comment = "pump not initialized" - return result + // Most of these things are already done when a connection is setup, but wo dont know how long the pump was connected for? + // So just do a syncronize to make sure we have the latest data + result = sendPacketAndGetResponse(SynchronizePacket(injector)) + + // Sync records (based on the info we have from the sync) + if (result) result = syncRecords() + if (!result) { + aapsLogger.error(LTag.PUMPCOMM, "Failed to sync records") + return } - // TODO need this? Check - val result = PumpEnactResult(injector) - return result } fun bolus(insulin: Double, carbs: Int, carbTime: Long, t: EventOverviewBolusProgress.Treatment): Boolean { @@ -165,24 +165,59 @@ class MedtrumService : DaggerService(), BLECommCallback { // TODO } + fun setTempBasal(absoluteRate: Double, durationInMinutes: Int): Boolean { + var result = true + if (medtrumPump.tempBasalInProgress) { + result = sendPacketAndGetResponse(CancelTempBasalPacket(injector)) + } + if (result) result = sendPacketAndGetResponse(SetTempBasalPacket(injector, absoluteRate, durationInMinutes)) + + // Get history records, this will update the pump state + if (result) result = syncRecords() + + return result + } + + fun cancelTempBasal(): Boolean { + var result = false + + result = sendPacketAndGetResponse(CancelTempBasalPacket(injector)) + + // Get history records, this will update the pump state + if (result) result = syncRecords() + + return result + } + fun updateBasalsInPump(profile: Profile): Boolean { + var result = false val packet = medtrumPump.buildMedtrumProfileArray(profile)?.let { SetBasalProfilePacket(injector, it) } - return packet?.let { sendPacketAndGetResponse(it) } == true + result = packet?.let { sendPacketAndGetResponse(it) } == true + + // TODO: We might want to get rid of this and cancel the TBR before we set the basal profile + // Update basal affects the TBR records (the pump will cancel the TBR, set our basal profile, and resume the TBR in a new record) + // Get history records, this will update the pump state and add changes in TBR to AAPS history + if (result) result = syncRecords() + + return result } fun changePump() { aapsLogger.debug(LTag.PUMP, "changePump: called!") - try { - medtrumPump.pumpSN = sp.getString(info.nightscout.pump.medtrum.R.string.key_snInput, " ").toLong(radix = 16) - } catch (e: NumberFormatException) { - aapsLogger.debug(LTag.PUMP, "changePump: Invalid input!") - } - medtrumPump.setPatchActivatedState(sp.getBoolean(R.string.key_patch_activated, false)) - medtrumPump.patchSessionToken = sp.getLong(R.string.key_session_token, 0) - if (medtrumPump.patchActivated) { - aapsLogger.debug(LTag.PUMP, "changePump: Patch is already activated, setting as ACTIVE") - medtrumPump.pumpState = MedtrumPumpState.ACTIVE // Set inital status as active will be updated on first connection + medtrumPump.loadUserSettingsFromSP() + } + + /** This gets the history records from the pump */ + private fun syncRecords(): Boolean { + aapsLogger.debug(LTag.PUMP, "syncRecords: called!, syncedSequenceNumber: ${medtrumPump.syncedSequenceNumber}, currentSequenceNumber: ${medtrumPump.currentSequenceNumber}") + var result = false + // TODO: Check if we need to sync older records as well + // Note: medtrum app fetches all records when they sync? + for (sequence in medtrumPump.syncedSequenceNumber..medtrumPump.currentSequenceNumber) { + result = sendPacketAndGetResponse(GetRecordPacket(injector, sequence)) + if (!result) break } + return result } /** BLECommCallbacks */ @@ -206,7 +241,7 @@ class MedtrumService : DaggerService(), BLECommCallback { override fun onSendMessageError(reason: String) { aapsLogger.debug(LTag.PUMPCOMM, "<<<<< error during send message $reason") - // TODO + currentState.onSendMessageError(reason) } /** Service stuff */ @@ -270,6 +305,10 @@ class MedtrumService : DaggerService(), BLECommCallback { open fun waitForResponse(): Boolean { return false } + + open fun onSendMessageError(reason: String) { + aapsLogger.debug(LTag.PUMPCOMM, "onSendMessageError: " + this.toString() + "reason: $reason") + } } private inner class IdleState : State() { @@ -350,16 +389,18 @@ class MedtrumService : DaggerService(), BLECommCallback { override fun onIndication(data: ByteArray) { if (mPacket?.handleResponse(data) == true) { // Succes! - val currTimeSec = dateUtil.nowWithoutMilliseconds() / 1000 - if (abs(timeUtil.convertPumpTimeToSystemTimeSeconds(medtrumPump.lastTimeReceivedFromPump) - currTimeSec) <= 5) { // Allow 5 sec deviation + val currTime = dateUtil.nowWithoutMilliseconds() + if (abs(medtrumPump.lastTimeReceivedFromPump - currTime) <= T.secs(5).msecs()) { // Allow 5 sec deviation toState(SynchronizeState()) } else { aapsLogger.debug( LTag.PUMPCOMM, - "GetTimeState.onIndication need to set time. systemTime: $currTimeSec PumpTime: ${medtrumPump.lastTimeReceivedFromPump} Pump Time to system time: " + timeUtil.convertPumpTimeToSystemTimeSeconds( + "GetTimeState.onIndication need to set time. systemTime: $currTime PumpTime: ${medtrumPump.lastTimeReceivedFromPump} Pump Time to system time: " + timeUtil + .convertPumpTimeToSystemTimeMillis( medtrumPump.lastTimeReceivedFromPump ) ) + // TODO: Setting time cancels any TBR, so we need to handle that and cancel? or let AAPS handle time syncs? toState(SetTimeState()) } } else if (mPacket?.failed == true) { @@ -497,6 +538,12 @@ class MedtrumService : DaggerService(), BLECommCallback { responseSuccess = false } + override fun onSendMessageError(reason: String) { + super.onSendMessageError(reason) + responseHandled = true + responseSuccess = false + } + override fun waitForResponse(): Boolean { val startTime = System.currentTimeMillis() val timeoutMillis = T.secs(45).msecs() 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 66c66249e0..2e5503df3f 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 @@ -46,9 +46,9 @@ class MedtrumOverviewFragment : MedtrumBaseFragment requireContext().apply { val step = convertToPatchStep(medtrumPump.pumpState) // TODO is stil needed? - if (step != PatchStep.PREPARE_PATCH) { - aapsLogger.warn(LTag.PUMP, "MedtrumOverviewFragment: Patch already in activation process, going to $step") - } + // if (step != PatchStep.PREPARE_PATCH) { + // aapsLogger.warn(LTag.PUMP, "MedtrumOverviewFragment: Patch already in activation process, going to $step") + // } startActivity(MedtrumActivity.createIntentFromMenu(this, step)) } 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 2b2650427b..70416c5d87 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 @@ -173,8 +173,7 @@ class MedtrumViewModel @Inject constructor( } // New session, generate new session token aapsLogger.info(LTag.PUMP, "preparePatch: new session") - medtrumPump.patchSessionToken = Crypt().generateRandomToken() - sp.putLong(R.string.key_session_token, medtrumPump.patchSessionToken) + medtrumPump.patchSessionToken = Crypt().generateRandomToken() // Connect to pump medtrumService?.connect("PreparePatch") } diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/util/MedtrumTimeUtil.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/util/MedtrumTimeUtil.kt index e470f90b63..ad3910f0ec 100644 --- a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/util/MedtrumTimeUtil.kt +++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/util/MedtrumTimeUtil.kt @@ -11,10 +11,16 @@ class MedtrumTimeUtil { return Duration.between(startInstant, currentInstant).seconds } - fun convertPumpTimeToSystemTimeSeconds(pumpTime: Long) : Long { + fun getCurrentTimePumpMillis() : Long { + val startInstant = Instant.parse("2014-01-01T00:00:00Z") + val currentInstant = Instant.now() + return Duration.between(startInstant, currentInstant).seconds * 1000 + } + + fun convertPumpTimeToSystemTimeMillis(pumpTime: Long) : Long { val startInstant = Instant.parse("2014-01-01T00:00:00Z") val pumpInstant = startInstant.plusSeconds(pumpTime) val epochInstant = Instant.EPOCH - return Duration.between(epochInstant, pumpInstant).seconds + return Duration.between(epochInstant, pumpInstant).seconds * 1000 } } diff --git a/pump/medtrum/src/main/res/values/strings.xml b/pump/medtrum/src/main/res/values/strings.xml index b9081caef4..d987e70c7a 100644 --- a/pump/medtrum/src/main/res/values/strings.xml +++ b/pump/medtrum/src/main/res/values/strings.xml @@ -1,10 +1,15 @@ - snInput - medtrumpump_settings - medtrum_patch_activated - medtrum_session_token + snInput + medtrumpump_settings + medtrum_patch_activated + medtrum_session_token + patch_id + actual_basal_profile + last_basal_type + current_sequence_number + synced_sequence_number Medtrum MEDTRUM diff --git a/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/MedtrumTestBase.kt b/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/MedtrumTestBase.kt index b7494eb858..fe5ffd7c9d 100644 --- a/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/MedtrumTestBase.kt +++ b/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/MedtrumTestBase.kt @@ -4,6 +4,7 @@ import info.nightscout.androidaps.TestBaseWithProfile import info.nightscout.shared.sharedPreferences.SP import info.nightscout.interfaces.profile.Instantiator import info.nightscout.interfaces.pump.PumpSync +import info.nightscout.interfaces.pump.TemporaryBasalStorage import info.nightscout.interfaces.stats.TddCalculator import org.junit.jupiter.api.BeforeEach import org.mockito.Mock @@ -14,11 +15,12 @@ open class MedtrumTestBase: TestBaseWithProfile() { @Mock lateinit var instantiator: Instantiator @Mock lateinit var tddCalculator: TddCalculator @Mock lateinit var pumpSync: PumpSync + @Mock lateinit var temporaryBasalStorage: TemporaryBasalStorage lateinit var medtrumPump: MedtrumPump @BeforeEach fun setup() { - medtrumPump = MedtrumPump(aapsLogger, sp, dateUtil) + medtrumPump = MedtrumPump(aapsLogger, sp, dateUtil, pumpSync, temporaryBasalStorage) } } diff --git a/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/packets/AuthorizePacketTest.kt b/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/packets/AuthorizePacketTest.kt index c72fcb4ecb..7c07339220 100644 --- a/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/packets/AuthorizePacketTest.kt +++ b/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/packets/AuthorizePacketTest.kt @@ -2,6 +2,7 @@ package info.nightscout.pump.medtrum.comm.packets import dagger.android.AndroidInjector import dagger.android.HasAndroidInjector +import info.nightscout.pump.medtrum.MedtrumPump import info.nightscout.pump.medtrum.MedtrumTestBase import info.nightscout.pump.medtrum.extension.toByteArray import org.junit.jupiter.api.Test @@ -23,7 +24,9 @@ class AuthorizePacketTest : MedtrumTestBase() { @Test fun getRequestGivenPacketAndSNWhenCalledThenReturnAuthorizePacket() { // Inputs val opCode = 5 - medtrumPump.pumpSN = 2859923929 + val _pumpSN = MedtrumPump::class.java.getDeclaredField("_pumpSN") + _pumpSN.isAccessible = true + _pumpSN.setLong(medtrumPump, 2859923929) medtrumPump.patchSessionToken = 667 // Call diff --git a/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/packets/GetTimePacketTest.kt b/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/packets/GetTimePacketTest.kt index 4fff76bc90..36c9751bb8 100644 --- a/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/packets/GetTimePacketTest.kt +++ b/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/packets/GetTimePacketTest.kt @@ -48,7 +48,7 @@ class GetTimePacketTest : MedtrumTestBase() { // Expected values assertEquals(true, result) assertEquals(false, packet.failed) - assertEquals(MedtrumTimeUtil().convertPumpTimeToSystemTimeSeconds(time), medtrumPump.lastTimeReceivedFromPump) + assertEquals(MedtrumTimeUtil().convertPumpTimeToSystemTimeMillis(time), medtrumPump.lastTimeReceivedFromPump) } @Test fun handleResponseGivenResponseWhenMessageTooShortThenResultFalse() {