Source ruffy scripter for the time being (already some fixes and tweaks in).

This commit is contained in:
Johannes Mockenhaupt 2017-07-13 19:50:11 +02:00
parent 481c63fa57
commit 3280092566
No known key found for this signature in database
GPG key ID: 9E1EA6AF7BBBB0D1
17 changed files with 1054 additions and 0 deletions

View file

@ -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();
}

View file

@ -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();
}

View file

@ -0,0 +1,3 @@
package org.monkey.d.ruffy.ruffy.driver.display;
parcelable Menu;

View 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);
}
}
}

View file

@ -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);
}
}
}

View file

@ -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);
}
}

View file

@ -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();
// }
}

View file

@ -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 + '\'' +
'}';
}
}

View file

@ -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 + '\'' +
'}';
}
}

View file

@ -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");
}
}
}

View file

@ -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];
}
};
}

View file

@ -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
}

View file

@ -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,
}

View file

@ -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,
}

View file

@ -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";
}
}

View file

@ -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)+".";
}
}

View file

@ -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);
}
}