Merge pull request #41 from AAPS-Omnipod/omnipod_eros_dev_prevent_0x31

Prevent 0x31 Pod faults (& improve recovery from uncertain delivery statuses)
This commit is contained in:
bartsopers 2020-11-22 21:41:25 +01:00 committed by GitHub
commit e24f8b3647
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 327 additions and 84 deletions

View file

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

View file

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

View file

@ -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<TemporaryBasal>) : RecyclerView.Adapter<TempBasalsViewHolder>() {
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)
}
}

View file

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

View file

@ -55,6 +55,8 @@ public interface TreatmentsInterface {
NonOverlappingIntervals<TemporaryBasal> getTemporaryBasalsFromHistory();
void removeTempBasal(TemporaryBasal temporaryBasal);
boolean isInHistoryExtendedBoluslInProgress();
ExtendedBolus getExtendedBolusFromHistory(long time);

View file

@ -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()) {
private void handleCancelledTbr() {
// Only report TBR cancellations if they haven't been explicitly requested
if (!isCancelTempBasalRunning) {
if (activePlugin.getActiveTreatments().isTempBasalInProgress() && !aapsOmnipodManager.hasSuspendedFakeTbr()) {
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");
}

View file

@ -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<DeliveryType> deliveryTypes, boolean acknowledgementBeep) {
assertReadyForDelivery();
if (deliveryTypes.contains(DeliveryType.BASAL)) {
podStateManager.setBasalCertain(false);
}
if (deliveryTypes.contains(DeliveryType.TEMP_BASAL)) {
podStateManager.setTempBasalCertain(false);
}
try {
return executeAndVerify(() -> {
StatusResponse statusResponse = communicationService.executeAction(new CancelDeliveryAction(podStateManager, deliveryTypes, acknowledgementBeep));
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<Double, Integer> 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;

View file

@ -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 {
// Triggers {@link #onTbrChanged() onTbrChanged()} when appropriate
setTempBasal(null, null, null, true, false);
// Don't trigger onTbrChanged as we will trigger onUncertainTbrRecovered below
podState.setTempBasalStartTime(null);
podState.setTempBasalAmount(null);
podState.setTempBasalDuration(null);
}
}
if (!isTempBasalCertain) {
podState.setTempBasalCertain(true);
onUncertainTbrRecovered();
}
if (!isBasalCertain) {
podState.setBasalCertain(true);
}
podState.setLastUpdatedFromResponse(DateTime.now());
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;
}

View file

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

View file

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

View file

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

View file

@ -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,6 +412,10 @@ class OmnipodOverviewFragment : DaggerFragment() {
private fun updateTempBasal() {
if (podStateManager.isPodActivationCompleted && podStateManager.isTempBasalRunning) {
if (!podStateManager.hasTempBasal()) {
omnipod_overview_temp_basal.text = "???"
omnipod_overview_temp_basal.setTextColor(Color.RED)
} else {
val now = DateTime.now()
val startTime = podStateManager.tempBasalStartTime
@ -426,9 +436,20 @@ 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)
var text = PLACEHOLDER
val textColor: Int
if (!podStateManager.isPodActivationCompleted || 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)
}
}

View file

@ -122,6 +122,7 @@
<string name="omnipod_error_unknown_custom_command">Unknown custom command: %1$s</string>
<string name="omnipod_error_failed_to_read_pulse_log">Failed to read Pulse Log</string>
<string name="omnipod_error_failed_to_refresh_status">Failed to refresh status</string>
<string name="omnipod_error_failed_to_refresh_status_on_startup">Failed to refresh status on startup</string>
<string name="omnipod_error_failed_to_acknowledge_alerts">Failed to acknowledge alerts</string>
<string name="omnipod_error_failed_to_suspend_delivery">Failed to suspend delivery</string>
<string name="omnipod_error_failed_to_set_time">Failed to set time</string>
@ -135,6 +136,7 @@
<string name="omnipod_error_pod_fault_activation_time_exceeded">The Pod\'s activation time has been exceeded. This Pod can no longer be activated.</string>
<string name="omnipod_error_failed_to_verify_activation_progress">Failed to verify activation progress. Please retry.</string>
<string name="omnipod_error_pod_suspended">Pod suspended</string>
<string name="omnipod_error_tbr_running_but_aaps_not_aware">A temporary basal is running on the Pod, but AAPS is unaware of this temporary basal. Please cancel your temporary basal manually.</string>
<!-- Omnipod - Confirmation -->
<string name="omnipod_confirmation">Confirmation</string>