Add alarm states, show active alarms in overview

This commit is contained in:
jbr7rr 2023-06-18 20:36:44 +02:00
parent 996ef8e5ef
commit 057b2e386e
7 changed files with 275 additions and 105 deletions

View file

@ -7,6 +7,7 @@ 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.AlarmState
import info.nightscout.pump.medtrum.comm.enums.BasalType
import info.nightscout.pump.medtrum.comm.enums.MedtrumPumpState
import info.nightscout.pump.medtrum.extension.toByteArray
@ -19,7 +20,7 @@ import info.nightscout.shared.utils.DateUtil
import info.nightscout.shared.utils.T
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import java.util.GregorianCalendar
import java.util.*
import javax.inject.Inject
import javax.inject.Singleton
import kotlin.math.round
@ -57,6 +58,14 @@ class MedtrumPump @Inject constructor(
sp.putInt(R.string.key_pump_state, value.state.toInt())
}
// Active alarms
private var _activeAlarms: EnumSet<AlarmState> = EnumSet.noneOf(AlarmState::class.java)
var activeAlarms: EnumSet<AlarmState>
get() = _activeAlarms
set(value) {
_activeAlarms = value
}
// Prime progress as state flow
private val _primeProgress = MutableStateFlow(0)
val primeProgressFlow: StateFlow<Int> = _primeProgress
@ -251,6 +260,8 @@ class MedtrumPump @Inject constructor(
_swVersion = sp.getString(R.string.key_sw_version, "")
_patchStartTime = sp.getLong(R.string.key_patch_start_time, 0L)
loadActiveAlarms()
val encodedString = sp.getString(R.string.key_actual_basal_profile, "0")
try {
_actualBasalProfile = Base64.decode(encodedString, Base64.DEFAULT)
@ -436,8 +447,40 @@ class MedtrumPump @Inject constructor(
"handleBasalStatusUpdate: ${if (newRecord) "**NEW** " else ""}EVENT TEMP_START (FAKE)"
)
}
fun temporaryBasalToString(): String {
if (!tempBasalInProgress) return ""
return tempBasalAbsoluteRate.toString() + "U/h"
}
fun addAlarm(alarm: AlarmState) {
activeAlarms.add(alarm)
saveActiveAlarms()
}
fun removeAlarm(alarm: AlarmState) {
activeAlarms.remove(alarm)
saveActiveAlarms()
}
fun clearAlarmState() {
activeAlarms.clear()
saveActiveAlarms()
}
private fun saveActiveAlarms() {
val alarmsStr = activeAlarms.joinToString(separator = ",") { it.name }
sp.putString(R.string.key_active_alarms, alarmsStr)
}
private fun loadActiveAlarms() {
val alarmsStr = sp.getString(R.string.key_active_alarms, "")
if (alarmsStr.isNullOrEmpty()) {
activeAlarms = EnumSet.noneOf(AlarmState::class.java)
} else {
activeAlarms = alarmsStr.split(",")
.mapNotNull { AlarmState.values().find { alarm -> alarm.name == it } }
.let { EnumSet.copyOf(it) }
}
}
}

View file

@ -0,0 +1,23 @@
package info.nightscout.pump.medtrum.comm.enums
enum class AlarmState {
NONE,
PUMP_LOW_BATTERY, // Mapped from error flag 1
PUMP_LOW_RESERVOIR, // Mapped from error flag 2
PUMP_EXPIRES_SOON, // Mapped from error flag 3
LOWBG_SUSPENDED, // Mapped from pump status 64
LOWBG_SUSPENDED2, // Mapped from pump status 65
AUTO_SUSPENDED, // Mapped from pump status 66
HMAX_SUSPENDED, // Mapped from pump status 67
DMAX_SUSPENDED, // Mapped from pump status 68
SUSPENDED, // Mapped from pump status 69
PAUSED, // Mapped from pump status 70
OCCLUSION, // Mapped from pump status 96
EXPIRED, // Mapped from pump status 97
RESERVOIR_EMPTY, // Mapped from pump status 98
PATCH_FAULT, // Mapped from pump status 99
PATCH_FAULT2, // Mapped from pump status 100
BASE_FAULT, // Mapped from pump status 101
BATTERY_OUT, // Mapped from pump status 102
NO_CALIBRATION // Mapped from pump status 103
}

