diff --git a/app/src/main/java/info/nightscout/androidaps/historyBrowser/TreatmentsPluginHistory.kt b/app/src/main/java/info/nightscout/androidaps/historyBrowser/TreatmentsPluginHistory.kt index caa0fb1b1a..f9d0a4019b 100644 --- a/app/src/main/java/info/nightscout/androidaps/historyBrowser/TreatmentsPluginHistory.kt +++ b/app/src/main/java/info/nightscout/androidaps/historyBrowser/TreatmentsPluginHistory.kt @@ -7,6 +7,7 @@ import info.nightscout.androidaps.interfaces.ProfileFunction import info.nightscout.androidaps.logging.AAPSLogger import info.nightscout.androidaps.plugins.bus.RxBusWrapper import info.nightscout.androidaps.plugins.general.nsclient.NSUpload +import info.nightscout.androidaps.plugins.general.nsclient.UploadQueue import info.nightscout.androidaps.plugins.treatments.TreatmentService import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin import info.nightscout.androidaps.utils.DateUtil @@ -27,8 +28,10 @@ class TreatmentsPluginHistory @Inject constructor( profileFunction: ProfileFunction, activePlugin: ActivePluginProvider, nsUpload: NSUpload, - fabricPrivacy: FabricPrivacy, dateUtil: DateUtil -) : TreatmentsPlugin(injector, aapsLogger, rxBus, resourceHelper, context, sp, profileFunction, activePlugin, nsUpload, fabricPrivacy, dateUtil) { + fabricPrivacy: FabricPrivacy, + dateUtil: DateUtil, + uploadQueue: UploadQueue +) : TreatmentsPlugin(injector, aapsLogger, rxBus, resourceHelper, context, sp, profileFunction, activePlugin, nsUpload, fabricPrivacy, dateUtil, uploadQueue) { init { onStart() diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/treatments/TreatmentsPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/treatments/TreatmentsPlugin.java index 01ae2f9dcd..a957fbe57a 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/treatments/TreatmentsPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/treatments/TreatmentsPlugin.java @@ -30,13 +30,12 @@ import info.nightscout.androidaps.data.NonOverlappingIntervals; import info.nightscout.androidaps.data.OverlappingIntervals; import info.nightscout.androidaps.data.Profile; import info.nightscout.androidaps.data.ProfileIntervals; -import info.nightscout.androidaps.db.Treatment; -import info.nightscout.androidaps.interfaces.ProfileStore; import info.nightscout.androidaps.db.ExtendedBolus; import info.nightscout.androidaps.db.ProfileSwitch; import info.nightscout.androidaps.db.Source; import info.nightscout.androidaps.db.TempTarget; import info.nightscout.androidaps.db.TemporaryBasal; +import info.nightscout.androidaps.db.Treatment; import info.nightscout.androidaps.events.EventReloadProfileSwitchData; import info.nightscout.androidaps.events.EventReloadTempBasalData; import info.nightscout.androidaps.events.EventReloadTreatmentData; @@ -45,13 +44,15 @@ import info.nightscout.androidaps.interfaces.ActivePluginProvider; import info.nightscout.androidaps.interfaces.PluginBase; import info.nightscout.androidaps.interfaces.PluginDescription; import info.nightscout.androidaps.interfaces.PluginType; +import info.nightscout.androidaps.interfaces.ProfileFunction; +import info.nightscout.androidaps.interfaces.ProfileStore; import info.nightscout.androidaps.interfaces.PumpInterface; import info.nightscout.androidaps.interfaces.TreatmentsInterface; import info.nightscout.androidaps.logging.AAPSLogger; import info.nightscout.androidaps.logging.LTag; import info.nightscout.androidaps.plugins.bus.RxBusWrapper; -import info.nightscout.androidaps.interfaces.ProfileFunction; 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; import info.nightscout.androidaps.plugins.general.overview.notifications.Notification; import info.nightscout.androidaps.plugins.iob.iobCobCalculator.AutosensResult; @@ -75,6 +76,7 @@ public class TreatmentsPlugin extends PluginBase implements TreatmentsInterface private final ProfileFunction profileFunction; private final ActivePluginProvider activePlugin; private final NSUpload nsUpload; + private final UploadQueue uploadQueue; private final FabricPrivacy fabricPrivacy; private final DateUtil dateUtil; @@ -103,7 +105,8 @@ public class TreatmentsPlugin extends PluginBase implements TreatmentsInterface ActivePluginProvider activePlugin, NSUpload nsUpload, FabricPrivacy fabricPrivacy, - DateUtil dateUtil + DateUtil dateUtil, + UploadQueue uploadQueue ) { super(new PluginDescription() .mainType(PluginType.TREATMENT) @@ -124,6 +127,7 @@ public class TreatmentsPlugin extends PluginBase implements TreatmentsInterface this.fabricPrivacy = fabricPrivacy; this.dateUtil = dateUtil; this.nsUpload = nsUpload; + this.uploadQueue = uploadQueue; } @Override @@ -338,8 +342,7 @@ public class TreatmentsPlugin extends PluginBase implements TreatmentsInterface if (last == null) { getAapsLogger().debug(LTag.DATATREATMENTS, "Last bolus time: NOTHING FOUND"); return 0; - } - else { + } else { getAapsLogger().debug(LTag.DATATREATMENTS, "Last bolus time: " + dateUtil.dateAndTimeString(last.date)); return last.date; } @@ -350,8 +353,7 @@ public class TreatmentsPlugin extends PluginBase implements TreatmentsInterface if (last == null) { getAapsLogger().debug(LTag.DATATREATMENTS, "Last manual bolus time: NOTHING FOUND"); return 0; - } - else { + } else { getAapsLogger().debug(LTag.DATATREATMENTS, "Last manual bolus time: " + dateUtil.dateAndTimeString(last.date)); return last.date; } @@ -362,8 +364,7 @@ public class TreatmentsPlugin extends PluginBase implements TreatmentsInterface if (last == null) { getAapsLogger().debug(LTag.DATATREATMENTS, "Last Carb time: NOTHING FOUND"); return 0; - } - else { + } else { getAapsLogger().debug(LTag.DATATREATMENTS, "Last Carb time: " + dateUtil.dateAndTimeString(last.date)); return last.date; } @@ -387,6 +388,16 @@ public class TreatmentsPlugin extends PluginBase implements TreatmentsInterface return getTempBasalFromHistory(System.currentTimeMillis()) != null; } + @Override public void removeTempBasal(TemporaryBasal tempBasal) { + String tempBasalId = tempBasal._id; + if (NSUpload.isIdValid(tempBasalId)) { + nsUpload.removeCareportalEntryFromNS(tempBasalId); + } else { + uploadQueue.removeID("dbAdd", tempBasalId); + } + MainApp.getDbHelper().delete(tempBasal); + } + @Override public boolean isInHistoryExtendedBoluslInProgress() { return getExtendedBolusFromHistory(System.currentTimeMillis()) != null; //TODO: crosscheck here diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/treatments/fragments/TreatmentsTemporaryBasalsFragment.kt b/app/src/main/java/info/nightscout/androidaps/plugins/treatments/fragments/TreatmentsTemporaryBasalsFragment.kt index fed0ba1137..dd98dbf381 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/treatments/fragments/TreatmentsTemporaryBasalsFragment.kt +++ b/app/src/main/java/info/nightscout/androidaps/plugins/treatments/fragments/TreatmentsTemporaryBasalsFragment.kt @@ -11,7 +11,6 @@ import androidx.cardview.widget.CardView import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import dagger.android.support.DaggerFragment -import info.nightscout.androidaps.MainApp import info.nightscout.androidaps.R import info.nightscout.androidaps.data.Intervals import info.nightscout.androidaps.data.IobTotal @@ -19,33 +18,29 @@ import info.nightscout.androidaps.db.Source import info.nightscout.androidaps.db.TemporaryBasal import info.nightscout.androidaps.events.EventTempBasalChange import info.nightscout.androidaps.interfaces.ActivePluginProvider -import info.nightscout.androidaps.plugins.bus.RxBusWrapper import info.nightscout.androidaps.interfaces.ProfileFunction +import info.nightscout.androidaps.plugins.bus.RxBusWrapper import info.nightscout.androidaps.plugins.general.nsclient.NSUpload -import info.nightscout.androidaps.plugins.general.nsclient.UploadQueue import info.nightscout.androidaps.plugins.iob.iobCobCalculator.events.EventAutosensCalculationFinished import info.nightscout.androidaps.plugins.treatments.fragments.TreatmentsTemporaryBasalsFragment.RecyclerViewAdapter.TempBasalsViewHolder import info.nightscout.androidaps.utils.DateUtil import info.nightscout.androidaps.utils.FabricPrivacy import info.nightscout.androidaps.utils.alertDialogs.OKDialog.showConfirmation import info.nightscout.androidaps.utils.resources.ResourceHelper -import info.nightscout.androidaps.utils.sharedPreferences.SP import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.disposables.CompositeDisposable import kotlinx.android.synthetic.main.treatments_tempbasals_fragment.* import javax.inject.Inject class TreatmentsTemporaryBasalsFragment : DaggerFragment() { + private val disposable = CompositeDisposable() @Inject lateinit var rxBus: RxBusWrapper - @Inject lateinit var sp: SP @Inject lateinit var resourceHelper: ResourceHelper @Inject lateinit var fabricPrivacy: FabricPrivacy @Inject lateinit var activePlugin: ActivePluginProvider @Inject lateinit var profileFunction: ProfileFunction - @Inject lateinit var nsUpload: NSUpload - @Inject lateinit var uploadQueue: UploadQueue @Inject lateinit var dateUtil: DateUtil override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, @@ -81,6 +76,7 @@ class TreatmentsTemporaryBasalsFragment : DaggerFragment() { } inner class RecyclerViewAdapter internal constructor(private var tempBasalList: Intervals) : RecyclerView.Adapter() { + override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): TempBasalsViewHolder { val v = LayoutInflater.from(viewGroup.context).inflate(R.layout.treatments_tempbasals_item, viewGroup, false) return TempBasalsViewHolder(v) @@ -142,6 +138,7 @@ class TreatmentsTemporaryBasalsFragment : DaggerFragment() { } inner class TempBasalsViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + var cv: CardView = itemView.findViewById(R.id.tempbasals_cardview) var date: TextView = itemView.findViewById(R.id.tempbasals_date) var duration: TextView = itemView.findViewById(R.id.tempbasals_duration) @@ -166,10 +163,7 @@ class TreatmentsTemporaryBasalsFragment : DaggerFragment() { ${resourceHelper.gs(R.string.date)}: ${dateUtil.dateAndTimeString(tempBasal.date)} """.trimIndent(), DialogInterface.OnClickListener { _: DialogInterface?, _: Int -> - val id = tempBasal._id - if (NSUpload.isIdValid(id)) nsUpload.removeCareportalEntryFromNS(id) - else uploadQueue.removeID("dbAdd", id) - MainApp.getDbHelper().delete(tempBasal) + activePlugin.activeTreatments.removeTempBasal(tempBasal) }, null) } } diff --git a/app/src/main/java/info/nightscout/androidaps/utils/stats/TddCalculator.kt b/app/src/main/java/info/nightscout/androidaps/utils/stats/TddCalculator.kt index a11b53464a..78cbaf4a9d 100644 --- a/app/src/main/java/info/nightscout/androidaps/utils/stats/TddCalculator.kt +++ b/app/src/main/java/info/nightscout/androidaps/utils/stats/TddCalculator.kt @@ -7,11 +7,12 @@ import info.nightscout.androidaps.MainApp import info.nightscout.androidaps.R import info.nightscout.androidaps.db.TDD import info.nightscout.androidaps.interfaces.ActivePluginProvider +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.interfaces.ProfileFunction import info.nightscout.androidaps.plugins.general.nsclient.NSUpload +import info.nightscout.androidaps.plugins.general.nsclient.UploadQueue import info.nightscout.androidaps.plugins.treatments.TreatmentService import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin import info.nightscout.androidaps.utils.DateUtil @@ -34,8 +35,9 @@ class TddCalculator @Inject constructor( val profileFunction: ProfileFunction, fabricPrivacy: FabricPrivacy, nsUpload: NSUpload, - private val dateUtil: DateUtil -) : TreatmentsPlugin(injector, aapsLogger, rxBus, resourceHelper, mainApp, sp, profileFunction, activePlugin, nsUpload, fabricPrivacy, dateUtil) { + private val dateUtil: DateUtil, + uploadQueue: UploadQueue +) : TreatmentsPlugin(injector, aapsLogger, rxBus, resourceHelper, mainApp, sp, profileFunction, activePlugin, nsUpload, fabricPrivacy, dateUtil, uploadQueue) { init { service = TreatmentService(injector) // plugin is not started diff --git a/core/src/main/java/info/nightscout/androidaps/interfaces/TreatmentsInterface.java b/core/src/main/java/info/nightscout/androidaps/interfaces/TreatmentsInterface.java index 8c8d7f026f..c17fc1119f 100644 --- a/core/src/main/java/info/nightscout/androidaps/interfaces/TreatmentsInterface.java +++ b/core/src/main/java/info/nightscout/androidaps/interfaces/TreatmentsInterface.java @@ -55,6 +55,8 @@ public interface TreatmentsInterface { NonOverlappingIntervals getTemporaryBasalsFromHistory(); + void removeTempBasal(TemporaryBasal temporaryBasal); + boolean isInHistoryExtendedBoluslInProgress(); ExtendedBolus getExtendedBolusFromHistory(long time); diff --git a/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/OmnipodPumpPlugin.java b/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/OmnipodPumpPlugin.java index 06b7c963a5..8f6cc6ced9 100644 --- a/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/OmnipodPumpPlugin.java +++ b/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/OmnipodPumpPlugin.java @@ -33,6 +33,7 @@ import info.nightscout.androidaps.data.DetailedBolusInfo; import info.nightscout.androidaps.data.Profile; import info.nightscout.androidaps.data.PumpEnactResult; import info.nightscout.androidaps.db.ExtendedBolus; +import info.nightscout.androidaps.db.Source; import info.nightscout.androidaps.db.TemporaryBasal; import info.nightscout.androidaps.events.EventAppExit; import info.nightscout.androidaps.events.EventAppInitialized; @@ -82,6 +83,7 @@ import info.nightscout.androidaps.plugins.pump.omnipod.event.EventOmnipodActiveA import info.nightscout.androidaps.plugins.pump.omnipod.event.EventOmnipodFaultEventChanged; 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.event.EventOmnipodUncertainTbrRecovered; import info.nightscout.androidaps.plugins.pump.omnipod.manager.AapsOmnipodManager; import info.nightscout.androidaps.plugins.pump.omnipod.queue.command.CommandAcknowledgeAlerts; import info.nightscout.androidaps.plugins.pump.omnipod.queue.command.CommandHandleTimeChange; @@ -154,6 +156,7 @@ public class OmnipodPumpPlugin extends PumpPluginBase implements PumpInterface, private final Handler loopHandler = new Handler(Looper.getMainLooper()); private final Runnable statusChecker; + private boolean isSetTempBasalRunning; private boolean isCancelTempBasalRunning; @Inject @@ -285,7 +288,12 @@ public class OmnipodPumpPlugin extends PumpPluginBase implements PumpInterface, disposables.add(rxBus .toObservable(EventOmnipodTbrChanged.class) .observeOn(Schedulers.io()) - .subscribe(event -> updateAapsTbr(), fabricPrivacy::logException) + .subscribe(event -> handleCancelledTbr(), fabricPrivacy::logException) + ); + disposables.add(rxBus + .toObservable(EventOmnipodUncertainTbrRecovered.class) + .observeOn(Schedulers.io()) + .subscribe(event -> handleUncertainTbrRecovery(), fabricPrivacy::logException) ); disposables.add(rxBus .toObservable(EventOmnipodActiveAlertsChanged.class) @@ -350,17 +358,46 @@ public class OmnipodPumpPlugin extends PumpPluginBase implements PumpInterface, return rileyLinkServiceData.rileyLinkServiceState.isReady(); } - private void updateAapsTbr() { - // As per the characteristics of the Omnipod, we only know whether or not a TBR is currently active - // But it doesn't tell us the duration or amount, so we can only update TBR status in AAPS if - // 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 (!isCancelTempBasalRunning) { - if (activePlugin.getActiveTreatments().isTempBasalInProgress() && !aapsOmnipodManager.hasSuspendedFakeTbr()) { - aapsOmnipodManager.reportCancelledTbr(); - } + private void handleCancelledTbr() { + // Only report TBR cancellations if they haven't been explicitly requested + if (isCancelTempBasalRunning) { + return; + } + if (!podStateManager.isTempBasalRunning() && activePlugin.getActiveTreatments().isTempBasalInProgress() && !aapsOmnipodManager.hasSuspendedFakeTbr()) { + aapsOmnipodManager.reportCancelledTbr(); + } + } + + private void handleUncertainTbrRecovery() { + // Ignore changes in certainty during tbr commands; these are normal + if (isSetTempBasalRunning || isCancelTempBasalRunning) { + return; + } + + TemporaryBasal tempBasal = activePlugin.getActiveTreatments().getTempBasalFromHistory(System.currentTimeMillis()); + + if (podStateManager.isTempBasalRunning() && tempBasal == null) { + if (podStateManager.hasTempBasal()) { + aapsLogger.warn(LTag.PUMP, "Registering TBR that AAPS was unaware of"); + long pumpId = aapsOmnipodManager.addTbrSuccessToHistory(podStateManager.getTempBasalStartTime().getMillis(), + new TempBasalPair(podStateManager.getTempBasalAmount(), false, (int) podStateManager.getTempBasalDuration().getStandardMinutes())); + + TemporaryBasal temporaryBasal = new TemporaryBasal(getInjector()) // + .absolute(podStateManager.getTempBasalAmount()) // + .duration((int) podStateManager.getTempBasalDuration().getStandardMinutes()) + .date(podStateManager.getTempBasalStartTime().getMillis()) // + .source(Source.PUMP) // + .pumpId(pumpId); + + activePlugin.getActiveTreatments().addToHistoryTempBasal(temporaryBasal); + } else { + // Not sure what's going on. Notify the user + aapsLogger.error(LTag.PUMP, "Unknown TBR in both Pod state and AAPS"); + rxBus.send(new EventNewNotification(new Notification(Notification.OMNIPOD_PUMP_ALARM, resourceHelper.gs(R.string.omnipod_error_tbr_running_but_aaps_not_aware), Notification.NORMAL).sound(R.raw.boluserror))); } + } else if (!podStateManager.isTempBasalRunning() && tempBasal != null) { + aapsLogger.warn(LTag.PUMP, "Removing AAPS TBR that actually hadn't succeeded"); + activePlugin.getActiveTreatments().removeTempBasal(tempBasal); } } @@ -506,18 +543,26 @@ public class OmnipodPumpPlugin extends PumpPluginBase implements PumpInterface, } /** - * The only actual status requests we send to the Pod here are on startup (in {@link #initializeAfterRileyLinkConnection() 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 + * The only actual status requests we send to the Pod here are on startup (in {@link #initializeAfterRileyLinkConnection() initializeAfterRileyLinkConnection()}) + * And when the basal and/or temp basal status is uncertain + * When the user explicitly requested it by clicking the Refresh button on the Omnipod tab (which is executed through {@link #executeCustomCommand(CustomCommand)}) */ @Override public void getPumpStatus() { if (firstRun) { initializeAfterRileyLinkConnection(); firstRun = false; + } else if (!podStateManager.isBasalCertain() || !podStateManager.isTempBasalCertain()) { + aapsLogger.info(LTag.PUMP, "Acknowledged AAPS getPumpStatus request because basal and/or temp basal is uncertain"); + getPodStatus(); } } + private PumpEnactResult getPodStatus() { + return executeCommand(OmnipodCommandType.GET_POD_STATUS, aapsOmnipodManager::getPodStatus); + } + @NonNull @Override public PumpEnactResult setNewBasalProfile(Profile profile) { @@ -620,7 +665,13 @@ public class OmnipodPumpPlugin extends PumpPluginBase implements PumpInterface, } } - PumpEnactResult result = executeCommand(OmnipodCommandType.SET_TEMPORARY_BASAL, () -> aapsOmnipodManager.setTemporaryBasal(new TempBasalPair(absoluteRate, false, durationInMinutes))); + isSetTempBasalRunning = true; + PumpEnactResult result; + try { + result = executeCommand(OmnipodCommandType.SET_TEMPORARY_BASAL, () -> aapsOmnipodManager.setTemporaryBasal(new TempBasalPair(absoluteRate, false, durationInMinutes))); + } finally { + isSetTempBasalRunning = false; + } aapsLogger.info(LTag.PUMP, "setTempBasalAbsolute - setTBR. Response: " + result.success); @@ -781,7 +832,7 @@ public class OmnipodPumpPlugin extends PumpPluginBase implements PumpInterface, case ACKNOWLEDGE_ALERTS: return executeCommand(OmnipodCommandType.ACKNOWLEDGE_ALERTS, aapsOmnipodManager::acknowledgeAlerts); case GET_POD_STATUS: - return executeCommand(OmnipodCommandType.GET_POD_STATUS, aapsOmnipodManager::getPodStatus); + return getPodStatus(); case READ_PULSE_LOG: return retrievePulseLog(); case SUSPEND_DELIVERY: @@ -857,7 +908,7 @@ public class OmnipodPumpPlugin extends PumpPluginBase implements PumpInterface, } 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); + result = getPodStatus(); } if (result.success) { @@ -988,15 +1039,19 @@ public class OmnipodPumpPlugin extends PumpPluginBase implements PumpInterface, private void initializeAfterRileyLinkConnection() { if (podStateManager.getActivationProgress().isAtLeast(ActivationProgress.PAIRING_COMPLETED)) { + boolean success = false; for (int i = 0; STARTUP_STATUS_REQUEST_TRIES > i; i++) { - PumpEnactResult result = executeCommand(OmnipodCommandType.GET_POD_STATUS, aapsOmnipodManager::getPodStatus); + PumpEnactResult result = getPodStatus(); if (result.success) { + success = true; aapsLogger.debug(LTag.PUMP, "Successfully retrieved Pod status on startup"); break; - } else { - aapsLogger.warn(LTag.PUMP, "Failed to retrieve Pod status on startup"); } } + if (!success) { + aapsLogger.warn(LTag.PUMP, "Failed to retrieve Pod status on startup"); + rxBus.send(new EventNewNotification(new Notification(Notification.OMNIPOD_PUMP_ALARM, resourceHelper.gs(R.string.omnipod_error_failed_to_refresh_status_on_startup), Notification.NORMAL))); + } } else { aapsLogger.debug(LTag.PUMP, "Not retrieving Pod status on startup: no Pod running"); } diff --git a/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/driver/manager/OmnipodManager.java b/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/driver/manager/OmnipodManager.java index 7158e9f98d..858186bc7f 100644 --- a/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/driver/manager/OmnipodManager.java +++ b/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/driver/manager/OmnipodManager.java @@ -171,6 +171,15 @@ public class OmnipodManager { public synchronized void setBasalSchedule(BasalSchedule schedule, boolean acknowledgementBeep) { assertReadyForDelivery(); + if (!podStateManager.isBasalCertain()) { + try { + getPodStatus(); + } catch (OmnipodException ex) { + ex.setCertainFailure(true); + throw ex; + } + } + boolean wasSuspended = podStateManager.isSuspended(); if (!wasSuspended) { try { @@ -185,12 +194,17 @@ public class OmnipodManager { } } + BasalSchedule oldBasalSchedule = podStateManager.getBasalSchedule(); + try { + podStateManager.setBasalSchedule(schedule); + podStateManager.setBasalCertain(false); executeAndVerify(() -> communicationService.executeAction(new SetBasalScheduleAction(podStateManager, schedule, false, podStateManager.getScheduleOffset(), acknowledgementBeep))); - podStateManager.setBasalSchedule(schedule); } catch (OmnipodException ex) { if (ex.isCertainFailure()) { + podStateManager.setBasalSchedule(oldBasalSchedule); + podStateManager.setBasalCertain(true); if (!wasSuspended) { throw new CommandFailedAfterChangingDeliveryStatusException("Suspending delivery succeeded but setting the new basal schedule did not", ex); } @@ -206,6 +220,19 @@ public class OmnipodManager { public synchronized void setTemporaryBasal(double rate, Duration duration, boolean acknowledgementBeep, boolean completionBeep) { assertReadyForDelivery(); + if (!podStateManager.isTempBasalCertain() || !podStateManager.isBasalCertain()) { + try { + getPodStatus(); + } catch (OmnipodException ex) { + ex.setCertainFailure(true); + throw ex; + } + } + + if (podStateManager.isSuspended()) { + throw new IllegalDeliveryStatusException(DeliveryStatus.NORMAL, DeliveryStatus.SUSPENDED); + } + boolean cancelCurrentTbr = podStateManager.isTempBasalRunning(); if (cancelCurrentTbr) { @@ -217,17 +244,19 @@ public class OmnipodManager { } // Uncertain failure - podStateManager.setTempBasalCertain(false); throw new PrecedingCommandFailedUncertainlyException(ex); } } try { + podStateManager.setTempBasal(DateTime.now().plus(OmnipodConstants.AVERAGE_TEMP_BASAL_COMMAND_COMMUNICATION_DURATION), rate, duration); + podStateManager.setTempBasalCertain(false); executeAndVerify(() -> communicationService.executeAction(new SetTempBasalAction( podStateManager, rate, duration, acknowledgementBeep, completionBeep))); - podStateManager.setTempBasal(DateTime.now().minus(OmnipodConstants.AVERAGE_TEMP_BASAL_COMMAND_COMMUNICATION_DURATION), rate, duration, true); } catch (OmnipodException ex) { if (ex.isCertainFailure()) { + podStateManager.clearTempBasal(); + podStateManager.setTempBasalCertain(true); if (cancelCurrentTbr) { throw new CommandFailedAfterChangingDeliveryStatusException("Failed to set new TBR while cancelling old TBR succeeded", ex); } @@ -235,7 +264,6 @@ public class OmnipodManager { } // Uncertain failure - podStateManager.setTempBasal(DateTime.now().minus(OmnipodConstants.AVERAGE_TEMP_BASAL_COMMAND_COMMUNICATION_DURATION), rate, duration, false); throw ex; } } @@ -247,11 +275,33 @@ public class OmnipodManager { private synchronized StatusResponse cancelDelivery(EnumSet deliveryTypes, boolean acknowledgementBeep) { assertReadyForDelivery(); - return executeAndVerify(() -> { - StatusResponse statusResponse = communicationService.executeAction(new CancelDeliveryAction(podStateManager, deliveryTypes, acknowledgementBeep)); - aapsLogger.info(LTag.PUMPCOMM, "Status response after cancel delivery[types={}]: {}", deliveryTypes.toString(), statusResponse.toString()); - return statusResponse; - }); + if (deliveryTypes.contains(DeliveryType.BASAL)) { + podStateManager.setBasalCertain(false); + } + if (deliveryTypes.contains(DeliveryType.TEMP_BASAL)) { + podStateManager.setTempBasalCertain(false); + } + + try { + return executeAndVerify(() -> { + StatusResponse statusResponse; + statusResponse = communicationService.executeAction(new CancelDeliveryAction(podStateManager, deliveryTypes, acknowledgementBeep)); + + aapsLogger.info(LTag.PUMPCOMM, "Status response after cancel delivery[types={}]: {}", deliveryTypes.toString(), statusResponse.toString()); + return statusResponse; + }); + } catch (OmnipodException ex) { + if (ex.isCertainFailure()) { + if (deliveryTypes.contains(DeliveryType.BASAL)) { + podStateManager.setBasalCertain(true); + } + if (deliveryTypes.contains(DeliveryType.TEMP_BASAL)) { + podStateManager.setTempBasalCertain(true); + } + } + + throw ex; + } } // Returns a SingleSubject that returns when the bolus has finished. @@ -260,6 +310,19 @@ public class OmnipodManager { public synchronized BolusCommandResult bolus(Double units, boolean acknowledgementBeep, boolean completionBeep, BiConsumer progressIndicationConsumer) { assertReadyForDelivery(); + if (!podStateManager.isBasalCertain()) { + try { + getPodStatus(); + } catch (OmnipodException ex) { + ex.setCertainFailure(true); + throw ex; + } + } + + if (podStateManager.isSuspended()) { + throw new IllegalDeliveryStatusException(DeliveryStatus.NORMAL, DeliveryStatus.SUSPENDED); + } + bolusCommandExecutionSubject = SingleSubject.create(); CommandDeliveryStatus commandDeliveryStatus = CommandDeliveryStatus.SUCCESS; diff --git a/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/driver/manager/PodStateManager.java b/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/driver/manager/PodStateManager.java index b482cc3a23..2163e9ffb1 100644 --- a/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/driver/manager/PodStateManager.java +++ b/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/driver/manager/PodStateManager.java @@ -368,6 +368,15 @@ public abstract class PodStateManager { setAndStore(() -> podState.setBasalSchedule(basalSchedule)); } + public final boolean isBasalCertain() { + Boolean certain = getSafe(() -> podState.isBasalCertain()); + return certain == null || certain; + } + + public final void setBasalCertain(boolean certain) { + setAndStore(() -> podState.setBasalCertain(certain)); + } + public final DateTime getLastBolusStartTime() { return getSafe(() -> podState.getLastBolusStartTime()); } @@ -416,14 +425,19 @@ public abstract class PodStateManager { } public final void setTempBasalCertain(boolean certain) { - setSafe(() -> podState.setTempBasalCertain(certain)); + setAndStore(() -> { + if (!Objects.equals(podState.isTempBasalCertain(), certain)) { + podState.setTempBasalCertain(certain); + onTbrChanged(); + } + }); } - public final void setTempBasal(DateTime startTime, Double amount, Duration duration, boolean certain) { - setTempBasal(startTime, amount, duration, certain, true); + public final void setTempBasal(DateTime startTime, Double amount, Duration duration) { + setTempBasal(startTime, amount, duration, true); } - public final void setTempBasal(DateTime startTime, Double amount, Duration duration, Boolean certain, boolean store) { + private void setTempBasal(DateTime startTime, Double amount, Duration duration, boolean store) { DateTime currentStartTime = getTempBasalStartTime(); Double currentAmount = getTempBasalAmount(); Duration currentDuration = getTempBasalDuration(); @@ -432,7 +446,6 @@ public abstract class PodStateManager { podState.setTempBasalStartTime(startTime); podState.setTempBasalAmount(amount); podState.setTempBasalDuration(duration); - podState.setTempBasalCertain(certain); }; if (store) { @@ -444,6 +457,14 @@ public abstract class PodStateManager { } } + public final void clearTempBasal() { + clearTempBasal(true); + } + + private void clearTempBasal(boolean store) { + setTempBasal(null, null, null, store); + } + /** * @return true when a Temp Basal is stored in the Pod Stated * Please note that this could also be an expired Temp Basal. For an indication on whether or not @@ -457,13 +478,22 @@ public abstract class PodStateManager { * @return true when a Temp Basal is stored in the Pod State and this temp basal is currently running (based on start time and duration) */ public final boolean isTempBasalRunning() { - return isTempBasalRunningAt(DateTime.now()); + return isTempBasalRunningAt(null); } /** - * @return true when a Temp Basal is stored in the Pod State and this temp basal is running at the given time (based on start time and duration) + * @param time the time for which to look up whether a temp basal is running, null meaning now + * @return true when a Temp Basal is stored in the Pod State and this temp basal is running at the given time (based on start time and duration), + * or when the time provided is null and the delivery status of the Pod inidicated that a TBR is running, but not TBR is stored + * This can happen in some rare cases. */ public final boolean isTempBasalRunningAt(DateTime time) { + if (time == null) { // now + if (!hasTempBasal() && getLastDeliveryStatus().isTbrRunning()) { + return true; + } + time = DateTime.now(); + } if (hasTempBasal()) { DateTime tempBasalStartTime = getTempBasalStartTime(); DateTime tempBasalEndTime = tempBasalStartTime.plus(getTempBasalDuration()); @@ -537,15 +567,26 @@ public abstract class PodStateManager { podState.setTotalTicksDelivered(status.getTicksDelivered()); podState.setPodProgressStatus(status.getPodProgressStatus()); podState.setTimeActive(status.getTimeActive()); - if (status.getDeliveryStatus().isTbrRunning()) { - if (!isTempBasalCertain() && isTempBasalRunning()) { - podState.setTempBasalCertain(true); + + boolean isBasalCertain = podState.isBasalCertain() == null || podState.isBasalCertain(); + boolean isTempBasalCertain = podState.isTempBasalCertain() == null || podState.isTempBasalCertain(); + if (!status.getDeliveryStatus().isTbrRunning()) { + if (isTempBasalCertain) { + clearTempBasal(); // Triggers onTbrChanged when appropriate + } else { + // Don't trigger onTbrChanged as we will trigger onUncertainTbrRecovered below + podState.setTempBasalStartTime(null); + podState.setTempBasalAmount(null); + podState.setTempBasalDuration(null); } - } else { - // Triggers {@link #onTbrChanged() onTbrChanged()} when appropriate - setTempBasal(null, null, null, true, false); } - podState.setLastUpdatedFromResponse(DateTime.now()); + if (!isTempBasalCertain) { + podState.setTempBasalCertain(true); + onUncertainTbrRecovered(); + } + if (!isBasalCertain) { + podState.setBasalCertain(true); + } if (status instanceof PodInfoDetailedStatus) { PodInfoDetailedStatus detailedStatus = (PodInfoDetailedStatus) status; @@ -556,6 +597,8 @@ public abstract class PodStateManager { } } } + + podState.setLastUpdatedFromResponse(DateTime.now()); }); } @@ -564,6 +607,11 @@ public abstract class PodStateManager { // Can be overridden in subclasses } + protected void onUncertainTbrRecovered() { + // Deliberately left empty + // Can be overridden in subclasses + } + protected void onActiveAlertsChanged() { // Deliberately left empty // Can be overridden in subclasses @@ -667,6 +715,7 @@ public abstract class PodStateManager { private DeliveryStatus lastDeliveryStatus; private AlertSet activeAlerts; private BasalSchedule basalSchedule; + private Boolean basalCertain; private DateTime lastBolusStartTime; private Double lastBolusAmount; private Duration lastBolusDuration; @@ -871,6 +920,14 @@ public abstract class PodStateManager { this.basalSchedule = basalSchedule; } + Boolean isBasalCertain() { + return basalCertain; + } + + void setBasalCertain(Boolean certain) { + this.basalCertain = certain; + } + DateTime getLastBolusStartTime() { return lastBolusStartTime; } diff --git a/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/event/EventOmnipodUncertainTbrRecovered.kt b/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/event/EventOmnipodUncertainTbrRecovered.kt new file mode 100644 index 0000000000..f2c4dc855a --- /dev/null +++ b/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/event/EventOmnipodUncertainTbrRecovered.kt @@ -0,0 +1,8 @@ +package info.nightscout.androidaps.plugins.pump.omnipod.event + +import info.nightscout.androidaps.events.Event + +/** + * Created by andy on 04.06.2018. + */ +class EventOmnipodUncertainTbrRecovered : Event() \ No newline at end of file diff --git a/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/manager/AapsOmnipodManager.java b/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/manager/AapsOmnipodManager.java index fec5c84cac..f430dcca4e 100644 --- a/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/manager/AapsOmnipodManager.java +++ b/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/manager/AapsOmnipodManager.java @@ -1,5 +1,8 @@ package info.nightscout.androidaps.plugins.pump.omnipod.manager; +import android.content.Context; +import android.content.Intent; + import org.joda.time.DateTime; import org.joda.time.Duration; import org.json.JSONException; @@ -14,6 +17,7 @@ import javax.inject.Inject; import javax.inject.Singleton; import dagger.android.HasAndroidInjector; +import info.nightscout.androidaps.activities.ErrorHelperActivity; import info.nightscout.androidaps.data.DetailedBolusInfo; import info.nightscout.androidaps.data.Profile; import info.nightscout.androidaps.data.PumpEnactResult; @@ -102,6 +106,7 @@ public class AapsOmnipodManager { private final OmnipodAlertUtil omnipodAlertUtil; private final NSUpload nsUpload; private final ProfileFunction profileFunction; + private final Context context; private boolean basalBeepsEnabled; private boolean bolusBeepsEnabled; @@ -128,8 +133,9 @@ public class AapsOmnipodManager { DatabaseHelperInterface databaseHelper, OmnipodAlertUtil omnipodAlertUtil, NSUpload nsUpload, - ProfileFunction profileFunction - ) { + ProfileFunction profileFunction, + Context context) { + this.podStateManager = podStateManager; this.aapsOmnipodUtil = aapsOmnipodUtil; this.aapsLogger = aapsLogger; @@ -142,6 +148,7 @@ public class AapsOmnipodManager { this.omnipodAlertUtil = omnipodAlertUtil; this.nsUpload = nsUpload; this.profileFunction = profileFunction; + this.context = context; delegate = new OmnipodManager(aapsLogger, communicationService, podStateManager); @@ -357,7 +364,7 @@ public class AapsOmnipodManager { if (detailedBolusInfo.isSMB) { showNotification(getStringResource(R.string.omnipod_error_bolus_failed_uncertain_smb, detailedBolusInfo.insulin), Notification.URGENT, isNotificationUncertainSmbSoundEnabled() ? R.raw.boluserror : null); } else { - showNotification(getStringResource(R.string.omnipod_error_bolus_failed_uncertain), Notification.URGENT, isNotificationUncertainBolusSoundEnabled() ? R.raw.boluserror : null); + showErrorDialog(getStringResource(R.string.omnipod_error_bolus_failed_uncertain), isNotificationUncertainBolusSoundEnabled() ? R.raw.boluserror : null); } } @@ -710,6 +717,10 @@ public class AapsOmnipodManager { activePlugin.getActiveTreatments().addToHistoryTempBasal(temporaryBasal); } + public long addTbrSuccessToHistory(long requestTime, TempBasalPair tempBasalPair) { + return addSuccessToHistory(requestTime, PodHistoryEntryType.SET_TEMPORARY_BASAL, tempBasalPair); + } + private void addTempBasalTreatment(long time, long pumpId, TempBasalPair tempBasalPair) { TemporaryBasal tempStart = new TemporaryBasal(injector) // .date(time) // @@ -850,6 +861,15 @@ public class AapsOmnipodManager { rxBus.send(event); } + private void showErrorDialog(String message, Integer sound) { + Intent intent = new Intent(context, ErrorHelperActivity.class); + intent.putExtra("soundid", sound); + intent.putExtra("status", message); + intent.putExtra("title", resourceHelper.gs(R.string.error)); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + context.startActivity(intent); + } + private void showPodFaultNotification(FaultEventCode faultEventCode) { showPodFaultNotification(faultEventCode, R.raw.boluserror); } diff --git a/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/manager/AapsPodStateManager.java b/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/manager/AapsPodStateManager.java index 551206190f..6ebaa1d3b9 100644 --- a/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/manager/AapsPodStateManager.java +++ b/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/manager/AapsPodStateManager.java @@ -10,6 +10,7 @@ import info.nightscout.androidaps.plugins.pump.omnipod.driver.manager.PodStateMa import info.nightscout.androidaps.plugins.pump.omnipod.event.EventOmnipodActiveAlertsChanged; import info.nightscout.androidaps.plugins.pump.omnipod.event.EventOmnipodFaultEventChanged; import info.nightscout.androidaps.plugins.pump.omnipod.event.EventOmnipodTbrChanged; +import info.nightscout.androidaps.plugins.pump.omnipod.event.EventOmnipodUncertainTbrRecovered; import info.nightscout.androidaps.utils.sharedPreferences.SP; @Singleton @@ -34,6 +35,10 @@ public class AapsPodStateManager extends PodStateManager { sp.putString(OmnipodStorageKeys.Preferences.POD_STATE, podState); } + @Override protected void onUncertainTbrRecovered() { + rxBus.send(new EventOmnipodUncertainTbrRecovered()); + } + @Override protected void onTbrChanged() { rxBus.send(new EventOmnipodTbrChanged()); } diff --git a/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/ui/OmnipodOverviewFragment.kt b/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/ui/OmnipodOverviewFragment.kt index 3fd3b27472..707a133a6f 100644 --- a/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/ui/OmnipodOverviewFragment.kt +++ b/omnipod/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/ui/OmnipodOverviewFragment.kt @@ -361,11 +361,17 @@ class OmnipodOverviewFragment : DaggerFragment() { } } else { if (podStateManager.podProgressStatus.isRunning) { - if (podStateManager.isSuspended) { + var status = if (podStateManager.isSuspended) { resourceHelper.gs(R.string.omnipod_pod_status_suspended) } else { resourceHelper.gs(R.string.omnipod_pod_status_running) } + + if (!podStateManager.isBasalCertain) { + status += " (" + resourceHelper.gs(R.string.omnipod_uncertain) + ")" + } + + status } else if (podStateManager.podProgressStatus == PodProgressStatus.FAULT_EVENT_OCCURRED) { resourceHelper.gs(R.string.omnipod_pod_status_pod_fault) } else if (podStateManager.podProgressStatus == PodProgressStatus.INACTIVE) { @@ -375,7 +381,7 @@ class OmnipodOverviewFragment : DaggerFragment() { } } - val podStatusColor = if (!podStateManager.isPodActivationCompleted || podStateManager.isPodDead || podStateManager.isSuspended) { + val podStatusColor = if (!podStateManager.isPodActivationCompleted || podStateManager.isPodDead || podStateManager.isSuspended || (podStateManager.isPodRunning && !podStateManager.isBasalCertain)) { Color.RED } else { Color.WHITE @@ -406,18 +412,36 @@ class OmnipodOverviewFragment : DaggerFragment() { private fun updateTempBasal() { if (podStateManager.isPodActivationCompleted && podStateManager.isTempBasalRunning) { - val now = DateTime.now() + if (!podStateManager.hasTempBasal()) { + omnipod_overview_temp_basal.text = "???" + omnipod_overview_temp_basal.setTextColor(Color.RED) + } else { + val now = DateTime.now() - val startTime = podStateManager.tempBasalStartTime - val amount = podStateManager.tempBasalAmount - val duration = podStateManager.tempBasalDuration + val startTime = podStateManager.tempBasalStartTime + val amount = podStateManager.tempBasalAmount + val duration = podStateManager.tempBasalDuration - val minutesRunning = Duration(startTime, now).standardMinutes + val minutesRunning = Duration(startTime, now).standardMinutes - var text: String + var text: String + val textColor: Int + text = resourceHelper.gs(R.string.omnipod_overview_temp_basal_value, amount, dateUtil.timeString(startTime.millis), minutesRunning, duration.standardMinutes) + if (podStateManager.isTempBasalCertain) { + textColor = Color.WHITE + } else { + textColor = Color.RED + text += " (" + resourceHelper.gs(R.string.omnipod_uncertain) + ")" + } + + omnipod_overview_temp_basal.text = text + omnipod_overview_temp_basal.setTextColor(textColor) + } + } else { + var text = PLACEHOLDER val textColor: Int - text = resourceHelper.gs(R.string.omnipod_overview_temp_basal_value, amount, dateUtil.timeString(startTime.millis), minutesRunning, duration.standardMinutes) - if (podStateManager.isTempBasalCertain) { + + if (!podStateManager.isPodActivationCompleted || podStateManager.isTempBasalCertain) { textColor = Color.WHITE } else { textColor = Color.RED @@ -426,9 +450,6 @@ class OmnipodOverviewFragment : DaggerFragment() { omnipod_overview_temp_basal.text = text omnipod_overview_temp_basal.setTextColor(textColor) - } else { - omnipod_overview_temp_basal.text = PLACEHOLDER - omnipod_overview_temp_basal.setTextColor(Color.WHITE) } } diff --git a/omnipod/src/main/res/values/strings.xml b/omnipod/src/main/res/values/strings.xml index 6cfea07b2b..476fa0a684 100644 --- a/omnipod/src/main/res/values/strings.xml +++ b/omnipod/src/main/res/values/strings.xml @@ -122,6 +122,7 @@ Unknown custom command: %1$s Failed to read Pulse Log Failed to refresh status + Failed to refresh status on startup Failed to acknowledge alerts Failed to suspend delivery Failed to set time @@ -135,6 +136,7 @@ The Pod\'s activation time has been exceeded. This Pod can no longer be activated. Failed to verify activation progress. Please retry. Pod suspended + A temporary basal is running on the Pod, but AAPS is unaware of this temporary basal. Please cancel your temporary basal manually. Confirmation