- Add custom queue command mechanism to replace abusing getPumpStatus for this purpose

- Display error and confirmation dialog for commands executed from the Omnipod tab
- Improve Exception logging in AapsOmnipodManager
- A few random improvements and bug fixes
This commit is contained in:
Bart Sopers 2020-09-14 16:19:09 +02:00
parent c7687aa6c7
commit feecfc5676
37 changed files with 827 additions and 401 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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<out CustomCommand>): 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<out CustomCommand>): Boolean {
val performing = this.performing
if (performing is CommandCustomCommand && customCommandType.isInstance(performing.customCommand)) {
return true
}
return false
}
@Synchronized
private fun removeAllCustomCommands(targetType: Class<out CustomCommand>) {
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

View file

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

View file

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

View file

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

View file

@ -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<out CustomCommand>): Boolean
fun isCustomCommandInQueue(customCommandType: Class<out CustomCommand>): Boolean
fun spannedStatus(): Spanned
fun isThisProfileSet(profile: Profile): Boolean
}

View file

@ -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<CustomAction> 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).

View file

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

View file

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

View file

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

View file

@ -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<CustomAction>? = 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() {

View file

@ -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) {

View file

@ -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<CustomAction> customActions = new ArrayList<>();
private final Set<OmnipodStatusRequestType> statusRequests = Collections.synchronizedSet(EnumSet.noneOf(OmnipodStatusRequestType.class));
private final List<CustomAction> 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<OmnipodStatusRequestType> 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<AlertConfiguration> 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<AlertConfiguration> 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> T executeCommand(OmnipodCommandType commandType, Supplier<T> 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());
}

View file

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

View file

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

View file

@ -17,11 +17,9 @@ import info.nightscout.androidaps.plugins.pump.omnipod.driver.util.AlertConfigur
public final class ExpirationReminderBuilder {
private final Map<AlertSlot, AlertConfiguration> 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;
}

View file

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

View file

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

View file

@ -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) {

View file

@ -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<AlertConfiguration> 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> T executeCommand(Supplier<T> 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;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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<String>();
val errors = ArrayList<String>()
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
}
}
}

View file

@ -806,18 +806,6 @@
android:text="@string/omnipod_read_pulse_log_short"
android:visibility="gone" />
<Button
android:id="@+id/omnipod_button_suspend_delivery"
style="@style/ButtonSmallFontStyle"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:drawableTop="@drawable/ic_loop_disabled"
android:paddingLeft="0dp"
android:paddingRight="0dp"
android:text="@string/omnipod_suspend_delivery_short"
android:visibility="gone" />
<Button
android:id="@+id/omnipod_button_set_time"
style="@style/ButtonSmallFontStyle"
@ -842,6 +830,18 @@
android:text="@string/omnipod_resume_delivery"
android:visibility="gone" />
<Button
android:id="@+id/omnipod_button_suspend_delivery"
style="@style/ButtonSmallFontStyle"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:drawableTop="@drawable/ic_loop_disabled"
android:paddingLeft="0dp"
android:paddingRight="0dp"
android:text="@string/omnipod_suspend_delivery_short"
android:visibility="gone" />
</LinearLayout>
</LinearLayout>

View file

