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;
}
}
runCommand("Initializing", () -> ruffyScripter.readHistory(new PumpHistoryRequest()));
runCommand("Initializing", 3, () -> ruffyScripter.readHistory(new PumpHistoryRequest()));
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
// checkForTbrMismatch();
@ -245,7 +245,7 @@ public class ComboPlugin implements PluginBase, PumpInterface {
* full pump history and update AAPS' DB.
*/
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(
new PumpHistoryRequest()
.bolusHistory(PumpHistoryRequest.LAST)
@ -410,6 +410,14 @@ public class ComboPlugin implements PluginBase, PumpInterface {
@NonNull
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
// 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
@ -495,11 +503,13 @@ public class ComboPlugin implements PluginBase, PumpInterface {
}
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) {
pump.tbrSetTime = System.currentTimeMillis();
TemporaryBasal tempStart = new TemporaryBasal(pump.tbrSetTime);
PumpState state = commandResult.state;
if (state.tbrActive && state.tbrPercent == percent
&& (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 ...
// 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?
@ -542,7 +552,7 @@ public class ComboPlugin implements PluginBase, PumpInterface {
if (activeTemp == null || userRequested) {
/* v1 compatibility to sync DB to pump if they diverged (activeTemp == null) */
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) {
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%.
final int percentage = (activeTemp.percentRate > 100) ? 110 : 90;
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) {
tempBasal = new TemporaryBasal(System.currentTimeMillis());
@ -634,9 +644,12 @@ public class ComboPlugin implements PluginBase, PumpInterface {
pump.reservoirLevel = commandResult.reservoirLevel;
}
if (commandResult.lastBolus != null) {
pump.lastBolus = commandResult.lastBolus;
}
if (!commandResult.success && retries > 0) {
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.bolusHistory.isEmpty()) {
@ -734,7 +747,7 @@ public class ComboPlugin implements PluginBase, PumpInterface {
}
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

View file

@ -273,15 +273,22 @@ public class RuffyScripter implements RuffyCommands {
// (to fail before the next loop iteration issues the next command)
long dynamicTimeout = calculateCmdInactivityTimeout();
long overallTimeout = calculateOverallCmdTimeout();
int maxReconnectAttempts = 3;
while (cmdThread.isAlive()) {
log.trace("Waiting for running command to complete");
SystemClock.sleep(500);
if (!ruffyService.isConnected()) {
if (maxReconnectAttempts > 0) {
maxReconnectAttempts--;
cmdThread.interrupt();
reconnect();
// on connection lose try to reconnect, confirm warning alerts caused by
// the disconnected and then return the command as failed (the caller
// can retry if needed).
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
* confirm the alarm and.
* confirm the alert.
*
* @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
// requires turning CommandResult from a return type into a command field
// 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;
}
private boolean recoverFromConnectionLoss() {
log.debug("Connection was lost, trying to reconnect");
ensureConnected();
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
returnToRootMenu();
return getCurrentMenu().getType() == MenuType.MAIN_MENU;
// A bolus cancelled is raised BEFORE a bolus is started. If a disconnect occurs after a
// 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
// 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() {
try {
if (ruffyService.isConnected()) {
@ -479,12 +457,15 @@ public class RuffyScripter implements RuffyCommands {
verifyMenuIsDisplayed(MenuType.WARNING_OR_ERROR);
Integer warningCode = (Integer) getCurrentMenu().getAttribute(MenuAttribute.WARNING);
Integer errorCode = (Integer) getCurrentMenu().getAttribute(MenuAttribute.ERROR);
while (warningCode == null && errorCode == null) {
int retries = 3;
while (warningCode == null && errorCode == null && retries > 0) {
waitForScreenUpdate();
warningCode = (Integer) getCurrentMenu().getAttribute(MenuAttribute.WARNING);
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
@ -772,23 +753,23 @@ public class RuffyScripter implements RuffyCommands {
/**
* 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;
while (System.currentTimeMillis() < timeout) {
if (getCurrentMenu().getType() == MenuType.WARNING_OR_ERROR) {
WarningOrErrorCode warningOrErrorCode = readWarningOrErrorCode();
if (warningOrErrorCode.errorCode != 0) {
if (warningOrErrorCode.errorCode != null) {
// TODO proper way to display such things in the UI;
throw new CommandException("Pump is in error state");
}
int displayedWarningCode = warningOrErrorCode.warningCode;
Integer displayedWarningCode = warningOrErrorCode.warningCode;
String errorMsg = null;
try {
errorMsg = (String) getCurrentMenu().getAttribute(MenuAttribute.MESSAGE);
} catch (Exception e) {
// 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: "
+ displayedWarningCode + "(" + errorMsg + "). Please check the pump.");
}
@ -797,8 +778,12 @@ public class RuffyScripter implements RuffyCommands {
verifyMenuIsDisplayed(MenuType.WARNING_OR_ERROR);
pressCheckKey();
// dismiss alert
verifyMenuIsDisplayed(MenuType.WARNING_OR_ERROR);
pressCheckKey();
// if the user has confirmed the alert we have dismissed it with the button press
// above already, so only do that if an alert is still displayed
waitForScreenUpdate();
if (getCurrentMenu().getType() == MenuType.WARNING_OR_ERROR) {
pressCheckKey();
}
return true;
}
SystemClock.sleep(10);