Merge branch 'just-a-flesh-wound' into develop

* just-a-flesh-wound: (50 commits)
  Small  refactoring.
  Restore bolus splitting hack.
  Make new BolusCommand configurable.
  Report bolus delivered if the pump raised a warning/error during delivery.
  Cleanup merge.
  Restore bolus comands alongside.
  Merge fixes without changes to behaviour.
  Try to be clever about slow screen updates when scrolling.
  Revert "Try building against ruffy stable, 8dae0c0fedd5e371e85da3433a07aaab27b05db0"
  Try building against ruffy stable, 8dae0c0fedd5e371e85da3433a07aaab27b05db0
  Workaound for #27 (TBR issued twice).
  Revert "Retry with current ruffy."
  Retry with current ruffy.
  Remove code to determine pump capabilities for the time being.
  Don't provide status JSON until we can provide it properly.
  ComboPlugin: don't implement ConstraintsInterface, ProfileInterface.
  Use string resource.
  Cleanup.
  Only display Extended Bolus tab in Treatments if pump supports them.
  Build against ruffy b916a900c0899ef58ad58c7427d1c30d3c8731f4.
  ...
This commit is contained in:
Johannes Mockenhaupt 2017-08-27 20:35:29 +02:00
commit f15373420c
No known key found for this signature in database
GPG key ID: 9E1EA6AF7BBBB0D1
35 changed files with 1132 additions and 1131 deletions

View file

@ -44,7 +44,7 @@ android {
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 23 targetSdkVersion 23
versionCode 1500 versionCode 1500
version "1.5h-combo-dev" version "1.5h-combo-dev-jotomo"
buildConfigField "String", "VERSION", '"' + version + '"' buildConfigField "String", "VERSION", '"' + version + '"'
buildConfigField "String", "BUILDVERSION", generateGitBuild() buildConfigField "String", "BUILDVERSION", generateGitBuild()
} }

View file

