diff --git a/app/src/main/java/info/nightscout/androidaps/dependencyInjection/CommandQueueModule.kt b/app/src/main/java/info/nightscout/androidaps/dependencyInjection/CommandQueueModule.kt index e9339e187b..014a8de8e3 100644 --- a/app/src/main/java/info/nightscout/androidaps/dependencyInjection/CommandQueueModule.kt +++ b/app/src/main/java/info/nightscout/androidaps/dependencyInjection/CommandQueueModule.kt @@ -26,4 +26,5 @@ abstract class CommandQueueModule { @ContributesAndroidInjector abstract fun commandTempBasalAbsoluteInjector(): CommandTempBasalAbsolute @ContributesAndroidInjector abstract fun commandTempBasalPercentInjector(): CommandTempBasalPercent @ContributesAndroidInjector abstract fun commandSetUserSettingsInjector(): CommandSetUserSettings + @ContributesAndroidInjector abstract fun commandCustomCommandInjector(): CommandCustomCommand } \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/combo/ComboPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/combo/ComboPlugin.java index e443ac4657..63d798ab0d 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/combo/ComboPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/combo/ComboPlugin.java @@ -29,6 +29,7 @@ import info.nightscout.androidaps.db.CareportalEvent; import info.nightscout.androidaps.db.Source; import info.nightscout.androidaps.db.TDD; import info.nightscout.androidaps.db.TemporaryBasal; +import info.nightscout.androidaps.db.Treatment; import info.nightscout.androidaps.events.EventInitializationChanged; import info.nightscout.androidaps.events.EventRefreshOverview; import info.nightscout.androidaps.interfaces.CommandQueueProvider; @@ -36,6 +37,7 @@ import info.nightscout.androidaps.interfaces.Constraint; import info.nightscout.androidaps.interfaces.ConstraintsInterface; import info.nightscout.androidaps.interfaces.PluginDescription; import info.nightscout.androidaps.interfaces.PluginType; +import info.nightscout.androidaps.interfaces.ProfileFunction; import info.nightscout.androidaps.interfaces.PumpDescription; import info.nightscout.androidaps.interfaces.PumpInterface; import info.nightscout.androidaps.interfaces.PumpPluginBase; @@ -43,9 +45,9 @@ import info.nightscout.androidaps.logging.AAPSLogger; import info.nightscout.androidaps.logging.LTag; import info.nightscout.androidaps.plugins.bus.RxBusWrapper; import info.nightscout.androidaps.plugins.common.ManufacturerType; -import info.nightscout.androidaps.interfaces.ProfileFunction; import info.nightscout.androidaps.plugins.general.actions.defs.CustomAction; import info.nightscout.androidaps.plugins.general.actions.defs.CustomActionType; +import info.nightscout.androidaps.plugins.general.command.defs.CustomCommand; import info.nightscout.androidaps.plugins.general.overview.events.EventDismissNotification; import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification; import info.nightscout.androidaps.plugins.general.overview.events.EventOverviewBolusProgress; @@ -64,7 +66,6 @@ import info.nightscout.androidaps.plugins.pump.combo.ruffyscripter.history.PumpH import info.nightscout.androidaps.plugins.pump.combo.ruffyscripter.history.PumpHistoryRequest; import info.nightscout.androidaps.plugins.pump.combo.ruffyscripter.history.Tdd; import info.nightscout.androidaps.plugins.pump.common.defs.PumpType; -import info.nightscout.androidaps.db.Treatment; import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin; import info.nightscout.androidaps.utils.DateUtil; import info.nightscout.androidaps.utils.InstanceId; @@ -1395,7 +1396,10 @@ public class ComboPlugin extends PumpPluginBase implements PumpInterface, Constr @Override public void executeCustomAction(CustomActionType customActionType) { + } + @Nullable @Override public PumpEnactResult executeCustomCommand(CustomCommand customCommand) { + return null; } @Override diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/LocalInsightPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/LocalInsightPlugin.java index 0da2b70209..ab61996023 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/LocalInsightPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/insight/LocalInsightPlugin.java @@ -12,6 +12,7 @@ import android.os.IBinder; import android.os.Looper; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import org.json.JSONException; import org.json.JSONObject; @@ -56,6 +57,7 @@ import info.nightscout.androidaps.plugins.bus.RxBusWrapper; import info.nightscout.androidaps.plugins.common.ManufacturerType; import info.nightscout.androidaps.plugins.general.actions.defs.CustomAction; import info.nightscout.androidaps.plugins.general.actions.defs.CustomActionType; +import info.nightscout.androidaps.plugins.general.command.defs.CustomCommand; import info.nightscout.androidaps.plugins.general.nsclient.NSUpload; import info.nightscout.androidaps.plugins.general.nsclient.UploadQueue; import info.nightscout.androidaps.plugins.general.overview.events.EventDismissNotification; @@ -1171,7 +1173,10 @@ public class LocalInsightPlugin extends PumpPluginBase implements PumpInterface, @Override public void executeCustomAction(CustomActionType customActionType) { + } + @Nullable @Override public PumpEnactResult executeCustomCommand(CustomCommand customCommand) { + return null; } private void readHistory() { diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/mdi/MDIPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/mdi/MDIPlugin.java index dbc8823395..d53b1a2104 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/mdi/MDIPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/mdi/MDIPlugin.java @@ -1,6 +1,7 @@ package info.nightscout.androidaps.plugins.pump.mdi; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import org.json.JSONException; import org.json.JSONObject; @@ -27,6 +28,7 @@ import info.nightscout.androidaps.plugins.bus.RxBusWrapper; import info.nightscout.androidaps.plugins.common.ManufacturerType; import info.nightscout.androidaps.plugins.general.actions.defs.CustomAction; import info.nightscout.androidaps.plugins.general.actions.defs.CustomActionType; +import info.nightscout.androidaps.plugins.general.command.defs.CustomCommand; import info.nightscout.androidaps.plugins.pump.common.defs.PumpType; import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin; import info.nightscout.androidaps.utils.DateUtil; @@ -281,7 +283,10 @@ public class MDIPlugin extends PumpPluginBase implements PumpInterface { @Override public void executeCustomAction(CustomActionType customActionType) { + } + @Nullable @Override public PumpEnactResult executeCustomCommand(CustomCommand customCommand) { + return null; } @Override diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/virtual/VirtualPumpPlugin.kt b/app/src/main/java/info/nightscout/androidaps/plugins/pump/virtual/VirtualPumpPlugin.kt index 2ef49e95b5..e102cc07d2 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/virtual/VirtualPumpPlugin.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/virtual/VirtualPumpPlugin.kt @@ -16,9 +16,9 @@ import info.nightscout.androidaps.logging.AAPSLogger import info.nightscout.androidaps.logging.LTag import info.nightscout.androidaps.plugins.bus.RxBusWrapper import info.nightscout.androidaps.plugins.common.ManufacturerType -import info.nightscout.androidaps.interfaces.ProfileFunction import info.nightscout.androidaps.plugins.general.actions.defs.CustomAction import info.nightscout.androidaps.plugins.general.actions.defs.CustomActionType +import info.nightscout.androidaps.plugins.general.command.defs.CustomCommand import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification import info.nightscout.androidaps.plugins.general.overview.events.EventOverviewBolusProgress import info.nightscout.androidaps.plugins.general.overview.notifications.Notification @@ -133,6 +133,11 @@ class VirtualPumpPlugin @Inject constructor( } override fun executeCustomAction(customActionType: CustomActionType) {} + + override fun executeCustomCommand(customCommand: CustomCommand?): PumpEnactResult? { + return null + } + override fun isInitialized(): Boolean { return true } diff --git a/app/src/main/java/info/nightscout/androidaps/queue/CommandQueue.kt b/app/src/main/java/info/nightscout/androidaps/queue/CommandQueue.kt index bbc541c7b9..4e10a9314f 100644 --- a/app/src/main/java/info/nightscout/androidaps/queue/CommandQueue.kt +++ b/app/src/main/java/info/nightscout/androidaps/queue/CommandQueue.kt @@ -20,16 +20,16 @@ import info.nightscout.androidaps.events.EventProfileNeedsUpdate import info.nightscout.androidaps.interfaces.ActivePluginProvider import info.nightscout.androidaps.interfaces.CommandQueueProvider import info.nightscout.androidaps.interfaces.Constraint +import info.nightscout.androidaps.interfaces.ProfileFunction import info.nightscout.androidaps.logging.AAPSLogger import info.nightscout.androidaps.logging.LTag import info.nightscout.androidaps.plugins.bus.RxBusWrapper import info.nightscout.androidaps.plugins.configBuilder.ConstraintChecker -import info.nightscout.androidaps.interfaces.ProfileFunction +import info.nightscout.androidaps.plugins.general.command.defs.CustomCommand import info.nightscout.androidaps.plugins.general.overview.events.EventDismissBolusProgressIfRunning import info.nightscout.androidaps.plugins.general.overview.events.EventDismissNotification import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification import info.nightscout.androidaps.plugins.general.overview.notifications.Notification -import info.nightscout.androidaps.queue.commands.Command import info.nightscout.androidaps.queue.commands.* import info.nightscout.androidaps.queue.commands.Command.CommandType import info.nightscout.androidaps.utils.FabricPrivacy @@ -260,7 +260,7 @@ class CommandQueue @Inject constructor( } else { add(CommandBolus(injector, detailedBolusInfo, callback, type)) if (type == CommandType.BOLUS) { // Bring up bolus progress dialog (start here, so the dialog is shown when the bolus is requested, -// not when the Bolus command is starting. The command closes the dialog upon completion). + // not when the Bolus command is starting. The command closes the dialog upon completion). showBolusProgressDialog(detailedBolusInfo.insulin, detailedBolusInfo.context) // Notify Wear about upcoming bolus rxBus.send(EventBolusRequested(detailedBolusInfo.insulin)) @@ -485,6 +485,55 @@ class CommandQueue @Inject constructor( return true } + override fun customCommand(customCommand: CustomCommand, callback: Callback?): Boolean { + if (isCustomCommandInQueue(customCommand.javaClass)) { + callback?.result(executingNowError())?.run() + return false + } + // remove all unfinished + removeAllCustomCommands(customCommand.javaClass) + // add new command to queue + add(CommandCustomCommand(injector, customCommand, callback)) + notifyAboutNewCommand() + return true + } + + @Synchronized + override fun isCustomCommandInQueue(customCommandType: Class): Boolean { + if(isCustomCommandRunning(customCommandType)) { + return true + } + synchronized(queue) { + for (i in queue.indices) { + val command = queue[i] + if (command is CommandCustomCommand && customCommandType.isInstance(command.customCommand)) { + return true + } + } + } + return false + } + + override fun isCustomCommandRunning(customCommandType: Class): Boolean { + val performing = this.performing + if (performing is CommandCustomCommand && customCommandType.isInstance(performing.customCommand)) { + return true + } + return false + } + + @Synchronized + private fun removeAllCustomCommands(targetType: Class) { + synchronized(queue) { + for (i in queue.indices.reversed()) { + val command = queue[i] + if (command is CustomCommand && targetType.isInstance(command.commandType)) { + queue.removeAt(i) + } + } + } + } + override fun spannedStatus(): Spanned { var s = "" var line = 0 diff --git a/app/src/main/java/info/nightscout/androidaps/queue/commands/CommandCustomCommand.kt b/app/src/main/java/info/nightscout/androidaps/queue/commands/CommandCustomCommand.kt new file mode 100644 index 0000000000..0be0b2d6bd --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/queue/commands/CommandCustomCommand.kt @@ -0,0 +1,27 @@ +package info.nightscout.androidaps.queue.commands + +import dagger.android.HasAndroidInjector +import info.nightscout.androidaps.interfaces.ActivePluginProvider +import info.nightscout.androidaps.logging.LTag +import info.nightscout.androidaps.plugins.general.command.defs.CustomCommand +import info.nightscout.androidaps.queue.Callback +import javax.inject.Inject + +class CommandCustomCommand( + injector: HasAndroidInjector, + val customCommand: CustomCommand, + callback: Callback? +) : Command(injector, CommandType.CUSTOM_COMMAND, callback) { + + @Inject lateinit var activePlugin: ActivePluginProvider + + override fun execute() { + val result = activePlugin.activePump.executeCustomCommand(customCommand) + aapsLogger.debug(LTag.PUMPQUEUE, "Result success: ${result?.success} enacted: ${result?.enacted}") + callback?.result(result)?.run() + } + + override fun status(): String { + return customCommand.statusDescription + } +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/queue/commands/CommandStartPump.kt b/app/src/main/java/info/nightscout/androidaps/queue/commands/CommandStartPump.kt index 57e94e8d31..da911775bf 100644 --- a/app/src/main/java/info/nightscout/androidaps/queue/commands/CommandStartPump.kt +++ b/app/src/main/java/info/nightscout/androidaps/queue/commands/CommandStartPump.kt @@ -2,8 +2,6 @@ package info.nightscout.androidaps.queue.commands import dagger.android.HasAndroidInjector import info.nightscout.androidaps.interfaces.ActivePluginProvider -import info.nightscout.androidaps.interfaces.ProfileFunction -import info.nightscout.androidaps.plugins.pump.common.defs.PumpType import info.nightscout.androidaps.plugins.pump.insight.LocalInsightPlugin import info.nightscout.androidaps.queue.Callback import javax.inject.Inject @@ -14,17 +12,12 @@ class CommandStartPump( ) : Command(injector, CommandType.START_PUMP, callback) { @Inject lateinit var activePlugin: ActivePluginProvider - @Inject lateinit var profileFunction: ProfileFunction override fun execute() { val pump = activePlugin.activePump if (pump is LocalInsightPlugin) { val result = pump.startPump() callback?.result(result)?.run() - } else if (pump.pumpDescription.pumpType == PumpType.Insulet_Omnipod) { - // When using CommandQueue.setProfile, it refuses to set the profile is the same as the current profile - // However we need to set the current profile to resume delivery in case the Pod is suspended - pump.setNewBasalProfile(profileFunction.getProfile()) } } diff --git a/app/src/test/java/info/nightscout/androidaps/queue/CommandQueueTest.kt b/app/src/test/java/info/nightscout/androidaps/queue/CommandQueueTest.kt index b375602bfb..2a7e93c8d1 100644 --- a/app/src/test/java/info/nightscout/androidaps/queue/CommandQueueTest.kt +++ b/app/src/test/java/info/nightscout/androidaps/queue/CommandQueueTest.kt @@ -11,6 +11,7 @@ import info.nightscout.androidaps.interfaces.ActivePluginProvider import info.nightscout.androidaps.interfaces.Constraint import info.nightscout.androidaps.interfaces.PumpDescription import info.nightscout.androidaps.plugins.configBuilder.ConstraintChecker +import info.nightscout.androidaps.plugins.general.command.defs.CustomCommand import info.nightscout.androidaps.plugins.pump.virtual.VirtualPumpPlugin import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin import info.nightscout.androidaps.queue.commands.Command @@ -192,4 +193,88 @@ class CommandQueueTest : TestBaseWithProfile() { Assert.assertFalse(queued) Assert.assertEquals(commandQueue.size(), 0) } + + @Test + fun isCustomCommandRunning() { + // given + Assert.assertEquals(0, commandQueue.size()) + + // when + val queued1 = commandQueue.customCommand(CustomCommand1(), null) + val queued2 = commandQueue.customCommand(CustomCommand2(), null) + commandQueue.pickup() + + // then + Assert.assertTrue(queued1) + Assert.assertTrue(queued2) + Assert.assertTrue(commandQueue.isCustomCommandInQueue(CustomCommand1::class.java)) + Assert.assertTrue(commandQueue.isCustomCommandInQueue(CustomCommand2::class.java)) + Assert.assertTrue(commandQueue.isCustomCommandRunning(CustomCommand1::class.java)) + Assert.assertFalse(commandQueue.isCustomCommandRunning(CustomCommand2::class.java)) + + Assert.assertEquals(1, commandQueue.size()) + } + + @Test + fun isCustomCommandInQueue() { + // given + Assert.assertEquals(0, commandQueue.size()) + + // when + val queued1 = commandQueue.customCommand(CustomCommand1(), null) + val queued2 = commandQueue.customCommand(CustomCommand2(), null) + + // then + Assert.assertTrue(queued1) + Assert.assertTrue(queued2) + Assert.assertTrue(commandQueue.isCustomCommandInQueue(CustomCommand1::class.java)) + Assert.assertTrue(commandQueue.isCustomCommandInQueue(CustomCommand2::class.java)) + Assert.assertFalse(commandQueue.isCustomCommandInQueue(CustomCommand3::class.java)) + Assert.assertEquals(2, commandQueue.size()) + } + + @Test + fun differentCustomCommandsAllowed() { + // given + Assert.assertEquals(0, commandQueue.size()) + + // when + val queued1 = commandQueue.customCommand(CustomCommand1(), null) + val queued2 = commandQueue.customCommand(CustomCommand2(), null) + + // then + Assert.assertTrue(queued1) + Assert.assertTrue(queued2) + Assert.assertEquals(2, commandQueue.size()) + } + + @Test + fun sameCustomCommandNotAllowed() { + // given + Assert.assertEquals(0, commandQueue.size()) + + // when + val queued1 = commandQueue.customCommand(CustomCommand1(), null) + val queued2 = commandQueue.customCommand(CustomCommand1(), null) + + // then + Assert.assertTrue(queued1) + Assert.assertFalse(queued2) + Assert.assertEquals(1, commandQueue.size()) + } + + private class CustomCommand1 : CustomCommand { + override val statusDescription: String + get() = "CUSTOM COMMAND 1" + } + + private class CustomCommand2 : CustomCommand { + override val statusDescription: String + get() = "CUSTOM COMMAND 2" + } + + private class CustomCommand3 : CustomCommand { + override val statusDescription: String + get() = "CUSTOM COMMAND 3" + } } \ No newline at end of file diff --git a/core/src/main/java/info/nightscout/androidaps/interfaces/CommandQueueProvider.kt b/core/src/main/java/info/nightscout/androidaps/interfaces/CommandQueueProvider.kt index a5f9bf21df..c535cdf60b 100644 --- a/core/src/main/java/info/nightscout/androidaps/interfaces/CommandQueueProvider.kt +++ b/core/src/main/java/info/nightscout/androidaps/interfaces/CommandQueueProvider.kt @@ -3,6 +3,7 @@ package info.nightscout.androidaps.interfaces import android.text.Spanned import info.nightscout.androidaps.data.DetailedBolusInfo import info.nightscout.androidaps.data.Profile +import info.nightscout.androidaps.plugins.general.command.defs.CustomCommand import info.nightscout.androidaps.queue.Callback import info.nightscout.androidaps.queue.commands.Command @@ -33,6 +34,9 @@ interface CommandQueueProvider { fun setUserOptions(callback: Callback?): Boolean fun loadTDDs(callback: Callback?): Boolean fun loadEvents(callback: Callback?): Boolean + fun customCommand(customCommand: CustomCommand, callback: Callback?): Boolean + fun isCustomCommandRunning(customCommandType: Class): Boolean + fun isCustomCommandInQueue(customCommandType: Class): Boolean fun spannedStatus(): Spanned fun isThisProfileSet(profile: Profile): Boolean } \ No newline at end of file diff --git a/core/src/main/java/info/nightscout/androidaps/interfaces/PumpInterface.java b/core/src/main/java/info/nightscout/androidaps/interfaces/PumpInterface.java index 8308cc34e2..a3ce523921 100644 --- a/core/src/main/java/info/nightscout/androidaps/interfaces/PumpInterface.java +++ b/core/src/main/java/info/nightscout/androidaps/interfaces/PumpInterface.java @@ -1,5 +1,7 @@ package info.nightscout.androidaps.interfaces; +import androidx.annotation.Nullable; + import org.jetbrains.annotations.NotNull; import org.json.JSONObject; @@ -11,7 +13,9 @@ import info.nightscout.androidaps.data.PumpEnactResult; import info.nightscout.androidaps.plugins.common.ManufacturerType; import info.nightscout.androidaps.plugins.general.actions.defs.CustomAction; import info.nightscout.androidaps.plugins.general.actions.defs.CustomActionType; +import info.nightscout.androidaps.plugins.general.command.defs.CustomCommand; import info.nightscout.androidaps.plugins.pump.common.defs.PumpType; +import info.nightscout.androidaps.queue.Callback; import info.nightscout.androidaps.utils.TimeChangeType; /** @@ -105,10 +109,32 @@ public interface PumpInterface { boolean canHandleDST(); + /** + * Provides a list of custom actions to be displayed in the Actions tab. + * Plese note that these actions will not be queued upon execution + * + * @return list of custom actions + */ + @Nullable List getCustomActions(); + /** + * Executes a custom action. Please not that these actions will not be queued + * + * @param customActionType action to be executed + */ void executeCustomAction(CustomActionType customActionType); + /** + * Executes a custom queued command + * To place a custom command on the queue, see {@link CommandQueueProvider#customCommand(CustomCommand, Callback)} + * + * @param customCommand the custom command to be executed + * @return PumpEnactResult that represents the command execution result + */ + @Nullable + PumpEnactResult executeCustomCommand(CustomCommand customCommand); + /** * This method will be called when time or Timezone changes, and pump driver can then do a specific action (for * example update clock on pump). diff --git a/core/src/main/java/info/nightscout/androidaps/plugins/general/command/defs/CustomCommand.kt b/core/src/main/java/info/nightscout/androidaps/plugins/general/command/defs/CustomCommand.kt new file mode 100644 index 0000000000..ad3eb80df9 --- /dev/null +++ b/core/src/main/java/info/nightscout/androidaps/plugins/general/command/defs/CustomCommand.kt @@ -0,0 +1,16 @@ +package info.nightscout.androidaps.plugins.general.command.defs + +import java.io.Serializable + +/** + * Implement this interface for every custom pump command that you want to be able to queue + * See [info.nightscout.androidaps.interfaces.CommandQueueProvider] for queuing a custom command. + */ +interface CustomCommand : Serializable { + + /** + * @return short description of this command to be used in [info.nightscout.androidaps.queue.commands.Command.status] + * The description is typically all caps. + */ + val statusDescription: String +} \ No newline at end of file diff --git a/core/src/main/java/info/nightscout/androidaps/queue/commands/Command.kt b/core/src/main/java/info/nightscout/androidaps/queue/commands/Command.kt index ea6a8b9196..149810ea35 100644 --- a/core/src/main/java/info/nightscout/androidaps/queue/commands/Command.kt +++ b/core/src/main/java/info/nightscout/androidaps/queue/commands/Command.kt @@ -31,7 +31,8 @@ abstract class Command( SET_USER_SETTINGS, // so far only Dana specific, START_PUMP, STOP_PUMP, - INSIGHT_SET_TBR_OVER_ALARM // insight only + INSIGHT_SET_TBR_OVER_ALARM, // insight only + CUSTOM_COMMAND } init { diff --git a/danar/src/main/java/info/nightscout/androidaps/danar/AbstractDanaRPlugin.java b/danar/src/main/java/info/nightscout/androidaps/danar/AbstractDanaRPlugin.java index f048c101ff..60a6da15a6 100644 --- a/danar/src/main/java/info/nightscout/androidaps/danar/AbstractDanaRPlugin.java +++ b/danar/src/main/java/info/nightscout/androidaps/danar/AbstractDanaRPlugin.java @@ -1,6 +1,7 @@ package info.nightscout.androidaps.danar; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import org.json.JSONException; import org.json.JSONObject; @@ -9,9 +10,9 @@ import java.util.Date; import java.util.List; import dagger.android.HasAndroidInjector; -import info.nightscout.androidaps.dana.DanaPumpInterface; import info.nightscout.androidaps.dana.DanaFragment; import info.nightscout.androidaps.dana.DanaPump; +import info.nightscout.androidaps.dana.DanaPumpInterface; import info.nightscout.androidaps.dana.comm.RecordTypes; import info.nightscout.androidaps.danar.services.AbstractDanaRExecutionService; import info.nightscout.androidaps.data.Profile; @@ -37,6 +38,7 @@ import info.nightscout.androidaps.plugins.common.ManufacturerType; import info.nightscout.androidaps.plugins.configBuilder.ConstraintChecker; import info.nightscout.androidaps.plugins.general.actions.defs.CustomAction; import info.nightscout.androidaps.plugins.general.actions.defs.CustomActionType; +import info.nightscout.androidaps.plugins.general.command.defs.CustomCommand; import info.nightscout.androidaps.plugins.general.overview.events.EventDismissNotification; import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification; import info.nightscout.androidaps.plugins.general.overview.notifications.Notification; @@ -511,7 +513,10 @@ public abstract class AbstractDanaRPlugin extends PumpPluginBase implements Pump @Override public void executeCustomAction(CustomActionType customActionType) { + } + @Nullable @Override public PumpEnactResult executeCustomCommand(CustomCommand customCommand) { + return null; } @Override diff --git a/danars/src/main/java/info/nightscout/androidaps/danars/DanaRSPlugin.kt b/danars/src/main/java/info/nightscout/androidaps/danars/DanaRSPlugin.kt index 291f2d3b9b..70141f05f1 100644 --- a/danars/src/main/java/info/nightscout/androidaps/danars/DanaRSPlugin.kt +++ b/danars/src/main/java/info/nightscout/androidaps/danars/DanaRSPlugin.kt @@ -26,6 +26,7 @@ import info.nightscout.androidaps.plugins.common.ManufacturerType import info.nightscout.androidaps.plugins.configBuilder.ConstraintChecker import info.nightscout.androidaps.plugins.general.actions.defs.CustomAction import info.nightscout.androidaps.plugins.general.actions.defs.CustomActionType +import info.nightscout.androidaps.plugins.general.command.defs.CustomCommand import info.nightscout.androidaps.plugins.general.overview.events.EventDismissNotification import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification import info.nightscout.androidaps.plugins.general.overview.notifications.Notification @@ -658,6 +659,7 @@ class DanaRSPlugin @Inject constructor( override fun loadTDDs(): PumpEnactResult = loadHistory(info.nightscout.androidaps.dana.comm.RecordTypes.RECORD_TYPE_DAILY) override fun getCustomActions(): List? = null override fun executeCustomAction(customActionType: CustomActionType) {} + override fun executeCustomCommand(customCommand: CustomCommand?): PumpEnactResult? = null override fun canHandleDST(): Boolean = false override fun timezoneOrDSTChanged(timeChangeType: TimeChangeType?) {} override fun clearPairing() { diff --git a/medtronic/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/MedtronicPumpPlugin.java b/medtronic/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/MedtronicPumpPlugin.java index ee58a61b6b..c968861718 100644 --- a/medtronic/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/MedtronicPumpPlugin.java +++ b/medtronic/src/main/java/info/nightscout/androidaps/plugins/pump/medtronic/MedtronicPumpPlugin.java @@ -48,6 +48,7 @@ import info.nightscout.androidaps.plugins.bus.RxBusWrapper; import info.nightscout.androidaps.plugins.common.ManufacturerType; import info.nightscout.androidaps.plugins.general.actions.defs.CustomAction; import info.nightscout.androidaps.plugins.general.actions.defs.CustomActionType; +import info.nightscout.androidaps.plugins.general.command.defs.CustomCommand; import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification; import info.nightscout.androidaps.plugins.general.overview.notifications.Notification; import info.nightscout.androidaps.plugins.pump.common.PumpPluginAbstract; @@ -1583,6 +1584,10 @@ public class MedtronicPumpPlugin extends PumpPluginAbstract implements PumpInter } + @Nullable @Override public PumpEnactResult executeCustomCommand(CustomCommand customCommand) { + return null; + } + @Override public void timezoneOrDSTChanged(TimeChangeType changeType) { diff --git a/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/OmnipodPumpPlugin.java b/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/OmnipodPumpPlugin.java index 5fb940cceb..b01b2312a2 100644 --- a/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/OmnipodPumpPlugin.java +++ b/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/OmnipodPumpPlugin.java @@ -12,21 +12,16 @@ import android.os.SystemClock; import androidx.annotation.NonNull; -import org.jetbrains.annotations.NotNull; import org.joda.time.DateTime; import org.joda.time.Duration; import org.json.JSONException; import org.json.JSONObject; -import java.util.ArrayList; import java.util.Collections; import java.util.Date; -import java.util.EnumSet; -import java.util.Iterator; import java.util.List; import java.util.Objects; import java.util.Optional; -import java.util.Set; import java.util.function.Supplier; import javax.inject.Inject; @@ -47,6 +42,7 @@ import info.nightscout.androidaps.interfaces.ActivePluginProvider; import info.nightscout.androidaps.interfaces.CommandQueueProvider; import info.nightscout.androidaps.interfaces.PluginDescription; import info.nightscout.androidaps.interfaces.PluginType; +import info.nightscout.androidaps.interfaces.ProfileFunction; import info.nightscout.androidaps.interfaces.PumpDescription; import info.nightscout.androidaps.interfaces.PumpInterface; import info.nightscout.androidaps.interfaces.PumpPluginBase; @@ -56,7 +52,7 @@ import info.nightscout.androidaps.plugins.bus.RxBusWrapper; import info.nightscout.androidaps.plugins.common.ManufacturerType; import info.nightscout.androidaps.plugins.general.actions.defs.CustomAction; import info.nightscout.androidaps.plugins.general.actions.defs.CustomActionType; -import info.nightscout.androidaps.plugins.general.overview.events.EventDismissNotification; +import info.nightscout.androidaps.plugins.general.command.defs.CustomCommand; import info.nightscout.androidaps.plugins.general.overview.events.EventNewNotification; import info.nightscout.androidaps.plugins.general.overview.notifications.Notification; import info.nightscout.androidaps.plugins.pump.common.data.TempBasalPair; @@ -74,7 +70,6 @@ import info.nightscout.androidaps.plugins.pump.omnipod.data.ActiveBolus; import info.nightscout.androidaps.plugins.pump.omnipod.data.RLHistoryItemOmnipod; import info.nightscout.androidaps.plugins.pump.omnipod.definition.OmnipodCommandType; import info.nightscout.androidaps.plugins.pump.omnipod.definition.OmnipodCustomActionType; -import info.nightscout.androidaps.plugins.pump.omnipod.definition.OmnipodStatusRequestType; import info.nightscout.androidaps.plugins.pump.omnipod.definition.OmnipodStorageKeys; import info.nightscout.androidaps.plugins.pump.omnipod.driver.communication.action.service.ExpirationReminderBuilder; import info.nightscout.androidaps.plugins.pump.omnipod.driver.communication.message.response.podinfo.PodInfoRecentPulseLog; @@ -84,6 +79,10 @@ import info.nightscout.androidaps.plugins.pump.omnipod.driver.manager.PodStateMa import info.nightscout.androidaps.plugins.pump.omnipod.event.EventOmnipodPumpValuesChanged; import info.nightscout.androidaps.plugins.pump.omnipod.event.EventOmnipodTbrChanged; import info.nightscout.androidaps.plugins.pump.omnipod.manager.AapsOmnipodManager; +import info.nightscout.androidaps.plugins.pump.omnipod.queue.command.CommandHandleTimeChange; +import info.nightscout.androidaps.plugins.pump.omnipod.queue.command.CommandUpdateAlertConfiguration; +import info.nightscout.androidaps.plugins.pump.omnipod.queue.command.OmnipodCustomCommand; +import info.nightscout.androidaps.plugins.pump.omnipod.queue.command.OmnipodCustomCommandType; import info.nightscout.androidaps.plugins.pump.omnipod.rileylink.service.RileyLinkOmnipodService; import info.nightscout.androidaps.plugins.pump.omnipod.ui.OmnipodFragment; import info.nightscout.androidaps.plugins.pump.omnipod.util.AapsOmnipodUtil; @@ -107,6 +106,7 @@ import io.reactivex.schedulers.Schedulers; public class OmnipodPumpPlugin extends PumpPluginBase implements PumpInterface, RileyLinkPumpDevice { private static final long RILEY_LINK_CONNECT_TIMEOUT_MILLIS = 3 * 60 * 1000L; // 3 minutes private static final long STATUS_CHECK_INTERVAL_MILLIS = 60 * 1000L; // 1 minute + public static final int STARTUP_STATUS_REQUEST_TRIES = 3; private final PodStateManager podStateManager; private final RileyLinkServiceData rileyLinkServiceData; @@ -115,6 +115,7 @@ public class OmnipodPumpPlugin extends PumpPluginBase implements PumpInterface, private final AapsOmnipodUtil aapsOmnipodUtil; private final RileyLinkUtil rileyLinkUtil; private final OmnipodAlertUtil omnipodAlertUtil; + private final ProfileFunction profileFunction; private final AAPSLogger aapsLogger; private final RxBusWrapper rxBus; private final ActivePluginProvider activePlugin; @@ -127,8 +128,8 @@ public class OmnipodPumpPlugin extends PumpPluginBase implements PumpInterface, private final ServiceConnection serviceConnection; private final PumpType pumpType = PumpType.Insulet_Omnipod; - private final List customActions = new ArrayList<>(); - private final Set statusRequests = Collections.synchronizedSet(EnumSet.noneOf(OmnipodStatusRequestType.class)); + private final List customActions = Collections.singletonList(new CustomAction( + R.string.omnipod_custom_action_reset_rileylink, OmnipodCustomActionType.RESET_RILEY_LINK_CONFIGURATION, true)); private final CompositeDisposable disposables = new CompositeDisposable(); // variables for handling statuses and history @@ -139,12 +140,11 @@ public class OmnipodPumpPlugin extends PumpPluginBase implements PumpInterface, private boolean busy = false; private int timeChangeRetries; private long nextPodCheck; - private boolean sentIdToFirebase; private long lastConnectionTimeMillis; private final Handler loopHandler = new Handler(Looper.getMainLooper()); private final Runnable statusChecker; - private OmnipodCommandType currentCommand; + private boolean isCancelTempBasalRunning; @Inject public OmnipodPumpPlugin( @@ -164,7 +164,8 @@ public class OmnipodPumpPlugin extends PumpPluginBase implements PumpInterface, DateUtil dateUtil, AapsOmnipodUtil aapsOmnipodUtil, RileyLinkUtil rileyLinkUtil, - OmnipodAlertUtil omnipodAlertUtil + OmnipodAlertUtil omnipodAlertUtil, + ProfileFunction profileFunction ) { super(new PluginDescription() // .mainType(PluginType.PUMP) // @@ -189,12 +190,10 @@ public class OmnipodPumpPlugin extends PumpPluginBase implements PumpInterface, this.aapsOmnipodUtil = aapsOmnipodUtil; this.rileyLinkUtil = rileyLinkUtil; this.omnipodAlertUtil = omnipodAlertUtil; + this.profileFunction = profileFunction; pumpDescription = new PumpDescription(pumpType); - customActions.add(new CustomAction( - R.string.omnipod_custom_action_reset_rileylink, OmnipodCustomActionType.RESET_RILEY_LINK_CONFIGURATION, true)); - this.serviceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { @@ -231,14 +230,11 @@ public class OmnipodPumpPlugin extends PumpPluginBase implements PumpInterface, aapsOmnipodManager.createSuspendedFakeTbrIfNotExists(); } - if (!getCommandQueue().statusInQueue()) { - if (!OmnipodPumpPlugin.this.statusRequests.isEmpty()) { - getCommandQueue().readStatus("Status Refresh Requested", null); - } else if (OmnipodPumpPlugin.this.hasTimeDateOrTimeZoneChanged) { - getCommandQueue().readStatus("Date or Time Zone Changed", null); - } else if (!OmnipodPumpPlugin.this.verifyPodAlertConfiguration()) { - getCommandQueue().readStatus("Expiration Alerts Changed", null); - } + if (OmnipodPumpPlugin.this.hasTimeDateOrTimeZoneChanged) { + getCommandQueue().customCommand(new CommandHandleTimeChange(false), null); + } + if (!OmnipodPumpPlugin.this.verifyPodAlertConfiguration()) { + getCommandQueue().customCommand(new CommandUpdateAlertConfiguration(), null); } doPodCheck(); @@ -291,7 +287,7 @@ public class OmnipodPumpPlugin extends PumpPluginBase implements PumpInterface, event.isChanged(getResourceHelper(), R.string.key_omnipod_low_reservoir_alert_enabled) || event.isChanged(getResourceHelper(), R.string.key_omnipod_low_reservoir_alert_units)) { if (!verifyPodAlertConfiguration() && !getCommandQueue().statusInQueue()) { - getCommandQueue().readStatus("Expiration Alerts Changed", null); + getCommandQueue().customCommand(new CommandUpdateAlertConfiguration(), null); } } }, fabricPrivacy::logException) @@ -335,7 +331,7 @@ public class OmnipodPumpPlugin extends PumpPluginBase implements PumpInterface, // The pod is not running a TBR, while AAPS thinks it is if (!podStateManager.isTempBasalRunning()) { // Only report TBR cancellations if they haven't been explicitly requested - if (currentCommand != OmnipodCommandType.CANCEL_TEMPORARY_BASAL) { + if (!isCancelTempBasalRunning) { if (activePlugin.getActiveTreatments().isTempBasalInProgress() && !aapsOmnipodManager.hasSuspendedFakeTbr()) { aapsOmnipodManager.reportCancelledTbr(); } @@ -361,13 +357,9 @@ public class OmnipodPumpPlugin extends PumpPluginBase implements PumpInterface, Notification notification = new Notification(Notification.OMNIPOD_POD_NOT_ATTACHED, resourceHelper.gs(R.string.omnipod_error_pod_not_attached), Notification.NORMAL); rxBus.send(new EventNewNotification(notification)); } else { - rxBus.send(new EventDismissNotification(Notification.OMNIPOD_POD_NOT_ATTACHED)); - if (podStateManager.isSuspended()) { Notification notification = new Notification(Notification.OMNIPOD_POD_SUSPENDED, resourceHelper.gs(R.string.omnipod_error_pod_suspended), Notification.NORMAL); rxBus.send(new EventNewNotification(notification)); - } else { - rxBus.send(new EventDismissNotification(Notification.OMNIPOD_POD_SUSPENDED)); } } @@ -375,7 +367,6 @@ public class OmnipodPumpPlugin extends PumpPluginBase implements PumpInterface, } } - // TODO is this correct? @Override public boolean isInitialized() { return isConnected() && podStateManager.isPodActivationCompleted(); @@ -455,130 +446,23 @@ public class OmnipodPumpPlugin extends PumpPluginBase implements PumpInterface, lastConnectionTimeMillis = System.currentTimeMillis(); } - // We abuse getPumpStatus to squeeze commands in the queue - // The only actual status requests we send to the Pod are on startup (in initializeAfterRileyLinkConnection) - // And when the user explicitly requested it by clicking the Refresh button on the Omnipod tab - // We don't do periodical status requests because that can drain the Pod's battery - // However that should be fine because we get a StatusResponse from all insulin commands sent to the Pod + /** + * The only actual status requests we send here to the Pod are on startup (in initializeAfterRileyLinkConnection) + * And when the user explicitly requested it by clicking the Refresh button on the Omnipod tab (which is executed through {@link #executeCustomCommand(CustomCommand)}) + * We don't do periodical status requests because that could drain the Pod's battery + */ @Override public void getPumpStatus() { if (firstRun) { initializeAfterRileyLinkConnection(); - } else if (!statusRequests.isEmpty()) { - synchronized (statusRequests) { - Iterator iterator = statusRequests.iterator(); - - while (iterator.hasNext()) { - OmnipodStatusRequestType statusRequest = iterator.next(); - switch (statusRequest) { - case GET_PULSE_LOG: - try { - PodInfoRecentPulseLog result = executeCommand(OmnipodCommandType.GET_POD_PULSE_LOG, aapsOmnipodManager::readPulseLog); - Intent i = new Intent(context, ErrorHelperActivity.class); - i.putExtra("soundid", 0); - i.putExtra("status", "Pulse Log (copied to clipboard):\n" + result.toString()); - i.putExtra("title", resourceHelper.gs(R.string.omnipod_warning)); - i.putExtra("clipboardContent", result.toString()); - i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - context.startActivity(i); - } catch (Exception ex) { - aapsLogger.warn(LTag.PUMP, "Failed to retrieve pulse log", ex); - Intent i = new Intent(context, ErrorHelperActivity.class); - i.putExtra("soundid", 0); - i.putExtra("status", "Failed to retrieve pulse log"); - i.putExtra("title", resourceHelper.gs(R.string.omnipod_warning)); - i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - context.startActivity(i); - } - break; - case ACKNOWLEDGE_ALERTS: - executeCommand(OmnipodCommandType.ACKNOWLEDGE_ALERTS, aapsOmnipodManager::acknowledgeAlerts); - break; - case GET_POD_STATE: - executeCommand(OmnipodCommandType.GET_POD_STATUS, aapsOmnipodManager::getPodStatus); - break; - case SUSPEND_DELIVERY: - executeCommand(OmnipodCommandType.SUSPEND_DELIVERY, aapsOmnipodManager::suspendDelivery); - break; - case SET_TIME: - executeCommand(OmnipodCommandType.SET_TIME, aapsOmnipodManager::setTime); - break; - default: - aapsLogger.error(LTag.PUMP, "Unknown status request: " + statusRequest.name()); - } - iterator.remove(); - } - } - } else if (this.hasTimeDateOrTimeZoneChanged) { - PumpEnactResult result; - if (aapsOmnipodManager.isTimeChangeEventEnabled()) { - result = executeCommand(OmnipodCommandType.SET_TIME, aapsOmnipodManager::setTime); - } else { - // Even if automatically changing the time is disabled, we still want to at least do a GetStatus request, - // in order to update the Pod's activation time, which we need for calculating the time on the Pod - result = executeCommand(OmnipodCommandType.GET_POD_STATUS, aapsOmnipodManager::getPodStatus); - } - - if (result.success) { - this.hasTimeDateOrTimeZoneChanged = false; - timeChangeRetries = 0; - - if (aapsOmnipodManager.isTimeChangeEventEnabled()) { - Notification notification = new Notification( - Notification.TIME_OR_TIMEZONE_CHANGE, - resourceHelper.gs(R.string.omnipod_time_or_timezone_change), - Notification.INFO, 60); - rxBus.send(new EventNewNotification(notification)); - } - - } else { - timeChangeRetries++; - - if (timeChangeRetries > 3) { - if (aapsOmnipodManager.isTimeChangeEventEnabled()) { - Notification notification = new Notification( - Notification.TIME_OR_TIMEZONE_CHANGE, - resourceHelper.gs(R.string.omnipod_time_or_timezone_change_failed), - Notification.INFO, 60); - rxBus.send(new EventNewNotification(notification)); - } - this.hasTimeDateOrTimeZoneChanged = false; - timeChangeRetries = 0; - } - } - } else if (!verifyPodAlertConfiguration()) { - Duration expirationReminderTimeBeforeShutdown = omnipodAlertUtil.getExpirationReminderTimeBeforeShutdown(); - Integer lowReservoirAlertUnits = omnipodAlertUtil.getLowReservoirAlertUnits(); - - List alertConfigurations = new ExpirationReminderBuilder(podStateManager) // - .expirationAdvisory(expirationReminderTimeBeforeShutdown != null, - Optional.ofNullable(expirationReminderTimeBeforeShutdown).orElse(Duration.ZERO)) // - .lowReservoir(lowReservoirAlertUnits != null, Optional.ofNullable(lowReservoirAlertUnits).orElse(0)) // - .build(); - - PumpEnactResult result = executeCommand(OmnipodCommandType.CONFIGURE_ALERTS, () -> aapsOmnipodManager.configureAlerts(alertConfigurations)); - - if (result.success) { - aapsLogger.info(LTag.PUMP, "Successfully configured alerts in Pod"); - - podStateManager.setExpirationAlertTimeBeforeShutdown(expirationReminderTimeBeforeShutdown); - podStateManager.setLowReservoirAlertUnits(lowReservoirAlertUnits); - - Notification notification = new Notification( - Notification.OMNIPOD_POD_ALERTS_UPDATED, - resourceHelper.gs(R.string.omnipod_expiration_alerts_updated), - Notification.INFO, 60); - rxBus.send(new EventNewNotification(notification)); - } else { - aapsLogger.warn(LTag.PUMP, "Failed to configure alerts in Pod"); - } + firstRun = false; } } - @NotNull + @NonNull @Override public PumpEnactResult setNewBasalProfile(Profile profile) { - PumpEnactResult result = executeCommand(OmnipodCommandType.SET_BASAL_PROFILE, () -> aapsOmnipodManager.setBasalProfile(profile)); + PumpEnactResult result = executeCommand(OmnipodCommandType.SET_BASAL_PROFILE, () -> aapsOmnipodManager.setBasalProfile(profile, true)); aapsLogger.info(LTag.PUMP, "Basal Profile was set: " + result.success); @@ -597,7 +481,7 @@ public class OmnipodPumpPlugin extends PumpPluginBase implements PumpInterface, @Override public long lastDataTime() { - return podStateManager.isPodActivationCompleted() ? podStateManager.getLastSuccessfulCommunication().getMillis() : 0; + return podStateManager.isPodInitialized() ? podStateManager.getLastSuccessfulCommunication().getMillis() : 0; } @Override @@ -652,7 +536,7 @@ public class OmnipodPumpPlugin extends PumpPluginBase implements PumpInterface, executeCommand(OmnipodCommandType.CANCEL_BOLUS, aapsOmnipodManager::cancelBolus); } - // if enforceNew===true current temp basal is cancelled and new TBR set (duration is prolonged), + // if enforceNew is true, current temp basal is cancelled and new TBR set (duration is prolonged), // if false and the same rate is requested enacted=false and success=true is returned and TBR is not changed @Override public PumpEnactResult setTempBasalAbsolute(Double absoluteRate, Integer @@ -694,7 +578,12 @@ public class OmnipodPumpPlugin extends PumpPluginBase implements PumpInterface, return new PumpEnactResult(getInjector()).success(true).enacted(false); } - return executeCommand(OmnipodCommandType.CANCEL_TEMPORARY_BASAL, aapsOmnipodManager::cancelTemporaryBasal); + isCancelTempBasalRunning = true; + try { + return executeCommand(OmnipodCommandType.CANCEL_TEMPORARY_BASAL, aapsOmnipodManager::cancelTemporaryBasal); + } finally { + isCancelTempBasalRunning = false; + } } // TODO improve (i8n and more) @@ -753,12 +642,12 @@ public class OmnipodPumpPlugin extends PumpPluginBase implements PumpInterface, return pumpType.getManufacturer(); } - @Override @NotNull + @Override @NonNull public PumpType model() { return pumpType; } - @NotNull + @NonNull @Override public String serialNumber() { return podStateManager.isPodInitialized() ? String.valueOf(podStateManager.getAddress()) : "-"; @@ -813,18 +702,143 @@ public class OmnipodPumpPlugin extends PumpPluginBase implements PumpInterface, break; default: - aapsLogger.warn(LTag.PUMP, "Unknown custom action: {}" + mcat); + aapsLogger.warn(LTag.PUMP, "Unknown custom action: " + mcat); break; } } + @Override + public PumpEnactResult executeCustomCommand(CustomCommand command) { + if (!(command instanceof OmnipodCustomCommand)) { + aapsLogger.warn(LTag.PUMP, "Unknown custom command: " + command.getClass().getName()); + return new PumpEnactResult(getInjector()).success(false).enacted(false).comment(resourceHelper.gs(R.string.omnipod_unknown_custom_command, command.getClass().getName())); + } + + OmnipodCustomCommandType commandType = ((OmnipodCustomCommand) command).getType(); + + aapsLogger.debug(LTag.PUMP, "Executing custom command: " + commandType); + + switch (commandType) { + case ACKNOWLEDGE_ALERTS: + return executeCommand(OmnipodCommandType.ACKNOWLEDGE_ALERTS, aapsOmnipodManager::acknowledgeAlerts); + case GET_POD_STATUS: + return executeCommand(OmnipodCommandType.GET_POD_STATUS, aapsOmnipodManager::getPodStatus); + case READ_PULSE_LOG: + return retrievePulseLog(); + case SUSPEND_DELIVERY: + return executeCommand(OmnipodCommandType.SUSPEND_DELIVERY, aapsOmnipodManager::suspendDelivery); + case RESUME_DELIVERY: + return executeCommand(OmnipodCommandType.RESUME_DELIVERY, () -> aapsOmnipodManager.setBasalProfile(profileFunction.getProfile(), false)); + case HANDLE_TIME_CHANGE: + return handleTimeChange(((CommandHandleTimeChange) command).isRequestedByUser()); + case UPDATE_ALERT_CONFIGURATION: + return updateAlertConfiguration(); + default: + aapsLogger.warn(LTag.PUMP, "Unknown custom command: " + commandType); + return new PumpEnactResult(getInjector()).success(false).enacted(false).comment(resourceHelper.gs(R.string.omnipod_unknown_custom_command, commandType)); + } + } + + private PumpEnactResult retrievePulseLog() { + PodInfoRecentPulseLog result; + try { + result = executeCommand(OmnipodCommandType.READ_POD_PULSE_LOG, aapsOmnipodManager::readPulseLog); + } catch (Exception ex) { + return new PumpEnactResult(getInjector()).success(false).enacted(false).comment(aapsOmnipodManager.translateException(ex)); + } + + Intent i = new Intent(context, ErrorHelperActivity.class); + i.putExtra("soundid", 0); + i.putExtra("status", resourceHelper.gs(R.string.omnipod_pulse_log_value) + ":\n" + result.toString()); + i.putExtra("title", resourceHelper.gs(R.string.omnipod_pulse_log)); + i.putExtra("clipboardContent", result.toString()); + i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + context.startActivity(i); + return new PumpEnactResult(getInjector()).success(true).enacted(false); + } + + @NonNull private PumpEnactResult updateAlertConfiguration() { + Duration expirationReminderTimeBeforeShutdown = omnipodAlertUtil.getExpirationReminderTimeBeforeShutdown(); + Integer lowReservoirAlertUnits = omnipodAlertUtil.getLowReservoirAlertUnits(); + + List alertConfigurations = new ExpirationReminderBuilder(podStateManager) // + .expirationAdvisory(expirationReminderTimeBeforeShutdown != null, + Optional.ofNullable(expirationReminderTimeBeforeShutdown).orElse(Duration.ZERO)) // + .lowReservoir(lowReservoirAlertUnits != null, Optional.ofNullable(lowReservoirAlertUnits).orElse(0)) // + .build(); + + PumpEnactResult result = executeCommand(OmnipodCommandType.CONFIGURE_ALERTS, () -> aapsOmnipodManager.configureAlerts(alertConfigurations)); + + if (result.success) { + aapsLogger.info(LTag.PUMP, "Successfully configured alerts in Pod"); + + podStateManager.setExpirationAlertTimeBeforeShutdown(expirationReminderTimeBeforeShutdown); + podStateManager.setLowReservoirAlertUnits(lowReservoirAlertUnits); + + Notification notification = new Notification( + Notification.OMNIPOD_POD_ALERTS_UPDATED, + resourceHelper.gs(R.string.omnipod_expiration_alerts_updated), + Notification.INFO, 60); + rxBus.send(new EventNewNotification(notification)); + } else { + aapsLogger.warn(LTag.PUMP, "Failed to configure alerts in Pod"); + } + + return result; + } + + @NonNull private PumpEnactResult handleTimeChange(boolean requestedByUser) { + aapsLogger.debug(LTag.PUMP, "Setting time, requestedByUser={}", requestedByUser); + + PumpEnactResult result; + if (requestedByUser || aapsOmnipodManager.isTimeChangeEventEnabled()) { + result = executeCommand(OmnipodCommandType.SET_TIME, () -> aapsOmnipodManager.setTime(!requestedByUser)); + } else { + // Even if automatically changing the time is disabled, we still want to at least do a GetStatus request, + // in order to update the Pod's activation time, which we need for calculating the time on the Pod + result = executeCommand(OmnipodCommandType.GET_POD_STATUS, aapsOmnipodManager::getPodStatus); + } + + if (result.success) { + this.hasTimeDateOrTimeZoneChanged = false; + timeChangeRetries = 0; + + if (!requestedByUser && aapsOmnipodManager.isTimeChangeEventEnabled()) { + Notification notification = new Notification( + Notification.TIME_OR_TIMEZONE_CHANGE, + resourceHelper.gs(R.string.omnipod_time_or_timezone_change), + Notification.INFO, 60); + rxBus.send(new EventNewNotification(notification)); + } + + } else { + if (!requestedByUser) { + timeChangeRetries++; + + if (timeChangeRetries > 3) { + if (aapsOmnipodManager.isTimeChangeEventEnabled()) { + Notification notification = new Notification( + Notification.TIME_OR_TIMEZONE_CHANGE, + resourceHelper.gs(R.string.omnipod_time_or_timezone_change_failed), + Notification.INFO, 60); + rxBus.send(new EventNewNotification(notification)); + } + this.hasTimeDateOrTimeZoneChanged = false; + timeChangeRetries = 0; + } + } + } + + return result; + } + @Override public void timezoneOrDSTChanged(TimeChangeType timeChangeType) { aapsLogger.warn(LTag.PUMP, "Time, Date and/or TimeZone changed. [changeType=" + timeChangeType.name() + ", eventHandlingEnabled=" + aapsOmnipodManager.isTimeChangeEventEnabled() + "]"); if (podStateManager.isPodRunning()) { aapsLogger.info(LTag.PUMP, "Time, Date and/or TimeZone changed event received and will be consumed by driver."); - this.hasTimeDateOrTimeZoneChanged = true; + hasTimeDateOrTimeZoneChanged = true; } } @@ -853,10 +867,6 @@ public class OmnipodPumpPlugin extends PumpPluginBase implements PumpInterface, return false; } - public void addPodStatusRequest(OmnipodStatusRequestType pumpStatusRequest) { - statusRequests.add(pumpStatusRequest); - } - @Override public boolean isFakingTempsByExtendedBoluses() { return false; @@ -887,54 +897,47 @@ public class OmnipodPumpPlugin extends PumpPluginBase implements PumpInterface, aapsLogger.debug(LTag.PUMP, "stopConnecting [PumpPluginAbstract] - default (empty) implementation."); } - @NotNull @Override public PumpEnactResult setTempBasalPercent(Integer percent, Integer + @NonNull @Override public PumpEnactResult setTempBasalPercent(Integer percent, Integer durationInMinutes, Profile profile, boolean enforceNew) { aapsLogger.debug(LTag.PUMP, "setTempBasalPercent [OmnipodPumpPlugin] - Not implemented."); return getOperationNotSupportedWithCustomText(info.nightscout.androidaps.core.R.string.pump_operation_not_supported_by_pump_driver); } - @NotNull @Override public PumpEnactResult setExtendedBolus(Double insulin, Integer + @NonNull @Override public PumpEnactResult setExtendedBolus(Double insulin, Integer durationInMinutes) { aapsLogger.debug(LTag.PUMP, "setExtendedBolus [OmnipodPumpPlugin] - Not implemented."); return getOperationNotSupportedWithCustomText(info.nightscout.androidaps.core.R.string.pump_operation_not_supported_by_pump_driver); } - @NotNull @Override public PumpEnactResult cancelExtendedBolus() { + @NonNull @Override public PumpEnactResult cancelExtendedBolus() { aapsLogger.debug(LTag.PUMP, "cancelExtendedBolus [OmnipodPumpPlugin] - Not implemented."); return getOperationNotSupportedWithCustomText(info.nightscout.androidaps.core.R.string.pump_operation_not_supported_by_pump_driver); } - @NotNull @Override public PumpEnactResult loadTDDs() { + @NonNull @Override public PumpEnactResult loadTDDs() { aapsLogger.debug(LTag.PUMP, "loadTDDs [OmnipodPumpPlugin] - Not implemented."); return getOperationNotSupportedWithCustomText(info.nightscout.androidaps.core.R.string.pump_operation_not_supported_by_pump_driver); } - public OmnipodCommandType getCurrentCommand() { - return currentCommand; - } - private void initializeAfterRileyLinkConnection() { if (podStateManager.isPodInitialized() && podStateManager.getPodProgressStatus().isAtLeast(PodProgressStatus.PAIRING_COMPLETED)) { PumpEnactResult result = executeCommand(OmnipodCommandType.GET_POD_STATUS, aapsOmnipodManager::getPodStatus); - if (result.success) { - aapsLogger.debug(LTag.PUMP, "Successfully retrieved Pod status on startup"); - } else { - aapsLogger.warn(LTag.PUMP, "Failed to retrieve Pod status on startup"); + for (int i = 0; STARTUP_STATUS_REQUEST_TRIES > i; i++) { + if (result.success) { + aapsLogger.debug(LTag.PUMP, "Successfully retrieved Pod status on startup"); + break; + } else { + aapsLogger.warn(LTag.PUMP, "Failed to retrieve Pod status on startup"); + } } } else { aapsLogger.debug(LTag.PUMP, "Not retrieving Pod status on startup: no Pod running"); } - if (!sentIdToFirebase) { - Bundle params = new Bundle(); - params.putString("version", BuildConfig.VERSION); + Bundle params = new Bundle(); + params.putString("version", BuildConfig.VERSION); - fabricPrivacy.getFirebaseAnalytics().logEvent("OmnipodPumpInit", params); - - sentIdToFirebase = true; - } - - this.firstRun = false; + fabricPrivacy.getFirebaseAnalytics().logEvent("OmnipodPumpInit", params); } @NonNull private PumpEnactResult deliverBolus(final DetailedBolusInfo detailedBolusInfo) { @@ -952,14 +955,12 @@ public class OmnipodPumpPlugin extends PumpPluginBase implements PumpInterface, private T executeCommand(OmnipodCommandType commandType, Supplier supplier) { try { - currentCommand = commandType; aapsLogger.debug(LTag.PUMP, "Executing command: {}", commandType); rileyLinkUtil.getRileyLinkHistory().add(new RLHistoryItemOmnipod(getInjector(), commandType)); return supplier.get(); } finally { - currentCommand = null; rxBus.send(new EventRefreshOverview("Omnipod command: " + commandType.name(), false)); rxBus.send(new EventOmnipodPumpValuesChanged()); } diff --git a/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/definition/OmnipodCommandType.java b/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/definition/OmnipodCommandType.java index b7be57c864..9cfdff382d 100644 --- a/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/definition/OmnipodCommandType.java +++ b/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/definition/OmnipodCommandType.java @@ -19,8 +19,9 @@ public enum OmnipodCommandType { SET_TIME(R.string.omnipod_cmd_set_time), // CONFIGURE_ALERTS(R.string.omnipod_cmd_configure_alerts), // ACKNOWLEDGE_ALERTS(R.string.omnipod_cmd_acknowledge_alerts), // - GET_POD_PULSE_LOG(R.string.omnipod_cmd_get_pulse_log), // - SUSPEND_DELIVERY(R.string.omnipod_cmd_suspend_delivery); + READ_POD_PULSE_LOG(R.string.omnipod_cmd_get_pulse_log), // + SUSPEND_DELIVERY(R.string.omnipod_cmd_suspend_delivery), + RESUME_DELIVERY(R.string.omnipod_cmd_resume_delivery); private int resourceId; diff --git a/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/definition/OmnipodStatusRequestType.java b/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/definition/OmnipodStatusRequestType.java deleted file mode 100644 index 50ccd62fb9..0000000000 --- a/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/definition/OmnipodStatusRequestType.java +++ /dev/null @@ -1,9 +0,0 @@ -package info.nightscout.androidaps.plugins.pump.omnipod.definition; - -public enum OmnipodStatusRequestType { - ACKNOWLEDGE_ALERTS, - GET_POD_STATE, - GET_PULSE_LOG, - SUSPEND_DELIVERY, - SET_TIME -} diff --git a/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/driver/communication/action/service/ExpirationReminderBuilder.java b/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/driver/communication/action/service/ExpirationReminderBuilder.java index a75104d25a..0c39fefd99 100644 --- a/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/driver/communication/action/service/ExpirationReminderBuilder.java +++ b/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/driver/communication/action/service/ExpirationReminderBuilder.java @@ -17,11 +17,9 @@ import info.nightscout.androidaps.plugins.pump.omnipod.driver.util.AlertConfigur public final class ExpirationReminderBuilder { private final Map alerts = new HashMap<>(); private final DateTime endOfServiceTime; - private final PodStateManager podStateManager; public ExpirationReminderBuilder(PodStateManager podStateManager) { this.endOfServiceTime = podStateManager.getActivatedAt().plus(OmnipodConstants.SERVICE_DURATION); - this.podStateManager = podStateManager; } public ExpirationReminderBuilder defaults() { @@ -45,21 +43,17 @@ public final class ExpirationReminderBuilder { public ExpirationReminderBuilder expirationAdvisory(boolean active, Duration timeBeforeShutdown) { DateTime expirationAdvisoryAlarmTime = endOfServiceTime.minus(timeBeforeShutdown); - if (DateTime.now().isBefore(expirationAdvisoryAlarmTime)) { - Duration timeUntilExpirationAdvisoryAlarm = new Duration(DateTime.now(), - expirationAdvisoryAlarmTime); - AlertConfiguration expirationAdvisoryAlertConfiguration = AlertConfigurationUtil.createExpirationAdvisoryAlertConfiguration(active, - timeUntilExpirationAdvisoryAlarm, timeBeforeShutdown); - alerts.put(expirationAdvisoryAlertConfiguration.getAlertSlot(), expirationAdvisoryAlertConfiguration); - } + Duration timeUntilExpirationAdvisoryAlarm = DateTime.now().isBefore(expirationAdvisoryAlarmTime) ? new Duration(DateTime.now(), + expirationAdvisoryAlarmTime) : Duration.ZERO; + AlertConfiguration expirationAdvisoryAlertConfiguration = AlertConfigurationUtil.createExpirationAdvisoryAlertConfiguration(active, + timeUntilExpirationAdvisoryAlarm, timeBeforeShutdown); + alerts.put(expirationAdvisoryAlertConfiguration.getAlertSlot(), expirationAdvisoryAlertConfiguration); return this; } public ExpirationReminderBuilder lowReservoir(boolean active, int units) { - if (podStateManager.getReservoirLevel() == null || podStateManager.getReservoirLevel().intValue() >= units) { - AlertConfiguration lowReservoirAlertConfiguration = AlertConfigurationUtil.createLowReservoirAlertConfiguration(active, (double) units); - alerts.put(lowReservoirAlertConfiguration.getAlertSlot(), lowReservoirAlertConfiguration); - } + AlertConfiguration lowReservoirAlertConfiguration = AlertConfigurationUtil.createLowReservoirAlertConfiguration(active, (double) units); + alerts.put(lowReservoirAlertConfiguration.getAlertSlot(), lowReservoirAlertConfiguration); return this; } diff --git a/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/driver/communication/message/OmnipodMessage.java b/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/driver/communication/message/OmnipodMessage.java index 995121c1c3..7531631b12 100644 --- a/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/driver/communication/message/OmnipodMessage.java +++ b/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/driver/communication/message/OmnipodMessage.java @@ -3,10 +3,13 @@ package info.nightscout.androidaps.plugins.pump.omnipod.driver.communication.mes import java.util.ArrayList; import java.util.List; +import info.nightscout.androidaps.logging.AAPSLogger; +import info.nightscout.androidaps.logging.LTag; import info.nightscout.androidaps.plugins.pump.common.utils.ByteUtil; import info.nightscout.androidaps.plugins.pump.omnipod.driver.communication.message.command.GetStatusCommand; import info.nightscout.androidaps.plugins.pump.omnipod.driver.definition.MessageBlockType; import info.nightscout.androidaps.plugins.pump.omnipod.driver.definition.OmnipodCrc; +import info.nightscout.androidaps.plugins.pump.omnipod.driver.definition.PacketType; import info.nightscout.androidaps.plugins.pump.omnipod.driver.definition.PodInfoType; import info.nightscout.androidaps.plugins.pump.omnipod.driver.exception.CrcMismatchException; import info.nightscout.androidaps.plugins.pump.omnipod.driver.exception.MessageDecodingException; @@ -91,8 +94,11 @@ public class OmnipodMessage { return encodedData; } - public void padWithGetStatusCommands(int packetSize) { - while (getEncoded().length < packetSize) { + public void padWithGetStatusCommands(int packetSize, AAPSLogger aapsLogger) { + while (getEncoded().length <= packetSize) { + if (getEncoded().length == PacketType.PDM.getMaxBodyLength()) { + aapsLogger.debug(LTag.PUMPBTCOMM, "Message length equals max body length: {}", this); + } messageBlocks.add(new GetStatusCommand(PodInfoType.NORMAL)); } } diff --git a/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/driver/manager/OmnipodManager.java b/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/driver/manager/OmnipodManager.java index 6af3de95a8..8442ff0940 100644 --- a/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/driver/manager/OmnipodManager.java +++ b/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/driver/manager/OmnipodManager.java @@ -224,10 +224,6 @@ public class OmnipodManager { suspendDelivery(acknowledgementBeep); } - // Store the new Basal schedule after successfully suspending delivery, so that if setting the Basal schedule fails, - // And we later try to resume delivery, the new schedule is used - podStateManager.setBasalSchedule(schedule); - try { executeAndVerify(() -> communicationService.executeAction(new SetBasalScheduleAction(podStateManager, schedule, false, podStateManager.getScheduleOffset(), acknowledgementBeep))); @@ -248,6 +244,8 @@ public class OmnipodManager { throw ex; } } + + } finally { logCommandExecutionFinished("setBasalSchedule"); } @@ -420,7 +418,7 @@ public class OmnipodManager { bolusCommandExecutionSubject = null; disposables.add(Completable.complete() // - .delay(estimatedRemainingBolusDuration.getMillis() + 250, TimeUnit.MILLISECONDS) // + .delay(estimatedRemainingBolusDuration.getMillis(), TimeUnit.MILLISECONDS) // .observeOn(Schedulers.io()) // .doOnComplete(() -> { synchronized (bolusDataMutex) { @@ -432,9 +430,8 @@ public class OmnipodManager { StatusResponse statusResponse = getPodStatus(); if (statusResponse.getDeliveryStatus().isBolusing()) { throw new IllegalDeliveryStatusException(DeliveryStatus.NORMAL, statusResponse.getDeliveryStatus()); - } else { - break; } + break; } catch (PodFaultException ex) { // Subtract units not delivered in case of a Pod failure bolusNotDelivered = ex.getFaultEvent().getBolusNotDelivered(); diff --git a/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/driver/util/AlertConfigurationUtil.java b/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/driver/util/AlertConfigurationUtil.java index 0d223458d6..92e2d2d74d 100644 --- a/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/driver/util/AlertConfigurationUtil.java +++ b/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/driver/util/AlertConfigurationUtil.java @@ -18,12 +18,12 @@ public class AlertConfigurationUtil { public static AlertConfiguration createExpirationAdvisoryAlertConfiguration(boolean active, Duration timeUntilAlert, Duration duration) { return new AlertConfiguration(AlertType.EXPIRATION_ADVISORY_ALERT, AlertSlot.SLOT7, active, false, duration, - new TimerAlertTrigger(timeUntilAlert), BeepType.BIP_BEEP_BIP_BEEP_BIP_BEEP_BIP_BEEP, BeepRepeat.EVERY_15_MINUTES); + new TimerAlertTrigger(timeUntilAlert), BeepType.BIP_BEEP_BIP_BEEP_BIP_BEEP_BIP_BEEP, BeepRepeat.EVERY_MINUTE_FOR_3_MINUTES_REPEAT_EVERY_15_MINUTES); } public static AlertConfiguration createShutdownImminentAlertConfiguration(Duration timeUntilAlert) { return new AlertConfiguration(AlertType.SHUTDOWN_IMMINENT_ALARM, AlertSlot.SLOT2, true, false, Duration.ZERO, - new TimerAlertTrigger(timeUntilAlert), BeepType.BIP_BEEP_BIP_BEEP_BIP_BEEP_BIP_BEEP, BeepRepeat.EVERY_MINUTE_FOR_3_MINUTES_REPEAT_EVERY_15_MINUTES); + new TimerAlertTrigger(timeUntilAlert), BeepType.BIP_BEEP_BIP_BEEP_BIP_BEEP_BIP_BEEP, BeepRepeat.EVERY_15_MINUTES); } public static AlertConfiguration createAutoOffAlertConfiguration(boolean active, Duration countdownDuration) { diff --git a/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/manager/AapsOmnipodManager.java b/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/manager/AapsOmnipodManager.java index 3aec54c7b5..1ce873c46a 100644 --- a/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/manager/AapsOmnipodManager.java +++ b/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/manager/AapsOmnipodManager.java @@ -6,6 +6,7 @@ import org.joda.time.Duration; import java.util.ArrayList; import java.util.Date; import java.util.List; +import java.util.function.Supplier; import javax.inject.Inject; import javax.inject.Singleton; @@ -75,7 +76,6 @@ import info.nightscout.androidaps.plugins.pump.omnipod.util.AapsOmnipodUtil; import info.nightscout.androidaps.plugins.pump.omnipod.util.OmnipodAlertUtil; import info.nightscout.androidaps.utils.resources.ResourceHelper; import info.nightscout.androidaps.utils.sharedPreferences.SP; -import io.reactivex.disposables.Disposable; import io.reactivex.subjects.SingleSubject; @Singleton @@ -120,11 +120,11 @@ public class AapsOmnipodManager { this.aapsOmnipodUtil = aapsOmnipodUtil; this.aapsLogger = aapsLogger; this.rxBus = rxBus; + this.sp = sp; this.resourceHelper = resourceHelper; this.injector = injector; this.activePlugin = activePlugin; this.databaseHelper = databaseHelper; - this.sp = sp; this.omnipodAlertUtil = omnipodAlertUtil; delegate = new OmnipodManager(aapsLogger, sp, communicationService, podStateManager); @@ -148,12 +148,12 @@ public class AapsOmnipodManager { } try { - Disposable disposable = delegate.pairAndPrime().subscribe(res -> // - handleSetupActionResult(podInitActionType, podInitReceiver, res, System.currentTimeMillis(), null)); + executeCommand(() -> delegate.pairAndPrime().subscribe(res -> // + handleSetupActionResult(podInitActionType, podInitReceiver, res, System.currentTimeMillis(), null))); return new PumpEnactResult(injector).success(true).enacted(true); } catch (Exception ex) { - String comment = handleAndTranslateException(ex); + String comment = translateException(ex); podInitReceiver.returnInitTaskStatus(podInitActionType, false, comment); addFailureToHistory(System.currentTimeMillis(), PodHistoryEntryType.PAIR_AND_PRIME, comment); return new PumpEnactResult(injector).success(false).enacted(false).comment(comment); @@ -180,66 +180,68 @@ public class AapsOmnipodManager { throw new CommandInitializationException("Basal profile mapping failed", ex); } - Disposable disposable = delegate.insertCannula(basalSchedule, omnipodAlertUtil.getExpirationReminderTimeBeforeShutdown(), omnipodAlertUtil.getLowReservoirAlertUnits()).subscribe(res -> // - handleSetupActionResult(podInitActionType, podInitReceiver, res, System.currentTimeMillis(), profile)); - - rxBus.send(new EventDismissNotification(Notification.OMNIPOD_POD_NOT_ATTACHED)); - - cancelSuspendedFakeTbrIfExists(); - - return new PumpEnactResult(injector).success(true).enacted(true); + executeCommand(() -> delegate.insertCannula(basalSchedule, omnipodAlertUtil.getExpirationReminderTimeBeforeShutdown(), omnipodAlertUtil.getLowReservoirAlertUnits()).subscribe(res -> // + handleSetupActionResult(podInitActionType, podInitReceiver, res, System.currentTimeMillis(), profile))); } catch (Exception ex) { - String comment = handleAndTranslateException(ex); + String comment = translateException(ex); podInitReceiver.returnInitTaskStatus(podInitActionType, false, comment); addFailureToHistory(PodHistoryEntryType.FILL_CANNULA_SET_BASAL_PROFILE, comment); return new PumpEnactResult(injector).success(false).enacted(false).comment(comment); } + + rxBus.send(new EventDismissNotification(Notification.OMNIPOD_POD_NOT_ATTACHED)); + + cancelSuspendedFakeTbrIfExists(); + + return new PumpEnactResult(injector).success(true).enacted(true); } public PumpEnactResult configureAlerts(List alertConfigurations) { try { - StatusResponse statusResponse = delegate.configureAlerts(alertConfigurations); - addSuccessToHistory(PodHistoryEntryType.CONFIGURE_ALERTS, alertConfigurations); - return new PumpEnactResult(injector).success(true).enacted(false); + executeCommand(() -> delegate.configureAlerts(alertConfigurations)); } catch (Exception ex) { - String comment = handleAndTranslateException(ex); + String comment = translateException(ex); addFailureToHistory(PodHistoryEntryType.CONFIGURE_ALERTS, comment); return new PumpEnactResult(injector).success(false).enacted(false).comment(comment); } + + addSuccessToHistory(PodHistoryEntryType.CONFIGURE_ALERTS, alertConfigurations); + return new PumpEnactResult(injector).success(true).enacted(false); } public PumpEnactResult getPodStatus() { + StatusResponse statusResponse; + try { - StatusResponse statusResponse = delegate.getPodStatus(); - addSuccessToHistory(PodHistoryEntryType.GET_POD_STATUS, statusResponse); - return new PumpEnactResult(injector).success(true).enacted(false); + statusResponse = executeCommand(delegate::getPodStatus); } catch (Exception ex) { - String comment = handleAndTranslateException(ex); + String comment = translateException(ex); addFailureToHistory(PodHistoryEntryType.GET_POD_STATUS, comment); return new PumpEnactResult(injector).success(false).enacted(false).comment(comment); } + + addSuccessToHistory(PodHistoryEntryType.GET_POD_STATUS, statusResponse); + return new PumpEnactResult(injector).success(true).enacted(false); } public PumpEnactResult deactivatePod(PodInitReceiver podInitReceiver) { try { - delegate.deactivatePod(); + executeCommand(delegate::deactivatePod); } catch (Exception ex) { - String comment = handleAndTranslateException(ex); + String comment = translateException(ex); podInitReceiver.returnInitTaskStatus(PodInitActionType.DEACTIVATE_POD_WIZARD_STEP, false, comment); addFailureToHistory(PodHistoryEntryType.DEACTIVATE_POD, comment); return new PumpEnactResult(injector).success(false).enacted(false).comment(comment); } addSuccessToHistory(PodHistoryEntryType.DEACTIVATE_POD, null); - createSuspendedFakeTbrIfNotExists(); - podInitReceiver.returnInitTaskStatus(PodInitActionType.DEACTIVATE_POD_WIZARD_STEP, true, null); return new PumpEnactResult(injector).success(true).enacted(true); } - public PumpEnactResult setBasalProfile(Profile profile) { + public PumpEnactResult setBasalProfile(Profile profile, boolean showNotifications) { if (profile == null) { String comment = getStringResource(R.string.omnipod_error_failed_to_set_profile_empty_profile); showNotification(Notification.FAILED_UDPATE_PROFILE, comment, Notification.URGENT, R.raw.boluserror); @@ -255,16 +257,13 @@ public class AapsOmnipodManager { } catch (Exception ex) { throw new CommandInitializationException("Basal profile mapping failed", ex); } - delegate.setBasalSchedule(basalSchedule, isBasalBeepsEnabled()); - - if (historyEntryType == PodHistoryEntryType.RESUME_DELIVERY) { - cancelSuspendedFakeTbrIfExists(); - } - addSuccessToHistory(historyEntryType, profile.getBasalValues()); + executeCommand(() -> delegate.setBasalSchedule(basalSchedule, isBasalBeepsEnabled())); } catch (CommandFailedAfterChangingDeliveryStatusException ex) { createSuspendedFakeTbrIfNotExists(); String comment = getStringResource(R.string.omnipod_error_set_basal_failed_delivery_suspended); - showNotification(Notification.FAILED_UDPATE_PROFILE, comment, Notification.URGENT, R.raw.boluserror); + if (showNotifications) { + showNotification(Notification.FAILED_UDPATE_PROFILE, comment, Notification.URGENT, R.raw.boluserror); + } addFailureToHistory(historyEntryType, comment); return new PumpEnactResult(injector).success(false).enacted(false).comment(comment); } catch (DeliveryStatusVerificationFailedException ex) { @@ -276,20 +275,29 @@ public class AapsOmnipodManager { // Happened when setting the new profile (after suspending delivery) comment = getStringResource(R.string.omnipod_error_set_basal_might_have_failed_delivery_might_be_suspended); } - showNotification(Notification.FAILED_UDPATE_PROFILE, comment, Notification.URGENT, R.raw.boluserror); + if (showNotifications) { + showNotification(Notification.FAILED_UDPATE_PROFILE, comment, Notification.URGENT, R.raw.boluserror); + } addFailureToHistory(historyEntryType, comment); return new PumpEnactResult(injector).success(false).enacted(false).comment(comment); } catch (Exception ex) { - String comment = handleAndTranslateException(ex); - showNotification(Notification.FAILED_UDPATE_PROFILE, comment, Notification.URGENT, R.raw.boluserror); + String comment = translateException(ex); + if (showNotifications) { + showNotification(Notification.FAILED_UDPATE_PROFILE, comment, Notification.URGENT, R.raw.boluserror); + } addFailureToHistory(historyEntryType, comment); return new PumpEnactResult(injector).success(false).enacted(false).comment(comment); } - rxBus.send(new EventDismissNotification(Notification.OMNIPOD_POD_SUSPENDED)); - showNotification(Notification.PROFILE_SET_OK, - resourceHelper.gs(R.string.profile_set_ok), - Notification.INFO, null); + if (historyEntryType == PodHistoryEntryType.RESUME_DELIVERY) { + cancelSuspendedFakeTbrIfExists(); + rxBus.send(new EventDismissNotification(Notification.OMNIPOD_POD_SUSPENDED)); + } + addSuccessToHistory(historyEntryType, profile.getBasalValues()); + + if (showNotifications) { + showNotification(Notification.PROFILE_SET_OK, resourceHelper.gs(R.string.profile_set_ok), Notification.INFO, null); + } return new PumpEnactResult(injector).success(true).enacted(true); } @@ -311,17 +319,17 @@ public class AapsOmnipodManager { Date bolusStarted; try { - bolusCommandResult = delegate.bolus(PumpType.Insulet_Omnipod.determineCorrectBolusSize(detailedBolusInfo.insulin), beepsEnabled, beepsEnabled, detailedBolusInfo.isSMB ? null : + bolusCommandResult = executeCommand(() -> delegate.bolus(PumpType.Insulet_Omnipod.determineCorrectBolusSize(detailedBolusInfo.insulin), beepsEnabled, beepsEnabled, detailedBolusInfo.isSMB ? null : (estimatedUnitsDelivered, percentage) -> { EventOverviewBolusProgress progressUpdateEvent = EventOverviewBolusProgress.INSTANCE; progressUpdateEvent.setStatus(getStringResource(R.string.bolusdelivering, detailedBolusInfo.insulin)); progressUpdateEvent.setPercent(percentage); sendEvent(progressUpdateEvent); - }); + })); bolusStarted = new Date(); } catch (Exception ex) { - String comment = handleAndTranslateException(ex); + String comment = translateException(ex); addFailureToHistory(System.currentTimeMillis(), PodHistoryEntryType.SET_BOLUS, comment); return new PumpEnactResult(injector).success(false).enacted(false).comment(comment); } @@ -400,19 +408,19 @@ public class AapsOmnipodManager { String comment = null; for (int i = 1; delegate.hasActiveBolus(); i++) { aapsLogger.debug(LTag.PUMP, "Attempting to cancel bolus (#{})", i); + try { - delegate.cancelBolus(isBolusBeepsEnabled()); + executeCommand(() -> delegate.cancelBolus(isBolusBeepsEnabled())); aapsLogger.debug(LTag.PUMP, "Successfully cancelled bolus", i); addSuccessToHistory(System.currentTimeMillis(), PodHistoryEntryType.CANCEL_BOLUS, null); return new PumpEnactResult(injector).success(true).enacted(true); } catch (PodFaultException ex) { aapsLogger.debug(LTag.PUMP, "Successfully cancelled bolus (implicitly because of a Pod Fault)"); - showPodFaultNotification(ex.getFaultEvent().getFaultEventCode()); addSuccessToHistory(System.currentTimeMillis(), PodHistoryEntryType.CANCEL_BOLUS, null); return new PumpEnactResult(injector).success(true).enacted(true); } catch (Exception ex) { aapsLogger.debug(LTag.PUMP, "Failed to cancel bolus", ex); - comment = handleAndTranslateException(ex); + comment = translateException(ex); } } @@ -423,7 +431,7 @@ public class AapsOmnipodManager { public PumpEnactResult setTemporaryBasal(TempBasalPair tempBasalPair) { boolean beepsEnabled = isTbrBeepsEnabled(); try { - delegate.setTemporaryBasal(PumpType.Insulet_Omnipod.determineCorrectBasalSize(tempBasalPair.getInsulinRate()), Duration.standardMinutes(tempBasalPair.getDurationMinutes()), beepsEnabled, beepsEnabled); + executeCommand(() -> delegate.setTemporaryBasal(PumpType.Insulet_Omnipod.determineCorrectBasalSize(tempBasalPair.getInsulinRate()), Duration.standardMinutes(tempBasalPair.getDurationMinutes()), beepsEnabled, beepsEnabled)); } catch (CommandFailedAfterChangingDeliveryStatusException ex) { String comment = getStringResource(R.string.omnipod_cancelled_old_tbr_failed_to_set_new); addFailureToHistory(PodHistoryEntryType.SET_TEMPORARY_BASAL, comment); @@ -453,7 +461,7 @@ public class AapsOmnipodManager { showNotification(comment, Notification.URGENT, R.raw.boluserror); return new PumpEnactResult(injector).success(false).enacted(false).comment(comment); } catch (Exception ex) { - String comment = handleAndTranslateException(ex); + String comment = translateException(ex); addFailureToHistory(PodHistoryEntryType.SET_TEMPORARY_BASAL, comment); return new PumpEnactResult(injector).success(false).enacted(false).comment(comment); } @@ -467,14 +475,14 @@ public class AapsOmnipodManager { public PumpEnactResult cancelTemporaryBasal() { try { - delegate.cancelTemporaryBasal(isTbrBeepsEnabled()); + executeCommand(() -> delegate.cancelTemporaryBasal(isTbrBeepsEnabled())); } catch (Exception ex) { String comment; if (ex instanceof OmnipodException && !((OmnipodException) ex).isCertainFailure()) { comment = getStringResource(R.string.omnipod_error_cancel_temp_basal_failed_uncertain); showNotification(comment, Notification.URGENT, R.raw.boluserror); } else { - comment = handleAndTranslateException(ex); + comment = translateException(ex); } addFailureToHistory(PodHistoryEntryType.CANCEL_TEMPORARY_BASAL, comment); return new PumpEnactResult(injector).success(false).enacted(false).comment(comment); @@ -495,59 +503,68 @@ public class AapsOmnipodManager { public PumpEnactResult acknowledgeAlerts() { try { - delegate.acknowledgeAlerts(); - addSuccessToHistory(PodHistoryEntryType.ACKNOWLEDGE_ALERTS, null); + executeCommand(delegate::acknowledgeAlerts); } catch (Exception ex) { - String comment = handleAndTranslateException(ex); + String comment = translateException(ex); addFailureToHistory(PodHistoryEntryType.ACKNOWLEDGE_ALERTS, comment); return new PumpEnactResult(injector).success(false).enacted(false).comment(comment); } + + addSuccessToHistory(PodHistoryEntryType.ACKNOWLEDGE_ALERTS, null); return new PumpEnactResult(injector).success(true).enacted(true); } public PumpEnactResult suspendDelivery() { try { - delegate.suspendDelivery(isBasalBeepsEnabled()); + executeCommand(() -> delegate.suspendDelivery(isBasalBeepsEnabled())); } catch (Exception ex) { - String comment = handleAndTranslateException(ex); + String comment; + if (ex instanceof OmnipodException && !((OmnipodException) ex).isCertainFailure()) { + comment = getStringResource(R.string.omnipod_error_suspend_delivery_failed_uncertain); + } else { + comment = getStringResource(R.string.omnipod_error_suspend_delivery_failed); + } addFailureToHistory(PodHistoryEntryType.SUSPEND_DELIVERY, comment); return new PumpEnactResult(injector).success(false).enacted(false).comment(comment); } addSuccessToHistory(PodHistoryEntryType.SUSPEND_DELIVERY, null); - createSuspendedFakeTbrIfNotExists(); return new PumpEnactResult(injector).success(true).enacted(true); } // Updates the pods current time based on the device timezone and the pod's time zone - public PumpEnactResult setTime() { + public PumpEnactResult setTime(boolean showNotifications) { try { - delegate.setTime(isBasalBeepsEnabled()); - addSuccessToHistory(PodHistoryEntryType.SET_TIME, null); + executeCommand(() -> delegate.setTime(isBasalBeepsEnabled())); } catch (CommandFailedAfterChangingDeliveryStatusException ex) { createSuspendedFakeTbrIfNotExists(); String comment = getStringResource(R.string.omnipod_error_set_time_failed_delivery_suspended); - showNotification(comment, Notification.URGENT, R.raw.boluserror); + if (showNotifications) { + showNotification(comment, Notification.URGENT, R.raw.boluserror); + } addFailureToHistory(PodHistoryEntryType.SET_TIME, comment); return new PumpEnactResult(injector).success(false).enacted(false).comment(comment); } catch (DeliveryStatusVerificationFailedException ex) { String comment = getStringResource(R.string.omnipod_error_set_time_failed_delivery_might_be_suspended); - showNotification(comment, Notification.URGENT, R.raw.boluserror); + if (showNotifications) { + showNotification(comment, Notification.URGENT, R.raw.boluserror); + } addFailureToHistory(PodHistoryEntryType.SET_TIME, comment); return new PumpEnactResult(injector).success(false).enacted(false).comment(comment); } catch (Exception ex) { - String comment = handleAndTranslateException(ex); + String comment = translateException(ex); addFailureToHistory(PodHistoryEntryType.SET_TIME, comment); return new PumpEnactResult(injector).success(false).enacted(false).comment(comment); } + addSuccessToHistory(PodHistoryEntryType.SET_TIME, null); return new PumpEnactResult(injector).success(true).enacted(true); } public PodInfoRecentPulseLog readPulseLog() { - PodInfoResponse response = delegate.getPodInfo(PodInfoType.RECENT_PULSE_LOG); + PodInfoResponse response = executeCommand(() -> delegate.getPodInfo(PodInfoType.RECENT_PULSE_LOG)); return (PodInfoRecentPulseLog) response.getPodInfo(); } @@ -734,7 +751,37 @@ public class AapsOmnipodManager { podInitReceiver.returnInitTaskStatus(podInitActionType, res.getResultType().isSuccess(), comment); } - private String handleAndTranslateException(Exception ex) { + private void executeCommand(Runnable runnable) { + try { + runnable.run(); + } catch (Exception ex) { + handleException(ex); + throw ex; + } + } + + private T executeCommand(Supplier supplier) { + try { + return supplier.get(); + } catch (Exception ex) { + handleException(ex); + throw ex; + } + } + + private void handleException(Exception ex) { + if (ex instanceof OmnipodException) { + aapsLogger.error(LTag.PUMP, String.format("Caught OmnipodException[certainFailure=%s] from OmnipodManager", ((OmnipodException) ex).isCertainFailure()), ex); + if (ex instanceof PodFaultException) { + FaultEventCode faultEventCode = ((PodFaultException) ex).getFaultEvent().getFaultEventCode(); + showPodFaultNotification(faultEventCode); + } + } else { + aapsLogger.error(LTag.PUMP, "Caught an unexpected non-OmnipodException from OmnipodManager", ex); + } + } + + public String translateException(Exception ex) { String comment; if (ex instanceof OmnipodException) { @@ -770,18 +817,15 @@ public class AapsOmnipodManager { comment = getStringResource(R.string.omnipod_driver_error_not_enough_data); } else if (ex instanceof PodFaultException) { FaultEventCode faultEventCode = ((PodFaultException) ex).getFaultEvent().getFaultEventCode(); - showPodFaultNotification(faultEventCode); comment = createPodFaultErrorMessage(faultEventCode); } else if (ex instanceof PodReturnedErrorResponseException) { comment = getStringResource(R.string.omnipod_driver_error_pod_returned_error_response); } else { // Shouldn't be reachable - comment = getStringResource(R.string.omnipod_driver_error_unexpected_exception_type, ex.getClass().getName()); + comment = getStringResource(R.string.omnipod_driver_error_unexpected_exception_type, ex.getClass().getName(), ex.getMessage()); } - aapsLogger.error(LTag.PUMP, String.format("Caught OmnipodException[certainFailure=%s] from OmnipodManager (user-friendly error message: %s)", ((OmnipodException) ex).isCertainFailure(), comment), ex); } else { comment = getStringResource(R.string.omnipod_driver_error_unexpected_exception_type, ex.getClass().getName(), ex.getMessage()); - aapsLogger.error(LTag.PUMP, String.format("Caught unexpected exception type[certainFailure=false] from OmnipodManager (user-friendly error message: %s)", comment), ex); } return comment; diff --git a/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/queue/command/CommandAcknowledgeAlerts.java b/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/queue/command/CommandAcknowledgeAlerts.java new file mode 100644 index 0000000000..649623e05e --- /dev/null +++ b/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/queue/command/CommandAcknowledgeAlerts.java @@ -0,0 +1,7 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.queue.command; + +public final class CommandAcknowledgeAlerts extends OmnipodCustomCommand { + public CommandAcknowledgeAlerts() { + super(OmnipodCustomCommandType.ACKNOWLEDGE_ALERTS); + } +} diff --git a/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/queue/command/CommandGetPodStatus.java b/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/queue/command/CommandGetPodStatus.java new file mode 100644 index 0000000000..0dfe7880f8 --- /dev/null +++ b/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/queue/command/CommandGetPodStatus.java @@ -0,0 +1,7 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.queue.command; + +public final class CommandGetPodStatus extends OmnipodCustomCommand { + public CommandGetPodStatus() { + super(OmnipodCustomCommandType.GET_POD_STATUS); + } +} diff --git a/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/queue/command/CommandHandleTimeChange.java b/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/queue/command/CommandHandleTimeChange.java new file mode 100644 index 0000000000..c760b401b5 --- /dev/null +++ b/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/queue/command/CommandHandleTimeChange.java @@ -0,0 +1,14 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.queue.command; + +public final class CommandHandleTimeChange extends OmnipodCustomCommand { + private final boolean requestedByUser; + + public CommandHandleTimeChange(boolean requestedByUser) { + super(OmnipodCustomCommandType.HANDLE_TIME_CHANGE); + this.requestedByUser = requestedByUser; + } + + public boolean isRequestedByUser() { + return requestedByUser; + } +} diff --git a/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/queue/command/CommandReadPulseLog.java b/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/queue/command/CommandReadPulseLog.java new file mode 100644 index 0000000000..5eb910c6db --- /dev/null +++ b/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/queue/command/CommandReadPulseLog.java @@ -0,0 +1,7 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.queue.command; + +public final class CommandReadPulseLog extends OmnipodCustomCommand { + public CommandReadPulseLog() { + super(OmnipodCustomCommandType.READ_PULSE_LOG); + } +} diff --git a/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/queue/command/CommandResumeDelivery.java b/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/queue/command/CommandResumeDelivery.java new file mode 100644 index 0000000000..fe3b07de9f --- /dev/null +++ b/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/queue/command/CommandResumeDelivery.java @@ -0,0 +1,7 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.queue.command; + +public final class CommandResumeDelivery extends OmnipodCustomCommand { + public CommandResumeDelivery() { + super(OmnipodCustomCommandType.RESUME_DELIVERY); + } +} diff --git a/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/queue/command/CommandSuspendDelivery.java b/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/queue/command/CommandSuspendDelivery.java new file mode 100644 index 0000000000..12dc8e8d42 --- /dev/null +++ b/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/queue/command/CommandSuspendDelivery.java @@ -0,0 +1,7 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.queue.command; + +public final class CommandSuspendDelivery extends OmnipodCustomCommand { + public CommandSuspendDelivery() { + super(OmnipodCustomCommandType.SUSPEND_DELIVERY); + } +} diff --git a/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/queue/command/CommandUpdateAlertConfiguration.java b/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/queue/command/CommandUpdateAlertConfiguration.java new file mode 100644 index 0000000000..4edc243845 --- /dev/null +++ b/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/queue/command/CommandUpdateAlertConfiguration.java @@ -0,0 +1,7 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.queue.command; + +public final class CommandUpdateAlertConfiguration extends OmnipodCustomCommand { + public CommandUpdateAlertConfiguration() { + super(OmnipodCustomCommandType.UPDATE_ALERT_CONFIGURATION); + } +} diff --git a/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/queue/command/OmnipodCustomCommand.java b/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/queue/command/OmnipodCustomCommand.java new file mode 100644 index 0000000000..a0b8432f85 --- /dev/null +++ b/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/queue/command/OmnipodCustomCommand.java @@ -0,0 +1,21 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.queue.command; + +import androidx.annotation.NonNull; + +import info.nightscout.androidaps.plugins.general.command.defs.CustomCommand; + +public abstract class OmnipodCustomCommand implements CustomCommand { + private final OmnipodCustomCommandType type; + + OmnipodCustomCommand(@NonNull OmnipodCustomCommandType type) { + this.type = type; + } + + public final OmnipodCustomCommandType getType() { + return type; + } + + @Override public final String getStatusDescription() { + return type.getDescription(); + } +} diff --git a/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/queue/command/OmnipodCustomCommandType.java b/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/queue/command/OmnipodCustomCommandType.java new file mode 100644 index 0000000000..ad1cd46652 --- /dev/null +++ b/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/queue/command/OmnipodCustomCommandType.java @@ -0,0 +1,21 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.queue.command; + +public enum OmnipodCustomCommandType { + ACKNOWLEDGE_ALERTS("ACKNOWLEDGE ALERTS"), + GET_POD_STATUS("GET POD STATUS"), + READ_PULSE_LOG("READ PULSE LOG"), + SUSPEND_DELIVERY("SUSPEND DELIVERY"), + RESUME_DELIVERY("RESUME DELIVERY"), + HANDLE_TIME_CHANGE("HANDLE TIME CHANGE"), + UPDATE_ALERT_CONFIGURATION("UPDATE ALERT CONFIGURATION"); + + private final String description; + + OmnipodCustomCommandType(String description) { + this.description = description; + } + + String getDescription() { + return description; + } +} diff --git a/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/rileylink/manager/OmnipodRileyLinkCommunicationManager.java b/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/rileylink/manager/OmnipodRileyLinkCommunicationManager.java index 19b6b6a4a0..8473daae0c 100644 --- a/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/rileylink/manager/OmnipodRileyLinkCommunicationManager.java +++ b/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/rileylink/manager/OmnipodRileyLinkCommunicationManager.java @@ -194,13 +194,13 @@ public class OmnipodRileyLinkCommunicationManager extends RileyLinkCommunication // receive an ACK instead of a normal response, or a partial response and a communication timeout if (message.isNonceResyncable() && !message.containsBlock(DeactivatePodCommand.class)) { OmnipodMessage paddedMessage = new OmnipodMessage(message); - // If messages are nonce resyncable, we want do distinguish between certain and uncertain failures for verification purposes + // If messages are nonce resyncable, we want to distinguish between certain and uncertain failures for verification purposes // However, some commands (e.g. cancel delivery) are single packet command by nature. When we get a timeout with a single packet, // we are unsure whether or not the command was received by the pod // However, if we send > 1 packet, we know that the command wasn't received if we never send the subsequent packets, // because the last packet contains the CRC. // So we pad the message with get status commands to make it > packet - paddedMessage.padWithGetStatusCommands(PacketType.PDM.getMaxBodyLength()); // First packet is of type PDM + paddedMessage.padWithGetStatusCommands(PacketType.PDM.getMaxBodyLength(), aapsLogger); // First packet is of type PDM encodedMessage = paddedMessage.getEncoded(); } else { encodedMessage = message.getEncoded(); diff --git a/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/ui/OmnipodFragment.kt b/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/ui/OmnipodFragment.kt index ccff4b0c05..2aa3e6df85 100644 --- a/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/ui/OmnipodFragment.kt +++ b/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/ui/OmnipodFragment.kt @@ -11,6 +11,7 @@ import android.view.View import android.view.ViewGroup import dagger.android.support.DaggerFragment import info.nightscout.androidaps.Constants +import info.nightscout.androidaps.activities.ErrorHelperActivity import info.nightscout.androidaps.events.EventPreferenceChange import info.nightscout.androidaps.interfaces.ActivePluginProvider import info.nightscout.androidaps.interfaces.CommandQueueProvider @@ -22,15 +23,14 @@ import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.dialog.RileyL import info.nightscout.androidaps.plugins.pump.common.hw.rileylink.service.RileyLinkServiceData import info.nightscout.androidaps.plugins.pump.omnipod.OmnipodPumpPlugin import info.nightscout.androidaps.plugins.pump.omnipod.R -import info.nightscout.androidaps.plugins.pump.omnipod.definition.OmnipodCommandType -import info.nightscout.androidaps.plugins.pump.omnipod.definition.OmnipodStatusRequestType import info.nightscout.androidaps.plugins.pump.omnipod.driver.definition.OmnipodConstants import info.nightscout.androidaps.plugins.pump.omnipod.driver.definition.PodProgressStatus import info.nightscout.androidaps.plugins.pump.omnipod.driver.manager.PodStateManager import info.nightscout.androidaps.plugins.pump.omnipod.event.EventOmnipodPumpValuesChanged import info.nightscout.androidaps.plugins.pump.omnipod.manager.AapsOmnipodManager +import info.nightscout.androidaps.plugins.pump.omnipod.queue.command.* import info.nightscout.androidaps.plugins.pump.omnipod.util.AapsOmnipodUtil -import info.nightscout.androidaps.queue.commands.Command +import info.nightscout.androidaps.queue.Callback import info.nightscout.androidaps.queue.events.EventQueueChanged import info.nightscout.androidaps.utils.DateUtil import info.nightscout.androidaps.utils.FabricPrivacy @@ -92,11 +92,6 @@ class OmnipodFragment : DaggerFragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - omnipod_button_resume_delivery.setOnClickListener { - disablePodActionButtons() - commandQueue.startPump(null) - } - omnipod_button_pod_mgmt.setOnClickListener { if (omnipodPumpPlugin.rileyLinkService?.verifyConfiguration() == true) { activity?.let { activity -> @@ -110,10 +105,16 @@ class OmnipodFragment : DaggerFragment() { } } + omnipod_button_resume_delivery.setOnClickListener { + disablePodActionButtons() + commandQueue.customCommand(CommandResumeDelivery(), + ErrorDialogCallback(resourceHelper.gs(R.string.omnipod_failed_to_resume_delivery), true).messageOnSuccess(resourceHelper.gs(R.string.omnipod_delivery_resumed))) + } + omnipod_button_refresh_status.setOnClickListener { disablePodActionButtons() - omnipodPumpPlugin.addPodStatusRequest(OmnipodStatusRequestType.GET_POD_STATE); - commandQueue.readStatus("Clicked Refresh", null) + commandQueue.customCommand(CommandGetPodStatus(), + ErrorDialogCallback(resourceHelper.gs(R.string.omnipod_failed_to_refresh_status), false)) } omnipod_button_rileylink_stats.setOnClickListener { @@ -126,26 +127,29 @@ class OmnipodFragment : DaggerFragment() { omnipod_button_acknowledge_active_alerts.setOnClickListener { disablePodActionButtons() - omnipodPumpPlugin.addPodStatusRequest(OmnipodStatusRequestType.ACKNOWLEDGE_ALERTS); - commandQueue.readStatus("Clicked Acknowledge Alert", null) + commandQueue.customCommand(CommandAcknowledgeAlerts(), + ErrorDialogCallback(resourceHelper.gs(R.string.omnipod_failed_to_acknowledge_alerts), false) + .messageOnSuccess(resourceHelper.gs(R.string.omnipod_acknowledged_alerts))) } omnipod_button_suspend_delivery.setOnClickListener { disablePodActionButtons() - omnipodPumpPlugin.addPodStatusRequest(OmnipodStatusRequestType.SUSPEND_DELIVERY); - commandQueue.readStatus("Clicked Suspend Delivery", null) + commandQueue.customCommand(CommandSuspendDelivery(), + ErrorDialogCallback(resourceHelper.gs(R.string.omnipod_failed_to_suspend_delivery), true) + .messageOnSuccess(resourceHelper.gs(R.string.omnipod_suspended_delivery))) } omnipod_button_set_time.setOnClickListener { disablePodActionButtons() - omnipodPumpPlugin.addPodStatusRequest(OmnipodStatusRequestType.SET_TIME); - commandQueue.readStatus("Clicked Set Time", null) + commandQueue.customCommand(CommandHandleTimeChange(true), + ErrorDialogCallback(resourceHelper.gs(R.string.omnipod_failed_to_set_time), true) + .messageOnSuccess(resourceHelper.gs(R.string.omnipod_time_on_pod_updated))) } omnipod_button_pulse_log.setOnClickListener { disablePodActionButtons() - omnipodPumpPlugin.addPodStatusRequest(OmnipodStatusRequestType.GET_PULSE_LOG); - commandQueue.readStatus("Clicked Pulse Log", null) + commandQueue.customCommand(CommandReadPulseLog(), + ErrorDialogCallback(resourceHelper.gs(R.string.omnipod_failed_to_retrieve_pulse_log), false)) } } @@ -219,7 +223,7 @@ class OmnipodFragment : DaggerFragment() { updateTempBasal() updatePodStatus() - val errors = ArrayList(); + val errors = ArrayList() if (omnipodPumpPlugin.rileyLinkService != null) { val rileyLinkErrorDescription = omnipodPumpPlugin.rileyLinkService.errorDescription if (StringUtils.isNotEmpty(rileyLinkErrorDescription)) { @@ -285,7 +289,7 @@ class OmnipodFragment : DaggerFragment() { // total delivered omnipod_total_delivered.text = if (podStateManager.isPodActivationCompleted && podStateManager.totalInsulinDelivered != null) { - resourceHelper.gs(R.string.omnipod_total_delivered, podStateManager.totalInsulinDelivered - OmnipodConstants.POD_SETUP_UNITS); + resourceHelper.gs(R.string.omnipod_total_delivered, podStateManager.totalInsulinDelivered - OmnipodConstants.POD_SETUP_UNITS) } else { PLACEHOLDER } @@ -386,7 +390,7 @@ class OmnipodFragment : DaggerFragment() { text += " (" + resourceHelper.gs(R.string.omnipod_uncertain) + ")" } - omnipod_last_bolus.text = text; + omnipod_last_bolus.text = text omnipod_last_bolus.setTextColor(textColor) } else { @@ -399,9 +403,9 @@ class OmnipodFragment : DaggerFragment() { if (podStateManager.isPodActivationCompleted && podStateManager.isTempBasalRunning) { val now = DateTime.now() - val startTime = podStateManager.tempBasalStartTime; + val startTime = podStateManager.tempBasalStartTime val amount = podStateManager.tempBasalAmount - val duration = podStateManager.tempBasalDuration; + val duration = podStateManager.tempBasalDuration val minutesRunning = Duration(startTime, now).standardMinutes @@ -415,7 +419,7 @@ class OmnipodFragment : DaggerFragment() { text += " (" + resourceHelper.gs(R.string.omnipod_uncertain) + ")" } - omnipod_temp_basal.text = text; + omnipod_temp_basal.text = text omnipod_temp_basal.setTextColor(textColor) } else { omnipod_temp_basal.text = PLACEHOLDER @@ -456,8 +460,7 @@ class OmnipodFragment : DaggerFragment() { } private fun updateResumeDeliveryButton() { - val queueEmptyOrStartingPump = isQueueEmpty() || commandQueue.isRunning(Command.CommandType.START_PUMP) - if (podStateManager.isPodActivationCompleted && podStateManager.isSuspended && queueEmptyOrStartingPump) { + if (podStateManager.isPodRunning && (podStateManager.isSuspended || commandQueue.isCustomCommandInQueue(CommandResumeDelivery::class.java))) { omnipod_button_resume_delivery.visibility = View.VISIBLE omnipod_button_resume_delivery.isEnabled = rileyLinkServiceData.rileyLinkServiceState.isReady && isQueueEmpty() } else { @@ -466,7 +469,7 @@ class OmnipodFragment : DaggerFragment() { } private fun updateAcknowledgeAlertsButton() { - if (podStateManager.isPodRunning && podStateManager.hasActiveAlerts()) { + if (podStateManager.isPodRunning && (podStateManager.hasActiveAlerts() || commandQueue.isCustomCommandInQueue(CommandAcknowledgeAlerts::class.java))) { omnipod_button_acknowledge_active_alerts.visibility = View.VISIBLE omnipod_button_acknowledge_active_alerts.isEnabled = rileyLinkServiceData.rileyLinkServiceState.isReady && isQueueEmpty() } else { @@ -476,7 +479,7 @@ class OmnipodFragment : DaggerFragment() { private fun updateSuspendDeliveryButton() { // If the Pod is currently suspended, we show the Resume delivery button instead. - if (omnipodManager.isSuspendDeliveryButtonEnabled && !(podStateManager.isPodRunning && podStateManager.isSuspended)) { + if (omnipodManager.isSuspendDeliveryButtonEnabled && podStateManager.isPodRunning && (!podStateManager.isSuspended || commandQueue.isCustomCommandInQueue(CommandSuspendDelivery::class.java))) { omnipod_button_suspend_delivery.visibility = View.VISIBLE omnipod_button_suspend_delivery.isEnabled = podStateManager.isPodRunning && !podStateManager.isSuspended && rileyLinkServiceData.rileyLinkServiceState.isReady && isQueueEmpty() } else { @@ -485,7 +488,7 @@ class OmnipodFragment : DaggerFragment() { } private fun updateSetTimeButton() { - if (podStateManager.isPodRunning && (podStateManager.timeDeviatesMoreThan(Duration.standardMinutes(5)) || omnipodPumpPlugin.currentCommand == OmnipodCommandType.SET_TIME)) { + if (podStateManager.isPodRunning && (podStateManager.timeDeviatesMoreThan(Duration.standardMinutes(5)) || commandQueue.isCustomCommandInQueue(CommandHandleTimeChange::class.java))) { omnipod_button_set_time.visibility = View.VISIBLE omnipod_button_set_time.isEnabled = !podStateManager.isSuspended && rileyLinkServiceData.rileyLinkServiceState.isReady && isQueueEmpty() } else { @@ -504,68 +507,109 @@ class OmnipodFragment : DaggerFragment() { private fun displayNotConfiguredDialog() { context?.let { - OKDialog.show(it, resourceHelper.gs(R.string.omnipod_warning), - resourceHelper.gs(R.string.omnipod_error_operation_not_possible_no_configuration), null) + UIRunnable(Runnable { + OKDialog.show(it, resourceHelper.gs(R.string.omnipod_warning), + resourceHelper.gs(R.string.omnipod_error_operation_not_possible_no_configuration), null) + }).run() + } +} + +private fun displayErrorDialog(title: String, message: String, withSound: Boolean) { + context?.let { + val i = Intent(it, ErrorHelperActivity::class.java) + i.putExtra("soundid", if (withSound) R.raw.boluserror else 0) + i.putExtra("status", message) + i.putExtra("title", title) + i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + it.startActivity(i) + } +} + +private fun displayOkDialog(title: String, message: String) { + context?.let { + UIRunnable(Runnable { + OKDialog.show(it, title, message, null) + }).run() + } +} + +private fun readableZonedTime(time: DateTime): String { + val timeAsJavaData = time.toLocalDateTime().toDate() + val timeZone = podStateManager.timeZone.toTimeZone() + if (timeZone == TimeZone.getDefault()) { + return dateUtil.dateAndTimeString(timeAsJavaData) + } + + val isDaylightTime = timeZone.inDaylightTime(timeAsJavaData) + val locale = resources.configuration.locales.get(0) + val timeZoneDisplayName = timeZone.getDisplayName(isDaylightTime, TimeZone.SHORT, locale) + " " + timeZone.getDisplayName(isDaylightTime, TimeZone.LONG, locale) + return resourceHelper.gs(R.string.omnipod_pod_time_with_timezone, dateUtil.dateAndTimeString(timeAsJavaData), timeZoneDisplayName) +} + +private fun readableDuration(dateTime: DateTime): String { + val duration = Duration(dateTime, DateTime.now()) + val hours = duration.standardHours.toInt() + val minutes = duration.standardMinutes.toInt() + val seconds = duration.standardSeconds.toInt() + when { + seconds < 10 -> { + return resourceHelper.gs(R.string.omnipod_moments_ago) + } + + seconds < 60 -> { + return resourceHelper.gs(R.string.omnipod_less_than_a_minute_ago) + } + + seconds < 60 * 60 -> { // < 1 hour + return resourceHelper.gs(R.string.omnipod_time_ago, resourceHelper.gq(R.plurals.omnipod_minutes, minutes, minutes)) + } + + seconds < 24 * 60 * 60 -> { // < 1 day + val minutesLeft = minutes % 60 + if (minutesLeft > 0) + return resourceHelper.gs(R.string.omnipod_time_ago, + resourceHelper.gs(R.string.omnipod_composite_time, resourceHelper.gq(R.plurals.omnipod_hours, hours, hours), resourceHelper.gq(R.plurals.omnipod_minutes, minutesLeft, minutesLeft))) + return resourceHelper.gs(R.string.omnipod_time_ago, resourceHelper.gq(R.plurals.omnipod_hours, hours, hours)) + } + + else -> { + val days = hours / 24 + val hoursLeft = hours % 24 + if (hoursLeft > 0) + return resourceHelper.gs(R.string.omnipod_time_ago, + resourceHelper.gs(R.string.omnipod_composite_time, resourceHelper.gq(R.plurals.omnipod_days, days, days), resourceHelper.gq(R.plurals.omnipod_hours, hoursLeft, hoursLeft))) + return resourceHelper.gs(R.string.omnipod_time_ago, resourceHelper.gq(R.plurals.omnipod_days, days, days)) + } + } +} + +private fun isQueueEmpty(): Boolean { + return commandQueue.size() == 0 && commandQueue.performing() == null +} + +// FIXME ideally we should just have access to LocalAlertUtils here +private fun getPumpUnreachableTimeout(): Duration { + return Duration.standardMinutes(sp.getInt(resourceHelper.gs(R.string.key_pump_unreachable_threshold_minutes), Constants.DEFAULT_PUMP_UNREACHABLE_THRESHOLD_MINUTES).toLong()) +} + +inner class ErrorDialogCallback(private val errorMessagePrefix: String, private val withSoundOnError: Boolean) : Callback() { + private var messageOnSuccess: String? = null + + override fun run() { + if (result.success) { + val messageOnSuccess = this.messageOnSuccess + if (messageOnSuccess != null) { + displayOkDialog(resourceHelper.gs(R.string.omnipod_confirmation), messageOnSuccess) + } + } else { + displayErrorDialog(resourceHelper.gs(R.string.omnipod_warning), resourceHelper.gs(R.string.omnipod_two_strings_concatenated_by_colon, errorMessagePrefix, result.comment), withSoundOnError) } } - private fun readableZonedTime(time: DateTime): String { - val timeAsJavaData = time.toLocalDateTime().toDate() - val timeZone = podStateManager.timeZone.toTimeZone() - if (timeZone == TimeZone.getDefault()) { - return dateUtil.dateAndTimeString(timeAsJavaData) - } - - val isDaylightTime = timeZone.inDaylightTime(timeAsJavaData) - val locale = resources.configuration.locales.get(0) - val timeZoneDisplayName = timeZone.getDisplayName(isDaylightTime, TimeZone.SHORT, locale) + " " + timeZone.getDisplayName(isDaylightTime, TimeZone.LONG, locale) - return resourceHelper.gs(R.string.omnipod_pod_time_with_timezone, dateUtil.dateAndTimeString(timeAsJavaData), timeZoneDisplayName) - } - - private fun readableDuration(dateTime: DateTime): String { - val duration = Duration(dateTime, DateTime.now()) - val hours = duration.standardHours.toInt() - val minutes = duration.standardMinutes.toInt() - val seconds = duration.standardSeconds.toInt() - when { - seconds < 10 -> { - return resourceHelper.gs(R.string.omnipod_moments_ago) - } - - seconds < 60 -> { - return resourceHelper.gs(R.string.omnipod_less_than_a_minute_ago) - } - - seconds < 60 * 60 -> { // < 1 hour - return resourceHelper.gs(R.string.omnipod_time_ago, resourceHelper.gq(R.plurals.omnipod_minutes, minutes, minutes)) - } - - seconds < 24 * 60 * 60 -> { // < 1 day - val minutesLeft = minutes % 60 - if (minutesLeft > 0) - return resourceHelper.gs(R.string.omnipod_time_ago, - resourceHelper.gs(R.string.omnipod_composite_time, resourceHelper.gq(R.plurals.omnipod_hours, hours, hours), resourceHelper.gq(R.plurals.omnipod_minutes, minutesLeft, minutesLeft))) - return resourceHelper.gs(R.string.omnipod_time_ago, resourceHelper.gq(R.plurals.omnipod_hours, hours, hours)) - } - - else -> { - val days = hours / 24 - val hoursLeft = hours % 24 - if (hoursLeft > 0) - return resourceHelper.gs(R.string.omnipod_time_ago, - resourceHelper.gs(R.string.omnipod_composite_time, resourceHelper.gq(R.plurals.omnipod_days, days, days), resourceHelper.gq(R.plurals.omnipod_hours, hoursLeft, hoursLeft))) - return resourceHelper.gs(R.string.omnipod_time_ago, resourceHelper.gq(R.plurals.omnipod_days, days, days)) - } - } - } - - private fun isQueueEmpty(): Boolean { - return commandQueue.size() == 0 && commandQueue.performing() == null - } - - // FIXME ideally we should just have access to LocalAlertUtils here - private fun getPumpUnreachableTimeout(): Duration { - return Duration.standardMinutes(sp.getInt(resourceHelper.gs(R.string.key_pump_unreachable_threshold_minutes), Constants.DEFAULT_PUMP_UNREACHABLE_THRESHOLD_MINUTES).toLong()) + fun messageOnSuccess(message: String): ErrorDialogCallback { + messageOnSuccess = message + return this } +} } diff --git a/omnipod/src/main/res/layout/omnipod_fragment.xml b/omnipod/src/main/res/layout/omnipod_fragment.xml index ee49ccba3e..9c84d746be 100644 --- a/omnipod/src/main/res/layout/omnipod_fragment.xml +++ b/omnipod/src/main/res/layout/omnipod_fragment.xml @@ -806,18 +806,6 @@ android:text="@string/omnipod_read_pulse_log_short" android:visibility="gone" /> -