Auto-retrying repeatable commands when there's a connection loss.

This commit is contained in:
Johannes Mockenhaupt 2017-10-31 11:05:07 +01:00
parent 88f500417d
commit f64e00fc79
No known key found for this signature in database
GPG key ID: 9E1EA6AF7BBBB0D1
2 changed files with 85 additions and 87 deletions

View file

@ -229,11 +229,11 @@ public class ComboPlugin implements PluginBase, PumpInterface {
return; return;
} }
} }
runCommand("Initializing", () -> ruffyScripter.readHistory(new PumpHistoryRequest())); runCommand("Initializing", 3, () -> ruffyScripter.readHistory(new PumpHistoryRequest()));
pump.initialized = true; pump.initialized = true;
} }
runCommand("Refreshing", ruffyScripter::readReservoirLevelAndLastBolus); runCommand("Refreshing", 3, ruffyScripter::readReservoirLevelAndLastBolus);
// TODO fuse the below into 'sync'? or make checkForTbrMismatch jut a trigger to issue a sync if needed; don't run sync twice as is nice // TODO fuse the below into 'sync'? or make checkForTbrMismatch jut a trigger to issue a sync if needed; don't run sync twice as is nice
// checkForTbrMismatch(); // checkForTbrMismatch();
@ -245,7 +245,7 @@ public class ComboPlugin implements PluginBase, PumpInterface {
* full pump history and update AAPS' DB. * full pump history and update AAPS' DB.
*/ */
private void checkPumpHistory() { private void checkPumpHistory() {
CommandResult commandResult = runCommand(MainApp.sResources.getString(R.string.combo_pump_action_checking_history), () -> CommandResult commandResult = runCommand(MainApp.sResources.getString(R.string.combo_pump_action_checking_history), 3, () ->
ruffyScripter.readHistory( ruffyScripter.readHistory(
new PumpHistoryRequest() new PumpHistoryRequest()
.bolusHistory(PumpHistoryRequest.LAST) .bolusHistory(PumpHistoryRequest.LAST)
@ -410,6 +410,14 @@ public class ComboPlugin implements PluginBase, PumpInterface {
@NonNull @NonNull
private PumpEnactResult deliverBolus(final DetailedBolusInfo detailedBolusInfo) { private PumpEnactResult deliverBolus(final DetailedBolusInfo detailedBolusInfo) {
// TODO for non-SMB: read resorvoir level first to make sure there's enough insulin left
CommandResult reservoirBolusResult = runCommand(MainApp.sResources.getString(R.string.combo_pump_action_refreshing), 3,
ruffyScripter::readReservoirLevelAndLastBolus);
if (!reservoirBolusResult.success) {
// TODO
}
// List<Bolus> bolusHistory = reservoirBolusResult.history.bolusHistory;
// if (bolusHistory)
// TODO // TODO
// before non-SMB: check enough insulin is available, check we're up to date on boluses // before non-SMB: check enough insulin is available, check we're up to date on boluses
// after bolus: update reservoir level and check the bolus we just did is actually there // after bolus: update reservoir level and check the bolus we just did is actually there
@ -495,11 +503,13 @@ public class ComboPlugin implements PluginBase, PumpInterface {
} }
final int finalAdjustedPercent = adjustedPercent; final int finalAdjustedPercent = adjustedPercent;
CommandResult commandResult = runCommand(MainApp.sResources.getString(R.string.combo_pump_action_setting_tbr), () -> ruffyScripter.setTbr(finalAdjustedPercent, durationInMinutes)); CommandResult commandResult= runCommand(MainApp.sResources.getString(R.string.combo_pump_action_setting_tbr), 3, () -> ruffyScripter.setTbr(finalAdjustedPercent, durationInMinutes));
if (commandResult.enacted) { PumpState state = commandResult.state;
pump.tbrSetTime = System.currentTimeMillis(); if (state.tbrActive && state.tbrPercent == percent
TemporaryBasal tempStart = new TemporaryBasal(pump.tbrSetTime); && (state.tbrRemainingDuration == durationInMinutes || state.tbrRemainingDuration == durationInMinutes - 1)) {
pump.tbrSetTime = state.timestamp;
TemporaryBasal tempStart = new TemporaryBasal(state.timestamp);
// TODO commandResult.state.tbrRemainingDuration might already display 29 if 30 was set, since 29:59 is shown as 29 ... // TODO commandResult.state.tbrRemainingDuration might already display 29 if 30 was set, since 29:59 is shown as 29 ...
// we should check this, but really ... something must be really screwed up if that number was anything different // we should check this, but really ... something must be really screwed up if that number was anything different
// TODO actually ... might setting 29 help with gaps between TBRs? w/o the hack in TemporaryBasal? // TODO actually ... might setting 29 help with gaps between TBRs? w/o the hack in TemporaryBasal?
@ -542,7 +552,7 @@ public class ComboPlugin implements PluginBase, PumpInterface {
if (activeTemp == null || userRequested) { if (activeTemp == null || userRequested) {
/* v1 compatibility to sync DB to pump if they diverged (activeTemp == null) */ /* v1 compatibility to sync DB to pump if they diverged (activeTemp == null) */
log.debug("cancelTempBasal: hard-cancelling TBR since user requested"); log.debug("cancelTempBasal: hard-cancelling TBR since user requested");
commandResult = runCommand(MainApp.sResources.getString(R.string.combo_pump_action_cancelling_tbr), ruffyScripter::cancelTbr); commandResult = runCommand(MainApp.sResources.getString(R.string.combo_pump_action_cancelling_tbr), 2, ruffyScripter::cancelTbr);
if (commandResult.enacted) { if (commandResult.enacted) {
tempBasal = new TemporaryBasal(System.currentTimeMillis()); tempBasal = new TemporaryBasal(System.currentTimeMillis());
@ -565,7 +575,7 @@ public class ComboPlugin implements PluginBase, PumpInterface {
// on whether the TBR we're cancelling is above or below 100%. // on whether the TBR we're cancelling is above or below 100%.
final int percentage = (activeTemp.percentRate > 100) ? 110 : 90; final int percentage = (activeTemp.percentRate > 100) ? 110 : 90;
log.debug("cancelTempBasal: changing TBR to " + percentage + "% for 15 mins."); log.debug("cancelTempBasal: changing TBR to " + percentage + "% for 15 mins.");
commandResult = runCommand(MainApp.sResources.getString(R.string.combo_pump_action_setting_tbr), () -> ruffyScripter.setTbr(percentage, 15)); commandResult = runCommand(MainApp.sResources.getString(R.string.combo_pump_action_setting_tbr), 2, () -> ruffyScripter.setTbr(percentage, 15));
if (commandResult.enacted) { if (commandResult.enacted) {
tempBasal = new TemporaryBasal(System.currentTimeMillis()); tempBasal = new TemporaryBasal(System.currentTimeMillis());
@ -634,9 +644,12 @@ public class ComboPlugin implements PluginBase, PumpInterface {
pump.reservoirLevel = commandResult.reservoirLevel; pump.reservoirLevel = commandResult.reservoirLevel;
} }
if (commandResult.lastBolus != null) { if (!commandResult.success && retries > 0) {
pump.lastBolus = commandResult.lastBolus; for(int retryAttempts = 1; !commandResult.success && retryAttempts <= retries; retryAttempts++) {
} log.debug("Command was not successful, retries request, doing retry #" + retryAttempts);
commandResult = commandExecution.execute();
}
}
if (commandResult.history != null) { if (commandResult.history != null) {
if (!commandResult.history.bolusHistory.isEmpty()) { if (!commandResult.history.bolusHistory.isEmpty()) {
@ -734,7 +747,7 @@ public class ComboPlugin implements PluginBase, PumpInterface {
} }
private void runFullSync(final PumpHistoryRequest request) { private void runFullSync(final PumpHistoryRequest request) {
CommandResult result = runCommand("Syncing full pump history", () -> ruffyScripter.readHistory(request)); CommandResult result = runCommand("Syncing full pump history", 3, () -> ruffyScripter.readHistory(request));
// boluses // boluses

View file

@ -273,15 +273,22 @@ public class RuffyScripter implements RuffyCommands {
// (to fail before the next loop iteration issues the next command) // (to fail before the next loop iteration issues the next command)
long dynamicTimeout = calculateCmdInactivityTimeout(); long dynamicTimeout = calculateCmdInactivityTimeout();
long overallTimeout = calculateOverallCmdTimeout(); long overallTimeout = calculateOverallCmdTimeout();
int maxReconnectAttempts = 3;
while (cmdThread.isAlive()) { while (cmdThread.isAlive()) {
log.trace("Waiting for running command to complete"); log.trace("Waiting for running command to complete");
SystemClock.sleep(500); SystemClock.sleep(500);
if (!ruffyService.isConnected()) { if (!ruffyService.isConnected()) {
if (maxReconnectAttempts > 0) { // on connection lose try to reconnect, confirm warning alerts caused by
maxReconnectAttempts--; // the disconnected and then return the command as failed (the caller
cmdThread.interrupt(); // can retry if needed).
reconnect(); cmdThread.interrupt();
activeCmd.getResult().success = false;
for(int attempts = 4; attempts > 0; attempts--) {
boolean reconnected = recoverFromConnectionLoss();
if (reconnected) {
break;
}
// try again in 20 sec
SystemClock.sleep(20 * 1000);
} }
} }
@ -339,76 +346,47 @@ public class RuffyScripter implements RuffyCommands {
} }
/** /**
* On connection lose the pump raises an error immediately (when setting a TBR or giving a bolus) - * On connection lose the pump raises an alert immediately (when setting a TBR or giving a bolus) -
* there's no timeout before that happens. But: a reconnect is still possible which can then * there's no timeout before that happens. But: a reconnect is still possible which can then
* confirm the alarm and. * confirm the alert.
* *
* @return whether the reconnect and return to main menu was successful * @return whether the reconnect and return to main menu was successful
*/ */
// TODO only reconnect, confirm the warning the disconnect caused ond then return to ComboPlugin, which shall decide whether/how to restart private boolean recoverFromConnectionLoss() {
// requires turning CommandResult from a return type into a command field log.debug("Connection was lost, trying to reconnect");
ensureConnected();
// TODO at least for bigger boluses we should check history after reconnect to make sure
// we haven't issued that bolus within the last 1-2m? in case there's a bug in the code ...
// TODO: only do the reconnect to confirm the alert, then return and let the ComboPlugin decide what to do next;
// for bolus: how ... run 'step 2': checking/reading history?! step1 being bolus delivery, so have different resume points? ggrrrmpf
// less logic in scripter; just reconnect to confirm alert if needed, then return with error;
// let CP read history.LAST to see what actually happened and then resume appropriately.
private boolean reconnect() {
try {
log.debug("Connection was lost, trying to reconnect");
ensureConnected();
if (getCurrentMenu().getType() == MenuType.WARNING_OR_ERROR) {
String errorMessage = (String) getCurrentMenu().getAttribute(MenuAttribute.MESSAGE);
// TODO bolus cancelled is raised BEFORE a bolus is started. if disconnect occurs after
// bolus has started (or the user interacts with the pump, the bolus continues.
// in that case, reconnecting and restarting the command lets to a DUPLICATE bolus.
// add a method restartAllowed(), which accesses a flag bolusDelivering started, which
// is set false then?
if (activeCmd.getReconnectAlarm() != null && activeCmd.getReconnectAlarm().equals(errorMessage)) {
log.debug("Confirming alert caused by disconnect: " + errorMessage);
// confirm alert
verifyMenuIsDisplayed(MenuType.WARNING_OR_ERROR);
pressCheckKey();
// dismiss alert
verifyMenuIsDisplayed(MenuType.WARNING_OR_ERROR);
pressCheckKey();
waitForMenuToBeLeft(MenuType.WARNING_OR_ERROR);
// it's possible that multiple alarms are raised, that can happen when
// a battery low/empty/occlusion alert occurs, which then causes a bolus/tbr
// cancelled alert.
// let's not try anything fancy, since it's non trivial to decide which of
// those cases can be dealt with and it's tricky to test it. Also,
// those situations aren't likely to occurs all that often, so let the alarms
// ring and catch up with history later.
// TODO this needs some thought though as how to propagate such errors,
// esp. if a kid is carrying the pump and a supervisor is monitoring and
// controlling the loop.
}
}
} catch (CommandException e) {
// TODO ...
return false;
} catch (Exception e) {
// TODO ...
return false;
}
if (getCurrentMenu().getType() == MenuType.WARNING_OR_ERROR) { if (getCurrentMenu().getType() == MenuType.WARNING_OR_ERROR) {
return false; WarningOrErrorCode warningOrErrorCode = readWarningOrErrorCode();
if (Objects.equals(activeCmd.getReconnectWarningId(), warningOrErrorCode.warningCode)) {
log.debug("Confirming warning caused by disconnect: #" + warningOrErrorCode.warningCode);
// confirm alert
verifyMenuIsDisplayed(MenuType.WARNING_OR_ERROR);
pressCheckKey();
// dismiss alert
verifyMenuIsDisplayed(MenuType.WARNING_OR_ERROR);
pressCheckKey();
}
} }
// aren't at main_menu after alert anyways? unless bolus i still going (also main_menu); low cartridge on bolus needs to be handled specially, by bolus command // A bolus cancelled is raised BEFORE a bolus is started. If a disconnect occurs after a
returnToRootMenu(); // bolus has started (or the user interacts with the pump) the bolus continues.
// If that happened, wait till the pump has finished the bolus, then it can be read from
return getCurrentMenu().getType() == MenuType.MAIN_MENU; // the history as delivered.
Double bolusRemaining = (Double) getCurrentMenu().getAttribute(MenuAttribute.BOLUS_REMAINING);
try {
while (ruffyService.isConnected() && bolusRemaining != null) {
waitForScreenUpdate();
}
boolean connected = ruffyService.isConnected();
log.debug("Recovery from connection loss successful:" + connected);
return connected;
} catch (RemoteException e) {
log.debug("Recovery from connection loss failed");
return false;
}
} }
/** /** If there's an issue, this times out eventually and throws a CommandException */
* If there's an issue, this times out eventually and throws a CommandException
*/
private void ensureConnected() { private void ensureConnected() {
try { try {
if (ruffyService.isConnected()) { if (ruffyService.isConnected()) {
@ -479,12 +457,15 @@ public class RuffyScripter implements RuffyCommands {
verifyMenuIsDisplayed(MenuType.WARNING_OR_ERROR); verifyMenuIsDisplayed(MenuType.WARNING_OR_ERROR);
Integer warningCode = (Integer) getCurrentMenu().getAttribute(MenuAttribute.WARNING); Integer warningCode = (Integer) getCurrentMenu().getAttribute(MenuAttribute.WARNING);
Integer errorCode = (Integer) getCurrentMenu().getAttribute(MenuAttribute.ERROR); Integer errorCode = (Integer) getCurrentMenu().getAttribute(MenuAttribute.ERROR);
while (warningCode == null && errorCode == null) { int retries = 3;
while (warningCode == null && errorCode == null && retries > 0) {
waitForScreenUpdate(); waitForScreenUpdate();
warningCode = (Integer) getCurrentMenu().getAttribute(MenuAttribute.WARNING); warningCode = (Integer) getCurrentMenu().getAttribute(MenuAttribute.WARNING);
errorCode = (Integer) getCurrentMenu().getAttribute(MenuAttribute.ERROR); errorCode = (Integer) getCurrentMenu().getAttribute(MenuAttribute.ERROR);
retries--;
} }
return new WarningOrErrorCode(warningCode, errorCode); return (warningCode != null || errorCode != null)
? new WarningOrErrorCode(warningCode, errorCode) : null;
} }
// below: methods to be used by commands // below: methods to be used by commands
@ -772,23 +753,23 @@ public class RuffyScripter implements RuffyCommands {
/** /**
* Confirms and dismisses the given alert if it's raised before the timeout * Confirms and dismisses the given alert if it's raised before the timeout
*/ */
public boolean confirmAlert(int warningCode, int maxWaitMs) { public boolean confirmAlert(@NonNull Integer warningCode, int maxWaitMs) {
long timeout = System.currentTimeMillis() + maxWaitMs; long timeout = System.currentTimeMillis() + maxWaitMs;
while (System.currentTimeMillis() < timeout) { while (System.currentTimeMillis() < timeout) {
if (getCurrentMenu().getType() == MenuType.WARNING_OR_ERROR) { if (getCurrentMenu().getType() == MenuType.WARNING_OR_ERROR) {
WarningOrErrorCode warningOrErrorCode = readWarningOrErrorCode(); WarningOrErrorCode warningOrErrorCode = readWarningOrErrorCode();
if (warningOrErrorCode.errorCode != 0) { if (warningOrErrorCode.errorCode != null) {
// TODO proper way to display such things in the UI; // TODO proper way to display such things in the UI;
throw new CommandException("Pump is in error state"); throw new CommandException("Pump is in error state");
} }
int displayedWarningCode = warningOrErrorCode.warningCode; Integer displayedWarningCode = warningOrErrorCode.warningCode;
String errorMsg = null; String errorMsg = null;
try { try {
errorMsg = (String) getCurrentMenu().getAttribute(MenuAttribute.MESSAGE); errorMsg = (String) getCurrentMenu().getAttribute(MenuAttribute.MESSAGE);
} catch (Exception e) { } catch (Exception e) {
// ignore // ignore
} }
if (displayedWarningCode != warningCode) { if (!Objects.equals(displayedWarningCode, warningCode)) {
throw new CommandException("An alert other than the expected warning " + warningCode + " was raised by the pump: " throw new CommandException("An alert other than the expected warning " + warningCode + " was raised by the pump: "
+ displayedWarningCode + "(" + errorMsg + "). Please check the pump."); + displayedWarningCode + "(" + errorMsg + "). Please check the pump.");
} }
@ -797,8 +778,12 @@ public class RuffyScripter implements RuffyCommands {
verifyMenuIsDisplayed(MenuType.WARNING_OR_ERROR); verifyMenuIsDisplayed(MenuType.WARNING_OR_ERROR);
pressCheckKey(); pressCheckKey();
// dismiss alert // dismiss alert
verifyMenuIsDisplayed(MenuType.WARNING_OR_ERROR); // if the user has confirmed the alert we have dismissed it with the button press
pressCheckKey(); // above already, so only do that if an alert is still displayed
waitForScreenUpdate();
if (getCurrentMenu().getType() == MenuType.WARNING_OR_ERROR) {
pressCheckKey();
}
return true; return true;
} }
SystemClock.sleep(10); SystemClock.sleep(10);