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 logDanaMessageDetail = 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 de.jotomo.ruffyscripter.PumpState;
import de.jotomo.ruffyscripter.commands.Command;
import de.jotomo.ruffyscripter.commands.CommandResult;
import info.nightscout.androidaps.plugins.PumpCombo.scripter.PumpState;
import info.nightscout.androidaps.plugins.PumpCombo.scripter.commands.Command;
import info.nightscout.androidaps.plugins.PumpCombo.scripter.commands.CommandResult;
import info.nightscout.androidaps.MainApp;
import info.nightscout.androidaps.R;
import info.nightscout.androidaps.plugins.PumpCombo.events.EventComboPumpUpdateGUI;

View file

@ -23,17 +23,7 @@ import org.slf4j.LoggerFactory;
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.Config;
import info.nightscout.androidaps.MainApp;
import info.nightscout.androidaps.R;
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.Overview.events.EventOverviewBolusProgress;
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.SP;
@ -55,7 +50,6 @@ import info.nightscout.utils.SP;
* Created by mike on 05.08.2016.
*/
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 boolean fragmentEnabled = false;
@ -68,6 +62,8 @@ public class ComboPlugin implements PluginBase, PumpInterface {
private ComboPump pump = new ComboPump();
private boolean ignoreLastSetTbrOrReadStateFailure = false;
@Nullable
private volatile BolusCommand runningBolusCommand;
@ -99,7 +95,7 @@ public class ComboPlugin implements PluginBase, PumpInterface {
pumpDescription.isTempBasalCapable = true;
pumpDescription.tempBasalStyle = PumpDescription.PERCENT;
pumpDescription.maxTempPercent = SP.getInt(COMBO_MAX_TEMP_PERCENT_SP, 500);
pumpDescription.maxTempPercent = 500;
pumpDescription.tempPercentStep = 10;
pumpDescription.tempDurationStep = 15;
@ -136,10 +132,12 @@ public class ComboPlugin implements PluginBase, PumpInterface {
while (true) {
Command localLastCmd = pump.lastCmd;
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 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 result: " + localLastCmdResult);
PumpState localPumpState = pump.state;
@ -345,7 +343,8 @@ public class ComboPlugin implements PluginBase, PumpInterface {
if (notAUserRequest && wasRunAtLeastOnce && ranWithinTheLastMinute) {
log.debug("Not fetching state from pump, since we did already within the last 60 seconds");
} else {
runCommand(new GetPumpStateCommand());
// TODO
// runCommand(new GetPumpStateCommand());
}
}
@ -359,27 +358,30 @@ public class ComboPlugin implements PluginBase, PumpInterface {
return basal;
}
private static CancellableBolusCommand.ProgressReportCallback bolusProgressReportCallback =
new CancellableBolusCommand.ProgressReportCallback() {
private static BolusCommand.ProgressReportCallback bolusProgressReportCallback =
new BolusCommand.ProgressReportCallback() {
@Override
public void report(CancellableBolusCommand.ProgressReportCallback.State state, int percent, double delivered) {
EventOverviewBolusProgress enent = EventOverviewBolusProgress.getInstance();
public void report(BolusCommand.ProgressReportCallback.State state, int percent, double delivered) {
EventOverviewBolusProgress event = EventOverviewBolusProgress.getInstance();
switch (state) {
case PROGRAMMING:
event.status = MainApp.sResources.getString(R.string.bolusprogramming);
break;
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;
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;
case STOPPING:
enent.status = MainApp.sResources.getString(R.string.bolusstopping);
event.status = MainApp.sResources.getString(R.string.bolusstopping);
break;
case STOPPED:
enent.status = MainApp.sResources.getString(R.string.bolusstopped);
event.status = MainApp.sResources.getString(R.string.bolusstopped);
break;
}
enent.percent = percent;
MainApp.bus().post(enent);
event.percent = percent;
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) {
// 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);
} else {
// split up bolus into 2 U parts
@ -456,9 +459,7 @@ public class ComboPlugin implements PluginBase, PumpInterface {
@NonNull
private PumpEnactResult deliverBolus(DetailedBolusInfo detailedBolusInfo) {
runningBolusCommand = Config.comboExperimentalBolus
? new CancellableBolusCommand(detailedBolusInfo.insulin, bolusProgressReportCallback)
: new BolusCommand(detailedBolusInfo.insulin);
runningBolusCommand = new BolusCommand(detailedBolusInfo.insulin, bolusProgressReportCallback);
CommandResult bolusCmdResult = runCommand(runningBolusCommand);
PumpEnactResult pumpEnactResult = new PumpEnactResult();
pumpEnactResult.success = bolusCmdResult.success;
@ -535,6 +536,20 @@ public class ComboPlugin implements PluginBase, PumpInterface {
if (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);
}
@ -556,7 +571,7 @@ public class ComboPlugin implements PluginBase, PumpInterface {
adjustedPercent = rounded.intValue();
}
CommandResult commandResult = runCommand(new SetTbrCommand(adjustedPercent, durationInMinutes));
CommandResult commandResult = ruffyScripter.setTbr(adjustedPercent, durationInMinutes);
if (commandResult.enacted) {
TemporaryBasal tempStart = new TemporaryBasal(commandResult.completionTime);
@ -602,7 +617,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(new CancelTbrCommand());
commandResult = ruffyScripter.cancelTbr();
if (commandResult.enacted) {
tempBasal = new TemporaryBasal(commandResult.completionTime);
tempBasal.durationInMinutes = 0;
@ -622,14 +637,14 @@ public class ComboPlugin implements PluginBase, PumpInterface {
} else {
// 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%.
long percentage = (activeTemp.percentRate > 100) ? 110 : 90;
int percentage = (activeTemp.percentRate > 100) ? 110 : 90;
log.debug("cancelTempBasal: changing tbr to " + percentage + "% for 15 mins.");
commandResult = runCommand(new SetTbrCommand(percentage, 15));
commandResult = ruffyScripter.setTbr(percentage, 15);
if (commandResult.enacted) {
tempBasal = new TemporaryBasal(commandResult.completionTime);
tempBasal.durationInMinutes = 15;
tempBasal.source = Source.USER;
tempBasal.percentRate = (int) percentage;
tempBasal.percentRate = percentage;
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
@Override
public JSONObject getJSONStatus() {
if (true) { //pump.lastCmdTime.getTime() + 5 * 60 * 1000L < System.currentTimeMillis()) {
if (pump.lastCmdTime.getTime() + 5 * 60 * 1000L < System.currentTimeMillis()) {
return null;
}

View file

@ -5,9 +5,9 @@ import android.support.annotation.Nullable;
import java.util.Date;
import de.jotomo.ruffyscripter.PumpState;
import de.jotomo.ruffyscripter.commands.Command;
import de.jotomo.ruffyscripter.commands.CommandResult;
import info.nightscout.androidaps.plugins.PumpCombo.scripter.PumpState;
import info.nightscout.androidaps.plugins.PumpCombo.scripter.commands.Command;
import info.nightscout.androidaps.plugins.PumpCombo.scripter.commands.CommandResult;
class ComboPump {
@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"

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;

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.SystemClock;
import android.support.annotation.Nullable;
import com.google.common.base.Joiner;
@ -16,10 +17,17 @@ import org.slf4j.LoggerFactory;
import java.util.List;
import de.jotomo.ruffyscripter.commands.Command;
import de.jotomo.ruffyscripter.commands.CommandException;
import de.jotomo.ruffyscripter.commands.CommandResult;
import de.jotomo.ruffyscripter.commands.GetPumpStateCommand;
import info.nightscout.androidaps.plugins.PumpCombo.scripter.commands.CancelTbrCommand;
import info.nightscout.androidaps.plugins.PumpCombo.scripter.commands.Command;
import info.nightscout.androidaps.plugins.PumpCombo.scripter.commands.CommandException;
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.
// 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
* 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 IRuffyService ruffyService;
private String unrecoverableError = null;
@Nullable
private volatile Menu currentMenu;
private volatile long menuLastUpdated = 0;
@ -193,6 +202,23 @@ public class RuffyScripter {
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 {
CommandResult cmdResult;
}
@ -464,6 +490,17 @@ public class RuffyScripter {
// === pump ops ===
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;
}
@ -534,52 +571,44 @@ public class RuffyScripter {
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
// then anything longer than a few seconds is an error;
// 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
/**
* Wait until the menu update is in
*/
// TODO donn't use this in ensureConnected
// TODO confirmAlarms? and report back which were cancelled?
/** Confirms and dismisses the given alert if it's raised before the timeout */
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() {
waitForMenuUpdate(60, "Timeout waiting for menu update");
}
@ -615,10 +644,10 @@ public class RuffyScripter {
}
public void navigateToMenu(MenuType desiredMenu) {
MenuType startedFrom = currentMenu.getType();
MenuType startedFrom = getCurrentMenu().getType();
boolean movedOnce = false;
while (currentMenu.getType() != desiredMenu) {
MenuType currentMenuType = currentMenu.getType();
while (getCurrentMenu().getType() != desiredMenu) {
MenuType currentMenuType = getCurrentMenu().getType();
log.debug("Navigating to menu " + desiredMenu + ", currenty menu: " + currentMenuType);
if (movedOnce && currentMenuType == startedFrom) {
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) {
long timeout = System.currentTimeMillis() + 60 * 1000;
while (currentMenu.getType() == menuType) {
while (getCurrentMenu().getType() == menuType) {
if (System.currentTimeMillis() > timeout) {
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) {
int retries = 600;
while (currentMenu.getType() != expectedMenu) {
while (getCurrentMenu().getType() != expectedMenu) {
if (retries > 0) {
SystemClock.sleep(100);
retries = retries - 1;
@ -665,9 +692,9 @@ public class RuffyScripter {
@SuppressWarnings("unchecked")
public <T> T readBlinkingValue(Class<T> expectedType, MenuAttribute attribute) {
int retries = 5;
Object value = currentMenu.getAttribute(attribute);
Object value = getCurrentMenu().getAttribute(attribute);
while (!expectedType.isInstance(value)) {
value = currentMenu.getAttribute(attribute);
value = getCurrentMenu().getAttribute(attribute);
waitForScreenUpdate(1000);
retries--;
if (retries == 0) {
@ -677,8 +704,45 @@ public class RuffyScripter {
return (T) value;
}
public long readDisplayedDuration() {
MenuTime duration = readBlinkingValue(MenuTime.class, MenuAttribute.RUNTIME);
return duration.getHour() * 60 + duration.getMinute();
@Override
public CommandResult deliverBolus(double amount, BolusCommand.ProgressReportCallback progressReportCallback) {
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;
@ -11,22 +11,23 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import de.jotomo.ruffyscripter.PumpState;
import de.jotomo.ruffyscripter.RuffyScripter;
import info.nightscout.androidaps.plugins.PumpCombo.scripter.RuffyScripter;
import static de.jotomo.ruffyscripter.commands.CancellableBolusCommand.ProgressReportCallback.State.DELIVERED;
import static de.jotomo.ruffyscripter.commands.CancellableBolusCommand.ProgressReportCallback.State.DELIVERING;
import static de.jotomo.ruffyscripter.commands.CancellableBolusCommand.ProgressReportCallback.State.STOPPED;
import static de.jotomo.ruffyscripter.commands.CancellableBolusCommand.ProgressReportCallback.State.STOPPING;
import static info.nightscout.androidaps.plugins.PumpCombo.scripter.commands.BolusCommand.ProgressReportCallback.State.DELIVERED;
import static info.nightscout.androidaps.plugins.PumpCombo.scripter.commands.BolusCommand.ProgressReportCallback.State.DELIVERING;
import static info.nightscout.androidaps.plugins.PumpCombo.scripter.commands.BolusCommand.ProgressReportCallback.State.PROGRAMMING;
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 {
private static final Logger log = LoggerFactory.getLogger(CancellableBolusCommand.class);
public class BolusCommand extends BaseCommand {
private static final Logger log = LoggerFactory.getLogger(BolusCommand.class);
protected final double bolus;
private final ProgressReportCallback progressReportCallback;
private volatile boolean cancelRequested;
public CancellableBolusCommand(double bolus, ProgressReportCallback progressReportCallback) {
super(bolus);
public BolusCommand(double bolus, ProgressReportCallback progressReportCallback) {
this.bolus = bolus;
this.progressReportCallback = progressReportCallback;
}
@ -45,14 +46,17 @@ public class CancellableBolusCommand extends BolusCommand {
public CommandResult execute() {
try {
// 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();
verifyDisplayedBolusAmount();
if (cancelRequested) {
progressReportCallback.report(STOPPING, 0, 0);
scripter.goToMainTypeScreen(MenuType.MAIN_MENU, 30 * 1000);
scripter.returnToMainMenu();
progressReportCallback.report(STOPPED, 0, 0);
return new CommandResult().success(true).enacted(false)
.message("Bolus cancelled as per user request with no insulin delivered");
@ -69,7 +73,7 @@ public class CancellableBolusCommand extends BolusCommand {
scripter.pressUpKey();
// 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
boolean alertWasCancelled = confirmAlert("BOLUS CANCELLED", 1000);
boolean alertWasCancelled = scripter.confirmAlert("BOLUS CANCELLED", 1000);
if (alertWasCancelled) {
progressReportCallback.report(STOPPED, 0, 0);
return new CommandResult().success(true).enacted(false)
@ -91,9 +95,11 @@ public class CancellableBolusCommand extends BolusCommand {
// TODO extract into method
// 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) {
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);
scripter.pressKeyMs(RuffyScripter.Key.UP, 3000);
progressReportCallback.report(STOPPED, 0, 0);
@ -116,30 +122,42 @@ public class CancellableBolusCommand extends BolusCommand {
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) {
String message = (String) scripter.getCurrentMenu().getAttribute(MenuAttribute.MESSAGE);
if (message.equals("LOW CARTRIDGE")) {
lowCartdrigeAlarmTriggered = true;
confirmAlert("LOW CARTRIDGE", 2000);
scripter.confirmAlert("LOW CARTRIDGE", 2000);
} else {
// any other alert
break;
}
}
*/
SystemClock.sleep(50);
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
long minWait = System.currentTimeMillis() + 2 * 1000;
while (scripter.getCurrentMenu().getType() != MenuType.WARNING_OR_ERROR || System.currentTimeMillis() < minWait) {
// TODO what could be raised here, other than those alarms than can ring at any time anyways?
long timeout = System.currentTimeMillis() + 2 * 1000;
while (scripter.getCurrentMenu().getType() != MenuType.WARNING_OR_ERROR && System.currentTimeMillis() < timeout) {
SystemClock.sleep(50);
}
// process warnings (confirm them, report back to AAPS about them)
while (scripter.getCurrentMenu().getType() == MenuType.WARNING_OR_ERROR || System.currentTimeMillis() < minWait) {
// TODO
// while (scripter.getCurrentMenu().getType() == MenuType.WARNING_OR_ERROR || System.currentTimeMillis() < timeout) {
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 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. "
+ "Check pump manually, the bolus might not have been delivered.");
// TODO report back what was read from history
// 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");
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)
.message("Bolus was correctly delivered and checked against history, but we "
+ "did not return the main menu successfully.");
}
progressReportCallback.report(DELIVERED, 100, bolus);
return new CommandResult().success(true).enacted(true)
.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) {
// TODO
return false;
private 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);
}
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() {
@ -212,6 +273,7 @@ public class CancellableBolusCommand extends BolusCommand {
public interface ProgressReportCallback {
enum State {
PROGRAMMING,
DELIVERING,
DELIVERED,
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.slf4j.Logger;
@ -7,7 +7,7 @@ import org.slf4j.LoggerFactory;
import java.util.Collections;
import java.util.List;
import de.jotomo.ruffyscripter.PumpState;
import info.nightscout.androidaps.plugins.PumpCombo.scripter.PumpState;
import info.nightscout.androidaps.MainApp;
// 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 de.jotomo.ruffyscripter.RuffyScripter;
import info.nightscout.androidaps.plugins.PumpCombo.scripter.RuffyScripter;
/**
* 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 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 de.jotomo.ruffyscripter.History;
import de.jotomo.ruffyscripter.PumpState;
import info.nightscout.androidaps.plugins.PumpCombo.scripter.History;
import info.nightscout.androidaps.plugins.PumpCombo.scripter.PumpState;
public class CommandResult {
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;
@ -10,7 +10,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import de.jotomo.ruffyscripter.RuffyScripter;
import info.nightscout.androidaps.plugins.PumpCombo.scripter.RuffyScripter;
public class GetBasalRateProfileCommand extends BaseCommand {
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 java.util.Collections;
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 {
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;

View file

@ -1,4 +1,4 @@
package de.jotomo.ruffyscripter.commands;
package info.nightscout.androidaps.plugins.PumpCombo.scripter.commands;
import android.os.SystemClock;
@ -12,8 +12,6 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import de.jotomo.ruffyscripter.PumpState;
public class SetTbrCommand extends BaseCommand {
private static final Logger log = LoggerFactory.getLogger(SetTbrCommand.class);
@ -108,17 +106,13 @@ public class SetTbrCommand extends BaseCommand {
private boolean inputTbrPercentage() {
scripter.verifyMenuIsDisplayed(MenuType.TBR_SET);
long currentPercent = scripter.readBlinkingValue(Double.class, MenuAttribute.BASAL_RATE).longValue();
long currentPercent = readDisplayedPercentage();
log.debug("Current TBR %: " + currentPercent);
long percentageChange = percentage - currentPercent;
long percentageSteps = percentageChange / 10;
boolean increasePercentage = true;
if (percentageSteps < 0) {
increasePercentage = false;
percentageSteps = Math.abs(percentageSteps);
}
boolean increasePercentage = percentageSteps > 0;
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);
log.debug("Push #" + (i + 1));
if (increasePercentage) scripter.pressUpKey();
@ -128,29 +122,31 @@ public class SetTbrCommand extends BaseCommand {
return increasePercentage;
}
// TODO refactor: extract verification into a method TBR percentage, duration and bolus amount
private void verifyDisplayedTbrPercentage(boolean increasingPercentage) {
scripter.verifyMenuIsDisplayed(MenuType.TBR_SET);
// 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;
while (timeout > System.currentTimeMillis()
&& ((increasingPercentage && displayedPercentage < percentage)
|| (!increasingPercentage && displayedPercentage > percentage))) {
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);
displayedPercentage = scripter.readBlinkingValue(Double.class, MenuAttribute.BASAL_RATE).longValue();
displayedPercentage = readDisplayedPercentage();
}
log.debug("Final displayed TBR percentage: " + displayedPercentage);
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);
scripter.verifyMenuIsDisplayed(MenuType.TBR_SET);
long refreshedDisplayedTbrPecentage = scripter.readBlinkingValue(Double.class, MenuAttribute.BASAL_RATE).longValue();
long refreshedDisplayedTbrPecentage = readDisplayedPercentage();
if (displayedPercentage != refreshedDisplayedTbrPecentage) {
throw new CommandException().message("Failed to set TBR percentage: " +
"percentage changed after input stopped from "
@ -174,7 +170,7 @@ public class SetTbrCommand extends BaseCommand {
}
private long calculateDurationSteps() {
long currentDuration = scripter.readDisplayedDuration();
long currentDuration = readDisplayedDuration();
log.debug("Initial TBR duration: " + currentDuration);
long difference = duration - currentDuration;
@ -190,26 +186,29 @@ public class SetTbrCommand extends BaseCommand {
scripter.verifyMenuIsDisplayed(MenuType.TBR_DURATION);
// wait up to 5s for any scrolling to finish
long displayedDuration = scripter.readDisplayedDuration();
long displayedDuration = readDisplayedDuration();
long timeout = System.currentTimeMillis() + 10 * 1000;
while (timeout > System.currentTimeMillis()
&& ((increasingPercentage && displayedDuration < duration)
|| (!increasingPercentage && displayedDuration > duration))) {
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);
displayedDuration = scripter.readDisplayedDuration();
displayedDuration = readDisplayedDuration();
}
log.debug("Final displayed TBR duration: " + displayedDuration);
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);
scripter.verifyMenuIsDisplayed(MenuType.TBR_DURATION);
long refreshedDisplayedTbrDuration = scripter.readDisplayedDuration();
long refreshedDisplayedTbrDuration = readDisplayedDuration();
if (displayedDuration != refreshedDisplayedTbrDuration) {
throw new CommandException().message("Failed to set TBR duration: " +
"duration changed after input stopped from "
@ -217,8 +216,6 @@ public class SetTbrCommand extends BaseCommand {
}
}
private void cancelTbrAndConfirmCancellationWarning() {
// confirm entered TBR
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
// 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,
// 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
long inFiveSeconds = System.currentTimeMillis() + 5 * 1000;
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);
}
scripter.confirmAlert("TBR CANCELLED", 5000);
}
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
public String toString() {
return "SetTbrCommand{" +

View file

@ -712,5 +712,23 @@
<string name="key_wizard_include_basal_iob">wizard_include_basal_iob</string>
<string name="bolusstopping">Stopping bolus delivery</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>

View file

@ -4,6 +4,33 @@
android:key="combopump"
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>
</PreferenceScreen>