Source ruffy scripter for the time being (already some fixes and tweaks in).
This commit is contained in:
parent
481c63fa57
commit
3280092566
17 changed files with 1054 additions and 0 deletions
|
@ -0,0 +1,21 @@
|
|||
// IRTHandler.aidl
|
||||
package org.monkey.d.ruffy.ruffy.driver;
|
||||
|
||||
// Declare any non-default types here with import statements
|
||||
|
||||
import org.monkey.d.ruffy.ruffy.driver.display.Menu;
|
||||
|
||||
interface IRTHandler {
|
||||
void log(String message);
|
||||
void fail(String message);
|
||||
|
||||
void requestBluetooth();
|
||||
void rtStopped();
|
||||
void rtStarted();
|
||||
|
||||
void rtClearDisplay();
|
||||
void rtUpdateDisplay(in byte[] quarter, int which);
|
||||
|
||||
void rtDisplayHandleMenu(in Menu menu);
|
||||
void rtDisplayHandleNoMenu();
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
// IRuffyService.aidl
|
||||
package org.monkey.d.ruffy.ruffy.driver;
|
||||
|
||||
// Declare any non-default types here with import statements
|
||||
import org.monkey.d.ruffy.ruffy.driver.IRTHandler;
|
||||
|
||||
interface IRuffyService {
|
||||
|
||||
void setHandler(IRTHandler handler);
|
||||
int doRTConnect();
|
||||
void doRTDisconnect();
|
||||
void rtSendKey(byte keyCode, boolean changed);
|
||||
void resetPairing();
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
package org.monkey.d.ruffy.ruffy.driver.display;
|
||||
|
||||
parcelable Menu;
|
286
app/src/main/java/de/jotomo/ruffyscripter/RuffyScripter.java
Normal file
286
app/src/main/java/de/jotomo/ruffyscripter/RuffyScripter.java
Normal file
|
@ -0,0 +1,286 @@
|
|||
package de.jotomo.ruffyscripter;
|
||||
|
||||
import android.os.RemoteException;
|
||||
import android.os.SystemClock;
|
||||
|
||||
import org.monkey.d.ruffy.ruffy.driver.IRTHandler;
|
||||
import org.monkey.d.ruffy.ruffy.driver.IRuffyService;
|
||||
import org.monkey.d.ruffy.ruffy.driver.display.Menu;
|
||||
import org.monkey.d.ruffy.ruffy.driver.display.MenuType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import de.jotomo.ruffyscripter.commands.Command;
|
||||
import de.jotomo.ruffyscripter.commands.CommandException;
|
||||
import de.jotomo.ruffyscripter.commands.CommandResult;
|
||||
|
||||
// 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
|
||||
// already logs those requests somewhere ... and verify they have all been ack'd by the pump properly
|
||||
|
||||
/**
|
||||
* provides scripting 'runtime' and operations. consider moving operations into a separate
|
||||
* class and inject that into executing commands, so that commands operately solely on
|
||||
* operations and are cleanly separated from the thread management, connection management etc
|
||||
*/
|
||||
public class RuffyScripter {
|
||||
private static final Logger log = LoggerFactory.getLogger(RuffyScripter.class);
|
||||
|
||||
public volatile Menu currentMenu;
|
||||
|
||||
private final IRuffyService ruffyService;
|
||||
private volatile CommandResult cmdResult;
|
||||
private volatile long menuLastUpdated = 0;
|
||||
|
||||
public RuffyScripter(IRuffyService ruffyService) {
|
||||
this.ruffyService = ruffyService;
|
||||
try {
|
||||
ruffyService.setHandler(mHandler);
|
||||
} catch (RemoteException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private IRTHandler mHandler = new IRTHandler.Stub() {
|
||||
@Override
|
||||
public void log(String message) throws RemoteException {
|
||||
log.trace(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fail(String message) throws RemoteException {
|
||||
log.warn(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestBluetooth() throws RemoteException {
|
||||
log.trace("Ruffy invoked requestBluetooth callback");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rtStopped() throws RemoteException {
|
||||
log.debug("rtStopped callback invoked");
|
||||
currentMenu = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rtStarted() throws RemoteException {
|
||||
log.debug("rtStarted callback invoked");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rtClearDisplay() throws RemoteException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rtUpdateDisplay(byte[] quarter, int which) throws RemoteException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rtDisplayHandleMenu(Menu menu) throws RemoteException {
|
||||
// method is called every ~500ms
|
||||
log.debug("rtDisplayHandleMenu: " + menu.getType());
|
||||
|
||||
currentMenu = menu;
|
||||
menuLastUpdated = System.currentTimeMillis();
|
||||
|
||||
// note that a WARNING_OR_ERROR menu can be a valid temporary state (cancelling TBR)
|
||||
// of a running command
|
||||
if (activeCmd == null && currentMenu.getType() == MenuType.WARNING_OR_ERROR) {
|
||||
log.warn("Warning/error menu encountered without a command running");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rtDisplayHandleNoMenu() throws RemoteException {
|
||||
log.debug("rtDisplayHandleNoMenu callback invoked");
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
public boolean isPumpBusy() {
|
||||
return activeCmd != null; // || currentMenu == null || currentMenu.getType() != MenuType.MAIN_MENU;
|
||||
}
|
||||
|
||||
private volatile Command activeCmd = null;
|
||||
|
||||
// TODO take a callback to call when command finishes?
|
||||
// TODO sometimes hangs ... we should timeout and raise an alert to check the pump and ruffy
|
||||
// TODO not getting a menu update in a while is a good cue ...
|
||||
// fire up a monitoring thread for that?
|
||||
public CommandResult runCommand(final Command cmd) {
|
||||
try {
|
||||
if (isPumpBusy()) {
|
||||
return new CommandResult().message("Pump is busy");
|
||||
}
|
||||
ensureConnected();
|
||||
|
||||
// TODO make this a safe lock
|
||||
synchronized (this) {
|
||||
cmdResult = null;
|
||||
activeCmd = cmd;
|
||||
final RuffyScripter scripter = this;
|
||||
// TODO hackish, to say the least ...
|
||||
// wait till pump is ready for input
|
||||
waitForMenuUpdate();
|
||||
log.debug("Cmd execution: connection ready, executing cmd " + cmd);
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
cmdResult = cmd.execute(scripter);
|
||||
} catch (Exception e) {
|
||||
cmdResult = new CommandResult().exception(e).message("Unexpected exception running cmd");
|
||||
} finally {
|
||||
activeCmd = null;
|
||||
}
|
||||
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
// TODO really?
|
||||
while (activeCmd != null) {
|
||||
SystemClock.sleep(500);
|
||||
log.trace("Waiting for running command to complete");
|
||||
}
|
||||
log.debug("Command result: " + cmdResult);
|
||||
|
||||
CommandResult r = cmdResult;
|
||||
cmdResult = null;
|
||||
return r;
|
||||
} catch (CommandException e) {
|
||||
return e.toCommandResult();
|
||||
} catch (Exception e) {
|
||||
return new CommandResult().exception(e).message("Unexpected exception communication with ruffy");
|
||||
}
|
||||
}
|
||||
|
||||
public void ensureConnected() {
|
||||
// did we get a menu update from the pump in the last 5s? Then we're connected
|
||||
if (currentMenu != null && menuLastUpdated + 5000 > System.currentTimeMillis()) {
|
||||
log.debug("Pump is sending us menu updating, so we're connected");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
boolean connectSuccesful = ruffyService.doRTConnect() == 0;
|
||||
log.debug("Connect init successful: " + connectSuccesful);
|
||||
while (currentMenu == null) {
|
||||
log.debug("Waiting for first menu update to be sent");
|
||||
waitForMenuUpdate();
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
throw new CommandException().exception(e).message("Unexpected exception while initiating/restoring pump connection");
|
||||
}
|
||||
}
|
||||
|
||||
public CommandResult disconnect() {
|
||||
try {
|
||||
ruffyService.doRTDisconnect();
|
||||
} catch (RemoteException e) {
|
||||
return new CommandResult().exception(e).message("Unexpected exception trying to disconnect");
|
||||
}
|
||||
return new CommandResult().success(true);
|
||||
}
|
||||
|
||||
// below: methods to be used by commands
|
||||
|
||||
private static class Key {
|
||||
static byte NO_KEY = (byte) 0x00;
|
||||
static byte MENU = (byte) 0x03;
|
||||
static byte CHECK = (byte) 0x0C;
|
||||
static byte UP = (byte) 0x30;
|
||||
static byte DOWN = (byte) 0xC0;
|
||||
}
|
||||
|
||||
public void pressUpKey() {
|
||||
pressKey(Key.UP);
|
||||
}
|
||||
|
||||
public void pressDownKey() {
|
||||
pressKey(Key.DOWN);
|
||||
}
|
||||
|
||||
public void pressCheckKey() {
|
||||
pressKey(Key.CHECK);
|
||||
}
|
||||
|
||||
public void pressMenuKey() {
|
||||
// TODO build 'wait for menu update' into this method? get current menu, press key, wait for update?
|
||||
pressKey(Key.MENU);
|
||||
}
|
||||
|
||||
/** Wait until the menu update is in */
|
||||
public void waitForMenuUpdate() {
|
||||
long timeoutExpired = System.currentTimeMillis() + 90 * 1000;
|
||||
long initialUpdateTime = menuLastUpdated;
|
||||
while (initialUpdateTime == menuLastUpdated) {
|
||||
if(System.currentTimeMillis() > timeoutExpired) {
|
||||
throw new CommandException().message("Timeout waiting for menu update");
|
||||
}
|
||||
SystemClock.sleep(50);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* "Virtual" key, emulated by pressing menu and up simultaneously
|
||||
*/
|
||||
// Doesn't work
|
||||
/* public void pressBackKey() throws RemoteException {
|
||||
ruffyService.rtSendKey(Key.MENU, true);
|
||||
SystemClock.sleep(50);
|
||||
ruffyService.rtSendKey(Key.UP, true);
|
||||
SystemClock.sleep(100);
|
||||
ruffyService.rtSendKey(Key.NO_KEY, true);
|
||||
}*/
|
||||
|
||||
private void pressKey(final byte key) {
|
||||
try {
|
||||
ruffyService.rtSendKey(key, true);
|
||||
SystemClock.sleep(100);
|
||||
ruffyService.rtSendKey(Key.NO_KEY, true);
|
||||
} catch (RemoteException e) {
|
||||
throw new CommandException().exception(e).message("Error while pressing buttons");
|
||||
}
|
||||
}
|
||||
|
||||
public void navigateToMenu(MenuType desiredMenu) {
|
||||
// TODO menu var might not have been initialized if this is called to early
|
||||
// though that's gonna be a problem for all code;
|
||||
// wait during init till this is set? create a getter for currentMenu to do this?
|
||||
MenuType startedFrom = currentMenu.getType();
|
||||
while (currentMenu.getType() != desiredMenu) {
|
||||
MenuType currentType = currentMenu.getType();
|
||||
/* if (currentType == startedFrom) {
|
||||
// TODO don't trigger right away, that's always a match ;-)
|
||||
// failed to find the menu, after going through all the menus, bail out
|
||||
throw new CommandException(false, null, "Menu not found searching for " + desiredMenu);
|
||||
}*/
|
||||
pressMenuKey();
|
||||
waitForMenuToBeLeft(currentType);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait till a menu changed has completed, "away" from the menu provided as argument.
|
||||
*/
|
||||
public void waitForMenuToBeLeft(MenuType menuType) {
|
||||
while (currentMenu.getType() == menuType) {
|
||||
SystemClock.sleep(250);
|
||||
}
|
||||
}
|
||||
|
||||
public void verifyMenuIsDisplayed(MenuType menu) {
|
||||
String message = "Invalid pump state, expected to be in menu "
|
||||
+ menu + ", but current menu is " + currentMenu.getType();
|
||||
verifyMenuIsDisplayed(menu, message);
|
||||
}
|
||||
|
||||
public void verifyMenuIsDisplayed(MenuType menu, String message) {
|
||||
waitForMenuUpdate();
|
||||
if (currentMenu.getType() != menu) {
|
||||
throw new CommandException().message(message);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
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.Locale;
|
||||
|
||||
import de.jotomo.ruffyscripter.RuffyScripter;
|
||||
|
||||
public class BolusCommand implements Command {
|
||||
private static final Logger log = LoggerFactory.getLogger(BolusCommand.class);
|
||||
|
||||
private final double bolus;
|
||||
|
||||
public BolusCommand(double bolus) {
|
||||
this.bolus = bolus;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommandResult execute(RuffyScripter scripter) {
|
||||
try {
|
||||
scripter.verifyMenuIsDisplayed(MenuType.MAIN_MENU);
|
||||
enterBolusMenu(scripter);
|
||||
inputBolusAmount(scripter);
|
||||
SystemClock.sleep(500);
|
||||
verifyDisplayedBolusAmount(scripter);
|
||||
|
||||
// confirm bolus
|
||||
scripter.pressCheckKey();
|
||||
|
||||
// the pump displays the entered bolus and waits a bit to let user check and cancel
|
||||
scripter.waitForMenuToBeLeft(MenuType.BOLUS_ENTER);
|
||||
|
||||
scripter.verifyMenuIsDisplayed(MenuType.MAIN_MENU,
|
||||
"Pump did not return to MAIN_MEU from BOLUS_ENTER to deliver bolus. "
|
||||
+ "Check pump manually, the bolus might not have been delivered.");
|
||||
|
||||
// wait for bolus delivery to complete
|
||||
Double bolusRemaining = (Double) scripter.currentMenu.getAttribute(MenuAttribute.BOLUS_REMAINING);
|
||||
while (bolusRemaining != null) {
|
||||
log.debug("Delivering bolus, remaining: " + bolusRemaining);
|
||||
SystemClock.sleep(200);
|
||||
bolusRemaining = (Double) scripter.currentMenu.getAttribute(MenuAttribute.BOLUS_REMAINING);
|
||||
}
|
||||
|
||||
// TODO what if we hit 'cartridge low' alert here? is it immediately displayed or after the bolus?
|
||||
// TODO how are error states reported back to the caller that occur outside of calls in genal? Low battery, low cartridge?
|
||||
|
||||
// make sure no alert (occlusion, cartridge empty) has occurred.
|
||||
scripter.verifyMenuIsDisplayed(MenuType.MAIN_MENU,
|
||||
"Bolus delivery did not complete as expected. "
|
||||
+ "Check pump manually, the bolus might not have been delivered.");
|
||||
|
||||
return new CommandResult().success(true).enacted(true)
|
||||
.message(String.format(Locale.US, "Delivered %02.1f U", bolus));
|
||||
} catch (CommandException e) {
|
||||
return e.toCommandResult();
|
||||
}
|
||||
}
|
||||
|
||||
private void enterBolusMenu(RuffyScripter scripter) {
|
||||
scripter.navigateToMenu(MenuType.BOLUS_MENU);
|
||||
scripter.pressCheckKey();
|
||||
scripter.waitForMenuUpdate();
|
||||
scripter.verifyMenuIsDisplayed(MenuType.BOLUS_ENTER);
|
||||
}
|
||||
|
||||
private void inputBolusAmount(RuffyScripter scripter) {
|
||||
// press 'up' once for each 0.1 U increment
|
||||
long steps = Math.round(bolus * 10);
|
||||
for (int i = 0; i < steps; i++) {
|
||||
scripter.pressUpKey();
|
||||
SystemClock.sleep(100);
|
||||
}
|
||||
}
|
||||
|
||||
private void verifyDisplayedBolusAmount(RuffyScripter scripter) {
|
||||
double displayedBolus = (double) scripter.currentMenu.getAttribute(MenuAttribute.BOLUS);
|
||||
log.debug("Final bolus: " + displayedBolus);
|
||||
// TODO can't we just use BigDecimal? doubles aren't precise ...
|
||||
if (Math.abs(displayedBolus - bolus) > 0.001) {
|
||||
throw new CommandException().message("Failed to set correct bolus. Expected: " + bolus + ", actual: " + displayedBolus);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
package de.jotomo.ruffyscripter.commands;
|
||||
|
||||
import org.monkey.d.ruffy.ruffy.driver.display.MenuAttribute;
|
||||
import org.monkey.d.ruffy.ruffy.driver.display.MenuType;
|
||||
|
||||
import de.jotomo.ruffyscripter.RuffyScripter;
|
||||
|
||||
// TODO robustness: can a TBR run out, whilst we're trying to cancel it?
|
||||
public class CancelTbrCommand implements Command {
|
||||
@Override
|
||||
public CommandResult execute(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) {
|
||||
return new CommandResult().success(true).enacted(false).message("No TBR active");
|
||||
}
|
||||
return new SetTbrCommand(100, 0).execute(scripter);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package de.jotomo.ruffyscripter.commands;
|
||||
|
||||
import de.jotomo.ruffyscripter.RuffyScripter;
|
||||
|
||||
public interface Command {
|
||||
CommandResult execute(RuffyScripter ruffyScripter);
|
||||
|
||||
// default String toString() {
|
||||
// return getClass().getSimpleName();
|
||||
// }
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
package de.jotomo.ruffyscripter.commands;
|
||||
|
||||
public class CommandException extends RuntimeException {
|
||||
public boolean success = false;
|
||||
public boolean enacted = false;
|
||||
public Exception exception = null;
|
||||
public String message = null;
|
||||
|
||||
public CommandException() {}
|
||||
|
||||
public CommandException success(boolean success) {
|
||||
this.success = success;
|
||||
return this;
|
||||
}
|
||||
|
||||
public CommandException enacted(boolean enacted) {
|
||||
this.enacted = enacted;
|
||||
return this;
|
||||
}
|
||||
|
||||
public CommandException exception(Exception exception) {
|
||||
this.exception = exception;
|
||||
return this;
|
||||
}
|
||||
|
||||
public CommandException message(String message) {
|
||||
this.message = message;
|
||||
return this;
|
||||
}
|
||||
|
||||
public CommandResult toCommandResult() {
|
||||
return new CommandResult().success(success).enacted(enacted).exception(exception).message(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "CommandException{" +
|
||||
"success=" + success +
|
||||
", enacted=" + enacted +
|
||||
", exception=" + exception +
|
||||
", message='" + message + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
package de.jotomo.ruffyscripter.commands;
|
||||
|
||||
public class CommandResult {
|
||||
public boolean success;
|
||||
public boolean enacted;
|
||||
public Exception exception;
|
||||
public String message;
|
||||
|
||||
public CommandResult() {
|
||||
}
|
||||
|
||||
public CommandResult success(boolean success) {
|
||||
this.success = success;
|
||||
return this;
|
||||
}
|
||||
|
||||
public CommandResult enacted(boolean enacted) {
|
||||
this.enacted = enacted;
|
||||
return this;
|
||||
}
|
||||
|
||||
public CommandResult exception(Exception exception) {
|
||||
this.exception = exception;
|
||||
return this;
|
||||
}
|
||||
|
||||
public CommandResult message(String message) {
|
||||
this.message = message;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "CommandResult{" +
|
||||
"success=" + success +
|
||||
", enacted=" + enacted +
|
||||
", exception=" + exception +
|
||||
", message='" + message + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
|
@ -0,0 +1,238 @@
|
|||
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.Locale;
|
||||
|
||||
import de.jotomo.ruffyscripter.RuffyScripter;
|
||||
|
||||
public class SetTbrCommand implements Command {
|
||||
private static final Logger log = LoggerFactory.getLogger(SetTbrCommand.class);
|
||||
|
||||
private final long percentage;
|
||||
private final long duration;
|
||||
|
||||
public SetTbrCommand(long percentage, long duration) {
|
||||
this.percentage = percentage;
|
||||
this.duration = duration;
|
||||
|
||||
if (percentage % 10 != 0) {
|
||||
throw new IllegalArgumentException("TBR percentage must be set in 10% steps");
|
||||
}
|
||||
if (percentage < 0 || percentage > 500) {
|
||||
throw new IllegalArgumentException("TBR percentage must be within 0-500%");
|
||||
}
|
||||
|
||||
if (percentage != 100) {
|
||||
if (duration % 15 != 0) {
|
||||
throw new IllegalArgumentException("TBR duration can only be set in 15 minute steps");
|
||||
}
|
||||
if (duration > 60 * 24) {
|
||||
throw new IllegalArgumentException("Maximum TBR duration is 24 hours");
|
||||
}
|
||||
}
|
||||
|
||||
if (percentage == 0 && duration > 120) {
|
||||
throw new IllegalArgumentException("Max allowed zero-temp duration is 2h");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommandResult execute(RuffyScripter scripter) {
|
||||
try {
|
||||
scripter.verifyMenuIsDisplayed(MenuType.MAIN_MENU);
|
||||
enterTbrMenu(scripter);
|
||||
inputTbrPercentage(scripter);
|
||||
SystemClock.sleep(500);
|
||||
verifyDisplayedTbrPercentage(scripter);
|
||||
|
||||
if (percentage == 100) {
|
||||
cancelTbrAndConfirmCancellationWarning(scripter);
|
||||
} else {
|
||||
// switch to TBR_DURATION menu by pressing menu key
|
||||
scripter.pressMenuKey();
|
||||
scripter.verifyMenuIsDisplayed(MenuType.TBR_DURATION);
|
||||
|
||||
inputTbrDuration(scripter);
|
||||
SystemClock.sleep(500);
|
||||
verifyDisplayedTbrDuration(scripter);
|
||||
|
||||
// confirm TBR
|
||||
scripter.pressCheckKey();
|
||||
SystemClock.sleep(500);
|
||||
}
|
||||
|
||||
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.navigateToMenu(MenuType.TBR_MENU);
|
||||
scripter.pressCheckKey();
|
||||
scripter.waitForMenuUpdate();
|
||||
scripter.verifyMenuIsDisplayed(MenuType.TBR_SET);
|
||||
}
|
||||
|
||||
private void inputTbrPercentage(RuffyScripter scripter) {
|
||||
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++) {
|
||||
if (increasePercentage) scripter.pressUpKey();
|
||||
else scripter.pressDownKey();
|
||||
// TODO waitForMenuChange instead // or have key press method handle that??
|
||||
SystemClock.sleep(100);
|
||||
log.debug("Push #" + (i + 1));
|
||||
}
|
||||
}
|
||||
|
||||
private void verifyDisplayedTbrPercentage(RuffyScripter scripter) {
|
||||
long displayedPercentage = readDisplayedTbrPercentage(scripter);
|
||||
if (displayedPercentage != this.percentage) {
|
||||
log.debug("Final displayed TBR percentage: " + displayedPercentage);
|
||||
throw new CommandException().message("Failed to set TBR percentage");
|
||||
}
|
||||
}
|
||||
|
||||
private long readDisplayedTbrPercentage(RuffyScripter scripter) {
|
||||
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) {
|
||||
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:43, hence not in 15 minute steps.
|
||||
// Pressing down will go to the next lower 15 minute step.
|
||||
scripter.pressDownKey();
|
||||
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++) {
|
||||
if (increaseDuration) scripter.pressUpKey();
|
||||
else scripter.pressDownKey();
|
||||
// TODO waitForMenuChange instead // or have key press method handle that??
|
||||
SystemClock.sleep(100);
|
||||
log.debug("Push #" + (i + 1));
|
||||
}
|
||||
}
|
||||
|
||||
private void verifyDisplayedTbrDuration(RuffyScripter scripter) {
|
||||
long displayedDuration = readDisplayedTbrDuration(scripter);
|
||||
if (displayedDuration != duration) {
|
||||
log.debug("Final displayed TBR duration: " + displayedDuration);
|
||||
throw new CommandException().message("Failed to set TBR duration");
|
||||
}
|
||||
}
|
||||
|
||||
private long readDisplayedTbrDuration(RuffyScripter scripter) {
|
||||
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) {
|
||||
// TODO this will fail if no TBR is running; detect and just throw CE(success=true, msg="nothing to do")?
|
||||
|
||||
// confirm entered TBR
|
||||
scripter.pressCheckKey();
|
||||
scripter.waitForMenuUpdate();
|
||||
|
||||
// hm, waiting here (more) makes things worse, if we don't press the alert away quickly,
|
||||
// the pump exits BT mode ... so I guess we'll live with the checks below,
|
||||
// verifying we made it back to the main menu and the displayed TBR data
|
||||
// corresponds to what we set. Hope the timing is stable enough ...
|
||||
|
||||
/* scripter.waitForMenuToBeLeft(MenuType.TBR_SET);
|
||||
if (scripter.currentMenu.getType() != MenuType.MAIN_MENU) {
|
||||
// pump shortly enters the main menu before raising the alert
|
||||
// TODO is this always entered?
|
||||
log.debug("TBR cancelled, going over main menu");
|
||||
scripter.waitForMenuToBeLeft(MenuType.MAIN_MENU);
|
||||
}
|
||||
if (scripter.currentMenu.getType() != MenuType.WARNING_OR_ERROR) {
|
||||
throw new CommandException(false, null, "Expected WARNING_OR_ERROR menu was not shown when cancelling TBR");
|
||||
}*/
|
||||
// confirm "TBR cancelled alert"
|
||||
scripter.pressCheckKey();
|
||||
SystemClock.sleep(200);
|
||||
// dismiss "TBR cancelled alert"
|
||||
scripter.pressCheckKey();
|
||||
scripter.waitForMenuToBeLeft(MenuType.WARNING_OR_ERROR);
|
||||
}
|
||||
|
||||
private void verifyMainMenuShowsNoActiveTbr(RuffyScripter scripter) {
|
||||
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) {
|
||||
// new TBR set; percentage and duration must be displayed ...
|
||||
if (!scripter.currentMenu.attributes().contains(MenuAttribute.TBR) ||
|
||||
!scripter.currentMenu.attributes().contains(MenuAttribute.TBR)) {
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,123 @@
|
|||
package org.monkey.d.ruffy.ruffy.driver.display;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.util.Log;
|
||||
|
||||
import org.monkey.d.ruffy.ruffy.driver.display.menu.BolusType;
|
||||
import org.monkey.d.ruffy.ruffy.driver.display.menu.MenuBlink;
|
||||
import org.monkey.d.ruffy.ruffy.driver.display.menu.MenuDate;
|
||||
import org.monkey.d.ruffy.ruffy.driver.display.menu.MenuTime;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Created by fishermen21 on 20.05.17.
|
||||
*/
|
||||
|
||||
public class Menu implements Parcelable{
|
||||
private MenuType type;
|
||||
private Map<MenuAttribute,Object> attributes = new HashMap<>();
|
||||
|
||||
public Menu(MenuType type)
|
||||
{
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public Menu(Parcel in) {
|
||||
this.type = MenuType.valueOf(in.readString());
|
||||
while(in.dataAvail()>0) {
|
||||
try {
|
||||
String attr = in.readString();
|
||||
String clas = in.readString();
|
||||
String value = in.readString();
|
||||
|
||||
MenuAttribute a = MenuAttribute.valueOf(attr);
|
||||
Object o = null;
|
||||
if (Integer.class.toString().equals(clas)) {
|
||||
o = new Integer(value);
|
||||
} else if (Double.class.toString().equals(clas)) {
|
||||
o = new Double(value);
|
||||
} else if (Boolean.class.toString().equals(clas)) {
|
||||
o = new Boolean(value);
|
||||
} else if (MenuDate.class.toString().equals(clas)) {
|
||||
o = new MenuDate(value);
|
||||
} else if (MenuTime.class.toString().equals(clas)) {
|
||||
o = new MenuTime(value);
|
||||
} else if (MenuBlink.class.toString().equals(clas)) {
|
||||
o = new MenuBlink();
|
||||
} else if (BolusType.class.toString().equals(clas)) {
|
||||
o = BolusType.valueOf(value);
|
||||
} else if (String.class.toString().equals(clas)) {
|
||||
o = new String(value);
|
||||
}
|
||||
|
||||
if (o != null) {
|
||||
attributes.put(a, o);
|
||||
} else {
|
||||
Log.e("MenuIn", "failed to parse: " + attr + " / " + clas + " / " + value);
|
||||
}
|
||||
}catch(Exception e)
|
||||
{
|
||||
Log.e("MenuIn","Exception in read",e);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public void setAttribute(MenuAttribute key, Object value)
|
||||
{
|
||||
attributes.put(key,value);
|
||||
}
|
||||
|
||||
public List<MenuAttribute> attributes()
|
||||
{
|
||||
return new LinkedList<MenuAttribute>(attributes.keySet());
|
||||
}
|
||||
|
||||
public Object getAttribute(MenuAttribute key)
|
||||
{
|
||||
return attributes.get(key);
|
||||
}
|
||||
|
||||
public MenuType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeString(type.toString());
|
||||
for(MenuAttribute a : attributes.keySet())
|
||||
{
|
||||
try
|
||||
{
|
||||
dest.writeString(a.toString());
|
||||
Object o = attributes.get(a);
|
||||
|
||||
dest.writeString(o.getClass().toString());
|
||||
dest.writeString(o.toString());
|
||||
}catch(Exception e)
|
||||
{
|
||||
Log.v("MenuOut","error in write",e);
|
||||
}
|
||||
}
|
||||
}
|
||||
public static final Creator<Menu> CREATOR = new
|
||||
Creator<Menu>() {
|
||||
public Menu createFromParcel(Parcel in) {
|
||||
return new Menu(in);
|
||||
}
|
||||
|
||||
public Menu[] newArray(int size) {
|
||||
return new Menu[size];
|
||||
}
|
||||
};
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
package org.monkey.d.ruffy.ruffy.driver.display;
|
||||
|
||||
/**
|
||||
* Created by fishermen21 on 22.05.17.
|
||||
*/
|
||||
|
||||
public enum MenuAttribute {
|
||||
RUNTIME,//runtime of current operation, remaining time on main menu
|
||||
BOLUS,//double units
|
||||
BOLUS_REMAINING,//double units remain from current bolus
|
||||
TBR,//double 0-500%
|
||||
BASAL_RATE,//double units/h
|
||||
BASAL_SELECTED,//int selected basal profile
|
||||
LOW_BATTERY,//boolean low battery warning
|
||||
INSULIN_STATE,//int insulin warning 0 == no warning, 1== low, 2 == empty
|
||||
LOCK_STATE,//int keylock state 0==no lock, 1==unlocked, 2==locked
|
||||
MULTIWAVE_BOLUS,//double immediate bolus on multiwave
|
||||
BOLUS_TYPE,//BolusType, only history uses MULTIWAVE
|
||||
TIME,//time MenuTime
|
||||
REMAINING_INSULIN,//double units
|
||||
DATE,//date MenuDate
|
||||
CURRENT_RECORD,//int current record
|
||||
TOTAL_RECORD, //int total num record
|
||||
ERROR, //int errorcode
|
||||
WARNING, //int errorcode
|
||||
MESSAGE, //string errormessage
|
||||
DAILY_TOTAL, //double units
|
||||
BASAL_TOTAL, //double total basal
|
||||
BASAL_START, //time MenuTime the basalrate starts
|
||||
BASAL_END, // time MenuTime the basalrate ends
|
||||
DEBUG_TIMING, //double with timing infos
|
||||
WARANTY, //boolean true if out of waranty
|
||||
ERROR_OR_WARNING, // set if menu in blink during error/warning
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
package org.monkey.d.ruffy.ruffy.driver.display;
|
||||
|
||||
/**
|
||||
* Created by fishermen21 on 22.05.17.
|
||||
*/
|
||||
|
||||
public enum MenuType {
|
||||
MAIN_MENU,
|
||||
STOP_MENU,
|
||||
BOLUS_MENU,
|
||||
BOLUS_ENTER,
|
||||
EXTENDED_BOLUS_MENU,
|
||||
BOLUS_DURATION,
|
||||
MULTIWAVE_BOLUS_MENU,
|
||||
IMMEDIATE_BOLUS,
|
||||
TBR_MENU,
|
||||
MY_DATA_MENU,
|
||||
BASAL_MENU,
|
||||
BASAL_1_MENU,
|
||||
BASAL_2_MENU,
|
||||
BASAL_3_MENU,
|
||||
BASAL_4_MENU,
|
||||
BASAL_5_MENU,
|
||||
DATE_AND_TIME_MENU,
|
||||
ALARM_MENU,
|
||||
MENU_SETTINGS_MENU,
|
||||
BLUETOOTH_MENU,
|
||||
THERAPY_MENU,
|
||||
PUMP_MENU,
|
||||
QUICK_INFO,
|
||||
BOLUS_DATA,
|
||||
DAILY_DATA,
|
||||
TBR_DATA,
|
||||
ERROR_DATA,
|
||||
TBR_SET,
|
||||
TBR_DURATION,
|
||||
STOP,
|
||||
START_MENU,
|
||||
BASAL_TOTAL,
|
||||
BASAL_SET,
|
||||
WARNING_OR_ERROR,
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
package org.monkey.d.ruffy.ruffy.driver.display.menu;
|
||||
|
||||
/**
|
||||
* Created by fishermen21 on 22.05.17.
|
||||
*/
|
||||
|
||||
public enum BolusType{
|
||||
NORMAL,
|
||||
EXTENDED,
|
||||
MULTIWAVE,
|
||||
MULTIWAVE_BOLUS,
|
||||
MULTIWAVE_EXTENDED,
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
package org.monkey.d.ruffy.ruffy.driver.display.menu;
|
||||
|
||||
/**
|
||||
* Created by fishermen21 on 22.05.17.
|
||||
*/
|
||||
|
||||
public class MenuBlink {
|
||||
@Override
|
||||
public String toString() {
|
||||
return "BLINK";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
package org.monkey.d.ruffy.ruffy.driver.display.menu;
|
||||
|
||||
/**
|
||||
* Created by fishermen21 on 24.05.17.
|
||||
*/
|
||||
|
||||
public class MenuDate {
|
||||
private final int day;
|
||||
private final int month;
|
||||
|
||||
|
||||
public MenuDate(int day, int month) {
|
||||
this.day = day;
|
||||
this.month = month;
|
||||
}
|
||||
|
||||
public MenuDate(String value) {
|
||||
String[] p = value.split("\\.");
|
||||
day = Integer.parseInt(p[0]);
|
||||
month = Integer.parseInt(p[1]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return day+"."+String.format("%02d",month)+".";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
package org.monkey.d.ruffy.ruffy.driver.display.menu;
|
||||
|
||||
/**
|
||||
* Created by fishermen21 on 22.05.17.
|
||||
*/
|
||||
|
||||
public class MenuTime {
|
||||
|
||||
private final int hour;
|
||||
private final int minute;
|
||||
|
||||
public MenuTime(int hour, int minute)
|
||||
{
|
||||
this.hour = hour;
|
||||
this.minute = minute;
|
||||
}
|
||||
|
||||
public MenuTime(String value) {
|
||||
String[] p = value.split(":");
|
||||
hour = Integer.parseInt(p[0]);
|
||||
minute = Integer.parseInt(p[1]);
|
||||
}
|
||||
|
||||
public int getHour() {
|
||||
return hour;
|
||||
}
|
||||
|
||||
public int getMinute() {
|
||||
return minute;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return hour+":"+String.format("%02d",minute);
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue