Merge branch 'experimental' into combo-v2

* experimental: (28 commits)
  Refactor, clean out stuff, add RuffyCommands interface.
  ExperimentalBolusCommand: update progress when starting to programm the pump.
  Remove GetReservoirLevelCommand, will be a method in the future.
  Add TODO.
  Make access to RuffyScripter.currentMenu safer.
  Update pref descriptions.
  Disable incomplete parts in ExperimentalBolusCommand.
  Declare RuffyScripter.currentMenu nullable.
  Wait for currentMenu to be != null
  Option to ignore TBR failures: also ignore errors when reading pump state.
  Refactorings and notes.
  A little less broken CancellableBolusCommand.
  Update pref descriptions
  Add pref to disable all pump comm alerts.
  Move RuffyScripter to PumpCombo package.
  Add option to skip small TBR changes, add summaries to prefs.
  Refactor.
  Move confirmAlert method to scripter.
  SetTbrCommand: extract confirrmAlert method.
  Experimental options: for specifc prefs, always check experimental options are turned on.
  ...
This commit is contained in:
Johannes Mockenhaupt 2017-10-17 11:39:01 +02:00
commit f0db2f0822
No known key found for this signature in database
GPG key ID: 9E1EA6AF7BBBB0D1
29 changed files with 520 additions and 416 deletions

View file

@ -1,17 +0,0 @@
package de.jotomo.ruffyscripter.commands;
import de.jotomo.ruffyscripter.RuffyScripter;
public abstract class BaseCommand implements Command {
// RS will inject itself here
protected RuffyScripter scripter;
@Override public void setScripter(RuffyScripter scripter) { this.scripter = scripter; }
// TODO upcoming
protected final boolean canBeCancelled = true;
protected volatile boolean cancelRequested = false;
public void requestCancellation() {
cancelRequested = true;
}
public boolean isCancellable() { return canBeCancelled; }
}

View file

@ -1,169 +0,0 @@
package de.jotomo.ruffyscripter.commands;
import android.os.SystemClock;
import org.monkey.d.ruffy.ruffy.driver.display.MenuAttribute;
import org.monkey.d.ruffy.ruffy.driver.display.MenuType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
public class BolusCommand extends BaseCommand {
private static final Logger log = LoggerFactory.getLogger(BolusCommand.class);
protected final double bolus;
public BolusCommand(double bolus) {
this.bolus = bolus;
}
@Override
public List<String> validateArguments() {
List<String> violations = new ArrayList<>();
if (bolus <= 0 || bolus > 25) {
violations.add("Requested bolus " + bolus + " out of limits (0-25)");
}
return violations;
}
@Override
public CommandResult execute() {
try {
enterBolusMenu();
inputBolusAmount();
verifyDisplayedBolusAmount();
// confirm bolus
scripter.verifyMenuIsDisplayed(MenuType.BOLUS_ENTER);
scripter.pressCheckKey();
// the pump displays the entered bolus and waits a bit to let user check and cancel
// and then returns to the main menu
scripter.waitForMenuToBeLeft(MenuType.BOLUS_ENTER);
scripter.verifyMenuIsDisplayed(MenuType.MAIN_MENU,
"Pump did not return to MAIN_MEU from BOLUS_ENTER to deliver bolus. "
+ "Check pump manually, the bolus might not have been delivered.");
// wait for bolus delivery to complete; the remaining units to deliver are counted
// down and are displayed on the main menu.
Double bolusRemaining = (Double) scripter.getCurrentMenu().getAttribute(MenuAttribute.BOLUS_REMAINING);
while (bolusRemaining != null) {
log.debug("Delivering bolus, remaining: " + bolusRemaining);
SystemClock.sleep(200);
if (scripter.getCurrentMenu().getType() == MenuType.WARNING_OR_ERROR) {
throw new CommandException().success(false).enacted(true)
.message("Warning/error raised after bolus delivery started. " +
"The treatment has been recorded, please check it against the " +
"pumps records and delete it if it hasn't finished successfully.");
}
bolusRemaining = (Double) scripter.getCurrentMenu().getAttribute(MenuAttribute.BOLUS_REMAINING);
}
// TODO what if we hit 'cartridge low' alert here? is it immediately displayed or after the bolus?
// TODO how are error states reported back to the caller that occur outside of calls in genal? Low battery, low cartridge?
// make sure no alert (occlusion, cartridge empty) has occurred.
scripter.verifyMenuIsDisplayed(MenuType.MAIN_MENU,
"Bolus delivery did not complete as expected. "
+ "Check pump manually, the bolus might not have been delivered.");
// read last bolus record; those menus display static data and therefore
// only a single menu update is sent
scripter.navigateToMenu(MenuType.MY_DATA_MENU);
scripter.verifyMenuIsDisplayed(MenuType.MY_DATA_MENU);
scripter.pressCheckKey();
if (scripter.getCurrentMenu().getType() != MenuType.BOLUS_DATA) {
scripter.waitForMenuUpdate();
}
if (!scripter.getCurrentMenu().attributes().contains(MenuAttribute.BOLUS)) {
throw new CommandException().success(false).enacted(true)
.message("Bolus was delivered, but unable to confirm it with history record");
}
double lastBolusInHistory = (double) scripter.getCurrentMenu().getAttribute(MenuAttribute.BOLUS);
if (Math.abs(bolus - lastBolusInHistory) > 0.05) {
throw new CommandException().success(false).enacted(true)
.message("Last bolus shows " + lastBolusInHistory
+ " U delievered, but " + bolus + " U were requested");
}
log.debug("Bolus record in history confirms delivered bolus");
// leave menu to go back to main menu
scripter.pressCheckKey();
scripter.waitForMenuToBeLeft(MenuType.BOLUS_DATA);
scripter.verifyMenuIsDisplayed(MenuType.MAIN_MENU,
"Bolus was correctly delivered and checked against history, but we "
+ "did not return the main menu successfully.");
return new CommandResult().success(true).enacted(true)
.message(String.format(Locale.US, "Delivered %02.1f U", bolus));
} catch (CommandException e) {
return e.toCommandResult();
}
}
protected void enterBolusMenu() {
scripter.verifyMenuIsDisplayed(MenuType.MAIN_MENU);
scripter.navigateToMenu(MenuType.BOLUS_MENU);
scripter.verifyMenuIsDisplayed(MenuType.BOLUS_MENU);
scripter.pressCheckKey();
scripter.waitForMenuUpdate();
scripter.verifyMenuIsDisplayed(MenuType.BOLUS_ENTER);
}
protected void inputBolusAmount() {
scripter.verifyMenuIsDisplayed(MenuType.BOLUS_ENTER);
// press 'up' once for each 0.1 U increment
long steps = Math.round(bolus * 10);
for (int i = 0; i < steps; i++) {
scripter.verifyMenuIsDisplayed(MenuType.BOLUS_ENTER);
scripter.pressUpKey();
SystemClock.sleep(100);
}
// Give the pump time to finish any scrolling that might still be going on, can take
// up to 1100ms. Plus some extra time to be sure
SystemClock.sleep(2000);
}
protected void verifyDisplayedBolusAmount() {
scripter.verifyMenuIsDisplayed(MenuType.BOLUS_ENTER);
// wait up to 5s for any scrolling to finish
double displayedBolus = scripter.readBlinkingValue(Double.class, MenuAttribute.BOLUS);
long timeout = System.currentTimeMillis() + 10 * 1000;
while (timeout > System.currentTimeMillis() && bolus - displayedBolus > 0.05) {
log.debug("Waiting for pump to process scrolling input for amount, current: " + displayedBolus + ", desired: " + bolus);
SystemClock.sleep(50);
displayedBolus = scripter.readBlinkingValue(Double.class, MenuAttribute.BOLUS);
}
log.debug("Final bolus: " + displayedBolus);
if (Math.abs(displayedBolus - bolus) > 0.05) {
throw new CommandException().message("Failed to set correct bolus. Expected: " + bolus + ", actual: " + displayedBolus);
}
// check again to ensure the displayed value hasn't change due to due scrolling taking extremely long
SystemClock.sleep(1000);
scripter.verifyMenuIsDisplayed(MenuType.BOLUS_ENTER);
double refreshedDisplayedBolus = scripter.readBlinkingValue(Double.class, MenuAttribute.BOLUS);
if (Math.abs(displayedBolus - refreshedDisplayedBolus) > 0.05) {
throw new CommandException().message("Failed to set bolus: bolus changed after input stopped from "
+ displayedBolus + " -> " + refreshedDisplayedBolus);
}
}
@Override
public String toString() {
return "BolusCommand{" +
"bolus=" + bolus +
'}';
}
}

View file

@ -1,24 +0,0 @@
package de.jotomo.ruffyscripter.commands;
import java.util.List;
public class GetReservoirLevelCommand extends BaseCommand {
@Override
public CommandResult execute() {
// TODO stub
// watch out, level goes into PumpState, which is usually set by RuffyScripter
// after a command ran, unless a command has already set it ... I don't like
// that, it's too implicit ...
// also, maybe ditch this command and add a parameter to GetPumpStateCommand to also
// read the reservoir level if possible (pump must be in a state to accept commands
// (possible on main, stop ...)
return null;
}
@Override
public List<String> validateArguments() {
// TODO stub
return null;
}
}

View file

