Refactor, clean out stuff, add RuffyCommands interface.

This commit is contained in:
Johannes Mockenhaupt 2017-10-11 14:01:52 +02:00
parent 960586b79d
commit 60c9a984b5
No known key found for this signature in database
GPG key ID: 9E1EA6AF7BBBB0D1
14 changed files with 335 additions and 338 deletions

View file

@ -23,15 +23,6 @@ import org.slf4j.LoggerFactory;
import java.util.Date;
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.CancelTbrCommand;
import info.nightscout.androidaps.plugins.PumpCombo.scripter.commands.ExperimentalBolusCommand;
import info.nightscout.androidaps.plugins.PumpCombo.scripter.commands.Command;
import info.nightscout.androidaps.plugins.PumpCombo.scripter.commands.CommandResult;
import info.nightscout.androidaps.plugins.PumpCombo.scripter.commands.GetPumpStateCommand;
import info.nightscout.androidaps.plugins.PumpCombo.scripter.commands.SetTbrCommand;
import info.nightscout.androidaps.BuildConfig;
import info.nightscout.androidaps.MainApp;
import info.nightscout.androidaps.R;
@ -47,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;
@ -54,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;
@ -142,11 +137,7 @@ public class ComboPlugin implements PluginBase, PumpInterface {
long now = System.currentTimeMillis();
long fiveMinutesSinceLastAlarm = lastAlarmTime + (5 * 60 * 1000) + (15 * 1000);
boolean loopEnabled = ConfigBuilderPlugin.getActiveLoop() != null;
boolean ignoreLastFailedTbrCmd = SP.getBoolean(R.string.key_combo_enable_experimental_features, false)
&& SP.getBoolean(R.string.key_combo_experimental_ignore_transient_errors, false)
&& (localLastCmd instanceof SetTbrCommand || localLastCmd instanceof GetPumpStateCommand)
&& ignoreLastSetTbrOrReadStateFailure;
if (now > fiveMinutesSinceLastAlarm && loopEnabled && !ignoreLastFailedTbrCmd) {
if (now > fiveMinutesSinceLastAlarm && loopEnabled) {
log.error("Command failed: " + localLastCmd);
log.error("Command result: " + localLastCmdResult);
PumpState localPumpState = pump.state;
@ -352,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());
}
}
@ -366,10 +358,10 @@ public class ComboPlugin implements PluginBase, PumpInterface {
return basal;
}
private static ExperimentalBolusCommand.ProgressReportCallback bolusProgressReportCallback =
new ExperimentalBolusCommand.ProgressReportCallback() {
private static BolusCommand.ProgressReportCallback bolusProgressReportCallback =
new BolusCommand.ProgressReportCallback() {
@Override
public void report(ExperimentalBolusCommand.ProgressReportCallback.State state, int percent, double delivered) {
public void report(BolusCommand.ProgressReportCallback.State state, int percent, double delivered) {
EventOverviewBolusProgress event = EventOverviewBolusProgress.getInstance();
switch (state) {
case PROGRAMMING:
@ -467,10 +459,7 @@ public class ComboPlugin implements PluginBase, PumpInterface {
@NonNull
private PumpEnactResult deliverBolus(DetailedBolusInfo detailedBolusInfo) {
runningBolusCommand = SP.getBoolean(R.string.key_combo_enable_experimental_features, false)
&& SP.getBoolean(R.string.key_combo_enable_experimental_bolus, false)
? new ExperimentalBolusCommand(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;
@ -517,19 +506,6 @@ public class ComboPlugin implements PluginBase, PumpInterface {
log.error("Exception received from pump", commandResult.exception);
}
// error tolerance
if (commandResult.success) ignoreLastSetTbrOrReadStateFailure = false;
if (command instanceof SetTbrCommand || command instanceof GetPumpStateCommand) {
if (!commandResult.success && !ignoreLastSetTbrOrReadStateFailure) {
// ignore this once
ignoreLastSetTbrOrReadStateFailure = true;
} else {
// second failure in a row
ignoreLastSetTbrOrReadStateFailure = false;
}
}
pump.lastCmd = command;
pump.lastCmdTime = new Date();
pump.lastCmdResult = commandResult;
@ -595,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);
@ -641,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;
@ -661,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;
}
}
@ -695,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

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

View file

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

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

@ -17,10 +17,17 @@ import org.slf4j.LoggerFactory;
import java.util.List;
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
@ -31,7 +38,7 @@ import info.nightscout.androidaps.plugins.PumpCombo.scripter.commands.GetPumpSta
* 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;
@ -696,4 +703,46 @@ public class RuffyScripter {
}
return (T) value;
}
@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

@ -11,16 +11,24 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import info.nightscout.androidaps.R;
import info.nightscout.utils.SP;
import info.nightscout.androidaps.plugins.PumpCombo.scripter.RuffyScripter;
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 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 BolusCommand(double bolus) {
public BolusCommand(double bolus, ProgressReportCallback progressReportCallback) {
this.bolus = bolus;
this.progressReportCallback = progressReportCallback;
}
@Override
@ -37,37 +45,119 @@ public class BolusCommand extends BaseCommand {
@Override
public CommandResult execute() {
try {
enterBolusMenu();
// TODO read reservoir level and reject request if reservoir < bolus
// 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.returnToMainMenu();
progressReportCallback.report(STOPPED, 0, 0);
return new CommandResult().success(true).enacted(false)
.message("Bolus cancelled as per user request with no insulin delivered");
}
// 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);
// the pump displays the entered bolus and waits a few seconds to let user check and cancel
while (scripter.getCurrentMenu().getType() == MenuType.BOLUS_ENTER) {
if (cancelRequested) {
progressReportCallback.report(STOPPING, 0, 0);
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 = scripter.confirmAlert("BOLUS CANCELLED", 1000);
if (alertWasCancelled) {
progressReportCallback.report(STOPPED, 0, 0);
return new CommandResult().success(true).enacted(false)
.message("Bolus cancelled as per user request with no insulin delivered");
}
}
SystemClock.sleep(10);
}
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.");
progressReportCallback.report(DELIVERING, 0, 0);
Double bolusRemaining = (Double) scripter.getCurrentMenu().getAttribute(MenuAttribute.BOLUS_REMAINING);
double lastBolusReported = 0;
boolean lowCartdrigeAlarmTriggered = false;
// 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);
// TODO extract into method
// TODO 'low cartrdige' alarm must be handled inside, since the bolus continues regardless;
// it must be cleared so we can see the remaining bolus again;
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.");
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);
// if the bolus finished while we attempted to cancel it, there'll be no alarm
long timeout = System.currentTimeMillis() + 2000;
while (scripter.getCurrentMenu().getType() != MenuType.WARNING_OR_ERROR && System.currentTimeMillis() < timeout) {
SystemClock.sleep(10);
}
while (scripter.getCurrentMenu().getType() == MenuType.WARNING_OR_ERROR) {
// TODO make this cleaner, extract method, needed below too
scripter.pressCheckKey();
SystemClock.sleep(200);
}
break;
}
if (lastBolusReported != bolusRemaining) {
log.debug("Delivering bolus, remaining: " + bolusRemaining);
int percentDelivered = (int) (100 - (bolusRemaining / bolus * 100));
progressReportCallback.report(DELIVERING, percentDelivered, bolus - 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) {
String message = (String) scripter.getCurrentMenu().getAttribute(MenuAttribute.MESSAGE);
if (message.equals("LOW CARTRIDGE")) {
lowCartdrigeAlarmTriggered = true;
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
// 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() < 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?
@ -77,6 +167,8 @@ public class BolusCommand extends BaseCommand {
"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
// only a single menu update is sent
scripter.navigateToMenu(MenuType.MY_DATA_MENU);
@ -91,6 +183,8 @@ public class BolusCommand extends BaseCommand {
.message("Bolus was delivered, but unable to confirm it with history record");
}
// TODO check date so we don't pick a false record if the previous bolus had the same amount;
// also, report back partial bolus. Just call ReadHsstory(timestamp, boluses=true) cmd ...
double lastBolusInHistory = (double) scripter.getCurrentMenu().getAttribute(MenuAttribute.BOLUS);
if (Math.abs(bolus - lastBolusInHistory) > 0.05) {
throw new CommandException().success(false).enacted(true)
@ -99,17 +193,14 @@ public class BolusCommand extends BaseCommand {
}
log.debug("Bolus record in history confirms delivered bolus");
if(SP.getBoolean(R.string.key_combo_enable_experimental_features, false)) {
scripter.returnToMainMenu();
} else {
// leave menu to go back to main menu
scripter.pressCheckKey();
scripter.waitForMenuToBeLeft(MenuType.BOLUS_DATA);
// 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.");
}
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));
@ -118,7 +209,7 @@ public class BolusCommand extends BaseCommand {
}
}
protected void enterBolusMenu() {
private void enterBolusMenu() {
scripter.verifyMenuIsDisplayed(MenuType.MAIN_MENU);
scripter.navigateToMenu(MenuType.BOLUS_MENU);
scripter.verifyMenuIsDisplayed(MenuType.BOLUS_MENU);
@ -127,7 +218,7 @@ public class BolusCommand extends BaseCommand {
scripter.verifyMenuIsDisplayed(MenuType.BOLUS_ENTER);
}
protected void inputBolusAmount() {
private void inputBolusAmount() {
scripter.verifyMenuIsDisplayed(MenuType.BOLUS_ENTER);
// press 'up' once for each 0.1 U increment
long steps = Math.round(bolus * 10);
@ -141,7 +232,7 @@ public class BolusCommand extends BaseCommand {
SystemClock.sleep(2000);
}
protected void verifyDisplayedBolusAmount() {
private void verifyDisplayedBolusAmount() {
scripter.verifyMenuIsDisplayed(MenuType.BOLUS_ENTER);
// wait up to 5s for any scrolling to finish
@ -168,10 +259,27 @@ public class BolusCommand extends BaseCommand {
}
}
public void requestCancellation() {
cancelRequested = true;
progressReportCallback.report(STOPPING, 0, 0);
}
@Override
public String toString() {
return "BolusCommand{" +
"bolus=" + bolus +
'}';
}
public interface ProgressReportCallback {
enum State {
PROGRAMMING,
DELIVERING,
DELIVERED,
STOPPING,
STOPPED
}
void report(State state, int percent, double delivered);
}
}

View file

@ -1,234 +0,0 @@
package info.nightscout.androidaps.plugins.PumpCombo.scripter.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;
import info.nightscout.androidaps.plugins.PumpCombo.scripter.RuffyScripter;
import static info.nightscout.androidaps.plugins.PumpCombo.scripter.commands.ExperimentalBolusCommand.ProgressReportCallback.State.DELIVERED;
import static info.nightscout.androidaps.plugins.PumpCombo.scripter.commands.ExperimentalBolusCommand.ProgressReportCallback.State.DELIVERING;
import static info.nightscout.androidaps.plugins.PumpCombo.scripter.commands.ExperimentalBolusCommand.ProgressReportCallback.State.PROGRAMMING;
import static info.nightscout.androidaps.plugins.PumpCombo.scripter.commands.ExperimentalBolusCommand.ProgressReportCallback.State.STOPPED;
import static info.nightscout.androidaps.plugins.PumpCombo.scripter.commands.ExperimentalBolusCommand.ProgressReportCallback.State.STOPPING;
public class ExperimentalBolusCommand extends BolusCommand {
private static final Logger log = LoggerFactory.getLogger(ExperimentalBolusCommand.class);
private final ProgressReportCallback progressReportCallback;
private volatile boolean cancelRequested;
public ExperimentalBolusCommand(double bolus, ProgressReportCallback progressReportCallback) {
super(bolus);
this.progressReportCallback = progressReportCallback;
}
@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 {
// TODO read reservoir level and reject request if reservoir < bolus
// 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.returnToMainMenu();
progressReportCallback.report(STOPPED, 0, 0);
return new CommandResult().success(true).enacted(false)
.message("Bolus cancelled as per user request with no insulin delivered");
}
// confirm bolus
scripter.verifyMenuIsDisplayed(MenuType.BOLUS_ENTER);
scripter.pressCheckKey();
// the pump displays the entered bolus and waits a few seconds to let user check and cancel
while (scripter.getCurrentMenu().getType() == MenuType.BOLUS_ENTER) {
if (cancelRequested) {
progressReportCallback.report(STOPPING, 0, 0);
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 = scripter.confirmAlert("BOLUS CANCELLED", 1000);
if (alertWasCancelled) {
progressReportCallback.report(STOPPED, 0, 0);
return new CommandResult().success(true).enacted(false)
.message("Bolus cancelled as per user request with no insulin delivered");
}
}
SystemClock.sleep(10);
}
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.");
progressReportCallback.report(DELIVERING, 0, 0);
Double bolusRemaining = (Double) scripter.getCurrentMenu().getAttribute(MenuAttribute.BOLUS_REMAINING);
double lastBolusReported = 0;
boolean lowCartdrigeAlarmTriggered = false;
// wait for bolus delivery to complete; the remaining units to deliver are counted
// down and are displayed on the main menu.
// TODO extract into method
// TODO 'low cartrdige' alarm must be handled inside, since the bolus continues regardless;
// 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);
// if the bolus finished while we attempted to cancel it, there'll be no alarm
long timeout = System.currentTimeMillis() + 2000;
while (scripter.getCurrentMenu().getType() != MenuType.WARNING_OR_ERROR && System.currentTimeMillis() < timeout) {
SystemClock.sleep(10);
}
while (scripter.getCurrentMenu().getType() == MenuType.WARNING_OR_ERROR) {
// TODO make this cleaner, extract method, needed below too
scripter.pressCheckKey();
SystemClock.sleep(200);
}
break;
}
if (lastBolusReported != bolusRemaining) {
log.debug("Delivering bolus, remaining: " + bolusRemaining);
int percentDelivered = (int) (100 - (bolusRemaining / bolus * 100));
progressReportCallback.report(DELIVERING, percentDelivered, bolus - 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) {
String message = (String) scripter.getCurrentMenu().getAttribute(MenuAttribute.MESSAGE);
if (message.equals("LOW CARTRIDGE")) {
lowCartdrigeAlarmTriggered = true;
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
// 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() < 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?
// 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.");
// TODO report back what was read from history
// 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");
}
// TODO check date so we don't pick a false record if the previous bolus had the same amount;
// also, report back partial bolus. Just call ReadHsstory(timestamp, boluses=true) cmd ...
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");
// 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.");
}
return new CommandResult().success(true).enacted(true)
.message(String.format(Locale.US, "Delivered %02.1f U", bolus));
} catch (CommandException e) {
return e.toCommandResult();
}
}
public void requestCancellation() {
cancelRequested = true;
progressReportCallback.report(STOPPING, 0, 0);
}
@Override
public String toString() {
return "BolusCommand{" +
"bolus=" + bolus +
'}';
}
public interface ProgressReportCallback {
enum State {
PROGRAMMING,
DELIVERING,
DELIVERED,
STOPPING,
STOPPED
}
void report(State state, int percent, double delivered);
}
}

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,15 +0,0 @@
package info.nightscout.androidaps.plugins.PumpCombo.scripter.commands;
/**
* Draft.
*
* small bolus, don't check reservoir level beforehand ... ??
* not cancellable
* less of an issue if it fails
* can be retried automatically
*/
public class SuperMicroBolusCommand extends BolusCommand {
public SuperMicroBolusCommand(double bolus) {
super(bolus);
}
}

View file

@ -715,15 +715,9 @@
<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_bolus">combo_enable_experimental_bolus</string>
<string name="combo_enable_experimental_bolus">Experimental bolus</string>
<string name="combo_enable_experimental_bolus_summary">Enables the in-development bolus with progress report and semi-working cancel option.</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_ignore_transient_errors">combo_ignore_transient_tbr_errors</string>
<string name="combo_experimental_ignore_transient_errors">Ignore transient errors</string>
<string name="combo_experimental_ignore_transient_errors_summary">Ignore failures when setting a TBR or reading pump state, unless the next attempt also fails.</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>

View file

@ -9,12 +9,6 @@
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_bolus"
android:title="@string/combo_enable_experimental_bolus"
android:summary="@string/combo_enable_experimental_bolus_summary" />
<SwitchPreference
android:dependency="@string/key_combo_enable_experimental_features"
android:defaultValue="false"
@ -22,13 +16,6 @@
android:title="@string/combo_enable_experimental_split_bolus"
android:summary="@string/combo_enable_experimental_split_bolus_summary"/>
<SwitchPreference
android:dependency="@string/key_combo_enable_experimental_features"
android:defaultValue="false"
android:key="@string/key_combo_experimental_ignore_transient_errors"
android:title="@string/combo_experimental_ignore_transient_errors"
android:summary="@string/combo_experimental_ignore_transient_errors_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"