diff --git a/app/build.gradle b/app/build.gradle index 9a994d38cb..174799d5c4 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -44,7 +44,7 @@ android { minSdkVersion 21 targetSdkVersion 23 versionCode 1500 - version "1.5h-combo-dev" + version "1.5h-combo-dev-jotomo" buildConfigField "String", "VERSION", '"' + version + '"' buildConfigField "String", "BUILDVERSION", generateGitBuild() } diff --git a/app/src/androidTest/java/ruffyscripter/RuffyScripterInstrumentedTest.java b/app/src/androidTest/java/ruffyscripter/RuffyScripterInstrumentedTest.java index afc17ff868..894eb292b4 100644 --- a/app/src/androidTest/java/ruffyscripter/RuffyScripterInstrumentedTest.java +++ b/app/src/androidTest/java/ruffyscripter/RuffyScripterInstrumentedTest.java @@ -19,7 +19,7 @@ import org.slf4j.LoggerFactory; import de.jotomo.ruffyscripter.RuffyScripter; import de.jotomo.ruffyscripter.commands.CommandResult; -import de.jotomo.ruffyscripter.commands.ReadPumpStateCommand; +import de.jotomo.ruffyscripter.commands.GetPumpStateCommand; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertNotNull; @@ -99,7 +99,7 @@ public class RuffyScripterInstrumentedTest { // TODO now, how to get ruffy fired up in this test? @Test public void readPumpState() throws Exception { - CommandResult commandResult = ruffyScripter.runCommand(new ReadPumpStateCommand()); + CommandResult commandResult = ruffyScripter.runCommand(new GetPumpStateCommand()); assertTrue(commandResult.success); assertFalse(commandResult.enacted); assertNotNull(commandResult.state); diff --git a/app/src/main/aidl/org/monkey/d/ruffy/ruffy/driver/IRTHandler.aidl b/app/src/main/aidl/org/monkey/d/ruffy/ruffy/driver/IRTHandler.aidl index b6a07226b8..996b10b666 100644 --- a/app/src/main/aidl/org/monkey/d/ruffy/ruffy/driver/IRTHandler.aidl +++ b/app/src/main/aidl/org/monkey/d/ruffy/ruffy/driver/IRTHandler.aidl @@ -10,17 +10,12 @@ interface IRTHandler { void fail(String message); void requestBluetooth(); - boolean canDisconnect(); void rtStopped(); void rtStarted(); void rtClearDisplay(); void rtUpdateDisplay(in byte[] quarter, int which); - void rtDisplayHandleMenu(in Menu menu, in int sequence); - void rtDisplayHandleNoMenu(in int sequence); - - void keySent(in int sequence); - - String getServiceIdentifier(); + void rtDisplayHandleMenu(in Menu menu); + void rtDisplayHandleNoMenu(); } diff --git a/app/src/main/aidl/org/monkey/d/ruffy/ruffy/driver/IRuffyService.aidl b/app/src/main/aidl/org/monkey/d/ruffy/ruffy/driver/IRuffyService.aidl index f4879445b7..6c988aa038 100644 --- a/app/src/main/aidl/org/monkey/d/ruffy/ruffy/driver/IRuffyService.aidl +++ b/app/src/main/aidl/org/monkey/d/ruffy/ruffy/driver/IRuffyService.aidl @@ -6,22 +6,18 @@ import org.monkey.d.ruffy.ruffy.driver.IRTHandler; interface IRuffyService { - void addHandler(IRTHandler handler); - void removeHandler(IRTHandler handler); + void setHandler(IRTHandler handler); /** Connect to the pump * * @return 0 if successful, -1 otherwise */ - int doRTConnect(IRTHandler handler); + int doRTConnect(); /** Disconnect from the pump */ - void doRTDisconnect(IRTHandler handler); + void doRTDisconnect(); - /*What's the meaning of 'changed'? - * changed means if a button state has been changed, like btton pressed is a change and button release another*/ void rtSendKey(byte keyCode, boolean changed); void resetPairing(); boolean isConnected(); - boolean isBoundToPump(); } diff --git a/app/src/main/aidl/org/monkey/d/ruffy/ruffy/driver/package-info.java b/app/src/main/aidl/org/monkey/d/ruffy/ruffy/driver/package-info.java new file mode 100644 index 0000000000..ab1def7407 --- /dev/null +++ b/app/src/main/aidl/org/monkey/d/ruffy/ruffy/driver/package-info.java @@ -0,0 +1 @@ +//b916a900c0899ef58ad58c7427d1c30d3c8731f4 \ No newline at end of file diff --git a/app/src/main/java/de/jotomo/ruffyscripter/History.java b/app/src/main/java/de/jotomo/ruffyscripter/History.java index 49e34d438f..cfe514565b 100644 --- a/app/src/main/java/de/jotomo/ruffyscripter/History.java +++ b/app/src/main/java/de/jotomo/ruffyscripter/History.java @@ -1,5 +1,7 @@ package de.jotomo.ruffyscripter; -/** The history data read from "My data" */ +/** + * The history data read from "My data" + */ public class History { } diff --git a/app/src/main/java/de/jotomo/ruffyscripter/PumpCapabilities.java b/app/src/main/java/de/jotomo/ruffyscripter/PumpCapabilities.java deleted file mode 100644 index 1270851ea0..0000000000 --- a/app/src/main/java/de/jotomo/ruffyscripter/PumpCapabilities.java +++ /dev/null @@ -1,23 +0,0 @@ -package de.jotomo.ruffyscripter; - -/** - * Created by adrian on 26/07/17. - * - * Contains the capabilities of the current pump model. - */ - -public class PumpCapabilities { - public long maxTempPercent; - - public PumpCapabilities maxTempPercent(long maxTempPercent) { - this.maxTempPercent = maxTempPercent; - return this; - } - - @Override - public String toString() { - return "PumpCapabilities{" + - "maxTempPercent=" + maxTempPercent + - '}'; - } -} diff --git a/app/src/main/java/de/jotomo/ruffyscripter/PumpState.java b/app/src/main/java/de/jotomo/ruffyscripter/PumpState.java index 4d519b4669..9323ab71a5 100644 --- a/app/src/main/java/de/jotomo/ruffyscripter/PumpState.java +++ b/app/src/main/java/de/jotomo/ruffyscripter/PumpState.java @@ -11,16 +11,18 @@ public class PumpState { public int tbrPercent = -1; public double tbrRate = -1; public int tbrRemainingDuration = -1; - /** This is the error message (if any) displayed by the pump if there is an alarm, - e.g. if a "TBR cancelled alarm" is active, the value will be "TBR CANCELLED". - Generally, an error code is also displayed, but it flashes and it might take - longer to read that and the pump connection gets interrupted if we're not - reacting quickly. + /** + * This is the error message (if any) displayed by the pump if there is an alarm, + * e.g. if a "TBR cancelled alarm" is active, the value will be "TBR CANCELLED". + * Generally, an error code is also displayed, but it flashes and it might take + * longer to read that and the pump connection gets interrupted if we're not + * reacting quickly. */ public String errorMsg; public boolean suspended; public boolean lowBattery; - public int insulinState; + public int insulinState = -1; + public int reservoirLevel = -1; public PumpState tbrActive(boolean tbrActive) { this.tbrActive = tbrActive; @@ -52,6 +54,21 @@ public class PumpState { return this; } + public PumpState lowBattery(boolean lowBattery) { + this.lowBattery = lowBattery; + return this; + } + + public PumpState insulinState(int insulinState) { + this.insulinState = insulinState; + return this; + } + + public PumpState reservoirLevel(int reservoirLevel) { + this.reservoirLevel = reservoirLevel; + return this; + } + @Override public String toString() { return "PumpState{" + @@ -63,6 +80,7 @@ public class PumpState { ", suspended=" + suspended + ", lowBattery=" + lowBattery + ", insulinState=" + insulinState + + ", reversoirLevel=" + reservoirLevel + ", timestamp=" + timestamp + '}'; } diff --git a/app/src/main/java/de/jotomo/ruffyscripter/RuffyScripter.java b/app/src/main/java/de/jotomo/ruffyscripter/RuffyScripter.java index 3f8a99f665..2934ee7eed 100644 --- a/app/src/main/java/de/jotomo/ruffyscripter/RuffyScripter.java +++ b/app/src/main/java/de/jotomo/ruffyscripter/RuffyScripter.java @@ -19,7 +19,7 @@ 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.ReadPumpStateCommand; +import de.jotomo.ruffyscripter.commands.GetPumpStateCommand; // 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 @@ -33,33 +33,22 @@ import de.jotomo.ruffyscripter.commands.ReadPumpStateCommand; public class RuffyScripter { private static final Logger log = LoggerFactory.getLogger(RuffyScripter.class); - private IRuffyService ruffyService; - private final long connectionTimeOutMs = 5000; private String unrecoverableError = null; - public volatile Menu currentMenu; + private volatile Menu currentMenu; private volatile long menuLastUpdated = 0; private volatile long lastCmdExecutionTime; private volatile Command activeCmd = null; - private volatile boolean connected = false; - private volatile long lastDisconnected = 0; - private boolean started = false; - private final Object keylock = new Object(); - private int keynotwait = 0; - private final Object screenlock = new Object(); - public RuffyScripter() { - - } - public void start(IRuffyService newService) { try { +/* if (ruffyService != null) { try { ruffyService.removeHandler(mHandler); @@ -67,6 +56,7 @@ public class RuffyScripter { // ignore } } +*/ if (newService != null) { this.ruffyService = newService; // TODO this'll be done better in v2 via ConnectionManager @@ -74,52 +64,38 @@ public class RuffyScripter { idleDisconnectMonitorThread.start(); } started = true; - newService.addHandler(mHandler); + newService.setHandler(mHandler); } } catch (Exception e) { + log.error("Unhandled exception starting RuffyScripter", e); throw new RuntimeException(e); } } - public void stop() { - if (started) { - started = false; - // TODO ruffy removes dead handlers automatically by now. - // still, check this when going through recovery logic -/* try { - ruffyService.removeHandler(mHandler); - } catch (RemoteException e) { - log.warn("Removing IRTHandler from Ruffy service failed, ignoring", e); - }*/ - } - } - public boolean isRunning() { return started; } - private boolean canDisconnect = false; + private volatile boolean connected = false; + private volatile long lastDisconnected = 0; + private Thread idleDisconnectMonitorThread = new Thread(new Runnable() { @Override public void run() { - long lastDisconnect = System.currentTimeMillis(); while (unrecoverableError == null) { try { long now = System.currentTimeMillis(); + long connectionTimeOutMs = 5000; if (connected && activeCmd == null && now > lastCmdExecutionTime + connectionTimeOutMs // don't disconnect too frequently, confuses ruffy? - && now > lastDisconnect + 15 * 1000) { + && now > lastDisconnected + 15 * 1000) { log.debug("Disconnecting after " + (connectionTimeOutMs / 1000) + "s inactivity timeout"); - lastDisconnect = now; - canDisconnect = true; - ruffyService.doRTDisconnect(mHandler); + lastDisconnected = now; + ruffyService.doRTDisconnect(); connected = false; - lastDisconnect = System.currentTimeMillis(); // don't attempt anything fancy in the next 10s, let the pump settle SystemClock.sleep(10 * 1000); - } else { - canDisconnect = false; } } catch (Exception e) { // TODO do we need to catch this exception somewhere else too? right now it's @@ -151,11 +127,6 @@ public class RuffyScripter { log.trace("Ruffy invoked requestBluetooth callback"); } - @Override - public boolean canDisconnect() throws RemoteException { - return canDisconnect; - } - @Override public void rtStopped() throws RemoteException { log.debug("rtStopped callback invoked"); @@ -178,7 +149,7 @@ public class RuffyScripter { } @Override - public void rtDisplayHandleMenu(Menu menu, int sequence) throws RemoteException { + public void rtDisplayHandleMenu(Menu menu) throws RemoteException { // method is called every ~500ms log.debug("rtDisplayHandleMenu: " + menu); @@ -201,26 +172,9 @@ public class RuffyScripter { } @Override - public void rtDisplayHandleNoMenu(int sequence) throws RemoteException { + public void rtDisplayHandleNoMenu() throws RemoteException { log.debug("rtDisplayHandleNoMenu callback invoked"); } - - - @Override - public void keySent(int sequence) throws RemoteException { - synchronized (keylock) { - if (keynotwait > 0) - keynotwait--; - else - keylock.notify(); - } - } - - @Override - public String getServiceIdentifier() throws RemoteException { - return this.toString(); - } - }; public boolean isPumpBusy() { @@ -228,12 +182,14 @@ public class RuffyScripter { } public void unbind() { + /* if (ruffyService != null) try { ruffyService.removeHandler(mHandler); } catch (Exception e) { // ignore } + */ this.ruffyService = null; } @@ -256,8 +212,8 @@ public class RuffyScripter { synchronized (RuffyScripter.class) { try { - long connectStart = System.currentTimeMillis(); activeCmd = cmd; + long connectStart = System.currentTimeMillis(); ensureConnected(); final RuffyScripter scripter = this; final Returnable returnable = new Returnable(); @@ -275,7 +231,7 @@ public class RuffyScripter { return; } } - // Except for ReadPumpStateCommand: fail on all requests if the pump is suspended. + // Except for GetPumpStateCommand: fail on all requests if the pump is suspended. // All trickery of not executing but returning success, so that AAPS can non-sensically TBR away when suspended // are dangerous in the current model where commands are dispatched without checking state beforehand, so // the above tactic would result in boluses not being applied and no warning being raised. @@ -285,7 +241,7 @@ public class RuffyScripter { // esp. with AAPS). // So, for v1, just check the pump is not suspended before executing commands and raising an error for all - // but the ReadPumpStateCommand. For v2, we'll have to come up with a better idea how to deal with the pump's + // but the GetPumpStateCommand. For v2, we'll have to come up with a better idea how to deal with the pump's // state. Maybe having read-only commands and write/treatment commands treated differently, or maybe // build an abstraction on top of the commands, so that e.g. a method on RuffyScripter encapsulates checking // pre-condititions, running one or several commands, checking-post conditions and what not. @@ -293,7 +249,7 @@ public class RuffyScripter { // level to handle state and logic. // For now, when changing cartridges and such: tell AAPS to stop the loop, change cartridge and resume the loop. if (currentMenu == null || currentMenu.getType() == MenuType.STOP) { - if (cmd instanceof ReadPumpStateCommand) { + if (cmd instanceof GetPumpStateCommand) { returnable.cmdResult = new CommandResult().success(true).enacted(false); } else { returnable.cmdResult = new CommandResult().success(false).enacted(false).message("Pump is suspended"); @@ -304,7 +260,8 @@ public class RuffyScripter { PumpState pumpState = readPumpState(); log.debug("Pump state before running command: " + pumpState); long cmdStartTime = System.currentTimeMillis(); - returnable.cmdResult = cmd.execute(scripter, pumpState); + cmd.setScripter(scripter); + returnable.cmdResult = cmd.execute(); long cmdEndTime = System.currentTimeMillis(); returnable.cmdResult.completionTime = cmdEndTime; log.debug("Executing " + cmd + " took " + (cmdEndTime - cmdStartTime) + "ms"); @@ -354,14 +311,14 @@ public class RuffyScripter { returnable.cmdResult.state = readPumpState(); } long connectDurationSec = (executionStart - connectStart) / 1000; - long now = System.currentTimeMillis(); - long executionDurationSec = (now - executionStart) / 1000; + long executionDurationSec = (System.currentTimeMillis() - executionStart) / 1000; returnable.cmdResult.duration = "Connect: " + connectDurationSec + "s, execution: " + executionDurationSec + "s"; log.debug("Command result: " + returnable.cmdResult); return returnable.cmdResult; } catch (CommandException e) { return e.toCommandResult(); } catch (Exception e) { + // TODO detect and report pump warnings/errors differently? log.error("Error in ruffyscripter/ruffy", e); return new CommandResult().exception(e).message("Unexpected exception communication with ruffy: " + e.getMessage()); } finally { @@ -387,13 +344,14 @@ public class RuffyScripter { // When connecting again shortly after disconnecting, the pump sometimes fails // to come up. So for v1, just wait. This happens rarely, so no overly fancy logic needed. // TODO v2 see if we can do this cleaner, use isDisconnected as well maybe. GL#34. + // TODO remove this, will be in the way of quickly reconnecting after an exception and dealing + // with an alarm; we'll then see if the pump can deal with this if (System.currentTimeMillis() < lastDisconnected + 10 * 1000) { log.debug("Waiting 10s to let pump settle after recent disconnect"); SystemClock.sleep(10 * 1000); } - canDisconnect = false; - boolean connectInitSuccessful = ruffyService.doRTConnect(mHandler) == 0; + boolean connectInitSuccessful = ruffyService.doRTConnect() == 0; log.debug("Connect init successful: " + connectInitSuccessful); log.debug("Waiting for first menu update to be sent"); // Note: there was an 'if(currentMenu == null)' around the next call, since @@ -413,6 +371,53 @@ public class RuffyScripter { } } + // TODO v2 add remaining info we can extract from the main menu, low battery and low + // cartridge warnings, running extended bolus (how does that look if a TBR is active as well?) + + /** This reads the state of the, which is whatever is currently displayed on the display, + * no actions are performed. */ + public PumpState readPumpState() { + PumpState state = new PumpState(); + Menu menu = currentMenu; + if (menu == null) { + return new PumpState().errorMsg("Menu is not available"); + } + MenuType menuType = menu.getType(); + if (menuType == MenuType.MAIN_MENU) { + Double tbrPercentage = (Double) menu.getAttribute(MenuAttribute.TBR); + if (tbrPercentage != 100) { + state.tbrActive = true; + Double displayedTbr = (Double) menu.getAttribute(MenuAttribute.TBR); + state.tbrPercent = displayedTbr.intValue(); + MenuTime durationMenuTime = ((MenuTime) menu.getAttribute(MenuAttribute.RUNTIME)); + state.tbrRemainingDuration = durationMenuTime.getHour() * 60 + durationMenuTime.getMinute(); + state.tbrRate = ((double) menu.getAttribute(MenuAttribute.BASAL_RATE)); + } + state.lowBattery = ((boolean) menu.getAttribute(MenuAttribute.LOW_BATTERY)); + state.insulinState = ((int) menu.getAttribute(MenuAttribute.INSULIN_STATE)); + // TODO v2, read current base basal rate, which is shown center when no TBR is active. + // Check if that holds true when an extended bolus is running. + // Add a field to PumpStatus, rather than renaming/overloading tbrRate to mean + // either TBR rate or basal rate depending on whether a TBR is active. + } else if (menuType == MenuType.WARNING_OR_ERROR) { + state.errorMsg = (String) menu.getAttribute(MenuAttribute.MESSAGE); + } else if (menuType == MenuType.STOP) { + state.suspended = true; + state.lowBattery = ((boolean) menu.getAttribute(MenuAttribute.LOW_BATTERY)); + state.insulinState = ((int) menu.getAttribute(MenuAttribute.INSULIN_STATE)); + } else { + StringBuilder sb = new StringBuilder(); + for (MenuAttribute menuAttribute : menu.attributes()) { + sb.append(menuAttribute); + sb.append(": "); + sb.append(menu.getAttribute(menuAttribute)); + sb.append("\n"); + } + state.errorMsg = "Pump is on menu " + menuType + ", listing attributes: \n" + sb.toString(); + } + return state; + } + // below: methods to be used by commands // TODO move into a new Operations(scripter) class commands can delegate to, // so this class can focus on providing a connection to run commands @@ -427,10 +432,50 @@ public class RuffyScripter { public static byte BACK = (byte) 0x33; } + interface Step { + void run(boolean waitForPumpUpdateAfterwards); + } +/* + + private long lastPumpWrite = 0; + + private void wrapNoNotTheSubwayKind(Step step, boolean waitForPumpUpdateAfterwards) { + if (!connected) { + // try to reconnect, with a timeout before the pump raises a menu timeout + // timeout = lastPumpWrite + 15 * 1000 // avoid default pump timeout of 20s + } + step.run(waitForPumpUpdateAfterwards); + // TODO there's a chance the above was not executed by the pump; assume that if we're not + // still connected and abort the command and retry if it it's retryable + // isConnected + lastPumpWrite = System.currentTimeMillis(); + + // TODO: spike: source the ruffy driver package and do away with the remote service + + refuse to debug and fix incomprehensive code that Sandra wrote, can't explain why she + did what she did nor commented on it + + if (!connected) { +// cancelInternal(); +// if (activeCmd.isRetriable) { + } + } +*/ + + // === pump ops === + public Menu getCurrentMenu() { + return currentMenu; + } + public void pressUpKey() { - log.debug("Pressing up key"); - pressKey(Key.UP, 2000); - log.debug("Releasing up key"); +// wrapNoNotTheSubwayKind(new Step() { +// @Override +// public void doStep() { + log.debug("Pressing up key"); + pressKey(Key.UP, 2000); + log.debug("Releasing up key"); +// } +// }); } public void pressDownKey() { @@ -457,6 +502,26 @@ public class RuffyScripter { log.debug("Releasing back key"); } + public void pressKeyMs(final byte key, long ms) { + long stepMs = 100; + try { + log.debug("Scroll: Pressing key for " + ms + " ms with step " + stepMs + " ms"); + ruffyService.rtSendKey(key, true); + ruffyService.rtSendKey(key, false); + while (ms > stepMs) { + SystemClock.sleep(stepMs); + ruffyService.rtSendKey(key, false); + ms -= stepMs; + } + SystemClock.sleep(ms); + ruffyService.rtSendKey(Key.NO_KEY, true); + log.debug("Releasing key"); + } catch (Exception e) { + throw new CommandException().exception(e).message("Error while pressing buttons"); + } + } + + public boolean waitForScreenUpdate(long timeout) { synchronized (screenlock) { try { @@ -532,17 +597,17 @@ public class RuffyScripter { private void pressKey(final byte key, long timeout) { try { ruffyService.rtSendKey(key, true); - //SystemClock.sleep(200); + SystemClock.sleep(200); ruffyService.rtSendKey(Key.NO_KEY, true); - if (timeout > 0) { - synchronized (keylock) { - keylock.wait(timeout); - } - } else { - synchronized (keylock) { - keynotwait++; - } - } +// if (timeout > 0) { +// synchronized (keylock) { +// keylock.wait(timeout); +// } +// } else { +// synchronized (keylock) { +// keynotwait++; +// } +// } } catch (Exception e) { throw new CommandException().exception(e).message("Error while pressing buttons"); } @@ -596,50 +661,23 @@ public class RuffyScripter { } } - // TODO v2 add remaining info we can extract from the main menu, low battery and low - // cartridge warnings, running extended bolus (how does that look if a TBR is active as well?) + @SuppressWarnings("unchecked") + public T readBlinkingValue(Class expectedType, MenuAttribute attribute) { + int retries = 5; + Object value = currentMenu.getAttribute(attribute); + while (!expectedType.isInstance(value)) { + value = currentMenu.getAttribute(attribute); + waitForScreenUpdate(1000); + retries--; + if (retries == 0) { + throw new CommandException().message("Failed to read blinkng value: " + attribute + "=" + value + " type=" + value.getClass()); + } + } + return (T) value; + } - /** This reads the state of the, which is whatever is currently displayed on the display, - * no actions are performed. */ - private PumpState readPumpState() { - PumpState state = new PumpState(); - Menu menu = currentMenu; - if (menu == null) { - return new PumpState().errorMsg("Menu is not available"); - } - MenuType menuType = menu.getType(); - if (menuType == MenuType.MAIN_MENU) { - Double tbrPercentage = (Double) menu.getAttribute(MenuAttribute.TBR); - if (tbrPercentage != 100) { - state.tbrActive = true; - Double displayedTbr = (Double) menu.getAttribute(MenuAttribute.TBR); - state.tbrPercent = displayedTbr.intValue(); - MenuTime durationMenuTime = ((MenuTime) menu.getAttribute(MenuAttribute.RUNTIME)); - state.tbrRemainingDuration = durationMenuTime.getHour() * 60 + durationMenuTime.getMinute(); - state.tbrRate = ((double) menu.getAttribute(MenuAttribute.BASAL_RATE)); - } - state.lowBattery = ((boolean) menu.getAttribute(MenuAttribute.LOW_BATTERY)); - state.insulinState = ((int) menu.getAttribute(MenuAttribute.INSULIN_STATE)); - // TODO v2, read current base basal rate, which is shown center when no TBR is active. - // Check if that holds true when an extended bolus is running. - // Add a field to PumpStatus, rather than renaming/overloading tbrRate to mean - // either TBR rate or basal rate depending on whether a TBR is active. - } else if (menuType == MenuType.WARNING_OR_ERROR) { - state.errorMsg = (String) menu.getAttribute(MenuAttribute.MESSAGE); - } else if (menuType == MenuType.STOP) { - state.suspended = true; - state.lowBattery = ((boolean) menu.getAttribute(MenuAttribute.LOW_BATTERY)); - state.insulinState = ((int) menu.getAttribute(MenuAttribute.INSULIN_STATE)); - } else { - StringBuilder sb = new StringBuilder(); - for (MenuAttribute menuAttribute : menu.attributes()) { - sb.append(menuAttribute); - sb.append(": "); - sb.append(menu.getAttribute(menuAttribute)); - sb.append("\n"); - } - state.errorMsg = "Pump is on menu " + menuType + ", listing attributes: \n" + sb.toString(); - } - return state; + public long readDisplayedDuration() { + MenuTime duration = readBlinkingValue(MenuTime.class, MenuAttribute.RUNTIME); + return duration.getHour() * 60 + duration.getMinute(); } } diff --git a/app/src/main/java/de/jotomo/ruffyscripter/commands/BaseCommand.java b/app/src/main/java/de/jotomo/ruffyscripter/commands/BaseCommand.java new file mode 100644 index 0000000000..0e83f0c1a1 --- /dev/null +++ b/app/src/main/java/de/jotomo/ruffyscripter/commands/BaseCommand.java @@ -0,0 +1,17 @@ +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; } +} diff --git a/app/src/main/java/de/jotomo/ruffyscripter/commands/BolusCommand.java b/app/src/main/java/de/jotomo/ruffyscripter/commands/BolusCommand.java index d5197452e3..84f56e62e7 100644 --- a/app/src/main/java/de/jotomo/ruffyscripter/commands/BolusCommand.java +++ b/app/src/main/java/de/jotomo/ruffyscripter/commands/BolusCommand.java @@ -11,13 +11,12 @@ import java.util.ArrayList; import java.util.List; import java.util.Locale; -import de.jotomo.ruffyscripter.PumpState; import de.jotomo.ruffyscripter.RuffyScripter; -public class BolusCommand implements Command { +public class BolusCommand extends BaseCommand { private static final Logger log = LoggerFactory.getLogger(BolusCommand.class); - private final double bolus; + protected final double bolus; public BolusCommand(double bolus) { this.bolus = bolus; @@ -35,7 +34,7 @@ public class BolusCommand implements Command { } @Override - public CommandResult execute(RuffyScripter scripter, PumpState initialPumpState) { + public CommandResult execute() { try { enterBolusMenu(scripter); @@ -55,13 +54,20 @@ public class BolusCommand implements Command { // 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.currentMenu.getAttribute(MenuAttribute.BOLUS_REMAINING); + Double bolusRemaining = (Double) scripter.getCurrentMenu().getAttribute(MenuAttribute.BOLUS_REMAINING); while (bolusRemaining != null) { log.debug("Delivering bolus, remaining: " + bolusRemaining); SystemClock.sleep(200); - bolusRemaining = (Double) scripter.currentMenu.getAttribute(MenuAttribute.BOLUS_REMAINING); + 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? @@ -75,16 +81,16 @@ public class BolusCommand implements Command { scripter.navigateToMenu(MenuType.MY_DATA_MENU); scripter.verifyMenuIsDisplayed(MenuType.MY_DATA_MENU); scripter.pressCheckKey(); - if (scripter.currentMenu.getType() != MenuType.BOLUS_DATA) { + if (scripter.getCurrentMenu().getType() != MenuType.BOLUS_DATA) { scripter.waitForMenuUpdate(); } - if (!scripter.currentMenu.attributes().contains(MenuAttribute.BOLUS)) { + 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.currentMenu.getAttribute(MenuAttribute.BOLUS); + 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 @@ -150,10 +156,10 @@ public class BolusCommand implements Command { // TODO v2 add timeout? Currently the command execution timeout would trigger if exceeded scripter.verifyMenuIsDisplayed(MenuType.BOLUS_ENTER); // bolus amount is blinking, so we need to make sure we catch it at the right moment - Object amountObj = scripter.currentMenu.getAttribute(MenuAttribute.BOLUS); + Object amountObj = scripter.getCurrentMenu().getAttribute(MenuAttribute.BOLUS); while (!(amountObj instanceof Double)) { scripter.waitForMenuUpdate(); - amountObj = scripter.currentMenu.getAttribute(MenuAttribute.BOLUS); + amountObj = scripter.getCurrentMenu().getAttribute(MenuAttribute.BOLUS); } return (double) amountObj; } diff --git a/app/src/main/java/de/jotomo/ruffyscripter/commands/CancelTbrCommand.java b/app/src/main/java/de/jotomo/ruffyscripter/commands/CancelTbrCommand.java index 2931f1d9d9..7d6fa98d37 100644 --- a/app/src/main/java/de/jotomo/ruffyscripter/commands/CancelTbrCommand.java +++ b/app/src/main/java/de/jotomo/ruffyscripter/commands/CancelTbrCommand.java @@ -8,13 +8,12 @@ import java.util.Collections; import java.util.List; import de.jotomo.ruffyscripter.PumpState; -import de.jotomo.ruffyscripter.RuffyScripter; import info.nightscout.androidaps.MainApp; // TODO robustness: can a TBR run out, whilst we're trying to cancel it? // Hm, we could just ignore TBRs that run out within the next 60s (0:01 or even 0:02 // given we need some time to process the request). -public class CancelTbrCommand implements Command { +public class CancelTbrCommand extends BaseCommand { private static final Logger log = LoggerFactory.getLogger(CancelTbrCommand.class); @Override @@ -23,10 +22,11 @@ public class CancelTbrCommand implements Command { } @Override - public CommandResult execute(RuffyScripter scripter, PumpState initialPumpState) { + public CommandResult execute() { try { scripter.verifyMenuIsDisplayed(MenuType.MAIN_MENU); - if (!initialPumpState.tbrActive) { + PumpState pumpState = scripter.readPumpState(); + if (!pumpState.tbrActive) { log.debug("active temp basal 90s ago: " + MainApp.getConfigBuilder().getTempBasalFromHistory(System.currentTimeMillis() - 90 * 1000)); log.debug("active temp basal 60s ago: " + @@ -47,9 +47,11 @@ public class CancelTbrCommand implements Command { .enacted(true) .message("No TBR active"); } - log.debug("Cancelling active TBR of " + initialPumpState.tbrPercent - + "% with " + initialPumpState.tbrRemainingDuration + " min remaining"); - return new SetTbrCommand(100, 0).execute(scripter, initialPumpState); + log.debug("Cancelling active TBR of " + pumpState.tbrPercent + + "% with " + pumpState.tbrRemainingDuration + " min remaining"); + SetTbrCommand setTbrCommand = new SetTbrCommand(100, 0); + setTbrCommand.setScripter(scripter); + return setTbrCommand.execute(); } catch (CommandException e) { return e.toCommandResult(); } diff --git a/app/src/main/java/de/jotomo/ruffyscripter/commands/CancellableBolusCommand.java b/app/src/main/java/de/jotomo/ruffyscripter/commands/CancellableBolusCommand.java new file mode 100644 index 0000000000..7d7283dce6 --- /dev/null +++ b/app/src/main/java/de/jotomo/ruffyscripter/commands/CancellableBolusCommand.java @@ -0,0 +1,272 @@ +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; + +import de.jotomo.ruffyscripter.PumpState; +import de.jotomo.ruffyscripter.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; + +public class CancellableBolusCommand extends BolusCommand { + private static final Logger log = LoggerFactory.getLogger(CancellableBolusCommand.class); + + private final ProgressReportCallback progressReportCallback; + private volatile boolean cancelRequested; + + public CancellableBolusCommand(double bolus, ProgressReportCallback progressReportCallback) { + super(bolus); + this.progressReportCallback = progressReportCallback; + } + + @Override + public List validateArguments() { + List 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 + enterBolusMenu(); + + inputBolusAmount(); + verifyDisplayedBolusAmount(); + + if (cancelRequested) { + progressReportCallback.report(STOPPING, 0, 0); + scripter.goToMainTypeScreen(MenuType.MAIN_MENU, 30 * 1000); + 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 = 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 claread so we can see the remaining bolus again; + while (bolusRemaining != null) { + if (cancelRequested) { + 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; + } + + 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); + } else { + // any other alert + break; + } + } + SystemClock.sleep(50); + bolusRemaining = (Double) scripter.getCurrentMenu().getAttribute(MenuAttribute.BOLUS_REMAINING); + } + + // 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) { + 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 + } + + // 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"); + + if (!scripter.goToMainTypeScreen(MenuType.MAIN_MENU, 15 * 1000)) { + 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)); + } catch (CommandException e) { + return e.toCommandResult(); + } + } + + // TODO confirmAlarms? and report back which were cancelled? + + private boolean confirmAlert(String alertText, int maxWaitTillExpectedAlert) { + // TODO + return false; + } + + 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 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); + 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() { + cancelRequested = true; + progressReportCallback.report(STOPPING, 0, 0); + } + + @Override + public String toString() { + return "BolusCommand{" + + "bolus=" + bolus + + '}'; + } + + public interface ProgressReportCallback { + enum State { + DELIVERING, + DELIVERED, + STOPPING, + STOPPED + } + + void report(State state, int percent, double delivered); + } +} diff --git a/app/src/main/java/de/jotomo/ruffyscripter/commands/Command.java b/app/src/main/java/de/jotomo/ruffyscripter/commands/Command.java index 33d10fc223..60fa3c730f 100644 --- a/app/src/main/java/de/jotomo/ruffyscripter/commands/Command.java +++ b/app/src/main/java/de/jotomo/ruffyscripter/commands/Command.java @@ -2,17 +2,17 @@ package de.jotomo.ruffyscripter.commands; import java.util.List; -import de.jotomo.ruffyscripter.PumpState; import de.jotomo.ruffyscripter.RuffyScripter; /** * Interface for all commands to be executed by the pump. - * + *

* Note on cammond methods and timing: a method shall wait before and after executing * as necessary to not cause timing issues, so the caller can just call methods in * sequence, letting the methods take care of waits. */ public interface Command { - CommandResult execute(RuffyScripter ruffyScripter, PumpState initialPumpState); + CommandResult execute(); List validateArguments(); + void setScripter(RuffyScripter scripter); } diff --git a/app/src/main/java/de/jotomo/ruffyscripter/commands/CommandException.java b/app/src/main/java/de/jotomo/ruffyscripter/commands/CommandException.java index 5c25ba914c..d2f31cc8e2 100644 --- a/app/src/main/java/de/jotomo/ruffyscripter/commands/CommandException.java +++ b/app/src/main/java/de/jotomo/ruffyscripter/commands/CommandException.java @@ -6,7 +6,8 @@ public class CommandException extends RuntimeException { public Exception exception = null; public String message = null; - public CommandException() {} + public CommandException() { + } public CommandException success(boolean success) { this.success = success; diff --git a/app/src/main/java/de/jotomo/ruffyscripter/commands/CommandResult.java b/app/src/main/java/de/jotomo/ruffyscripter/commands/CommandResult.java index ec9c444a97..9d1bbb321e 100644 --- a/app/src/main/java/de/jotomo/ruffyscripter/commands/CommandResult.java +++ b/app/src/main/java/de/jotomo/ruffyscripter/commands/CommandResult.java @@ -3,7 +3,6 @@ package de.jotomo.ruffyscripter.commands; import java.util.Date; import de.jotomo.ruffyscripter.History; -import de.jotomo.ruffyscripter.PumpCapabilities; import de.jotomo.ruffyscripter.PumpState; public class CommandResult { @@ -14,7 +13,6 @@ public class CommandResult { public String message; public PumpState state; public History history; - public PumpCapabilities capabilities; public String duration; public CommandResult() { @@ -31,7 +29,7 @@ public class CommandResult { } public CommandResult completionTime(long completionTime) { - this.completionTime = completionTime ; + this.completionTime = completionTime; return this; } @@ -57,12 +55,7 @@ public class CommandResult { public CommandResult history(History history) { this.history = history; - return this; - } - - public CommandResult capabilities(PumpCapabilities capabilities) { - this.capabilities = capabilities; - return this; + return this; } @Override diff --git a/app/src/main/java/de/jotomo/ruffyscripter/commands/DetermineCapabilitiesCommand.java b/app/src/main/java/de/jotomo/ruffyscripter/commands/DetermineCapabilitiesCommand.java deleted file mode 100644 index adc740c13e..0000000000 --- a/app/src/main/java/de/jotomo/ruffyscripter/commands/DetermineCapabilitiesCommand.java +++ /dev/null @@ -1,165 +0,0 @@ -package de.jotomo.ruffyscripter.commands; - -import android.os.SystemClock; - -import com.j256.ormlite.stmt.query.In; - -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.Collections; -import java.util.List; -import java.util.Locale; - -import de.jotomo.ruffyscripter.PumpCapabilities; -import de.jotomo.ruffyscripter.PumpState; -import de.jotomo.ruffyscripter.RuffyScripter; - - -public class DetermineCapabilitiesCommand implements Command { - private static final Logger log = LoggerFactory.getLogger(DetermineCapabilitiesCommand.class); - public static final int UP_STEPS = 75; - public static final int RETRIES = 5; - - @Override - public List validateArguments() { - return Collections.emptyList(); - } - - @Override - public CommandResult execute(RuffyScripter scripter, PumpState initialPumpState) { - try { - - //read main menu 100% or TBR? Read remaining duration. - long durationBefore = readDisplayedTbrDurationMainMenu(scripter); - long percentageBefore = readDisplayedTbrPercentageMainMenu(scripter); - - enterTbrMenu(scripter); - long maxTbrPercentage = findMaxTbrPercentage(scripter); - - // TODO v2 this can probably be removed by now - SystemClock.sleep(750); - - scripter.verifyMenuIsDisplayed(MenuType.MAIN_MENU, - "Pump did not return to MAIN_MEU after finding max tbr. " + - "Check pump manually, the TBR might be wrong."); - - - //TODO: check if TBR is still the same or duration was less than 5 minutes - long durationAfter = readDisplayedTbrDurationMainMenu(scripter); - long percentageAfter = readDisplayedTbrPercentageMainMenu(scripter); - - if(Math.abs(durationBefore-durationAfter) > 5){ - throw new CommandException().message("Duration jump during DetermineCapabilities"); - } - if(percentageAfter != percentageBefore){ - if(durationBefore<5 && percentageAfter == 100){ - log.debug("(percentageBefore != percentageAfter) - ignoring as tbr is now 100% and had a very short duration left"); - } - throw new CommandException().message("TBR changed while determining maxTBR."); - } - - //TODO return Result - return new CommandResult().success(true).enacted(false).message("Capablities: {maxTbrPercentage = " + maxTbrPercentage + ", success=" + "success }").capabilities((new PumpCapabilities()).maxTempPercent(maxTbrPercentage)); - } 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 long findMaxTbrPercentage(RuffyScripter scripter) { - scripter.verifyMenuIsDisplayed(MenuType.TBR_SET); - long activeTempBasal = readDisplayedTbrPercentage(scripter); - - // pretend to increase the TBR to more than 500% - log.debug("Pressing up " + UP_STEPS + " times to get to maximum"); - for (int i = 0; i < UP_STEPS; i++) { - scripter.verifyMenuIsDisplayed(MenuType.TBR_SET); - scripter.pressUpKey(); - SystemClock.sleep(200); - log.debug("Push #" + (i + 1)); - } - - //read the displayed maximum value - scripter.verifyMenuIsDisplayed(MenuType.TBR_SET); - long maximumTempBasal = readDisplayedTbrPercentage(scripter); - - //reset the TBR in a controlled manner - long percentageChange = maximumTempBasal - activeTempBasal; - long percentageSteps = percentageChange / 10; - - int retries= 0; - while (percentageSteps > 0 && retries < RETRIES) { - log.debug("Pressing down " + percentageSteps + " times to get to previous value. Retry " + retries); - for (int i = 0; i < percentageSteps; i++) { - scripter.verifyMenuIsDisplayed(MenuType.TBR_SET); - scripter.pressDownKey(); - SystemClock.sleep(200); - log.debug("Push #" + (i + 1)); - } - //do the rest if button-presses failed. - scripter.verifyMenuIsDisplayed(MenuType.TBR_SET); - long currentPercentage = readDisplayedTbrPercentage(scripter); - percentageChange = currentPercentage - activeTempBasal; - percentageSteps = percentageChange / 10; - retries++; - } - - - //exit menu - scripter.pressCheckKey(); - scripter.waitForMenuToBeLeft(MenuType.TBR_SET); - return maximumTempBasal; - } - - - private long readDisplayedTbrPercentage(RuffyScripter scripter) { - SystemClock.sleep(1000); - // 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 int readDisplayedTbrDurationMainMenu(RuffyScripter scripter) { - scripter.verifyMenuIsDisplayed(MenuType.MAIN_MENU); - if(scripter.currentMenu.attributes().contains(MenuAttribute.RUNTIME)){ - // TODO v2 add timeout? Currently the command execution timeout would trigger if exceeded - Object durationObj = scripter.currentMenu.getAttribute(MenuAttribute.RUNTIME); - MenuTime duration = (MenuTime) durationObj; - return duration.getHour() * 60 + duration.getMinute(); - } else { - return 0; - } - } - - private int readDisplayedTbrPercentageMainMenu(RuffyScripter scripter) { - scripter.verifyMenuIsDisplayed(MenuType.MAIN_MENU); - if(scripter.currentMenu.attributes().contains(MenuAttribute.TBR)){ - return (int)((Double) scripter.currentMenu.getAttribute(MenuAttribute.TBR)).doubleValue(); - } else { - return 100; - } - } - - @Override - public String toString() { - return "DetermineCapabilitiesCommand{}"; - } -} diff --git a/app/src/main/java/de/jotomo/ruffyscripter/commands/GetBasalCommand.java b/app/src/main/java/de/jotomo/ruffyscripter/commands/GetBasalRateProfileCommand.java similarity index 86% rename from app/src/main/java/de/jotomo/ruffyscripter/commands/GetBasalCommand.java rename to app/src/main/java/de/jotomo/ruffyscripter/commands/GetBasalRateProfileCommand.java index 09958f6bf1..ff842af5dd 100644 --- a/app/src/main/java/de/jotomo/ruffyscripter/commands/GetBasalCommand.java +++ b/app/src/main/java/de/jotomo/ruffyscripter/commands/GetBasalRateProfileCommand.java @@ -10,15 +10,14 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import de.jotomo.ruffyscripter.PumpState; import de.jotomo.ruffyscripter.RuffyScripter; -public class GetBasalCommand implements Command { - private static final Logger log = LoggerFactory.getLogger(GetBasalCommand.class); +public class GetBasalRateProfileCommand extends BaseCommand { + private static final Logger log = LoggerFactory.getLogger(GetBasalRateProfileCommand.class); private RuffyScripter scripter; - public GetBasalCommand() {} + public GetBasalRateProfileCommand() {} @Override public List validateArguments() { @@ -27,7 +26,7 @@ public class GetBasalCommand implements Command { return violations; } -// private void tick() + // private void tick() // { // switch (state) // { @@ -95,24 +94,23 @@ public class GetBasalCommand implements Command { // case ERROR: // case AFTER: // scripter.goToMainMenuScreen(MenuType.MAIN_MENU,2000); -// synchronized(GetBasalCommand.this) { -// GetBasalCommand.this.notify(); +// synchronized(GetBasalRateProfileCommand.this) { +// GetBasalRateProfileCommand.this.notify(); // } // break; // } // } @Override - public CommandResult execute(RuffyScripter scripter, PumpState initialPumpState) { + public CommandResult execute() { try { - Map rate = new HashMap<>(); + Map rate = new HashMap<>(); - for(int i = 0; i < 24;i++) - { - Log.v("BASAL_RATE","BASAL_RATE from "+String.format("%02d",i)+":00 = "+rate.get(i)); + for (int i = 0; i < 24; i++) { + Log.v("BASAL_RATE", "BASAL_RATE from " + String.format("%02d", i) + ":00 = " + rate.get(i)); } } catch (Exception e) { - log.error("failed to get basal",e); - return new CommandResult().success(false).message("failed to get basal: "+e.getMessage()); + log.error("failed to get basal", e); + return new CommandResult().success(false).message("failed to get basal: " + e.getMessage()); } return new CommandResult().success(true).enacted(true).message("Basal Rate was read"); } diff --git a/app/src/main/java/de/jotomo/ruffyscripter/commands/GetPumpStateCommand.java b/app/src/main/java/de/jotomo/ruffyscripter/commands/GetPumpStateCommand.java new file mode 100644 index 0000000000..b298fcb41e --- /dev/null +++ b/app/src/main/java/de/jotomo/ruffyscripter/commands/GetPumpStateCommand.java @@ -0,0 +1,168 @@ +package de.jotomo.ruffyscripter.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; + +public class GetPumpStateCommand extends BaseCommand { + interface Step { + void doStep(); + } + + static boolean cancelRequested; + + + public static class Stepper { + public static void runStep(int retries, String desc, Step step) { + runStep(retries, desc, step, null, null); + } + public static void runStep(int retries, String desc, Step step, String recoveryDesc, Step recovery) { + // pre-checks here + if (cancelRequested) { +// runStep(0, "return to neutral state", () -> scripter.navigateToMainMenu()); +// if (recovery != null) recovery.doStep(); +// throw new CommandAbortedException(true); + } + if (true /*conectionLost*/) { + // either try to reconnect and deal with raised alarms (corfirm them and forward to AAPS) + // or let RS know he should reconnect and handle it (if that routine handles + // recovery in a generic way + } + /*boolean success/result =*/ step.doStep(); + if (true /*successful*/) { + // + } else { + runStep(retries - 1, desc, step, recoveryDesc, recovery); + } + } + } + + static class StepBuilder { + public StepBuilder(String desc) {} + + public String result; + + Step cancel = new Step() { + @Override + public void doStep() { + // default recovery + } + }; + + public StepBuilder retries(int retries) { return this; } + public StepBuilder description(Step step) { + return this; + } + public StepBuilder step(Step step) { + return this; + } + public StepBuilder recover(Step step) { + return this; + } + public StepBuilder cancel(Step step) { + return this; + } + public StepBuilder failure(Step step) { return this; } + public StepBuilder run() { + return this; + } + } + + + + // state/info on whether an abort in that situtaion will raise a pump alert that we need + // to connect to the pump for quickly and dismiss it + + +// void step(String description, Code c) { +// c.run(); +// +// exception/unexpected state +// user requested cancel +// disconnect info from ruffy +// +// } + + public CommandResult execute2() { + + new StepBuilder("Navigate to bolus menu") // turn into a method createStep() or so, which has access to the scripter + .step(new Step() { + @Override + public void doStep() { + System.out.println("something"); + } + }) + .recover(new Step() { + @Override + public void doStep() { + System.out.println("default impl: navigate back to main menu, no alarms"); + } + }) + .run(); + new StepBuilder("Input bolus") // turn into a method createStep() or so, which has access to the scripter + .retries(5) + .failure(new Step() { + @Override + public void doStep() { + System.out.println("retry command"); + } + }) + .step(new Step() { + @Override + public void doStep() { + System.out.println("something"); + } + }) + .recover(new Step() { + @Override + public void doStep() { + System.out.println("navigate back and cancel 'bolus cancelled' alert"); + } + }) + .run(); + // ^^ would allow overriding a default recovery or abort method + // vv below code as well, with varargs and overloading or simply more methods like runStepWithCustomRecovery + runStep(0, "check things", new Step() { + @Override + public void doStep() { + scripter.navigateToMenu(MenuType.MY_DATA_MENU); + } + }); + runStep(0, "check things", new Step() { + @Override + public void doStep() { + scripter.navigateToMenu(MenuType.MY_DATA_MENU); + } + }, "recover by doing x", new Step() { + @Override + public void doStep() { + // recover + } + }); + + return null; + } + + @Override + public CommandResult execute() { + return new CommandResult().success(true).enacted(false).message("Returning pump state only"); + } + +// @Override +// public CommandResult execute() { +// return new CommandResult().success(true).enacted(false).message("Returning pump state only"); +// } + + @Override + public List validateArguments() { + return Collections.emptyList(); + } + + @Override + public String toString() { + return "ReadPumpStateCommand{}"; + } +} diff --git a/app/src/main/java/de/jotomo/ruffyscripter/commands/GetReservoirLevelCommand.java b/app/src/main/java/de/jotomo/ruffyscripter/commands/GetReservoirLevelCommand.java new file mode 100644 index 0000000000..3a620a0fef --- /dev/null +++ b/app/src/main/java/de/jotomo/ruffyscripter/commands/GetReservoirLevelCommand.java @@ -0,0 +1,24 @@ +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 validateArguments() { + // TODO stub + return null; + } +} diff --git a/app/src/main/java/de/jotomo/ruffyscripter/commands/ReadPumpStateCommand.java b/app/src/main/java/de/jotomo/ruffyscripter/commands/ReadPumpStateCommand.java deleted file mode 100644 index 58e52b0a4f..0000000000 --- a/app/src/main/java/de/jotomo/ruffyscripter/commands/ReadPumpStateCommand.java +++ /dev/null @@ -1,24 +0,0 @@ -package de.jotomo.ruffyscripter.commands; - -import java.util.Collections; -import java.util.List; - -import de.jotomo.ruffyscripter.PumpState; -import de.jotomo.ruffyscripter.RuffyScripter; - -public class ReadPumpStateCommand implements Command { - @Override - public CommandResult execute(RuffyScripter ruffyScripter, PumpState initialPumpState) { - return new CommandResult().success(true).enacted(false).message("Returning pump state only"); - } - - @Override - public List validateArguments() { - return Collections.emptyList(); - } - - @Override - public String toString() { - return "ReadPumpStateCommand{}"; - } -} diff --git a/app/src/main/java/de/jotomo/ruffyscripter/commands/SetBasalRateProfileCommand.java b/app/src/main/java/de/jotomo/ruffyscripter/commands/SetBasalRateProfileCommand.java new file mode 100644 index 0000000000..6d98c94d42 --- /dev/null +++ b/app/src/main/java/de/jotomo/ruffyscripter/commands/SetBasalRateProfileCommand.java @@ -0,0 +1,17 @@ +package de.jotomo.ruffyscripter.commands; + +import java.util.List; + +public class SetBasalRateProfileCommand extends BaseCommand { + @Override + public CommandResult execute() { + // TODO stub + return null; + } + + @Override + public List validateArguments() { + // TODO stub + return null; + } +} diff --git a/app/src/main/java/de/jotomo/ruffyscripter/commands/SetTbrCommand.java b/app/src/main/java/de/jotomo/ruffyscripter/commands/SetTbrCommand.java index 8af26863f7..bde829df9c 100644 --- a/app/src/main/java/de/jotomo/ruffyscripter/commands/SetTbrCommand.java +++ b/app/src/main/java/de/jotomo/ruffyscripter/commands/SetTbrCommand.java @@ -13,15 +13,8 @@ import java.util.List; import java.util.Locale; import de.jotomo.ruffyscripter.PumpState; -import de.jotomo.ruffyscripter.RuffyScripter; -import static org.monkey.d.ruffy.ruffy.driver.display.MenuType.MAIN_MENU; -import static org.monkey.d.ruffy.ruffy.driver.display.MenuType.TBR_DURATION; -import static org.monkey.d.ruffy.ruffy.driver.display.MenuType.TBR_MENU; -import static org.monkey.d.ruffy.ruffy.driver.display.MenuType.TBR_SET; -import static org.monkey.d.ruffy.ruffy.driver.display.MenuType.WARNING_OR_ERROR; - -public class SetTbrCommand implements Command { +public class SetTbrCommand extends BaseCommand { private static final Logger log = LoggerFactory.getLogger(SetTbrCommand.class); private final long percentage; @@ -60,207 +53,249 @@ public class SetTbrCommand implements Command { } @Override - public CommandResult execute(RuffyScripter scripter, PumpState initialPumpState) { + public CommandResult execute() { try { - log.debug("1. going from " + scripter.currentMenu + " to TBR_MENU"); - int retries = 5; - while (!scripter.goToMainTypeScreen(TBR_MENU, 3000)) { - retries--; - if (retries == 0) - throw new CommandException().message("not able to find TBR_MENU: stuck in " + scripter.currentMenu); - SystemClock.sleep(500); - if (scripter.currentMenu.getType() == TBR_MENU) - break; + boolean cancellingTbr = percentage == 100; + PumpState pumpState = scripter.readPumpState(); + + // TODO hack, cancelling a TBR that isn't running is dealt with in CancelTbrCommand, + // this avoids setting a TBR twice until that AAPS bug is squished which calls this + // twice within a minute GL#27 + if (!cancellingTbr + && pumpState.tbrActive + && pumpState.tbrPercent == percentage + && (pumpState.tbrRemainingDuration == duration || pumpState.tbrRemainingDuration + 1 == duration)) { + return new CommandResult().success(true).enacted(false).message("Requested TBR already running"); } - if (scripter.currentMenu.getType() != TBR_MENU) - throw new CommandException().message("not able to find TBR_MENU: stuck in " + scripter.currentMenu); + enterTbrMenu(); + boolean increasingPercentage = inputTbrPercentage(); + verifyDisplayedTbrPercentage(increasingPercentage); - log.debug("2. entering " + scripter.currentMenu); - retries = 5; - while (!scripter.enterMenu(TBR_MENU, MenuType.TBR_SET, RuffyScripter.Key.CHECK, 2000)) { - retries--; - if (retries == 0) - throw new CommandException().message("not able to find TBR_SET: stuck in " + scripter.currentMenu); - SystemClock.sleep(500); - if (scripter.currentMenu.getType() == TBR_SET) - break; - if (scripter.currentMenu.getType() == TBR_DURATION) { - scripter.pressMenuKey(); - scripter.waitForScreenUpdate(1000); - } - } + if (cancellingTbr) { + cancelTbrAndConfirmCancellationWarning(); + } else { + // switch to TBR_DURATION menu by pressing menu key + scripter.verifyMenuIsDisplayed(MenuType.TBR_SET); + scripter.pressMenuKey(); + scripter.waitForMenuUpdate(); + scripter.verifyMenuIsDisplayed(MenuType.TBR_DURATION); - log.debug("SetTbrCommand: 3. getting/setting basal percentage in " + scripter.currentMenu); - retries = 30; + boolean increasingDuration = inputTbrDuration(); + verifyDisplayedTbrDuration(increasingDuration); - double currentPercentage = -100; - while (currentPercentage != percentage && retries >= 0) { - retries--; - Object percentageObj = scripter.currentMenu.getAttribute(MenuAttribute.BASAL_RATE); - - if (percentageObj != null && (percentageObj instanceof Double)) { - currentPercentage = ((Double) percentageObj).doubleValue(); - - if (currentPercentage != percentage) { - int requestedPercentage = (int) percentage; - int actualPercentage = (int) currentPercentage; - int steps = (requestedPercentage - actualPercentage) / 10; - log.debug("Adjusting basal(" + requestedPercentage + "/" + actualPercentage + ") with " + steps + " steps and " + retries + " retries left"); - scripter.step(steps, (steps < 0 ? RuffyScripter.Key.DOWN : RuffyScripter.Key.UP), 500); - scripter.waitForScreenUpdate(1000); - } - - } else { - currentPercentage = -100; - } - scripter.waitForScreenUpdate(1000); - } - if (currentPercentage < 0 || retries < 0) - throw new CommandException().message("unable to set basal percentage"); - - log.debug("4. checking basal percentage in " + scripter.currentMenu); - scripter.waitForScreenUpdate(1000); - currentPercentage = -1000; - retries = 10; - while (currentPercentage < 0 && retries >= 0) { - retries--; - Object percentageObj = scripter.currentMenu.getAttribute(MenuAttribute.BASAL_RATE); - - if (percentageObj != null && (percentageObj instanceof Double)) { - currentPercentage = ((Double) percentageObj).doubleValue(); - } else { - scripter.waitForScreenUpdate(1000); - } - } - - if (retries < 0 || currentPercentage != percentage) - throw new CommandException().message("Unable to set percentage. Requested: " + percentage + ", value displayed on pump: " + currentPercentage); - - if (currentPercentage != 100) { - log.debug("5. change to TBR_DURATION from " + scripter.currentMenu); - retries = 5; - while (retries >= 0 && !scripter.enterMenu(TBR_SET, MenuType.TBR_DURATION, RuffyScripter.Key.MENU, 2000)) { - retries--; - if (retries == 0) - throw new CommandException().message("not able to find TBR_SET: stuck in " + scripter.currentMenu); - SystemClock.sleep(500); - if (scripter.currentMenu.getType() == TBR_DURATION) - break; - if (scripter.currentMenu.getType() == TBR_SET) { - scripter.pressMenuKey(); - scripter.waitForScreenUpdate(1000); - } - } - - log.debug("6. getting/setting duration in " + scripter.currentMenu); - retries = 30; - - double currentDuration = -100; - while (currentDuration != duration && retries >= 0) { - retries--; - Object durationObj = scripter.currentMenu.getAttribute(MenuAttribute.RUNTIME); - log.debug("Requested time: " + duration + " actual time: " + durationObj); - if (durationObj != null && durationObj instanceof MenuTime) { - MenuTime time = (MenuTime) durationObj; - currentDuration = (time.getHour() * 60) + time.getMinute(); - if (currentDuration != duration) { - int requestedDuration = (int) duration; - int actualDuration = (int) currentDuration; - int steps = (requestedDuration - actualDuration) / 15; - if (currentDuration + (steps * 15) < requestedDuration) - steps++; - else if (currentDuration + (steps * 15) > requestedDuration) - steps--; - log.debug("Adjusting duration(" + requestedDuration + "/" + actualDuration + ") with " + steps + " steps and " + retries + " retries left"); - scripter.step(steps, (steps > 0 ? RuffyScripter.Key.UP : RuffyScripter.Key.DOWN), 500); - scripter.waitForScreenUpdate(1000); - } - } - scripter.waitForScreenUpdate(1000); - } - if (currentDuration < 0 || retries < 0) - throw new CommandException().message("unable to set duration, requested:" + duration + ", displayed on pump: " + currentDuration); - - log.debug("7. checking duration in " + scripter.currentMenu); - scripter.waitForScreenUpdate(1000); - currentDuration = -1000; - retries = 10; - while (currentDuration < 0 && retries >= 0) { - retries--; - Object durationObj = scripter.currentMenu.getAttribute(MenuAttribute.RUNTIME); - - if (durationObj != null && durationObj instanceof MenuTime) { - MenuTime time = (MenuTime) durationObj; - currentDuration = (time.getHour() * 60) + time.getMinute(); - } else - scripter.waitForScreenUpdate(1000); - } - if (retries < 0 || currentDuration != duration) - throw new CommandException().message("wrong duration! Requested: " + duration + ", displayed on pump: " + currentDuration); - } - - log.debug("8. confirming TBR om " + scripter.currentMenu); - retries = 5; - while (retries >= 0 && (scripter.currentMenu.getType() == TBR_DURATION || scripter.currentMenu.getType() == TBR_SET)) { - retries--; + // confirm TBR scripter.pressCheckKey(); - scripter.waitForScreenUpdate(1000); - } - if (retries < 0 || scripter.currentMenu.getType() == TBR_DURATION || scripter.currentMenu.getType() == TBR_SET) - throw new CommandException().message("failed setting basal!"); - retries = 10; - boolean cancelledError = true; - if (percentage == 100) - cancelledError = false; - while (retries >= 0 && scripter.currentMenu.getType() != MAIN_MENU) { - // TODO how probable is it, that a totally unrelated error (like occlusion alert) - // is raised at this point, which we'd cancel together with the TBR cancelled alert? - if (percentage == 100 && scripter.currentMenu.getType() == WARNING_OR_ERROR) { - scripter.pressCheckKey(); - retries++; - cancelledError = true; - scripter.waitForScreenUpdate(1000); - } else { - retries--; - if (scripter.currentMenu.getType() == MAIN_MENU && cancelledError) - break; - } + scripter.waitForMenuToBeLeft(MenuType.TBR_DURATION); } - log.debug("9. verifying the main menu display the TBR we just set/cancelled"); - if (retries < 0 || scripter.currentMenu.getType() != MAIN_MENU) - throw new CommandException().message("failed going to main!"); - - Object percentageObj = scripter.currentMenu.getAttribute(MenuAttribute.TBR); - Object durationObj = scripter.currentMenu.getAttribute(MenuAttribute.RUNTIME); - - if (percentage == 100) { - if (durationObj != null) - throw new CommandException().message("TBR cancelled, but main menu shows a running TBR"); + 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 (cancellingTbr) { + verifyMainMenuShowsNoActiveTbr(); return new CommandResult().success(true).enacted(true).message("TBR was cancelled"); + } else { + verifyMainMenuShowsExpectedTbrActive(); + return new CommandResult().success(true).enacted(true).message( + String.format(Locale.US, "TBR set to %d%% for %d min", percentage, duration)); } - if (percentageObj == null || !(percentageObj instanceof Double)) - throw new CommandException().message("not percentage"); + } catch (CommandException e) { + return e.toCommandResult(); + } + } - if (((double) percentageObj) != percentage) - throw new CommandException().message("wrong percentage set!"); + private void enterTbrMenu() { + scripter.verifyMenuIsDisplayed(MenuType.MAIN_MENU); + scripter.navigateToMenu(MenuType.TBR_MENU); + scripter.verifyMenuIsDisplayed(MenuType.TBR_MENU); + scripter.pressCheckKey(); + scripter.waitForMenuUpdate(); + scripter.verifyMenuIsDisplayed(MenuType.TBR_SET); + } - if (durationObj == null || !(durationObj instanceof MenuTime)) - throw new CommandException().message("not time"); + private boolean inputTbrPercentage() { + scripter.verifyMenuIsDisplayed(MenuType.TBR_SET); + long currentPercent = scripter.readBlinkingValue(Double.class, MenuAttribute.BASAL_RATE).longValue(); + 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); + log.debug("Push #" + (i + 1)); + if (increasePercentage) scripter.pressUpKey(); + else scripter.pressDownKey(); + SystemClock.sleep(100); + } + return increasePercentage; + } - MenuTime t = (MenuTime) durationObj; - if (t.getMinute() + (60 * t.getHour()) > duration || t.getMinute() + (60 * t.getHour()) < duration - 5) - throw new CommandException().message("wrong time set!"); + // TODO 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 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 = scripter.readBlinkingValue(Double.class, MenuAttribute.BASAL_RATE).longValue(); + } + log.debug("Final displayed TBR percentage: " + displayedPercentage); + if (displayedPercentage != percentage) { + 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 + SystemClock.sleep(1000); + scripter.verifyMenuIsDisplayed(MenuType.TBR_SET); + long refreshedDisplayedTbrPecentage = scripter.readBlinkingValue(Double.class, MenuAttribute.BASAL_RATE).longValue(); + if (displayedPercentage != refreshedDisplayedTbrPecentage) { + throw new CommandException().message("Failed to set TBR percentage: " + + "percentage changed after input stopped from " + + displayedPercentage + " -> " + refreshedDisplayedTbrPecentage); + } + } + + private boolean inputTbrDuration() { + scripter.verifyMenuIsDisplayed(MenuType.TBR_DURATION); + long currentDuration = scripter.readDisplayedDuration(); + 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 = scripter.readDisplayedDuration(); + } + 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)); + } + return increaseDuration; + } + + private void verifyDisplayedTbrDuration(boolean increasingPercentage) { + scripter.verifyMenuIsDisplayed(MenuType.TBR_DURATION); + + // wait up to 5s for any scrolling to finish + long displayedDuration = scripter.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 = scripter.readDisplayedDuration(); + } + + log.debug("Final displayed TBR duration: " + displayedDuration); + if (displayedDuration != duration) { + 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 + SystemClock.sleep(1000); + scripter.verifyMenuIsDisplayed(MenuType.TBR_DURATION); + long refreshedDisplayedTbrDuration = scripter.readDisplayedDuration(); + if (displayedDuration != refreshedDisplayedTbrDuration) { + throw new CommandException().message("Failed to set TBR duration: " + + "duration changed after input stopped from " + + displayedDuration + " -> " + refreshedDisplayedTbrDuration); + } + } - return new CommandResult().success(true).enacted(true).message( - String.format(Locale.US, "TBR set to %d%% for %d min", percentage, duration)); - } catch (Exception e) { - log.error("got exception: ", e); - return new CommandResult().success(false).message(e.getMessage()).exception(e); + + private void cancelTbrAndConfirmCancellationWarning() { + // 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.getCurrentMenu().getType() == MenuType.WARNING_OR_ERROR) { + // Check the raised alarm is TBR CANCELLED, so we're not accidentally cancelling + // a different alarm that might be raised at the same time. + // Note that the message is permanently displayed, while the error code is blinking. + // A wait till the error code can be read results in the code hanging, despite + // menu updates coming in, so just check the message. + // TODO v2 this only works when the pump's language is English + String errorMsg = (String) scripter.getCurrentMenu().getAttribute(MenuAttribute.MESSAGE); + if (!errorMsg.equals("TBR CANCELLED")) { + throw new CommandException().success(false).enacted(false) + .message("An alert other than the expected TBR CANCELLED was raised by the pump: " + + errorMsg + ". Please check the pump."); + } + // confirm "TBR CANCELLED" alert + scripter.verifyMenuIsDisplayed(MenuType.WARNING_OR_ERROR); + scripter.pressCheckKey(); + // dismiss "TBR CANCELLED" alert + scripter.verifyMenuIsDisplayed(MenuType.WARNING_OR_ERROR); + scripter.pressCheckKey(); + scripter.waitForMenuToBeLeft(MenuType.WARNING_OR_ERROR); + alertProcessed = true; + } + SystemClock.sleep(10); + } + } + + private void verifyMainMenuShowsNoActiveTbr() { + scripter.verifyMenuIsDisplayed(MenuType.MAIN_MENU); + Double tbrPercentage = (Double) scripter.getCurrentMenu().getAttribute(MenuAttribute.TBR); + boolean runtimeDisplayed = scripter.getCurrentMenu().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() { + scripter.verifyMenuIsDisplayed(MenuType.MAIN_MENU); + // new TBR set; percentage and duration must be displayed ... + if (!scripter.getCurrentMenu().attributes().contains(MenuAttribute.TBR) || + !scripter.getCurrentMenu().attributes().contains(MenuAttribute.RUNTIME)) { + throw new CommandException().message("Setting TBR failed, according to MAIN_MENU no TBR is active"); + } + Double mmTbrPercentage = (Double) scripter.getCurrentMenu().getAttribute(MenuAttribute.TBR); + MenuTime mmTbrDuration = (MenuTime) scripter.getCurrentMenu().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"); } } diff --git a/app/src/main/java/de/jotomo/ruffyscripter/commands/SetTbrCommandAlt.java b/app/src/main/java/de/jotomo/ruffyscripter/commands/SetTbrCommandAlt.java deleted file mode 100644 index 7a9035cfe5..0000000000 --- a/app/src/main/java/de/jotomo/ruffyscripter/commands/SetTbrCommandAlt.java +++ /dev/null @@ -1,302 +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.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); - log.debug("Push #" + (i + 1)); - if (increasePercentage) scripter.pressUpKey(); - else scripter.pressDownKey(); - 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 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 d5d056da61..285e4e6895 100644 --- a/app/src/main/java/info/nightscout/androidaps/Config.java +++ b/app/src/main/java/info/nightscout/androidaps/Config.java @@ -49,8 +49,9 @@ public class Config { 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; + /** enable the UNFINISHED and currently BROKEN bolus cammand that reports progress and can be cancelled */ + public static final boolean comboExperimentalBolus = true; + /** very quick hack to split up bolus into 2 U parts, spaced roughly 45s apart. + * Don't combine with experimental bolus */ + public static final boolean comboSplitBoluses = false && !comboExperimentalBolus; } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/Loop/LoopPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/Loop/LoopPlugin.java index 6e459a03b2..2ad9ab1ffb 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/Loop/LoopPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/Loop/LoopPlugin.java @@ -257,7 +257,7 @@ public class LoopPlugin implements PluginBase { return; } - // check rate for constrais + // check rate for constraints final APSResult resultAfterConstraints = result.clone(); resultAfterConstraints.rate = constraintsInterface.applyBasalConstraints(resultAfterConstraints.rate); diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ComboFragment.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ComboFragment.java index 2ae111ab6b..f4d49c0a94 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ComboFragment.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ComboFragment.java @@ -34,7 +34,6 @@ public class ComboFragment extends Fragment implements View.OnClickListener { } private Button refresh; - private TextView updateCapabilities; private TextView statusText; @@ -48,7 +47,6 @@ public class ComboFragment extends Fragment implements View.OnClickListener { private TextView lastCmdResultText; private TextView lastCmdDurationText; - private TextView tbrCapabilityText; private TextView pumpstateBatteryText; private TextView insulinstateText; @@ -59,7 +57,6 @@ public class ComboFragment extends Fragment implements View.OnClickListener { View view = inflater.inflate(R.layout.combopump_fragment, container, false); refresh = (Button) view.findViewById(R.id.combo_refresh); - updateCapabilities = (TextView) view.findViewById(R.id.combo_update_capabilities); statusText = (TextView) view.findViewById(R.id.combo_status); @@ -72,12 +69,10 @@ public class ComboFragment extends Fragment implements View.OnClickListener { lastCmdTimeText = (TextView) view.findViewById(R.id.combo_last_command_time); lastCmdResultText = (TextView) view.findViewById(R.id.combo_last_command_result); lastCmdDurationText = (TextView) view.findViewById(R.id.combo_last_command_duration); - tbrCapabilityText = (TextView) view.findViewById(R.id.combo_tbr_capability); pumpstateBatteryText = (TextView) view.findViewById(R.id.combo_pumpstate_battery); insulinstateText = (TextView) view.findViewById(R.id.combo_insulinstate); refresh.setOnClickListener(this); - updateCapabilities.setOnClickListener(this); updateGUI(); return view; @@ -113,32 +108,6 @@ public class ComboFragment extends Fragment implements View.OnClickListener { }); thread.start(); break; - case R.id.combo_update_capabilities: - (new Thread(new Runnable() { - @Override - public void run() { - Activity activity = getActivity(); - if (activity != null) - activity.runOnUiThread(new Runnable() { - @Override - public void run() { - updateCapabilities.setText("{fa-bluetooth spin}"); - } - }); - - getPlugin().updateCapabilities(); - - if (activity != null) - activity.runOnUiThread(new Runnable() { - @Override - public void run() { - updateCapabilities.setText("{fa-bluetooth-b}"); - } - }); - - } - })).start(); - break; } } @@ -148,9 +117,9 @@ public class ComboFragment extends Fragment implements View.OnClickListener { activity.runOnUiThread(new Runnable() { @Override public void run() { - statusText.setText(getPlugin().statusSummary); + statusText.setText(getPlugin().getPump().stateSummary); if (getPlugin().isInitialized()) { - PumpState ps = getPlugin().pumpState; + PumpState ps = getPlugin().getPump().state; if (ps != null) { boolean tbrActive = ps.tbrPercent != -1 && ps.tbrPercent != 100; if (tbrActive) { @@ -183,16 +152,16 @@ public class ComboFragment extends Fragment implements View.OnClickListener { } } - Command lastCmd = getPlugin().lastCmd; + Command lastCmd = getPlugin().getPump().lastCmd; if (lastCmd != null) { lastCmdText.setText(lastCmd.toString()); - lastCmdTimeText.setText(getPlugin().lastCmdTime.toLocaleString()); + lastCmdTimeText.setText(getPlugin().getPump().lastCmdTime.toLocaleString()); } else { lastCmdText.setText(""); lastCmdTimeText.setText(""); } - CommandResult lastCmdResult = getPlugin().lastCmdResult; + CommandResult lastCmdResult = getPlugin().getPump().lastCmdResult; if (lastCmdResult != null && lastCmdResult.message != null) { lastCmdResultText.setText(lastCmdResult.message); lastCmdDurationText.setText(lastCmdResult.duration); @@ -201,7 +170,6 @@ public class ComboFragment extends Fragment implements View.OnClickListener { lastCmdDurationText.setText(""); } } - tbrCapabilityText.setText(getPlugin().getPumpDescription().maxTempPercent + "%"); } }); } 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 2f61f95e80..9b2861ced0 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 @@ -5,13 +5,11 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; -import android.content.SharedPreferences; import android.graphics.Color; import android.media.RingtoneManager; import android.net.Uri; import android.os.IBinder; import android.os.SystemClock; -import android.preference.PreferenceManager; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.app.NotificationCompat; @@ -29,12 +27,11 @@ 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.DetermineCapabilitiesCommand; -import de.jotomo.ruffyscripter.commands.ReadPumpStateCommand; +import de.jotomo.ruffyscripter.commands.GetPumpStateCommand; import de.jotomo.ruffyscripter.commands.SetTbrCommand; -import de.jotomo.ruffyscripter.commands.SetTbrCommandAlt; import info.nightscout.androidaps.BuildConfig; import info.nightscout.androidaps.Config; import info.nightscout.androidaps.MainApp; @@ -49,10 +46,15 @@ import info.nightscout.androidaps.interfaces.PluginBase; import info.nightscout.androidaps.interfaces.PumpDescription; 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.utils.DateUtil; import info.nightscout.utils.SP; -import info.nightscout.utils.ToastUtils; + +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; /** * Created by mike on 05.08.2016. @@ -69,16 +71,10 @@ public class ComboPlugin implements PluginBase, PumpInterface { private RuffyScripter ruffyScripter; private ServiceConnection mRuffyServiceConnection; - // package-protected only so ComboFragment can access these - @NonNull - volatile String statusSummary = "Initializing"; + private ComboPump pump = new ComboPump(); + @Nullable - volatile Command lastCmd; - @Nullable - volatile CommandResult lastCmdResult; - @NonNull - volatile Date lastCmdTime = new Date(0); - volatile PumpState pumpState = new PumpState(); + private volatile BolusCommand runningBolusCommand; private static PumpEnactResult OPERATION_NOT_SUPPORTED = new PumpEnactResult(); @@ -100,7 +96,7 @@ public class ComboPlugin implements PluginBase, PumpInterface { pumpDescription.isBolusCapable = true; pumpDescription.bolusStep = 0.1d; - pumpDescription.isExtendedBolusCapable = false; // TODO + pumpDescription.isExtendedBolusCapable = false; pumpDescription.extendedBolusStep = 0.1d; pumpDescription.extendedBolusDurationStep = 15; pumpDescription.extendedBolusMaxDuration = 12 * 60; @@ -115,18 +111,22 @@ public class ComboPlugin implements PluginBase, PumpInterface { pumpDescription.tempMaxDuration = 24 * 60; - pumpDescription.isSetBasalProfileCapable = false; // TODO + pumpDescription.isSetBasalProfileCapable = false; // TODO GL#14 pumpDescription.basalStep = 0.01d; pumpDescription.basalMinimumRate = 0.0d; pumpDescription.isRefillingCapable = true; } + public ComboPump getPump() { + return pump; + } + /** * The alerter frequently checks the result of the last executed command via the lastCmdResult * field and shows a notification with sound and vibration if an error occurred. * More details on the error can then be looked up in the Combo tab. - * + *

* The alarm is re-raised every 5 minutes for as long as the error persist. As soon * as a command succeeds no more new alerts are raised. */ @@ -139,15 +139,15 @@ public class ComboPlugin implements PluginBase, PumpInterface { int id = 1000; long lastAlarmTime = 0; while (true) { - Command localLastCmd = lastCmd; - CommandResult localLastCmdResult = lastCmdResult; + Command localLastCmd = pump.lastCmd; + CommandResult localLastCmdResult = pump.lastCmdResult; if (localLastCmdResult != null && !localLastCmdResult.success) { long now = System.currentTimeMillis(); long fiveMinutesSinceLastAlarm = lastAlarmTime + (5 * 60 * 1000) + (15 * 1000); if (now > fiveMinutesSinceLastAlarm) { log.error("Command failed: " + localLastCmd); log.error("Command result: " + localLastCmdResult); - PumpState localPumpState = pumpState; + PumpState localPumpState = pump.state; if (localPumpState != null && localPumpState.errorMsg != null) { log.warn("Pump is in error state, displaying; " + localPumpState.errorMsg); } @@ -189,10 +189,11 @@ public class ComboPlugin implements PluginBase, PumpInterface { // this must be the base package of the app (check package attribute in // manifest element in the manifest file of the providing app) "org.monkey.d.ruffy.ruffy", - // full path to the driver + // full path to the driver; // in the logs this service is mentioned as (note the slash) - // "org.monkey.d.ruffy.ruffy/.driver.Ruffy" - //org.monkey.d.ruffy.ruffy is the base package identifier and /.driver.Ruffy the service within the package + // "org.monkey.d.ruffy.ruffy/.driver.Ruffy"; + // org.monkey.d.ruffy.ruffy is the base package identifier + // and /.driver.Ruffy the service within the package "org.monkey.d.ruffy.ruffy.driver.Ruffy" )); context.startService(intent); @@ -201,18 +202,18 @@ public class ComboPlugin implements PluginBase, PumpInterface { @Override public void onServiceConnected(ComponentName name, IBinder service) { - keepUnbound=false; + keepUnbound = false; ruffyScripter.start(IRuffyService.Stub.asInterface(service)); log.debug("ruffy serivce connected"); } @Override public void onServiceDisconnected(ComponentName name) { - ruffyScripter.stop(); + // TODO stop? log.debug("ruffy service disconnected"); - // try to reconnect ruffy service unless unbind was explicitely requested + // try to reconnect ruffy service unless unbind was explicitly requested // via unbindRuffyService - if(!keepUnbound) { + if (!keepUnbound) { SystemClock.sleep(250); bindRuffyService(); } @@ -224,12 +225,13 @@ public class ComboPlugin implements PluginBase, PumpInterface { } if (!boundSucceeded) { - statusSummary = "No connection to ruffy. Pump control not available."; + pump.stateSummary = "No connection to ruffy. Pump control unavailable."; } return true; } private boolean keepUnbound = false; + private void unbindRuffyService() { keepUnbound = true; ruffyScripter.unbind(); @@ -301,17 +303,17 @@ public class ComboPlugin implements PluginBase, PumpInterface { public boolean isInitialized() { // consider initialized when the pump's state was initially fetched, // after that lastCmd* variables will have values - return lastCmdTime.getTime() > 0; + return pump.lastCmdTime.getTime() > 0; } @Override public boolean isSuspended() { - return pumpState != null && pumpState.suspended; + return pump.state != null && pump.state.suspended; } @Override public boolean isBusy() { - return ruffyScripter == null || ruffyScripter.isPumpBusy(); + return ruffyScripter.isPumpBusy(); } // TODO @@ -328,7 +330,7 @@ public class ComboPlugin implements PluginBase, PumpInterface { @Override public Date lastDataTime() { - return lastCmdTime; + return pump.lastCmdTime; } // this method is regularly called from info.nightscout.androidaps.receivers.KeepAliveReceiver @@ -337,18 +339,18 @@ public class ComboPlugin implements PluginBase, PumpInterface { log.debug("RefreshDataFromPump called"); // if Android is sluggish this might get called before ruffy is bound - if (ruffyScripter == null) { - log.warn("Rejecting call to RefreshDataFromPump: ruffy service not bound (yet)"); + if (!ruffyScripter.isRunning()) { + log.warn("Rejecting call to RefreshDataFromPump: scripter not ready yet."); return; } boolean notAUserRequest = !reason.toLowerCase().contains("user"); - boolean wasRunAtLeastOnce = lastCmdTime.getTime() > 0; - boolean ranWithinTheLastMinute = System.currentTimeMillis() < lastCmdTime.getTime() + 60 * 1000; + boolean wasRunAtLeastOnce = pump.lastCmdTime.getTime() > 0; + boolean ranWithinTheLastMinute = System.currentTimeMillis() < pump.lastCmdTime.getTime() + 60 * 1000; if (notAUserRequest && wasRunAtLeastOnce && ranWithinTheLastMinute) { log.debug("Not fetching state from pump, since we did already within the last 60 seconds"); } else { - runCommand(new ReadPumpStateCommand()); + runCommand(new GetPumpStateCommand()); } } @@ -362,8 +364,31 @@ public class ComboPlugin implements PluginBase, PumpInterface { return basal; } - // what a mess: pump integration code reading carb info from Detailed**Bolus**Info, - // writing carb treatments to the history table. What's PumpEnactResult for again? + private static CancellableBolusCommand.ProgressReportCallback bolusProgressReportCallback = + new CancellableBolusCommand.ProgressReportCallback() { + @Override + public void report(CancellableBolusCommand.ProgressReportCallback.State state, int percent, double delivered) { + EventOverviewBolusProgress enent = EventOverviewBolusProgress.getInstance(); + switch (state) { + case DELIVERING: + enent.status = String.format(MainApp.sResources.getString(R.string.bolusdelivering), delivered); + break; + case DELIVERED: + enent.status = String.format(MainApp.sResources.getString(R.string.bolusdelivered), delivered); + break; + case STOPPING: + enent.status = MainApp.sResources.getString(R.string.bolusstopping); + break; + case STOPPED: + enent.status = MainApp.sResources.getString(R.string.bolusstopped); + break; + } + enent.percent = percent; + MainApp.bus().post(enent); + } + }; + + /** Updates Treatment records with carbs and boluses and delivers a bolus if needed */ @Override public PumpEnactResult deliverTreatment(DetailedBolusInfo detailedBolusInfo) { if (detailedBolusInfo.insulin > 0 || detailedBolusInfo.carbs > 0) { @@ -378,7 +403,6 @@ public class ComboPlugin implements PluginBase, PumpInterface { pumpEnactResult.enacted = true; pumpEnactResult.bolusDelivered = 0d; pumpEnactResult.carbsDelivered = detailedBolusInfo.carbs; - pumpEnactResult.comment = MainApp.instance().getString(R.string.virtualpump_resultok); double remainingBolus = detailedBolusInfo.insulin; int split = 1; @@ -413,6 +437,10 @@ public class ComboPlugin implements PluginBase, PumpInterface { pumpEnactResult.carbsDelivered = detailedBolusInfo.carbs; pumpEnactResult.comment = MainApp.instance().getString(R.string.virtualpump_resultok); MainApp.getConfigBuilder().addToHistoryTreatment(detailedBolusInfo); + + EventOverviewBolusProgress bolusingEvent = EventOverviewBolusProgress.getInstance(); + bolusingEvent.percent = 100; + MainApp.bus().post(bolusingEvent); return pumpEnactResult; } } else { @@ -430,7 +458,10 @@ public class ComboPlugin implements PluginBase, PumpInterface { @NonNull private PumpEnactResult deliverBolus(DetailedBolusInfo detailedBolusInfo) { - CommandResult bolusCmdResult = runCommand(new BolusCommand(detailedBolusInfo.insulin)); + runningBolusCommand = Config.comboExperimentalBolus + ? new CancellableBolusCommand(detailedBolusInfo.insulin, bolusProgressReportCallback) + : new BolusCommand(detailedBolusInfo.insulin); + CommandResult bolusCmdResult = runCommand(runningBolusCommand); PumpEnactResult pumpEnactResult = new PumpEnactResult(); pumpEnactResult.success = bolusCmdResult.success; pumpEnactResult.enacted = bolusCmdResult.enacted; @@ -454,14 +485,20 @@ public class ComboPlugin implements PluginBase, PumpInterface { return pumpEnactResult; } + @Override + public void stopBolusDelivering() { + BolusCommand localRunningBolusCommand = runningBolusCommand; + if (localRunningBolusCommand != null) localRunningBolusCommand.requestCancellation(); + } + private CommandResult runCommand(Command command) { if (ruffyScripter == null) { String msg = "No connection to ruffy. Pump control not available."; - statusSummary = msg; + pump.stateSummary = msg; return new CommandResult().message(msg); } - statusSummary = "Executing " + command; + pump.stateSummary = "Executing " + command; MainApp.bus().post(new EventComboPumpUpdateGUI()); CommandResult commandResult = ruffyScripter.runCommand(command); @@ -470,30 +507,23 @@ public class ComboPlugin implements PluginBase, PumpInterface { log.error("Exception received from pump", commandResult.exception); } - lastCmd = command; - lastCmdTime = new Date(); - lastCmdResult = commandResult; - pumpState = commandResult.state; + pump.lastCmd = command; + pump.lastCmdTime = new Date(); + pump.lastCmdResult = commandResult; + pump.state = commandResult.state; if (commandResult.success && commandResult.state.suspended) { - statusSummary = "Suspended"; + pump.stateSummary = "Suspended"; } else if (commandResult.success) { - statusSummary = "Idle"; + pump.stateSummary = "Idle"; } else { - statusSummary = "Error"; + pump.stateSummary = "Error"; } MainApp.bus().post(new EventComboPumpUpdateGUI()); return commandResult; } - @Override - public void stopBolusDelivering() { - // there's no way to stop the combo once delivery has started - // but before that, we could interrupt the command thread ... pause - // till pump times out or raises an error - } - // Note: AAPS calls this only to enact OpenAPS recommendations @Override public PumpEnactResult setTempBasalAbsolute(Double absoluteRate, Integer durationInMinutes, boolean force) { @@ -528,10 +558,7 @@ public class ComboPlugin implements PluginBase, PumpInterface { adjustedPercent = rounded.intValue(); } - Command cmd = !Config.comboUseAlternateSetTbrCommand - ? new SetTbrCommand(adjustedPercent, durationInMinutes) - : new SetTbrCommandAlt(adjustedPercent, durationInMinutes); - CommandResult commandResult = runCommand(cmd); + CommandResult commandResult = runCommand(new SetTbrCommand(adjustedPercent, durationInMinutes)); if (commandResult.enacted) { TemporaryBasal tempStart = new TemporaryBasal(commandResult.completionTime); @@ -584,7 +611,7 @@ public class ComboPlugin implements PluginBase, PumpInterface { tempBasal.source = Source.USER; pumpEnactResult.isTempCancel = true; } - } else if ((activeTemp.percentRate >= 90 && activeTemp.percentRate <= 110) && activeTemp.getPlannedRemainingMinutes() <= 15 ) { + } else if ((activeTemp.percentRate >= 90 && activeTemp.percentRate <= 110) && activeTemp.getPlannedRemainingMinutes() <= 15) { // Let fake neutral temp keep running (see below) log.debug("cancelTempBasal: skipping changing tbr since it already is at " + activeTemp.percentRate + "% and running for another " + activeTemp.getPlannedRemainingMinutes() + " mins."); pumpEnactResult.comment = "cancelTempBasal skipping changing tbr since it already is at " + activeTemp.percentRate + "% and running for another " + activeTemp.getPlannedRemainingMinutes() + " mins."; @@ -597,7 +624,7 @@ 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; + long percentage = (activeTemp.percentRate > 100) ? 110 : 90; log.debug("cancelTempBasal: changing tbr to " + percentage + "% for 15 mins."); commandResult = runCommand(new SetTbrCommand(percentage, 15)); if (commandResult.enacted) { @@ -622,7 +649,6 @@ public class ComboPlugin implements PluginBase, PumpInterface { return pumpEnactResult; } - // TODO @Override public PumpEnactResult cancelExtendedBolus() { return OPERATION_NOT_SUPPORTED; @@ -632,41 +658,41 @@ 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 (lastCmdTime.getTime() + 5 * 60 * 1000L < System.currentTimeMillis()) { + if (true) { //pump.lastCmdTime.getTime() + 5 * 60 * 1000L < System.currentTimeMillis()) { return null; } try { - JSONObject pump = new JSONObject(); - JSONObject status = new JSONObject(); - JSONObject extended = new JSONObject(); - status.put("status", statusSummary); - extended.put("Version", BuildConfig.VERSION_NAME + "-" + BuildConfig.BUILDVERSION); + JSONObject pumpJson = new JSONObject(); + JSONObject statusJson = new JSONObject(); + JSONObject extendedJson = new JSONObject(); + statusJson.put("status", pump.stateSummary); + extendedJson.put("Version", BuildConfig.VERSION_NAME + "-" + BuildConfig.BUILDVERSION); try { - extended.put("ActiveProfile", MainApp.getConfigBuilder().getProfileName()); + extendedJson.put("ActiveProfile", MainApp.getConfigBuilder().getProfileName()); } catch (Exception e) { } - status.put("timestamp", lastCmdTime); + statusJson.put("timestamp", pump.lastCmdTime); - PumpState ps = pumpState; + PumpState ps = pump.state; if (ps != null) { if (ps.tbrActive) { - extended.put("TempBasalAbsoluteRate", ps.tbrRate); - extended.put("TempBasalPercent", ps.tbrPercent); - extended.put("TempBasalRemaining", ps.tbrRemainingDuration); + extendedJson.put("TempBasalAbsoluteRate", ps.tbrRate); + extendedJson.put("TempBasalPercent", ps.tbrPercent); + extendedJson.put("TempBasalRemaining", ps.tbrRemainingDuration); } if (ps.errorMsg != null) { - extended.put("ErrorMessage", ps.errorMsg); + extendedJson.put("ErrorMessage", ps.errorMsg); } } // more info here .... look at dana plugin - pump.put("status", status); - pump.put("extended", extended); - pump.put("clock", DateUtil.toISOString(lastCmdTime)); + pumpJson.put("status", statusJson); + pumpJson.put("extended", extendedJson); + pumpJson.put("clock", DateUtil.toISOString(pump.lastCmdTime)); - return pump; + return pumpJson; } catch (Exception e) { log.warn("Failed to gather device status for upload", e); } @@ -688,7 +714,8 @@ public class ComboPlugin implements PluginBase, PumpInterface { @Override public String shortStatus(boolean veryShort) { - return statusSummary; + // TODO trim for wear if veryShort==true + return pump.stateSummary; } @Override @@ -698,40 +725,7 @@ public class ComboPlugin implements PluginBase, PumpInterface { @SuppressWarnings("UnusedParameters") @Subscribe - public void onStatusEvent(final EventAppExit e) { + public void onStatusEvent(final EventAppExit ignored) { unbindRuffyService(); } - - - public void updateCapabilities() { - - // if Android is sluggish this might get called before ruffy is bound - if (ruffyScripter == null) { - log.warn("Rejecting call to RefreshDataFromPump: ruffy service not bound (yet)"); - ToastUtils.showToastInUiThread(MainApp.instance(), "Ruffy not initialized."); - return; - } - if (isBusy()){ - ToastUtils.showToastInUiThread(MainApp.instance(), "Pump busy!"); - return; - } - CommandResult result = runCommand(new DetermineCapabilitiesCommand()); - if (result.success){ - pumpDescription.maxTempPercent = (int) result.capabilities.maxTempPercent; - SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(MainApp.instance()); - SharedPreferences.Editor editor = preferences.edit(); - editor.putInt(COMBO_MAX_TEMP_PERCENT_SP, pumpDescription.maxTempPercent); - editor.commit(); - MainApp.bus().post(new EventComboPumpUpdateGUI()); - } else { - ToastUtils.showToastInUiThread(MainApp.instance(), "No success."); - } - } - - -} - - -// If you want update fragment call -// MainApp.bus().post(new EventComboPumpUpdateGUI()); -// fragment should fetch data from plugin and display status, buttons etc ... +} \ No newline at end of file diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ComboPump.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ComboPump.java new file mode 100644 index 0000000000..d24a31607c --- /dev/null +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpCombo/ComboPump.java @@ -0,0 +1,22 @@ +package info.nightscout.androidaps.plugins.PumpCombo; + +import android.support.annotation.NonNull; +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; + +class ComboPump { + @NonNull + volatile String stateSummary = "Initializing"; + @Nullable + volatile Command lastCmd; + @Nullable + volatile CommandResult lastCmdResult; + @NonNull + volatile Date lastCmdTime = new Date(0); + volatile PumpState state = new PumpState(); +} diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/DanaRPlugin.java b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/DanaRPlugin.java index bc5b23ad92..b8ee6db8bf 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/DanaRPlugin.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/PumpDanaR/DanaRPlugin.java @@ -5,6 +5,7 @@ import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.os.IBinder; +import android.support.annotation.NonNull; import android.support.annotation.Nullable; import com.squareup.otto.Subscribe; @@ -66,6 +67,7 @@ public class DanaRPlugin implements PluginBase, PumpInterface, DanaRInterface, C public static DanaRExecutionService sExecutionService; + @NonNull private static DanaRPump pump = DanaRPump.getInstance(); private static boolean useExtendedBoluses = false; @@ -743,21 +745,17 @@ public class DanaRPlugin implements PluginBase, PumpInterface, DanaRInterface, C return true; } - @SuppressWarnings("PointlessBooleanExpression") @Override public Double applyBasalConstraints(Double absoluteRate) { double origAbsoluteRate = absoluteRate; - if (pump != null) { - if (absoluteRate > pump.maxBasal) { - absoluteRate = pump.maxBasal; - if (Config.logConstraintsChanges && origAbsoluteRate != Constants.basalAbsoluteOnlyForCheckLimit) - log.debug("Limiting rate " + origAbsoluteRate + "U/h by pump constraint to " + absoluteRate + "U/h"); - } + if (absoluteRate > pump.maxBasal) { + absoluteRate = pump.maxBasal; + if (Config.logConstraintsChanges && origAbsoluteRate != Constants.basalAbsoluteOnlyForCheckLimit) + log.debug("Limiting rate " + origAbsoluteRate + "U/h by pump constraint to " + absoluteRate + "U/h"); } return absoluteRate; } - @SuppressWarnings("PointlessBooleanExpression") @Override public Integer applyBasalConstraints(Integer percentRate) { Integer origPercentRate = percentRate; @@ -769,16 +767,13 @@ public class DanaRPlugin implements PluginBase, PumpInterface, DanaRInterface, C return percentRate; } - @SuppressWarnings("PointlessBooleanExpression") @Override public Double applyBolusConstraints(Double insulin) { double origInsulin = insulin; - if (pump != null) { - if (insulin > pump.maxBolus) { - insulin = pump.maxBolus; - if (Config.logConstraintsChanges && origInsulin != Constants.bolusOnlyForCheckLimit) - log.debug("Limiting bolus " + origInsulin + "U by pump constraint to " + insulin + "U"); - } + if (insulin > pump.maxBolus) { + insulin = pump.maxBolus; + if (Config.logConstraintsChanges && origInsulin != Constants.bolusOnlyForCheckLimit) + log.debug("Limiting bolus " + origInsulin + "U by pump constraint to " + insulin + "U"); } return insulin; } diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/Treatments/TreatmentsFragment.java b/app/src/main/java/info/nightscout/androidaps/plugins/Treatments/TreatmentsFragment.java index 1bf66ca5ec..9f839e6fef 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/Treatments/TreatmentsFragment.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/Treatments/TreatmentsFragment.java @@ -47,6 +47,9 @@ public class TreatmentsFragment extends Fragment implements View.OnClickListener profileSwitchTab = (TextView) view.findViewById(R.id.treatments_profileswitches); treatmentsTab.setOnClickListener(this); extendedBolusesTab.setOnClickListener(this); + if (!MainApp.getConfigBuilder().getPumpDescription().isExtendedBolusCapable) { + extendedBolusesTab.setVisibility(View.GONE); + } tempBasalsTab.setOnClickListener(this); tempTargetTab.setOnClickListener(this); profileSwitchTab.setOnClickListener(this); diff --git a/app/src/main/java/info/nightscout/androidaps/plugins/Wear/ActionStringHandler.java b/app/src/main/java/info/nightscout/androidaps/plugins/Wear/ActionStringHandler.java index e60e30d158..a0760a84ee 100644 --- a/app/src/main/java/info/nightscout/androidaps/plugins/Wear/ActionStringHandler.java +++ b/app/src/main/java/info/nightscout/androidaps/plugins/Wear/ActionStringHandler.java @@ -440,7 +440,7 @@ public class ActionStringHandler { ret += "OPEN LOOP\n"; } final APSInterface aps = MainApp.getConfigBuilder().getActiveAPS(); - ret += "APS: " + ((aps == null) ? "NO APS SELECTED!" : ((PluginBase) aps).getName()); + ret += "APS: " + ((aps == null) ? R.string.noapsselected : ((PluginBase) aps).getName()); if (activeloop.lastRun != null) { if (activeloop.lastRun.lastAPSRun != null) ret += "\nLast Run: " + DateUtil.timeString(activeloop.lastRun.lastAPSRun); diff --git a/app/src/main/res/layout/combopump_fragment.xml b/app/src/main/res/layout/combopump_fragment.xml index a4270240ef..b515ceda2f 100644 --- a/app/src/main/res/layout/combopump_fragment.xml +++ b/app/src/main/res/layout/combopump_fragment.xml @@ -418,60 +418,6 @@ - - - - - - - - - - - - - ACTIVATE PROFILE Date INVALID + Stopping bolus delivery + Bolus delivery stopped diff --git a/wear/src/main/java/info/nightscout/androidaps/interaction/actions/BolusActivity.java b/wear/src/main/java/info/nightscout/androidaps/interaction/actions/BolusActivity.java index 814020d742..4860cb3d56 100644 --- a/wear/src/main/java/info/nightscout/androidaps/interaction/actions/BolusActivity.java +++ b/wear/src/main/java/info/nightscout/androidaps/interaction/actions/BolusActivity.java @@ -70,6 +70,7 @@ public class BolusActivity extends ViewSelectorActivity { if (editInsulin != null){ def = SafeParse.stringToDouble(editInsulin.editText.getText().toString()); } + // TODO use pump supported stet size editInsulin = new PlusMinusEditText(view, R.id.amountfield, R.id.plusbutton, R.id.minusbutton, def, 0d, 30d, 0.1d, new DecimalFormat("#0.0"), false); setLabelToPlusMinusView(view, "insulin"); container.addView(view);