@ -47,13 +47,4 @@ public class Config {
public static final boolean logDanaBTComm = true; public static final boolean logDanaBTComm = true;
public static final boolean logDanaMessageDetail = true; public static final boolean logDanaMessageDetail = true;
public static final boolean logDanaSerialEngine = true; public static final boolean logDanaSerialEngine = true;
// Combo specific
/** enable the UNFINISHED and currently BROKEN bolus cammand that reports progress and can be cancelled */
public static final boolean comboExperimentalBolus = false;
/** Very quick hack to split up bolus into 2 U parts, spaced roughly 45s apart.
* If there's an error during bolusing, no record is created in AAPS.
* Don't combine with experimental bolus! */
public static final boolean comboExperimentalSplitBoluses = false && !comboExperimentalBolus;
} }

View file

@ -17,9 +17,9 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import de.jotomo.ruffyscripter.PumpState; import info.nightscout.androidaps.plugins.PumpCombo.scripter.PumpState;
import de.jotomo.ruffyscripter.commands.Command; import info.nightscout.androidaps.plugins.PumpCombo.scripter.commands.Command;
import de.jotomo.ruffyscripter.commands.CommandResult; import info.nightscout.androidaps.plugins.PumpCombo.scripter.commands.CommandResult;
import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.MainApp;
import info.nightscout.androidaps.R; import info.nightscout.androidaps.R;
import info.nightscout.androidaps.plugins.PumpCombo.events.EventComboPumpUpdateGUI; import info.nightscout.androidaps.plugins.PumpCombo.events.EventComboPumpUpdateGUI;

View file