View file

@ -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.AlarmState
import info.nightscout.pump.medtrum.comm.enums.BasalType
import info.nightscout.pump.medtrum.comm.enums.MedtrumPumpState
import info.nightscout.pump.medtrum.extension.toInt
@ -71,7 +72,7 @@ class NotificationPacket(val injector: HasAndroidInjector) {
if (state != medtrumPump.pumpState) {
aapsLogger.debug(LTag.PUMPCOMM, "State changed from ${medtrumPump.pumpState} to $state")
medtrumPump.pumpState = state
}
}
if (notification.size > NOTIF_STATE_END) {
handleMaskedMessage(notification.copyOfRange(NOTIF_STATE_END, notification.size))
@ -127,7 +128,7 @@ class NotificationPacket(val injector: HasAndroidInjector) {
// Don't spam with basal updates here, only if the running basal rate has changed, or a new basal is set
if (medtrumPump.lastBasalRate != basalRate || medtrumPump.lastBasalStartTime != basalStartTime) {
medtrumPump.handleBasalStatusUpdate(basalType, basalRate, basalSequence, basalPatchId, basalStartTime)
}
}
offset += 12
}
@ -178,11 +179,26 @@ class NotificationPacket(val injector: HasAndroidInjector) {
}
if (fieldMask and MASK_ALARM != 0) {
aapsLogger.debug(LTag.PUMPCOMM, "Alarm notification received")
// Set only flags here, Alarms will be picked up by the state change
medtrumPump.alarmFlags = data.copyOfRange(offset, offset + 2).toInt()
medtrumPump.alarmParameter = data.copyOfRange(offset + 2, offset + 4).toInt()
aapsLogger.debug(LTag.PUMPCOMM, "Alarm flags: ${medtrumPump.alarmFlags}, alarm parameter: ${medtrumPump.alarmParameter}")
val alarmFlags = data.copyOfRange(offset, offset + 2).toInt()
val alarmParameter = data.copyOfRange(offset + 2, offset + 4).toInt()
aapsLogger.debug(LTag.PUMPCOMM, "Alarm notification received, Alarm flags: $alarmFlags, alarm parameter: $alarmParameter")
// If no alarm, clear activeAlarm list
if (alarmFlags == 0 && medtrumPump.activeAlarms.isNotEmpty()) {
medtrumPump.clearAlarmState()
} else if (alarmFlags != 0) {
// Check each alarm bit
for (i in 0..3) { // Only the first 3 flags are interesting for us, the rest we will get from the pump state
val alarmState = AlarmState.values()[i]
if ((alarmFlags shr i) and 1 != 0) {
// If the alarm bit is set, add the corresponding alarm to activeAlarms
medtrumPump.addAlarm(alarmState)
} else if (medtrumPump.activeAlarms.contains(alarmState)) {
// If the alarm bit is not set, and the corresponding alarm is in activeAlarms, remove it
medtrumPump.removeAlarm(alarmState)
}
}
}
offset += 4
}

View file