@ -114,7 +114,7 @@
<string name="omnipod_init_pod_wizard_step1_title">Fill the Pod</string>
<string name="omnipod_init_pod_wizard_step1_desc">\nFill the new Pod with enough insulin for 3 days.\n\nListen for two beeps from the Pod during the filling process. These indicate that the minimum amount of 85U has been inserted. Be sure to completely empty the fill syringe, even after hearing the two beeps.\n\nAfter filling the Pod, please press <b>Next</b>.\n\n<b>Note:</b> do not remove the Pod\'s needle cap at this time.\n<b>Note:</b> please place the RileyLink in an upright position and place the Pod a few inches away.</string>
<string name="omnipod_init_pod_wizard_step2_title">Priming</string>
<string name="omnipod_init_pod_wizard_step2_action_header">Trying to pair with the new Pod and prime it.\n\nWhen all items are checked, you can press <b>Next</b>.\n\n<b>Note:</b> please place the RileyLink in an upright position and place the Pod a few inches away it.</string>
<string name="omnipod_init_pod_wizard_step2_action_header">Trying to pair with the new Pod and prime it.\n\nWhen all items are checked, you can press <b>Next</b>.\n\n<b>Note:</b> please place the RileyLink in an upright position and place the Pod a few inches away from it.</string>
<string name="omnipod_init_pod_wizard_step3_title">Attach the Pod</string>
<string name="omnipod_init_pod_wizard_step3_desc">\nPrepare the infusion site. Remove the Pod\'s needle cap and adhesive backing and attach the Pod to the infusion site.\n\nIf the cannula sticks out, please press <b>Cancel</b> and discard your Pod.\n\nPress <b>Next</b> to insert the cannula and begin basal delivery.</string>
<string name="omnipod_init_pod_wizard_step4_title">Inserting cannula</string>
@ -145,6 +145,8 @@
<string name="omnipod_error_set_basal_might_have_failed_delivery_might_be_suspended">Setting basal profile might have failed. Delivery might be suspended! Please manually refresh the Pod status from the Omnipod tab and resume delivery if needed.</string>
<string name="omnipod_error_set_basal_failed_delivery_suspended">Setting basal profile failed. Delivery is suspended! Please manually resume delivery from the Omnipod tab.</string>
<string name="omnipod_error_cancel_temp_basal_failed_uncertain">Cancelling temp basal might have failed. Please manually refresh the Pod status from the Omnipod tab.</string>
<string name="omnipod_error_suspend_delivery_failed_uncertain">Suspending delivery might have failed. Please manually refresh the Pod status from the Omnipod tab.</string>
<string name="omnipod_error_suspend_delivery_failed">Suspending delivery failed.</string>
<string name="omnipod_error_set_temp_basal_failed_old_tbr_might_be_cancelled">Setting temp basal failed. If a temp basal was previously running, it might have been cancelled. Please manually refresh the Pod status from the Omnipod tab.</string>
<string name="omnipod_error_set_temp_basal_failed_old_tbr_cancelled_new_might_have_failed">Setting temp might have basal failed. If a temp basal was previously running, it has been cancelled. Please manually refresh the Pod status from the Omnipod tab.</string>
<string name="omnipod_error_set_time_failed_delivery_might_be_suspended">Setting time might have failed. Delivery might be suspended! Please manually refresh the Pod status from the Omnipod tab and resume delivery if needed.</string>
@ -197,6 +199,21 @@
<string name="omnipod_error_set_initial_basal_schedule_no_profile">No basal profile is active. Make sure to activate your basal profile.</string>
<string name="omnipod_pod_time_on_pod">Time on Pod</string>
<string name="omnipod_set_time">Set time</string>
<string name="omnipod_unknown_custom_command">Unknown custom command: %1$s</string>
<string name="omnipod_failed_to_retrieve_pulse_log">Failed to retrieve Pulse Log</string>
<string name="omnipod_failed_to_refresh_status">Failed to refresh status</string>
<string name="omnipod_failed_to_acknowledge_alerts">Failed to acknowledge alerts</string>
<string name="omnipod_failed_to_suspend_delivery">Failed to suspend delivery</string>
<string name="omnipod_failed_to_set_time">Failed to set time</string>
<string name="omnipod_failed_to_resume_delivery">Failed to resume delivery</string>
<string name="omnipod_pulse_log">Pulse log</string>
<string name="omnipod_pulse_log_value">Pulse Log (copied to clipboard)</string>
<string name="omnipod_two_strings_concatenated_by_colon" translatable="false">%1$s: %2$s</string>
<string name="omnipod_confirmation">Confirmation</string>
<string name="omnipod_time_on_pod_updated">The time on Pod updated.</string>
<string name="omnipod_suspended_delivery">All insulin delivery suspended.</string>
<string name="omnipod_acknowledged_alerts">Acknowledged alerts.</string>
<string name="omnipod_delivery_resumed">Insulin delivery resumed.</string>
<plurals name="omnipod_minutes">
<item quantity="one">%1$d minute</item>