Merge pull request #984 from 0pen-dash/avereha/suspend-beep

dash: suspend alerts
This commit is contained in:
Milos Kozak 2021-11-25 07:55:50 +01:00 committed by GitHub
commit da623e92d8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 232 additions and 54 deletions

View file

@ -0,0 +1,9 @@
package info.nightscout.androidaps.plugins.pump.omnipod.common.queue.command
import info.nightscout.androidaps.queue.commands.CustomCommand
class CommandDisableSuspendAlerts: CustomCommand {
override val statusDescription: String
get() = "DISABLE SUSPEND ALERTS"
}

View file

@ -502,7 +502,10 @@ class OmnipodDashPumpPlugin @Inject constructor(
// prevent setBasal requests // prevent setBasal requests
return true return true
} }
// TODO: what do we have to answer here if delivery is suspended? if (podStateManager.isSuspended) {
// set new basal profile failed midway
return false
}
val running = podStateManager.basalProgram val running = podStateManager.basalProgram
val equal = (mapProfileToBasalProgram(profile) == running) val equal = (mapProfileToBasalProgram(profile) == running)
aapsLogger.info(LTag.PUMP, "set: $equal. profile=$profile, running=$running") aapsLogger.info(LTag.PUMP, "set: $equal. profile=$profile, running=$running")
@ -1089,8 +1092,6 @@ class OmnipodDashPumpPlugin @Inject constructor(
return when (customCommand) { return when (customCommand) {
is CommandSilenceAlerts -> is CommandSilenceAlerts ->
silenceAlerts() silenceAlerts()
is CommandSuspendDelivery ->
suspendDelivery()
is CommandResumeDelivery -> is CommandResumeDelivery ->
resumeDelivery() resumeDelivery()
is CommandDeactivatePod -> is CommandDeactivatePod ->
@ -1101,7 +1102,8 @@ class OmnipodDashPumpPlugin @Inject constructor(
updateAlertConfiguration() updateAlertConfiguration()
is CommandPlayTestBeep -> is CommandPlayTestBeep ->
playTestBeep() playTestBeep()
is CommandDisableSuspendAlerts ->
disableSuspendAlerts()
else -> { else -> {
aapsLogger.warn(LTag.PUMP, "Unsupported custom command: " + customCommand.javaClass.name) aapsLogger.warn(LTag.PUMP, "Unsupported custom command: " + customCommand.javaClass.name)
PumpEnactResult(injector).success(false).enacted(false).comment( PumpEnactResult(injector).success(false).enacted(false).comment(
@ -1124,27 +1126,28 @@ class OmnipodDashPumpPlugin @Inject constructor(
} ?: PumpEnactResult(injector).success(false).enacted(false).comment("No active alerts") // TODO i18n } ?: PumpEnactResult(injector).success(false).enacted(false).comment("No active alerts") // TODO i18n
} }
private fun suspendDelivery(): PumpEnactResult { private fun disableSuspendAlerts(): PumpEnactResult {
return executeProgrammingCommand( val alerts = listOf(
historyEntry = history.createRecord(OmnipodCommandType.SUSPEND_DELIVERY), AlertConfiguration(
command = omnipodManager.suspendDelivery(hasBasalBeepEnabled()) AlertType.SUSPEND_ENDED,
.filter { podEvent -> podEvent.isCommandSent() } enabled = false,
.map { durationInMinutes = 0,
pumpSyncTempBasal( autoOff = false,
0.0, AlertTrigger.TimerTrigger(
PodConstants.MAX_POD_LIFETIME.toMinutes(), 0
PumpSync.TemporaryBasalType.PUMP_SUSPEND ),
) BeepType.FOUR_TIMES_BIP_BEEP,
} BeepRepetitionType.EVERY_MINUTE_AND_EVERY_15_MIN
.ignoreElements(), ),
pre = observeDeliveryActive(), )
).doFinally { val ret = executeProgrammingCommand(
notifyOnUnconfirmed( historyEntry = history.createRecord(OmnipodCommandType.CONFIGURE_ALERTS),
Notification.PUMP_ERROR, command = omnipodManager.programAlerts(alerts).ignoreElements(),
"Unconfirmed suspendDelivery command. Please refresh pod status", ).toPumpEnactResult()
R.raw.boluserror if (ret.success && ret.enacted) {
) podStateManager.suspendAlertsEnabled = false
}.toPumpEnactResult() }
return ret
} }
private fun observeDeliveryActive(): Completable = Completable.defer { private fun observeDeliveryActive(): Completable = Completable.defer {
@ -1249,7 +1252,7 @@ class OmnipodDashPumpPlugin @Inject constructor(
expiryAlertDelay.toMinutes().toShort() expiryAlertDelay.toMinutes().toShort()
), ),
BeepType.FOUR_TIMES_BIP_BEEP, BeepType.FOUR_TIMES_BIP_BEEP,
BeepRepetitionType.XXX2 BeepRepetitionType.EVERY_MINUTE_AND_EVERY_15_MIN
) )
) )
return executeProgrammingCommand( return executeProgrammingCommand(
@ -1369,9 +1372,11 @@ class OmnipodDashPumpPlugin @Inject constructor(
) )
podStateManager.tempBasal = null podStateManager.tempBasal = null
rxBus.send(EventDismissNotification(Notification.OMNIPOD_POD_SUSPENDED)) rxBus.send(EventDismissNotification(Notification.OMNIPOD_POD_SUSPENDED))
rxBus.send(EventDismissNotification(Notification.FAILED_UPDATE_PROFILE))
rxBus.send(EventDismissNotification(Notification.OMNIPOD_TBR_ALERTS))
rxBus.send(EventDismissNotification(Notification.OMNIPOD_TIME_OUT_OF_SYNC))
commandQueue.customCommand(CommandDisableSuspendAlerts(), null)
} }
rxBus.send(EventDismissNotification(Notification.OMNIPOD_TBR_ALERTS))
rxBus.send(EventDismissNotification(Notification.OMNIPOD_TIME_OUT_OF_SYNC))
} }
OmnipodCommandType.SET_BASAL_PROFILE -> { OmnipodCommandType.SET_BASAL_PROFILE -> {
@ -1394,6 +1399,7 @@ class OmnipodDashPumpPlugin @Inject constructor(
rxBus.send(EventDismissNotification(Notification.FAILED_UPDATE_PROFILE)) rxBus.send(EventDismissNotification(Notification.FAILED_UPDATE_PROFILE))
rxBus.send(EventDismissNotification(Notification.OMNIPOD_TBR_ALERTS)) rxBus.send(EventDismissNotification(Notification.OMNIPOD_TBR_ALERTS))
rxBus.send(EventDismissNotification(Notification.OMNIPOD_TIME_OUT_OF_SYNC)) rxBus.send(EventDismissNotification(Notification.OMNIPOD_TIME_OUT_OF_SYNC))
commandQueue.customCommand(CommandDisableSuspendAlerts(), null)
} }
} }

