From 98daf4b12f0329a710eebce7efc9c22dcd6bdfe0 Mon Sep 17 00:00:00 2001 From: jbr7rr <> Date: Sat, 23 Sep 2023 08:55:43 +0200 Subject: [PATCH] Medtrum: Sync stops (on pump errors) with proper timestamp instead of just fakeTBR --- .../nightscout/pump/medtrum/MedtrumPump.kt | 13 +++- .../pump/medtrum/comm/enums/BasalEndReason.kt | 4 +- .../pump/medtrum/comm/enums/BasalType.kt | 15 ++++- .../medtrum/comm/packets/GetRecordPacket.kt | 18 ++++-- .../pump/medtrum/services/MedtrumService.kt | 23 ++++--- .../pump/medtrum/MedtrumPumpTest.kt | 61 ++++++++++++++++-- .../comm/packets/GetRecordPacketTest.kt | 62 +++++++++++++++++++ 7 files changed, 173 insertions(+), 23 deletions(-) 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 ad537792a5..795989b650 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 @@ -242,7 +242,7 @@ class MedtrumPump @Inject constructor( var bolusingTreatment: EventOverviewBolusProgress.Treatment? = null // actually delivered treatment var bolusProgressLastTimeStamp: Long = 0 // timestamp of last bolus progress message var bolusStopped = false // bolus stopped by user - var bolusDone = false // Bolus completed or stopped on pump + var bolusDone = true // Bolus completed or stopped on pump, initialize as true as to don't show bolus on init private val _bolusAmountDelivered = MutableStateFlow(0.0) val bolusAmountDeliveredFlow: StateFlow = _bolusAmountDelivered @@ -426,6 +426,10 @@ class MedtrumPump @Inject constructor( } basalType.isSuspendedByPump() && expectedTemporaryBasal?.pumpId != basalStartTime -> { + if (expectedTemporaryBasal != null && expectedTemporaryBasal.timestamp > basalStartTime && expectedTemporaryBasal.duration == T.mins(FAKE_TBR_LENGTH).msecs()) { + aapsLogger.debug(LTag.PUMPCOMM, "handleBasalStatusUpdate: invalidateTemporaryBasal: ${expectedTemporaryBasal.timestamp}") + pumpSync.invalidateTemporaryBasalWithPumpId(expectedTemporaryBasal.pumpId ?: 0L, expectedTemporaryBasal.pumpType, expectedTemporaryBasal.pumpSerial) + } val newRecord = pumpSync.syncTemporaryBasalWithPumpId( timestamp = basalStartTime, rate = 0.0, @@ -438,7 +442,8 @@ class MedtrumPump @Inject constructor( ) aapsLogger.debug( LTag.PUMPCOMM, - "handleBasalStatusUpdate: ${newRecordInfo(newRecord)}EVENT TEMP_START ($basalType) ${dateUtil.dateAndTimeString(basalStartTime)} ($basalStartTime) expectedTemporaryBasal: $expectedTemporaryBasal" + "handleBasalStatusUpdate: ${newRecordInfo(newRecord)}EVENT TEMP_START ($basalType) ${dateUtil.dateAndTimeString(basalStartTime)} ($basalStartTime) " + + "expectedTemporaryBas$expectedTemporaryBasal" ) } @@ -487,10 +492,12 @@ class MedtrumPump @Inject constructor( } } - fun setFakeTBRIfNeeded() { + fun setFakeTBRIfNotSet() { val expectedTemporaryBasal = pumpSync.expectedPumpState().temporaryBasal if (expectedTemporaryBasal?.duration != T.mins(FAKE_TBR_LENGTH).msecs()) { setFakeTBR() + _lastBasalType.value = BasalType.NONE + _lastBasalRate.value = 0.0 } } diff --git a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/enums/BasalEndReason.kt b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/enums/BasalEndReason.kt index 632f49608e..170fee7729 100644 --- a/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/enums/BasalEndReason.kt +++ b/pump/medtrum/src/main/java/info/nightscout/pump/medtrum/comm/enums/BasalEndReason.kt @@ -14,7 +14,7 @@ enum class BasalEndReason { STOP_PATCH_FAULT, STOP_PATCH_FAULT2, STOP_BASE_FAULT, - STOP_PATCH_BATTERY_EXAUSTED, + STOP_PATCH_BATTERY_EMPTY, STOP_MAG_SENSOR_NO_CALIBRATION, STOP, STOP_LOW_BATTERY, @@ -29,6 +29,6 @@ enum class BasalEndReason { AUTO_MODE_EXIT_MAX_DELIVERY_TOO_LONG; fun isSuspendedByPump(): Boolean { - return this in SUSPEND_LOW_GLUCOSE..SUSPEND_MANUAL + return this in SUSPEND_LOW_GLUCOSE..STOP_DISCARD } } 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 index 3490f0531f..9d59d9853a 100644 --- 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 @@ -28,7 +28,7 @@ enum class BasalType { STOP_PATCH_FAULT2, STOP_BASE_FAULT, STOP_DISCARD, - STOP_BATTERY_EXHAUSTED, + STOP_BATTERY_EMPTY, STOP, PAUSE_INTERRUPT, PRIME, @@ -62,6 +62,19 @@ enum class BasalType { BasalEndReason.SUSPEND_MORE_THAN_MAX_PER_HOUR -> SUSPEND_MORE_THAN_MAX_PER_HOUR BasalEndReason.SUSPEND_MORE_THAN_MAX_PER_DAY -> SUSPEND_MORE_THAN_MAX_PER_DAY BasalEndReason.SUSPEND_MANUAL -> SUSPEND_MANUAL + BasalEndReason.STOP_OCCLUSION -> STOP_OCCLUSION + BasalEndReason.STOP_EXPIRED -> STOP_EXPIRED + BasalEndReason.STOP_EMPTY -> STOP_EMPTY + BasalEndReason.STOP_PATCH_FAULT -> STOP_PATCH_FAULT + BasalEndReason.STOP_PATCH_FAULT2 -> STOP_PATCH_FAULT2 + BasalEndReason.STOP_BASE_FAULT -> STOP_BASE_FAULT + BasalEndReason.STOP_PATCH_BATTERY_EMPTY -> STOP_BATTERY_EMPTY + BasalEndReason.STOP_MAG_SENSOR_NO_CALIBRATION -> STOP + BasalEndReason.STOP_LOW_BATTERY -> STOP + BasalEndReason.STOP_AUTO_EXIT -> STOP + BasalEndReason.STOP_CANCEL -> STOP + BasalEndReason.STOP_LOW_SUPER_CAPACITOR -> STOP + BasalEndReason.STOP_DISCARD -> STOP_DISCARD else -> NONE } } 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 5aa1f12843..477fa7ee7e 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 @@ -267,7 +267,8 @@ class GetRecordPacket(injector: HasAndroidInjector, private val recordIndex: Int 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 = enumValues()[data.copyOfRange(RESP_RECORD_DATA_START + 9, RESP_RECORD_DATA_START + 10).toInt()] + val basalEndReasonInt = data.copyOfRange(RESP_RECORD_DATA_START + 9, RESP_RECORD_DATA_START + 10).toInt() + val basalEndReason = enumValues().getOrNull(basalEndReasonInt) 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() @@ -333,11 +334,18 @@ class GetRecordPacket(injector: HasAndroidInjector, private val recordIndex: Int } } - if (basalEndReason.isSuspendedByPump()) { - // Pump doesn't seem to sync suspend explicitly, so we need to do it here + if (basalEndReason == null) { + aapsLogger.error(LTag.PUMPCOMM, "GetRecordPacket HandleResponse: BASAL_RECORD: Unknown basal end reason: $basalEndReasonInt") + } else if (basalEndReason.isSuspendedByPump()) { + // Pump doesn't seem to sync suspend/stop explicitly, so we need to do it here // Sync suspend using handleBasalStatusUpdate to make sure other variables are updated as well - aapsLogger.warn(LTag.PUMPCOMM, "GetRecordPacket HandleResponse: Got suspended end reason, syncing suspend") - medtrumPump.handleBasalStatusUpdate(BasalType.fromBasalEndReason(basalEndReason), 0.0, recordSequence, recordPatchId, basalEndTime) + // Check if we don't have another temp basal running which is not a suspend + // (record maybe to old and information not valid). For suspends we want to update timestamp + val expectedTemporaryBasal = pumpSync.expectedPumpState().temporaryBasal + if (expectedTemporaryBasal == null || expectedTemporaryBasal.timestamp <= basalEndTime || expectedTemporaryBasal.duration == T.mins(MedtrumPump.FAKE_TBR_LENGTH).msecs()) { + aapsLogger.warn(LTag.PUMPCOMM, "GetRecordPacket HandleResponse: Got suspended end reason, syncing suspend") + medtrumPump.handleBasalStatusUpdate(BasalType.fromBasalEndReason(basalEndReason), 0.0, recordSequence, recordPatchId, basalEndTime) + } } } 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 b5165b47c4..d4c57eb9c3 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 @@ -133,7 +133,7 @@ class MedtrumService : DaggerService(), BLECommCallback { medtrumPump.deviceType = MedtrumSnUtil().getDeviceTypeFromSerial(medtrumPump.pumpSN) medtrumPump.resetPatchParameters() pumpSync.connectNewPump() - medtrumPump.setFakeTBRIfNeeded() + medtrumPump.setFakeTBRIfNotSet() } if (event.isChanged(rh.gs(R.string.key_alarm_setting)) || event.isChanged(rh.gs(R.string.key_patch_expiration)) @@ -584,7 +584,7 @@ class MedtrumService : DaggerService(), BLECommCallback { rh.gs(R.string.patch_not_active), Notification.URGENT, ) - medtrumPump.setFakeTBRIfNeeded() + medtrumPump.setFakeTBRIfNotSet() medtrumPump.clearAlarmState() // Reset sequence numbers, make sure AAPS history can be synced properly on next activation @@ -599,7 +599,7 @@ class MedtrumService : DaggerService(), BLECommCallback { MedtrumPumpState.EJECTED -> { rxBus.send(EventDismissNotification(Notification.PUMP_ERROR)) rxBus.send(EventDismissNotification(Notification.PUMP_SUSPENDED)) - medtrumPump.setFakeTBRIfNeeded() + medtrumPump.setFakeTBRIfNotSet() medtrumPump.clearAlarmState() } @@ -620,7 +620,8 @@ class MedtrumService : DaggerService(), BLECommCallback { rh.gs(R.string.pump_is_suspended), Notification.NORMAL, ) - // Pump will report proper TBR for this + // Pump will report proper TBR for this from loadEvents() + commandQueue.loadEvents(null) } MedtrumPumpState.HOURLY_MAX_SUSPENDED -> { @@ -629,7 +630,8 @@ class MedtrumService : DaggerService(), BLECommCallback { rh.gs(R.string.pump_is_suspended_hour_max), Notification.NORMAL, ) - // Pump will report proper TBR for this + // Pump will report proper TBR for this from loadEvents() + commandQueue.loadEvents(null) } MedtrumPumpState.DAILY_MAX_SUSPENDED -> { @@ -638,7 +640,8 @@ class MedtrumService : DaggerService(), BLECommCallback { rh.gs(R.string.pump_is_suspended_day_max), Notification.NORMAL, ) - // Pump will report proper TBR for this + // Pump will report proper TBR for this from loadEvents() + commandQueue.loadEvents(null) } MedtrumPumpState.OCCLUSION, @@ -658,7 +661,13 @@ class MedtrumService : DaggerService(), BLECommCallback { Notification.URGENT, info.nightscout.core.ui.R.raw.alarm ) - medtrumPump.setFakeTBRIfNeeded() + // Get pump status, use readStatus here as for loadEvents() we cannot be sure callback is executed + commandQueue.readStatus(rh.gs(info.nightscout.core.ui.R.string.device_changed), object : Callback() { + override fun run() { + // Make sure a 0 temp is set + medtrumPump.setFakeTBRIfNotSet() + } + }) } } } diff --git a/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/MedtrumPumpTest.kt b/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/MedtrumPumpTest.kt index 803bacf2ed..bc9e8e0952 100644 --- a/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/MedtrumPumpTest.kt +++ b/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/MedtrumPumpTest.kt @@ -337,7 +337,7 @@ class MedtrumPumpTest : MedtrumTestBase() { Assertions.assertEquals(basalStartTime, medtrumPump.lastBasalStartTime) } - @Test fun handleBasalStatusUpdateWhenBasalTypeIsSuspendedAndThenExpectNewData() { + @Test fun handleBasalStatusUpdateWhenBasalTypeIsSuspendedThenExpectNewData() { // Inputs val basalType = BasalType.SUSPEND_MORE_THAN_MAX_PER_DAY val basalRate = 0.0 @@ -432,6 +432,57 @@ class MedtrumPumpTest : MedtrumTestBase() { Assertions.assertEquals(basalStartTime, medtrumPump.lastBasalStartTime) } + @Test fun handleBasalStatusUpdateWhenBasalTypeIsSuspendedAndNewerFakeTBRThenExpectInvalidateAndNewData() { + // Inputs + val basalType = BasalType.SUSPEND_MORE_THAN_MAX_PER_DAY + val basalRate = 0.0 + val basalSequence = 123 + val basalPatchId = 1L + val basalStartTime = 1000L + val receivedTime = 1500L + + medtrumPump.deviceType = MedtrumSnUtil.MD_8301 + + // Mocks + val expectedTemporaryBasal: PumpSync.PumpState.TemporaryBasal = mock(PumpSync.PumpState.TemporaryBasal::class.java) + Mockito.`when`(expectedTemporaryBasal.pumpId).thenReturn(basalStartTime + T.mins(10).msecs()) // Ensure it's different + Mockito.`when`(expectedTemporaryBasal.timestamp).thenReturn(basalStartTime + T.mins(10).msecs()) // Newer Fake TBR + Mockito.`when`(expectedTemporaryBasal.duration).thenReturn(T.mins(4800L).msecs()) // Fake TBR duration + + Mockito.`when`(pumpSync.expectedPumpState()).thenReturn( + PumpSync.PumpState( + temporaryBasal = expectedTemporaryBasal, + extendedBolus = null, + bolus = null, + profile = null, + serialNumber = "someSerialNumber" + ) + ) + Mockito.`when`(temporaryBasalStorage.findTemporaryBasal(basalStartTime, basalRate)).thenReturn(null) + + // Call + medtrumPump.handleBasalStatusUpdate(basalType, basalRate, basalSequence, basalPatchId, basalStartTime, receivedTime) + + // Expected values + Mockito.verify(pumpSync).syncTemporaryBasalWithPumpId( + timestamp = basalStartTime, + rate = basalRate, + duration = T.mins(4800L).msecs(), + isAbsolute = true, + type = PumpSync.TemporaryBasalType.PUMP_SUSPEND, + pumpId = basalStartTime, + pumpType = PumpType.MEDTRUM_300U, + pumpSerial = medtrumPump.pumpSN.toString(radix = 16) + ) + + Assertions.assertEquals(basalType, medtrumPump.lastBasalType) + Assertions.assertEquals(basalRate, medtrumPump.lastBasalRate, 0.01) + Assertions.assertEquals(basalSequence, medtrumPump.lastBasalSequence) + Assertions.assertEquals(basalSequence, medtrumPump.currentSequenceNumber) + Assertions.assertEquals(basalPatchId, medtrumPump.lastBasalPatchId) + Assertions.assertEquals(basalStartTime, medtrumPump.lastBasalStartTime) + } + @Test fun handleBasalStatusUpdateWhenBasalTypeIsNoneAndThenExpectFakeTBR() { // Inputs val basalType = BasalType.NONE @@ -597,7 +648,7 @@ class MedtrumPumpTest : MedtrumTestBase() { Assertions.assertEquals(sequence, medtrumPump.currentSequenceNumber) } - @Test fun setFakeTBRIfNeededWhenNoFakeTBRAlreadyRunningExpectPumpSync() { + @Test fun setFakeTBRIfNotSetWhenNoFakeTBRAlreadyRunningExpectPumpSync() { // Inputs medtrumPump.deviceType = MedtrumSnUtil.MD_8301 @@ -616,7 +667,7 @@ class MedtrumPumpTest : MedtrumTestBase() { ) // Call - medtrumPump.setFakeTBRIfNeeded() + medtrumPump.setFakeTBRIfNotSet() // Expected values Mockito.verify(pumpSync).syncTemporaryBasalWithPumpId( @@ -631,7 +682,7 @@ class MedtrumPumpTest : MedtrumTestBase() { ) } - @Test fun setFakeTBRIfNeededWhenFakeTBRAlreadyRunningExpectNoPumpSync() { + @Test fun setFakeTBRIfNotSetWhenFakeTBRAlreadyRunningExpectNoPumpSync() { // Inputs medtrumPump.deviceType = MedtrumSnUtil.MD_8301 @@ -650,7 +701,7 @@ class MedtrumPumpTest : MedtrumTestBase() { ) // Call - medtrumPump.setFakeTBRIfNeeded() + medtrumPump.setFakeTBRIfNotSet() // Expected values Mockito.verify(pumpSync, Mockito.never()).syncTemporaryBasalWithPumpId( diff --git a/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/packets/GetRecordPacketTest.kt b/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/packets/GetRecordPacketTest.kt index ab2d3dd871..b6ee3914c4 100644 --- a/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/packets/GetRecordPacketTest.kt +++ b/pump/medtrum/src/test/java/info/nightscout/pump/medtrum/comm/packets/GetRecordPacketTest.kt @@ -287,6 +287,17 @@ class GetRecordPacketTest : MedtrumTestBase() { val data = byteArrayOf(35, 99, 8, 1, 0, 0, -86, 28, 2, -1, -39, -7, 118, -86, -85, 1, 4, 0, -117, 113, -16, 17, 9, 116, -16, 17, 1, 4, 10, 0, 2, 0, 0, 0, 57) val endTime = 1689505417000 + // Mocks + Mockito.`when`(pumpSync.expectedPumpState()).thenReturn( + PumpSync.PumpState( + temporaryBasal = null, + extendedBolus = null, + bolus = null, + profile = null, + serialNumber = "someSerialNumber" + ) + ) + // Call val packet = GetRecordPacket(packetInjector, 0) val result = packet.handleResponse(data) @@ -312,6 +323,17 @@ class GetRecordPacketTest : MedtrumTestBase() { val data = byteArrayOf(35, 99, 8, 1, 0, 0, -86, 28, 2, -1, -39, -7, 118, -86, -82, 1, 5, 0, 75, 24, -14, 17, 44, 27, -14, 17, 6, 4, 16, 0, 3, 0, 16, 0, -73) val endTime = 1689613740000 + // Mocks + Mockito.`when`(pumpSync.expectedPumpState()).thenReturn( + PumpSync.PumpState( + temporaryBasal = null, + extendedBolus = null, + bolus = null, + profile = null, + serialNumber = "someSerialNumber" + ) + ) + // Call val packet = GetRecordPacket(packetInjector, 0) val result = packet.handleResponse(data) @@ -333,6 +355,46 @@ class GetRecordPacketTest : MedtrumTestBase() { Assertions.assertEquals(false, packet.failed) } + @Test fun handleResponseGivenBasalRecordWhenStandardAndSuspendEndReasonAndNewerExistingTBRThenExpectNoPumpSync() { + val data = byteArrayOf(35, 99, 8, 1, 0, 0, -86, 28, 2, -1, -39, -7, 118, -86, -85, 1, 4, 0, -117, 113, -16, 17, 9, 116, -16, 17, 1, 4, 10, 0, 2, 0, 0, 0, 57) + val endTime = 1689505417000 + + // Mocks + val expectedTemporaryBasal: PumpSync.PumpState.TemporaryBasal = mock(PumpSync.PumpState.TemporaryBasal::class.java) + Mockito.`when`(expectedTemporaryBasal.timestamp).thenReturn(endTime + T.mins(1).msecs()) // Existing temp basal is newer + Mockito.`when`(expectedTemporaryBasal.duration).thenReturn(endTime + T.mins(30).msecs()) // Normal TBR + + Mockito.`when`(pumpSync.expectedPumpState()).thenReturn( + PumpSync.PumpState( + temporaryBasal = expectedTemporaryBasal, + extendedBolus = null, + bolus = null, + profile = null, + serialNumber = "someSerialNumber" + ) + ) + + // Call + val packet = GetRecordPacket(packetInjector, 0) + val result = packet.handleResponse(data) + + // Just check the pumpSync here, rest of the behavoir of medtrumPump is tested in MedtrumPumpTest + // Expected values + Mockito.verify(pumpSync, Mockito.never()).syncTemporaryBasalWithPumpId( + timestamp = endTime, + rate = 0.0, + duration = T.mins(4800L).msecs(), + isAbsolute = true, + type = PumpSync.TemporaryBasalType.PUMP_SUSPEND, + pumpId = endTime, + pumpType = medtrumPump.pumpType(), + pumpSerial = medtrumPump.pumpSN.toString(radix = 16) + ) + + Assertions.assertEquals(true, result) + Assertions.assertEquals(false, packet.failed) + } + @Test fun handleResponseGivenTDDRecordThenExpectPumpSync() { val data = byteArrayOf( 87, 99, 8, 1, 0, 0, -86, 80, 9, -1, 38, 105, -77, 57, 56, 0, 82, 0, -32, -124, 61, 18, 120, 0, -120, 5, 0, 0, 0, 0, -102, -103, 84, 66, 0, 0,