From 83be0a8315d5aea47f99113e24e8f88d0c990488 Mon Sep 17 00:00:00 2001 From: Johannes Mockenhaupt Date: Wed, 22 Nov 2017 22:14:54 +0100 Subject: [PATCH] Setting basal rate on pump (combo side). --- .../plugins/PumpCombo/ComboPlugin.java | 30 +++- .../de/jotomo/ruffy/spi/BasalProfile.java | 7 +- .../jotomo/ruffyscripter/RuffyScripter.java | 3 +- .../commands/ReadBasalProfileCommand.java | 1 + .../commands/SetBasalProfileCommand.java | 142 ++++++++++++++++++ 5 files changed, 178 insertions(+), 5 deletions(-) create mode 100644 ruffyscripter/src/main/java/de/jotomo/ruffyscripter/commands/SetBasalProfileCommand.java 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 b26643180b..c575273bbd 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 @@ -12,6 +12,7 @@ import java.util.Date; import java.util.List; import java.util.Objects; +import de.jotomo.ruffy.spi.BasalProfile; import de.jotomo.ruffy.spi.BolusProgressReporter; import de.jotomo.ruffy.spi.CommandResult; import de.jotomo.ruffy.spi.PumpState; @@ -215,13 +216,36 @@ public class ComboPlugin implements PluginBase, PumpInterface, ConstraintsInterf } @Override - public int setNewBasalProfile(Profile profile) { - return PumpInterface.FAILED; + public synchronized int setNewBasalProfile(Profile profile) { + BasalProfile requestedBasalProfile = convertProfileToComboProfile(profile); + if (pump.basalProfile.equals(requestedBasalProfile)) { + return PumpInterface.NOT_NEEDED; + } + CommandResult setResult = runCommand(MainApp.sResources.getString(R.string.combo_activity_setting_basal_profile), 0, + () -> ruffyScripter.setBasalProfile(requestedBasalProfile)); + if (!setResult.success) { + return PumpInterface.FAILED; + } + + CommandResult readResult = runCommand(MainApp.sResources.getString(R.string.combo_activity_setting_basal_profile), 0, + ruffyScripter::readBasalProfile); + + return readResult.success && readResult.basalProfile.equals(requestedBasalProfile) + ? PumpInterface.SUCCESS : PumpInterface.FAILED; } @Override public boolean isThisProfileSet(Profile profile) { - return true; + return pump.basalProfile.equals(convertProfileToComboProfile(profile)); + } + + @NonNull + private BasalProfile convertProfileToComboProfile(Profile profile) { + BasalProfile basalProfile = new BasalProfile(); + for (int i = 0; i < 24; i++) { + basalProfile.hourlyRates[i] = profile.getBasal(Integer.valueOf(i * 60 * 60)); + } + return basalProfile; } @NonNull diff --git a/ruffy-spi/src/main/java/de/jotomo/ruffy/spi/BasalProfile.java b/ruffy-spi/src/main/java/de/jotomo/ruffy/spi/BasalProfile.java index ff8f2cf07f..c1ead296db 100644 --- a/ruffy-spi/src/main/java/de/jotomo/ruffy/spi/BasalProfile.java +++ b/ruffy-spi/src/main/java/de/jotomo/ruffy/spi/BasalProfile.java @@ -20,7 +20,12 @@ public class BasalProfile { BasalProfile that = (BasalProfile) o; - return Arrays.equals(hourlyRates, that.hourlyRates); + for(int i = 0; i < 23; i++) { + if (Math.abs(hourlyRates[i] - that.hourlyRates[i]) > 0.01) { + return false; + } + } + return true; } @Override diff --git a/ruffyscripter/src/main/java/de/jotomo/ruffyscripter/RuffyScripter.java b/ruffyscripter/src/main/java/de/jotomo/ruffyscripter/RuffyScripter.java index 68fbaf0f35..d03847810f 100644 --- a/ruffyscripter/src/main/java/de/jotomo/ruffyscripter/RuffyScripter.java +++ b/ruffyscripter/src/main/java/de/jotomo/ruffyscripter/RuffyScripter.java @@ -41,6 +41,7 @@ import de.jotomo.ruffyscripter.commands.ReadBasalProfileCommand; import de.jotomo.ruffyscripter.commands.ReadHistoryCommand; import de.jotomo.ruffyscripter.commands.ReadPumpStateCommand; import de.jotomo.ruffyscripter.commands.ReadReservoirLevelAndLastBolus; +import de.jotomo.ruffyscripter.commands.SetBasalProfileCommand; import de.jotomo.ruffyscripter.commands.SetDateAndTimeCommand; import de.jotomo.ruffyscripter.commands.SetTbrCommand; @@ -756,7 +757,7 @@ public class RuffyScripter implements RuffyCommands { @Override public CommandResult setBasalProfile(BasalProfile basalProfile) { - throw new CommandException("Setting basal profile is not supported"); + return runCommand(new SetBasalProfileCommand(basalProfile)); } @Override diff --git a/ruffyscripter/src/main/java/de/jotomo/ruffyscripter/commands/ReadBasalProfileCommand.java b/ruffyscripter/src/main/java/de/jotomo/ruffyscripter/commands/ReadBasalProfileCommand.java index 5cd4c32b9a..51358532c1 100644 --- a/ruffyscripter/src/main/java/de/jotomo/ruffyscripter/commands/ReadBasalProfileCommand.java +++ b/ruffyscripter/src/main/java/de/jotomo/ruffyscripter/commands/ReadBasalProfileCommand.java @@ -33,6 +33,7 @@ public class ReadBasalProfileCommand extends BaseCommand { menu = scripter.getCurrentMenu(); } scripter.verifyMenuIsDisplayed(MenuType.BASAL_SET); + MenuTime startTime = (MenuTime) scripter.getCurrentMenu().getAttribute(MenuAttribute.BASAL_START); if (i != startTime.getHour()) { throw new CommandException("Attempting to read basal rate for hour " + i + ", but hour " + startTime.getHour() + " is displayed"); diff --git a/ruffyscripter/src/main/java/de/jotomo/ruffyscripter/commands/SetBasalProfileCommand.java b/ruffyscripter/src/main/java/de/jotomo/ruffyscripter/commands/SetBasalProfileCommand.java new file mode 100644 index 0000000000..5c44fbf2c5 --- /dev/null +++ b/ruffyscripter/src/main/java/de/jotomo/ruffyscripter/commands/SetBasalProfileCommand.java @@ -0,0 +1,142 @@ +package de.jotomo.ruffyscripter.commands; + +import android.os.SystemClock; + +import org.monkey.d.ruffy.ruffy.driver.display.Menu; +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 de.jotomo.ruffy.spi.BasalProfile; + +public class SetBasalProfileCommand extends BaseCommand { + private static final Logger log = LoggerFactory.getLogger(SetBasalProfileCommand.class); + + private final BasalProfile basalProfile; + + public SetBasalProfileCommand(BasalProfile basalProfile) { + this.basalProfile = basalProfile; + } + + @Override + public void execute() { + scripter.navigateToMenu(MenuType.BASAL_1_MENU); + scripter.verifyMenuIsDisplayed(MenuType.BASAL_1_MENU); + scripter.pressCheckKey(); + + // summary screen is shown; press menu to page through hours, wraps around to summary; + scripter.verifyMenuIsDisplayed(MenuType.BASAL_TOTAL); + for (int i = 0; i < 24; i++) { + scripter.pressMenuKey(); + Menu menu = scripter.getCurrentMenu(); + while (menu.getType() != MenuType.BASAL_SET + || ((MenuTime) scripter.getCurrentMenu().getAttribute(MenuAttribute.BASAL_START)).getHour() != i) { + scripter.waitForScreenUpdate(); + menu = scripter.getCurrentMenu(); + } + scripter.verifyMenuIsDisplayed(MenuType.BASAL_SET); + + double requestedRate = basalProfile.hourlyRates[i]; + Boolean increasing = inputBasalRate(requestedRate); + if (increasing != null) { + verifyDisplayedRate(requestedRate, increasing); + } + + log.debug("Set basal profile, hour " + i + ": " + requestedRate); + } + + scripter.pressCheckKey(); + scripter.verifyMenuIsDisplayed(MenuType.BASAL_TOTAL); + + // check total basal total on pump matches requested total + Double pumpTotal = (Double) scripter.getCurrentMenu().getAttribute(MenuAttribute.BASAL_TOTAL); + Double requestedTotal = 0d; + for (int i = 0; i < 24; i++) { + requestedTotal += basalProfile.hourlyRates[i]; + } + if (Math.abs(pumpTotal - requestedTotal) > 0.05) { // TODO leniency actually needed? + throw new CommandException("Basal total of " + pumpTotal + " differs from requested total of " + requestedTotal); + } + + scripter.returnToRootMenu(); + scripter.verifyRootMenuIsDisplayed(); + + result.success(true).basalProfile(basalProfile); + } + + // TODO boolean to indicate, up, down or neither? yikes + private Boolean inputBasalRate(double requestedRate) { + // 0.05 steps; jumps to 0.10 steps if buttons are kept pressed, so there's room for optimization + double currentRate = scripter.readBlinkingValue(Double.class, MenuAttribute.BASAL_RATE); + if (Math.abs(currentRate - requestedRate) < 0.01) { + return null; + } + log.debug("Current rate: " + currentRate + " = requested => " + requestedRate); + double change = requestedRate - currentRate; + long steps = Math.round(change * 100); + boolean increasing = steps > 0; + log.debug("Pressing " + (increasing ? "up" : "down") + " " + Math.abs(steps) + " times"); + for (int i = 0; i < Math.abs(steps); i++) { + scripter.verifyMenuIsDisplayed(MenuType.BASAL_SET); + log.debug("Push #" + (i + 1) + "/" + Math.abs(steps)); + if (increasing) scripter.pressUpKey(); + else scripter.pressDownKey(); + SystemClock.sleep(50); + } + return increasing; + } + + private void verifyDisplayedRate(double requestedRate, boolean increasingPercentage) { + scripter.verifyMenuIsDisplayed(MenuType.BASAL_SET); + // wait up to 5s for any scrolling to finish + double displayedRate = scripter.readBlinkingValue(Double.class, MenuAttribute.BASAL_RATE); + long timeout = System.currentTimeMillis() + 10 * 1000; + while (timeout > System.currentTimeMillis() + && ((increasingPercentage && displayedRate < requestedRate) + || (!increasingPercentage && displayedRate > requestedRate))) { + log.debug("Waiting for pump to process scrolling input for rate, current: " + + displayedRate + ", desired: " + requestedRate + ", scrolling " + + (increasingPercentage ? "up" : "down")); + scripter.waitForScreenUpdate(); + displayedRate = scripter.readBlinkingValue(Double.class, MenuAttribute.BASAL_RATE); + } + log.debug("Final displayed basal rate: " + displayedRate); + if (displayedRate != requestedRate) { + throw new CommandException("Failed to set basal rate, requested: " + + requestedRate + ", actual: " + displayedRate); + } + + // 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.BASAL_SET); + double refreshedDisplayedRate = scripter.readBlinkingValue(Double.class, MenuAttribute.BASAL_RATE); + if (displayedRate != refreshedDisplayedRate) { + throw new CommandException("Failed to set basal rate: " + + "percentage changed after input stopped from " + + displayedRate + " -> " + refreshedDisplayedRate); + } + } + + @Override + public List validateArguments() { + ArrayList violations = new ArrayList<>(); + if (basalProfile == null) { + violations.add("No basal profile supplied"); + } + + return violations; + } + + @Override + public String toString() { + return "SetBasalProfileCommand{" + + "basalProfile=" + basalProfile + + '}'; + } +}