alternate setTBR based on StateMachine

This commit is contained in:
Sandra Keßler 2017-07-31 11:52:31 +02:00
parent 3497296b6c
commit 58e6791441

View file

@ -1,7 +1,5 @@
package de.jotomo.ruffyscripter.commands; 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.MenuAttribute;
import org.monkey.d.ruffy.ruffy.driver.display.MenuType; import org.monkey.d.ruffy.ruffy.driver.display.MenuType;
import org.monkey.d.ruffy.ruffy.driver.display.menu.MenuTime; import org.monkey.d.ruffy.ruffy.driver.display.menu.MenuTime;
@ -15,11 +13,21 @@ import java.util.Locale;
import de.jotomo.ruffyscripter.PumpState; import de.jotomo.ruffyscripter.PumpState;
import de.jotomo.ruffyscripter.RuffyScripter; import de.jotomo.ruffyscripter.RuffyScripter;
import static de.jotomo.ruffyscripter.commands.SetTbrCommand.State.AFTER;
import static de.jotomo.ruffyscripter.commands.SetTbrCommand.State.BEFORE;
import static de.jotomo.ruffyscripter.commands.SetTbrCommand.State.ERROR;
import static de.jotomo.ruffyscripter.commands.SetTbrCommand.State.MAIN;
import static de.jotomo.ruffyscripter.commands.SetTbrCommand.State.SET;
import static de.jotomo.ruffyscripter.commands.SetTbrCommand.State.SET_TBR;
import static de.jotomo.ruffyscripter.commands.SetTbrCommand.State.SET_TIME;
import static de.jotomo.ruffyscripter.commands.SetTbrCommand.State.TBR;
public class SetTbrCommand implements Command { public class SetTbrCommand implements Command {
private static final Logger log = LoggerFactory.getLogger(SetTbrCommand.class); private static final Logger log = LoggerFactory.getLogger(SetTbrCommand.class);
private final long percentage; private final long percentage;
private final long duration; private final long duration;
private RuffyScripter scripter;
public SetTbrCommand(long percentage, long duration) { public SetTbrCommand(long percentage, long duration) {
this.percentage = percentage; this.percentage = percentage;
@ -53,248 +61,296 @@ public class SetTbrCommand implements Command {
return violations; return violations;
} }
@Override enum State {
public CommandResult execute(RuffyScripter scripter, PumpState initialPumpState) { BEFORE,
try { MAIN,
enterTbrMenu(scripter); TBR,
inputTbrPercentage(scripter); SET_TBR,
verifyDisplayedTbrPercentage(scripter); SET_TIME,
SET,
if (percentage == 100) { AFTER,
cancelTbrAndConfirmCancellationWarning(scripter); ERROR
} else { };
// switch to TBR_DURATION menu by pressing menu key private State lastState,state;
scripter.verifyMenuIsDisplayed(MenuType.TBR_SET); private long last;
scripter.pressMenuKey(); private long timeout;
scripter.waitForMenuUpdate(); private Thread timeoutThread = new Thread()
scripter.verifyMenuIsDisplayed(MenuType.TBR_DURATION); {
@Override
inputTbrDuration(scripter); public void run() {
verifyDisplayedTbrDuration(scripter); while(state != ERROR && state!=AFTER) {
if (timeout + last < System.currentTimeMillis()) {
// confirm TBR lastState = state;
scripter.pressCheckKey(); state = ERROR;
scripter.waitForMenuToBeLeft(MenuType.TBR_DURATION); log.debug("timeout reached -> state:ERROR");
} }
tick();
scripter.verifyMenuIsDisplayed(MenuType.MAIN_MENU, try {
"Pump did not return to MAIN_MEU after setting TBR. " + Thread.sleep(100);
"Check pump manually, the TBR might not have been set/cancelled."); } catch (InterruptedException e) {
e.printStackTrace();
// check main menu shows the same values we just set }
if (percentage == 100) { }
verifyMainMenuShowsNoActiveTbr(scripter); tick();
return new CommandResult().success(true).enacted(true).message("TBR was cancelled"); }
} else { };
verifyMainMenuShowsExpectedTbrActive(scripter); private void updateState(State newState,long timeoutSec)
return new CommandResult().success(true).enacted(true).message( {
String.format(Locale.US, "TBR set to %d%% for %d min", percentage, duration)); lastState = state;
} state = newState;
last = System.currentTimeMillis();
} catch (CommandException e) { timeout = timeoutSec*1000;
return e.toCommandResult(); }
} private MenuType lastMenu;
} private void tick()
{
private void enterTbrMenu(RuffyScripter scripter) { switch (state)
scripter.verifyMenuIsDisplayed(MenuType.MAIN_MENU); {
scripter.navigateToMenu(MenuType.TBR_MENU); case BEFORE:
scripter.verifyMenuIsDisplayed(MenuType.TBR_MENU); if(scripter.currentMenu.getType()==MenuType.MAIN_MENU)
scripter.pressCheckKey(); {
scripter.waitForMenuUpdate(); updateState(MAIN,120);
scripter.verifyMenuIsDisplayed(MenuType.TBR_SET); lastMenu = MenuType.MAIN_MENU;
} log.debug("found MAIN_MENU -> state:MAIN");
}
private void inputTbrPercentage(RuffyScripter scripter) { break;
scripter.verifyMenuIsDisplayed(MenuType.TBR_SET); case MAIN:
long currentPercent = readDisplayedTbrPercentage(scripter); if(scripter.currentMenu.getType()==MenuType.TBR_MENU)
log.debug("Current TBR %: " + currentPercent); {
long percentageChange = percentage - currentPercent; updateState(TBR,30);
long percentageSteps = percentageChange / 10; scripter.pressCheckKey();
boolean increasePercentage = true; log.debug("found TBR_MENU -> state:TBR");
if (percentageSteps < 0) { try {
increasePercentage = false; Thread.sleep(750);
percentageSteps = Math.abs(percentageSteps); } catch (InterruptedException e) {
} e.printStackTrace();
log.debug("Pressing " + (increasePercentage ? "up" : "down") + " " + percentageSteps + " times"); }
for (int i = 0; i < percentageSteps; i++) { }
scripter.verifyMenuIsDisplayed(MenuType.TBR_SET); else if(scripter.currentMenu.getType()!=lastMenu)
if (increasePercentage) scripter.pressUpKey(); {
else scripter.pressDownKey(); lastMenu = scripter.currentMenu.getType();
SystemClock.sleep(400); updateState(MAIN,30);
log.debug("Push #" + (i + 1)); scripter.pressMenuKey();
} log.debug("found Menu:"+lastMenu+" -> state:MAIN");
// Give the pump time to finish any scrolling that might still be going on, can take try {
// up to 1100ms. Plus some extra time to be sure Thread.sleep(750);
SystemClock.sleep(750); } catch (InterruptedException e) {
} e.printStackTrace();
}
private void verifyDisplayedTbrPercentage(RuffyScripter scripter) { }
scripter.verifyMenuIsDisplayed(MenuType.TBR_SET); else
long displayedPercentage = readDisplayedTbrPercentage(scripter); {
if (displayedPercentage != percentage) { scripter.pressMenuKey();
log.debug("Final displayed TBR percentage: " + displayedPercentage); try {
throw new CommandException().message("Failed to set TBR percentage"); Thread.sleep(750);
} } catch (InterruptedException e) {
e.printStackTrace();
// check again to ensure the displayed value hasn't change due to due scrolling taking extremely long }
SystemClock.sleep(750); }
long refreshedDisplayedTbrPecentage = readDisplayedTbrPercentage(scripter); break;
if (displayedPercentage != refreshedDisplayedTbrPecentage) { case TBR:
throw new CommandException().message("Failed to set TBR percentage: " + if(scripter.currentMenu.getType()==MenuType.TBR_SET)
"percentage changed after input stopped from " {
+ displayedPercentage + " -> " + refreshedDisplayedTbrPecentage); updateState(SET_TBR,60);
} }
} else
{
private long readDisplayedTbrPercentage(RuffyScripter scripter) { scripter.pressMenuKey();
// TODO v2 add timeout? Currently the command execution timeout would trigger if exceeded try {
Object percentageObj = scripter.currentMenu.getAttribute(MenuAttribute.BASAL_RATE); Thread.sleep(750);
// this as a bit hacky, the display value is blinking, so we might catch that, so } catch (InterruptedException e) {
// keep trying till we get the Double we want e.printStackTrace();
while (!(percentageObj instanceof Double)) { }
scripter.waitForMenuUpdate(); updateState(TBR,60);
percentageObj = scripter.currentMenu.getAttribute(MenuAttribute.BASAL_RATE); }
} break;
return ((Double) percentageObj).longValue(); case SET_TBR:
} if(scripter.currentMenu.getType()==MenuType.TBR_SET)
{
private void inputTbrDuration(RuffyScripter scripter) { Object percentageObj = scripter.currentMenu.getAttribute(MenuAttribute.BASAL_RATE);
scripter.verifyMenuIsDisplayed(MenuType.TBR_DURATION); if(percentageObj != null && percentageObj instanceof Double)
long currentDuration = readDisplayedTbrDuration(scripter); {
while(currentDuration % 15 != 0) double currentPercentage = ((Double) percentageObj).doubleValue();
if(currentPercentage < percentage)
{ {
// 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.pressUpKey();
SystemClock.sleep(300); updateState(SET_TBR,30);
scripter.waitForMenuUpdate(); try {
currentDuration = readDisplayedTbrDuration(scripter); Thread.sleep(750);
} } catch (InterruptedException e) {
SystemClock.sleep(300); e.printStackTrace();
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(400);
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(750);
}
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(750);
long refreshedDisplayedTbrDuration = readDisplayedTbrDuration(scripter);
if (displayedDuration != refreshedDisplayedTbrDuration) {
throw new CommandException().message("Failed to set TBR duration: " +
"duration changed after input stopped from "
+ displayedDuration + " -> " + refreshedDisplayedTbrDuration);
} }
} }
else if(currentPercentage > percentage)
private long readDisplayedTbrDuration(RuffyScripter scripter) { {
// TODO v2 add timeout? Currently the command execution timeout would trigger if exceeded scripter.pressDownKey();
scripter.verifyMenuIsDisplayed(MenuType.TBR_DURATION); updateState(SET_TBR,30);
try {
Thread.sleep(750);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
else
{
if(percentage==100)
{
scripter.pressCheckKey();
updateState(SET, 30);
}
else {
scripter.pressMenuKey();
updateState(SET_TIME, 30);
}
}
}
}
else if(scripter.currentMenu.getType()==MenuType.TBR_DURATION)
{
scripter.pressMenuKey();
updateState(TBR,60);
try {
Thread.sleep(750);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
else
{
updateState(ERROR,30);
}
break;
case SET_TIME:
if(scripter.currentMenu.getType()==MenuType.TBR_DURATION)
{
Object durationObj = scripter.currentMenu.getAttribute(MenuAttribute.RUNTIME); Object durationObj = scripter.currentMenu.getAttribute(MenuAttribute.RUNTIME);
// this as a bit hacky, the display value is blinking, so we might catch that, so if(durationObj != null && durationObj instanceof MenuTime)
// keep trying till we get the Double we want {
while (!(durationObj instanceof MenuTime)) { MenuTime time = (MenuTime) durationObj;
scripter.waitForMenuUpdate(); double currentDuration = (time.getHour()*60)+time.getMinute();
durationObj = scripter.currentMenu.getAttribute(MenuAttribute.RUNTIME); if(currentDuration < duration)
{
scripter.pressUpKey();
updateState(SET_TIME,30);
try {
Thread.sleep(750);
} catch (InterruptedException e) {
e.printStackTrace();
} }
MenuTime duration = (MenuTime) durationObj;
return duration.getHour() * 60 + duration.getMinute();
} }
else if(currentDuration > duration)
private void cancelTbrAndConfirmCancellationWarning(RuffyScripter scripter) { {
// confirm entered TBR scripter.pressDownKey();
scripter.verifyMenuIsDisplayed(MenuType.TBR_SET); updateState(SET_TIME,30);
try {
Thread.sleep(750);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
else
{
scripter.pressCheckKey(); scripter.pressCheckKey();
updateState(SET, 30);
// A "TBR CANCELLED alert" is only raised by the pump when the remaining time is try {
// greater than 60s (displayed as 0:01, the pump goes from there to finished. Thread.sleep(750);
// We could read the remaining duration from MAIN_MENU, but by the time we're here, } catch (InterruptedException e) {
// the pumup could have moved from 0:02 to 0:01, so instead, check if a "TBR CANCELLED" alert e.printStackTrace();
// 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); }
}
else if(scripter.currentMenu.getType()==MenuType.TBR_SET)
{
scripter.pressMenuKey();
updateState(SET_TIME,60);
try {
Thread.sleep(750);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
else
{
updateState(ERROR,60);
}
break;
case SET:
if(scripter.currentMenu.getType()==MenuType.WARNING_OR_ERROR)
{
lastMenu = scripter.currentMenu.getType();
scripter.pressCheckKey(); scripter.pressCheckKey();
// dismiss "TBR CANCELLED" alert updateState(SET, 30);
scripter.verifyMenuIsDisplayed(MenuType.WARNING_OR_ERROR); try {
scripter.pressCheckKey(); Thread.sleep(750);
scripter.waitForMenuToBeLeft(MenuType.WARNING_OR_ERROR); } catch (InterruptedException e) {
alertProcessed = true; e.printStackTrace();
}
SystemClock.sleep(20);
} }
} }
else if(scripter.currentMenu.getType()==MenuType.MAIN_MENU) {
private void verifyMainMenuShowsNoActiveTbr(RuffyScripter scripter) { Object setPercentage = scripter.currentMenu.getAttribute(MenuAttribute.TBR);
scripter.verifyMenuIsDisplayed(MenuType.MAIN_MENU); Object setDuration = scripter.currentMenu.getAttribute(MenuAttribute.RUNTIME);
Double tbrPercentage = (Double) scripter.currentMenu.getAttribute(MenuAttribute.TBR); if (setPercentage== null ||setDuration==null) {
boolean runtimeDisplayed = scripter.currentMenu.attributes().contains(MenuAttribute.RUNTIME); if(percentage!=100)
if (tbrPercentage != 100 || runtimeDisplayed) { {
throw new CommandException().message("Cancelling TBR failed, TBR is still set according to MAIN_MENU"); updateState(ERROR,10);
}
else
{
if(lastMenu==MenuType.WARNING_OR_ERROR)
updateState(AFTER,10);
else
updateState(SET,10);
} }
} }
else {
private void verifyMainMenuShowsExpectedTbrActive(RuffyScripter scripter) { double mmTbrPercentage = (Double) setPercentage;
scripter.verifyMenuIsDisplayed(MenuType.MAIN_MENU); MenuTime mmTbrDuration = (MenuTime) setDuration;
// 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 // ... and be the same as what we set
// note that displayed duration might have already counted down, e.g. from 30 minutes to // 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 // 29 minutes and 59 seconds, so that 29 minutes are displayed
int mmTbrDurationInMinutes = mmTbrDuration.getHour() * 60 + mmTbrDuration.getMinute(); int mmTbrDurationInMinutes = mmTbrDuration.getHour() * 60 + mmTbrDuration.getMinute();
if (mmTbrPercentage != percentage || (mmTbrDurationInMinutes != duration && mmTbrDurationInMinutes + 1 != duration)) { if (mmTbrPercentage == percentage && mmTbrDurationInMinutes <= duration) {
throw new CommandException().message("Setting TBR failed, TBR in MAIN_MENU differs from expected"); updateState(AFTER, 10);
} else {
updateState(ERROR, 10);
} }
} }
}
break;
case ERROR:
case AFTER:
synchronized(SetTbrCommand.this) {
SetTbrCommand.this.notify();
}
break;
}
}
@Override
public CommandResult execute(RuffyScripter scripter, PumpState initialPumpState) {
state = BEFORE;
this.scripter = scripter;
updateState(BEFORE,120);
timeoutThread.start();
try {
synchronized (this) {
this.wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
return new CommandResult().success(false).message("failed to wait: "+e.getMessage());
}
if(state==AFTER)
{
if(percentage==100)
return new CommandResult().success(true).enacted(true).message("TBR was cancelled");
return new CommandResult().success(true).enacted(true).message(
String.format(Locale.US, "TBR set to %d%% for %d min", percentage, duration));
}
return new CommandResult().success(false).message("failed with state: "+state+" from: "+lastState);
}
@Override @Override
public String toString() { public String toString() {