@ -22,6 +22,7 @@ import info.nightscout.pump.medtrum.MedtrumPlugin
import info.nightscout.pump.medtrum.MedtrumPump
import info.nightscout.pump.medtrum.R
import info.nightscout.pump.medtrum.code.ConnectionState
import info.nightscout.pump.medtrum.comm.enums.AlarmState
import info.nightscout.pump.medtrum.comm.enums.MedtrumPumpState
import info.nightscout.pump.medtrum.comm.packets.*
import info.nightscout.pump.medtrum.util.MedtrumTimeUtil
@ -129,81 +130,7 @@ class MedtrumService : DaggerService(), BLECommCallback {
}, fabricPrivacy::logException)
scope.launch {
medtrumPump.pumpStateFlow.collect { state ->
when (state) {
MedtrumPumpState.NONE,
MedtrumPumpState.IDLE,
MedtrumPumpState.FILLED,
MedtrumPumpState.PRIMING,
MedtrumPumpState.PRIMED,
MedtrumPumpState.EJECTING,
MedtrumPumpState.EJECTED,
MedtrumPumpState.STOPPED -> {
rxBus.send(EventDismissNotification(Notification.PUMP_ERROR))
rxBus.send(EventDismissNotification(Notification.PUMP_SUSPENDED))
uiInteraction.addNotification(
Notification.PATCH_NOT_ACTIVE,
rh.gs(R.string.patch_not_active),
Notification.URGENT,
)
medtrumPump.setFakeTBRIfNeeded()
}
MedtrumPumpState.ACTIVE,
MedtrumPumpState.ACTIVE_ALT -> {
rxBus.send(EventDismissNotification(Notification.PATCH_NOT_ACTIVE))
rxBus.send(EventDismissNotification(Notification.PUMP_SUSPENDED))
}
MedtrumPumpState.LOWBG_SUSPENDED,
MedtrumPumpState.LOWBG_SUSPENDED2,
MedtrumPumpState.AUTO_SUSPENDED,
MedtrumPumpState.SUSPENDED,
MedtrumPumpState.PAUSED -> {
uiInteraction.addNotification(
Notification.PUMP_SUSPENDED,
rh.gs(R.string.pump_is_suspended),
Notification.NORMAL,
)
// Pump will report proper TBR for this
}
MedtrumPumpState.HMAX_SUSPENDED -> {
uiInteraction.addNotification(
Notification.PUMP_SUSPENDED,
rh.gs(R.string.pump_is_suspended_hour_max),
Notification.NORMAL,
)
// Pump will report proper TBR for this
}
MedtrumPumpState.DMAX_SUSPENDED -> {
uiInteraction.addNotification(
Notification.PUMP_SUSPENDED,
rh.gs(R.string.pump_is_suspended_day_max),
Notification.NORMAL,
)
// Pump will report proper TBR for this
}
MedtrumPumpState.OCCLUSION,
MedtrumPumpState.EXPIRED,
MedtrumPumpState.RESERVOIR_EMPTY,
MedtrumPumpState.PATCH_FAULT,
MedtrumPumpState.PATCH_FAULT2,
MedtrumPumpState.BASE_FAULT,
MedtrumPumpState.BATTERY_OUT,
MedtrumPumpState.NO_CALIBRATION -> {
rxBus.send(EventDismissNotification(Notification.PATCH_NOT_ACTIVE))
rxBus.send(EventDismissNotification(Notification.PUMP_SUSPENDED))
// Pump suspended due to error, show error!
uiInteraction.addNotificationWithSound(
Notification.PUMP_ERROR,
rh.gs(R.string.pump_error, state.toString()),
Notification.URGENT,
info.nightscout.core.ui.R.raw.alarm
)
medtrumPump.setFakeTBRIfNeeded()
}
}
handlePumpStateUpdate(state)
}
}
@ -284,31 +211,37 @@ class MedtrumService : DaggerService(), BLECommCallback {
fun clearAlarms(): Boolean {
var result = true
when (medtrumPump.pumpState) {
MedtrumPumpState.HMAX_SUSPENDED -> {
result = sendPacketAndGetResponse(ClearPumpAlarmPacket(injector, ALARM_HOURLY_MAX_CLEAR_CODE))
}
MedtrumPumpState.DMAX_SUSPENDED -> {
result = sendPacketAndGetResponse(ClearPumpAlarmPacket(injector, ALARM_DAILY_MAX_CLEAR_CODE))
}
else -> {
// Do nothing
// TODO: Remove me before release!!!
// Try to brute force the commands
aapsLogger.warn(LTag.PUMPCOMM, "Trying to clear alarms brutus!")
for (i in 0..100) {
result = sendPacketAndGetResponse(ClearPumpAlarmPacket(injector, i))
if (result) {
aapsLogger.warn(LTag.PUMPCOMM, "Alarm cleared: $i")
break
if (medtrumPump.activeAlarms.isNotEmpty()) {
when (medtrumPump.pumpState) {
MedtrumPumpState.HMAX_SUSPENDED -> {
result = sendPacketAndGetResponse(ClearPumpAlarmPacket(injector, ALARM_HOURLY_MAX_CLEAR_CODE))
}
MedtrumPumpState.DMAX_SUSPENDED -> {
result = sendPacketAndGetResponse(ClearPumpAlarmPacket(injector, ALARM_DAILY_MAX_CLEAR_CODE))
}
else -> {
// TODO: Remove me before release!!!
// Try to brute force the commands
aapsLogger.warn(LTag.PUMPCOMM, "Trying to clear alarms brutus!")
for (i in 0..100) {
result = sendPacketAndGetResponse(ClearPumpAlarmPacket(injector, i))
if (result) {
aapsLogger.warn(LTag.PUMPCOMM, "Alarm cleared: $i")
break
}
}
}
}
}
// Resume suspended pump
// TODO: We might not want to do this for alarms which don't suspend the pump
if (medtrumPump.pumpState in listOf(MedtrumPumpState.LOWBG_SUSPENDED, MedtrumPumpState.PAUSED)) {
if (result) result = sendPacketAndGetResponse(ResumePumpPacket(injector))
}
if (result) result = sendPacketAndGetResponse(ResumePumpPacket(injector))
return result
}
@ -443,6 +376,112 @@ class MedtrumService : DaggerService(), BLECommCallback {
return result
}
private fun handlePumpStateUpdate(state: MedtrumPumpState) {
// Map the pump state to an alarm state and add it to the active alarms
val alarmState = when (state) {
MedtrumPumpState.NONE -> AlarmState.NONE
MedtrumPumpState.LOWBG_SUSPENDED -> AlarmState.LOWBG_SUSPENDED
MedtrumPumpState.LOWBG_SUSPENDED2 -> AlarmState.LOWBG_SUSPENDED2
MedtrumPumpState.AUTO_SUSPENDED -> AlarmState.AUTO_SUSPENDED
MedtrumPumpState.HMAX_SUSPENDED -> AlarmState.HMAX_SUSPENDED
MedtrumPumpState.DMAX_SUSPENDED -> AlarmState.DMAX_SUSPENDED
MedtrumPumpState.SUSPENDED -> AlarmState.SUSPENDED
MedtrumPumpState.PAUSED -> AlarmState.PAUSED
MedtrumPumpState.OCCLUSION -> AlarmState.OCCLUSION
MedtrumPumpState.EXPIRED -> AlarmState.EXPIRED
MedtrumPumpState.RESERVOIR_EMPTY -> AlarmState.RESERVOIR_EMPTY
MedtrumPumpState.PATCH_FAULT -> AlarmState.PATCH_FAULT
MedtrumPumpState.PATCH_FAULT2 -> AlarmState.PATCH_FAULT2
MedtrumPumpState.BASE_FAULT -> AlarmState.BASE_FAULT
MedtrumPumpState.BATTERY_OUT -> AlarmState.BATTERY_OUT
MedtrumPumpState.NO_CALIBRATION -> AlarmState.NO_CALIBRATION
else -> null
}
if (alarmState != null && alarmState != AlarmState.NONE) {
medtrumPump.addAlarm(alarmState)
}
// Map the pump state to a notification
when (state) {
MedtrumPumpState.NONE,
MedtrumPumpState.IDLE,
MedtrumPumpState.FILLED,
MedtrumPumpState.PRIMING,
MedtrumPumpState.PRIMED,
MedtrumPumpState.EJECTING,
MedtrumPumpState.EJECTED,
MedtrumPumpState.STOPPED -> {
rxBus.send(EventDismissNotification(Notification.PUMP_ERROR))
rxBus.send(EventDismissNotification(Notification.PUMP_SUSPENDED))
uiInteraction.addNotification(
Notification.PATCH_NOT_ACTIVE,
rh.gs(R.string.patch_not_active),
Notification.URGENT,
)
medtrumPump.setFakeTBRIfNeeded()
medtrumPump.clearAlarmState()
}
MedtrumPumpState.ACTIVE,
MedtrumPumpState.ACTIVE_ALT -> {
rxBus.send(EventDismissNotification(Notification.PATCH_NOT_ACTIVE))
rxBus.send(EventDismissNotification(Notification.PUMP_SUSPENDED))
medtrumPump.clearAlarmState()
}
MedtrumPumpState.LOWBG_SUSPENDED,
MedtrumPumpState.LOWBG_SUSPENDED2,
MedtrumPumpState.AUTO_SUSPENDED,
MedtrumPumpState.SUSPENDED,
MedtrumPumpState.PAUSED -> {
uiInteraction.addNotification(
Notification.PUMP_SUSPENDED,
rh.gs(R.string.pump_is_suspended),
Notification.NORMAL,
)
// Pump will report proper TBR for this
}
MedtrumPumpState.HMAX_SUSPENDED -> {
uiInteraction.addNotification(
Notification.PUMP_SUSPENDED,
rh.gs(R.string.pump_is_suspended_hour_max),
Notification.NORMAL,
)
// Pump will report proper TBR for this
}
MedtrumPumpState.DMAX_SUSPENDED -> {
uiInteraction.addNotification(
Notification.PUMP_SUSPENDED,
rh.gs(R.string.pump_is_suspended_day_max),
Notification.NORMAL,
)
// Pump will report proper TBR for this
}
MedtrumPumpState.OCCLUSION,
MedtrumPumpState.EXPIRED,
MedtrumPumpState.RESERVOIR_EMPTY,
MedtrumPumpState.PATCH_FAULT,
MedtrumPumpState.PATCH_FAULT2,
MedtrumPumpState.BASE_FAULT,
MedtrumPumpState.BATTERY_OUT,
MedtrumPumpState.NO_CALIBRATION -> {
rxBus.send(EventDismissNotification(Notification.PATCH_NOT_ACTIVE))
rxBus.send(EventDismissNotification(Notification.PUMP_SUSPENDED))
// Pump suspended due to error, show error!
uiInteraction.addNotificationWithSound(
Notification.PUMP_ERROR,
rh.gs(R.string.pump_error, state.toString()),
Notification.URGENT,
info.nightscout.core.ui.R.raw.alarm
)
medtrumPump.setFakeTBRIfNeeded()
}
}
}
/** BLECommCallbacks */
override fun onBLEConnected() {
aapsLogger.debug(LTag.PUMPCOMM, "<<<<< onBLEConnected")
@ -586,7 +625,7 @@ class MedtrumService : DaggerService(), BLECommCallback {
if (mPacket?.handleResponse(data) == true) {
// Succes!
responseHandled = true
responseSuccess = true
responseSuccess = true
toState(GetDeviceTypeState())
} else if (mPacket?.failed == true) {
// Failure

View file

@ -49,6 +49,7 @@ class MedtrumOverviewFragment : MedtrumBaseFragment<FragmentMedtrumOverviewBindi
startActivity(MedtrumActivity.createIntentFromMenu(this, PatchStep.START_DEACTIVATION))
} else {
startActivity(MedtrumActivity.createIntentFromMenu(this, PatchStep.PREPARE_PATCH))
medtrumPump.pumpState = MedtrumPumpState.NONE // Reset pumpstate here, fetch on next connection
}
}
else -> Unit

View file

@ -10,6 +10,7 @@ import info.nightscout.interfaces.queue.CommandQueue
import info.nightscout.pump.medtrum.MedtrumPump
import info.nightscout.pump.medtrum.R
import info.nightscout.pump.medtrum.code.ConnectionState
import info.nightscout.pump.medtrum.comm.enums.AlarmState
import info.nightscout.pump.medtrum.comm.enums.MedtrumPumpState
import info.nightscout.rx.logging.AAPSLogger
import info.nightscout.rx.logging.LTag
@ -168,8 +169,8 @@ class MedtrumOverviewViewModel @Inject constructor(
_lastBolus.postValue("")
}
// TODO: Update these values
// _activeAlarms.postValue(rh.gs(R.string.active_alarms, pump.activeAlarms))
val activeAlarmStrings = medtrumPump.activeAlarms.map { alarmStateToString(it) }
_activeAlarms.postValue(activeAlarmStrings.joinToString("\n"))
_pumpType.postValue(medtrumPump.deviceType.toString())
_fwVersion.postValue(medtrumPump.swVersion)
_patchNo.postValue(medtrumPump.patchId.toString())
@ -181,5 +182,30 @@ class MedtrumOverviewViewModel @Inject constructor(
_patchExpiry.postValue(rh.gs(R.string.expiry_not_enabled))
}
}
private fun alarmStateToString(alarmState: AlarmState): String {
val stringId = when (alarmState) {
AlarmState.NONE -> R.string.alarm_none
AlarmState.PUMP_LOW_BATTERY -> R.string.alarm_pump_low_battery
AlarmState.PUMP_LOW_RESERVOIR -> R.string.alarm_pump_low_reservoir
AlarmState.PUMP_EXPIRES_SOON -> R.string.alarm_pump_expires_soon
AlarmState.LOWBG_SUSPENDED -> R.string.alarm_lowbg_suspended
AlarmState.LOWBG_SUSPENDED2 -> R.string.alarm_lowbg_suspended2
AlarmState.AUTO_SUSPENDED -> R.string.alarm_auto_suspended
AlarmState.HMAX_SUSPENDED -> R.string.alarm_hmax_suspended
AlarmState.DMAX_SUSPENDED -> R.string.alarm_dmax_suspended
AlarmState.SUSPENDED -> R.string.alarm_suspended
AlarmState.PAUSED -> R.string.alarm_paused
AlarmState.OCCLUSION -> R.string.alarm_occlusion
AlarmState.EXPIRED -> R.string.alarm_expired
AlarmState.RESERVOIR_EMPTY -> R.string.alarm_reservoir_empty
AlarmState.PATCH_FAULT -> R.string.alarm_patch_fault
AlarmState.PATCH_FAULT2 -> R.string.alarm_patch_fault2
AlarmState.BASE_FAULT -> R.string.alarm_base_fault
AlarmState.BATTERY_OUT -> R.string.alarm_battery_out
AlarmState.NO_CALIBRATION -> R.string.alarm_no_calibration
}
return rh.gs(stringId)
}
}

View file

@ -9,6 +9,7 @@
<string name="key_medtrumpump_settings" translatable="false">medtrumpump_settings</string>
<string name="key_pump_state" translatable="false">pump_state</string>
<string name="key_active_alarms" translatable="false">active_alarms</string>
<string name="key_last_connection" translatable="false">last_connection</string>
<string name="key_last_bolus_time" translatable="false">last_bolus_time</string>
<string name="key_last_bolus_amount" translatable="false">last_bolus_amount</string>
@ -52,6 +53,27 @@
<string name="requested_by_user" comment="26 characters max for translation">Requested by user</string>
<string name="expiry_not_enabled">Not enabled</string>
<!-- Alarm strings -->
<string name="alarm_none">None</string>
<string name="alarm_pump_low_battery">Pump low battery</string>
<string name="alarm_pump_low_reservoir">Pump low reservoir</string>
<string name="alarm_pump_expires_soon">Pump expires soon</string>
<string name="alarm_lowbg_suspended">Low BG suspended</string>
<string name="alarm_lowbg_suspended2">Low BG suspended 2</string>
<string name="alarm_auto_suspended">Auto suspended</string>
<string name="alarm_hmax_suspended">hourly max suspended</string>
<string name="alarm_dmax_suspended">daily max suspended</string>
<string name="alarm_suspended">Suspended</string>
<string name="alarm_paused">Paused</string>
<string name="alarm_occlusion">Occlusion</string>
<string name="alarm_expired">Expired</string>
<string name="alarm_reservoir_empty">Reservoir empty</string>
<string name="alarm_patch_fault">Patch fault</string>
<string name="alarm_patch_fault2">Patch fault 2</string>
<string name="alarm_base_fault">Base fault</string>
<string name="alarm_battery_out">Battery out</string>
<string name="alarm_no_calibration">No calibration</string>
<!-- wizard-->
<string name="string_change_patch">Discard/Change Patch</string> <!-- TODO check-->
<string name="string_next">Next</string>