Medtrum: Sync stops (on pump errors) with proper timestamp instead of just fakeTBR

This commit is contained in:
jbr7rr 2023-09-23 08:55:43 +02:00
parent ccabee0399
commit 98daf4b12f
7 changed files with 173 additions and 23 deletions

View file

@ -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<Double> = _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
}
}

View file

@ -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
}
}

View file

@ -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
}
}

View file

@ -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<BasalType>()[data.copyOfRange(RESP_RECORD_DATA_START + 8, RESP_RECORD_DATA_START + 9).toInt()]
val basalEndReason = enumValues<BasalEndReason>()[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<BasalEndReason>().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)
}
}
}

View file

@ -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()
}
})
}
}
}

View file

@ -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(

View file

@ -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,