- Also show bolus progress dialog for uncertain boluses

- Improve bolus progress indication
This commit is contained in:
Bart Sopers 2019-12-06 00:13:50 +01:00
parent ea5ff4cc3a
commit dfb15e0f82
3 changed files with 88 additions and 58 deletions

View file

@ -52,8 +52,6 @@ import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable; import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.subjects.SingleSubject; import io.reactivex.subjects.SingleSubject;
import static info.nightscout.androidaps.plugins.pump.omnipod.util.OmnipodConst.AVERAGE_BOLUS_COMMAND_COMMUNICATION_DURATION;
public class OmnipodManager { public class OmnipodManager {
private static final int ACTION_VERIFICATION_TRIES = 3; 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. // 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 // When a bolus is cancelled, it will return after cancellation and report the estimated units delivered
public synchronized SingleSubject<BolusResult> bolus(Double units, BolusProgressIndicationConsumer progressIndicationConsumer) { // Only throws OmnipodException[certainFailure=false]
public synchronized BolusCommandResult bolus(Double units, BolusProgressIndicationConsumer progressIndicationConsumer) {
assertReadyForDelivery(); 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(); CompositeDisposable disposables = new CompositeDisposable();
Duration bolusDuration = calculateBolusDuration(units, OmnipodConst.POD_BOLUS_DELIVERY_RATE); 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) { if (progressIndicationConsumer != null) {
int numberOfProgressReports = 20; int numberOfProgressReports = Math.max(20, Math.min(100, (int) Math.ceil(units) * 10));
long progressReportInterval = estimatedRemainingBolusDuration.getMillis() / numberOfProgressReports; 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 -> { .subscribe(count -> {
int percentage = (int) ((double) count / numberOfProgressReports * 100); int percentage = (int) ((double) count / numberOfProgressReports * 100);
double estimatedUnitsDelivered = activeBolusData == null ? 0 : activeBolusData.estimateUnitsDelivered(); double estimatedUnitsDelivered = activeBolusData == null ? 0 : activeBolusData.estimateUnitsDelivered();
@ -184,10 +198,11 @@ public class OmnipodManager {
})); }));
} }
SingleSubject<BolusResult> bolusCompletionSubject = SingleSubject.create(); SingleSubject<BolusDeliveryResult> bolusCompletionSubject = SingleSubject.create();
disposables.add(Completable.complete() disposables.add(Completable.complete() //
.delay(estimatedRemainingBolusDuration.getStandardSeconds(), TimeUnit.SECONDS) .delay(estimatedRemainingBolusDuration.getMillis() + 250, TimeUnit.MILLISECONDS) //
.observeOn(AndroidSchedulers.mainThread()) //
.doOnComplete(() -> { .doOnComplete(() -> {
synchronized (bolusDataLock) { synchronized (bolusDataLock) {
for (int i = 0; i < ACTION_VERIFICATION_TRIES; i++) { for (int i = 0; i < ACTION_VERIFICATION_TRIES; i++) {
@ -207,7 +222,7 @@ public class OmnipodManager {
} }
if (activeBolusData != null) { if (activeBolusData != null) {
activeBolusData.bolusCompletionSubject.onSuccess(new BolusResult(units)); activeBolusData.bolusCompletionSubject.onSuccess(new BolusDeliveryResult(units));
activeBolusData = null; activeBolusData = null;
} }
} }
@ -218,7 +233,7 @@ public class OmnipodManager {
activeBolusData = new ActiveBolusData(units, startDate, bolusCompletionSubject, disposables); activeBolusData = new ActiveBolusData(units, startDate, bolusCompletionSubject, disposables);
} }
return bolusCompletionSubject; return new BolusCommandResult(commandDeliveryStatus, bolusCompletionSubject);
} }
public synchronized void cancelBolus() { public synchronized void cancelBolus() {
@ -232,7 +247,7 @@ public class OmnipodManager {
executeAndVerify(() -> communicationService.executeAction(new CancelDeliveryAction(podState, DeliveryType.BOLUS, true))); executeAndVerify(() -> communicationService.executeAction(new CancelDeliveryAction(podState, DeliveryType.BOLUS, true)));
activeBolusData.getDisposables().dispose(); activeBolusData.getDisposables().dispose();
activeBolusData.getBolusCompletionSubject().onSuccess(new BolusResult(activeBolusData.estimateUnitsDelivered())); activeBolusData.getBolusCompletionSubject().onSuccess(new BolusDeliveryResult(activeBolusData.estimateUnitsDelivered()));
activeBolusData = null; activeBolusData = null;
} }
} }
@ -308,7 +323,7 @@ public class OmnipodManager {
if (isCertainFailure(ex)) { if (isCertainFailure(ex)) {
throw ex; throw ex;
} else { } else {
CommandVerificationResult verificationResult = verifyCommand(); CommandDeliveryStatus verificationResult = verifyCommand();
switch (verificationResult) { switch (verificationResult) {
case CERTAIN_FAILURE: case CERTAIN_FAILURE:
if (ex instanceof OmnipodException) { 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; SetupActionResult result = null;
for (int i = 0; ACTION_VERIFICATION_TRIES > i; i++) { for (int i = 0; ACTION_VERIFICATION_TRIES > i; i++) {
try { try {
StatusResponse delayedStatusResponse = communicationService.executeAction(new GetStatusAction(podState)); StatusResponse delayedStatusResponse = communicationService.executeAction(new GetStatusAction(podState));
setupActionResponseHandler.handle(delayedStatusResponse); setupActionResponseHandler.accept(delayedStatusResponse);
if (podState.getSetupProgress().equals(expectedSetupProgress)) { if (podState.getSetupProgress().equals(expectedSetupProgress)) {
result = new SetupActionResult(SetupActionResult.ResultType.SUCCESS); result = new SetupActionResult(SetupActionResult.ResultType.SUCCESS);
@ -359,7 +374,7 @@ public class OmnipodManager {
} }
// Only works for commands which contain nonce resyncable message blocks // Only works for commands which contain nonce resyncable message blocks
private CommandVerificationResult verifyCommand() { private CommandDeliveryStatus verifyCommand() {
if (isLoggingEnabled()) { if (isLoggingEnabled()) {
LOG.warn("Verifying command by using cancel none command to verify nonce"); LOG.warn("Verifying command by using cancel none command to verify nonce");
} }
@ -370,12 +385,12 @@ public class OmnipodManager {
if (isLoggingEnabled()) { if (isLoggingEnabled()) {
LOG.info("Command resolved to FAILURE (CERTAIN_FAILURE)"); LOG.info("Command resolved to FAILURE (CERTAIN_FAILURE)");
} }
return CommandVerificationResult.CERTAIN_FAILURE; return CommandDeliveryStatus.CERTAIN_FAILURE;
} catch (Exception ex) { } catch (Exception ex) {
if (isLoggingEnabled()) { if (isLoggingEnabled()) {
LOG.error("Command unresolved (UNCERTAIN_FAILURE)"); LOG.error("Command unresolved (UNCERTAIN_FAILURE)");
} }
return CommandVerificationResult.UNCERTAIN_FAILURE; return CommandDeliveryStatus.UNCERTAIN_FAILURE;
} }
if (isLoggingEnabled()) { if (isLoggingEnabled()) {
@ -383,7 +398,7 @@ public class OmnipodManager {
LOG.info("Command status resolved to SUCCESS"); LOG.info("Command status resolved to SUCCESS");
} }
} }
return CommandVerificationResult.SUCCESS; return CommandDeliveryStatus.SUCCESS;
} }
private boolean isLoggingEnabled() { private boolean isLoggingEnabled() {
@ -402,10 +417,28 @@ public class OmnipodManager {
return ex instanceof OmnipodException && ((OmnipodException) ex).isCertainFailure(); return ex instanceof OmnipodException && ((OmnipodException) ex).isCertainFailure();
} }
public static class BolusResult { public static class BolusCommandResult {
private final CommandDeliveryStatus commandDeliveryStatus;
private final SingleSubject<BolusDeliveryResult> deliveryResultSubject;
public BolusCommandResult(CommandDeliveryStatus commandDeliveryStatus, SingleSubject<BolusDeliveryResult> deliveryResultSubject) {
this.commandDeliveryStatus = commandDeliveryStatus;
this.deliveryResultSubject = deliveryResultSubject;
}
public CommandDeliveryStatus getCommandDeliveryStatus() {
return commandDeliveryStatus;
}
public SingleSubject<BolusDeliveryResult> getDeliveryResultSubject() {
return deliveryResultSubject;
}
}
public static class BolusDeliveryResult {
private final double unitsDelivered; private final double unitsDelivered;
public BolusResult(double unitsDelivered) { public BolusDeliveryResult(double unitsDelivered) {
this.unitsDelivered = unitsDelivered; this.unitsDelivered = unitsDelivered;
} }
@ -414,7 +447,7 @@ public class OmnipodManager {
} }
} }
private enum CommandVerificationResult { public enum CommandDeliveryStatus {
SUCCESS, SUCCESS,
CERTAIN_FAILURE, CERTAIN_FAILURE,
UNCERTAIN_FAILURE UNCERTAIN_FAILURE
@ -422,17 +455,17 @@ public class OmnipodManager {
// TODO replace with Consumer when our min API level >= 24 // TODO replace with Consumer when our min API level >= 24
@FunctionalInterface @FunctionalInterface
private interface StatusResponseHandler { private interface StatusResponseConsumer {
void handle(StatusResponse statusResponse); void accept(StatusResponse statusResponse);
} }
private static class ActiveBolusData { private static class ActiveBolusData {
private final double units; private final double units;
private volatile DateTime startDate; private volatile DateTime startDate;
private volatile SingleSubject<BolusResult> bolusCompletionSubject; private volatile SingleSubject<BolusDeliveryResult> bolusCompletionSubject;
private volatile CompositeDisposable disposables; private volatile CompositeDisposable disposables;
private ActiveBolusData(double units, DateTime startDate, SingleSubject<BolusResult> bolusCompletionSubject, CompositeDisposable disposables) { private ActiveBolusData(double units, DateTime startDate, SingleSubject<BolusDeliveryResult> bolusCompletionSubject, CompositeDisposable disposables) {
this.units = units; this.units = units;
this.startDate = startDate; this.startDate = startDate;
this.bolusCompletionSubject = bolusCompletionSubject; this.bolusCompletionSubject = bolusCompletionSubject;
@ -451,11 +484,11 @@ public class OmnipodManager {
return disposables; return disposables;
} }
public SingleSubject<BolusResult> getBolusCompletionSubject() { public SingleSubject<BolusDeliveryResult> getBolusCompletionSubject() {
return bolusCompletionSubject; return bolusCompletionSubject;
} }
public void setBolusCompletionSubject(SingleSubject<BolusResult> bolusCompletionSubject) { public void setBolusCompletionSubject(SingleSubject<BolusDeliveryResult> bolusCompletionSubject) {
this.bolusCompletionSubject = bolusCompletionSubject; this.bolusCompletionSubject = bolusCompletionSubject;
} }

View file

@ -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.PodFaultException;
import info.nightscout.androidaps.plugins.pump.omnipod.exception.PodReturnedErrorResponseException; import info.nightscout.androidaps.plugins.pump.omnipod.exception.PodReturnedErrorResponseException;
import info.nightscout.androidaps.plugins.pump.omnipod.util.OmnipodUtil; import info.nightscout.androidaps.plugins.pump.omnipod.util.OmnipodUtil;
import io.reactivex.Single;
import io.reactivex.disposables.Disposable; import io.reactivex.disposables.Disposable;
public class AapsOmnipodManager implements OmnipodCommunicationManagerInterface { public class AapsOmnipodManager implements OmnipodCommunicationManagerInterface {
@ -148,25 +147,35 @@ public class AapsOmnipodManager implements OmnipodCommunicationManagerInterface
return new PumpEnactResult().success(true).enacted(true); return new PumpEnactResult().success(true).enacted(true);
} }
// TODO add boolean isSmb so we can disable progress indication for SMB
@Override @Override
public PumpEnactResult setBolus(Double units) { public PumpEnactResult setBolus(Double units/*, boolean isSmb*/) {
OmnipodManager.BolusCommandResult bolusCommandResult;
try { try {
Single<OmnipodManager.BolusResult> responseObserver = delegate.bolus(units, bolusCommandResult = delegate.bolus(units, /* isSmb ? null : */
(estimatedUnitsDelivered, percentage) -> { (estimatedUnitsDelivered, percentage) -> {
EventOverviewBolusProgress progressUpdateEvent = EventOverviewBolusProgress.INSTANCE; EventOverviewBolusProgress progressUpdateEvent = EventOverviewBolusProgress.INSTANCE;
progressUpdateEvent.setStatus(getStringResource(R.string.bolusdelivering, units)); progressUpdateEvent.setStatus(getStringResource(R.string.bolusdelivering, units));
progressUpdateEvent.setPercent(percentage); progressUpdateEvent.setPercent(percentage);
RxBus.INSTANCE.send(progressUpdateEvent); RxBus.INSTANCE.send(progressUpdateEvent);
}); });
} catch (Exception ex) {
String comment = handleAndTranslateException(ex);
return new PumpEnactResult().success(false).enacted(false).comment(comment);
}
// At this point, we know that the bolus command has been succesfully sent 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; double unitsDelivered = units;
try { try {
// Wait for the bolus to finish // Wait for the bolus to finish
OmnipodManager.BolusResult bolusResult = responseObserver.blockingGet(); OmnipodManager.BolusDeliveryResult bolusDeliveryResult =
unitsDelivered = bolusResult.getUnitsDelivered(); bolusCommandResult.getDeliveryResultSubject().blockingGet();
unitsDelivered = bolusDeliveryResult.getUnitsDelivered();
} catch (Exception ex) { } catch (Exception ex) {
if (loggingEnabled()) { if (loggingEnabled()) {
LOG.debug("Ignoring failed status response for bolus completion verification", ex); LOG.debug("Ignoring failed status response for bolus completion verification", ex);
@ -174,18 +183,6 @@ public class AapsOmnipodManager implements OmnipodCommunicationManagerInterface
} }
return new PumpEnactResult().success(true).enacted(true).bolusDelivered(unitsDelivered); 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);
}
}
} }
@Override @Override

View file

@ -35,7 +35,7 @@ public class OmnipodConst {
public static final Duration MAX_TEMP_BASAL_DURATION = Duration.standardHours(12); public static final Duration MAX_TEMP_BASAL_DURATION = Duration.standardHours(12);
public static final int DEFAULT_ADDRESS = 0xffffffff; 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 SERVICE_DURATION = Duration.standardHours(80);
public static final Duration EXPIRATION_ADVISORY_WINDOW = Duration.standardHours(2); public static final Duration EXPIRATION_ADVISORY_WINDOW = Duration.standardHours(2);