View file

@ -439,7 +439,7 @@ class OmnipodDashManagerImpl @Inject constructor(
userExpiryAlertDelay.toMinutes().toShort() userExpiryAlertDelay.toMinutes().toShort()
), ),
BeepType.FOUR_TIMES_BIP_BEEP, BeepType.FOUR_TIMES_BIP_BEEP,
BeepRepetitionType.XXX2 BeepRepetitionType.EVERY_MINUTE_AND_EVERY_15_MIN
) )
) )
} }
@ -504,8 +504,29 @@ class OmnipodDashManagerImpl @Inject constructor(
return Observable.concat( return Observable.concat(
observePodRunning, observePodRunning,
observeConnectToPod, observeConnectToPod,
observeSendStopDeliveryCommand(StopDeliveryCommand.DeliveryType.ALL, hasBasalBeepEnabled) observeSuspendDeliveryCommand(hasBasalBeepEnabled)
).interceptPodEvents() ).doOnComplete {
podStateManager.suspendAlertsEnabled = true
}.interceptPodEvents()
}
private fun observeSuspendDeliveryCommand(hasBasalBeepEnabled: Boolean): Observable<PodEvent> {
return Observable.defer {
val beepType = if (!hasBasalBeepEnabled)
BeepType.SILENT
else
BeepType.LONG_SINGLE_BEEP
bleManager.sendCommand(
SuspendDeliveryCommand.Builder()
.setSequenceNumber(podStateManager.messageSequenceNumber)
.setUniqueId(podStateManager.uniqueId!!.toInt())
.setNonce(NONCE)
.setBeepType(beepType)
.build(),
DefaultStatusResponse::class
)
}
} }
private fun observeSendProgramTempBasalCommand(rate: Double, durationInMinutes: Short, tempBasalBeeps: Boolean): Observable<PodEvent> { private fun observeSendProgramTempBasalCommand(rate: Double, durationInMinutes: Short, tempBasalBeeps: Boolean): Observable<PodEvent> {

View file

@ -38,6 +38,18 @@ class ProgramAlertsCommand private constructor(
return appendCrc(byteBuffer.array()) return appendCrc(byteBuffer.array())
} }
val encodedWithoutHeaderAndCRC32: ByteArray
get() {
val byteBuffer: ByteBuffer = ByteBuffer.allocate(getLength().toInt())
.put(commandType.value)
.put(getBodyLength())
.putInt(nonce)
for (configuration in alertConfigurations) {
byteBuffer.put(configuration.encoded)
}
return byteBuffer.array()
}
override fun toString(): String { override fun toString(): String {
return "ProgramAlertsCommand{" + return "ProgramAlertsCommand{" +
"alertConfigurations=" + alertConfigurations + "alertConfigurations=" + alertConfigurations +

View file

@ -0,0 +1,113 @@
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command.base.CommandType
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command.base.NonceEnabledCommand
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command.base.builder.NonceEnabledCommandBuilder
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.definition.*
import java.nio.ByteBuffer
import java.util.*
// StopDelivery.ALL followed by ProgramAlerts
class SuspendDeliveryCommand private constructor(
uniqueId: Int,
sequenceNumber: Short,
multiCommandFlag: Boolean,
private val beepType: BeepType,
nonce: Int
) : NonceEnabledCommand(CommandType.STOP_DELIVERY, uniqueId, sequenceNumber, multiCommandFlag, nonce) {
override val encoded: ByteArray
get() {
val alerts = listOf(
AlertConfiguration(
AlertType.SUSPEND_ENDED,
enabled = true,
durationInMinutes = 0,
autoOff = false,
AlertTrigger.TimerTrigger(
20
),
BeepType.FOUR_TIMES_BIP_BEEP,
BeepRepetitionType.EVERY_MINUTE_AND_EVERY_15_MIN
),
)
val programAlerts = ProgramAlertsCommand.Builder()
.setNonce(nonce)
.setUniqueId(uniqueId)
.setAlertConfigurations(alerts)
.setSequenceNumber(sequenceNumber)
.setMultiCommandFlag(false)
.build()
.encodedWithoutHeaderAndCRC32
val byteBuffer = ByteBuffer.allocate(LENGTH + HEADER_LENGTH + programAlerts.size)
.put(encodeHeader(uniqueId, sequenceNumber, (LENGTH + programAlerts.size).toShort(), multiCommandFlag))
.put(commandType.value)
.put(BODY_LENGTH)
.putInt(nonce)
.put((beepType.value.toInt() shl 4 or DeliveryType.ALL.encoded[0].toInt()).toByte())
.put(programAlerts)
.array()
return appendCrc(
byteBuffer
)
}
override fun toString(): String {
return "SuspendDeliveryCommand{" +
"deliveryType=" + DeliveryType.ALL +
", beepType=" + beepType +
", nonce=" + nonce +
", commandType=" + commandType +
", uniqueId=" + uniqueId +
", sequenceNumber=" + sequenceNumber +
", multiCommandFlag=" + multiCommandFlag +
'}'
}
enum class DeliveryType(
private val basal: Boolean,
private val tempBasal: Boolean,
private val bolus: Boolean
) : Encodable {
BASAL(true, false, false), TEMP_BASAL(false, true, false), BOLUS(false, false, true), ALL(true, true, true);
override val encoded: ByteArray
get() {
val bitSet = BitSet(8)
bitSet[0] = basal
bitSet[1] = tempBasal
bitSet[2] = bolus
return bitSet.toByteArray()
}
}
class Builder : NonceEnabledCommandBuilder<Builder, SuspendDeliveryCommand>() {
private var beepType: BeepType? = BeepType.LONG_SINGLE_BEEP
fun setBeepType(beepType: BeepType): Builder {
this.beepType = beepType
return this
}
override fun buildCommand(): SuspendDeliveryCommand {
requireNotNull(beepType) { "beepType can not be null" }
return SuspendDeliveryCommand(
uniqueId!!,
sequenceNumber!!,
multiCommandFlag,
beepType!!,
nonce!!
)
}
}
companion object {
private const val LENGTH: Short = 7
private const val BODY_LENGTH: Byte = 5
}
}

View file

@ -6,8 +6,8 @@ enum class BeepRepetitionType(
) { ) {
XXX(0x01.toByte()), // Used in lump of coal alert, LOW_RESERVOIR XXX(0x01.toByte()), // Used in lump of coal alert, LOW_RESERVOIR
XXX2(0x03.toByte()), // Used in USER_SET_EXPIRATION EVERY_MINUTE_AND_EVERY_15_MIN(0x03.toByte()), // Used in USER_SET_EXPIRATION, suspend delivery
XXX3(0x05.toByte()), // published system expiration alert XXX3(0x05.toByte()), // published system expiration alert
XXX4(0x06.toByte()), // Used in imminent pod expiration alert, suspend in progress XXX4(0x06.toByte()), // Used in imminent pod expiration alert, suspend in progress. No repeat?
XXX5(0x08.toByte()); // Lump of coal alert XXX5(0x08.toByte()); // Lump of coal alert
} }

View file

@ -77,6 +77,7 @@ interface OmnipodDashPodStateManager {
var basalProgram: BasalProgram? var basalProgram: BasalProgram?
val activeCommand: ActiveCommand? val activeCommand: ActiveCommand?
val lastBolus: LastBolus? val lastBolus: LastBolus?
var suspendAlertsEnabled: Boolean
fun increaseMessageSequenceNumber() fun increaseMessageSequenceNumber()
fun increaseEapAkaSequenceNumber(): ByteArray fun increaseEapAkaSequenceNumber(): ByteArray

View file

@ -225,6 +225,13 @@ class OmnipodDashPodStateManagerImpl @Inject constructor(
store() store()
} }
override var suspendAlertsEnabled: Boolean
get() = podState.suspendAlertsEnabled
set(enabled) {
podState.suspendAlertsEnabled = enabled
store()
}
override val lastStatusResponseReceived: Long override val lastStatusResponseReceived: Long
get() = podState.lastStatusResponseReceived get() = podState.lastStatusResponseReceived
@ -714,6 +721,7 @@ class OmnipodDashPodStateManagerImpl @Inject constructor(
var timeZoneOffset: Int? = null var timeZoneOffset: Int? = null
var timeZoneUpdated: Long? = null var timeZoneUpdated: Long? = null
var alarmSynced: Boolean = false var alarmSynced: Boolean = false
var suspendAlertsEnabled: Boolean = false
var bleVersion: SoftwareVersion? = null var bleVersion: SoftwareVersion? = null
var firmwareVersion: SoftwareVersion? = null var firmwareVersion: SoftwareVersion? = null

View file

@ -439,6 +439,10 @@ class OmnipodDashOverviewFragment : DaggerFragment() {
R.string.omnipod_common_alert_expiration_advisory R.string.omnipod_common_alert_expiration_advisory
AlertType.AUTO_OFF -> AlertType.AUTO_OFF ->
R.string.omnipod_common_alert_shutdown_imminent R.string.omnipod_common_alert_shutdown_imminent
AlertType.SUSPEND_IN_PROGRESS ->
R.string.omnipod_common_alert_delivery_suspended
AlertType.SUSPEND_ENDED ->
R.string.omnipod_common_alert_delivery_suspended
else -> else ->
R.string.omnipod_common_alert_unknown_alert R.string.omnipod_common_alert_unknown_alert
} }
@ -629,16 +633,8 @@ class OmnipodDashOverviewFragment : DaggerFragment() {
private fun updateSuspendDeliveryButton() { private fun updateSuspendDeliveryButton() {
// If the Pod is currently suspended, we show the Resume delivery button instead. // If the Pod is currently suspended, we show the Resume delivery button instead.
if (isSuspendDeliveryButtonEnabled() && // disable the 'suspendDelivery' button.
podStateManager.isPodRunning && buttonBinding.buttonSuspendDelivery.visibility = View.GONE
(!podStateManager.isSuspended || commandQueue.isCustomCommandInQueue(CommandSuspendDelivery::class.java))
) {
buttonBinding.buttonSuspendDelivery.visibility = View.VISIBLE
buttonBinding.buttonSuspendDelivery.isEnabled =
podStateManager.isPodRunning && !podStateManager.isSuspended && isQueueEmpty()
} else {
buttonBinding.buttonSuspendDelivery.visibility = View.GONE
}
} }
private fun updateSetTimeButton() { private fun updateSetTimeButton() {
@ -654,10 +650,6 @@ class OmnipodDashOverviewFragment : DaggerFragment() {
return sp.getBoolean(R.string.omnipod_common_preferences_automatically_silence_alerts, false) return sp.getBoolean(R.string.omnipod_common_preferences_automatically_silence_alerts, false)
} }
private fun isSuspendDeliveryButtonEnabled(): Boolean {
return sp.getBoolean(R.string.key_omnipod_common_suspend_delivery_button_enabled, false)
}
private fun displayErrorDialog(title: String, message: String, withSound: Boolean) { private fun displayErrorDialog(title: String, message: String, withSound: Boolean) {
context?.let { context?.let {
ErrorHelperActivity.runAlarm(it, message, title, if (withSound) R.raw.boluserror else 0) ErrorHelperActivity.runAlarm(it, message, title, if (withSound) R.raw.boluserror else 0)

View file

@ -47,4 +47,5 @@
<string name="omnipod_common_history_tbr_value">Rate: %1$.2f U, duration: %2$d minutes</string> <string name="omnipod_common_history_tbr_value">Rate: %1$.2f U, duration: %2$d minutes</string>
<string name="omnipod_common_history_bolus_value">%1$.2f U</string> <string name="omnipod_common_history_bolus_value">%1$.2f U</string>
<string name="dash_bolusdelivering">Delivering %1$.2f U</string> <string name="dash_bolusdelivering">Delivering %1$.2f U</string>
<string name="omnipod_common_alert_delivery_suspended">Insulin delivery is suspended</string>
</resources> </resources>

View file

@ -105,11 +105,6 @@
android:title="@string/omnipod_common_preferences_category_other" android:title="@string/omnipod_common_preferences_category_other"
app:initialExpandedChildrenCount="0"> app:initialExpandedChildrenCount="0">
<SwitchPreference
android:defaultValue="false"
android:key="@string/key_omnipod_common_suspend_delivery_button_enabled"
android:title="@string/omnipod_common_preferences_suspend_delivery_button_enabled" />
<SwitchPreference <SwitchPreference
android:defaultValue="true" android:defaultValue="true"
android:key="@string/key_omnipod_common_time_change_event_enabled" android:key="@string/key_omnipod_common_time_change_event_enabled"

View file

@ -85,7 +85,7 @@ class ProgramAlertsCommandTest {
false, false,
AlertTrigger.TimerTrigger(4079.toShort()), AlertTrigger.TimerTrigger(4079.toShort()),
BeepType.FOUR_TIMES_BIP_BEEP, BeepType.FOUR_TIMES_BIP_BEEP,
BeepRepetitionType.XXX2 BeepRepetitionType.EVERY_MINUTE_AND_EVERY_15_MIN
) )
) )

View file

@ -0,0 +1,20 @@
package info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.command
import info.nightscout.androidaps.plugins.pump.omnipod.dash.driver.pod.definition.BeepType
import org.apache.commons.codec.DecoderException
import org.apache.commons.codec.binary.Hex
import org.junit.Assert
import org.junit.Test
class SuspendDeliveryCommandTest {
@Test @Throws(DecoderException::class) fun testSuspendDelivery() {
val encoded = SuspendDeliveryCommand.Builder()
.setUniqueId(37879811)
.setSequenceNumber(0.toShort())
.setNonce(1229869870)
.setBeepType(BeepType.LONG_SINGLE_BEEP)
.build()
.encoded
Assert.assertArrayEquals(Hex.decodeHex("0242000300131f05494e532e67190a494e532e680000140302811f"), encoded)
}
}