From 387ceff5258c2524815c3d7ec711f81ea8e9b3dd Mon Sep 17 00:00:00 2001 From: Johannes Mockenhaupt Date: Thu, 17 Aug 2017 11:21:16 +0200 Subject: [PATCH] Add config options: * alternate SetTbrCommand impl (original impl) * split bolus (quick hack) to split up boluses to slow delivery --- .../commands/SetTbrCommandAlt.java | 302 ++++++++++++++++++ .../info/nightscout/androidaps/Config.java | 6 + .../plugins/PumpCombo/ComboPlugin.java | 83 +++-- 3 files changed, 370 insertions(+), 21 deletions(-) create mode 100644 app/src/main/java/de/jotomo/ruffyscripter/commands/SetTbrCommandAlt.java diff --git a/app/src/main/java/de/jotomo/ruffyscripter/commands/SetTbrCommandAlt.java b/app/src/main/java/de/jotomo/ruffyscripter/commands/SetTbrCommandAlt.java new file mode 100644 index 0000000000..f223e188dd --- /dev/null +++ b/app/src/main/java/de/jotomo/ruffyscripter/commands/SetTbrCommandAlt.java @@ -0,0 +1,302 @@ +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.monkey.d.ruffy.ruffy.driver.display.menu.MenuTime; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +import de.jotomo.ruffyscripter.PumpState; +import de.jotomo.ruffyscripter.RuffyScripter; + +public class SetTbrCommandAlt implements Command { + private static final Logger log = LoggerFactory.getLogger(SetTbrCommand.class); + + private final long percentage; + private final long duration; + + public SetTbrCommandAlt(long percentage, long duration) { + this.percentage = percentage; + this.duration = duration; + } + + @Override + public List validateArguments() { + List violations = new ArrayList<>(); + + if (percentage % 10 != 0) { + violations.add("TBR percentage must be set in 10% steps"); + } + if (percentage < 0 || percentage > 500) { + violations.add("TBR percentage must be within 0-500%"); + } + + if (percentage != 100) { + if (duration % 15 != 0) { + violations.add("TBR duration can only be set in 15 minute steps"); + } + if (duration > 60 * 24) { + violations.add("Maximum TBR duration is 24 hours"); + } + } + + if (percentage == 0 && duration > 120) { + violations.add("Max allowed zero-temp duration is 2h"); + } + + return violations; + } + + @Override + public CommandResult execute(RuffyScripter scripter, PumpState initialPumpState) { + try { + enterTbrMenu(scripter); + inputTbrPercentage(scripter); + verifyDisplayedTbrPercentage(scripter); + + if (percentage == 100) { + cancelTbrAndConfirmCancellationWarning(scripter); + } else { + // switch to TBR_DURATION menu by pressing menu key + scripter.verifyMenuIsDisplayed(MenuType.TBR_SET); + scripter.pressMenuKey(); + scripter.waitForMenuUpdate(); + scripter.verifyMenuIsDisplayed(MenuType.TBR_DURATION); + + inputTbrDuration(scripter); + verifyDisplayedTbrDuration(scripter); + + // confirm TBR + scripter.pressCheckKey(); + scripter.waitForMenuToBeLeft(MenuType.TBR_DURATION); + } + + scripter.verifyMenuIsDisplayed(MenuType.MAIN_MENU, + "Pump did not return to MAIN_MEU after setting TBR. " + + "Check pump manually, the TBR might not have been set/cancelled."); + + // check main menu shows the same values we just set + if (percentage == 100) { + verifyMainMenuShowsNoActiveTbr(scripter); + return new CommandResult().success(true).enacted(true).message("TBR was cancelled"); + } else { + verifyMainMenuShowsExpectedTbrActive(scripter); + return new CommandResult().success(true).enacted(true).message( + String.format(Locale.US, "TBR set to %d%% for %d min", percentage, duration)); + } + + } catch (CommandException e) { + return e.toCommandResult(); + } + } + + private void enterTbrMenu(RuffyScripter scripter) { + scripter.verifyMenuIsDisplayed(MenuType.MAIN_MENU); + scripter.navigateToMenu(MenuType.TBR_MENU); + scripter.verifyMenuIsDisplayed(MenuType.TBR_MENU); + scripter.pressCheckKey(); + scripter.waitForMenuUpdate(); + scripter.verifyMenuIsDisplayed(MenuType.TBR_SET); + } + + private void inputTbrPercentage(RuffyScripter scripter) { + scripter.verifyMenuIsDisplayed(MenuType.TBR_SET); + long currentPercent = readDisplayedTbrPercentage(scripter); + 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); + } + log.debug("Pressing " + (increasePercentage ? "up" : "down") + " " + percentageSteps + " times"); + for (int i = 0; i < percentageSteps; i++) { + scripter.verifyMenuIsDisplayed(MenuType.TBR_SET); + if (increasePercentage) scripter.pressUpKey(); + else scripter.pressDownKey(); + SystemClock.sleep(100); + log.debug("Push #" + (i + 1)); + } + // 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 verifyDisplayedTbrPercentage(RuffyScripter scripter) { + scripter.verifyMenuIsDisplayed(MenuType.TBR_SET); + long displayedPercentage = readDisplayedTbrPercentage(scripter); + if (displayedPercentage != percentage) { + log.debug("Final displayed TBR percentage: " + displayedPercentage); + throw new CommandException().message("Failed to set TBR percentage"); + } + + // check again to ensure the displayed value hasn't change due to due scrolling taking extremely long + SystemClock.sleep(2000); + long refreshedDisplayedTbrPecentage = readDisplayedTbrPercentage(scripter); + if (displayedPercentage != refreshedDisplayedTbrPecentage) { + throw new CommandException().message("Failed to set TBR percentage: " + + "percentage changed after input stopped from " + + displayedPercentage + " -> " + refreshedDisplayedTbrPecentage); + } + } + + private long readDisplayedTbrPercentage(RuffyScripter scripter) { + // TODO v2 add timeout? Currently the command execution timeout would trigger if exceeded + Object percentageObj = scripter.currentMenu.getAttribute(MenuAttribute.BASAL_RATE); + // this as a bit hacky, the display value is blinking, so we might catch that, so + // keep trying till we get the Double we want + while (!(percentageObj instanceof Double)) { + scripter.waitForMenuUpdate(); + percentageObj = scripter.currentMenu.getAttribute(MenuAttribute.BASAL_RATE); + } + return ((Double) percentageObj).longValue(); + } + + private void inputTbrDuration(RuffyScripter scripter) { + scripter.verifyMenuIsDisplayed(MenuType.TBR_DURATION); + long currentDuration = readDisplayedTbrDuration(scripter); + if (currentDuration % 15 != 0) { + // The duration displayed is how long an active TBR will still run, + // which might be something like 0:13, hence not in 15 minute steps. + // Pressing up will go to the next higher 15 minute step. + // Don't press down, from 0:13 it can't go down, so press up. + // Pressing up from 23:59 works to go to 24:00. + scripter.verifyMenuIsDisplayed(MenuType.TBR_DURATION); + scripter.pressUpKey(); + scripter.waitForMenuUpdate(); + currentDuration = readDisplayedTbrDuration(scripter); + } + log.debug("Current TBR duration: " + currentDuration); + long durationChange = duration - currentDuration; + long durationSteps = durationChange / 15; + boolean increaseDuration = true; + if (durationSteps < 0) { + increaseDuration = false; + durationSteps = Math.abs(durationSteps); + } + log.debug("Pressing " + (increaseDuration ? "up" : "down") + " " + durationSteps + " times"); + for (int i = 0; i < durationSteps; i++) { + scripter.verifyMenuIsDisplayed(MenuType.TBR_DURATION); + if (increaseDuration) scripter.pressUpKey(); + else scripter.pressDownKey(); + SystemClock.sleep(100); + log.debug("Push #" + (i + 1)); + } + // 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 verifyDisplayedTbrDuration(RuffyScripter scripter) { + scripter.verifyMenuIsDisplayed(MenuType.TBR_DURATION); + long displayedDuration = readDisplayedTbrDuration(scripter); + if (displayedDuration != duration) { + log.debug("Final displayed TBR duration: " + displayedDuration); + throw new CommandException().message("Failed to set TBR duration"); + } + + // check again to ensure the displayed value hasn't change due to due scrolling taking extremely long + SystemClock.sleep(2000); + long refreshedDisplayedTbrDuration = readDisplayedTbrDuration(scripter); + if (displayedDuration != refreshedDisplayedTbrDuration) { + throw new CommandException().message("Failed to set TBR duration: " + + "duration changed after input stopped from " + + displayedDuration + " -> " + refreshedDisplayedTbrDuration); + } + } + + private long readDisplayedTbrDuration(RuffyScripter scripter) { + // TODO v2 add timeout? Currently the command execution timeout would trigger if exceeded + scripter.verifyMenuIsDisplayed(MenuType.TBR_DURATION); + Object durationObj = scripter.currentMenu.getAttribute(MenuAttribute.RUNTIME); + // this as a bit hacky, the display value is blinking, so we might catch that, so + // keep trying till we get the Double we want + while (!(durationObj instanceof MenuTime)) { + scripter.waitForMenuUpdate(); + durationObj = scripter.currentMenu.getAttribute(MenuAttribute.RUNTIME); + } + MenuTime duration = (MenuTime) durationObj; + return duration.getHour() * 60 + duration.getMinute(); + } + + private void cancelTbrAndConfirmCancellationWarning(RuffyScripter scripter) { + // confirm entered TBR + scripter.verifyMenuIsDisplayed(MenuType.TBR_SET); + scripter.pressCheckKey(); + + // 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 + // is raised and if so dismiss it + long inFiveSeconds = System.currentTimeMillis() + 5 * 1000; + boolean alertProcessed = false; + while (System.currentTimeMillis() < inFiveSeconds && !alertProcessed) { + if (scripter.currentMenu.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.currentMenu.getAttribute(MenuAttribute.MESSAGE); + if (!errorMsg.equals("TBR CANCELLED")) { + throw new CommandException().success(false).enacted(false) + .message("An alert other than the expected TBR CANCELLED was raised by the pump: " + + errorMsg + ". Please check the pump."); + } + // confirm "TBR CANCELLED" alert + scripter.verifyMenuIsDisplayed(MenuType.WARNING_OR_ERROR); + scripter.pressCheckKey(); + // dismiss "TBR CANCELLED" alert + scripter.verifyMenuIsDisplayed(MenuType.WARNING_OR_ERROR); + scripter.pressCheckKey(); + scripter.waitForMenuToBeLeft(MenuType.WARNING_OR_ERROR); + alertProcessed = true; + } + SystemClock.sleep(10); + } + } + + private void verifyMainMenuShowsNoActiveTbr(RuffyScripter scripter) { + scripter.verifyMenuIsDisplayed(MenuType.MAIN_MENU); + Double tbrPercentage = (Double) scripter.currentMenu.getAttribute(MenuAttribute.TBR); + boolean runtimeDisplayed = scripter.currentMenu.attributes().contains(MenuAttribute.RUNTIME); + if (tbrPercentage != 100 || runtimeDisplayed) { + throw new CommandException().message("Cancelling TBR failed, TBR is still set according to MAIN_MENU"); + } + } + + private void verifyMainMenuShowsExpectedTbrActive(RuffyScripter scripter) { + scripter.verifyMenuIsDisplayed(MenuType.MAIN_MENU); + // new TBR set; percentage and duration must be displayed ... + if (!scripter.currentMenu.attributes().contains(MenuAttribute.TBR) || + !scripter.currentMenu.attributes().contains(MenuAttribute.RUNTIME)) { + throw new CommandException().message("Setting TBR failed, according to MAIN_MENU no TBR is active"); + } + Double mmTbrPercentage = (Double) scripter.currentMenu.getAttribute(MenuAttribute.TBR); + MenuTime mmTbrDuration = (MenuTime) scripter.currentMenu.getAttribute(MenuAttribute.RUNTIME); + // ... and be the same as what we set + // note that displayed duration might have already counted down, e.g. from 30 minutes to + // 29 minutes and 59 seconds, so that 29 minutes are displayed + int mmTbrDurationInMinutes = mmTbrDuration.getHour() * 60 + mmTbrDuration.getMinute(); + if (mmTbrPercentage != percentage || (mmTbrDurationInMinutes != duration && mmTbrDurationInMinutes + 1 != duration)) { + throw new CommandException().message("Setting TBR failed, TBR in MAIN_MENU differs from expected"); + } + } + + @Override + public String toString() { + return getClass().getSimpleName() + "{" + + "percentage=" + percentage + + ", duration=" + duration + + '}'; + } +} diff --git a/app/src/main/java/info/nightscout/androidaps/Config.java b/app/src/main/java/info/nightscout/androidaps/Config.java index 7f3aad2f93..d5d056da61 100644 --- a/app/src/main/java/info/nightscout/androidaps/Config.java +++ b/app/src/main/java/info/nightscout/androidaps/Config.java @@ -47,4 +47,10 @@ public class Config { public static final boolean logDanaBTComm = true; public static final boolean logDanaMessageDetail = true; public static final boolean logDanaSerialEngine = true; + + // Combo specific + /** use alternate SetTbrCommand (uses the initial implementation) */ + public static final boolean comboUseAlternateSetTbrCommand = false; + /** very quick hack to split up bolus into 2 U parts, spaced roughly 45s apart */ + public static final boolean comboSplitBoluses = false; } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ComboPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ComboPlugin.java index a0bc347ea9..2f61f95e80 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ComboPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ComboPlugin.java @@ -25,6 +25,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; @@ -33,8 +34,9 @@ import de.jotomo.ruffyscripter.commands.CommandResult; import de.jotomo.ruffyscripter.commands.DetermineCapabilitiesCommand; import de.jotomo.ruffyscripter.commands.ReadPumpStateCommand; import de.jotomo.ruffyscripter.commands.SetTbrCommand; -import de.jotomo.ruffyscripter.PumpState; +import de.jotomo.ruffyscripter.commands.SetTbrCommandAlt; 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; @@ -367,28 +369,36 @@ public class ComboPlugin implements PluginBase, PumpInterface { if (detailedBolusInfo.insulin > 0 || detailedBolusInfo.carbs > 0) { if (detailedBolusInfo.insulin > 0) { // bolus needed, ask pump to deliver it - CommandResult bolusCmdResult = runCommand(new BolusCommand(detailedBolusInfo.insulin)); - PumpEnactResult pumpEnactResult = new PumpEnactResult(); - pumpEnactResult.success = bolusCmdResult.success; - pumpEnactResult.enacted = bolusCmdResult.enacted; - pumpEnactResult.comment = bolusCmdResult.message; - - // if enacted, add bolus and carbs to treatment history - if (pumpEnactResult.enacted) { - // TODO if no error occurred, the requested bolus is what the pump delievered, - // that has been checked. If an error occurred, we should check how much insulin - // was delivered, e.g. when the cartridge went empty mid-bolus - // For the first iteration, the alert the pump raises must suffice - pumpEnactResult.bolusDelivered = detailedBolusInfo.insulin; - pumpEnactResult.carbsDelivered = detailedBolusInfo.carbs; - - detailedBolusInfo.date = bolusCmdResult.completionTime; - MainApp.getConfigBuilder().addToHistoryTreatment(detailedBolusInfo); + if (!Config.comboSplitBoluses) { + return deliverBolus(detailedBolusInfo); } else { + // split up bolus into 2 U parts + PumpEnactResult pumpEnactResult = new PumpEnactResult(); + pumpEnactResult.success = true; + pumpEnactResult.enacted = true; pumpEnactResult.bolusDelivered = 0d; - pumpEnactResult.carbsDelivered = 0d; + pumpEnactResult.carbsDelivered = detailedBolusInfo.carbs; + pumpEnactResult.comment = MainApp.instance().getString(R.string.virtualpump_resultok); + + double remainingBolus = detailedBolusInfo.insulin; + int split = 1; + while (remainingBolus > 0.05) { + double bolus = remainingBolus > 2 ? 2 : remainingBolus; + DetailedBolusInfo bolusInfo = new DetailedBolusInfo(); + bolusInfo.insulin = bolus; + bolusInfo.isValid = false; + log.debug("Delivering split bolus #" + split + " with " + bolus + " U"); + PumpEnactResult bolusResult = deliverBolus(bolusInfo); + if (!bolusResult.success) { + return bolusResult; + } + pumpEnactResult.bolusDelivered += bolus; + remainingBolus -= 2; + split++; + } + MainApp.getConfigBuilder().addToHistoryTreatment(detailedBolusInfo); + return pumpEnactResult; } - return pumpEnactResult; } else { // no bolus required, carb only treatment @@ -418,6 +428,32 @@ public class ComboPlugin implements PluginBase, PumpInterface { } } + @NonNull + private PumpEnactResult deliverBolus(DetailedBolusInfo detailedBolusInfo) { + CommandResult bolusCmdResult = runCommand(new BolusCommand(detailedBolusInfo.insulin)); + PumpEnactResult pumpEnactResult = new PumpEnactResult(); + pumpEnactResult.success = bolusCmdResult.success; + pumpEnactResult.enacted = bolusCmdResult.enacted; + pumpEnactResult.comment = bolusCmdResult.message; + + // if enacted, add bolus and carbs to treatment history + if (pumpEnactResult.enacted) { + // TODO if no error occurred, the requested bolus is what the pump delievered, + // that has been checked. If an error occurred, we should check how much insulin + // was delivered, e.g. when the cartridge went empty mid-bolus + // For the first iteration, the alert the pump raises must suffice + pumpEnactResult.bolusDelivered = detailedBolusInfo.insulin; + pumpEnactResult.carbsDelivered = detailedBolusInfo.carbs; + + detailedBolusInfo.date = bolusCmdResult.completionTime; + MainApp.getConfigBuilder().addToHistoryTreatment(detailedBolusInfo); + } else { + pumpEnactResult.bolusDelivered = 0d; + pumpEnactResult.carbsDelivered = 0d; + } + return pumpEnactResult; + } + private CommandResult runCommand(Command command) { if (ruffyScripter == null) { String msg = "No connection to ruffy. Pump control not available."; @@ -492,12 +528,17 @@ public class ComboPlugin implements PluginBase, PumpInterface { adjustedPercent = rounded.intValue(); } - CommandResult commandResult = runCommand(new SetTbrCommand(adjustedPercent, durationInMinutes)); + Command cmd = !Config.comboUseAlternateSetTbrCommand + ? new SetTbrCommand(adjustedPercent, durationInMinutes) + : new SetTbrCommandAlt(adjustedPercent, durationInMinutes); + CommandResult commandResult = runCommand(cmd); if (commandResult.enacted) { TemporaryBasal tempStart = new TemporaryBasal(commandResult.completionTime); // TODO commandResult.state.tbrRemainingDuration might already display 29 if 30 was set, since 29:59 is shown as 29 ... // we should check this, but really ... something must be really screwed up if that number was anything different + // TODO actually ... might setting 29 help with gaps between TBRs? w/o the hack in TemporaryBasal? + // nah, fucks up duration in treatment history tempStart.durationInMinutes = durationInMinutes; tempStart.percentRate = adjustedPercent; tempStart.isAbsolute = false;