@ -19,7 +19,7 @@ import org.slf4j.LoggerFactory;
import de.jotomo.ruffyscripter.RuffyScripter; import de.jotomo.ruffyscripter.RuffyScripter;
import de.jotomo.ruffyscripter.commands.CommandResult; 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.assertFalse;
import static junit.framework.Assert.assertNotNull; import static junit.framework.Assert.assertNotNull;
@ -99,7 +99,7 @@ public class RuffyScripterInstrumentedTest {
// TODO now, how to get ruffy fired up in this test? // TODO now, how to get ruffy fired up in this test?
@Test @Test
public void readPumpState() throws Exception { public void readPumpState() throws Exception {
CommandResult commandResult = ruffyScripter.runCommand(new ReadPumpStateCommand()); CommandResult commandResult = ruffyScripter.runCommand(new GetPumpStateCommand());
assertTrue(commandResult.success); assertTrue(commandResult.success);
assertFalse(commandResult.enacted); assertFalse(commandResult.enacted);
assertNotNull(commandResult.state); assertNotNull(commandResult.state);

View file

@ -10,17 +10,12 @@ interface IRTHandler {
void fail(String message); void fail(String message);
void requestBluetooth(); void requestBluetooth();
boolean canDisconnect();
void rtStopped(); void rtStopped();
void rtStarted(); void rtStarted();
void rtClearDisplay(); void rtClearDisplay();
void rtUpdateDisplay(in byte[] quarter, int which); void rtUpdateDisplay(in byte[] quarter, int which);
void rtDisplayHandleMenu(in Menu menu, in int sequence); void rtDisplayHandleMenu(in Menu menu);
void rtDisplayHandleNoMenu(in int sequence); void rtDisplayHandleNoMenu();
void keySent(in int sequence);
String getServiceIdentifier();
} }

View file

@ -6,22 +6,18 @@ import org.monkey.d.ruffy.ruffy.driver.IRTHandler;
interface IRuffyService { interface IRuffyService {
void addHandler(IRTHandler handler); void setHandler(IRTHandler handler);
void removeHandler(IRTHandler handler);
/** Connect to the pump /** Connect to the pump
* *
* @return 0 if successful, -1 otherwise * @return 0 if successful, -1 otherwise
*/ */
int doRTConnect(IRTHandler handler); int doRTConnect();
/** Disconnect from the pump */ /** 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 rtSendKey(byte keyCode, boolean changed);
void resetPairing(); void resetPairing();
boolean isConnected(); boolean isConnected();
boolean isBoundToPump();
} }

View file

@ -0,0 +1 @@
//b916a900c0899ef58ad58c7427d1c30d3c8731f4

View file

@ -1,5 +1,7 @@
package de.jotomo.ruffyscripter; package de.jotomo.ruffyscripter;
/** The history data read from "My data" */ /**
* The history data read from "My data"
*/
public class History { public class History {
} }

View file

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

View file

@ -11,16 +11,18 @@ public class PumpState {
public int tbrPercent = -1; public int tbrPercent = -1;
public double tbrRate = -1; public double tbrRate = -1;
public int tbrRemainingDuration = -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". * This is the error message (if any) displayed by the pump if there is an alarm,
Generally, an error code is also displayed, but it flashes and it might take * e.g. if a "TBR cancelled alarm" is active, the value will be "TBR CANCELLED".
longer to read that and the pump connection gets interrupted if we're not * Generally, an error code is also displayed, but it flashes and it might take
reacting quickly. * longer to read that and the pump connection gets interrupted if we're not
* reacting quickly.
*/ */
public String errorMsg; public String errorMsg;
public boolean suspended; public boolean suspended;
public boolean lowBattery; public boolean lowBattery;
public int insulinState; public int insulinState = -1;
public int reservoirLevel = -1;
public PumpState tbrActive(boolean tbrActive) { public PumpState tbrActive(boolean tbrActive) {
this.tbrActive = tbrActive; this.tbrActive = tbrActive;
@ -52,6 +54,21 @@ public class PumpState {
return this; 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 @Override
public String toString() { public String toString() {
return "PumpState{" + return "PumpState{" +
@ -63,6 +80,7 @@ public class PumpState {
", suspended=" + suspended + ", suspended=" + suspended +
", lowBattery=" + lowBattery + ", lowBattery=" + lowBattery +
", insulinState=" + insulinState + ", insulinState=" + insulinState +
", reversoirLevel=" + reservoirLevel +
", timestamp=" + timestamp + ", timestamp=" + timestamp +
'}'; '}';
} }

View file

@ -19,7 +19,7 @@ import java.util.List;
import de.jotomo.ruffyscripter.commands.Command; import de.jotomo.ruffyscripter.commands.Command;
import de.jotomo.ruffyscripter.commands.CommandException; import de.jotomo.ruffyscripter.commands.CommandException;
import de.jotomo.ruffyscripter.commands.CommandResult; 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. // 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 // 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 { public class RuffyScripter {
private static final Logger log = LoggerFactory.getLogger(RuffyScripter.class); private static final Logger log = LoggerFactory.getLogger(RuffyScripter.class);
private IRuffyService ruffyService; private IRuffyService ruffyService;
private final long connectionTimeOutMs = 5000;
private String unrecoverableError = null; private String unrecoverableError = null;
public volatile Menu currentMenu; private volatile Menu currentMenu;
private volatile long menuLastUpdated = 0; private volatile long menuLastUpdated = 0;
private volatile long lastCmdExecutionTime; private volatile long lastCmdExecutionTime;
private volatile Command activeCmd = null; private volatile Command activeCmd = null;
private volatile boolean connected = false;
private volatile long lastDisconnected = 0;
private boolean started = false; private boolean started = false;
private final Object keylock = new Object();
private int keynotwait = 0;
private final Object screenlock = new Object(); private final Object screenlock = new Object();
public RuffyScripter() {
}
public void start(IRuffyService newService) { public void start(IRuffyService newService) {
try { try {
/*
if (ruffyService != null) { if (ruffyService != null) {
try { try {
ruffyService.removeHandler(mHandler); ruffyService.removeHandler(mHandler);
@ -67,6 +56,7 @@ public class RuffyScripter {
// ignore // ignore
} }
} }
*/
if (newService != null) { if (newService != null) {
this.ruffyService = newService; this.ruffyService = newService;
// TODO this'll be done better in v2 via ConnectionManager // TODO this'll be done better in v2 via ConnectionManager
@ -74,52 +64,38 @@ public class RuffyScripter {
idleDisconnectMonitorThread.start(); idleDisconnectMonitorThread.start();
} }
started = true; started = true;
newService.addHandler(mHandler); newService.setHandler(mHandler);
} }
} catch (Exception e) { } catch (Exception e) {
log.error("Unhandled exception starting RuffyScripter", e);
throw new RuntimeException(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() { public boolean isRunning() {
return started; return started;
} }
private boolean canDisconnect = false; private volatile boolean connected = false;
private volatile long lastDisconnected = 0;
private Thread idleDisconnectMonitorThread = new Thread(new Runnable() { private Thread idleDisconnectMonitorThread = new Thread(new Runnable() {
@Override @Override
public void run() { public void run() {
long lastDisconnect = System.currentTimeMillis();
while (unrecoverableError == null) { while (unrecoverableError == null) {
try { try {
long now = System.currentTimeMillis(); long now = System.currentTimeMillis();
long connectionTimeOutMs = 5000;
if (connected && activeCmd == null if (connected && activeCmd == null
&& now > lastCmdExecutionTime + connectionTimeOutMs && now > lastCmdExecutionTime + connectionTimeOutMs
// don't disconnect too frequently, confuses ruffy? // don't disconnect too frequently, confuses ruffy?
&& now > lastDisconnect + 15 * 1000) { && now > lastDisconnected + 15 * 1000) {
log.debug("Disconnecting after " + (connectionTimeOutMs / 1000) + "s inactivity timeout"); log.debug("Disconnecting after " + (connectionTimeOutMs / 1000) + "s inactivity timeout");
lastDisconnect = now; lastDisconnected = now;
canDisconnect = true; ruffyService.doRTDisconnect();
ruffyService.doRTDisconnect(mHandler);
connected = false; connected = false;
lastDisconnect = System.currentTimeMillis();
// don't attempt anything fancy in the next 10s, let the pump settle // don't attempt anything fancy in the next 10s, let the pump settle
SystemClock.sleep(10 * 1000); SystemClock.sleep(10 * 1000);
} else {
canDisconnect = false;
} }
} catch (Exception e) { } catch (Exception e) {
// TODO do we need to catch this exception somewhere else too? right now it's // 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"); log.trace("Ruffy invoked requestBluetooth callback");
} }
@Override
public boolean canDisconnect() throws RemoteException {
return canDisconnect;
}
@Override @Override
public void rtStopped() throws RemoteException { public void rtStopped() throws RemoteException {
log.debug("rtStopped callback invoked"); log.debug("rtStopped callback invoked");
@ -178,7 +149,7 @@ public class RuffyScripter {
} }
@Override @Override
public void rtDisplayHandleMenu(Menu menu, int sequence) throws RemoteException { public void rtDisplayHandleMenu(Menu menu) throws RemoteException {
// method is called every ~500ms // method is called every ~500ms
log.debug("rtDisplayHandleMenu: " + menu); log.debug("rtDisplayHandleMenu: " + menu);
@ -201,26 +172,9 @@ public class RuffyScripter {
} }
@Override @Override
public void rtDisplayHandleNoMenu(int sequence) throws RemoteException { public void rtDisplayHandleNoMenu() throws RemoteException {
log.debug("rtDisplayHandleNoMenu callback invoked"); 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() { public boolean isPumpBusy() {
@ -228,12 +182,14 @@ public class RuffyScripter {
} }
public void unbind() { public void unbind() {
/*
if (ruffyService != null) if (ruffyService != null)
try { try {
ruffyService.removeHandler(mHandler); ruffyService.removeHandler(mHandler);
} catch (Exception e) { } catch (Exception e) {
// ignore // ignore
} }
*/
this.ruffyService = null; this.ruffyService = null;
} }
@ -256,8 +212,8 @@ public class RuffyScripter {
synchronized (RuffyScripter.class) { synchronized (RuffyScripter.class) {
try { try {
long connectStart = System.currentTimeMillis();
activeCmd = cmd; activeCmd = cmd;
long connectStart = System.currentTimeMillis();
ensureConnected(); ensureConnected();
final RuffyScripter scripter = this; final RuffyScripter scripter = this;
final Returnable returnable = new Returnable(); final Returnable returnable = new Returnable();
@ -275,7 +231,7 @@ public class RuffyScripter {
return; 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 // 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 // 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. // 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). // esp. with AAPS).
// So, for v1, just check the pump is not suspended before executing commands and raising an error for all // 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 // 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 // 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. // 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. // 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. // 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 (currentMenu == null || currentMenu.getType() == MenuType.STOP) {
if (cmd instanceof ReadPumpStateCommand) { if (cmd instanceof GetPumpStateCommand) {
returnable.cmdResult = new CommandResult().success(true).enacted(false); returnable.cmdResult = new CommandResult().success(true).enacted(false);
} else { } else {
returnable.cmdResult = new CommandResult().success(false).enacted(false).message("Pump is suspended"); returnable.cmdResult = new CommandResult().success(false).enacted(false).message("Pump is suspended");
@ -304,7 +260,8 @@ public class RuffyScripter {
PumpState pumpState = readPumpState(); PumpState pumpState = readPumpState();
log.debug("Pump state before running command: " + pumpState); log.debug("Pump state before running command: " + pumpState);
long cmdStartTime = System.currentTimeMillis(); long cmdStartTime = System.currentTimeMillis();
returnable.cmdResult = cmd.execute(scripter, pumpState); cmd.setScripter(scripter);
returnable.cmdResult = cmd.execute();
long cmdEndTime = System.currentTimeMillis(); long cmdEndTime = System.currentTimeMillis();
returnable.cmdResult.completionTime = cmdEndTime; returnable.cmdResult.completionTime = cmdEndTime;
log.debug("Executing " + cmd + " took " + (cmdEndTime - cmdStartTime) + "ms"); log.debug("Executing " + cmd + " took " + (cmdEndTime - cmdStartTime) + "ms");
@ -354,14 +311,14 @@ public class RuffyScripter {
returnable.cmdResult.state = readPumpState(); returnable.cmdResult.state = readPumpState();
} }
long connectDurationSec = (executionStart - connectStart) / 1000; long connectDurationSec = (executionStart - connectStart) / 1000;
long now = System.currentTimeMillis(); long executionDurationSec = (System.currentTimeMillis() - executionStart) / 1000;
long executionDurationSec = (now - executionStart) / 1000;
returnable.cmdResult.duration = "Connect: " + connectDurationSec + "s, execution: " + executionDurationSec + "s"; returnable.cmdResult.duration = "Connect: " + connectDurationSec + "s, execution: " + executionDurationSec + "s";
log.debug("Command result: " + returnable.cmdResult); log.debug("Command result: " + returnable.cmdResult);
return returnable.cmdResult; return returnable.cmdResult;
} catch (CommandException e) { } catch (CommandException e) {
return e.toCommandResult(); return e.toCommandResult();
} catch (Exception e) { } catch (Exception e) {
// TODO detect and report pump warnings/errors differently?
log.error("Error in ruffyscripter/ruffy", e); log.error("Error in ruffyscripter/ruffy", e);
return new CommandResult().exception(e).message("Unexpected exception communication with ruffy: " + e.getMessage()); return new CommandResult().exception(e).message("Unexpected exception communication with ruffy: " + e.getMessage());
} finally { } finally {
@ -387,13 +344,14 @@ public class RuffyScripter {
// When connecting again shortly after disconnecting, the pump sometimes fails // 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. // 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 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) { if (System.currentTimeMillis() < lastDisconnected + 10 * 1000) {
log.debug("Waiting 10s to let pump settle after recent disconnect"); log.debug("Waiting 10s to let pump settle after recent disconnect");
SystemClock.sleep(10 * 1000); SystemClock.sleep(10 * 1000);
} }
canDisconnect = false; boolean connectInitSuccessful = ruffyService.doRTConnect() == 0;
boolean connectInitSuccessful = ruffyService.doRTConnect(mHandler) == 0;
log.debug("Connect init successful: " + connectInitSuccessful); log.debug("Connect init successful: " + connectInitSuccessful);
log.debug("Waiting for first menu update to be sent"); log.debug("Waiting for first menu update to be sent");
// Note: there was an 'if(currentMenu == null)' around the next call, since // 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 // below: methods to be used by commands
// TODO move into a new Operations(scripter) class commands can delegate to, // TODO move into a new Operations(scripter) class commands can delegate to,
// so this class can focus on providing a connection to run commands // 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; 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() { public void pressUpKey() {
log.debug("Pressing up key"); // wrapNoNotTheSubwayKind(new Step() {
pressKey(Key.UP, 2000); // @Override
log.debug("Releasing up key"); // public void doStep() {
log.debug("Pressing up key");
pressKey(Key.UP, 2000);
log.debug("Releasing up key");
// }
// });
} }
public void pressDownKey() { public void pressDownKey() {
@ -457,6 +502,26 @@ public class RuffyScripter {
log.debug("Releasing back key"); 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) { public boolean waitForScreenUpdate(long timeout) {
synchronized (screenlock) { synchronized (screenlock) {
try { try {
@ -532,17 +597,17 @@ public class RuffyScripter {
private void pressKey(final byte key, long timeout) { private void pressKey(final byte key, long timeout) {
try { try {
ruffyService.rtSendKey(key, true); ruffyService.rtSendKey(key, true);
//SystemClock.sleep(200); SystemClock.sleep(200);
ruffyService.rtSendKey(Key.NO_KEY, true); ruffyService.rtSendKey(Key.NO_KEY, true);
if (timeout > 0) { // if (timeout > 0) {
synchronized (keylock) { // synchronized (keylock) {
keylock.wait(timeout); // keylock.wait(timeout);
} // }
} else { // } else {
synchronized (keylock) { // synchronized (keylock) {
keynotwait++; // keynotwait++;
} // }
} // }
} catch (Exception e) { } catch (Exception e) {
throw new CommandException().exception(e).message("Error while pressing buttons"); 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 @SuppressWarnings("unchecked")
// cartridge warnings, running extended bolus (how does that look if a TBR is active as well?) public <T> T readBlinkingValue(Class<T> 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, public long readDisplayedDuration() {
* no actions are performed. */ MenuTime duration = readBlinkingValue(MenuTime.class, MenuAttribute.RUNTIME);
private PumpState readPumpState() { return duration.getHour() * 60 + duration.getMinute();
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;
} }
} }

View file

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

View file

@ -11,13 +11,12 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import de.jotomo.ruffyscripter.PumpState;
import de.jotomo.ruffyscripter.RuffyScripter; 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 static final Logger log = LoggerFactory.getLogger(BolusCommand.class);
private final double bolus; protected final double bolus;
public BolusCommand(double bolus) { public BolusCommand(double bolus) {
this.bolus = bolus; this.bolus = bolus;
@ -35,7 +34,7 @@ public class BolusCommand implements Command {
} }
@Override @Override
public CommandResult execute(RuffyScripter scripter, PumpState initialPumpState) { public CommandResult execute() {
try { try {
enterBolusMenu(scripter); enterBolusMenu(scripter);
@ -55,13 +54,20 @@ public class BolusCommand implements Command {
// wait for bolus delivery to complete; the remaining units to deliver are counted // wait for bolus delivery to complete; the remaining units to deliver are counted
// down and are displayed on the main menu. // 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) { while (bolusRemaining != null) {
log.debug("Delivering bolus, remaining: " + bolusRemaining); log.debug("Delivering bolus, remaining: " + bolusRemaining);
SystemClock.sleep(200); 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 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? // 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.navigateToMenu(MenuType.MY_DATA_MENU);
scripter.verifyMenuIsDisplayed(MenuType.MY_DATA_MENU); scripter.verifyMenuIsDisplayed(MenuType.MY_DATA_MENU);
scripter.pressCheckKey(); scripter.pressCheckKey();
if (scripter.currentMenu.getType() != MenuType.BOLUS_DATA) { if (scripter.getCurrentMenu().getType() != MenuType.BOLUS_DATA) {
scripter.waitForMenuUpdate(); scripter.waitForMenuUpdate();
} }
if (!scripter.currentMenu.attributes().contains(MenuAttribute.BOLUS)) { if (!scripter.getCurrentMenu().attributes().contains(MenuAttribute.BOLUS)) {
throw new CommandException().success(false).enacted(true) throw new CommandException().success(false).enacted(true)
.message("Bolus was delivered, but unable to confirm it with history record"); .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) { if (Math.abs(bolus - lastBolusInHistory) > 0.05) {
throw new CommandException().success(false).enacted(true) throw new CommandException().success(false).enacted(true)
.message("Last bolus shows " + lastBolusInHistory .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 // TODO v2 add timeout? Currently the command execution timeout would trigger if exceeded
scripter.verifyMenuIsDisplayed(MenuType.BOLUS_ENTER); scripter.verifyMenuIsDisplayed(MenuType.BOLUS_ENTER);
// bolus amount is blinking, so we need to make sure we catch it at the right moment // 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)) { while (!(amountObj instanceof Double)) {
scripter.waitForMenuUpdate(); scripter.waitForMenuUpdate();
amountObj = scripter.currentMenu.getAttribute(MenuAttribute.BOLUS); amountObj = scripter.getCurrentMenu().getAttribute(MenuAttribute.BOLUS);
} }
return (double) amountObj; return (double) amountObj;
} }

View file

@ -8,13 +8,12 @@ import java.util.Collections;
import java.util.List; import java.util.List;
import de.jotomo.ruffyscripter.PumpState; import de.jotomo.ruffyscripter.PumpState;
import de.jotomo.ruffyscripter.RuffyScripter;
import info.nightscout.androidaps.MainApp; import info.nightscout.androidaps.MainApp;
// TODO robustness: can a TBR run out, whilst we're trying to cancel it? // 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 // 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). // 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); private static final Logger log = LoggerFactory.getLogger(CancelTbrCommand.class);
@Override @Override
@ -23,10 +22,11 @@ public class CancelTbrCommand implements Command {
} }
@Override @Override
public CommandResult execute(RuffyScripter scripter, PumpState initialPumpState) { public CommandResult execute() {
try { try {
scripter.verifyMenuIsDisplayed(MenuType.MAIN_MENU); scripter.verifyMenuIsDisplayed(MenuType.MAIN_MENU);
if (!initialPumpState.tbrActive) { PumpState pumpState = scripter.readPumpState();
if (!pumpState.tbrActive) {
log.debug("active temp basal 90s ago: " + log.debug("active temp basal 90s ago: " +
MainApp.getConfigBuilder().getTempBasalFromHistory(System.currentTimeMillis() - 90 * 1000)); MainApp.getConfigBuilder().getTempBasalFromHistory(System.currentTimeMillis() - 90 * 1000));
log.debug("active temp basal 60s ago: " + log.debug("active temp basal 60s ago: " +
@ -47,9 +47,11 @@ public class CancelTbrCommand implements Command {
.enacted(true) .enacted(true)
.message("No TBR active"); .message("No TBR active");
} }
log.debug("Cancelling active TBR of " + initialPumpState.tbrPercent log.debug("Cancelling active TBR of " + pumpState.tbrPercent
+ "% with " + initialPumpState.tbrRemainingDuration + " min remaining"); + "% with " + pumpState.tbrRemainingDuration + " min remaining");
return new SetTbrCommand(100, 0).execute(scripter, initialPumpState); SetTbrCommand setTbrCommand = new SetTbrCommand(100, 0);
setTbrCommand.setScripter(scripter);
return setTbrCommand.execute();
} catch (CommandException e) { } catch (CommandException e) {
return e.toCommandResult(); return e.toCommandResult();
} }

View file

@ -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<String> validateArguments() {
List<String> 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);
}
}

View file

@ -2,17 +2,17 @@ package de.jotomo.ruffyscripter.commands;
import java.util.List; import java.util.List;
import de.jotomo.ruffyscripter.PumpState;
import de.jotomo.ruffyscripter.RuffyScripter; import de.jotomo.ruffyscripter.RuffyScripter;
/** /**
* Interface for all commands to be executed by the pump. * Interface for all commands to be executed by the pump.
* * <p>
* Note on cammond methods and timing: a method shall wait before and after executing * 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 * as necessary to not cause timing issues, so the caller can just call methods in
* sequence, letting the methods take care of waits. * sequence, letting the methods take care of waits.
*/ */
public interface Command { public interface Command {
CommandResult execute(RuffyScripter ruffyScripter, PumpState initialPumpState); CommandResult execute();
List<String> validateArguments(); List<String> validateArguments();
void setScripter(RuffyScripter scripter);
} }

View file

@ -6,7 +6,8 @@ public class CommandException extends RuntimeException {
public Exception exception = null; public Exception exception = null;
public String message = null; public String message = null;
public CommandException() {} public CommandException() {
}
public CommandException success(boolean success) { public CommandException success(boolean success) {
this.success = success; this.success = success;

View file

@ -3,7 +3,6 @@ package de.jotomo.ruffyscripter.commands;
import java.util.Date; import java.util.Date;
import de.jotomo.ruffyscripter.History; import de.jotomo.ruffyscripter.History;
import de.jotomo.ruffyscripter.PumpCapabilities;
import de.jotomo.ruffyscripter.PumpState; import de.jotomo.ruffyscripter.PumpState;
public class CommandResult { public class CommandResult {
@ -14,7 +13,6 @@ public class CommandResult {
public String message; public String message;
public PumpState state; public PumpState state;
public History history; public History history;
public PumpCapabilities capabilities;
public String duration; public String duration;
public CommandResult() { public CommandResult() {
@ -31,7 +29,7 @@ public class CommandResult {
} }
public CommandResult completionTime(long completionTime) { public CommandResult completionTime(long completionTime) {
this.completionTime = completionTime ; this.completionTime = completionTime;
return this; return this;
} }
@ -57,12 +55,7 @@ public class CommandResult {
public CommandResult history(History history) { public CommandResult history(History history) {
this.history = history; this.history = history;
return this; return this;
}
public CommandResult capabilities(PumpCapabilities capabilities) {
this.capabilities = capabilities;
return this;
} }
@Override @Override

View file

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

View file

@ -10,15 +10,14 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import de.jotomo.ruffyscripter.PumpState;
import de.jotomo.ruffyscripter.RuffyScripter; import de.jotomo.ruffyscripter.RuffyScripter;
public class GetBasalCommand implements Command { public class GetBasalRateProfileCommand extends BaseCommand {
private static final Logger log = LoggerFactory.getLogger(GetBasalCommand.class); private static final Logger log = LoggerFactory.getLogger(GetBasalRateProfileCommand.class);
private RuffyScripter scripter; private RuffyScripter scripter;
public GetBasalCommand() {} public GetBasalRateProfileCommand() {}
@Override @Override
public List<String> validateArguments() { public List<String> validateArguments() {
@ -27,7 +26,7 @@ public class GetBasalCommand implements Command {
return violations; return violations;
} }
// private void tick() // private void tick()
// { // {
// switch (state) // switch (state)
// { // {
@ -95,24 +94,23 @@ public class GetBasalCommand implements Command {
// case ERROR: // case ERROR:
// case AFTER: // case AFTER:
// scripter.goToMainMenuScreen(MenuType.MAIN_MENU,2000); // scripter.goToMainMenuScreen(MenuType.MAIN_MENU,2000);
// synchronized(GetBasalCommand.this) { // synchronized(GetBasalRateProfileCommand.this) {
// GetBasalCommand.this.notify(); // GetBasalRateProfileCommand.this.notify();
// } // }
// break; // break;
// } // }
// } // }
@Override @Override
public CommandResult execute(RuffyScripter scripter, PumpState initialPumpState) { public CommandResult execute() {
try { try {
Map<Integer,Double> rate = new HashMap<>(); Map<Integer, Double> rate = new HashMap<>();
for(int i = 0; i < 24;i++) for (int i = 0; i < 24; i++) {
{ Log.v("BASAL_RATE", "BASAL_RATE from " + String.format("%02d", i) + ":00 = " + rate.get(i));
Log.v("BASAL_RATE","BASAL_RATE from "+String.format("%02d",i)+":00 = "+rate.get(i));
} }
} catch (Exception e) { } catch (Exception e) {
log.error("failed to get basal",e); log.error("failed to get basal", e);
return new CommandResult().success(false).message("failed to get basal: "+e.getMessage()); return new CommandResult().success(false).message("failed to get basal: " + e.getMessage());
} }
return new CommandResult().success(true).enacted(true).message("Basal Rate was read"); return new CommandResult().success(true).enacted(true).message("Basal Rate was read");
} }

View file

@ -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<String> validateArguments() {
return Collections.emptyList();
}
@Override
public String toString() {
return "ReadPumpStateCommand{}";
}
}

View file

@ -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<String> validateArguments() {
// TODO stub
return null;
}
}

View file

@ -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<String> validateArguments() {
return Collections.emptyList();
}
@Override
public String toString() {
return "ReadPumpStateCommand{}";
}
}

View file

@ -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<String> validateArguments() {
// TODO stub
return null;
}
}

View file

@ -13,15 +13,8 @@ import java.util.List;
import java.util.Locale; import java.util.Locale;
import de.jotomo.ruffyscripter.PumpState; import de.jotomo.ruffyscripter.PumpState;
import de.jotomo.ruffyscripter.RuffyScripter;
import static org.monkey.d.ruffy.ruffy.driver.display.MenuType.MAIN_MENU; public class SetTbrCommand extends BaseCommand {
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 {
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;
@ -60,207 +53,249 @@ public class SetTbrCommand implements Command {
} }
@Override @Override
public CommandResult execute(RuffyScripter scripter, PumpState initialPumpState) { public CommandResult execute() {
try { try {
log.debug("1. going from " + scripter.currentMenu + " to TBR_MENU"); boolean cancellingTbr = percentage == 100;
int retries = 5; PumpState pumpState = scripter.readPumpState();
while (!scripter.goToMainTypeScreen(TBR_MENU, 3000)) {
retries--; // TODO hack, cancelling a TBR that isn't running is dealt with in CancelTbrCommand,
if (retries == 0) // this avoids setting a TBR twice until that AAPS bug is squished which calls this
throw new CommandException().message("not able to find TBR_MENU: stuck in " + scripter.currentMenu); // twice within a minute GL#27
SystemClock.sleep(500); if (!cancellingTbr
if (scripter.currentMenu.getType() == TBR_MENU) && pumpState.tbrActive
break; && 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) enterTbrMenu();
throw new CommandException().message("not able to find TBR_MENU: stuck in " + scripter.currentMenu); boolean increasingPercentage = inputTbrPercentage();
verifyDisplayedTbrPercentage(increasingPercentage);
log.debug("2. entering " + scripter.currentMenu); if (cancellingTbr) {
retries = 5; cancelTbrAndConfirmCancellationWarning();
while (!scripter.enterMenu(TBR_MENU, MenuType.TBR_SET, RuffyScripter.Key.CHECK, 2000)) { } else {
retries--; // switch to TBR_DURATION menu by pressing menu key
if (retries == 0) scripter.verifyMenuIsDisplayed(MenuType.TBR_SET);
throw new CommandException().message("not able to find TBR_SET: stuck in " + scripter.currentMenu); scripter.pressMenuKey();
SystemClock.sleep(500); scripter.waitForMenuUpdate();
if (scripter.currentMenu.getType() == TBR_SET) scripter.verifyMenuIsDisplayed(MenuType.TBR_DURATION);
break;
if (scripter.currentMenu.getType() == TBR_DURATION) {
scripter.pressMenuKey();
scripter.waitForScreenUpdate(1000);
}
}
log.debug("SetTbrCommand: 3. getting/setting basal percentage in " + scripter.currentMenu); boolean increasingDuration = inputTbrDuration();
retries = 30; verifyDisplayedTbrDuration(increasingDuration);
double currentPercentage = -100; // confirm TBR
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--;
scripter.pressCheckKey(); scripter.pressCheckKey();
scripter.waitForScreenUpdate(1000); scripter.waitForMenuToBeLeft(MenuType.TBR_DURATION);
}
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;
}
} }
log.debug("9. verifying the main menu display the TBR we just set/cancelled"); scripter.verifyMenuIsDisplayed(MenuType.MAIN_MENU,
if (retries < 0 || scripter.currentMenu.getType() != MAIN_MENU) "Pump did not return to MAIN_MEU after setting TBR. " +
throw new CommandException().message("failed going to main!"); "Check pump manually, the TBR might not have been set/cancelled.");
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");
// check main menu shows the same values we just set
if (cancellingTbr) {
verifyMainMenuShowsNoActiveTbr();
return new CommandResult().success(true).enacted(true).message("TBR was cancelled"); 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)) } catch (CommandException e) {
throw new CommandException().message("not percentage"); return e.toCommandResult();
}
}
if (((double) percentageObj) != percentage) private void enterTbrMenu() {
throw new CommandException().message("wrong percentage set!"); 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)) private boolean inputTbrPercentage() {
throw new CommandException().message("not time"); 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; // TODO extract verification into a method TBR percentage, duration and bolus amount
if (t.getMinute() + (60 * t.getHour()) > duration || t.getMinute() + (60 * t.getHour()) < duration - 5) private void verifyDisplayedTbrPercentage(boolean increasingPercentage) {
throw new CommandException().message("wrong time set!"); 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)); private void cancelTbrAndConfirmCancellationWarning() {
} catch (Exception e) { // confirm entered TBR
log.error("got exception: ", e); scripter.verifyMenuIsDisplayed(MenuType.TBR_SET);
return new CommandResult().success(false).message(e.getMessage()).exception(e); 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");
} }
} }

View file

@ -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<String> validateArguments() {
List<String> 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 +
'}';
}
}

View file

@ -49,8 +49,9 @@ public class Config {
public static final boolean logDanaSerialEngine = true; public static final boolean logDanaSerialEngine = true;
// Combo specific // Combo specific
/** use alternate SetTbrCommand (uses the initial implementation) */ /** enable the UNFINISHED and currently BROKEN bolus cammand that reports progress and can be cancelled */
public static final boolean comboUseAlternateSetTbrCommand = false; public static final boolean comboExperimentalBolus = true;
/** very quick hack to split up bolus into 2 U parts, spaced roughly 45s apart */ /** very quick hack to split up bolus into 2 U parts, spaced roughly 45s apart.
public static final boolean comboSplitBoluses = false; * Don't combine with experimental bolus */
public static final boolean comboSplitBoluses = false && !comboExperimentalBolus;
} }

View file

@ -257,7 +257,7 @@ public class LoopPlugin implements PluginBase {
return; return;
} }
// check rate for constrais // check rate for constraints
final APSResult resultAfterConstraints = result.clone(); final APSResult resultAfterConstraints = result.clone();
resultAfterConstraints.rate = constraintsInterface.applyBasalConstraints(resultAfterConstraints.rate); resultAfterConstraints.rate = constraintsInterface.applyBasalConstraints(resultAfterConstraints.rate);

View file

@ -34,7 +34,6 @@ public class ComboFragment extends Fragment implements View.OnClickListener {
} }
private Button refresh; private Button refresh;
private TextView updateCapabilities;
private TextView statusText; private TextView statusText;
@ -48,7 +47,6 @@ public class ComboFragment extends Fragment implements View.OnClickListener {
private TextView lastCmdResultText; private TextView lastCmdResultText;
private TextView lastCmdDurationText; private TextView lastCmdDurationText;
private TextView tbrCapabilityText;
private TextView pumpstateBatteryText; private TextView pumpstateBatteryText;
private TextView insulinstateText; 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); View view = inflater.inflate(R.layout.combopump_fragment, container, false);
refresh = (Button) view.findViewById(R.id.combo_refresh); 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); 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); lastCmdTimeText = (TextView) view.findViewById(R.id.combo_last_command_time);
lastCmdResultText = (TextView) view.findViewById(R.id.combo_last_command_result); lastCmdResultText = (TextView) view.findViewById(R.id.combo_last_command_result);
lastCmdDurationText = (TextView) view.findViewById(R.id.combo_last_command_duration); 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); pumpstateBatteryText = (TextView) view.findViewById(R.id.combo_pumpstate_battery);
insulinstateText = (TextView) view.findViewById(R.id.combo_insulinstate); insulinstateText = (TextView) view.findViewById(R.id.combo_insulinstate);
refresh.setOnClickListener(this); refresh.setOnClickListener(this);
updateCapabilities.setOnClickListener(this);
updateGUI(); updateGUI();
return view; return view;
@ -113,32 +108,6 @@ public class ComboFragment extends Fragment implements View.OnClickListener {
}); });
thread.start(); thread.start();
break; 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() { activity.runOnUiThread(new Runnable() {
@Override @Override
public void run() { public void run() {
statusText.setText(getPlugin().statusSummary); statusText.setText(getPlugin().getPump().stateSummary);
if (getPlugin().isInitialized()) { if (getPlugin().isInitialized()) {
PumpState ps = getPlugin().pumpState; PumpState ps = getPlugin().getPump().state;
if (ps != null) { if (ps != null) {
boolean tbrActive = ps.tbrPercent != -1 && ps.tbrPercent != 100; boolean tbrActive = ps.tbrPercent != -1 && ps.tbrPercent != 100;
if (tbrActive) { 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) { if (lastCmd != null) {
lastCmdText.setText(lastCmd.toString()); lastCmdText.setText(lastCmd.toString());
lastCmdTimeText.setText(getPlugin().lastCmdTime.toLocaleString()); lastCmdTimeText.setText(getPlugin().getPump().lastCmdTime.toLocaleString());
} else { } else {
lastCmdText.setText(""); lastCmdText.setText("");
lastCmdTimeText.setText(""); lastCmdTimeText.setText("");
} }
CommandResult lastCmdResult = getPlugin().lastCmdResult; CommandResult lastCmdResult = getPlugin().getPump().lastCmdResult;
if (lastCmdResult != null && lastCmdResult.message != null) { if (lastCmdResult != null && lastCmdResult.message != null) {
lastCmdResultText.setText(lastCmdResult.message); lastCmdResultText.setText(lastCmdResult.message);
lastCmdDurationText.setText(lastCmdResult.duration); lastCmdDurationText.setText(lastCmdResult.duration);
@ -201,7 +170,6 @@ public class ComboFragment extends Fragment implements View.OnClickListener {
lastCmdDurationText.setText(""); lastCmdDurationText.setText("");
} }
} }
tbrCapabilityText.setText(getPlugin().getPumpDescription().maxTempPercent + "%");
} }
}); });
} }

View file

@ -5,13 +5,11 @@ import android.content.ComponentName;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.ServiceConnection; import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.graphics.Color; import android.graphics.Color;
import android.media.RingtoneManager; import android.media.RingtoneManager;
import android.net.Uri; import android.net.Uri;
import android.os.IBinder; import android.os.IBinder;
import android.os.SystemClock; import android.os.SystemClock;
import android.preference.PreferenceManager;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.v4.app.NotificationCompat; import android.support.v4.app.NotificationCompat;
@ -29,12 +27,11 @@ import de.jotomo.ruffyscripter.PumpState;
import de.jotomo.ruffyscripter.RuffyScripter; import de.jotomo.ruffyscripter.RuffyScripter;
import de.jotomo.ruffyscripter.commands.BolusCommand; import de.jotomo.ruffyscripter.commands.BolusCommand;
import de.jotomo.ruffyscripter.commands.CancelTbrCommand; import de.jotomo.ruffyscripter.commands.CancelTbrCommand;
import de.jotomo.ruffyscripter.commands.CancellableBolusCommand;
import de.jotomo.ruffyscripter.commands.Command; import de.jotomo.ruffyscripter.commands.Command;
import de.jotomo.ruffyscripter.commands.CommandResult; import de.jotomo.ruffyscripter.commands.CommandResult;
import de.jotomo.ruffyscripter.commands.DetermineCapabilitiesCommand; import de.jotomo.ruffyscripter.commands.GetPumpStateCommand;
import de.jotomo.ruffyscripter.commands.ReadPumpStateCommand;
import de.jotomo.ruffyscripter.commands.SetTbrCommand; import de.jotomo.ruffyscripter.commands.SetTbrCommand;
import de.jotomo.ruffyscripter.commands.SetTbrCommandAlt;
import info.nightscout.androidaps.BuildConfig; import info.nightscout.androidaps.BuildConfig;
import info.nightscout.androidaps.Config; import info.nightscout.androidaps.Config;
import info.nightscout.androidaps.MainApp; 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.PumpDescription;
import info.nightscout.androidaps.interfaces.PumpInterface; import info.nightscout.androidaps.interfaces.PumpInterface;
import info.nightscout.androidaps.plugins.ConfigBuilder.ConfigBuilderPlugin; 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.androidaps.plugins.PumpCombo.events.EventComboPumpUpdateGUI;
import info.nightscout.utils.DateUtil; import info.nightscout.utils.DateUtil;
import info.nightscout.utils.SP; 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. * Created by mike on 05.08.2016.
@ -69,16 +71,10 @@ public class ComboPlugin implements PluginBase, PumpInterface {
private RuffyScripter ruffyScripter; private RuffyScripter ruffyScripter;
private ServiceConnection mRuffyServiceConnection; private ServiceConnection mRuffyServiceConnection;
// package-protected only so ComboFragment can access these private ComboPump pump = new ComboPump();
@NonNull
volatile String statusSummary = "Initializing";
@Nullable @Nullable
volatile Command lastCmd; private volatile BolusCommand runningBolusCommand;
@Nullable
volatile CommandResult lastCmdResult;
@NonNull
volatile Date lastCmdTime = new Date(0);
volatile PumpState pumpState = new PumpState();
private static PumpEnactResult OPERATION_NOT_SUPPORTED = new PumpEnactResult(); private static PumpEnactResult OPERATION_NOT_SUPPORTED = new PumpEnactResult();
@ -100,7 +96,7 @@ public class ComboPlugin implements PluginBase, PumpInterface {
pumpDescription.isBolusCapable = true; pumpDescription.isBolusCapable = true;
pumpDescription.bolusStep = 0.1d; pumpDescription.bolusStep = 0.1d;
pumpDescription.isExtendedBolusCapable = false; // TODO pumpDescription.isExtendedBolusCapable = false;
pumpDescription.extendedBolusStep = 0.1d; pumpDescription.extendedBolusStep = 0.1d;
pumpDescription.extendedBolusDurationStep = 15; pumpDescription.extendedBolusDurationStep = 15;
pumpDescription.extendedBolusMaxDuration = 12 * 60; pumpDescription.extendedBolusMaxDuration = 12 * 60;
@ -115,18 +111,22 @@ public class ComboPlugin implements PluginBase, PumpInterface {
pumpDescription.tempMaxDuration = 24 * 60; pumpDescription.tempMaxDuration = 24 * 60;
pumpDescription.isSetBasalProfileCapable = false; // TODO pumpDescription.isSetBasalProfileCapable = false; // TODO GL#14
pumpDescription.basalStep = 0.01d; pumpDescription.basalStep = 0.01d;
pumpDescription.basalMinimumRate = 0.0d; pumpDescription.basalMinimumRate = 0.0d;
pumpDescription.isRefillingCapable = true; pumpDescription.isRefillingCapable = true;
} }
public ComboPump getPump() {
return pump;
}
/** /**
* The alerter frequently checks the result of the last executed command via the lastCmdResult * 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. * 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. * More details on the error can then be looked up in the Combo tab.
* * <p>
* The alarm is re-raised every 5 minutes for as long as the error persist. As soon * 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. * as a command succeeds no more new alerts are raised.
*/ */
@ -139,15 +139,15 @@ public class ComboPlugin implements PluginBase, PumpInterface {
int id = 1000; int id = 1000;
long lastAlarmTime = 0; long lastAlarmTime = 0;
while (true) { while (true) {
Command localLastCmd = lastCmd; Command localLastCmd = pump.lastCmd;
CommandResult localLastCmdResult = lastCmdResult; CommandResult localLastCmdResult = pump.lastCmdResult;
if (localLastCmdResult != null && !localLastCmdResult.success) { if (localLastCmdResult != null && !localLastCmdResult.success) {
long now = System.currentTimeMillis(); long now = System.currentTimeMillis();
long fiveMinutesSinceLastAlarm = lastAlarmTime + (5 * 60 * 1000) + (15 * 1000); long fiveMinutesSinceLastAlarm = lastAlarmTime + (5 * 60 * 1000) + (15 * 1000);
if (now > fiveMinutesSinceLastAlarm) { if (now > fiveMinutesSinceLastAlarm) {
log.error("Command failed: " + localLastCmd); log.error("Command failed: " + localLastCmd);
log.error("Command result: " + localLastCmdResult); log.error("Command result: " + localLastCmdResult);
PumpState localPumpState = pumpState; PumpState localPumpState = pump.state;
if (localPumpState != null && localPumpState.errorMsg != null) { if (localPumpState != null && localPumpState.errorMsg != null) {
log.warn("Pump is in error state, displaying; " + localPumpState.errorMsg); 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 // this must be the base package of the app (check package attribute in
// manifest element in the manifest file of the providing app) // manifest element in the manifest file of the providing app)
"org.monkey.d.ruffy.ruffy", "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) // in the logs this service is mentioned as (note the slash)
// "org.monkey.d.ruffy.ruffy/.driver.Ruffy" // "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 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.driver.Ruffy"
)); ));
context.startService(intent); context.startService(intent);
@ -201,18 +202,18 @@ public class ComboPlugin implements PluginBase, PumpInterface {
@Override @Override
public void onServiceConnected(ComponentName name, IBinder service) { public void onServiceConnected(ComponentName name, IBinder service) {
keepUnbound=false; keepUnbound = false;
ruffyScripter.start(IRuffyService.Stub.asInterface(service)); ruffyScripter.start(IRuffyService.Stub.asInterface(service));
log.debug("ruffy serivce connected"); log.debug("ruffy serivce connected");
} }
@Override @Override
public void onServiceDisconnected(ComponentName name) { public void onServiceDisconnected(ComponentName name) {
ruffyScripter.stop(); // TODO stop?
log.debug("ruffy service disconnected"); 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 // via unbindRuffyService
if(!keepUnbound) { if (!keepUnbound) {
SystemClock.sleep(250); SystemClock.sleep(250);
bindRuffyService(); bindRuffyService();
} }
@ -224,12 +225,13 @@ public class ComboPlugin implements PluginBase, PumpInterface {
} }
if (!boundSucceeded) { if (!boundSucceeded) {
statusSummary = "No connection to ruffy. Pump control not available."; pump.stateSummary = "No connection to ruffy. Pump control unavailable.";
} }
return true; return true;
} }
private boolean keepUnbound = false; private boolean keepUnbound = false;
private void unbindRuffyService() { private void unbindRuffyService() {
keepUnbound = true; keepUnbound = true;
ruffyScripter.unbind(); ruffyScripter.unbind();
@ -301,17 +303,17 @@ public class ComboPlugin implements PluginBase, PumpInterface {
public boolean isInitialized() { public boolean isInitialized() {
// consider initialized when the pump's state was initially fetched, // consider initialized when the pump's state was initially fetched,
// after that lastCmd* variables will have values // after that lastCmd* variables will have values
return lastCmdTime.getTime() > 0; return pump.lastCmdTime.getTime() > 0;
} }
@Override @Override
public boolean isSuspended() { public boolean isSuspended() {
return pumpState != null && pumpState.suspended; return pump.state != null && pump.state.suspended;
} }
@Override @Override
public boolean isBusy() { public boolean isBusy() {
return ruffyScripter == null || ruffyScripter.isPumpBusy(); return ruffyScripter.isPumpBusy();
} }
// TODO // TODO
@ -328,7 +330,7 @@ public class ComboPlugin implements PluginBase, PumpInterface {
@Override @Override
public Date lastDataTime() { public Date lastDataTime() {
return lastCmdTime; return pump.lastCmdTime;
} }
// this method is regularly called from info.nightscout.androidaps.receivers.KeepAliveReceiver // 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"); log.debug("RefreshDataFromPump called");
// if Android is sluggish this might get called before ruffy is bound // if Android is sluggish this might get called before ruffy is bound
if (ruffyScripter == null) { if (!ruffyScripter.isRunning()) {
log.warn("Rejecting call to RefreshDataFromPump: ruffy service not bound (yet)"); log.warn("Rejecting call to RefreshDataFromPump: scripter not ready yet.");
return; return;
} }
boolean notAUserRequest = !reason.toLowerCase().contains("user"); boolean notAUserRequest = !reason.toLowerCase().contains("user");
boolean wasRunAtLeastOnce = lastCmdTime.getTime() > 0; boolean wasRunAtLeastOnce = pump.lastCmdTime.getTime() > 0;
boolean ranWithinTheLastMinute = System.currentTimeMillis() < lastCmdTime.getTime() + 60 * 1000; boolean ranWithinTheLastMinute = System.currentTimeMillis() < pump.lastCmdTime.getTime() + 60 * 1000;
if (notAUserRequest && wasRunAtLeastOnce && ranWithinTheLastMinute) { if (notAUserRequest && wasRunAtLeastOnce && ranWithinTheLastMinute) {
log.debug("Not fetching state from pump, since we did already within the last 60 seconds"); log.debug("Not fetching state from pump, since we did already within the last 60 seconds");
} else { } else {
runCommand(new ReadPumpStateCommand()); runCommand(new GetPumpStateCommand());
} }
} }
@ -362,8 +364,31 @@ public class ComboPlugin implements PluginBase, PumpInterface {
return basal; return basal;
} }
// what a mess: pump integration code reading carb info from Detailed**Bolus**Info, private static CancellableBolusCommand.ProgressReportCallback bolusProgressReportCallback =
// writing carb treatments to the history table. What's PumpEnactResult for again? 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 @Override
public PumpEnactResult deliverTreatment(DetailedBolusInfo detailedBolusInfo) { public PumpEnactResult deliverTreatment(DetailedBolusInfo detailedBolusInfo) {
if (detailedBolusInfo.insulin > 0 || detailedBolusInfo.carbs > 0) { if (detailedBolusInfo.insulin > 0 || detailedBolusInfo.carbs > 0) {
@ -378,7 +403,6 @@ public class ComboPlugin implements PluginBase, PumpInterface {
pumpEnactResult.enacted = true; pumpEnactResult.enacted = true;
pumpEnactResult.bolusDelivered = 0d; pumpEnactResult.bolusDelivered = 0d;
pumpEnactResult.carbsDelivered = detailedBolusInfo.carbs; pumpEnactResult.carbsDelivered = detailedBolusInfo.carbs;
pumpEnactResult.comment = MainApp.instance().getString(R.string.virtualpump_resultok);
double remainingBolus = detailedBolusInfo.insulin; double remainingBolus = detailedBolusInfo.insulin;
int split = 1; int split = 1;
@ -413,6 +437,10 @@ public class ComboPlugin implements PluginBase, PumpInterface {
pumpEnactResult.carbsDelivered = detailedBolusInfo.carbs; pumpEnactResult.carbsDelivered = detailedBolusInfo.carbs;
pumpEnactResult.comment = MainApp.instance().getString(R.string.virtualpump_resultok); pumpEnactResult.comment = MainApp.instance().getString(R.string.virtualpump_resultok);
MainApp.getConfigBuilder().addToHistoryTreatment(detailedBolusInfo); MainApp.getConfigBuilder().addToHistoryTreatment(detailedBolusInfo);
EventOverviewBolusProgress bolusingEvent = EventOverviewBolusProgress.getInstance();
bolusingEvent.percent = 100;
MainApp.bus().post(bolusingEvent);
return pumpEnactResult; return pumpEnactResult;
} }
} else { } else {
@ -430,7 +458,10 @@ public class ComboPlugin implements PluginBase, PumpInterface {
@NonNull @NonNull
private PumpEnactResult deliverBolus(DetailedBolusInfo detailedBolusInfo) { 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 pumpEnactResult = new PumpEnactResult();
pumpEnactResult.success = bolusCmdResult.success; pumpEnactResult.success = bolusCmdResult.success;
pumpEnactResult.enacted = bolusCmdResult.enacted; pumpEnactResult.enacted = bolusCmdResult.enacted;
@ -454,14 +485,20 @@ public class ComboPlugin implements PluginBase, PumpInterface {
return pumpEnactResult; return pumpEnactResult;
} }
@Override
public void stopBolusDelivering() {
BolusCommand localRunningBolusCommand = runningBolusCommand;
if (localRunningBolusCommand != null) localRunningBolusCommand.requestCancellation();
}
private CommandResult runCommand(Command command) { private CommandResult runCommand(Command command) {
if (ruffyScripter == null) { if (ruffyScripter == null) {
String msg = "No connection to ruffy. Pump control not available."; String msg = "No connection to ruffy. Pump control not available.";
statusSummary = msg; pump.stateSummary = msg;
return new CommandResult().message(msg); return new CommandResult().message(msg);
} }
statusSummary = "Executing " + command; pump.stateSummary = "Executing " + command;
MainApp.bus().post(new EventComboPumpUpdateGUI()); MainApp.bus().post(new EventComboPumpUpdateGUI());
CommandResult commandResult = ruffyScripter.runCommand(command); CommandResult commandResult = ruffyScripter.runCommand(command);
@ -470,30 +507,23 @@ public class ComboPlugin implements PluginBase, PumpInterface {
log.error("Exception received from pump", commandResult.exception); log.error("Exception received from pump", commandResult.exception);
} }
lastCmd = command; pump.lastCmd = command;
lastCmdTime = new Date(); pump.lastCmdTime = new Date();
lastCmdResult = commandResult; pump.lastCmdResult = commandResult;
pumpState = commandResult.state; pump.state = commandResult.state;
if (commandResult.success && commandResult.state.suspended) { if (commandResult.success && commandResult.state.suspended) {
statusSummary = "Suspended"; pump.stateSummary = "Suspended";
} else if (commandResult.success) { } else if (commandResult.success) {
statusSummary = "Idle"; pump.stateSummary = "Idle";
} else { } else {
statusSummary = "Error"; pump.stateSummary = "Error";
} }
MainApp.bus().post(new EventComboPumpUpdateGUI()); MainApp.bus().post(new EventComboPumpUpdateGUI());
return commandResult; 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 // Note: AAPS calls this only to enact OpenAPS recommendations
@Override @Override
public PumpEnactResult setTempBasalAbsolute(Double absoluteRate, Integer durationInMinutes, boolean force) { public PumpEnactResult setTempBasalAbsolute(Double absoluteRate, Integer durationInMinutes, boolean force) {
@ -528,10 +558,7 @@ public class ComboPlugin implements PluginBase, PumpInterface {
adjustedPercent = rounded.intValue(); adjustedPercent = rounded.intValue();
} }
Command cmd = !Config.comboUseAlternateSetTbrCommand CommandResult commandResult = runCommand(new SetTbrCommand(adjustedPercent, durationInMinutes));
? new SetTbrCommand(adjustedPercent, durationInMinutes)
: new SetTbrCommandAlt(adjustedPercent, durationInMinutes);
CommandResult commandResult = runCommand(cmd);
if (commandResult.enacted) { if (commandResult.enacted) {
TemporaryBasal tempStart = new TemporaryBasal(commandResult.completionTime); TemporaryBasal tempStart = new TemporaryBasal(commandResult.completionTime);
@ -584,7 +611,7 @@ public class ComboPlugin implements PluginBase, PumpInterface {
tempBasal.source = Source.USER; tempBasal.source = Source.USER;
pumpEnactResult.isTempCancel = true; 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) // 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."); 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."; 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 { } else {
// Set a fake neutral temp to avoid TBR cancel alert. Decide 90% vs 110% based on // 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%. // 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."); log.debug("cancelTempBasal: changing tbr to " + percentage + "% for 15 mins.");
commandResult = runCommand(new SetTbrCommand(percentage, 15)); commandResult = runCommand(new SetTbrCommand(percentage, 15));
if (commandResult.enacted) { if (commandResult.enacted) {
@ -622,7 +649,6 @@ public class ComboPlugin implements PluginBase, PumpInterface {
return pumpEnactResult; return pumpEnactResult;
} }
// TODO
@Override @Override
public PumpEnactResult cancelExtendedBolus() { public PumpEnactResult cancelExtendedBolus() {
return OPERATION_NOT_SUPPORTED; 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 // TODO v2 add battery, reservoir info when we start reading that and clean up the code
@Override @Override
public JSONObject getJSONStatus() { public JSONObject getJSONStatus() {
if (lastCmdTime.getTime() + 5 * 60 * 1000L < System.currentTimeMillis()) { if (true) { //pump.lastCmdTime.getTime() + 5 * 60 * 1000L < System.currentTimeMillis()) {
return null; return null;
} }
try { try {
JSONObject pump = new JSONObject(); JSONObject pumpJson = new JSONObject();
JSONObject status = new JSONObject(); JSONObject statusJson = new JSONObject();
JSONObject extended = new JSONObject(); JSONObject extendedJson = new JSONObject();
status.put("status", statusSummary); statusJson.put("status", pump.stateSummary);
extended.put("Version", BuildConfig.VERSION_NAME + "-" + BuildConfig.BUILDVERSION); extendedJson.put("Version", BuildConfig.VERSION_NAME + "-" + BuildConfig.BUILDVERSION);
try { try {
extended.put("ActiveProfile", MainApp.getConfigBuilder().getProfileName()); extendedJson.put("ActiveProfile", MainApp.getConfigBuilder().getProfileName());
} catch (Exception e) { } catch (Exception e) {
} }
status.put("timestamp", lastCmdTime); statusJson.put("timestamp", pump.lastCmdTime);
PumpState ps = pumpState; PumpState ps = pump.state;
if (ps != null) { if (ps != null) {
if (ps.tbrActive) { if (ps.tbrActive) {
extended.put("TempBasalAbsoluteRate", ps.tbrRate); extendedJson.put("TempBasalAbsoluteRate", ps.tbrRate);
extended.put("TempBasalPercent", ps.tbrPercent); extendedJson.put("TempBasalPercent", ps.tbrPercent);
extended.put("TempBasalRemaining", ps.tbrRemainingDuration); extendedJson.put("TempBasalRemaining", ps.tbrRemainingDuration);
} }
if (ps.errorMsg != null) { if (ps.errorMsg != null) {
extended.put("ErrorMessage", ps.errorMsg); extendedJson.put("ErrorMessage", ps.errorMsg);
} }
} }
// more info here .... look at dana plugin // more info here .... look at dana plugin
pump.put("status", status); pumpJson.put("status", statusJson);
pump.put("extended", extended); pumpJson.put("extended", extendedJson);
pump.put("clock", DateUtil.toISOString(lastCmdTime)); pumpJson.put("clock", DateUtil.toISOString(pump.lastCmdTime));
return pump; return pumpJson;
} catch (Exception e) { } catch (Exception e) {
log.warn("Failed to gather device status for upload", e); log.warn("Failed to gather device status for upload", e);
} }
@ -688,7 +714,8 @@ public class ComboPlugin implements PluginBase, PumpInterface {
@Override @Override
public String shortStatus(boolean veryShort) { public String shortStatus(boolean veryShort) {
return statusSummary; // TODO trim for wear if veryShort==true
return pump.stateSummary;
} }
@Override @Override
@ -698,40 +725,7 @@ public class ComboPlugin implements PluginBase, PumpInterface {
@SuppressWarnings("UnusedParameters") @SuppressWarnings("UnusedParameters")
@Subscribe @Subscribe
public void onStatusEvent(final EventAppExit e) { public void onStatusEvent(final EventAppExit ignored) {
unbindRuffyService(); 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 ...

View file

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

View file

@ -5,6 +5,7 @@ import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.ServiceConnection; import android.content.ServiceConnection;
import android.os.IBinder; import android.os.IBinder;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import com.squareup.otto.Subscribe; import com.squareup.otto.Subscribe;
@ -66,6 +67,7 @@ public class DanaRPlugin implements PluginBase, PumpInterface, DanaRInterface, C
public static DanaRExecutionService sExecutionService; public static DanaRExecutionService sExecutionService;
@NonNull
private static DanaRPump pump = DanaRPump.getInstance(); private static DanaRPump pump = DanaRPump.getInstance();
private static boolean useExtendedBoluses = false; private static boolean useExtendedBoluses = false;
@ -743,21 +745,17 @@ public class DanaRPlugin implements PluginBase, PumpInterface, DanaRInterface, C
return true; return true;
} }
@SuppressWarnings("PointlessBooleanExpression")
@Override @Override
public Double applyBasalConstraints(Double absoluteRate) { public Double applyBasalConstraints(Double absoluteRate) {
double origAbsoluteRate = absoluteRate; double origAbsoluteRate = absoluteRate;
if (pump != null) { if (absoluteRate > pump.maxBasal) {
if (absoluteRate > pump.maxBasal) { absoluteRate = pump.maxBasal;
absoluteRate = pump.maxBasal; if (Config.logConstraintsChanges && origAbsoluteRate != Constants.basalAbsoluteOnlyForCheckLimit)
if (Config.logConstraintsChanges && origAbsoluteRate != Constants.basalAbsoluteOnlyForCheckLimit) log.debug("Limiting rate " + origAbsoluteRate + "U/h by pump constraint to " + absoluteRate + "U/h");
log.debug("Limiting rate " + origAbsoluteRate + "U/h by pump constraint to " + absoluteRate + "U/h");
}
} }
return absoluteRate; return absoluteRate;
} }
@SuppressWarnings("PointlessBooleanExpression")
@Override @Override
public Integer applyBasalConstraints(Integer percentRate) { public Integer applyBasalConstraints(Integer percentRate) {
Integer origPercentRate = percentRate; Integer origPercentRate = percentRate;
@ -769,16 +767,13 @@ public class DanaRPlugin implements PluginBase, PumpInterface, DanaRInterface, C
return percentRate; return percentRate;
} }
@SuppressWarnings("PointlessBooleanExpression")
@Override @Override
public Double applyBolusConstraints(Double insulin) { public Double applyBolusConstraints(Double insulin) {
double origInsulin = insulin; double origInsulin = insulin;
if (pump != null) { if (insulin > pump.maxBolus) {
if (insulin > pump.maxBolus) { insulin = pump.maxBolus;
insulin = pump.maxBolus; if (Config.logConstraintsChanges && origInsulin != Constants.bolusOnlyForCheckLimit)
if (Config.logConstraintsChanges && origInsulin != Constants.bolusOnlyForCheckLimit) log.debug("Limiting bolus " + origInsulin + "U by pump constraint to " + insulin + "U");
log.debug("Limiting bolus " + origInsulin + "U by pump constraint to " + insulin + "U");
}
} }
return insulin; return insulin;
} }

View file

@ -47,6 +47,9 @@ public class TreatmentsFragment extends Fragment implements View.OnClickListener
profileSwitchTab = (TextView) view.findViewById(R.id.treatments_profileswitches); profileSwitchTab = (TextView) view.findViewById(R.id.treatments_profileswitches);
treatmentsTab.setOnClickListener(this); treatmentsTab.setOnClickListener(this);
extendedBolusesTab.setOnClickListener(this); extendedBolusesTab.setOnClickListener(this);
if (!MainApp.getConfigBuilder().getPumpDescription().isExtendedBolusCapable) {
extendedBolusesTab.setVisibility(View.GONE);
}
tempBasalsTab.setOnClickListener(this); tempBasalsTab.setOnClickListener(this);
tempTargetTab.setOnClickListener(this); tempTargetTab.setOnClickListener(this);
profileSwitchTab.setOnClickListener(this); profileSwitchTab.setOnClickListener(this);

View file

@ -440,7 +440,7 @@ public class ActionStringHandler {
ret += "OPEN LOOP\n"; ret += "OPEN LOOP\n";
} }
final APSInterface aps = MainApp.getConfigBuilder().getActiveAPS(); 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 != null) {
if (activeloop.lastRun.lastAPSRun != null) if (activeloop.lastRun.lastAPSRun != null)
ret += "\nLast Run: " + DateUtil.timeString(activeloop.lastRun.lastAPSRun); ret += "\nLast Run: " + DateUtil.timeString(activeloop.lastRun.lastAPSRun);

View file

@ -418,60 +418,6 @@
</LinearLayout> </LinearLayout>
<View
android:layout_width="fill_parent"
android:layout_height="2dip"
android:layout_marginBottom="5dp"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:layout_marginTop="5dp"
android:background="@color/listdelimiter" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="end"
android:paddingRight="5dp"
android:text="TBR Capability"
android:textSize="14sp" />
<TextView
android:layout_width="5dp"
android:layout_height="wrap_content"
android:layout_weight="0"
android:gravity="center_horizontal"
android:paddingEnd="2dp"
android:paddingStart="2dp"
android:text=":"
android:textSize="14sp" />
<TextView
android:id="@+id/combo_tbr_capability"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="start"
android:paddingLeft="5dp"
android:textColor="@android:color/white"
android:textSize="14sp" />
<com.joanzapata.iconify.widget.IconTextView
android:id="@+id/combo_update_capabilities"
android:layout_weight="1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@android:color/white"
android:textSize="20sp"
android:gravity="center_horizontal"
android:text="{fa-bluetooth-b}" />
</LinearLayout>
<View <View
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:layout_height="2dip" android:layout_height="2dip"

View file

@ -704,5 +704,7 @@
<string name="activate_profile">ACTIVATE PROFILE</string> <string name="activate_profile">ACTIVATE PROFILE</string>
<string name="date">Date</string> <string name="date">Date</string>
<string name="invalid">INVALID</string> <string name="invalid">INVALID</string>
<string name="bolusstopping">Stopping bolus delivery</string>
<string name="bolusstopped">Bolus delivery stopped</string>
</resources> </resources>

View file

@ -70,6 +70,7 @@ public class BolusActivity extends ViewSelectorActivity {
if (editInsulin != null){ if (editInsulin != null){
def = SafeParse.stringToDouble(editInsulin.editText.getText().toString()); 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); 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"); setLabelToPlusMinusView(view, "insulin");
container.addView(view); container.addView(view);