@ -23,17 +23,7 @@ import org.slf4j.LoggerFactory;
import java.util.Date; import java.util.Date;
import de.jotomo.ruffyscripter.PumpState;
import de.jotomo.ruffyscripter.RuffyScripter;
import de.jotomo.ruffyscripter.commands.BolusCommand;
import de.jotomo.ruffyscripter.commands.CancelTbrCommand;
import de.jotomo.ruffyscripter.commands.CancellableBolusCommand;
import de.jotomo.ruffyscripter.commands.Command;
import de.jotomo.ruffyscripter.commands.CommandResult;
import de.jotomo.ruffyscripter.commands.GetPumpStateCommand;
import de.jotomo.ruffyscripter.commands.SetTbrCommand;
import info.nightscout.androidaps.BuildConfig; import info.nightscout.androidaps.BuildConfig;
import info.nightscout.androidaps.Config;
import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.MainApp;
import info.nightscout.androidaps.R; import info.nightscout.androidaps.R;
import info.nightscout.androidaps.data.DetailedBolusInfo; import info.nightscout.androidaps.data.DetailedBolusInfo;
@ -48,6 +38,11 @@ import info.nightscout.androidaps.interfaces.PumpInterface;
import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin;
import info.nightscout.androidaps.plugins.Overview.events.EventOverviewBolusProgress; import info.nightscout.androidaps.plugins.Overview.events.EventOverviewBolusProgress;
import info.nightscout.androidaps.plugins.PumpCombo.events.EventComboPumpUpdateGUI; import info.nightscout.androidaps.plugins.PumpCombo.events.EventComboPumpUpdateGUI;
import info.nightscout.androidaps.plugins.PumpCombo.scripter.PumpState;
import info.nightscout.androidaps.plugins.PumpCombo.scripter.RuffyScripter;
import info.nightscout.androidaps.plugins.PumpCombo.scripter.commands.BolusCommand;
import info.nightscout.androidaps.plugins.PumpCombo.scripter.commands.Command;
import info.nightscout.androidaps.plugins.PumpCombo.scripter.commands.CommandResult;
import info.nightscout.utils.DateUtil; import info.nightscout.utils.DateUtil;
import info.nightscout.utils.SP; import info.nightscout.utils.SP;
@ -55,7 +50,6 @@ import info.nightscout.utils.SP;
* Created by mike on 05.08.2016. * Created by mike on 05.08.2016.
*/ */
public class ComboPlugin implements PluginBase, PumpInterface { public class ComboPlugin implements PluginBase, PumpInterface {
public static final String COMBO_MAX_TEMP_PERCENT_SP = "combo_maxTempPercent";
private static Logger log = LoggerFactory.getLogger(ComboPlugin.class); private static Logger log = LoggerFactory.getLogger(ComboPlugin.class);
private boolean fragmentEnabled = false; private boolean fragmentEnabled = false;
@ -68,6 +62,8 @@ public class ComboPlugin implements PluginBase, PumpInterface {
private ComboPump pump = new ComboPump(); private ComboPump pump = new ComboPump();
private boolean ignoreLastSetTbrOrReadStateFailure = false;
@Nullable @Nullable
private volatile BolusCommand runningBolusCommand; private volatile BolusCommand runningBolusCommand;
@ -99,7 +95,7 @@ public class ComboPlugin implements PluginBase, PumpInterface {
pumpDescription.isTempBasalCapable = true; pumpDescription.isTempBasalCapable = true;
pumpDescription.tempBasalStyle = PumpDescription.PERCENT; pumpDescription.tempBasalStyle = PumpDescription.PERCENT;
pumpDescription.maxTempPercent = SP.getInt(COMBO_MAX_TEMP_PERCENT_SP, 500); pumpDescription.maxTempPercent = 500;
pumpDescription.tempPercentStep = 10; pumpDescription.tempPercentStep = 10;
pumpDescription.tempDurationStep = 15; pumpDescription.tempDurationStep = 15;
@ -136,10 +132,12 @@ public class ComboPlugin implements PluginBase, PumpInterface {
while (true) { while (true) {
Command localLastCmd = pump.lastCmd; Command localLastCmd = pump.lastCmd;
CommandResult localLastCmdResult = pump.lastCmdResult; CommandResult localLastCmdResult = pump.lastCmdResult;
if (localLastCmdResult != null && !localLastCmdResult.success) { if (!SP.getBoolean(R.string.combo_disable_alerts, false) &&
localLastCmdResult != null && !localLastCmdResult.success) {
long now = System.currentTimeMillis(); long now = System.currentTimeMillis();
long fiveMinutesSinceLastAlarm = lastAlarmTime + (5 * 60 * 1000) + (15 * 1000); long fiveMinutesSinceLastAlarm = lastAlarmTime + (5 * 60 * 1000) + (15 * 1000);
if (now > fiveMinutesSinceLastAlarm) { boolean loopEnabled = ConfigBuilderPlugin.getActiveLoop() != null;
if (now > fiveMinutesSinceLastAlarm && loopEnabled) {
log.error("Command failed: " + localLastCmd); log.error("Command failed: " + localLastCmd);
log.error("Command result: " + localLastCmdResult); log.error("Command result: " + localLastCmdResult);
PumpState localPumpState = pump.state; PumpState localPumpState = pump.state;
@ -345,7 +343,8 @@ public class ComboPlugin implements PluginBase, PumpInterface {
if (notAUserRequest && wasRunAtLeastOnce && ranWithinTheLastMinute) { if (notAUserRequest && wasRunAtLeastOnce && ranWithinTheLastMinute) {
log.debug("Not fetching state from pump, since we did already within the last 60 seconds"); log.debug("Not fetching state from pump, since we did already within the last 60 seconds");
} else { } else {
runCommand(new GetPumpStateCommand()); // TODO
// runCommand(new GetPumpStateCommand());
} }
} }
@ -359,27 +358,30 @@ public class ComboPlugin implements PluginBase, PumpInterface {
return basal; return basal;
} }
private static CancellableBolusCommand.ProgressReportCallback bolusProgressReportCallback = private static BolusCommand.ProgressReportCallback bolusProgressReportCallback =
new CancellableBolusCommand.ProgressReportCallback() { new BolusCommand.ProgressReportCallback() {
@Override @Override
public void report(CancellableBolusCommand.ProgressReportCallback.State state, int percent, double delivered) { public void report(BolusCommand.ProgressReportCallback.State state, int percent, double delivered) {
EventOverviewBolusProgress enent = EventOverviewBolusProgress.getInstance(); EventOverviewBolusProgress event = EventOverviewBolusProgress.getInstance();
switch (state) { switch (state) {
case PROGRAMMING:
event.status = MainApp.sResources.getString(R.string.bolusprogramming);
break;
case DELIVERING: case DELIVERING:
enent.status = String.format(MainApp.sResources.getString(R.string.bolusdelivering), delivered); event.status = String.format(MainApp.sResources.getString(R.string.bolusdelivering), delivered);
break; break;
case DELIVERED: case DELIVERED:
enent.status = String.format(MainApp.sResources.getString(R.string.bolusdelivered), delivered); event.status = String.format(MainApp.sResources.getString(R.string.bolusdelivered), delivered);
break; break;
case STOPPING: case STOPPING:
enent.status = MainApp.sResources.getString(R.string.bolusstopping); event.status = MainApp.sResources.getString(R.string.bolusstopping);
break; break;
case STOPPED: case STOPPED:
enent.status = MainApp.sResources.getString(R.string.bolusstopped); event.status = MainApp.sResources.getString(R.string.bolusstopped);
break; break;
} }
enent.percent = percent; event.percent = percent;
MainApp.bus().post(enent); MainApp.bus().post(event);
} }
}; };
@ -389,7 +391,8 @@ public class ComboPlugin implements PluginBase, PumpInterface {
if (detailedBolusInfo.insulin > 0 || detailedBolusInfo.carbs > 0) { if (detailedBolusInfo.insulin > 0 || detailedBolusInfo.carbs > 0) {
if (detailedBolusInfo.insulin > 0) { if (detailedBolusInfo.insulin > 0) {
// bolus needed, ask pump to deliver it // bolus needed, ask pump to deliver it
if (!Config.comboExperimentalSplitBoluses) { if (!(SP.getBoolean(R.string.key_combo_enable_experimental_features, false)
&& SP.getBoolean(R.string.key_combo_enable_experimental_split_bolus, false))) {
return deliverBolus(detailedBolusInfo); return deliverBolus(detailedBolusInfo);
} else { } else {
// split up bolus into 2 U parts // split up bolus into 2 U parts
@ -456,9 +459,7 @@ public class ComboPlugin implements PluginBase, PumpInterface {
@NonNull @NonNull
private PumpEnactResult deliverBolus(DetailedBolusInfo detailedBolusInfo) { private PumpEnactResult deliverBolus(DetailedBolusInfo detailedBolusInfo) {
runningBolusCommand = Config.comboExperimentalBolus runningBolusCommand = new BolusCommand(detailedBolusInfo.insulin, bolusProgressReportCallback);
? new CancellableBolusCommand(detailedBolusInfo.insulin, bolusProgressReportCallback)
: new BolusCommand(detailedBolusInfo.insulin);
CommandResult bolusCmdResult = runCommand(runningBolusCommand); CommandResult bolusCmdResult = runCommand(runningBolusCommand);
PumpEnactResult pumpEnactResult = new PumpEnactResult(); PumpEnactResult pumpEnactResult = new PumpEnactResult();
pumpEnactResult.success = bolusCmdResult.success; pumpEnactResult.success = bolusCmdResult.success;
@ -535,6 +536,20 @@ public class ComboPlugin implements PluginBase, PumpInterface {
if (unroundedPercentage != roundedPercentage) { if (unroundedPercentage != roundedPercentage) {
log.debug("Rounded requested rate " + unroundedPercentage + "% -> " + roundedPercentage + "%"); log.debug("Rounded requested rate " + unroundedPercentage + "% -> " + roundedPercentage + "%");
} }
TemporaryBasal activeTemp = MainApp.getConfigBuilder().getTempBasalFromHistory(System.currentTimeMillis());
if (!force && activeTemp != null) {
int minRequiredDelta = SP.getInt(R.string.key_combo_experimental_skip_tbr_changes_below_delta, 0);
boolean deltaBelowThreshold = Math.abs(activeTemp.percentRate - roundedPercentage) < minRequiredDelta;
if (deltaBelowThreshold) {
log.debug("Skipping setting APS-requested TBR change, since the requested change from "
+ activeTemp.percentRate + " -> " + roundedPercentage + " is below the delta threshold of " + minRequiredDelta);
PumpEnactResult pumpEnactResult = new PumpEnactResult();
pumpEnactResult.success = true;
pumpEnactResult.enacted = false;
return pumpEnactResult;
}
}
return setTempBasalPercent(roundedPercentage, durationInMinutes); return setTempBasalPercent(roundedPercentage, durationInMinutes);
} }
@ -556,7 +571,7 @@ public class ComboPlugin implements PluginBase, PumpInterface {
adjustedPercent = rounded.intValue(); adjustedPercent = rounded.intValue();
} }
CommandResult commandResult = runCommand(new SetTbrCommand(adjustedPercent, durationInMinutes)); CommandResult commandResult = ruffyScripter.setTbr(adjustedPercent, durationInMinutes);
if (commandResult.enacted) { if (commandResult.enacted) {
TemporaryBasal tempStart = new TemporaryBasal(commandResult.completionTime); TemporaryBasal tempStart = new TemporaryBasal(commandResult.completionTime);
@ -602,7 +617,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(new CancelTbrCommand()); commandResult = ruffyScripter.cancelTbr();
if (commandResult.enacted) { if (commandResult.enacted) {
tempBasal = new TemporaryBasal(commandResult.completionTime); tempBasal = new TemporaryBasal(commandResult.completionTime);
tempBasal.durationInMinutes = 0; tempBasal.durationInMinutes = 0;
@ -622,14 +637,14 @@ public class ComboPlugin implements PluginBase, PumpInterface {
} else { } else {
// Set a fake neutral temp to avoid TBR cancel alert. Decide 90% vs 110% based on // Set a fake neutral temp to avoid TBR cancel alert. Decide 90% vs 110% based on
// on whether the TBR we're cancelling is above or below 100%. // on whether the TBR we're cancelling is above or below 100%.
long percentage = (activeTemp.percentRate > 100) ? 110 : 90; 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(new SetTbrCommand(percentage, 15)); commandResult = ruffyScripter.setTbr(percentage, 15);
if (commandResult.enacted) { if (commandResult.enacted) {
tempBasal = new TemporaryBasal(commandResult.completionTime); tempBasal = new TemporaryBasal(commandResult.completionTime);
tempBasal.durationInMinutes = 15; tempBasal.durationInMinutes = 15;
tempBasal.source = Source.USER; tempBasal.source = Source.USER;
tempBasal.percentRate = (int) percentage; tempBasal.percentRate = percentage;
tempBasal.isAbsolute = false; tempBasal.isAbsolute = false;
} }
} }
@ -656,7 +671,7 @@ public class ComboPlugin implements PluginBase, PumpInterface {
// TODO v2 add battery, reservoir info when we start reading that and clean up the code // TODO v2 add battery, reservoir info when we start reading that and clean up the code
@Override @Override
public JSONObject getJSONStatus() { public JSONObject getJSONStatus() {
if (true) { //pump.lastCmdTime.getTime() + 5 * 60 * 1000L < System.currentTimeMillis()) { if (pump.lastCmdTime.getTime() + 5 * 60 * 1000L < System.currentTimeMillis()) {
return null; return null;
} }

View file

@ -5,9 +5,9 @@ import android.support.annotation.Nullable;
import java.util.Date; import java.util.Date;
import de.jotomo.ruffyscripter.PumpState; import info.nightscout.androidaps.plugins.PumpCombo.scripter.PumpState;
import de.jotomo.ruffyscripter.commands.Command; import info.nightscout.androidaps.plugins.PumpCombo.scripter.commands.Command;
import de.jotomo.ruffyscripter.commands.CommandResult; import info.nightscout.androidaps.plugins.PumpCombo.scripter.commands.CommandResult;
class ComboPump { class ComboPump {
@NonNull @NonNull

View file

@ -0,0 +1,4 @@
package info.nightscout.androidaps.plugins.PumpCombo.scripter;
public class BasalProfile {
}

View file

@ -1,4 +1,4 @@
package de.jotomo.ruffyscripter; package info.nightscout.androidaps.plugins.PumpCombo.scripter;
/** /**
* The history data read from "My data" * The history data read from "My data"

View file

@ -0,0 +1,4 @@
package info.nightscout.androidaps.plugins.PumpCombo.scripter;
public class PumpHistory {
}

View file

@ -1,4 +1,4 @@
package de.jotomo.ruffyscripter; package info.nightscout.androidaps.plugins.PumpCombo.scripter;
import java.util.Date; import java.util.Date;

View file

@ -0,0 +1,27 @@
package info.nightscout.androidaps.plugins.PumpCombo.scripter;
import info.nightscout.androidaps.plugins.PumpCombo.scripter.commands.CommandResult;
import info.nightscout.androidaps.plugins.PumpCombo.scripter.commands.BolusCommand;
/**
* Main entry point for clients, implemented by RuffyScripter.
*/
public interface RuffyCommands {
CommandResult deliverBolus(double amount, BolusCommand.ProgressReportCallback progressReportCallback);
void cancelBolus();
CommandResult setTbr(int percent, int duraton);
CommandResult cancelTbr();
CommandResult readReservoirLevel();
// PumpHistory.fields.*: null=don't care. empty history=we know nothing yet. filled history=this is what we know so far
CommandResult readHistory(PumpHistory knownHistory);
CommandResult readBasalProfile();
CommandResult setBasalProfile(BasalProfile basalProfile);
}

View file

@ -1,7 +1,8 @@
package de.jotomo.ruffyscripter; package info.nightscout.androidaps.plugins.PumpCombo.scripter;
import android.os.RemoteException; import android.os.RemoteException;
import android.os.SystemClock; import android.os.SystemClock;
import android.support.annotation.Nullable;
import com.google.common.base.Joiner; import com.google.common.base.Joiner;
@ -16,10 +17,17 @@ import org.slf4j.LoggerFactory;
import java.util.List; import java.util.List;
import de.jotomo.ruffyscripter.commands.Command; import info.nightscout.androidaps.plugins.PumpCombo.scripter.commands.CancelTbrCommand;
import de.jotomo.ruffyscripter.commands.CommandException; import info.nightscout.androidaps.plugins.PumpCombo.scripter.commands.Command;
import de.jotomo.ruffyscripter.commands.CommandResult; import info.nightscout.androidaps.plugins.PumpCombo.scripter.commands.CommandException;
import de.jotomo.ruffyscripter.commands.GetPumpStateCommand; import info.nightscout.androidaps.plugins.PumpCombo.scripter.commands.CommandResult;
import info.nightscout.androidaps.plugins.PumpCombo.scripter.commands.BolusCommand;
import info.nightscout.androidaps.plugins.PumpCombo.scripter.commands.GetPumpStateCommand;
import info.nightscout.androidaps.plugins.PumpCombo.scripter.commands.ReadBasalProfile;
import info.nightscout.androidaps.plugins.PumpCombo.scripter.commands.ReadHistoryCommand;
import info.nightscout.androidaps.plugins.PumpCombo.scripter.commands.ReadReserverLevelCommand;
import info.nightscout.androidaps.plugins.PumpCombo.scripter.commands.SetBasalProfile;
import info.nightscout.androidaps.plugins.PumpCombo.scripter.commands.SetTbrCommand;
// TODO regularly read "My data" history (boluses, TBR) to double check all commands ran successfully. // TODO regularly read "My data" history (boluses, TBR) to double check all commands ran successfully.
// Automatically compare against AAPS db, or log all requests in the PumpInterface (maybe Milos // Automatically compare against AAPS db, or log all requests in the PumpInterface (maybe Milos
@ -30,12 +38,13 @@ import de.jotomo.ruffyscripter.commands.GetPumpStateCommand;
* class and inject that into executing commands, so that commands operately solely on * class and inject that into executing commands, so that commands operately solely on
* operations and are cleanly separated from the thread management, connection management etc * operations and are cleanly separated from the thread management, connection management etc
*/ */
public class RuffyScripter { public class RuffyScripter implements RuffyCommands {
private static final Logger log = LoggerFactory.getLogger(RuffyScripter.class); private static final Logger log = LoggerFactory.getLogger(RuffyScripter.class);
private IRuffyService ruffyService; private IRuffyService ruffyService;
private String unrecoverableError = null; private String unrecoverableError = null;
@Nullable
private volatile Menu currentMenu; private volatile Menu currentMenu;
private volatile long menuLastUpdated = 0; private volatile long menuLastUpdated = 0;
@ -193,6 +202,23 @@ public class RuffyScripter {
this.ruffyService = null; this.ruffyService = null;
} }
public void returnToMainMenu() {
// returning to main menu using the 'back' key does not cause a vibration
while (getCurrentMenu().getType() != MenuType.MAIN_MENU) {
if (getCurrentMenu().getType() == MenuType.WARNING_OR_ERROR) {
String errorMsg = (String) getCurrentMenu().getAttribute(MenuAttribute.MESSAGE);
confirmAlert(errorMsg, 1000);
// TODO this isn't gonna work out ... this method can't know if something was enacted ...
// gotta keep that state in the command instance
throw new CommandException().success(false).enacted(false)
.message("Warning/error " + errorMsg + " raised while returning to main menu");
}
log.debug("Going back to main menu, currently at " + getCurrentMenu().getType());
pressBackKey();
waitForMenuUpdate();
}
}
private static class Returnable { private static class Returnable {
CommandResult cmdResult; CommandResult cmdResult;
} }
@ -464,6 +490,17 @@ public class RuffyScripter {
// === pump ops === // === pump ops ===
public Menu getCurrentMenu() { public Menu getCurrentMenu() {
long timeout = System.currentTimeMillis() + 5 * 1000;
// TODO this is probably due to a disconnect and rtDisconnect having nulled currentMenu.
// This here might just work, but needs a more controlled approach when implementing
// something to deal with connection loses
while (currentMenu == null) {
if (System.currentTimeMillis() > timeout) {
throw new CommandException().message("Unable to read current menu");
}
log.debug("currentMenu == null, waiting");
waitForMenuUpdate();
}
return currentMenu; return currentMenu;
} }
@ -534,52 +571,44 @@ public class RuffyScripter {
return true; return true;
} }
public boolean goToMainTypeScreen(MenuType screen, long timeout) {
long start = System.currentTimeMillis();
while ((currentMenu == null || currentMenu.getType() != screen) && start + timeout > System.currentTimeMillis()) {
if (currentMenu != null && currentMenu.getType() == MenuType.WARNING_OR_ERROR) {
throw new CommandException().message("Warning/errors raised by pump, please check pump");
// since warnings and errors can occur at any time, they should be dealt with in
// a more general way, see the handleMenuUpdate callback above
//FIXME bad thing to do :D
// yup, commenting this out since I don't want an occlusionn alert to hidden by this :-)
//pressCheckKey();
} else if (currentMenu != null && !currentMenu.getType().isMaintype()) {
pressBackKey();
} else
pressMenuKey();
waitForScreenUpdate(250);
}
return currentMenu != null && currentMenu.getType() == screen;
}
public boolean enterMenu(MenuType startType, MenuType targetType, byte key, long timeout) {
if (currentMenu.getType() == targetType)
return true;
if (currentMenu == null || currentMenu.getType() != startType)
return false;
long start = System.currentTimeMillis();
pressKey(key, 2000);
while ((currentMenu == null || currentMenu.getType() != targetType) && start + timeout > System.currentTimeMillis()) {
waitForScreenUpdate(100);
}
return currentMenu != null && currentMenu.getType() == targetType;
}
public void step(int steps, byte key, long timeout) {
for (int i = 0; i < Math.abs(steps); i++)
pressKey(key, timeout);
}
// TODO v2, rework these two methods: waitForMenuUpdate shoud only be used by commands // TODO v2, rework these two methods: waitForMenuUpdate shoud only be used by commands
// then anything longer than a few seconds is an error; // then anything longer than a few seconds is an error;
// only ensureConnected() uses the method with the timeout parameter; inline that code, // only ensureConnected() uses the method with the timeout parameter; inline that code,
// so we can use a custom timeout and give a better error message in case of failure // so we can use a custom timeout and give a better error message in case of failure
/** // TODO confirmAlarms? and report back which were cancelled?
* Wait until the menu update is in
*/ /** Confirms and dismisses the given alert if it's raised before the timeout */
// TODO donn't use this in ensureConnected public boolean confirmAlert(String alertMessage, int maxWaitMs) {
long inFiveSeconds = System.currentTimeMillis() + maxWaitMs;
boolean alertProcessed = false;
while (System.currentTimeMillis() < inFiveSeconds && !alertProcessed) {
if (getCurrentMenu().getType() == MenuType.WARNING_OR_ERROR) {
// Note that the message is permanently displayed, while the error code is blinking.
// A wait till the error code can be read results in the code hanging, despite
// menu updates coming in, so just check the message.
// TODO quick try if the can't make reading the error code work ..
String errorMsg = (String) getCurrentMenu().getAttribute(MenuAttribute.MESSAGE);
if (!errorMsg.equals(alertMessage)) {
throw new CommandException().success(false).enacted(false)
.message("An alert other than the expected " + alertMessage + " was raised by the pump: "
+ errorMsg + ". Please check the pump.");
}
// confirm alert
verifyMenuIsDisplayed(MenuType.WARNING_OR_ERROR);
pressCheckKey();
// dismiss alert
verifyMenuIsDisplayed(MenuType.WARNING_OR_ERROR);
pressCheckKey();
waitForMenuToBeLeft(MenuType.WARNING_OR_ERROR);
alertProcessed = true;
}
SystemClock.sleep(10);
}
return alertProcessed;
}
/** Wait until the menu is updated */
public void waitForMenuUpdate() { public void waitForMenuUpdate() {
waitForMenuUpdate(60, "Timeout waiting for menu update"); waitForMenuUpdate(60, "Timeout waiting for menu update");
} }
@ -615,10 +644,10 @@ public class RuffyScripter {
} }
public void navigateToMenu(MenuType desiredMenu) { public void navigateToMenu(MenuType desiredMenu) {
MenuType startedFrom = currentMenu.getType(); MenuType startedFrom = getCurrentMenu().getType();
boolean movedOnce = false; boolean movedOnce = false;
while (currentMenu.getType() != desiredMenu) { while (getCurrentMenu().getType() != desiredMenu) {
MenuType currentMenuType = currentMenu.getType(); MenuType currentMenuType = getCurrentMenu().getType();
log.debug("Navigating to menu " + desiredMenu + ", currenty menu: " + currentMenuType); log.debug("Navigating to menu " + desiredMenu + ", currenty menu: " + currentMenuType);
if (movedOnce && currentMenuType == startedFrom) { if (movedOnce && currentMenuType == startedFrom) {
throw new CommandException().message("Menu not found searching for " + desiredMenu throw new CommandException().message("Menu not found searching for " + desiredMenu
@ -630,12 +659,10 @@ public class RuffyScripter {
} }
} }
/** /** Wait till a menu changed has completed, "away" from the menu provided as argument. */
* Wait till a menu changed has completed, "away" from the menu provided as argument.
*/
public void waitForMenuToBeLeft(MenuType menuType) { public void waitForMenuToBeLeft(MenuType menuType) {
long timeout = System.currentTimeMillis() + 60 * 1000; long timeout = System.currentTimeMillis() + 60 * 1000;
while (currentMenu.getType() == menuType) { while (getCurrentMenu().getType() == menuType) {
if (System.currentTimeMillis() > timeout) { if (System.currentTimeMillis() > timeout) {
throw new CommandException().message("Timeout waiting for menu " + menuType + " to be left"); throw new CommandException().message("Timeout waiting for menu " + menuType + " to be left");
} }
@ -649,7 +676,7 @@ public class RuffyScripter {
public void verifyMenuIsDisplayed(MenuType expectedMenu, String failureMessage) { public void verifyMenuIsDisplayed(MenuType expectedMenu, String failureMessage) {
int retries = 600; int retries = 600;
while (currentMenu.getType() != expectedMenu) { while (getCurrentMenu().getType() != expectedMenu) {
if (retries > 0) { if (retries > 0) {
SystemClock.sleep(100); SystemClock.sleep(100);
retries = retries - 1; retries = retries - 1;
@ -665,9 +692,9 @@ public class RuffyScripter {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public <T> T readBlinkingValue(Class<T> expectedType, MenuAttribute attribute) { public <T> T readBlinkingValue(Class<T> expectedType, MenuAttribute attribute) {
int retries = 5; int retries = 5;
Object value = currentMenu.getAttribute(attribute); Object value = getCurrentMenu().getAttribute(attribute);
while (!expectedType.isInstance(value)) { while (!expectedType.isInstance(value)) {
value = currentMenu.getAttribute(attribute); value = getCurrentMenu().getAttribute(attribute);
waitForScreenUpdate(1000); waitForScreenUpdate(1000);
retries--; retries--;
if (retries == 0) { if (retries == 0) {
@ -677,8 +704,45 @@ public class RuffyScripter {
return (T) value; return (T) value;
} }
public long readDisplayedDuration() { @Override
MenuTime duration = readBlinkingValue(MenuTime.class, MenuAttribute.RUNTIME); public CommandResult deliverBolus(double amount, BolusCommand.ProgressReportCallback progressReportCallback) {
return duration.getHour() * 60 + duration.getMinute(); return runCommand(new BolusCommand(amount, progressReportCallback));
}
@Override
public void cancelBolus() {
if (activeCmd instanceof BolusCommand) {
((BolusCommand) activeCmd).requestCancellation();
}
}
@Override
public CommandResult setTbr(int percent, int duraton) {
return runCommand(new SetTbrCommand(percent, duraton));
}
@Override
public CommandResult cancelTbr() {
return runCommand(new CancelTbrCommand());
}
@Override
public CommandResult readReservoirLevel() {
return runCommand(new ReadReserverLevelCommand());
}
@Override
public CommandResult readHistory(PumpHistory knownHistory) {
return runCommand(new ReadHistoryCommand(knownHistory));
}
@Override
public CommandResult readBasalProfile() {
return runCommand(new ReadBasalProfile());
}
@Override
public CommandResult setBasalProfile(BasalProfile basalProfile) {
return runCommand(new SetBasalProfile(basalProfile));
} }
} }

View file

@ -0,0 +1,25 @@
package info.nightscout.androidaps.plugins.PumpCombo.scripter.commands;
import info.nightscout.androidaps.plugins.PumpCombo.scripter.RuffyScripter;
public abstract class BaseCommand implements Command {
// RS will inject itself here
protected RuffyScripter scripter;
@Override
public void setScripter(RuffyScripter scripter) {
this.scripter = scripter;
}
// TODO upcoming
protected final boolean canBeCancelled = true;
protected volatile boolean cancelRequested = false;
public void requestCancellation() {
cancelRequested = true;
}
public boolean isCancellable() {
return canBeCancelled;
}
}

View file

@ -1,4 +1,4 @@
package de.jotomo.ruffyscripter.commands; package info.nightscout.androidaps.plugins.PumpCombo.scripter.commands;
import android.os.SystemClock; import android.os.SystemClock;
@ -11,22 +11,23 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import de.jotomo.ruffyscripter.PumpState; import info.nightscout.androidaps.plugins.PumpCombo.scripter.RuffyScripter;
import de.jotomo.ruffyscripter.RuffyScripter;
import static de.jotomo.ruffyscripter.commands.CancellableBolusCommand.ProgressReportCallback.State.DELIVERED; import static info.nightscout.androidaps.plugins.PumpCombo.scripter.commands.BolusCommand.ProgressReportCallback.State.DELIVERED;
import static de.jotomo.ruffyscripter.commands.CancellableBolusCommand.ProgressReportCallback.State.DELIVERING; import static info.nightscout.androidaps.plugins.PumpCombo.scripter.commands.BolusCommand.ProgressReportCallback.State.DELIVERING;
import static de.jotomo.ruffyscripter.commands.CancellableBolusCommand.ProgressReportCallback.State.STOPPED; import static info.nightscout.androidaps.plugins.PumpCombo.scripter.commands.BolusCommand.ProgressReportCallback.State.PROGRAMMING;
import static de.jotomo.ruffyscripter.commands.CancellableBolusCommand.ProgressReportCallback.State.STOPPING; import static info.nightscout.androidaps.plugins.PumpCombo.scripter.commands.BolusCommand.ProgressReportCallback.State.STOPPED;
import static info.nightscout.androidaps.plugins.PumpCombo.scripter.commands.BolusCommand.ProgressReportCallback.State.STOPPING;
public class CancellableBolusCommand extends BolusCommand { public class BolusCommand extends BaseCommand {
private static final Logger log = LoggerFactory.getLogger(CancellableBolusCommand.class); private static final Logger log = LoggerFactory.getLogger(BolusCommand.class);
protected final double bolus;
private final ProgressReportCallback progressReportCallback; private final ProgressReportCallback progressReportCallback;
private volatile boolean cancelRequested; private volatile boolean cancelRequested;
public CancellableBolusCommand(double bolus, ProgressReportCallback progressReportCallback) { public BolusCommand(double bolus, ProgressReportCallback progressReportCallback) {
super(bolus); this.bolus = bolus;
this.progressReportCallback = progressReportCallback; this.progressReportCallback = progressReportCallback;
} }
@ -45,14 +46,17 @@ public class CancellableBolusCommand extends BolusCommand {
public CommandResult execute() { public CommandResult execute() {
try { try {
// TODO read reservoir level and reject request if reservoir < bolus // TODO read reservoir level and reject request if reservoir < bolus
enterBolusMenu(); // TODO also check if there's a bolus in history we're not aware of
// press check twice to get reservoir level and last bolus quickly
progressReportCallback.report(PROGRAMMING, 0, 0);
enterBolusMenu();
inputBolusAmount(); inputBolusAmount();
verifyDisplayedBolusAmount(); verifyDisplayedBolusAmount();
if (cancelRequested) { if (cancelRequested) {
progressReportCallback.report(STOPPING, 0, 0); progressReportCallback.report(STOPPING, 0, 0);
scripter.goToMainTypeScreen(MenuType.MAIN_MENU, 30 * 1000); scripter.returnToMainMenu();
progressReportCallback.report(STOPPED, 0, 0); progressReportCallback.report(STOPPED, 0, 0);
return new CommandResult().success(true).enacted(false) return new CommandResult().success(true).enacted(false)
.message("Bolus cancelled as per user request with no insulin delivered"); .message("Bolus cancelled as per user request with no insulin delivered");
@ -69,7 +73,7 @@ public class CancellableBolusCommand extends BolusCommand {
scripter.pressUpKey(); scripter.pressUpKey();
// wait up to 1s for a BOLUS_CANCELLED alert, if it doesn't happen we missed // wait up to 1s for a BOLUS_CANCELLED alert, if it doesn't happen we missed
// the window, simply continue and let the next cancel attempt try its luck // the window, simply continue and let the next cancel attempt try its luck
boolean alertWasCancelled = confirmAlert("BOLUS CANCELLED", 1000); boolean alertWasCancelled = scripter.confirmAlert("BOLUS CANCELLED", 1000);
if (alertWasCancelled) { if (alertWasCancelled) {
progressReportCallback.report(STOPPED, 0, 0); progressReportCallback.report(STOPPED, 0, 0);
return new CommandResult().success(true).enacted(false) return new CommandResult().success(true).enacted(false)
@ -91,9 +95,11 @@ public class CancellableBolusCommand extends BolusCommand {
// TODO extract into method // TODO extract into method
// TODO 'low cartrdige' alarm must be handled inside, since the bolus continues regardless; // TODO 'low cartrdige' alarm must be handled inside, since the bolus continues regardless;
// it must be claread so we can see the remaining bolus again; // it must be cleared so we can see the remaining bolus again;
while (bolusRemaining != null) { while (bolusRemaining != null) {
if (cancelRequested) { if (cancelRequested) {
// cancel running bolus by pressing up for 3s, while raise a BOLUS CANCELLED
// alert, unless the bolus finished within those 3s.
progressReportCallback.report(STOPPING, 0, 0); progressReportCallback.report(STOPPING, 0, 0);
scripter.pressKeyMs(RuffyScripter.Key.UP, 3000); scripter.pressKeyMs(RuffyScripter.Key.UP, 3000);
progressReportCallback.report(STOPPED, 0, 0); progressReportCallback.report(STOPPED, 0, 0);
@ -116,30 +122,42 @@ public class CancellableBolusCommand extends BolusCommand {
lastBolusReported = bolusRemaining; lastBolusReported = bolusRemaining;
} }
/*
// TODO think through situatiotns where an alarm can be raised, not just when pressing a button,
// but a 'low battery' alarm can trigger at any time ...
if (scripter.getCurrentMenu().getType() == MenuType.WARNING_OR_ERROR) { if (scripter.getCurrentMenu().getType() == MenuType.WARNING_OR_ERROR) {
String message = (String) scripter.getCurrentMenu().getAttribute(MenuAttribute.MESSAGE); String message = (String) scripter.getCurrentMenu().getAttribute(MenuAttribute.MESSAGE);
if (message.equals("LOW CARTRIDGE")) { if (message.equals("LOW CARTRIDGE")) {
lowCartdrigeAlarmTriggered = true; lowCartdrigeAlarmTriggered = true;
confirmAlert("LOW CARTRIDGE", 2000); scripter.confirmAlert("LOW CARTRIDGE", 2000);
} else { } else {
// any other alert // any other alert
break; break;
} }
} }
*/
SystemClock.sleep(50); SystemClock.sleep(50);
bolusRemaining = (Double) scripter.getCurrentMenu().getAttribute(MenuAttribute.BOLUS_REMAINING); bolusRemaining = (Double) scripter.getCurrentMenu().getAttribute(MenuAttribute.BOLUS_REMAINING);
} }
progressReportCallback.report(DELIVERED, 100, bolus);
/*
// wait up to 2s for any possible warning to be raised, if not raised already // wait up to 2s for any possible warning to be raised, if not raised already
long minWait = System.currentTimeMillis() + 2 * 1000; // TODO what could be raised here, other than those alarms than can ring at any time anyways?
while (scripter.getCurrentMenu().getType() != MenuType.WARNING_OR_ERROR || System.currentTimeMillis() < minWait) { long timeout = System.currentTimeMillis() + 2 * 1000;
while (scripter.getCurrentMenu().getType() != MenuType.WARNING_OR_ERROR && System.currentTimeMillis() < timeout) {
SystemClock.sleep(50); SystemClock.sleep(50);
} }
// process warnings (confirm them, report back to AAPS about them) // process warnings (confirm them, report back to AAPS about them)
while (scripter.getCurrentMenu().getType() == MenuType.WARNING_OR_ERROR || System.currentTimeMillis() < minWait) { // while (scripter.getCurrentMenu().getType() == MenuType.WARNING_OR_ERROR || System.currentTimeMillis() < timeout) {
// TODO if (scripter.getCurrentMenu().getType() == MenuType.WARNING_OR_ERROR) {
scripter.confirmAlert(((String) scripter.getCurrentMenu().getAttribute(MenuAttribute.MESSAGE)), 1000);
} }
// SystemClock.sleep(50);
// }
*/
// TODO what if we hit 'cartridge low' alert here? is it immediately displayed or after the bolus? // TODO what if we hit 'cartridge low' alert here? is it immediately displayed or after the bolus?
// TODO how are error states reported back to the caller that occur outside of calls in genal? Low battery, low cartridge? // TODO how are error states reported back to the caller that occur outside of calls in genal? Low battery, low cartridge?
@ -149,7 +167,6 @@ public class CancellableBolusCommand extends BolusCommand {
"Bolus delivery did not complete as expected. " "Bolus delivery did not complete as expected. "
+ "Check pump manually, the bolus might not have been delivered."); + "Check pump manually, the bolus might not have been delivered.");
// TODO report back what was read from history // TODO report back what was read from history
// read last bolus record; those menus display static data and therefore // read last bolus record; those menus display static data and therefore
@ -176,13 +193,14 @@ public class CancellableBolusCommand extends BolusCommand {
} }
log.debug("Bolus record in history confirms delivered bolus"); log.debug("Bolus record in history confirms delivered bolus");
if (!scripter.goToMainTypeScreen(MenuType.MAIN_MENU, 15 * 1000)) { // TODO how would this call fail? more generally ......
scripter.returnToMainMenu();
if (scripter.getCurrentMenu().getType() != MenuType.MAIN_MENU) {
throw new CommandException().success(false).enacted(true) throw new CommandException().success(false).enacted(true)
.message("Bolus was correctly delivered and checked against history, but we " .message("Bolus was correctly delivered and checked against history, but we "
+ "did not return the main menu successfully."); + "did not return the main menu successfully.");
} }
progressReportCallback.report(DELIVERED, 100, bolus);
return new CommandResult().success(true).enacted(true) return new CommandResult().success(true).enacted(true)
.message(String.format(Locale.US, "Delivered %02.1f U", bolus)); .message(String.format(Locale.US, "Delivered %02.1f U", bolus));
@ -191,11 +209,54 @@ public class CancellableBolusCommand extends BolusCommand {
} }
} }
// TODO confirmAlarms? and report back which were cancelled? private void enterBolusMenu() {
scripter.verifyMenuIsDisplayed(MenuType.MAIN_MENU);
scripter.navigateToMenu(MenuType.BOLUS_MENU);
scripter.verifyMenuIsDisplayed(MenuType.BOLUS_MENU);
scripter.pressCheckKey();
scripter.waitForMenuUpdate();
scripter.verifyMenuIsDisplayed(MenuType.BOLUS_ENTER);
}
private boolean confirmAlert(String alertText, int maxWaitTillExpectedAlert) { private void inputBolusAmount() {
// TODO scripter.verifyMenuIsDisplayed(MenuType.BOLUS_ENTER);
return false; // press 'up' once for each 0.1 U increment
long steps = Math.round(bolus * 10);
for (int i = 0; i < steps; i++) {
scripter.verifyMenuIsDisplayed(MenuType.BOLUS_ENTER);
scripter.pressUpKey();
SystemClock.sleep(100);
}
// Give the pump time to finish any scrolling that might still be going on, can take
// up to 1100ms. Plus some extra time to be sure
SystemClock.sleep(2000);
}
private void verifyDisplayedBolusAmount() {
scripter.verifyMenuIsDisplayed(MenuType.BOLUS_ENTER);
// wait up to 5s for any scrolling to finish
double displayedBolus = scripter.readBlinkingValue(Double.class, MenuAttribute.BOLUS);
long timeout = System.currentTimeMillis() + 10 * 1000;
while (timeout > System.currentTimeMillis() && bolus - displayedBolus > 0.05) {
log.debug("Waiting for pump to process scrolling input for amount, current: " + displayedBolus + ", desired: " + bolus);
SystemClock.sleep(50);
displayedBolus = scripter.readBlinkingValue(Double.class, MenuAttribute.BOLUS);
}
log.debug("Final bolus: " + displayedBolus);
if (Math.abs(displayedBolus - bolus) > 0.05) {
throw new CommandException().message("Failed to set correct bolus. Expected: " + bolus + ", actual: " + displayedBolus);
}
// check again to ensure the displayed value hasn't change due to due scrolling taking extremely long
SystemClock.sleep(1000);
scripter.verifyMenuIsDisplayed(MenuType.BOLUS_ENTER);
double refreshedDisplayedBolus = scripter.readBlinkingValue(Double.class, MenuAttribute.BOLUS);
if (Math.abs(displayedBolus - refreshedDisplayedBolus) > 0.05) {
throw new CommandException().message("Failed to set bolus: bolus changed after input stopped from "
+ displayedBolus + " -> " + refreshedDisplayedBolus);
}
} }
public void requestCancellation() { public void requestCancellation() {
@ -212,6 +273,7 @@ public class CancellableBolusCommand extends BolusCommand {
public interface ProgressReportCallback { public interface ProgressReportCallback {
enum State { enum State {
PROGRAMMING,
DELIVERING, DELIVERING,
DELIVERED, DELIVERED,
STOPPING, STOPPING,

View file

@ -1,4 +1,4 @@
package de.jotomo.ruffyscripter.commands; package info.nightscout.androidaps.plugins.PumpCombo.scripter.commands;
import org.monkey.d.ruffy.ruffy.driver.display.MenuType; import org.monkey.d.ruffy.ruffy.driver.display.MenuType;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -7,7 +7,7 @@ import org.slf4j.LoggerFactory;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import de.jotomo.ruffyscripter.PumpState; import info.nightscout.androidaps.plugins.PumpCombo.scripter.PumpState;
import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.MainApp;
// TODO robustness: can a TBR run out, whilst we're trying to cancel it? // TODO robustness: can a TBR run out, whilst we're trying to cancel it?

View file

@ -1,8 +1,8 @@
package de.jotomo.ruffyscripter.commands; package info.nightscout.androidaps.plugins.PumpCombo.scripter.commands;
import java.util.List; import java.util.List;
import de.jotomo.ruffyscripter.RuffyScripter; import info.nightscout.androidaps.plugins.PumpCombo.scripter.RuffyScripter;
/** /**
* Interface for all commands to be executed by the pump. * Interface for all commands to be executed by the pump.

View file

@ -1,4 +1,4 @@
package de.jotomo.ruffyscripter.commands; package info.nightscout.androidaps.plugins.PumpCombo.scripter.commands;
public class CommandException extends RuntimeException { public class CommandException extends RuntimeException {
public boolean success = false; public boolean success = false;

View file

@ -1,9 +1,9 @@
package de.jotomo.ruffyscripter.commands; package info.nightscout.androidaps.plugins.PumpCombo.scripter.commands;
import java.util.Date; import java.util.Date;
import de.jotomo.ruffyscripter.History; import info.nightscout.androidaps.plugins.PumpCombo.scripter.History;
import de.jotomo.ruffyscripter.PumpState; import info.nightscout.androidaps.plugins.PumpCombo.scripter.PumpState;
public class CommandResult { public class CommandResult {
public boolean success; public boolean success;

View file

@ -1,4 +1,4 @@
package de.jotomo.ruffyscripter.commands; package info.nightscout.androidaps.plugins.PumpCombo.scripter.commands;
import android.util.Log; import android.util.Log;
@ -10,7 +10,7 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import de.jotomo.ruffyscripter.RuffyScripter; import info.nightscout.androidaps.plugins.PumpCombo.scripter.RuffyScripter;
public class GetBasalRateProfileCommand extends BaseCommand { public class GetBasalRateProfileCommand extends BaseCommand {
private static final Logger log = LoggerFactory.getLogger(GetBasalRateProfileCommand.class); private static final Logger log = LoggerFactory.getLogger(GetBasalRateProfileCommand.class);

View file

@ -1,11 +1,11 @@
package de.jotomo.ruffyscripter.commands; package info.nightscout.androidaps.plugins.PumpCombo.scripter.commands;
import org.monkey.d.ruffy.ruffy.driver.display.MenuType; import org.monkey.d.ruffy.ruffy.driver.display.MenuType;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import static de.jotomo.ruffyscripter.commands.GetPumpStateCommand.Stepper.runStep; import static info.nightscout.androidaps.plugins.PumpCombo.scripter.commands.GetPumpStateCommand.Stepper.runStep;
public class GetPumpStateCommand extends BaseCommand { public class GetPumpStateCommand extends BaseCommand {
interface Step { interface Step {

View file

@ -0,0 +1,22 @@
package info.nightscout.androidaps.plugins.PumpCombo.scripter.commands;
import java.util.List;
import info.nightscout.androidaps.plugins.PumpCombo.scripter.RuffyScripter;
public class ReadBasalProfile implements Command {
@Override
public CommandResult execute() {
return null;
}
@Override
public List<String> validateArguments() {
return null;
}
@Override
public void setScripter(RuffyScripter scripter) {
}
}

View file

@ -0,0 +1,26 @@
package info.nightscout.androidaps.plugins.PumpCombo.scripter.commands;
import java.util.List;
import info.nightscout.androidaps.plugins.PumpCombo.scripter.PumpHistory;
import info.nightscout.androidaps.plugins.PumpCombo.scripter.RuffyScripter;
public class ReadHistoryCommand implements Command {
public ReadHistoryCommand(PumpHistory knownHistory) {
}
@Override
public CommandResult execute() {
return null;
}
@Override
public List<String> validateArguments() {
return null;
}
@Override
public void setScripter(RuffyScripter scripter) {
}
}

View file

@ -0,0 +1,22 @@
package info.nightscout.androidaps.plugins.PumpCombo.scripter.commands;
import java.util.List;
import info.nightscout.androidaps.plugins.PumpCombo.scripter.RuffyScripter;
public class ReadReserverLevelCommand implements Command {
@Override
public CommandResult execute() {
return null;
}
@Override
public List<String> validateArguments() {
return null;
}
@Override
public void setScripter(RuffyScripter scripter) {
}
}

View file

@ -0,0 +1,27 @@
package info.nightscout.androidaps.plugins.PumpCombo.scripter.commands;
import java.util.List;
import info.nightscout.androidaps.plugins.PumpCombo.scripter.BasalProfile;
import info.nightscout.androidaps.plugins.PumpCombo.scripter.RuffyScripter;
public class SetBasalProfile implements Command {
public SetBasalProfile(BasalProfile basalProfile) {
}
@Override
public CommandResult execute() {
return null;
}
@Override
public List<String> validateArguments() {
return null;
}
@Override
public void setScripter(RuffyScripter scripter) {
}
}

View file

@ -1,4 +1,4 @@
package de.jotomo.ruffyscripter.commands; package info.nightscout.androidaps.plugins.PumpCombo.scripter.commands;
import java.util.List; import java.util.List;

View file

@ -1,4 +1,4 @@
package de.jotomo.ruffyscripter.commands; package info.nightscout.androidaps.plugins.PumpCombo.scripter.commands;
import android.os.SystemClock; import android.os.SystemClock;
@ -12,8 +12,6 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import de.jotomo.ruffyscripter.PumpState;
public class SetTbrCommand extends BaseCommand { public class SetTbrCommand extends BaseCommand {
private static final Logger log = LoggerFactory.getLogger(SetTbrCommand.class); private static final Logger log = LoggerFactory.getLogger(SetTbrCommand.class);
@ -108,17 +106,13 @@ public class SetTbrCommand extends BaseCommand {
private boolean inputTbrPercentage() { private boolean inputTbrPercentage() {
scripter.verifyMenuIsDisplayed(MenuType.TBR_SET); scripter.verifyMenuIsDisplayed(MenuType.TBR_SET);
long currentPercent = scripter.readBlinkingValue(Double.class, MenuAttribute.BASAL_RATE).longValue(); long currentPercent = readDisplayedPercentage();
log.debug("Current TBR %: " + currentPercent); log.debug("Current TBR %: " + currentPercent);
long percentageChange = percentage - currentPercent; long percentageChange = percentage - currentPercent;
long percentageSteps = percentageChange / 10; long percentageSteps = percentageChange / 10;
boolean increasePercentage = true; boolean increasePercentage = percentageSteps > 0;
if (percentageSteps < 0) {
increasePercentage = false;
percentageSteps = Math.abs(percentageSteps);
}
log.debug("Pressing " + (increasePercentage ? "up" : "down") + " " + percentageSteps + " times"); log.debug("Pressing " + (increasePercentage ? "up" : "down") + " " + percentageSteps + " times");
for (int i = 0; i < percentageSteps; i++) { for (int i = 0; i < Math.abs(percentageSteps); i++) {
scripter.verifyMenuIsDisplayed(MenuType.TBR_SET); scripter.verifyMenuIsDisplayed(MenuType.TBR_SET);
log.debug("Push #" + (i + 1)); log.debug("Push #" + (i + 1));
if (increasePercentage) scripter.pressUpKey(); if (increasePercentage) scripter.pressUpKey();
@ -128,29 +122,31 @@ public class SetTbrCommand extends BaseCommand {
return increasePercentage; return increasePercentage;
} }
// TODO refactor: extract verification into a method TBR percentage, duration and bolus amount
private void verifyDisplayedTbrPercentage(boolean increasingPercentage) { private void verifyDisplayedTbrPercentage(boolean increasingPercentage) {
scripter.verifyMenuIsDisplayed(MenuType.TBR_SET); scripter.verifyMenuIsDisplayed(MenuType.TBR_SET);
// wait up to 5s for any scrolling to finish // wait up to 5s for any scrolling to finish
long displayedPercentage = scripter.readBlinkingValue(Double.class, MenuAttribute.BASAL_RATE).longValue(); long displayedPercentage = readDisplayedPercentage();
long timeout = System.currentTimeMillis() + 10 * 1000; long timeout = System.currentTimeMillis() + 10 * 1000;
while (timeout > System.currentTimeMillis() while (timeout > System.currentTimeMillis()
&& ((increasingPercentage && displayedPercentage < percentage) && ((increasingPercentage && displayedPercentage < percentage)
|| (!increasingPercentage && displayedPercentage > percentage))) { || (!increasingPercentage && displayedPercentage > percentage))) {
log.debug("Waiting for pump to process scrolling input for percentage, current: " log.debug("Waiting for pump to process scrolling input for percentage, current: "
+ displayedPercentage + ", desired: " + percentage + ", scrolling up: " + increasingPercentage); + displayedPercentage + ", desired: " + percentage + ", scrolling "
+ (increasingPercentage ? "up" : "down"));
SystemClock.sleep(50); SystemClock.sleep(50);
displayedPercentage = scripter.readBlinkingValue(Double.class, MenuAttribute.BASAL_RATE).longValue(); displayedPercentage = readDisplayedPercentage();
} }
log.debug("Final displayed TBR percentage: " + displayedPercentage); log.debug("Final displayed TBR percentage: " + displayedPercentage);
if (displayedPercentage != percentage) { if (displayedPercentage != percentage) {
throw new CommandException().message("Failed to set TBR percentage, requested: " + percentage + ", actual: " + displayedPercentage); throw new CommandException().message("Failed to set TBR percentage, requested: "
+ percentage + ", actual: " + displayedPercentage);
} }
// check again to ensure the displayed value hasn't change due to due scrolling taking extremely long // check again to ensure the displayed value hasn't change and scrolled past the desired
// value due to due scrolling taking extremely long
SystemClock.sleep(1000); SystemClock.sleep(1000);
scripter.verifyMenuIsDisplayed(MenuType.TBR_SET); scripter.verifyMenuIsDisplayed(MenuType.TBR_SET);
long refreshedDisplayedTbrPecentage = scripter.readBlinkingValue(Double.class, MenuAttribute.BASAL_RATE).longValue(); long refreshedDisplayedTbrPecentage = readDisplayedPercentage();
if (displayedPercentage != refreshedDisplayedTbrPecentage) { if (displayedPercentage != refreshedDisplayedTbrPecentage) {
throw new CommandException().message("Failed to set TBR percentage: " + throw new CommandException().message("Failed to set TBR percentage: " +
"percentage changed after input stopped from " "percentage changed after input stopped from "
@ -174,7 +170,7 @@ public class SetTbrCommand extends BaseCommand {
} }
private long calculateDurationSteps() { private long calculateDurationSteps() {
long currentDuration = scripter.readDisplayedDuration(); long currentDuration = readDisplayedDuration();
log.debug("Initial TBR duration: " + currentDuration); log.debug("Initial TBR duration: " + currentDuration);
long difference = duration - currentDuration; long difference = duration - currentDuration;
@ -190,26 +186,29 @@ public class SetTbrCommand extends BaseCommand {
scripter.verifyMenuIsDisplayed(MenuType.TBR_DURATION); scripter.verifyMenuIsDisplayed(MenuType.TBR_DURATION);
// wait up to 5s for any scrolling to finish // wait up to 5s for any scrolling to finish
long displayedDuration = scripter.readDisplayedDuration(); long displayedDuration = readDisplayedDuration();
long timeout = System.currentTimeMillis() + 10 * 1000; long timeout = System.currentTimeMillis() + 10 * 1000;
while (timeout > System.currentTimeMillis() while (timeout > System.currentTimeMillis()
&& ((increasingPercentage && displayedDuration < duration) && ((increasingPercentage && displayedDuration < duration)
|| (!increasingPercentage && displayedDuration > duration))) { || (!increasingPercentage && displayedDuration > duration))) {
log.debug("Waiting for pump to process scrolling input for duration, current: " log.debug("Waiting for pump to process scrolling input for duration, current: "
+ displayedDuration + ", desired: " + duration + ", scrolling up: " + increasingPercentage); + displayedDuration + ", desired: " + duration
+ ", scrolling " + (increasingPercentage ? "up" : "down"));
SystemClock.sleep(50); SystemClock.sleep(50);
displayedDuration = scripter.readDisplayedDuration(); displayedDuration = readDisplayedDuration();
} }
log.debug("Final displayed TBR duration: " + displayedDuration); log.debug("Final displayed TBR duration: " + displayedDuration);
if (displayedDuration != duration) { if (displayedDuration != duration) {
throw new CommandException().message("Failed to set TBR duration, requested: " + duration + ", actual: " + displayedDuration); throw new CommandException().message("Failed to set TBR duration, requested: "
+ duration + ", actual: " + displayedDuration);
} }
// check again to ensure the displayed value hasn't change due to due scrolling taking extremely long // check again to ensure the displayed value hasn't change and scrolled past the desired
// value due to due scrolling taking extremely long
SystemClock.sleep(1000); SystemClock.sleep(1000);
scripter.verifyMenuIsDisplayed(MenuType.TBR_DURATION); scripter.verifyMenuIsDisplayed(MenuType.TBR_DURATION);
long refreshedDisplayedTbrDuration = scripter.readDisplayedDuration(); long refreshedDisplayedTbrDuration = readDisplayedDuration();
if (displayedDuration != refreshedDisplayedTbrDuration) { if (displayedDuration != refreshedDisplayedTbrDuration) {
throw new CommandException().message("Failed to set TBR duration: " + throw new CommandException().message("Failed to set TBR duration: " +
"duration changed after input stopped from " "duration changed after input stopped from "
@ -217,8 +216,6 @@ public class SetTbrCommand extends BaseCommand {
} }
} }
private void cancelTbrAndConfirmCancellationWarning() { private void cancelTbrAndConfirmCancellationWarning() {
// confirm entered TBR // confirm entered TBR
scripter.verifyMenuIsDisplayed(MenuType.TBR_SET); scripter.verifyMenuIsDisplayed(MenuType.TBR_SET);
@ -227,35 +224,9 @@ public class SetTbrCommand extends BaseCommand {
// A "TBR CANCELLED alert" is only raised by the pump when the remaining time is // A "TBR CANCELLED alert" is only raised by the pump when the remaining time is
// greater than 60s (displayed as 0:01, the pump goes from there to finished. // greater than 60s (displayed as 0:01, the pump goes from there to finished.
// We could read the remaining duration from MAIN_MENU, but by the time we're here, // We could read the remaining duration from MAIN_MENU, but by the time we're here,
// the pumup could have moved from 0:02 to 0:01, so instead, check if a "TBR CANCELLED" alert // the pump could have moved from 0:02 to 0:01, so instead, check if a "TBR CANCELLED" alert
// is raised and if so dismiss it // is raised and if so dismiss it
long inFiveSeconds = System.currentTimeMillis() + 5 * 1000; scripter.confirmAlert("TBR CANCELLED", 5000);
boolean alertProcessed = false;
while (System.currentTimeMillis() < inFiveSeconds && !alertProcessed) {
if (scripter.getCurrentMenu().getType() == MenuType.WARNING_OR_ERROR) {
// Check the raised alarm is TBR CANCELLED, so we're not accidentally cancelling
// a different alarm that might be raised at the same time.
// Note that the message is permanently displayed, while the error code is blinking.
// A wait till the error code can be read results in the code hanging, despite
// menu updates coming in, so just check the message.
// TODO v2 this only works when the pump's language is English
String errorMsg = (String) scripter.getCurrentMenu().getAttribute(MenuAttribute.MESSAGE);
if (!errorMsg.equals("TBR CANCELLED")) {
throw new CommandException().success(false).enacted(false)
.message("An alert other than the expected TBR CANCELLED was raised by the pump: "
+ errorMsg + ". Please check the pump.");
}
// confirm "TBR CANCELLED" alert
scripter.verifyMenuIsDisplayed(MenuType.WARNING_OR_ERROR);
scripter.pressCheckKey();
// dismiss "TBR CANCELLED" alert
scripter.verifyMenuIsDisplayed(MenuType.WARNING_OR_ERROR);
scripter.pressCheckKey();
scripter.waitForMenuToBeLeft(MenuType.WARNING_OR_ERROR);
alertProcessed = true;
}
SystemClock.sleep(10);
}
} }
private void verifyMainMenuShowsNoActiveTbr() { private void verifyMainMenuShowsNoActiveTbr() {
@ -285,6 +256,15 @@ public class SetTbrCommand extends BaseCommand {
} }
} }
private long readDisplayedDuration() {
MenuTime duration = scripter.readBlinkingValue(MenuTime.class, MenuAttribute.RUNTIME);
return duration.getHour() * 60 + duration.getMinute();
}
private long readDisplayedPercentage() {
return scripter.readBlinkingValue(Double.class, MenuAttribute.BASAL_RATE).longValue();
}
@Override @Override
public String toString() { public String toString() {
return "SetTbrCommand{" + return "SetTbrCommand{" +

View file

@ -712,5 +712,23 @@
<string name="key_wizard_include_basal_iob">wizard_include_basal_iob</string> <string name="key_wizard_include_basal_iob">wizard_include_basal_iob</string>
<string name="bolusstopping">Stopping bolus delivery</string> <string name="bolusstopping">Stopping bolus delivery</string>
<string name="bolusstopped">Bolus delivery stopped</string> <string name="bolusstopped">Bolus delivery stopped</string>
<string name="key_combo_enable_experimental_features">combo_enable_experimental_features</string>
<string name="combo_enable_experimental_features">Enable experimental features</string>
<string name="combo_enable_experimental_features_summary">Unlocks experimental features which are in development and might be broken entirely.</string>
<string name="key_combo_enable_experimental_split_bolus">combo_experimental_split_bolus</string>
<string name="combo_enable_experimental_split_bolus">Experimental split bolus feature</string>
<string name="combo_enable_experimental_split_bolus_summary">Splits boluses into 2 U parts and waits around 45s after each to slow down bolus delivery (only active with non-experimental bolus).</string>
<string name="key_combo_experimental_skip_tbr_changes_below_delta">combo_experimental_reject_tbr_changes_below_delta</string>
<string name="combo_experimental_skip_tbr_changes_below_delta">Skip TBR changes below threshold (%).</string>
<string name="combo_experimental_skip_tbr_changes_below_delta_summary">Don\'t set a TBR if the difference between the new and a running TBR is below this threshold in percent. Specifying 0 disables this option.</string>
<string name="key_combo_disable_alerts">combo_disable_alerts</string>
<string name="combo_disable_alerts">Disable alerts</string>
<string name="combo_disable_alerts_summary">Ignore all errors encountered while communicating with the pump. Alerts raised by the pump (including those caused by AAPS) will still be raised.</string>
<string name="bolusprogramming">Programming pump for bolusing</string>
<string name="key_wizard_include_bg">wizard_include_bg</string>
<string name="key_wizard_include_cob">wizard_include_cob</string>
<string name="key_wizard_include_trend_bg">wizard_include_trend_bg</string>
<string name="key_wizard_include_bolus_iob">wizard_include_bolus_iob</string>
<string name="key_wizard_include_basal_iob">wizard_include_basal_iob</string>
</resources> </resources>

View file

@ -4,6 +4,33 @@
android:key="combopump" android:key="combopump"
android:title="@string/combopump_settings"> android:title="@string/combopump_settings">
<SwitchPreference
android:defaultValue="false"
android:key="@string/key_combo_enable_experimental_features"
android:title="@string/combo_enable_experimental_features"
android:summary="@string/combo_enable_experimental_features_summary" />
<SwitchPreference
android:dependency="@string/key_combo_enable_experimental_features"
android:defaultValue="false"
android:key="@string/key_combo_enable_experimental_split_bolus"
android:title="@string/combo_enable_experimental_split_bolus"
android:summary="@string/combo_enable_experimental_split_bolus_summary"/>
<!-- TODO add validation, to restrict values to rang 0-100, see pref_absorption_oref0.xml -->
<EditTextPreference
android:dependency="@string/key_combo_enable_experimental_features"
android:key="@string/key_combo_experimental_skip_tbr_changes_below_delta"
android:title="@string/combo_experimental_skip_tbr_changes_below_delta"
android:defaultValue="0"
android:numeric="decimal"
android:dialogMessage="@string/combo_experimental_skip_tbr_changes_below_delta_summary"/>
<SwitchPreference
android:defaultValue="false"
android:key="@string/key_combo_disable_alerts"
android:title="@string/combo_disable_alerts"
android:summary="@string/combo_disable_alerts_summary"/>
</PreferenceCategory> </PreferenceCategory>
</PreferenceScreen> </PreferenceScreen>