diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/OmnipodManager.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/OmnipodManager.java index 48c8114407..34fbf3b276 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/OmnipodManager.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/comm/OmnipodManager.java @@ -52,8 +52,6 @@ import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.CompositeDisposable; import io.reactivex.subjects.SingleSubject; -import static info.nightscout.androidaps.plugins.pump.omnipod.util.OmnipodConst.AVERAGE_BOLUS_COMMAND_COMMUNICATION_DURATION; - public class OmnipodManager { private static final int ACTION_VERIFICATION_TRIES = 3; @@ -161,22 +159,38 @@ public class OmnipodManager { // Returns a SingleSubject that returns when the bolus has finished. // When a bolus is cancelled, it will return after cancellation and report the estimated units delivered - public synchronized SingleSubject bolus(Double units, BolusProgressIndicationConsumer progressIndicationConsumer) { + // Only throws OmnipodException[certainFailure=false] + public synchronized BolusCommandResult bolus(Double units, BolusProgressIndicationConsumer progressIndicationConsumer) { assertReadyForDelivery(); - executeAndVerify(() -> communicationService.executeAction(new BolusAction(podState, units, true, true))); + CommandDeliveryStatus commandDeliveryStatus = CommandDeliveryStatus.SUCCESS; - DateTime startDate = DateTime.now().minus(AVERAGE_BOLUS_COMMAND_COMMUNICATION_DURATION); + try { + executeAndVerify(() -> communicationService.executeAction(new BolusAction(podState, units, true, true))); + } catch (OmnipodException ex) { + if (ex.isCertainFailure()) { + throw ex; + } + + // Catch uncertain exceptions as we still want to report bolus progress indication + if (isLoggingEnabled()) { + LOG.error("Caught exception[certainFailure=false] in bolus", ex); + } + commandDeliveryStatus = CommandDeliveryStatus.UNCERTAIN_FAILURE; + } + + DateTime startDate = DateTime.now().minus(OmnipodConst.AVERAGE_BOLUS_COMMAND_COMMUNICATION_DURATION); CompositeDisposable disposables = new CompositeDisposable(); Duration bolusDuration = calculateBolusDuration(units, OmnipodConst.POD_BOLUS_DELIVERY_RATE); - Duration estimatedRemainingBolusDuration = bolusDuration.minus(AVERAGE_BOLUS_COMMAND_COMMUNICATION_DURATION); + Duration estimatedRemainingBolusDuration = bolusDuration.minus(OmnipodConst.AVERAGE_BOLUS_COMMAND_COMMUNICATION_DURATION); if (progressIndicationConsumer != null) { - int numberOfProgressReports = 20; + int numberOfProgressReports = Math.max(20, Math.min(100, (int) Math.ceil(units) * 10)); long progressReportInterval = estimatedRemainingBolusDuration.getMillis() / numberOfProgressReports; - disposables.add(Flowable.intervalRange(1, numberOfProgressReports, 0, progressReportInterval, TimeUnit.MILLISECONDS) // + disposables.add(Flowable.intervalRange(0, numberOfProgressReports + 1, 0, progressReportInterval, TimeUnit.MILLISECONDS) // + .observeOn(AndroidSchedulers.mainThread()) // .subscribe(count -> { int percentage = (int) ((double) count / numberOfProgressReports * 100); double estimatedUnitsDelivered = activeBolusData == null ? 0 : activeBolusData.estimateUnitsDelivered(); @@ -184,10 +198,11 @@ public class OmnipodManager { })); } - SingleSubject bolusCompletionSubject = SingleSubject.create(); + SingleSubject bolusCompletionSubject = SingleSubject.create(); - disposables.add(Completable.complete() - .delay(estimatedRemainingBolusDuration.getStandardSeconds(), TimeUnit.SECONDS) + disposables.add(Completable.complete() // + .delay(estimatedRemainingBolusDuration.getMillis() + 250, TimeUnit.MILLISECONDS) // + .observeOn(AndroidSchedulers.mainThread()) // .doOnComplete(() -> { synchronized (bolusDataLock) { for (int i = 0; i < ACTION_VERIFICATION_TRIES; i++) { @@ -207,7 +222,7 @@ public class OmnipodManager { } if (activeBolusData != null) { - activeBolusData.bolusCompletionSubject.onSuccess(new BolusResult(units)); + activeBolusData.bolusCompletionSubject.onSuccess(new BolusDeliveryResult(units)); activeBolusData = null; } } @@ -218,7 +233,7 @@ public class OmnipodManager { activeBolusData = new ActiveBolusData(units, startDate, bolusCompletionSubject, disposables); } - return bolusCompletionSubject; + return new BolusCommandResult(commandDeliveryStatus, bolusCompletionSubject); } public synchronized void cancelBolus() { @@ -232,7 +247,7 @@ public class OmnipodManager { executeAndVerify(() -> communicationService.executeAction(new CancelDeliveryAction(podState, DeliveryType.BOLUS, true))); activeBolusData.getDisposables().dispose(); - activeBolusData.getBolusCompletionSubject().onSuccess(new BolusResult(activeBolusData.estimateUnitsDelivered())); + activeBolusData.getBolusCompletionSubject().onSuccess(new BolusDeliveryResult(activeBolusData.estimateUnitsDelivered())); activeBolusData = null; } } @@ -308,7 +323,7 @@ public class OmnipodManager { if (isCertainFailure(ex)) { throw ex; } else { - CommandVerificationResult verificationResult = verifyCommand(); + CommandDeliveryStatus verificationResult = verifyCommand(); switch (verificationResult) { case CERTAIN_FAILURE: if (ex instanceof OmnipodException) { @@ -335,12 +350,12 @@ public class OmnipodManager { } } - private SetupActionResult verifySetupAction(StatusResponseHandler setupActionResponseHandler, SetupProgress expectedSetupProgress) { + private SetupActionResult verifySetupAction(StatusResponseConsumer setupActionResponseHandler, SetupProgress expectedSetupProgress) { SetupActionResult result = null; for (int i = 0; ACTION_VERIFICATION_TRIES > i; i++) { try { StatusResponse delayedStatusResponse = communicationService.executeAction(new GetStatusAction(podState)); - setupActionResponseHandler.handle(delayedStatusResponse); + setupActionResponseHandler.accept(delayedStatusResponse); if (podState.getSetupProgress().equals(expectedSetupProgress)) { result = new SetupActionResult(SetupActionResult.ResultType.SUCCESS); @@ -359,7 +374,7 @@ public class OmnipodManager { } // Only works for commands which contain nonce resyncable message blocks - private CommandVerificationResult verifyCommand() { + private CommandDeliveryStatus verifyCommand() { if (isLoggingEnabled()) { LOG.warn("Verifying command by using cancel none command to verify nonce"); } @@ -370,12 +385,12 @@ public class OmnipodManager { if (isLoggingEnabled()) { LOG.info("Command resolved to FAILURE (CERTAIN_FAILURE)"); } - return CommandVerificationResult.CERTAIN_FAILURE; + return CommandDeliveryStatus.CERTAIN_FAILURE; } catch (Exception ex) { if (isLoggingEnabled()) { LOG.error("Command unresolved (UNCERTAIN_FAILURE)"); } - return CommandVerificationResult.UNCERTAIN_FAILURE; + return CommandDeliveryStatus.UNCERTAIN_FAILURE; } if (isLoggingEnabled()) { @@ -383,7 +398,7 @@ public class OmnipodManager { LOG.info("Command status resolved to SUCCESS"); } } - return CommandVerificationResult.SUCCESS; + return CommandDeliveryStatus.SUCCESS; } private boolean isLoggingEnabled() { @@ -402,10 +417,28 @@ public class OmnipodManager { return ex instanceof OmnipodException && ((OmnipodException) ex).isCertainFailure(); } - public static class BolusResult { + public static class BolusCommandResult { + private final CommandDeliveryStatus commandDeliveryStatus; + private final SingleSubject deliveryResultSubject; + + public BolusCommandResult(CommandDeliveryStatus commandDeliveryStatus, SingleSubject deliveryResultSubject) { + this.commandDeliveryStatus = commandDeliveryStatus; + this.deliveryResultSubject = deliveryResultSubject; + } + + public CommandDeliveryStatus getCommandDeliveryStatus() { + return commandDeliveryStatus; + } + + public SingleSubject getDeliveryResultSubject() { + return deliveryResultSubject; + } + } + + public static class BolusDeliveryResult { private final double unitsDelivered; - public BolusResult(double unitsDelivered) { + public BolusDeliveryResult(double unitsDelivered) { this.unitsDelivered = unitsDelivered; } @@ -414,7 +447,7 @@ public class OmnipodManager { } } - private enum CommandVerificationResult { + public enum CommandDeliveryStatus { SUCCESS, CERTAIN_FAILURE, UNCERTAIN_FAILURE @@ -422,17 +455,17 @@ public class OmnipodManager { // TODO replace with Consumer when our min API level >= 24 @FunctionalInterface - private interface StatusResponseHandler { - void handle(StatusResponse statusResponse); + private interface StatusResponseConsumer { + void accept(StatusResponse statusResponse); } private static class ActiveBolusData { private final double units; private volatile DateTime startDate; - private volatile SingleSubject bolusCompletionSubject; + private volatile SingleSubject bolusCompletionSubject; private volatile CompositeDisposable disposables; - private ActiveBolusData(double units, DateTime startDate, SingleSubject bolusCompletionSubject, CompositeDisposable disposables) { + private ActiveBolusData(double units, DateTime startDate, SingleSubject bolusCompletionSubject, CompositeDisposable disposables) { this.units = units; this.startDate = startDate; this.bolusCompletionSubject = bolusCompletionSubject; @@ -451,11 +484,11 @@ public class OmnipodManager { return disposables; } - public SingleSubject getBolusCompletionSubject() { + public SingleSubject getBolusCompletionSubject() { return bolusCompletionSubject; } - public void setBolusCompletionSubject(SingleSubject bolusCompletionSubject) { + public void setBolusCompletionSubject(SingleSubject bolusCompletionSubject) { this.bolusCompletionSubject = bolusCompletionSubject; } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/driver/comm/AapsOmnipodManager.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/driver/comm/AapsOmnipodManager.java index 87b35b40e7..e0d50a70b1 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/driver/comm/AapsOmnipodManager.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/driver/comm/AapsOmnipodManager.java @@ -47,7 +47,6 @@ import info.nightscout.androidaps.plugins.pump.omnipod.exception.OmnipodExceptio import info.nightscout.androidaps.plugins.pump.omnipod.exception.PodFaultException; import info.nightscout.androidaps.plugins.pump.omnipod.exception.PodReturnedErrorResponseException; import info.nightscout.androidaps.plugins.pump.omnipod.util.OmnipodUtil; -import io.reactivex.Single; import io.reactivex.disposables.Disposable; public class AapsOmnipodManager implements OmnipodCommunicationManagerInterface { @@ -148,44 +147,42 @@ public class AapsOmnipodManager implements OmnipodCommunicationManagerInterface return new PumpEnactResult().success(true).enacted(true); } + // TODO add boolean isSmb so we can disable progress indication for SMB @Override - public PumpEnactResult setBolus(Double units) { + public PumpEnactResult setBolus(Double units/*, boolean isSmb*/) { + OmnipodManager.BolusCommandResult bolusCommandResult; try { - Single responseObserver = delegate.bolus(units, + bolusCommandResult = delegate.bolus(units, /* isSmb ? null : */ (estimatedUnitsDelivered, percentage) -> { EventOverviewBolusProgress progressUpdateEvent = EventOverviewBolusProgress.INSTANCE; progressUpdateEvent.setStatus(getStringResource(R.string.bolusdelivering, units)); progressUpdateEvent.setPercent(percentage); RxBus.INSTANCE.send(progressUpdateEvent); }); - - // At this point, we know that the bolus command has been succesfully sent - - double unitsDelivered = units; - - try { - // Wait for the bolus to finish - OmnipodManager.BolusResult bolusResult = responseObserver.blockingGet(); - unitsDelivered = bolusResult.getUnitsDelivered(); - } catch (Exception ex) { - if (loggingEnabled()) { - LOG.debug("Ignoring failed status response for bolus completion verification", ex); - } - } - - return new PumpEnactResult().success(true).enacted(true).bolusDelivered(unitsDelivered); } catch (Exception ex) { - // Sending the command failed String comment = handleAndTranslateException(ex); - if (OmnipodManager.isCertainFailure(ex)) { - return new PumpEnactResult().success(false).enacted(false).comment(comment); - } else { - // TODO notify user about uncertain failure - // we don't know if the bolus failed, so for safety reasons, we choose to register the bolus as succesful. - // TODO also manually sleep until the bolus should have been finished here (after notifying the user) - return new PumpEnactResult().success(true).enacted(true).comment(comment).bolusDelivered(units); + return new PumpEnactResult().success(false).enacted(false).comment(comment); + } + + if (OmnipodManager.CommandDeliveryStatus.UNCERTAIN_FAILURE.equals(bolusCommandResult.getCommandDeliveryStatus()) /* && !isSmb */) { + // TODO notify user about uncertain failure ---> we're unsure whether or not the bolus has been delivered + // For safety reasons, we should treat this as a bolus that has been delivered, in order to prevent insulin overdose + } + + double unitsDelivered = units; + + try { + // Wait for the bolus to finish + OmnipodManager.BolusDeliveryResult bolusDeliveryResult = + bolusCommandResult.getDeliveryResultSubject().blockingGet(); + unitsDelivered = bolusDeliveryResult.getUnitsDelivered(); + } catch (Exception ex) { + if (loggingEnabled()) { + LOG.debug("Ignoring failed status response for bolus completion verification", ex); } } + + return new PumpEnactResult().success(true).enacted(true).bolusDelivered(unitsDelivered); } @Override diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/util/OmnipodConst.java b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/util/OmnipodConst.java index b5de4eb538..06713f4d75 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/util/OmnipodConst.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/pump/omnipod/util/OmnipodConst.java @@ -35,7 +35,7 @@ public class OmnipodConst { public static final Duration MAX_TEMP_BASAL_DURATION = Duration.standardHours(12); public static final int DEFAULT_ADDRESS = 0xffffffff; - public static final Duration AVERAGE_BOLUS_COMMAND_COMMUNICATION_DURATION = Duration.standardSeconds(2); + public static final Duration AVERAGE_BOLUS_COMMAND_COMMUNICATION_DURATION = Duration.millis(1500); public static final Duration SERVICE_DURATION = Duration.standardHours(80); public static final Duration EXPIRATION_ADVISORY_WINDOW = Duration.standardHours(2);